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 {
EdgelessSurfaceBlockMarkdownAdapterExtension,
SurfaceBlockMarkdownAdapterExtension,
@@ -10,12 +11,14 @@ import {
export const SurfaceBlockAdapterExtensions = [
...elementToPlainTextAdapterMatchers,
...elementToMarkdownAdapterMatchers,
SurfaceBlockPlainTextAdapterExtension,
SurfaceBlockMarkdownAdapterExtension,
];
export const EdgelessSurfaceBlockAdapterExtensions = [
...elementToPlainTextAdapterMatchers,
...elementToMarkdownAdapterMatchers,
EdgelessSurfaceBlockPlainTextAdapterExtension,
EdgelessSurfaceBlockMarkdownAdapterExtension,
];

View File

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

View File

@@ -1,8 +1,8 @@
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',
match: elementModel => elementModel.type === 'connector',
toAST: elementModel => {
@@ -22,4 +22,4 @@ export const connectorToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMat
],
};
},
};
});

View File

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

View File

@@ -1,10 +1,10 @@
import type { MindMapTreeNode } from '../../../types/mindmap.js';
import { buildMindMapTree } from '../../../utils/mindmap.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',
match: elementModel => elementModel.type === 'mindmap',
toAST: (elementModel, context) => {
@@ -64,4 +64,4 @@ export const mindmapToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatch
return null;
},
};
});

View File

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

View File

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

View File

@@ -1,6 +1,29 @@
import type { ExtensionType } from '@blocksuite/affine/store';
import type { MarkdownAST } from '@blocksuite/affine-shared/adapters';
import {
createIdentifier,
type ServiceIdentifier,
} from '@blocksuite/global/di';
import type { ElementModelMatcher } from '../../type.js';
export type ElementModelToMarkdownAdapterMatcher =
ElementModelMatcher<MarkdownAST>;
export type ElementToMarkdownAdapterMatcher = 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 { MarkdownElementModelAdapter } from './element-adapter/index.js';
import { ElementToMarkdownAdapterMatcherIdentifier } from './element-adapter/type.js';
export const surfaceBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
flavour: 'affine:surface',
@@ -29,8 +30,18 @@ export const edgelessSurfaceBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMat
toBlockSnapshot: {},
fromBlockSnapshot: {
enter: (o, context) => {
const { walkerContext } = context;
const markdownElementModelAdapter = new MarkdownElementModelAdapter();
const { walkerContext, provider } = context;
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) {
const elements = o.node.props.elements as Record<
string,