import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; import { type BlockStdScope, type ExtensionType, StdIdentifier, } from '@blocksuite/block-std'; import { createIdentifier, type ServiceIdentifier, } from '@blocksuite/global/di'; import { type AttributeRenderer, baseTextAttributes, type DeltaInsert, getDefaultAttributeRenderer, KEYBOARD_ALLOW_DEFAULT, type KeyboardBindingContext, } from '@blocksuite/inline'; import type { Y } from '@blocksuite/store'; import { z, type ZodObject, type ZodTypeAny } from 'zod'; import { MarkdownMatcherIdentifier } from './markdown-matcher.js'; import type { InlineMarkdownMatch, InlineSpecs } from './type.js'; export class InlineManager { embedChecker = (delta: DeltaInsert) => { for (const spec of this.specs) { if (spec.embed && spec.match(delta)) { return true; } } return false; }; getRenderer = (): AttributeRenderer => { const defaultRenderer = getDefaultAttributeRenderer(); const renderer: AttributeRenderer = props => { // Priority increases from front to back for (const spec of this.specs.toReversed()) { if (spec.match(props.delta)) { return spec.renderer(props); } } return defaultRenderer(props); }; return renderer; }; getSchema = (): ZodObject> => { const defaultSchema = baseTextAttributes as unknown as ZodObject< Record >; const schema: ZodObject> = this.specs.reduce((acc, cur) => { const currentSchema = z.object({ [cur.name]: cur.schema, }) as ZodObject>; return acc.merge(currentSchema) as ZodObject< Record >; }, defaultSchema); return schema; }; markdownShortcutHandler = ( context: KeyboardBindingContext, undoManager: Y.UndoManager ) => { const { inlineEditor, prefixText, inlineRange } = context; for (const match of this.markdownMatches) { const matchedText = prefixText.match(match.pattern); if (matchedText) { return match.action({ inlineEditor, prefixText, inlineRange, pattern: match.pattern, undoManager, }); } } return KEYBOARD_ALLOW_DEFAULT; }; readonly specs: Array>; constructor( readonly std: BlockStdScope, readonly markdownMatches: InlineMarkdownMatch[], ...specs: Array> ) { this.specs = specs; } } export const InlineManagerIdentifier = createIdentifier( 'AffineInlineManager' ); export type InlineManagerExtensionConfig = { id: string; enableMarkdown?: boolean; specs: ServiceIdentifier>[]; }; export function InlineManagerExtension({ id, enableMarkdown = true, specs, }: InlineManagerExtensionConfig): ExtensionType & { identifier: ServiceIdentifier; } { const identifier = InlineManagerIdentifier(id); return { setup: di => { di.addImpl(identifier, provider => { return new InlineManager( provider.get(StdIdentifier), enableMarkdown ? Array.from(provider.getAll(MarkdownMatcherIdentifier).values()) : [], ...specs.map(spec => provider.get(spec)) ); }); }, identifier, }; }