refactor(editor): improve element adapters (#11473)

This commit is contained in:
Saul-Mirone
2025-04-05 09:40:13 +00:00
parent 0fbca31c27
commit aed7f40568
45 changed files with 292 additions and 248 deletions
@@ -17,14 +17,40 @@ import {
} from '@blocksuite/affine-block-note';
import { ParagraphBlockSpec } from '@blocksuite/affine-block-paragraph';
import {
EdgelessSurfaceBlockAdapterExtensions,
EdgelessSurfaceBlockSpec,
PageSurfaceBlockSpec,
SurfaceBlockAdapterExtensions,
} from '@blocksuite/affine-block-surface';
import {
EdgelessSurfaceRefBlockSpec,
PageSurfaceRefBlockSpec,
} from '@blocksuite/affine-block-surface-ref';
import { TableBlockSpec } from '@blocksuite/affine-block-table';
import {
brushToMarkdownAdapterMatcher,
brushToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-brush';
import {
connectorToMarkdownAdapterMatcher,
connectorToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-connector';
import {
groupToMarkdownAdapterMatcher,
groupToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-group';
import {
mindmapToMarkdownAdapterMatcher,
mindmapToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-mindmap';
import {
shapeToMarkdownAdapterMatcher,
shapeToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-shape';
import {
textToMarkdownAdapterMatcher,
textToPlainTextAdapterMatcher,
} from '@blocksuite/affine-gfx-text';
import { inlinePresetExtensions } from '@blocksuite/affine-inline-preset';
import {
DefaultOpenDocExtension,
@@ -34,6 +60,24 @@ import {
} from '@blocksuite/affine-shared/services';
import type { ExtensionType } from '@blocksuite/store';
const elementToPlainTextAdapterMatchers = [
groupToPlainTextAdapterMatcher,
shapeToPlainTextAdapterMatcher,
connectorToPlainTextAdapterMatcher,
brushToPlainTextAdapterMatcher,
textToPlainTextAdapterMatcher,
mindmapToPlainTextAdapterMatcher,
];
const elementToMarkdownAdapterMatchers = [
groupToMarkdownAdapterMatcher,
shapeToMarkdownAdapterMatcher,
connectorToMarkdownAdapterMatcher,
brushToMarkdownAdapterMatcher,
textToMarkdownAdapterMatcher,
mindmapToMarkdownAdapterMatcher,
];
export const CommonBlockSpecs: ExtensionType[] = [
inlinePresetExtensions,
DocDisplayMetaService,
@@ -54,6 +98,9 @@ export const CommonBlockSpecs: ExtensionType[] = [
FontLoaderService,
CalloutBlockSpec,
FrameBlockSpec,
elementToPlainTextAdapterMatchers,
elementToMarkdownAdapterMatchers,
].flat();
export const PageFirstPartyBlockSpecs: ExtensionType[] = [
@@ -61,6 +108,8 @@ export const PageFirstPartyBlockSpecs: ExtensionType[] = [
NoteBlockSpec,
PageSurfaceBlockSpec,
PageSurfaceRefBlockSpec,
...SurfaceBlockAdapterExtensions,
].flat();
export const EdgelessFirstPartyBlockSpecs: ExtensionType[] = [
@@ -70,4 +119,6 @@ export const EdgelessFirstPartyBlockSpecs: ExtensionType[] = [
EdgelessSurfaceBlockSpec,
EdgelessSurfaceRefBlockSpec,
EdgelessTextBlockSpec,
...EdgelessSurfaceBlockAdapterExtensions,
].flat();
@@ -1,24 +1,21 @@
import { elementToMarkdownAdapterMatchers } from './markdown/element-adapter/elements/index.js';
import {
EdgelessSurfaceBlockMarkdownAdapterExtension,
SurfaceBlockMarkdownAdapterExtension,
} from './markdown/markdown.js';
import { elementToPlainTextAdapterMatchers } from './plain-text/element-adapter/elements/index.js';
import {
EdgelessSurfaceBlockPlainTextAdapterExtension,
SurfaceBlockPlainTextAdapterExtension,
} from './plain-text/plain-text.js';
export const SurfaceBlockAdapterExtensions = [
...elementToPlainTextAdapterMatchers,
...elementToMarkdownAdapterMatchers,
SurfaceBlockPlainTextAdapterExtension,
SurfaceBlockMarkdownAdapterExtension,
];
export const EdgelessSurfaceBlockAdapterExtensions = [
...elementToPlainTextAdapterMatchers,
...elementToMarkdownAdapterMatchers,
EdgelessSurfaceBlockPlainTextAdapterExtension,
EdgelessSurfaceBlockMarkdownAdapterExtension,
];
export * from './markdown/element-adapter/index.js';
export * from './plain-text/element-adapter/index.js';
@@ -1,4 +1 @@
export {
EdgelessSurfaceBlockAdapterExtensions,
SurfaceBlockAdapterExtensions,
} from './extension.js';
export * from './extension.js';
@@ -1,15 +0,0 @@
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,
];
@@ -29,3 +29,5 @@ export class MarkdownElementModelAdapter extends ElementModelAdapter<
return markdownAST;
}
}
export * from './type.js';
@@ -1,9 +1,9 @@
import { getMindMapNodeMap } from '@blocksuite/affine-model';
import {
BlockMarkdownAdapterExtension,
type BlockMarkdownAdapterMatcher,
} from '@blocksuite/affine-shared/adapters';
import { getMindMapNodeMap } from '../utils/mindmap.js';
import { MarkdownElementModelAdapter } from './element-adapter/index.js';
import { ElementToMarkdownAdapterMatcherIdentifier } from './element-adapter/type.js';
@@ -1,15 +0,0 @@
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 elementToPlainTextAdapterMatchers = [
groupToPlainTextAdapterMatcher,
shapeToPlainTextAdapterMatcher,
connectorToPlainTextAdapterMatcher,
brushToPlainTextAdapterMatcher,
textToPlainTextAdapterMatcher,
mindmapToPlainTextAdapterMatcher,
];
@@ -28,3 +28,5 @@ export class PlainTextElementModelAdapter extends ElementModelAdapter<
return '';
}
}
export * from './type.js';
@@ -1,9 +1,9 @@
import { getMindMapNodeMap } from '@blocksuite/affine-model';
import {
BlockPlainTextAdapterExtension,
type BlockPlainTextAdapterMatcher,
} from '@blocksuite/affine-shared/adapters';
import { getMindMapNodeMap } from '../utils/mindmap.js';
import { PlainTextElementModelAdapter } from './element-adapter/index.js';
import { ElementToPlainTextAdapterMatcherIdentifier } from './element-adapter/type.js';
@@ -1,162 +0,0 @@
import type { DeltaInsert } from '@blocksuite/store';
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;
}
@@ -30,10 +30,7 @@ import {
isSameFontFamily,
wrapFontFamily,
} from './utils/font.js';
export {
EdgelessSurfaceBlockAdapterExtensions,
SurfaceBlockAdapterExtensions,
} from './adapters/index.js';
export * from './adapters/index.js';
export * from './extensions';
export type { SurfaceContext } from './surface-block.js';
export { SurfaceBlockComponent } from './surface-block.js';
@@ -2,10 +2,6 @@ import { BlockViewExtension, FlavourExtension } from '@blocksuite/std';
import type { ExtensionType } from '@blocksuite/store';
import { literal } from 'lit/static-html.js';
import {
EdgelessSurfaceBlockAdapterExtensions,
SurfaceBlockAdapterExtensions,
} from './adapters/extension';
import {
EdgelessCRUDExtension,
EdgelessLegacySlotExtension,
@@ -21,12 +17,10 @@ const CommonSurfaceBlockSpec: ExtensionType[] = [
export const PageSurfaceBlockSpec: ExtensionType[] = [
...CommonSurfaceBlockSpec,
...SurfaceBlockAdapterExtensions,
BlockViewExtension('affine:surface', literal`affine-surface-void`),
];
export const EdgelessSurfaceBlockSpec: ExtensionType[] = [
...CommonSurfaceBlockSpec,
...EdgelessSurfaceBlockAdapterExtensions,
BlockViewExtension('affine:surface', literal`affine-surface`),
];
@@ -0,0 +1,2 @@
export * from './markdown';
export * from './plain-text';
@@ -1,4 +1,4 @@
import { ElementToMarkdownAdapterExtension } from '../type.js';
import { ElementToMarkdownAdapterExtension } from '@blocksuite/affine-block-surface';
export const brushToMarkdownAdapterMatcher = ElementToMarkdownAdapterExtension({
name: 'brush',
@@ -1,4 +1,4 @@
import { ElementToPlainTextAdapterExtension } from '../type.js';
import { ElementToPlainTextAdapterExtension } from '@blocksuite/affine-block-surface';
export const brushToPlainTextAdapterMatcher =
ElementToPlainTextAdapterExtension({
+1
View File
@@ -1,3 +1,4 @@
export * from './adapter';
export * from './brush-tool';
export * from './element-renderer';
export * from './eraser-tool';
@@ -0,0 +1,2 @@
export * from './markdown';
export * from './plain-text';
@@ -1,5 +1,6 @@
import { getConnectorText } from '../../../utils/text.js';
import { ElementToMarkdownAdapterExtension } from '../type.js';
import { ElementToMarkdownAdapterExtension } from '@blocksuite/affine-block-surface';
import { getConnectorText } from '../text';
export const connectorToMarkdownAdapterMatcher =
ElementToMarkdownAdapterExtension({
@@ -1,5 +1,6 @@
import { getConnectorText } from '../../../utils/text.js';
import { ElementToPlainTextAdapterExtension } from '../type.js';
import { ElementToPlainTextAdapterExtension } from '@blocksuite/affine-block-surface';
import { getConnectorText } from '../text';
export const connectorToPlainTextAdapterMatcher =
ElementToPlainTextAdapterExtension({
@@ -0,0 +1,23 @@
import type { DeltaInsert } from '@blocksuite/affine/store';
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;
}
@@ -1,3 +1,4 @@
export * from './adapter';
export * from './connector-tool';
export * from './element-renderer';
export * from './element-transform';
@@ -0,0 +1,2 @@
export * from './markdown';
export * from './plain-text';
@@ -1,5 +1,6 @@
import { getGroupTitle } from '../../../utils/text.js';
import { ElementToMarkdownAdapterExtension } from '../type.js';
import { ElementToMarkdownAdapterExtension } from '@blocksuite/affine-block-surface';
import { getGroupTitle } from '../text';
export const groupToMarkdownAdapterMatcher = ElementToMarkdownAdapterExtension({
name: 'group',
@@ -1,5 +1,6 @@
import { getGroupTitle } from '../../../utils/text.js';
import { ElementToPlainTextAdapterExtension } from '../type.js';
import { ElementToPlainTextAdapterExtension } from '@blocksuite/affine-block-surface';
import { getGroupTitle } from '../text';
export const groupToPlainTextAdapterMatcher =
ElementToPlainTextAdapterExtension({
@@ -0,0 +1,21 @@
import type { DeltaInsert } from '@blocksuite/affine/store';
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;
}
+1
View File
@@ -1,3 +1,4 @@
export * from './adapter';
export * from './command';
export * from './element-renderer';
export * from './text/text';
@@ -0,0 +1,2 @@
export * from './markdown';
export * from './plain-text';
@@ -1,7 +1,9 @@
import type { MindMapTreeNode } from '../../../types/mindmap.js';
import { buildMindMapTree } from '../../../utils/mindmap.js';
import { getShapeText } from '../../../utils/text.js';
import { ElementToMarkdownAdapterExtension } from '../type.js';
import { ElementToMarkdownAdapterExtension } from '@blocksuite/affine-block-surface';
import { getShapeText } from '@blocksuite/affine-gfx-shape';
import {
buildMindMapTree,
type MindMapTreeNode,
} from '@blocksuite/affine-model';
export const mindmapToMarkdownAdapterMatcher =
ElementToMarkdownAdapterExtension({
@@ -1,5 +1,6 @@
import { getMindMapTreeText } from '../../../utils/text.js';
import { ElementToPlainTextAdapterExtension } from '../type.js';
import { ElementToPlainTextAdapterExtension } from '@blocksuite/affine-block-surface';
import { getMindMapTreeText } from '../utils';
export const mindmapToPlainTextAdapterMatcher =
ElementToPlainTextAdapterExtension({
@@ -0,0 +1,65 @@
import { getShapeText } from '@blocksuite/affine-gfx-shape';
import {
buildMindMapTree,
type MindMapTreeNode,
} from '@blocksuite/affine-model';
/**
* 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;
}
@@ -1,3 +1,4 @@
export * from './adapter';
export * from './element-renderer';
export * from './element-transform';
export * from './indicator-overlay';
@@ -0,0 +1,3 @@
export * from './markdown';
export * from './plain-text';
export * from './utils';
@@ -1,6 +1,7 @@
import type { MindMapTreeNode } from '../../../types/mindmap.js';
import { getShapeText, getShapeType } from '../../../utils/text.js';
import { ElementToMarkdownAdapterExtension } from '../type.js';
import { ElementToMarkdownAdapterExtension } from '@blocksuite/affine-block-surface';
import type { MindMapTreeNode } from '@blocksuite/affine-model';
import { getShapeText, getShapeType } from '../utils';
export const shapeToMarkdownAdapterMatcher = ElementToMarkdownAdapterExtension({
name: 'shape',
@@ -1,6 +1,7 @@
import type { MindMapTreeNode } from '../../../types/mindmap.js';
import { getShapeText, getShapeType } from '../../../utils/text.js';
import { ElementToPlainTextAdapterExtension } from '../type.js';
import { ElementToPlainTextAdapterExtension } from '@blocksuite/affine-block-surface';
import type { MindMapTreeNode } from '@blocksuite/affine-model';
import { getShapeText, getShapeType } from '../utils';
export const shapeToPlainTextAdapterMatcher =
ElementToPlainTextAdapterExtension({
@@ -0,0 +1,36 @@
import type { DeltaInsert } from '@blocksuite/store';
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;
}
+1
View File
@@ -1,3 +1,4 @@
export * from './adapter';
export * from './consts';
export * from './draggable';
export * from './element-renderer';
@@ -0,0 +1,2 @@
export * from './markdown';
export * from './plain-text';
@@ -1,5 +1,6 @@
import { getTextElementText } from '../../../utils/text.js';
import { ElementToMarkdownAdapterExtension } from '../type.js';
import { ElementToMarkdownAdapterExtension } from '@blocksuite/affine-block-surface';
import { getTextElementText } from '../text';
export const textToMarkdownAdapterMatcher = ElementToMarkdownAdapterExtension({
name: 'text',
@@ -1,5 +1,6 @@
import { getTextElementText } from '../../../utils/text.js';
import { ElementToPlainTextAdapterExtension } from '../type.js';
import { ElementToPlainTextAdapterExtension } from '@blocksuite/affine-block-surface';
import { getTextElementText } from '../text';
export const textToPlainTextAdapterMatcher = ElementToPlainTextAdapterExtension(
{
@@ -0,0 +1,22 @@
import type { DeltaInsert } from '@blocksuite/affine/store';
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;
}
+1
View File
@@ -1,3 +1,4 @@
export * from './adapter';
export * from './commands';
export * from './edgeless-text-editor';
export * from './element-renderer';
@@ -1,2 +1,3 @@
export * from './mindmap.js';
export * from './style.js';
export * from './mindmap';
export * from './snapshot';
export * from './style';
@@ -0,0 +1,2 @@
export * from './types';
export * from './utils';
@@ -1,8 +1,4 @@
import type {
MindMapElement,
MindMapJson,
MindMapTreeNode,
} from '../types/mindmap.js';
import type { MindMapElement, MindMapJson, MindMapTreeNode } from './types';
function isMindMapElement(element: unknown): element is MindMapElement {
return (
@@ -16,7 +12,7 @@ function isMindMapElement(element: unknown): element is MindMapElement {
);
}
export function getMindMapChildrenJson(
function getMindMapChildrenJson(
element: Record<string, unknown>
): MindMapJson | null {
if (!isMindMapElement(element)) {