1.2.2原版
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div class="editor-block__actions-separator"></div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.editor-block__actions-separator {
|
||||
@apply h-5 bg-slate-200 mx-1.5;
|
||||
width: 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Editor } from "@halo-dev/richtext-editor";
|
||||
import { computed, type Component } from "vue";
|
||||
import Douban from "./douban-edited";
|
||||
|
||||
const props = defineProps<{
|
||||
editor: Editor;
|
||||
isActive: ({ editor }: { editor: Editor }) => boolean;
|
||||
visible?: ({ editor }: { editor: Editor }) => boolean;
|
||||
icon?: Component;
|
||||
title?: string;
|
||||
action?: ({ editor }: { editor: Editor }) => void;
|
||||
}>();
|
||||
|
||||
const src = computed({
|
||||
get: () => {
|
||||
return props.editor.getAttributes(Douban.name)?.src;
|
||||
},
|
||||
set: (src: string) => {
|
||||
props.editor
|
||||
.chain()
|
||||
.updateAttributes(Douban.name, { src: src })
|
||||
.setNodeSelection(props.editor.state.selection.from)
|
||||
.focus()
|
||||
.run();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-model.lazy="src"
|
||||
placeholder="输入链接,按回车确定"
|
||||
class="bg-gray-50 rounded-md hover:bg-gray-100 block px-2 w-full py-1.5 text-sm text-gray-900 border border-gray-300 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,267 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PMNode } from "@halo-dev/richtext-editor";
|
||||
import type { Editor, Node } from "@halo-dev/richtext-editor";
|
||||
import { NodeViewWrapper } from "@halo-dev/richtext-editor";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import {formatDatetime} from "@/utils/date";
|
||||
import { VButton,VSpace,VDropdown} from "@halo-dev/components";
|
||||
import {doubanApiClient} from "@/api";
|
||||
import type { DoubanMovie } from "@/api/generated";
|
||||
|
||||
const selecteDoubanMovie = ref<DoubanMovie | undefined>();
|
||||
|
||||
const props = defineProps<{
|
||||
editor: Editor;
|
||||
node: PMNode;
|
||||
selected: boolean;
|
||||
extension: Node<any, any>;
|
||||
getPos: () => number;
|
||||
updateAttributes: (attributes: Record<string, any>) => void;
|
||||
deleteNode: () => void;
|
||||
}>();
|
||||
|
||||
const src = computed({
|
||||
get: () => {
|
||||
return props.node?.attrs.src;
|
||||
},
|
||||
set: (src: string) => {
|
||||
props.updateAttributes({ src: src });
|
||||
},
|
||||
});
|
||||
|
||||
function handleSetFocus() {
|
||||
props.editor.commands.setNodeSelection(props.getPos());
|
||||
}
|
||||
|
||||
const editorLinkObtain = ref();
|
||||
|
||||
onMounted(() => {
|
||||
if (!src.value) {
|
||||
}else {
|
||||
handleCheckAllChange();
|
||||
}
|
||||
});
|
||||
|
||||
const handleCheckAllChange = async () => {
|
||||
const { data: data } = await doubanApiClient.doubanMovie.getDoubanDetail({
|
||||
url: src.value
|
||||
});
|
||||
selecteDoubanMovie.value = data
|
||||
};
|
||||
|
||||
const handleEnterSetExternalLink = () => {
|
||||
if (!editorLinkObtain.value) {
|
||||
return;
|
||||
}
|
||||
props.updateAttributes({
|
||||
src: editorLinkObtain.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleResetInit = () => {
|
||||
props.updateAttributes({src: ""});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<node-view-wrapper as="div" class="inline-block-box inline-block">
|
||||
<div
|
||||
class="inline-block overflow-hidden transition-all text-center relative h-full w-full rounded-md"
|
||||
:class="{
|
||||
'rounded ring-2': selected,
|
||||
}"
|
||||
>
|
||||
<div v-if="!src || selecteDoubanMovie?.spec==undefined" class="relative">
|
||||
<div class="flex h-64 w-full items-center justify-center" style="height: 180px;">
|
||||
<div
|
||||
class="flex h-full w-full cursor-pointer flex-col items-center justify-center border-2 border-dashed border-gray-300 bg-gray-50"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center space-y-7 pb-6 pt-5 editor-link-obtain"
|
||||
>
|
||||
<VSpace>
|
||||
<VDropdown>
|
||||
<div class="flex h-14 w-14 items-center justify-center rounded-full bg-primary/20" style="margin: 0.8em 1.5em;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" style="font-size: 1.6rem;">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 20h16M5 4h14M8 8h8a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2m8 6l-2 6m-6-3l1 3">
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
<VButton>输入链接</VButton>
|
||||
<template #popper>
|
||||
<input
|
||||
v-model="editorLinkObtain"
|
||||
class="block w-full rounded-md border border-gray-300 bg-gray-50 px-2 py-1.5 text-sm text-gray-900 hover:bg-gray-100"
|
||||
placeholder="输入链接,按回车确定"
|
||||
@keydown.enter="handleEnterSetExternalLink"
|
||||
@change="handleCheckAllChange"
|
||||
/>
|
||||
</template>
|
||||
</VDropdown>
|
||||
</VSpace>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="group relative">
|
||||
<div class="doulist-item">
|
||||
<div class="doulist-subject">
|
||||
<div class="doulist-post"><img decoding="async" referrerpolicy="no-referrer"
|
||||
:src="selecteDoubanMovie?.spec.poster" class="fade-before fade-after"></div>
|
||||
<div class="db--viewTime JiEun">Marked {{formatDatetime(selecteDoubanMovie?.faves.createTime)}}</div>
|
||||
<div class="doulist-content">
|
||||
<div class="doulist-title"><a :href="selecteDoubanMovie?.spec.link" class="cute" target="_blank"
|
||||
rel="external nofollow">{{selecteDoubanMovie?.spec.name}}</a></div>
|
||||
<div class="rating"><span class="allstardark"><span class="allstarlight" :style="'width:'+selecteDoubanMovie?.spec.score * 10+'%'"></span></span><span
|
||||
class="rating_nums"> {{selecteDoubanMovie?.spec.score}} </span></div>
|
||||
<div class="abstract">
|
||||
{{selecteDoubanMovie?.faves.remark !=null && selecteDoubanMovie?.faves.remark !='' ? selecteDoubanMovie?.faves.remark : selecteDoubanMovie?.spec.cardSubtitle}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="src"
|
||||
class="absolute left-0 top-0 hidden h-1/4 w-full cursor-pointer justify-end bg-gradient-to-b from-gray-300 to-transparent p-2 ease-in-out group-hover:flex"
|
||||
>
|
||||
<VButton size="sm" type="secondary" @click="handleResetInit">
|
||||
替换
|
||||
</VButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</node-view-wrapper>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
:root {
|
||||
--db--text-color-light: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.inline-block-box {
|
||||
width: calc(100% - 1px);
|
||||
}
|
||||
|
||||
.doulist-item {
|
||||
margin: 5px 0;
|
||||
color: var(--db--text-color-light);
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px
|
||||
}
|
||||
|
||||
.doulist-item:hover {
|
||||
box-shadow: 0 3px 12px rgba(0,0,0,.05)
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
line-height: 1.6;
|
||||
padding: 12px;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .db--viewTime {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background: #f5c518;
|
||||
color: #000;
|
||||
border-radius: 4px 4px 0 4px;
|
||||
line-height: 1;
|
||||
padding: 3px 5px 3px 10px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
margin-bottom: 2px;
|
||||
font-weight: 900
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .db--viewTime:after {
|
||||
content: "";
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin-left: -.2rem;
|
||||
border-radius: 0 4px 4px 4px;
|
||||
background: inherit;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: .75rem;
|
||||
transform: skewX(20deg)
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .doulist-content {
|
||||
flex: 1 1 auto
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .doulist-post {
|
||||
width: 96px;
|
||||
margin-right: 15px;
|
||||
display: flex;
|
||||
flex: 0 0 auto
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .doulist-title {
|
||||
margin-bottom: 5px;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .doulist-title a {
|
||||
text-decoration: none!important
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .rating {
|
||||
margin: 0 0 5px;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .rating .allstardark {
|
||||
position: relative;
|
||||
color: #f99b01;
|
||||
height: 16px;
|
||||
width: 80px;
|
||||
background-repeat: repeat;
|
||||
background-image: url(/plugins/plugin-douban/assets/static/img/star.svg);
|
||||
background-size: auto 100%;
|
||||
margin-right: 5px
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .rating .allstarlight {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #f99b01;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
background-repeat: repeat;
|
||||
background-image: url(/plugins/plugin-douban/assets/static/img/star-fill.svg);
|
||||
background-size: auto 100%
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject .abstract {
|
||||
font-size: 14px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.6;
|
||||
max-height: 3.2em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.doulist-item .doulist-subject img {
|
||||
width: 96px!important;
|
||||
height: 96px!important;
|
||||
border-radius: 4px;
|
||||
object-fit: cover
|
||||
}
|
||||
.editor-link-obtain .v-popper--theme-dropdown {
|
||||
margin-top: -10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,186 @@
|
||||
import {
|
||||
type Editor,
|
||||
isActive,
|
||||
mergeAttributes,
|
||||
Node,
|
||||
nodeInputRule,
|
||||
type Range,
|
||||
VueNodeViewRenderer,
|
||||
type EditorState,
|
||||
} from "@halo-dev/richtext-editor";
|
||||
import DoubanView from "./DoubanView.vue";
|
||||
import { markRaw } from "vue";
|
||||
import { ToolboxItem } from "@halo-dev/richtext-editor";
|
||||
import MdiShare from "~icons/mdi/share";
|
||||
import BlockActionSeparator from "./BlockActionSeparator.vue";
|
||||
import MdiDeleteForeverOutline from "~icons/mdi/delete-forever-outline?color=red";
|
||||
import TablerBrandDouban from '~icons/tabler/brand-douban';
|
||||
import { deleteNode } from "../utils/delete-node";
|
||||
declare module "@halo-dev/richtext-editor" {
|
||||
interface Commands<ReturnType> {
|
||||
douban: {
|
||||
setDouban: (options: { src: string }) => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const Douban = Node.create({
|
||||
name: "douban",
|
||||
|
||||
inline() {
|
||||
return true;
|
||||
},
|
||||
|
||||
group() {
|
||||
return "inline";
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
src: {
|
||||
default: null,
|
||||
parseHTML: (element) => {
|
||||
return element.getAttribute("src");
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: "douban",
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ["douban", mergeAttributes(HTMLAttributes)];
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
setDouban:
|
||||
(options) =>
|
||||
({ commands }) => {
|
||||
return commands.insertContent({
|
||||
type: this.name,
|
||||
attrs: options,
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
addInputRules() {
|
||||
return [
|
||||
nodeInputRule({
|
||||
find: /^\$douban\$$/,
|
||||
type: this.type,
|
||||
getAttributes: () => {
|
||||
return { width: "100%" };
|
||||
},
|
||||
}),
|
||||
];
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return VueNodeViewRenderer(DoubanView);
|
||||
},
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
getCommandMenuItems() {
|
||||
return {
|
||||
priority: 2e2,
|
||||
icon: markRaw(TablerBrandDouban),
|
||||
title: "豆瓣展示",
|
||||
keywords: ["douban", "豆瓣展示"],
|
||||
command: ({ editor, range }: { editor: Editor; range: Range }) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.insertContent([
|
||||
{ type: "douban", attrs: { src: "" } },
|
||||
{ type: "paragraph", content: "" },
|
||||
])
|
||||
.run();
|
||||
},
|
||||
};
|
||||
},
|
||||
getToolboxItems({ editor }: { editor: Editor }) {
|
||||
return {
|
||||
priority: 59,
|
||||
component: markRaw(ToolboxItem),
|
||||
props: {
|
||||
editor,
|
||||
icon: markRaw(TablerBrandDouban),
|
||||
title: "豆瓣展示",
|
||||
action: () => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContent([{ type: "douban", attrs: { src: "" } }])
|
||||
.run();
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
getBubbleMenu({ editor }: { editor: Editor }) {
|
||||
return {
|
||||
pluginKey: "doubanBubbleMenu",
|
||||
shouldShow: ({ state }: { state: EditorState }) => {
|
||||
return isActive(state, Douban.name);
|
||||
},
|
||||
items: [
|
||||
{
|
||||
priority: 50,
|
||||
props: {
|
||||
icon: markRaw(MdiShare),
|
||||
title: "打开链接",
|
||||
action: () => {
|
||||
window.open(editor.getAttributes(Douban.name).src, "_blank");
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
priority: 60,
|
||||
component: markRaw(BlockActionSeparator),
|
||||
},
|
||||
{
|
||||
priority: 70,
|
||||
props: {
|
||||
icon: markRaw(MdiDeleteForeverOutline),
|
||||
title: "删除",
|
||||
action: ({ editor }: { editor: Editor }) => {
|
||||
deleteNode(Douban.name, editor);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
getDraggable() {
|
||||
return {
|
||||
getRenderContainer({ dom }: { dom: HTMLElement }) {
|
||||
let container = dom;
|
||||
while (
|
||||
container &&
|
||||
!container.hasAttribute("data-node-view-wrapper")
|
||||
) {
|
||||
container = container.parentElement as HTMLElement;
|
||||
}
|
||||
return {
|
||||
el: container,
|
||||
};
|
||||
},
|
||||
allowPropagationDownward: true,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
export default Douban;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DoubanExtension } from "./douban-edited";
|
||||
Reference in New Issue
Block a user