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[]) => {