diff --git a/blocksuite/affine/blocks/code/src/code-block-inline.ts b/blocksuite/affine/blocks/code/src/code-block-inline.ts index 131241158..5c27c92cb 100644 --- a/blocksuite/affine/blocks/code/src/code-block-inline.ts +++ b/blocksuite/affine/blocks/code/src/code-block-inline.ts @@ -20,7 +20,9 @@ import { z } from 'zod'; export const CodeBlockUnitSpecExtension = InlineSpecExtension({ name: 'code-block-unit', - schema: z.undefined(), + schema: z.object({ + 'code-block-uint': z.undefined(), + }), match: () => true, renderer: ({ delta }) => { return html``; diff --git a/blocksuite/affine/inlines/footnote/src/inline-spec.ts b/blocksuite/affine/inlines/footnote/src/inline-spec.ts index a69a6fe3c..6a72404a2 100644 --- a/blocksuite/affine/inlines/footnote/src/inline-spec.ts +++ b/blocksuite/affine/inlines/footnote/src/inline-spec.ts @@ -3,6 +3,7 @@ import type { AffineTextAttributes } from '@blocksuite/affine-shared/types'; import { StdIdentifier } from '@blocksuite/std'; import { InlineSpecExtension } from '@blocksuite/std/inline'; import { html } from 'lit'; +import z from 'zod'; import { FootNoteNodeConfigIdentifier } from './footnote-node/footnote-config'; @@ -13,7 +14,9 @@ export const FootNoteInlineSpecExtension = provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined; return { name: 'footnote', - schema: FootNoteSchema.optional().nullable().catch(undefined), + schema: z.object({ + footnote: FootNoteSchema.optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.footnote; }, diff --git a/blocksuite/affine/inlines/latex/src/inline-spec.ts b/blocksuite/affine/inlines/latex/src/inline-spec.ts index c06cdd309..0c9592cba 100644 --- a/blocksuite/affine/inlines/latex/src/inline-spec.ts +++ b/blocksuite/affine/inlines/latex/src/inline-spec.ts @@ -9,7 +9,9 @@ export const LatexInlineSpecExtension = const std = provider.get(StdIdentifier); return { name: 'latex', - schema: z.string().optional().nullable().catch(undefined), + schema: z.object({ + latex: z.string().optional().nullable().catch(undefined), + }), match: delta => typeof delta.attributes?.latex === 'string', renderer: ({ delta, selected, editor, startOffset, endOffset }) => { return html`({ name: 'latex-editor-unit', - schema: z.undefined(), + schema: z.object({ + 'latex-editor-unit': z.undefined(), + }), match: () => true, renderer: ({ delta }) => { return html``; diff --git a/blocksuite/affine/inlines/link/src/inline-spec.ts b/blocksuite/affine/inlines/link/src/inline-spec.ts index 513243b6d..aa4111267 100644 --- a/blocksuite/affine/inlines/link/src/inline-spec.ts +++ b/blocksuite/affine/inlines/link/src/inline-spec.ts @@ -9,7 +9,9 @@ export const LinkInlineSpecExtension = const std = provider.get(StdIdentifier); return { name: 'link', - schema: z.string().optional().nullable().catch(undefined), + schema: z.object({ + link: z.string().optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.link; }, diff --git a/blocksuite/affine/inlines/mention/src/inline-spec.ts b/blocksuite/affine/inlines/mention/src/inline-spec.ts index cc4f0ad43..decb2b64c 100644 --- a/blocksuite/affine/inlines/mention/src/inline-spec.ts +++ b/blocksuite/affine/inlines/mention/src/inline-spec.ts @@ -9,14 +9,16 @@ export const MentionInlineSpecExtension = const std = provider.get(StdIdentifier); return { name: 'mention', - schema: z - .object({ - member: z.string(), - notification: z.string().optional(), - }) - .optional() - .nullable() - .catch(undefined), + schema: z.object({ + mention: z + .object({ + member: z.string(), + notification: z.string().optional(), + }) + .optional() + .nullable() + .catch(undefined), + }), match: delta => { return !!delta.attributes?.mention?.member; }, diff --git a/blocksuite/affine/inlines/preset/src/inline-spec.ts b/blocksuite/affine/inlines/preset/src/inline-spec.ts index 2c6e17822..efeadf5e8 100644 --- a/blocksuite/affine/inlines/preset/src/inline-spec.ts +++ b/blocksuite/affine/inlines/preset/src/inline-spec.ts @@ -12,7 +12,9 @@ export type AffineInlineRootElement = InlineRootElement; export const BoldInlineSpecExtension = InlineSpecExtension({ name: 'bold', - schema: z.literal(true).optional().nullable().catch(undefined), + schema: z.object({ + bold: z.literal(true).optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.bold; }, @@ -24,7 +26,9 @@ export const BoldInlineSpecExtension = export const ItalicInlineSpecExtension = InlineSpecExtension({ name: 'italic', - schema: z.literal(true).optional().nullable().catch(undefined), + schema: z.object({ + italic: z.literal(true).optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.italic; }, @@ -36,7 +40,9 @@ export const ItalicInlineSpecExtension = export const UnderlineInlineSpecExtension = InlineSpecExtension({ name: 'underline', - schema: z.literal(true).optional().nullable().catch(undefined), + schema: z.object({ + underline: z.literal(true).optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.underline; }, @@ -48,7 +54,9 @@ export const UnderlineInlineSpecExtension = export const StrikeInlineSpecExtension = InlineSpecExtension({ name: 'strike', - schema: z.literal(true).optional().nullable().catch(undefined), + schema: z.object({ + strike: z.literal(true).optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.strike; }, @@ -60,7 +68,9 @@ export const StrikeInlineSpecExtension = export const CodeInlineSpecExtension = InlineSpecExtension({ name: 'inline-code', - schema: z.literal(true).optional().nullable().catch(undefined), + schema: z.object({ + code: z.literal(true).optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.code; }, @@ -72,7 +82,9 @@ export const CodeInlineSpecExtension = export const BackgroundInlineSpecExtension = InlineSpecExtension({ name: 'background', - schema: z.string().optional().nullable().catch(undefined), + schema: z.object({ + background: z.string().optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.background; }, @@ -84,7 +96,9 @@ export const BackgroundInlineSpecExtension = export const ColorInlineSpecExtension = InlineSpecExtension({ name: 'color', - schema: z.string().optional().nullable().catch(undefined), + schema: z.object({ + color: z.string().optional().nullable().catch(undefined), + }), match: delta => { return !!delta.attributes?.color; }, diff --git a/blocksuite/affine/inlines/reference/src/inline-spec.ts b/blocksuite/affine/inlines/reference/src/inline-spec.ts index 73dab95dc..ff3a960c3 100644 --- a/blocksuite/affine/inlines/reference/src/inline-spec.ts +++ b/blocksuite/affine/inlines/reference/src/inline-spec.ts @@ -27,18 +27,20 @@ export const ReferenceInlineSpecExtension = } return { name: 'reference', - schema: z - .object({ - type: z.enum([ - // @deprecated Subpage is deprecated, use LinkedPage instead - 'Subpage', - 'LinkedPage', - ]), - }) - .merge(ReferenceInfoSchema) - .optional() - .nullable() - .catch(undefined), + schema: z.object({ + reference: z + .object({ + type: z.enum([ + // @deprecated Subpage is deprecated, use LinkedPage instead + 'Subpage', + 'LinkedPage', + ]), + }) + .merge(ReferenceInfoSchema) + .optional() + .nullable() + .catch(undefined), + }), match: delta => { return !!delta.attributes?.reference; }, diff --git a/blocksuite/framework/std/src/inline/extensions/inline-manager.ts b/blocksuite/framework/std/src/inline/extensions/inline-manager.ts index 08a79aec9..810211bea 100644 --- a/blocksuite/framework/std/src/inline/extensions/inline-manager.ts +++ b/blocksuite/framework/std/src/inline/extensions/inline-manager.ts @@ -8,7 +8,7 @@ import { type DeltaInsert, type ExtensionType, } from '@blocksuite/store'; -import { z, type ZodObject, type ZodTypeAny } from 'zod'; +import { z } from 'zod'; import { StdIdentifier } from '../../identifier.js'; import type { BlockStdScope } from '../../scope/index.js'; @@ -42,20 +42,10 @@ export class InlineManager { 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); + getSchema = (): z.ZodSchema => { + const schema = this.specs.reduce((acc, cur) => { + return z.intersection(acc, cur.schema); + }, baseTextAttributes); return schema; }; diff --git a/blocksuite/framework/std/src/inline/extensions/type.ts b/blocksuite/framework/std/src/inline/extensions/type.ts index b7def0574..b66fa5ccf 100644 --- a/blocksuite/framework/std/src/inline/extensions/type.ts +++ b/blocksuite/framework/std/src/inline/extensions/type.ts @@ -5,13 +5,19 @@ import type { } from '@blocksuite/std/inline'; import type { BaseTextAttributes, DeltaInsert } from '@blocksuite/store'; import type * as Y from 'yjs'; -import type { ZodTypeAny } from 'zod'; +import type { AnyZodObject, KeySchema, ZodEffects, ZodRecord } from 'zod'; export type InlineSpecs< TextAttributes extends BaseTextAttributes = BaseTextAttributes, > = { name: keyof TextAttributes | string; - schema: ZodTypeAny; + schema: + | AnyZodObject + | ZodEffects< + ZodRecord, + Partial>, + unknown + >; match: (delta: DeltaInsert) => boolean; renderer: AttributeRenderer; embed?: boolean; diff --git a/blocksuite/framework/std/src/inline/utils/dynamic-schema.ts b/blocksuite/framework/std/src/inline/utils/dynamic-schema.ts new file mode 100644 index 000000000..7f352f754 --- /dev/null +++ b/blocksuite/framework/std/src/inline/utils/dynamic-schema.ts @@ -0,0 +1,26 @@ +import { z, type ZodTypeAny } from 'zod'; + +export function dynamicSchema( + keyValidator: (key: string) => key is Key, + valueType: Value +) { + return z.preprocess( + record => { + // check it is a record + if (typeof record !== 'object' || record === null) { + return {}; + } + + return Object.entries(record) + .filter((data): data is [Key, unknown] => keyValidator(data[0])) + .reduce( + (acc, [key, value]) => { + acc[key] = value; + return acc; + }, + {} as Record + ); + }, + z.record(z.custom(keyValidator), valueType) + ); +} diff --git a/blocksuite/framework/std/src/inline/utils/index.ts b/blocksuite/framework/std/src/inline/utils/index.ts index b16acd543..d475fdcaf 100644 --- a/blocksuite/framework/std/src/inline/utils/index.ts +++ b/blocksuite/framework/std/src/inline/utils/index.ts @@ -1,5 +1,6 @@ export * from './attribute-renderer.js'; export * from './delta-convert.js'; +export * from './dynamic-schema.js'; export * from './embed.js'; export * from './guard.js'; export * from './point-conversion.js';