From c74b03da9ae8db218daff0660f74716b1838733d Mon Sep 17 00:00:00 2001 From: alt0 Date: Tue, 11 Oct 2022 17:10:38 +0800 Subject: [PATCH] fix: shortcut for cut --- .../common/src/lib/text/slate-utils.ts | 9 + .../src/editor/block/block-helper.ts | 31 ++- .../src/editor/clipboard/clipboard.ts | 10 +- .../editor-core/src/editor/clipboard/cut.ts | 183 ++++++++++++++++++ 4 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 libs/components/editor-core/src/editor/clipboard/cut.ts diff --git a/libs/components/common/src/lib/text/slate-utils.ts b/libs/components/common/src/lib/text/slate-utils.ts index 2f7684b547..65bb65a164 100644 --- a/libs/components/common/src/lib/text/slate-utils.ts +++ b/libs/components/common/src/lib/text/slate-utils.ts @@ -1138,6 +1138,15 @@ class SlateUtils { }); } + public removeBetweenPoints(startPoint: Point, endPoint: Point) { + const at = { + anchor: startPoint, + focus: endPoint, + }; + Transforms.select(this.editor, at); + this.editor.deleteFragment(); + } + public setDoubleLinkSearchSlash(point: Point) { const str = Editor.string(this.editor, { anchor: this.getStart(), 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 9e3090b527..7d5f3bb98a 100644 --- a/libs/components/editor-core/src/editor/block/block-helper.ts +++ b/libs/components/editor-core/src/editor/block/block-helper.ts @@ -13,7 +13,7 @@ import { } from 'slate'; import { AsyncBlock } from '../block'; import { Editor } from '../editor'; -import { SelectBlock } from '../selection'; +import { SelectBlock, type SelectInfo } from '../selection'; type TextUtilsFunctions = | 'getString' @@ -40,6 +40,7 @@ type TextUtilsFunctions = | 'getCommentsIdsBySelection' | 'getCurrentSelection' | 'removeSelection' + | 'removeBetweenPoints' | 'isCollapsed' | 'blur' | 'setSelection' @@ -528,4 +529,32 @@ export class BlockHelper { console.warn('Could find the block text utils'); return undefined; } + + public removeSelection(selection: SelectInfo) { + if (selection.type === 'Range') { + selection.blocks.forEach(async selectBlockInfo => { + const text_utils = + this._blockTextUtilsMap[selectBlockInfo.blockId]; + text_utils.removeBetweenPoints( + { + path: [0, selectBlockInfo.startInfo.arrayIndex], + offset: selectBlockInfo.startInfo.offset, + }, + { + path: [0, selectBlockInfo.endInfo.arrayIndex], + offset: selectBlockInfo.endInfo.offset, + } + ); + }); + } else if (selection.type === 'Block') { + selection.blocks.forEach(async selectBlockInfo => { + ( + await this._editor.getBlock({ + workspace: this._editor.workspace, + id: selectBlockInfo.blockId, + }) + ).remove(); + }); + } + } } diff --git a/libs/components/editor-core/src/editor/clipboard/clipboard.ts b/libs/components/editor-core/src/editor/clipboard/clipboard.ts index e5e869ad1c..f364cfaa52 100644 --- a/libs/components/editor-core/src/editor/clipboard/clipboard.ts +++ b/libs/components/editor-core/src/editor/clipboard/clipboard.ts @@ -1,7 +1,8 @@ -import { ClipboardEventDispatcher } from './clipboardEventDispatcher'; -import { HookType } from '../types'; import { Editor } from '../editor'; +import { HookType } from '../types'; +import { ClipboardEventDispatcher } from './clipboardEventDispatcher'; import { Copy } from './copy'; +import { Cut } from './cut'; import { Paste } from './paste'; import { ClipboardUtils } from './clipboardUtils'; @@ -9,6 +10,7 @@ import { ClipboardUtils } from './clipboardUtils'; export class Clipboard { private _clipboardEventDispatcher: ClipboardEventDispatcher; private _copy: Copy; + private _cut: Cut; private _paste: Paste; public clipboardUtils: ClipboardUtils; private _clipboardTarget: HTMLElement; @@ -17,7 +19,7 @@ export class Clipboard { this.clipboardUtils = new ClipboardUtils(editor); this._clipboardTarget = clipboardTarget; this._copy = new Copy(editor); - + this._cut = new Cut(editor); this._paste = new Paste(editor); this._clipboardEventDispatcher = new ClipboardEventDispatcher( @@ -30,7 +32,7 @@ export class Clipboard { .get(HookType.ON_COPY) .subscribe(this._copy.handleCopy); - editor.getHooks().get(HookType.ON_CUT).subscribe(this._copy.handleCopy); + editor.getHooks().get(HookType.ON_CUT).subscribe(this._cut.handleCut); editor .getHooks() diff --git a/libs/components/editor-core/src/editor/clipboard/cut.ts b/libs/components/editor-core/src/editor/clipboard/cut.ts new file mode 100644 index 0000000000..4019b1707f --- /dev/null +++ b/libs/components/editor-core/src/editor/clipboard/cut.ts @@ -0,0 +1,183 @@ +import { Editor } from '../editor'; +import { SelectInfo } from '../selection'; +import { Clip } from './clip'; +import { ClipboardUtils } from './clipboardUtils'; +import { OFFICE_CLIPBOARD_MIMETYPE } from './types'; +class Cut { + private _editor: Editor; + private _utils: ClipboardUtils; + constructor(editor: Editor) { + this._editor = editor; + this._utils = new ClipboardUtils(editor); + this.handleCut = this.handleCut.bind(this); + } + + public async handleCut(e: ClipboardEvent) { + e.preventDefault(); + e.stopPropagation(); + const selectInfo: SelectInfo = + await this._editor.selectionManager.getSelectInfo(); + + const clips = await this.getClips(selectInfo); + if (!clips.length) { + return; + } + const success = this._copyToClipboardFromPc(clips); + if (!success) { + // This way, not compatible with firefox + const clipboardData = e.clipboardData; + if (clipboardData) { + try { + clips.forEach(clip => { + clipboardData.setData( + clip.getMimeType(), + clip.getData() + ); + }); + } catch (e) { + // TODO handle exception + } + } + } + + this._editor.blockHelper.removeSelection(selectInfo); + } + + async getClips(selectInfo: SelectInfo) { + const clips: Clip[] = []; + + // get custom clip + const affineClip = await this._getAffineClip(selectInfo); + clips.push(affineClip); + + const textClip = await this._getTextClip(selectInfo); + clips.push(textClip); + + const htmlClip = await this._getHtmlClip(selectInfo); + + clips.push(htmlClip); + + return clips; + } + + private async _getHtmlClip(selectInfo: SelectInfo): Promise { + 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(selectInfo: SelectInfo): Promise { + 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(selectInfo: SelectInfo): Promise { + 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'); + tempElem.value = 'temp'; + document.body.appendChild(tempElem); + tempElem.select(); + tempElem.setSelectionRange(0, tempElem.value.length); + + const listener = function (e: any) { + const clipboardData = e.clipboardData; + if (clipboardData) { + clips.forEach(clip => { + clipboardData.setData(clip.getMimeType(), clip.getData()); + }); + } + + e.preventDefault(); + e.stopPropagation(); + tempElem.removeEventListener('copy', listener); + } as any; + + tempElem.addEventListener('copy', listener); + try { + success = document.execCommand('copy'); + } finally { + tempElem.removeEventListener('copy', listener); + document.body.removeChild(tempElem); + } + return success; + } +} + +export { Cut };