From 7811d19ec0fe5a552018d94ef87763cea62201ba Mon Sep 17 00:00:00 2001 From: liaoxuezhi <2698393+2betop@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:36:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20amis-editor=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=86=85=E8=81=94=E7=BC=96=E8=BE=91=20(#11470)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis-editor-core/scss/editor.scss | 19 ++ .../amis-editor-core/src/component/Editor.tsx | 5 + .../src/component/HighlightBox.tsx | 6 +- .../src/component/Preview.tsx | 43 +++ packages/amis-editor-core/src/inlineEdit.ts | 261 ++++++++++++++++++ packages/amis-editor-core/src/manager.ts | 47 +++- packages/amis-editor-core/src/plugin.ts | 18 ++ packages/amis-editor-core/src/store/editor.ts | 5 + packages/amis-editor/src/plugin/Button.tsx | 12 +- packages/amis-editor/src/plugin/Form/Form.tsx | 9 + packages/amis-editor/src/plugin/Form/Item.tsx | 29 +- packages/amis-editor/src/plugin/Page.tsx | 14 + packages/amis-editor/src/plugin/Panel.tsx | 9 + packages/amis-editor/src/plugin/Tpl.tsx | 10 + packages/amis-ui/package.json | 4 +- packages/amis-ui/src/components/RichText.tsx | 2 + packages/amis-ui/src/components/Tinymce.tsx | 2 + 17 files changed, 486 insertions(+), 9 deletions(-) create mode 100644 packages/amis-editor-core/src/inlineEdit.ts diff --git a/packages/amis-editor-core/scss/editor.scss b/packages/amis-editor-core/scss/editor.scss index 79e8129a5ef..f90494b5b95 100644 --- a/packages/amis-editor-core/scss/editor.scss +++ b/packages/amis-editor-core/scss/editor.scss @@ -730,6 +730,14 @@ } } + &.focused { + border: 1px solid $Editor-hlbox--onActive-bg; + padding: 5px; + box-sizing: content-box; + transform: translate(-5px, -5px); + box-shadow: inset 0 0 10px rgba($Editor-hlbox--onActive-bg, 0.5); + } + &.regionOn { background: transparent; z-index: 5; @@ -1266,6 +1274,17 @@ position: relative !important; } +[data-editor-id] [contenteditable]:focus { + outline: 0px solid transparent; +} + +[data-editor-id] { + .fr-quick-insert, + .fr-qi-helper { + transform: translateX(60px); + } +} + .ae-Region-placeholder { display: none; text-align: center; diff --git a/packages/amis-editor-core/src/component/Editor.tsx b/packages/amis-editor-core/src/component/Editor.tsx index 5dd90ab9813..46ff31d4442 100644 --- a/packages/amis-editor-core/src/component/Editor.tsx +++ b/packages/amis-editor-core/src/component/Editor.tsx @@ -463,6 +463,11 @@ export default class Editor extends Component { // 右键菜单 @autobind async handleContextMenu(e: React.MouseEvent) { + // inline edit 模式不要右键 + if (this.store.activeElement) { + return; + } + e.persist(); await closeContextMenus(); let targetId: string = ''; diff --git a/packages/amis-editor-core/src/component/HighlightBox.tsx b/packages/amis-editor-core/src/component/HighlightBox.tsx index 657f25829b9..63134f25eff 100644 --- a/packages/amis-editor-core/src/component/HighlightBox.tsx +++ b/packages/amis-editor-core/src/component/HighlightBox.tsx @@ -269,7 +269,9 @@ export default observer(function ({ 'ae-Editor-hlbox', { shake: id === store.insertOrigId, - selected: isActive || ~store.selections.indexOf(id), + focused: store.activeElement && isActive, + selected: + (isActive && !store.activeElement) || ~store.selections.indexOf(id), hover: isHover, regionOn: node.childRegions.some(region => store.isRegionHighlighted(region.id, region.region) @@ -293,7 +295,7 @@ export default observer(function ({ onDragStart={handleDragStart} onClick={handleClick} > - {isActive && !readonly ? ( + {isActive && !store.activeElement && !readonly ? (
{ this.currentDom.addEventListener('mouseleave', this.handleMouseLeave); this.currentDom.addEventListener('mousemove', this.handleMouseMove); this.currentDom.addEventListener('click', this.handleClick, true); + this.currentDom.addEventListener('dblclick', this.handleDBClick); this.currentDom.addEventListener('mouseover', this.handeMouseOver); this.currentDom.addEventListener('mousedown', this.handeMouseDown); @@ -97,6 +98,7 @@ export default class Preview extends Component { this.currentDom.removeEventListener('mouseleave', this.handleMouseLeave); this.currentDom.removeEventListener('mousemove', this.handleMouseMove); this.currentDom.removeEventListener('click', this.handleClick, true); + this.currentDom.removeEventListener('dblclick', this.handleDBClick); this.currentDom.removeEventListener('mouseover', this.handeMouseOver); this.currentDom.removeEventListener('mousedown', this.handeMouseDown); this.props.manager.off('after-update', this.handlePanelChange); @@ -302,6 +304,12 @@ export default class Preview extends Component { @autobind handleClick(e: MouseEvent) { const store = this.props.store; + + // 处于编辑态时,不响应点击事件 + if (store.activeElement) { + return; + } + const target = (e.target as HTMLElement).closest(`[data-editor-id]`); if ((e.target as HTMLElement).closest('.ae-editor-action-btn')) { @@ -353,6 +361,40 @@ export default class Preview extends Component { } } + @autobind + handleDBClick(e: MouseEvent) { + const store = this.props.store; + const target = e.target as HTMLElement; + const hostElem = target.closest(`[data-editor-id]`) as HTMLElement; + if (hostElem) { + const node = store.getNodeById(hostElem.getAttribute('data-editor-id')!); + if (!node) { + return; + } + + const rendererInfo = node.info; + + // 需要支持 :scope > xxx 语法,所以才这么写 + let inlineElem: HTMLElement | undefined | null = null; + const inlineSetting = (rendererInfo.inlineEditableElements || []).find( + elem => { + inlineElem = ( + [].slice.call( + hostElem.querySelectorAll(elem.match) + ) as Array + ).find(dom => dom.contains(target)); + return !!inlineElem; + } + )!; + + // 如果命中了支持内联编辑的元素,则开始内联编辑 + if (inlineElem && inlineSetting) { + const manager = this.props.manager; + manager.startInlineEdit(node, inlineElem, inlineSetting, e); + } + } + } + @autobind handleNavSwitch(id: string) { const store = this.props.store; @@ -625,6 +667,7 @@ export default class Preview extends Component { > {node.childRegions.map(region => !node.memberImmutable(region.region) && + !store.activeElement && !this.props.readonly && store.isRegionActive(region.id, region.region) ? ( void; + onCancel: () => void; +} + +/** + * 启动内联编辑 + * 根据配置的mode选择对应的编辑模式 + * @param {InlineEditContext} context - 编辑上下文 + */ +export function startInlineEdit(context: InlineEditContext) { + if (context.config.mode === 'rich-text') { + startRichTextEdit(context); + } else { + startPlainTextEdit(context); + } +} + +/** + * 启动纯文本编辑模式 + * 设置元素为可编辑状态,监听键盘事件 + * 支持ESC取消和回车确认 + * @param {InlineEditContext} context - 编辑上下文 + */ +function startPlainTextEdit({ + elem, + onConfirm, + onCancel, + event +}: InlineEditContext) { + let origin = elem.innerText.trim(); + let forceCancel = false; + const onKeyDown = (e: Event) => { + const code = keycode(e); + if (code === 'esc') { + // 还是不要把内容都还原了,直接取消吧 + // forceCancel = true; + cleanup(); + } else if (code === 'enter') { + e.stopPropagation(); + e.preventDefault(); + cleanup(); + } + }; + + /** + * 清理编辑状态 + * 移除事件监听,还原元素属性 + * 根据内容是否变化决定触发确认还是取消 + */ + const cleanup = () => { + elem.removeEventListener('blur', cleanup); + elem.removeAttribute('contenteditable'); + elem.removeEventListener('keydown', onKeyDown); + const value = elem.innerText.trim(); + if (!forceCancel && value !== origin) { + onConfirm(value); + } else { + onCancel(); + } + }; + + elem.addEventListener('blur', cleanup); + elem.addEventListener('keydown', onKeyDown); + elem.setAttribute('contenteditable', 'plaintext-only'); + elem.focus(); + + let caretRange = event + ? getMouseEventCaretRange(event) + : createRangeAtTheEnd(elem); + + // Set a timer to allow the selection to happen and the dust settle first + setTimeout(function () { + selectRange(caretRange); + }, 10); +} + +/** + * 启动富文本编辑模式 + * 使用Froala编辑器,支持文本格式化、插入图片表格等功能 + * @param {InlineEditContext} context - 编辑上下文 + */ +async function startRichTextEdit({ + elem, + event, + node, + onConfirm, + onCancel +}: InlineEditContext) { + const {FroalaEditor} = await import('amis-ui/lib/components/RichText'); + const id = `u_${guid()}`; + elem.setAttribute('data-froala-id', id); + + let origin = ''; + let forceCancel = false; + + /** + * 清理富文本编辑器 + * 销毁编辑器实例,移除关联属性 + * 根据内容变化决定触发确认还是取消 + */ + const cleanup = () => { + const value = editor.html.get(); + editor.destroy(); + elem.removeAttribute('data-froala-id'); + + if (!forceCancel && value !== origin) { + onConfirm(value); + } else { + onCancel(); + } + }; + + const editor = new FroalaEditor( + `[data-froala-id="${id}"]`, + { + toolbarInline: true, + charCounterCount: false, + // todo 现在这个按钮的位置又问题,先忽略 + // quickInsertEnabled: false, + toolbarButtons: [ + 'paragraphFormat', + 'textColor', + 'backgroundColor', + 'bold', + 'underline', + 'strikeThrough', + 'formatOL', + 'formatUL', + 'align', + 'quote', + 'insertLink', + 'insertImage', + 'insertEmotion', + 'insertTable' + ], + events: { + blur: cleanup, + keydown: (e: KeyboardEvent) => { + if (e.key === 'Escape') { + // 还是不要把内容都还原了,直接取消吧 + // forceCancel = true; + cleanup(); + } + }, + contentChanged: () => { + node.calculateHighlightBox(); + } + } + }, + () => { + editor.events.focus(); + origin = editor.html.get(); + + let caretRange = event + ? getMouseEventCaretRange(event) + : createRangeAtTheEnd(elem); + + // Set a timer to allow the selection to happen and the dust settle first + setTimeout(function () { + selectRange(caretRange); + }, 10); + } + ); +} + +/** + * 在元素末尾创建一个Range对象 + * 用于设置光标位置 + * @param {HTMLElement} elem - 目标元素 + * @returns {Range} 创建的Range对象 + */ +function createRangeAtTheEnd(elem: HTMLElement) { + const range = document.createRange(); + range.selectNodeContents(elem); + range.collapse(false); + return range; +} + +/** + * 根据鼠标事件获取光标Range + * 支持多种浏览器的光标位置获取方式 + * 包括IE、Mozilla、WebKit等 + * @param {MouseEvent} evt - 鼠标事件对象 + * @returns {Range} 光标位置的Range对象 + */ +function getMouseEventCaretRange(evt: MouseEvent) { + let range, + x = evt.clientX, + y = evt.clientY; + + // Try the simple IE way first + if ((document.body as any).createTextRange) { + range = (document.body as any).createTextRange(); + range.moveToPoint(x, y); + } else if (typeof document.createRange != 'undefined') { + // Try Mozilla's rangeOffset and rangeParent properties, + // which are exactly what we want + if (typeof (evt as any).rangeParent != 'undefined') { + range = document.createRange(); + range.setStart((evt as any).rangeParent, (evt as any).rangeOffset); + range.collapse(true); + } + + // Try the standards-based way next + else if ((document as any).caretPositionFromPoint) { + let pos: any = (document as any).caretPositionFromPoint(x, y); + range = document.createRange(); + range.setStart(pos.offsetNode, pos.offset); + range.collapse(true); + } + + // Next, the WebKit way + else if (document.caretRangeFromPoint) { + range = document.caretRangeFromPoint(x, y); + } + } + + return range; +} + +/** + * 选中指定的Range区域 + * 支持IE和标准浏览器两种方式 + * @param {Range} range - 要选中的Range对象 + */ +function selectRange(range: any) { + if (range) { + if (typeof range.select != 'undefined') { + range.select(); + } else if (typeof window.getSelection != 'undefined') { + let sel: any = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } + } +} diff --git a/packages/amis-editor-core/src/manager.ts b/packages/amis-editor-core/src/manager.ts index ea6add37911..8bfb9a07698 100644 --- a/packages/amis-editor-core/src/manager.ts +++ b/packages/amis-editor-core/src/manager.ts @@ -14,7 +14,8 @@ import { RenderOptions, JSONTraverse, wrapFetcher, - GlobalVariableItem + GlobalVariableItem, + setVariable } from 'amis-core'; import { PluginInterface, @@ -45,7 +46,8 @@ import { PluginActions, BasePlugin, GlobalVariablesEventContext, - GlobalVariableEventContext + GlobalVariableEventContext, + InlineEditableElement } from './plugin'; import { EditorStoreType, @@ -66,7 +68,8 @@ import { scrollToActive, JSONPipeIn, generateNodeId, - JSONGetNodesById + JSONGetNodesById, + diff } from './util'; import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory'; import {env} from './env'; @@ -79,6 +82,7 @@ import type {IScopedContext} from 'amis'; import type {SchemaObject, SchemaCollection} from 'amis'; import type {Api, Payload, RendererConfig, RendererEnv} from 'amis-core'; import {loadAsyncRenderer} from 'amis-core'; +import {startInlineEdit} from './inlineEdit'; export interface EditorManagerConfig extends Omit {} @@ -2347,6 +2351,43 @@ export class EditorManager { } } + startInlineEdit( + node: EditorNodeType, + elem: HTMLElement, + config: InlineEditableElement, + event?: MouseEvent + ) { + const store = this.store; + store.setActiveId(node.id); + store.setActiveElement(config.match); + + startInlineEdit({ + node, + event, + elem, + config, + onCancel: () => { + store.setActiveElement(''); + }, + onConfirm: (value: string) => { + store.setActiveElement(''); + + if (config.key) { + const originValue = store.getValueOf(node.id); + const newValue = {...originValue}; + setVariable(newValue, config.key, value); + + const diffValue = diff(originValue, newValue); + // 没有变化时不触发onChange + if (!diffValue) { + return; + } + this.panelChangeValue(newValue, diffValue, undefined, node.id); + } + } + }); + } + /** * 初始化全局变量 */ diff --git a/packages/amis-editor-core/src/plugin.ts b/packages/amis-editor-core/src/plugin.ts index b8862ea0fed..c837e1cae55 100644 --- a/packages/amis-editor-core/src/plugin.ts +++ b/packages/amis-editor-core/src/plugin.ts @@ -189,6 +189,18 @@ export interface RendererScaffoldInfo { scaffold?: any; } +export interface InlineEditableElement { + // 元素选择器,当命中这个规则时支持内联编辑 + match: string; + + // 内联编辑模式 + // 默认为 plain-text + mode?: 'plain-text' | 'rich-text'; + + // onChange?: (node: EditorNodeType, value: any, elem: HTMLElement) => void; + key: string; +} + /** * 渲染器信息。 */ @@ -217,6 +229,11 @@ export interface RendererInfo extends RendererScaffoldInfo { */ regions?: Array; + /** + * 支持内联编辑的元素集合 + */ + inlineEditableElements?: Array; + /** * 选中不需要高亮 */ @@ -1100,6 +1117,7 @@ export abstract class BasePlugin implements PluginInterface { return { name: curPluginName, regions: plugin.regions, + inlineEditableElements: plugin.inlineEditableElements, patchContainers: plugin.patchContainers, // wrapper: plugin.wrapper, vRendererConfig: plugin.vRendererConfig, diff --git a/packages/amis-editor-core/src/store/editor.ts b/packages/amis-editor-core/src/store/editor.ts index 0b111d9d2d3..d3617670b54 100644 --- a/packages/amis-editor-core/src/store/editor.ts +++ b/packages/amis-editor-core/src/store/editor.ts @@ -173,6 +173,7 @@ export const MainStore = types hoverRegion: '', activeId: '', activeRegion: '', // 记录当前激活的子区域 + activeElement: '', // 记录当前编辑的内联元素 mouseMoveRegion: '', // 记录当前鼠标hover到的区域,后续需要优化(合并MouseMoveRegion和hoverRegion) // 点选多个的时候用来记录, 单选单个的时候还是 activeId @@ -1408,6 +1409,10 @@ export const MainStore = types } }, + setActiveElement(selector: string) { + self.activeElement = selector; + }, + setSelections(ids: Array) { self.activeId = ''; self.activeRegion = ''; diff --git a/packages/amis-editor/src/plugin/Button.tsx b/packages/amis-editor/src/plugin/Button.tsx index 236250b97f6..647220b1da3 100644 --- a/packages/amis-editor/src/plugin/Button.tsx +++ b/packages/amis-editor/src/plugin/Button.tsx @@ -14,6 +14,7 @@ import {RendererPluginAction, RendererPluginEvent} from 'amis-editor-core'; import type {SchemaObject} from 'amis'; import {getOldActionSchema} from '../renderer/event-control/helper'; import {buttonStateFunc} from '../renderer/style-control/helper'; +import {InlineEditableElement} from 'amis-editor-core'; export class ButtonPlugin extends BasePlugin { static id = 'ButtonPlugin'; @@ -121,6 +122,14 @@ export class ButtonPlugin extends BasePlugin { // } ]; + // 定义可以内联编辑的元素 + inlineEditableElements: Array = [ + { + match: ':scope>span', + key: 'label' + } + ]; + // 动作定义 actions: RendererPluginAction[] = []; @@ -432,7 +441,8 @@ export class ButtonPlugin extends BasePlugin { wrapperResolve: plugin.wrapperResolve, filterProps: plugin.filterProps, $schema: plugin.$schema, - renderRenderer: plugin.renderRenderer + renderRenderer: plugin.renderRenderer, + inlineEditableElements: plugin.inlineEditableElements }; } } diff --git a/packages/amis-editor/src/plugin/Form/Form.tsx b/packages/amis-editor/src/plugin/Form/Form.tsx index 1d28cad37f8..cd6a2560291 100644 --- a/packages/amis-editor/src/plugin/Form/Form.tsx +++ b/packages/amis-editor/src/plugin/Form/Form.tsx @@ -44,6 +44,7 @@ import { } from '../../renderer/event-control/helper'; import {FieldSetting} from '../../renderer/FieldSetting'; import {_isModelComp, generateId} from '../../util'; +import {InlineEditableElement} from 'amis-editor-core'; import type {FormScaffoldConfig} from '../../builder'; @@ -141,6 +142,14 @@ export class FormPlugin extends BasePlugin { } ]; + // 定义可以内联编辑的元素 + inlineEditableElements: Array = [ + { + match: ':scope.cxd-Panel .cxd-Panel-title', + key: 'title' + } + ]; + // 事件定义 events: RendererPluginEvent[] = [ { diff --git a/packages/amis-editor/src/plugin/Form/Item.tsx b/packages/amis-editor/src/plugin/Form/Item.tsx index 7ffe536fffc..21fc0e7ada2 100644 --- a/packages/amis-editor/src/plugin/Form/Item.tsx +++ b/packages/amis-editor/src/plugin/Form/Item.tsx @@ -1,4 +1,7 @@ -import {registerEditorPlugin} from 'amis-editor-core'; +import { + registerEditorPlugin, + RendererInfoResolveEventContext +} from 'amis-editor-core'; import { BasePlugin, BaseEventContext, @@ -29,6 +32,30 @@ export class ItemPlugin extends BasePlugin { order = -990; pluginIcon = 'form-plugin'; + afterResolveEditorInfo(event: PluginEvent) { + if (event.data && event.context.renderer.isFormItem) { + // 给表单项目 label, description 添加快速内联编辑功能 + let inlineEditableElements = + event.data.inlineEditableElements?.concat() || []; + + inlineEditableElements.push( + { + match: '.cxd-Form-label', + key: 'label' + }, + { + match: '.cxd-Form-description', + key: 'description' + } + ); + + event.setData({ + ...event.data, + inlineEditableElements + }); + } + } + buildEditorPanel( context: BuildPanelEventContext, panels: Array diff --git a/packages/amis-editor/src/plugin/Page.tsx b/packages/amis-editor/src/plugin/Page.tsx index 1823071f40e..accbdd62f56 100644 --- a/packages/amis-editor/src/plugin/Page.tsx +++ b/packages/amis-editor/src/plugin/Page.tsx @@ -16,6 +16,7 @@ import {tipedLabel} from 'amis-editor-core'; import {jsonToJsonSchema, EditorNodeType} from 'amis-editor-core'; import omit from 'lodash/omit'; import {generateId} from '../util'; +import {InlineEditableElement} from 'amis-editor-core'; export class PagePlugin extends BasePlugin { static id = 'PagePlugin'; @@ -132,6 +133,19 @@ export class PagePlugin extends BasePlugin { {key: 'aside', label: '边栏', placeholder: '边栏内容'}, {key: 'body', label: '内容区', placeholder: '页面内容'} ]; + + // 定义可以内联编辑的元素 + inlineEditableElements: Array = [ + { + match: '.cxd-Page-title', + key: 'title' + }, + { + match: '.cxd-Page-subTitle', + key: 'subTitle' + } + ]; + wrapper = ContainerWrapper; panelTitle = '页面'; diff --git a/packages/amis-editor/src/plugin/Panel.tsx b/packages/amis-editor/src/plugin/Panel.tsx index 5a0f8533e08..be42ab5cfce 100644 --- a/packages/amis-editor/src/plugin/Panel.tsx +++ b/packages/amis-editor/src/plugin/Panel.tsx @@ -12,6 +12,7 @@ import { registerEditorPlugin, PluginInterface } from 'amis-editor-core'; +import {InlineEditableElement} from 'amis-editor-core'; export class PanelPlugin extends BasePlugin { static id = 'PanelPlugin'; @@ -81,6 +82,14 @@ export class PanelPlugin extends BasePlugin { } ]; + // 定义可以内联编辑的元素 + inlineEditableElements: Array = [ + { + match: ':scope.cxd-Panel .cxd-Panel-title', + key: 'title' + } + ]; + panelTitle = '面板'; panelJustify = true; diff --git a/packages/amis-editor/src/plugin/Tpl.tsx b/packages/amis-editor/src/plugin/Tpl.tsx index dc024f65a1e..ad3dfc0d900 100644 --- a/packages/amis-editor/src/plugin/Tpl.tsx +++ b/packages/amis-editor/src/plugin/Tpl.tsx @@ -8,6 +8,7 @@ import {defaultValue, getSchemaTpl, setSchemaTpl} from 'amis-editor-core'; import {tipedLabel} from 'amis-editor-core'; import {getEventControlConfig} from '../renderer/event-control/helper'; import {ValidatorTag} from '../validator'; +import {InlineEditableElement} from 'amis-editor-core'; setSchemaTpl( 'tpl:content', @@ -132,6 +133,15 @@ export class TplPlugin extends BasePlugin { wrapperComponent: '' }; + // 定义可以内联编辑的元素 + inlineEditableElements: Array = [ + { + match: ':scope > *', + key: 'tpl', + mode: 'rich-text' + } + ]; + panelTitle = '文字'; panelJustify = true; diff --git a/packages/amis-ui/package.json b/packages/amis-ui/package.json index 101a7d96b58..f710b9a4eb2 100644 --- a/packages/amis-ui/package.json +++ b/packages/amis-ui/package.json @@ -43,7 +43,7 @@ "codemirror": "^5.63.0", "downshift": "6.1.12", "echarts": "5.5.1", - "froala-editor": "3.1.1", + "froala-editor": "3.2.7", "hoist-non-react-statics": "^3.3.2", "jsbarcode": "^3.11.5", "keycode": "^2.2.1", @@ -148,4 +148,4 @@ ] }, "gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4" -} \ No newline at end of file +} diff --git a/packages/amis-ui/src/components/RichText.tsx b/packages/amis-ui/src/components/RichText.tsx index 29d7074ce97..3ff8c5f1814 100644 --- a/packages/amis-ui/src/components/RichText.tsx +++ b/packages/amis-ui/src/components/RichText.tsx @@ -319,3 +319,5 @@ export default class extends React.Component { ); } } + +export {FroalaEditor}; diff --git a/packages/amis-ui/src/components/Tinymce.tsx b/packages/amis-ui/src/components/Tinymce.tsx index edadf0ec316..1c9efa32928 100644 --- a/packages/amis-ui/src/components/Tinymce.tsx +++ b/packages/amis-ui/src/components/Tinymce.tsx @@ -697,3 +697,5 @@ tinymce.addI18n('zh_CN', { 'Caption': '\u6807\u9898', 'Insert template': '\u63d2\u5165\u6a21\u677f' }); + +export {tinymce};