refactor: recode html2block
This commit is contained in:
@@ -1,9 +1,15 @@
|
|||||||
import { AsyncBlock, BaseView } from '@toeverything/framework/virgo';
|
import {
|
||||||
|
AsyncBlock,
|
||||||
|
BaseView,
|
||||||
|
BlockEditor,
|
||||||
|
HTML2BlockResult,
|
||||||
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { defaultBulletProps, BulletView } from './BulletView';
|
import { defaultBulletProps, BulletView } from './BulletView';
|
||||||
import {
|
import {
|
||||||
Block2HtmlProps,
|
Block2HtmlProps,
|
||||||
commonBlock2HtmlContent,
|
commonBlock2HtmlContent,
|
||||||
|
commonHTML2block,
|
||||||
} from '../../utils/commonBlockClip';
|
} from '../../utils/commonBlockClip';
|
||||||
export class BulletBlock extends BaseView {
|
export class BulletBlock extends BaseView {
|
||||||
public type = Protocol.Block.Type.bullet;
|
public type = Protocol.Block.Type.bullet;
|
||||||
@@ -18,50 +24,42 @@ export class BulletBlock extends BaseView {
|
|||||||
}
|
}
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
override async html2block2({
|
||||||
|
element,
|
||||||
|
editor,
|
||||||
|
}: {
|
||||||
|
element: Element;
|
||||||
|
editor: BlockEditor;
|
||||||
|
}): Promise<HTML2BlockResult> {
|
||||||
|
if (element.tagName === 'UL') {
|
||||||
|
const firstList = element.querySelector('li');
|
||||||
|
|
||||||
override html2block(
|
if (!firstList || firstList.innerText.startsWith('[ ] ')) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
return result.length > 0 ? result : 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 null;
|
||||||
}
|
}
|
||||||
|
const children = Array.from(element.children);
|
||||||
|
const childrenBlockInfos = (
|
||||||
|
await Promise.all(
|
||||||
|
children.map(childElement =>
|
||||||
|
this.html2block2({
|
||||||
|
editor,
|
||||||
|
element: childElement,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
.filter(v => v);
|
||||||
|
return childrenBlockInfos.length ? childrenBlockInfos : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return commonHTML2block({
|
||||||
|
element,
|
||||||
|
editor,
|
||||||
|
type: this.type,
|
||||||
|
tagName: 'LI',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
return `<ul><li>${await commonBlock2HtmlContent(props)}</li></ul>`;
|
return `<ul><li>${await commonBlock2HtmlContent(props)}</li></ul>`;
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { BaseView, AsyncBlock } from '@toeverything/framework/virgo';
|
import {
|
||||||
|
BaseView,
|
||||||
|
AsyncBlock,
|
||||||
|
BlockEditor,
|
||||||
|
HTML2BlockResult,
|
||||||
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { CodeView } from './CodeView';
|
import { CodeView } from './CodeView';
|
||||||
import {
|
import {
|
||||||
Block2HtmlProps,
|
Block2HtmlProps,
|
||||||
commonBlock2HtmlContent,
|
commonBlock2HtmlContent,
|
||||||
|
commonHTML2block,
|
||||||
} from '../../utils/commonBlockClip';
|
} from '../../utils/commonBlockClip';
|
||||||
|
|
||||||
export class CodeBlock extends BaseView {
|
export class CodeBlock extends BaseView {
|
||||||
@@ -21,39 +27,19 @@ export class CodeBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: internal format not implemented yet
|
override async html2block2({
|
||||||
override html2block(
|
element,
|
||||||
el: Element,
|
editor,
|
||||||
parseEl: (el: Element) => any[]
|
}: {
|
||||||
): any[] | null {
|
element: Element;
|
||||||
const tag_name = el.tagName;
|
editor: BlockEditor;
|
||||||
if (tag_name === 'CODE') {
|
}): Promise<HTML2BlockResult> {
|
||||||
const childNodes = el.childNodes;
|
return commonHTML2block({
|
||||||
let text_value = '';
|
element,
|
||||||
for (let i = 0; i < childNodes.length; i++) {
|
editor,
|
||||||
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,
|
type: this.type,
|
||||||
properties: {
|
tagName: 'CODE',
|
||||||
text: { value: [{ text: text_value }] },
|
});
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
BaseView,
|
BaseView,
|
||||||
|
BlockEditor,
|
||||||
|
HTML2BlockResult,
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { DividerView } from './divider-view';
|
import { DividerView } from './divider-view';
|
||||||
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
import { Block2HtmlProps, commonHTML2block } from '../../utils/commonBlockClip';
|
||||||
|
|
||||||
export class DividerBlock extends BaseView {
|
export class DividerBlock extends BaseView {
|
||||||
type = Protocol.Block.Type.divider;
|
type = Protocol.Block.Type.divider;
|
||||||
View = DividerView;
|
View = DividerView;
|
||||||
override html2block(
|
|
||||||
el: Element,
|
override async html2block2({
|
||||||
parseEl: (el: Element) => any[]
|
element,
|
||||||
): any[] | null {
|
editor,
|
||||||
const tag_name = el.tagName;
|
}: {
|
||||||
if (tag_name === 'HR') {
|
element: Element;
|
||||||
return [
|
editor: BlockEditor;
|
||||||
{
|
}): Promise<HTML2BlockResult> {
|
||||||
|
return commonHTML2block({
|
||||||
|
element,
|
||||||
|
editor,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
properties: {
|
tagName: 'HR',
|
||||||
text: {},
|
ignoreEmptyElement: false,
|
||||||
},
|
});
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
return `<hr/>`;
|
return `<hr/>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,29 +13,6 @@ export class EmbedLinkBlock extends BaseView {
|
|||||||
type = Protocol.Block.Type.embedLink;
|
type = Protocol.Block.Type.embedLink;
|
||||||
View = EmbedLinkView;
|
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 }: Block2HtmlProps) {
|
override async block2html({ block }: Block2HtmlProps) {
|
||||||
const url = block.getProperty('embedLink')?.value;
|
const url = block.getProperty('embedLink')?.value;
|
||||||
return `<p><a href="${url}">${url}</a></p>`;
|
return `<p><a href="${url}">${url}</a></p>`;
|
||||||
|
|||||||
@@ -13,35 +13,6 @@ export class FigmaBlock extends BaseView {
|
|||||||
type = Protocol.Block.Type.figma;
|
type = Protocol.Block.Type.figma;
|
||||||
View = FigmaView;
|
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 }: Block2HtmlProps) {
|
override async block2html({ block }: Block2HtmlProps) {
|
||||||
const figmaUrl = block.getProperty('embedLink')?.value;
|
const figmaUrl = block.getProperty('embedLink')?.value;
|
||||||
return `<p><a href="${figmaUrl}">${figmaUrl}</a></p>`;
|
return `<p><a href="${figmaUrl}">${figmaUrl}</a></p>`;
|
||||||
|
|||||||
@@ -10,25 +10,7 @@ import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
|||||||
export class GroupDividerBlock extends BaseView {
|
export class GroupDividerBlock extends BaseView {
|
||||||
type = Protocol.Block.Type.groupDivider;
|
type = Protocol.Block.Type.groupDivider;
|
||||||
View = GroupDividerView;
|
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(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
return `<hr/>`;
|
return `<hr/>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { BaseView } from '@toeverything/framework/virgo';
|
import {
|
||||||
|
BaseView,
|
||||||
|
BlockEditor,
|
||||||
|
HTML2BlockResult,
|
||||||
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { ImageView } from './ImageView';
|
import { ImageView } from './ImageView';
|
||||||
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
import { Block2HtmlProps } from '../../utils/commonBlockClip';
|
||||||
|
import { getRandomString } from '@toeverything/components/common';
|
||||||
|
|
||||||
export class ImageBlock extends BaseView {
|
export class ImageBlock extends BaseView {
|
||||||
public override selectable = true;
|
public override selectable = true;
|
||||||
@@ -10,27 +15,30 @@ export class ImageBlock extends BaseView {
|
|||||||
View = ImageView;
|
View = ImageView;
|
||||||
|
|
||||||
// TODO: needs to download the image and then upload it to get a new link and then assign it
|
// TODO: needs to download the image and then upload it to get a new link and then assign it
|
||||||
override html2block(
|
override async html2block2({
|
||||||
el: Element,
|
element,
|
||||||
parseEl: (el: Element) => any[]
|
editor,
|
||||||
): any[] | null {
|
}: {
|
||||||
const tag_name = el.tagName;
|
element: Element;
|
||||||
if (tag_name === 'IMG') {
|
editor: BlockEditor;
|
||||||
|
}): Promise<HTML2BlockResult> {
|
||||||
|
if (element.tagName === 'IMG') {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: this.type,
|
type: this.type,
|
||||||
properties: {
|
properties: {
|
||||||
value: '',
|
image: {
|
||||||
url: el.getAttribute('src'),
|
value: getRandomString('image'),
|
||||||
name: el.getAttribute('src'),
|
url: element.getAttribute('src'),
|
||||||
|
name: element.getAttribute('src'),
|
||||||
size: 0,
|
size: 0,
|
||||||
type: 'link',
|
type: 'link',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { AsyncBlock, BaseView } from '@toeverything/framework/virgo';
|
import {
|
||||||
|
AsyncBlock,
|
||||||
|
BaseView,
|
||||||
|
BlockEditor,
|
||||||
|
HTML2BlockResult,
|
||||||
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { defaultTodoProps, NumberedView } from './NumberedView';
|
import { defaultTodoProps, NumberedView } from './NumberedView';
|
||||||
import {
|
import {
|
||||||
Block2HtmlProps,
|
Block2HtmlProps,
|
||||||
commonBlock2HtmlContent,
|
commonBlock2HtmlContent,
|
||||||
|
commonHTML2block,
|
||||||
} from '../../utils/commonBlockClip';
|
} from '../../utils/commonBlockClip';
|
||||||
|
|
||||||
export class NumberedBlock extends BaseView {
|
export class NumberedBlock extends BaseView {
|
||||||
@@ -22,55 +28,38 @@ export class NumberedBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override html2block(
|
override async html2block2({
|
||||||
el: Element,
|
element,
|
||||||
parseEl: (el: Element) => any[]
|
editor,
|
||||||
): any[] | null {
|
}: {
|
||||||
const tag_name = el.tagName;
|
element: Element;
|
||||||
if (tag_name === 'OL') {
|
editor: BlockEditor;
|
||||||
const result = [];
|
}): Promise<HTML2BlockResult> {
|
||||||
for (let i = 0; i < el.children.length; i++) {
|
if (element.tagName === 'OL') {
|
||||||
const blocks_info = parseEl(el.children[i]);
|
const children = Array.from(element.children);
|
||||||
result.push(...blocks_info);
|
const childrenBlockInfos = (
|
||||||
}
|
await Promise.all(
|
||||||
return result.length > 0 ? result : null;
|
children.map(childElement =>
|
||||||
|
this.html2block2({
|
||||||
|
editor,
|
||||||
|
element: childElement,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
.filter(v => v);
|
||||||
|
return childrenBlockInfos.length ? childrenBlockInfos : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag_name == 'LI' && el.textContent.startsWith('[ ] ')) {
|
return commonHTML2block({
|
||||||
const childNodes = el.childNodes;
|
element,
|
||||||
let texts = [];
|
editor,
|
||||||
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,
|
type: this.type,
|
||||||
properties: {
|
tagName: 'LI',
|
||||||
text: { value: texts },
|
});
|
||||||
},
|
|
||||||
children: children,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
return `<ol><li>${await commonBlock2HtmlContent(props)}</li></ol>`;
|
return `<ol><li>${await commonBlock2HtmlContent(props)}</li></ol>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ import { Protocol } from '@toeverything/datasource/db-service';
|
|||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
BaseView,
|
BaseView,
|
||||||
|
BlockEditor,
|
||||||
CreateView,
|
CreateView,
|
||||||
|
HTML2BlockResult,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
import {
|
import {
|
||||||
Block2HtmlProps,
|
Block2HtmlProps,
|
||||||
commonBlock2HtmlContent,
|
commonBlock2HtmlContent,
|
||||||
|
commonHTML2block,
|
||||||
} from '../../utils/commonBlockClip';
|
} from '../../utils/commonBlockClip';
|
||||||
|
|
||||||
import { TextView } from './TextView';
|
import { TextView } from './TextView';
|
||||||
@@ -27,36 +30,19 @@ export class QuoteBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override html2block(
|
override async html2block2({
|
||||||
el: Element,
|
element,
|
||||||
parseEl: (el: Element) => any[]
|
editor,
|
||||||
): any[] | null {
|
}: {
|
||||||
const tag_name = el.tagName;
|
element: Element;
|
||||||
if (tag_name === 'BLOCKQUOTE') {
|
editor: BlockEditor;
|
||||||
const childNodes = el.childNodes;
|
}): Promise<HTML2BlockResult> {
|
||||||
const texts = [];
|
return commonHTML2block({
|
||||||
for (let i = 0; i < childNodes.length; i++) {
|
element,
|
||||||
const blocks_info = parseEl(childNodes[i] as Element);
|
editor,
|
||||||
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,
|
type: this.type,
|
||||||
properties: {
|
tagName: 'BLOCKQUOTE',
|
||||||
text: { value: texts },
|
});
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
@@ -82,52 +68,19 @@ export class CalloutBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override html2block(
|
override async html2block2({
|
||||||
el: Element,
|
element,
|
||||||
parseEl: (el: Element) => any[]
|
editor,
|
||||||
): any[] | null {
|
}: {
|
||||||
const tag_name = el.tagName;
|
element: Element;
|
||||||
if (
|
editor: BlockEditor;
|
||||||
tag_name === 'ASIDE' ||
|
}): Promise<HTML2BlockResult> {
|
||||||
el.firstChild?.nodeValue?.startsWith('<aside>')
|
return commonHTML2block({
|
||||||
) {
|
element,
|
||||||
const childNodes = el.childNodes;
|
editor,
|
||||||
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,
|
type: this.type,
|
||||||
properties: {
|
tagName: 'ASIDE',
|
||||||
text: { value: texts },
|
});
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (el.firstChild?.nodeValue?.startsWith('</aside>')) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import {
|
|||||||
BaseView,
|
BaseView,
|
||||||
CreateView,
|
CreateView,
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
|
HTML2BlockResult,
|
||||||
|
BlockEditor,
|
||||||
} from '@toeverything/framework/virgo';
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { TextView } from './TextView';
|
import { TextView } from './TextView';
|
||||||
|
|
||||||
import { getRandomString } from '@toeverything/components/common';
|
|
||||||
import {
|
import {
|
||||||
Block2HtmlProps,
|
Block2HtmlProps,
|
||||||
commonBlock2HtmlContent,
|
commonBlock2HtmlContent,
|
||||||
|
commonHTML2block,
|
||||||
} from '../../utils/commonBlockClip';
|
} from '../../utils/commonBlockClip';
|
||||||
|
|
||||||
export class TextBlock extends BaseView {
|
export class TextBlock extends BaseView {
|
||||||
@@ -26,93 +27,31 @@ export class TextBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override html2block(
|
override async html2block2({
|
||||||
el: Element,
|
element,
|
||||||
parseEl: (el: Element) => any[]
|
editor,
|
||||||
): any[] | null {
|
}: {
|
||||||
if (el instanceof Text) {
|
element: Element;
|
||||||
// TODO: parsing style
|
editor: BlockEditor;
|
||||||
return el.textContent.split('\n').map(text => {
|
}): Promise<HTML2BlockResult> {
|
||||||
return {
|
return commonHTML2block({
|
||||||
|
element,
|
||||||
|
editor,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
properties: {
|
tagName: [
|
||||||
text: { value: [{ text: text }] },
|
'DIV',
|
||||||
},
|
'P',
|
||||||
children: [],
|
'PRE',
|
||||||
};
|
'B',
|
||||||
|
'A',
|
||||||
|
'EM',
|
||||||
|
'U',
|
||||||
|
'CODE',
|
||||||
|
'S',
|
||||||
|
'DEL',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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(props: Block2HtmlProps) {
|
|
||||||
return `<p>${await commonBlock2HtmlContent(props)}</p>`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Heading1Block extends BaseView {
|
export class Heading1Block extends BaseView {
|
||||||
@@ -128,37 +67,19 @@ export class Heading1Block extends BaseView {
|
|||||||
}
|
}
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
override async html2block2({
|
||||||
override html2block(
|
element,
|
||||||
el: Element,
|
editor,
|
||||||
parseEl: (el: Element) => any[]
|
}: {
|
||||||
): any[] | null {
|
element: Element;
|
||||||
const tag_name = el.tagName;
|
editor: BlockEditor;
|
||||||
if (tag_name === 'H1') {
|
}): Promise<HTML2BlockResult> {
|
||||||
const childNodes = el.childNodes;
|
return commonHTML2block({
|
||||||
const texts = [];
|
element,
|
||||||
for (let i = 0; i < childNodes.length; i++) {
|
editor,
|
||||||
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,
|
type: this.type,
|
||||||
properties: {
|
tagName: 'H1',
|
||||||
text: { value: texts },
|
});
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
@@ -179,37 +100,19 @@ export class Heading2Block extends BaseView {
|
|||||||
}
|
}
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
override async html2block2({
|
||||||
override html2block(
|
element,
|
||||||
el: Element,
|
editor,
|
||||||
parseEl: (el: Element) => any[]
|
}: {
|
||||||
): any[] | null {
|
element: Element;
|
||||||
const tag_name = el.tagName;
|
editor: BlockEditor;
|
||||||
if (tag_name === 'H2') {
|
}): Promise<HTML2BlockResult> {
|
||||||
const childNodes = el.childNodes;
|
return commonHTML2block({
|
||||||
const texts = [];
|
element,
|
||||||
for (let i = 0; i < childNodes.length; i++) {
|
editor,
|
||||||
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,
|
type: this.type,
|
||||||
properties: {
|
tagName: 'H2',
|
||||||
text: { value: texts },
|
});
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
@@ -231,36 +134,19 @@ export class Heading3Block extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override html2block(
|
override async html2block2({
|
||||||
el: Element,
|
element,
|
||||||
parseEl: (el: Element) => any[]
|
editor,
|
||||||
): any[] | null {
|
}: {
|
||||||
const tag_name = el.tagName;
|
element: Element;
|
||||||
if (tag_name === 'H3') {
|
editor: BlockEditor;
|
||||||
const childNodes = el.childNodes;
|
}): Promise<HTML2BlockResult> {
|
||||||
const texts = [];
|
return commonHTML2block({
|
||||||
for (let i = 0; i < childNodes.length; i++) {
|
element,
|
||||||
const blocks_info = parseEl(childNodes[i] as Element);
|
editor,
|
||||||
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,
|
type: this.type,
|
||||||
properties: {
|
tagName: 'H3',
|
||||||
text: { value: texts },
|
});
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { BaseView } from '@toeverything/framework/virgo';
|
import {
|
||||||
|
BaseView,
|
||||||
|
BlockEditor,
|
||||||
|
HTML2BlockResult,
|
||||||
|
} from '@toeverything/framework/virgo';
|
||||||
import { Protocol } from '@toeverything/datasource/db-service';
|
import { Protocol } from '@toeverything/datasource/db-service';
|
||||||
import { withTreeViewChildren } from '../../utils/WithTreeViewChildren';
|
import { withTreeViewChildren } from '../../utils/WithTreeViewChildren';
|
||||||
import { TodoView, defaultTodoProps } from './TodoView';
|
import { TodoView, defaultTodoProps } from './TodoView';
|
||||||
@@ -6,6 +10,7 @@ import type { TodoAsyncBlock } from './types';
|
|||||||
import {
|
import {
|
||||||
Block2HtmlProps,
|
Block2HtmlProps,
|
||||||
commonBlock2HtmlContent,
|
commonBlock2HtmlContent,
|
||||||
|
commonHTML2block,
|
||||||
} from '../../utils/commonBlockClip';
|
} from '../../utils/commonBlockClip';
|
||||||
|
|
||||||
export class TodoBlock extends BaseView {
|
export class TodoBlock extends BaseView {
|
||||||
@@ -22,56 +27,43 @@ export class TodoBlock extends BaseView {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
override html2block(
|
override async html2block2({
|
||||||
el: Element,
|
element,
|
||||||
parseEl: (el: Element) => any[]
|
editor,
|
||||||
): any[] | null {
|
}: {
|
||||||
const tag_name = el.tagName;
|
element: Element;
|
||||||
if (tag_name === 'UL') {
|
editor: BlockEditor;
|
||||||
const result = [];
|
}): Promise<HTML2BlockResult> {
|
||||||
for (let i = 0; i < el.children.length; i++) {
|
if (element.tagName === 'UL') {
|
||||||
const blocks_info = parseEl(el.children[i]);
|
const firstList = element.querySelector('li');
|
||||||
result.push(...blocks_info);
|
if (!firstList || !firstList.innerText.startsWith('[ ] ')) {
|
||||||
}
|
|
||||||
return result.length > 0 ? result : 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 null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const children = Array.from(element.children);
|
||||||
|
const childrenBlockInfos = (
|
||||||
|
await Promise.all(
|
||||||
|
children.map(childElement =>
|
||||||
|
this.html2block2({
|
||||||
|
editor,
|
||||||
|
element: childElement,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
.filter(v => v);
|
||||||
|
return childrenBlockInfos.length ? childrenBlockInfos : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return commonHTML2block({
|
||||||
|
element,
|
||||||
|
editor,
|
||||||
|
type: this.type,
|
||||||
|
tagName: 'LI',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
override async block2html(props: Block2HtmlProps) {
|
override async block2html(props: Block2HtmlProps) {
|
||||||
return `<ul><li>[ ] ${await commonBlock2HtmlContent(props)}</li></ul>`;
|
return `<ul><li>[ ] ${await commonBlock2HtmlContent(props)}</li></ul>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,35 +16,6 @@ export class YoutubeBlock extends BaseView {
|
|||||||
type = Protocol.Block.Type.youtube;
|
type = Protocol.Block.Type.youtube;
|
||||||
View = YoutubeView;
|
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) {
|
override async block2Text(block: AsyncBlock, selectInfo: SelectBlock) {
|
||||||
return block.getProperty('embedLink')?.value ?? '';
|
return block.getProperty('embedLink')?.value ?? '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
BlockEditor,
|
BlockEditor,
|
||||||
|
HTML2BlockResult,
|
||||||
SelectBlock,
|
SelectBlock,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
|
import { BlockFlavorKeys } from '@toeverything/datasource/db-service';
|
||||||
|
|
||||||
export type Block2HtmlProps = {
|
export type Block2HtmlProps = {
|
||||||
editor: BlockEditor;
|
editor: BlockEditor;
|
||||||
@@ -28,3 +30,28 @@ export const commonBlock2HtmlContent = async ({
|
|||||||
);
|
);
|
||||||
return `${html}${childrenHtml}`;
|
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",
|
"date-fns": "^2.28.0",
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
"hotkeys-js": "^3.9.4",
|
"hotkeys-js": "^3.9.4",
|
||||||
|
"html-escaper": "^3.0.3",
|
||||||
"lru-cache": "^7.10.1",
|
"lru-cache": "^7.10.1",
|
||||||
|
"marked": "^4.0.19",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"slate": "^0.81.0",
|
"slate": "^0.81.0",
|
||||||
"style9": "^0.13.3"
|
"style9": "^0.13.3"
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
import { Protocol, BlockFlavorKeys } from '@toeverything/datasource/db-service';
|
|
||||||
import { Editor } from '../editor';
|
|
||||||
import { ClipBlockInfo } from './types';
|
|
||||||
|
|
||||||
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 dispose() {
|
|
||||||
this.editor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,29 +3,21 @@ import { HookType } from '@toeverything/components/editor-core';
|
|||||||
import { Editor } from '../editor';
|
import { Editor } from '../editor';
|
||||||
import { Copy } from './copy';
|
import { Copy } from './copy';
|
||||||
import { Paste } from './paste';
|
import { Paste } from './paste';
|
||||||
import ClipboardParse from './clipboard-parse';
|
|
||||||
import { MarkdownParser } from './markdown-parse';
|
|
||||||
import { ClipboardUtils } from './clipboardUtils';
|
import { ClipboardUtils } from './clipboardUtils';
|
||||||
|
|
||||||
export class Clipboard {
|
export class Clipboard {
|
||||||
private _clipboardEventDispatcher: ClipboardEventDispatcher;
|
private _clipboardEventDispatcher: ClipboardEventDispatcher;
|
||||||
private _copy: Copy;
|
private _copy: Copy;
|
||||||
private _paste: Paste;
|
private _paste: Paste;
|
||||||
private _clipboardParse: ClipboardParse;
|
private readonly _supportMarkdownPaste = true;
|
||||||
private _markdownParse: MarkdownParser;
|
|
||||||
public clipboardUtils: ClipboardUtils;
|
public clipboardUtils: ClipboardUtils;
|
||||||
|
|
||||||
constructor(editor: Editor, clipboardTarget: HTMLElement) {
|
constructor(editor: Editor, clipboardTarget: HTMLElement) {
|
||||||
this._clipboardParse = new ClipboardParse(editor);
|
|
||||||
this._markdownParse = new MarkdownParser();
|
|
||||||
this.clipboardUtils = new ClipboardUtils(editor);
|
this.clipboardUtils = new ClipboardUtils(editor);
|
||||||
this._copy = new Copy(editor);
|
this._copy = new Copy(editor);
|
||||||
|
|
||||||
this._paste = new Paste(
|
this._paste = new Paste(editor, this._supportMarkdownPaste);
|
||||||
editor,
|
|
||||||
this._clipboardParse,
|
|
||||||
this._markdownParse
|
|
||||||
);
|
|
||||||
|
|
||||||
this._clipboardEventDispatcher = new ClipboardEventDispatcher(
|
this._clipboardEventDispatcher = new ClipboardEventDispatcher(
|
||||||
editor,
|
editor,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
||||||
import { Clip } from './clip';
|
import { Clip } from './clip';
|
||||||
|
import { commonHTML2Block, commonHTML2Text } from './utils';
|
||||||
|
|
||||||
export class ClipboardUtils {
|
export class ClipboardUtils {
|
||||||
private _editor: Editor;
|
private _editor: Editor;
|
||||||
@@ -146,6 +147,65 @@ export class ClipboardUtils {
|
|||||||
).join('');
|
).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async convertHTMLString2Blocks(html: string) {
|
||||||
|
const htmlEl = document.createElement('html');
|
||||||
|
htmlEl.innerHTML = html;
|
||||||
|
htmlEl.querySelector('head')?.remove();
|
||||||
|
|
||||||
|
return this.convertHtml2Block(htmlEl);
|
||||||
|
}
|
||||||
|
async convertHtml2Block(element: Element): Promise<ClipBlockInfo[]> {
|
||||||
|
const editableViews = this._editor.getEditableViews();
|
||||||
|
// 如果block能够捕捉htmlElement则返回block的html2block
|
||||||
|
const [clipBlockInfos] = (
|
||||||
|
await Promise.all(
|
||||||
|
editableViews.map(editableView => {
|
||||||
|
return editableView?.html2block2?.({
|
||||||
|
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.convertHtml2Block(
|
||||||
|
childElement
|
||||||
|
);
|
||||||
|
|
||||||
|
if (clipBlockInfos && clipBlockInfos.length) {
|
||||||
|
return clipBlockInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.commonHTML2Block(childElement);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
|
.filter(v => v);
|
||||||
|
}
|
||||||
|
|
||||||
|
commonHTML2Block = commonHTML2Block;
|
||||||
|
|
||||||
|
commonHTML2Text = commonHTML2Text;
|
||||||
|
|
||||||
|
textToBlock = (clipData: string = ''): ClipBlockInfo[] => {
|
||||||
|
return clipData.split('\n').map((str: string) => {
|
||||||
|
return {
|
||||||
|
type: 'text',
|
||||||
|
properties: {
|
||||||
|
text: { value: [{ text: str }] },
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
async page2html() {
|
async page2html() {
|
||||||
const rootBlockId = this._editor.getRootBlockId();
|
const rootBlockId = this._editor.getRootBlockId();
|
||||||
if (!rootBlockId) {
|
if (!rootBlockId) {
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ import { Editor } from '../editor';
|
|||||||
import { SelectInfo } from '../selection';
|
import { SelectInfo } from '../selection';
|
||||||
import { OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
import { OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
||||||
import { Clip } from './clip';
|
import { Clip } from './clip';
|
||||||
import ClipboardParse from './clipboard-parse';
|
|
||||||
import { ClipboardUtils } from './clipboardUtils';
|
import { ClipboardUtils } from './clipboardUtils';
|
||||||
class Copy {
|
class Copy {
|
||||||
private _editor: Editor;
|
private _editor: Editor;
|
||||||
private _clipboardParse: ClipboardParse;
|
|
||||||
private _utils: ClipboardUtils;
|
private _utils: ClipboardUtils;
|
||||||
constructor(editor: Editor) {
|
constructor(editor: Editor) {
|
||||||
this._editor = editor;
|
this._editor = editor;
|
||||||
this._clipboardParse = new ClipboardParse(editor);
|
|
||||||
this._utils = new ClipboardUtils(editor);
|
this._utils = new ClipboardUtils(editor);
|
||||||
this.handleCopy = this.handleCopy.bind(this);
|
this.handleCopy = this.handleCopy.bind(this);
|
||||||
}
|
}
|
||||||
@@ -53,9 +50,6 @@ class Copy {
|
|||||||
const htmlClip = await this._getHtmlClip();
|
const htmlClip = await this._getHtmlClip();
|
||||||
|
|
||||||
clips.push(htmlClip);
|
clips.push(htmlClip);
|
||||||
// const htmlClip = await this._clipboardParse.generateHtml();
|
|
||||||
// htmlClip &&
|
|
||||||
// clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip));
|
|
||||||
|
|
||||||
return clips;
|
return clips;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,11 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { Editor } from '../editor';
|
import { Editor } from '../editor';
|
||||||
import { AsyncBlock } from '../block';
|
import { AsyncBlock } from '../block';
|
||||||
import ClipboardParse from './clipboard-parse';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
import { SelectInfo } from '../selection';
|
|
||||||
import {
|
|
||||||
Protocol,
|
|
||||||
BlockFlavorKeys,
|
|
||||||
services,
|
|
||||||
} from '@toeverything/datasource/db-service';
|
|
||||||
import { MarkdownParser } from './markdown-parse';
|
import { MarkdownParser } from './markdown-parse';
|
||||||
const SUPPORT_MARKDOWN_PASTE = true;
|
import { escape } from 'html-escaper';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
import { ClipboardUtils } from './clipboardUtils';
|
||||||
type TextValueItem = {
|
type TextValueItem = {
|
||||||
text: string;
|
text: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@@ -24,22 +19,24 @@ type TextValueItem = {
|
|||||||
export class Paste {
|
export class Paste {
|
||||||
private _editor: Editor;
|
private _editor: Editor;
|
||||||
private _markdownParse: MarkdownParser;
|
private _markdownParse: MarkdownParser;
|
||||||
private _clipboardParse: ClipboardParse;
|
private readonly _supportMarkdownPaste: boolean = true;
|
||||||
constructor(
|
private _utils: ClipboardUtils;
|
||||||
editor: Editor,
|
constructor(editor: Editor, supportMarkdownPaste?: boolean) {
|
||||||
clipboardParse: ClipboardParse,
|
this._markdownParse = new MarkdownParser();
|
||||||
markdownParse: MarkdownParser
|
|
||||||
) {
|
|
||||||
this._markdownParse = markdownParse;
|
|
||||||
this._clipboardParse = clipboardParse;
|
|
||||||
this._editor = editor;
|
this._editor = editor;
|
||||||
|
this._supportMarkdownPaste =
|
||||||
|
supportMarkdownPaste ?? this._supportMarkdownPaste;
|
||||||
|
|
||||||
|
this._utils = new ClipboardUtils(editor);
|
||||||
this.handlePaste = this.handlePaste.bind(this);
|
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.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED,
|
||||||
OFFICE_CLIPBOARD_MIMETYPE.HTML,
|
OFFICE_CLIPBOARD_MIMETYPE.HTML,
|
||||||
OFFICE_CLIPBOARD_MIMETYPE.TEXT,
|
OFFICE_CLIPBOARD_MIMETYPE.TEXT,
|
||||||
];
|
];
|
||||||
|
|
||||||
public handlePaste(e: ClipboardEvent) {
|
public handlePaste(e: ClipboardEvent) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
@@ -52,60 +49,59 @@ export class Paste {
|
|||||||
this._pasteContent(clipboardData);
|
this._pasteContent(clipboardData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public getOptimalClip(clipboardData: any) {
|
// Get the most needed clipboard data based on `_optimalMimeTypes` order
|
||||||
const mimeTypeArr = Paste._optimalMimeType;
|
public getOptimalClip(clipboardData: ClipboardEvent['clipboardData']) {
|
||||||
|
for (let i = 0; i < Paste._optimalMimeTypes.length; i++) {
|
||||||
for (let i = 0; i < mimeTypeArr.length; i++) {
|
const mimeType = Paste._optimalMimeTypes[i];
|
||||||
const data =
|
const data = clipboardData.getData(mimeType);
|
||||||
clipboardData[mimeTypeArr[i]] ||
|
|
||||||
clipboardData.getData(mimeTypeArr[i]);
|
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
return {
|
return {
|
||||||
type: mimeTypeArr[i],
|
type: mimeType,
|
||||||
data: data,
|
data: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _pasteContent(clipboardData: any) {
|
private _pasteContent(clipboardData: ClipboardEvent['clipboardData']) {
|
||||||
const originClip: { data: any; type: any } = this.getOptimalClip(
|
const optimalClip = this.getOptimalClip(clipboardData);
|
||||||
clipboardData
|
if (
|
||||||
) as { data: any; type: any };
|
optimalClip?.type ===
|
||||||
|
OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED
|
||||||
|
) {
|
||||||
|
return this._firePasteEditAction(optimalClip.data);
|
||||||
|
}
|
||||||
|
|
||||||
const originTextClipData = clipboardData.getData(
|
const textClipData = escape(
|
||||||
OFFICE_CLIPBOARD_MIMETYPE.TEXT
|
clipboardData.getData(OFFICE_CLIPBOARD_MIMETYPE.TEXT)
|
||||||
);
|
);
|
||||||
|
const shouldConvertMarkdown =
|
||||||
|
this._supportMarkdownPaste &&
|
||||||
|
this._markdownParse.checkIfTextContainsMd(textClipData);
|
||||||
|
|
||||||
let clipData = originClip['data'];
|
if (
|
||||||
|
optimalClip?.type === OFFICE_CLIPBOARD_MIMETYPE.HTML &&
|
||||||
if (originClip['type'] === OFFICE_CLIPBOARD_MIMETYPE.TEXT) {
|
!shouldConvertMarkdown
|
||||||
clipData = Paste._excapeHtml(clipData);
|
) {
|
||||||
|
return this._pasteHtml(optimalClip.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (originClip['type']) {
|
if (shouldConvertMarkdown) {
|
||||||
/** Protocol paste */
|
const md2html = marked.parse(textClipData);
|
||||||
case OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED:
|
return this._pasteHtml(md2html);
|
||||||
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:
|
return this._pasteText(textClipData);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
private async _firePasteEditAction(clipboardData: any) {
|
private async _firePasteEditAction(clipboardData: string) {
|
||||||
const clipInfo: InnerClipInfo = JSON.parse(clipboardData);
|
const clipInfo: InnerClipInfo = JSON.parse(clipboardData);
|
||||||
clipInfo && this._insertBlocks(clipInfo.data, clipInfo.select);
|
clipInfo && this._insertBlocks(clipInfo.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _pasteFile(clipboardData: any) {
|
private async _pasteFile(clipboardData: any) {
|
||||||
const file = Paste._getImageFile(clipboardData);
|
const file = Paste._getImageFile(clipboardData);
|
||||||
if (file) {
|
if (file) {
|
||||||
@@ -139,26 +135,7 @@ export class Paste {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static _isTextEditBlock(type: BlockFlavorKeys) {
|
private async _insertBlocks(blocks: ClipBlockInfo[]) {
|
||||||
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
|
|
||||||
) {
|
|
||||||
if (blocks.length === 0) {
|
if (blocks.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -172,15 +149,16 @@ export class Paste {
|
|||||||
const selectedBlock = await this._editor.getBlockById(
|
const selectedBlock = await this._editor.getBlockById(
|
||||||
currentSelectInfo.blocks[0].blockId
|
currentSelectInfo.blocks[0].blockId
|
||||||
);
|
);
|
||||||
const isSelectedBlockCanEdit = Paste._isTextEditBlock(
|
const isSelectedBlockCanEdit = this._editor.isEditableView(
|
||||||
selectedBlock.type
|
selectedBlock?.type
|
||||||
);
|
);
|
||||||
|
|
||||||
const blockView = this._editor.getView(selectedBlock.type);
|
const blockView = this._editor.getView(selectedBlock.type);
|
||||||
const isSelectedBlockEmpty = blockView.isEmpty(selectedBlock);
|
const isSelectedBlockEmpty = blockView.isEmpty(selectedBlock);
|
||||||
if (isSelectedBlockCanEdit && !isSelectedBlockEmpty) {
|
if (isSelectedBlockCanEdit && !isSelectedBlockEmpty) {
|
||||||
const shouldSplitBlock =
|
const shouldSplitBlock =
|
||||||
blocks.length > 1 ||
|
blocks.length > 1 ||
|
||||||
!Paste._isTextEditBlock(blocks[0].type);
|
!this._editor.isEditableView(blocks[0].type);
|
||||||
const pureText = !shouldSplitBlock
|
const pureText = !shouldSplitBlock
|
||||||
? blocks[0].properties.text.value
|
? blocks[0].properties.text.value
|
||||||
: [{ text: '' }];
|
: [{ text: '' }];
|
||||||
@@ -400,7 +378,7 @@ export class Paste {
|
|||||||
}
|
}
|
||||||
private async _setEndSelectToBlock(blockId: string) {
|
private async _setEndSelectToBlock(blockId: string) {
|
||||||
const block = await this._editor.getBlockById(blockId);
|
const block = await this._editor.getBlockById(blockId);
|
||||||
const isBlockCanEdit = Paste._isTextEditBlock(block.type);
|
const isBlockCanEdit = this._editor.isEditableView(block.type);
|
||||||
if (!isBlockCanEdit) {
|
if (!isBlockCanEdit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -445,31 +423,13 @@ export class Paste {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _pasteHtml(clipData: any, originTextClipData: any) {
|
private async _pasteHtml(clipData: string) {
|
||||||
if (SUPPORT_MARKDOWN_PASTE) {
|
const block2 = await this._utils.convertHTMLString2Blocks(clipData);
|
||||||
const hasMarkdown =
|
await this._insertBlocks(block2);
|
||||||
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);
|
private async _pasteText(clipData: string) {
|
||||||
|
const blocks = this._utils.textToBlock(clipData);
|
||||||
await this._insertBlocks(blocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _pasteText(clipData: any, originTextClipData: any) {
|
|
||||||
const blocks = this._clipboardParse.text2blocks(clipData);
|
|
||||||
await this._insertBlocks(blocks);
|
await this._insertBlocks(blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,16 +440,4 @@ export class Paste {
|
|||||||
}
|
}
|
||||||
return;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
BlockFlavorKeys,
|
BlockFlavorKeys,
|
||||||
DefaultColumnsValue,
|
DefaultColumnsValue,
|
||||||
|
ReturnEditorBlock,
|
||||||
} from '@toeverything/datasource/db-service';
|
} from '@toeverything/datasource/db-service';
|
||||||
import { SelectInfo } from '../selection';
|
import { SelectInfo } from '../selection';
|
||||||
|
|
||||||
|
// export type ClipBlockInfo = Partial<ReturnEditorBlock> & {
|
||||||
|
// children?: ClipBlockInfo[];
|
||||||
|
// };
|
||||||
export const OFFICE_CLIPBOARD_MIMETYPE = {
|
export const OFFICE_CLIPBOARD_MIMETYPE = {
|
||||||
DOCS_DOCUMENT_SLICE_CLIP_WRAPPED: 'affine/x-c+w',
|
DOCS_DOCUMENT_SLICE_CLIP_WRAPPED: 'affine/x-c+w',
|
||||||
HTML: 'text/html',
|
HTML: 'text/html',
|
||||||
@@ -20,8 +24,9 @@ export const OFFICE_CLIPBOARD_MIMETYPE = {
|
|||||||
export interface ClipBlockInfo {
|
export interface ClipBlockInfo {
|
||||||
type: BlockFlavorKeys;
|
type: BlockFlavorKeys;
|
||||||
properties?: Partial<DefaultColumnsValue>;
|
properties?: Partial<DefaultColumnsValue>;
|
||||||
children: ClipBlockInfo[];
|
children?: ClipBlockInfo[];
|
||||||
}
|
}
|
||||||
|
export type HTML2BlockResult = ClipBlockInfo[] | null;
|
||||||
|
|
||||||
export interface InnerClipInfo {
|
export interface InnerClipInfo {
|
||||||
select: SelectInfo;
|
select: SelectInfo;
|
||||||
|
|||||||
150
libs/components/editor-core/src/editor/clipboard/utils.ts
Normal file
150
libs/components/editor-core/src/editor/clipboard/utils.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import { getRandomString } from '@toeverything/components/common';
|
||||||
|
import { ClipBlockInfo } from '@toeverything/components/editor-core';
|
||||||
|
import { BlockFlavorKeys, Protocol } from '@toeverything/datasource/db-service';
|
||||||
|
|
||||||
|
const getIsLink = (htmlElement: HTMLElement) => {
|
||||||
|
return ['A', 'IMG'].includes(htmlElement.tagName);
|
||||||
|
};
|
||||||
|
const getTextStyle = (htmlElement: HTMLElement) => {
|
||||||
|
const tagName = htmlElement.tagName;
|
||||||
|
const textStyle: { [key: string]: any } = {};
|
||||||
|
|
||||||
|
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 (getIsLink(htmlElement)) {
|
||||||
|
textStyle['type'] = 'link';
|
||||||
|
textStyle['url'] =
|
||||||
|
htmlElement.getAttribute('href') || htmlElement.getAttribute('src');
|
||||||
|
textStyle['id'] = getRandomString('link');
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 getSingleLabelHTMLElementContent = (htmlElement: HTMLElement) => {
|
||||||
|
if (htmlElement.tagName === 'IMG') {
|
||||||
|
return (
|
||||||
|
htmlElement.getAttribute('alt') || htmlElement.getAttribute('src')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
export const commonHTML2Text = (
|
||||||
|
element: HTMLElement | Node,
|
||||||
|
textStyle: { [key: string]: any } = {},
|
||||||
|
ignoreEmptyText = true
|
||||||
|
) => {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -255,6 +255,14 @@ export class Editor implements Virgo {
|
|||||||
getView(type: string) {
|
getView(type: string) {
|
||||||
return this.views[type];
|
return this.views[type];
|
||||||
}
|
}
|
||||||
|
getEditableViews() {
|
||||||
|
return Object.values(this.views)
|
||||||
|
.map(view => (view.activatable ? view : null))
|
||||||
|
.filter(v => v);
|
||||||
|
}
|
||||||
|
isEditableView(type: string) {
|
||||||
|
return this.views[type].activatable;
|
||||||
|
}
|
||||||
|
|
||||||
private async _initBlock(
|
private async _initBlock(
|
||||||
blockData: ReturnEditorBlock
|
blockData: ReturnEditorBlock
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import ClipboardParseInner from './clipboard/clipboard-parse';
|
export { HTML2BlockResult, ClipBlockInfo } from './clipboard/types';
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
export const ClipboardParse = ClipboardParseInner;
|
|
||||||
|
|
||||||
export { AsyncBlock } from './block';
|
export { AsyncBlock } from './block';
|
||||||
export * from './commands/types';
|
export * from './commands/types';
|
||||||
export { Editor as BlockEditor } from './editor';
|
export { Editor as BlockEditor } from './editor';
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
import { ComponentType, ReactElement } from 'react';
|
import { ComponentType, ReactElement } from 'react';
|
||||||
|
import type { Column } from '@toeverything/datasource/db-service';
|
||||||
import type {
|
|
||||||
Column,
|
|
||||||
DefaultColumnsValue,
|
|
||||||
} from '@toeverything/datasource/db-service';
|
|
||||||
import {
|
import {
|
||||||
ArrayOperation,
|
ArrayOperation,
|
||||||
BlockDecoration,
|
BlockDecoration,
|
||||||
MapOperation,
|
MapOperation,
|
||||||
} from '@toeverything/datasource/jwt';
|
} from '@toeverything/datasource/jwt';
|
||||||
import { cloneDeep } from '@toeverything/utils';
|
|
||||||
import type { EventData } from '../block';
|
import type { EventData } from '../block';
|
||||||
import { AsyncBlock } from '../block';
|
import { AsyncBlock } from '../block';
|
||||||
import type { Editor } from '../editor';
|
import type { Editor } from '../editor';
|
||||||
import { SelectBlock } from '../selection';
|
import { SelectBlock } from '../selection';
|
||||||
|
import { HTML2BlockResult } from '../clipboard/types';
|
||||||
export interface CreateView {
|
export interface CreateView {
|
||||||
block: AsyncBlock;
|
block: AsyncBlock;
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
@@ -135,6 +130,13 @@ export abstract class BaseView {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async html2block2(props: {
|
||||||
|
element: HTMLElement | Node;
|
||||||
|
editor: Editor;
|
||||||
|
}): Promise<HTML2BlockResult> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
async block2Text(
|
async block2Text(
|
||||||
block: AsyncBlock,
|
block: AsyncBlock,
|
||||||
// The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range
|
// The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range
|
||||||
|
|||||||
101
pnpm-lock.yaml
generated
101
pnpm-lock.yaml
generated
@@ -454,8 +454,12 @@ importers:
|
|||||||
date-fns: ^2.28.0
|
date-fns: ^2.28.0
|
||||||
eventemitter3: ^4.0.7
|
eventemitter3: ^4.0.7
|
||||||
hotkeys-js: ^3.9.4
|
hotkeys-js: ^3.9.4
|
||||||
|
html-escaper: ^3.0.3
|
||||||
|
i: ^0.3.7
|
||||||
lru-cache: ^7.10.1
|
lru-cache: ^7.10.1
|
||||||
|
marked: ^4.0.19
|
||||||
nanoid: ^4.0.0
|
nanoid: ^4.0.0
|
||||||
|
npm: ^8.18.0
|
||||||
slate: ^0.81.0
|
slate: ^0.81.0
|
||||||
style9: ^0.13.3
|
style9: ^0.13.3
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -463,8 +467,12 @@ importers:
|
|||||||
date-fns: 2.28.0
|
date-fns: 2.28.0
|
||||||
eventemitter3: 4.0.7
|
eventemitter3: 4.0.7
|
||||||
hotkeys-js: 3.9.4
|
hotkeys-js: 3.9.4
|
||||||
|
html-escaper: 3.0.3
|
||||||
|
i: 0.3.7
|
||||||
lru-cache: 7.12.0
|
lru-cache: 7.12.0
|
||||||
|
marked: 4.0.19
|
||||||
nanoid: 4.0.0
|
nanoid: 4.0.0
|
||||||
|
npm: 8.18.0
|
||||||
slate: 0.81.1
|
slate: 0.81.1
|
||||||
style9: 0.13.3
|
style9: 0.13.3
|
||||||
|
|
||||||
@@ -11107,6 +11115,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/html-escaper/3.0.3:
|
||||||
|
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/html-minifier-terser/6.1.0:
|
/html-minifier-terser/6.1.0:
|
||||||
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
|
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -11306,6 +11318,11 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/i/0.3.7:
|
||||||
|
resolution: {integrity: sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==}
|
||||||
|
engines: {node: '>=0.4'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/iconv-lite/0.4.24:
|
/iconv-lite/0.4.24:
|
||||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -13578,6 +13595,12 @@ packages:
|
|||||||
tmpl: 1.0.5
|
tmpl: 1.0.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/marked/4.0.19:
|
||||||
|
resolution: {integrity: sha512-rgQF/OxOiLcvgUAj1Q1tAf4Bgxn5h5JZTp04Fx4XUkVhs7B+7YA9JEWJhJpoO8eJt8MkZMwqLCNeNqj1bCREZQ==}
|
||||||
|
engines: {node: '>= 12'}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/mdn-data/2.0.14:
|
/mdn-data/2.0.14:
|
||||||
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
|
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -13998,6 +14021,84 @@ packages:
|
|||||||
path-key: 4.0.0
|
path-key: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/npm/8.18.0:
|
||||||
|
resolution: {integrity: sha512-G07/yKvNUwhwxYhk8BxcuDPB/4s+y755i6CnH3lf9LQBHP5siUx66WbuNGWEnN3xaBER4+IR3OWApKX7eBO5Dw==}
|
||||||
|
engines: {node: ^12.13.0 || ^14.15.0 || >=16}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
bundledDependencies:
|
||||||
|
- '@isaacs/string-locale-compare'
|
||||||
|
- '@npmcli/arborist'
|
||||||
|
- '@npmcli/ci-detect'
|
||||||
|
- '@npmcli/config'
|
||||||
|
- '@npmcli/fs'
|
||||||
|
- '@npmcli/map-workspaces'
|
||||||
|
- '@npmcli/package-json'
|
||||||
|
- '@npmcli/run-script'
|
||||||
|
- abbrev
|
||||||
|
- archy
|
||||||
|
- cacache
|
||||||
|
- chalk
|
||||||
|
- chownr
|
||||||
|
- cli-columns
|
||||||
|
- cli-table3
|
||||||
|
- columnify
|
||||||
|
- fastest-levenshtein
|
||||||
|
- glob
|
||||||
|
- graceful-fs
|
||||||
|
- hosted-git-info
|
||||||
|
- ini
|
||||||
|
- init-package-json
|
||||||
|
- is-cidr
|
||||||
|
- json-parse-even-better-errors
|
||||||
|
- libnpmaccess
|
||||||
|
- libnpmdiff
|
||||||
|
- libnpmexec
|
||||||
|
- libnpmfund
|
||||||
|
- libnpmhook
|
||||||
|
- libnpmorg
|
||||||
|
- libnpmpack
|
||||||
|
- libnpmpublish
|
||||||
|
- libnpmsearch
|
||||||
|
- libnpmteam
|
||||||
|
- libnpmversion
|
||||||
|
- make-fetch-happen
|
||||||
|
- minipass
|
||||||
|
- minipass-pipeline
|
||||||
|
- mkdirp
|
||||||
|
- mkdirp-infer-owner
|
||||||
|
- ms
|
||||||
|
- node-gyp
|
||||||
|
- nopt
|
||||||
|
- npm-audit-report
|
||||||
|
- npm-install-checks
|
||||||
|
- npm-package-arg
|
||||||
|
- npm-pick-manifest
|
||||||
|
- npm-profile
|
||||||
|
- npm-registry-fetch
|
||||||
|
- npm-user-validate
|
||||||
|
- npmlog
|
||||||
|
- opener
|
||||||
|
- p-map
|
||||||
|
- pacote
|
||||||
|
- parse-conflict-json
|
||||||
|
- proc-log
|
||||||
|
- qrcode-terminal
|
||||||
|
- read
|
||||||
|
- read-package-json
|
||||||
|
- read-package-json-fast
|
||||||
|
- readdir-scoped-modules
|
||||||
|
- rimraf
|
||||||
|
- semver
|
||||||
|
- ssri
|
||||||
|
- tar
|
||||||
|
- text-table
|
||||||
|
- tiny-relative-date
|
||||||
|
- treeverse
|
||||||
|
- validate-npm-package-name
|
||||||
|
- which
|
||||||
|
- write-file-atomic
|
||||||
|
|
||||||
/npmlog/4.1.2:
|
/npmlog/4.1.2:
|
||||||
resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==}
|
resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user