mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +08:00
fix(editor): markdown html and image import (#11712)
Close [BS-3145](https://linear.app/affine-design/issue/BS-3145/markdown-adapter-html-标签导入成-code-block) [BS-3154](https://linear.app/affine-design/issue/BS-3154/[bug]-使用-markdown-with-files-导入到-affine-图片丢失)
This commit is contained in:
@@ -2448,203 +2448,262 @@ World!
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('markdown to snapshot', () => {
|
describe('markdown to snapshot', () => {
|
||||||
test('code', async () => {
|
describe('code', () => {
|
||||||
const markdown = '```python\nimport this\n```\n';
|
test('markdown code block', async () => {
|
||||||
|
const markdown = '```python\nimport this\n```\n';
|
||||||
|
|
||||||
const blockSnapshot: BlockSnapshot = {
|
const blockSnapshot: BlockSnapshot = {
|
||||||
type: 'block',
|
type: 'block',
|
||||||
id: 'matchesReplaceMap[0]',
|
id: 'matchesReplaceMap[0]',
|
||||||
flavour: 'affine:note',
|
flavour: 'affine:note',
|
||||||
props: {
|
props: {
|
||||||
xywh: '[0,0,800,95]',
|
xywh: '[0,0,800,95]',
|
||||||
background: DefaultTheme.noteBackgrounColor,
|
background: DefaultTheme.noteBackgrounColor,
|
||||||
index: 'a0',
|
index: 'a0',
|
||||||
hidden: false,
|
hidden: false,
|
||||||
displayMode: NoteDisplayMode.DocAndEdgeless,
|
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: 'block',
|
|
||||||
id: 'matchesReplaceMap[1]',
|
|
||||||
flavour: 'affine:code',
|
|
||||||
props: {
|
|
||||||
language: 'python',
|
|
||||||
wrap: false,
|
|
||||||
text: {
|
|
||||||
'$blocksuite:internal:text$': true,
|
|
||||||
delta: [
|
|
||||||
{
|
|
||||||
insert: 'import this',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
},
|
||||||
],
|
children: [
|
||||||
};
|
{
|
||||||
|
type: 'block',
|
||||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
id: 'matchesReplaceMap[1]',
|
||||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
flavour: 'affine:code',
|
||||||
file: markdown,
|
props: {
|
||||||
});
|
language: 'python',
|
||||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
wrap: false,
|
||||||
});
|
text: {
|
||||||
|
'$blocksuite:internal:text$': true,
|
||||||
test('code with indentation 1 - slice', async () => {
|
delta: [
|
||||||
const markdown = '```python\n import this\n```';
|
{
|
||||||
|
insert: 'import this',
|
||||||
const sliceSnapshot: SliceSnapshot = {
|
},
|
||||||
type: 'slice',
|
],
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'block',
|
|
||||||
id: 'matchesReplaceMap[0]',
|
|
||||||
flavour: 'affine:note',
|
|
||||||
props: {
|
|
||||||
xywh: '[0,0,800,95]',
|
|
||||||
background: DefaultTheme.noteBackgrounColor,
|
|
||||||
index: 'a0',
|
|
||||||
hidden: false,
|
|
||||||
displayMode: 'both',
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: 'block',
|
|
||||||
id: 'matchesReplaceMap[1]',
|
|
||||||
flavour: 'affine:code',
|
|
||||||
props: {
|
|
||||||
language: 'python',
|
|
||||||
wrap: false,
|
|
||||||
text: {
|
|
||||||
'$blocksuite:internal:text$': true,
|
|
||||||
delta: [
|
|
||||||
{
|
|
||||||
insert: ' import this',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
children: [],
|
|
||||||
},
|
},
|
||||||
],
|
children: [],
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId: '',
|
|
||||||
pageId: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
|
||||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
|
||||||
file: markdown,
|
|
||||||
workspaceId: '',
|
|
||||||
pageId: '',
|
|
||||||
});
|
|
||||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('code with indentation 2 - slice', async () => {
|
|
||||||
const markdown = '````python\n```python\n import this\n```\n````';
|
|
||||||
|
|
||||||
const sliceSnapshot: SliceSnapshot = {
|
|
||||||
type: 'slice',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'block',
|
|
||||||
id: 'matchesReplaceMap[0]',
|
|
||||||
flavour: 'affine:note',
|
|
||||||
props: {
|
|
||||||
xywh: '[0,0,800,95]',
|
|
||||||
background: DefaultTheme.noteBackgrounColor,
|
|
||||||
index: 'a0',
|
|
||||||
hidden: false,
|
|
||||||
displayMode: 'both',
|
|
||||||
},
|
},
|
||||||
children: [
|
],
|
||||||
{
|
};
|
||||||
type: 'block',
|
|
||||||
id: 'matchesReplaceMap[1]',
|
|
||||||
flavour: 'affine:code',
|
|
||||||
props: {
|
|
||||||
language: 'python',
|
|
||||||
wrap: false,
|
|
||||||
text: {
|
|
||||||
'$blocksuite:internal:text$': true,
|
|
||||||
delta: [
|
|
||||||
{
|
|
||||||
insert: '```python\n import this\n```',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId: '',
|
|
||||||
pageId: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||||
file: markdown,
|
file: markdown,
|
||||||
workspaceId: '',
|
});
|
||||||
pageId: '',
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
});
|
});
|
||||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('code with indentation 3 - slice', async () => {
|
test('code with indentation 1 - slice', async () => {
|
||||||
const markdown = '~~~~python\n````python\n import this\n````\n~~~~';
|
const markdown = '```python\n import this\n```';
|
||||||
|
|
||||||
const sliceSnapshot: SliceSnapshot = {
|
const sliceSnapshot: SliceSnapshot = {
|
||||||
type: 'slice',
|
type: 'slice',
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'block',
|
type: 'block',
|
||||||
id: 'matchesReplaceMap[0]',
|
id: 'matchesReplaceMap[0]',
|
||||||
flavour: 'affine:note',
|
flavour: 'affine:note',
|
||||||
props: {
|
props: {
|
||||||
xywh: '[0,0,800,95]',
|
xywh: '[0,0,800,95]',
|
||||||
background: DefaultTheme.noteBackgrounColor,
|
background: DefaultTheme.noteBackgrounColor,
|
||||||
index: 'a0',
|
index: 'a0',
|
||||||
hidden: false,
|
hidden: false,
|
||||||
displayMode: 'both',
|
displayMode: 'both',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'matchesReplaceMap[1]',
|
||||||
|
flavour: 'affine:code',
|
||||||
|
props: {
|
||||||
|
language: 'python',
|
||||||
|
wrap: false,
|
||||||
|
text: {
|
||||||
|
'$blocksuite:internal:text$': true,
|
||||||
|
delta: [
|
||||||
|
{
|
||||||
|
insert: ' import this',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
children: [
|
],
|
||||||
{
|
workspaceId: '',
|
||||||
type: 'block',
|
pageId: '',
|
||||||
id: 'matchesReplaceMap[1]',
|
};
|
||||||
flavour: 'affine:code',
|
|
||||||
props: {
|
|
||||||
language: 'python',
|
|
||||||
wrap: false,
|
|
||||||
text: {
|
|
||||||
'$blocksuite:internal:text$': true,
|
|
||||||
delta: [
|
|
||||||
{
|
|
||||||
insert: '````python\n import this\n````',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId: '',
|
|
||||||
pageId: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||||
file: markdown,
|
file: markdown,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
pageId: '',
|
pageId: '',
|
||||||
|
});
|
||||||
|
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('code with indentation 2 - slice', async () => {
|
||||||
|
const markdown = '````python\n```python\n import this\n```\n````';
|
||||||
|
|
||||||
|
const sliceSnapshot: SliceSnapshot = {
|
||||||
|
type: 'slice',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'matchesReplaceMap[0]',
|
||||||
|
flavour: 'affine:note',
|
||||||
|
props: {
|
||||||
|
xywh: '[0,0,800,95]',
|
||||||
|
background: DefaultTheme.noteBackgrounColor,
|
||||||
|
index: 'a0',
|
||||||
|
hidden: false,
|
||||||
|
displayMode: 'both',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'matchesReplaceMap[1]',
|
||||||
|
flavour: 'affine:code',
|
||||||
|
props: {
|
||||||
|
language: 'python',
|
||||||
|
wrap: false,
|
||||||
|
text: {
|
||||||
|
'$blocksuite:internal:text$': true,
|
||||||
|
delta: [
|
||||||
|
{
|
||||||
|
insert: '```python\n import this\n```',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
workspaceId: '',
|
||||||
|
pageId: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
|
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||||
|
file: markdown,
|
||||||
|
workspaceId: '',
|
||||||
|
pageId: '',
|
||||||
|
});
|
||||||
|
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('code with indentation 3 - slice', async () => {
|
||||||
|
const markdown = '~~~~python\n````python\n import this\n````\n~~~~';
|
||||||
|
|
||||||
|
const sliceSnapshot: SliceSnapshot = {
|
||||||
|
type: 'slice',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'matchesReplaceMap[0]',
|
||||||
|
flavour: 'affine:note',
|
||||||
|
props: {
|
||||||
|
xywh: '[0,0,800,95]',
|
||||||
|
background: DefaultTheme.noteBackgrounColor,
|
||||||
|
index: 'a0',
|
||||||
|
hidden: false,
|
||||||
|
displayMode: 'both',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'matchesReplaceMap[1]',
|
||||||
|
flavour: 'affine:code',
|
||||||
|
props: {
|
||||||
|
language: 'python',
|
||||||
|
wrap: false,
|
||||||
|
text: {
|
||||||
|
'$blocksuite:internal:text$': true,
|
||||||
|
delta: [
|
||||||
|
{
|
||||||
|
insert: '````python\n import this\n````',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
workspaceId: '',
|
||||||
|
pageId: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
|
const rawSliceSnapshot = await mdAdapter.toSliceSnapshot({
|
||||||
|
file: markdown,
|
||||||
|
workspaceId: '',
|
||||||
|
pageId: '',
|
||||||
|
});
|
||||||
|
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('html block import as code block', async () => {
|
||||||
|
const markdown = `<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Welcome to My Page</h1>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#home">Home</a></li>
|
||||||
|
<li><a href="#about">About</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<p>This is a sample HTML content</p>
|
||||||
|
</main>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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:code',
|
||||||
|
props: {
|
||||||
|
language: 'html',
|
||||||
|
wrap: false,
|
||||||
|
text: {
|
||||||
|
'$blocksuite:internal:text$': true,
|
||||||
|
delta: [
|
||||||
|
{
|
||||||
|
insert:
|
||||||
|
'<div class="container">\n <header>\n <h1>Welcome to My Page</h1>\n <nav>\n <ul>\n <li><a href="#home">Home</a></li>\n <li><a href="#about">About</a></li>\n </ul>\n </nav>\n </header>\n <main>\n <p>This is a sample HTML content</p>\n </main>\n</div>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
|
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||||
|
file: markdown,
|
||||||
|
});
|
||||||
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
});
|
});
|
||||||
expect(nanoidReplacement(rawSliceSnapshot!)).toEqual(sliceSnapshot);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('paragraph', async () => {
|
test('paragraph', async () => {
|
||||||
@@ -3638,48 +3697,6 @@ bbb
|
|||||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('html tag', async () => {
|
|
||||||
const markdown = `<aaa>\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: {
|
|
||||||
text: {
|
|
||||||
'$blocksuite:internal:text$': true,
|
|
||||||
delta: [
|
|
||||||
{
|
|
||||||
insert: '<aaa>',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
type: 'text',
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
|
||||||
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
|
||||||
file: markdown,
|
|
||||||
});
|
|
||||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('inline latex', () => {
|
describe('inline latex', () => {
|
||||||
test.each([
|
test.each([
|
||||||
['dollar sign syntax', 'inline $E=mc^2$ latex\n'],
|
['dollar sign syntax', 'inline $E=mc^2$ latex\n'],
|
||||||
|
|||||||
@@ -3,25 +3,51 @@ import {
|
|||||||
BlockMarkdownAdapterExtension,
|
BlockMarkdownAdapterExtension,
|
||||||
type BlockMarkdownAdapterMatcher,
|
type BlockMarkdownAdapterMatcher,
|
||||||
CODE_BLOCK_WRAP_KEY,
|
CODE_BLOCK_WRAP_KEY,
|
||||||
|
IN_PARAGRAPH_NODE_CONTEXT_KEY,
|
||||||
type MarkdownAST,
|
type MarkdownAST,
|
||||||
} from '@blocksuite/affine-shared/adapters';
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
import type { DeltaInsert } from '@blocksuite/store';
|
import type { DeltaInsert } from '@blocksuite/store';
|
||||||
import { nanoid } from '@blocksuite/store';
|
import { nanoid } from '@blocksuite/store';
|
||||||
import type { Code } from 'mdast';
|
import type { Code, Html } from 'mdast';
|
||||||
|
|
||||||
const isCodeNode = (node: MarkdownAST): node is Code => node.type === 'code';
|
const isCodeNode = (node: MarkdownAST): node is Code => node.type === 'code';
|
||||||
|
const isHtmlNode = (node: MarkdownAST): node is Html => node.type === 'html';
|
||||||
|
|
||||||
|
const isCodeOrHtmlNode = (node: MarkdownAST): node is Code | Html =>
|
||||||
|
isCodeNode(node) || isHtmlNode(node);
|
||||||
|
|
||||||
export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
||||||
flavour: CodeBlockSchema.model.flavour,
|
flavour: CodeBlockSchema.model.flavour,
|
||||||
toMatch: o => isCodeNode(o.node),
|
toMatch: o => isCodeOrHtmlNode(o.node),
|
||||||
fromMatch: o => o.node.flavour === 'affine:code',
|
fromMatch: o => o.node.flavour === 'affine:code',
|
||||||
toBlockSnapshot: {
|
toBlockSnapshot: {
|
||||||
enter: (o, context) => {
|
enter: (o, context) => {
|
||||||
if (!isCodeNode(o.node)) {
|
if (!isCodeOrHtmlNode(o.node)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { walkerContext, configs } = context;
|
const { walkerContext, configs } = context;
|
||||||
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
|
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
|
||||||
|
let language = 'plain text';
|
||||||
|
switch (o.node.type) {
|
||||||
|
case 'code': {
|
||||||
|
if (o.node.lang) {
|
||||||
|
language = o.node.lang;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'html': {
|
||||||
|
const inParagraphNode = !!walkerContext.getGlobalContext(
|
||||||
|
IN_PARAGRAPH_NODE_CONTEXT_KEY
|
||||||
|
);
|
||||||
|
// only handle top level html node
|
||||||
|
if (inParagraphNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
language = 'html';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
walkerContext
|
walkerContext
|
||||||
.openNode(
|
.openNode(
|
||||||
{
|
{
|
||||||
@@ -29,7 +55,7 @@ export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
|||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
flavour: 'affine:code',
|
flavour: 'affine:code',
|
||||||
props: {
|
props: {
|
||||||
language: o.node.lang ?? 'Plain Text',
|
language,
|
||||||
wrap,
|
wrap,
|
||||||
text: {
|
text: {
|
||||||
'$blocksuite:internal:text$': true,
|
'$blocksuite:internal:text$': true,
|
||||||
|
|||||||
@@ -2,18 +2,14 @@ import { ParagraphBlockSchema } from '@blocksuite/affine-model';
|
|||||||
import {
|
import {
|
||||||
BlockMarkdownAdapterExtension,
|
BlockMarkdownAdapterExtension,
|
||||||
type BlockMarkdownAdapterMatcher,
|
type BlockMarkdownAdapterMatcher,
|
||||||
|
IN_PARAGRAPH_NODE_CONTEXT_KEY,
|
||||||
type MarkdownAST,
|
type MarkdownAST,
|
||||||
} from '@blocksuite/affine-shared/adapters';
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
import type { DeltaInsert } from '@blocksuite/store';
|
import type { DeltaInsert } from '@blocksuite/store';
|
||||||
import { nanoid } from '@blocksuite/store';
|
import { nanoid } from '@blocksuite/store';
|
||||||
import type { Heading } from 'mdast';
|
import type { Heading } from 'mdast';
|
||||||
|
|
||||||
const PARAGRAPH_MDAST_TYPE = new Set([
|
const PARAGRAPH_MDAST_TYPE = new Set(['paragraph', 'heading', 'blockquote']);
|
||||||
'paragraph',
|
|
||||||
'html',
|
|
||||||
'heading',
|
|
||||||
'blockquote',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const isParagraphMDASTType = (node: MarkdownAST) =>
|
const isParagraphMDASTType = (node: MarkdownAST) =>
|
||||||
PARAGRAPH_MDAST_TYPE.has(node.type);
|
PARAGRAPH_MDAST_TYPE.has(node.type);
|
||||||
@@ -27,32 +23,8 @@ export const paragraphBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
enter: (o, context) => {
|
enter: (o, context) => {
|
||||||
const { walkerContext, deltaConverter } = context;
|
const { walkerContext, deltaConverter } = context;
|
||||||
switch (o.node.type) {
|
switch (o.node.type) {
|
||||||
case 'html': {
|
|
||||||
walkerContext
|
|
||||||
.openNode(
|
|
||||||
{
|
|
||||||
type: 'block',
|
|
||||||
id: nanoid(),
|
|
||||||
flavour: 'affine:paragraph',
|
|
||||||
props: {
|
|
||||||
type: 'text',
|
|
||||||
text: {
|
|
||||||
'$blocksuite:internal:text$': true,
|
|
||||||
delta: [
|
|
||||||
{
|
|
||||||
insert: o.node.value,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
'children'
|
|
||||||
)
|
|
||||||
.closeNode();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'paragraph': {
|
case 'paragraph': {
|
||||||
|
walkerContext.setGlobalContext(IN_PARAGRAPH_NODE_CONTEXT_KEY, true);
|
||||||
walkerContext
|
walkerContext
|
||||||
.openNode(
|
.openNode(
|
||||||
{
|
{
|
||||||
@@ -71,7 +43,6 @@ export const paragraphBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
'children'
|
'children'
|
||||||
)
|
)
|
||||||
.closeNode();
|
.closeNode();
|
||||||
walkerContext.skipAllChildren();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'heading': {
|
case 'heading': {
|
||||||
@@ -119,6 +90,12 @@ export const paragraphBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
leave: (o, context) => {
|
||||||
|
if (o.node.type === 'paragraph') {
|
||||||
|
const { walkerContext } = context;
|
||||||
|
walkerContext.setGlobalContext(IN_PARAGRAPH_NODE_CONTEXT_KEY, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fromBlockSnapshot: {
|
fromBlockSnapshot: {
|
||||||
enter: (o, context) => {
|
enter: (o, context) => {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export {
|
|||||||
type BlockMarkdownAdapterMatcher,
|
type BlockMarkdownAdapterMatcher,
|
||||||
BlockMarkdownAdapterMatcherIdentifier,
|
BlockMarkdownAdapterMatcherIdentifier,
|
||||||
FOOTNOTE_DEFINITION_PREFIX,
|
FOOTNOTE_DEFINITION_PREFIX,
|
||||||
|
IN_PARAGRAPH_NODE_CONTEXT_KEY,
|
||||||
InlineDeltaToMarkdownAdapterExtension,
|
InlineDeltaToMarkdownAdapterExtension,
|
||||||
type InlineDeltaToMarkdownAdapterMatcher,
|
type InlineDeltaToMarkdownAdapterMatcher,
|
||||||
InlineDeltaToMarkdownAdapterMatcherIdentifier,
|
InlineDeltaToMarkdownAdapterMatcherIdentifier,
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ export const isMarkdownAST = (node: unknown): node is MarkdownAST =>
|
|||||||
(node as MarkdownAST).type !== undefined;
|
(node as MarkdownAST).type !== undefined;
|
||||||
|
|
||||||
export const FOOTNOTE_DEFINITION_PREFIX = 'footnoteDefinition:';
|
export const FOOTNOTE_DEFINITION_PREFIX = 'footnoteDefinition:';
|
||||||
|
export const IN_PARAGRAPH_NODE_CONTEXT_KEY = 'mdast:paragraph';
|
||||||
|
|||||||
Reference in New Issue
Block a user