feat(editor): support markdown adapter preprocessed with latex delimiters (#11431)
To close [BS-2870](https://linear.app/affine-design/issue/BS-2870/支持识别-和-[-包裹内容为公式) ## Add Markdown Preprocessor Extension and Enhanced LaTeX Support ### Markdown Preprocessor Extension This PR introduces a new preprocessor extension for Markdown adapters that allows preprocessing of content before conversion: Adds a new PreprocessorManager for handling text transformations Introduces extensible preprocessor interface that supports different processing levels (block/slice/doc) Integrates preprocessor extension into the existing Markdown adapter workflow ### LaTeX Support Enhancement Extends LaTeX support to handle both traditional and alternative syntax: Adds support for backslash LaTeX syntax: Block math: ```\[...\] ``` alongside existing ```$$...$$``` Inline math: ```\(...\) ``` alongside existing ```$...$``` Implements LaTeX preprocessor to standardize syntax before conversion Updates tests to cover both syntax variants
This commit is contained in:
@@ -3680,88 +3680,95 @@ bbb
|
|||||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('inline latex', async () => {
|
describe('inline latex', () => {
|
||||||
const markdown = 'inline $E=mc^2$ latex\n';
|
test.each([
|
||||||
const blockSnapshot: BlockSnapshot = {
|
['dollar sign syntax', 'inline $E=mc^2$ latex\n'],
|
||||||
type: 'block',
|
['backslash syntax', 'inline \\(E=mc^2\\) latex\n'],
|
||||||
id: 'matchesReplaceMap[0]',
|
])('should convert %s correctly', async (_, markdown) => {
|
||||||
flavour: 'affine:note',
|
const blockSnapshot: BlockSnapshot = {
|
||||||
props: {
|
type: 'block',
|
||||||
xywh: '[0,0,800,95]',
|
id: 'matchesReplaceMap[0]',
|
||||||
background: DefaultTheme.noteBackgrounColor,
|
flavour: 'affine:note',
|
||||||
index: 'a0',
|
props: {
|
||||||
hidden: false,
|
xywh: '[0,0,800,95]',
|
||||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
background: DefaultTheme.noteBackgrounColor,
|
||||||
},
|
index: 'a0',
|
||||||
children: [
|
hidden: false,
|
||||||
{
|
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||||
type: 'block',
|
|
||||||
id: 'matchesReplaceMap[1]',
|
|
||||||
flavour: 'affine:paragraph',
|
|
||||||
props: {
|
|
||||||
type: 'text',
|
|
||||||
text: {
|
|
||||||
'$blocksuite:internal:text$': true,
|
|
||||||
delta: [
|
|
||||||
{
|
|
||||||
insert: 'inline ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
insert: ' ',
|
|
||||||
attributes: {
|
|
||||||
latex: 'E=mc^2',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
insert: ' latex',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
},
|
||||||
],
|
children: [
|
||||||
};
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'matchesReplaceMap[1]',
|
||||||
|
flavour: 'affine:paragraph',
|
||||||
|
props: {
|
||||||
|
type: 'text',
|
||||||
|
text: {
|
||||||
|
'$blocksuite:internal:text$': true,
|
||||||
|
delta: [
|
||||||
|
{
|
||||||
|
insert: 'inline ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: ' ',
|
||||||
|
attributes: {
|
||||||
|
latex: 'E=mc^2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: ' latex',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||||
file: markdown,
|
file: markdown,
|
||||||
|
});
|
||||||
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
});
|
});
|
||||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('latex block', async () => {
|
describe('latex block', () => {
|
||||||
const markdown = '$$\nE=mc^2\n$$\n';
|
test.each([
|
||||||
|
['dollar sign syntax', '$$\nE=mc^2\n$$\n'],
|
||||||
const blockSnapshot: BlockSnapshot = {
|
['backslash syntax', '\\[\nE=mc^2\n\\]\n'],
|
||||||
type: 'block',
|
])('should convert %s correctly', async (_, markdown) => {
|
||||||
id: 'matchesReplaceMap[0]',
|
const blockSnapshot: BlockSnapshot = {
|
||||||
flavour: 'affine:note',
|
type: 'block',
|
||||||
props: {
|
id: 'matchesReplaceMap[0]',
|
||||||
xywh: '[0,0,800,95]',
|
flavour: 'affine:note',
|
||||||
background: DefaultTheme.noteBackgrounColor,
|
props: {
|
||||||
index: 'a0',
|
xywh: '[0,0,800,95]',
|
||||||
hidden: false,
|
background: DefaultTheme.noteBackgrounColor,
|
||||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
index: 'a0',
|
||||||
},
|
hidden: false,
|
||||||
children: [
|
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||||
{
|
|
||||||
type: 'block',
|
|
||||||
id: 'matchesReplaceMap[1]',
|
|
||||||
flavour: 'affine:latex',
|
|
||||||
props: {
|
|
||||||
latex: 'E=mc^2',
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
},
|
||||||
],
|
children: [
|
||||||
};
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'matchesReplaceMap[1]',
|
||||||
|
flavour: 'affine:latex',
|
||||||
|
props: {
|
||||||
|
latex: 'E=mc^2',
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||||
file: markdown,
|
file: markdown,
|
||||||
|
});
|
||||||
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
});
|
});
|
||||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('reference', async () => {
|
test('reference', async () => {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type { ExtensionType } from '@blocksuite/store';
|
|||||||
|
|
||||||
import { defaultBlockHtmlAdapterMatchers } from './html/block-matcher';
|
import { defaultBlockHtmlAdapterMatchers } from './html/block-matcher';
|
||||||
import { defaultBlockMarkdownAdapterMatchers } from './markdown/block-matcher';
|
import { defaultBlockMarkdownAdapterMatchers } from './markdown/block-matcher';
|
||||||
|
import { defaultMarkdownPreprocessors } from './markdown/preprocessor';
|
||||||
import { defaultBlockNotionHtmlAdapterMatchers } from './notion-html/block-matcher';
|
import { defaultBlockNotionHtmlAdapterMatchers } from './notion-html/block-matcher';
|
||||||
import { defaultBlockPlainTextAdapterMatchers } from './plain-text/block-matcher';
|
import { defaultBlockPlainTextAdapterMatchers } from './plain-text/block-matcher';
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ export const MarkdownAdapterExtension: ExtensionType[] = [
|
|||||||
...MarkdownInlineToDeltaAdapterExtensions,
|
...MarkdownInlineToDeltaAdapterExtensions,
|
||||||
...defaultBlockMarkdownAdapterMatchers,
|
...defaultBlockMarkdownAdapterMatchers,
|
||||||
...InlineDeltaToMarkdownAdapterExtensions,
|
...InlineDeltaToMarkdownAdapterExtensions,
|
||||||
|
...defaultMarkdownPreprocessors,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const NotionHtmlAdapterExtension: ExtensionType[] = [
|
export const NotionHtmlAdapterExtension: ExtensionType[] = [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './extension.js';
|
export * from './extension.js';
|
||||||
export * from './html/block-matcher.js';
|
export * from './html/block-matcher.js';
|
||||||
export * from './markdown/block-matcher.js';
|
export * from './markdown/block-matcher.js';
|
||||||
|
export * from './markdown/preprocessor.js';
|
||||||
export * from './notion-html/block-matcher.js';
|
export * from './notion-html/block-matcher.js';
|
||||||
export * from './plain-text/block-matcher.js';
|
export * from './plain-text/block-matcher.js';
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { CodeMarkdownPreprocessorExtension } from '@blocksuite/affine-block-code';
|
||||||
|
import { LatexMarkdownPreprocessorExtension } from '@blocksuite/affine-block-latex';
|
||||||
|
|
||||||
|
export const defaultMarkdownPreprocessors = [
|
||||||
|
LatexMarkdownPreprocessorExtension,
|
||||||
|
CodeMarkdownPreprocessorExtension,
|
||||||
|
];
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { ExtensionType } from '@blocksuite/store';
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
import { CodeBlockHtmlAdapterExtension } from './html.js';
|
import { CodeBlockHtmlAdapterExtension } from './html.js';
|
||||||
import { CodeBlockMarkdownAdapterExtension } from './markdown.js';
|
import { CodeBlockMarkdownAdapterExtensions } from './markdown/index.js';
|
||||||
import { CodeBlockNotionHtmlAdapterExtension } from './notion-html.js';
|
import { CodeBlockNotionHtmlAdapterExtension } from './notion-html.js';
|
||||||
import { CodeBlockPlainTextAdapterExtension } from './plain-text.js';
|
import { CodeBlockPlainTextAdapterExtension } from './plain-text.js';
|
||||||
|
|
||||||
export const CodeBlockAdapterExtensions: ExtensionType[] = [
|
export const CodeBlockAdapterExtensions: ExtensionType[] = [
|
||||||
CodeBlockHtmlAdapterExtension,
|
CodeBlockHtmlAdapterExtension,
|
||||||
CodeBlockMarkdownAdapterExtension,
|
CodeBlockMarkdownAdapterExtensions,
|
||||||
CodeBlockPlainTextAdapterExtension,
|
CodeBlockPlainTextAdapterExtension,
|
||||||
CodeBlockNotionHtmlAdapterExtension,
|
CodeBlockNotionHtmlAdapterExtension,
|
||||||
];
|
].flat();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from './html.js';
|
export * from './html.js';
|
||||||
export * from './markdown.js';
|
export * from './markdown/index.js';
|
||||||
export * from './notion-html.js';
|
export * from './notion-html.js';
|
||||||
export * from './plain-text.js';
|
export * from './plain-text.js';
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { CodeBlockMarkdownAdapterExtension } from './markdown.js';
|
||||||
|
import { CodeMarkdownPreprocessorExtension } from './preprocessor.js';
|
||||||
|
|
||||||
|
export * from './markdown.js';
|
||||||
|
export * from './preprocessor.js';
|
||||||
|
|
||||||
|
export const CodeBlockMarkdownAdapterExtensions: ExtensionType[] = [
|
||||||
|
CodeMarkdownPreprocessorExtension,
|
||||||
|
CodeBlockMarkdownAdapterExtension,
|
||||||
|
];
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
type MarkdownAdapterPreprocessor,
|
||||||
|
MarkdownPreprocessorExtension,
|
||||||
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
|
|
||||||
|
const codePreprocessor: MarkdownAdapterPreprocessor = {
|
||||||
|
name: 'code',
|
||||||
|
levels: ['slice'],
|
||||||
|
preprocess: content => {
|
||||||
|
let codeFence = '';
|
||||||
|
const lines = content
|
||||||
|
.split('\n')
|
||||||
|
.map(line => {
|
||||||
|
if (line.trimStart().startsWith('-')) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
let trimmedLine = line.trimStart();
|
||||||
|
if (!codeFence && trimmedLine.startsWith('```')) {
|
||||||
|
codeFence = trimmedLine.substring(
|
||||||
|
0,
|
||||||
|
trimmedLine.lastIndexOf('```') + 3
|
||||||
|
);
|
||||||
|
if (codeFence.split('').every(c => c === '`')) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
codeFence = '';
|
||||||
|
}
|
||||||
|
if (!codeFence && trimmedLine.startsWith('~~~')) {
|
||||||
|
codeFence = trimmedLine.substring(
|
||||||
|
0,
|
||||||
|
trimmedLine.lastIndexOf('~~~') + 3
|
||||||
|
);
|
||||||
|
if (codeFence.split('').every(c => c === '~')) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
codeFence = '';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!!codeFence &&
|
||||||
|
trimmedLine.startsWith(codeFence) &&
|
||||||
|
trimmedLine.lastIndexOf(codeFence) === 0
|
||||||
|
) {
|
||||||
|
codeFence = '';
|
||||||
|
}
|
||||||
|
if (codeFence) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmedLine = trimmedLine.trimEnd();
|
||||||
|
if (!trimmedLine.startsWith('<') && !trimmedLine.endsWith('>')) {
|
||||||
|
// check if it is a url link and wrap it with the angle brackets
|
||||||
|
// sometimes the url includes emphasis `_` that will break URL parsing
|
||||||
|
//
|
||||||
|
// eg. /MuawcBMT1Mzvoar09-_66?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_
|
||||||
|
// https://www.markdownguide.org/basic-syntax/#urls-and-email-addresses
|
||||||
|
try {
|
||||||
|
const valid =
|
||||||
|
URL.canParse?.(trimmedLine) ?? Boolean(new URL(trimmedLine));
|
||||||
|
if (valid) {
|
||||||
|
return `<${trimmedLine}>`;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return line.replace(/^ /, ' ');
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CodeMarkdownPreprocessorExtension =
|
||||||
|
MarkdownPreprocessorExtension(codePreprocessor);
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { ExtensionType } from '@blocksuite/store';
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
import { LatexBlockMarkdownAdapterExtension } from './markdown.js';
|
import { LatexMarkdownAdapterExtensions } from './markdown/index.js';
|
||||||
import { LatexBlockNotionHtmlAdapterExtension } from './notion-html.js';
|
import { LatexBlockNotionHtmlAdapterExtension } from './notion-html.js';
|
||||||
import { LatexBlockPlainTextAdapterExtension } from './plain-text.js';
|
import { LatexBlockPlainTextAdapterExtension } from './plain-text.js';
|
||||||
|
|
||||||
export const LatexBlockAdapterExtensions: ExtensionType[] = [
|
export const LatexBlockAdapterExtensions: ExtensionType[] = [
|
||||||
LatexBlockMarkdownAdapterExtension,
|
LatexMarkdownAdapterExtensions,
|
||||||
LatexBlockNotionHtmlAdapterExtension,
|
LatexBlockNotionHtmlAdapterExtension,
|
||||||
LatexBlockPlainTextAdapterExtension,
|
LatexBlockPlainTextAdapterExtension,
|
||||||
];
|
].flat();
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export * from './markdown.js';
|
export * from './markdown/index.js';
|
||||||
export * from './notion-html.js';
|
export * from './notion-html.js';
|
||||||
export * from './plain-text.js';
|
export * from './plain-text.js';
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { LatexBlockMarkdownAdapterExtension } from './markdown.js';
|
||||||
|
import { LatexMarkdownPreprocessorExtension } from './preprocessor.js';
|
||||||
|
|
||||||
|
export * from './markdown.js';
|
||||||
|
export * from './preprocessor.js';
|
||||||
|
|
||||||
|
export const LatexMarkdownAdapterExtensions: ExtensionType[] = [
|
||||||
|
LatexMarkdownPreprocessorExtension,
|
||||||
|
LatexBlockMarkdownAdapterExtension,
|
||||||
|
];
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
type MarkdownAdapterPreprocessor,
|
||||||
|
MarkdownPreprocessorExtension,
|
||||||
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
|
|
||||||
|
const latexPreprocessor: MarkdownAdapterPreprocessor = {
|
||||||
|
name: 'latex',
|
||||||
|
levels: ['block', 'slice', 'doc'],
|
||||||
|
preprocess: content => {
|
||||||
|
// Replace block-level LaTeX delimiters \[ \] with $$ $$
|
||||||
|
const blockProcessedContent = content.replace(
|
||||||
|
/\\\[(.*?)\\\]/gs,
|
||||||
|
(_, equation) => `$$${equation}$$`
|
||||||
|
);
|
||||||
|
// Replace inline LaTeX delimiters \( \) with $ $
|
||||||
|
const inlineProcessedContent = blockProcessedContent.replace(
|
||||||
|
/\\\((.*?)\\\)/gs,
|
||||||
|
(_, equation) => `$${equation}$`
|
||||||
|
);
|
||||||
|
return inlineProcessedContent;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LatexMarkdownPreprocessorExtension =
|
||||||
|
MarkdownPreprocessorExtension(latexPreprocessor);
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
DeltaASTConverter,
|
DeltaASTConverter,
|
||||||
type DeltaASTConverterOptions,
|
type DeltaASTConverterOptions,
|
||||||
type InlineDeltaMatcher,
|
type InlineDeltaMatcher,
|
||||||
} from '../types/adapter.js';
|
} from '../types/delta-converter.js';
|
||||||
import type { HtmlAST, InlineHtmlAST } from '../types/hast.js';
|
import type { HtmlAST, InlineHtmlAST } from '../types/hast.js';
|
||||||
import { AdapterTextUtils } from '../utils/text.js';
|
import { AdapterTextUtils } from '../utils/text.js';
|
||||||
|
|
||||||
|
|||||||
@@ -30,11 +30,14 @@ export {
|
|||||||
MarkdownAdapter,
|
MarkdownAdapter,
|
||||||
MarkdownAdapterFactoryExtension,
|
MarkdownAdapterFactoryExtension,
|
||||||
MarkdownAdapterFactoryIdentifier,
|
MarkdownAdapterFactoryIdentifier,
|
||||||
|
type MarkdownAdapterPreprocessor,
|
||||||
type MarkdownAST,
|
type MarkdownAST,
|
||||||
MarkdownASTToDeltaExtension,
|
MarkdownASTToDeltaExtension,
|
||||||
type MarkdownASTToDeltaMatcher,
|
type MarkdownASTToDeltaMatcher,
|
||||||
MarkdownASTToDeltaMatcherIdentifier,
|
MarkdownASTToDeltaMatcherIdentifier,
|
||||||
MarkdownDeltaConverter,
|
MarkdownDeltaConverter,
|
||||||
|
MarkdownPreprocessorExtension,
|
||||||
|
MarkdownPreprocessorManager,
|
||||||
} from './markdown';
|
} from './markdown';
|
||||||
export * from './middlewares';
|
export * from './middlewares';
|
||||||
export * from './mix-text';
|
export * from './mix-text';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
type ASTToDeltaMatcher,
|
type ASTToDeltaMatcher,
|
||||||
DeltaASTConverter,
|
DeltaASTConverter,
|
||||||
type InlineDeltaMatcher,
|
type InlineDeltaMatcher,
|
||||||
} from '../types/adapter.js';
|
} from '../types/delta-converter.js';
|
||||||
import type { MarkdownAST } from './type.js';
|
import type { MarkdownAST } from './type.js';
|
||||||
|
|
||||||
export type InlineDeltaToMarkdownAdapterMatcher =
|
export type InlineDeltaToMarkdownAdapterMatcher =
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './block-adapter.js';
|
export * from './block-adapter.js';
|
||||||
export * from './delta-converter.js';
|
export * from './delta-converter.js';
|
||||||
export * from './markdown.js';
|
export * from './markdown.js';
|
||||||
|
export * from './preprocessor.js';
|
||||||
export * from './type.js';
|
export * from './type.js';
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
MarkdownDeltaConverter,
|
MarkdownDeltaConverter,
|
||||||
} from './delta-converter';
|
} from './delta-converter';
|
||||||
import { remarkGfm } from './gfm';
|
import { remarkGfm } from './gfm';
|
||||||
|
import { MarkdownPreprocessorManager } from './preprocessor';
|
||||||
import type { Markdown, MarkdownAST } from './type';
|
import type { Markdown, MarkdownAST } from './type';
|
||||||
|
|
||||||
type MarkdownToSliceSnapshotPayload = {
|
type MarkdownToSliceSnapshotPayload = {
|
||||||
@@ -167,6 +168,7 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
deltaConverter: MarkdownDeltaConverter;
|
deltaConverter: MarkdownDeltaConverter;
|
||||||
|
preprocessorManager: MarkdownPreprocessorManager;
|
||||||
|
|
||||||
readonly blockMatchers: BlockMarkdownAdapterMatcher[];
|
readonly blockMatchers: BlockMarkdownAdapterMatcher[];
|
||||||
|
|
||||||
@@ -187,6 +189,7 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
|
|||||||
inlineDeltaToMarkdownAdapterMatchers,
|
inlineDeltaToMarkdownAdapterMatchers,
|
||||||
markdownInlineToDeltaMatchers
|
markdownInlineToDeltaMatchers
|
||||||
);
|
);
|
||||||
|
this.preprocessorManager = new MarkdownPreprocessorManager(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _astToMarkdown(ast: Root) {
|
private _astToMarkdown(ast: Root) {
|
||||||
@@ -273,7 +276,11 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
|
|||||||
async toBlockSnapshot(
|
async toBlockSnapshot(
|
||||||
payload: ToBlockSnapshotPayload<Markdown>
|
payload: ToBlockSnapshotPayload<Markdown>
|
||||||
): Promise<BlockSnapshot> {
|
): Promise<BlockSnapshot> {
|
||||||
const markdownAst = this._markdownToAst(payload.file);
|
const markdownFile = this.preprocessorManager.process(
|
||||||
|
'block',
|
||||||
|
payload.file
|
||||||
|
);
|
||||||
|
const markdownAst = this._markdownToAst(markdownFile);
|
||||||
const blockSnapshotRoot = {
|
const blockSnapshotRoot = {
|
||||||
type: 'block',
|
type: 'block',
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
@@ -297,7 +304,8 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
|
|||||||
async toDocSnapshot(
|
async toDocSnapshot(
|
||||||
payload: ToDocSnapshotPayload<Markdown>
|
payload: ToDocSnapshotPayload<Markdown>
|
||||||
): Promise<DocSnapshot> {
|
): Promise<DocSnapshot> {
|
||||||
const markdownAst = this._markdownToAst(payload.file);
|
const markdownFile = this.preprocessorManager.process('doc', payload.file);
|
||||||
|
const markdownAst = this._markdownToAst(markdownFile);
|
||||||
const blockSnapshotRoot = {
|
const blockSnapshotRoot = {
|
||||||
type: 'block',
|
type: 'block',
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
@@ -356,67 +364,11 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
|
|||||||
async toSliceSnapshot(
|
async toSliceSnapshot(
|
||||||
payload: MarkdownToSliceSnapshotPayload
|
payload: MarkdownToSliceSnapshotPayload
|
||||||
): Promise<SliceSnapshot | null> {
|
): Promise<SliceSnapshot | null> {
|
||||||
let codeFence = '';
|
const markdownFile = this.preprocessorManager.process(
|
||||||
payload.file = payload.file
|
'slice',
|
||||||
.split('\n')
|
payload.file
|
||||||
.map(line => {
|
);
|
||||||
if (line.trimStart().startsWith('-')) {
|
const markdownAst = this._markdownToAst(markdownFile);
|
||||||
return line;
|
|
||||||
}
|
|
||||||
let trimmedLine = line.trimStart();
|
|
||||||
if (!codeFence && trimmedLine.startsWith('```')) {
|
|
||||||
codeFence = trimmedLine.substring(
|
|
||||||
0,
|
|
||||||
trimmedLine.lastIndexOf('```') + 3
|
|
||||||
);
|
|
||||||
if (codeFence.split('').every(c => c === '`')) {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
codeFence = '';
|
|
||||||
}
|
|
||||||
if (!codeFence && trimmedLine.startsWith('~~~')) {
|
|
||||||
codeFence = trimmedLine.substring(
|
|
||||||
0,
|
|
||||||
trimmedLine.lastIndexOf('~~~') + 3
|
|
||||||
);
|
|
||||||
if (codeFence.split('').every(c => c === '~')) {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
codeFence = '';
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!!codeFence &&
|
|
||||||
trimmedLine.startsWith(codeFence) &&
|
|
||||||
trimmedLine.lastIndexOf(codeFence) === 0
|
|
||||||
) {
|
|
||||||
codeFence = '';
|
|
||||||
}
|
|
||||||
if (codeFence) {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
trimmedLine = trimmedLine.trimEnd();
|
|
||||||
if (!trimmedLine.startsWith('<') && !trimmedLine.endsWith('>')) {
|
|
||||||
// check if it is a url link and wrap it with the angle brackets
|
|
||||||
// sometimes the url includes emphasis `_` that will break URL parsing
|
|
||||||
//
|
|
||||||
// eg. /MuawcBMT1Mzvoar09-_66?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_
|
|
||||||
// https://www.markdownguide.org/basic-syntax/#urls-and-email-addresses
|
|
||||||
try {
|
|
||||||
const valid =
|
|
||||||
URL.canParse?.(trimmedLine) ?? Boolean(new URL(trimmedLine));
|
|
||||||
if (valid) {
|
|
||||||
return `<${trimmedLine}>`;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return line.replace(/^ /, ' ');
|
|
||||||
})
|
|
||||||
.join('\n');
|
|
||||||
const markdownAst = this._markdownToAst(payload.file);
|
|
||||||
const blockSnapshotRoot = {
|
const blockSnapshotRoot = {
|
||||||
type: 'block',
|
type: 'block',
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
createIdentifier,
|
||||||
|
type ServiceIdentifier,
|
||||||
|
type ServiceProvider,
|
||||||
|
} from '@blocksuite/global/di';
|
||||||
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type AdapterPreprocessor,
|
||||||
|
PreprocessorManager,
|
||||||
|
} from '../types/preprocessor';
|
||||||
|
import type { Markdown } from './type';
|
||||||
|
|
||||||
|
export type MarkdownAdapterPreprocessor = AdapterPreprocessor<Markdown>;
|
||||||
|
|
||||||
|
const MarkdownPreprocessorIdentifier =
|
||||||
|
createIdentifier<MarkdownAdapterPreprocessor>('MarkdownPreprocessor');
|
||||||
|
|
||||||
|
export const MarkdownPreprocessorExtension = (
|
||||||
|
preprocessor: MarkdownAdapterPreprocessor
|
||||||
|
): ExtensionType & {
|
||||||
|
identifier: ServiceIdentifier<MarkdownAdapterPreprocessor>;
|
||||||
|
} => {
|
||||||
|
const identifier = MarkdownPreprocessorIdentifier(preprocessor.name);
|
||||||
|
return {
|
||||||
|
setup: di => {
|
||||||
|
di.addImpl(identifier, () => preprocessor);
|
||||||
|
},
|
||||||
|
identifier,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MarkdownPreprocessorManager extends PreprocessorManager<
|
||||||
|
Markdown,
|
||||||
|
MarkdownAdapterPreprocessor
|
||||||
|
> {
|
||||||
|
constructor(provider: ServiceProvider) {
|
||||||
|
super(provider, MarkdownPreprocessorIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
DeltaASTConverter,
|
DeltaASTConverter,
|
||||||
type DeltaASTConverterOptions,
|
type DeltaASTConverterOptions,
|
||||||
type InlineDeltaMatcher,
|
type InlineDeltaMatcher,
|
||||||
} from '../types/adapter.js';
|
} from '../types/delta-converter.js';
|
||||||
import type { HtmlAST, InlineHtmlAST } from '../types/hast.js';
|
import type { HtmlAST, InlineHtmlAST } from '../types/hast.js';
|
||||||
|
|
||||||
export type InlineDeltaToNotionHtmlAdapterMatcher =
|
export type InlineDeltaToNotionHtmlAdapterMatcher =
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import {
|
|||||||
import type { DeltaInsert, ExtensionType } from '@blocksuite/store';
|
import type { DeltaInsert, ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
import type { AffineTextAttributes } from '../../types/index.js';
|
import type { AffineTextAttributes } from '../../types/index.js';
|
||||||
|
import type { TextBuffer } from '../types/adapter.js';
|
||||||
import {
|
import {
|
||||||
type ASTToDeltaMatcher,
|
type ASTToDeltaMatcher,
|
||||||
DeltaASTConverter,
|
DeltaASTConverter,
|
||||||
type InlineDeltaMatcher,
|
type InlineDeltaMatcher,
|
||||||
type TextBuffer,
|
} from '../types/delta-converter.js';
|
||||||
} from '../types/adapter.js';
|
|
||||||
|
|
||||||
export type InlineDeltaToPlainTextAdapterMatcher =
|
export type InlineDeltaToPlainTextAdapterMatcher =
|
||||||
InlineDeltaMatcher<TextBuffer>;
|
InlineDeltaMatcher<TextBuffer>;
|
||||||
|
|||||||
@@ -4,15 +4,13 @@ import {
|
|||||||
type ASTWalker,
|
type ASTWalker,
|
||||||
type ASTWalkerContext,
|
type ASTWalkerContext,
|
||||||
type BaseAdapter,
|
type BaseAdapter,
|
||||||
type BaseTextAttributes,
|
|
||||||
type BlockSnapshot,
|
type BlockSnapshot,
|
||||||
BlockSnapshotSchema,
|
BlockSnapshotSchema,
|
||||||
type DeltaInsert,
|
|
||||||
type NodeProps,
|
type NodeProps,
|
||||||
type Transformer,
|
type Transformer,
|
||||||
} from '@blocksuite/store';
|
} from '@blocksuite/store';
|
||||||
|
|
||||||
import type { AffineTextAttributes } from '../../types/index.js';
|
import type { DeltaASTConverter } from './delta-converter.js';
|
||||||
|
|
||||||
export const isBlockSnapshotNode = (node: unknown): node is BlockSnapshot =>
|
export const isBlockSnapshotNode = (node: unknown): node is BlockSnapshot =>
|
||||||
BlockSnapshotSchema.safeParse(node).success;
|
BlockSnapshotSchema.safeParse(node).success;
|
||||||
@@ -21,13 +19,6 @@ export type TextBuffer = {
|
|||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeltaASTConverterOptions = {
|
|
||||||
trim?: boolean;
|
|
||||||
pre?: boolean;
|
|
||||||
pageMap?: Map<string, string>;
|
|
||||||
removeLastBr?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AdapterContext<
|
export type AdapterContext<
|
||||||
ONode extends object,
|
ONode extends object,
|
||||||
TNode extends object = never,
|
TNode extends object = never,
|
||||||
@@ -124,56 +115,6 @@ export type BlockAdapterMatcher<
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class DeltaASTConverter<
|
|
||||||
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
|
||||||
AST = unknown,
|
|
||||||
> {
|
|
||||||
/**
|
|
||||||
* Convert AST format to delta format
|
|
||||||
*/
|
|
||||||
abstract astToDelta(
|
|
||||||
ast: AST,
|
|
||||||
options?: unknown
|
|
||||||
): DeltaInsert<TextAttributes>[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert delta format to AST format
|
|
||||||
*/
|
|
||||||
abstract deltaToAST(
|
|
||||||
deltas: DeltaInsert<TextAttributes>[],
|
|
||||||
options?: unknown
|
|
||||||
): AST[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type InlineDeltaMatcher<TNode extends object = never> = {
|
|
||||||
name: keyof AffineTextAttributes | string;
|
|
||||||
match: (delta: DeltaInsert<AffineTextAttributes>) => boolean;
|
|
||||||
toAST: (
|
|
||||||
delta: DeltaInsert<AffineTextAttributes>,
|
|
||||||
context: {
|
|
||||||
configs: Map<string, string>;
|
|
||||||
current: TNode;
|
|
||||||
},
|
|
||||||
provider?: ServiceProvider
|
|
||||||
) => TNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ASTToDeltaMatcher<AST> = {
|
|
||||||
name: string;
|
|
||||||
match: (ast: AST) => boolean;
|
|
||||||
toDelta: (
|
|
||||||
ast: AST,
|
|
||||||
context: {
|
|
||||||
configs: Map<string, string>;
|
|
||||||
options: DeltaASTConverterOptions;
|
|
||||||
toDelta: (
|
|
||||||
ast: AST,
|
|
||||||
options?: DeltaASTConverterOptions
|
|
||||||
) => DeltaInsert<AffineTextAttributes>[];
|
|
||||||
}
|
|
||||||
) => DeltaInsert<AffineTextAttributes>[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AdapterFactory = {
|
export type AdapterFactory = {
|
||||||
// TODO(@chen): Make it return the specific adapter type
|
// TODO(@chen): Make it return the specific adapter type
|
||||||
get: (job: Transformer) => BaseAdapter;
|
get: (job: Transformer) => BaseAdapter;
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import type { ServiceProvider } from '@blocksuite/global/di';
|
||||||
|
import type { BaseTextAttributes, DeltaInsert } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import type { AffineTextAttributes } from '../../types';
|
||||||
|
|
||||||
|
export type DeltaASTConverterOptions = {
|
||||||
|
trim?: boolean;
|
||||||
|
pre?: boolean;
|
||||||
|
pageMap?: Map<string, string>;
|
||||||
|
removeLastBr?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export abstract class DeltaASTConverter<
|
||||||
|
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||||
|
AST = unknown,
|
||||||
|
> {
|
||||||
|
/**
|
||||||
|
* Convert AST format to delta format
|
||||||
|
*/
|
||||||
|
abstract astToDelta(
|
||||||
|
ast: AST,
|
||||||
|
options?: unknown
|
||||||
|
): DeltaInsert<TextAttributes>[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert delta format to AST format
|
||||||
|
*/
|
||||||
|
abstract deltaToAST(
|
||||||
|
deltas: DeltaInsert<TextAttributes>[],
|
||||||
|
options?: unknown
|
||||||
|
): AST[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InlineDeltaMatcher<TNode extends object = never> = {
|
||||||
|
name: keyof AffineTextAttributes | string;
|
||||||
|
match: (delta: DeltaInsert<AffineTextAttributes>) => boolean;
|
||||||
|
toAST: (
|
||||||
|
delta: DeltaInsert<AffineTextAttributes>,
|
||||||
|
context: {
|
||||||
|
configs: Map<string, string>;
|
||||||
|
current: TNode;
|
||||||
|
},
|
||||||
|
provider?: ServiceProvider
|
||||||
|
) => TNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ASTToDeltaMatcher<AST> = {
|
||||||
|
name: string;
|
||||||
|
match: (ast: AST) => boolean;
|
||||||
|
toDelta: (
|
||||||
|
ast: AST,
|
||||||
|
context: {
|
||||||
|
configs: Map<string, string>;
|
||||||
|
options: DeltaASTConverterOptions;
|
||||||
|
toDelta: (
|
||||||
|
ast: AST,
|
||||||
|
options?: DeltaASTConverterOptions
|
||||||
|
) => DeltaInsert<AffineTextAttributes>[];
|
||||||
|
}
|
||||||
|
) => DeltaInsert<AffineTextAttributes>[];
|
||||||
|
};
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
export * from './adapter.js';
|
export * from './adapter.js';
|
||||||
|
export * from './delta-converter.js';
|
||||||
export * from './hast.js';
|
export * from './hast.js';
|
||||||
|
export * from './preprocessor.js';
|
||||||
|
|||||||
88
blocksuite/affine/shared/src/adapters/types/preprocessor.ts
Normal file
88
blocksuite/affine/shared/src/adapters/types/preprocessor.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import type { ServiceIdentifier, ServiceProvider } from '@blocksuite/global/di';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Level of preprocessing
|
||||||
|
* - doc: Process at to doc snapshot level
|
||||||
|
* - slice: Process at to slice snapshot level
|
||||||
|
* - block: Process at to block snapshot level
|
||||||
|
*/
|
||||||
|
export type PreprocessLevel = 'doc' | 'slice' | 'block';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for adapter preprocessor
|
||||||
|
* @template T Type of content to process, defaults to string
|
||||||
|
*/
|
||||||
|
export type AdapterPreprocessor<T = string> = {
|
||||||
|
/**
|
||||||
|
* Unique name of the preprocessor
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Levels this preprocessor supports
|
||||||
|
*/
|
||||||
|
levels: PreprocessLevel[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the content
|
||||||
|
* @param content Content to process
|
||||||
|
* @returns Processed content
|
||||||
|
*/
|
||||||
|
preprocess: (content: T) => T;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager class for handling preprocessors
|
||||||
|
* @template T Type of content to process
|
||||||
|
* @template P Type of preprocessor
|
||||||
|
*/
|
||||||
|
export abstract class PreprocessorManager<T, P extends AdapterPreprocessor<T>> {
|
||||||
|
protected readonly preprocessors: Map<PreprocessLevel, Set<P>>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly provider: ServiceProvider,
|
||||||
|
protected readonly identifier: ServiceIdentifier<P>
|
||||||
|
) {
|
||||||
|
this.preprocessors = new Map();
|
||||||
|
|
||||||
|
// Initialize Sets for each level
|
||||||
|
this.preprocessors.set('doc', new Set());
|
||||||
|
this.preprocessors.set('slice', new Set());
|
||||||
|
this.preprocessors.set('block', new Set());
|
||||||
|
|
||||||
|
// Register all preprocessors from provider
|
||||||
|
this.initializePreprocessors();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize preprocessors from provider
|
||||||
|
*/
|
||||||
|
private initializePreprocessors(): void {
|
||||||
|
const preprocessors = Array.from(
|
||||||
|
this.provider.getAll(this.identifier).values()
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const preprocessor of preprocessors) {
|
||||||
|
for (const level of preprocessor.levels) {
|
||||||
|
const levelSet = this.preprocessors.get(level);
|
||||||
|
if (levelSet) {
|
||||||
|
levelSet.add(preprocessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre process content at specified level
|
||||||
|
* @param level Level to process at
|
||||||
|
* @param content Content to process
|
||||||
|
* @returns Processed content
|
||||||
|
*/
|
||||||
|
process(level: PreprocessLevel, content: T): T {
|
||||||
|
const processors = this.preprocessors.get(level) ?? new Set();
|
||||||
|
return Array.from(processors).reduce(
|
||||||
|
(result, preprocessor) => preprocessor.preprocess(result),
|
||||||
|
content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
import { defaultBlockMarkdownAdapterMatchers } from '@blocksuite/affine/adapters';
|
import { MarkdownAdapterExtension } from '@blocksuite/affine/adapters';
|
||||||
import { Container } from '@blocksuite/affine/global/di';
|
import { Container } from '@blocksuite/affine/global/di';
|
||||||
import {
|
|
||||||
InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
} from '@blocksuite/affine/inlines/preset';
|
|
||||||
import type {
|
import type {
|
||||||
AttachmentBlockModel,
|
AttachmentBlockModel,
|
||||||
BookmarkBlockModel,
|
BookmarkBlockModel,
|
||||||
@@ -123,11 +119,7 @@ function generateMarkdownPreviewBuilder(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
[
|
[...MarkdownAdapterExtension].forEach(ext => {
|
||||||
...MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
...defaultBlockMarkdownAdapterMatchers,
|
|
||||||
...InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
].forEach(ext => {
|
|
||||||
ext.setup(container);
|
ext.setup(container);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,8 @@ import { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspac
|
|||||||
import { getWorkerUrl } from '@affine/env/worker';
|
import { getWorkerUrl } from '@affine/env/worker';
|
||||||
import { I18n } from '@affine/i18n';
|
import { I18n } from '@affine/i18n';
|
||||||
import { StoreManagerClient } from '@affine/nbstore/worker/client';
|
import { StoreManagerClient } from '@affine/nbstore/worker/client';
|
||||||
import { defaultBlockMarkdownAdapterMatchers } from '@blocksuite/affine/adapters';
|
import { MarkdownAdapterExtension } from '@blocksuite/affine/adapters';
|
||||||
import { Container } from '@blocksuite/affine/global/di';
|
import { Container } from '@blocksuite/affine/global/di';
|
||||||
import {
|
|
||||||
InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
} from '@blocksuite/affine/inlines/preset';
|
|
||||||
import {
|
import {
|
||||||
docLinkBaseURLMiddleware,
|
docLinkBaseURLMiddleware,
|
||||||
MarkdownAdapter,
|
MarkdownAdapter,
|
||||||
@@ -203,11 +199,7 @@ framework.impl(AIButtonProvider, {
|
|||||||
const snapshot = transformer.docToSnapshot(blockSuiteDoc);
|
const snapshot = transformer.docToSnapshot(blockSuiteDoc);
|
||||||
|
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
[
|
[...MarkdownAdapterExtension].forEach(ext => {
|
||||||
...MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
...defaultBlockMarkdownAdapterMatchers,
|
|
||||||
...InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
].forEach(ext => {
|
|
||||||
ext.setup(container);
|
ext.setup(container);
|
||||||
});
|
});
|
||||||
const provider = container.provider();
|
const provider = container.provider();
|
||||||
|
|||||||
@@ -38,13 +38,9 @@ import { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspac
|
|||||||
import { getWorkerUrl } from '@affine/env/worker';
|
import { getWorkerUrl } from '@affine/env/worker';
|
||||||
import { I18n } from '@affine/i18n';
|
import { I18n } from '@affine/i18n';
|
||||||
import { StoreManagerClient } from '@affine/nbstore/worker/client';
|
import { StoreManagerClient } from '@affine/nbstore/worker/client';
|
||||||
import { defaultBlockMarkdownAdapterMatchers } from '@blocksuite/affine/adapters';
|
import { MarkdownAdapterExtension } from '@blocksuite/affine/adapters';
|
||||||
import { MarkdownTransformer } from '@blocksuite/affine/blocks/root';
|
import { MarkdownTransformer } from '@blocksuite/affine/blocks/root';
|
||||||
import { Container } from '@blocksuite/affine/global/di';
|
import { Container } from '@blocksuite/affine/global/di';
|
||||||
import {
|
|
||||||
InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
} from '@blocksuite/affine/inlines/preset';
|
|
||||||
import {
|
import {
|
||||||
docLinkBaseURLMiddleware,
|
docLinkBaseURLMiddleware,
|
||||||
MarkdownAdapter,
|
MarkdownAdapter,
|
||||||
@@ -266,11 +262,7 @@ const frameworkProvider = framework.provider();
|
|||||||
const snapshot = transformer.docToSnapshot(blockSuiteDoc);
|
const snapshot = transformer.docToSnapshot(blockSuiteDoc);
|
||||||
|
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
[
|
[...MarkdownAdapterExtension].forEach(ext => {
|
||||||
...MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
...defaultBlockMarkdownAdapterMatchers,
|
|
||||||
...InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
].forEach(ext => {
|
|
||||||
ext.setup(container);
|
ext.setup(container);
|
||||||
});
|
});
|
||||||
const provider = container.provider();
|
const provider = container.provider();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createReactComponentFromLit } from '@affine/component';
|
import { createReactComponentFromLit } from '@affine/component';
|
||||||
import { defaultBlockMarkdownAdapterMatchers } from '@blocksuite/affine/adapters';
|
import { MarkdownAdapterExtension } from '@blocksuite/affine/adapters';
|
||||||
import {
|
import {
|
||||||
defaultImageProxyMiddleware,
|
defaultImageProxyMiddleware,
|
||||||
ImageProxyService,
|
ImageProxyService,
|
||||||
@@ -7,10 +7,6 @@ import {
|
|||||||
import { PageEditorBlockSpecs } from '@blocksuite/affine/extensions';
|
import { PageEditorBlockSpecs } from '@blocksuite/affine/extensions';
|
||||||
import { Container, type ServiceProvider } from '@blocksuite/affine/global/di';
|
import { Container, type ServiceProvider } from '@blocksuite/affine/global/di';
|
||||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||||
import {
|
|
||||||
InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
} from '@blocksuite/affine/inlines/preset';
|
|
||||||
import { codeBlockWrapMiddleware } from '@blocksuite/affine/shared/adapters';
|
import { codeBlockWrapMiddleware } from '@blocksuite/affine/shared/adapters';
|
||||||
import { LinkPreviewerService } from '@blocksuite/affine/shared/services';
|
import { LinkPreviewerService } from '@blocksuite/affine/shared/services';
|
||||||
import {
|
import {
|
||||||
@@ -215,11 +211,7 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
|
|||||||
provider = this.host.std.provider;
|
provider = this.host.std.provider;
|
||||||
} else {
|
} else {
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
[
|
[...MarkdownAdapterExtension].forEach(ext => {
|
||||||
...MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
...defaultBlockMarkdownAdapterMatchers,
|
|
||||||
...InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
].forEach(ext => {
|
|
||||||
ext.setup(container);
|
ext.setup(container);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
import { defaultBlockMarkdownAdapterMatchers } from '@blocksuite/affine/adapters';
|
import { MarkdownAdapterExtension } from '@blocksuite/affine/adapters';
|
||||||
import { Container } from '@blocksuite/affine/global/di';
|
import { Container } from '@blocksuite/affine/global/di';
|
||||||
import {
|
|
||||||
InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
} from '@blocksuite/affine/inlines/preset';
|
|
||||||
import { TestWorkspace } from '@blocksuite/affine/store/test';
|
import { TestWorkspace } from '@blocksuite/affine/store/test';
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
import { markdownToMindmap } from '../mindmap-preview.js';
|
import { markdownToMindmap } from '../mindmap-preview.js';
|
||||||
|
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
[
|
[...MarkdownAdapterExtension].forEach(ext => {
|
||||||
...MarkdownInlineToDeltaAdapterExtensions,
|
|
||||||
...defaultBlockMarkdownAdapterMatchers,
|
|
||||||
...InlineDeltaToMarkdownAdapterExtensions,
|
|
||||||
].forEach(ext => {
|
|
||||||
ext.setup(container);
|
ext.setup(container);
|
||||||
});
|
});
|
||||||
const provider = container.provider();
|
const provider = container.provider();
|
||||||
|
|||||||
Reference in New Issue
Block a user