From 8da00f1ba974e2ae7248263a691467d3e9ae75ef Mon Sep 17 00:00:00 2001 From: yanmao <55792257+yanmao-cc@users.noreply.github.com> Date: Thu, 25 Nov 2021 19:11:52 +0800 Subject: [PATCH] fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 表格中不能导出未渲染卡片的html - 优化标记插件协同交互 - 协同交互影响了当前编辑者的光标 - 浏览器会响应默认快捷键命令与当前绑定的快捷键会重复执行命令 --- examples/react/components/editor/config.tsx | 5 +- packages/engine/src/card/index.ts | 26 +++-- packages/engine/src/change/event.ts | 8 +- packages/engine/src/change/range.ts | 4 +- packages/engine/src/index.ts | 3 +- packages/engine/src/node/index.ts | 4 +- packages/engine/src/ot/consumer.ts | 2 +- packages/engine/src/parser/index.ts | 7 +- packages/engine/src/types/card.ts | 9 +- packages/engine/src/types/change.ts | 3 +- packages/engine/src/types/engine.ts | 6 +- plugins/codeblock-vue/src/index.ts | 106 ++++++++++---------- plugins/codeblock/src/index.ts | 106 ++++++++++---------- plugins/file/src/index.ts | 10 +- plugins/image/src/component/image/index.ts | 5 + plugins/image/src/index.ts | 71 +++++++------ plugins/image/src/uploader.ts | 18 +++- plugins/mark-range/src/index.ts | 35 ++++++- plugins/math/src/index.ts | 15 ++- plugins/mention/src/index.ts | 41 ++++---- plugins/table/src/index.ts | 98 +++++++++--------- plugins/video/src/index.ts | 60 +++++------ 22 files changed, 365 insertions(+), 277 deletions(-) diff --git a/examples/react/components/editor/config.tsx b/examples/react/components/editor/config.tsx index 0c33cec1..6a5d3cd7 100644 --- a/examples/react/components/editor/config.tsx +++ b/examples/react/components/editor/config.tsx @@ -126,7 +126,7 @@ export const pluginConfig: { [key: string]: PluginOptions } = { }, [Image.pluginName]: { onBeforeRender: (status: string, url: string) => { - if (!url) return url; + if (!url || url.indexOf('http') === 0) return url; return url + `?token=12323`; }, }, @@ -138,7 +138,8 @@ export const pluginConfig: { [key: string]: PluginOptions } = { remote: { action: `${DOMAIN}/upload/image`, }, - isRemote: (src: string) => src.indexOf(DOMAIN) < 0, + isRemote: (src: string) => + src.indexOf(DOMAIN) < 0 && src.indexOf('192.168') < 0, }, [FileUploader.pluginName]: { action: `${DOMAIN}/upload/file`, diff --git a/packages/engine/src/card/index.ts b/packages/engine/src/card/index.ts index d9f14182..b8451786 100644 --- a/packages/engine/src/card/index.ts +++ b/packages/engine/src/card/index.ts @@ -627,16 +627,20 @@ class CardModel implements CardModelInterface { /** * 渲染 * @param container 需要重新渲染包含卡片的节点,如果不传,则渲染全部待创建的卡片节点 - * @param options 是否异步渲染, 全部异步渲染完成后触发 + * @param callback 渲染完成后回调 + * @param lazyRender 是否懒渲染,默认取决于editor的lazyRender属性 */ - render(container?: NodeInterface, callback?: (count: number) => void) { + render( + container?: NodeInterface, + callback?: (count: number) => void, + lazyRender = this.lazyRender, + ) { const cards = container ? container.isCard() ? container : container.find(`${READY_CARD_SELECTOR}`) : this.editor.container.find(READY_CARD_SELECTOR); this.gc(); - let setp = 0; const asyncRenderCards: Array = []; cards.each((node) => { @@ -686,8 +690,8 @@ class CardModel implements CardModelInterface { } }); - asyncRenderCards.forEach(async (card) => { - if (this.lazyRender && (card.constructor as CardEntry).lazyRender) { + asyncRenderCards.forEach((card) => { + if (lazyRender && (card.constructor as CardEntry).lazyRender) { if (card.beforeRender) { const result = card.beforeRender(); const center = card.getCenter(); @@ -702,15 +706,9 @@ class CardModel implements CardModelInterface { } else { this.renderComponent(card); } - setp++; - if (setp === asyncRenderCards.length) { - if (callback) callback(asyncRenderCards.length); - } }); - if (asyncRenderCards.length === 0) { - if (callback) callback(0); - } - if (asyncRenderCards.length > 0) { + if (callback) callback(asyncRenderCards.length); + if (this.asyncComponents.length > 0) { // 触发当前在视图内的卡片渲染 this.renderAsnycComponents(); } @@ -718,7 +716,7 @@ class CardModel implements CardModelInterface { renderComponent(card: CardInterface, ...args: any) { const center = card.getCenter(); - const result = card.render(); + const result = card.render(...args); if (result !== undefined) { center.append(typeof result === 'string' ? $(result) : result); } diff --git a/packages/engine/src/change/event.ts b/packages/engine/src/change/event.ts index 56cf745a..13c99ae4 100644 --- a/packages/engine/src/change/event.ts +++ b/packages/engine/src/change/event.ts @@ -159,7 +159,13 @@ class ChangeEvent implements ChangeEventInterface { const commandName = inputType .substring(type.length) .toLowerCase(); - this.engine.command.execute(commandName); + if (this.engine.command.queryEnabled(commandName)) { + this.engine.hotkey.disable(); + this.engine.command.execute(commandName); + setTimeout(() => { + this.engine.hotkey.enable(); + }, 0); + } } }); }); diff --git a/packages/engine/src/change/range.ts b/packages/engine/src/change/range.ts index 3a5f401d..e4748497 100644 --- a/packages/engine/src/change/range.ts +++ b/packages/engine/src/change/range.ts @@ -133,7 +133,7 @@ class ChangeRange implements ChangeRangeInterface { return range; } - select(range: RangeInterface) { + select(range: RangeInterface, triggerSelect: boolean = true) { const { container, inline, node, change } = this.engine; const { window } = container; const selection = window?.getSelection(); @@ -321,7 +321,7 @@ class ChangeRange implements ChangeRangeInterface { selection.addRange(range.toRange()); } const { onSelect } = this.#otpions; - if (onSelect) onSelect(range); + if (onSelect && triggerSelect) onSelect(range); } /** diff --git a/packages/engine/src/index.ts b/packages/engine/src/index.ts index 09920c9d..1cd2cb5c 100644 --- a/packages/engine/src/index.ts +++ b/packages/engine/src/index.ts @@ -26,7 +26,7 @@ import Request, { } from './request'; import Scrollbar from './scrollbar'; import Position from './position'; -import { $, getHashId } from './node'; +import { $, getHashId, uuid } from './node'; export * from './types'; export * from './utils'; @@ -39,6 +39,7 @@ export default Engine; export { $, + uuid, getHashId, Selection, Range, diff --git a/packages/engine/src/node/index.ts b/packages/engine/src/node/index.ts index f779a9e8..d4ea0c89 100644 --- a/packages/engine/src/node/index.ts +++ b/packages/engine/src/node/index.ts @@ -23,7 +23,7 @@ import { } from '../constants'; import { getDocument, getStyleMap, isEngine } from '../utils'; import $ from './query'; -import getHashId from './hash'; +import getHashId, { uuid } from './hash'; import { isNode, isNodeEntry } from './utils'; class NodeModel implements NodeModelInterface { @@ -1027,4 +1027,4 @@ class NodeModel implements NodeModelInterface { export default NodeModel; -export { Entry as NodeEntry, Event, $, getHashId }; +export { Entry as NodeEntry, Event, $, getHashId, uuid }; diff --git a/packages/engine/src/ot/consumer.ts b/packages/engine/src/ot/consumer.ts index 7b084d06..2b497068 100644 --- a/packages/engine/src/ot/consumer.ts +++ b/packages/engine/src/ot/consumer.ts @@ -453,7 +453,7 @@ class Consumer implements ConsumerInterface { if (endInfo && endInfo.container) { range.setEnd(endInfo.container, endInfo.offset); } - this.engine.change.range.select(range); + this.engine.change.range.select(range, false); } catch (error) { console.error(error); } diff --git a/packages/engine/src/parser/index.ts b/packages/engine/src/parser/index.ts index de465579..d5105bd3 100644 --- a/packages/engine/src/parser/index.ts +++ b/packages/engine/src/parser/index.ts @@ -9,12 +9,7 @@ import { ConversionRule, SchemaRule, } from '../types'; -import { - CARD_ELEMENT_KEY, - CARD_KEY, - CARD_SELECTOR, - READY_CARD_KEY, -} from '../constants'; +import { CARD_ELEMENT_KEY, CARD_SELECTOR } from '../constants'; import { escape, unescape, diff --git a/packages/engine/src/types/card.ts b/packages/engine/src/types/card.ts index 1fbf3738..f9a56426 100644 --- a/packages/engine/src/types/card.ts +++ b/packages/engine/src/types/card.ts @@ -530,9 +530,14 @@ export interface CardModelInterface { /** * 渲染 * @param container 需要重新渲染包含卡片的节点,如果不传,则渲染全部待创建的卡片节点 - * @param callback 全部异步渲染完成后触发 + * @param callback 渲染完成后回调 + * @param lazyRender 是否懒渲染,默认取决于editor的lazyRender属性 */ - render(container?: NodeInterface, callback?: (count: number) => void): void; + render( + container?: NodeInterface, + callback?: (count: number) => void, + lazyRender?: boolean, + ): void; /** * 重新渲染卡片 * @param cards 卡片集合 diff --git a/packages/engine/src/types/change.ts b/packages/engine/src/types/change.ts index 999d507b..1c1f7658 100644 --- a/packages/engine/src/types/change.ts +++ b/packages/engine/src/types/change.ts @@ -137,8 +137,9 @@ export interface ChangeRangeInterface { /** * 选中指定的范围 * @param range 光标 + * @param triggerSelect 时候触发onSelect事件 */ - select(range: RangeInterface): void; + select(range: RangeInterface, triggerSelect?: boolean): void; /** * 聚焦编辑器 * @param toStart true:开始位置,false:结束位置,默认为之前操作位置 diff --git a/packages/engine/src/types/engine.ts b/packages/engine/src/types/engine.ts index 79f09d0e..bafbcb81 100644 --- a/packages/engine/src/types/engine.ts +++ b/packages/engine/src/types/engine.ts @@ -23,7 +23,7 @@ import { BlockModelInterface } from './block'; import { RequestInterface } from './request'; import { RangeInterface } from './range'; import { Op } from 'sharedb'; -import { NodeIdInterface } from './'; +import { HotkeyInterface, NodeIdInterface } from './'; /** * 编辑器容器接口 @@ -508,6 +508,10 @@ export interface EngineInterface extends EditorInterface { * 历史记录 */ history: HistoryInterface; + /** + * 快捷键 + */ + hotkey: HotkeyInterface; /** * 聚焦到编辑器 */ diff --git a/plugins/codeblock-vue/src/index.ts b/plugins/codeblock-vue/src/index.ts index 073eea1e..71c5e3c8 100644 --- a/plugins/codeblock-vue/src/index.ts +++ b/plugins/codeblock-vue/src/index.ts @@ -12,6 +12,8 @@ import { unescape, CARD_TYPE_KEY, PluginOptions, + READY_CARD_KEY, + decodeCardValue, } from '@aomao/engine'; import CodeBlockComponent, { CodeBlockEditor } from './component'; @@ -298,62 +300,60 @@ export default class extends Plugin { parseHtml(root: NodeInterface) { if (isServer) return; - root.find(`[${CARD_KEY}=${CodeBlockComponent.cardName}`).each( - (cardNode) => { - const node = $(cardNode); - const card = this.editor.card.find(node) as CodeBlockComponent; - const value = card?.getValue(); - if (value && value.code) { - node.empty(); - const synatxMap: { [key: string]: string } = {}; - CodeBlockComponent.getModes().forEach((item) => { - synatxMap[item.value] = item.syntax; - }); - const codeEditor = new CodeBlockEditor(this.editor, { - synatxMap, - }); + root.find( + `[${CARD_KEY}="${CodeBlockComponent.cardName}"],[${READY_CARD_KEY}="${CodeBlockComponent.cardName}]"`, + ).each((cardNode) => { + const node = $(cardNode); + const card = this.editor.card.find(node) as CodeBlockComponent; + const value = + card?.getValue() || + decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (value && value.code) { + node.empty(); + const synatxMap: { [key: string]: string } = {}; + CodeBlockComponent.getModes().forEach((item) => { + synatxMap[item.value] = item.syntax; + }); + const codeEditor = new CodeBlockEditor(this.editor, { + synatxMap, + }); - const content = codeEditor.container.find( - '.data-codeblock-content', - ); - content.css({ - border: '1px solid #e8e8e8', - 'max-width': '750px', - }); - codeEditor.render(value.mode || 'plain', value.code || ''); - content.addClass('am-engine-view'); - content.hide(); - document.body.appendChild(content[0]); - content.traverse((node) => { - if ( - node.type === Node.ELEMENT_NODE && - (node.get()?.classList?.length || 0) > - 0 - ) { - const element = node.get()!; - const style = window.getComputedStyle(element); - [ - 'color', - 'margin', - 'padding', - 'background', - ].forEach((attr) => { + const content = codeEditor.container.find( + '.data-codeblock-content', + ); + content.css({ + border: '1px solid #e8e8e8', + 'max-width': '750px', + }); + codeEditor.render(value.mode || 'plain', value.code || ''); + content.addClass('am-engine-view'); + content.hide(); + document.body.appendChild(content[0]); + content.traverse((node) => { + if ( + node.type === Node.ELEMENT_NODE && + (node.get()?.classList?.length || 0) > 0 + ) { + const element = node.get()!; + const style = window.getComputedStyle(element); + ['color', 'margin', 'padding', 'background'].forEach( + (attr) => { (element.style as any)[attr] = style.getPropertyValue(attr); - }); - } - }); - content.show(); - content.css('background', '#f9f9f9'); - node.append(content); - node.removeAttributes(CARD_KEY); - node.removeAttributes(CARD_TYPE_KEY); - node.removeAttributes(CARD_VALUE_KEY); - node.attributes('data-syntax', value.mode || 'plain'); - content.removeClass('am-engine-view'); - } else node.remove(); - }, - ); + }, + ); + } + }); + content.show(); + content.css('background', '#f9f9f9'); + node.append(content); + node.removeAttributes(CARD_KEY); + node.removeAttributes(CARD_TYPE_KEY); + node.removeAttributes(CARD_VALUE_KEY); + node.attributes('data-syntax', value.mode || 'plain'); + content.removeClass('am-engine-view'); + } else node.remove(); + }); } } export { CodeBlockComponent }; diff --git a/plugins/codeblock/src/index.ts b/plugins/codeblock/src/index.ts index e5acdfcd..56aaf272 100644 --- a/plugins/codeblock/src/index.ts +++ b/plugins/codeblock/src/index.ts @@ -12,6 +12,8 @@ import { unescape, CARD_TYPE_KEY, PluginOptions, + READY_CARD_KEY, + decodeCardValue, } from '@aomao/engine'; import CodeBlockComponent, { CodeBlockEditor } from './component'; @@ -299,62 +301,60 @@ export default class extends Plugin { parseHtml(root: NodeInterface) { if (isServer) return; - root.find(`[${CARD_KEY}=${CodeBlockComponent.cardName}`).each( - (cardNode) => { - const node = $(cardNode); - const card = this.editor.card.find(node) as CodeBlockComponent; - const value = card?.getValue(); - if (value) { - node.empty(); - const synatxMap = {}; - CodeBlockComponent.getModes().forEach((item) => { - synatxMap[item.value] = item.syntax; - }); - const codeEditor = new CodeBlockEditor(this.editor, { - synatxMap, - }); + root.find( + `[${CARD_KEY}="${CodeBlockComponent.cardName}"],[${READY_CARD_KEY}="${CodeBlockComponent.cardName}"]`, + ).each((cardNode) => { + const node = $(cardNode); + const card = this.editor.card.find(node) as CodeBlockComponent; + const value = + card?.getValue() || + decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (value) { + node.empty(); + const synatxMap = {}; + CodeBlockComponent.getModes().forEach((item) => { + synatxMap[item.value] = item.syntax; + }); + const codeEditor = new CodeBlockEditor(this.editor, { + synatxMap, + }); - const content = codeEditor.container.find( - '.data-codeblock-content', - ); - content.css({ - border: '1px solid #e8e8e8', - 'max-width': '750px', - }); - codeEditor.render(value.mode || 'plain', value.code || ''); - content.addClass('am-engine-view'); - content.hide(); - document.body.appendChild(content[0]); - content.traverse((node) => { - if ( - node.type === Node.ELEMENT_NODE && - (node.get()?.classList?.length || 0) > - 0 - ) { - const element = node.get()!; - const style = window.getComputedStyle(element); - [ - 'color', - 'margin', - 'padding', - 'background', - ].forEach((attr) => { + const content = codeEditor.container.find( + '.data-codeblock-content', + ); + content.css({ + border: '1px solid #e8e8e8', + 'max-width': '750px', + }); + codeEditor.render(value.mode || 'plain', value.code || ''); + content.addClass('am-engine-view'); + content.hide(); + document.body.appendChild(content[0]); + content.traverse((node) => { + if ( + node.type === Node.ELEMENT_NODE && + (node.get()?.classList?.length || 0) > 0 + ) { + const element = node.get()!; + const style = window.getComputedStyle(element); + ['color', 'margin', 'padding', 'background'].forEach( + (attr) => { element.style[attr] = style.getPropertyValue(attr); - }); - } - }); - content.show(); - content.css('background', '#f9f9f9'); - node.append(content); - node.removeAttributes(CARD_KEY); - node.removeAttributes(CARD_TYPE_KEY); - node.removeAttributes(CARD_VALUE_KEY); - node.attributes('data-syntax', value.mode || 'plain'); - content.removeClass('am-engine-view'); - } else node.remove(); - }, - ); + }, + ); + } + }); + content.show(); + content.css('background', '#f9f9f9'); + node.append(content); + node.removeAttributes(CARD_KEY); + node.removeAttributes(CARD_TYPE_KEY); + node.removeAttributes(CARD_VALUE_KEY); + node.attributes('data-syntax', value.mode || 'plain'); + content.removeClass('am-engine-view'); + } else node.remove(); + }); } } export { CodeBlockComponent }; diff --git a/plugins/file/src/index.ts b/plugins/file/src/index.ts index a55c112e..d816d795 100644 --- a/plugins/file/src/index.ts +++ b/plugins/file/src/index.ts @@ -3,12 +3,14 @@ import { CardEntry, CardInterface, CARD_KEY, + CARD_VALUE_KEY, decodeCardValue, encodeCardValue, isEngine, NodeInterface, Plugin, PluginEntry, + READY_CARD_KEY, SchemaInterface, } from '@aomao/engine'; import FileComponent, { FileValue } from './component'; @@ -154,10 +156,14 @@ export default class extends Plugin { } parseHtml(root: NodeInterface) { - root.find(`[${CARD_KEY}=${FileComponent.cardName}`).each((cardNode) => { + root.find( + `[${CARD_KEY}="${FileComponent.cardName}"],[${READY_CARD_KEY}="${FileComponent.cardName}"`, + ).each((cardNode) => { const node = $(cardNode); const card = this.editor.card.find(node) as FileComponent; - const value = card?.getValue(); + const value = + card?.getValue() || + decodeCardValue(node.attributes(CARD_VALUE_KEY)); if (value?.url && value.status === 'done') { const html = ` { + const node = $(cardNode); + const card = this.editor.card.find(node) as ImageComponent; + const value = + card?.getValue() || + decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (value?.src && value.status === 'done') { + let img = $(''); + node.empty(); + let src = value.src; + const { onBeforeRender } = this.options; + if (onBeforeRender) { + src = onBeforeRender(value.status, value.src); + } + const type = node.attributes(CARD_TYPE_KEY); + img.attributes('src', src); + img.css('visibility', 'visible'); + const size = value.size; + if (size.width) img.css('width', `${size.width}px`); + if (size.height) img.css('height', `${size.height}px`); + img.removeAttributes('class'); + img.attributes('data-type', type); + if (img.length > 0) { + if (type === CardType.BLOCK) { + img = this.editor.node.wrap( + img, + $(`

`), + ); } - img.attributes('src', src); - img.css('visibility', 'visible'); - img.css('background', ''); - img.css('background-color', ''); - img.css('background-repeat', ''); - img.css('background-position', ''); - img.css('background-image', ''); - img.removeAttributes('class'); - - if (img.length > 0) { - node.replaceWith(img); - if (card.type === CardType.BLOCK) { - this.editor.node.wrap( - img, - $(`

`), - ); - } - } - } else node.remove(); - }, - ); + node.replaceWith(img); + } + } else node.remove(); + }); } } diff --git a/plugins/image/src/uploader.ts b/plugins/image/src/uploader.ts index c442be1c..9d000ce5 100644 --- a/plugins/image/src/uploader.ts +++ b/plugins/image/src/uploader.ts @@ -14,6 +14,7 @@ import { decodeCardValue, encodeCardValue, removeUnit, + CardType, } from '@aomao/engine'; import ImageComponent, { ImageValue } from './component'; @@ -476,15 +477,28 @@ export default class extends Plugin { } */ //图片 if (node.name === 'img') { - const src = node.attributes('src') || node.attributes('data-src'); - const alt = node.attributes('alt'); + const attributes = node.attributes(); + const src = attributes['src'] || attributes['data-src']; + const alt = attributes['alt']; if (!src) { node.remove(); return; } const width = node.css('width'); const height = node.css('height'); + const dataTypeValue = attributes['data-type']; + let type = CardType.INLINE; + if (dataTypeValue === 'block') { + const parent = node.parent(); + // 移除转换为html的时候加载的额外p标签 + if (parent && parent.name === 'p') { + this.editor.node.unwrap(node); + } + type = CardType.BLOCK; + } + this.editor.card.replaceNode(node, 'image', { + type, src, status: (isRemote && isRemote(src)) || /^data:image\//i.test(src) diff --git a/plugins/mark-range/src/index.ts b/plugins/mark-range/src/index.ts index 5961af91..9dcca6fe 100644 --- a/plugins/mark-range/src/index.ts +++ b/plugins/mark-range/src/index.ts @@ -12,6 +12,7 @@ import { SchemaMark, Selection, PluginOptions, + uuid, } from '@aomao/engine'; import { Path } from 'sharedb'; @@ -26,7 +27,9 @@ export default class extends MarkPlugin { private range?: RangeInterface; private executeBySelf: boolean = false; private MARK_KEY = `data-mark-key`; + private MARK_UUID = `data-mark-uuid`; private ids: { [key: string]: Array } = {}; + private m_uuid = uuid(18, 24); readonly followStyle: boolean = false; @@ -84,8 +87,8 @@ export default class extends MarkPlugin { if (isEngine(this.editor)) { const { change } = this.editor; - this.editor.on('change', () => { - this.triggerChange(); + this.editor.on('change', (_, trigger) => { + this.triggerChange(trigger !== 'local'); }); this.editor.on('select', () => this.onSelectionChange()); this.editor.on('parse:value', (node, atts) => { @@ -141,6 +144,7 @@ export default class extends MarkPlugin { required: true, value: key, }, + [this.MARK_UUID]: '*', [this.getIdName(key)]: '*', }, }; @@ -277,7 +281,9 @@ export default class extends MarkPlugin { this.MARK_KEY }="${key}" ${DATA_TRANSIENT_ATTRIBUTES}="${this.getPreviewName( key, - )}" ${this.getPreviewName(key)}="true" />`, + )}" ${this.MARK_UUID}="${this.m_uuid}" ${this.getPreviewName( + key, + )}="true" />`, range, ); //遍历当前光标选择节点,拼接选择的文本 @@ -478,7 +484,6 @@ export default class extends MarkPlugin { execute() {} action(key: string, action: string, ...args: any): any { - const history = isEngine(this.editor) ? this.editor.history : undefined; const id = args[0]; switch (action) { case 'preview': @@ -552,10 +557,11 @@ export default class extends MarkPlugin { this.range = range; } - triggerChange() { + triggerChange(remote: boolean = false) { const addIds: { [key: string]: Array } = {}; const removeIds: { [key: string]: Array } = {}; const ids = this.getIds(); + this.options.keys.forEach((key) => { const prevIds = this.ids[key] || []; const curIds = ids[key] || []; @@ -572,6 +578,25 @@ export default class extends MarkPlugin { } }); }); + if (remote) { + const currentElements = this.editor.container.find( + `[${this.MARK_UUID}="${this.m_uuid}"]`, + ); + currentElements.each((_, index) => { + const child = currentElements.eq(index); + const attributes = child?.attributes() || {}; + const key = attributes[this.MARK_KEY]; + // 如果这个元素没有被标记,并且没有创建、没有预览选项就增加预览样式 + const previewName = this.getPreviewName(key); + if ( + key && + !attributes[this.getIdName(key)] && + !attributes[previewName] + ) { + child!.attributes(previewName, 'true'); + } + }); + } this.ids = ids; this.editor.trigger(`${PLUGIN_NAME}:change`, addIds, removeIds, ids); } diff --git a/plugins/math/src/index.ts b/plugins/math/src/index.ts index d30f00c4..b4a49cce 100644 --- a/plugins/math/src/index.ts +++ b/plugins/math/src/index.ts @@ -12,6 +12,8 @@ import { decodeCardValue, encodeCardValue, AjaxInterface, + READY_CARD_KEY, + CARD_VALUE_KEY, } from '@aomao/engine'; import MathComponent from './component'; import locales from './locales'; @@ -241,9 +243,10 @@ export default class Math extends Plugin { pasteHtml(node: NodeInterface) { if (!isEngine(this.editor)) return; if (node.isElement()) { - const type = node.attributes('data-type'); + const attributes = node.attributes(); + const type = attributes['data-type']; if (type === MathComponent.cardName) { - const value = node.attributes('data-value'); + const value = attributes['data-value']; const cardValue = decodeCardValue(value); if (!cardValue.url) return; this.editor.card.replaceNode( @@ -259,10 +262,14 @@ export default class Math extends Plugin { } parseHtml(root: NodeInterface) { - root.find(`[${CARD_KEY}=${MathComponent.cardName}`).each((cardNode) => { + root.find( + `[${CARD_KEY}="${MathComponent.cardName}"],[${READY_CARD_KEY}="${MathComponent.cardName}"]`, + ).each((cardNode) => { const node = $(cardNode); const card = this.editor.card.find(node) as MathComponent; - const value = card?.getValue(); + const value = + card?.getValue() || + decodeCardValue(node.attributes(CARD_VALUE_KEY)); if (value) { const img = node.find('img'); node.empty(); diff --git a/plugins/mention/src/index.ts b/plugins/mention/src/index.ts index ada469a7..a9004256 100644 --- a/plugins/mention/src/index.ts +++ b/plugins/mention/src/index.ts @@ -14,6 +14,8 @@ import { encodeCardValue, CardInterface, AjaxInterface, + READY_CARD_KEY, + CARD_VALUE_KEY, } from '@aomao/engine'; import MentionComponent from './component'; import locales from './locales'; @@ -229,9 +231,10 @@ class MentionPlugin extends Plugin { pasteHtml(node: NodeInterface) { if (!isEngine(this.editor)) return; if (node.isElement()) { - const type = node.attributes('data-type'); + const attributes = node.attributes(); + const type = attributes['data-type']; if (type === MentionComponent.cardName) { - const value = node.attributes('data-value'); + const value = attributes['data-value']; const cardValue = decodeCardValue(value); if (!cardValue.name) return; this.editor.card.replaceNode( @@ -247,22 +250,24 @@ class MentionPlugin extends Plugin { } parseHtml(root: NodeInterface) { - root.find(`[${CARD_KEY}=${MentionComponent.cardName}`).each( - (cardNode) => { - const node = $(cardNode); - const card = this.editor.card.find(node) as MentionComponent; - const value = card?.getValue(); - if (value?.id && value.name) { - const html = `@${value.name}`; - node.empty(); - node.replaceWith($(html)); - } else node.remove(); - }, - ); + root.find( + `[${CARD_KEY}="${MentionComponent.cardName}"],[${READY_CARD_KEY}="${MentionComponent.cardName}"]`, + ).each((cardNode) => { + const node = $(cardNode); + const card = this.editor.card.find(node) as MentionComponent; + const value = + card?.getValue() || + decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (value?.id && value.name) { + const html = `@${value.name}`; + node.empty(); + node.replaceWith($(html)); + } else node.remove(); + }); } execute() {} diff --git a/plugins/table/src/index.ts b/plugins/table/src/index.ts index fa0950fc..86371ca5 100644 --- a/plugins/table/src/index.ts +++ b/plugins/table/src/index.ts @@ -9,6 +9,10 @@ import { PluginOptions, SchemaInterface, getDocument, + Parser, + READY_CARD_KEY, + decodeCardValue, + CARD_VALUE_KEY, } from '@aomao/engine'; import TableComponent, { Template } from './component'; import locales from './locale'; @@ -355,55 +359,57 @@ class Table extends Plugin { } parseHtml(root: NodeInterface) { - root.find(`[${CARD_KEY}=${TableComponent.cardName}`).each( - (tableNode) => { - const node = $(tableNode); - const card = this.editor.card.find(node) as TableComponent; - const value = card?.getValue(); - if (value && value.html) { - let table = node.find('table'); + root.find( + `[${CARD_KEY}="${TableComponent.cardName}"],[${READY_CARD_KEY}="${TableComponent.cardName}"]`, + ).each((tableNode) => { + const node = $(tableNode); + const card = this.editor.card.find(node) as TableComponent; + const value = + card?.getValue() || + decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (value && value.html) { + let table = node.find('table'); + if (table.length === 0) { + // 表格值里面的卡片都是没有被转换过的,所以需要先把卡片转换过来 + table = $(value.html); if (table.length === 0) { - table = $(value.html); - if (table.length === 0) { - node.remove(); - return; - } + node.remove(); + return; + } else { + table = $(new Parser(table, this.editor).toHTML()); } - const width = - table.attributes('width') || table.css('width'); - table.css({ - outline: 'none', - 'border-collapse': 'collapse', - width: '100%', - }); - table.attributes('data-width', width); - const tds = table.find('td'); - tds.each((_, index) => { - const tdElement = tds.eq(index); - tdElement?.css({ - 'min-width': 'auto', - 'white-space': 'flat', - 'word-wrap': 'break-word', - margin: '4px 8px', - border: !!table.attributes('data-table-no-border') - ? '0 none' - : '1px solid #d9d9d9', - padding: '4px 8px', - cursor: 'default', - 'vertical-align': - tdElement.css('vertical-align') || 'top', - }); - }); - table.find(Template.TABLE_TD_BG_CLASS).remove(); - table - .find(Template.TABLE_TD_CONTENT_CLASS) - .each((content) => { - this.editor.node.unwrap($(content)); - }); - node.replaceWith(table); } - }, - ); + const width = table.attributes('width') || table.css('width'); + table.css({ + outline: 'none', + 'border-collapse': 'collapse', + width: '100%', + }); + table.attributes('data-width', width); + const tds = table.find('td'); + tds.each((_, index) => { + const tdElement = tds.eq(index); + tdElement?.css({ + 'min-width': 'auto', + 'white-space': 'flat', + 'word-wrap': 'break-word', + margin: '4px 8px', + border: !!table.attributes('data-table-no-border') + ? '0 none' + : '1px solid #d9d9d9', + padding: '4px 8px', + cursor: 'default', + 'vertical-align': + tdElement.css('vertical-align') || 'top', + }); + }); + table.find(Template.TABLE_TD_BG_CLASS).remove(); + table.find(Template.TABLE_TD_CONTENT_CLASS).each((content) => { + this.editor.node.unwrap($(content)); + }); + node.replaceWith(table); + } + }); } getMarkdownCell(match: RegExpExecArray, count?: number) { diff --git a/plugins/video/src/index.ts b/plugins/video/src/index.ts index 50836ba2..770a84e4 100644 --- a/plugins/video/src/index.ts +++ b/plugins/video/src/index.ts @@ -3,12 +3,14 @@ import { CardEntry, CardInterface, CARD_KEY, + CARD_VALUE_KEY, decodeCardValue, encodeCardValue, isEngine, NodeInterface, Plugin, PluginEntry, + READY_CARD_KEY, sanitizeUrl, SchemaInterface, } from '@aomao/engine'; @@ -162,34 +164,36 @@ export default class VideoPlugin extends Plugin<{ } parseHtml(root: NodeInterface) { - root.find(`[${CARD_KEY}=${VideoComponent.cardName}`).each( - (cardNode) => { - const node = $(cardNode); - const card = this.editor.card.find(node) as VideoComponent; - const value = card?.getValue(); - if (value?.url && value.status === 'done') { - const { onBeforeRender } = this.options; - const { cover, url } = value; - const html = `
`; - node.empty(); - node.replaceWith($(html)); - } else node.remove(); - }, - ); + root.find( + `[${CARD_KEY}="${VideoComponent.cardName}"],[${READY_CARD_KEY}="${VideoComponent.cardName}"]`, + ).each((cardNode) => { + const node = $(cardNode); + const card = this.editor.card.find(node) as VideoComponent; + const value = + card?.getValue() || + decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (value?.url && value.status === 'done') { + const { onBeforeRender } = this.options; + const { cover, url } = value; + const html = `
`; + node.empty(); + node.replaceWith($(html)); + } else node.remove(); + }); } }