跳到主要内容
版本:2.17

编辑器集成

此扩展点可以为文章提供新的独立编辑器。

编辑器集成

定义方式

export default definePlugin({
extensionPoints: {
"editor:create": (): EditorProvider[] | Promise<EditorProvider[]> => {
return [
{
name: "foo",
displayName: "foo",
logo: "/plugins/plugin-foo/assets/logo.png",
component: FooComponent,
rawType: "markdown",
},
];
},
},
});
EditorProvider
export interface EditorProvider {
name: string;
displayName: string;
logo?: string;
component: Component;
rawType: string;
}

其中,component 可以是组件对象或组件名称,且此组件有以下实现要求:

  1. 组件包含以下 props:
    1. title:string:用于接收标题。
    2. raw:string:用于接收原始内容。
    3. content:string:用于接收渲染后的内容。
    4. uploadImage?: (file: File) => Promise<Attachment>:用于上传图片,在编辑器内部获取到 File 之后直接调用此方法即可得到上传后的附件信息。
  2. 组件包含以下 emit 事件:
    1. update:title:用于更新标题。
    2. update:raw:用于更新原始内容。
    3. update:content:用于更新渲染后的内容。
    4. update:发送更新事件。

示例

此示例将实现一个简单的 Markdown 编辑器。

index.ts
import { definePlugin } from "@halo-dev/console-shared";
import { markRaw } from "vue";
import MarkdownEditor from "./components/markdown-editor.vue";

export default definePlugin({
extensionPoints: {
"editor:create": () => {
return [
{
name: "markdown",
displayName: "Markdown 编辑器",
component: markRaw(MarkdownEditor),
rawType: "markdown",
logo: "/plugins/markdown-editor/assets/logo.png",
},
];
},
},
});
./components/markdown-editor.vue
<script setup lang="ts">
import { marked } from "marked";
import { debounce } from "lodash-es";
import { ref, computed, onMounted } from "vue";
import type { Attachment } from "@halo-dev/api-client";

const props = withDefaults(
defineProps<{
raw: string;
content: string;
uploadImage?: (file: File) => Promise<Attachment>;
}>(),
{
raw: "",
content: "",
uploadImage: undefined,
}
);

const emit = defineEmits<{
(event: "update:raw", value: string): void;
(event: "update:content", value: string): void;
(event: "update", value: string): void;
}>();

const output = computed(() => marked(props.raw));

const update = debounce((e) => {
emit("update:raw", e.target.value);
emit("update:content", marked(e.target.value));

if (e.target.value !== props.raw) {
emit("update", e.target.value);
}
}, 100);

const textareaRef = ref();

onMounted(() => {
textareaRef.value.addEventListener("paste", (e) => {
if (e.clipboardData && e.clipboardData.items) {
const items = e.clipboardData.items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
const file = items[i].getAsFile();
props.uploadImage?.(file).then((attachment: Attachment) => {
emit(
"update:raw",
props.raw +
`![${attachment.spec.displayName}](${attachment.status?.permalink})`
);
});
}
}
}
});
});
</script>

<template>
<div class="editor">
<textarea ref="textareaRef" class="input" :value="raw" @input="update"></textarea>
<div class="output" v-html="output"></div>
</div>
</template>

<style>
body {
margin: 0;
}

.editor {
height: 100vh;
display: flex;
}

.input,
.output {
overflow: auto;
width: 50%;
height: 100%;
box-sizing: border-box;
padding: 0 20px;
}

.input {
border: none;
border-right: 1px solid #ccc;
resize: none;
outline: none;
background-color: #f6f6f6;
font-size: 14px;
font-family: 'Monaco', courier, monospace;
padding: 20px;
}

code {
color: #f66;
}
</style>

来源:https://vuejs.org/examples/#markdown

实现案例