Skip to content

Commit

Permalink
feat(blocks): add surface element plain text adapter (#8977)
Browse files Browse the repository at this point in the history
  • Loading branch information
donteatfriedrice committed Dec 17, 2024
1 parent bc04f27 commit 0169084
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 0 deletions.
12 changes: 12 additions & 0 deletions packages/affine/block-surface/src/adapters/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {
EdgelessSurfaceBlockPlainTextAdapterExtension,
SurfaceBlockPlainTextAdapterExtension,
} from './plain-text/plain-text.js';

export const SurfaceBlockAdapterExtensions = [
SurfaceBlockPlainTextAdapterExtension,
];

export const EdgelessSurfaceBlockAdapterExtensions = [
EdgelessSurfaceBlockPlainTextAdapterExtension,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ElementModelToPlainTextAdapterMatcher } from './type.js';

export const brushElementModelToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
{
name: 'brush',
match: elementModel => elementModel.type === 'brush',
toAST: () => {
const content = `Brush Stroke`;
return { content };
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { DeltaInsert } from '@blocksuite/inline/types';

import type { ElementModelToPlainTextAdapterMatcher } from './type.js';

export const connectorElementModelToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
{
name: 'connector',
match: elementModel => elementModel.type === 'connector',
toAST: elementModel => {
let text = '';
if ('text' in elementModel && elementModel.text) {
let delta: DeltaInsert[] = [];
if ('delta' in elementModel.text) {
delta = elementModel.text.delta as DeltaInsert[];
}
text = delta.map(d => d.insert).join('');
}
const content = `Connector, with text label "${text}"`;
return { content };
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { DeltaInsert } from '@blocksuite/inline/types';

import type { ElementModelToPlainTextAdapterMatcher } from './type.js';

export const groupElementModelToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
{
name: 'group',
match: elementModel => elementModel.type === 'group',
toAST: elementModel => {
let title = '';
if ('title' in elementModel && elementModel.title) {
let delta: DeltaInsert[] = [];
if ('delta' in elementModel.title) {
delta = elementModel.title.delta as DeltaInsert[];
}
title = delta.map(d => d.insert).join('');
}
const content = `Group, with title "${title}"`;
return { content };
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { brushElementModelToPlainTextAdapterMatcher } from './brush.js';
import { connectorElementModelToPlainTextAdapterMatcher } from './connector.js';
import { groupElementModelToPlainTextAdapterMatcher } from './group.js';
import { shapeElementModelToPlainTextAdapterMatcher } from './shape.js';
import { textElementModelToPlainTextAdapterMatcher } from './text.js';

export const elementModelToPlainTextAdapterMatchers = [
groupElementModelToPlainTextAdapterMatcher,
shapeElementModelToPlainTextAdapterMatcher,
connectorElementModelToPlainTextAdapterMatcher,
brushElementModelToPlainTextAdapterMatcher,
textElementModelToPlainTextAdapterMatcher,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { DeltaInsert } from '@blocksuite/inline/types';

import type { ElementModelToPlainTextAdapterMatcher } from './type.js';

export const shapeElementModelToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
{
name: 'shape',
match: elementModel => elementModel.type === 'shape',
toAST: elementModel => {
let text = '';
let shapeType = '';
if ('text' in elementModel && elementModel.text) {
let delta: DeltaInsert[] = [];
if ('delta' in elementModel.text) {
delta = elementModel.text.delta as DeltaInsert[];
}
text = delta.map(d => d.insert).join('');
}
if ('shapeType' in elementModel) {
shapeType =
elementModel.shapeType.charAt(0).toUpperCase() +
elementModel.shapeType.slice(1);
}
const content = `${shapeType}, with text label "${text}"`;
return { content };
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { DeltaInsert } from '@blocksuite/inline/types';

import type { ElementModelToPlainTextAdapterMatcher } from './type.js';

export const textElementModelToPlainTextAdapterMatcher: ElementModelToPlainTextAdapterMatcher =
{
name: 'text',
match: elementModel => elementModel.type === 'text',
toAST: elementModel => {
let content = '';
if ('text' in elementModel && elementModel.text) {
let delta: DeltaInsert[] = [];
if ('delta' in elementModel.text) {
delta = elementModel.text.delta as DeltaInsert[];
}
content = delta.map(d => d.insert).join('');
}
return { content };
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { TextBuffer } from '@blocksuite/affine-shared/adapters';

import type { ElementModelMatcher } from '../../../type.js';

export type ElementModelToPlainTextAdapterMatcher =
ElementModelMatcher<TextBuffer>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { ElementModelMap } from '../../../element-model/index.js';
import type { ElementModelToPlainTextAdapterMatcher } from './elements/type.js';

import { ElementModelAdapter } from '../../type.js';
import { elementModelToPlainTextAdapterMatchers } from './elements/index.js';

export class PlainTextElementModelAdapter extends ElementModelAdapter<string> {
constructor(
readonly elementModelMatchers: ElementModelToPlainTextAdapterMatcher[] = elementModelToPlainTextAdapterMatchers
) {
super();
}

fromElementModel(elementModel: ElementModelMap[keyof ElementModelMap]) {
for (const matcher of this.elementModelMatchers) {
if (matcher.match(elementModel)) {
return matcher.toAST(elementModel).content;
}
}
return '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
BlockPlainTextAdapterExtension,
type BlockPlainTextAdapterMatcher,
} from '@blocksuite/affine-shared/adapters';

import type { ElementModelMap } from '../../element-model/index.js';

import { SurfaceBlockSchema } from '../../surface-model.js';
import { PlainTextElementModelAdapter } from './element-adapter/index.js';

export const surfaceBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
{
flavour: SurfaceBlockSchema.model.flavour,
toMatch: () => false,
fromMatch: o => o.node.flavour === SurfaceBlockSchema.model.flavour,
toBlockSnapshot: {},
fromBlockSnapshot: {
enter: (_, context) => {
context.walkerContext.skipAllChildren();
},
},
};

export const SurfaceBlockPlainTextAdapterExtension =
BlockPlainTextAdapterExtension(surfaceBlockPlainTextAdapterMatcher);

export const edgelessSurfaceBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher =
{
flavour: SurfaceBlockSchema.model.flavour,
toMatch: () => false,
fromMatch: o => o.node.flavour === SurfaceBlockSchema.model.flavour,
toBlockSnapshot: {},
fromBlockSnapshot: {
enter: (o, context) => {
const plainTextElementModelAdapter = new PlainTextElementModelAdapter();
if ('elements' in o.node.props) {
Object.entries(
o.node.props.elements as Record<string, Record<string, unknown>>
).forEach(([_, elementModel]) => {
const element =
elementModel as unknown as ElementModelMap[keyof ElementModelMap];
const plainText =
plainTextElementModelAdapter.fromElementModel(element);
if (plainText) {
context.textBuffer.content += plainText + '\n';
}
});
}
},
},
};

export const EdgelessSurfaceBlockPlainTextAdapterExtension =
BlockPlainTextAdapterExtension(edgelessSurfaceBlockPlainTextAdapterMatcher);
16 changes: 16 additions & 0 deletions packages/affine/block-surface/src/adapters/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ElementModelMap } from '../element-model/index.js';

export type ElementModelMatcher<TNode extends object = never> = {
name: keyof ElementModelMap;
match: (elementModel: ElementModelMap[keyof ElementModelMap]) => boolean;
toAST: (elementModel: ElementModelMap[keyof ElementModelMap]) => TNode;
};

export abstract class ElementModelAdapter<AST = unknown> {
/**
* Convert element model to AST format
*/
abstract fromElementModel(
elementModel: ElementModelMap[keyof ElementModelMap]
): AST;
}
6 changes: 6 additions & 0 deletions packages/affine/block-surface/src/surface-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {
} from '@blocksuite/block-std';
import { literal } from 'lit/static-html.js';

import {
EdgelessSurfaceBlockAdapterExtensions,
SurfaceBlockAdapterExtensions,
} from './adapters/extension.js';
import { commands } from './commands/index.js';
import { SurfaceBlockService } from './surface-service.js';

Expand All @@ -19,10 +23,12 @@ 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 comments on commit 0169084

Please sign in to comment.