mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-13 16:16:46 +08:00
Merge pull request #307 from toeverything/fix/experience
refactor: refactor clipboard
This commit is contained in:
@@ -5,10 +5,7 @@ import { getSession } from '@toeverything/components/board-sessions';
|
||||
import { deepCopy, TldrawApp } from '@toeverything/components/board-state';
|
||||
import { tools } from '@toeverything/components/board-tools';
|
||||
import { TDShapeType } from '@toeverything/components/board-types';
|
||||
import {
|
||||
getClipDataOfBlocksById,
|
||||
RecastBlockProvider,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { RecastBlockProvider } from '@toeverything/components/editor-core';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
import { AsyncBlock, BlockEditor } from '@toeverything/framework/virgo';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -72,10 +69,10 @@ const AffineBoard = ({
|
||||
console.log('e,data: ', e, data);
|
||||
},
|
||||
async onCopy(e, groupIds) {
|
||||
const clip = await getClipDataOfBlocksById(
|
||||
editor,
|
||||
groupIds
|
||||
);
|
||||
const clip =
|
||||
await editor.clipboard.clipboardUtils.getClipDataOfBlocksById(
|
||||
groupIds
|
||||
);
|
||||
|
||||
e.clipboardData?.setData(
|
||||
clip.getMimeType(),
|
||||
|
||||
@@ -968,7 +968,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() {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import {
|
||||
DefaultColumnsValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
getTextHtml,
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
BlockEditor,
|
||||
HTML2BlockResult,
|
||||
} from '@toeverything/framework/virgo';
|
||||
// import { withTreeViewChildren } from '../../utils/with-tree-view-children';
|
||||
|
||||
import {
|
||||
Block2HtmlProps,
|
||||
commonBlock2HtmlContent,
|
||||
commonHTML2block,
|
||||
} from '../../utils/commonBlockClip';
|
||||
import { BulletView, defaultBulletProps } from './BulletView';
|
||||
|
||||
export class BulletBlock extends BaseView {
|
||||
@@ -25,66 +26,44 @@ export class BulletBlock extends BaseView {
|
||||
}
|
||||
return block;
|
||||
}
|
||||
override async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
if (element.tagName === 'UL') {
|
||||
const firstList = element.querySelector('li');
|
||||
|
||||
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[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'UL') {
|
||||
const result = [];
|
||||
for (let i = 0; i < el.children.length; i++) {
|
||||
const blocks_info = parseEl(el.children[i]);
|
||||
result.push(...blocks_info);
|
||||
if (!firstList || firstList.innerText.startsWith('[ ] ')) {
|
||||
return null;
|
||||
}
|
||||
return result.length > 0 ? result : null;
|
||||
const children = Array.from(element.children);
|
||||
const childrenBlockInfos = (
|
||||
await Promise.all(
|
||||
children.map(childElement =>
|
||||
this.html2block({
|
||||
editor,
|
||||
element: childElement,
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
.filter(v => v);
|
||||
return childrenBlockInfos.length ? childrenBlockInfos : null;
|
||||
}
|
||||
|
||||
if (tag_name == 'LI' && !el.textContent.startsWith('[ ] ')) {
|
||||
const childNodes = el.childNodes;
|
||||
const texts = [];
|
||||
const children = [];
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
} else {
|
||||
children.push(blocks_info[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: children,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'LI',
|
||||
});
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<ul><li>${content}</li></ul>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<ul><li>${await commonBlock2HtmlContent(props)}</li></ul>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import {
|
||||
BaseView,
|
||||
AsyncBlock,
|
||||
getTextProperties,
|
||||
CreateView,
|
||||
SelectBlock,
|
||||
getTextHtml,
|
||||
BlockEditor,
|
||||
HTML2BlockResult,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import {
|
||||
Protocol,
|
||||
DefaultColumnsValue,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { CodeView } from './CodeView';
|
||||
import { ComponentType } from 'react';
|
||||
import {
|
||||
Block2HtmlProps,
|
||||
commonBlock2HtmlContent,
|
||||
commonHTML2block,
|
||||
} from '../../utils/commonBlockClip';
|
||||
|
||||
export class CodeBlock extends BaseView {
|
||||
type = Protocol.Block.Type.code;
|
||||
@@ -28,56 +27,22 @@ export class CodeBlock extends BaseView {
|
||||
return block;
|
||||
}
|
||||
|
||||
override getSelProperties(
|
||||
block: AsyncBlock,
|
||||
selectInfo: any
|
||||
): DefaultColumnsValue {
|
||||
const properties = super.getSelProperties(block, selectInfo);
|
||||
return getTextProperties(properties, selectInfo);
|
||||
override async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'CODE',
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: internal format not implemented yet
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'CODE') {
|
||||
const childNodes = el.childNodes;
|
||||
let text_value = '';
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
if (block_texts.length > 0) {
|
||||
text_value += block_texts[0].text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: [{ text: text_value }] },
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<code>${content}</code>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<code>${await commonBlock2HtmlContent(props)}<code/>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
BlockEditor,
|
||||
HTML2BlockResult,
|
||||
SelectBlock,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { DividerView } from './divider-view';
|
||||
import { Block2HtmlProps, commonHTML2block } from '../../utils/commonBlockClip';
|
||||
|
||||
export class DividerBlock extends BaseView {
|
||||
type = Protocol.Block.Type.divider;
|
||||
View = DividerView;
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'HR') {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: {},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
override async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'HR',
|
||||
ignoreEmptyElement: false,
|
||||
});
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
return `<hr>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<hr/>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { EmbedLinkView } from './EmbedLinkView';
|
||||
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
||||
|
||||
export class EmbedLinkBlock extends BaseView {
|
||||
public override selectable = true;
|
||||
@@ -12,36 +13,8 @@ export class EmbedLinkBlock extends BaseView {
|
||||
type = Protocol.Block.Type.embedLink;
|
||||
View = EmbedLinkView;
|
||||
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
// TODO: Not sure what value to fill for name
|
||||
embedLink: {
|
||||
name: this.type,
|
||||
value: el.getAttribute('href'),
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
const figma_url = block.getProperty('embedLink')?.value;
|
||||
return `<p><a src=${figma_url}>${figma_url}</p>`;
|
||||
override async block2html({ block }: Block2HtmlProps) {
|
||||
const url = block.getProperty('embedLink')?.value;
|
||||
return `<p><a href="${url}">${url}</a></p>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
SelectBlock,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { FigmaView } from './FigmaView';
|
||||
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
||||
|
||||
export class FigmaBlock extends BaseView {
|
||||
public override selectable = true;
|
||||
@@ -12,42 +13,8 @@ export class FigmaBlock extends BaseView {
|
||||
type = Protocol.Block.Type.figma;
|
||||
View = FigmaView;
|
||||
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
||||
const href = el.getAttribute('href');
|
||||
const allowedHosts = ['www.figma.com'];
|
||||
const host = new URL(href).host;
|
||||
|
||||
if (allowedHosts.includes(host)) {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
// TODO: Not sure what value to fill for name
|
||||
embedLink: {
|
||||
name: this.type,
|
||||
value: el.getAttribute('href'),
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
const figma_url = block.getProperty('embedLink')?.value;
|
||||
return `<p><a src=${figma_url}>${figma_url}</p>`;
|
||||
override async block2html({ block }: Block2HtmlProps) {
|
||||
const figmaUrl = block.getProperty('embedLink')?.value;
|
||||
return `<p><a href="${figmaUrl}">${figmaUrl}</a></p>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,22 @@ import {
|
||||
services,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { FileView } from './FileView';
|
||||
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
||||
|
||||
export class FileBlock extends BaseView {
|
||||
public override selectable = true;
|
||||
public override editable = false;
|
||||
type = Protocol.Block.Type.file;
|
||||
View = FileView;
|
||||
override async block2html({ block }: Block2HtmlProps) {
|
||||
const fileProperty = block.getProperty('file');
|
||||
const fileId = fileProperty?.value;
|
||||
const fileInfo = fileId
|
||||
? await services.api.file.get(fileId, block.workspace)
|
||||
: null;
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
const file_property =
|
||||
block.getProperty('file') || ({} as FileColumnValue);
|
||||
const file_id = file_property.value;
|
||||
let file_info = null;
|
||||
if (file_id) {
|
||||
file_info = await services.api.file.get(file_id, block.workspace);
|
||||
}
|
||||
return `<p><a src=${file_info?.url}>${file_property?.name}</p>`;
|
||||
return fileInfo
|
||||
? `<p><a href=${fileInfo?.url}>${fileProperty?.name}</a></p>`
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
SelectBlock,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { GroupView } from './GroupView';
|
||||
import {
|
||||
Block2HtmlProps,
|
||||
commonBlock2HtmlContent,
|
||||
} from '../../utils/commonBlockClip';
|
||||
|
||||
export class Group extends BaseView {
|
||||
public override selectable = true;
|
||||
@@ -25,13 +29,12 @@ export class Group extends BaseView {
|
||||
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
|
||||
return block;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
const content = await generateHtml(children);
|
||||
return `<div>${content}<div>`;
|
||||
override async block2html({ editor, selectInfo, block }: Block2HtmlProps) {
|
||||
const childrenHtml =
|
||||
await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos(
|
||||
block,
|
||||
selectInfo?.children
|
||||
);
|
||||
return `<div>${childrenHtml}<code/>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,35 +5,13 @@ import {
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { GroupDividerView } from './groupDividerView';
|
||||
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
||||
|
||||
export class GroupDividerBlock extends BaseView {
|
||||
type = Protocol.Block.Type.groupDivider;
|
||||
View = GroupDividerView;
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'HR') {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: {},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
return `<hr>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<hr/>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
SelectBlock,
|
||||
BlockEditor,
|
||||
HTML2BlockResult,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { ImageView } from './ImageView';
|
||||
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
||||
import { getRandomString } from '@toeverything/components/common';
|
||||
|
||||
export class ImageBlock extends BaseView {
|
||||
public override selectable = true;
|
||||
@@ -13,42 +15,40 @@ export class ImageBlock extends BaseView {
|
||||
View = ImageView;
|
||||
|
||||
// TODO: needs to download the image and then upload it to get a new link and then assign it
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'IMG') {
|
||||
override async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
if (element.tagName === 'IMG') {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
value: '',
|
||||
url: el.getAttribute('src'),
|
||||
name: el.getAttribute('src'),
|
||||
size: 0,
|
||||
type: 'link',
|
||||
image: {
|
||||
value: getRandomString('image'),
|
||||
url: element.getAttribute('src'),
|
||||
name: element.getAttribute('src'),
|
||||
size: 0,
|
||||
type: 'link',
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
const text = block.getProperty('text');
|
||||
override async block2html({ block, editor }: Block2HtmlProps) {
|
||||
const textValue = block.getProperty('text');
|
||||
const content = '';
|
||||
if (text) {
|
||||
text.value.map(text => `<span>${text}</span>`).join('');
|
||||
}
|
||||
// TODO: child
|
||||
return `<p><img src=${content}></p>`;
|
||||
// TODO: text.value should export with style??
|
||||
const figcaption = (textValue?.value ?? [])
|
||||
.map(({ text }) => `<span>${text}</span>`)
|
||||
.join('');
|
||||
return `<figure><img src="${content}" alt="${figcaption}"/><figcaption>${figcaption}<figcaption/></figure>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import {
|
||||
DefaultColumnsValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
getTextHtml,
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
BlockEditor,
|
||||
HTML2BlockResult,
|
||||
} from '@toeverything/framework/virgo';
|
||||
// import { withTreeViewChildren } from '../../utils/with-tree-view-children';
|
||||
import {
|
||||
Block2HtmlProps,
|
||||
commonBlock2HtmlContent,
|
||||
commonHTML2block,
|
||||
} from '../../utils/commonBlockClip';
|
||||
import { defaultTodoProps, NumberedView } from './NumberedView';
|
||||
|
||||
export class NumberedBlock extends BaseView {
|
||||
@@ -28,71 +28,39 @@ 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[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'OL') {
|
||||
const result = [];
|
||||
for (let i = 0; i < el.children.length; i++) {
|
||||
const blocks_info = parseEl(el.children[i]);
|
||||
result.push(...blocks_info);
|
||||
}
|
||||
return result.length > 0 ? result : null;
|
||||
override async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
if (element.tagName === 'OL') {
|
||||
const children = Array.from(element.children);
|
||||
const childrenBlockInfos = (
|
||||
await Promise.all(
|
||||
children.map(childElement =>
|
||||
this.html2block({
|
||||
editor,
|
||||
element: childElement,
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
.filter(v => v);
|
||||
return childrenBlockInfos.length ? childrenBlockInfos : null;
|
||||
}
|
||||
|
||||
if (tag_name == 'LI' && el.textContent.startsWith('[ ] ')) {
|
||||
const childNodes = el.childNodes;
|
||||
let texts = [];
|
||||
const children = [];
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
} else {
|
||||
children.push(blocks_info[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (texts.length > 0 && (texts[0].text || '').startsWith('[ ] ')) {
|
||||
texts[0].text = texts[0].text.substring('[ ] '.length);
|
||||
if (!texts[0].text) {
|
||||
texts = texts.slice(1);
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: children,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'LI',
|
||||
});
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<ol><li>${content}</li></ol>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<ol><li>${await commonBlock2HtmlContent(props)}</li></ol>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
import { withRecastBlock } from '@toeverything/components/editor-core';
|
||||
import {
|
||||
Protocol,
|
||||
DefaultColumnsValue,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
ChildrenView,
|
||||
getTextHtml,
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { AsyncBlock, BaseView } from '@toeverything/framework/virgo';
|
||||
|
||||
import { PageView } from './PageView';
|
||||
|
||||
export const PageChildrenView: (prop: ChildrenView) => JSX.Element = props =>
|
||||
props.children;
|
||||
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
||||
|
||||
export class PageBlock extends BaseView {
|
||||
type = Protocol.Block.Type.page;
|
||||
@@ -35,21 +23,17 @@ 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[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
const content = getTextHtml(block);
|
||||
const childrenContent = await generateHtml(children);
|
||||
return `<h1>${content}</h1> ${childrenContent}`;
|
||||
override async block2html({ block, editor, selectInfo }: Block2HtmlProps) {
|
||||
const header =
|
||||
await editor.clipboard.clipboardUtils.convertTextValue2HtmlBySelectInfo(
|
||||
block,
|
||||
selectInfo
|
||||
);
|
||||
const childrenHtml =
|
||||
await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos(
|
||||
block,
|
||||
selectInfo?.children
|
||||
);
|
||||
return `<h1>${header}</h1>${childrenHtml}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import {
|
||||
DefaultColumnsValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
BlockEditor,
|
||||
CreateView,
|
||||
getTextHtml,
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
HTML2BlockResult,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import {
|
||||
Block2HtmlProps,
|
||||
commonBlock2HtmlContent,
|
||||
commonHTML2block,
|
||||
} from '../../utils/commonBlockClip';
|
||||
|
||||
import { TextView } from './TextView';
|
||||
|
||||
@@ -29,54 +30,25 @@ 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 async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'BLOCKQUOTE',
|
||||
});
|
||||
}
|
||||
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'BLOCKQUOTE') {
|
||||
const childNodes = el.childNodes;
|
||||
const texts = [];
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<blockquote>${content}</blockquote>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<blockquote>${await commonBlock2HtmlContent(
|
||||
props
|
||||
)}</blockquote>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,69 +68,22 @@ 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 async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'ASIDE',
|
||||
});
|
||||
}
|
||||
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (
|
||||
tag_name === 'ASIDE' ||
|
||||
el.firstChild?.nodeValue?.startsWith('<aside>')
|
||||
) {
|
||||
const childNodes = el.childNodes;
|
||||
let texts = [];
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
texts.length > 0 &&
|
||||
(texts[0].text || '').startsWith('<aside>')
|
||||
) {
|
||||
texts[0].text = texts[0].text.substring('<aside>'.length);
|
||||
if (!texts[0].text) {
|
||||
texts = texts.slice(1);
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (el.firstChild?.nodeValue?.startsWith('</aside>')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<aside>${content}</aside>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<aside>${await commonBlock2HtmlContent(props)}</aside>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@ import {
|
||||
BaseView,
|
||||
CreateView,
|
||||
AsyncBlock,
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
getTextHtml,
|
||||
HTML2BlockResult,
|
||||
BlockEditor,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import {
|
||||
DefaultColumnsValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { TextView } from './TextView';
|
||||
|
||||
import { getRandomString } from '@toeverything/components/common';
|
||||
import {
|
||||
Block2HtmlProps,
|
||||
commonBlock2HtmlContent,
|
||||
commonHTML2block,
|
||||
} from '../../utils/commonBlockClip';
|
||||
|
||||
export class TextBlock extends BaseView {
|
||||
type = Protocol.Block.Type.text;
|
||||
@@ -28,106 +27,30 @@ 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[]
|
||||
): any[] | null {
|
||||
if (el instanceof Text) {
|
||||
// TODO: parsing style
|
||||
return el.textContent.split('\n').map(text => {
|
||||
return {
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: [{ text: text }] },
|
||||
},
|
||||
children: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const tag_name = el.tagName;
|
||||
const block_style: any = {};
|
||||
switch (tag_name) {
|
||||
case 'STRONG':
|
||||
case 'B':
|
||||
block_style.bold = true;
|
||||
break;
|
||||
case 'A':
|
||||
block_style.type = 'link';
|
||||
block_style.url = el.getAttribute('href');
|
||||
block_style.id = getRandomString('link');
|
||||
block_style.children = [];
|
||||
break;
|
||||
case 'EM':
|
||||
block_style.italic = true;
|
||||
break;
|
||||
case 'U':
|
||||
block_style.underline = true;
|
||||
break;
|
||||
case 'CODE':
|
||||
block_style.inlinecode = true;
|
||||
break;
|
||||
case 'S':
|
||||
case 'DEL':
|
||||
block_style.strikethrough = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const child_nodes = el.childNodes;
|
||||
let texts = [];
|
||||
if (Object.keys(block_style).length > 0) {
|
||||
for (let i = 0; i < child_nodes.length; i++) {
|
||||
const blocks_info: any[] = parseEl(child_nodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
const block = blocks_info[j];
|
||||
if (block.type === this.type) {
|
||||
const block_texts = block.properties.text.value.map(
|
||||
(text_value: any) => {
|
||||
return tag_name === 'A'
|
||||
? { ...text_value }
|
||||
: { ...block_style, ...text_value };
|
||||
}
|
||||
);
|
||||
texts.push(...block_texts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tag_name === 'A') {
|
||||
block_style.children.push(...texts);
|
||||
texts = [block_style];
|
||||
}
|
||||
return texts.length > 0
|
||||
? [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
]
|
||||
: null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<p>${content}</p>`;
|
||||
override async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: [
|
||||
'DIV',
|
||||
'P',
|
||||
'PRE',
|
||||
'B',
|
||||
'A',
|
||||
'EM',
|
||||
'U',
|
||||
'CODE',
|
||||
'S',
|
||||
'DEL',
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,55 +67,23 @@ 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 async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'H1',
|
||||
});
|
||||
}
|
||||
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'H1') {
|
||||
const childNodes = el.childNodes;
|
||||
const texts = [];
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<h1>${content}</h1>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<h1>${await commonBlock2HtmlContent(props)}</h1>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,55 +100,23 @@ 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 async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'H2',
|
||||
});
|
||||
}
|
||||
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'H2') {
|
||||
const childNodes = el.childNodes;
|
||||
const texts = [];
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<h2>${content}</h2>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<h2>${await commonBlock2HtmlContent(props)}</h2>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,53 +134,22 @@ 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 async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'H3',
|
||||
});
|
||||
}
|
||||
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'H3') {
|
||||
const childNodes = el.childNodes;
|
||||
const texts = [];
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<h3>${content}</h3>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<h3>${await commonBlock2HtmlContent(props)}</h3>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
getTextHtml,
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
BlockEditor,
|
||||
HTML2BlockResult,
|
||||
withTreeViewChildren,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import {
|
||||
DefaultColumnsValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { defaultTodoProps, TodoView } from './TodoView';
|
||||
|
||||
import {
|
||||
Block2HtmlProps,
|
||||
commonBlock2HtmlContent,
|
||||
commonHTML2block,
|
||||
} from '../../utils/commonBlockClip';
|
||||
import type { TodoAsyncBlock } from './types';
|
||||
|
||||
export class TodoBlock extends BaseView {
|
||||
@@ -27,71 +28,44 @@ 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[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'UL') {
|
||||
const result = [];
|
||||
for (let i = 0; i < el.children.length; i++) {
|
||||
const blocks_info = parseEl(el.children[i]);
|
||||
result.push(...blocks_info);
|
||||
override async html2block({
|
||||
element,
|
||||
editor,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
if (element.tagName === 'UL') {
|
||||
const firstList = element.querySelector('li');
|
||||
if (!firstList || !firstList.innerText.startsWith('[ ] ')) {
|
||||
return null;
|
||||
}
|
||||
return result.length > 0 ? result : null;
|
||||
|
||||
const children = Array.from(element.children);
|
||||
const childrenBlockInfos = (
|
||||
await Promise.all(
|
||||
children.map(childElement =>
|
||||
this.html2block({
|
||||
editor,
|
||||
element: childElement,
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
.filter(v => v);
|
||||
return childrenBlockInfos.length ? childrenBlockInfos : null;
|
||||
}
|
||||
|
||||
if (tag_name == 'LI' && el.textContent.startsWith('[ ] ')) {
|
||||
const childNodes = el.childNodes;
|
||||
let texts = [];
|
||||
const children = [];
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const blocks_info = parseEl(childNodes[i] as Element);
|
||||
for (let j = 0; j < blocks_info.length; j++) {
|
||||
if (blocks_info[j].type === 'text') {
|
||||
const block_texts =
|
||||
blocks_info[j].properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
} else {
|
||||
children.push(blocks_info[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (texts.length > 0 && (texts[0].text || '').startsWith('[ ] ')) {
|
||||
texts[0].text = texts[0].text.substring('[ ] '.length);
|
||||
if (!texts[0].text) {
|
||||
texts = texts.slice(1);
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
text: { value: texts },
|
||||
},
|
||||
children: children,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
return commonHTML2block({
|
||||
element,
|
||||
editor,
|
||||
type: this.type,
|
||||
tagName: 'LI',
|
||||
});
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
let content = getTextHtml(block);
|
||||
content += await generateHtml(children);
|
||||
return `<ul><li>[ ] ${content}</li></ul>`;
|
||||
override async block2html(props: Block2HtmlProps) {
|
||||
return `<ul><li>[ ] ${await commonBlock2HtmlContent(props)}</li></ul>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import {
|
||||
SelectBlock,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { YoutubeView } from './YoutubeView';
|
||||
import {
|
||||
Block2HtmlProps,
|
||||
commonBlock2HtmlContent,
|
||||
} from '../../utils/commonBlockClip';
|
||||
|
||||
export class YoutubeBlock extends BaseView {
|
||||
public override selectable = true;
|
||||
@@ -12,42 +16,11 @@ export class YoutubeBlock extends BaseView {
|
||||
type = Protocol.Block.Type.youtube;
|
||||
View = YoutubeView;
|
||||
|
||||
override html2block(
|
||||
el: Element,
|
||||
parseEl: (el: Element) => any[]
|
||||
): any[] | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
||||
const href = el.getAttribute('href');
|
||||
const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
|
||||
const host = new URL(href).host;
|
||||
|
||||
if (allowedHosts.includes(host)) {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
properties: {
|
||||
// TODO: is not sure what value to fill in name
|
||||
embedLink: {
|
||||
name: this.type,
|
||||
value: el.getAttribute('href'),
|
||||
},
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
override async block2Text(block: AsyncBlock, selectInfo: SelectBlock) {
|
||||
return block.getProperty('embedLink')?.value ?? '';
|
||||
}
|
||||
|
||||
override async block2html(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
): Promise<string> {
|
||||
override async block2html({ block }: Block2HtmlProps) {
|
||||
const url = block.getProperty('embedLink')?.value;
|
||||
return `<p><a src=${url}>${url}</p>`;
|
||||
return `<p><a href="${url}">${url}</a></p>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
AsyncBlock,
|
||||
BlockEditor,
|
||||
HTML2BlockResult,
|
||||
SelectBlock,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { BlockFlavorKeys } from '@toeverything/datasource/db-service';
|
||||
|
||||
export type Block2HtmlProps = {
|
||||
editor: BlockEditor;
|
||||
block: AsyncBlock;
|
||||
// The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range
|
||||
selectInfo?: SelectBlock;
|
||||
};
|
||||
|
||||
export const commonBlock2HtmlContent = async ({
|
||||
editor,
|
||||
block,
|
||||
selectInfo,
|
||||
}: Block2HtmlProps) => {
|
||||
const html =
|
||||
await editor.clipboard.clipboardUtils.convertTextValue2HtmlBySelectInfo(
|
||||
block,
|
||||
selectInfo
|
||||
);
|
||||
const childrenHtml =
|
||||
await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos(
|
||||
block,
|
||||
selectInfo?.children
|
||||
);
|
||||
return `${html}${childrenHtml}`;
|
||||
};
|
||||
|
||||
export const commonHTML2block = ({
|
||||
element,
|
||||
editor,
|
||||
tagName,
|
||||
type,
|
||||
ignoreEmptyElement = true,
|
||||
}: {
|
||||
element: Element;
|
||||
editor: BlockEditor;
|
||||
tagName: string | string[];
|
||||
type: BlockFlavorKeys;
|
||||
ignoreEmptyElement?: boolean;
|
||||
}): HTML2BlockResult => {
|
||||
const tagNames = typeof tagName === 'string' ? [tagName] : tagName;
|
||||
if (tagNames.includes(element.tagName)) {
|
||||
const res = editor.clipboard.clipboardUtils.commonHTML2Block(
|
||||
element,
|
||||
type,
|
||||
ignoreEmptyElement
|
||||
);
|
||||
return res ? [res] : null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -7,7 +7,9 @@
|
||||
"date-fns": "^2.28.0",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"hotkeys-js": "^3.9.4",
|
||||
"html-escaper": "^3.0.3",
|
||||
"lru-cache": "^7.10.1",
|
||||
"marked": "^4.0.19",
|
||||
"nanoid": "^4.0.0",
|
||||
"slate": "^0.81.0",
|
||||
"style9": "^0.13.3"
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
Selection as SlateSelection,
|
||||
} from 'slate';
|
||||
import { Editor } from '../editor';
|
||||
import { AsyncBlock } from '../block';
|
||||
import { SelectBlock } from '../selection';
|
||||
|
||||
type TextUtilsFunctions =
|
||||
| 'getString'
|
||||
@@ -41,7 +43,9 @@ type TextUtilsFunctions =
|
||||
| 'blur'
|
||||
| 'setSelection'
|
||||
| 'insertNodes'
|
||||
| 'getNodeByPath';
|
||||
| 'getNodeByPath'
|
||||
| 'getNodeByRange'
|
||||
| 'convertLeaf2Html';
|
||||
|
||||
type ExtendedTextUtils = SlateUtils & {
|
||||
setLinkModalVisible: (visible: boolean) => void;
|
||||
@@ -102,15 +106,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.editable;
|
||||
}
|
||||
|
||||
public async getFlatBlocksUnderParent(
|
||||
parentBlockId: string,
|
||||
includeParent = 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.editable) {
|
||||
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) {
|
||||
|
||||
@@ -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,207 +0,0 @@
|
||||
import { Protocol, BlockFlavorKeys } from '@toeverything/datasource/db-service';
|
||||
import { escape } from '@toeverything/utils';
|
||||
import { Editor } from '../editor';
|
||||
import { SelectBlock } from '../selection';
|
||||
import { ClipBlockInfo } from './types';
|
||||
|
||||
class DefaultBlockParse {
|
||||
public static html2block(el: Element): ClipBlockInfo[] | undefined | null {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'DIV' || el instanceof Text) {
|
||||
return el.textContent?.split('\n').map(str => {
|
||||
const data = {
|
||||
text: escape(str),
|
||||
};
|
||||
return {
|
||||
type: 'text',
|
||||
properties: {
|
||||
text: { value: [data] },
|
||||
},
|
||||
children: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default class ClipboardParse {
|
||||
private editor: Editor;
|
||||
private static block_types: BlockFlavorKeys[] = [
|
||||
Protocol.Block.Type.page,
|
||||
Protocol.Block.Type.reference,
|
||||
Protocol.Block.Type.heading1,
|
||||
Protocol.Block.Type.heading2,
|
||||
Protocol.Block.Type.heading3,
|
||||
Protocol.Block.Type.quote,
|
||||
Protocol.Block.Type.todo,
|
||||
Protocol.Block.Type.code,
|
||||
Protocol.Block.Type.text,
|
||||
Protocol.Block.Type.toc,
|
||||
Protocol.Block.Type.file,
|
||||
Protocol.Block.Type.image,
|
||||
Protocol.Block.Type.divider,
|
||||
Protocol.Block.Type.callout,
|
||||
Protocol.Block.Type.youtube,
|
||||
Protocol.Block.Type.figma,
|
||||
Protocol.Block.Type.group,
|
||||
Protocol.Block.Type.embedLink,
|
||||
Protocol.Block.Type.numbered,
|
||||
Protocol.Block.Type.bullet,
|
||||
];
|
||||
private static break_flags: Set<string> = new Set([
|
||||
'BLOCKQUOTE',
|
||||
'BODY',
|
||||
'CENTER',
|
||||
'DD',
|
||||
'DIR',
|
||||
'DIV',
|
||||
'DL',
|
||||
'DT',
|
||||
'FORM',
|
||||
'H1',
|
||||
'H2',
|
||||
'H3',
|
||||
'H4',
|
||||
'H5',
|
||||
'H6',
|
||||
'HEAD',
|
||||
'HTML',
|
||||
'ISINDEX',
|
||||
'MENU',
|
||||
'NOFRAMES',
|
||||
'P',
|
||||
'PRE',
|
||||
'TABLE',
|
||||
'TD',
|
||||
'TH',
|
||||
'TITLE',
|
||||
'TR',
|
||||
]);
|
||||
|
||||
constructor(editor: Editor) {
|
||||
this.editor = editor;
|
||||
this.generate_html = this.generate_html.bind(this);
|
||||
this.parse_dom = this.parse_dom.bind(this);
|
||||
}
|
||||
// TODO: escape
|
||||
public text2blocks(clipData: string): ClipBlockInfo[] {
|
||||
return (clipData || '').split('\n').map((str: string) => {
|
||||
const block_info: ClipBlockInfo = {
|
||||
type: 'text',
|
||||
properties: {
|
||||
text: { value: [{ text: str }] },
|
||||
},
|
||||
children: [] as ClipBlockInfo[],
|
||||
};
|
||||
return block_info;
|
||||
});
|
||||
}
|
||||
|
||||
public html2blocks(clipData: string): ClipBlockInfo[] {
|
||||
return this.common_html2blocks(clipData);
|
||||
}
|
||||
|
||||
private common_html2blocks(clipData: string): ClipBlockInfo[] {
|
||||
const html_el = document.createElement('html');
|
||||
html_el.innerHTML = clipData;
|
||||
return this.parse_dom(html_el);
|
||||
}
|
||||
|
||||
// tTODO:odo escape
|
||||
private parse_dom(el: Element): ClipBlockInfo[] {
|
||||
for (let i = 0; i < ClipboardParse.block_types.length; i++) {
|
||||
const block_utils = this.editor.getView(
|
||||
ClipboardParse.block_types[i]
|
||||
);
|
||||
const blocks = block_utils?.html2block?.(el, this.parse_dom);
|
||||
|
||||
if (blocks) {
|
||||
return blocks;
|
||||
}
|
||||
}
|
||||
const blocks: ClipBlockInfo[] = [];
|
||||
// blocks = DefaultBlockParse.html2block(el);
|
||||
for (let i = 0; i < el.childNodes.length; i++) {
|
||||
const child = el.childNodes[i];
|
||||
const last_block_info =
|
||||
blocks.length === 0 ? null : blocks[blocks.length - 1];
|
||||
let blocks_info = this.parse_dom(child as Element);
|
||||
if (
|
||||
last_block_info &&
|
||||
last_block_info.type === 'text' &&
|
||||
!ClipboardParse.break_flags.has((child as Element).tagName)
|
||||
) {
|
||||
const texts = last_block_info.properties?.text?.value || [];
|
||||
let j = 0;
|
||||
for (; j < blocks_info.length; j++) {
|
||||
const block = blocks_info[j];
|
||||
if (block.type === 'text') {
|
||||
const block_texts = block.properties.text.value;
|
||||
texts.push(...block_texts);
|
||||
}
|
||||
}
|
||||
last_block_info.properties = {
|
||||
text: { value: texts },
|
||||
};
|
||||
blocks_info = blocks_info.slice(j);
|
||||
}
|
||||
blocks.push(...blocks_info);
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public async generateHtml(): Promise<string> {
|
||||
const select_info = await this.editor.selectionManager.getSelectInfo();
|
||||
return await this.generate_html(select_info.blocks);
|
||||
}
|
||||
|
||||
public async page2html(): Promise<string> {
|
||||
const root_block_id = this.editor.getRootBlockId();
|
||||
if (!root_block_id) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const block_info = await this.get_select_info(root_block_id);
|
||||
return await this.generate_html([block_info]);
|
||||
}
|
||||
|
||||
private async get_select_info(blockId: string) {
|
||||
const block = await this.editor.getBlockById(blockId);
|
||||
if (!block) return null;
|
||||
const block_info: SelectBlock = {
|
||||
blockId: block.id,
|
||||
children: [],
|
||||
};
|
||||
const children_ids = block.childrenIds;
|
||||
for (let i = 0; i < children_ids.length; i++) {
|
||||
block_info.children.push(
|
||||
await this.get_select_info(children_ids[i])
|
||||
);
|
||||
}
|
||||
return block_info;
|
||||
}
|
||||
|
||||
private async generate_html(selectBlocks: SelectBlock[]): Promise<string> {
|
||||
let result = '';
|
||||
for (let i = 0; i < selectBlocks.length; i++) {
|
||||
const sel_block = selectBlocks[i];
|
||||
if (!sel_block || !sel_block.blockId) continue;
|
||||
const block = await this.editor.getBlockById(sel_block.blockId);
|
||||
if (!block) continue;
|
||||
const block_utils = this.editor.getView(block.type);
|
||||
const html = await block_utils.block2html(
|
||||
block,
|
||||
sel_block.children,
|
||||
this.generate_html
|
||||
);
|
||||
result += html;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
@@ -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,58 @@
|
||||
import { ClipboardEventDispatcher } from './clipboardEventDispatcher';
|
||||
import { HookType } from '../types';
|
||||
import { Editor } from '../editor';
|
||||
import { Copy } from './copy';
|
||||
import { Paste } from './paste';
|
||||
|
||||
import { ClipboardUtils } from './clipboardUtils';
|
||||
|
||||
export class Clipboard {
|
||||
private _clipboardEventDispatcher: ClipboardEventDispatcher;
|
||||
private _copy: Copy;
|
||||
private _paste: Paste;
|
||||
public clipboardUtils: ClipboardUtils;
|
||||
private _clipboardTarget: HTMLElement;
|
||||
|
||||
constructor(editor: Editor, clipboardTarget: HTMLElement) {
|
||||
this.clipboardUtils = new ClipboardUtils(editor);
|
||||
this._clipboardTarget = clipboardTarget;
|
||||
this._copy = new Copy(editor);
|
||||
|
||||
this._paste = new Paste(editor);
|
||||
|
||||
this._clipboardEventDispatcher = new ClipboardEventDispatcher(
|
||||
editor,
|
||||
this._clipboardTarget
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
set clipboardTarget(clipboardTarget: HTMLElement) {
|
||||
this._clipboardTarget = clipboardTarget;
|
||||
this._clipboardEventDispatcher.initialClipboardTargetEvent(
|
||||
this._clipboardTarget
|
||||
);
|
||||
}
|
||||
get clipboardTarget() {
|
||||
return this._clipboardTarget;
|
||||
}
|
||||
|
||||
public clipboardEvent2Blocks(e: ClipboardEvent) {
|
||||
return this._paste.clipboardEvent2Blocks(e);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._clipboardEventDispatcher.dispose(this.clipboardTarget);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Editor } from '../editor';
|
||||
import { ClipboardUtils } from './clipboardUtils';
|
||||
|
||||
enum ClipboardAction {
|
||||
copy = 'copy',
|
||||
cut = 'cut',
|
||||
paste = 'paste',
|
||||
}
|
||||
|
||||
export class ClipboardEventDispatcher {
|
||||
private _editor: Editor;
|
||||
private _utils: ClipboardUtils;
|
||||
|
||||
constructor(editor: Editor, clipboardTarget: HTMLElement) {
|
||||
this._editor = editor;
|
||||
this._utils = new ClipboardUtils(editor);
|
||||
|
||||
this._copyHandler = this._copyHandler.bind(this);
|
||||
this._cutHandler = this._cutHandler.bind(this);
|
||||
this._pasteHandler = this._pasteHandler.bind(this);
|
||||
|
||||
this.initialDocumentEvent();
|
||||
if (clipboardTarget) {
|
||||
this.initialClipboardTargetEvent(clipboardTarget);
|
||||
}
|
||||
}
|
||||
initialDocumentEvent() {
|
||||
this.disposeDocumentEvent();
|
||||
|
||||
document.addEventListener(ClipboardAction.copy, this._copyHandler);
|
||||
document.addEventListener(ClipboardAction.cut, this._cutHandler);
|
||||
document.addEventListener(ClipboardAction.paste, this._pasteHandler);
|
||||
}
|
||||
initialClipboardTargetEvent(clipboardTarget: HTMLElement) {
|
||||
if (!clipboardTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.disposeClipboardTargetEvent(clipboardTarget);
|
||||
|
||||
clipboardTarget.addEventListener(
|
||||
ClipboardAction.copy,
|
||||
this._copyHandler
|
||||
);
|
||||
clipboardTarget.addEventListener(ClipboardAction.cut, this._cutHandler);
|
||||
clipboardTarget.addEventListener(
|
||||
ClipboardAction.paste,
|
||||
this._pasteHandler
|
||||
);
|
||||
}
|
||||
disposeDocumentEvent() {
|
||||
document.removeEventListener(ClipboardAction.copy, this._copyHandler);
|
||||
document.removeEventListener(ClipboardAction.cut, this._cutHandler);
|
||||
document.removeEventListener(ClipboardAction.paste, this._pasteHandler);
|
||||
}
|
||||
disposeClipboardTargetEvent(clipboardTarget: HTMLElement) {
|
||||
if (!clipboardTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
clipboardTarget.removeEventListener(
|
||||
ClipboardAction.copy,
|
||||
this._copyHandler
|
||||
);
|
||||
clipboardTarget.removeEventListener(
|
||||
ClipboardAction.cut,
|
||||
this._cutHandler
|
||||
);
|
||||
clipboardTarget.removeEventListener(
|
||||
ClipboardAction.paste,
|
||||
this._pasteHandler
|
||||
);
|
||||
}
|
||||
|
||||
private _copyHandler(e: ClipboardEvent) {
|
||||
if (!this._utils.shouldHandlerContinue(e)) {
|
||||
return;
|
||||
}
|
||||
this._editor.getHooks().onCopy(e);
|
||||
}
|
||||
|
||||
private _cutHandler(e: ClipboardEvent) {
|
||||
if (!this._utils.shouldHandlerContinue(e)) {
|
||||
return;
|
||||
}
|
||||
this._editor.getHooks().onCut(e);
|
||||
}
|
||||
private _pasteHandler(e: ClipboardEvent) {
|
||||
if (!this._utils.shouldHandlerContinue(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._editor.getHooks().onPaste(e);
|
||||
}
|
||||
|
||||
dispose(clipboardTarget: HTMLElement) {
|
||||
this.disposeDocumentEvent();
|
||||
this.disposeClipboardTargetEvent(clipboardTarget);
|
||||
this._editor = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import { Editor } from '../editor';
|
||||
import { SelectBlock, SelectInfo } from '../selection';
|
||||
import { AsyncBlock } from '../block';
|
||||
import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
||||
import { Clip } from './clip';
|
||||
import { commonHTML2Block, commonHTML2Text } from './utils';
|
||||
|
||||
export class ClipboardUtils {
|
||||
private _editor: Editor;
|
||||
constructor(editor: Editor) {
|
||||
this._editor = editor;
|
||||
}
|
||||
|
||||
shouldHandlerContinue(event: ClipboardEvent) {
|
||||
const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA'];
|
||||
|
||||
if (event.defaultPrevented) {
|
||||
return false;
|
||||
}
|
||||
if (filterNodes.includes((event.target as HTMLElement)?.tagName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._editor.selectionManager.currentSelectInfo.type !== 'None';
|
||||
}
|
||||
|
||||
async getClipInfoOfBlockById(blockId: string) {
|
||||
const block = await this._editor.getBlockById(blockId);
|
||||
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 convertTextValue2HtmlBySelectInfo(
|
||||
blockOrBlockId: AsyncBlock | string,
|
||||
selectBlockInfo?: SelectBlock
|
||||
) {
|
||||
const block =
|
||||
typeof blockOrBlockId === 'string'
|
||||
? await this._editor.getBlockById(blockOrBlockId)
|
||||
: blockOrBlockId;
|
||||
const selectedProperties =
|
||||
await this._editor.blockHelper.getEditableBlockPropertiesBySelectInfo(
|
||||
block,
|
||||
selectBlockInfo
|
||||
);
|
||||
return this._editor.blockHelper.convertTextValue2Html(
|
||||
block.id,
|
||||
selectedProperties.text.value
|
||||
);
|
||||
}
|
||||
async convertBlock2HtmlBySelectInfos(
|
||||
blockOrBlockId: AsyncBlock | string,
|
||||
selectBlockInfos?: SelectBlock[]
|
||||
) {
|
||||
if (!selectBlockInfos) {
|
||||
const block =
|
||||
typeof blockOrBlockId === 'string'
|
||||
? await this._editor.getBlockById(blockOrBlockId)
|
||||
: blockOrBlockId;
|
||||
const children = await block?.children();
|
||||
return (
|
||||
await Promise.all(
|
||||
children.map(async childBlock => {
|
||||
const blockView = this._editor.getView(childBlock.type);
|
||||
return await blockView.block2html({
|
||||
editor: this._editor,
|
||||
block: childBlock,
|
||||
});
|
||||
})
|
||||
)
|
||||
).join('');
|
||||
}
|
||||
|
||||
return (
|
||||
await Promise.all(
|
||||
selectBlockInfos.map(async selectBlockInfo => {
|
||||
const block = await this._editor.getBlockById(
|
||||
selectBlockInfo.blockId
|
||||
);
|
||||
const blockView = this._editor.getView(block.type);
|
||||
return await blockView.block2html({
|
||||
editor: this._editor,
|
||||
block,
|
||||
selectInfo: selectBlockInfo,
|
||||
});
|
||||
})
|
||||
)
|
||||
).join('');
|
||||
}
|
||||
|
||||
async convertHTMLString2Blocks(html: string): Promise<ClipBlockInfo[]> {
|
||||
const htmlEl = document.createElement('html');
|
||||
htmlEl.innerHTML = html;
|
||||
htmlEl.querySelector('head')?.remove();
|
||||
|
||||
return this.convertHtml2Blocks(htmlEl);
|
||||
}
|
||||
async convertHtml2Blocks(element: Element): Promise<ClipBlockInfo[]> {
|
||||
const editableViews = this._editor.getEditableViews();
|
||||
// 如果block能够捕捉htmlElement则返回block的html2block
|
||||
const [clipBlockInfos] = (
|
||||
await Promise.all(
|
||||
editableViews.map(editableView => {
|
||||
return editableView?.html2block?.({
|
||||
editor: this._editor,
|
||||
element: element,
|
||||
});
|
||||
})
|
||||
)
|
||||
).filter(v => v && v.length);
|
||||
|
||||
if (clipBlockInfos) {
|
||||
return clipBlockInfos;
|
||||
}
|
||||
return (
|
||||
await Promise.all(
|
||||
Array.from(element.children).map(async childElement => {
|
||||
const clipBlockInfos = await this.convertHtml2Blocks(
|
||||
childElement
|
||||
);
|
||||
|
||||
if (clipBlockInfos && clipBlockInfos.length) {
|
||||
return clipBlockInfos;
|
||||
}
|
||||
|
||||
return this.commonHTML2Block(childElement);
|
||||
})
|
||||
)
|
||||
)
|
||||
.flat()
|
||||
.filter(v => v);
|
||||
}
|
||||
|
||||
commonHTML2Block = commonHTML2Block;
|
||||
|
||||
commonHTML2Text = commonHTML2Text;
|
||||
|
||||
textToBlock(clipData = ''): ClipBlockInfo[] {
|
||||
return clipData.split('\n').map((str: string) => {
|
||||
return {
|
||||
type: 'text',
|
||||
properties: {
|
||||
text: { value: [{ text: str }] },
|
||||
},
|
||||
children: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async page2html() {
|
||||
const rootBlockId = this._editor.getRootBlockId();
|
||||
if (!rootBlockId) {
|
||||
return '';
|
||||
}
|
||||
const rootBlock = await this._editor.getBlockById(rootBlockId);
|
||||
const blockView = this._editor.getView(rootBlock.type);
|
||||
|
||||
return await blockView.block2html({
|
||||
editor: this._editor,
|
||||
block: rootBlock,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,13 @@ import { Editor } from '../editor';
|
||||
import { SelectInfo } from '../selection';
|
||||
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 +18,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,24 +44,113 @@ class Copy {
|
||||
const affineClip = await this._getAffineClip();
|
||||
clips.push(affineClip);
|
||||
|
||||
// get common html clip
|
||||
const htmlClip = await this._clipboardParse.generateHtml();
|
||||
htmlClip &&
|
||||
clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip));
|
||||
const textClip = await this._getTextClip();
|
||||
clips.push(textClip);
|
||||
|
||||
const htmlClip = await this._getHtmlClip();
|
||||
|
||||
clips.push(htmlClip);
|
||||
|
||||
return clips;
|
||||
}
|
||||
|
||||
private async _getHtmlClip(): Promise<Clip> {
|
||||
const selectInfo: SelectInfo =
|
||||
await this._editor.selectionManager.getSelectInfo();
|
||||
|
||||
const htmlStr = (
|
||||
await Promise.all(
|
||||
selectInfo.blocks.map(async selectBlockInfo => {
|
||||
const block = await this._editor.getBlockById(
|
||||
selectBlockInfo.blockId
|
||||
);
|
||||
const blockView = this._editor.getView(block.type);
|
||||
return await blockView.block2html({
|
||||
editor: this._editor,
|
||||
block,
|
||||
selectInfo: selectBlockInfo,
|
||||
});
|
||||
})
|
||||
)
|
||||
).join('');
|
||||
|
||||
return new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlStr);
|
||||
}
|
||||
|
||||
private async _getAffineClip(): Promise<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');
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { HTML2BlockResult, ClipBlockInfo } from './types';
|
||||
export { Clipboard } from './clipboard';
|
||||
@@ -6,17 +6,11 @@ import {
|
||||
} from './types';
|
||||
import { Editor } from '../editor';
|
||||
import { AsyncBlock } from '../block';
|
||||
import ClipboardParse from './clipboard-parse';
|
||||
import { SelectInfo } from '../selection';
|
||||
import {
|
||||
Protocol,
|
||||
BlockFlavorKeys,
|
||||
services,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
import { MarkdownParser } from './markdown-parse';
|
||||
import { shouldHandlerContinue } from './utils';
|
||||
const SUPPORT_MARKDOWN_PASTE = true;
|
||||
|
||||
import { escape } from 'html-escaper';
|
||||
import { marked } from 'marked';
|
||||
import { ClipboardUtils } from './clipboardUtils';
|
||||
type TextValueItem = {
|
||||
text: string;
|
||||
[key: string]: any;
|
||||
@@ -25,93 +19,89 @@ type TextValueItem = {
|
||||
export class Paste {
|
||||
private _editor: Editor;
|
||||
private _markdownParse: MarkdownParser;
|
||||
private _clipboardParse: ClipboardParse;
|
||||
|
||||
constructor(
|
||||
editor: Editor,
|
||||
clipboardParse: ClipboardParse,
|
||||
markdownParse: MarkdownParser
|
||||
) {
|
||||
this._markdownParse = markdownParse;
|
||||
this._clipboardParse = clipboardParse;
|
||||
private _utils: ClipboardUtils;
|
||||
constructor(editor: Editor) {
|
||||
this._markdownParse = new MarkdownParser();
|
||||
this._editor = editor;
|
||||
|
||||
this._utils = new ClipboardUtils(editor);
|
||||
this.handlePaste = this.handlePaste.bind(this);
|
||||
}
|
||||
private static _optimalMimeType: string[] = [
|
||||
// The event handler will get the most needed clipboard data based on this array order
|
||||
private static _optimalMimeTypes: string[] = [
|
||||
OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED,
|
||||
OFFICE_CLIPBOARD_MIMETYPE.HTML,
|
||||
OFFICE_CLIPBOARD_MIMETYPE.TEXT,
|
||||
];
|
||||
public handlePaste(e: Event) {
|
||||
if (!shouldHandlerContinue(e, this._editor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
public async handlePaste(e: ClipboardEvent) {
|
||||
e.stopPropagation();
|
||||
|
||||
const clipboardData = (e as ClipboardEvent).clipboardData;
|
||||
|
||||
const isPureFile = Paste._isPureFileInClipboard(clipboardData);
|
||||
if (isPureFile) {
|
||||
this._pasteFile(clipboardData);
|
||||
} else {
|
||||
this._pasteContent(clipboardData);
|
||||
}
|
||||
const blocks = await this.clipboardEvent2Blocks(e);
|
||||
await this._insertBlocks(blocks);
|
||||
}
|
||||
public getOptimalClip(clipboardData: any) {
|
||||
const mimeTypeArr = Paste._optimalMimeType;
|
||||
|
||||
for (let i = 0; i < mimeTypeArr.length; i++) {
|
||||
const data =
|
||||
clipboardData[mimeTypeArr[i]] ||
|
||||
clipboardData.getData(mimeTypeArr[i]);
|
||||
public async clipboardEvent2Blocks(e: ClipboardEvent) {
|
||||
const clipboardData = e.clipboardData;
|
||||
const isPureFile = Paste._isPureFileInClipboard(clipboardData);
|
||||
|
||||
if (isPureFile) {
|
||||
return this._file2Blocks(clipboardData);
|
||||
}
|
||||
return this._clipboardData2Blocks(clipboardData);
|
||||
}
|
||||
// Get the most needed clipboard data based on `_optimalMimeTypes` order
|
||||
public getOptimalClip(clipboardData: ClipboardEvent['clipboardData']) {
|
||||
for (let i = 0; i < Paste._optimalMimeTypes.length; i++) {
|
||||
const mimeType = Paste._optimalMimeTypes[i];
|
||||
const data = clipboardData.getData(mimeType);
|
||||
|
||||
if (data) {
|
||||
return {
|
||||
type: mimeTypeArr[i],
|
||||
type: mimeType,
|
||||
data: data,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
private _pasteContent(clipboardData: any) {
|
||||
const originClip: { data: any; type: any } = this.getOptimalClip(
|
||||
clipboardData
|
||||
) as { data: any; type: any };
|
||||
private async _clipboardData2Blocks(
|
||||
clipboardData: ClipboardEvent['clipboardData']
|
||||
): Promise<ClipBlockInfo[]> {
|
||||
const optimalClip = this.getOptimalClip(clipboardData);
|
||||
if (
|
||||
optimalClip?.type ===
|
||||
OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED
|
||||
) {
|
||||
const clipInfo: InnerClipInfo = JSON.parse(optimalClip.data);
|
||||
return clipInfo.data;
|
||||
}
|
||||
|
||||
const originTextClipData = clipboardData.getData(
|
||||
OFFICE_CLIPBOARD_MIMETYPE.TEXT
|
||||
const textClipData = escape(
|
||||
clipboardData.getData(OFFICE_CLIPBOARD_MIMETYPE.TEXT)
|
||||
);
|
||||
|
||||
let clipData = originClip['data'];
|
||||
const shouldConvertMarkdown =
|
||||
this._markdownParse.checkIfTextContainsMd(textClipData);
|
||||
|
||||
if (originClip['type'] === OFFICE_CLIPBOARD_MIMETYPE.TEXT) {
|
||||
clipData = Paste._excapeHtml(clipData);
|
||||
if (
|
||||
optimalClip?.type === OFFICE_CLIPBOARD_MIMETYPE.HTML &&
|
||||
!shouldConvertMarkdown
|
||||
) {
|
||||
return this._utils.convertHTMLString2Blocks(optimalClip.data);
|
||||
}
|
||||
|
||||
switch (originClip['type']) {
|
||||
/** Protocol paste */
|
||||
case OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED:
|
||||
this._firePasteEditAction(clipData);
|
||||
break;
|
||||
case OFFICE_CLIPBOARD_MIMETYPE.HTML:
|
||||
this._pasteHtml(clipData, originTextClipData);
|
||||
break;
|
||||
case OFFICE_CLIPBOARD_MIMETYPE.TEXT:
|
||||
this._pasteText(clipData, originTextClipData);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (shouldConvertMarkdown) {
|
||||
const md2html = marked.parse(textClipData);
|
||||
return this._utils.convertHTMLString2Blocks(md2html);
|
||||
}
|
||||
|
||||
return this._utils.textToBlock(textClipData);
|
||||
}
|
||||
private async _firePasteEditAction(clipboardData: any) {
|
||||
const clipInfo: InnerClipInfo = JSON.parse(clipboardData);
|
||||
clipInfo && this._insertBlocks(clipInfo.data, clipInfo.select);
|
||||
}
|
||||
private async _pasteFile(clipboardData: any) {
|
||||
|
||||
private async _file2Blocks(clipboardData: any): Promise<ClipBlockInfo[]> {
|
||||
const file = Paste._getImageFile(clipboardData);
|
||||
if (file) {
|
||||
const result = await services.api.file.create({
|
||||
@@ -130,8 +120,9 @@ export class Paste {
|
||||
},
|
||||
children: [] as ClipBlockInfo[],
|
||||
};
|
||||
await this._insertBlocks([blockInfo]);
|
||||
return [blockInfo];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
private static _isPureFileInClipboard(clipboardData: DataTransfer) {
|
||||
const types = clipboardData.types;
|
||||
@@ -144,32 +135,14 @@ export class Paste {
|
||||
);
|
||||
}
|
||||
|
||||
private static _isTextEditBlock(type: BlockFlavorKeys) {
|
||||
return (
|
||||
type === Protocol.Block.Type.page ||
|
||||
type === Protocol.Block.Type.text ||
|
||||
type === Protocol.Block.Type.heading1 ||
|
||||
type === Protocol.Block.Type.heading2 ||
|
||||
type === Protocol.Block.Type.heading3 ||
|
||||
type === Protocol.Block.Type.quote ||
|
||||
type === Protocol.Block.Type.todo ||
|
||||
type === Protocol.Block.Type.code ||
|
||||
type === Protocol.Block.Type.callout ||
|
||||
type === Protocol.Block.Type.numbered ||
|
||||
type === Protocol.Block.Type.bullet
|
||||
);
|
||||
}
|
||||
|
||||
private async _insertBlocks(
|
||||
blocks: ClipBlockInfo[],
|
||||
pasteSelect?: SelectInfo
|
||||
) {
|
||||
private async _insertBlocks(blocks: ClipBlockInfo[]) {
|
||||
if (blocks.length === 0) {
|
||||
return;
|
||||
}
|
||||
const currentSelectInfo =
|
||||
await this._editor.selectionManager.getSelectInfo();
|
||||
|
||||
// TODO: Logic of insert blocks maybe should declare in blockHelper
|
||||
// When the selection is in one of the blocks, select?.type === 'Range'
|
||||
// Currently the selection does not support cross-blocking, so this case is not considered
|
||||
if (currentSelectInfo.type === 'Range') {
|
||||
@@ -177,15 +150,16 @@ export class Paste {
|
||||
const selectedBlock = await this._editor.getBlockById(
|
||||
currentSelectInfo.blocks[0].blockId
|
||||
);
|
||||
const isSelectedBlockCanEdit = Paste._isTextEditBlock(
|
||||
selectedBlock.type
|
||||
const isSelectedBlockCanEdit = this._editor.isEditableView(
|
||||
selectedBlock?.type
|
||||
);
|
||||
|
||||
const blockView = this._editor.getView(selectedBlock.type);
|
||||
const isSelectedBlockEmpty = blockView.isEmpty(selectedBlock);
|
||||
if (isSelectedBlockCanEdit && !isSelectedBlockEmpty) {
|
||||
const shouldSplitBlock =
|
||||
blocks.length > 1 ||
|
||||
!Paste._isTextEditBlock(blocks[0].type);
|
||||
!this._editor.isEditableView(blocks[0].type);
|
||||
const pureText = !shouldSplitBlock
|
||||
? blocks[0].properties.text.value
|
||||
: [{ text: '' }];
|
||||
@@ -405,7 +379,7 @@ export class Paste {
|
||||
}
|
||||
private async _setEndSelectToBlock(blockId: string) {
|
||||
const block = await this._editor.getBlockById(blockId);
|
||||
const isBlockCanEdit = Paste._isTextEditBlock(block.type);
|
||||
const isBlockCanEdit = this._editor.isEditableView(block.type);
|
||||
if (!isBlockCanEdit) {
|
||||
return;
|
||||
}
|
||||
@@ -450,34 +424,6 @@ export class Paste {
|
||||
);
|
||||
}
|
||||
|
||||
private async _pasteHtml(clipData: any, originTextClipData: any) {
|
||||
if (SUPPORT_MARKDOWN_PASTE) {
|
||||
const hasMarkdown =
|
||||
this._markdownParse.checkIfTextContainsMd(originTextClipData);
|
||||
if (hasMarkdown) {
|
||||
try {
|
||||
const convertedDataObj =
|
||||
this._markdownParse.md2Html(originTextClipData);
|
||||
if (convertedDataObj.isConverted) {
|
||||
clipData = convertedDataObj.text;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
clipData = originTextClipData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const blocks = this._clipboardParse.html2blocks(clipData);
|
||||
|
||||
await this._insertBlocks(blocks);
|
||||
}
|
||||
|
||||
private async _pasteText(clipData: any, originTextClipData: any) {
|
||||
const blocks = this._clipboardParse.text2blocks(clipData);
|
||||
await this._insertBlocks(blocks);
|
||||
}
|
||||
|
||||
private static _getImageFile(clipboardData: any) {
|
||||
const files = clipboardData.files;
|
||||
if (files && files[0] && files[0].type.indexOf('image') > -1) {
|
||||
@@ -485,16 +431,4 @@ export class Paste {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private static _excapeHtml(data: any, onlySpace?: any) {
|
||||
if (!onlySpace) {
|
||||
// TODO:
|
||||
// data = string.htmlEscape(data);
|
||||
// data = data.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
// data = data.replace(/ /g, ' ');
|
||||
// data = data.replace(/\t/g, ' ');
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { SelectInfo } from '../selection';
|
||||
|
||||
// export type ClipBlockInfo = Partial<ReturnEditorBlock> & {
|
||||
// children?: ClipBlockInfo[];
|
||||
// };
|
||||
export const OFFICE_CLIPBOARD_MIMETYPE = {
|
||||
DOCS_DOCUMENT_SLICE_CLIP_WRAPPED: 'affine/x-c+w',
|
||||
HTML: 'text/html',
|
||||
@@ -20,8 +23,9 @@ export const OFFICE_CLIPBOARD_MIMETYPE = {
|
||||
export interface ClipBlockInfo {
|
||||
type: BlockFlavorKeys;
|
||||
properties?: Partial<DefaultColumnsValue>;
|
||||
children: ClipBlockInfo[];
|
||||
children?: ClipBlockInfo[];
|
||||
}
|
||||
export type HTML2BlockResult = ClipBlockInfo[] | null;
|
||||
|
||||
export interface InnerClipInfo {
|
||||
select: SelectInfo;
|
||||
|
||||
@@ -1,52 +1,150 @@
|
||||
import { Editor } from '../editor';
|
||||
import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
||||
import { Clip } from './clip';
|
||||
import { getRandomString } from '@toeverything/components/common';
|
||||
import { BlockFlavorKeys, Protocol } from '@toeverything/datasource/db-service';
|
||||
import { ClipBlockInfo } from './types';
|
||||
|
||||
export const shouldHandlerContinue = (event: Event, editor: Editor) => {
|
||||
const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA'];
|
||||
const getIsLink = (htmlElement: HTMLElement) => {
|
||||
return ['A', 'IMG'].includes(htmlElement.tagName);
|
||||
};
|
||||
const getTextStyle = (htmlElement: HTMLElement) => {
|
||||
const tagName = htmlElement.tagName;
|
||||
const textStyle: { [key: string]: any } = {};
|
||||
|
||||
if (event.defaultPrevented) {
|
||||
return false;
|
||||
const style = (htmlElement.getAttribute('style') || '')
|
||||
.split(';')
|
||||
.reduce((style: { [key: string]: any }, styleString) => {
|
||||
const [key, value] = styleString.split(':');
|
||||
if (key && value) {
|
||||
style[key] = value;
|
||||
}
|
||||
return style;
|
||||
}, {});
|
||||
|
||||
if (
|
||||
style['font-weight'] === 'bold' ||
|
||||
Number(style['font-weight']) > 400 ||
|
||||
['STRONG', 'B', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(
|
||||
htmlElement.tagName
|
||||
)
|
||||
) {
|
||||
textStyle['bold'] = true;
|
||||
}
|
||||
if (filterNodes.includes((event.target as HTMLElement)?.tagName)) {
|
||||
return false;
|
||||
if (getIsLink(htmlElement)) {
|
||||
textStyle['type'] = 'link';
|
||||
textStyle['url'] =
|
||||
htmlElement.getAttribute('href') || htmlElement.getAttribute('src');
|
||||
textStyle['id'] = getRandomString('link');
|
||||
}
|
||||
|
||||
return editor.selectionManager.currentSelectInfo.type !== 'None';
|
||||
if (tagName === 'EM' || style['fontStyle'] === 'italic') {
|
||||
textStyle['italic'] = true;
|
||||
}
|
||||
if (
|
||||
tagName === 'U' ||
|
||||
(style['text-decoration'] &&
|
||||
style['text-decoration'].indexOf('underline') !== -1) ||
|
||||
style['border-bottom']
|
||||
) {
|
||||
textStyle['underline'] = true;
|
||||
}
|
||||
if (tagName === 'CODE') {
|
||||
textStyle['inlinecode'] = true;
|
||||
}
|
||||
if (
|
||||
tagName === 'S' ||
|
||||
tagName === 'DEL' ||
|
||||
(style['text-decoration'] &&
|
||||
style['text-decoration'].indexOf('line-through') !== -1)
|
||||
) {
|
||||
textStyle['strikethrough'] = true;
|
||||
}
|
||||
|
||||
return textStyle;
|
||||
};
|
||||
|
||||
export const getClipInfoOfBlockById = async (
|
||||
editor: Editor,
|
||||
blockId: string
|
||||
) => {
|
||||
const block = await editor.getBlockById(blockId);
|
||||
const blockView = editor.getView(block.type);
|
||||
const blockInfo: ClipBlockInfo = {
|
||||
type: block.type,
|
||||
properties: blockView.getSelProperties(block, {}),
|
||||
children: [] as ClipBlockInfo[],
|
||||
export const commonHTML2Block = (
|
||||
element: HTMLElement | Node,
|
||||
type: BlockFlavorKeys = Protocol.Block.Type.text,
|
||||
ignoreEmptyElement = true
|
||||
): ClipBlockInfo => {
|
||||
const textValue = commonHTML2Text(element, {}, ignoreEmptyElement);
|
||||
if (!textValue.length && ignoreEmptyElement) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type,
|
||||
properties: {
|
||||
text: { value: textValue },
|
||||
},
|
||||
children: [],
|
||||
};
|
||||
const children = (await block?.children()) ?? [];
|
||||
};
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const childInfo = await getClipInfoOfBlockById(editor, children[i].id);
|
||||
blockInfo.children.push(childInfo);
|
||||
const getSingleLabelHTMLElementContent = (htmlElement: HTMLElement) => {
|
||||
if (htmlElement.tagName === 'IMG') {
|
||||
return (
|
||||
htmlElement.getAttribute('alt') || htmlElement.getAttribute('src')
|
||||
);
|
||||
}
|
||||
return blockInfo;
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getClipDataOfBlocksById = async (
|
||||
editor: Editor,
|
||||
blockIds: string[]
|
||||
export const commonHTML2Text = (
|
||||
element: HTMLElement | Node,
|
||||
textStyle: { [key: string]: any } = {},
|
||||
ignoreEmptyText = true
|
||||
) => {
|
||||
const clipInfos = await Promise.all(
|
||||
blockIds.map(blockId => getClipInfoOfBlockById(editor, blockId))
|
||||
);
|
||||
if (element instanceof Text) {
|
||||
return element.textContent.split('\n').map(text => {
|
||||
return { text: text, ...textStyle };
|
||||
});
|
||||
}
|
||||
const htmlElement = element as HTMLElement;
|
||||
const childNodes = Array.from(htmlElement.childNodes);
|
||||
|
||||
return new Clip(
|
||||
OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED,
|
||||
JSON.stringify({
|
||||
data: clipInfos,
|
||||
})
|
||||
);
|
||||
const isLink = getIsLink(htmlElement);
|
||||
const currentTextStyle = getTextStyle(htmlElement);
|
||||
|
||||
if (!childNodes.length) {
|
||||
const singleLabelContent =
|
||||
getSingleLabelHTMLElementContent(htmlElement);
|
||||
if (isLink && singleLabelContent) {
|
||||
return [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: singleLabelContent,
|
||||
},
|
||||
],
|
||||
...currentTextStyle,
|
||||
},
|
||||
];
|
||||
}
|
||||
return ignoreEmptyText ? [] : [{ text: '', ...currentTextStyle }];
|
||||
}
|
||||
|
||||
const childTexts = childNodes
|
||||
.reduce((result, childNode) => {
|
||||
const textBlocks = commonHTML2Text(
|
||||
childNode,
|
||||
isLink
|
||||
? textStyle
|
||||
: {
|
||||
...textStyle,
|
||||
...currentTextStyle,
|
||||
},
|
||||
ignoreEmptyText
|
||||
);
|
||||
result.push(...textBlocks);
|
||||
return result;
|
||||
}, [])
|
||||
.filter(v => v);
|
||||
|
||||
if (isLink && childTexts.length) {
|
||||
return [
|
||||
{
|
||||
children: childTexts,
|
||||
...currentTextStyle,
|
||||
},
|
||||
];
|
||||
}
|
||||
return childTexts;
|
||||
};
|
||||
|
||||
@@ -13,8 +13,7 @@ import HotKeys from 'hotkeys-js';
|
||||
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';
|
||||
import { EditorCommands } from './commands';
|
||||
import { EditorConfig } from './config';
|
||||
import { DragDropManager } from './drag-drop';
|
||||
@@ -63,8 +62,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;
|
||||
@@ -78,6 +78,7 @@ export class Editor implements Virgo {
|
||||
this._rootBlockId = props.rootBlockId;
|
||||
this.hooks = new Hooks();
|
||||
this.plugin_manager = new PluginManager(this, this.hooks);
|
||||
this._clipboard = new Clipboard(this, this.ui_container);
|
||||
this.plugin_manager.registerAll(props.plugins);
|
||||
if (props.isEdgeless) {
|
||||
this.isEdgeless = true;
|
||||
@@ -136,13 +137,17 @@ export class Editor implements Virgo {
|
||||
|
||||
public set container(v: HTMLDivElement) {
|
||||
this.ui_container = v;
|
||||
this._initClipboard();
|
||||
this._clipboard.clipboardTarget = v;
|
||||
}
|
||||
|
||||
public get container() {
|
||||
return this.ui_container;
|
||||
}
|
||||
|
||||
public get clipboard() {
|
||||
return this._clipboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use it discreetly.
|
||||
* Preference to use {@link withBatch}
|
||||
@@ -189,26 +194,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() {
|
||||
@@ -249,6 +254,14 @@ export class Editor implements Virgo {
|
||||
getView(type: string) {
|
||||
return this.views[type];
|
||||
}
|
||||
getEditableViews() {
|
||||
return Object.values(this.views)
|
||||
.map(view => (view.editable ? view : null))
|
||||
.filter(v => v);
|
||||
}
|
||||
isEditableView(type: string) {
|
||||
return this.views[type].editable;
|
||||
}
|
||||
|
||||
private async _initBlock(
|
||||
blockData: ReturnEditorBlock
|
||||
@@ -496,12 +509,7 @@ 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;
|
||||
return this.clipboard?.clipboardUtils?.page2html?.();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -517,6 +525,6 @@ export class Editor implements Virgo {
|
||||
this.plugin_manager.dispose();
|
||||
this.selectionManager.dispose();
|
||||
this.dragDropManager.dispose();
|
||||
this._disposeClipboard();
|
||||
this._clipboard?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import ClipboardParseInner from './clipboard/clipboard-parse';
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const ClipboardParse = ClipboardParseInner;
|
||||
|
||||
export { AsyncBlock } from './block';
|
||||
export * from './commands/types';
|
||||
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 } from './views/base-view';
|
||||
export type { ChildrenView, CreateView } from './views/base-view';
|
||||
export { getClipDataOfBlocksById } from './clipboard/utils';
|
||||
export type { HTML2BlockResult, ClipBlockInfo } from './clipboard';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { ComponentType, ReactElement } from 'react';
|
||||
|
||||
import type {
|
||||
Column,
|
||||
DefaultColumnsValue,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import type { Column } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
ArrayOperation,
|
||||
BlockDecoration,
|
||||
MapOperation,
|
||||
} from '@toeverything/datasource/jwt';
|
||||
import { cloneDeep } from '@toeverything/utils';
|
||||
import type { EventData } from '../block';
|
||||
import { AsyncBlock } from '../block';
|
||||
import type { Editor } from '../editor';
|
||||
import { SelectBlock } from '../selection';
|
||||
|
||||
import { HTML2BlockResult } from '../clipboard';
|
||||
export interface CreateView {
|
||||
block: AsyncBlock;
|
||||
editor: Editor;
|
||||
@@ -130,76 +125,27 @@ 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 {
|
||||
async html2block(props: {
|
||||
element: HTMLElement | Node;
|
||||
editor: Editor;
|
||||
}): Promise<HTML2BlockResult> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async block2html(
|
||||
async block2Text(
|
||||
block: AsyncBlock,
|
||||
children: SelectBlock[],
|
||||
generateHtml: (el: any[]) => Promise<string>
|
||||
// The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range
|
||||
selectInfo?: SelectBlock
|
||||
): Promise<string> {
|
||||
return '';
|
||||
}
|
||||
|
||||
async block2html(props: {
|
||||
editor: Editor;
|
||||
block: AsyncBlock;
|
||||
// The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range
|
||||
selectInfo?: SelectBlock;
|
||||
}) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export const 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;
|
||||
};
|
||||
|
||||
export const getTextHtml = (block: AsyncBlock) => {
|
||||
const generate = (textList: any[]) => {
|
||||
let content = '';
|
||||
textList.forEach(text_obj => {
|
||||
let text = text_obj.text || '';
|
||||
if (text_obj.bold) {
|
||||
text = `<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);
|
||||
};
|
||||
|
||||
@@ -153,9 +153,7 @@ function PageSettingPortal() {
|
||||
|
||||
const handleExportHtml = async () => {
|
||||
//@ts-ignore
|
||||
const htmlContent = await virgo.clipboard
|
||||
.getClipboardParse()
|
||||
.page2html();
|
||||
const htmlContent = await virgo.clipboard.clipboardUtils.page2html();
|
||||
const htmlTitle = pageBlock.title;
|
||||
|
||||
FileExporter.exportHtml(htmlTitle, htmlContent);
|
||||
@@ -163,9 +161,7 @@ function PageSettingPortal() {
|
||||
|
||||
const handleExportMarkdown = async () => {
|
||||
//@ts-ignore
|
||||
const htmlContent = await virgo.clipboard
|
||||
.getClipboardParse()
|
||||
.page2html();
|
||||
const htmlContent = await virgo.clipboard.clipboardUtils.page2html();
|
||||
const htmlTitle = pageBlock.title;
|
||||
FileExporter.exportMarkdown(htmlTitle, htmlContent);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createEditor } from '@toeverything/components/affine-editor';
|
||||
import { ClipboardParse } from '@toeverything/components/editor-core';
|
||||
import { fileExporter } from './file-exporter';
|
||||
|
||||
interface CreateClipboardParseProps {
|
||||
@@ -7,13 +6,12 @@ interface CreateClipboardParseProps {
|
||||
rootBlockId: string;
|
||||
}
|
||||
|
||||
const createClipboardParse = ({
|
||||
const createClipboardUtils = ({
|
||||
workspaceId,
|
||||
rootBlockId,
|
||||
}: CreateClipboardParseProps) => {
|
||||
const editor = createEditor(workspaceId, rootBlockId);
|
||||
|
||||
return new ClipboardParse(editor);
|
||||
return editor.clipboard.clipboardUtils;
|
||||
};
|
||||
|
||||
interface ExportHandlerProps extends CreateClipboardParseProps {
|
||||
@@ -25,9 +23,8 @@ export const exportHtml = async ({
|
||||
rootBlockId,
|
||||
title,
|
||||
}: ExportHandlerProps) => {
|
||||
const clipboardParse = createClipboardParse({ workspaceId, rootBlockId });
|
||||
const htmlContent = await clipboardParse.page2html();
|
||||
fileExporter.exportHtml(title, htmlContent);
|
||||
const clipboardUtils = createClipboardUtils({ workspaceId, rootBlockId });
|
||||
fileExporter.exportHtml(title, await clipboardUtils.page2html());
|
||||
};
|
||||
|
||||
export const exportMarkdown = async ({
|
||||
@@ -35,7 +32,6 @@ export const exportMarkdown = async ({
|
||||
rootBlockId,
|
||||
title,
|
||||
}: ExportHandlerProps) => {
|
||||
const clipboardParse = createClipboardParse({ workspaceId, rootBlockId });
|
||||
const htmlContent = await clipboardParse.page2html();
|
||||
fileExporter.exportMarkdown(title, htmlContent);
|
||||
const clipboardUtils = createClipboardUtils({ workspaceId, rootBlockId });
|
||||
fileExporter.exportMarkdown(title, await clipboardUtils.page2html());
|
||||
};
|
||||
|
||||
Generated
+59
-27
@@ -413,18 +413,18 @@ importers:
|
||||
dependencies:
|
||||
'@codemirror/commands': 6.0.1
|
||||
'@codemirror/lang-cpp': 6.0.1
|
||||
'@codemirror/lang-css': 6.0.0
|
||||
'@codemirror/lang-html': 6.1.0
|
||||
'@codemirror/lang-css': 6.0.0_bmjizg7gr5ieupmvn5u62mbipm
|
||||
'@codemirror/lang-html': 6.1.0_@codemirror+view@6.2.0
|
||||
'@codemirror/lang-java': 6.0.0
|
||||
'@codemirror/lang-javascript': 6.0.2
|
||||
'@codemirror/lang-json': 6.0.0
|
||||
'@codemirror/lang-lezer': 6.0.0
|
||||
'@codemirror/lang-markdown': 6.0.1
|
||||
'@codemirror/lang-php': 6.0.0
|
||||
'@codemirror/lang-php': 6.0.0_@codemirror+view@6.2.0
|
||||
'@codemirror/lang-python': 6.0.1
|
||||
'@codemirror/lang-rust': 6.0.0
|
||||
'@codemirror/lang-sql': 6.1.0
|
||||
'@codemirror/lang-xml': 6.0.0
|
||||
'@codemirror/lang-sql': 6.1.0_bmjizg7gr5ieupmvn5u62mbipm
|
||||
'@codemirror/lang-xml': 6.0.0_@codemirror+view@6.2.0
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/legacy-modes': 6.1.0
|
||||
'@codemirror/next': 0.16.0
|
||||
@@ -438,7 +438,7 @@ importers:
|
||||
'@emotion/styled': 11.9.3_dc5dh2wp562rsjxvguwi2i3yzq
|
||||
'@mui/system': 5.8.7_d6menda4vqwq6peqnkbe7mkj4i
|
||||
code-example: 3.3.6
|
||||
codemirror: 6.0.1
|
||||
codemirror: 6.0.1_@lezer+common@1.0.0
|
||||
codemirror-lang-elixir: 3.0.0_@codemirror+language@6.2.1
|
||||
keymap: link:@codemirror/next/keymap
|
||||
nanoid: 4.0.0
|
||||
@@ -458,7 +458,9 @@ importers:
|
||||
date-fns: ^2.28.0
|
||||
eventemitter3: ^4.0.7
|
||||
hotkeys-js: ^3.9.4
|
||||
html-escaper: ^3.0.3
|
||||
lru-cache: ^7.10.1
|
||||
marked: ^4.0.19
|
||||
nanoid: ^4.0.0
|
||||
slate: ^0.81.0
|
||||
style9: ^0.13.3
|
||||
@@ -467,7 +469,9 @@ importers:
|
||||
date-fns: 2.28.0
|
||||
eventemitter3: 4.0.7
|
||||
hotkeys-js: 3.9.4
|
||||
html-escaper: 3.0.3
|
||||
lru-cache: 7.12.0
|
||||
marked: 4.0.19
|
||||
nanoid: 4.0.0
|
||||
slate: 0.81.1
|
||||
style9: 0.13.3
|
||||
@@ -566,9 +570,6 @@ importers:
|
||||
dependencies:
|
||||
ffc-js-client-side-sdk: 1.1.5
|
||||
|
||||
libs/datasource/jwst/pkg:
|
||||
specifiers: {}
|
||||
|
||||
libs/datasource/jwt:
|
||||
specifiers:
|
||||
'@types/debug': ^4.1.7
|
||||
@@ -2257,8 +2258,13 @@ packages:
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
dev: true
|
||||
|
||||
/@codemirror/autocomplete/6.0.3:
|
||||
/@codemirror/autocomplete/6.0.3_nq4pwfkqi5icglf26kczcr4s2i:
|
||||
resolution: {integrity: sha512-JTSBDC4tUyR8iRmCwQJaYpTXtOZmRn4gKjw1Fu4xIatFPqTJ7m0QRCdkdbzlvMovzjTiuHp4a8WUEB1c/LtiHg==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
'@lezer/common': ^1.0.0
|
||||
dependencies:
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/state': 6.1.1
|
||||
@@ -2282,25 +2288,30 @@ packages:
|
||||
'@lezer/cpp': 1.0.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-css/6.0.0:
|
||||
/@codemirror/lang-css/6.0.0_bmjizg7gr5ieupmvn5u62mbipm:
|
||||
resolution: {integrity: sha512-jBqc+BTuwhNOTlrimFghLlSrN6iFuE44HULKWoR4qKYObhOIl9Lci1iYj6zMIte1XTQmZguNvjXMyr43LUKwSw==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.0.3
|
||||
'@codemirror/autocomplete': 6.0.3_nq4pwfkqi5icglf26kczcr4s2i
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/state': 6.1.1
|
||||
'@lezer/css': 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/view'
|
||||
- '@lezer/common'
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-html/6.1.0:
|
||||
/@codemirror/lang-html/6.1.0_@codemirror+view@6.2.0:
|
||||
resolution: {integrity: sha512-gA7NmJxqvnhwza05CvR7W/39Ap9r/4Vs9uiC0IeFYo1hSlJzc/8N6Evviz6vTW1x8SpHcRYyqKOf6rpl6LfWtg==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.0.3
|
||||
'@codemirror/lang-css': 6.0.0
|
||||
'@codemirror/autocomplete': 6.0.3_nq4pwfkqi5icglf26kczcr4s2i
|
||||
'@codemirror/lang-css': 6.0.0_bmjizg7gr5ieupmvn5u62mbipm
|
||||
'@codemirror/lang-javascript': 6.0.2
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/state': 6.1.1
|
||||
'@lezer/common': 1.0.0
|
||||
'@lezer/html': 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/view'
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-java/6.0.0:
|
||||
@@ -2313,7 +2324,7 @@ packages:
|
||||
/@codemirror/lang-javascript/6.0.2:
|
||||
resolution: {integrity: sha512-BZRJ9u/zl16hLkSpDAWm73mrfIR7HJrr0lvnhoSOCQVea5BglguWI/slxexhvUb0CB5cXgKWuo2bM+N9EhIaZw==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.0.3
|
||||
'@codemirror/autocomplete': 6.0.3_nq4pwfkqi5icglf26kczcr4s2i
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/lint': 6.0.0
|
||||
'@codemirror/state': 6.1.1
|
||||
@@ -2341,7 +2352,7 @@ packages:
|
||||
/@codemirror/lang-markdown/6.0.1:
|
||||
resolution: {integrity: sha512-pHPQuRwf9cUrmkmsTHRjtS9ZnGu3fA9YzAdh2++d+L9wbfnC2XbKh0Xvm/0YiUjdCnoCx9wDFEoCuAnkqKWLIw==}
|
||||
dependencies:
|
||||
'@codemirror/lang-html': 6.1.0
|
||||
'@codemirror/lang-html': 6.1.0_@codemirror+view@6.2.0
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/state': 6.1.1
|
||||
'@codemirror/view': 6.2.0
|
||||
@@ -2349,14 +2360,16 @@ packages:
|
||||
'@lezer/markdown': 1.0.1
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-php/6.0.0:
|
||||
/@codemirror/lang-php/6.0.0_@codemirror+view@6.2.0:
|
||||
resolution: {integrity: sha512-96CEjq0xEgbzc6bdFPwILPfZ6m8917JRbh2oPszZJABlYxG4Y+eYjtYkUTDb4yuyjQKyigHoeGC6zoIOYA1NWA==}
|
||||
dependencies:
|
||||
'@codemirror/lang-html': 6.1.0
|
||||
'@codemirror/lang-html': 6.1.0_@codemirror+view@6.2.0
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/state': 6.1.1
|
||||
'@lezer/common': 1.0.0
|
||||
'@lezer/php': 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/view'
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-python/6.0.1:
|
||||
@@ -2373,24 +2386,29 @@ packages:
|
||||
'@lezer/rust': 1.0.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-sql/6.1.0:
|
||||
/@codemirror/lang-sql/6.1.0_bmjizg7gr5ieupmvn5u62mbipm:
|
||||
resolution: {integrity: sha512-eTNTP0+uNHqYClCvJ3QGE7mn1S96QJFNsK76dB4c1pYAQjbgVVjy5DqtD3//A44rp2kuRkgBccRaPKrWDzBdNQ==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.0.3
|
||||
'@codemirror/autocomplete': 6.0.3_nq4pwfkqi5icglf26kczcr4s2i
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/state': 6.1.1
|
||||
'@lezer/highlight': 1.0.0
|
||||
'@lezer/lr': 1.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/view'
|
||||
- '@lezer/common'
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-xml/6.0.0:
|
||||
/@codemirror/lang-xml/6.0.0_@codemirror+view@6.2.0:
|
||||
resolution: {integrity: sha512-M/HLWxIiP956xGjtrxkeHkCmDGVQGKu782x8pOH5CLJIMkWtiB1DWfDoDHqpFjdEE9dkfcqPWvYfVi6GbhuXEg==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.0.3
|
||||
'@codemirror/autocomplete': 6.0.3_nq4pwfkqi5icglf26kczcr4s2i
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/state': 6.1.1
|
||||
'@lezer/common': 1.0.0
|
||||
'@lezer/xml': 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/view'
|
||||
dev: false
|
||||
|
||||
/@codemirror/language/6.2.1:
|
||||
@@ -7123,8 +7141,10 @@ packages:
|
||||
indent-string: 4.0.0
|
||||
dev: true
|
||||
|
||||
/ajv-formats/2.1.1:
|
||||
/ajv-formats/2.1.1_ajv@8.11.0:
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
@@ -8324,16 +8344,18 @@ packages:
|
||||
'@codemirror/language': 6.2.1
|
||||
dev: false
|
||||
|
||||
/codemirror/6.0.1:
|
||||
/codemirror/6.0.1_@lezer+common@1.0.0:
|
||||
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.0.3
|
||||
'@codemirror/autocomplete': 6.0.3_nq4pwfkqi5icglf26kczcr4s2i
|
||||
'@codemirror/commands': 6.0.1
|
||||
'@codemirror/language': 6.2.1
|
||||
'@codemirror/lint': 6.0.0
|
||||
'@codemirror/search': 6.0.0
|
||||
'@codemirror/state': 6.1.1
|
||||
'@codemirror/view': 6.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@lezer/common'
|
||||
dev: false
|
||||
|
||||
/collect-v8-coverage/1.0.1:
|
||||
@@ -11184,6 +11206,10 @@ packages:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
dev: true
|
||||
|
||||
/html-escaper/3.0.3:
|
||||
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
|
||||
dev: false
|
||||
|
||||
/html-minifier-terser/6.1.0:
|
||||
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -13673,6 +13699,12 @@ packages:
|
||||
tmpl: 1.0.5
|
||||
dev: true
|
||||
|
||||
/marked/4.0.19:
|
||||
resolution: {integrity: sha512-rgQF/OxOiLcvgUAj1Q1tAf4Bgxn5h5JZTp04Fx4XUkVhs7B+7YA9JEWJhJpoO8eJt8MkZMwqLCNeNqj1bCREZQ==}
|
||||
engines: {node: '>= 12'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/mdn-data/2.0.14:
|
||||
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
|
||||
dev: true
|
||||
@@ -16131,7 +16163,7 @@ packages:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.11
|
||||
ajv: 8.11.0
|
||||
ajv-formats: 2.1.1
|
||||
ajv-formats: 2.1.1_ajv@8.11.0
|
||||
ajv-keywords: 5.1.0_ajv@8.11.0
|
||||
dev: true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user