feat(editor): std inline extensions (#11038)
This commit is contained in:
@@ -3,39 +3,44 @@ import {
|
|||||||
BoldInlineSpecExtension,
|
BoldInlineSpecExtension,
|
||||||
CodeInlineSpecExtension,
|
CodeInlineSpecExtension,
|
||||||
ColorInlineSpecExtension,
|
ColorInlineSpecExtension,
|
||||||
InlineManagerExtension,
|
|
||||||
InlineSpecExtension,
|
|
||||||
ItalicInlineSpecExtension,
|
ItalicInlineSpecExtension,
|
||||||
LatexInlineSpecExtension,
|
LatexInlineSpecExtension,
|
||||||
LinkInlineSpecExtension,
|
LinkInlineSpecExtension,
|
||||||
StrikeInlineSpecExtension,
|
StrikeInlineSpecExtension,
|
||||||
UnderlineInlineSpecExtension,
|
UnderlineInlineSpecExtension,
|
||||||
} from '@blocksuite/affine-rich-text';
|
} 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 { html } from 'lit';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const CodeBlockUnitSpecExtension = InlineSpecExtension({
|
export const CodeBlockUnitSpecExtension =
|
||||||
name: 'code-block-unit',
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
schema: z.undefined(),
|
name: 'code-block-unit',
|
||||||
match: () => true,
|
schema: z.undefined(),
|
||||||
renderer: ({ delta }) => {
|
match: () => true,
|
||||||
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
|
renderer: ({ delta }) => {
|
||||||
},
|
return html`<affine-code-unit .delta=${delta}></affine-code-unit>`;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const CodeBlockInlineManagerExtension = InlineManagerExtension({
|
export const CodeBlockInlineManagerExtension =
|
||||||
id: 'CodeBlockInlineManager',
|
InlineManagerExtension<AffineTextAttributes>({
|
||||||
enableMarkdown: false,
|
id: 'CodeBlockInlineManager',
|
||||||
specs: [
|
enableMarkdown: false,
|
||||||
BoldInlineSpecExtension.identifier,
|
specs: [
|
||||||
ItalicInlineSpecExtension.identifier,
|
BoldInlineSpecExtension.identifier,
|
||||||
UnderlineInlineSpecExtension.identifier,
|
ItalicInlineSpecExtension.identifier,
|
||||||
StrikeInlineSpecExtension.identifier,
|
UnderlineInlineSpecExtension.identifier,
|
||||||
CodeInlineSpecExtension.identifier,
|
StrikeInlineSpecExtension.identifier,
|
||||||
BackgroundInlineSpecExtension.identifier,
|
CodeInlineSpecExtension.identifier,
|
||||||
ColorInlineSpecExtension.identifier,
|
BackgroundInlineSpecExtension.identifier,
|
||||||
LatexInlineSpecExtension.identifier,
|
ColorInlineSpecExtension.identifier,
|
||||||
LinkInlineSpecExtension.identifier,
|
LatexInlineSpecExtension.identifier,
|
||||||
CodeBlockUnitSpecExtension.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 type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
import { InlineManagerExtension } from './extension/index.js';
|
|
||||||
import {
|
import {
|
||||||
BackgroundInlineSpecExtension,
|
BackgroundInlineSpecExtension,
|
||||||
BoldInlineSpecExtension,
|
BoldInlineSpecExtension,
|
||||||
@@ -19,22 +20,23 @@ import {
|
|||||||
} from './inline/index.js';
|
} from './inline/index.js';
|
||||||
import { LatexEditorInlineManagerExtension } from './inline/presets/nodes/latex-node/latex-editor-menu.js';
|
import { LatexEditorInlineManagerExtension } from './inline/presets/nodes/latex-node/latex-editor-menu.js';
|
||||||
|
|
||||||
export const DefaultInlineManagerExtension = InlineManagerExtension({
|
export const DefaultInlineManagerExtension =
|
||||||
id: 'DefaultInlineManager',
|
InlineManagerExtension<AffineTextAttributes>({
|
||||||
specs: [
|
id: 'DefaultInlineManager',
|
||||||
BoldInlineSpecExtension.identifier,
|
specs: [
|
||||||
ItalicInlineSpecExtension.identifier,
|
BoldInlineSpecExtension.identifier,
|
||||||
UnderlineInlineSpecExtension.identifier,
|
ItalicInlineSpecExtension.identifier,
|
||||||
StrikeInlineSpecExtension.identifier,
|
UnderlineInlineSpecExtension.identifier,
|
||||||
CodeInlineSpecExtension.identifier,
|
StrikeInlineSpecExtension.identifier,
|
||||||
BackgroundInlineSpecExtension.identifier,
|
CodeInlineSpecExtension.identifier,
|
||||||
ColorInlineSpecExtension.identifier,
|
BackgroundInlineSpecExtension.identifier,
|
||||||
LatexInlineSpecExtension.identifier,
|
ColorInlineSpecExtension.identifier,
|
||||||
ReferenceInlineSpecExtension.identifier,
|
LatexInlineSpecExtension.identifier,
|
||||||
LinkInlineSpecExtension.identifier,
|
ReferenceInlineSpecExtension.identifier,
|
||||||
FootNoteInlineSpecExtension.identifier,
|
LinkInlineSpecExtension.identifier,
|
||||||
],
|
FootNoteInlineSpecExtension.identifier,
|
||||||
});
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export const RichTextExtensions: ExtensionType[] = [
|
export const RichTextExtensions: ExtensionType[] = [
|
||||||
InlineSpecExtensions,
|
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 './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 { ToolbarModuleExtension } from '@blocksuite/affine-shared/services';
|
||||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||||
import { BlockFlavourIdentifier, StdIdentifier } from '@blocksuite/block-std';
|
import { BlockFlavourIdentifier, StdIdentifier } from '@blocksuite/block-std';
|
||||||
import type {
|
import {
|
||||||
InlineEditor,
|
type InlineEditor,
|
||||||
InlineRootElement,
|
type InlineRootElement,
|
||||||
|
InlineSpecExtension,
|
||||||
} from '@blocksuite/block-std/inline';
|
} from '@blocksuite/block-std/inline';
|
||||||
import { html } from 'lit';
|
import { html } from 'lit';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { InlineSpecExtension } from '../../extension/index.js';
|
|
||||||
import { FootNoteNodeConfigIdentifier } from './nodes/footnote-node/footnote-config.js';
|
import { FootNoteNodeConfigIdentifier } from './nodes/footnote-node/footnote-config.js';
|
||||||
import { builtinInlineLinkToolbarConfig } from './nodes/link-node/configs/toolbar.js';
|
import { builtinInlineLinkToolbarConfig } from './nodes/link-node/configs/toolbar.js';
|
||||||
import { builtinInlineReferenceToolbarConfig } from './nodes/reference-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 AffineInlineEditor = InlineEditor<AffineTextAttributes>;
|
||||||
export type AffineInlineRootElement = InlineRootElement<AffineTextAttributes>;
|
export type AffineInlineRootElement = InlineRootElement<AffineTextAttributes>;
|
||||||
|
|
||||||
export const BoldInlineSpecExtension = InlineSpecExtension({
|
export const BoldInlineSpecExtension =
|
||||||
name: 'bold',
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
name: 'bold',
|
||||||
match: delta => {
|
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||||
return !!delta.attributes?.bold;
|
match: delta => {
|
||||||
},
|
return !!delta.attributes?.bold;
|
||||||
renderer: ({ delta }) => {
|
},
|
||||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
renderer: ({ delta }) => {
|
||||||
},
|
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const ItalicInlineSpecExtension = InlineSpecExtension({
|
export const ItalicInlineSpecExtension =
|
||||||
name: 'italic',
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
name: 'italic',
|
||||||
match: delta => {
|
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||||
return !!delta.attributes?.italic;
|
match: delta => {
|
||||||
},
|
return !!delta.attributes?.italic;
|
||||||
renderer: ({ delta }) => {
|
},
|
||||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
renderer: ({ delta }) => {
|
||||||
},
|
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const UnderlineInlineSpecExtension = InlineSpecExtension({
|
export const UnderlineInlineSpecExtension =
|
||||||
name: 'underline',
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
name: 'underline',
|
||||||
match: delta => {
|
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||||
return !!delta.attributes?.underline;
|
match: delta => {
|
||||||
},
|
return !!delta.attributes?.underline;
|
||||||
renderer: ({ delta }) => {
|
},
|
||||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
renderer: ({ delta }) => {
|
||||||
},
|
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const StrikeInlineSpecExtension = InlineSpecExtension({
|
export const StrikeInlineSpecExtension =
|
||||||
name: 'strike',
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
name: 'strike',
|
||||||
match: delta => {
|
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||||
return !!delta.attributes?.strike;
|
match: delta => {
|
||||||
},
|
return !!delta.attributes?.strike;
|
||||||
renderer: ({ delta }) => {
|
},
|
||||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
renderer: ({ delta }) => {
|
||||||
},
|
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const CodeInlineSpecExtension = InlineSpecExtension({
|
export const CodeInlineSpecExtension =
|
||||||
name: 'code',
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
schema: z.literal(true).optional().nullable().catch(undefined),
|
name: 'code',
|
||||||
match: delta => {
|
schema: z.literal(true).optional().nullable().catch(undefined),
|
||||||
return !!delta.attributes?.code;
|
match: delta => {
|
||||||
},
|
return !!delta.attributes?.code;
|
||||||
renderer: ({ delta }) => {
|
},
|
||||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
renderer: ({ delta }) => {
|
||||||
},
|
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const BackgroundInlineSpecExtension = InlineSpecExtension({
|
export const BackgroundInlineSpecExtension =
|
||||||
name: 'background',
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
schema: z.string().optional().nullable().catch(undefined),
|
name: 'background',
|
||||||
match: delta => {
|
schema: z.string().optional().nullable().catch(undefined),
|
||||||
return !!delta.attributes?.background;
|
match: delta => {
|
||||||
},
|
return !!delta.attributes?.background;
|
||||||
renderer: ({ delta }) => {
|
},
|
||||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
renderer: ({ delta }) => {
|
||||||
},
|
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const ColorInlineSpecExtension = InlineSpecExtension({
|
export const ColorInlineSpecExtension =
|
||||||
name: 'color',
|
InlineSpecExtension<AffineTextAttributes>({
|
||||||
schema: z.string().optional().nullable().catch(undefined),
|
name: 'color',
|
||||||
match: delta => {
|
schema: z.string().optional().nullable().catch(undefined),
|
||||||
return !!delta.attributes?.color;
|
match: delta => {
|
||||||
},
|
return !!delta.attributes?.color;
|
||||||
renderer: ({ delta }) => {
|
},
|
||||||
return html`<affine-text .delta=${delta}></affine-text>`;
|
renderer: ({ delta }) => {
|
||||||
},
|
return html`<affine-text .delta=${delta}></affine-text>`;
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const LatexInlineSpecExtension = InlineSpecExtension(
|
export const LatexInlineSpecExtension =
|
||||||
'latex',
|
InlineSpecExtension<AffineTextAttributes>('latex', provider => {
|
||||||
provider => {
|
|
||||||
const std = provider.get(StdIdentifier);
|
const std = provider.get(StdIdentifier);
|
||||||
return {
|
return {
|
||||||
name: 'latex',
|
name: 'latex',
|
||||||
@@ -118,12 +124,10 @@ export const LatexInlineSpecExtension = InlineSpecExtension(
|
|||||||
},
|
},
|
||||||
embed: true,
|
embed: true,
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const ReferenceInlineSpecExtension = InlineSpecExtension(
|
export const ReferenceInlineSpecExtension =
|
||||||
'reference',
|
InlineSpecExtension<AffineTextAttributes>('reference', provider => {
|
||||||
provider => {
|
|
||||||
const std = provider.get(StdIdentifier);
|
const std = provider.get(StdIdentifier);
|
||||||
const configProvider = new ReferenceNodeConfigProvider(std);
|
const configProvider = new ReferenceNodeConfigProvider(std);
|
||||||
const config =
|
const config =
|
||||||
@@ -164,35 +168,35 @@ export const ReferenceInlineSpecExtension = InlineSpecExtension(
|
|||||||
},
|
},
|
||||||
embed: true,
|
embed: true,
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const LinkInlineSpecExtension = InlineSpecExtension('link', provider => {
|
export const LinkInlineSpecExtension =
|
||||||
const std = provider.get(StdIdentifier);
|
InlineSpecExtension<AffineTextAttributes>('link', provider => {
|
||||||
return {
|
const std = provider.get(StdIdentifier);
|
||||||
name: 'link',
|
return {
|
||||||
schema: z.string().optional().nullable().catch(undefined),
|
name: 'link',
|
||||||
match: delta => {
|
schema: z.string().optional().nullable().catch(undefined),
|
||||||
return !!delta.attributes?.link;
|
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 }) => {
|
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({
|
export const FootNoteInlineSpecExtension =
|
||||||
name: 'latex-editor-unit',
|
InlineSpecExtension<AffineTextAttributes>('footnote', provider => {
|
||||||
schema: z.undefined(),
|
|
||||||
match: () => true,
|
|
||||||
renderer: ({ delta }) => {
|
|
||||||
return html`<latex-editor-unit .delta=${delta}></latex-editor-unit>`;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const FootNoteInlineSpecExtension = InlineSpecExtension(
|
|
||||||
'footnote',
|
|
||||||
provider => {
|
|
||||||
const std = provider.get(StdIdentifier);
|
const std = provider.get(StdIdentifier);
|
||||||
const config =
|
const config =
|
||||||
provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined;
|
provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined;
|
||||||
@@ -211,8 +215,7 @@ export const FootNoteInlineSpecExtension = InlineSpecExtension(
|
|||||||
},
|
},
|
||||||
embed: true,
|
embed: true,
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const InlineSpecExtensions = [
|
export const InlineSpecExtensions = [
|
||||||
BoldInlineSpecExtension,
|
BoldInlineSpecExtension,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||||
import type { BlockComponent } from '@blocksuite/block-std';
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
|
import { InlineMarkdownExtension } from '@blocksuite/block-std/inline';
|
||||||
import type { ExtensionType } from '@blocksuite/store';
|
import type { ExtensionType } from '@blocksuite/store';
|
||||||
|
|
||||||
import { InlineMarkdownExtension } from '../../extension/markdown-matcher.js';
|
|
||||||
|
|
||||||
// inline markdown match rules:
|
// inline markdown match rules:
|
||||||
// covert: ***test*** + space
|
// covert: ***test*** + space
|
||||||
// covert: ***t est*** + space
|
// covert: ***t est*** + space
|
||||||
@@ -10,63 +10,71 @@ import { InlineMarkdownExtension } from '../../extension/markdown-matcher.js';
|
|||||||
// not convert: ***test *** + space
|
// not convert: ***test *** + space
|
||||||
// not convert: *** test *** + space
|
// not convert: *** test *** + space
|
||||||
|
|
||||||
export const BoldItalicMarkdown = InlineMarkdownExtension({
|
export const BoldItalicMarkdown = InlineMarkdownExtension<AffineTextAttributes>(
|
||||||
name: 'bolditalic',
|
{
|
||||||
pattern: /.*\*{3}([^\s*][^*]*[^\s*])\*{3}$|.*\*{3}([^\s*])\*{3}$/,
|
name: 'bolditalic',
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
pattern: /.*\*{3}([^\s*][^*]*[^\s*])\*{3}$|.*\*{3}([^\s*])\*{3}$/,
|
||||||
const match = prefixText.match(pattern);
|
action: ({
|
||||||
if (!match) return;
|
inlineEditor,
|
||||||
|
prefixText,
|
||||||
|
inlineRange,
|
||||||
|
pattern,
|
||||||
|
undoManager,
|
||||||
|
}) => {
|
||||||
|
const match = prefixText.match(pattern);
|
||||||
|
if (!match) return;
|
||||||
|
|
||||||
const targetText = match[1] ?? match[2];
|
const targetText = match[1] ?? match[2];
|
||||||
const annotatedText = match[0].slice(-targetText.length - 3 * 2);
|
const annotatedText = match[0].slice(-targetText.length - 3 * 2);
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
{
|
{
|
||||||
index: startIndex + annotatedText.length,
|
index: startIndex + annotatedText.length,
|
||||||
|
length: 0,
|
||||||
|
},
|
||||||
|
' '
|
||||||
|
);
|
||||||
|
inlineEditor.setInlineRange({
|
||||||
|
index: startIndex + annotatedText.length + 1,
|
||||||
length: 0,
|
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,
|
index: startIndex,
|
||||||
length: annotatedText.length,
|
length: 3,
|
||||||
},
|
});
|
||||||
{
|
|
||||||
bold: true,
|
|
||||||
italic: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
inlineEditor.setInlineRange({
|
||||||
index: startIndex + annotatedText.length,
|
index: startIndex + annotatedText.length - 6,
|
||||||
length: 1,
|
length: 0,
|
||||||
});
|
});
|
||||||
inlineEditor.deleteText({
|
},
|
||||||
index: startIndex + annotatedText.length - 3,
|
}
|
||||||
length: 3,
|
);
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex,
|
|
||||||
length: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
export const BoldMarkdown = InlineMarkdownExtension<AffineTextAttributes>({
|
||||||
index: startIndex + annotatedText.length - 6,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const BoldMarkdown = InlineMarkdownExtension({
|
|
||||||
name: 'bold',
|
name: 'bold',
|
||||||
pattern: /.*\*{2}([^\s][^*]*[^\s*])\*{2}$|.*\*{2}([^\s*])\*{2}$/,
|
pattern: /.*\*{2}([^\s][^*]*[^\s*])\*{2}$|.*\*{2}([^\s*])\*{2}$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
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',
|
name: 'italic',
|
||||||
pattern: /.*\*{1}([^\s][^*]*[^\s*])\*{1}$|.*\*{1}([^\s*])\*{1}$/,
|
pattern: /.*\*{1}([^\s][^*]*[^\s*])\*{1}$|.*\*{1}([^\s*])\*{1}$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
||||||
@@ -176,117 +184,131 @@ export const ItalicExtension = InlineMarkdownExtension({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StrikethroughExtension = InlineMarkdownExtension({
|
export const StrikethroughExtension =
|
||||||
name: 'strikethrough',
|
InlineMarkdownExtension<AffineTextAttributes>({
|
||||||
pattern: /.*~{2}([^\s][^~]*[^\s])~{2}$|.*~{2}([^\s~])~{2}$/,
|
name: 'strikethrough',
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
pattern: /.*~{2}([^\s][^~]*[^\s])~{2}$|.*~{2}([^\s~])~{2}$/,
|
||||||
const match = prefixText.match(pattern);
|
action: ({
|
||||||
if (!match) return;
|
inlineEditor,
|
||||||
|
prefixText,
|
||||||
|
inlineRange,
|
||||||
|
pattern,
|
||||||
|
undoManager,
|
||||||
|
}) => {
|
||||||
|
const match = prefixText.match(pattern);
|
||||||
|
if (!match) return;
|
||||||
|
|
||||||
const targetText = match[1] ?? match[2];
|
const targetText = match[1] ?? match[2];
|
||||||
const annotatedText = match[0].slice(-targetText.length - 2 * 2);
|
const annotatedText = match[0].slice(-targetText.length - 2 * 2);
|
||||||
const startIndex = inlineRange.index - annotatedText.length;
|
const startIndex = inlineRange.index - annotatedText.length;
|
||||||
|
|
||||||
inlineEditor.insertText(
|
inlineEditor.insertText(
|
||||||
{
|
{
|
||||||
index: startIndex + annotatedText.length,
|
index: startIndex + annotatedText.length,
|
||||||
|
length: 0,
|
||||||
|
},
|
||||||
|
' '
|
||||||
|
);
|
||||||
|
inlineEditor.setInlineRange({
|
||||||
|
index: startIndex + annotatedText.length + 1,
|
||||||
length: 0,
|
length: 0,
|
||||||
},
|
});
|
||||||
' '
|
|
||||||
);
|
|
||||||
inlineEditor.setInlineRange({
|
|
||||||
index: startIndex + annotatedText.length + 1,
|
|
||||||
length: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
undoManager.stopCapturing();
|
undoManager.stopCapturing();
|
||||||
|
|
||||||
inlineEditor.formatText(
|
inlineEditor.formatText(
|
||||||
{
|
{
|
||||||
index: startIndex,
|
index: startIndex,
|
||||||
length: annotatedText.length,
|
length: annotatedText.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
strike: true,
|
strike: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
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(
|
|
||||||
{
|
|
||||||
index: startIndex + annotatedText.length,
|
index: startIndex + annotatedText.length,
|
||||||
length: 0,
|
length: 1,
|
||||||
},
|
});
|
||||||
' '
|
inlineEditor.deleteText({
|
||||||
);
|
index: startIndex + annotatedText.length - 2,
|
||||||
inlineEditor.setInlineRange({
|
length: 2,
|
||||||
index: startIndex + annotatedText.length + 1,
|
});
|
||||||
length: 0,
|
inlineEditor.deleteText({
|
||||||
});
|
|
||||||
|
|
||||||
undoManager.stopCapturing();
|
|
||||||
|
|
||||||
inlineEditor.formatText(
|
|
||||||
{
|
|
||||||
index: startIndex,
|
index: startIndex,
|
||||||
length: annotatedText.length,
|
length: 2,
|
||||||
},
|
});
|
||||||
{
|
|
||||||
underline: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
inlineEditor.deleteText({
|
inlineEditor.setInlineRange({
|
||||||
index: startIndex + annotatedText.length,
|
index: startIndex + annotatedText.length - 4,
|
||||||
length: 1,
|
length: 0,
|
||||||
});
|
});
|
||||||
inlineEditor.deleteText({
|
},
|
||||||
index: inlineRange.index - 1,
|
});
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
inlineEditor.deleteText({
|
|
||||||
index: startIndex,
|
|
||||||
length: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
inlineEditor.setInlineRange({
|
export const UnderthroughExtension =
|
||||||
index: startIndex + annotatedText.length - 2,
|
InlineMarkdownExtension<AffineTextAttributes>({
|
||||||
length: 0,
|
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',
|
name: 'code',
|
||||||
pattern: /.*`([^\s][^`]*[^\s])`$|.*`([^\s`])`$/,
|
pattern: /.*`([^\s][^`]*[^\s])`$|.*`([^\s`])`$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
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',
|
name: 'link',
|
||||||
pattern: /.*\[(.+?)\]\((.+?)\)$/,
|
pattern: /.*\[(.+?)\]\((.+?)\)$/,
|
||||||
action: ({ inlineEditor, prefixText, inlineRange, pattern, undoManager }) => {
|
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',
|
name: 'latex',
|
||||||
|
|
||||||
pattern:
|
pattern:
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { ColorScheme } from '@blocksuite/affine-model';
|
import { ColorScheme } from '@blocksuite/affine-model';
|
||||||
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
import { ThemeProvider } from '@blocksuite/affine-shared/services';
|
||||||
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
|
import { unsafeCSSVar } from '@blocksuite/affine-shared/theme';
|
||||||
|
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||||
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/block-std';
|
import { type BlockStdScope, ShadowlessElement } from '@blocksuite/block-std';
|
||||||
|
import { InlineManagerExtension } from '@blocksuite/block-std/inline';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
import { noop } from '@blocksuite/global/utils';
|
import { noop } from '@blocksuite/global/utils';
|
||||||
import { DoneIcon } from '@blocksuite/icons/lit';
|
import { DoneIcon } from '@blocksuite/icons/lit';
|
||||||
@@ -11,14 +13,14 @@ import { property } from 'lit/decorators.js';
|
|||||||
import { codeToTokensBase, type ThemedToken } from 'shiki';
|
import { codeToTokensBase, type ThemedToken } from 'shiki';
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import { InlineManagerExtension } from '../../../../extension/index.js';
|
|
||||||
import { LatexEditorUnitSpecExtension } from '../../affine-inline-specs.js';
|
import { LatexEditorUnitSpecExtension } from '../../affine-inline-specs.js';
|
||||||
|
|
||||||
export const LatexEditorInlineManagerExtension = InlineManagerExtension({
|
export const LatexEditorInlineManagerExtension =
|
||||||
id: 'latex-inline-editor',
|
InlineManagerExtension<AffineTextAttributes>({
|
||||||
enableMarkdown: false,
|
id: 'latex-inline-editor',
|
||||||
specs: [LatexEditorUnitSpecExtension.identifier],
|
enableMarkdown: false,
|
||||||
});
|
specs: [LatexEditorUnitSpecExtension.identifier],
|
||||||
|
});
|
||||||
|
|
||||||
export class LatexEditorMenu extends SignalWatcher(
|
export class LatexEditorMenu extends SignalWatcher(
|
||||||
WithDisposable(ShadowlessElement)
|
WithDisposable(ShadowlessElement)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ShadowlessElement } from '@blocksuite/block-std';
|
|||||||
import {
|
import {
|
||||||
type AttributeRenderer,
|
type AttributeRenderer,
|
||||||
InlineEditor,
|
InlineEditor,
|
||||||
|
type InlineMarkdownMatch,
|
||||||
type InlineRange,
|
type InlineRange,
|
||||||
type InlineRangeProvider,
|
type InlineRangeProvider,
|
||||||
type VLine,
|
type VLine,
|
||||||
@@ -17,7 +18,6 @@ import { classMap } from 'lit/directives/class-map.js';
|
|||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import type { InlineMarkdownMatch } from './extension/type.js';
|
|
||||||
import { onVBeforeinput, onVCompositionEnd } from './hooks.js';
|
import { onVBeforeinput, onVCompositionEnd } from './hooks.js';
|
||||||
import type { AffineInlineEditor } from './inline/index.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';
|
import type { ZodTypeAny } from 'zod';
|
||||||
|
|
||||||
export type InlineSpecs<
|
export type InlineSpecs<
|
||||||
AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
TextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||||
> = {
|
> = {
|
||||||
name: keyof AffineTextAttributes | string;
|
name: keyof TextAttributes | string;
|
||||||
schema: ZodTypeAny;
|
schema: ZodTypeAny;
|
||||||
match: (delta: DeltaInsert<AffineTextAttributes>) => boolean;
|
match: (delta: DeltaInsert<TextAttributes>) => boolean;
|
||||||
renderer: AttributeRenderer<AffineTextAttributes>;
|
renderer: AttributeRenderer<TextAttributes>;
|
||||||
embed?: boolean;
|
embed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './consts';
|
export * from './consts';
|
||||||
|
export * from './extensions';
|
||||||
export * from './inline-editor';
|
export * from './inline-editor';
|
||||||
export * from './range';
|
export * from './range';
|
||||||
export * from './services';
|
export * from './services';
|
||||||
|
|||||||
Reference in New Issue
Block a user