mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-11 22:18:54 +08:00
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);
|
||||
});
|
||||
|
||||
test('inline latex', async () => {
|
||||
const markdown = 'inline $E=mc^2$ latex\n';
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
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: [],
|
||||
describe('inline latex', () => {
|
||||
test.each([
|
||||
['dollar sign syntax', 'inline $E=mc^2$ latex\n'],
|
||||
['backslash syntax', 'inline \\(E=mc^2\\) latex\n'],
|
||||
])('should convert %s correctly', async (_, markdown) => {
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
],
|
||||
};
|
||||
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 rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('latex block', async () => {
|
||||
const markdown = '$$\nE=mc^2\n$$\n';
|
||||
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:latex',
|
||||
props: {
|
||||
latex: 'E=mc^2',
|
||||
},
|
||||
children: [],
|
||||
describe('latex block', () => {
|
||||
test.each([
|
||||
['dollar sign syntax', '$$\nE=mc^2\n$$\n'],
|
||||
['backslash syntax', '\\[\nE=mc^2\n\\]\n'],
|
||||
])('should convert %s correctly', async (_, markdown) => {
|
||||
const blockSnapshot: BlockSnapshot = {
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[0]',
|
||||
flavour: 'affine:note',
|
||||
props: {
|
||||
xywh: '[0,0,800,95]',
|
||||
background: DefaultTheme.noteBackgrounColor,
|
||||
index: 'a0',
|
||||
hidden: false,
|
||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||
},
|
||||
],
|
||||
};
|
||||
children: [
|
||||
{
|
||||
type: 'block',
|
||||
id: 'matchesReplaceMap[1]',
|
||||
flavour: 'affine:latex',
|
||||
props: {
|
||||
latex: 'E=mc^2',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||
file: markdown,
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||
});
|
||||
|
||||
test('reference', async () => {
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { defaultBlockHtmlAdapterMatchers } from './html/block-matcher';
|
||||
import { defaultBlockMarkdownAdapterMatchers } from './markdown/block-matcher';
|
||||
import { defaultMarkdownPreprocessors } from './markdown/preprocessor';
|
||||
import { defaultBlockNotionHtmlAdapterMatchers } from './notion-html/block-matcher';
|
||||
import { defaultBlockPlainTextAdapterMatchers } from './plain-text/block-matcher';
|
||||
|
||||
@@ -44,6 +45,7 @@ export const MarkdownAdapterExtension: ExtensionType[] = [
|
||||
...MarkdownInlineToDeltaAdapterExtensions,
|
||||
...defaultBlockMarkdownAdapterMatchers,
|
||||
...InlineDeltaToMarkdownAdapterExtensions,
|
||||
...defaultMarkdownPreprocessors,
|
||||
];
|
||||
|
||||
export const NotionHtmlAdapterExtension: ExtensionType[] = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './extension.js';
|
||||
export * from './html/block-matcher.js';
|
||||
export * from './markdown/block-matcher.js';
|
||||
export * from './markdown/preprocessor.js';
|
||||
export * from './notion-html/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,
|
||||
];
|
||||
Reference in New Issue
Block a user