mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-27 02:42:25 +08:00
feat(editor): add affine inline footnote (#9745)
[BS-2369](https://linear.app/affine-design/issue/BS-2369/新增-affinetextattribute-footnote) [BS-2370](https://linear.app/affine-design/issue/BS-2370/支持-footnote-自定义渲染行内内容) [BS-2372](https://linear.app/affine-design/issue/BS-2372/提供-footnoteconfigextension) [BS-2375](https://linear.app/affine-design/issue/BS-2375/footnote-自定义渲染-popup) ### Add new AffineTextAttribute: footnote ``` /** * FootNote is used to reference a doc, attachment or url. */ export interface AffineTextAttributes { ... footnote?: { label: string; // label of the footnote reference: { type: 'doc' | 'attachment' | 'url'; // type of reference docId?: string; // the id of the reference doc url?: string; // the url of the reference network resource blobId?: string; // the id of the reference attachment fileName?: string; // the name of the reference attachment fileType?: string; // the type of the reference attachment } } | null } ``` ### FootNoteNodeConfigProvider Extension #### FootNoteNodeConfig Type Definition ``` type FootNoteNodeRenderer = ( footnote: FootNote, std: BlockStdScope ) => TemplateResult<1>; type FootNotePopupRenderer = ( footnote: FootNote, std: BlockStdScope, abortController: AbortController ) => TemplateResult<1>; export interface FootNoteNodeConfig { customNodeRenderer?: FootNoteNodeRenderer; customPopupRenderer?: FootNotePopupRenderer; interactive?: boolean; hidePopup?: boolean; } ``` #### FootNoteNodeConfigProvider Class ``` export class FootNoteNodeConfigProvider { private _customNodeRenderer?: FootNoteNodeRenderer; private _customPopupRenderer?: FootNotePopupRenderer; private _hidePopup: boolean; private _interactive: boolean; get customNodeRenderer() { return this._customNodeRenderer; } get customPopupRenderer() { return this._customPopupRenderer; } get doc() { return this.std.store; } get hidePopup() { return this._hidePopup; } get interactive() { return this._interactive; } constructor( config: FootNoteNodeConfig, readonly std: BlockStdScope ) { this._customNodeRenderer = config.customNodeRenderer; this._customPopupRenderer = config.customPopupRenderer; this._hidePopup = config.hidePopup ?? false; this._interactive = config.interactive ?? true; } setCustomNodeRenderer(renderer: FootNoteNodeRenderer) { this._customNodeRenderer = renderer; } setCustomPopupRenderer(renderer: FootNotePopupRenderer) { this._customPopupRenderer = renderer; } setHidePopup(hidePopup: boolean) { this._hidePopup = hidePopup; } setInteractive(interactive: boolean) { this._interactive = interactive; } } ``` #### FootNoteNodeConfigProvider Extension ``` export const FootNoteNodeConfigIdentifier = createIdentifier<FootNoteNodeConfigProvider>('AffineFootNoteNodeConfig'); export function FootNoteNodeConfigExtension( config: FootNoteNodeConfig ): ExtensionType { return { setup: di => { di.addImpl( FootNoteNodeConfigIdentifier, provider => new FootNoteNodeConfigProvider(config, provider.get(StdIdentifier)) ); }, }; } ``` The footnote node can be extended by this extension. ### FootnoteInlineSpec ``` export const FootNoteInlineSpecExtension = InlineSpecExtension( 'footnote', provider => { const std = provider.get(StdIdentifier); const config = provider.getOptional(FootNoteNodeConfigIdentifier) ?? undefined; return { name: 'footnote', schema: FootNoteSchema.optional().nullable().catch(undefined), match: delta => { return !!delta.attributes?.footnote; }, renderer: ({ delta }) => { return html`<affine-footnote-node .delta=${delta} .std=${std} .config=${config} ></affine-footnote-node>`; }, embed: true, }; } ); ```
This commit is contained in:
@@ -4,6 +4,10 @@ export type DocMode = 'edgeless' | 'page';
|
||||
|
||||
export const DocModes = ['edgeless', 'page'] as const;
|
||||
|
||||
export type FootNoteReferenceType = 'doc' | 'attachment' | 'url';
|
||||
|
||||
export const FootNoteReferenceTypes = ['doc', 'attachment', 'url'] as const;
|
||||
|
||||
/**
|
||||
* Custom title and description information.
|
||||
*
|
||||
@@ -42,3 +46,32 @@ export const ReferenceInfoSchema = z
|
||||
.merge(AliasInfoSchema);
|
||||
|
||||
export type ReferenceInfo = z.infer<typeof ReferenceInfoSchema>;
|
||||
|
||||
/**
|
||||
* FootNoteReferenceParamsSchema is used to define the parameters for a footnote reference.
|
||||
* It supports the following types:
|
||||
* 1. docId: string - the id of the doc
|
||||
* 2. blobId: string - the id of the attachment
|
||||
* 3. url: string - the url of the reference
|
||||
* 4. fileName: string - the name of the attachment
|
||||
* 5. fileType: string - the type of the attachment
|
||||
*/
|
||||
export const FootNoteReferenceParamsSchema = z.object({
|
||||
type: z.enum(FootNoteReferenceTypes),
|
||||
docId: z.string().optional(),
|
||||
blobId: z.string().optional(),
|
||||
fileName: z.string().optional(),
|
||||
fileType: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
});
|
||||
|
||||
export type FootNoteReferenceParams = z.infer<
|
||||
typeof FootNoteReferenceParamsSchema
|
||||
>;
|
||||
|
||||
export const FootNoteSchema = z.object({
|
||||
label: z.string(),
|
||||
reference: FootNoteReferenceParamsSchema,
|
||||
});
|
||||
|
||||
export type FootNote = z.infer<typeof FootNoteSchema>;
|
||||
|
||||
Reference in New Issue
Block a user