mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-21 12:02:15 +08:00
feat(editor): rich text package (#10689)
This PR performs a significant architectural refactoring by extracting rich text functionality into a dedicated package. Here are the key changes: 1. **New Package Creation** - Created a new package `@blocksuite/affine-rich-text` to house rich text related functionality - Moved rich text components, utilities, and types from `@blocksuite/affine-components` to this new package 2. **Dependency Updates** - Updated multiple block packages to include the new `@blocksuite/affine-rich-text` as a direct dependency: - block-callout - block-code - block-database - block-edgeless-text - block-embed - block-list - block-note - block-paragraph 3. **Import Path Updates** - Refactored all imports that previously referenced rich text functionality from `@blocksuite/affine-components/rich-text` to now use `@blocksuite/affine-rich-text` - Updated imports for components like: - DefaultInlineManagerExtension - RichText types and interfaces - Text manipulation utilities (focusTextModel, textKeymap, etc.) - Reference node components and providers 4. **Build Configuration Updates** - Added references to the new rich text package in the `tsconfig.json` files of all affected packages - Maintained workspace dependencies using the `workspace:*` version specifier The primary motivation appears to be: 1. Better separation of concerns by isolating rich text functionality 2. Improved maintainability through more modular package structure 3. Clearer dependencies between packages 4. Potential for better tree-shaking and bundle optimization This is primarily an architectural improvement that should make the codebase more maintainable and better organized.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
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';
|
||||
@@ -0,0 +1,104 @@
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import { type BlockStdScope, StdIdentifier } from '@blocksuite/block-std';
|
||||
import {
|
||||
createIdentifier,
|
||||
type ServiceIdentifier,
|
||||
} from '@blocksuite/global/di';
|
||||
import {
|
||||
type AttributeRenderer,
|
||||
baseTextAttributes,
|
||||
type DeltaInsert,
|
||||
getDefaultAttributeRenderer,
|
||||
} from '@blocksuite/inline';
|
||||
import 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,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
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);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createIdentifier } from '@blocksuite/global/di';
|
||||
import { Slot } from '@blocksuite/global/slot';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import type { RefNodeSlots } from '../inline/index.js';
|
||||
|
||||
export const RefNodeSlotsProvider =
|
||||
createIdentifier<RefNodeSlots>('AffineRefNodeSlots');
|
||||
|
||||
const slots: RefNodeSlots = {
|
||||
docLinkClicked: new Slot(),
|
||||
};
|
||||
|
||||
export const RefNodeSlotsExtension: ExtensionType = {
|
||||
setup: di => {
|
||||
di.addImpl(RefNodeSlotsProvider, () => slots);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import type {
|
||||
AttributeRenderer,
|
||||
BaseTextAttributes,
|
||||
DeltaInsert,
|
||||
InlineEditor,
|
||||
InlineRange,
|
||||
} from '@blocksuite/inline';
|
||||
import type * as Y from 'yjs';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
|
||||
export type InlineSpecs<
|
||||
AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
> = {
|
||||
name: keyof AffineTextAttributes | string;
|
||||
schema: ZodTypeAny;
|
||||
match: (delta: DeltaInsert<AffineTextAttributes>) => boolean;
|
||||
renderer: AttributeRenderer<AffineTextAttributes>;
|
||||
embed?: boolean;
|
||||
};
|
||||
|
||||
export type InlineMarkdownMatchAction<
|
||||
// @ts-expect-error We allow to covariance for AffineTextAttributes
|
||||
in AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
> = (props: {
|
||||
inlineEditor: InlineEditor<AffineTextAttributes>;
|
||||
prefixText: string;
|
||||
inlineRange: InlineRange;
|
||||
pattern: RegExp;
|
||||
undoManager: Y.UndoManager;
|
||||
}) => void;
|
||||
|
||||
export type InlineMarkdownMatch<
|
||||
AffineTextAttributes extends BaseTextAttributes = BaseTextAttributes,
|
||||
> = {
|
||||
name: string;
|
||||
pattern: RegExp;
|
||||
action: InlineMarkdownMatchAction<AffineTextAttributes>;
|
||||
};
|
||||
Reference in New Issue
Block a user