diff --git a/blocksuite/affine/blocks/block-code/src/code-block-inline.ts b/blocksuite/affine/blocks/block-code/src/code-block-inline.ts
index 302972cfb2..63f585b852 100644
--- a/blocksuite/affine/blocks/block-code/src/code-block-inline.ts
+++ b/blocksuite/affine/blocks/block-code/src/code-block-inline.ts
@@ -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``;
- },
-});
+export const CodeBlockUnitSpecExtension =
+ InlineSpecExtension({
+ name: 'code-block-unit',
+ schema: z.undefined(),
+ match: () => true,
+ renderer: ({ delta }) => {
+ return html``;
+ },
+ });
-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({
+ 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,
+ ],
+ });
diff --git a/blocksuite/affine/rich-text/src/all-extensions.ts b/blocksuite/affine/rich-text/src/all-extensions.ts
index a0213db486..7d909186b6 100644
--- a/blocksuite/affine/rich-text/src/all-extensions.ts
+++ b/blocksuite/affine/rich-text/src/all-extensions.ts
@@ -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({
+ 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,
diff --git a/blocksuite/affine/rich-text/src/extension/index.ts b/blocksuite/affine/rich-text/src/extension/index.ts
index 5d8771e273..5e8d60898e 100644
--- a/blocksuite/affine/rich-text/src/extension/index.ts
+++ b/blocksuite/affine/rich-text/src/extension/index.ts
@@ -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';
diff --git a/blocksuite/affine/rich-text/src/extension/inline-manager.ts b/blocksuite/affine/rich-text/src/extension/inline-manager.ts
deleted file mode 100644
index 77bfc49487..0000000000
--- a/blocksuite/affine/rich-text/src/extension/inline-manager.ts
+++ /dev/null
@@ -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) => {
- 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;
- };
-
- 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,
- };
-}
diff --git a/blocksuite/affine/rich-text/src/extension/inline-spec.ts b/blocksuite/affine/rich-text/src/extension/inline-spec.ts
deleted file mode 100644
index cc291bd405..0000000000
--- a/blocksuite/affine/rich-text/src/extension/inline-spec.ts
+++ /dev/null
@@ -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>('AffineInlineSpec');
-
-export function InlineSpecExtension(
- name: string,
- getSpec: (provider: ServiceProvider) => InlineSpecs
-): ExtensionType & {
- identifier: ServiceIdentifier>;
-};
-export function InlineSpecExtension(
- spec: InlineSpecs
-): ExtensionType & {
- identifier: ServiceIdentifier>;
-};
-export function InlineSpecExtension(
- nameOrSpec: string | InlineSpecs,
- getSpec?: (provider: ServiceProvider) => InlineSpecs
-): ExtensionType & {
- identifier: ServiceIdentifier>;
-} {
- 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);
- },
- };
-}
diff --git a/blocksuite/affine/rich-text/src/extension/markdown-matcher.ts b/blocksuite/affine/rich-text/src/extension/markdown-matcher.ts
deleted file mode 100644
index b63018537f..0000000000
--- a/blocksuite/affine/rich-text/src/extension/markdown-matcher.ts
+++ /dev/null
@@ -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
->('AffineMarkdownMatcher');
-
-export function InlineMarkdownExtension(
- matcher: InlineMarkdownMatch
-): ExtensionType & {
- identifier: ServiceIdentifier>;
-} {
- const identifier = MarkdownMatcherIdentifier(matcher.name);
-
- return {
- setup: di => {
- di.addImpl(identifier, () => ({ ...matcher }));
- },
- identifier,
- };
-}
diff --git a/blocksuite/affine/rich-text/src/inline/presets/affine-inline-specs.ts b/blocksuite/affine/rich-text/src/inline/presets/affine-inline-specs.ts
index c6baf1e59f..417b92e2b3 100644
--- a/blocksuite/affine/rich-text/src/inline/presets/affine-inline-specs.ts
+++ b/blocksuite/affine/rich-text/src/inline/presets/affine-inline-specs.ts
@@ -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;
export type AffineInlineRootElement = InlineRootElement;
-export const BoldInlineSpecExtension = InlineSpecExtension({
- name: 'bold',
- schema: z.literal(true).optional().nullable().catch(undefined),
- match: delta => {
- return !!delta.attributes?.bold;
- },
- renderer: ({ delta }) => {
- return html``;
- },
-});
+export const BoldInlineSpecExtension =
+ InlineSpecExtension({
+ name: 'bold',
+ schema: z.literal(true).optional().nullable().catch(undefined),
+ match: delta => {
+ return !!delta.attributes?.bold;
+ },
+ renderer: ({ delta }) => {
+ return html``;
+ },
+ });
-export const ItalicInlineSpecExtension = InlineSpecExtension({
- name: 'italic',
- schema: z.literal(true).optional().nullable().catch(undefined),
- match: delta => {
- return !!delta.attributes?.italic;
- },
- renderer: ({ delta }) => {
- return html``;
- },
-});
+export const ItalicInlineSpecExtension =
+ InlineSpecExtension({
+ name: 'italic',
+ schema: z.literal(true).optional().nullable().catch(undefined),
+ match: delta => {
+ return !!delta.attributes?.italic;
+ },
+ renderer: ({ delta }) => {
+ return html``;
+ },
+ });
-export const UnderlineInlineSpecExtension = InlineSpecExtension({
- name: 'underline',
- schema: z.literal(true).optional().nullable().catch(undefined),
- match: delta => {
- return !!delta.attributes?.underline;
- },
- renderer: ({ delta }) => {
- return html``;
- },
-});
+export const UnderlineInlineSpecExtension =
+ InlineSpecExtension({
+ name: 'underline',
+ schema: z.literal(true).optional().nullable().catch(undefined),
+ match: delta => {
+ return !!delta.attributes?.underline;
+ },
+ renderer: ({ delta }) => {
+ return html``;
+ },
+ });
-export const StrikeInlineSpecExtension = InlineSpecExtension({
- name: 'strike',
- schema: z.literal(true).optional().nullable().catch(undefined),
- match: delta => {
- return !!delta.attributes?.strike;
- },
- renderer: ({ delta }) => {
- return html``;
- },
-});
+export const StrikeInlineSpecExtension =
+ InlineSpecExtension({
+ name: 'strike',
+ schema: z.literal(true).optional().nullable().catch(undefined),
+ match: delta => {
+ return !!delta.attributes?.strike;
+ },
+ renderer: ({ delta }) => {
+ return html``;
+ },
+ });
-export const CodeInlineSpecExtension = InlineSpecExtension({
- name: 'code',
- schema: z.literal(true).optional().nullable().catch(undefined),
- match: delta => {
- return !!delta.attributes?.code;
- },
- renderer: ({ delta }) => {
- return html``;
- },
-});
+export const CodeInlineSpecExtension =
+ InlineSpecExtension({
+ name: 'code',
+ schema: z.literal(true).optional().nullable().catch(undefined),
+ match: delta => {
+ return !!delta.attributes?.code;
+ },
+ renderer: ({ delta }) => {
+ return html``;
+ },
+ });
-export const BackgroundInlineSpecExtension = InlineSpecExtension({
- name: 'background',
- schema: z.string().optional().nullable().catch(undefined),
- match: delta => {
- return !!delta.attributes?.background;
- },
- renderer: ({ delta }) => {
- return html``;
- },
-});
+export const BackgroundInlineSpecExtension =
+ InlineSpecExtension({
+ name: 'background',
+ schema: z.string().optional().nullable().catch(undefined),
+ match: delta => {
+ return !!delta.attributes?.background;
+ },
+ renderer: ({ delta }) => {
+ return html``;
+ },
+ });
-export const ColorInlineSpecExtension = InlineSpecExtension({
- name: 'color',
- schema: z.string().optional().nullable().catch(undefined),
- match: delta => {
- return !!delta.attributes?.color;
- },
- renderer: ({ delta }) => {
- return html``;
- },
-});
+export const ColorInlineSpecExtension =
+ InlineSpecExtension({
+ name: 'color',
+ schema: z.string().optional().nullable().catch(undefined),
+ match: delta => {
+ return !!delta.attributes?.color;
+ },
+ renderer: ({ delta }) => {
+ return html``;
+ },
+ });
-export const LatexInlineSpecExtension = InlineSpecExtension(
- 'latex',
- provider => {
+export const LatexInlineSpecExtension =
+ InlineSpecExtension('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('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('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``;
+ },
+ };
+ });
+
+export const LatexEditorUnitSpecExtension =
+ InlineSpecExtension({
+ name: 'latex-editor-unit',
+ schema: z.undefined(),
+ match: () => true,
renderer: ({ delta }) => {
- return html``;
+ return html``;
},
- };
-});
+ });
-export const LatexEditorUnitSpecExtension = InlineSpecExtension({
- name: 'latex-editor-unit',
- schema: z.undefined(),
- match: () => true,
- renderer: ({ delta }) => {
- return html``;
- },
-});
-
-export const FootNoteInlineSpecExtension = InlineSpecExtension(
- 'footnote',
- provider => {
+export const FootNoteInlineSpecExtension =
+ InlineSpecExtension('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,
diff --git a/blocksuite/affine/rich-text/src/inline/presets/markdown.ts b/blocksuite/affine/rich-text/src/inline/presets/markdown.ts
index 4c1b2d41ce..689f3f49be 100644
--- a/blocksuite/affine/rich-text/src/inline/presets/markdown.ts
+++ b/blocksuite/affine/rich-text/src/inline/presets/markdown.ts
@@ -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(
+ {
+ 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({
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({
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({
+ 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({
+ 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({
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({
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({
name: 'latex',
pattern:
diff --git a/blocksuite/affine/rich-text/src/inline/presets/nodes/latex-node/latex-editor-menu.ts b/blocksuite/affine/rich-text/src/inline/presets/nodes/latex-node/latex-editor-menu.ts
index 57b2b80367..2b73993e55 100644
--- a/blocksuite/affine/rich-text/src/inline/presets/nodes/latex-node/latex-editor-menu.ts
+++ b/blocksuite/affine/rich-text/src/inline/presets/nodes/latex-node/latex-editor-menu.ts
@@ -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({
+ id: 'latex-inline-editor',
+ enableMarkdown: false,
+ specs: [LatexEditorUnitSpecExtension.identifier],
+ });
export class LatexEditorMenu extends SignalWatcher(
WithDisposable(ShadowlessElement)
diff --git a/blocksuite/affine/rich-text/src/rich-text.ts b/blocksuite/affine/rich-text/src/rich-text.ts
index ac45221b72..8145d0dc43 100644
--- a/blocksuite/affine/rich-text/src/rich-text.ts
+++ b/blocksuite/affine/rich-text/src/rich-text.ts
@@ -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';
diff --git a/blocksuite/framework/block-std/src/inline/extensions/index.ts b/blocksuite/framework/block-std/src/inline/extensions/index.ts
new file mode 100644
index 0000000000..945e318216
--- /dev/null
+++ b/blocksuite/framework/block-std/src/inline/extensions/index.ts
@@ -0,0 +1,4 @@
+export * from './inline-manager';
+export * from './inline-spec';
+export * from './markdown-matcher';
+export * from './type';
diff --git a/blocksuite/framework/block-std/src/inline/extensions/inline-manager.ts b/blocksuite/framework/block-std/src/inline/extensions/inline-manager.ts
new file mode 100644
index 0000000000..08a79aec98
--- /dev/null
+++ b/blocksuite/framework/block-std/src/inline/extensions/inline-manager.ts
@@ -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 {
+ 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;
+ };
+
+ get markdownMatches(): InlineMarkdownMatch[] {
+ if (!this.enableMarkdown) {
+ return [];
+ }
+ const matches = Array.from(
+ this.std.provider.getAll(MarkdownMatcherIdentifier).values()
+ );
+ return matches as InlineMarkdownMatch[];
+ }
+
+ readonly specs: Array>;
+
+ constructor(
+ readonly std: BlockStdScope,
+ readonly enableMarkdown: boolean,
+ ...specs: Array>
+ ) {
+ this.specs = specs;
+ }
+}
+
+export type InlineManagerExtensionConfig<
+ TextAttributes extends BaseTextAttributes,
+> = {
+ id: string;
+ enableMarkdown?: boolean;
+ specs: ServiceIdentifier>[];
+};
+
+const InlineManagerIdentifier = createIdentifier(
+ 'AffineInlineManager'
+);
+
+export function InlineManagerExtension<
+ TextAttributes extends BaseTextAttributes,
+>({
+ 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,
+ ...specs.map(spec => provider.get(spec))
+ );
+ });
+ },
+ identifier,
+ };
+}
diff --git a/blocksuite/framework/block-std/src/inline/extensions/inline-spec.ts b/blocksuite/framework/block-std/src/inline/extensions/inline-spec.ts
new file mode 100644
index 0000000000..35cfc7caa4
--- /dev/null
+++ b/blocksuite/framework/block-std/src/inline/extensions/inline-spec.ts
@@ -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('AffineInlineSpec');
+
+export function InlineSpecExtension(
+ name: string,
+ getSpec: (provider: ServiceProvider) => InlineSpecs
+): ExtensionType & {
+ identifier: ServiceIdentifier>;
+};
+export function InlineSpecExtension(
+ spec: InlineSpecs
+): ExtensionType & {
+ identifier: ServiceIdentifier>;
+};
+export function InlineSpecExtension(
+ nameOrSpec: string | InlineSpecs,
+ getSpec?: (provider: ServiceProvider) => InlineSpecs
+): ExtensionType & {
+ identifier: ServiceIdentifier>;
+} {
+ if (typeof nameOrSpec === 'string') {
+ const identifier =
+ InlineSpecIdentifier>(nameOrSpec);
+ return {
+ identifier,
+ setup: di => {
+ di.addImpl(identifier, provider => getSpec!(provider));
+ },
+ };
+ }
+ const identifier = InlineSpecIdentifier>(
+ nameOrSpec.name as string
+ );
+ return {
+ identifier,
+ setup: di => {
+ di.addImpl(identifier, nameOrSpec);
+ },
+ };
+}
diff --git a/blocksuite/framework/block-std/src/inline/extensions/markdown-matcher.ts b/blocksuite/framework/block-std/src/inline/extensions/markdown-matcher.ts
new file mode 100644
index 0000000000..0319eb6cab
--- /dev/null
+++ b/blocksuite/framework/block-std/src/inline/extensions/markdown-matcher.ts
@@ -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(
+ 'AffineMarkdownMatcher'
+);
+
+export function InlineMarkdownExtension<
+ TextAttributes extends BaseTextAttributes,
+>(
+ matcher: InlineMarkdownMatch
+): ExtensionType & {
+ identifier: ServiceIdentifier>;
+} {
+ const identifier = MarkdownMatcherIdentifier<
+ InlineMarkdownMatch
+ >(matcher.name);
+
+ return {
+ setup: di => {
+ di.addImpl(identifier, () => ({ ...matcher }));
+ },
+ identifier,
+ };
+}
diff --git a/blocksuite/affine/rich-text/src/extension/type.ts b/blocksuite/framework/block-std/src/inline/extensions/type.ts
similarity index 79%
rename from blocksuite/affine/rich-text/src/extension/type.ts
rename to blocksuite/framework/block-std/src/inline/extensions/type.ts
index fb044a0075..ee2d364eea 100644
--- a/blocksuite/affine/rich-text/src/extension/type.ts
+++ b/blocksuite/framework/block-std/src/inline/extensions/type.ts
@@ -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) => boolean;
- renderer: AttributeRenderer;
+ match: (delta: DeltaInsert) => boolean;
+ renderer: AttributeRenderer;
embed?: boolean;
};
diff --git a/blocksuite/framework/block-std/src/inline/index.ts b/blocksuite/framework/block-std/src/inline/index.ts
index f2a0f8cb5e..f332899ece 100644
--- a/blocksuite/framework/block-std/src/inline/index.ts
+++ b/blocksuite/framework/block-std/src/inline/index.ts
@@ -1,5 +1,6 @@
export * from './components';
export * from './consts';
+export * from './extensions';
export * from './inline-editor';
export * from './range';
export * from './services';