From 17e454b1e65026cdd804602d2fb076f6b4f1013e Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Sun, 21 Aug 2022 22:58:41 +0800 Subject: [PATCH 1/8] refactor: refactor clipboard --- .../common/src/lib/text/slate-utils.ts | 37 ++++- .../editor-blocks/src/blocks/bullet/index.ts | 9 -- .../editor-blocks/src/blocks/code/index.ts | 9 -- .../src/blocks/numbered/index.ts | 9 -- .../editor-blocks/src/blocks/page/index.ts | 9 -- .../src/blocks/text/QuoteBlock.tsx | 17 --- .../src/blocks/text/TextBlock.tsx | 33 ----- .../editor-blocks/src/blocks/todo/index.ts | 9 -- .../editor-blocks/src/blocks/youtube/index.ts | 4 +- .../src/editor/block/block-helper.ts | 114 +++++++++++++++- .../src/editor/clipboard/browser-clipboard.ts | 128 ----------------- .../editor/clipboard/clipboard-populator.ts | 38 ------ .../src/editor/clipboard/clipboard.ts | 47 +++++++ .../clipboard/clipboardEventDispatcher.ts | 82 +++++++++++ .../src/editor/clipboard/clipboardUtils.ts | 121 ++++++++++++++++ .../editor-core/src/editor/clipboard/copy.ts | 129 +++++++++++++++++- .../editor-core/src/editor/clipboard/index.ts | 0 .../editor-core/src/editor/clipboard/paste.ts | 4 +- .../editor-core/src/editor/clipboard/utils.ts | 8 +- .../editor-core/src/editor/editor.ts | 63 ++++----- .../editor-core/src/editor/index.ts | 2 +- .../editor-core/src/editor/plugin/hooks.ts | 11 +- .../editor-core/src/editor/types.ts | 10 +- .../editor-core/src/editor/views/base-view.ts | 45 +++--- 24 files changed, 592 insertions(+), 346 deletions(-) delete mode 100644 libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts delete mode 100644 libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts create mode 100644 libs/components/editor-core/src/editor/clipboard/clipboard.ts create mode 100644 libs/components/editor-core/src/editor/clipboard/clipboardEventDispatcher.ts create mode 100644 libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts create mode 100644 libs/components/editor-core/src/editor/clipboard/index.ts diff --git a/libs/components/common/src/lib/text/slate-utils.ts b/libs/components/common/src/lib/text/slate-utils.ts index ec7e219810..fb183a7c35 100644 --- a/libs/components/common/src/lib/text/slate-utils.ts +++ b/libs/components/common/src/lib/text/slate-utils.ts @@ -24,6 +24,7 @@ import { MARKDOWN_STYLE_MAP, MatchRes, } from './utils'; +import { AsyncBlock, SelectBlock } from '@toeverything/components/editor-core'; function isInlineAndVoid(editor: Editor, el: any) { return editor.isInline(el) && editor.isVoid(el); @@ -958,7 +959,41 @@ class SlateUtils { } public getNodeByPath(path: Path) { - Editor.node(this.editor, path); + return Editor.node(this.editor, path); + } + + public getNodeByRange(range: Range) { + return Editor.node(this.editor, range); + } + + // This may should write with logic of render slate + public convertLeaf2Html(textValue: any) { + const { text, fontColor, fontBgColor } = textValue; + + const style = `style="${fontColor ? `color: ${fontColor};` : ''}${ + fontBgColor ? `backgroundColor: ${fontBgColor};` : '' + }"`; + if (textValue.bold) { + return `${text}`; + } + if (textValue.italic) { + return `${text}`; + } + if (textValue.underline) { + return `${text}`; + } + if (textValue.inlinecode) { + return `${text}`; + } + if (textValue.strikethrough) { + return `${text}`; + } + if (textValue.type === 'link') { + return `${this.convertLeaf2Html( + textValue.children + )}`; + } + return `${text}>`; } public getStartSelection() { diff --git a/libs/components/editor-blocks/src/blocks/bullet/index.ts b/libs/components/editor-blocks/src/blocks/bullet/index.ts index 087afb3056..58360954ce 100644 --- a/libs/components/editor-blocks/src/blocks/bullet/index.ts +++ b/libs/components/editor-blocks/src/blocks/bullet/index.ts @@ -2,7 +2,6 @@ import { AsyncBlock, BaseView, CreateView, - getTextProperties, SelectBlock, getTextHtml, } from '@toeverything/framework/virgo'; @@ -28,14 +27,6 @@ export class BulletBlock extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] diff --git a/libs/components/editor-blocks/src/blocks/code/index.ts b/libs/components/editor-blocks/src/blocks/code/index.ts index 9b089d6db5..1fc84f1df8 100644 --- a/libs/components/editor-blocks/src/blocks/code/index.ts +++ b/libs/components/editor-blocks/src/blocks/code/index.ts @@ -1,7 +1,6 @@ import { BaseView, AsyncBlock, - getTextProperties, CreateView, SelectBlock, getTextHtml, @@ -28,14 +27,6 @@ export class CodeBlock extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - // TODO: internal format not implemented yet override html2block( el: Element, diff --git a/libs/components/editor-blocks/src/blocks/numbered/index.ts b/libs/components/editor-blocks/src/blocks/numbered/index.ts index b8b3c3dc0a..37295e026f 100644 --- a/libs/components/editor-blocks/src/blocks/numbered/index.ts +++ b/libs/components/editor-blocks/src/blocks/numbered/index.ts @@ -1,7 +1,6 @@ import { AsyncBlock, BaseView, - getTextProperties, SelectBlock, getTextHtml, } from '@toeverything/framework/virgo'; @@ -29,14 +28,6 @@ export class NumberedBlock extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] diff --git a/libs/components/editor-blocks/src/blocks/page/index.ts b/libs/components/editor-blocks/src/blocks/page/index.ts index 412bbdbd7c..06151bc481 100644 --- a/libs/components/editor-blocks/src/blocks/page/index.ts +++ b/libs/components/editor-blocks/src/blocks/page/index.ts @@ -8,7 +8,6 @@ import { BaseView, ChildrenView, getTextHtml, - getTextProperties, SelectBlock, } from '@toeverything/framework/virgo'; @@ -35,14 +34,6 @@ export class PageBlock extends BaseView { return this.get_decoration(content, 'text')?.value?.[0].text; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override async block2html( block: AsyncBlock, children: SelectBlock[], diff --git a/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx b/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx index f62dfae040..94938aa595 100644 --- a/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx +++ b/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx @@ -7,7 +7,6 @@ import { BaseView, CreateView, getTextHtml, - getTextProperties, SelectBlock, } from '@toeverything/framework/virgo'; @@ -29,14 +28,6 @@ export class QuoteBlock extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] @@ -96,14 +87,6 @@ export class CalloutBlock extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] diff --git a/libs/components/editor-blocks/src/blocks/text/TextBlock.tsx b/libs/components/editor-blocks/src/blocks/text/TextBlock.tsx index 64902e252d..74085db922 100644 --- a/libs/components/editor-blocks/src/blocks/text/TextBlock.tsx +++ b/libs/components/editor-blocks/src/blocks/text/TextBlock.tsx @@ -2,7 +2,6 @@ import { BaseView, CreateView, AsyncBlock, - getTextProperties, SelectBlock, getTextHtml, } from '@toeverything/framework/virgo'; @@ -28,14 +27,6 @@ export class TextBlock extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] @@ -145,14 +136,6 @@ export class Heading1Block extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] @@ -210,14 +193,6 @@ export class Heading2Block extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] @@ -275,14 +250,6 @@ export class Heading3Block extends BaseView { return block; } - override getSelProperties( - block: AsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] diff --git a/libs/components/editor-blocks/src/blocks/todo/index.ts b/libs/components/editor-blocks/src/blocks/todo/index.ts index c6580009d0..db4187a890 100644 --- a/libs/components/editor-blocks/src/blocks/todo/index.ts +++ b/libs/components/editor-blocks/src/blocks/todo/index.ts @@ -1,6 +1,5 @@ import { BaseView, - getTextProperties, AsyncBlock, SelectBlock, getTextHtml, @@ -29,14 +28,6 @@ export class TodoBlock extends BaseView { return block; } - override getSelProperties( - block: TodoAsyncBlock, - selectInfo: any - ): DefaultColumnsValue { - const properties = super.getSelProperties(block, selectInfo); - return getTextProperties(properties, selectInfo); - } - override html2block( el: Element, parseEl: (el: Element) => any[] diff --git a/libs/components/editor-blocks/src/blocks/youtube/index.ts b/libs/components/editor-blocks/src/blocks/youtube/index.ts index 42f22bcda4..94ad3ac59d 100644 --- a/libs/components/editor-blocks/src/blocks/youtube/index.ts +++ b/libs/components/editor-blocks/src/blocks/youtube/index.ts @@ -41,7 +41,9 @@ export class YoutubeBlock extends BaseView { return null; } - + override async block2Text(block: AsyncBlock, selectInfo: SelectBlock) { + return block.getProperty('embedLink')?.value ?? ''; + } override async block2html( block: AsyncBlock, children: SelectBlock[], diff --git a/libs/components/editor-core/src/editor/block/block-helper.ts b/libs/components/editor-core/src/editor/block/block-helper.ts index 7bd0284ffc..70238c4be5 100644 --- a/libs/components/editor-core/src/editor/block/block-helper.ts +++ b/libs/components/editor-core/src/editor/block/block-helper.ts @@ -11,6 +11,11 @@ import { Selection as SlateSelection, } from 'slate'; import { Editor } from '../editor'; +import { + AsyncBlock, + SelectBlock, + SelectInfo, +} from '@toeverything/components/editor-core'; type TextUtilsFunctions = | 'getString' @@ -37,7 +42,9 @@ type TextUtilsFunctions = | 'blur' | 'setSelection' | 'insertNodes' - | 'getNodeByPath'; + | 'getNodeByPath' + | 'getNodeByRange' + | 'convertLeaf2Html'; type ExtendedTextUtils = SlateUtils & { setLinkModalVisible: (visible: boolean) => void; @@ -98,15 +105,116 @@ export class BlockHelper { return ''; } - public getBlockTextBetweenSelection(blockId: string) { + public async isBlockEditable(blockOrBlockId: AsyncBlock | string) { + const block = + typeof blockOrBlockId === 'string' + ? await this._editor.getBlockById(blockOrBlockId) + : blockOrBlockId; + const blockView = this._editor.getView(block.type); + + return blockView.activatable; + } + + public async getFlatBlocksUnderParent( + parentBlockId: string, + includeParent: boolean = false + ): Promise { + const blocks = []; + const block = await this._editor.getBlockById(parentBlockId); + if (includeParent) { + blocks.push(block); + } + const children = await block.children(); + ( + await Promise.all( + children.map(child => { + return this.getFlatBlocksUnderParent(child.id, true); + }) + ) + ).forEach(editableChildren => { + blocks.push(...editableChildren); + }); + return blocks; + } + + public getBlockTextBetweenSelection( + blockId: string, + shouldUsePreviousSelection = true + ) { const text_utils = this._blockTextUtilsMap[blockId]; if (text_utils) { - return text_utils.getStringBetweenSelection(true); + return text_utils.getStringBetweenSelection( + shouldUsePreviousSelection + ); } console.warn('Could find the block text utils'); return ''; } + public async getEditableBlockPropertiesBySelectInfo( + block: AsyncBlock, + selectInfo: SelectBlock + ) { + const properties = block.getProperties(); + if (properties.text.value.length === 0) { + return properties; + } + let text_value = properties.text.value; + + const { + text: { value: originTextValue, ...otherTextProperties }, + ...otherProperties + } = properties; + + // Use deepClone method will throw incomprehensible error + let textValue = JSON.parse(JSON.stringify(originTextValue)); + + if (selectInfo.endInfo) { + textValue = textValue.slice(0, selectInfo.endInfo.arrayIndex + 1); + textValue[textValue.length - 1].text = text_value[ + textValue.length - 1 + ].text.substring(0, selectInfo.endInfo.offset); + } + if (selectInfo.startInfo) { + textValue = textValue.slice(selectInfo.startInfo.arrayIndex); + textValue[0].text = textValue[0].text.substring( + selectInfo.startInfo.offset + ); + } + return { + ...otherProperties, + text: { + ...otherTextProperties, + value: textValue, + }, + }; + } + + // For editable blocks, the properties containing the selected text will be returned with the selection information + public async getBlockPropertiesBySelectInfo(selectBlockInfo: SelectBlock) { + const block = await this._editor.getBlockById(selectBlockInfo.blockId); + const blockView = this._editor.getView(block.type); + if (blockView.activatable) { + return this.getEditableBlockPropertiesBySelectInfo( + block, + selectBlockInfo + ); + } else { + return block?.getProperties(); + } + } + + public convertTextValue2Html(blockId: string, textValue: any) { + const text_utils = this._blockTextUtilsMap[blockId]; + if (!text_utils) { + return ''; + } + return textValue.reduce((html: string, textValueItem: any) => { + const fragment = text_utils.convertLeaf2Html(textValueItem); + return `${html}${fragment}`; + }, ''); + } + public setBlockBlur(blockId: string) { const text_utils = this._blockTextUtilsMap[blockId]; if (text_utils) { diff --git a/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts b/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts deleted file mode 100644 index 687c5280ee..0000000000 --- a/libs/components/editor-core/src/editor/clipboard/browser-clipboard.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { HooksRunner } from '../types'; -import { Editor } from '../editor'; -import ClipboardParse from './clipboard-parse'; -import { MarkdownParser } from './markdown-parse'; -import { shouldHandlerContinue } from './utils'; -import { Paste } from './paste'; -// todo needs to be a switch - -enum ClipboardAction { - COPY = 'copy', - CUT = 'cut', - PASTE = 'paste', -} - -//TODO: need to consider the cursor position after inserting the children -class BrowserClipboard { - private _eventTarget: Element; - private _hooks: HooksRunner; - private _editor: Editor; - private _clipboardParse: ClipboardParse; - private _markdownParse: MarkdownParser; - private _paste: Paste; - - constructor(eventTarget: Element, hooks: HooksRunner, editor: Editor) { - this._eventTarget = eventTarget; - this._hooks = hooks; - this._editor = editor; - this._clipboardParse = new ClipboardParse(editor); - this._markdownParse = new MarkdownParser(); - this._paste = new Paste( - editor, - this._clipboardParse, - this._markdownParse - ); - this._initialize(); - } - - public getClipboardParse() { - return this._clipboardParse; - } - - private _initialize() { - this._handleCopy = this._handleCopy.bind(this); - this._handleCut = this._handleCut.bind(this); - - document.addEventListener(ClipboardAction.COPY, this._handleCopy); - document.addEventListener(ClipboardAction.CUT, this._handleCut); - document.addEventListener( - ClipboardAction.PASTE, - this._paste.handlePaste - ); - this._eventTarget.addEventListener( - ClipboardAction.COPY, - this._handleCopy - ); - this._eventTarget.addEventListener( - ClipboardAction.CUT, - this._handleCut - ); - this._eventTarget.addEventListener( - ClipboardAction.PASTE, - this._paste.handlePaste - ); - } - - private _handleCopy(e: Event) { - if (!shouldHandlerContinue(e, this._editor)) { - return; - } - - this._dispatchClipboardEvent(ClipboardAction.COPY, e as ClipboardEvent); - } - - private _handleCut(e: Event) { - if (!shouldHandlerContinue(e, this._editor)) { - return; - } - - this._dispatchClipboardEvent(ClipboardAction.CUT, e as ClipboardEvent); - } - - private _preCopyCut(action: ClipboardAction, e: ClipboardEvent) { - switch (action) { - case ClipboardAction.COPY: - this._hooks.beforeCopy(e); - break; - - case ClipboardAction.CUT: - this._hooks.beforeCut(e); - break; - } - } - - private _dispatchClipboardEvent( - action: ClipboardAction, - e: ClipboardEvent - ) { - this._preCopyCut(action, e); - } - - dispose() { - document.removeEventListener(ClipboardAction.COPY, this._handleCopy); - document.removeEventListener(ClipboardAction.CUT, this._handleCut); - document.removeEventListener( - ClipboardAction.PASTE, - this._paste.handlePaste - ); - this._eventTarget.removeEventListener( - ClipboardAction.COPY, - this._handleCopy - ); - this._eventTarget.removeEventListener( - ClipboardAction.CUT, - this._handleCut - ); - this._eventTarget.removeEventListener( - ClipboardAction.PASTE, - this._paste.handlePaste - ); - this._clipboardParse.dispose(); - this._clipboardParse = null; - this._eventTarget = null; - this._hooks = null; - this._editor = null; - } -} - -export { BrowserClipboard }; diff --git a/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts b/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts deleted file mode 100644 index d7a8172566..0000000000 --- a/libs/components/editor-core/src/editor/clipboard/clipboard-populator.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Editor } from '../editor'; -import { SelectionManager } from '../selection'; -import { HookType, PluginHooks } from '../types'; -import ClipboardParse from './clipboard-parse'; -import { Subscription } from 'rxjs'; -import { Copy } from './copy'; -class ClipboardPopulator { - private _editor: Editor; - private _hooks: PluginHooks; - private _selectionManager: SelectionManager; - private _clipboardParse: ClipboardParse; - private _sub = new Subscription(); - private _copy: Copy; - constructor( - editor: Editor, - hooks: PluginHooks, - selectionManager: SelectionManager - ) { - this._editor = editor; - this._hooks = hooks; - this._selectionManager = selectionManager; - this._clipboardParse = new ClipboardParse(editor); - this._copy = new Copy(editor); - this._sub.add( - hooks.get(HookType.BEFORE_COPY).subscribe(this._copy.handleCopy) - ); - this._sub.add( - hooks.get(HookType.BEFORE_CUT).subscribe(this._copy.handleCopy) - ); - } - - disposeInternal() { - this._sub.unsubscribe(); - this._hooks = null; - } -} - -export { ClipboardPopulator }; diff --git a/libs/components/editor-core/src/editor/clipboard/clipboard.ts b/libs/components/editor-core/src/editor/clipboard/clipboard.ts new file mode 100644 index 0000000000..4891f7dffc --- /dev/null +++ b/libs/components/editor-core/src/editor/clipboard/clipboard.ts @@ -0,0 +1,47 @@ +import { ClipboardEventDispatcher } from './clipboardEventDispatcher'; +import { HookType } from '@toeverything/components/editor-core'; +import { Editor } from '../editor'; +import { Copy } from './copy'; +import { Paste } from './paste'; +import ClipboardParse from './clipboard-parse'; +import { MarkdownParser } from './markdown-parse'; + +export class Clipboard { + private _clipboardEventDispatcher: ClipboardEventDispatcher; + private _copy: Copy; + private _paste: Paste; + private _clipboardParse: ClipboardParse; + private _markdownParse: MarkdownParser; + + constructor(editor: Editor, clipboardTarget: HTMLElement) { + this._clipboardEventDispatcher = new ClipboardEventDispatcher( + editor, + clipboardTarget + ); + this._clipboardParse = new ClipboardParse(editor); + this._markdownParse = new MarkdownParser(); + this._copy = new Copy(editor); + + this._paste = new Paste( + editor, + this._clipboardParse, + this._markdownParse + ); + + editor + .getHooks() + .get(HookType.ON_COPY) + .subscribe(this._copy.handleCopy); + + editor.getHooks().get(HookType.ON_CUT).subscribe(this._copy.handleCopy); + + editor + .getHooks() + .get(HookType.ON_PASTE) + .subscribe(this._paste.handlePaste); + } + + public dispose() { + this._clipboardEventDispatcher.dispose(); + } +} diff --git a/libs/components/editor-core/src/editor/clipboard/clipboardEventDispatcher.ts b/libs/components/editor-core/src/editor/clipboard/clipboardEventDispatcher.ts new file mode 100644 index 0000000000..d529e20a84 --- /dev/null +++ b/libs/components/editor-core/src/editor/clipboard/clipboardEventDispatcher.ts @@ -0,0 +1,82 @@ +import { Editor } from '../editor'; +import { shouldHandlerContinue } from './utils'; + +enum ClipboardAction { + copy = 'copy', + cut = 'cut', + paste = 'paste', +} + +//TODO: need to consider the cursor position after inserting the children +export class ClipboardEventDispatcher { + private _editor: Editor; + private _clipboardTarget: HTMLElement; + + constructor(editor: Editor, clipboardTarget: HTMLElement) { + this._editor = editor; + this._clipboardTarget = clipboardTarget; + this._initialize(); + } + + private _initialize() { + this._copyHandler = this._copyHandler.bind(this); + this._cutHandler = this._cutHandler.bind(this); + this._pasteHandler = this._pasteHandler.bind(this); + + document.addEventListener(ClipboardAction.copy, this._copyHandler); + document.addEventListener(ClipboardAction.cut, this._cutHandler); + document.addEventListener(ClipboardAction.paste, this._pasteHandler); + this._clipboardTarget.addEventListener( + ClipboardAction.copy, + this._copyHandler + ); + this._clipboardTarget.addEventListener( + ClipboardAction.cut, + this._cutHandler + ); + this._clipboardTarget.addEventListener( + ClipboardAction.paste, + this._pasteHandler + ); + } + + private _copyHandler(e: ClipboardEvent) { + if (!shouldHandlerContinue(e, this._editor)) { + return; + } + this._editor.getHooks().onCopy(e); + } + + private _cutHandler(e: ClipboardEvent) { + if (!shouldHandlerContinue(e, this._editor)) { + return; + } + this._editor.getHooks().onCut(e); + } + private _pasteHandler(e: ClipboardEvent) { + if (!shouldHandlerContinue(e, this._editor)) { + return; + } + + this._editor.getHooks().onPaste(e); + } + + dispose() { + document.removeEventListener(ClipboardAction.copy, this._copyHandler); + document.removeEventListener(ClipboardAction.cut, this._cutHandler); + document.removeEventListener(ClipboardAction.paste, this._pasteHandler); + this._clipboardTarget.removeEventListener( + ClipboardAction.copy, + this._copyHandler + ); + this._clipboardTarget.removeEventListener( + ClipboardAction.cut, + this._cutHandler + ); + this._clipboardTarget.removeEventListener( + ClipboardAction.paste, + this._pasteHandler + ); + this._editor = null; + } +} diff --git a/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts b/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts new file mode 100644 index 0000000000..1a31620fb6 --- /dev/null +++ b/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts @@ -0,0 +1,121 @@ +import { Editor } from '../editor'; +import { + AsyncBlock, + SelectBlock, + SelectInfo, +} from '@toeverything/components/editor-core'; +import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types'; +import { getClipInfoOfBlockById } from './utils'; +import { Clip } from './clip'; + +export class ClipboardUtils { + private _editor: Editor; + constructor(editor: Editor) { + this._editor = editor; + } + + async isBlockEditable(blockOrBlockId: AsyncBlock | string) { + const block = + typeof blockOrBlockId === 'string' + ? await this._editor.getBlockById(blockOrBlockId) + : blockOrBlockId; + const blockView = this._editor.getView(block.type); + + return blockView.activatable; + } + + async getClipInfoOfBlockById(blockId: string) { + const block = await this._editor.getBlockById(blockId); + const blockInfo: ClipBlockInfo = { + type: block.type, + properties: block?.getProperties(), + children: [] as ClipBlockInfo[], + }; + const children = (await block?.children()) ?? []; + + await Promise.all( + children.map(async childBlock => { + const childInfo = await this.getClipInfoOfBlockById( + childBlock.id + ); + blockInfo.children.push(childInfo); + }) + ); + + return blockInfo; + } + + async getClipDataOfBlocksById(blockIds: string[]) { + const clipInfos = await Promise.all( + blockIds.map(blockId => this.getClipInfoOfBlockById(blockId)) + ); + + return new Clip( + OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, + JSON.stringify({ + data: clipInfos, + }) + ); + } + + async getClipInfoOfBlockBySelectInfo(selectBlockInfo: SelectBlock) { + const block = await this._editor.getBlockById(selectBlockInfo.blockId); + const blockInfo: ClipBlockInfo = { + type: block?.type, + properties: + await this._editor.blockHelper.getBlockPropertiesBySelectInfo( + selectBlockInfo + ), + // Editable has no children + children: [], + }; + return blockInfo; + } + + async getClipDataOfBlocksBySelectInfo(selectInfo: SelectInfo) { + const clipInfos = await Promise.all( + selectInfo.blocks.map(selectBlockInfo => + this.getClipInfoOfBlockBySelectInfo(selectBlockInfo) + ) + ); + return new Clip( + OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, + JSON.stringify({ + data: clipInfos, + }) + ); + } + + async convertEditableBlockToHtml(block: AsyncBlock) { + const generate = (textList: any[]) => { + let content = ''; + textList.forEach(text_obj => { + let text = text_obj.text || ''; + if (text_obj.bold) { + text = `${text}`; + } + if (text_obj.italic) { + text = `${text}`; + } + if (text_obj.underline) { + text = `${text}`; + } + if (text_obj.inlinecode) { + text = `${text}`; + } + if (text_obj.strikethrough) { + text = `${text}`; + } + if (text_obj.type === 'link') { + text = `${generate( + text_obj.children + )}`; + } + content += text; + }); + return content; + }; + const text_list: any[] = block.getProperty('text').value; + return generate(text_list); + } +} diff --git a/libs/components/editor-core/src/editor/clipboard/copy.ts b/libs/components/editor-core/src/editor/clipboard/copy.ts index 94b3117264..8509af23fc 100644 --- a/libs/components/editor-core/src/editor/clipboard/copy.ts +++ b/libs/components/editor-core/src/editor/clipboard/copy.ts @@ -4,15 +4,15 @@ import { OFFICE_CLIPBOARD_MIMETYPE } from './types'; import { Clip } from './clip'; import ClipboardParse from './clipboard-parse'; import { getClipDataOfBlocksById } from './utils'; -import { copyToClipboard } from '@toeverything/utils'; +import { ClipboardUtils } from './clipboardUtils'; class Copy { private _editor: Editor; private _clipboardParse: ClipboardParse; - + private _utils: ClipboardUtils; constructor(editor: Editor) { this._editor = editor; this._clipboardParse = new ClipboardParse(editor); - + this._utils = new ClipboardUtils(editor); this.handleCopy = this.handleCopy.bind(this); } public async handleCopy(e: ClipboardEvent) { @@ -22,7 +22,6 @@ class Copy { if (!clips.length) { return; } - // TODO: is not compatible with safari const success = this._copyToClipboardFromPc(clips); if (!success) { // This way, not compatible with firefox @@ -49,7 +48,11 @@ class Copy { const affineClip = await this._getAffineClip(); clips.push(affineClip); - // get common html clip + const textClip = await this._getTextClip(); + clips.push(textClip); + + // const htmlClip = await this._getHtmlClip(); + // clips.push(htmlClip); const htmlClip = await this._clipboardParse.generateHtml(); htmlClip && clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip)); @@ -57,16 +60,128 @@ class Copy { return clips; } + // private async _getHtmlClip(): Promise { + // const selectInfo: SelectInfo = + // await this._editor.selectionManager.getSelectInfo(); + // + // if (selectInfo.type === 'Range') { + // const html = ( + // await Promise.all( + // selectInfo.blocks.map(async selectBlockInfo => { + // const block = await this._editor.getBlockById( + // selectBlockInfo.blockId + // ); + // const blockView = this._editor.getView(block.type); + // const block2html = await blockView.block2html({ + // editor: this._editor, + // block, + // selectInfo: selectBlockInfo, + // }); + // + // if ( + // await this._editor.blockHelper.isBlockEditable( + // block + // ) + // ) { + // const selectedProperties = + // await this._editor.blockHelper.getEditableBlockPropertiesBySelectInfo( + // block, + // selectBlockInfo + // ); + // + // return ( + // block2html || + // this._editor.blockHelper.convertTextValue2Html( + // block.id, + // selectedProperties.text.value + // ) + // ); + // } + // + // return block2html; + // }) + // ) + // ).join(''); + // console.log('html', html); + // } + // + // return new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, 'blockText'); + // } + private async _getAffineClip(): Promise { const selectInfo: SelectInfo = await this._editor.selectionManager.getSelectInfo(); - return getClipDataOfBlocksById( - this._editor, + if (selectInfo.type === 'Range') { + return this._utils.getClipDataOfBlocksBySelectInfo(selectInfo); + } + + // The only remaining case is that selectInfo.type === 'Block' + return this._utils.getClipDataOfBlocksById( selectInfo.blocks.map(block => block.blockId) ); } + private async _getTextClip(): Promise { + const selectInfo: SelectInfo = + await this._editor.selectionManager.getSelectInfo(); + + if (selectInfo.type === 'Range') { + const text = ( + await Promise.all( + selectInfo.blocks.map(async selectBlockInfo => { + const block = await this._editor.getBlockById( + selectBlockInfo.blockId + ); + const blockView = this._editor.getView(block.type); + const block2Text = await blockView.block2Text( + block, + selectBlockInfo + ); + + return ( + block2Text || + this._editor.blockHelper.getBlockTextBetweenSelection( + selectBlockInfo.blockId, + false + ) + ); + }) + ) + ).join('\n'); + return new Clip(OFFICE_CLIPBOARD_MIMETYPE.TEXT, text); + } + + // The only remaining case is that selectInfo.type === 'Block' + const selectedBlocks = ( + await Promise.all( + selectInfo.blocks.map(selectBlockInfo => + this._editor.blockHelper.getFlatBlocksUnderParent( + selectBlockInfo.blockId, + true + ) + ) + ) + ).flat(); + + const blockText = ( + await Promise.all( + selectedBlocks.map(async block => { + const blockView = this._editor.getView(block.type); + const block2Text = await blockView.block2Text(block); + return ( + block2Text || + this._editor.blockHelper.getBlockText(block.id) + ); + }) + ) + ).join('\n'); + + return new Clip(OFFICE_CLIPBOARD_MIMETYPE.TEXT, blockText); + } + + // TODO: Optimization + // TODO: is not compatible with safari private _copyToClipboardFromPc(clips: any[]) { let success = false; const tempElem = document.createElement('textarea'); diff --git a/libs/components/editor-core/src/editor/clipboard/index.ts b/libs/components/editor-core/src/editor/clipboard/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/components/editor-core/src/editor/clipboard/paste.ts b/libs/components/editor-core/src/editor/clipboard/paste.ts index 37168c2614..0d631991c2 100644 --- a/libs/components/editor-core/src/editor/clipboard/paste.ts +++ b/libs/components/editor-core/src/editor/clipboard/paste.ts @@ -42,13 +42,13 @@ export class Paste { OFFICE_CLIPBOARD_MIMETYPE.HTML, OFFICE_CLIPBOARD_MIMETYPE.TEXT, ]; - public handlePaste(e: Event) { + public handlePaste(e: ClipboardEvent) { if (!shouldHandlerContinue(e, this._editor)) { return; } e.stopPropagation(); - const clipboardData = (e as ClipboardEvent).clipboardData; + const clipboardData = e.clipboardData; const isPureFile = Paste._isPureFileInClipboard(clipboardData); if (isPureFile) { diff --git a/libs/components/editor-core/src/editor/clipboard/utils.ts b/libs/components/editor-core/src/editor/clipboard/utils.ts index c807a869a0..548e187814 100644 --- a/libs/components/editor-core/src/editor/clipboard/utils.ts +++ b/libs/components/editor-core/src/editor/clipboard/utils.ts @@ -2,7 +2,10 @@ import { Editor } from '../editor'; import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types'; import { Clip } from './clip'; -export const shouldHandlerContinue = (event: Event, editor: Editor) => { +export const shouldHandlerContinue = ( + event: ClipboardEvent, + editor: Editor +) => { const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; if (event.defaultPrevented) { @@ -20,10 +23,9 @@ export const getClipInfoOfBlockById = async ( blockId: string ) => { const block = await editor.getBlockById(blockId); - const blockView = editor.getView(block.type); const blockInfo: ClipBlockInfo = { type: block.type, - properties: blockView.getSelProperties(block, {}), + properties: block?.getProperties(), children: [] as ClipBlockInfo[], }; const children = (await block?.children()) ?? []; diff --git a/libs/components/editor-core/src/editor/editor.ts b/libs/components/editor-core/src/editor/editor.ts index e85db2ba65..931fd25da0 100644 --- a/libs/components/editor-core/src/editor/editor.ts +++ b/libs/components/editor-core/src/editor/editor.ts @@ -15,8 +15,7 @@ import assert from 'assert'; import type { WorkspaceAndBlockId } from './block'; import { AsyncBlock } from './block'; import { BlockHelper } from './block/block-helper'; -import { BrowserClipboard } from './clipboard/browser-clipboard'; -import { ClipboardPopulator } from './clipboard/clipboard-populator'; +import { Clipboard } from './clipboard/clipboard'; import { EditorCommands } from './commands'; import { EditorConfig } from './config'; import { DragDropManager } from './drag-drop'; @@ -65,8 +64,9 @@ export class Editor implements Virgo { readonly = false; private _rootBlockId: string; private storage_manager?: StorageManager; - private clipboard?: BrowserClipboard; - private clipboard_populator?: ClipboardPopulator; + private _clipboard: Clipboard; + // private clipboardActionDispacher?: ClipboardEventDispatcher; + // private clipboard_populator?: ClipboardPopulator; public reactRenderRoot: { render: PatchNode; has: (key: string) => boolean; @@ -138,7 +138,7 @@ export class Editor implements Virgo { public set container(v: HTMLDivElement) { this.ui_container = v; - this._initClipboard(); + this._clipboard = new Clipboard(this, this.ui_container); } public get container() { @@ -191,26 +191,26 @@ export class Editor implements Virgo { }; } - private _disposeClipboard() { - this.clipboard?.dispose(); - this.clipboard_populator?.disposeInternal(); - } + // private _disposeClipboard() { + // this.clipboard?.dispose(); + // this.clipboard_populator?.disposeInternal(); + // } - private _initClipboard() { - this._disposeClipboard(); - if (this.ui_container && !this._isDisposed) { - this.clipboard = new BrowserClipboard( - this.ui_container, - this.hooks, - this - ); - this.clipboard_populator = new ClipboardPopulator( - this, - this.hooks, - this.selectionManager - ); - } - } + // private _initClipboard() { + // this._disposeClipboard(); + // if (this.ui_container && !this._isDisposed) { + // this.clipboardActionDispacher = new ClipboardEventDispatcher({ + // clipboardTarget: this.ui_container, + // hooks: this.hooks, + // editor: this, + // }); + // this.clipboard_populator = new ClipboardPopulator( + // this, + // this.hooks, + // this.selectionManager + // ); + // } + // } /** Root Block Id */ getRootBlockId() { @@ -498,12 +498,13 @@ export class Editor implements Virgo { } public async page2html(): Promise { - const parse = this.clipboard?.getClipboardParse(); - if (!parse) { - return ''; - } - const html_str = await parse.page2html(); - return html_str; + return ''; + // const parse = this.clipboard?.getClipboardParse(); + // if (!parse) { + // return ''; + // } + // const html_str = await parse.page2html(); + // return html_str; } dispose() { @@ -519,6 +520,6 @@ export class Editor implements Virgo { this.plugin_manager.dispose(); this.selectionManager.dispose(); this.dragDropManager.dispose(); - this._disposeClipboard(); + this._clipboard?.dispose(); } } diff --git a/libs/components/editor-core/src/editor/index.ts b/libs/components/editor-core/src/editor/index.ts index 3131e6f0a1..08c479ba8f 100644 --- a/libs/components/editor-core/src/editor/index.ts +++ b/libs/components/editor-core/src/editor/index.ts @@ -8,6 +8,6 @@ export { Editor as BlockEditor } from './editor'; export * from './selection'; export { BlockDropPlacement, HookType, GroupDirection } from './types'; export type { Plugin, PluginCreator, PluginHooks, Virgo } from './types'; -export { BaseView, getTextHtml, getTextProperties } from './views/base-view'; +export { BaseView, getTextHtml } from './views/base-view'; export type { ChildrenView, CreateView } from './views/base-view'; export { getClipDataOfBlocksById } from './clipboard/utils'; diff --git a/libs/components/editor-core/src/editor/plugin/hooks.ts b/libs/components/editor-core/src/editor/plugin/hooks.ts index 42b9c262a0..747f0b42e2 100644 --- a/libs/components/editor-core/src/editor/plugin/hooks.ts +++ b/libs/components/editor-core/src/editor/plugin/hooks.ts @@ -116,12 +116,15 @@ export class Hooks implements HooksRunner, PluginHooks { this._runHook(HookType.ON_SEARCH); } - public beforeCopy(e: ClipboardEvent): void { - this._runHook(HookType.BEFORE_COPY, e); + public onCopy(e: ClipboardEvent): void { + this._runHook(HookType.ON_COPY, e); } - public beforeCut(e: ClipboardEvent): void { - this._runHook(HookType.BEFORE_CUT, e); + public onCut(e: ClipboardEvent): void { + this._runHook(HookType.ON_CUT, e); + } + public onPaste(e: ClipboardEvent): void { + this._runHook(HookType.ON_PASTE, e); } public onRootNodeScroll(e: React.UIEvent): void { diff --git a/libs/components/editor-core/src/editor/types.ts b/libs/components/editor-core/src/editor/types.ts index 1b97cf0f86..f2621164b6 100644 --- a/libs/components/editor-core/src/editor/types.ts +++ b/libs/components/editor-core/src/editor/types.ts @@ -174,8 +174,9 @@ export enum HookType { ON_ROOTNODE_DRAG_END = 'onRootNodeDragEnd', ON_ROOTNODE_DRAG_OVER_CAPTURE = 'onRootNodeDragOverCapture', ON_ROOTNODE_DROP = 'onRootNodeDrop', - BEFORE_COPY = 'beforeCopy', - BEFORE_CUT = 'beforeCut', + ON_COPY = 'onCopy', + ON_CUT = 'onCut', + ON_PASTE = 'onPaste', ON_ROOTNODE_SCROLL = 'onRootNodeScroll', } @@ -207,8 +208,9 @@ export interface HooksRunner { onRootNodeDragEnd: (e: React.DragEvent) => void; onRootNodeDragLeave: (e: React.DragEvent) => void; onRootNodeDrop: (e: React.DragEvent) => void; - beforeCopy: (e: ClipboardEvent) => void; - beforeCut: (e: ClipboardEvent) => void; + onCopy: (e: ClipboardEvent) => void; + onCut: (e: ClipboardEvent) => void; + onPaste: (e: ClipboardEvent) => void; onRootNodeScroll: (e: React.UIEvent) => void; } diff --git a/libs/components/editor-core/src/editor/views/base-view.ts b/libs/components/editor-core/src/editor/views/base-view.ts index b69cdd848a..f9db79fbc5 100644 --- a/libs/components/editor-core/src/editor/views/base-view.ts +++ b/libs/components/editor-core/src/editor/views/base-view.ts @@ -131,10 +131,6 @@ export abstract class BaseView { return result; } - getSelProperties(block: AsyncBlock, selectInfo: any): DefaultColumnsValue { - return cloneDeep(block.getProperties()); - } - html2block(el: Element, parseEl: (el: Element) => any[]): any[] | null { return null; } @@ -146,31 +142,24 @@ export abstract class BaseView { ): Promise { return ''; } -} + async block2Text( + block: AsyncBlock, + // The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range + selectInfo?: SelectBlock + ): Promise { + return ''; + } -export const getTextProperties = ( - properties: DefaultColumnsValue, - selectInfo: any -) => { - let text_value = properties.text.value; - if (text_value.length === 0) { - return properties; - } - if (selectInfo.endInfo) { - text_value = text_value.slice(0, selectInfo.endInfo.arrayIndex + 1); - text_value[text_value.length - 1].text = text_value[ - text_value.length - 1 - ].text.substring(0, selectInfo.endInfo.offset); - } - if (selectInfo.startInfo) { - text_value = text_value.slice(selectInfo.startInfo.arrayIndex); - text_value[0].text = text_value[0].text.substring( - selectInfo.startInfo.offset - ); - } - properties.text.value = text_value; - return properties; -}; + // TODO: Try using new methods + // async block2html2(props: { + // editor: Editor; + // block: AsyncBlock; + // // The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range + // selectInfo?: SelectBlock; + // }) { + // return ''; + // } +} export const getTextHtml = (block: AsyncBlock) => { const generate = (textList: any[]) => { From 065f833564f25aecf547f4c967e6e1f07386f087 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Mon, 22 Aug 2022 10:29:42 +0800 Subject: [PATCH 2/8] refactor: remove clipboard utils to ClipboardUtils --- .../src/editor/clipboard/clipboard.ts | 12 ++-- .../clipboard/clipboardEventDispatcher.ts | 10 ++-- .../src/editor/clipboard/clipboardUtils.ts | 59 ++++--------------- .../editor-core/src/editor/clipboard/copy.ts | 1 - .../editor-core/src/editor/clipboard/index.ts | 0 .../editor-core/src/editor/clipboard/paste.ts | 5 -- .../editor-core/src/editor/clipboard/utils.ts | 54 ----------------- .../editor-core/src/editor/editor.ts | 4 ++ .../editor-core/src/editor/index.ts | 1 - 9 files changed, 30 insertions(+), 116 deletions(-) delete mode 100644 libs/components/editor-core/src/editor/clipboard/index.ts delete mode 100644 libs/components/editor-core/src/editor/clipboard/utils.ts diff --git a/libs/components/editor-core/src/editor/clipboard/clipboard.ts b/libs/components/editor-core/src/editor/clipboard/clipboard.ts index 4891f7dffc..4cee691a08 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboard.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboard.ts @@ -5,6 +5,7 @@ import { Copy } from './copy'; import { Paste } from './paste'; import ClipboardParse from './clipboard-parse'; import { MarkdownParser } from './markdown-parse'; +import { ClipboardUtils } from './clipboardUtils'; export class Clipboard { private _clipboardEventDispatcher: ClipboardEventDispatcher; @@ -12,14 +13,12 @@ export class Clipboard { private _paste: Paste; private _clipboardParse: ClipboardParse; private _markdownParse: MarkdownParser; + public clipboardUtils: ClipboardUtils; constructor(editor: Editor, clipboardTarget: HTMLElement) { - this._clipboardEventDispatcher = new ClipboardEventDispatcher( - editor, - clipboardTarget - ); this._clipboardParse = new ClipboardParse(editor); this._markdownParse = new MarkdownParser(); + this.clipboardUtils = new ClipboardUtils(editor); this._copy = new Copy(editor); this._paste = new Paste( @@ -28,6 +27,11 @@ export class Clipboard { this._markdownParse ); + this._clipboardEventDispatcher = new ClipboardEventDispatcher( + editor, + clipboardTarget + ); + editor .getHooks() .get(HookType.ON_COPY) diff --git a/libs/components/editor-core/src/editor/clipboard/clipboardEventDispatcher.ts b/libs/components/editor-core/src/editor/clipboard/clipboardEventDispatcher.ts index d529e20a84..b3fd8305db 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboardEventDispatcher.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboardEventDispatcher.ts @@ -1,5 +1,5 @@ import { Editor } from '../editor'; -import { shouldHandlerContinue } from './utils'; +import { ClipboardUtils } from './clipboardUtils'; enum ClipboardAction { copy = 'copy', @@ -11,10 +11,12 @@ enum ClipboardAction { export class ClipboardEventDispatcher { private _editor: Editor; private _clipboardTarget: HTMLElement; + private _utils: ClipboardUtils; constructor(editor: Editor, clipboardTarget: HTMLElement) { this._editor = editor; this._clipboardTarget = clipboardTarget; + this._utils = new ClipboardUtils(editor); this._initialize(); } @@ -41,20 +43,20 @@ export class ClipboardEventDispatcher { } private _copyHandler(e: ClipboardEvent) { - if (!shouldHandlerContinue(e, this._editor)) { + if (!this._utils.shouldHandlerContinue(e)) { return; } this._editor.getHooks().onCopy(e); } private _cutHandler(e: ClipboardEvent) { - if (!shouldHandlerContinue(e, this._editor)) { + if (!this._utils.shouldHandlerContinue(e)) { return; } this._editor.getHooks().onCut(e); } private _pasteHandler(e: ClipboardEvent) { - if (!shouldHandlerContinue(e, this._editor)) { + if (!this._utils.shouldHandlerContinue(e)) { return; } diff --git a/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts b/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts index 1a31620fb6..d50b2fbdae 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts @@ -1,11 +1,6 @@ import { Editor } from '../editor'; -import { - AsyncBlock, - SelectBlock, - SelectInfo, -} from '@toeverything/components/editor-core'; +import { SelectBlock, SelectInfo } from '@toeverything/components/editor-core'; import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types'; -import { getClipInfoOfBlockById } from './utils'; import { Clip } from './clip'; export class ClipboardUtils { @@ -14,15 +9,18 @@ export class ClipboardUtils { this._editor = editor; } - async isBlockEditable(blockOrBlockId: AsyncBlock | string) { - const block = - typeof blockOrBlockId === 'string' - ? await this._editor.getBlockById(blockOrBlockId) - : blockOrBlockId; - const blockView = this._editor.getView(block.type); + shouldHandlerContinue = (event: ClipboardEvent) => { + const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; - return blockView.activatable; - } + if (event.defaultPrevented) { + return false; + } + if (filterNodes.includes((event.target as HTMLElement)?.tagName)) { + return false; + } + + return this._editor.selectionManager.currentSelectInfo.type !== 'None'; + }; async getClipInfoOfBlockById(blockId: string) { const block = await this._editor.getBlockById(blockId); @@ -85,37 +83,4 @@ export class ClipboardUtils { }) ); } - - async convertEditableBlockToHtml(block: AsyncBlock) { - const generate = (textList: any[]) => { - let content = ''; - textList.forEach(text_obj => { - let text = text_obj.text || ''; - if (text_obj.bold) { - text = `${text}`; - } - if (text_obj.italic) { - text = `${text}`; - } - if (text_obj.underline) { - text = `${text}`; - } - if (text_obj.inlinecode) { - text = `${text}`; - } - if (text_obj.strikethrough) { - text = `${text}`; - } - if (text_obj.type === 'link') { - text = `${generate( - text_obj.children - )}`; - } - content += text; - }); - return content; - }; - const text_list: any[] = block.getProperty('text').value; - return generate(text_list); - } } diff --git a/libs/components/editor-core/src/editor/clipboard/copy.ts b/libs/components/editor-core/src/editor/clipboard/copy.ts index 8509af23fc..3056091cd3 100644 --- a/libs/components/editor-core/src/editor/clipboard/copy.ts +++ b/libs/components/editor-core/src/editor/clipboard/copy.ts @@ -3,7 +3,6 @@ import { SelectInfo } from '../selection'; import { OFFICE_CLIPBOARD_MIMETYPE } from './types'; import { Clip } from './clip'; import ClipboardParse from './clipboard-parse'; -import { getClipDataOfBlocksById } from './utils'; import { ClipboardUtils } from './clipboardUtils'; class Copy { private _editor: Editor; diff --git a/libs/components/editor-core/src/editor/clipboard/index.ts b/libs/components/editor-core/src/editor/clipboard/index.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/libs/components/editor-core/src/editor/clipboard/paste.ts b/libs/components/editor-core/src/editor/clipboard/paste.ts index 0d631991c2..10643decbd 100644 --- a/libs/components/editor-core/src/editor/clipboard/paste.ts +++ b/libs/components/editor-core/src/editor/clipboard/paste.ts @@ -14,7 +14,6 @@ import { services, } from '@toeverything/datasource/db-service'; import { MarkdownParser } from './markdown-parse'; -import { shouldHandlerContinue } from './utils'; const SUPPORT_MARKDOWN_PASTE = true; type TextValueItem = { @@ -26,7 +25,6 @@ export class Paste { private _editor: Editor; private _markdownParse: MarkdownParser; private _clipboardParse: ClipboardParse; - constructor( editor: Editor, clipboardParse: ClipboardParse, @@ -43,9 +41,6 @@ export class Paste { OFFICE_CLIPBOARD_MIMETYPE.TEXT, ]; public handlePaste(e: ClipboardEvent) { - if (!shouldHandlerContinue(e, this._editor)) { - return; - } e.stopPropagation(); const clipboardData = e.clipboardData; diff --git a/libs/components/editor-core/src/editor/clipboard/utils.ts b/libs/components/editor-core/src/editor/clipboard/utils.ts deleted file mode 100644 index 548e187814..0000000000 --- a/libs/components/editor-core/src/editor/clipboard/utils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Editor } from '../editor'; -import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types'; -import { Clip } from './clip'; - -export const shouldHandlerContinue = ( - event: ClipboardEvent, - editor: Editor -) => { - const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA']; - - if (event.defaultPrevented) { - return false; - } - if (filterNodes.includes((event.target as HTMLElement)?.tagName)) { - return false; - } - - return editor.selectionManager.currentSelectInfo.type !== 'None'; -}; - -export const getClipInfoOfBlockById = async ( - editor: Editor, - blockId: string -) => { - const block = await editor.getBlockById(blockId); - const blockInfo: ClipBlockInfo = { - type: block.type, - properties: block?.getProperties(), - children: [] as ClipBlockInfo[], - }; - const children = (await block?.children()) ?? []; - - for (let i = 0; i < children.length; i++) { - const childInfo = await getClipInfoOfBlockById(editor, children[i].id); - blockInfo.children.push(childInfo); - } - return blockInfo; -}; - -export const getClipDataOfBlocksById = async ( - editor: Editor, - blockIds: string[] -) => { - const clipInfos = await Promise.all( - blockIds.map(blockId => getClipInfoOfBlockById(editor, blockId)) - ); - - return new Clip( - OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED, - JSON.stringify({ - data: clipInfos, - }) - ); -}; diff --git a/libs/components/editor-core/src/editor/editor.ts b/libs/components/editor-core/src/editor/editor.ts index 931fd25da0..9253ab2cbe 100644 --- a/libs/components/editor-core/src/editor/editor.ts +++ b/libs/components/editor-core/src/editor/editor.ts @@ -145,6 +145,10 @@ export class Editor implements Virgo { return this.ui_container; } + public get clipboard() { + return this._clipboard; + } + /** * Use it discreetly. * Preference to use {@link withBatch} diff --git a/libs/components/editor-core/src/editor/index.ts b/libs/components/editor-core/src/editor/index.ts index 08c479ba8f..d03326f386 100644 --- a/libs/components/editor-core/src/editor/index.ts +++ b/libs/components/editor-core/src/editor/index.ts @@ -10,4 +10,3 @@ export { BlockDropPlacement, HookType, GroupDirection } from './types'; export type { Plugin, PluginCreator, PluginHooks, Virgo } from './types'; export { BaseView, getTextHtml } from './views/base-view'; export type { ChildrenView, CreateView } from './views/base-view'; -export { getClipDataOfBlocksById } from './clipboard/utils'; From 4b904ef76204655e6253deabfab22d47d98feec4 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Mon, 22 Aug 2022 22:03:11 +0800 Subject: [PATCH 3/8] refactor: refactor block2html in clipboard --- libs/components/affine-board/src/Board.tsx | 13 ++- .../common/src/lib/text/slate-utils.ts | 2 +- .../editor-blocks/src/blocks/bullet/index.ts | 30 ++----- .../editor-blocks/src/blocks/code/index.ts | 28 ++----- .../editor-blocks/src/blocks/divider/index.ts | 10 +-- .../src/blocks/embed-link/index.ts | 12 +-- .../editor-blocks/src/blocks/figma/index.ts | 12 +-- .../editor-blocks/src/blocks/file/index.ts | 23 +++--- .../editor-blocks/src/blocks/group/Group.tsx | 19 +++-- .../src/blocks/groupDvider/index.ts | 10 +-- .../editor-blocks/src/blocks/image/index.ts | 26 +++--- .../src/blocks/numbered/index.ts | 29 ++----- .../editor-blocks/src/blocks/page/index.ts | 37 ++++----- .../src/blocks/text/QuoteBlock.tsx | 33 +++----- .../src/blocks/text/TextBlock.tsx | 51 +++--------- .../editor-blocks/src/blocks/todo/index.ts | 28 ++----- .../editor-blocks/src/blocks/youtube/index.ts | 12 +-- .../src/utils/commonBlockClip.ts | 30 +++++++ .../src/editor/block/block-helper.ts | 6 +- .../src/editor/clipboard/clipboard-parse.ts | 75 +---------------- .../src/editor/clipboard/clipboardUtils.ts | 78 +++++++++++++++++- .../editor-core/src/editor/clipboard/copy.ts | 80 +++++++------------ .../editor-core/src/editor/editor.ts | 8 +- .../editor-core/src/editor/index.ts | 2 +- .../editor-core/src/editor/views/base-view.ts | 56 ++----------- .../layout/src/header/PageSettingPortal.tsx | 8 +- .../Settings/util/handle-export.ts | 16 ++-- 27 files changed, 284 insertions(+), 450 deletions(-) create mode 100644 libs/components/editor-blocks/src/utils/commonBlockClip.ts diff --git a/libs/components/affine-board/src/Board.tsx b/libs/components/affine-board/src/Board.tsx index 4e99d6bbb0..5730649545 100644 --- a/libs/components/affine-board/src/Board.tsx +++ b/libs/components/affine-board/src/Board.tsx @@ -5,10 +5,7 @@ import { getSession } from '@toeverything/components/board-sessions'; import { deepCopy, TldrawApp } from '@toeverything/components/board-state'; import { tools } from '@toeverything/components/board-tools'; import { TDShapeType } from '@toeverything/components/board-types'; -import { - RecastBlockProvider, - getClipDataOfBlocksById, -} from '@toeverything/components/editor-core'; +import { RecastBlockProvider } from '@toeverything/components/editor-core'; import { services } from '@toeverything/datasource/db-service'; import { AsyncBlock, BlockEditor } from '@toeverything/framework/virgo'; import { useEffect, useState } from 'react'; @@ -70,10 +67,10 @@ const AffineBoard = ({ set_app(app); }, async onCopy(e, groupIds) { - const clip = await getClipDataOfBlocksById( - editor, - groupIds - ); + const clip = + await editor.clipboard.clipboardUtils.getClipDataOfBlocksById( + groupIds + ); e.clipboardData?.setData( clip.getMimeType(), diff --git a/libs/components/common/src/lib/text/slate-utils.ts b/libs/components/common/src/lib/text/slate-utils.ts index fb183a7c35..160f1a416b 100644 --- a/libs/components/common/src/lib/text/slate-utils.ts +++ b/libs/components/common/src/lib/text/slate-utils.ts @@ -993,7 +993,7 @@ class SlateUtils { textValue.children )}`; } - return `${text}>`; + return `${text}`; } public getStartSelection() { diff --git a/libs/components/editor-blocks/src/blocks/bullet/index.ts b/libs/components/editor-blocks/src/blocks/bullet/index.ts index 58360954ce..1577263af3 100644 --- a/libs/components/editor-blocks/src/blocks/bullet/index.ts +++ b/libs/components/editor-blocks/src/blocks/bullet/index.ts @@ -1,18 +1,10 @@ -import { - AsyncBlock, - BaseView, - CreateView, - SelectBlock, - getTextHtml, -} from '@toeverything/framework/virgo'; -import { - Protocol, - DefaultColumnsValue, -} from '@toeverything/datasource/db-service'; -// import { withTreeViewChildren } from '../../utils/with-tree-view-children'; +import { AsyncBlock, BaseView } from '@toeverything/framework/virgo'; +import { Protocol } from '@toeverything/datasource/db-service'; import { defaultBulletProps, BulletView } from './BulletView'; -import { IndentWrapper } from '../../components/IndentWrapper'; - +import { + Block2HtmlProps, + commonBlock2HtmlContent, +} from '../../utils/commonBlockClip'; export class BulletBlock extends BaseView { public type = Protocol.Block.Type.bullet; @@ -71,13 +63,7 @@ export class BulletBlock extends BaseView { return null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `
  • ${content}
`; + override async block2html(props: Block2HtmlProps) { + return `
  • ${await commonBlock2HtmlContent(props)}
`; } } diff --git a/libs/components/editor-blocks/src/blocks/code/index.ts b/libs/components/editor-blocks/src/blocks/code/index.ts index 1fc84f1df8..a204779656 100644 --- a/libs/components/editor-blocks/src/blocks/code/index.ts +++ b/libs/components/editor-blocks/src/blocks/code/index.ts @@ -1,16 +1,10 @@ -import { - BaseView, - AsyncBlock, - CreateView, - SelectBlock, - getTextHtml, -} from '@toeverything/framework/virgo'; -import { - Protocol, - DefaultColumnsValue, -} from '@toeverything/datasource/db-service'; +import { BaseView, AsyncBlock } from '@toeverything/framework/virgo'; +import { Protocol } from '@toeverything/datasource/db-service'; import { CodeView } from './CodeView'; -import { ComponentType } from 'react'; +import { + Block2HtmlProps, + commonBlock2HtmlContent, +} from '../../utils/commonBlockClip'; export class CodeBlock extends BaseView { type = Protocol.Block.Type.code; @@ -62,13 +56,7 @@ export class CodeBlock extends BaseView { return null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `${content}`; + override async block2html(props: Block2HtmlProps) { + return `${await commonBlock2HtmlContent(props)}`; } } diff --git a/libs/components/editor-blocks/src/blocks/divider/index.ts b/libs/components/editor-blocks/src/blocks/divider/index.ts index 6d67611cda..eb4615963c 100644 --- a/libs/components/editor-blocks/src/blocks/divider/index.ts +++ b/libs/components/editor-blocks/src/blocks/divider/index.ts @@ -5,6 +5,7 @@ import { } from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { DividerView } from './divider-view'; +import { Block2HtmlProps } from '../../utils/commonBlockClip'; export class DividerBlock extends BaseView { type = Protocol.Block.Type.divider; @@ -28,12 +29,7 @@ export class DividerBlock extends BaseView { return null; } - - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - return `
`; + override async block2html(props: Block2HtmlProps) { + return `
`; } } diff --git a/libs/components/editor-blocks/src/blocks/embed-link/index.ts b/libs/components/editor-blocks/src/blocks/embed-link/index.ts index 731ae801d6..7674c71c3e 100644 --- a/libs/components/editor-blocks/src/blocks/embed-link/index.ts +++ b/libs/components/editor-blocks/src/blocks/embed-link/index.ts @@ -5,6 +5,7 @@ import { } from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { EmbedLinkView } from './EmbedLinkView'; +import { Block2HtmlProps } from '../../utils/commonBlockClip'; export class EmbedLinkBlock extends BaseView { public override selectable = true; @@ -35,13 +36,8 @@ export class EmbedLinkBlock extends BaseView { return null; } - - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - const figma_url = block.getProperty('embedLink')?.value; - return `

${figma_url}

`; + override async block2html({ block }: Block2HtmlProps) { + const url = block.getProperty('embedLink')?.value; + return `

${url}

`; } } diff --git a/libs/components/editor-blocks/src/blocks/figma/index.ts b/libs/components/editor-blocks/src/blocks/figma/index.ts index 3ed44c9c85..652abfbc1d 100644 --- a/libs/components/editor-blocks/src/blocks/figma/index.ts +++ b/libs/components/editor-blocks/src/blocks/figma/index.ts @@ -5,6 +5,7 @@ import { SelectBlock, } from '@toeverything/framework/virgo'; import { FigmaView } from './FigmaView'; +import { Block2HtmlProps } from '../../utils/commonBlockClip'; export class FigmaBlock extends BaseView { public override selectable = true; @@ -41,13 +42,8 @@ export class FigmaBlock extends BaseView { return null; } - - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - const figma_url = block.getProperty('embedLink')?.value; - return `

${figma_url}

`; + override async block2html({ block }: Block2HtmlProps) { + const figmaUrl = block.getProperty('embedLink')?.value; + return `

${figmaUrl}

`; } } diff --git a/libs/components/editor-blocks/src/blocks/file/index.ts b/libs/components/editor-blocks/src/blocks/file/index.ts index e55eed7721..42c2bee1f6 100644 --- a/libs/components/editor-blocks/src/blocks/file/index.ts +++ b/libs/components/editor-blocks/src/blocks/file/index.ts @@ -9,25 +9,22 @@ import { services, } from '@toeverything/datasource/db-service'; import { FileView } from './FileView'; +import { Block2HtmlProps } from '../../utils/commonBlockClip'; export class FileBlock extends BaseView { public override selectable = true; public override activatable = false; type = Protocol.Block.Type.file; View = FileView; + override async block2html({ block }: Block2HtmlProps) { + const fileProperty = block.getProperty('file'); + const fileId = fileProperty?.value; + const fileInfo = fileId + ? await services.api.file.get(fileId, block.workspace) + : null; - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - const file_property = - block.getProperty('file') || ({} as FileColumnValue); - const file_id = file_property.value; - let file_info = null; - if (file_id) { - file_info = await services.api.file.get(file_id, block.workspace); - } - return `

${file_property?.name}

`; + return fileInfo + ? `

${fileProperty?.name}

` + : ''; } } diff --git a/libs/components/editor-blocks/src/blocks/group/Group.tsx b/libs/components/editor-blocks/src/blocks/group/Group.tsx index ac194d7eab..b5fdcac58c 100644 --- a/libs/components/editor-blocks/src/blocks/group/Group.tsx +++ b/libs/components/editor-blocks/src/blocks/group/Group.tsx @@ -6,6 +6,10 @@ import { SelectBlock, } from '@toeverything/framework/virgo'; import { GroupView } from './GroupView'; +import { + Block2HtmlProps, + commonBlock2HtmlContent, +} from '../../utils/commonBlockClip'; export class Group extends BaseView { public override selectable = true; @@ -25,13 +29,12 @@ export class Group extends BaseView { override async onCreate(block: AsyncBlock): Promise { return block; } - - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - const content = await generateHtml(children); - return `
${content}
`; + override async block2html({ editor, selectInfo, block }: Block2HtmlProps) { + const childrenHtml = + await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos( + block, + selectInfo?.children + ); + return `
${childrenHtml}`; } } diff --git a/libs/components/editor-blocks/src/blocks/groupDvider/index.ts b/libs/components/editor-blocks/src/blocks/groupDvider/index.ts index 4120f1a525..52940173e6 100644 --- a/libs/components/editor-blocks/src/blocks/groupDvider/index.ts +++ b/libs/components/editor-blocks/src/blocks/groupDvider/index.ts @@ -5,6 +5,7 @@ import { } from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { GroupDividerView } from './groupDividerView'; +import { Block2HtmlProps } from '../../utils/commonBlockClip'; export class GroupDividerBlock extends BaseView { type = Protocol.Block.Type.groupDivider; @@ -28,12 +29,7 @@ export class GroupDividerBlock extends BaseView { return null; } - - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - return `
`; + override async block2html(props: Block2HtmlProps) { + return `
`; } } diff --git a/libs/components/editor-blocks/src/blocks/image/index.ts b/libs/components/editor-blocks/src/blocks/image/index.ts index 8f56468b41..190f83d72a 100644 --- a/libs/components/editor-blocks/src/blocks/image/index.ts +++ b/libs/components/editor-blocks/src/blocks/image/index.ts @@ -1,10 +1,7 @@ -import { - AsyncBlock, - BaseView, - SelectBlock, -} from '@toeverything/framework/virgo'; +import { BaseView } from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { ImageView } from './ImageView'; +import { Block2HtmlProps } from '../../utils/commonBlockClip'; export class ImageBlock extends BaseView { public override selectable = true; @@ -37,18 +34,13 @@ export class ImageBlock extends BaseView { return null; } - // TODO: - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - const text = block.getProperty('text'); + override async block2html({ block, editor }: Block2HtmlProps) { + const textValue = block.getProperty('text'); const content = ''; - if (text) { - text.value.map(text => `${text}`).join(''); - } - // TODO: child - return `

`; + // TODO: text.value should export with style?? + const figcaption = (textValue?.value ?? []) + .map(({ text }) => `${text}`) + .join(''); + return `
${figcaption}
${figcaption}
`; } } diff --git a/libs/components/editor-blocks/src/blocks/numbered/index.ts b/libs/components/editor-blocks/src/blocks/numbered/index.ts index 37295e026f..d6eb9c1839 100644 --- a/libs/components/editor-blocks/src/blocks/numbered/index.ts +++ b/libs/components/editor-blocks/src/blocks/numbered/index.ts @@ -1,16 +1,10 @@ -import { - AsyncBlock, - BaseView, - SelectBlock, - getTextHtml, -} from '@toeverything/framework/virgo'; -import { - Protocol, - DefaultColumnsValue, -} from '@toeverything/datasource/db-service'; -// import { withTreeViewChildren } from '../../utils/with-tree-view-children'; +import { AsyncBlock, BaseView } from '@toeverything/framework/virgo'; +import { Protocol } from '@toeverything/datasource/db-service'; import { defaultTodoProps, NumberedView } from './NumberedView'; -import { IndentWrapper } from '../../components/IndentWrapper'; +import { + Block2HtmlProps, + commonBlock2HtmlContent, +} from '../../utils/commonBlockClip'; export class NumberedBlock extends BaseView { public type = Protocol.Block.Type.numbered; @@ -77,14 +71,7 @@ export class NumberedBlock extends BaseView { return null; } - - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `
  1. ${content}
`; + override async block2html(props: Block2HtmlProps) { + return `
  1. ${await commonBlock2HtmlContent(props)}
`; } } diff --git a/libs/components/editor-blocks/src/blocks/page/index.ts b/libs/components/editor-blocks/src/blocks/page/index.ts index 06151bc481..4209cbbdb6 100644 --- a/libs/components/editor-blocks/src/blocks/page/index.ts +++ b/libs/components/editor-blocks/src/blocks/page/index.ts @@ -1,20 +1,9 @@ import { withRecastBlock } from '@toeverything/components/editor-core'; -import { - Protocol, - DefaultColumnsValue, -} from '@toeverything/datasource/db-service'; -import { - AsyncBlock, - BaseView, - ChildrenView, - getTextHtml, - SelectBlock, -} from '@toeverything/framework/virgo'; +import { Protocol } from '@toeverything/datasource/db-service'; +import { AsyncBlock, BaseView } from '@toeverything/framework/virgo'; import { PageView } from './PageView'; - -export const PageChildrenView: (prop: ChildrenView) => JSX.Element = props => - props.children; +import { Block2HtmlProps } from '../../utils/commonBlockClip'; export class PageBlock extends BaseView { type = Protocol.Block.Type.page; @@ -34,13 +23,17 @@ export class PageBlock extends BaseView { return this.get_decoration(content, 'text')?.value?.[0].text; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - const content = getTextHtml(block); - const childrenContent = await generateHtml(children); - return `

${content}

${childrenContent}`; + override async block2html({ block, editor, selectInfo }: Block2HtmlProps) { + const header = + await editor.clipboard.clipboardUtils.convertTextValue2HtmlBySelectInfo( + block, + selectInfo + ); + const childrenHtml = + await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos( + block, + selectInfo?.children + ); + return `

${header}

${childrenHtml}`; } } diff --git a/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx b/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx index 94938aa595..c3613f585d 100644 --- a/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx +++ b/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx @@ -1,14 +1,13 @@ -import { - DefaultColumnsValue, - Protocol, -} from '@toeverything/datasource/db-service'; +import { Protocol } from '@toeverything/datasource/db-service'; import { AsyncBlock, BaseView, CreateView, - getTextHtml, - SelectBlock, } from '@toeverything/framework/virgo'; +import { + Block2HtmlProps, + commonBlock2HtmlContent, +} from '../../utils/commonBlockClip'; import { TextView } from './TextView'; @@ -60,14 +59,10 @@ export class QuoteBlock extends BaseView { return null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `
${content}
`; + override async block2html(props: Block2HtmlProps) { + return `
${await commonBlock2HtmlContent( + props + )}
`; } } @@ -135,13 +130,7 @@ export class CalloutBlock extends BaseView { return null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return ``; + override async block2html(props: Block2HtmlProps) { + return ``; } } diff --git a/libs/components/editor-blocks/src/blocks/text/TextBlock.tsx b/libs/components/editor-blocks/src/blocks/text/TextBlock.tsx index 74085db922..7161d0ec9e 100644 --- a/libs/components/editor-blocks/src/blocks/text/TextBlock.tsx +++ b/libs/components/editor-blocks/src/blocks/text/TextBlock.tsx @@ -2,16 +2,15 @@ import { BaseView, CreateView, AsyncBlock, - SelectBlock, - getTextHtml, } from '@toeverything/framework/virgo'; -import { - DefaultColumnsValue, - Protocol, -} from '@toeverything/datasource/db-service'; +import { Protocol } from '@toeverything/datasource/db-service'; import { TextView } from './TextView'; import { getRandomString } from '@toeverything/components/common'; +import { + Block2HtmlProps, + commonBlock2HtmlContent, +} from '../../utils/commonBlockClip'; export class TextBlock extends BaseView { type = Protocol.Block.Type.text; @@ -111,14 +110,8 @@ export class TextBlock extends BaseView { : null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `

${content}

`; + override async block2html(props: Block2HtmlProps) { + return `

${await commonBlock2HtmlContent(props)}

`; } } @@ -168,14 +161,8 @@ export class Heading1Block extends BaseView { return null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `

${content}

`; + override async block2html(props: Block2HtmlProps) { + return `

${await commonBlock2HtmlContent(props)}

`; } } @@ -225,14 +212,8 @@ export class Heading2Block extends BaseView { return null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `

${content}

`; + override async block2html(props: Block2HtmlProps) { + return `

${await commonBlock2HtmlContent(props)}

`; } } @@ -282,13 +263,7 @@ export class Heading3Block extends BaseView { return null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `

${content}

`; + override async block2html(props: Block2HtmlProps) { + return `

${await commonBlock2HtmlContent(props)}

`; } } diff --git a/libs/components/editor-blocks/src/blocks/todo/index.ts b/libs/components/editor-blocks/src/blocks/todo/index.ts index db4187a890..9ea9bbccad 100644 --- a/libs/components/editor-blocks/src/blocks/todo/index.ts +++ b/libs/components/editor-blocks/src/blocks/todo/index.ts @@ -1,18 +1,12 @@ -import { - BaseView, - AsyncBlock, - SelectBlock, - getTextHtml, -} from '@toeverything/framework/virgo'; -// import type { CreateView } from '@toeverything/framework/virgo'; -import { - Protocol, - DefaultColumnsValue, -} from '@toeverything/datasource/db-service'; -// import { withTreeViewChildren } from '../../utils/with-tree-view-children'; +import { BaseView } from '@toeverything/framework/virgo'; +import { Protocol } from '@toeverything/datasource/db-service'; import { withTreeViewChildren } from '../../utils/WithTreeViewChildren'; import { TodoView, defaultTodoProps } from './TodoView'; import type { TodoAsyncBlock } from './types'; +import { + Block2HtmlProps, + commonBlock2HtmlContent, +} from '../../utils/commonBlockClip'; export class TodoBlock extends BaseView { type = Protocol.Block.Type.todo; @@ -78,13 +72,7 @@ export class TodoBlock extends BaseView { return null; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - let content = getTextHtml(block); - content += await generateHtml(children); - return `
  • [ ] ${content}
`; + override async block2html(props: Block2HtmlProps) { + return `
  • [ ] ${await commonBlock2HtmlContent(props)}
`; } } diff --git a/libs/components/editor-blocks/src/blocks/youtube/index.ts b/libs/components/editor-blocks/src/blocks/youtube/index.ts index 94ad3ac59d..f1f6e1faa3 100644 --- a/libs/components/editor-blocks/src/blocks/youtube/index.ts +++ b/libs/components/editor-blocks/src/blocks/youtube/index.ts @@ -5,6 +5,10 @@ import { SelectBlock, } from '@toeverything/framework/virgo'; import { YoutubeView } from './YoutubeView'; +import { + Block2HtmlProps, + commonBlock2HtmlContent, +} from '../../utils/commonBlockClip'; export class YoutubeBlock extends BaseView { public override selectable = true; @@ -44,12 +48,8 @@ export class YoutubeBlock extends BaseView { override async block2Text(block: AsyncBlock, selectInfo: SelectBlock) { return block.getProperty('embedLink')?.value ?? ''; } - override async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { + override async block2html({ block }: Block2HtmlProps) { const url = block.getProperty('embedLink')?.value; - return `

${url}

`; + return `

${url}

`; } } diff --git a/libs/components/editor-blocks/src/utils/commonBlockClip.ts b/libs/components/editor-blocks/src/utils/commonBlockClip.ts new file mode 100644 index 0000000000..b381f465de --- /dev/null +++ b/libs/components/editor-blocks/src/utils/commonBlockClip.ts @@ -0,0 +1,30 @@ +import { + AsyncBlock, + BlockEditor, + SelectBlock, +} from '@toeverything/components/editor-core'; + +export type Block2HtmlProps = { + editor: BlockEditor; + block: AsyncBlock; + // The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range + selectInfo?: SelectBlock; +}; + +export const commonBlock2HtmlContent = async ({ + editor, + block, + selectInfo, +}: Block2HtmlProps) => { + const html = + await editor.clipboard.clipboardUtils.convertTextValue2HtmlBySelectInfo( + block, + selectInfo + ); + const childrenHtml = + await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos( + block, + selectInfo?.children + ); + return `${html}${childrenHtml}`; +}; diff --git a/libs/components/editor-core/src/editor/block/block-helper.ts b/libs/components/editor-core/src/editor/block/block-helper.ts index 70238c4be5..947e7eafe6 100644 --- a/libs/components/editor-core/src/editor/block/block-helper.ts +++ b/libs/components/editor-core/src/editor/block/block-helper.ts @@ -153,7 +153,7 @@ export class BlockHelper { public async getEditableBlockPropertiesBySelectInfo( block: AsyncBlock, - selectInfo: SelectBlock + selectInfo?: SelectBlock ) { const properties = block.getProperties(); if (properties.text.value.length === 0) { @@ -169,13 +169,13 @@ export class BlockHelper { // Use deepClone method will throw incomprehensible error let textValue = JSON.parse(JSON.stringify(originTextValue)); - if (selectInfo.endInfo) { + if (selectInfo?.endInfo) { textValue = textValue.slice(0, selectInfo.endInfo.arrayIndex + 1); textValue[textValue.length - 1].text = text_value[ textValue.length - 1 ].text.substring(0, selectInfo.endInfo.offset); } - if (selectInfo.startInfo) { + if (selectInfo?.startInfo) { textValue = textValue.slice(selectInfo.startInfo.arrayIndex); textValue[0].text = textValue[0].text.substring( selectInfo.startInfo.offset diff --git a/libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts b/libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts index 43b72ed6e7..04d976612d 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts @@ -1,31 +1,7 @@ import { Protocol, BlockFlavorKeys } from '@toeverything/datasource/db-service'; -import { escape } from '@toeverything/utils'; import { Editor } from '../editor'; -import { SelectBlock } from '../selection'; import { ClipBlockInfo } from './types'; -class DefaultBlockParse { - public static html2block(el: Element): ClipBlockInfo[] | undefined | null { - const tag_name = el.tagName; - if (tag_name === 'DIV' || el instanceof Text) { - return el.textContent?.split('\n').map(str => { - const data = { - text: escape(str), - }; - return { - type: 'text', - properties: { - text: { value: [data] }, - }, - children: [], - }; - }); - } - - return null; - } -} - export default class ClipboardParse { private editor: Editor; private static block_types: BlockFlavorKeys[] = [ @@ -82,7 +58,7 @@ export default class ClipboardParse { constructor(editor: Editor) { this.editor = editor; - this.generate_html = this.generate_html.bind(this); + // this.generate_html = this.generate_html.bind(this); this.parse_dom = this.parse_dom.bind(this); } // TODO: escape @@ -152,55 +128,6 @@ export default class ClipboardParse { return blocks; } - public async generateHtml(): Promise { - const select_info = await this.editor.selectionManager.getSelectInfo(); - return await this.generate_html(select_info.blocks); - } - - public async page2html(): Promise { - const root_block_id = this.editor.getRootBlockId(); - if (!root_block_id) { - return ''; - } - - const block_info = await this.get_select_info(root_block_id); - return await this.generate_html([block_info]); - } - - private async get_select_info(blockId: string) { - const block = await this.editor.getBlockById(blockId); - if (!block) return null; - const block_info: SelectBlock = { - blockId: block.id, - children: [], - }; - const children_ids = block.childrenIds; - for (let i = 0; i < children_ids.length; i++) { - block_info.children.push( - await this.get_select_info(children_ids[i]) - ); - } - return block_info; - } - - private async generate_html(selectBlocks: SelectBlock[]): Promise { - let result = ''; - for (let i = 0; i < selectBlocks.length; i++) { - const sel_block = selectBlocks[i]; - if (!sel_block || !sel_block.blockId) continue; - const block = await this.editor.getBlockById(sel_block.blockId); - if (!block) continue; - const block_utils = this.editor.getView(block.type); - const html = await block_utils.block2html( - block, - sel_block.children, - this.generate_html - ); - result += html; - } - return result; - } - public dispose() { this.editor = null; } diff --git a/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts b/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts index d50b2fbdae..f4e9665b7e 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboardUtils.ts @@ -1,5 +1,9 @@ import { Editor } from '../editor'; -import { SelectBlock, SelectInfo } from '@toeverything/components/editor-core'; +import { + AsyncBlock, + SelectBlock, + SelectInfo, +} from '@toeverything/components/editor-core'; import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types'; import { Clip } from './clip'; @@ -83,4 +87,76 @@ export class ClipboardUtils { }) ); } + + async convertTextValue2HtmlBySelectInfo( + blockOrBlockId: AsyncBlock | string, + selectBlockInfo?: SelectBlock + ) { + const block = + typeof blockOrBlockId === 'string' + ? await this._editor.getBlockById(blockOrBlockId) + : blockOrBlockId; + const selectedProperties = + await this._editor.blockHelper.getEditableBlockPropertiesBySelectInfo( + block, + selectBlockInfo + ); + return this._editor.blockHelper.convertTextValue2Html( + block.id, + selectedProperties.text.value + ); + } + async convertBlock2HtmlBySelectInfos( + blockOrBlockId: AsyncBlock | string, + selectBlockInfos?: SelectBlock[] + ) { + if (!selectBlockInfos) { + const block = + typeof blockOrBlockId === 'string' + ? await this._editor.getBlockById(blockOrBlockId) + : blockOrBlockId; + const children = await block?.children(); + return ( + await Promise.all( + children.map(async childBlock => { + const blockView = this._editor.getView(childBlock.type); + return await blockView.block2html({ + editor: this._editor, + block: childBlock, + }); + }) + ) + ).join(''); + } + + return ( + await Promise.all( + selectBlockInfos.map(async selectBlockInfo => { + const block = await this._editor.getBlockById( + selectBlockInfo.blockId + ); + const blockView = this._editor.getView(block.type); + return await blockView.block2html({ + editor: this._editor, + block, + selectInfo: selectBlockInfo, + }); + }) + ) + ).join(''); + } + + async page2html() { + const rootBlockId = this._editor.getRootBlockId(); + if (!rootBlockId) { + return ''; + } + const rootBlock = await this._editor.getBlockById(rootBlockId); + const blockView = this._editor.getView(rootBlock.type); + + return await blockView.block2html({ + editor: this._editor, + block: rootBlock, + }); + } } diff --git a/libs/components/editor-core/src/editor/clipboard/copy.ts b/libs/components/editor-core/src/editor/clipboard/copy.ts index 3056091cd3..4d89a500b6 100644 --- a/libs/components/editor-core/src/editor/clipboard/copy.ts +++ b/libs/components/editor-core/src/editor/clipboard/copy.ts @@ -50,62 +50,38 @@ class Copy { const textClip = await this._getTextClip(); clips.push(textClip); - // const htmlClip = await this._getHtmlClip(); - // clips.push(htmlClip); - const htmlClip = await this._clipboardParse.generateHtml(); - htmlClip && - clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip)); + const htmlClip = await this._getHtmlClip(); + + clips.push(htmlClip); + // const htmlClip = await this._clipboardParse.generateHtml(); + // htmlClip && + // clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip)); return clips; } - // private async _getHtmlClip(): Promise { - // const selectInfo: SelectInfo = - // await this._editor.selectionManager.getSelectInfo(); - // - // if (selectInfo.type === 'Range') { - // const html = ( - // await Promise.all( - // selectInfo.blocks.map(async selectBlockInfo => { - // const block = await this._editor.getBlockById( - // selectBlockInfo.blockId - // ); - // const blockView = this._editor.getView(block.type); - // const block2html = await blockView.block2html({ - // editor: this._editor, - // block, - // selectInfo: selectBlockInfo, - // }); - // - // if ( - // await this._editor.blockHelper.isBlockEditable( - // block - // ) - // ) { - // const selectedProperties = - // await this._editor.blockHelper.getEditableBlockPropertiesBySelectInfo( - // block, - // selectBlockInfo - // ); - // - // return ( - // block2html || - // this._editor.blockHelper.convertTextValue2Html( - // block.id, - // selectedProperties.text.value - // ) - // ); - // } - // - // return block2html; - // }) - // ) - // ).join(''); - // console.log('html', html); - // } - // - // return new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, 'blockText'); - // } + private async _getHtmlClip(): Promise { + const selectInfo: SelectInfo = + await this._editor.selectionManager.getSelectInfo(); + + const htmlStr = ( + await Promise.all( + selectInfo.blocks.map(async selectBlockInfo => { + const block = await this._editor.getBlockById( + selectBlockInfo.blockId + ); + const blockView = this._editor.getView(block.type); + return await blockView.block2html({ + editor: this._editor, + block, + selectInfo: selectBlockInfo, + }); + }) + ) + ).join(''); + + return new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlStr); + } private async _getAffineClip(): Promise { const selectInfo: SelectInfo = diff --git a/libs/components/editor-core/src/editor/editor.ts b/libs/components/editor-core/src/editor/editor.ts index 9253ab2cbe..acdf4233c5 100644 --- a/libs/components/editor-core/src/editor/editor.ts +++ b/libs/components/editor-core/src/editor/editor.ts @@ -502,13 +502,7 @@ export class Editor implements Virgo { } public async page2html(): Promise { - return ''; - // const parse = this.clipboard?.getClipboardParse(); - // if (!parse) { - // return ''; - // } - // const html_str = await parse.page2html(); - // return html_str; + return this.clipboard?.clipboardUtils?.page2html?.(); } dispose() { diff --git a/libs/components/editor-core/src/editor/index.ts b/libs/components/editor-core/src/editor/index.ts index d03326f386..2525fe5683 100644 --- a/libs/components/editor-core/src/editor/index.ts +++ b/libs/components/editor-core/src/editor/index.ts @@ -8,5 +8,5 @@ export { Editor as BlockEditor } from './editor'; export * from './selection'; export { BlockDropPlacement, HookType, GroupDirection } from './types'; export type { Plugin, PluginCreator, PluginHooks, Virgo } from './types'; -export { BaseView, getTextHtml } from './views/base-view'; +export { BaseView } from './views/base-view'; export type { ChildrenView, CreateView } from './views/base-view'; diff --git a/libs/components/editor-core/src/editor/views/base-view.ts b/libs/components/editor-core/src/editor/views/base-view.ts index f9db79fbc5..d632f6e70a 100644 --- a/libs/components/editor-core/src/editor/views/base-view.ts +++ b/libs/components/editor-core/src/editor/views/base-view.ts @@ -135,13 +135,6 @@ export abstract class BaseView { return null; } - async block2html( - block: AsyncBlock, - children: SelectBlock[], - generateHtml: (el: any[]) => Promise - ): Promise { - return ''; - } async block2Text( block: AsyncBlock, // The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range @@ -151,45 +144,12 @@ export abstract class BaseView { } // TODO: Try using new methods - // async block2html2(props: { - // editor: Editor; - // block: AsyncBlock; - // // The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range - // selectInfo?: SelectBlock; - // }) { - // return ''; - // } + async block2html(props: { + editor: Editor; + block: AsyncBlock; + // The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range + selectInfo?: SelectBlock; + }) { + return ''; + } } - -export const getTextHtml = (block: AsyncBlock) => { - const generate = (textList: any[]) => { - let content = ''; - textList.forEach(text_obj => { - let text = text_obj.text || ''; - if (text_obj.bold) { - text = `${text}`; - } - if (text_obj.italic) { - text = `${text}`; - } - if (text_obj.underline) { - text = `${text}`; - } - if (text_obj.inlinecode) { - text = `${text}`; - } - if (text_obj.strikethrough) { - text = `${text}`; - } - if (text_obj.type === 'link') { - text = `${generate( - text_obj.children - )}`; - } - content += text; - }); - return content; - }; - const text_list: any[] = block.getProperty('text').value; - return generate(text_list); -}; diff --git a/libs/components/layout/src/header/PageSettingPortal.tsx b/libs/components/layout/src/header/PageSettingPortal.tsx index 452734ff3c..9defcf754d 100644 --- a/libs/components/layout/src/header/PageSettingPortal.tsx +++ b/libs/components/layout/src/header/PageSettingPortal.tsx @@ -153,9 +153,7 @@ function PageSettingPortal() { const handleExportHtml = async () => { //@ts-ignore - const htmlContent = await virgo.clipboard - .getClipboardParse() - .page2html(); + const htmlContent = await virgo.clipboard.clipboardUtils.page2html(); const htmlTitle = pageBlock.title; FileExporter.exportHtml(htmlTitle, htmlContent); @@ -163,9 +161,7 @@ function PageSettingPortal() { const handleExportMarkdown = async () => { //@ts-ignore - const htmlContent = await virgo.clipboard - .getClipboardParse() - .page2html(); + const htmlContent = await virgo.clipboard.clipboardUtils.page2html(); const htmlTitle = pageBlock.title; FileExporter.exportMarkdown(htmlTitle, htmlContent); }; diff --git a/libs/components/layout/src/settings-sidebar/Settings/util/handle-export.ts b/libs/components/layout/src/settings-sidebar/Settings/util/handle-export.ts index fe284b986c..d329701f25 100644 --- a/libs/components/layout/src/settings-sidebar/Settings/util/handle-export.ts +++ b/libs/components/layout/src/settings-sidebar/Settings/util/handle-export.ts @@ -1,5 +1,4 @@ import { createEditor } from '@toeverything/components/affine-editor'; -import { ClipboardParse } from '@toeverything/components/editor-core'; import { fileExporter } from './file-exporter'; interface CreateClipboardParseProps { @@ -7,13 +6,12 @@ interface CreateClipboardParseProps { rootBlockId: string; } -const createClipboardParse = ({ +const createClipboardUtils = ({ workspaceId, rootBlockId, }: CreateClipboardParseProps) => { const editor = createEditor(workspaceId, rootBlockId); - - return new ClipboardParse(editor); + return editor.clipboard.clipboardUtils; }; interface ExportHandlerProps extends CreateClipboardParseProps { @@ -25,9 +23,8 @@ export const exportHtml = async ({ rootBlockId, title, }: ExportHandlerProps) => { - const clipboardParse = createClipboardParse({ workspaceId, rootBlockId }); - const htmlContent = await clipboardParse.page2html(); - fileExporter.exportHtml(title, htmlContent); + const clipboardUtils = createClipboardUtils({ workspaceId, rootBlockId }); + fileExporter.exportHtml(title, await clipboardUtils.page2html()); }; export const exportMarkdown = async ({ @@ -35,7 +32,6 @@ export const exportMarkdown = async ({ rootBlockId, title, }: ExportHandlerProps) => { - const clipboardParse = createClipboardParse({ workspaceId, rootBlockId }); - const htmlContent = await clipboardParse.page2html(); - fileExporter.exportMarkdown(title, htmlContent); + const clipboardUtils = createClipboardUtils({ workspaceId, rootBlockId }); + fileExporter.exportMarkdown(title, await clipboardUtils.page2html()); }; From 012c0441d0e96a4b6f2dead990ab69a32d064ad8 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Wed, 24 Aug 2022 14:41:21 +0800 Subject: [PATCH 4/8] refactor: recode html2block --- .../editor-blocks/src/blocks/bullet/index.ts | 78 +++--- .../editor-blocks/src/blocks/code/index.ts | 54 ++-- .../editor-blocks/src/blocks/divider/index.ts | 36 +-- .../src/blocks/embed-link/index.ts | 23 -- .../editor-blocks/src/blocks/figma/index.ts | 29 --- .../src/blocks/groupDvider/index.ts | 18 -- .../editor-blocks/src/blocks/image/index.ts | 34 ++- .../src/blocks/numbered/index.ts | 83 +++--- .../src/blocks/text/QuoteBlock.tsx | 105 +++----- .../src/blocks/text/TextBlock.tsx | 246 +++++------------- .../editor-blocks/src/blocks/todo/index.ts | 84 +++--- .../editor-blocks/src/blocks/youtube/index.ts | 29 --- .../src/utils/commonBlockClip.ts | 27 ++ libs/components/editor-core/package.json | 2 + .../src/editor/clipboard/clipboard-parse.ts | 134 ---------- .../src/editor/clipboard/clipboard.ts | 14 +- .../src/editor/clipboard/clipboardUtils.ts | 60 +++++ .../editor-core/src/editor/clipboard/copy.ts | 6 - .../editor-core/src/editor/clipboard/paste.ts | 172 +++++------- .../editor-core/src/editor/clipboard/types.ts | 7 +- .../editor-core/src/editor/clipboard/utils.ts | 150 +++++++++++ .../editor-core/src/editor/editor.ts | 8 + .../editor-core/src/editor/index.ts | 5 +- .../editor-core/src/editor/views/base-view.ts | 16 +- pnpm-lock.yaml | 101 +++++++ 25 files changed, 693 insertions(+), 828 deletions(-) delete mode 100644 libs/components/editor-core/src/editor/clipboard/clipboard-parse.ts create mode 100644 libs/components/editor-core/src/editor/clipboard/utils.ts diff --git a/libs/components/editor-blocks/src/blocks/bullet/index.ts b/libs/components/editor-blocks/src/blocks/bullet/index.ts index 1577263af3..212575da31 100644 --- a/libs/components/editor-blocks/src/blocks/bullet/index.ts +++ b/libs/components/editor-blocks/src/blocks/bullet/index.ts @@ -1,9 +1,15 @@ -import { AsyncBlock, BaseView } from '@toeverything/framework/virgo'; +import { + AsyncBlock, + BaseView, + BlockEditor, + HTML2BlockResult, +} from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { defaultBulletProps, BulletView } from './BulletView'; import { Block2HtmlProps, commonBlock2HtmlContent, + commonHTML2block, } from '../../utils/commonBlockClip'; export class BulletBlock extends BaseView { public type = Protocol.Block.Type.bullet; @@ -18,49 +24,41 @@ export class BulletBlock extends BaseView { } return block; } + override async html2block2({ + element, + editor, + }: { + element: Element; + editor: BlockEditor; + }): Promise { + if (element.tagName === 'UL') { + const firstList = element.querySelector('li'); - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'UL') { - const result = []; - for (let i = 0; i < el.children.length; i++) { - const blocks_info = parseEl(el.children[i]); - result.push(...blocks_info); + if (!firstList || firstList.innerText.startsWith('[ ] ')) { + return null; } - return result.length > 0 ? result : null; + const children = Array.from(element.children); + const childrenBlockInfos = ( + await Promise.all( + children.map(childElement => + this.html2block2({ + editor, + element: childElement, + }) + ) + ) + ) + .flat() + .filter(v => v); + return childrenBlockInfos.length ? childrenBlockInfos : null; } - if (tag_name == 'LI' && !el.textContent.startsWith('[ ] ')) { - const childNodes = el.childNodes; - const texts = []; - const children = []; - for (let i = 0; i < childNodes.length; i++) { - const blocks_info = parseEl(childNodes[i] as Element); - for (let j = 0; j < blocks_info.length; j++) { - if (blocks_info[j].type === 'text') { - const block_texts = - blocks_info[j].properties.text.value; - texts.push(...block_texts); - } else { - children.push(blocks_info[j]); - } - } - } - return [ - { - type: this.type, - properties: { - text: { value: texts }, - }, - children: children, - }, - ]; - } - - return null; + return commonHTML2block({ + element, + editor, + type: this.type, + tagName: 'LI', + }); } override async block2html(props: Block2HtmlProps) { diff --git a/libs/components/editor-blocks/src/blocks/code/index.ts b/libs/components/editor-blocks/src/blocks/code/index.ts index a204779656..306d909716 100644 --- a/libs/components/editor-blocks/src/blocks/code/index.ts +++ b/libs/components/editor-blocks/src/blocks/code/index.ts @@ -1,9 +1,15 @@ -import { BaseView, AsyncBlock } from '@toeverything/framework/virgo'; +import { + BaseView, + AsyncBlock, + BlockEditor, + HTML2BlockResult, +} from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { CodeView } from './CodeView'; import { Block2HtmlProps, commonBlock2HtmlContent, + commonHTML2block, } from '../../utils/commonBlockClip'; export class CodeBlock extends BaseView { @@ -21,39 +27,19 @@ export class CodeBlock extends BaseView { return block; } - // TODO: internal format not implemented yet - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'CODE') { - const childNodes = el.childNodes; - let text_value = ''; - for (let i = 0; i < childNodes.length; i++) { - const blocks_info = parseEl(childNodes[i] as Element); - for (let j = 0; j < blocks_info.length; j++) { - if (blocks_info[j].type === 'text') { - const block_texts = - blocks_info[j].properties.text.value; - if (block_texts.length > 0) { - text_value += block_texts[0].text; - } - } - } - } - return [ - { - type: this.type, - properties: { - text: { value: [{ text: text_value }] }, - }, - children: [], - }, - ]; - } - - return null; + override async html2block2({ + element, + editor, + }: { + element: Element; + editor: BlockEditor; + }): Promise { + return commonHTML2block({ + element, + editor, + type: this.type, + tagName: 'CODE', + }); } override async block2html(props: Block2HtmlProps) { diff --git a/libs/components/editor-blocks/src/blocks/divider/index.ts b/libs/components/editor-blocks/src/blocks/divider/index.ts index eb4615963c..14f29b8807 100644 --- a/libs/components/editor-blocks/src/blocks/divider/index.ts +++ b/libs/components/editor-blocks/src/blocks/divider/index.ts @@ -1,34 +1,34 @@ import { AsyncBlock, BaseView, + BlockEditor, + HTML2BlockResult, SelectBlock, } from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { DividerView } from './divider-view'; -import { Block2HtmlProps } from '../../utils/commonBlockClip'; +import { Block2HtmlProps, commonHTML2block } from '../../utils/commonBlockClip'; export class DividerBlock extends BaseView { type = Protocol.Block.Type.divider; View = DividerView; - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'HR') { - return [ - { - type: this.type, - properties: { - text: {}, - }, - children: [], - }, - ]; - } - return null; + override async html2block2({ + element, + editor, + }: { + element: Element; + editor: BlockEditor; + }): Promise { + return commonHTML2block({ + element, + editor, + type: this.type, + tagName: 'HR', + ignoreEmptyElement: false, + }); } + override async block2html(props: Block2HtmlProps) { return `
`; } diff --git a/libs/components/editor-blocks/src/blocks/embed-link/index.ts b/libs/components/editor-blocks/src/blocks/embed-link/index.ts index 7674c71c3e..8b6e23f527 100644 --- a/libs/components/editor-blocks/src/blocks/embed-link/index.ts +++ b/libs/components/editor-blocks/src/blocks/embed-link/index.ts @@ -13,29 +13,6 @@ export class EmbedLinkBlock extends BaseView { type = Protocol.Block.Type.embedLink; View = EmbedLinkView; - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'A' && el.parentElement?.childElementCount === 1) { - return [ - { - type: this.type, - properties: { - // TODO: Not sure what value to fill for name - embedLink: { - name: this.type, - value: el.getAttribute('href'), - }, - }, - children: [], - }, - ]; - } - - return null; - } override async block2html({ block }: Block2HtmlProps) { const url = block.getProperty('embedLink')?.value; return `

${url}

`; diff --git a/libs/components/editor-blocks/src/blocks/figma/index.ts b/libs/components/editor-blocks/src/blocks/figma/index.ts index 652abfbc1d..382429b137 100644 --- a/libs/components/editor-blocks/src/blocks/figma/index.ts +++ b/libs/components/editor-blocks/src/blocks/figma/index.ts @@ -13,35 +13,6 @@ export class FigmaBlock extends BaseView { type = Protocol.Block.Type.figma; View = FigmaView; - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'A' && el.parentElement?.childElementCount === 1) { - const href = el.getAttribute('href'); - const allowedHosts = ['www.figma.com']; - const host = new URL(href).host; - - if (allowedHosts.includes(host)) { - return [ - { - type: this.type, - properties: { - // TODO: Not sure what value to fill for name - embedLink: { - name: this.type, - value: el.getAttribute('href'), - }, - }, - children: [], - }, - ]; - } - } - - return null; - } override async block2html({ block }: Block2HtmlProps) { const figmaUrl = block.getProperty('embedLink')?.value; return `

${figmaUrl}

`; diff --git a/libs/components/editor-blocks/src/blocks/groupDvider/index.ts b/libs/components/editor-blocks/src/blocks/groupDvider/index.ts index 52940173e6..458ef2f708 100644 --- a/libs/components/editor-blocks/src/blocks/groupDvider/index.ts +++ b/libs/components/editor-blocks/src/blocks/groupDvider/index.ts @@ -10,25 +10,7 @@ import { Block2HtmlProps } from '../../utils/commonBlockClip'; export class GroupDividerBlock extends BaseView { type = Protocol.Block.Type.groupDivider; View = GroupDividerView; - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'HR') { - return [ - { - type: this.type, - properties: { - text: {}, - }, - children: [], - }, - ]; - } - return null; - } override async block2html(props: Block2HtmlProps) { return `
`; } diff --git a/libs/components/editor-blocks/src/blocks/image/index.ts b/libs/components/editor-blocks/src/blocks/image/index.ts index 190f83d72a..844ae43cb2 100644 --- a/libs/components/editor-blocks/src/blocks/image/index.ts +++ b/libs/components/editor-blocks/src/blocks/image/index.ts @@ -1,7 +1,12 @@ -import { BaseView } from '@toeverything/framework/virgo'; +import { + BaseView, + BlockEditor, + HTML2BlockResult, +} from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { ImageView } from './ImageView'; import { Block2HtmlProps } from '../../utils/commonBlockClip'; +import { getRandomString } from '@toeverything/components/common'; export class ImageBlock extends BaseView { public override selectable = true; @@ -10,27 +15,30 @@ export class ImageBlock extends BaseView { View = ImageView; // TODO: needs to download the image and then upload it to get a new link and then assign it - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'IMG') { + override async html2block2({ + element, + editor, + }: { + element: Element; + editor: BlockEditor; + }): Promise { + if (element.tagName === 'IMG') { return [ { type: this.type, properties: { - value: '', - url: el.getAttribute('src'), - name: el.getAttribute('src'), - size: 0, - type: 'link', + image: { + value: getRandomString('image'), + url: element.getAttribute('src'), + name: element.getAttribute('src'), + size: 0, + type: 'link', + }, }, children: [], }, ]; } - return null; } diff --git a/libs/components/editor-blocks/src/blocks/numbered/index.ts b/libs/components/editor-blocks/src/blocks/numbered/index.ts index d6eb9c1839..5c3f8a4e4e 100644 --- a/libs/components/editor-blocks/src/blocks/numbered/index.ts +++ b/libs/components/editor-blocks/src/blocks/numbered/index.ts @@ -1,9 +1,15 @@ -import { AsyncBlock, BaseView } from '@toeverything/framework/virgo'; +import { + AsyncBlock, + BaseView, + BlockEditor, + HTML2BlockResult, +} from '@toeverything/framework/virgo'; import { Protocol } from '@toeverything/datasource/db-service'; import { defaultTodoProps, NumberedView } from './NumberedView'; import { Block2HtmlProps, commonBlock2HtmlContent, + commonHTML2block, } from '../../utils/commonBlockClip'; export class NumberedBlock extends BaseView { @@ -22,55 +28,38 @@ export class NumberedBlock extends BaseView { return block; } - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'OL') { - const result = []; - for (let i = 0; i < el.children.length; i++) { - const blocks_info = parseEl(el.children[i]); - result.push(...blocks_info); - } - return result.length > 0 ? result : null; + override async html2block2({ + element, + editor, + }: { + element: Element; + editor: BlockEditor; + }): Promise { + if (element.tagName === 'OL') { + const children = Array.from(element.children); + const childrenBlockInfos = ( + await Promise.all( + children.map(childElement => + this.html2block2({ + editor, + element: childElement, + }) + ) + ) + ) + .flat() + .filter(v => v); + return childrenBlockInfos.length ? childrenBlockInfos : null; } - if (tag_name == 'LI' && el.textContent.startsWith('[ ] ')) { - const childNodes = el.childNodes; - let texts = []; - const children = []; - for (let i = 0; i < childNodes.length; i++) { - const blocks_info = parseEl(childNodes[i] as Element); - for (let j = 0; j < blocks_info.length; j++) { - if (blocks_info[j].type === 'text') { - const block_texts = - blocks_info[j].properties.text.value; - texts.push(...block_texts); - } else { - children.push(blocks_info[j]); - } - } - } - if (texts.length > 0 && (texts[0].text || '').startsWith('[ ] ')) { - texts[0].text = texts[0].text.substring('[ ] '.length); - if (!texts[0].text) { - texts = texts.slice(1); - } - } - return [ - { - type: this.type, - properties: { - text: { value: texts }, - }, - children: children, - }, - ]; - } - - return null; + return commonHTML2block({ + element, + editor, + type: this.type, + tagName: 'LI', + }); } + override async block2html(props: Block2HtmlProps) { return `
  1. ${await commonBlock2HtmlContent(props)}
`; } diff --git a/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx b/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx index c3613f585d..51f63e3dec 100644 --- a/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx +++ b/libs/components/editor-blocks/src/blocks/text/QuoteBlock.tsx @@ -2,11 +2,14 @@ import { Protocol } from '@toeverything/datasource/db-service'; import { AsyncBlock, BaseView, + BlockEditor, CreateView, + HTML2BlockResult, } from '@toeverything/framework/virgo'; import { Block2HtmlProps, commonBlock2HtmlContent, + commonHTML2block, } from '../../utils/commonBlockClip'; import { TextView } from './TextView'; @@ -27,36 +30,19 @@ export class QuoteBlock extends BaseView { return block; } - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if (tag_name === 'BLOCKQUOTE') { - const childNodes = el.childNodes; - const texts = []; - for (let i = 0; i < childNodes.length; i++) { - const blocks_info = parseEl(childNodes[i] as Element); - for (let j = 0; j < blocks_info.length; j++) { - if (blocks_info[j].type === 'text') { - const block_texts = - blocks_info[j].properties.text.value; - texts.push(...block_texts); - } - } - } - return [ - { - type: this.type, - properties: { - text: { value: texts }, - }, - children: [], - }, - ]; - } - - return null; + override async html2block2({ + element, + editor, + }: { + element: Element; + editor: BlockEditor; + }): Promise { + return commonHTML2block({ + element, + editor, + type: this.type, + tagName: 'BLOCKQUOTE', + }); } override async block2html(props: Block2HtmlProps) { @@ -82,52 +68,19 @@ export class CalloutBlock extends BaseView { return block; } - override html2block( - el: Element, - parseEl: (el: Element) => any[] - ): any[] | null { - const tag_name = el.tagName; - if ( - tag_name === 'ASIDE' || - el.firstChild?.nodeValue?.startsWith('