diff --git a/CHANGELOG.md b/CHANGELOG.md index 68efd5a592bd93b6b9f63be3d1f265487ffc5b41..fbdde680b89e76f9507725101335ff0739a6a3b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,14 @@ ## [Unreleased] +### Changed + +- 更新AI行内聊天上下文菜单样式 +- 更新html全屏样式 + ### Added +- 新增markdown支持AI行内聊天 - 新增markdown支持手动编辑模式 - 新增重复器表格样式2组件 - 新增代码编辑器支持AI行内聊天 diff --git a/src/editor/html/wang-editor/wang-editor.scss b/src/editor/html/wang-editor/wang-editor.scss index 2b4e41eae362a4f310eaad45266de7929141580f..10e6aa67193eb9671a1e9ce89e951e0e082ba704 100644 --- a/src/editor/html/wang-editor/wang-editor.scss +++ b/src/editor/html/wang-editor/wang-editor.scss @@ -84,6 +84,18 @@ $html: ( width: 100%; } } + + @include when('enable-edit'){ + // 整体处于允许编辑的情况下,默认的工具栏样式变为聚焦时的样式 + .w-e-bar-item>button{ + color: var(--w-e-toolbar-color); + cursor: pointer; + + svg{ + fill: var(--w-e-toolbar-color); + } + } + } } @include b('html-editor-readonly') { @@ -96,6 +108,21 @@ $html: ( } } +@include b('html-toolbar'){ + .w-e-toolbar{ + background-color: getCssVar(color, bg, 0); + border: 1px solid getCssVar(color, border); + border-bottom: none; + box-shadow: none; + + .w-e-bar-item>button{ + &:hover{ + background-color: getCssVar(color, bg, 0); + } + } + } +} + @include b('html-footer') { display: flex; align-items: center; @@ -187,28 +214,50 @@ $html: ( @include b('html-dialog-full-screen') { height: 80%; + .el-dialog__header{ + display: none; + } + + .el-dialog__body{ + height: 100%; + padding-top: 0; + } + + @include when('editing'){ + .el-dialog__body{ + padding-bottom: 0; + } + } + @include b('html') { - padding: 0 getCssVar('spacing', 'extra-loose'); + padding: 0; --w-e-toolbar-bg-color: #{getCssVar(color, bg, 0)}; } @include b('html-custom-toolbar') { height: 56px; + padding-right: 0; } @include b('html-content') { - height: calc(100% - 124px); + height: calc(100% - 56px); @include b('html-editor') { height: 100% !important; } + + @include when('editing'){ + height: calc(100% - 128px); + } } + } @include b('html-footer-dialog') { height: 68px; margin-top: 0; + margin-right: 0; } // 表情元素 diff --git a/src/editor/html/wang-editor/wang-editor.tsx b/src/editor/html/wang-editor/wang-editor.tsx index 970f79bd4c5774de55e99d7c1894475f19add0d2..3c2ae8ccac0894cd43a7a3ef7ac9f1653ee460f1 100644 --- a/src/editor/html/wang-editor/wang-editor.tsx +++ b/src/editor/html/wang-editor/wang-editor.tsx @@ -966,7 +966,11 @@ const IBizHtml = defineComponent({ // 绘制编辑器内容 const renderEditorContent = () => { return ( -
+
{this.renderHeaserToolbar()} @@ -1053,7 +1058,10 @@ const IBizHtml = defineComponent({ v-model={this.isFullScreen} width='80%' top='10vh' - class={this.ns.b('dialog-full-screen')} + class={[ + this.ns.b('dialog-full-screen'), + this.ns.is('editing', !this.readonlyState), + ]} onClose={() => this.changeFullScreenState()} >
+ + + + + `, + }, + onClick: (_selection: string, _menukey: string, event: MouseEvent) => { + const startPos = c.mdeditor?.editor.editor.getCursor('start'); + const endPos = c.mdeditor?.editor.editor.getCursor('end'); + c.setCursorPos(startPos, endPos); + event.stopPropagation(); + event.preventDefault(); + nextTick(() => { + if (c.mdeditor?.bubble) { + c.mdeditor.bubble.visible = true; + const selectionPosition = + c.mdeditor.bubble.bubbleDom.getBoundingClientRect(); + if ( + !selectionPosition || + !selectionPosition.left || + !selectionPosition.top + ) + return; + const items: MenuItem[] = ibiz.inLineAIUtil.calcContextMenus( + c.deACMode, + (tag: string) => { + c.doInLineAIUIAction(tag, c.model.appId); + }, + ); + if (items.length === 0) return; + ibiz.inLineAIUtil.showContextMenus( + selectionPosition.left, + selectionPosition.top + 60, + items, + ); + } + }); + }, + }); + return [AIMenu]; +} diff --git a/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.tsx b/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.tsx index 7ad9133b7d5bb5b64699e2e5151dc09111f78b5b..0604dac9f0afa33e6fd9b6160fd68147a3df08b3 100644 --- a/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.tsx +++ b/src/editor/markdown/ibiz-markdown-editor/ibiz-markdown-editor.tsx @@ -19,6 +19,7 @@ import { createUUID } from 'qx-util'; import Cherry from 'cherry-markdown'; import { MarkDownEditorController } from '../markdown-editor.controller'; import './ibiz-markdown-editor.scss'; +import { initCustomMenu } from './custom-menu'; /** * Markdown编辑框 @@ -52,6 +53,8 @@ const IBizMarkDown: any = defineComponent({ const id = createUUID(); + const [AIMenu] = initCustomMenu(c); + // 请求头 const uploadHeaders = ibiz.util.file.getUploadHeaders(); const headers: Ref = ref({ ...uploadHeaders }); @@ -317,6 +320,7 @@ const IBizMarkDown: any = defineComponent({ // 关闭全屏 if (isFullscreen()) { closeFullscreen(); + isFullScreen.value = false; } } }; @@ -326,6 +330,20 @@ const IBizMarkDown: any = defineComponent({ defaultModel.value = 'previewOnly'; } nextTick(() => { + const bubble = [ + 'bold', + 'italic', + 'underline', + 'strikethrough', + 'sub', + 'sup', + '|', + 'size', + 'color', + ]; + if (c.editorParams.ac && c.deACMode) { + bubble.unshift('AI'); + } editor = new Cherry({ id, value: currentVal.value, @@ -386,17 +404,7 @@ const IBizMarkDown: any = defineComponent({ 'settings', 'togglePreview', ], - bubble: [ - 'bold', - 'italic', - 'underline', - 'strikethrough', - 'sub', - 'sup', - '|', - 'size', - 'color', - ], + bubble, float: [ 'h1', 'h2', @@ -407,7 +415,9 @@ const IBizMarkDown: any = defineComponent({ 'quickTable', 'code', ], - customMenu: [], + customMenu: { + AI: AIMenu, + }, // 定义侧边栏,默认为空 sidebar: ['theme', 'copy'], // 定义顶部右侧工具栏,默认为空 @@ -456,6 +466,7 @@ const IBizMarkDown: any = defineComponent({ '.cherry-toolbar>.toolbar-right', ); parentElement?.appendChild(span); + c.setMDEditor(editor); }); }; @@ -542,15 +553,23 @@ const IBizMarkDown: any = defineComponent({ onClick={onEnableEdit} title={ibiz.i18n.t('editor.markdown.edit')} > - +
)} -
- +
+ {isFullScreen.value ? ( + + ) : ( + + )}
); diff --git a/src/editor/markdown/markdown-editor.controller.ts b/src/editor/markdown/markdown-editor.controller.ts index 2f99b7bb5b6929f795aae9d9d8df490101deca5c..fa7509996322c760e13c0d6ddc3304aabed18f5b 100644 --- a/src/editor/markdown/markdown-editor.controller.ts +++ b/src/editor/markdown/markdown-editor.controller.ts @@ -2,6 +2,9 @@ import { RuntimeModelError } from '@ibiz-template/core'; import { EditorController, IAppDEService, + IInLineAIEditor, + IInLineAiChatOptions, + UIActionUtil, getDeACMode, } from '@ibiz-template/runtime'; import { IAppDEACMode, IDEACModeDataItem, IMarkdown } from '@ibiz/model-core'; @@ -13,7 +16,10 @@ import { IAppDEACMode, IDEACModeDataItem, IMarkdown } from '@ibiz/model-core'; * @class MarkDownEditorController * @extends {EditorController} */ -export class MarkDownEditorController extends EditorController { +export class MarkDownEditorController + extends EditorController + implements IInLineAIEditor +{ /** * 上传参数 */ @@ -87,6 +93,22 @@ export class MarkDownEditorController extends EditorController { */ chatCompletion: boolean = false; + /** + * 编辑器实例 + * + * @type {IData} + * @memberof MarkDownEditorController + */ + mdeditor: IData | null = null; + + /** + * 选区位置缓存 + * + * @type {(IData | null)} + * @memberof MarkDownEditorController + */ + selectionAreaPosition: IData | null = null; + protected async onInit(): Promise { await super.onInit(); @@ -161,4 +183,109 @@ export class MarkDownEditorController extends EditorController { } } } + + /** + * 设置编辑器实例 + * + * @param {IData} mdeditor + * @memberof MarkDownEditorController + */ + public setMDEditor(mdeditor: IData): void { + this.mdeditor = mdeditor; + } + + /** + * 设置保存当前选区位置 + * + * @param {IData} start + * @param {IData} end + * @memberof MarkDownEditorController + */ + setCursorPos(start: IData, end: IData): void { + this.selectionAreaPosition = { start, end }; + } + + /** + * 获取选中文本 + * + * @return {*} {string} + * @memberof MarkDownEditorController + */ + public getSelectionText(): string { + return this.mdeditor?.editor.editor.getSelection(); + } + + /** + * 插入文本 + * + * @param {string} text + * @memberof MarkDownEditorController + */ + insertText(text: string): void { + this.mdeditor?.insert(text); + } + + /** + * 替换选中文本 + * + * @param {string} text + * @memberof MarkDownEditorController + */ + replaceSelectionText(text: string): void { + this.mdeditor?.editor.editor.replaceSelection(text); + } + + /** + * 恢复选区 + * + * @memberof MarkDownEditorController + */ + restoreSelection(): void { + this.mdeditor?.editor.editor.setSelection( + this.selectionAreaPosition?.start, + this.selectionAreaPosition?.end, + ); + } + + /** + * 获取内联AI参数 + * + * @return {*} {IData} + * @memberof MarkDownEditorController + */ + getInLineAiChatOptions(): IInLineAiChatOptions { + const editorRect = this.mdeditor?.wrapperDom.getBoundingClientRect(); + return { + left: editorRect?.left, + top: editorRect?.top, + width: editorRect?.width, + }; + } + + /** + * 执行内联AIUI操作 + * + * @param {string} _uiAction + * @param {string} _appId + * @return {*} {Promise} + * @memberof MarkDownEditorController + */ + async doInLineAIUIAction(uiActionId: string, appId: string): Promise { + const eventArgs = this.ctrl.getEventArgs(); + eventArgs.params.editor = this; + // 编辑器参数srfaiappendcurdata,是否传入对象参数,用于历史查询传参 + if ( + this.editorParams.srfaiappendcurdata && + this.editorParams.srfaiappendcurdata === 'true' + ) { + eventArgs.context.srfaiappendcurdata = true; + } + await UIActionUtil.exec( + uiActionId!, + { + ...eventArgs, + }, + appId, + ); + } } diff --git a/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.scss b/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.scss index e13df9738a6dd56831c7f2ac6a981b61d0f8e170..64fc5a39ceab4ebf21e84a49925cb651dfbf9dde 100644 --- a/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.scss +++ b/src/util/inline-ai-util/inline-ai-textarea/inline-ai-textarea.scss @@ -1,5 +1,27 @@ +$inline-ai-context-menu: ( + color-bg: getCssVar(color,bg,2), + color-text: getCssVar(color,text,0), + color-bg-active: getCssVar(color,fill,0), +); @include b(inline-ai-container-context-menu) { - color: red; + @include set-component-css-var('inline-ai-context-menu',$inline-ai-context-menu); + + --mx-menu-text: #{getCssVar(inline-ai-context-menu,color-text)}; + --mx-menu-backgroud: #{getCssVar(inline-ai-context-menu,color-bg)}; + --mx-menu-hover-backgroud: #{getCssVar(inline-ai-context-menu,color-bg-active)}; + --mx-menu-hover-text: #{getCssVar(inline-ai-context-menu,color-text)}; + --mx-menu-open-text: #{getCssVar(inline-ai-context-menu,color-text)}; + --mx-menu-open-backgroud:#{getCssVar(inline-ai-context-menu,color-bg-active)}; + --mx-menu-open-hover-text:#{getCssVar(inline-ai-context-menu,color-text)}; + --mx-menu-open-hover-backgroud:#{getCssVar(inline-ai-context-menu,color-bg-active)}; + + .scroll-content{ + pointer-events: unset; + } + + .mx-context-menu{ + cursor: pointer; + } } @include b(inline-ai-textarea-container) {