refactor: refactor clipboard

This commit is contained in:
QiShaoXuan 2022-08-21 22:58:41 +08:00
parent 66a36481e1
commit 17e454b1e6
24 changed files with 592 additions and 346 deletions

View File

@ -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 `<strong ${style}>${text}</strong>`;
}
if (textValue.italic) {
return `<em ${style}>${text}</em>`;
}
if (textValue.underline) {
return `<u ${style}>${text}</u>`;
}
if (textValue.inlinecode) {
return `<code ${style}>${text}</code>`;
}
if (textValue.strikethrough) {
return `<s ${style}>${text}</s>`;
}
if (textValue.type === 'link') {
return `<a href='${textValue.url}' ${style}>${this.convertLeaf2Html(
textValue.children
)}</a>`;
}
return `<span ${style}>${text}</span>>`;
}
public getStartSelection() {

View File

@ -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[]

View File

@ -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,

View File

@ -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[]

View File

@ -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<any>(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[],

View File

@ -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[]

View File

@ -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[]

View File

@ -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[]

View File

@ -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[],

View File

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

View File

@ -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 };

View File

@ -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 };

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 = `<strong>${text}</strong>`;
}
if (text_obj.italic) {
text = `<em>${text}</em>`;
}
if (text_obj.underline) {
text = `<u>${text}</u>`;
}
if (text_obj.inlinecode) {
text = `<code>${text}</code>`;
}
if (text_obj.strikethrough) {
text = `<s>${text}</s>`;
}
if (text_obj.type === 'link') {
text = `<a href='${text_obj.url}'>${generate(
text_obj.children
)}</a>`;
}
content += text;
});
return content;
};
const text_list: any[] = block.getProperty('text').value;
return generate(text_list);
}
}

View File

@ -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<Clip> {
// 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<Clip> {
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<Clip> {
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');

View File

@ -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) {

View File

@ -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()) ?? [];

View File

@ -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<string> {
const parse = this.clipboard?.getClipboardParse();
if (!parse) {
return '';
}
const html_str = await parse.page2html();
return html_str;
// 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();
}
}

View File

@ -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';

View File

@ -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 {

View File

@ -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<Element>) => void;
onRootNodeDragLeave: (e: React.DragEvent<Element>) => void;
onRootNodeDrop: (e: React.DragEvent<Element>) => 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;
}

View File

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