mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-06 08:50:50 +08:00
refactor: refactor clipboard
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
|||||||
MARKDOWN_STYLE_MAP,
|
MARKDOWN_STYLE_MAP,
|
||||||
MatchRes,
|
MatchRes,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
import { AsyncBlock, SelectBlock } from '@toeverything/components/editor-core';
|
||||||
|
|
||||||
function isInlineAndVoid(editor: Editor, el: any) {
|
function isInlineAndVoid(editor: Editor, el: any) {
|
||||||
return editor.isInline(el) && editor.isVoid(el);
|
return editor.isInline(el) && editor.isVoid(el);
|
||||||
@@ -958,7 +959,41 @@ class SlateUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getNodeByPath(path: Path) {
|
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() {
|
public getStartSelection() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
BaseView,
|
BaseView,
|
||||||
CreateView,
|
CreateView,
|
||||||
getTextProperties,
|
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
getTextHtml,
|
getTextHtml,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
@@ -28,14 +27,6 @@ export class BulletBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: AsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BaseView,
|
BaseView,
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
getTextProperties,
|
|
||||||
CreateView,
|
CreateView,
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
getTextHtml,
|
getTextHtml,
|
||||||
@@ -28,14 +27,6 @@ export class CodeBlock extends BaseView {
|
|||||||
return block;
|
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
|
// TODO: internal format not implemented yet
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
BaseView,
|
BaseView,
|
||||||
getTextProperties,
|
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
getTextHtml,
|
getTextHtml,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
@@ -29,14 +28,6 @@ export class NumberedBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: AsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
BaseView,
|
BaseView,
|
||||||
ChildrenView,
|
ChildrenView,
|
||||||
getTextHtml,
|
getTextHtml,
|
||||||
getTextProperties,
|
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
|
|
||||||
@@ -35,14 +34,6 @@ export class PageBlock extends BaseView {
|
|||||||
return this.get_decoration<any>(content, 'text')?.value?.[0].text;
|
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(
|
override async block2html(
|
||||||
block: AsyncBlock,
|
block: AsyncBlock,
|
||||||
children: SelectBlock[],
|
children: SelectBlock[],
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
BaseView,
|
BaseView,
|
||||||
CreateView,
|
CreateView,
|
||||||
getTextHtml,
|
getTextHtml,
|
||||||
getTextProperties,
|
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
|
|
||||||
@@ -29,14 +28,6 @@ export class QuoteBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: AsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
@@ -96,14 +87,6 @@ export class CalloutBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: AsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
BaseView,
|
BaseView,
|
||||||
CreateView,
|
CreateView,
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
getTextProperties,
|
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
getTextHtml,
|
getTextHtml,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
@@ -28,14 +27,6 @@ export class TextBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: AsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
@@ -145,14 +136,6 @@ export class Heading1Block extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: AsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
@@ -210,14 +193,6 @@ export class Heading2Block extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: AsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
@@ -275,14 +250,6 @@ export class Heading3Block extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: AsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
BaseView,
|
BaseView,
|
||||||
getTextProperties,
|
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
getTextHtml,
|
getTextHtml,
|
||||||
@@ -29,14 +28,6 @@ export class TodoBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSelProperties(
|
|
||||||
block: TodoAsyncBlock,
|
|
||||||
selectInfo: any
|
|
||||||
): DefaultColumnsValue {
|
|
||||||
const properties = super.getSelProperties(block, selectInfo);
|
|
||||||
return getTextProperties(properties, selectInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
override html2block(
|
override html2block(
|
||||||
el: Element,
|
el: Element,
|
||||||
parseEl: (el: Element) => any[]
|
parseEl: (el: Element) => any[]
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export class YoutubeBlock extends BaseView {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
override async block2Text(block: AsyncBlock, selectInfo: SelectBlock) {
|
||||||
|
return block.getProperty('embedLink')?.value ?? '';
|
||||||
|
}
|
||||||
override async block2html(
|
override async block2html(
|
||||||
block: AsyncBlock,
|
block: AsyncBlock,
|
||||||
children: SelectBlock[],
|
children: SelectBlock[],
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import {
|
|||||||
Selection as SlateSelection,
|
Selection as SlateSelection,
|
||||||
} from 'slate';
|
} from 'slate';
|
||||||
import { Editor } from '../editor';
|
import { Editor } from '../editor';
|
||||||
|
import {
|
||||||
|
AsyncBlock,
|
||||||
|
SelectBlock,
|
||||||
|
SelectInfo,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
|
|
||||||
type TextUtilsFunctions =
|
type TextUtilsFunctions =
|
||||||
| 'getString'
|
| 'getString'
|
||||||
@@ -37,7 +42,9 @@ type TextUtilsFunctions =
|
|||||||
| 'blur'
|
| 'blur'
|
||||||
| 'setSelection'
|
| 'setSelection'
|
||||||
| 'insertNodes'
|
| 'insertNodes'
|
||||||
| 'getNodeByPath';
|
| 'getNodeByPath'
|
||||||
|
| 'getNodeByRange'
|
||||||
|
| 'convertLeaf2Html';
|
||||||
|
|
||||||
type ExtendedTextUtils = SlateUtils & {
|
type ExtendedTextUtils = SlateUtils & {
|
||||||
setLinkModalVisible: (visible: boolean) => void;
|
setLinkModalVisible: (visible: boolean) => void;
|
||||||
@@ -98,15 +105,116 @@ export class BlockHelper {
|
|||||||
return '';
|
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];
|
const text_utils = this._blockTextUtilsMap[blockId];
|
||||||
if (text_utils) {
|
if (text_utils) {
|
||||||
return text_utils.getStringBetweenSelection(true);
|
return text_utils.getStringBetweenSelection(
|
||||||
|
shouldUsePreviousSelection
|
||||||
|
);
|
||||||
}
|
}
|
||||||
console.warn('Could find the block text utils');
|
console.warn('Could find the block text utils');
|
||||||
return '';
|
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) {
|
public setBlockBlur(blockId: string) {
|
||||||
const text_utils = this._blockTextUtilsMap[blockId];
|
const text_utils = this._blockTextUtilsMap[blockId];
|
||||||
if (text_utils) {
|
if (text_utils) {
|
||||||
|
|||||||
@@ -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 };
|
|
||||||
@@ -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 };
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,15 @@ import { OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
|||||||
import { Clip } from './clip';
|
import { Clip } from './clip';
|
||||||
import ClipboardParse from './clipboard-parse';
|
import ClipboardParse from './clipboard-parse';
|
||||||
import { getClipDataOfBlocksById } from './utils';
|
import { getClipDataOfBlocksById } from './utils';
|
||||||
import { copyToClipboard } from '@toeverything/utils';
|
import { ClipboardUtils } from './clipboardUtils';
|
||||||
class Copy {
|
class Copy {
|
||||||
private _editor: Editor;
|
private _editor: Editor;
|
||||||
private _clipboardParse: ClipboardParse;
|
private _clipboardParse: ClipboardParse;
|
||||||
|
private _utils: ClipboardUtils;
|
||||||
constructor(editor: Editor) {
|
constructor(editor: Editor) {
|
||||||
this._editor = editor;
|
this._editor = editor;
|
||||||
this._clipboardParse = new ClipboardParse(editor);
|
this._clipboardParse = new ClipboardParse(editor);
|
||||||
|
this._utils = new ClipboardUtils(editor);
|
||||||
this.handleCopy = this.handleCopy.bind(this);
|
this.handleCopy = this.handleCopy.bind(this);
|
||||||
}
|
}
|
||||||
public async handleCopy(e: ClipboardEvent) {
|
public async handleCopy(e: ClipboardEvent) {
|
||||||
@@ -22,7 +22,6 @@ class Copy {
|
|||||||
if (!clips.length) {
|
if (!clips.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: is not compatible with safari
|
|
||||||
const success = this._copyToClipboardFromPc(clips);
|
const success = this._copyToClipboardFromPc(clips);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
// This way, not compatible with firefox
|
// This way, not compatible with firefox
|
||||||
@@ -49,7 +48,11 @@ class Copy {
|
|||||||
const affineClip = await this._getAffineClip();
|
const affineClip = await this._getAffineClip();
|
||||||
clips.push(affineClip);
|
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();
|
const htmlClip = await this._clipboardParse.generateHtml();
|
||||||
htmlClip &&
|
htmlClip &&
|
||||||
clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip));
|
clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip));
|
||||||
@@ -57,16 +60,128 @@ class Copy {
|
|||||||
return clips;
|
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> {
|
private async _getAffineClip(): Promise<Clip> {
|
||||||
const selectInfo: SelectInfo =
|
const selectInfo: SelectInfo =
|
||||||
await this._editor.selectionManager.getSelectInfo();
|
await this._editor.selectionManager.getSelectInfo();
|
||||||
|
|
||||||
return getClipDataOfBlocksById(
|
if (selectInfo.type === 'Range') {
|
||||||
this._editor,
|
return this._utils.getClipDataOfBlocksBySelectInfo(selectInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The only remaining case is that selectInfo.type === 'Block'
|
||||||
|
return this._utils.getClipDataOfBlocksById(
|
||||||
selectInfo.blocks.map(block => block.blockId)
|
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[]) {
|
private _copyToClipboardFromPc(clips: any[]) {
|
||||||
let success = false;
|
let success = false;
|
||||||
const tempElem = document.createElement('textarea');
|
const tempElem = document.createElement('textarea');
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ export class Paste {
|
|||||||
OFFICE_CLIPBOARD_MIMETYPE.HTML,
|
OFFICE_CLIPBOARD_MIMETYPE.HTML,
|
||||||
OFFICE_CLIPBOARD_MIMETYPE.TEXT,
|
OFFICE_CLIPBOARD_MIMETYPE.TEXT,
|
||||||
];
|
];
|
||||||
public handlePaste(e: Event) {
|
public handlePaste(e: ClipboardEvent) {
|
||||||
if (!shouldHandlerContinue(e, this._editor)) {
|
if (!shouldHandlerContinue(e, this._editor)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const clipboardData = (e as ClipboardEvent).clipboardData;
|
const clipboardData = e.clipboardData;
|
||||||
|
|
||||||
const isPureFile = Paste._isPureFileInClipboard(clipboardData);
|
const isPureFile = Paste._isPureFileInClipboard(clipboardData);
|
||||||
if (isPureFile) {
|
if (isPureFile) {
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { Editor } from '../editor';
|
|||||||
import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
||||||
import { Clip } from './clip';
|
import { Clip } from './clip';
|
||||||
|
|
||||||
export const shouldHandlerContinue = (event: Event, editor: Editor) => {
|
export const shouldHandlerContinue = (
|
||||||
|
event: ClipboardEvent,
|
||||||
|
editor: Editor
|
||||||
|
) => {
|
||||||
const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA'];
|
const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA'];
|
||||||
|
|
||||||
if (event.defaultPrevented) {
|
if (event.defaultPrevented) {
|
||||||
@@ -20,10 +23,9 @@ export const getClipInfoOfBlockById = async (
|
|||||||
blockId: string
|
blockId: string
|
||||||
) => {
|
) => {
|
||||||
const block = await editor.getBlockById(blockId);
|
const block = await editor.getBlockById(blockId);
|
||||||
const blockView = editor.getView(block.type);
|
|
||||||
const blockInfo: ClipBlockInfo = {
|
const blockInfo: ClipBlockInfo = {
|
||||||
type: block.type,
|
type: block.type,
|
||||||
properties: blockView.getSelProperties(block, {}),
|
properties: block?.getProperties(),
|
||||||
children: [] as ClipBlockInfo[],
|
children: [] as ClipBlockInfo[],
|
||||||
};
|
};
|
||||||
const children = (await block?.children()) ?? [];
|
const children = (await block?.children()) ?? [];
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import assert from 'assert';
|
|||||||
import type { WorkspaceAndBlockId } from './block';
|
import type { WorkspaceAndBlockId } from './block';
|
||||||
import { AsyncBlock } from './block';
|
import { AsyncBlock } from './block';
|
||||||
import { BlockHelper } from './block/block-helper';
|
import { BlockHelper } from './block/block-helper';
|
||||||
import { BrowserClipboard } from './clipboard/browser-clipboard';
|
import { Clipboard } from './clipboard/clipboard';
|
||||||
import { ClipboardPopulator } from './clipboard/clipboard-populator';
|
|
||||||
import { EditorCommands } from './commands';
|
import { EditorCommands } from './commands';
|
||||||
import { EditorConfig } from './config';
|
import { EditorConfig } from './config';
|
||||||
import { DragDropManager } from './drag-drop';
|
import { DragDropManager } from './drag-drop';
|
||||||
@@ -65,8 +64,9 @@ export class Editor implements Virgo {
|
|||||||
readonly = false;
|
readonly = false;
|
||||||
private _rootBlockId: string;
|
private _rootBlockId: string;
|
||||||
private storage_manager?: StorageManager;
|
private storage_manager?: StorageManager;
|
||||||
private clipboard?: BrowserClipboard;
|
private _clipboard: Clipboard;
|
||||||
private clipboard_populator?: ClipboardPopulator;
|
// private clipboardActionDispacher?: ClipboardEventDispatcher;
|
||||||
|
// private clipboard_populator?: ClipboardPopulator;
|
||||||
public reactRenderRoot: {
|
public reactRenderRoot: {
|
||||||
render: PatchNode;
|
render: PatchNode;
|
||||||
has: (key: string) => boolean;
|
has: (key: string) => boolean;
|
||||||
@@ -138,7 +138,7 @@ export class Editor implements Virgo {
|
|||||||
|
|
||||||
public set container(v: HTMLDivElement) {
|
public set container(v: HTMLDivElement) {
|
||||||
this.ui_container = v;
|
this.ui_container = v;
|
||||||
this._initClipboard();
|
this._clipboard = new Clipboard(this, this.ui_container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get container() {
|
public get container() {
|
||||||
@@ -191,26 +191,26 @@ export class Editor implements Virgo {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _disposeClipboard() {
|
// private _disposeClipboard() {
|
||||||
this.clipboard?.dispose();
|
// this.clipboard?.dispose();
|
||||||
this.clipboard_populator?.disposeInternal();
|
// this.clipboard_populator?.disposeInternal();
|
||||||
}
|
// }
|
||||||
|
|
||||||
private _initClipboard() {
|
// private _initClipboard() {
|
||||||
this._disposeClipboard();
|
// this._disposeClipboard();
|
||||||
if (this.ui_container && !this._isDisposed) {
|
// if (this.ui_container && !this._isDisposed) {
|
||||||
this.clipboard = new BrowserClipboard(
|
// this.clipboardActionDispacher = new ClipboardEventDispatcher({
|
||||||
this.ui_container,
|
// clipboardTarget: this.ui_container,
|
||||||
this.hooks,
|
// hooks: this.hooks,
|
||||||
this
|
// editor: this,
|
||||||
);
|
// });
|
||||||
this.clipboard_populator = new ClipboardPopulator(
|
// this.clipboard_populator = new ClipboardPopulator(
|
||||||
this,
|
// this,
|
||||||
this.hooks,
|
// this.hooks,
|
||||||
this.selectionManager
|
// this.selectionManager
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/** Root Block Id */
|
/** Root Block Id */
|
||||||
getRootBlockId() {
|
getRootBlockId() {
|
||||||
@@ -498,12 +498,13 @@ export class Editor implements Virgo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async page2html(): Promise<string> {
|
public async page2html(): Promise<string> {
|
||||||
const parse = this.clipboard?.getClipboardParse();
|
return '';
|
||||||
if (!parse) {
|
// const parse = this.clipboard?.getClipboardParse();
|
||||||
return '';
|
// if (!parse) {
|
||||||
}
|
// return '';
|
||||||
const html_str = await parse.page2html();
|
// }
|
||||||
return html_str;
|
// const html_str = await parse.page2html();
|
||||||
|
// return html_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
@@ -519,6 +520,6 @@ export class Editor implements Virgo {
|
|||||||
this.plugin_manager.dispose();
|
this.plugin_manager.dispose();
|
||||||
this.selectionManager.dispose();
|
this.selectionManager.dispose();
|
||||||
this.dragDropManager.dispose();
|
this.dragDropManager.dispose();
|
||||||
this._disposeClipboard();
|
this._clipboard?.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ export { Editor as BlockEditor } from './editor';
|
|||||||
export * from './selection';
|
export * from './selection';
|
||||||
export { BlockDropPlacement, HookType, GroupDirection } from './types';
|
export { BlockDropPlacement, HookType, GroupDirection } from './types';
|
||||||
export type { Plugin, PluginCreator, PluginHooks, Virgo } 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 type { ChildrenView, CreateView } from './views/base-view';
|
||||||
export { getClipDataOfBlocksById } from './clipboard/utils';
|
export { getClipDataOfBlocksById } from './clipboard/utils';
|
||||||
|
|||||||
@@ -116,12 +116,15 @@ export class Hooks implements HooksRunner, PluginHooks {
|
|||||||
this._runHook(HookType.ON_SEARCH);
|
this._runHook(HookType.ON_SEARCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public beforeCopy(e: ClipboardEvent): void {
|
public onCopy(e: ClipboardEvent): void {
|
||||||
this._runHook(HookType.BEFORE_COPY, e);
|
this._runHook(HookType.ON_COPY, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public beforeCut(e: ClipboardEvent): void {
|
public onCut(e: ClipboardEvent): void {
|
||||||
this._runHook(HookType.BEFORE_CUT, e);
|
this._runHook(HookType.ON_CUT, e);
|
||||||
|
}
|
||||||
|
public onPaste(e: ClipboardEvent): void {
|
||||||
|
this._runHook(HookType.ON_PASTE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRootNodeScroll(e: React.UIEvent): void {
|
public onRootNodeScroll(e: React.UIEvent): void {
|
||||||
|
|||||||
@@ -174,8 +174,9 @@ export enum HookType {
|
|||||||
ON_ROOTNODE_DRAG_END = 'onRootNodeDragEnd',
|
ON_ROOTNODE_DRAG_END = 'onRootNodeDragEnd',
|
||||||
ON_ROOTNODE_DRAG_OVER_CAPTURE = 'onRootNodeDragOverCapture',
|
ON_ROOTNODE_DRAG_OVER_CAPTURE = 'onRootNodeDragOverCapture',
|
||||||
ON_ROOTNODE_DROP = 'onRootNodeDrop',
|
ON_ROOTNODE_DROP = 'onRootNodeDrop',
|
||||||
BEFORE_COPY = 'beforeCopy',
|
ON_COPY = 'onCopy',
|
||||||
BEFORE_CUT = 'beforeCut',
|
ON_CUT = 'onCut',
|
||||||
|
ON_PASTE = 'onPaste',
|
||||||
ON_ROOTNODE_SCROLL = 'onRootNodeScroll',
|
ON_ROOTNODE_SCROLL = 'onRootNodeScroll',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +208,9 @@ export interface HooksRunner {
|
|||||||
onRootNodeDragEnd: (e: React.DragEvent<Element>) => void;
|
onRootNodeDragEnd: (e: React.DragEvent<Element>) => void;
|
||||||
onRootNodeDragLeave: (e: React.DragEvent<Element>) => void;
|
onRootNodeDragLeave: (e: React.DragEvent<Element>) => void;
|
||||||
onRootNodeDrop: (e: React.DragEvent<Element>) => void;
|
onRootNodeDrop: (e: React.DragEvent<Element>) => void;
|
||||||
beforeCopy: (e: ClipboardEvent) => void;
|
onCopy: (e: ClipboardEvent) => void;
|
||||||
beforeCut: (e: ClipboardEvent) => void;
|
onCut: (e: ClipboardEvent) => void;
|
||||||
|
onPaste: (e: ClipboardEvent) => void;
|
||||||
onRootNodeScroll: (e: React.UIEvent) => void;
|
onRootNodeScroll: (e: React.UIEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,10 +131,6 @@ export abstract class BaseView {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelProperties(block: AsyncBlock, selectInfo: any): DefaultColumnsValue {
|
|
||||||
return cloneDeep(block.getProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
html2block(el: Element, parseEl: (el: Element) => any[]): any[] | null {
|
html2block(el: Element, parseEl: (el: Element) => any[]): any[] | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -146,31 +142,24 @@ export abstract class BaseView {
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return '';
|
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 = (
|
// TODO: Try using new methods
|
||||||
properties: DefaultColumnsValue,
|
// async block2html2(props: {
|
||||||
selectInfo: any
|
// editor: Editor;
|
||||||
) => {
|
// block: AsyncBlock;
|
||||||
let text_value = properties.text.value;
|
// // The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range
|
||||||
if (text_value.length === 0) {
|
// selectInfo?: SelectBlock;
|
||||||
return properties;
|
// }) {
|
||||||
}
|
// return '';
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTextHtml = (block: AsyncBlock) => {
|
export const getTextHtml = (block: AsyncBlock) => {
|
||||||
const generate = (textList: any[]) => {
|
const generate = (textList: any[]) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user