diff --git a/src/editor/html/html-editor.controller.ts b/src/editor/html/html-editor.controller.ts
index 7ad26fbb99f2191140495bf471fb41297a159a72..9e051d7fa7eed7c88dc9ac7a2720d90bbde95d19 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 0000000000000000000000000000000000000000..f289378667180d30fa3acab0297a9eb89f0dce7d
--- /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 0000000000000000000000000000000000000000..9054c5f27eb7c279b962aa1170f9bd60e7869d57
--- /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 0000000000000000000000000000000000000000..398a8a620998f65c285bfcc11af1b66a7b432a40
--- /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 0000000000000000000000000000000000000000..27a9fea052cf2264c216575a2ec237b15fa3ee8c
--- /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 0000000000000000000000000000000000000000..075f3f35754d33fda618e9abf7d6aafce8f7ddcc
--- /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 0000000000000000000000000000000000000000..90f85de86d551642a777a82e421a4ea097897f30
--- /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 0000000000000000000000000000000000000000..17856361711f7d2c8d53d3deec7dce9678679f29
--- /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 0000000000000000000000000000000000000000..0305527d095ecede238611657dd53ab271262b00
--- /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 0000000000000000000000000000000000000000..ce56a8e71292e11912966c8193640a0c2350cfc4
--- /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 0000000000000000000000000000000000000000..cbdad2300ea38eb7d4fa50cf9fffca8afe3f9d69
--- /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 5a06a126e1126bc3110b5956b26f7eda3a7d2057..1aee1cabc43b32df1b21aa860e7a6fda2ca4049c 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 9ccd88f3094f1522dc6096f86250ce245fa548c7..cad7b561a74bc888375b9478c9533226ffaf937e 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 1600ca18835cada11bf321c3776581310611a68c..8adf427a49d2a5653ffd4eef840d87987ade5a40 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: {