refactor(editor): surface markdown adapter extensionalize (#9560)

[BS-2212](https://linear.app/affine-design/issue/BS-2212/adapter-extension化修复)
This commit is contained in:
donteatfriedrice
2025-01-07 03:14:08 +00:00
parent 69e9aa087e
commit 52f2e8d5d5
11 changed files with 156 additions and 117 deletions

View File

@@ -1,3 +1,4 @@
import { elementToMarkdownAdapterMatchers } from './markdown/element-adapter/elements/index.js';
import { import {
EdgelessSurfaceBlockMarkdownAdapterExtension, EdgelessSurfaceBlockMarkdownAdapterExtension,
SurfaceBlockMarkdownAdapterExtension, SurfaceBlockMarkdownAdapterExtension,
@@ -10,12 +11,14 @@ import {
export const SurfaceBlockAdapterExtensions = [ export const SurfaceBlockAdapterExtensions = [
...elementToPlainTextAdapterMatchers, ...elementToPlainTextAdapterMatchers,
...elementToMarkdownAdapterMatchers,
SurfaceBlockPlainTextAdapterExtension, SurfaceBlockPlainTextAdapterExtension,
SurfaceBlockMarkdownAdapterExtension, SurfaceBlockMarkdownAdapterExtension,
]; ];
export const EdgelessSurfaceBlockAdapterExtensions = [ export const EdgelessSurfaceBlockAdapterExtensions = [
...elementToPlainTextAdapterMatchers, ...elementToPlainTextAdapterMatchers,
...elementToMarkdownAdapterMatchers,
EdgelessSurfaceBlockPlainTextAdapterExtension, EdgelessSurfaceBlockPlainTextAdapterExtension,
EdgelessSurfaceBlockMarkdownAdapterExtension, EdgelessSurfaceBlockMarkdownAdapterExtension,
]; ];

View File

@@ -1,19 +1,18 @@
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js'; import { ElementToMarkdownAdapterExtension } from '../type.js';
export const brushToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher = export const brushToMarkdownAdapterMatcher = ElementToMarkdownAdapterExtension({
{ name: 'brush',
name: 'brush', match: elementModel => elementModel.type === 'brush',
match: elementModel => elementModel.type === 'brush', toAST: () => {
toAST: () => { const content = `Brush Stroke`;
const content = `Brush Stroke`; return {
return { type: 'paragraph',
type: 'paragraph', children: [
children: [ {
{ type: 'text',
type: 'text', value: content,
value: content, },
}, ],
], };
}; },
}, });
};

View File

@@ -1,8 +1,8 @@
import { getConnectorText } from '../../../utils/text.js'; import { getConnectorText } from '../../../utils/text.js';
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js'; import { ElementToMarkdownAdapterExtension } from '../type.js';
export const connectorToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher = export const connectorToMarkdownAdapterMatcher =
{ ElementToMarkdownAdapterExtension({
name: 'connector', name: 'connector',
match: elementModel => elementModel.type === 'connector', match: elementModel => elementModel.type === 'connector',
toAST: elementModel => { toAST: elementModel => {
@@ -22,4 +22,4 @@ export const connectorToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMat
], ],
}; };
}, },
}; });

View File

@@ -1,25 +1,24 @@
import { getGroupTitle } from '../../../utils/text.js'; import { getGroupTitle } from '../../../utils/text.js';
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js'; import { ElementToMarkdownAdapterExtension } from '../type.js';
export const groupToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher = export const groupToMarkdownAdapterMatcher = ElementToMarkdownAdapterExtension({
{ name: 'group',
name: 'group', match: elementModel => elementModel.type === 'group',
match: elementModel => elementModel.type === 'group', toAST: elementModel => {
toAST: elementModel => { const title = getGroupTitle(elementModel);
const title = getGroupTitle(elementModel); if (!title) {
if (!title) { return null;
return null; }
}
const content = `Group, with title "${title}"`; const content = `Group, with title "${title}"`;
return { return {
type: 'paragraph', type: 'paragraph',
children: [ children: [
{ {
type: 'text', type: 'text',
value: content, value: content,
}, },
], ],
}; };
}, },
}; });

View File

@@ -1,10 +1,10 @@
import type { MindMapTreeNode } from '../../../types/mindmap.js'; import type { MindMapTreeNode } from '../../../types/mindmap.js';
import { buildMindMapTree } from '../../../utils/mindmap.js'; import { buildMindMapTree } from '../../../utils/mindmap.js';
import { getShapeText } from '../../../utils/text.js'; import { getShapeText } from '../../../utils/text.js';
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js'; import { ElementToMarkdownAdapterExtension } from '../type.js';
export const mindmapToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher = export const mindmapToMarkdownAdapterMatcher =
{ ElementToMarkdownAdapterExtension({
name: 'mindmap', name: 'mindmap',
match: elementModel => elementModel.type === 'mindmap', match: elementModel => elementModel.type === 'mindmap',
toAST: (elementModel, context) => { toAST: (elementModel, context) => {
@@ -64,4 +64,4 @@ export const mindmapToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatch
return null; return null;
}, },
}; });

View File

@@ -1,46 +1,45 @@
import type { MindMapTreeNode } from '../../../types/mindmap.js'; import type { MindMapTreeNode } from '../../../types/mindmap.js';
import { getShapeText, getShapeType } from '../../../utils/text.js'; import { getShapeText, getShapeType } from '../../../utils/text.js';
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js'; import { ElementToMarkdownAdapterExtension } from '../type.js';
export const shapeToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher = export const shapeToMarkdownAdapterMatcher = ElementToMarkdownAdapterExtension({
{ name: 'shape',
name: 'shape', match: elementModel => elementModel.type === 'shape',
match: elementModel => elementModel.type === 'shape', toAST: (elementModel, context) => {
toAST: (elementModel, context) => { let content = '';
let content = ''; const { walkerContext } = context;
const { walkerContext } = context; const mindMapNodeMaps = walkerContext.getGlobalContext(
const mindMapNodeMaps = walkerContext.getGlobalContext( 'surface:mindMap:nodeMapArray'
'surface:mindMap:nodeMapArray' ) as Array<Map<string, MindMapTreeNode>>;
) as Array<Map<string, MindMapTreeNode>>; if (mindMapNodeMaps && mindMapNodeMaps.length > 0) {
if (mindMapNodeMaps && mindMapNodeMaps.length > 0) { // Check if the elementModel is a mindMap node
// Check if the elementModel is a mindMap node // If it is, we should return { content: '' } directly
// If it is, we should return { content: '' } directly // And get the content when we handle the whole mindMap
// And get the content when we handle the whole mindMap const isMindMapNode = mindMapNodeMaps.some(nodeMap =>
const isMindMapNode = mindMapNodeMaps.some(nodeMap => nodeMap.has(elementModel.id as string)
nodeMap.has(elementModel.id as string) );
); if (isMindMapNode) {
if (isMindMapNode) {
return null;
}
}
// If it is not, we should return the text and shapeType
const text = getShapeText(elementModel);
const type = getShapeType(elementModel);
if (!text && !type) {
return null; return null;
} }
}
const shapeType = type.charAt(0).toUpperCase() + type.slice(1); // If it is not, we should return the text and shapeType
content = `${shapeType}, with text label "${text}"`; const text = getShapeText(elementModel);
return { const type = getShapeType(elementModel);
type: 'paragraph', if (!text && !type) {
children: [ return null;
{ }
type: 'text',
value: content, const shapeType = type.charAt(0).toUpperCase() + type.slice(1);
}, content = `${shapeType}, with text label "${text}"`;
], return {
}; type: 'paragraph',
}, children: [
}; {
type: 'text',
value: content,
},
],
};
},
});

View File

@@ -1,24 +1,23 @@
import { getTextElementText } from '../../../utils/text.js'; import { getTextElementText } from '../../../utils/text.js';
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js'; import { ElementToMarkdownAdapterExtension } from '../type.js';
export const textToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher = export const textToMarkdownAdapterMatcher = ElementToMarkdownAdapterExtension({
{ name: 'text',
name: 'text', match: elementModel => elementModel.type === 'text',
match: elementModel => elementModel.type === 'text', toAST: elementModel => {
toAST: elementModel => { const content = getTextElementText(elementModel);
const content = getTextElementText(elementModel); if (!content) {
if (!content) { return null;
return null; }
}
return { return {
type: 'paragraph', type: 'paragraph',
children: [ children: [
{ {
type: 'text', type: 'text',
value: content, value: content,
}, },
], ],
}; };
}, },
}; });

View File

@@ -4,15 +4,14 @@ import {
ElementModelAdapter, ElementModelAdapter,
type ElementModelAdapterContext, type ElementModelAdapterContext,
} from '../../type.js'; } from '../../type.js';
import { elementToMarkdownAdapterMatchers } from './elements/index.js'; import type { ElementToMarkdownAdapterMatcher } from './type.js';
import type { ElementModelToMarkdownAdapterMatcher } from './type.js';
export class MarkdownElementModelAdapter extends ElementModelAdapter< export class MarkdownElementModelAdapter extends ElementModelAdapter<
MarkdownAST, MarkdownAST,
MarkdownAST MarkdownAST
> { > {
constructor( constructor(
readonly elementModelMatchers: ElementModelToMarkdownAdapterMatcher[] = elementToMarkdownAdapterMatchers readonly elementModelMatchers: ElementToMarkdownAdapterMatcher[]
) { ) {
super(); super();
} }

View File

@@ -1,6 +1,29 @@
import type { ExtensionType } from '@blocksuite/affine/store';
import type { MarkdownAST } from '@blocksuite/affine-shared/adapters'; import type { MarkdownAST } from '@blocksuite/affine-shared/adapters';
import {
createIdentifier,
type ServiceIdentifier,
} from '@blocksuite/global/di';
import type { ElementModelMatcher } from '../../type.js'; import type { ElementModelMatcher } from '../../type.js';
export type ElementModelToMarkdownAdapterMatcher = export type ElementToMarkdownAdapterMatcher = ElementModelMatcher<MarkdownAST>;
ElementModelMatcher<MarkdownAST>;
export const ElementToMarkdownAdapterMatcherIdentifier =
createIdentifier<ElementToMarkdownAdapterMatcher>(
'elementToMarkdownAdapterMatcher'
);
export function ElementToMarkdownAdapterExtension(
matcher: ElementToMarkdownAdapterMatcher
): ExtensionType & {
identifier: ServiceIdentifier<ElementToMarkdownAdapterMatcher>;
} {
const identifier = ElementToMarkdownAdapterMatcherIdentifier(matcher.name);
return {
setup: di => {
di.addImpl(identifier, () => matcher);
},
identifier,
};
}

View File

@@ -5,6 +5,7 @@ import {
import { getMindMapNodeMap } from '../utils/mindmap.js'; import { getMindMapNodeMap } from '../utils/mindmap.js';
import { MarkdownElementModelAdapter } from './element-adapter/index.js'; import { MarkdownElementModelAdapter } from './element-adapter/index.js';
import { ElementToMarkdownAdapterMatcherIdentifier } from './element-adapter/type.js';
export const surfaceBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = { export const surfaceBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
flavour: 'affine:surface', flavour: 'affine:surface',
@@ -29,8 +30,18 @@ export const edgelessSurfaceBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMat
toBlockSnapshot: {}, toBlockSnapshot: {},
fromBlockSnapshot: { fromBlockSnapshot: {
enter: (o, context) => { enter: (o, context) => {
const { walkerContext } = context; const { walkerContext, provider } = context;
const markdownElementModelAdapter = new MarkdownElementModelAdapter(); if (!provider) {
context.walkerContext.skipAllChildren();
return;
}
const elementModelMatchers = Array.from(
provider.getAll(ElementToMarkdownAdapterMatcherIdentifier).values()
);
const markdownElementModelAdapter = new MarkdownElementModelAdapter(
elementModelMatchers
);
if ('elements' in o.node.props) { if ('elements' in o.node.props) {
const elements = o.node.props.elements as Record< const elements = o.node.props.elements as Record<
string, string,

View File

@@ -72,6 +72,7 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
configs: this.configs, configs: this.configs,
job: this.job, job: this.job,
deltaConverter: this.deltaConverter, deltaConverter: this.deltaConverter,
provider: this.provider,
textBuffer: { content: '' }, textBuffer: { content: '' },
assets, assets,
}; };
@@ -92,6 +93,7 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
configs: this.configs, configs: this.configs,
job: this.job, job: this.job,
deltaConverter: this.deltaConverter, deltaConverter: this.deltaConverter,
provider: this.provider,
textBuffer: { content: '' }, textBuffer: { content: '' },
assets, assets,
}; };
@@ -126,6 +128,7 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
configs: this.configs, configs: this.configs,
job: this.job, job: this.job,
deltaConverter: this.deltaConverter, deltaConverter: this.deltaConverter,
provider: this.provider,
textBuffer: { content: '' }, textBuffer: { content: '' },
assets, assets,
updateAssetIds: (assetsId: string) => { updateAssetIds: (assetsId: string) => {
@@ -149,6 +152,7 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
configs: this.configs, configs: this.configs,
job: this.job, job: this.job,
deltaConverter: this.deltaConverter, deltaConverter: this.deltaConverter,
provider: this.provider,
textBuffer: { content: '' }, textBuffer: { content: '' },
assets, assets,
}; };
@@ -166,7 +170,10 @@ export class MarkdownAdapter extends BaseAdapter<Markdown> {
readonly blockMatchers: BlockMarkdownAdapterMatcher[]; readonly blockMatchers: BlockMarkdownAdapterMatcher[];
constructor(job: Job, provider: ServiceProvider) { constructor(
job: Job,
readonly provider: ServiceProvider
) {
super(job); super(job);
const blockMatchers = Array.from( const blockMatchers = Array.from(
provider.getAll(BlockMarkdownAdapterMatcherIdentifier).values() provider.getAll(BlockMarkdownAdapterMatcherIdentifier).values()