mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-24 05:07:06 +08:00
feat(editor): support footnote adapter (#9844)
[BS-2373](https://linear.app/affine-design/issue/BS-2373/适配-footnote-adapter)
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@preact/signals-core": "^1.8.0",
|
"@preact/signals-core": "^1.8.0",
|
||||||
"@toeverything/theme": "^1.1.7",
|
"@toeverything/theme": "^1.1.7",
|
||||||
|
"@types/mdast": "^4.0.4",
|
||||||
"lit": "^3.2.0",
|
"lit": "^3.2.0",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
|
|||||||
@@ -3,15 +3,19 @@ import type { ExtensionType } from '@blocksuite/store';
|
|||||||
import {
|
import {
|
||||||
DocNoteBlockHtmlAdapterExtension,
|
DocNoteBlockHtmlAdapterExtension,
|
||||||
EdgelessNoteBlockHtmlAdapterExtension,
|
EdgelessNoteBlockHtmlAdapterExtension,
|
||||||
} from './html.js';
|
} from './html';
|
||||||
import {
|
import {
|
||||||
DocNoteBlockMarkdownAdapterExtension,
|
DocNoteBlockMarkdownAdapterExtension,
|
||||||
EdgelessNoteBlockMarkdownAdapterExtension,
|
EdgelessNoteBlockMarkdownAdapterExtension,
|
||||||
} from './markdown.js';
|
} from './markdown';
|
||||||
import {
|
import {
|
||||||
DocNoteBlockPlainTextAdapterExtension,
|
DocNoteBlockPlainTextAdapterExtension,
|
||||||
EdgelessNoteBlockPlainTextAdapterExtension,
|
EdgelessNoteBlockPlainTextAdapterExtension,
|
||||||
} from './plain-text.js';
|
} from './plain-text';
|
||||||
|
|
||||||
|
export * from './html';
|
||||||
|
export * from './markdown';
|
||||||
|
export * from './plain-text';
|
||||||
|
|
||||||
export const DocNoteBlockAdapterExtensions: ExtensionType[] = [
|
export const DocNoteBlockAdapterExtensions: ExtensionType[] = [
|
||||||
DocNoteBlockMarkdownAdapterExtension,
|
DocNoteBlockMarkdownAdapterExtension,
|
||||||
|
|||||||
@@ -2,7 +2,35 @@ import { NoteBlockSchema, NoteDisplayMode } from '@blocksuite/affine-model';
|
|||||||
import {
|
import {
|
||||||
BlockMarkdownAdapterExtension,
|
BlockMarkdownAdapterExtension,
|
||||||
type BlockMarkdownAdapterMatcher,
|
type BlockMarkdownAdapterMatcher,
|
||||||
|
FOOTNOTE_DEFINITION_PREFIX,
|
||||||
|
type MarkdownAST,
|
||||||
} from '@blocksuite/affine-shared/adapters';
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
|
import type { FootnoteDefinition, Root } from 'mdast';
|
||||||
|
|
||||||
|
const isRootNode = (node: MarkdownAST): node is Root => node.type === 'root';
|
||||||
|
const isFootnoteDefinitionNode = (
|
||||||
|
node: MarkdownAST
|
||||||
|
): node is FootnoteDefinition => node.type === 'footnoteDefinition';
|
||||||
|
|
||||||
|
const createFootnoteDefinition = (
|
||||||
|
identifier: string,
|
||||||
|
content: string
|
||||||
|
): MarkdownAST => ({
|
||||||
|
type: 'footnoteDefinition',
|
||||||
|
label: identifier,
|
||||||
|
identifier,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
value: content,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a markdown adapter matcher for note block.
|
* Create a markdown adapter matcher for note block.
|
||||||
@@ -15,9 +43,36 @@ const createNoteBlockMarkdownAdapterMatcher = (
|
|||||||
displayModeToSkip: NoteDisplayMode
|
displayModeToSkip: NoteDisplayMode
|
||||||
): BlockMarkdownAdapterMatcher => ({
|
): BlockMarkdownAdapterMatcher => ({
|
||||||
flavour: NoteBlockSchema.model.flavour,
|
flavour: NoteBlockSchema.model.flavour,
|
||||||
toMatch: () => false,
|
toMatch: o => isRootNode(o.node),
|
||||||
fromMatch: o => o.node.flavour === NoteBlockSchema.model.flavour,
|
fromMatch: o => o.node.flavour === NoteBlockSchema.model.flavour,
|
||||||
toBlockSnapshot: {},
|
toBlockSnapshot: {
|
||||||
|
enter: (o, context) => {
|
||||||
|
if (!isRootNode(o.node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const noteAst = o.node;
|
||||||
|
// Find all the footnoteDefinition in the noteAst
|
||||||
|
const { configs } = context;
|
||||||
|
noteAst.children.forEach(child => {
|
||||||
|
if (isFootnoteDefinitionNode(child)) {
|
||||||
|
const identifier = child.identifier;
|
||||||
|
const definitionKey = `${FOOTNOTE_DEFINITION_PREFIX}${identifier}`;
|
||||||
|
// Get the text content of the footnoteDefinition
|
||||||
|
const textContent = child.children
|
||||||
|
.find(child => child.type === 'paragraph')
|
||||||
|
?.children.find(child => child.type === 'text')?.value;
|
||||||
|
if (textContent) {
|
||||||
|
configs.set(definitionKey, textContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the footnoteDefinition node from the noteAst
|
||||||
|
noteAst.children = noteAst.children.filter(
|
||||||
|
child => !isFootnoteDefinitionNode(child)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
fromBlockSnapshot: {
|
fromBlockSnapshot: {
|
||||||
enter: (o, context) => {
|
enter: (o, context) => {
|
||||||
const node = o.node;
|
const node = o.node;
|
||||||
@@ -25,6 +80,33 @@ const createNoteBlockMarkdownAdapterMatcher = (
|
|||||||
context.walkerContext.skipAllChildren();
|
context.walkerContext.skipAllChildren();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
leave: (_, context) => {
|
||||||
|
const { walkerContext, configs } = context;
|
||||||
|
// Get all the footnote definitions config starts with FOOTNOTE_DEFINITION_PREFIX
|
||||||
|
// And create footnoteDefinition AST node for each of them
|
||||||
|
Array.from(configs.keys())
|
||||||
|
.filter(key => key.startsWith(FOOTNOTE_DEFINITION_PREFIX))
|
||||||
|
.forEach(key => {
|
||||||
|
const hasFootnoteDefinition = !!walkerContext.getGlobalContext(key);
|
||||||
|
// If the footnoteDefinition node is already in md ast, skip it
|
||||||
|
// In markdown file, we only need to create footnoteDefinition once
|
||||||
|
if (hasFootnoteDefinition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const definition = configs.get(key);
|
||||||
|
const identifier = key.slice(FOOTNOTE_DEFINITION_PREFIX.length);
|
||||||
|
if (definition && identifier) {
|
||||||
|
walkerContext
|
||||||
|
.openNode(
|
||||||
|
createFootnoteDefinition(identifier, definition),
|
||||||
|
'children'
|
||||||
|
)
|
||||||
|
.closeNode();
|
||||||
|
// Set the footnoteDefinition node as global context to avoid duplicate creation
|
||||||
|
walkerContext.setGlobalContext(key, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './inline-delta';
|
||||||
|
export * from './markdown-inline';
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
FOOTNOTE_DEFINITION_PREFIX,
|
||||||
InlineDeltaToMarkdownAdapterExtension,
|
InlineDeltaToMarkdownAdapterExtension,
|
||||||
TextUtils,
|
TextUtils,
|
||||||
} from '@blocksuite/affine-shared/adapters';
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
@@ -146,6 +147,46 @@ export const latexDeltaToMarkdownAdapterMatcher =
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const footnoteReferenceDeltaToMarkdownAdapterMatcher =
|
||||||
|
InlineDeltaToMarkdownAdapterExtension({
|
||||||
|
name: 'footnote-reference',
|
||||||
|
match: delta => !!delta.attributes?.footnote,
|
||||||
|
toAST: (delta, context) => {
|
||||||
|
const mdast: PhrasingContent = {
|
||||||
|
type: 'text',
|
||||||
|
value: delta.insert,
|
||||||
|
};
|
||||||
|
const footnote = delta.attributes?.footnote;
|
||||||
|
if (!footnote) {
|
||||||
|
return mdast;
|
||||||
|
}
|
||||||
|
const footnoteDefinitionKey = `${FOOTNOTE_DEFINITION_PREFIX}${footnote.label}`;
|
||||||
|
const { configs } = context;
|
||||||
|
// FootnoteReference should be paired with FootnoteDefinition
|
||||||
|
// If the footnoteDefinition is not in the configs, set it to configs
|
||||||
|
// We should add the footnoteDefinition markdown ast nodes to tree after all the footnoteReference markdown ast nodes are added
|
||||||
|
if (!configs.has(footnoteDefinitionKey)) {
|
||||||
|
// clone the footnote reference
|
||||||
|
const clonedFootnoteReference = { ...footnote.reference };
|
||||||
|
// If the footnote reference contains url, encode it
|
||||||
|
if (clonedFootnoteReference.url) {
|
||||||
|
clonedFootnoteReference.url = encodeURIComponent(
|
||||||
|
clonedFootnoteReference.url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
configs.set(
|
||||||
|
footnoteDefinitionKey,
|
||||||
|
JSON.stringify(clonedFootnoteReference)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'footnoteReference',
|
||||||
|
label: footnote.label,
|
||||||
|
identifier: footnote.label,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const InlineDeltaToMarkdownAdapterExtensions = [
|
export const InlineDeltaToMarkdownAdapterExtensions = [
|
||||||
referenceDeltaToMarkdownAdapterMatcher,
|
referenceDeltaToMarkdownAdapterMatcher,
|
||||||
linkDeltaToMarkdownAdapterMatcher,
|
linkDeltaToMarkdownAdapterMatcher,
|
||||||
@@ -154,4 +195,5 @@ export const InlineDeltaToMarkdownAdapterExtensions = [
|
|||||||
italicDeltaToMarkdownAdapterMatcher,
|
italicDeltaToMarkdownAdapterMatcher,
|
||||||
strikeDeltaToMarkdownAdapterMatcher,
|
strikeDeltaToMarkdownAdapterMatcher,
|
||||||
latexDeltaToMarkdownAdapterMatcher,
|
latexDeltaToMarkdownAdapterMatcher,
|
||||||
|
footnoteReferenceDeltaToMarkdownAdapterMatcher,
|
||||||
];
|
];
|
||||||
|
|||||||
+43
-1
@@ -1,4 +1,8 @@
|
|||||||
import { MarkdownASTToDeltaExtension } from '@blocksuite/affine-shared/adapters';
|
import { FootNoteReferenceParamsSchema } from '@blocksuite/affine-model';
|
||||||
|
import {
|
||||||
|
FOOTNOTE_DEFINITION_PREFIX,
|
||||||
|
MarkdownASTToDeltaExtension,
|
||||||
|
} from '@blocksuite/affine-shared/adapters';
|
||||||
|
|
||||||
export const markdownTextToDeltaMatcher = MarkdownASTToDeltaExtension({
|
export const markdownTextToDeltaMatcher = MarkdownASTToDeltaExtension({
|
||||||
name: 'text',
|
name: 'text',
|
||||||
@@ -138,6 +142,43 @@ export const markdownInlineMathToDeltaMatcher = MarkdownASTToDeltaExtension({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const markdownFootnoteReferenceToDeltaMatcher =
|
||||||
|
MarkdownASTToDeltaExtension({
|
||||||
|
name: 'footnote-reference',
|
||||||
|
match: ast => ast.type === 'footnoteReference',
|
||||||
|
toDelta: (ast, context) => {
|
||||||
|
if (ast.type !== 'footnoteReference') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { configs } = context;
|
||||||
|
const footnoteDefinitionKey = `${FOOTNOTE_DEFINITION_PREFIX}${ast.identifier}`;
|
||||||
|
const footnoteDefinition = configs.get(footnoteDefinitionKey);
|
||||||
|
if (!footnoteDefinition) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const footnoteDefinitionJson = JSON.parse(footnoteDefinition);
|
||||||
|
// If the footnote definition contains url, decode it
|
||||||
|
if (footnoteDefinitionJson.url) {
|
||||||
|
footnoteDefinitionJson.url = decodeURIComponent(
|
||||||
|
footnoteDefinitionJson.url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const footnoteReference = FootNoteReferenceParamsSchema.parse(
|
||||||
|
footnoteDefinitionJson
|
||||||
|
);
|
||||||
|
const footnote = {
|
||||||
|
label: ast.identifier,
|
||||||
|
reference: footnoteReference,
|
||||||
|
};
|
||||||
|
return [{ insert: ' ', attributes: { footnote } }];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing footnote reference', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const MarkdownInlineToDeltaAdapterExtensions = [
|
export const MarkdownInlineToDeltaAdapterExtensions = [
|
||||||
markdownTextToDeltaMatcher,
|
markdownTextToDeltaMatcher,
|
||||||
markdownInlineCodeToDeltaMatcher,
|
markdownInlineCodeToDeltaMatcher,
|
||||||
@@ -147,4 +188,5 @@ export const MarkdownInlineToDeltaAdapterExtensions = [
|
|||||||
markdownLinkToDeltaMatcher,
|
markdownLinkToDeltaMatcher,
|
||||||
markdownInlineMathToDeltaMatcher,
|
markdownInlineMathToDeltaMatcher,
|
||||||
markdownListToDeltaMatcher,
|
markdownListToDeltaMatcher,
|
||||||
|
markdownFootnoteReferenceToDeltaMatcher,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
export * from './adapters/extensions';
|
export * from './adapters/extensions';
|
||||||
export * from './adapters/html/html-inline';
|
export * from './adapters/html/html-inline';
|
||||||
export * from './adapters/html/inline-delta';
|
export * from './adapters/html/inline-delta';
|
||||||
export * from './adapters/markdown/inline-delta';
|
export * from './adapters/markdown';
|
||||||
export * from './adapters/markdown/markdown-inline';
|
|
||||||
export * from './adapters/notion-html/html-inline';
|
export * from './adapters/notion-html/html-inline';
|
||||||
export * from './adapters/plain-text/inline-delta';
|
export * from './adapters/plain-text/inline-delta';
|
||||||
export * from './presets/affine-inline-specs';
|
export * from './presets/affine-inline-specs';
|
||||||
|
|||||||
@@ -29,10 +29,12 @@
|
|||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.mergewith": "^4.6.2",
|
"lodash.mergewith": "^4.6.2",
|
||||||
"mdast-util-gfm-autolink-literal": "^2.0.1",
|
"mdast-util-gfm-autolink-literal": "^2.0.1",
|
||||||
|
"mdast-util-gfm-footnote": "^2.0.0",
|
||||||
"mdast-util-gfm-strikethrough": "^2.0.0",
|
"mdast-util-gfm-strikethrough": "^2.0.0",
|
||||||
"mdast-util-gfm-table": "^2.0.0",
|
"mdast-util-gfm-table": "^2.0.0",
|
||||||
"mdast-util-gfm-task-list-item": "^2.0.0",
|
"mdast-util-gfm-task-list-item": "^2.0.0",
|
||||||
"micromark-extension-gfm-autolink-literal": "^2.1.0",
|
"micromark-extension-gfm-autolink-literal": "^2.1.0",
|
||||||
|
"micromark-extension-gfm-footnote": "^2.1.0",
|
||||||
"micromark-extension-gfm-strikethrough": "^2.1.0",
|
"micromark-extension-gfm-strikethrough": "^2.1.0",
|
||||||
"micromark-extension-gfm-table": "^2.1.0",
|
"micromark-extension-gfm-table": "^2.1.0",
|
||||||
"micromark-extension-gfm-task-list-item": "^2.1.0",
|
"micromark-extension-gfm-task-list-item": "^2.1.0",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export {
|
|||||||
BlockMarkdownAdapterExtension,
|
BlockMarkdownAdapterExtension,
|
||||||
type BlockMarkdownAdapterMatcher,
|
type BlockMarkdownAdapterMatcher,
|
||||||
BlockMarkdownAdapterMatcherIdentifier,
|
BlockMarkdownAdapterMatcherIdentifier,
|
||||||
|
FOOTNOTE_DEFINITION_PREFIX,
|
||||||
InlineDeltaToMarkdownAdapterExtension,
|
InlineDeltaToMarkdownAdapterExtension,
|
||||||
type InlineDeltaToMarkdownAdapterMatcher,
|
type InlineDeltaToMarkdownAdapterMatcher,
|
||||||
InlineDeltaToMarkdownAdapterMatcherIdentifier,
|
InlineDeltaToMarkdownAdapterMatcherIdentifier,
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ MIT License
|
|||||||
Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
|
Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
|
||||||
|
|
||||||
mdast-util-gfm-autolink-literal is from markdown only.
|
mdast-util-gfm-autolink-literal is from markdown only.
|
||||||
mdast-util-gfm-footnote is not included.
|
|
||||||
*/
|
*/
|
||||||
import { gfmAutolinkLiteralFromMarkdown } from 'mdast-util-gfm-autolink-literal';
|
import { gfmAutolinkLiteralFromMarkdown } from 'mdast-util-gfm-autolink-literal';
|
||||||
|
import {
|
||||||
|
gfmFootnoteFromMarkdown,
|
||||||
|
gfmFootnoteToMarkdown,
|
||||||
|
} from 'mdast-util-gfm-footnote';
|
||||||
import {
|
import {
|
||||||
gfmStrikethroughFromMarkdown,
|
gfmStrikethroughFromMarkdown,
|
||||||
gfmStrikethroughToMarkdown,
|
gfmStrikethroughToMarkdown,
|
||||||
@@ -17,6 +20,7 @@ import {
|
|||||||
gfmTaskListItemToMarkdown,
|
gfmTaskListItemToMarkdown,
|
||||||
} from 'mdast-util-gfm-task-list-item';
|
} from 'mdast-util-gfm-task-list-item';
|
||||||
import { gfmAutolinkLiteral } from 'micromark-extension-gfm-autolink-literal';
|
import { gfmAutolinkLiteral } from 'micromark-extension-gfm-autolink-literal';
|
||||||
|
import { gfmFootnote } from 'micromark-extension-gfm-footnote';
|
||||||
import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough';
|
import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough';
|
||||||
import { gfmTable } from 'micromark-extension-gfm-table';
|
import { gfmTable } from 'micromark-extension-gfm-table';
|
||||||
import { gfmTaskListItem } from 'micromark-extension-gfm-task-list-item';
|
import { gfmTaskListItem } from 'micromark-extension-gfm-task-list-item';
|
||||||
@@ -29,6 +33,7 @@ export function gfm() {
|
|||||||
gfmStrikethrough(),
|
gfmStrikethrough(),
|
||||||
gfmTable(),
|
gfmTable(),
|
||||||
gfmTaskListItem(),
|
gfmTaskListItem(),
|
||||||
|
gfmFootnote(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +43,7 @@ function gfmFromMarkdown() {
|
|||||||
gfmTableFromMarkdown(),
|
gfmTableFromMarkdown(),
|
||||||
gfmTaskListItemFromMarkdown(),
|
gfmTaskListItemFromMarkdown(),
|
||||||
gfmAutolinkLiteralFromMarkdown(),
|
gfmAutolinkLiteralFromMarkdown(),
|
||||||
|
gfmFootnoteFromMarkdown(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +53,7 @@ function gfmToMarkdown() {
|
|||||||
gfmStrikethroughToMarkdown(),
|
gfmStrikethroughToMarkdown(),
|
||||||
gfmTableToMarkdown(),
|
gfmTableToMarkdown(),
|
||||||
gfmTaskListItemToMarkdown(),
|
gfmTaskListItemToMarkdown(),
|
||||||
|
gfmFootnoteToMarkdown(),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ export const isMarkdownAST = (node: unknown): node is MarkdownAST =>
|
|||||||
!Array.isArray(node) &&
|
!Array.isArray(node) &&
|
||||||
'type' in (node as object) &&
|
'type' in (node as object) &&
|
||||||
(node as MarkdownAST).type !== undefined;
|
(node as MarkdownAST).type !== undefined;
|
||||||
|
|
||||||
|
export const FOOTNOTE_DEFINITION_PREFIX = 'footnoteDefinition:';
|
||||||
|
|||||||
@@ -2350,6 +2350,109 @@ World!
|
|||||||
});
|
});
|
||||||
expect(target.file).toBe(docMd);
|
expect(target.file).toBe(docMd);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('footnote', async () => {
|
||||||
|
const blockSnapshot: BlockSnapshot = {
|
||||||
|
type: 'block',
|
||||||
|
id: 'block:vu6SK6WJpW',
|
||||||
|
flavour: 'affine:page',
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
'$blocksuite:internal:text$': true,
|
||||||
|
delta: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'block:Tk4gSPocAt',
|
||||||
|
flavour: 'affine:surface',
|
||||||
|
props: {
|
||||||
|
elements: {},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'block:WfnS5ZDCJT',
|
||||||
|
flavour: 'affine:note',
|
||||||
|
props: {
|
||||||
|
xywh: '[0,0,800,95]',
|
||||||
|
background: DefaultTheme.noteBackgrounColor,
|
||||||
|
index: 'a0',
|
||||||
|
hidden: false,
|
||||||
|
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'block',
|
||||||
|
id: 'block:zxDyvrg1Mh',
|
||||||
|
flavour: 'affine:paragraph',
|
||||||
|
props: {
|
||||||
|
type: 'text',
|
||||||
|
text: {
|
||||||
|
'$blocksuite:internal:text$': true,
|
||||||
|
delta: [
|
||||||
|
{
|
||||||
|
insert: 'aaa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: ' ',
|
||||||
|
attributes: {
|
||||||
|
footnote: {
|
||||||
|
label: '1',
|
||||||
|
reference: {
|
||||||
|
type: 'url',
|
||||||
|
url: 'https://www.example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: ' ',
|
||||||
|
attributes: {
|
||||||
|
footnote: {
|
||||||
|
label: '2',
|
||||||
|
reference: {
|
||||||
|
type: 'doc',
|
||||||
|
docId: 'deadbeef',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: ' ',
|
||||||
|
attributes: {
|
||||||
|
footnote: {
|
||||||
|
label: '3',
|
||||||
|
reference: {
|
||||||
|
type: 'attachment',
|
||||||
|
blobId: 'abcdefg',
|
||||||
|
fileName: 'test.txt',
|
||||||
|
fileType: 'text/plain',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const markdown =
|
||||||
|
'aaa[^1][^2][^3]\n\n[^1]: {"type":"url","url":"https%3A%2F%2Fwww.example.com"}\n\n[^2]: {"type":"doc","docId":"deadbeef"}\n\n[^3]: {"type":"attachment","blobId":"abcdefg","fileName":"test.txt","fileType":"text/plain"}\n';
|
||||||
|
|
||||||
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
|
const target = await mdAdapter.fromBlockSnapshot({
|
||||||
|
snapshot: blockSnapshot,
|
||||||
|
});
|
||||||
|
expect(target.file).toBe(markdown);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('markdown to snapshot', () => {
|
describe('markdown to snapshot', () => {
|
||||||
@@ -3858,4 +3961,85 @@ hhh
|
|||||||
});
|
});
|
||||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('without footnote middleware', async () => {
|
||||||
|
const markdown =
|
||||||
|
'aaa[^1][^2][^3]\n\n[^1]: {"type":"url","url":"https%3A%2F%2Fwww.example.com"}\n\n[^2]: {"type":"doc","docId":"deadbeef"}\n\n[^3]: {"type":"attachment","blobId":"abcdefg","fileName":"test.txt","fileType":"text/plain"}\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: 'aaa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: ' ',
|
||||||
|
attributes: {
|
||||||
|
footnote: {
|
||||||
|
label: '1',
|
||||||
|
reference: {
|
||||||
|
type: 'url',
|
||||||
|
url: 'https://www.example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: ' ',
|
||||||
|
attributes: {
|
||||||
|
footnote: {
|
||||||
|
label: '2',
|
||||||
|
reference: {
|
||||||
|
type: 'doc',
|
||||||
|
docId: 'deadbeef',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: ' ',
|
||||||
|
attributes: {
|
||||||
|
footnote: {
|
||||||
|
label: '3',
|
||||||
|
reference: {
|
||||||
|
type: 'attachment',
|
||||||
|
blobId: 'abcdefg',
|
||||||
|
fileName: 'test.txt',
|
||||||
|
fileType: 'text/plain',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mdAdapter = new MarkdownAdapter(createJob(), provider);
|
||||||
|
const rawBlockSnapshot = await mdAdapter.toBlockSnapshot({
|
||||||
|
file: markdown,
|
||||||
|
});
|
||||||
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ import {
|
|||||||
import { ImageBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-image';
|
import { ImageBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-image';
|
||||||
import { LatexBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-latex';
|
import { LatexBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-latex';
|
||||||
import { ListBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-list';
|
import { ListBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-list';
|
||||||
|
import { DocNoteBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-note';
|
||||||
import { ParagraphBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-paragraph';
|
import { ParagraphBlockMarkdownAdapterExtension } from '@blocksuite/affine-block-paragraph';
|
||||||
|
|
||||||
import { RootBlockMarkdownAdapterExtension } from '../../../root-block/adapters/markdown.js';
|
import { RootBlockMarkdownAdapterExtension } from '../../../root-block/adapters/markdown.js';
|
||||||
|
|
||||||
export const defaultBlockMarkdownAdapterMatchers = [
|
export const defaultBlockMarkdownAdapterMatchers = [
|
||||||
|
RootBlockMarkdownAdapterExtension,
|
||||||
|
DocNoteBlockMarkdownAdapterExtension,
|
||||||
EmbedFigmaMarkdownAdapterExtension,
|
EmbedFigmaMarkdownAdapterExtension,
|
||||||
EmbedGithubMarkdownAdapterExtension,
|
EmbedGithubMarkdownAdapterExtension,
|
||||||
EmbedLinkedDocMarkdownAdapterExtension,
|
EmbedLinkedDocMarkdownAdapterExtension,
|
||||||
@@ -32,5 +35,4 @@ export const defaultBlockMarkdownAdapterMatchers = [
|
|||||||
DividerBlockMarkdownAdapterExtension,
|
DividerBlockMarkdownAdapterExtension,
|
||||||
ImageBlockMarkdownAdapterExtension,
|
ImageBlockMarkdownAdapterExtension,
|
||||||
LatexBlockMarkdownAdapterExtension,
|
LatexBlockMarkdownAdapterExtension,
|
||||||
RootBlockMarkdownAdapterExtension,
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const escapedSnapshotAttributes = new Set([
|
|||||||
'"background"',
|
'"background"',
|
||||||
'"LinkedPage"',
|
'"LinkedPage"',
|
||||||
'"elementIds"',
|
'"elementIds"',
|
||||||
|
'"attachment"',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function nanoidReplacementString(snapshotString: string) {
|
function nanoidReplacementString(snapshotString: string) {
|
||||||
|
|||||||
@@ -3569,6 +3569,7 @@ __metadata:
|
|||||||
"@lit/context": "npm:^1.1.2"
|
"@lit/context": "npm:^1.1.2"
|
||||||
"@preact/signals-core": "npm:^1.8.0"
|
"@preact/signals-core": "npm:^1.8.0"
|
||||||
"@toeverything/theme": "npm:^1.1.7"
|
"@toeverything/theme": "npm:^1.1.7"
|
||||||
|
"@types/mdast": "npm:^4.0.4"
|
||||||
lit: "npm:^3.2.0"
|
lit: "npm:^3.2.0"
|
||||||
minimatch: "npm:^10.0.1"
|
minimatch: "npm:^10.0.1"
|
||||||
zod: "npm:^3.23.8"
|
zod: "npm:^3.23.8"
|
||||||
@@ -3716,10 +3717,12 @@ __metadata:
|
|||||||
lodash.clonedeep: "npm:^4.5.0"
|
lodash.clonedeep: "npm:^4.5.0"
|
||||||
lodash.mergewith: "npm:^4.6.2"
|
lodash.mergewith: "npm:^4.6.2"
|
||||||
mdast-util-gfm-autolink-literal: "npm:^2.0.1"
|
mdast-util-gfm-autolink-literal: "npm:^2.0.1"
|
||||||
|
mdast-util-gfm-footnote: "npm:^2.0.0"
|
||||||
mdast-util-gfm-strikethrough: "npm:^2.0.0"
|
mdast-util-gfm-strikethrough: "npm:^2.0.0"
|
||||||
mdast-util-gfm-table: "npm:^2.0.0"
|
mdast-util-gfm-table: "npm:^2.0.0"
|
||||||
mdast-util-gfm-task-list-item: "npm:^2.0.0"
|
mdast-util-gfm-task-list-item: "npm:^2.0.0"
|
||||||
micromark-extension-gfm-autolink-literal: "npm:^2.1.0"
|
micromark-extension-gfm-autolink-literal: "npm:^2.1.0"
|
||||||
|
micromark-extension-gfm-footnote: "npm:^2.1.0"
|
||||||
micromark-extension-gfm-strikethrough: "npm:^2.1.0"
|
micromark-extension-gfm-strikethrough: "npm:^2.1.0"
|
||||||
micromark-extension-gfm-table: "npm:^2.1.0"
|
micromark-extension-gfm-table: "npm:^2.1.0"
|
||||||
micromark-extension-gfm-task-list-item: "npm:^2.1.0"
|
micromark-extension-gfm-task-list-item: "npm:^2.1.0"
|
||||||
@@ -26903,7 +26906,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"micromark-extension-gfm-footnote@npm:^2.0.0":
|
"micromark-extension-gfm-footnote@npm:^2.0.0, micromark-extension-gfm-footnote@npm:^2.1.0":
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
resolution: "micromark-extension-gfm-footnote@npm:2.1.0"
|
resolution: "micromark-extension-gfm-footnote@npm:2.1.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user