diff --git a/blocksuite/affine/blocks/code/src/view.ts b/blocksuite/affine/blocks/code/src/view.ts index d5aa50c5b1..eab094fd3b 100644 --- a/blocksuite/affine/blocks/code/src/view.ts +++ b/blocksuite/affine/blocks/code/src/view.ts @@ -11,6 +11,7 @@ import { import { literal, unsafeStatic } from 'lit/static-html.js'; import { getCodeClipboardExtensions } from './clipboard/index.js'; +import { CodeBlockConfigExtension } from './code-block-config'; import { CodeBlockInlineManagerExtension, CodeBlockUnitSpecExtension, @@ -21,7 +22,7 @@ import { AFFINE_CODE_TOOLBAR_WIDGET } from './code-toolbar/index.js'; import { codeSlashMenuConfig } from './configs/slash-menu.js'; import { effects } from './effects.js'; -export const codeToolbarWidget = WidgetViewExtension( +const codeToolbarWidget = WidgetViewExtension( 'affine:code', AFFINE_CODE_TOOLBAR_WIDGET, literal`${unsafeStatic(AFFINE_CODE_TOOLBAR_WIDGET)}` @@ -51,6 +52,12 @@ export class CodeBlockViewExtension extends ViewExtensionProvider { ]); if (!this.isMobile(context.scope)) { context.register(codeToolbarWidget); + } else { + context.register( + CodeBlockConfigExtension({ + showLineNumbers: false, + }) + ); } } } diff --git a/blocksuite/affine/ext-loader/src/base-provider.ts b/blocksuite/affine/ext-loader/src/base-provider.ts index 6c54cbd9d4..adcc9bd5b5 100644 --- a/blocksuite/affine/ext-loader/src/base-provider.ts +++ b/blocksuite/affine/ext-loader/src/base-provider.ts @@ -17,7 +17,7 @@ export type Context = { /** The scope this context is associated with */ scope: Scope; /** Function to register one or more extensions */ - register(extensions: ExtensionType[] | ExtensionType): void; + register(extensions: ExtensionType[] | ExtensionType): Context; }; /** diff --git a/blocksuite/affine/ext-loader/src/manager.ts b/blocksuite/affine/ext-loader/src/manager.ts index da7857e039..f6b55ef2d4 100644 --- a/blocksuite/affine/ext-loader/src/manager.ts +++ b/blocksuite/affine/ext-loader/src/manager.ts @@ -75,11 +75,14 @@ export class ExtensionManager { /** @internal */ private readonly _getContextByScope = (scope: Scope): Context => { - return { + const context: Context = { scope, - register: (extensions: ExtensionType[] | ExtensionType) => - this._registerToScope(scope, extensions), + register: (extensions: ExtensionType[] | ExtensionType) => { + this._registerToScope(scope, extensions); + return context; + }, }; + return context; }; /** diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx b/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx index cdaf3f432c..835b355225 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/lit-adaper.tsx @@ -98,7 +98,8 @@ const usePatchSpecs = (mode: DocMode) => { }) .database(framework) .linkedDoc(framework) - .paragraph(enableAI).value; + .paragraph(enableAI) + .mobile(framework).value; if (BUILD_CONFIG.isMobileEdition) { if (mode === 'page') { diff --git a/packages/frontend/core/src/blocksuite/extensions/mobile/index.ts b/packages/frontend/core/src/blocksuite/extensions/mobile/index.ts new file mode 100644 index 0000000000..cbec7ed4ef --- /dev/null +++ b/packages/frontend/core/src/blocksuite/extensions/mobile/index.ts @@ -0,0 +1,33 @@ +import { KeyboardToolbarExtension } from '@affine/core/blocksuite/extensions/mobile/keyboard-toolbar-extension'; +import { MobileFeatureFlagControl } from '@affine/core/blocksuite/extensions/mobile/mobile-feature-flag-control'; +import { + type ViewExtensionContext, + ViewExtensionProvider, +} from '@blocksuite/affine/ext-loader'; +import { FrameworkProvider } from '@toeverything/infra'; +import { z } from 'zod'; + +const optionsSchema = z.object({ + framework: z.instanceof(FrameworkProvider).optional(), +}); + +type MobileViewOptions = z.infer; + +export class MobileViewExtension extends ViewExtensionProvider { + override name = 'mobile-view-extension'; + + override schema = optionsSchema; + + override setup(context: ViewExtensionContext, options?: MobileViewOptions) { + super.setup(context, options); + const isMobile = BUILD_CONFIG.isMobileEdition; + if (!isMobile) return; + + const framework = options?.framework; + if (framework) { + context.register(KeyboardToolbarExtension(framework)); + } + + context.register(MobileFeatureFlagControl); + } +} diff --git a/packages/frontend/core/src/blocksuite/extensions/mobile-config.ts b/packages/frontend/core/src/blocksuite/extensions/mobile/keyboard-toolbar-extension.ts similarity index 66% rename from packages/frontend/core/src/blocksuite/extensions/mobile-config.ts rename to packages/frontend/core/src/blocksuite/extensions/mobile/keyboard-toolbar-extension.ts index 2e85f890b9..ff0d567a61 100644 --- a/packages/frontend/core/src/blocksuite/extensions/mobile-config.ts +++ b/packages/frontend/core/src/blocksuite/extensions/mobile/keyboard-toolbar-extension.ts @@ -1,50 +1,15 @@ import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard'; -import { CodeBlockConfigExtension } from '@blocksuite/affine/blocks/code'; -import { ParagraphBlockConfigExtension } from '@blocksuite/affine/blocks/paragraph'; import type { Container } from '@blocksuite/affine/global/di'; import { DisposableGroup } from '@blocksuite/affine/global/disposable'; import { - FeatureFlagService, VirtualKeyboardProvider as BSVirtualKeyboardProvider, type VirtualKeyboardProviderWithAction, } from '@blocksuite/affine/shared/services'; -import { type BlockStdScope, LifeCycleWatcher } from '@blocksuite/affine/std'; +import { LifeCycleWatcher } from '@blocksuite/affine/std'; import type { ExtensionType } from '@blocksuite/affine/store'; import { batch, signal } from '@preact/signals-core'; import type { FrameworkProvider } from '@toeverything/infra'; -export class MobileSpecsPatches extends LifeCycleWatcher { - static override key = 'mobile-patches'; - - constructor(std: BlockStdScope) { - super(std); - const featureFlagService = std.get(FeatureFlagService); - - featureFlagService.setFlag('enable_mobile_keyboard_toolbar', true); - featureFlagService.setFlag('enable_mobile_linked_doc_menu', true); - } -} - -export const mobileParagraphConfig = ParagraphBlockConfigExtension({ - getPlaceholder: model => { - const placeholders = { - text: '', - h1: 'Heading 1', - h2: 'Heading 2', - h3: 'Heading 3', - h4: 'Heading 4', - h5: 'Heading 5', - h6: 'Heading 6', - quote: '', - }; - return placeholders[model.props.type]; - }, -}); - -export const mobileCodeConfig = CodeBlockConfigExtension({ - showLineNumbers: false, -}); - export function KeyboardToolbarExtension( framework: FrameworkProvider ): ExtensionType { @@ -89,6 +54,7 @@ export function KeyboardToolbarExtension( if ('show' in affineVirtualKeyboardProvider) { const providerWithAction = affineVirtualKeyboardProvider; + class BSVirtualKeyboardServiceWithShowAndHide extends BSVirtualKeyboardService implements VirtualKeyboardProviderWithAction @@ -96,6 +62,7 @@ export function KeyboardToolbarExtension( show() { providerWithAction.show(); } + hide() { providerWithAction.hide(); } diff --git a/packages/frontend/core/src/blocksuite/extensions/mobile/mobile-feature-flag-control.ts b/packages/frontend/core/src/blocksuite/extensions/mobile/mobile-feature-flag-control.ts new file mode 100644 index 0000000000..62bade7156 --- /dev/null +++ b/packages/frontend/core/src/blocksuite/extensions/mobile/mobile-feature-flag-control.ts @@ -0,0 +1,14 @@ +import { FeatureFlagService } from '@blocksuite/affine/shared/services'; +import { type BlockStdScope, LifeCycleWatcher } from '@blocksuite/affine/std'; + +export class MobileFeatureFlagControl extends LifeCycleWatcher { + static override key = 'mobile-patches'; + + constructor(std: BlockStdScope) { + super(std); + const featureFlagService = std.get(FeatureFlagService); + + featureFlagService.setFlag('enable_mobile_keyboard_toolbar', true); + featureFlagService.setFlag('enable_mobile_linked_doc_menu', true); + } +} diff --git a/packages/frontend/core/src/blocksuite/manager/common-view.ts b/packages/frontend/core/src/blocksuite/manager/common-view.ts index 10036a8630..72c8f189c6 100644 --- a/packages/frontend/core/src/blocksuite/manager/common-view.ts +++ b/packages/frontend/core/src/blocksuite/manager/common-view.ts @@ -11,6 +11,7 @@ import { CopilotTool } from '@affine/core/blocksuite/ai/tool/copilot-tool'; import { aiPanelWidget } from '@affine/core/blocksuite/ai/widgets/ai-panel/ai-panel'; import { edgelessCopilotWidget } from '@affine/core/blocksuite/ai/widgets/edgeless-copilot'; import { buildDocDisplayMetaExtension } from '@affine/core/blocksuite/extensions/display-meta'; +import { EdgelessClipboardAIChatConfig } from '@affine/core/blocksuite/extensions/edgeless-clipboard'; import { patchFileSizeLimitExtension } from '@affine/core/blocksuite/extensions/file-size-limit'; import { getFontConfigExtension } from '@affine/core/blocksuite/extensions/font-config'; import { patchPeekViewService } from '@affine/core/blocksuite/extensions/peek-view-service'; @@ -41,15 +42,17 @@ export class AffineCommonViewExtension extends ViewExtensionProvider< context: ViewExtensionContext, framework: FrameworkProvider ) { - context.register(AIChatBlockSpec); - context.register(AITranscriptionBlockSpec); - context.register([ - AICodeBlockWatcher, - ToolbarModuleExtension({ - id: BlockFlavourIdentifier('custom:affine:image'), - config: imageToolbarAIEntryConfig(), - }), - ]); + context + .register(AIChatBlockSpec) + .register(AITranscriptionBlockSpec) + .register(EdgelessClipboardAIChatConfig) + .register(AICodeBlockWatcher) + .register( + ToolbarModuleExtension({ + id: BlockFlavourIdentifier('custom:affine:image'), + config: imageToolbarAIEntryConfig(), + }) + ); if (context.scope === 'edgeless' || context.scope === 'page') { context.register([ aiPanelWidget, diff --git a/packages/frontend/core/src/blocksuite/manager/editor-view.tsx b/packages/frontend/core/src/blocksuite/manager/editor-view.tsx index 3dfabf579f..5061fba87c 100644 --- a/packages/frontend/core/src/blocksuite/manager/editor-view.tsx +++ b/packages/frontend/core/src/blocksuite/manager/editor-view.tsx @@ -3,7 +3,6 @@ import { patchForAudioEmbedView } from '@affine/core/blocksuite/extensions/audio import { patchDatabaseBlockConfigService } from '@affine/core/blocksuite/extensions/database-block-config-service'; import { patchDocModeService } from '@affine/core/blocksuite/extensions/doc-mode-service'; import { patchDocUrlExtensions } from '@affine/core/blocksuite/extensions/doc-url'; -import { EdgelessClipboardAIChatConfig } from '@affine/core/blocksuite/extensions/edgeless-clipboard'; import { patchForClipboardInElectron } from '@affine/core/blocksuite/extensions/electron-clipboard'; import { patchNotificationService } from '@affine/core/blocksuite/extensions/notification-service'; import { patchOpenDocExtension } from '@affine/core/blocksuite/extensions/open-doc'; @@ -29,13 +28,6 @@ import { FrameworkProvider } from '@toeverything/infra'; import type { TemplateResult } from 'lit'; import { z } from 'zod'; -import { - KeyboardToolbarExtension, - mobileCodeConfig, - mobileParagraphConfig, - MobileSpecsPatches, -} from '../extensions/mobile-config'; - const optionsSchema = z.object({ // services framework: z.instanceof(FrameworkProvider), @@ -106,7 +98,6 @@ export class AffineEditorViewExtension extends ViewExtensionProvider Configure; + featureFlag: (featureFlagService?: FeatureFlagService) => Configure; + value: StoreExtensionManager; } diff --git a/packages/frontend/core/src/blocksuite/manager/migrating-view.ts b/packages/frontend/core/src/blocksuite/manager/migrating-view.ts index bbaee5e637..6b5a9b793a 100644 --- a/packages/frontend/core/src/blocksuite/manager/migrating-view.ts +++ b/packages/frontend/core/src/blocksuite/manager/migrating-view.ts @@ -7,6 +7,7 @@ import { import { AffineEditorConfigViewExtension } from '@affine/core/blocksuite/extensions/editor-config'; import { createDatabaseOptionsConfig } from '@affine/core/blocksuite/extensions/editor-config/database'; import { createLinkedWidgetConfig } from '@affine/core/blocksuite/extensions/editor-config/linked'; +import { MobileViewExtension } from '@affine/core/blocksuite/extensions/mobile'; import { PdfViewExtension } from '@affine/core/blocksuite/extensions/pdf'; import { AffineThemeViewExtension } from '@affine/core/blocksuite/extensions/theme'; import { TurboRendererViewExtension } from '@affine/core/blocksuite/extensions/turbo-renderer'; @@ -24,6 +25,25 @@ import type { FrameworkProvider } from '@toeverything/infra'; import { CodeBlockPreviewViewExtension } from './code-block-preview'; +type Configure = { + init: () => Configure; + + common: (framework?: FrameworkProvider, enableAI?: boolean) => Configure; + editorView: (options?: AffineEditorViewOptions) => Configure; + theme: (framework?: FrameworkProvider) => Configure; + editorConfig: (framework?: FrameworkProvider) => Configure; + edgelessBlockHeader: (options?: EdgelessBlockHeaderViewOptions) => Configure; + database: (framework?: FrameworkProvider) => Configure; + linkedDoc: (framework?: FrameworkProvider) => Configure; + paragraph: (enableAI?: boolean) => Configure; + cloud: (framework?: FrameworkProvider, enableCloud?: boolean) => Configure; + turboRenderer: (enableTurboRenderer?: boolean) => Configure; + pdf: (enablePDFEmbedPreview?: boolean, reactToLit?: ReactToLit) => Configure; + mobile: (framework?: FrameworkProvider) => Configure; + + value: ViewExtensionManager; +}; + class ViewProvider { static instance: ViewProvider | null = null; static getInstance() { @@ -48,6 +68,7 @@ class ViewProvider { TurboRendererViewExtension, CloudViewExtension, PdfViewExtension, + MobileViewExtension, ]); } @@ -55,7 +76,7 @@ class ViewProvider { return this._manager; } - get config() { + get config(): Configure { return { init: this._initDefaultConfig, common: this._configureCommon, @@ -69,6 +90,7 @@ class ViewProvider { cloud: this._configureCloud, turboRenderer: this._configureTurboRenderer, pdf: this._configurePdf, + mobile: this._configureMobile, value: this._manager, }; } @@ -85,7 +107,8 @@ class ViewProvider { .paragraph() .cloud() .turboRenderer() - .pdf(); + .pdf() + .mobile(); return this.config; }; @@ -146,7 +169,23 @@ class ViewProvider { }; private readonly _configureParagraph = (enableAI?: boolean) => { - if (enableAI) { + if (BUILD_CONFIG.isMobileEdition) { + this._manager.configure(ParagraphViewExtension, { + getPlaceholder: model => { + const placeholders = { + text: '', + h1: 'Heading 1', + h2: 'Heading 2', + h3: 'Heading 3', + h4: 'Heading 4', + h5: 'Heading 5', + h6: 'Heading 6', + quote: '', + }; + return placeholders[model.props.type] ?? ''; + }, + }); + } else if (enableAI) { this._manager.configure(ParagraphViewExtension, { getPlaceholder: model => { const placeholders = { @@ -193,6 +232,11 @@ class ViewProvider { }); return this.config; }; + + private readonly _configureMobile = (framework?: FrameworkProvider) => { + this._manager.configure(MobileViewExtension, { framework }); + return this.config; + }; } export function getViewManager() {