refactor(editor): extensionalize html adapter (#9299)

This commit is contained in:
Saul-Mirone
2024-12-25 04:04:51 +00:00
parent a15009ce84
commit 50ff3655e5
16 changed files with 183 additions and 144 deletions

View File

@@ -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,

View File

@@ -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&#x26;blockIds=abc%2C123&#x26;elementIds=def%2C456&#x26;databaseId=deadbeef&#x26;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,
});

View File

@@ -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,

View File

@@ -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,
];

View 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,
];

View File

@@ -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,

View File

@@ -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,
];

View File

@@ -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),
}));
},
};

View File

@@ -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';

View File

@@ -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,

View File

@@ -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;