Skip to content

Commit

Permalink
✨ Add support for custom inject
Browse files Browse the repository at this point in the history
  • Loading branch information
justice2001 committed Jan 4, 2024
1 parent 078ac58 commit 3eb9376
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 1 deletion.
7 changes: 7 additions & 0 deletions console/src/type/editor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,11 @@ export interface QuickInsert {
icon: string;
// 配置结构
schema: Schema[];
inject?: Inject[];
}

export interface Inject {
id: string;
type: "script" | "style";
url?: string;
}
18 changes: 18 additions & 0 deletions console/src/utils/dom-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const addScript = (url: string, id: string): HTMLElement => {
const headElement = document.getElementsByTagName("head")[0];
const scriptElement = document.createElement("script");
scriptElement.id = id;
scriptElement.src = url;
headElement.append(scriptElement);
return scriptElement;
};

export const addStyleSheet = (url: string, id: string): HTMLElement => {
const headElement = document.getElementsByTagName("head")[0];
const linkElement = document.createElement("link");
linkElement.id = id;
linkElement.rel = "stylesheet";
linkElement.href = url;
headElement.append(linkElement);
return linkElement;
};
16 changes: 16 additions & 0 deletions console/src/utils/quick-insert-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Inject } from "@/type/editor";
import { addScript, addStyleSheet } from "@/utils/dom-utils";

export const quickInsertInject = (injectList: Inject[], id: string): void => {
injectList.forEach((inject) => {
const injectId = `${id}-${inject.id}`;
switch (inject.type) {
case "script":
addScript(inject.url || "", injectId);
break;
case "style":
addStyleSheet(inject.url || "", injectId);
break;
}
});
};
2 changes: 1 addition & 1 deletion console/src/views/Vditor.vue
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<script setup lang="ts">import Vditor from "@zhengyi/vditor";import { onMounted, onUnmounted, ref } from "vue";import "@zhengyi/vditor/dist/index.css";import type { EditorConfig, Schema } from "@/type/editor";import { getOptions } from "@/utils/vditor-utils";import type { AttachmentLike } from "@halo-dev/console-shared";import type { Attachment } from "@halo-dev/api-client";import { VLoading } from "@halo-dev/components";import TemplateModal from "@/model/TemplateModal.vue";import joeProgress from "@/schema/joe-progress";import { fetchAllQuickInsert } from "@/utils/fetch-utils";const props = withDefaults( defineProps<{ raw?: string; content: string; uploadImage?: (file: File) => Promise<Attachment>; }>(), { raw: "", content: "", uploadImage: undefined, });const vditor = ref();const vditorRef = ref();const vditorLoaded = ref(false);const attachmentSelectorModalShow = ref(false);// 特殊插入框, 当前支持none/tips/git// 自定义插入const customInsertOpen = ref(false);const customInsertSchema = ref<Schema>(joeProgress);let lastSelectionRange: Range| undefined = undefined;const emit = defineEmits<{ (event: "update:raw", value: string): void; (event: "update:content", value: string): void; (event: "update", value: string): void;}>();const debounceOnUpdate = () => { emit("update:raw", vditor.value.getValue()); emit("update:content", vditor.value.getHTML() || ""); emit("update", vditor.value.getValue());};// 选取附件后处理const attachmentSelect = (attachments: AttachmentLike[]) => { // Reference https://github.com/guqing/willow-mde/blob/4b8e697132f8a8f4b08dd0f92cf10d070cb26793/console/src/components/toolbar/Toolbar.vue#L104 attachments.forEach((attachment) => { if (typeof attachment === "string") { vditor.value.insertValue(`![](${attachment})`); } else if ("url" in attachment) { vditor.value.insertValue(`![${attachment.type}](${attachment.url})`); } else if ("spec" in attachment) { const { displayName } = attachment.spec; const { permalink } = attachment.status || {}; vditor.value.insertValue(`![${displayName}](${permalink})`); } });};onUnmounted(async () => { document .querySelectorAll("script[id^='vditor']") .forEach((el) => el.remove()); document.querySelectorAll("link[id^='vditor']").forEach((el) => el.remove()); vditorLoaded.value = false;});onMounted(async () => { let mode: "ir" | "wysiwyg" | "sv" | undefined = "ir"; let typeWriterMode = false; let codeBlockPreview = true; let enableQuickInsert = false; let quickInsertUrls: { url: string }[] = []; // 实验性功能: 获取当前语言 const lang = localStorage.getItem("locale") || "zh-CN"; try { const response = await fetch( "/apis/api.vditor.mczhengyi.top/editor-options" ); const editorConfig: EditorConfig = await response.json(); mode = editorConfig.basic.defaultRenderMode; typeWriterMode = editorConfig.basic.typeWriterMode; codeBlockPreview = editorConfig.basic.codeBlockPreview; enableQuickInsert = editorConfig.basic.enableQuickInsert; quickInsertUrls = editorConfig.basic.quickInsertUrl; } catch (e) { // ignore this } const qil = await fetchAllQuickInsert(quickInsertUrls); vditor.value = new Vditor( vditorRef.value, getOptions({ defaultRenderMode: mode, typeWriterMode: typeWriterMode, after: () => { vditor.value.setValue(props.raw || "# Title Here"); vditorLoaded.value = true; }, input: debounceOnUpdate, showAttachment: () => (attachmentSelectorModalShow.value = true), language: lang, codeBlockPreview: codeBlockPreview, uploadImage: (files: File[]) => { const acceptType = ["png", "jpg", "jpeg", "bmp", "gif", "webp", "svg"]; const extendName = files[0].name .slice(files[0].name.lastIndexOf(".") + 1) .toLowerCase(); if (acceptType.indexOf(extendName) === -1) { vditor.value.tip("不允许上传该类型图片!", 2000); return null; } if (props.uploadImage) { vditor.value.tip("正在上传图片...", 2000); props.uploadImage(files[0]).then((res: Attachment) => { vditor.value.insertValue( `\n\n![${res.spec.displayName}](${res.status.permalink})\n\n` ); }); } return null; }, openModal: (schema: Schema) => { lastSelectionRange = window.getSelection()?.getRangeAt(0) customInsertSchema.value = schema; customInsertOpen.value = true; }, enableQuickInsert: enableQuickInsert, quickInsertList: qil, }) );});const update = (val: string | null) => { if (lastSelectionRange) { const selection = window.getSelection(); selection?.removeAllRanges(); selection?.addRange(lastSelectionRange) } if (!val) { vditor.value.tip("未知错误,插入失败", 3000); } else { vditor.value.focus() vditor.value.insertValue(`\n\n${val}\n\n`); } customInsertOpen.value = false;};</script><template> <div id="plugin-vditor-mde"> <VLoading v-if="!vditorLoaded" style="height: 100%" /> <div id="vditor" ref="vditorRef"></div> <TemplateModal :open="customInsertOpen" :schema="customInsertSchema" @close="customInsertOpen = false" @done="update" /> <AttachmentSelectorModal v-model:visible="attachmentSelectorModalShow" :accepts="['image/*']" :max="1" @select="attachmentSelect" /> </div></template><style>#plugin-vditor-mde ol { list-style: decimal;}/** Fix content was covered by vditor panel in wysiwyg mode */#plugin-vditor-mde button,#plugin-vditor-mde input { line-height: normal;}.insert-modals label { width: 100%; display: flex;}.insert-modals label span { width: 60px; text-align: right;}.insert-modals select { border: 1px solid #cccccc; border-radius: 3px; padding-top: 8px; padding-bottom: 8px; margin-left: 10px; flex: 1;}.insert-modals textarea { border: 1px solid #cccccc; border-radius: 3px; margin-left: 10px; flex: 1;}.insert-modals input[type="text"] { border: 1px solid #cccccc; border-radius: 3px; margin-left: 10px; flex: 1; padding: 8px 10px;}</style>
<script setup lang="ts">import Vditor from "@zhengyi/vditor";import { onMounted, onUnmounted, ref } from "vue";import "@zhengyi/vditor/dist/index.css";import type { EditorConfig, Schema } from "@/type/editor";import { getOptions } from "@/utils/vditor-utils";import type { AttachmentLike } from "@halo-dev/console-shared";import type { Attachment } from "@halo-dev/api-client";import { VLoading } from "@halo-dev/components";import TemplateModal from "@/model/TemplateModal.vue";import joeProgress from "@/schema/joe-progress";import { fetchAllQuickInsert } from "@/utils/fetch-utils";import {quickInsertInject} from "@/utils/quick-insert-utils";const props = withDefaults( defineProps<{ raw?: string; content: string; uploadImage?: (file: File) => Promise<Attachment>; }>(), { raw: "", content: "", uploadImage: undefined, });const vditor = ref();const vditorRef = ref();const vditorLoaded = ref(false);const attachmentSelectorModalShow = ref(false);// 特殊插入框, 当前支持none/tips/git// 自定义插入const customInsertOpen = ref(false);const customInsertSchema = ref<Schema>(joeProgress);let lastSelectionRange: Range| undefined = undefined;const emit = defineEmits<{ (event: "update:raw", value: string): void; (event: "update:content", value: string): void; (event: "update", value: string): void;}>();const debounceOnUpdate = () => { emit("update:raw", vditor.value.getValue()); emit("update:content", vditor.value.getHTML() || ""); emit("update", vditor.value.getValue());};// 选取附件后处理const attachmentSelect = (attachments: AttachmentLike[]) => { // Reference https://github.com/guqing/willow-mde/blob/4b8e697132f8a8f4b08dd0f92cf10d070cb26793/console/src/components/toolbar/Toolbar.vue#L104 attachments.forEach((attachment) => { if (typeof attachment === "string") { vditor.value.insertValue(`![](${attachment})`); } else if ("url" in attachment) { vditor.value.insertValue(`![${attachment.type}](${attachment.url})`); } else if ("spec" in attachment) { const { displayName } = attachment.spec; const { permalink } = attachment.status || {}; vditor.value.insertValue(`![${displayName}](${permalink})`); } });};onUnmounted(async () => { document .querySelectorAll("script[id^='vditor']") .forEach((el) => el.remove()); document.querySelectorAll("link[id^='vditor']").forEach((el) => el.remove()); vditorLoaded.value = false;});onMounted(async () => { let mode: "ir" | "wysiwyg" | "sv" | undefined = "ir"; let typeWriterMode = false; let codeBlockPreview = true; let enableQuickInsert = false; let quickInsertUrls: { url: string }[] = []; // 实验性功能: 获取当前语言 const lang = localStorage.getItem("locale") || "zh-CN"; try { const response = await fetch( "/apis/api.vditor.mczhengyi.top/editor-options" ); const editorConfig: EditorConfig = await response.json(); mode = editorConfig.basic.defaultRenderMode; typeWriterMode = editorConfig.basic.typeWriterMode; codeBlockPreview = editorConfig.basic.codeBlockPreview; enableQuickInsert = editorConfig.basic.enableQuickInsert; quickInsertUrls = editorConfig.basic.quickInsertUrl; } catch (e) { // ignore this } const qil = await fetchAllQuickInsert(quickInsertUrls); qil.forEach(q => { quickInsertInject(q.inject || [], q.provider); }) vditor.value = new Vditor( vditorRef.value, getOptions({ defaultRenderMode: mode, typeWriterMode: typeWriterMode, after: () => { vditor.value.setValue(props.raw || "# Title Here"); vditorLoaded.value = true; }, input: debounceOnUpdate, showAttachment: () => (attachmentSelectorModalShow.value = true), language: lang, codeBlockPreview: codeBlockPreview, uploadImage: (files: File[]) => { const acceptType = ["png", "jpg", "jpeg", "bmp", "gif", "webp", "svg"]; const extendName = files[0].name .slice(files[0].name.lastIndexOf(".") + 1) .toLowerCase(); if (acceptType.indexOf(extendName) === -1) { vditor.value.tip("不允许上传该类型图片!", 2000); return null; } if (props.uploadImage) { vditor.value.tip("正在上传图片...", 2000); props.uploadImage(files[0]).then((res: Attachment) => { vditor.value.insertValue( `\n\n![${res.spec.displayName}](${res.status.permalink})\n\n` ); }); } return null; }, openModal: (schema: Schema) => { lastSelectionRange = window.getSelection()?.getRangeAt(0) customInsertSchema.value = schema; customInsertOpen.value = true; }, enableQuickInsert: enableQuickInsert, quickInsertList: qil, }) );});const update = (val: string | null) => { if (lastSelectionRange) { const selection = window.getSelection(); selection?.removeAllRanges(); selection?.addRange(lastSelectionRange) } if (!val) { vditor.value.tip("未知错误,插入失败", 3000); } else { vditor.value.focus() vditor.value.insertValue(`\n\n${val}\n\n`); } customInsertOpen.value = false;};</script><template> <div id="plugin-vditor-mde"> <VLoading v-if="!vditorLoaded" style="height: 100%" /> <div id="vditor" ref="vditorRef"></div> <TemplateModal :open="customInsertOpen" :schema="customInsertSchema" @close="customInsertOpen = false" @done="update" /> <AttachmentSelectorModal v-model:visible="attachmentSelectorModalShow" :accepts="['image/*']" :max="1" @select="attachmentSelect" /> </div></template><style>#plugin-vditor-mde ol { list-style: decimal;}/** Fix content was covered by vditor panel in wysiwyg mode */#plugin-vditor-mde button,#plugin-vditor-mde input { line-height: normal;}.insert-modals label { width: 100%; display: flex;}.insert-modals label span { width: 60px; text-align: right;}.insert-modals select { border: 1px solid #cccccc; border-radius: 3px; padding-top: 8px; padding-bottom: 8px; margin-left: 10px; flex: 1;}.insert-modals textarea { border: 1px solid #cccccc; border-radius: 3px; margin-left: 10px; flex: 1;}.insert-modals input[type="text"] { border: 1px solid #cccccc; border-radius: 3px; margin-left: 10px; flex: 1; padding: 8px 10px;}</style>
Expand Down
54 changes: 54 additions & 0 deletions src/main/resources/static/inject-demo.css

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions src/main/resources/static/inject-demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
customElements.define(
"joe-cloud",
class JoeCloud extends HTMLElement {
constructor() {
super();
this.options = {
type: this.getAttribute("type") || "default",
title: this.getAttribute("title") || "默认标题",
url: this.getAttribute("url"),
password: this.getAttribute("password"),
};
const type = {
default: "默认网盘",
360: "360网盘",
bd: "百度网盘",
ty: "天翼网盘",
ct: "城通网盘",
wy: "微云网盘",
github: "Github仓库",
gitee: "Gitee仓库",
lz: "蓝奏云网盘",
ad: "阿里云盘",
};
this.innerHTML = `
<span class="joe_cloud">
<div class="joe_cloud__logo _${this.options.type}"></div>
<div class="joe_cloud__describe">
<div class="joe_cloud__describe-title">${this.options.title}</div>
<div class="joe_cloud__describe-type">来源:${
type[this.options.type] || "默认网盘"
}${
this.options.password ? " | 提取码:" + this.options.password : ""
}</div>
</div>
<a class="joe_cloud__btn" href="${
this.options.url
}" target="_blank" rel="noopener noreferrer nofollow">
<i class="fa fa-download"></i>
</a>
</span>
`;
}
}
);
70 changes: 70 additions & 0 deletions src/main/resources/static/quick-insert-demo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "Joe Theme",
"tip": "Joe自定义模块",
"provider": "halo-theme-joe-3.0",
"icon": "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 -960 960 960\" width=\"24\"><path d=\"M480-80q-26 0-47-12.5T400-126q-33 0-56.5-23.5T320-206v-142q-59-39-94.5-103T190-590q0-121 84.5-205.5T480-880q121 0 205.5 84.5T770-590q0 77-35.5 140T640-348v142q0 33-23.5 56.5T560-126q-12 21-33 33.5T480-80Zm-80-126h160v-36H400v36Zm0-76h160v-38H400v38Zm-8-118h58v-108l-88-88 42-42 76 76 76-76 42 42-88 88v108h58q54-26 88-76.5T690-590q0-88-61-149t-149-61q-88 0-149 61t-61 149q0 63 34 113.5t88 76.5Zm88-162Zm0-38Z\"/></svg>",
"schema": [
{
"type": "template",
"id": "joe-cloud",
"icon": "",
"name": "网盘资源",
"formKit": [
{
"$formkit": "select",
"name": "cloud-type",
"label": "云盘类型",
"help": "Choose the type of cloud service.",
"value": "default",
"options": [
{"label": "默认网盘", "value": "default"},
{"label": "百度网盘", "value": "bd"},
{"label": "阿里网盘", "value": "ad"},
{"label": "蓝奏云网盘", "value": "lz"},
{"label": "微云网盘", "value": "wy"},
{"label": "Github仓库", "value": "github"},
{"label": "Gitee仓库", "value": "gitee"}
]
},
{
"$formkit": "text",
"name": "cloud-title",
"label": "网盘名称",
"help": "留空则显示默认标题"
},
{
"$formkit": "url",
"name": "cloud-url",
"label": "跳转链接",
"help": "网盘链接地址",
"value": ""
},
{
"$formkit": "text",
"name": "cloud-password",
"label": "密码",
"help": "网盘的访问密码,无则留空",
"value": ""
}
],
"template": "<joe-cloud type=\"$cloud-type$\" url=\"$cloud-url$\" password=\"$cloud-password$\" title=\"$cloud-title$\"></joe-cloud>"
}
],
"inject": [
{
"id": "inject-js",
"type": "script",
"url": "/plugins/vditor-mde/assets/static/inject-demo.js"
},
{
"id": "inject-css",
"type": "style",
"url": "/plugins/vditor-mde/assets/static/inject-demo.css"
},
{
"id": "inject-font",
"type": "style",
"url": "/themes/theme-Joe3/assets/lib/font-awesome/css/font-awesome.min.css"
}
]
}

0 comments on commit 3eb9376

Please sign in to comment.