mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 06:47:02 +08:00
refactor(editor): extract mobile extension builder (#12239)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new mobile view extension that activates mobile-specific UI features based on the runtime environment. - Automatically enables mobile keyboard toolbar and linked document menu features in mobile contexts. - **Improvements** - Simplified code and paragraph block configurations on mobile, including disabling line numbers and adjusting placeholders. - Enhanced configuration chaining to include mobile-specific settings by default. - Improved extension registration flow with method chaining support. - **Refactor** - Removed deprecated mobile patch classes and configurations, consolidating mobile logic into dedicated extensions. - Streamlined mobile-related code for better maintainability and performance. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -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') {
|
||||
|
||||
@@ -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<typeof optionsSchema>;
|
||||
|
||||
export class MobileViewExtension extends ViewExtensionProvider<MobileViewOptions> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<AffineEdito
|
||||
reactToLit,
|
||||
confirmModal,
|
||||
} = options;
|
||||
const isMobileEdition = BUILD_CONFIG.isMobileEdition;
|
||||
const isElectron = BUILD_CONFIG.isElectron;
|
||||
|
||||
const docService = framework.get(DocService);
|
||||
@@ -119,7 +110,6 @@ export class AffineEditorViewExtension extends ViewExtensionProvider<AffineEdito
|
||||
patchReferenceRenderer(reactToLit, referenceRenderer),
|
||||
patchNotificationService(confirmModal),
|
||||
patchOpenDocExtension(),
|
||||
EdgelessClipboardAIChatConfig,
|
||||
patchSideBarService(framework),
|
||||
patchDocModeService(docService, docsService, editorService),
|
||||
]);
|
||||
@@ -129,14 +119,6 @@ export class AffineEditorViewExtension extends ViewExtensionProvider<AffineEdito
|
||||
patchDatabaseBlockConfigService(),
|
||||
patchForAudioEmbedView(reactToLit),
|
||||
]);
|
||||
if (isMobileEdition) {
|
||||
context.register([
|
||||
KeyboardToolbarExtension(framework),
|
||||
MobileSpecsPatches,
|
||||
mobileParagraphConfig,
|
||||
mobileCodeConfig,
|
||||
]);
|
||||
}
|
||||
if (isElectron) {
|
||||
context.register(patchForClipboardInElectron(framework));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ class MigratingAffineStoreExtension extends StoreExtensionProvider {
|
||||
|
||||
interface Configure {
|
||||
init: () => Configure;
|
||||
|
||||
featureFlag: (featureFlagService?: FeatureFlagService) => Configure;
|
||||
|
||||
value: StoreExtensionManager;
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user