refactor(editor): support dynamic text attribute key (#12947)
#### PR Dependency Tree * **PR #12946** * **PR #12947** 👈 * **PR #12948** This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal)
This commit is contained in:
@@ -20,7 +20,9 @@ import { z } from 'zod';
|
|||||||
export const CodeBlockUnitSpecExtension =
|
export const CodeBlockUnitSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'code-block-unit',
|
name: 'code-block-unit',
|
||||||
schema: z.undefined(),
|
schema: z.object({
|
||||||
|
'code-block-uint': z.undefined(),
|
||||||
|
}),
|
||||||
match: () => true,
|
match: () => true,
|
||||||
renderer: ({ delta }) => {
|
renderer: ({ delta }) => {
|
||||||
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
|
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
|||||||
import { StdIdentifier } from '@blocksuite/std';
|
import { StdIdentifier } from '@blocksuite/std';
|
||||||
import { InlineSpecExtension } from '@blocksuite/std/inline';
|
import { InlineSpecExtension } from '@blocksuite/std/inline';
|
||||||
import { html } from 'lit';
|
import { html } from 'lit';
|
||||||
|
import z from 'zod';
|
||||||
|
|
||||||
import { FootNoteNodeConfigIdentifier } from './footnote-node/footnote-config';
|
import { FootNoteNodeConfigIdentifier } from './footnote-node/footnote-config';
|
||||||
|
|
||||||
@@ -13,7 +14,9 @@ export const FootNoteInlineSpecExtension =
|
|||||||
provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined;
|
provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined;
|
||||||
return {
|
return {
|
||||||
name: 'footnote',
|
name: 'footnote',
|
||||||
schema: FootNoteSchema.optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
footnote: FootNoteSchema.optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.footnote;
|
return !!delta.attributes?.footnote;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ export const LatexInlineSpecExtension =
|
|||||||
const std = provider.get(StdIdentifier);
|
const std = provider.get(StdIdentifier);
|
||||||
return {
|
return {
|
||||||
name: 'latex',
|
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',
|
match: delta => typeof delta.attributes?.latex === 'string',
|
||||||
renderer: ({ delta, selected, editor, startOffset, endOffset }) => {
|
renderer: ({ delta, selected, editor, startOffset, endOffset }) => {
|
||||||
return html`<affine-latex-node
|
return html`<affine-latex-node
|
||||||
@@ -28,7 +30,9 @@ export const LatexInlineSpecExtension =
|
|||||||
export const LatexEditorUnitSpecExtension =
|
export const LatexEditorUnitSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'latex-editor-unit',
|
name: 'latex-editor-unit',
|
||||||
schema: z.undefined(),
|
schema: z.object({
|
||||||
|
'latex-editor-unit': z.undefined(),
|
||||||
|
}),
|
||||||
match: () => true,
|
match: () => true,
|
||||||
renderer: ({ delta }) => {
|
renderer: ({ delta }) => {
|
||||||
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
|
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ export const LinkInlineSpecExtension =
|
|||||||
const std = provider.get(StdIdentifier);
|
const std = provider.get(StdIdentifier);
|
||||||
return {
|
return {
|
||||||
name: 'link',
|
name: 'link',
|
||||||
schema: z.string().optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
link: z.string().optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.link;
|
return !!delta.attributes?.link;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ export const MentionInlineSpecExtension =
|
|||||||
const std = provider.get(StdIdentifier);
|
const std = provider.get(StdIdentifier);
|
||||||
return {
|
return {
|
||||||
name: 'mention',
|
name: 'mention',
|
||||||
schema: z
|
schema: z.object({
|
||||||
.object({
|
mention: z
|
||||||
member: z.string(),
|
.object({
|
||||||
notification: z.string().optional(),
|
member: z.string(),
|
||||||
})
|
notification: z.string().optional(),
|
||||||
.optional()
|
})
|
||||||
.nullable()
|
.optional()
|
||||||
.catch(undefined),
|
.nullable()
|
||||||
|
.catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.mention?.member;
|
return !!delta.attributes?.mention?.member;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export type AffineInlineRootElement = InlineRootElement<AffineTextAttributes>;
|
|||||||
export const BoldInlineSpecExtension =
|
export const BoldInlineSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'bold',
|
name: 'bold',
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
bold: z.literal(true).optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.bold;
|
return !!delta.attributes?.bold;
|
||||||
},
|
},
|
||||||
@@ -24,7 +26,9 @@ export const BoldInlineSpecExtension =
|
|||||||
export const ItalicInlineSpecExtension =
|
export const ItalicInlineSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'italic',
|
name: 'italic',
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
italic: z.literal(true).optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.italic;
|
return !!delta.attributes?.italic;
|
||||||
},
|
},
|
||||||
@@ -36,7 +40,9 @@ export const ItalicInlineSpecExtension =
|
|||||||
export const UnderlineInlineSpecExtension =
|
export const UnderlineInlineSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'underline',
|
name: 'underline',
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
underline: z.literal(true).optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.underline;
|
return !!delta.attributes?.underline;
|
||||||
},
|
},
|
||||||
@@ -48,7 +54,9 @@ export const UnderlineInlineSpecExtension =
|
|||||||
export const StrikeInlineSpecExtension =
|
export const StrikeInlineSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'strike',
|
name: 'strike',
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
strike: z.literal(true).optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.strike;
|
return !!delta.attributes?.strike;
|
||||||
},
|
},
|
||||||
@@ -60,7 +68,9 @@ export const StrikeInlineSpecExtension =
|
|||||||
export const CodeInlineSpecExtension =
|
export const CodeInlineSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'inline-code',
|
name: 'inline-code',
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
code: z.literal(true).optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.code;
|
return !!delta.attributes?.code;
|
||||||
},
|
},
|
||||||
@@ -72,7 +82,9 @@ export const CodeInlineSpecExtension =
|
|||||||
export const BackgroundInlineSpecExtension =
|
export const BackgroundInlineSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'background',
|
name: 'background',
|
||||||
schema: z.string().optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
background: z.string().optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.background;
|
return !!delta.attributes?.background;
|
||||||
},
|
},
|
||||||
@@ -84,7 +96,9 @@ export const BackgroundInlineSpecExtension =
|
|||||||
export const ColorInlineSpecExtension =
|
export const ColorInlineSpecExtension =
|
||||||
InlineSpecExtension<AffineTextAttributes>({
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
name: 'color',
|
name: 'color',
|
||||||
schema: z.string().optional().nullable().catch(undefined),
|
schema: z.object({
|
||||||
|
color: z.string().optional().nullable().catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.color;
|
return !!delta.attributes?.color;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,18 +27,20 @@ export const ReferenceInlineSpecExtension =
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name: 'reference',
|
name: 'reference',
|
||||||
schema: z
|
schema: z.object({
|
||||||
.object({
|
reference: z
|
||||||
type: z.enum([
|
.object({
|
||||||
// @deprecated Subpage is deprecated, use LinkedPage instead
|
type: z.enum([
|
||||||
'Subpage',
|
// @deprecated Subpage is deprecated, use LinkedPage instead
|
||||||
'LinkedPage',
|
'Subpage',
|
||||||
]),
|
'LinkedPage',
|
||||||
})
|
]),
|
||||||
.merge(ReferenceInfoSchema)
|
})
|
||||||
.optional()
|
.merge(ReferenceInfoSchema)
|
||||||
.nullable()
|
.optional()
|
||||||
.catch(undefined),
|
.nullable()
|
||||||
|
.catch(undefined),
|
||||||
|
}),
|
||||||
match: delta => {
|
match: delta => {
|
||||||
return !!delta.attributes?.reference;
|
return !!delta.attributes?.reference;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
type DeltaInsert,
|
type DeltaInsert,
|
||||||
type ExtensionType,
|
type ExtensionType,
|
||||||
} from '@blocksuite/store';
|
} from '@blocksuite/store';
|
||||||
import { z, type ZodObject, type ZodTypeAny } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { StdIdentifier } from '../../identifier.js';
|
import { StdIdentifier } from '../../identifier.js';
|
||||||
import type { BlockStdScope } from '../../scope/index.js';
|
import type { BlockStdScope } from '../../scope/index.js';
|
||||||
@@ -42,20 +42,10 @@ export class InlineManager<TextAttributes extends BaseTextAttributes> {
|
|||||||
return renderer;
|
return renderer;
|
||||||
};
|
};
|
||||||
|
|
||||||
getSchema = (): ZodObject<Record<keyof TextAttributes, ZodTypeAny>> => {
|
getSchema = (): z.ZodSchema => {
|
||||||
const defaultSchema = baseTextAttributes as unknown as ZodObject<
|
const schema = this.specs.reduce<z.ZodSchema>((acc, cur) => {
|
||||||
Record<keyof TextAttributes, ZodTypeAny>
|
return z.intersection(acc, cur.schema);
|
||||||
>;
|
}, baseTextAttributes);
|
||||||
|
|
||||||
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;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,19 @@ import type {
|
|||||||
} from '@blocksuite/std/inline';
|
} from '@blocksuite/std/inline';
|
||||||
import type { BaseTextAttributes, DeltaInsert } from '@blocksuite/store';
|
import type { BaseTextAttributes, DeltaInsert } from '@blocksuite/store';
|
||||||
import type * as Y from 'yjs';
|
import type * as Y from 'yjs';
|
||||||
import type { ZodTypeAny } from 'zod';
|
import type { AnyZodObject, KeySchema, ZodEffects, ZodRecord } from 'zod';
|
||||||
|
|
||||||
export type InlineSpecs<
|
export type InlineSpecs<
|
||||||
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||||
> = {
|
> = {
|
||||||
name: keyof TextAttributes | string;
|
name: keyof TextAttributes | string;
|
||||||
schema: ZodTypeAny;
|
schema:
|
||||||
|
| AnyZodObject
|
||||||
|
| ZodEffects<
|
||||||
|
ZodRecord<KeySchema>,
|
||||||
|
Partial<Record<string, unknown>>,
|
||||||
|
unknown
|
||||||
|
>;
|
||||||
match: (delta: DeltaInsert<TextAttributes>) => boolean;
|
match: (delta: DeltaInsert<TextAttributes>) => boolean;
|
||||||
renderer: AttributeRenderer<TextAttributes>;
|
renderer: AttributeRenderer<TextAttributes>;
|
||||||
embed?: boolean;
|
embed?: boolean;
|
||||||
|
|||||||
26
blocksuite/framework/std/src/inline/utils/dynamic-schema.ts
Normal file
26
blocksuite/framework/std/src/inline/utils/dynamic-schema.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { z, type ZodTypeAny } from 'zod';
|
||||||
|
|
||||||
|
export function dynamicSchema<Key extends string, Value extends ZodTypeAny>(
|
||||||
|
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<Key, unknown>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
z.record(z.custom<Key>(keyValidator), valueType)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './attribute-renderer.js';
|
export * from './attribute-renderer.js';
|
||||||
export * from './delta-convert.js';
|
export * from './delta-convert.js';
|
||||||
|
export * from './dynamic-schema.js';
|
||||||
export * from './embed.js';
|
export * from './embed.js';
|
||||||
export * from './guard.js';
|
export * from './guard.js';
|
||||||
export * from './point-conversion.js';
|
export * from './point-conversion.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user