mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 14:27:02 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
20
blocksuite/affine/block-surface/src/adapters/extension.ts
Normal file
20
blocksuite/affine/block-surface/src/adapters/extension.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { SurfaceBlockHtmlAdapterExtension } from './html-adapter/html.js';
|
||||
import {
|
||||
EdgelessSurfaceBlockMarkdownAdapterExtension,
|
||||
SurfaceBlockMarkdownAdapterExtension,
|
||||
} from './markdown/markdown.js';
|
||||
import {
|
||||
EdgelessSurfaceBlockPlainTextAdapterExtension,
|
||||
SurfaceBlockPlainTextAdapterExtension,
|
||||
} from './plain-text/plain-text.js';
|
||||
|
||||
export const SurfaceBlockAdapterExtensions = [
|
||||
SurfaceBlockPlainTextAdapterExtension,
|
||||
SurfaceBlockMarkdownAdapterExtension,
|
||||
SurfaceBlockHtmlAdapterExtension,
|
||||
];
|
||||
|
||||
export const EdgelessSurfaceBlockAdapterExtensions = [
|
||||
EdgelessSurfaceBlockPlainTextAdapterExtension,
|
||||
EdgelessSurfaceBlockMarkdownAdapterExtension,
|
||||
];
|
||||
@@ -0,0 +1,20 @@
|
||||
import {
|
||||
BlockHtmlAdapterExtension,
|
||||
type BlockHtmlAdapterMatcher,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
export const surfaceBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||
flavour: 'affine:surface',
|
||||
toMatch: () => false,
|
||||
fromMatch: o => o.node.flavour === 'affine:surface',
|
||||
toBlockSnapshot: {},
|
||||
fromBlockSnapshot: {
|
||||
enter: (_, context) => {
|
||||
context.walkerContext.skipAllChildren();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const SurfaceBlockHtmlAdapterExtension = BlockHtmlAdapterExtension(
|
||||
surfaceBlockHtmlAdapterMatcher
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ElementModelToMarkdownAdapterMatcher } 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,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { getConnectorText } from '../../../utils/text.js';
|
||||
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js';
|
||||
|
||||
export const connectorToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher =
|
||||
{
|
||||
name: 'connector',
|
||||
match: elementModel => elementModel.type === 'connector',
|
||||
toAST: elementModel => {
|
||||
const text = getConnectorText(elementModel);
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = `Connector, with text label "${text}"`;
|
||||
return {
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
value: content,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { getGroupTitle } from '../../../utils/text.js';
|
||||
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js';
|
||||
|
||||
export const groupToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher =
|
||||
{
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { brushToMarkdownAdapterMatcher } from './brush.js';
|
||||
import { connectorToMarkdownAdapterMatcher } from './connector.js';
|
||||
import { groupToMarkdownAdapterMatcher } from './group.js';
|
||||
import { mindmapToMarkdownAdapterMatcher } from './mindmap.js';
|
||||
import { shapeToMarkdownAdapterMatcher } from './shape.js';
|
||||
import { textToMarkdownAdapterMatcher } from './text.js';
|
||||
|
||||
export const elementToMarkdownAdapterMatchers = [
|
||||
groupToMarkdownAdapterMatcher,
|
||||
shapeToMarkdownAdapterMatcher,
|
||||
connectorToMarkdownAdapterMatcher,
|
||||
brushToMarkdownAdapterMatcher,
|
||||
textToMarkdownAdapterMatcher,
|
||||
mindmapToMarkdownAdapterMatcher,
|
||||
];
|
||||
@@ -0,0 +1,67 @@
|
||||
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';
|
||||
|
||||
export const mindmapToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher =
|
||||
{
|
||||
name: 'mindmap',
|
||||
match: elementModel => elementModel.type === 'mindmap',
|
||||
toAST: (elementModel, context) => {
|
||||
if (elementModel.type !== 'mindmap') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mindMapTree = buildMindMapTree(elementModel);
|
||||
if (!mindMapTree) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { walkerContext, elements } = context;
|
||||
const traverseMindMapTree = (node: MindMapTreeNode) => {
|
||||
const shapeElement = elements[node.id as string];
|
||||
const shapeText = getShapeText(shapeElement);
|
||||
walkerContext
|
||||
.openNode(
|
||||
{
|
||||
type: 'listItem',
|
||||
spread: false,
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
)
|
||||
.openNode(
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
type: 'text',
|
||||
value: shapeText,
|
||||
},
|
||||
],
|
||||
},
|
||||
'children'
|
||||
)
|
||||
.closeNode();
|
||||
node.children.forEach(child => {
|
||||
traverseMindMapTree(child);
|
||||
walkerContext.closeNode();
|
||||
});
|
||||
};
|
||||
|
||||
// First create a list node for the mind map tree
|
||||
walkerContext.openNode(
|
||||
{
|
||||
type: 'list',
|
||||
ordered: false,
|
||||
spread: false,
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
);
|
||||
traverseMindMapTree(mindMapTree);
|
||||
walkerContext.closeNode().closeNode();
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { MindMapTreeNode } from '../../../types/mindmap.js';
|
||||
import { getShapeText, getShapeType } from '../../../utils/text.js';
|
||||
import type { ElementModelToMarkdownAdapterMatcher } 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) {
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { getTextElementText } from '../../../utils/text.js';
|
||||
import type { ElementModelToMarkdownAdapterMatcher } from '../type.js';
|
||||
|
||||
export const textToMarkdownAdapterMatcher: ElementModelToMarkdownAdapterMatcher =
|
||||
{
|
||||
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,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { MarkdownAST } from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
import {
|
||||
ElementModelAdapter,
|
||||
type ElementModelAdapterContext,
|
||||
} from '../../type.js';
|
||||
import { elementToMarkdownAdapterMatchers } from './elements/index.js';
|
||||
import type { ElementModelToMarkdownAdapterMatcher } from './type.js';
|
||||
|
||||
export class MarkdownElementModelAdapter extends ElementModelAdapter<
|
||||
MarkdownAST,
|
||||
MarkdownAST
|
||||
> {
|
||||
constructor(
|
||||
readonly elementModelMatchers: ElementModelToMarkdownAdapterMatcher[] = elementToMarkdownAdapterMatchers
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
fromElementModel(
|
||||
element: Record<string, unknown>,
|
||||
context: ElementModelAdapterContext<MarkdownAST>
|
||||
) {
|
||||
const markdownAST: MarkdownAST | null = null;
|
||||
for (const matcher of this.elementModelMatchers) {
|
||||
if (matcher.match(element)) {
|
||||
return matcher.toAST(element, context);
|
||||
}
|
||||
}
|
||||
return markdownAST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { MarkdownAST } from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
import type { ElementModelMatcher } from '../../type.js';
|
||||
|
||||
export type ElementModelToMarkdownAdapterMatcher =
|
||||
ElementModelMatcher<MarkdownAST>;
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
import { getMindMapNodeMap } from '../utils/mindmap.js';
|
||||
import { MarkdownElementModelAdapter } from './element-adapter/index.js';
|
||||
|
||||
export const surfaceBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
||||
flavour: 'affine:surface',
|
||||
toMatch: () => false,
|
||||
fromMatch: o => o.node.flavour === 'affine:surface',
|
||||
toBlockSnapshot: {},
|
||||
fromBlockSnapshot: {
|
||||
enter: (_, context) => {
|
||||
context.walkerContext.skipAllChildren();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const SurfaceBlockMarkdownAdapterExtension =
|
||||
BlockMarkdownAdapterExtension(surfaceBlockMarkdownAdapterMatcher);
|
||||
|
||||
export const edgelessSurfaceBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher =
|
||||
{
|
||||
flavour: 'affine:surface',
|
||||
toMatch: () => false,
|
||||
fromMatch: o => o.node.flavour === 'affine:surface',
|
||||
toBlockSnapshot: {},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
const markdownElementModelAdapter = new MarkdownElementModelAdapter();
|
||||
if ('elements' in o.node.props) {
|
||||
const elements = o.node.props.elements as Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
>;
|
||||
// Get all the node maps of mindMap elements
|
||||
const mindMapArray = Object.entries(elements)
|
||||
.filter(([_, element]) => element.type === 'mindmap')
|
||||
.map(([_, element]) => getMindMapNodeMap(element));
|
||||
walkerContext.setGlobalContext(
|
||||
'surface:mindMap:nodeMapArray',
|
||||
mindMapArray
|
||||
);
|
||||
|
||||
Object.entries(
|
||||
o.node.props.elements as Record<string, Record<string, unknown>>
|
||||
).forEach(([_, element]) => {
|
||||
const markdownAST = markdownElementModelAdapter.fromElementModel(
|
||||
element,
|
||||
{ walkerContext, elements }
|
||||
);
|
||||
if (markdownAST) {
|
||||
walkerContext.openNode(markdownAST, 'children').closeNode();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const EdgelessSurfaceBlockMarkdownAdapterExtension =
|
||||
BlockMarkdownAdapterExtension(edgelessSurfaceBlockMarkdownAdapterMatcher);
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { ElementModelToPlainTextAdapterMatcher } from '../type.js';
|
||||
|
||||
export const brushToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
|
||||
{
|
||||
name: 'brush',
|
||||
match: elementModel => elementModel.type === 'brush',
|
||||
toAST: () => {
|
||||
const content = `Brush Stroke`;
|
||||
return { content };
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getConnectorText } from '../../../utils/text.js';
|
||||
import type { ElementModelToPlainTextAdapterMatcher } from '../type.js';
|
||||
|
||||
export const connectorToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
|
||||
{
|
||||
name: 'connector',
|
||||
match: elementModel => elementModel.type === 'connector',
|
||||
toAST: elementModel => {
|
||||
const text = getConnectorText(elementModel);
|
||||
const content = `Connector, with text label "${text}"`;
|
||||
return { content };
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getGroupTitle } from '../../../utils/text.js';
|
||||
import type { ElementModelToPlainTextAdapterMatcher } from '../type.js';
|
||||
|
||||
export const groupToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
|
||||
{
|
||||
name: 'group',
|
||||
match: elementModel => elementModel.type === 'group',
|
||||
toAST: elementModel => {
|
||||
const title = getGroupTitle(elementModel);
|
||||
const content = `Group, with title "${title}"`;
|
||||
return { content };
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { brushToPlainTextAdapterMatcher } from './brush.js';
|
||||
import { connectorToPlainTextAdapterMatcher } from './connector.js';
|
||||
import { groupToPlainTextAdapterMatcher } from './group.js';
|
||||
import { mindmapToPlainTextAdapterMatcher } from './mindmap.js';
|
||||
import { shapeToPlainTextAdapterMatcher } from './shape.js';
|
||||
import { textToPlainTextAdapterMatcher } from './text.js';
|
||||
|
||||
export const elementModelToPlainTextAdapterMatchers = [
|
||||
groupToPlainTextAdapterMatcher,
|
||||
shapeToPlainTextAdapterMatcher,
|
||||
connectorToPlainTextAdapterMatcher,
|
||||
brushToPlainTextAdapterMatcher,
|
||||
textToPlainTextAdapterMatcher,
|
||||
mindmapToPlainTextAdapterMatcher,
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
import { getMindMapTreeText } from '../../../utils/text.js';
|
||||
import type { ElementModelToPlainTextAdapterMatcher } from '../type.js';
|
||||
|
||||
export const mindmapToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
|
||||
{
|
||||
name: 'mindmap',
|
||||
match: elementModel => elementModel.type === 'mindmap',
|
||||
toAST: (elementModel, context) => {
|
||||
const mindMapContent = getMindMapTreeText(elementModel, context.elements);
|
||||
return { content: mindMapContent };
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { MindMapTreeNode } from '../../../types/mindmap.js';
|
||||
import { getShapeText, getShapeType } from '../../../utils/text.js';
|
||||
import type { ElementModelToPlainTextAdapterMatcher } from '../type.js';
|
||||
|
||||
export const shapeToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
|
||||
{
|
||||
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 { content };
|
||||
}
|
||||
}
|
||||
|
||||
// If it is not, we should return the text and shapeType
|
||||
const text = getShapeText(elementModel);
|
||||
const type = getShapeType(elementModel);
|
||||
const shapeType = type.charAt(0).toUpperCase() + type.slice(1);
|
||||
content = `${shapeType}, with text label "${text}"`;
|
||||
return { content };
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { getTextElementText } from '../../../utils/text.js';
|
||||
import type { ElementModelToPlainTextAdapterMatcher } from '../type.js';
|
||||
|
||||
export const textToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
|
||||
{
|
||||
name: 'text',
|
||||
match: elementModel => elementModel.type === 'text',
|
||||
toAST: elementModel => {
|
||||
const content = getTextElementText(elementModel);
|
||||
return { content };
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { TextBuffer } from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
import {
|
||||
ElementModelAdapter,
|
||||
type ElementModelAdapterContext,
|
||||
} from '../../type.js';
|
||||
import { elementModelToPlainTextAdapterMatchers } from './elements/index.js';
|
||||
import type { ElementModelToPlainTextAdapterMatcher } from './type.js';
|
||||
|
||||
export class PlainTextElementModelAdapter extends ElementModelAdapter<
|
||||
string,
|
||||
TextBuffer
|
||||
> {
|
||||
constructor(
|
||||
readonly elementModelMatchers: ElementModelToPlainTextAdapterMatcher[] = elementModelToPlainTextAdapterMatchers
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
fromElementModel(
|
||||
element: Record<string, unknown>,
|
||||
context: ElementModelAdapterContext<TextBuffer>
|
||||
) {
|
||||
for (const matcher of this.elementModelMatchers) {
|
||||
if (matcher.match(element)) {
|
||||
return matcher.toAST(element, context)?.content ?? '';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { TextBuffer } from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
import type { ElementModelMatcher } from '../../type.js';
|
||||
|
||||
export type ElementModelToPlainTextAdapterMatcher =
|
||||
ElementModelMatcher<TextBuffer>;
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
BlockPlainTextAdapterExtension,
|
||||
type BlockPlainTextAdapterMatcher,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
|
||||
import { getMindMapNodeMap } from '../utils/mindmap.js';
|
||||
import { PlainTextElementModelAdapter } from './element-adapter/index.js';
|
||||
|
||||
export const surfaceBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
|
||||
{
|
||||
flavour: 'affine:surface',
|
||||
toMatch: () => false,
|
||||
fromMatch: o => o.node.flavour === 'affine:surface',
|
||||
toBlockSnapshot: {},
|
||||
fromBlockSnapshot: {
|
||||
enter: (_, context) => {
|
||||
context.walkerContext.skipAllChildren();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const SurfaceBlockPlainTextAdapterExtension =
|
||||
BlockPlainTextAdapterExtension(surfaceBlockPlainTextAdapterMatcher);
|
||||
|
||||
export const edgelessSurfaceBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
|
||||
{
|
||||
flavour: 'affine:surface',
|
||||
toMatch: () => false,
|
||||
fromMatch: o => o.node.flavour === 'affine:surface',
|
||||
toBlockSnapshot: {},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
const plainTextElementModelAdapter = new PlainTextElementModelAdapter();
|
||||
if ('elements' in o.node.props) {
|
||||
const elements = o.node.props.elements as Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
>;
|
||||
// Get all the node maps of mindMap elements
|
||||
const mindMapArray = Object.entries(elements)
|
||||
.filter(([_, element]) => element.type === 'mindmap')
|
||||
.map(([_, element]) => getMindMapNodeMap(element));
|
||||
walkerContext.setGlobalContext(
|
||||
'surface:mindMap:nodeMapArray',
|
||||
mindMapArray
|
||||
);
|
||||
|
||||
Object.entries(
|
||||
o.node.props.elements as Record<string, Record<string, unknown>>
|
||||
).forEach(([_, element]) => {
|
||||
const plainText = plainTextElementModelAdapter.fromElementModel(
|
||||
element,
|
||||
{ walkerContext, elements }
|
||||
);
|
||||
if (plainText) {
|
||||
context.textBuffer.content += plainText + '\n';
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const EdgelessSurfaceBlockPlainTextAdapterExtension =
|
||||
BlockPlainTextAdapterExtension(edgelessSurfaceBlockPlainTextAdapterMatcher);
|
||||
30
blocksuite/affine/block-surface/src/adapters/type.ts
Normal file
30
blocksuite/affine/block-surface/src/adapters/type.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { ASTWalkerContext } from '@blocksuite/store';
|
||||
|
||||
import type { ElementModelMap } from '../element-model/index.js';
|
||||
|
||||
export type ElementModelAdapterContext<TNode extends object = never> = {
|
||||
walkerContext: ASTWalkerContext<TNode>;
|
||||
elements: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
|
||||
export type ElementModelMatcher<TNode extends object = never> = {
|
||||
name: keyof ElementModelMap;
|
||||
match: (element: Record<string, unknown>) => boolean;
|
||||
toAST: (
|
||||
element: Record<string, unknown>,
|
||||
context: ElementModelAdapterContext<TNode>
|
||||
) => TNode | null;
|
||||
};
|
||||
|
||||
export abstract class ElementModelAdapter<
|
||||
AST = unknown,
|
||||
TNode extends object = never,
|
||||
> {
|
||||
/**
|
||||
* Convert element model to AST format
|
||||
*/
|
||||
abstract fromElementModel(
|
||||
element: Record<string, unknown>,
|
||||
context: ElementModelAdapterContext<TNode>
|
||||
): AST | null;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export interface MindMapTreeNode {
|
||||
id: string;
|
||||
index: string;
|
||||
children: MindMapTreeNode[];
|
||||
}
|
||||
|
||||
export interface MindMapNode {
|
||||
index: string;
|
||||
parent?: string;
|
||||
}
|
||||
|
||||
export type MindMapJson = Record<string, MindMapNode>;
|
||||
|
||||
export interface MindMapElement {
|
||||
index: string;
|
||||
seed: number;
|
||||
children: {
|
||||
'affine:surface:ymap': boolean;
|
||||
json: MindMapJson;
|
||||
};
|
||||
layoutType: number;
|
||||
style: number;
|
||||
type: 'mindmap';
|
||||
id: string;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type {
|
||||
MindMapElement,
|
||||
MindMapJson,
|
||||
MindMapTreeNode,
|
||||
} from '../types/mindmap.js';
|
||||
|
||||
function isMindMapElement(element: unknown): element is MindMapElement {
|
||||
return (
|
||||
typeof element === 'object' &&
|
||||
element !== null &&
|
||||
'type' in element &&
|
||||
(element as MindMapElement).type === 'mindmap' &&
|
||||
'children' in element &&
|
||||
typeof (element as MindMapElement).children === 'object' &&
|
||||
'json' in (element as MindMapElement).children
|
||||
);
|
||||
}
|
||||
|
||||
export function getMindMapChildrenJson(
|
||||
element: Record<string, unknown>
|
||||
): MindMapJson | null {
|
||||
if (!isMindMapElement(element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return element.children.json;
|
||||
}
|
||||
|
||||
export function getMindMapNodeMap(
|
||||
element: Record<string, unknown>
|
||||
): Map<string, MindMapTreeNode> {
|
||||
const nodeMap = new Map<string, MindMapTreeNode>();
|
||||
const childrenJson = getMindMapChildrenJson(element);
|
||||
if (!childrenJson) {
|
||||
return nodeMap;
|
||||
}
|
||||
|
||||
for (const [id, info] of Object.entries(childrenJson)) {
|
||||
nodeMap.set(id, {
|
||||
id,
|
||||
index: info.index,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
|
||||
return nodeMap;
|
||||
}
|
||||
|
||||
export function buildMindMapTree(element: Record<string, unknown>) {
|
||||
let root: MindMapTreeNode | null = null;
|
||||
|
||||
// First traverse to get node map
|
||||
const nodeMap = getMindMapNodeMap(element);
|
||||
const childrenJson = getMindMapChildrenJson(element);
|
||||
if (!childrenJson) {
|
||||
return root;
|
||||
}
|
||||
|
||||
// Second traverse to build tree
|
||||
for (const [id, info] of Object.entries(childrenJson)) {
|
||||
const node = nodeMap.get(id)!;
|
||||
|
||||
if (info.parent) {
|
||||
const parentNode = nodeMap.get(info.parent);
|
||||
if (parentNode) {
|
||||
parentNode.children.push(node);
|
||||
}
|
||||
} else {
|
||||
root = node;
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
162
blocksuite/affine/block-surface/src/adapters/utils/text.ts
Normal file
162
blocksuite/affine/block-surface/src/adapters/utils/text.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import type { DeltaInsert } from '@blocksuite/inline/types';
|
||||
|
||||
import type { MindMapTreeNode } from '../types/mindmap.js';
|
||||
import { buildMindMapTree } from './mindmap.js';
|
||||
|
||||
export function getShapeType(elementModel: Record<string, unknown>): string {
|
||||
let shapeType = '';
|
||||
if (elementModel.type !== 'shape') {
|
||||
return shapeType;
|
||||
}
|
||||
|
||||
if (
|
||||
'shapeType' in elementModel &&
|
||||
typeof elementModel.shapeType === 'string'
|
||||
) {
|
||||
shapeType = elementModel.shapeType;
|
||||
}
|
||||
return shapeType;
|
||||
}
|
||||
|
||||
export function getShapeText(elementModel: Record<string, unknown>): string {
|
||||
let text = '';
|
||||
if (elementModel.type !== 'shape') {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (
|
||||
'text' in elementModel &&
|
||||
typeof elementModel.text === 'object' &&
|
||||
elementModel.text
|
||||
) {
|
||||
let delta: DeltaInsert[] = [];
|
||||
if ('delta' in elementModel.text) {
|
||||
delta = elementModel.text.delta as DeltaInsert[];
|
||||
}
|
||||
text = delta.map(d => d.insert).join('');
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export function getConnectorText(
|
||||
elementModel: Record<string, unknown>
|
||||
): string {
|
||||
let text = '';
|
||||
if (elementModel.type !== 'connector') {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (
|
||||
'text' in elementModel &&
|
||||
typeof elementModel.text === 'object' &&
|
||||
elementModel.text
|
||||
) {
|
||||
let delta: DeltaInsert[] = [];
|
||||
if ('delta' in elementModel.text) {
|
||||
delta = elementModel.text.delta as DeltaInsert[];
|
||||
}
|
||||
text = delta.map(d => d.insert).join('');
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export function getGroupTitle(elementModel: Record<string, unknown>): string {
|
||||
let title = '';
|
||||
if (elementModel.type !== 'group') {
|
||||
return title;
|
||||
}
|
||||
|
||||
if (
|
||||
'title' in elementModel &&
|
||||
typeof elementModel.title === 'object' &&
|
||||
elementModel.title
|
||||
) {
|
||||
let delta: DeltaInsert[] = [];
|
||||
if ('delta' in elementModel.title) {
|
||||
delta = elementModel.title.delta as DeltaInsert[];
|
||||
}
|
||||
title = delta.map(d => d.insert).join('');
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
export function getTextElementText(
|
||||
elementModel: Record<string, unknown>
|
||||
): string {
|
||||
let text = '';
|
||||
if (elementModel.type !== 'text') {
|
||||
return text;
|
||||
}
|
||||
if (
|
||||
'text' in elementModel &&
|
||||
typeof elementModel.text === 'object' &&
|
||||
elementModel.text
|
||||
) {
|
||||
let delta: DeltaInsert[] = [];
|
||||
if ('delta' in elementModel.text) {
|
||||
delta = elementModel.text.delta as DeltaInsert[];
|
||||
}
|
||||
text = delta.map(d => d.insert).join('');
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* traverse the mindMapTree and construct the content string
|
||||
* like:
|
||||
* - Root
|
||||
* - Child 1
|
||||
* - Child 1.1
|
||||
* - Child 1.2
|
||||
* - Child 2
|
||||
* - Child 2.1
|
||||
* - Child 2.2
|
||||
* - Child 3
|
||||
* - Child 3.1
|
||||
* - Child 3.2
|
||||
* @param elementModel - the mindmap element model
|
||||
* @param elements - the elements map
|
||||
* @returns the mindmap tree text
|
||||
*/
|
||||
export function getMindMapTreeText(
|
||||
elementModel: Record<string, unknown>,
|
||||
elements: Record<string, Record<string, unknown>>,
|
||||
options: {
|
||||
prefix: string;
|
||||
repeat: number;
|
||||
} = {
|
||||
prefix: ' ',
|
||||
repeat: 2,
|
||||
}
|
||||
): string {
|
||||
let mindMapContent = '';
|
||||
if (elementModel.type !== 'mindmap') {
|
||||
return mindMapContent;
|
||||
}
|
||||
|
||||
const mindMapTree = buildMindMapTree(elementModel);
|
||||
if (!mindMapTree) {
|
||||
return mindMapContent;
|
||||
}
|
||||
|
||||
let layer = 0;
|
||||
const traverseMindMapTree = (
|
||||
node: MindMapTreeNode,
|
||||
prefix: string,
|
||||
repeat: number
|
||||
) => {
|
||||
const shapeElement = elements[node.id as string];
|
||||
const shapeText = getShapeText(shapeElement);
|
||||
if (shapeElement) {
|
||||
mindMapContent += `${prefix.repeat(layer * repeat)}- ${shapeText}\n`;
|
||||
}
|
||||
node.children.forEach(child => {
|
||||
layer++;
|
||||
traverseMindMapTree(child, prefix, repeat);
|
||||
layer--;
|
||||
});
|
||||
};
|
||||
traverseMindMapTree(mindMapTree, options.prefix, options.repeat);
|
||||
|
||||
return mindMapContent;
|
||||
}
|
||||
Reference in New Issue
Block a user