diff --git a/blocksuite/affine/blocks/database/src/config.ts b/blocksuite/affine/blocks/database/src/config.ts index 9ad4b88bdc..ec88a1c956 100644 --- a/blocksuite/affine/blocks/database/src/config.ts +++ b/blocksuite/affine/blocks/database/src/config.ts @@ -1,10 +1,6 @@ -import type { MenuOptions } from '@blocksuite/affine-components/context-menu'; -import { type DatabaseBlockModel } from '@blocksuite/affine-model'; import { ConfigExtensionFactory } from '@blocksuite/std'; -export interface DatabaseOptionsConfig { - configure: (model: DatabaseBlockModel, options: MenuOptions) => MenuOptions; -} +import type { DatabaseViewExtensionOptions } from './view'; export const DatabaseConfigExtension = - ConfigExtensionFactory('affine:database'); + ConfigExtensionFactory('affine:database'); diff --git a/blocksuite/affine/blocks/database/src/database-block.ts b/blocksuite/affine/blocks/database/src/database-block.ts index 59d7b1bf03..4aef0e97f0 100644 --- a/blocksuite/affine/blocks/database/src/database-block.ts +++ b/blocksuite/affine/blocks/database/src/database-block.ts @@ -46,10 +46,7 @@ import { computed, signal } from '@preact/signals-core'; import { css, html, nothing, unsafeCSS } from 'lit'; import { popSideDetail } from './components/layout.js'; -import { - DatabaseConfigExtension, - type DatabaseOptionsConfig, -} from './config.js'; +import { DatabaseConfigExtension } from './config.js'; import { HostContextKey } from './context/host-context.js'; import { DatabaseBlockDataSource } from './data-source.js'; import { BlockRenderer } from './detail-panel/block-renderer.js'; @@ -57,6 +54,7 @@ import { NoteRenderer } from './detail-panel/note-renderer.js'; import { DatabaseSelection } from './selection.js'; import { currentViewStorage } from './utils/current-view.js'; import { getSingleDocIdFromText } from './utils/title-doc.js'; +import type { DatabaseViewExtensionOptions } from './view'; export class DatabaseBlockComponent extends CaptionedBlockComponent { static override styles = css` @@ -345,7 +343,7 @@ export class DatabaseBlockComponent extends CaptionedBlockComponent options, ...this.std.getOptional(DatabaseConfigExtension.identifier), diff --git a/blocksuite/affine/blocks/database/src/view.ts b/blocksuite/affine/blocks/database/src/view.ts index a677b8b228..83c39a9311 100644 --- a/blocksuite/affine/blocks/database/src/view.ts +++ b/blocksuite/affine/blocks/database/src/view.ts @@ -1,28 +1,51 @@ +import type { MenuOptions } from '@blocksuite/affine-components/context-menu'; import { type ViewExtensionContext, ViewExtensionProvider, } from '@blocksuite/affine-ext-loader'; +import { DatabaseBlockModel } from '@blocksuite/affine-model'; import { SlashMenuConfigExtension } from '@blocksuite/affine-widget-slash-menu'; import { BlockViewExtension, FlavourExtension } from '@blocksuite/std'; import { literal } from 'lit/static-html.js'; +import { z } from 'zod'; +import { DatabaseConfigExtension } from './config'; import { databaseSlashMenuConfig } from './configs/slash-menu.js'; import { effects } from './effects'; -export class DatabaseViewExtension extends ViewExtensionProvider { +const optionsSchema = z.object({ + configure: z + .function() + .args(z.instanceof(DatabaseBlockModel), z.custom()) + .returns(z.custom()), +}); + +export type DatabaseViewExtensionOptions = z.infer; + +export class DatabaseViewExtension extends ViewExtensionProvider { override name = 'affine-database-block'; + override schema = optionsSchema; + override effect() { super.effect(); effects(); } - override setup(context: ViewExtensionContext) { + override setup( + context: ViewExtensionContext, + options?: DatabaseViewExtensionOptions + ) { super.setup(context); context.register([ FlavourExtension('affine:database'), BlockViewExtension('affine:database', literal`affine-database`), SlashMenuConfigExtension('affine:database', databaseSlashMenuConfig), ]); + if (options) { + context.register( + DatabaseConfigExtension({ configure: options.configure }) + ); + } } } diff --git a/blocksuite/affine/widgets/linked-doc/package.json b/blocksuite/affine/widgets/linked-doc/package.json index a467c5db66..47db0215e7 100644 --- a/blocksuite/affine/widgets/linked-doc/package.json +++ b/blocksuite/affine/widgets/linked-doc/package.json @@ -28,7 +28,8 @@ "fflate": "^0.8.2", "lit": "^3.2.0", "lodash-es": "^4.17.21", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "zod": "^3.23.8" }, "exports": { ".": "./src/index.ts", diff --git a/blocksuite/affine/widgets/linked-doc/src/config.ts b/blocksuite/affine/widgets/linked-doc/src/config.ts index ef460b6c47..82798d1eea 100644 --- a/blocksuite/affine/widgets/linked-doc/src/config.ts +++ b/blocksuite/affine/widgets/linked-doc/src/config.ts @@ -27,62 +27,12 @@ import type { InlineRange } from '@blocksuite/std/inline'; import type { TemplateResult } from 'lit'; import { showImportModal } from './import-doc/index.js'; +import type { LinkedDocViewExtensionOptions } from './view'; -export interface LinkedWidgetConfig { - /** - * The first item of the trigger keys will be the primary key - * e.g. @, [[ - */ - triggerKeys: [string, ...string[]]; - /** - * Convert trigger key to primary key (the first item of the trigger keys) - * [[ -> @ - */ - convertTriggerKey: boolean; - ignoreBlockTypes: string[]; - ignoreSelector: string; - getMenus: ( - query: string, - abort: () => void, - editorHost: EditorHost, - inlineEditor: AffineInlineEditor, - abortSignal: AbortSignal - ) => Promise | LinkedMenuGroup[]; - - /** - * Auto focused item - * - * Will be called when the menu is - * - opened - * - query changed - * - menu group or its items changed - * - * If the return value is not null, no action will be taken. - */ - autoFocusedItemKey?: ( - menus: LinkedMenuGroup[], - query: string, - currentActiveKey: string | null, - editorHost: EditorHost, - inlineEditor: AffineInlineEditor - ) => string | null; - - mobile: { - /** - * The linked doc menu widget will scroll the container to make sure the input cursor is visible in viewport. - * It accepts a selector string, HTMLElement or Window - * - * @default getViewportElement(editorHost) this is the scrollable container in playground - */ - scrollContainer?: string | HTMLElement | Window; - /** - * The offset between the top of viewport and the input cursor - * - * @default 46 The height of header in playground - */ - scrollTopOffset?: number | (() => number); - }; -} +export type LinkedWidgetConfig = Required< + Omit +> & + Pick; export type LinkedMenuItem = { key: string; @@ -269,7 +219,6 @@ export function getMenus( } export const LinkedWidgetUtils = { - createLinkedDocMenuGroup, createNewDocMenuGroup, insertLinkedNode, }; diff --git a/blocksuite/affine/widgets/linked-doc/src/view.ts b/blocksuite/affine/widgets/linked-doc/src/view.ts index 6ffa2a1973..9530f19e4f 100644 --- a/blocksuite/affine/widgets/linked-doc/src/view.ts +++ b/blocksuite/affine/widgets/linked-doc/src/view.ts @@ -2,20 +2,82 @@ import { type ViewExtensionContext, ViewExtensionProvider, } from '@blocksuite/affine-ext-loader'; +import type { AffineInlineEditor } from '@blocksuite/affine-shared/types'; +import type { EditorHost } from '@blocksuite/std'; +import { z } from 'zod'; +import { type LinkedMenuGroup, LinkedWidgetConfigExtension } from './config'; import { effects } from './effects'; import { linkedDocWidget } from './widget'; -export class LinkedDocViewExtension extends ViewExtensionProvider { +const optionsSchema = z.object({ + triggerKeys: z.optional(z.tuple([z.string()]).rest(z.string())), + convertTriggerKey: z.boolean().optional(), + ignoreBlockTypes: z.array(z.string()).optional(), + ignoreSelector: z.string().optional(), + getMenus: z.optional( + z + .function() + .args( + z.string(), + z.function().returns(z.void()), + z.custom(), + z.custom(), + z.instanceof(AbortSignal) + ) + .returns( + z.union([ + z.promise(z.array(z.custom())), + z.array(z.custom()), + ]) + ) + ), + + autoFocusedItemKey: z.optional( + z + .function() + .args( + z.array(z.custom()), + z.string(), + z.string().nullable(), + z.custom(), + z.custom() + ) + .returns(z.string().nullable()) + ), + + mobile: z + .object({ + scrollContainer: z.optional( + z.union([z.string(), z.instanceof(HTMLElement), z.custom()]) + ), + scrollTopOffset: z.optional( + z.union([z.number(), z.function().returns(z.number())]) + ), + }) + .optional(), +}); + +export type LinkedDocViewExtensionOptions = z.infer; + +export class LinkedDocViewExtension extends ViewExtensionProvider { override name = 'affine-linked-doc-widget'; + override schema = optionsSchema; + override effect() { super.effect(); effects(); } - override setup(context: ViewExtensionContext) { + override setup( + context: ViewExtensionContext, + options?: LinkedDocViewExtensionOptions + ) { super.setup(context); context.register(linkedDocWidget); + if (options) { + context.register(LinkedWidgetConfigExtension(options)); + } } } diff --git a/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts b/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts index 34af7241bb..834dc066e6 100644 --- a/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts +++ b/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts @@ -1,14 +1,10 @@ import { WorkspaceServerService } from '@affine/core/modules/cloud'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; -import { DatabaseConfigExtension } from '@blocksuite/affine/blocks/database'; import { ToolbarMoreMenuConfigExtension } from '@blocksuite/affine/components/toolbar'; import { EditorSettingExtension } from '@blocksuite/affine/shared/services'; import type { ExtensionType } from '@blocksuite/affine/store'; -import { LinkedWidgetConfigExtension } from '@blocksuite/affine/widgets/linked-doc'; import type { FrameworkProvider } from '@toeverything/infra'; -import { createDatabaseOptionsConfig } from './database'; -import { createLinkedWidgetConfig } from './linked'; import { createCustomToolbarExtension, createToolbarMoreMenuConfig, @@ -27,8 +23,6 @@ export function getEditorConfigExtension( setting$: editorSettingService.editorSetting.settingSignal, set: (k, v) => editorSettingService.editorSetting.set(k, v), }), - DatabaseConfigExtension(createDatabaseOptionsConfig(framework)), - LinkedWidgetConfigExtension(createLinkedWidgetConfig(framework)), ToolbarMoreMenuConfigExtension(createToolbarMoreMenuConfig(framework)), createCustomToolbarExtension(editorSettingService.editorSetting, baseUrl), diff --git a/packages/frontend/core/src/blocksuite/extensions/editor-config/linked.ts b/packages/frontend/core/src/blocksuite/extensions/editor-config/linked.ts index f30fe8ae61..8b6bf910c0 100644 --- a/packages/frontend/core/src/blocksuite/extensions/editor-config/linked.ts +++ b/packages/frontend/core/src/blocksuite/extensions/editor-config/linked.ts @@ -4,6 +4,8 @@ import { type FrameworkProvider } from '@toeverything/infra'; export function createLinkedWidgetConfig( framework: FrameworkProvider -): Partial { - return framework.get(AtMenuConfigService).getConfig(); +): Partial | undefined { + const service = framework.getOptional(AtMenuConfigService); + if (!service) return; + return service.getConfig(); } diff --git a/packages/frontend/core/src/blocksuite/manager/common-view.ts b/packages/frontend/core/src/blocksuite/manager/common-view.ts index eadd3cfae1..1ecabcec5d 100644 --- a/packages/frontend/core/src/blocksuite/manager/common-view.ts +++ b/packages/frontend/core/src/blocksuite/manager/common-view.ts @@ -21,7 +21,6 @@ import { getThemeExtension, } from '@affine/core/blocksuite/extensions/theme'; import { PeekViewService } from '@affine/core/modules/peek-view'; -import { ParagraphBlockConfigExtension } from '@blocksuite/affine/blocks/paragraph'; import { type ViewExtensionContext, ViewExtensionProvider, @@ -60,58 +59,37 @@ export class AffineCommonViewExtension extends ViewExtensionProvider< ) { context.register(AIChatBlockSpec); context.register(AITranscriptionBlockSpec); - context.register( - [ - AICodeBlockWatcher, + context.register([ + AICodeBlockWatcher, + ToolbarModuleExtension({ + id: BlockFlavourIdentifier('custom:affine:image'), + config: imageToolbarAIEntryConfig(), + }), + ]); + if (context.scope === 'edgeless' || context.scope === 'page') { + context.register([ + aiPanelWidget, + AiSlashMenuConfigExtension(), ToolbarModuleExtension({ - id: BlockFlavourIdentifier('custom:affine:image'), - config: imageToolbarAIEntryConfig(), + id: BlockFlavourIdentifier('custom:affine:note'), + config: toolbarAIEntryConfig(), }), - ParagraphBlockConfigExtension({ - getPlaceholder: model => { - const placeholders = { - text: "Type '/' for commands, 'space' for AI", - h1: 'Heading 1', - h2: 'Heading 2', - h3: 'Heading 3', - h4: 'Heading 4', - h5: 'Heading 5', - h6: 'Heading 6', - quote: '', - }; - return placeholders[model.props.type]; - }, - }), - ].flat() - ); + ]); + } if (context.scope === 'edgeless') { context.register([ CopilotTool, - aiPanelWidget, edgelessCopilotWidget, getAIEdgelessRootWatcher(framework), // In note - ToolbarModuleExtension({ - id: BlockFlavourIdentifier('custom:affine:note'), - config: toolbarAIEntryConfig(), - }), ToolbarModuleExtension({ id: BlockFlavourIdentifier('custom:affine:surface:*'), config: edgelessToolbarAIEntryConfig(), }), - AiSlashMenuConfigExtension(), ]); } if (context.scope === 'page') { - context.register([ - aiPanelWidget, - getAIPageRootWatcher(framework), - ToolbarModuleExtension({ - id: BlockFlavourIdentifier('custom:affine:note'), - config: toolbarAIEntryConfig(), - }), - AiSlashMenuConfigExtension(), - ]); + context.register(getAIPageRootWatcher(framework)); } } diff --git a/packages/frontend/core/src/blocksuite/manager/migrating-view.ts b/packages/frontend/core/src/blocksuite/manager/migrating-view.ts index 3d456277d8..8e7587dd9b 100644 --- a/packages/frontend/core/src/blocksuite/manager/migrating-view.ts +++ b/packages/frontend/core/src/blocksuite/manager/migrating-view.ts @@ -1,10 +1,15 @@ +import { createDatabaseOptionsConfig } from '@affine/core/blocksuite/extensions/editor-config/database'; +import { createLinkedWidgetConfig } from '@affine/core/blocksuite/extensions/editor-config/linked'; import { AffineCommonViewExtension } from '@affine/core/blocksuite/manager/common-view'; import { AffineEditorViewExtension, type AffineEditorViewOptions, } from '@affine/core/blocksuite/manager/editor-view'; +import { DatabaseViewExtension } from '@blocksuite/affine/blocks/database/view'; +import { ParagraphViewExtension } from '@blocksuite/affine/blocks/paragraph/view'; import { ViewExtensionManager } from '@blocksuite/affine/ext-loader'; import { getInternalViewExtensions } from '@blocksuite/affine/extensions/view'; +import { LinkedDocViewExtension } from '@blocksuite/affine/widgets/linked-doc/view'; import type { FrameworkProvider } from '@toeverything/infra'; const manager = new ViewExtensionManager([ @@ -24,5 +29,34 @@ export function getViewManager( enableAI, }); manager.configure(AffineEditorViewExtension, options); + + if (framework) { + manager.configure( + DatabaseViewExtension, + createDatabaseOptionsConfig(framework) + ); + manager.configure( + LinkedDocViewExtension, + createLinkedWidgetConfig(framework) + ); + } + + if (enableAI) { + manager.configure(ParagraphViewExtension, { + getPlaceholder: model => { + const placeholders = { + text: "Type '/' for commands, 'space' for AI", + h1: 'Heading 1', + h2: 'Heading 2', + h3: 'Heading 3', + h4: 'Heading 4', + h5: 'Heading 5', + h6: 'Heading 6', + quote: '', + }; + return placeholders[model.props.type] ?? ''; + }, + }); + } return manager; } diff --git a/yarn.lock b/yarn.lock index f60c31a080..49085d7043 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3921,6 +3921,7 @@ __metadata: lit: "npm:^3.2.0" lodash-es: "npm:^4.17.21" rxjs: "npm:^7.8.1" + zod: "npm:^3.23.8" languageName: unknown linkType: soft