refactor: recode html2block

This commit is contained in:
QiShaoXuan
2022-08-24 14:41:21 +08:00
parent 4b904ef762
commit 012c0441d0
25 changed files with 693 additions and 828 deletions
@@ -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 { defaultBulletProps, BulletView } from './BulletView';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
export class BulletBlock extends BaseView {
public type = Protocol.Block.Type.bullet;
@@ -18,49 +24,41 @@ export class BulletBlock extends BaseView {
}
return block;
}
override async html2block2({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
if (element.tagName === 'UL') {
const firstList = element.querySelector('li');
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.html2block2({
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(props: Block2HtmlProps) {
@@ -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 { CodeView } from './CodeView';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
export class CodeBlock extends BaseView {
@@ -21,39 +27,19 @@ export class CodeBlock extends BaseView {
return block;
}
// 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 html2block2({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'CODE',
});
}
override async block2html(props: Block2HtmlProps) {
@@ -1,34 +1,34 @@
import {
AsyncBlock,
BaseView,
BlockEditor,
HTML2BlockResult,
SelectBlock,
} from '@toeverything/framework/virgo';
import { Protocol } from '@toeverything/datasource/db-service';
import { DividerView } from './divider-view';
import { Block2HtmlProps } from '../../utils/commonBlockClip';
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 html2block2({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'HR',
ignoreEmptyElement: false,
});
}
override async block2html(props: Block2HtmlProps) {
return `<hr/>`;
}
@@ -13,29 +13,6 @@ 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 }: Block2HtmlProps) {
const url = block.getProperty('embedLink')?.value;
return `<p><a href="${url}">${url}</a></p>`;
@@ -13,35 +13,6 @@ 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 }: Block2HtmlProps) {
const figmaUrl = block.getProperty('embedLink')?.value;
return `<p><a href="${figmaUrl}">${figmaUrl}</a></p>`;
@@ -10,25 +10,7 @@ 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(props: Block2HtmlProps) {
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 { ImageView } from './ImageView';
import { Block2HtmlProps } from '../../utils/commonBlockClip';
import { getRandomString } from '@toeverything/components/common';
export class ImageBlock extends BaseView {
public override selectable = true;
@@ -10,27 +15,30 @@ 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 html2block2({
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;
}
@@ -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 { defaultTodoProps, NumberedView } from './NumberedView';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
export class NumberedBlock extends BaseView {
@@ -22,55 +28,38 @@ export class NumberedBlock extends BaseView {
return block;
}
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 html2block2({
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.html2block2({
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(props: Block2HtmlProps) {
return `<ol><li>${await commonBlock2HtmlContent(props)}</li></ol>`;
}
@@ -2,11 +2,14 @@ import { Protocol } from '@toeverything/datasource/db-service';
import {
AsyncBlock,
BaseView,
BlockEditor,
CreateView,
HTML2BlockResult,
} from '@toeverything/framework/virgo';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
import { TextView } from './TextView';
@@ -27,36 +30,19 @@ export class QuoteBlock extends BaseView {
return block;
}
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 html2block2({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'BLOCKQUOTE',
});
}
override async block2html(props: Block2HtmlProps) {
@@ -82,52 +68,19 @@ export class CalloutBlock extends BaseView {
return block;
}
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 html2block2({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'ASIDE',
});
}
override async block2html(props: Block2HtmlProps) {
@@ -2,14 +2,15 @@ import {
BaseView,
CreateView,
AsyncBlock,
HTML2BlockResult,
BlockEditor,
} from '@toeverything/framework/virgo';
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 {
@@ -26,92 +27,30 @@ export class TextBlock extends BaseView {
return block;
}
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(props: Block2HtmlProps) {
return `<p>${await commonBlock2HtmlContent(props)}</p>`;
override async html2block2({
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',
],
});
}
}
@@ -128,37 +67,19 @@ export class Heading1Block extends BaseView {
}
return block;
}
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 html2block2({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'H1',
});
}
override async block2html(props: Block2HtmlProps) {
@@ -179,37 +100,19 @@ export class Heading2Block extends BaseView {
}
return block;
}
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 html2block2({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'H2',
});
}
override async block2html(props: Block2HtmlProps) {
@@ -231,36 +134,19 @@ export class Heading3Block extends BaseView {
return block;
}
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 html2block2({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'H3',
});
}
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 { withTreeViewChildren } from '../../utils/WithTreeViewChildren';
import { TodoView, defaultTodoProps } from './TodoView';
@@ -6,6 +10,7 @@ import type { TodoAsyncBlock } from './types';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
export class TodoBlock extends BaseView {
@@ -22,54 +27,41 @@ export class TodoBlock extends BaseView {
return block;
}
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 html2block2({
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.html2block2({
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(props: Block2HtmlProps) {
@@ -16,35 +16,6 @@ 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 ?? '';
}
@@ -1,8 +1,10 @@
import {
AsyncBlock,
BlockEditor,
HTML2BlockResult,
SelectBlock,
} from '@toeverything/components/editor-core';
import { BlockFlavorKeys } from '@toeverything/datasource/db-service';
export type Block2HtmlProps = {
editor: BlockEditor;
@@ -28,3 +30,28 @@ export const commonBlock2HtmlContent = async ({
);
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;
};