mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
refactor(editor): extensionalize html adapter (#9299)
This commit is contained in:
@@ -3,12 +3,14 @@ export {
|
||||
type BlockHtmlAdapterMatcher,
|
||||
BlockHtmlAdapterMatcherIdentifier,
|
||||
type Html,
|
||||
HtmlASTToDeltaExtension,
|
||||
type HtmlASTToDeltaMatcher,
|
||||
HtmlASTToDeltaMatcherIdentifier,
|
||||
HtmlDeltaConverter,
|
||||
InlineDeltaToHtmlAdapterExtension,
|
||||
type InlineDeltaToHtmlAdapterMatcher,
|
||||
InlineDeltaToHtmlAdapterMatcherIdentifier,
|
||||
} from './html-adapter/index.js';
|
||||
} from './html/index.js';
|
||||
export {
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
DEFAULT_NOTE_BACKGROUND_COLOR,
|
||||
NoteDisplayMode,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { Container } from '@blocksuite/global/di';
|
||||
import type {
|
||||
BlockSnapshot,
|
||||
DocSnapshot,
|
||||
@@ -10,11 +11,24 @@ import type {
|
||||
import { AssetsManager, MemoryBlobCRUD } from '@blocksuite/store';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { HtmlAdapter } from '../../_common/adapters/html-adapter/html.js';
|
||||
import { defaultBlockHtmlAdapterMatchers } from '../../_common/adapters/html/block-matcher.js';
|
||||
import { htmlInlineToDeltaMatchers } from '../../_common/adapters/html/delta-converter/html-inline.js';
|
||||
import { inlineDeltaToHtmlAdapterMatchers } from '../../_common/adapters/html/delta-converter/inline-delta.js';
|
||||
import { HtmlAdapter } from '../../_common/adapters/html/html.js';
|
||||
import { nanoidReplacement } from '../../_common/test-utils/test-utils.js';
|
||||
import { embedSyncedDocMiddleware } from '../../_common/transformers/middlewares.js';
|
||||
import { createJob } from '../utils/create-job.js';
|
||||
|
||||
const container = new Container();
|
||||
[
|
||||
...htmlInlineToDeltaMatchers,
|
||||
...defaultBlockHtmlAdapterMatchers,
|
||||
...inlineDeltaToHtmlAdapterMatchers,
|
||||
].forEach(ext => {
|
||||
ext.setup(container);
|
||||
});
|
||||
const provider = container.provider();
|
||||
|
||||
describe('snapshot to html', () => {
|
||||
const template = (html: string, title?: string) => {
|
||||
let htmlTemplate = `
|
||||
@@ -126,7 +140,7 @@ describe('snapshot to html', () => {
|
||||
`<pre class="shiki light-plus" style="background-color:#FFFFFF;color:#000000" tabindex="0"><code><span class="line"><span style="color:#AF00DB">import</span><span style="color:#000000"> this</span></span></code></pre>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -192,7 +206,7 @@ describe('snapshot to html', () => {
|
||||
`<pre class="shiki light-plus" style="background-color:#FFFFFF;color:#000000" tabindex="0"><code><span class="line"><span>import this</span></span></code></pre>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -258,7 +272,7 @@ describe('snapshot to html', () => {
|
||||
`<pre class="shiki light-plus" style="background-color:#FFFFFF;color:#000000" tabindex="0"><code><span class="line"><span>import this</span></span></code></pre>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -444,7 +458,7 @@ describe('snapshot to html', () => {
|
||||
`<div class="affine-paragraph-block-container"><p>aaa</p><div class="affine-block-children-container" style="padding-left: 26px;"><div class="affine-paragraph-block-container"><p>bbb</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div><div class="affine-paragraph-block-container"><p>ccc</p><div class="affine-block-children-container" style="padding-left: 26px;"><div class="affine-paragraph-block-container"><p>ddd</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div><div class="affine-paragraph-block-container"><p>eee</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div><div class="affine-paragraph-block-container"><p>fff</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div></div></div><div class="affine-paragraph-block-container"><p>ggg</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div></div></div><div class="affine-paragraph-block-container"><p>hhh</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -589,7 +603,7 @@ describe('snapshot to html', () => {
|
||||
`<ul class="bulleted-list"><li class="affine-list-block-container">aaa<ul class="bulleted-list"><li class="affine-list-block-container">bbb<ul class="bulleted-list"><li class="affine-list-block-container">ccc</li></ul></li><li class="affine-list-block-container">ddd</li></ul></li><li class="affine-list-block-container">eee</li></ul>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -694,7 +708,7 @@ describe('snapshot to html', () => {
|
||||
`<ul class="bulleted-list"><li class="affine-list-block-container">aaa</li></ul><ul style="list-style-type: none; padding-inline-start: 18px;" class="todo-list"><li class="affine-list-block-container"><input type="checkbox"><label style="margin-right: 3px;"></label></input>bbb</li></ul><ul class="bulleted-list"><li class="affine-list-block-container">ccc</li></ul>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -768,7 +782,7 @@ describe('snapshot to html', () => {
|
||||
`<div class="affine-paragraph-block-container"><p>aaa <code>bbb</code> ccc</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -842,7 +856,7 @@ describe('snapshot to html', () => {
|
||||
`<div class="affine-paragraph-block-container"><p>aaa <a href="https://affine.pro/">bbb</a> ccc</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -917,7 +931,7 @@ describe('snapshot to html', () => {
|
||||
`<div class="affine-paragraph-block-container"><p>aaa<strong>bbb</strong>ccc</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -992,7 +1006,7 @@ describe('snapshot to html', () => {
|
||||
`<div class="affine-paragraph-block-container"><p>aaa<em>bbb</em>ccc</p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -1069,7 +1083,7 @@ describe('snapshot to html', () => {
|
||||
`<figure class="affine-image-block-container"><img src="assets/YXXTjRmLlNyiOUnHb8nAIvUP6V7PAXhwW9F5_tc2LGs=.blob" alt="YXXTjRmLlNyiOUnHb8nAIvUP6V7PAXhwW9F5_tc2LGs=.blob" title="aaa"></figure><div class="affine-paragraph-block-container"><p></p><div class="affine-block-children-container" style="padding-left: 26px;"></div></div>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const blobManager = new MemoryBlobCRUD();
|
||||
await blobManager.set(
|
||||
'YXXTjRmLlNyiOUnHb8nAIvUP6V7PAXhwW9F5_tc2LGs=',
|
||||
@@ -1171,7 +1185,7 @@ describe('snapshot to html', () => {
|
||||
`<div class="affine-paragraph-block-container"><a href="${testCase.url}">${testCase.title}</a></div>`
|
||||
);
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -1430,7 +1444,7 @@ describe('snapshot to html', () => {
|
||||
const html = template(
|
||||
'<table><thead><tr><th>Title</th><th>Status</th><th>Date</th><th>Number</th><th>Progress</th><th>MultiSelect</th><th>RichText</th><th>Link</th><th>Checkbox</th></tr></thead><tbody><tr><td>Task 1</td><td>TODO</td><td>2023-12-15</td><td>1</td><td>65</td><td>test1,test2</td><td><a href="https://google.com">test2</a></td><td>https://google.com</td><td>true</td></tr><tr><td>Task 2</td><td>In Progress</td><td>2023-12-20</td><td></td><td></td><td></td><td>test1</td><td></td><td></td></tr></tbody></table>'
|
||||
);
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapshot,
|
||||
});
|
||||
@@ -1510,7 +1524,7 @@ describe('snapshot to html', () => {
|
||||
const html = template(
|
||||
'<div class="affine-paragraph-block-container"><a href="https://example.com/4T5ObMgEIMII-4Bexyta1?mode=page&blockIds=abc%2C123&elementIds=def%2C456&databaseId=deadbeef&databaseRowId=123">Test Doc</a></div>'
|
||||
);
|
||||
const htmlAdapter = new HtmlAdapter(createJob([middleware]));
|
||||
const htmlAdapter = new HtmlAdapter(createJob([middleware]), provider);
|
||||
const target = await htmlAdapter.fromBlockSnapshot({
|
||||
snapshot: blockSnapShot,
|
||||
});
|
||||
@@ -1896,7 +1910,7 @@ describe('snapshot to html', () => {
|
||||
await job.snapshotToDoc(syncedDocSnapshot);
|
||||
await job.snapshotToDoc(docSnapShot);
|
||||
|
||||
const mdAdapter = new HtmlAdapter(job);
|
||||
const mdAdapter = new HtmlAdapter(job, provider);
|
||||
const target = await mdAdapter.fromDocSnapshot({
|
||||
snapshot: docSnapShot,
|
||||
});
|
||||
@@ -1981,7 +1995,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2091,7 +2105,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2157,7 +2171,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2202,7 +2216,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2238,7 +2252,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2350,7 +2364,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2394,7 +2408,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2438,7 +2452,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2491,7 +2505,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2544,7 +2558,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
@@ -2585,7 +2599,7 @@ describe('html to snapshot', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const htmlAdapter = new HtmlAdapter(createJob());
|
||||
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||
file: html,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { ExtensionType } from '@blocksuite/block-std';
|
||||
|
||||
import { AttachmentAdapterFactoryExtension } from './attachment.js';
|
||||
import { HtmlAdapterFactoryExtension } from './html-adapter/html.js';
|
||||
import { htmlInlineToDeltaMatchers } from './html/delta-converter/html-inline.js';
|
||||
import { inlineDeltaToHtmlAdapterMatchers } from './html/delta-converter/inline-delta.js';
|
||||
import { HtmlAdapterFactoryExtension } from './html/html.js';
|
||||
import { ImageAdapterFactoryExtension } from './image.js';
|
||||
import { MarkdownAdapterFactoryExtension } from './markdown/markdown.js';
|
||||
import { MixTextAdapterFactoryExtension } from './mix-text.js';
|
||||
@@ -10,6 +12,8 @@ import { NotionTextAdapterFactoryExtension } from './notion-text.js';
|
||||
import { PlainTextAdapterFactoryExtension } from './plain-text/plain-text.js';
|
||||
|
||||
export const AdapterFactoryExtensions: ExtensionType[] = [
|
||||
...htmlInlineToDeltaMatchers,
|
||||
...inlineDeltaToHtmlAdapterMatchers,
|
||||
AttachmentAdapterFactoryExtension,
|
||||
ImageAdapterFactoryExtension,
|
||||
MarkdownAdapterFactoryExtension,
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import {
|
||||
embedFigmaBlockHtmlAdapterMatcher,
|
||||
embedGithubBlockHtmlAdapterMatcher,
|
||||
embedLinkedDocBlockHtmlAdapterMatcher,
|
||||
embedLoomBlockHtmlAdapterMatcher,
|
||||
embedSyncedDocBlockHtmlAdapterMatcher,
|
||||
embedYoutubeBlockHtmlAdapterMatcher,
|
||||
} from '@blocksuite/affine-block-embed';
|
||||
import { listBlockHtmlAdapterMatcher } from '@blocksuite/affine-block-list';
|
||||
import { paragraphBlockHtmlAdapterMatcher } from '@blocksuite/affine-block-paragraph';
|
||||
|
||||
import { bookmarkBlockHtmlAdapterMatcher } from '../../../bookmark-block/adapters/html.js';
|
||||
import { codeBlockHtmlAdapterMatcher } from '../../../code-block/adapters/html.js';
|
||||
import { databaseBlockHtmlAdapterMatcher } from '../../../database-block/adapters/html.js';
|
||||
import { dividerBlockHtmlAdapterMatcher } from '../../../divider-block/adapters/html.js';
|
||||
import { imageBlockHtmlAdapterMatcher } from '../../../image-block/adapters/html.js';
|
||||
import { rootBlockHtmlAdapterMatcher } from '../../../root-block/adapters/html.js';
|
||||
|
||||
export const defaultBlockHtmlAdapterMatchers = [
|
||||
listBlockHtmlAdapterMatcher,
|
||||
paragraphBlockHtmlAdapterMatcher,
|
||||
codeBlockHtmlAdapterMatcher,
|
||||
dividerBlockHtmlAdapterMatcher,
|
||||
imageBlockHtmlAdapterMatcher,
|
||||
rootBlockHtmlAdapterMatcher,
|
||||
embedYoutubeBlockHtmlAdapterMatcher,
|
||||
embedFigmaBlockHtmlAdapterMatcher,
|
||||
embedLoomBlockHtmlAdapterMatcher,
|
||||
embedGithubBlockHtmlAdapterMatcher,
|
||||
bookmarkBlockHtmlAdapterMatcher,
|
||||
databaseBlockHtmlAdapterMatcher,
|
||||
embedLinkedDocBlockHtmlAdapterMatcher,
|
||||
embedSyncedDocBlockHtmlAdapterMatcher,
|
||||
];
|
||||
34
blocksuite/blocks/src/_common/adapters/html/block-matcher.ts
Normal file
34
blocksuite/blocks/src/_common/adapters/html/block-matcher.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
EmbedFigmaBlockHtmlAdapterExtension,
|
||||
EmbedGithubBlockHtmlAdapterExtension,
|
||||
EmbedLinkedDocHtmlAdapterExtension,
|
||||
EmbedLoomBlockHtmlAdapterExtension,
|
||||
EmbedSyncedDocBlockHtmlAdapterExtension,
|
||||
EmbedYoutubeBlockHtmlAdapterExtension,
|
||||
} from '@blocksuite/affine-block-embed';
|
||||
import { ListBlockHtmlAdapterExtension } from '@blocksuite/affine-block-list';
|
||||
import { ParagraphBlockHtmlAdapterExtension } from '@blocksuite/affine-block-paragraph';
|
||||
|
||||
import { BookmarkBlockHtmlAdapterExtension } from '../../../bookmark-block/adapters/html.js';
|
||||
import { CodeBlockHtmlAdapterExtension } from '../../../code-block/adapters/html.js';
|
||||
import { DatabaseBlockHtmlAdapterExtension } from '../../../database-block/adapters/html.js';
|
||||
import { DividerBlockHtmlAdapterExtension } from '../../../divider-block/adapters/html.js';
|
||||
import { ImageBlockHtmlAdapterExtension } from '../../../image-block/adapters/html.js';
|
||||
import { RootBlockHtmlAdapterExtension } from '../../../root-block/adapters/html.js';
|
||||
|
||||
export const defaultBlockHtmlAdapterMatchers = [
|
||||
ListBlockHtmlAdapterExtension,
|
||||
ParagraphBlockHtmlAdapterExtension,
|
||||
CodeBlockHtmlAdapterExtension,
|
||||
DividerBlockHtmlAdapterExtension,
|
||||
ImageBlockHtmlAdapterExtension,
|
||||
RootBlockHtmlAdapterExtension,
|
||||
EmbedYoutubeBlockHtmlAdapterExtension,
|
||||
EmbedFigmaBlockHtmlAdapterExtension,
|
||||
EmbedLoomBlockHtmlAdapterExtension,
|
||||
EmbedGithubBlockHtmlAdapterExtension,
|
||||
BookmarkBlockHtmlAdapterExtension,
|
||||
DatabaseBlockHtmlAdapterExtension,
|
||||
EmbedLinkedDocHtmlAdapterExtension,
|
||||
EmbedSyncedDocBlockHtmlAdapterExtension,
|
||||
];
|
||||
@@ -1,6 +1,6 @@
|
||||
import type {
|
||||
HtmlAST,
|
||||
HtmlASTToDeltaMatcher,
|
||||
import {
|
||||
type HtmlAST,
|
||||
HtmlASTToDeltaExtension,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { collapseWhiteSpace } from 'collapse-white-space';
|
||||
import type { Element } from 'hast';
|
||||
@@ -14,7 +14,7 @@ const listElementTags = new Set(['ol', 'ul']);
|
||||
const strongElementTags = new Set(['strong', 'b']);
|
||||
const italicElementTags = new Set(['i', 'em']);
|
||||
|
||||
export const htmlTextToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlTextToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'text',
|
||||
match: ast => ast.type === 'text',
|
||||
toDelta: (ast, context) => {
|
||||
@@ -33,9 +33,9 @@ export const htmlTextToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
: collapseWhiteSpace(ast.value);
|
||||
return value ? [{ insert: value }] : [];
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlTextLikeElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlTextLikeElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'text-like-element',
|
||||
match: ast => isElement(ast) && textLikeElementTags.has(ast.tagName),
|
||||
toDelta: (ast, context) => {
|
||||
@@ -46,17 +46,17 @@ export const htmlTextLikeElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
context.toDelta(child, { trim: false })
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlListToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlListToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'list-element',
|
||||
match: ast => isElement(ast) && listElementTags.has(ast.tagName),
|
||||
toDelta: () => {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlStrongElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlStrongElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'strong-element',
|
||||
match: ast => isElement(ast) && strongElementTags.has(ast.tagName),
|
||||
toDelta: (ast, context) => {
|
||||
@@ -70,9 +70,9 @@ export const htmlStrongElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlItalicElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlItalicElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'italic-element',
|
||||
match: ast => isElement(ast) && italicElementTags.has(ast.tagName),
|
||||
toDelta: (ast, context) => {
|
||||
@@ -86,8 +86,9 @@ export const htmlItalicElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
export const htmlCodeElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
});
|
||||
|
||||
export const htmlCodeElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'code-element',
|
||||
match: ast => isElement(ast) && ast.tagName === 'code',
|
||||
toDelta: (ast, context) => {
|
||||
@@ -101,9 +102,9 @@ export const htmlCodeElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlDelElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlDelElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'del-element',
|
||||
match: ast => isElement(ast) && ast.tagName === 'del',
|
||||
toDelta: (ast, context) => {
|
||||
@@ -117,9 +118,9 @@ export const htmlDelElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlUnderlineElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlUnderlineElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'underline-element',
|
||||
match: ast => isElement(ast) && ast.tagName === 'u',
|
||||
toDelta: (ast, context) => {
|
||||
@@ -133,9 +134,9 @@ export const htmlUnderlineElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlLinkElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlLinkElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'link-element',
|
||||
match: ast => isElement(ast) && ast.tagName === 'a',
|
||||
toDelta: (ast, context) => {
|
||||
@@ -194,9 +195,9 @@ export const htmlLinkElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlMarkElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlMarkElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'mark-element',
|
||||
match: ast => isElement(ast) && ast.tagName === 'mark',
|
||||
toDelta: (ast, context) => {
|
||||
@@ -210,17 +211,17 @@ export const htmlMarkElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlBrElementToDeltaMatcher: HtmlASTToDeltaMatcher = {
|
||||
export const htmlBrElementToDeltaMatcher = HtmlASTToDeltaExtension({
|
||||
name: 'br-element',
|
||||
match: ast => isElement(ast) && ast.tagName === 'br',
|
||||
toDelta: () => {
|
||||
return [{ insert: '\n' }];
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const htmlInlineToDeltaMatchers: HtmlASTToDeltaMatcher[] = [
|
||||
export const htmlInlineToDeltaMatchers = [
|
||||
htmlTextToDeltaMatcher,
|
||||
htmlTextLikeElementToDeltaMatcher,
|
||||
htmlStrongElementToDeltaMatcher,
|
||||
@@ -1,10 +1,8 @@
|
||||
import { generateDocUrl } from '@blocksuite/affine-block-embed';
|
||||
import type {
|
||||
InlineDeltaToHtmlAdapterMatcher,
|
||||
InlineHtmlAST,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { InlineHtmlAST } from '@blocksuite/affine-shared/adapters';
|
||||
import { InlineDeltaToHtmlAdapterExtension } from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
export const boldDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher = {
|
||||
export const boldDeltaToHtmlAdapterMatcher = InlineDeltaToHtmlAdapterExtension({
|
||||
name: 'bold',
|
||||
match: delta => !!delta.attributes?.bold,
|
||||
toAST: (_, context) => {
|
||||
@@ -15,10 +13,10 @@ export const boldDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher = {
|
||||
children: [context.current],
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const italicDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher =
|
||||
{
|
||||
export const italicDeltaToHtmlAdapterMatcher =
|
||||
InlineDeltaToHtmlAdapterExtension({
|
||||
name: 'italic',
|
||||
match: delta => !!delta.attributes?.italic,
|
||||
toAST: (_, context) => {
|
||||
@@ -29,10 +27,10 @@ export const italicDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher =
|
||||
children: [context.current],
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const strikeDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher =
|
||||
{
|
||||
export const strikeDeltaToHtmlAdapterMatcher =
|
||||
InlineDeltaToHtmlAdapterExtension({
|
||||
name: 'strike',
|
||||
match: delta => !!delta.attributes?.strike,
|
||||
toAST: (_, context) => {
|
||||
@@ -43,10 +41,10 @@ export const strikeDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher =
|
||||
children: [context.current],
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const inlineCodeDeltaToMarkdownAdapterMatcher: InlineDeltaToHtmlAdapterMatcher =
|
||||
{
|
||||
export const inlineCodeDeltaToMarkdownAdapterMatcher =
|
||||
InlineDeltaToHtmlAdapterExtension({
|
||||
name: 'inlineCode',
|
||||
match: delta => !!delta.attributes?.code,
|
||||
toAST: (_, context) => {
|
||||
@@ -57,10 +55,10 @@ export const inlineCodeDeltaToMarkdownAdapterMatcher: InlineDeltaToHtmlAdapterMa
|
||||
children: [context.current],
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const underlineDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher =
|
||||
{
|
||||
export const underlineDeltaToHtmlAdapterMatcher =
|
||||
InlineDeltaToHtmlAdapterExtension({
|
||||
name: 'underline',
|
||||
match: delta => !!delta.attributes?.underline,
|
||||
toAST: (_, context) => {
|
||||
@@ -71,10 +69,10 @@ export const underlineDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher
|
||||
children: [context.current],
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const referenceDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher =
|
||||
{
|
||||
export const referenceDeltaToHtmlAdapterMatcher =
|
||||
InlineDeltaToHtmlAdapterExtension({
|
||||
name: 'reference',
|
||||
match: delta => !!delta.attributes?.reference,
|
||||
toAST: (delta, context) => {
|
||||
@@ -108,9 +106,9 @@ export const referenceDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher
|
||||
|
||||
return hast;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const linkDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher = {
|
||||
export const linkDeltaToHtmlAdapterMatcher = InlineDeltaToHtmlAdapterExtension({
|
||||
name: 'link',
|
||||
match: delta => !!delta.attributes?.link,
|
||||
toAST: (delta, _) => {
|
||||
@@ -131,15 +129,14 @@ export const linkDeltaToHtmlAdapterMatcher: InlineDeltaToHtmlAdapterMatcher = {
|
||||
children: [hast],
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const inlineDeltaToHtmlAdapterMatchers: InlineDeltaToHtmlAdapterMatcher[] =
|
||||
[
|
||||
boldDeltaToHtmlAdapterMatcher,
|
||||
italicDeltaToHtmlAdapterMatcher,
|
||||
strikeDeltaToHtmlAdapterMatcher,
|
||||
underlineDeltaToHtmlAdapterMatcher,
|
||||
inlineCodeDeltaToMarkdownAdapterMatcher,
|
||||
referenceDeltaToHtmlAdapterMatcher,
|
||||
linkDeltaToHtmlAdapterMatcher,
|
||||
];
|
||||
export const inlineDeltaToHtmlAdapterMatchers = [
|
||||
boldDeltaToHtmlAdapterMatcher,
|
||||
italicDeltaToHtmlAdapterMatcher,
|
||||
strikeDeltaToHtmlAdapterMatcher,
|
||||
underlineDeltaToHtmlAdapterMatcher,
|
||||
inlineCodeDeltaToMarkdownAdapterMatcher,
|
||||
referenceDeltaToHtmlAdapterMatcher,
|
||||
linkDeltaToHtmlAdapterMatcher,
|
||||
];
|
||||
@@ -8,9 +8,12 @@ import {
|
||||
BlockHtmlAdapterMatcherIdentifier,
|
||||
HastUtils,
|
||||
type HtmlAST,
|
||||
HtmlASTToDeltaMatcherIdentifier,
|
||||
HtmlDeltaConverter,
|
||||
InlineDeltaToHtmlAdapterMatcherIdentifier,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import type { ServiceProvider } from '@blocksuite/global/di';
|
||||
import {
|
||||
type AssetsManager,
|
||||
ASTWalker,
|
||||
@@ -36,9 +39,6 @@ import rehypeStringify from 'rehype-stringify';
|
||||
import { unified } from 'unified';
|
||||
|
||||
import { AdapterFactoryIdentifier } from '../type.js';
|
||||
import { defaultBlockHtmlAdapterMatchers } from './block-matcher.js';
|
||||
import { htmlInlineToDeltaMatchers } from './delta-converter/html-inline.js';
|
||||
import { inlineDeltaToHtmlAdapterMatchers } from './delta-converter/inline-delta.js';
|
||||
|
||||
export type Html = string;
|
||||
|
||||
@@ -170,11 +170,20 @@ export class HtmlAdapter extends BaseAdapter<Html> {
|
||||
|
||||
deltaConverter: HtmlDeltaConverter;
|
||||
|
||||
constructor(
|
||||
job: Job,
|
||||
readonly blockMatchers: BlockHtmlAdapterMatcher[] = defaultBlockHtmlAdapterMatchers
|
||||
) {
|
||||
readonly blockMatchers: BlockHtmlAdapterMatcher[];
|
||||
|
||||
constructor(job: Job, provider: ServiceProvider) {
|
||||
super(job);
|
||||
const blockMatchers = Array.from(
|
||||
provider.getAll(BlockHtmlAdapterMatcherIdentifier).values()
|
||||
);
|
||||
const inlineDeltaToHtmlAdapterMatchers = Array.from(
|
||||
provider.getAll(InlineDeltaToHtmlAdapterMatcherIdentifier).values()
|
||||
);
|
||||
const htmlInlineToDeltaMatchers = Array.from(
|
||||
provider.getAll(HtmlASTToDeltaMatcherIdentifier).values()
|
||||
);
|
||||
this.blockMatchers = blockMatchers;
|
||||
this.deltaConverter = new HtmlDeltaConverter(
|
||||
job.adapterConfigs,
|
||||
inlineDeltaToHtmlAdapterMatchers,
|
||||
@@ -373,13 +382,7 @@ export const HtmlAdapterFactoryIdentifier = AdapterFactoryIdentifier('Html');
|
||||
export const HtmlAdapterFactoryExtension: ExtensionType = {
|
||||
setup: di => {
|
||||
di.addImpl(HtmlAdapterFactoryIdentifier, provider => ({
|
||||
get: (job: Job) =>
|
||||
new HtmlAdapter(
|
||||
job,
|
||||
Array.from(
|
||||
provider.getAll(BlockHtmlAdapterMatcherIdentifier).values()
|
||||
)
|
||||
),
|
||||
get: job => new HtmlAdapter(job, provider),
|
||||
}));
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
export * from './attachment.js';
|
||||
export * from './extension.js';
|
||||
export * from './html-adapter/html.js';
|
||||
export * from './html/html.js';
|
||||
export * from './image.js';
|
||||
export * from './markdown/index.js';
|
||||
export * from './mix-text.js';
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Container } from '@blocksuite/global/di';
|
||||
import { sha } from '@blocksuite/global/utils';
|
||||
import type { Doc, DocCollection } from '@blocksuite/store';
|
||||
import { extMimeMap, Job } from '@blocksuite/store';
|
||||
|
||||
import { HtmlAdapter } from '../adapters/html-adapter/html.js';
|
||||
import { defaultBlockHtmlAdapterMatchers } from '../adapters/html/block-matcher.js';
|
||||
import { htmlInlineToDeltaMatchers } from '../adapters/html/delta-converter/html-inline.js';
|
||||
import { inlineDeltaToHtmlAdapterMatchers } from '../adapters/html/delta-converter/inline-delta.js';
|
||||
import { HtmlAdapter } from '../adapters/html/html.js';
|
||||
import {
|
||||
defaultImageProxyMiddleware,
|
||||
docLinkBaseURLMiddleware,
|
||||
@@ -22,6 +26,17 @@ type ImportHTMLZipOptions = {
|
||||
imported: Blob;
|
||||
};
|
||||
|
||||
const container = new Container();
|
||||
[
|
||||
...htmlInlineToDeltaMatchers,
|
||||
...defaultBlockHtmlAdapterMatchers,
|
||||
...inlineDeltaToHtmlAdapterMatchers,
|
||||
].forEach(ext => {
|
||||
ext.setup(container);
|
||||
});
|
||||
|
||||
const provider = container.provider();
|
||||
|
||||
/**
|
||||
* Exports a doc to HTML format.
|
||||
*
|
||||
@@ -34,7 +49,7 @@ async function exportDoc(doc: Doc) {
|
||||
middlewares: [docLinkBaseURLMiddleware, titleMiddleware],
|
||||
});
|
||||
const snapshot = job.docToSnapshot(doc);
|
||||
const adapter = new HtmlAdapter(job);
|
||||
const adapter = new HtmlAdapter(job, provider);
|
||||
if (!snapshot) {
|
||||
return;
|
||||
}
|
||||
@@ -83,7 +98,7 @@ async function importHTMLToDoc({
|
||||
docLinkBaseURLMiddleware,
|
||||
],
|
||||
});
|
||||
const htmlAdapter = new HtmlAdapter(job);
|
||||
const htmlAdapter = new HtmlAdapter(job, provider);
|
||||
const page = await htmlAdapter.toDoc({
|
||||
file: html,
|
||||
assets: job.assetsManager,
|
||||
@@ -147,7 +162,7 @@ async function importHTMLZip({ collection, imported }: ImportHTMLZipOptions) {
|
||||
for (const [key, value] of pendingPathBlobIdMap.entries()) {
|
||||
pathBlobIdMap.set(key, value);
|
||||
}
|
||||
const htmlAdapter = new HtmlAdapter(job);
|
||||
const htmlAdapter = new HtmlAdapter(job, provider);
|
||||
const html = await blob.text();
|
||||
const doc = await htmlAdapter.toDoc({
|
||||
file: html,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ServiceProvider } from '@blocksuite/global/di';
|
||||
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
|
||||
import type {
|
||||
BaseAdapter,
|
||||
@@ -15,7 +16,9 @@ import { unified } from 'unified';
|
||||
|
||||
import { LifeCycleWatcher } from '../extension/index.js';
|
||||
|
||||
type AdapterConstructor<T extends BaseAdapter> = new (job: Job) => T;
|
||||
type AdapterConstructor<T extends BaseAdapter> =
|
||||
| { new (job: Job): T }
|
||||
| (new (job: Job, provider: ServiceProvider) => T);
|
||||
|
||||
type AdapterMap = Map<
|
||||
string,
|
||||
@@ -131,7 +134,7 @@ export class Clipboard extends LifeCycleWatcher {
|
||||
}
|
||||
if (item) {
|
||||
const job = this._getJob();
|
||||
const adapterInstance = new adapter(job);
|
||||
const adapterInstance = new adapter(job, this.std.provider);
|
||||
const payload = {
|
||||
file: item,
|
||||
assets: job.assetsManager,
|
||||
@@ -274,7 +277,7 @@ export class Clipboard extends LifeCycleWatcher {
|
||||
return;
|
||||
}
|
||||
const { adapter } = adapterItem;
|
||||
const adapterInstance = new adapter(job);
|
||||
const adapterInstance = new adapter(job, this.std.provider);
|
||||
const result = await adapterInstance.fromSlice(slice);
|
||||
if (!result) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user