chore: merge blocksuite source code (#9213)

This commit is contained in:
Mirone
2024-12-20 15:38:06 +08:00
committed by GitHub
parent 2c9ef916f4
commit 30200ff86d
2031 changed files with 238888 additions and 229 deletions

View 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,
];

View File

@@ -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
);

View File

@@ -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,
},
],
};
},
};

View File

@@ -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,
},
],
};
},
};

View File

@@ -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,
},
],
};
},
};

View File

@@ -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,
];

View File

@@ -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;
},
};

View File

@@ -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,
},
],
};
},
};

View File

@@ -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,
},
],
};
},
};

View File

@@ -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;
}
}

View File

@@ -0,0 +1,6 @@
import type { MarkdownAST } from '@blocksuite/affine-shared/adapters';
import type { ElementModelMatcher } from '../../type.js';
export type ElementModelToMarkdownAdapterMatcher =
ElementModelMatcher<MarkdownAST>;

View File

@@ -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);

View File

@@ -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 };
},
};

View File

@@ -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 };
},
};

View File

@@ -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 };
},
};

View File

@@ -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,
];

View File

@@ -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 };
},
};

View File

@@ -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 };
},
};

View File

@@ -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 };
},
};

View File

@@ -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 '';
}
}

View File

@@ -0,0 +1,6 @@
import type { TextBuffer } from '@blocksuite/affine-shared/adapters';
import type { ElementModelMatcher } from '../../type.js';
export type ElementModelToPlainTextAdapterMatcher =
ElementModelMatcher<TextBuffer>;

View File

@@ -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);

View 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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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;
}