mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(editor): std inline extensions (#11038)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
export * from './inline-manager';
|
||||
export * from './inline-spec';
|
||||
export * from './markdown-matcher';
|
||||
export * from './type';
|
||||
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
} from '@blocksuite/global/di';
|
||||
import {
|
||||
type BaseTextAttributes,
|
||||
baseTextAttributes,
|
||||
type DeltaInsert,
|
||||
type ExtensionType,
|
||||
} from '@blocksuite/store';
|
||||
import { z, type ZodObject, type ZodTypeAny } from 'zod';
|
||||
|
||||
import { StdIdentifier } from '../../identifier.js';
|
||||
import type { BlockStdScope } from '../../scope/index.js';
|
||||
import type { AttributeRenderer } from '../types.js';
|
||||
import { getDefaultAttributeRenderer } from '../utils/attribute-renderer.js';
|
||||
import { MarkdownMatcherIdentifier } from './markdown-matcher.js';
|
||||
import type { InlineMarkdownMatch, InlineSpecs } from './type.js';
|
||||
|
||||
export class InlineManager<TextAttributes extends BaseTextAttributes> {
|
||||
embedChecker = (delta: DeltaInsert<TextAttributes>) => {
|
||||
for (const spec of this.specs) {
|
||||
if (spec.embed && spec.match(delta)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
getRenderer = (): AttributeRenderer<TextAttributes> => {
|
||||
const defaultRenderer = getDefaultAttributeRenderer<TextAttributes>();
|
||||
|
||||
const renderer: AttributeRenderer<TextAttributes> = 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<Record<keyof TextAttributes, ZodTypeAny>> => {
|
||||
const defaultSchema = baseTextAttributes as unknown as ZodObject<
|
||||
Record<keyof TextAttributes, ZodTypeAny>
|
||||
>;
|
||||
|
||||
const schema: ZodObject<Record<keyof TextAttributes, ZodTypeAny>> =
|
||||
this.specs.reduce((acc, cur) => {
|
||||
const currentSchema = z.object({
|
||||
[cur.name]: cur.schema,
|
||||
}) as ZodObject<Record<keyof TextAttributes, ZodTypeAny>>;
|
||||
return acc.merge(currentSchema) as ZodObject<
|
||||
Record<keyof TextAttributes, ZodTypeAny>
|
||||
>;
|
||||
}, defaultSchema);
|
||||
return schema;
|
||||
};
|
||||
|
||||
get markdownMatches(): InlineMarkdownMatch<TextAttributes>[] {
|
||||
if (!this.enableMarkdown) {
|
||||
return [];
|
||||
}
|
||||
const matches = Array.from(
|
||||
this.std.provider.getAll(MarkdownMatcherIdentifier).values()
|
||||
);
|
||||
return matches as InlineMarkdownMatch<TextAttributes>[];
|
||||
}
|
||||
|
||||
readonly specs: Array<InlineSpecs<TextAttributes>>;
|
||||
|
||||
constructor(
|
||||
readonly std: BlockStdScope,
|
||||
readonly enableMarkdown: boolean,
|
||||
...specs: Array<InlineSpecs<TextAttributes>>
|
||||
) {
|
||||
this.specs = specs;
|
||||
}
|
||||
}
|
||||
|
||||
export type InlineManagerExtensionConfig<
|
||||
TextAttributes extends BaseTextAttributes,
|
||||
> = {
|
||||
id: string;
|
||||
enableMarkdown?: boolean;
|
||||
specs: ServiceIdentifier<InlineSpecs<TextAttributes>>[];
|
||||
};
|
||||
|
||||
const InlineManagerIdentifier = createIdentifier<unknown>(
|
||||
'AffineInlineManager'
|
||||
);
|
||||
|
||||
export function InlineManagerExtension<
|
||||
TextAttributes extends BaseTextAttributes,
|
||||
>({
|
||||
id,
|
||||
enableMarkdown = true,
|
||||
specs,
|
||||
}: InlineManagerExtensionConfig<TextAttributes>): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineManager<TextAttributes>>;
|
||||
} {
|
||||
const identifier = InlineManagerIdentifier<InlineManager<TextAttributes>>(id);
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(identifier, provider => {
|
||||
return new InlineManager(
|
||||
provider.get(StdIdentifier),
|
||||
enableMarkdown,
|
||||
...specs.map(spec => provider.get(spec))
|
||||
);
|
||||
});
|
||||
},
|
||||
identifier,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
type ServiceProvider,
|
||||
} from '@blocksuite/global/di';
|
||||
import type { BaseTextAttributes, ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import type { InlineSpecs } from './type.js';
|
||||
|
||||
export const InlineSpecIdentifier =
|
||||
createIdentifier<unknown>('AffineInlineSpec');
|
||||
|
||||
export function InlineSpecExtension<TextAttributes extends BaseTextAttributes>(
|
||||
name: string,
|
||||
getSpec: (provider: ServiceProvider) => InlineSpecs<TextAttributes>
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineSpecs<TextAttributes>>;
|
||||
};
|
||||
export function InlineSpecExtension<TextAttributes extends BaseTextAttributes>(
|
||||
spec: InlineSpecs<TextAttributes>
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineSpecs<TextAttributes>>;
|
||||
};
|
||||
export function InlineSpecExtension<TextAttributes extends BaseTextAttributes>(
|
||||
nameOrSpec: string | InlineSpecs<TextAttributes>,
|
||||
getSpec?: (provider: ServiceProvider) => InlineSpecs<TextAttributes>
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineSpecs<TextAttributes>>;
|
||||
} {
|
||||
if (typeof nameOrSpec === 'string') {
|
||||
const identifier =
|
||||
InlineSpecIdentifier<InlineSpecs<TextAttributes>>(nameOrSpec);
|
||||
return {
|
||||
identifier,
|
||||
setup: di => {
|
||||
di.addImpl(identifier, provider => getSpec!(provider));
|
||||
},
|
||||
};
|
||||
}
|
||||
const identifier = InlineSpecIdentifier<InlineSpecs<TextAttributes>>(
|
||||
nameOrSpec.name as string
|
||||
);
|
||||
return {
|
||||
identifier,
|
||||
setup: di => {
|
||||
di.addImpl(identifier, nameOrSpec);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
} from '@blocksuite/global/di';
|
||||
import type { BaseTextAttributes, ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import type { InlineMarkdownMatch } from './type.js';
|
||||
|
||||
export const MarkdownMatcherIdentifier = createIdentifier<unknown>(
|
||||
'AffineMarkdownMatcher'
|
||||
);
|
||||
|
||||
export function InlineMarkdownExtension<
|
||||
TextAttributes extends BaseTextAttributes,
|
||||
>(
|
||||
matcher: InlineMarkdownMatch<TextAttributes>
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineMarkdownMatch<TextAttributes>>;
|
||||
} {
|
||||
const identifier = MarkdownMatcherIdentifier<
|
||||
InlineMarkdownMatch<TextAttributes>
|
||||
>(matcher.name);
|
||||
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(identifier, () => ({ ...matcher }));
|
||||
},
|
||||
identifier,
|
||||
};
|
||||
}
|
||||
37
blocksuite/framework/block-std/src/inline/extensions/type.ts
Normal file
37
blocksuite/framework/block-std/src/inline/extensions/type.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type {
|
||||
AttributeRenderer,
|
||||
InlineEditor,
|
||||
InlineRange,
|
||||
} from '@blocksuite/block-std/inline';
|
||||
import type { BaseTextAttributes, DeltaInsert } from '@blocksuite/store';
|
||||
import type * as Y from 'yjs';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
|
||||
export type InlineSpecs<
|
||||
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
> = {
|
||||
name: keyof TextAttributes | string;
|
||||
schema: ZodTypeAny;
|
||||
match: (delta: DeltaInsert<TextAttributes>) => boolean;
|
||||
renderer: AttributeRenderer<TextAttributes>;
|
||||
embed?: boolean;
|
||||
};
|
||||
|
||||
export type InlineMarkdownMatchAction<
|
||||
// @ts-expect-error We allow to covariance for AffineTextAttributes
|
||||
in AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
> = (props: {
|
||||
inlineEditor: InlineEditor<AffineTextAttributes>;
|
||||
prefixText: string;
|
||||
inlineRange: InlineRange;
|
||||
pattern: RegExp;
|
||||
undoManager: Y.UndoManager;
|
||||
}) => void;
|
||||
|
||||
export type InlineMarkdownMatch<
|
||||
AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
> = {
|
||||
name: string;
|
||||
pattern: RegExp;
|
||||
action: InlineMarkdownMatchAction<AffineTextAttributes>;
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './components';
|
||||
export * from './consts';
|
||||
export * from './extensions';
|
||||
export * from './inline-editor';
|
||||
export * from './range';
|
||||
export * from './services';
|
||||
|
||||
Reference in New Issue
Block a user