mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-18 10:31:50 +08:00
refactor(editor): extensionalize html adapter (#9299)
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
} from '@blocksuite/global/di';
|
||||
|
||||
import type { BlockAdapterMatcher } from '../types/adapter.js';
|
||||
import type { HtmlAST } from '../types/hast.js';
|
||||
import type { HtmlDeltaConverter } from './delta-converter.js';
|
||||
|
||||
export type BlockHtmlAdapterMatcher = BlockAdapterMatcher<
|
||||
HtmlAST,
|
||||
HtmlDeltaConverter
|
||||
>;
|
||||
|
||||
export const BlockHtmlAdapterMatcherIdentifier =
|
||||
createIdentifier<BlockHtmlAdapterMatcher>('BlockHtmlAdapterMatcher');
|
||||
|
||||
export function BlockHtmlAdapterExtension(
|
||||
matcher: BlockHtmlAdapterMatcher
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<BlockHtmlAdapterMatcher>;
|
||||
} {
|
||||
const identifier = BlockHtmlAdapterMatcherIdentifier(matcher.flavour);
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(identifier, () => matcher);
|
||||
},
|
||||
identifier,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { ExtensionType } from '@blocksuite/block-std';
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
} from '@blocksuite/global/di';
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
|
||||
import type { AffineTextAttributes } from '../../types/index.js';
|
||||
import {
|
||||
type ASTToDeltaMatcher,
|
||||
DeltaASTConverter,
|
||||
type DeltaASTConverterOptions,
|
||||
type InlineDeltaMatcher,
|
||||
} from '../types/adapter.js';
|
||||
import type { HtmlAST, InlineHtmlAST } from '../types/hast.js';
|
||||
import { TextUtils } from '../utils/text.js';
|
||||
|
||||
export type InlineDeltaToHtmlAdapterMatcher = InlineDeltaMatcher<InlineHtmlAST>;
|
||||
|
||||
export const InlineDeltaToHtmlAdapterMatcherIdentifier =
|
||||
createIdentifier<InlineDeltaToHtmlAdapterMatcher>(
|
||||
'InlineDeltaToHtmlAdapterMatcher'
|
||||
);
|
||||
|
||||
export function InlineDeltaToHtmlAdapterExtension(
|
||||
matcher: InlineDeltaToHtmlAdapterMatcher
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineDeltaToHtmlAdapterMatcher>;
|
||||
} {
|
||||
const identifier = InlineDeltaToHtmlAdapterMatcherIdentifier(matcher.name);
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(identifier, () => matcher);
|
||||
},
|
||||
identifier,
|
||||
};
|
||||
}
|
||||
|
||||
export type HtmlASTToDeltaMatcher = ASTToDeltaMatcher<HtmlAST>;
|
||||
|
||||
export const HtmlASTToDeltaMatcherIdentifier =
|
||||
createIdentifier<HtmlASTToDeltaMatcher>('HtmlASTToDeltaMatcher');
|
||||
|
||||
export function HtmlASTToDeltaExtension(
|
||||
matcher: HtmlASTToDeltaMatcher
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<HtmlASTToDeltaMatcher>;
|
||||
} {
|
||||
const identifier = HtmlASTToDeltaMatcherIdentifier(matcher.name);
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(identifier, () => matcher);
|
||||
},
|
||||
identifier,
|
||||
};
|
||||
}
|
||||
|
||||
export class HtmlDeltaConverter extends DeltaASTConverter<
|
||||
AffineTextAttributes,
|
||||
HtmlAST
|
||||
> {
|
||||
constructor(
|
||||
readonly configs: Map<string, string>,
|
||||
readonly inlineDeltaMatchers: InlineDeltaToHtmlAdapterMatcher[],
|
||||
readonly htmlASTToDeltaMatchers: HtmlASTToDeltaMatcher[]
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private _applyTextFormatting(
|
||||
delta: DeltaInsert<AffineTextAttributes>
|
||||
): InlineHtmlAST {
|
||||
let hast: InlineHtmlAST = {
|
||||
type: 'text',
|
||||
value: delta.insert,
|
||||
};
|
||||
|
||||
const context: {
|
||||
configs: Map<string, string>;
|
||||
current: InlineHtmlAST;
|
||||
} = {
|
||||
configs: this.configs,
|
||||
current: hast,
|
||||
};
|
||||
for (const matcher of this.inlineDeltaMatchers) {
|
||||
if (matcher.match(delta)) {
|
||||
hast = matcher.toAST(delta, context);
|
||||
context.current = hast;
|
||||
}
|
||||
}
|
||||
|
||||
return hast;
|
||||
}
|
||||
|
||||
private _spreadAstToDelta(
|
||||
ast: HtmlAST,
|
||||
options: DeltaASTConverterOptions = Object.create(null)
|
||||
): DeltaInsert<AffineTextAttributes>[] {
|
||||
const context = {
|
||||
configs: this.configs,
|
||||
options,
|
||||
toDelta: (ast: HtmlAST, options?: DeltaASTConverterOptions) =>
|
||||
this._spreadAstToDelta(ast, options),
|
||||
};
|
||||
for (const matcher of this.htmlASTToDeltaMatchers) {
|
||||
if (matcher.match(ast)) {
|
||||
return matcher.toDelta(ast, context);
|
||||
}
|
||||
}
|
||||
return 'children' in ast
|
||||
? ast.children.flatMap(child => this._spreadAstToDelta(child, options))
|
||||
: [];
|
||||
}
|
||||
|
||||
astToDelta(
|
||||
ast: HtmlAST,
|
||||
options: DeltaASTConverterOptions = Object.create(null)
|
||||
): DeltaInsert<AffineTextAttributes>[] {
|
||||
return this._spreadAstToDelta(ast, options).reduce((acc, cur) => {
|
||||
return TextUtils.mergeDeltas(acc, cur);
|
||||
}, [] as DeltaInsert<AffineTextAttributes>[]);
|
||||
}
|
||||
|
||||
deltaToAST(
|
||||
deltas: DeltaInsert<AffineTextAttributes>[],
|
||||
depth = 0
|
||||
): InlineHtmlAST[] {
|
||||
if (depth > 0) {
|
||||
deltas.unshift({ insert: ' '.repeat(4).repeat(depth) });
|
||||
}
|
||||
|
||||
return deltas.map(delta => this._applyTextFormatting(delta));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './block-adapter.js';
|
||||
export * from './delta-converter.js';
|
||||
export * from './type.js';
|
||||
@@ -0,0 +1 @@
|
||||
export type Html = string;
|
||||
Reference in New Issue
Block a user