mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 02:13:00 +08:00
feat(editor): std inline extensions (#11038)
This commit is contained in:
@@ -3,39 +3,44 @@ import {
|
||||
BoldInlineSpecExtension,
|
||||
CodeInlineSpecExtension,
|
||||
ColorInlineSpecExtension,
|
||||
InlineManagerExtension,
|
||||
InlineSpecExtension,
|
||||
ItalicInlineSpecExtension,
|
||||
LatexInlineSpecExtension,
|
||||
LinkInlineSpecExtension,
|
||||
StrikeInlineSpecExtension,
|
||||
UnderlineInlineSpecExtension,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
InlineManagerExtension,
|
||||
InlineSpecExtension,
|
||||
} from '@blocksuite/block-std/inline';
|
||||
import { html } from 'lit';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const CodeBlockUnitSpecExtension = InlineSpecExtension({
|
||||
name: 'code-block-unit',
|
||||
schema: z.undefined(),
|
||||
match: () => true,
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
|
||||
},
|
||||
});
|
||||
export const CodeBlockUnitSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'code-block-unit',
|
||||
schema: z.undefined(),
|
||||
match: () => true,
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const CodeBlockInlineManagerExtension = InlineManagerExtension({
|
||||
id: 'CodeBlockInlineManager',
|
||||
enableMarkdown: false,
|
||||
specs: [
|
||||
BoldInlineSpecExtension.identifier,
|
||||
ItalicInlineSpecExtension.identifier,
|
||||
UnderlineInlineSpecExtension.identifier,
|
||||
StrikeInlineSpecExtension.identifier,
|
||||
CodeInlineSpecExtension.identifier,
|
||||
BackgroundInlineSpecExtension.identifier,
|
||||
ColorInlineSpecExtension.identifier,
|
||||
LatexInlineSpecExtension.identifier,
|
||||
LinkInlineSpecExtension.identifier,
|
||||
CodeBlockUnitSpecExtension.identifier,
|
||||
],
|
||||
});
|
||||
export const CodeBlockInlineManagerExtension =
|
||||
InlineManagerExtension<AffineTextAttributes>({
|
||||
id: 'CodeBlockInlineManager',
|
||||
enableMarkdown: false,
|
||||
specs: [
|
||||
BoldInlineSpecExtension.identifier,
|
||||
ItalicInlineSpecExtension.identifier,
|
||||
UnderlineInlineSpecExtension.identifier,
|
||||
StrikeInlineSpecExtension.identifier,
|
||||
CodeInlineSpecExtension.identifier,
|
||||
BackgroundInlineSpecExtension.identifier,
|
||||
ColorInlineSpecExtension.identifier,
|
||||
LatexInlineSpecExtension.identifier,
|
||||
LinkInlineSpecExtension.identifier,
|
||||
CodeBlockUnitSpecExtension.identifier,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { InlineManagerExtension } from '@blocksuite/block-std/inline';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { InlineManagerExtension } from './extension/index.js';
|
||||
import {
|
||||
BackgroundInlineSpecExtension,
|
||||
BoldInlineSpecExtension,
|
||||
@@ -19,22 +20,23 @@ import {
|
||||
} from './inline/index.js';
|
||||
import { LatexEditorInlineManagerExtension } from './inline/presets/nodes/latex-node/latex-editor-menu.js';
|
||||
|
||||
export const DefaultInlineManagerExtension = InlineManagerExtension({
|
||||
id: 'DefaultInlineManager',
|
||||
specs: [
|
||||
BoldInlineSpecExtension.identifier,
|
||||
ItalicInlineSpecExtension.identifier,
|
||||
UnderlineInlineSpecExtension.identifier,
|
||||
StrikeInlineSpecExtension.identifier,
|
||||
CodeInlineSpecExtension.identifier,
|
||||
BackgroundInlineSpecExtension.identifier,
|
||||
ColorInlineSpecExtension.identifier,
|
||||
LatexInlineSpecExtension.identifier,
|
||||
ReferenceInlineSpecExtension.identifier,
|
||||
LinkInlineSpecExtension.identifier,
|
||||
FootNoteInlineSpecExtension.identifier,
|
||||
],
|
||||
});
|
||||
export const DefaultInlineManagerExtension =
|
||||
InlineManagerExtension<AffineTextAttributes>({
|
||||
id: 'DefaultInlineManager',
|
||||
specs: [
|
||||
BoldInlineSpecExtension.identifier,
|
||||
ItalicInlineSpecExtension.identifier,
|
||||
UnderlineInlineSpecExtension.identifier,
|
||||
StrikeInlineSpecExtension.identifier,
|
||||
CodeInlineSpecExtension.identifier,
|
||||
BackgroundInlineSpecExtension.identifier,
|
||||
ColorInlineSpecExtension.identifier,
|
||||
LatexInlineSpecExtension.identifier,
|
||||
ReferenceInlineSpecExtension.identifier,
|
||||
LinkInlineSpecExtension.identifier,
|
||||
FootNoteInlineSpecExtension.identifier,
|
||||
],
|
||||
});
|
||||
|
||||
export const RichTextExtensions: ExtensionType[] = [
|
||||
InlineSpecExtensions,
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
export * from './inline-manager.js';
|
||||
export * from './inline-spec.js';
|
||||
export * from './markdown-matcher.js';
|
||||
export * from './ref-node-slots.js';
|
||||
export * from './type.js';
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { type BlockStdScope, StdIdentifier } from '@blocksuite/block-std';
|
||||
import {
|
||||
type AttributeRenderer,
|
||||
getDefaultAttributeRenderer,
|
||||
} from '@blocksuite/block-std/inline';
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
} from '@blocksuite/global/di';
|
||||
import {
|
||||
baseTextAttributes,
|
||||
type DeltaInsert,
|
||||
type ExtensionType,
|
||||
} 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<AffineTextAttributes>) => {
|
||||
for (const spec of this.specs) {
|
||||
if (spec.embed && spec.match(delta)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
getRenderer = (): AttributeRenderer<AffineTextAttributes> => {
|
||||
const defaultRenderer = getDefaultAttributeRenderer<AffineTextAttributes>();
|
||||
|
||||
const renderer: AttributeRenderer<AffineTextAttributes> = 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 AffineTextAttributes, ZodTypeAny>> => {
|
||||
const defaultSchema = baseTextAttributes as unknown as ZodObject<
|
||||
Record<keyof AffineTextAttributes, ZodTypeAny>
|
||||
>;
|
||||
|
||||
const schema: ZodObject<Record<keyof AffineTextAttributes, ZodTypeAny>> =
|
||||
this.specs.reduce((acc, cur) => {
|
||||
const currentSchema = z.object({
|
||||
[cur.name]: cur.schema,
|
||||
}) as ZodObject<Record<keyof AffineTextAttributes, ZodTypeAny>>;
|
||||
return acc.merge(currentSchema) as ZodObject<
|
||||
Record<keyof AffineTextAttributes, ZodTypeAny>
|
||||
>;
|
||||
}, defaultSchema);
|
||||
return schema;
|
||||
};
|
||||
|
||||
readonly specs: Array<InlineSpecs<AffineTextAttributes>>;
|
||||
|
||||
constructor(
|
||||
readonly std: BlockStdScope,
|
||||
readonly markdownMatches: InlineMarkdownMatch<AffineTextAttributes>[],
|
||||
...specs: Array<InlineSpecs<AffineTextAttributes>>
|
||||
) {
|
||||
this.specs = specs;
|
||||
}
|
||||
}
|
||||
|
||||
export const InlineManagerIdentifier = createIdentifier<InlineManager>(
|
||||
'AffineInlineManager'
|
||||
);
|
||||
|
||||
export type InlineManagerExtensionConfig = {
|
||||
id: string;
|
||||
enableMarkdown?: boolean;
|
||||
specs: ServiceIdentifier<InlineSpecs<AffineTextAttributes>>[];
|
||||
};
|
||||
|
||||
export function InlineManagerExtension({
|
||||
id,
|
||||
enableMarkdown = true,
|
||||
specs,
|
||||
}: InlineManagerExtensionConfig): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineManager>;
|
||||
} {
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
type ServiceProvider,
|
||||
} from '@blocksuite/global/di';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import type { InlineSpecs } from './type.js';
|
||||
|
||||
export const InlineSpecIdentifier =
|
||||
createIdentifier<InlineSpecs<AffineTextAttributes>>('AffineInlineSpec');
|
||||
|
||||
export function InlineSpecExtension(
|
||||
name: string,
|
||||
getSpec: (provider: ServiceProvider) => InlineSpecs<AffineTextAttributes>
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineSpecs<AffineTextAttributes>>;
|
||||
};
|
||||
export function InlineSpecExtension(
|
||||
spec: InlineSpecs<AffineTextAttributes>
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineSpecs<AffineTextAttributes>>;
|
||||
};
|
||||
export function InlineSpecExtension(
|
||||
nameOrSpec: string | InlineSpecs<AffineTextAttributes>,
|
||||
getSpec?: (provider: ServiceProvider) => InlineSpecs<AffineTextAttributes>
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineSpecs<AffineTextAttributes>>;
|
||||
} {
|
||||
if (typeof nameOrSpec === 'string') {
|
||||
const identifier = InlineSpecIdentifier(nameOrSpec);
|
||||
return {
|
||||
identifier,
|
||||
setup: di => {
|
||||
di.addImpl(identifier, provider => getSpec!(provider));
|
||||
},
|
||||
};
|
||||
}
|
||||
const identifier = InlineSpecIdentifier(nameOrSpec.name);
|
||||
return {
|
||||
identifier,
|
||||
setup: di => {
|
||||
di.addImpl(identifier, nameOrSpec);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
} from '@blocksuite/global/di';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import type { InlineMarkdownMatch } from './type.js';
|
||||
|
||||
export const MarkdownMatcherIdentifier = createIdentifier<
|
||||
InlineMarkdownMatch<AffineTextAttributes>
|
||||
>('AffineMarkdownMatcher');
|
||||
|
||||
export function InlineMarkdownExtension(
|
||||
matcher: InlineMarkdownMatch<AffineTextAttributes>
|
||||
): ExtensionType & {
|
||||
identifier: ServiceIdentifier<InlineMarkdownMatch<AffineTextAttributes>>;
|
||||
} {
|
||||
const identifier = MarkdownMatcherIdentifier(matcher.name);
|
||||
|
||||
return {
|
||||
setup: di => {
|
||||
di.addImpl(identifier, () => ({ ...matcher }));
|
||||
},
|
||||
identifier,
|
||||
};
|
||||
}
|
||||
@@ -2,14 +2,14 @@ import { FootNoteSchema, ReferenceInfoSchema } from '@blocksuite/affine-model';
|
||||
import { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { BlockFlavourIdentifier, StdIdentifier } from '@blocksuite/block-std';
|
||||
import type {
|
||||
InlineEditor,
|
||||
InlineRootElement,
|
||||
import {
|
||||
type InlineEditor,
|
||||
type InlineRootElement,
|
||||
InlineSpecExtension,
|
||||
} from '@blocksuite/block-std/inline';
|
||||
import { html } from 'lit';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { InlineSpecExtension } from '../../extension/index.js';
|
||||
import { FootNoteNodeConfigIdentifier } from './nodes/footnote-node/footnote-config.js';
|
||||
import { builtinInlineLinkToolbarConfig } from './nodes/link-node/configs/toolbar.js';
|
||||
import { builtinInlineReferenceToolbarConfig } from './nodes/reference-node/configs/toolbar.js';
|
||||
@@ -21,86 +21,92 @@ import {
|
||||
export type AffineInlineEditor = InlineEditor<AffineTextAttributes>;
|
||||
export type AffineInlineRootElement = InlineRootElement<AffineTextAttributes>;
|
||||
|
||||
export const BoldInlineSpecExtension = InlineSpecExtension({
|
||||
name: 'bold',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.bold;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
export const BoldInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'bold',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.bold;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const ItalicInlineSpecExtension = InlineSpecExtension({
|
||||
name: 'italic',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.italic;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
export const ItalicInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'italic',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.italic;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const UnderlineInlineSpecExtension = InlineSpecExtension({
|
||||
name: 'underline',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.underline;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
export const UnderlineInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'underline',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.underline;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const StrikeInlineSpecExtension = InlineSpecExtension({
|
||||
name: 'strike',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.strike;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
export const StrikeInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'strike',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.strike;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const CodeInlineSpecExtension = InlineSpecExtension({
|
||||
name: 'code',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.code;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
export const CodeInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'code',
|
||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.code;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const BackgroundInlineSpecExtension = InlineSpecExtension({
|
||||
name: 'background',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.background;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
export const BackgroundInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'background',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.background;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const ColorInlineSpecExtension = InlineSpecExtension({
|
||||
name: 'color',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.color;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
export const ColorInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'color',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.color;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const LatexInlineSpecExtension = InlineSpecExtension(
|
||||
'latex',
|
||||
provider => {
|
||||
export const LatexInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>('latex', provider => {
|
||||
const std = provider.get(StdIdentifier);
|
||||
return {
|
||||
name: 'latex',
|
||||
@@ -118,12 +124,10 @@ export const LatexInlineSpecExtension = InlineSpecExtension(
|
||||
},
|
||||
embed: true,
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export const ReferenceInlineSpecExtension = InlineSpecExtension(
|
||||
'reference',
|
||||
provider => {
|
||||
export const ReferenceInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>('reference', provider => {
|
||||
const std = provider.get(StdIdentifier);
|
||||
const configProvider = new ReferenceNodeConfigProvider(std);
|
||||
const config =
|
||||
@@ -164,35 +168,35 @@ export const ReferenceInlineSpecExtension = InlineSpecExtension(
|
||||
},
|
||||
embed: true,
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export const LinkInlineSpecExtension = InlineSpecExtension('link', provider => {
|
||||
const std = provider.get(StdIdentifier);
|
||||
return {
|
||||
name: 'link',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.link;
|
||||
},
|
||||
export const LinkInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>('link', provider => {
|
||||
const std = provider.get(StdIdentifier);
|
||||
return {
|
||||
name: 'link',
|
||||
schema: z.string().optional().nullable().catch(undefined),
|
||||
match: delta => {
|
||||
return !!delta.attributes?.link;
|
||||
},
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-link .std=${std} .delta=${delta}></affine-link>`;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const LatexEditorUnitSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>({
|
||||
name: 'latex-editor-unit',
|
||||
schema: z.undefined(),
|
||||
match: () => true,
|
||||
renderer: ({ delta }) => {
|
||||
return html`<affine-link .std=${std} .delta=${delta}></affine-link>`;
|
||||
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
export const LatexEditorUnitSpecExtension = InlineSpecExtension({
|
||||
name: 'latex-editor-unit',
|
||||
schema: z.undefined(),
|
||||
match: () => true,
|
||||
renderer: ({ delta }) => {
|
||||
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
|
||||
},
|
||||
});
|
||||
|
||||
export const FootNoteInlineSpecExtension = InlineSpecExtension(
|
||||
'footnote',
|
||||
provider => {
|
||||
export const FootNoteInlineSpecExtension =
|
||||
InlineSpecExtension<AffineTextAttributes>('footnote', provider => {
|
||||
const std = provider.get(StdIdentifier);
|
||||
const config =
|
||||
provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined;
|
||||
@@ -211,8 +215,7 @@ export const FootNoteInlineSpecExtension = InlineSpecExtension(
|
||||
},
|
||||
embed: true,
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export const InlineSpecExtensions = [
|
||||
BoldInlineSpecExtension,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import type { BlockComponent } from '@blocksuite/block-std';
|
||||
import { InlineMarkdownExtension } from '@blocksuite/block-std/inline';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { InlineMarkdownExtension } from '../../extension/markdown-matcher.js';
|
||||
|
||||
// inline markdown match rules:
|
||||
// covert: ***test*** + space
|
||||
// covert: ***t est*** + space
|
||||
@@ -10,63 +10,71 @@ import { InlineMarkdownExtension } from '../../extension/markdown-matcher.js';
|
||||
// not convert: ***test *** + space
|
||||
// not convert: *** test *** + space
|
||||
|
||||
export const BoldItalicMarkdown = InlineMarkdownExtension({
|
||||
name: 'bolditalic',
|
||||
pattern: /.*\*{3}([^\s*][^*]*[^\s*])\*{3}$|.*\*{3}([^\s*])\*{3}$/,
|
||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||
const match = prefixText.match(pattern);
|
||||
if (!match) return;
|
||||
export const BoldItalicMarkdown = InlineMarkdownExtension<AffineTextAttributes>(
|
||||
{
|
||||
name: 'bolditalic',
|
||||
pattern: /.*\*{3}([^\s*][^*]*[^\s*])\*{3}$|.*\*{3}([^\s*])\*{3}$/,
|
||||
action: ({
|
||||
inlineEditor,
|
||||
prefixText,
|
||||
inlineRange,
|
||||
pattern,
|
||||
undoManager,
|
||||
}) => {
|
||||
const match = prefixText.match(pattern);
|
||||
if (!match) return;
|
||||
|
||||
const targetText = match[1] ?? match[2];
|
||||
const annotatedText = match[0].slice(-targetText.length - 3 * 2);
|
||||
const startIndex = inlineRange.index - annotatedText.length;
|
||||
const targetText = match[1] ?? match[2];
|
||||
const annotatedText = match[0].slice(-targetText.length - 3 * 2);
|
||||
const startIndex = inlineRange.index - annotatedText.length;
|
||||
|
||||
inlineEditor.insertText(
|
||||
{
|
||||
index: startIndex + annotatedText.length,
|
||||
inlineEditor.insertText(
|
||||
{
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 0,
|
||||
},
|
||||
' '
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length + 1,
|
||||
length: 0,
|
||||
},
|
||||
' '
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length + 1,
|
||||
length: 0,
|
||||
});
|
||||
});
|
||||
|
||||
undoManager.stopCapturing();
|
||||
undoManager.stopCapturing();
|
||||
|
||||
inlineEditor.formatText(
|
||||
{
|
||||
inlineEditor.formatText(
|
||||
{
|
||||
index: startIndex,
|
||||
length: annotatedText.length,
|
||||
},
|
||||
{
|
||||
bold: true,
|
||||
italic: true,
|
||||
}
|
||||
);
|
||||
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length - 3,
|
||||
length: 3,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex,
|
||||
length: annotatedText.length,
|
||||
},
|
||||
{
|
||||
bold: true,
|
||||
italic: true,
|
||||
}
|
||||
);
|
||||
length: 3,
|
||||
});
|
||||
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length - 3,
|
||||
length: 3,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex,
|
||||
length: 3,
|
||||
});
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length - 6,
|
||||
length: 0,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length - 6,
|
||||
length: 0,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const BoldMarkdown = InlineMarkdownExtension({
|
||||
export const BoldMarkdown = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
name: 'bold',
|
||||
pattern: /.*\*{2}([^\s][^*]*[^\s*])\*{2}$|.*\*{2}([^\s*])\*{2}$/,
|
||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||
@@ -121,7 +129,7 @@ export const BoldMarkdown = InlineMarkdownExtension({
|
||||
},
|
||||
});
|
||||
|
||||
export const ItalicExtension = InlineMarkdownExtension({
|
||||
export const ItalicExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
name: 'italic',
|
||||
pattern: /.*\*{1}([^\s][^*]*[^\s*])\*{1}$|.*\*{1}([^\s*])\*{1}$/,
|
||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||
@@ -176,117 +184,131 @@ export const ItalicExtension = InlineMarkdownExtension({
|
||||
},
|
||||
});
|
||||
|
||||
export const StrikethroughExtension = InlineMarkdownExtension({
|
||||
name: 'strikethrough',
|
||||
pattern: /.*~{2}([^\s][^~]*[^\s])~{2}$|.*~{2}([^\s~])~{2}$/,
|
||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||
const match = prefixText.match(pattern);
|
||||
if (!match) return;
|
||||
export const StrikethroughExtension =
|
||||
InlineMarkdownExtension<AffineTextAttributes>({
|
||||
name: 'strikethrough',
|
||||
pattern: /.*~{2}([^\s][^~]*[^\s])~{2}$|.*~{2}([^\s~])~{2}$/,
|
||||
action: ({
|
||||
inlineEditor,
|
||||
prefixText,
|
||||
inlineRange,
|
||||
pattern,
|
||||
undoManager,
|
||||
}) => {
|
||||
const match = prefixText.match(pattern);
|
||||
if (!match) return;
|
||||
|
||||
const targetText = match[1] ?? match[2];
|
||||
const annotatedText = match[0].slice(-targetText.length - 2 * 2);
|
||||
const startIndex = inlineRange.index - annotatedText.length;
|
||||
const targetText = match[1] ?? match[2];
|
||||
const annotatedText = match[0].slice(-targetText.length - 2 * 2);
|
||||
const startIndex = inlineRange.index - annotatedText.length;
|
||||
|
||||
inlineEditor.insertText(
|
||||
{
|
||||
index: startIndex + annotatedText.length,
|
||||
inlineEditor.insertText(
|
||||
{
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 0,
|
||||
},
|
||||
' '
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length + 1,
|
||||
length: 0,
|
||||
},
|
||||
' '
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length + 1,
|
||||
length: 0,
|
||||
});
|
||||
});
|
||||
|
||||
undoManager.stopCapturing();
|
||||
undoManager.stopCapturing();
|
||||
|
||||
inlineEditor.formatText(
|
||||
{
|
||||
index: startIndex,
|
||||
length: annotatedText.length,
|
||||
},
|
||||
{
|
||||
strike: true,
|
||||
}
|
||||
);
|
||||
inlineEditor.formatText(
|
||||
{
|
||||
index: startIndex,
|
||||
length: annotatedText.length,
|
||||
},
|
||||
{
|
||||
strike: true,
|
||||
}
|
||||
);
|
||||
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length - 2,
|
||||
length: 2,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex,
|
||||
length: 2,
|
||||
});
|
||||
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length - 4,
|
||||
length: 0,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const UnderthroughExtension = InlineMarkdownExtension({
|
||||
name: 'underthrough',
|
||||
pattern: /.*~{1}([^\s][^~]*[^\s~])~{1}$|.*~{1}([^\s~])~{1}$/,
|
||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||
const match = prefixText.match(pattern);
|
||||
if (!match) return;
|
||||
|
||||
const targetText = match[1] ?? match[2];
|
||||
const annotatedText = match[0].slice(-targetText.length - 1 * 2);
|
||||
const startIndex = inlineRange.index - annotatedText.length;
|
||||
|
||||
inlineEditor.insertText(
|
||||
{
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 0,
|
||||
},
|
||||
' '
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length + 1,
|
||||
length: 0,
|
||||
});
|
||||
|
||||
undoManager.stopCapturing();
|
||||
|
||||
inlineEditor.formatText(
|
||||
{
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length - 2,
|
||||
length: 2,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex,
|
||||
length: annotatedText.length,
|
||||
},
|
||||
{
|
||||
underline: true,
|
||||
}
|
||||
);
|
||||
length: 2,
|
||||
});
|
||||
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: inlineRange.index - 1,
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex,
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length - 4,
|
||||
length: 0,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length - 2,
|
||||
length: 0,
|
||||
});
|
||||
},
|
||||
});
|
||||
export const UnderthroughExtension =
|
||||
InlineMarkdownExtension<AffineTextAttributes>({
|
||||
name: 'underthrough',
|
||||
pattern: /.*~{1}([^\s][^~]*[^\s~])~{1}$|.*~{1}([^\s~])~{1}$/,
|
||||
action: ({
|
||||
inlineEditor,
|
||||
prefixText,
|
||||
inlineRange,
|
||||
pattern,
|
||||
undoManager,
|
||||
}) => {
|
||||
const match = prefixText.match(pattern);
|
||||
if (!match) return;
|
||||
|
||||
export const CodeExtension = InlineMarkdownExtension({
|
||||
const targetText = match[1] ?? match[2];
|
||||
const annotatedText = match[0].slice(-targetText.length - 1 * 2);
|
||||
const startIndex = inlineRange.index - annotatedText.length;
|
||||
|
||||
inlineEditor.insertText(
|
||||
{
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 0,
|
||||
},
|
||||
' '
|
||||
);
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length + 1,
|
||||
length: 0,
|
||||
});
|
||||
|
||||
undoManager.stopCapturing();
|
||||
|
||||
inlineEditor.formatText(
|
||||
{
|
||||
index: startIndex,
|
||||
length: annotatedText.length,
|
||||
},
|
||||
{
|
||||
underline: true,
|
||||
}
|
||||
);
|
||||
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex + annotatedText.length,
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: inlineRange.index - 1,
|
||||
length: 1,
|
||||
});
|
||||
inlineEditor.deleteText({
|
||||
index: startIndex,
|
||||
length: 1,
|
||||
});
|
||||
|
||||
inlineEditor.setInlineRange({
|
||||
index: startIndex + annotatedText.length - 2,
|
||||
length: 0,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const CodeExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
name: 'code',
|
||||
pattern: /.*`([^\s][^`]*[^\s])`$|.*`([^\s`])`$/,
|
||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||
@@ -341,7 +363,7 @@ export const CodeExtension = InlineMarkdownExtension({
|
||||
},
|
||||
});
|
||||
|
||||
export const LinkExtension = InlineMarkdownExtension({
|
||||
export const LinkExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
name: 'link',
|
||||
pattern: /.*\[(.+?)\]\((.+?)\)$/,
|
||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||
@@ -401,7 +423,7 @@ export const LinkExtension = InlineMarkdownExtension({
|
||||
},
|
||||
});
|
||||
|
||||
export const LatexExtension = InlineMarkdownExtension({
|
||||
export const LatexExtension = InlineMarkdownExtension<AffineTextAttributes>({
|
||||
name: 'latex',
|
||||
|
||||
pattern:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ColorScheme } from '@blocksuite/affine-model';
|
||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/block-std';
|
||||
import { InlineManagerExtension } from '@blocksuite/block-std/inline';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||
import { noop } from '@blocksuite/global/utils';
|
||||
import { DoneIcon } from '@blocksuite/icons/lit';
|
||||
@@ -11,14 +13,14 @@ import { property } from 'lit/decorators.js';
|
||||
import { codeToTokensBase, type ThemedToken } from 'shiki';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { InlineManagerExtension } from '../../../../extension/index.js';
|
||||
import { LatexEditorUnitSpecExtension } from '../../affine-inline-specs.js';
|
||||
|
||||
export const LatexEditorInlineManagerExtension = InlineManagerExtension({
|
||||
id: 'latex-inline-editor',
|
||||
enableMarkdown: false,
|
||||
specs: [LatexEditorUnitSpecExtension.identifier],
|
||||
});
|
||||
export const LatexEditorInlineManagerExtension =
|
||||
InlineManagerExtension<AffineTextAttributes>({
|
||||
id: 'latex-inline-editor',
|
||||
enableMarkdown: false,
|
||||
specs: [LatexEditorUnitSpecExtension.identifier],
|
||||
});
|
||||
|
||||
export class LatexEditorMenu extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ShadowlessElement } from '@blocksuite/block-std';
|
||||
import {
|
||||
type AttributeRenderer,
|
||||
InlineEditor,
|
||||
type InlineMarkdownMatch,
|
||||
type InlineRange,
|
||||
type InlineRangeProvider,
|
||||
type VLine,
|
||||
@@ -17,7 +18,6 @@ import { classMap } from 'lit/directives/class-map.js';
|
||||
import * as Y from 'yjs';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { InlineMarkdownMatch } from './extension/type.js';
|
||||
import { onVBeforeinput, onVCompositionEnd } from './hooks.js';
|
||||
import type { AffineInlineEditor } from './inline/index.js';
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -8,12 +8,12 @@ import type * as Y from 'yjs';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
|
||||
export type InlineSpecs<
|
||||
AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
> = {
|
||||
name: keyof AffineTextAttributes | string;
|
||||
name: keyof TextAttributes | string;
|
||||
schema: ZodTypeAny;
|
||||
match: (delta: DeltaInsert<AffineTextAttributes>) => boolean;
|
||||
renderer: AttributeRenderer<AffineTextAttributes>;
|
||||
match: (delta: DeltaInsert<TextAttributes>) => boolean;
|
||||
renderer: AttributeRenderer<TextAttributes>;
|
||||
embed?: boolean;
|
||||
};
|
||||
|
||||
@@ -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