From 615a9933c53b370415c5b0b10df625df4f756ded Mon Sep 17 00:00:00 2001 From: lijisanxiong <1518062161@qq.com> Date: Tue, 2 Jul 2024 22:17:08 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9Awang=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E9=80=82=E9=85=8D=E8=A1=A8=E6=83=85=E7=BB=98=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/html/html-editor.controller.ts | 75 +++- .../wang-editor/component/emoji/emoji.scss | 4 + .../wang-editor/component/emoji/emoji.tsx | 51 +++ .../html/wang-editor/component/index.ts | 1 + .../wang-editor/emoji/emoji-controller.ts | 319 ++++++++++++++++++ .../wang-editor/emoji/emoji-node-module.ts | 172 ++++++++++ src/editor/html/wang-editor/emoji/index.ts | 2 + .../factory/custom-node-factory.ts | 127 +++++++ .../html/wang-editor/interface/index.ts | 1 + .../wang-editor/interface/node-interface.ts | 61 ++++ .../html/wang-editor/wang-editor-toolbars.ts | 69 ++++ src/editor/html/wang-editor/wang-editor.tsx | 8 +- src/locale/en/index.ts | 1 + src/locale/zh-CN/index.ts | 1 + 14 files changed, 889 insertions(+), 3 deletions(-) create mode 100644 src/editor/html/wang-editor/component/emoji/emoji.scss create mode 100644 src/editor/html/wang-editor/component/emoji/emoji.tsx create mode 100644 src/editor/html/wang-editor/component/index.ts create mode 100644 src/editor/html/wang-editor/emoji/emoji-controller.ts create mode 100644 src/editor/html/wang-editor/emoji/emoji-node-module.ts create mode 100644 src/editor/html/wang-editor/emoji/index.ts create mode 100644 src/editor/html/wang-editor/factory/custom-node-factory.ts create mode 100644 src/editor/html/wang-editor/interface/index.ts create mode 100644 src/editor/html/wang-editor/interface/node-interface.ts create mode 100644 src/editor/html/wang-editor/wang-editor-toolbars.ts diff --git a/src/editor/html/html-editor.controller.ts b/src/editor/html/html-editor.controller.ts index 7ad26fbb9..9e051d7fa 100644 --- a/src/editor/html/html-editor.controller.ts +++ b/src/editor/html/html-editor.controller.ts @@ -1,11 +1,16 @@ import { EditorController, IAppDEService, + IViewController, + IViewEvent, + IViewState, getDeACMode, } from '@ibiz-template/runtime'; -import { IAppDEACMode, IHtml } from '@ibiz/model-core'; -import { Boot } from '@wangeditor/editor'; +import { IAppDEACMode, IAppView, IHtml } from '@ibiz/model-core'; +import { Boot, IDomEditor } from '@wangeditor/editor'; +import { createUUID } from 'qx-util'; import { AIMenu } from './wang-editor/ai/ai-modules'; +import { CustomNodeFactory } from './wang-editor/factory/custom-node-factory'; /** * html框编辑器控制器 @@ -51,6 +56,14 @@ export class HtmlEditorController extends EditorController { */ chatCompletion: boolean = false; + /** + * 唯一标识 + * + * @type {string} + * @memberof HtmlEditorController + */ + public uuid: string = createUUID(); + protected async onInit(): Promise { await super.onInit(); @@ -97,6 +110,7 @@ export class HtmlEditorController extends EditorController { } } + // ai聊天菜单注册 if (!(window as IData).aichartRegister) { Boot.registerMenu({ key: 'aichart', @@ -106,5 +120,62 @@ export class HtmlEditorController extends EditorController { }); (window as IData).aichartRegister = true; } + + CustomNodeFactory.init(this.uuid); + } + + /** + * 初始化 + * + * @param {IDomEditor} editor + * @memberof HtmlEditorController + */ + public onCreated( + editor: IDomEditor, + data: IData, + toolbarConfig: IData, + ): void { + const controllers = CustomNodeFactory.getPluginsById(this.uuid); + controllers.forEach(controller => { + controller.init(editor, { + model: this.model, + data, + toolbarConfig, + context: this.context, + params: this.params, + }); + }); + } + + /** + * 获取当前视图 + * + * @return {*} + * @memberof HtmlEditorController + */ + public getView(): IViewController | void { + const ctrl = + (this.parent as IData).form || + (this.parent as IData).grid || + (this.parent as IData).panel; + if (ctrl) { + return ctrl.view; + } + } + + /** + * 解析所有注册节点 + * + * @param {string} value + * @return {*} + * @memberof HtmlCommentController + */ + public parseNode(value: string): string { + let result: string = value; + const controllers = CustomNodeFactory.getPluginsById(this.uuid); + controllers.forEach(controller => { + result = controller.parseNode(result); + }); + return result; } } diff --git a/src/editor/html/wang-editor/component/emoji/emoji.scss b/src/editor/html/wang-editor/component/emoji/emoji.scss new file mode 100644 index 000000000..f28937866 --- /dev/null +++ b/src/editor/html/wang-editor/component/emoji/emoji.scss @@ -0,0 +1,4 @@ +@include b('html-emoji') { + width: auto; + height: auto; +} diff --git a/src/editor/html/wang-editor/component/emoji/emoji.tsx b/src/editor/html/wang-editor/component/emoji/emoji.tsx new file mode 100644 index 000000000..9054c5f27 --- /dev/null +++ b/src/editor/html/wang-editor/component/emoji/emoji.tsx @@ -0,0 +1,51 @@ +import { PropType, defineComponent, onBeforeUnmount, ref } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IModal, IModalData } from '@ibiz-template/runtime'; +import { strToBase64 } from '@ibiz-template/core'; +import { EmojiController } from '../../emoji'; +import './emoji.scss'; + +export const Emoji = defineComponent({ + name: 'IBizHtmlEmoji', + props: { + controller: { + type: Object as PropType, + required: true, + }, + modal: { type: Object as PropType }, + }, + setup(props) { + const ns = useNamespace('html-emoji'); + const emoji = ref(''); + + // 选择表情 + const onSelect = (val: IData) => { + emoji.value = val.data; + if (props.modal) { + const modalData: IModalData = { + ok: true, + data: [{ emoji: strToBase64(val.data) }], + }; + props.modal?.dismiss(modalData); + } + }; + + // 组件销毁前销毁监听 + onBeforeUnmount(() => { + emoji.value = ''; + }); + + return { ns, emoji, onSelect }; + }, + render() { + return ( +
+ +
+ ); + }, +}); diff --git a/src/editor/html/wang-editor/component/index.ts b/src/editor/html/wang-editor/component/index.ts new file mode 100644 index 000000000..398a8a620 --- /dev/null +++ b/src/editor/html/wang-editor/component/index.ts @@ -0,0 +1 @@ +export * from './emoji/emoji'; diff --git a/src/editor/html/wang-editor/emoji/emoji-controller.ts b/src/editor/html/wang-editor/emoji/emoji-controller.ts new file mode 100644 index 000000000..27a9fea05 --- /dev/null +++ b/src/editor/html/wang-editor/emoji/emoji-controller.ts @@ -0,0 +1,319 @@ +import { + IModal, + IModalData, + IOverlayPopoverContainer, + ScriptFactory, +} from '@ibiz-template/runtime'; +import { h } from 'vue'; +import { Boot, DomEditor, IDomEditor } from '@wangeditor/editor'; +import { NOOP, listenJSEvent } from '@ibiz-template/core'; +import emojiModule from './emoji-node-module'; +import { Emoji } from '../component'; +import { INodeController } from '../interface'; + +/** + * 表情适配器 + * + * @export + * @class EmojiController + * @extends {EditorController} + */ +export class EmojiController implements INodeController { + /** + * 编辑器示例 + * + * @type {IDomEditor} + * @memberof EmojiController + */ + public editor!: IDomEditor; + + /** + * 气泡容器 + * + * @type {IOverlayPopoverContainer} + * @memberof EmojiController + */ + public overlay: IOverlayPopoverContainer | null = null; + + /** + * 转换脚本 + * + * @type {string} + * @memberof EmojiController + */ + // eslint-disable-next-line no-template-curly-in-string + public emojiScript: string = '`{"emoji":"${data.emoji}"}`'; + + /** + * 逆转换脚本 + * + * @type {string} + * @memberof EmojiController + */ + public emojiInScript: string = `value.replaceAll(/\\{\\"\\emoji\\":\\"(.+?)\\"\\}/g,(x, emoji) => {return controller.getNodeInfo({ emoji })})`; + + /** + * 预定义阻止捕获事件code + * + * @type {number[]} + * @memberof EmojiController + */ + public presetPreventEvents: number[] = [13, 38, 40]; + + /** + * 预定义阻止冒泡事件code + * + * @type {number[]} + * @memberof EmojiController + */ + public presetPreventPropEvents: number[] = [27]; + + /** + * 删除回调 + * + * @type {NOOP} + * @memberof EmojiController + */ + public cleanup = NOOP; + + /** + * 声明表情插件 + * + * @template T + * @param {T} editor + * @return {IDomEditor} {IDomEditor} + * @memberof EmojiController + */ + public emojiPlugin(editor: T): IDomEditor { + const { insertText, isInline, isVoid, apply } = editor; + + // 重写 insertText + editor.insertText = t => { + // 默认富文本编辑器走默认逻辑 + if (!this.editor) { + insertText(t); + return; + } + if (!this.editor.isFocused()) { + insertText(t); + return; + } + // 选中了 void 元素 + const elems = DomEditor.getSelectedElems(editor); + const isSelectedVoidElem = elems.some(elem => editor.isVoid(elem)); + if (isSelectedVoidElem) { + insertText(t); + return; + } + + if (t === ' :' && !this.overlay) { + insertText(''); + this.openModal(); + return; + } + + // 非 ' :' 则执行默认行为 + insertText(t); + }; + + // 重写 isInline + editor.isInline = elem => { + const type = DomEditor.getNodeType(elem); + if (type === 'emoji') { + return true; + } + + return isInline(elem); + }; + + // 重写 isVoid + editor.isVoid = elem => { + const type = DomEditor.getNodeType(elem); + if (type === 'emoji') { + return true; + } + + return isVoid(elem); + }; + + // 实时编辑 + editor.apply = operation => { + this.closeModal(); + apply(operation); + }; + + return editor; + } + + /** + * Creates an instance of EmojiController. + * @param {IData} option + * @memberof EmojiController + */ + constructor() { + this.registerNode(); + } + + /** + * 注册节点 + * + * @return {*} + * @memberof INodeController + */ + public registerNode(): void { + if (!(window as IData).emojiIsRegiter) { + Boot.registerModule(emojiModule); + (window as IData).emojiIsRegiter = true; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Boot.registerPlugin(this.emojiPlugin.bind(this) as any); + } + + /** + * 初始化 + * + * @param {IDomEditor} editor + * @param {IData} option + * @return {*} + * @memberof INodeController + */ + public async init(editor: IDomEditor): Promise { + this.editor = editor; + const container = this.editor.getEditableContainer(); + if (container) { + this.cleanup = listenJSEvent(container, 'keydown', event => { + if (this.overlay && this.presetPreventEvents.includes(event.keyCode)) { + event.preventDefault(); + } + // 监听esc按键,销毁弹框 + if ( + this.overlay && + this.presetPreventPropEvents.includes(event.keyCode) + ) { + event.stopPropagation(); + this.overlay?.dismiss(); + } + }); + } + } + + /** + * 销毁 + * + * @return {*} + * @memberof INodeController + */ + public onDestroyed(): void { + if (this.cleanup !== NOOP) { + this.cleanup(); + } + if (this.overlay) { + this.overlay.dismiss(); + } + } + + /** + * 显示弹框 + * + * @return {*} + * @memberof EmojiController + */ + public async openModal(): Promise { + // 获取光标位置,定位 modal + const domSelection = document.getSelection(); + if (!domSelection) { + return; + } + const { focusNode } = domSelection; + if (focusNode) { + this.closeModal(); + this.overlay = ibiz.overlay.createPopover( + (modal: IModal) => { + return h(Emoji, { + controller: this, + modal, + }); + }, + undefined, + { + placement: 'bottom-start', + autoClose: true, + width: 'auto', + noArrow: true, + }, + ); + await this.overlay.present(focusNode.parentNode as HTMLElement); + const result: IModalData = await this.overlay.onWillDismiss(); + this.overlay = null; + const data = result.data || []; + if (data.length > 0) { + this.insertNode(data[0]); + } + } + } + + /** + * 关闭弹框 + * + * @return {*} + * @memberof EmojiController + */ + public async closeModal(): Promise { + if (this.overlay) { + await this.overlay?.dismiss(); + this.overlay = null; + } + } + + /** + * 插入表情 + * + * @param {IDomEditor} editor + * @return {*} + * @memberof EmojiController + */ + public insertNode(data: IData): void { + const emojiNode = { + type: 'emoji', + script: this.emojiScript, + data, + children: [{ text: '' }], + }; + + this.editor.restoreSelection(); // 恢复选区 + this.editor.insertNode(emojiNode); // 插入 emoji + this.editor.move(1); // 移动光标 + } + + /** + * 获取节点信息 + * + * @param {IData} data + * @return {string} {string} + * @memberof EmojiController + */ + public getNodeInfo(data: IData): string { + Object.assign(data, { script: this.emojiScript }); + return ``; + } + + /** + * 解析emoji节点 + * + * @param {string} value + * @return {string} {string} + * @memberof EmojiController + */ + public parseNode(value: string): string { + return ScriptFactory.execScriptFn( + { value, controller: this }, + this.emojiInScript, + { + singleRowReturn: true, + isAsync: false, + }, + ) as string; + } +} diff --git a/src/editor/html/wang-editor/emoji/emoji-node-module.ts b/src/editor/html/wang-editor/emoji/emoji-node-module.ts new file mode 100644 index 000000000..075f3f357 --- /dev/null +++ b/src/editor/html/wang-editor/emoji/emoji-node-module.ts @@ -0,0 +1,172 @@ +import { + IDomEditor, + IButtonMenu, + IModuleConf, + SlateElement, +} from '@wangeditor/editor'; +import { DOMElement } from '@wangeditor/editor/dist/editor/src/utils/dom'; +import { isBase64, strToBase64 } from '@ibiz-template/core'; +import { ScriptFactory } from '@ibiz-template/runtime'; + +/** + * 空表情符号 + */ +type EmptyEmoji = { + text: ''; +}; + +/** + * 表情节点类型 + */ +export type EmojiElement = { + type: 'emoji'; + script: string; + data: IData; + children: EmptyEmoji[]; // void 元素必须有一个空 text +}; + +/** + * 表情转html + * + * @param {SlateElement} elem + * @return {string} {string} + */ +function emojiToHtml(elem: SlateElement): string { + const { script = '', data = {} } = elem as EmojiElement; + const tempData = { data: { emoji: '' } }; + // 处理提交数据 + if (data.emoji) { + Object.assign(tempData.data, { + emoji: isBase64(data.emoji) ? data.emoji : strToBase64(data.emoji), + }); + } + const result = ScriptFactory.execScriptFn(tempData, script, { + singleRowReturn: true, + isAsync: false, + }) as string; + return `${result}`; +} + +/** + * 表情装html配置 + */ +const elemToHtmlConf = { + type: 'emoji', + elemToHtml: emojiToHtml, +}; + +/** + * 解析html数据 + * + * @param {DOMElement} elem + * @return {SlateElement} {SlateElement} + */ +function parseHtml(elem: DOMElement): SlateElement { + const datastr = decodeURIComponent(elem.getAttribute('data-value') || ''); + const data: IData = JSON.parse(datastr); + return { + type: 'emoji', + script: data.script, + data, + children: [{ text: '' }], + } as EmojiElement; +} + +/** + * 解析html数据配置 + */ +const parseHtmlConf = { + selector: 'span[data-w-e-type="emoji"]', + parseElemHtml: parseHtml, +}; + +/** + * 绘制表情 + * + * @param {SlateElement} elem + * @return {VNode} {VNode} + */ +function renderEmoji(elem: SlateElement): string { + const { data = {} } = elem as EmojiElement; + + const tempData = { emoji: '' }; + + // 处理显示数据 + if (data.emoji) { + Object.assign(tempData, { + emoji: isBase64(data.emoji) + ? `${decodeURIComponent(atob(data.emoji))}` + : data.emoji, + }); + } + + return tempData.emoji; +} + +/** + * 绘制表情节点配置 + */ +const renderElemConf = { + type: 'emoji', + renderElem: renderEmoji, +}; + +/** + * 表情菜单 + * + * @export + * @class EmojiMenu + */ +export class EmojiMenu implements IButtonMenu { + readonly title = `${ibiz.i18n.t('editor.html.wangEditor.emoji')}`; + + readonly tag = 'button'; + + readonly iconSvg = ` `; + + // eslint-disable-next-line no-useless-constructor, no-empty-function + constructor() {} + + // 菜单是否需要激活(如选中加粗文本,“加粗”菜单会激活),用不到则返回 false + isActive(): boolean { + return false; + } + + // 获取菜单执行时的 value ,用不到则返回空 字符串或 false + getValue(): string | boolean { + return ''; + } + + // 菜单是否需要禁用(如选中 H1 ,“引用”菜单被禁用),用不到则返回 false + isDisabled(): boolean { + return false; + } + + // 点击菜单时触发的函数 + exec(editor: IDomEditor): void { + editor.insertText(' :'); + } +} + +/** + * 表情菜单配置 + */ +const atMenu = { + key: 'emoji', + factory(): EmojiMenu { + return new EmojiMenu(); + }, +}; + +/** + * 配置模块 + */ +const module: Partial = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + renderElems: [renderElemConf as any], + elemsToHtml: [elemToHtmlConf], + parseElemsHtml: [parseHtmlConf], + menus: [atMenu], +}; + +export default module; diff --git a/src/editor/html/wang-editor/emoji/index.ts b/src/editor/html/wang-editor/emoji/index.ts new file mode 100644 index 000000000..90f85de86 --- /dev/null +++ b/src/editor/html/wang-editor/emoji/index.ts @@ -0,0 +1,2 @@ +export * from './emoji-controller'; +export * from './emoji-node-module'; diff --git a/src/editor/html/wang-editor/factory/custom-node-factory.ts b/src/editor/html/wang-editor/factory/custom-node-factory.ts new file mode 100644 index 000000000..178563617 --- /dev/null +++ b/src/editor/html/wang-editor/factory/custom-node-factory.ts @@ -0,0 +1,127 @@ +import { INodeController } from '../interface/node-interface'; +import { EmojiController } from '../emoji'; + +/** + * 自定义节点工厂类 + * + * @export + * @class CustomNodeFactory + */ +export class CustomNodeFactory { + /** + * 自定义节点 + * + * @static + * @type {IData} + * @memberof CustomNodeFactory + */ + public static customNodeMap: Map = new Map(); + + /** + * 预置注册表 + * + * @static + * @type {Map} + * @memberof CustomNodeFactory + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public static registerMap: Map = new Map(); + + /** + * 预置注册节点 + * + * @static + * @type {['emoji']} + * @memberof CustomNodeFactory + */ + public static presetNodes = ['emoji']; + + /** + * 初始化 + * + * @static + * @param {string} uuid + * @return {*} + * @memberof CustomNodeFactory + */ + public static init(uuid: string): void { + this.registerMap.set('emoji', EmojiController); + this.presetNodes.forEach(type => { + this.registerPlugin({ + type, + id: `${type}${uuid}`, + }); + }); + } + + /** + * 注册节点适配器 + * + * @static + * @param {IData} data + * @return {IParams | void} {IParams | void} + * @memberof CustomNodeFactory + */ + public static registerPlugin(data: IData): IParams | void { + const { type, id } = data; + if (this.customNodeMap.has(id)) { + return this.customNodeMap.get(id)!; + } + const NewController = this.registerMap.get(type); + if (NewController) { + const controller = new NewController(); + this.customNodeMap.set(id, controller); + return controller; + } + } + + /** + * 根据uuid获取注册插件 + * + * @static + * @param {string} id + * @return {*} + * @memberof CustomNodeFactory + */ + public static getPluginsById(id: string): INodeController[] { + const controllers: INodeController[] = []; + this.presetNodes.forEach(type => { + if (this.customNodeMap.has(`${type}${id}`)) { + controllers.push(this.customNodeMap.get(`${type}${id}`)!); + } + }); + return controllers; + } + + /** + * 卸载节点适配器 + * + * @static + * @param {IData} data + * @return {*} + * @memberof CustomNodeFactory + */ + public static unregisterPlugin(data: IData): void { + const { id } = data; + if (this.customNodeMap.has(id)) { + this.customNodeMap.delete(id)!; + } + } + + /** + * 初始化 + * + * @static + * @param {string} uuid + * @return {*} + * @memberof CustomNodeFactory + */ + public static destroy(uuid: string): void { + this.presetNodes.forEach(type => { + this.unregisterPlugin({ + id: `${type}${uuid}`, + }); + }); + this.registerMap.delete('emoji'); + } +} diff --git a/src/editor/html/wang-editor/interface/index.ts b/src/editor/html/wang-editor/interface/index.ts new file mode 100644 index 000000000..0305527d0 --- /dev/null +++ b/src/editor/html/wang-editor/interface/index.ts @@ -0,0 +1 @@ +export * from './node-interface'; diff --git a/src/editor/html/wang-editor/interface/node-interface.ts b/src/editor/html/wang-editor/interface/node-interface.ts new file mode 100644 index 000000000..ce56a8e71 --- /dev/null +++ b/src/editor/html/wang-editor/interface/node-interface.ts @@ -0,0 +1,61 @@ +import { IDomEditor } from '@wangeditor/editor'; + +/** + * 自定义节点控制器 + * + * @export + * @interface INodeController + */ +export interface INodeController { + /** + * 初始化 + * + * @param {IDomEditor} editor + * @param {IData} option + * @return {*} {string} + * @memberof INodeController + */ + init(editor: IDomEditor, option: IData): Promise; + + /** + * 销毁 + * + * @return {*} + * @memberof INodeController + */ + onDestroyed(): void; + + /** + * 注册节点 + * + * @return {*} + * @memberof INodeController + */ + registerNode(): void; + + /** + * 打开模态 + * + * @return {*} + * @memberof INodeController + */ + openModal(): void; + + /** + * 插入节点 + * + * @param {IData} data + * @return {*} + * @memberof INodeController + */ + insertNode(data: IData): void; + + /** + * 解析节点 + * + * @param {string} value + * @return {string} {string} + * @memberof INodeController + */ + parseNode(value: string): string; +} diff --git a/src/editor/html/wang-editor/wang-editor-toolbars.ts b/src/editor/html/wang-editor/wang-editor-toolbars.ts new file mode 100644 index 000000000..cbdad2300 --- /dev/null +++ b/src/editor/html/wang-editor/wang-editor-toolbars.ts @@ -0,0 +1,69 @@ +export const defaultToolbars = [ + 'headerSelect', + 'blockquote', + '|', + 'bold', + 'underline', + 'italic', + { + key: 'group-more-style', + title: '更多', + iconSvg: + '', + menuKeys: ['through', 'code', 'sup', 'sub', 'clearStyle'], + }, + 'color', + 'bgColor', + '|', + 'fontSize', + 'fontFamily', + 'lineHeight', + '|', + 'bulletedList', + 'numberedList', + 'todo', + { + key: 'group-justify', + title: '对齐', + iconSvg: + '', + menuKeys: [ + 'justifyLeft', + 'justifyRight', + 'justifyCenter', + 'justifyJustify', + ], + }, + { + key: 'group-indent', + title: '缩进', + iconSvg: + '', + menuKeys: ['indent', 'delIndent'], + }, + '|', + 'emoji', + 'insertLink', + { + key: 'group-image', + title: '图片', + iconSvg: + '', + menuKeys: ['insertImage', 'uploadImage'], + }, + { + key: 'group-video', + title: '视频', + iconSvg: + '', + menuKeys: ['insertVideo', 'uploadVideo'], + }, + 'insertTable', + 'codeBlock', + 'divider', + '|', + 'undo', + 'redo', + '|', + 'fullScreen', +]; diff --git a/src/editor/html/wang-editor/wang-editor.tsx b/src/editor/html/wang-editor/wang-editor.tsx index 5a06a126e..1aee1cabc 100644 --- a/src/editor/html/wang-editor/wang-editor.tsx +++ b/src/editor/html/wang-editor/wang-editor.tsx @@ -28,6 +28,7 @@ import { import { ElMessageBox } from 'element-plus'; import { HtmlEditorController } from '../html-editor.controller'; import './wang-editor.scss'; +import { defaultToolbars } from './wang-editor-toolbars'; type InsertFnType = (_url: string, _alt: string, _href: string) => void; @@ -157,6 +158,7 @@ const IBizHtml = defineComponent({ // 工具栏配置 const toolbarConfig: Partial = { excludeKeys: ['group-video'], + toolbarKeys: defaultToolbars, }; if (c.chatCompletion) { @@ -352,7 +354,11 @@ const IBizHtml = defineComponent({ // 编辑器创建完毕时的回调函数 const handleCreated = (editor: IDomEditor) => { editorRef.value = editor; // 记录 editor 实例,重要! - editor.setHtml(valueHtml.value); + c.onCreated(editorRef.value, props.data, toolbarConfig); + nextTick(() => { + const html = c.parseNode(valueHtml.value); + editorRef.value.setHtml(html); + }); // 配置菜单 // setTimeout(() => { // const toolbar = DomEditor.getToolbar(editor); diff --git a/src/locale/en/index.ts b/src/locale/en/index.ts index 9ccd88f30..cad7b561a 100644 --- a/src/locale/en/index.ts +++ b/src/locale/en/index.ts @@ -533,6 +533,7 @@ export default { html: { wangEditor: { customTips: 'Custom tip', + emoji: 'Emoji', }, }, markdown: { diff --git a/src/locale/zh-CN/index.ts b/src/locale/zh-CN/index.ts index 1600ca188..8adf427a4 100644 --- a/src/locale/zh-CN/index.ts +++ b/src/locale/zh-CN/index.ts @@ -499,6 +499,7 @@ export default { html: { wangEditor: { customTips: '自定义提示', + emoji: '表情', }, }, markdown: { -- Gitee