mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-23 20:18:42 +08:00
refactor(editor): improve implementation of lit adapter (#12101)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved mobile experience by disabling certain toolbars and slash menu features on mobile devices. - Introduced new modular extension classes for editor and view customization, enabling more flexible configuration of themes, AI features, and editor enhancements. - Added clipboard adapter configurations for a wide range of data types, improving clipboard compatibility. - Added a new theme extension specifically for preview scenarios. - Provided new hooks for block scope management in document modules. - **Refactor** - Streamlined editor extension setup, consolidating options and reducing complexity for better maintainability. - Reorganized mobile-specific extension exports for clearer usage. - Refined React-to-Lit rendering API by introducing a typed alias and updating related function signatures. - Simplified extension registration by splitting monolithic view extension into separate common and editor view extensions. - **Bug Fixes** - Corrected naming inconsistencies in internal effect tracking. - **Chores** - Updated type exports and documentation comments for improved clarity and consistency. - Removed unused or redundant exports and functions to clean up the codebase. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -41,7 +41,6 @@ export class CodeBlockViewExtension extends ViewExtensionProvider {
|
||||
FlavourExtension('affine:code'),
|
||||
CodeBlockHighlighter,
|
||||
BlockViewExtension('affine:code', literal`affine-code`),
|
||||
codeToolbarWidget,
|
||||
SlashMenuConfigExtension('affine:code', codeSlashMenuConfig),
|
||||
CodeKeymapExtension,
|
||||
...getCodeClipboardExtensions(),
|
||||
@@ -50,5 +49,8 @@ export class CodeBlockViewExtension extends ViewExtensionProvider {
|
||||
CodeBlockInlineManagerExtension,
|
||||
CodeBlockUnitSpecExtension,
|
||||
]);
|
||||
if (!this.isMobile(context.scope)) {
|
||||
context.register(codeToolbarWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,21 +208,21 @@ it('should effect only run once', () => {
|
||||
|
||||
const manager = new ViewExtensionManager([ViewExt1]);
|
||||
|
||||
expect(ViewExt1.effectRunned).toBe(false);
|
||||
expect(ViewExt2.effectRunned).toBe(false);
|
||||
expect(ViewExt1.effectRan).toBe(false);
|
||||
expect(ViewExt2.effectRan).toBe(false);
|
||||
|
||||
manager.get('page');
|
||||
|
||||
expect(ViewExt1.effectRunned).toBe(true);
|
||||
expect(ViewExt2.effectRunned).toBe(false);
|
||||
expect(ViewExt1.effectRan).toBe(true);
|
||||
expect(ViewExt2.effectRan).toBe(false);
|
||||
|
||||
expect(effect1).toHaveBeenCalledTimes(1);
|
||||
expect(effect2).toHaveBeenCalledTimes(0);
|
||||
|
||||
manager.get('edgeless');
|
||||
|
||||
expect(ViewExt1.effectRunned).toBe(true);
|
||||
expect(ViewExt2.effectRunned).toBe(false);
|
||||
expect(ViewExt1.effectRan).toBe(true);
|
||||
expect(ViewExt2.effectRan).toBe(false);
|
||||
|
||||
expect(effect1).toHaveBeenCalledTimes(1);
|
||||
expect(effect2).toHaveBeenCalledTimes(0);
|
||||
|
||||
@@ -64,7 +64,7 @@ export class ViewExtensionProvider<
|
||||
* Static flag to ensure effect is only run once per provider class
|
||||
* @internal
|
||||
*/
|
||||
static effectRunned = false;
|
||||
static effectRan = false;
|
||||
|
||||
/**
|
||||
* Override this method to implement one-time initialization logic for the provider.
|
||||
@@ -94,11 +94,29 @@ export class ViewExtensionProvider<
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the scope is preview
|
||||
* @param scope - The scope to check
|
||||
* @returns True if the scope is preview, false otherwise
|
||||
*/
|
||||
isPreview = (scope: ViewScope) => {
|
||||
return scope === 'preview-page' || scope === 'preview-edgeless';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the scope is mobile
|
||||
* @param scope - The scope to check
|
||||
* @returns True if the scope is mobile, false otherwise
|
||||
*/
|
||||
isMobile = (scope: ViewScope) => {
|
||||
return scope === 'mobile-page' || scope === 'mobile-edgeless';
|
||||
};
|
||||
|
||||
override setup(context: ViewExtensionContext, options?: Options) {
|
||||
super.setup(context, options);
|
||||
const constructer = this.constructor as typeof ViewExtensionProvider;
|
||||
if (!constructer.effectRunned) {
|
||||
constructer.effectRunned = true;
|
||||
const constructor = this.constructor as typeof ViewExtensionProvider;
|
||||
if (!constructor.effectRan) {
|
||||
constructor.effectRan = true;
|
||||
this.effect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
AttachmentAdapter,
|
||||
ClipboardAdapter,
|
||||
HtmlAdapter,
|
||||
ImageAdapter,
|
||||
MixTextAdapter,
|
||||
NotionTextAdapter,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import { ClipboardAdapterConfigExtension } from '@blocksuite/std';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
const SnapshotClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: ClipboardAdapter.MIME,
|
||||
adapter: ClipboardAdapter,
|
||||
priority: 100,
|
||||
});
|
||||
|
||||
const NotionClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/_notion-text-production',
|
||||
adapter: NotionTextAdapter,
|
||||
priority: 95,
|
||||
});
|
||||
|
||||
const HtmlClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/html',
|
||||
adapter: HtmlAdapter,
|
||||
priority: 90,
|
||||
});
|
||||
|
||||
const imageClipboardConfigs = [
|
||||
'image/apng',
|
||||
'image/avif',
|
||||
'image/gif',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/svg+xml',
|
||||
'image/webp',
|
||||
].map(mimeType => {
|
||||
return ClipboardAdapterConfigExtension({
|
||||
mimeType,
|
||||
adapter: ImageAdapter,
|
||||
priority: 80,
|
||||
});
|
||||
});
|
||||
|
||||
const PlainTextClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/plain',
|
||||
adapter: MixTextAdapter,
|
||||
priority: 70,
|
||||
});
|
||||
|
||||
const AttachmentClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: '*/*',
|
||||
adapter: AttachmentAdapter,
|
||||
priority: 60,
|
||||
});
|
||||
|
||||
export const clipboardConfigs: ExtensionType[] = [
|
||||
SnapshotClipboardConfig,
|
||||
NotionClipboardConfig,
|
||||
HtmlClipboardConfig,
|
||||
...imageClipboardConfigs,
|
||||
PlainTextClipboardConfig,
|
||||
AttachmentClipboardConfig,
|
||||
];
|
||||
@@ -3,14 +3,6 @@ import {
|
||||
type ViewExtensionContext,
|
||||
ViewExtensionProvider,
|
||||
} from '@blocksuite/affine-ext-loader';
|
||||
import {
|
||||
AttachmentAdapter,
|
||||
ClipboardAdapter,
|
||||
HtmlAdapter,
|
||||
ImageAdapter,
|
||||
MixTextAdapter,
|
||||
NotionTextAdapter,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import {
|
||||
AutoClearSelectionService,
|
||||
DefaultOpenDocExtension,
|
||||
@@ -25,67 +17,11 @@ import {
|
||||
ThemeService,
|
||||
ToolbarRegistryExtension,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import { ClipboardAdapterConfigExtension } from '@blocksuite/std';
|
||||
import { InteractivityManager, ToolController } from '@blocksuite/std/gfx';
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { clipboardConfigs } from './clipboard';
|
||||
import { effects } from './effects';
|
||||
|
||||
const SnapshotClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: ClipboardAdapter.MIME,
|
||||
adapter: ClipboardAdapter,
|
||||
priority: 100,
|
||||
});
|
||||
|
||||
const NotionClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/_notion-text-production',
|
||||
adapter: NotionTextAdapter,
|
||||
priority: 95,
|
||||
});
|
||||
|
||||
const HtmlClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/html',
|
||||
adapter: HtmlAdapter,
|
||||
priority: 90,
|
||||
});
|
||||
|
||||
const imageClipboardConfigs = [
|
||||
'image/apng',
|
||||
'image/avif',
|
||||
'image/gif',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/svg+xml',
|
||||
'image/webp',
|
||||
].map(mimeType => {
|
||||
return ClipboardAdapterConfigExtension({
|
||||
mimeType,
|
||||
adapter: ImageAdapter,
|
||||
priority: 80,
|
||||
});
|
||||
});
|
||||
|
||||
const PlainTextClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: 'text/plain',
|
||||
adapter: MixTextAdapter,
|
||||
priority: 70,
|
||||
});
|
||||
|
||||
const AttachmentClipboardConfig = ClipboardAdapterConfigExtension({
|
||||
mimeType: '*/*',
|
||||
adapter: AttachmentAdapter,
|
||||
priority: 60,
|
||||
});
|
||||
|
||||
export const clipboardConfigs: ExtensionType[] = [
|
||||
SnapshotClipboardConfig,
|
||||
NotionClipboardConfig,
|
||||
HtmlClipboardConfig,
|
||||
...imageClipboardConfigs,
|
||||
PlainTextClipboardConfig,
|
||||
AttachmentClipboardConfig,
|
||||
];
|
||||
|
||||
export class FoundationViewExtension extends ViewExtensionProvider {
|
||||
override name = 'foundation';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from '@blocksuite/affine-model';
|
||||
import { type Container, createIdentifier } from '@blocksuite/global/di';
|
||||
import { type BlockStdScope, StdIdentifier } from '@blocksuite/std';
|
||||
import { Extension, type ExtensionType } from '@blocksuite/store';
|
||||
import { Extension } from '@blocksuite/store';
|
||||
import { type Signal, signal } from '@preact/signals-core';
|
||||
import {
|
||||
type AffineCssVariables,
|
||||
@@ -25,14 +25,6 @@ export interface ThemeExtension {
|
||||
getEdgelessTheme?: (docId?: string) => Signal<ColorScheme>;
|
||||
}
|
||||
|
||||
export function OverrideThemeExtension(service: ThemeExtension): ExtensionType {
|
||||
return {
|
||||
setup: di => {
|
||||
di.override(ThemeExtensionIdentifier, () => service);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const ThemeProvider = createIdentifier<ThemeService>(
|
||||
'AffineThemeProvider'
|
||||
);
|
||||
@@ -78,6 +70,7 @@ export class ThemeService extends Extension {
|
||||
*
|
||||
* @param color - A color value.
|
||||
* @param fallback - If color value processing fails, it will be used as a fallback.
|
||||
* @param theme - Target theme, default is the current theme.
|
||||
* @returns - A color property string.
|
||||
*
|
||||
* @example
|
||||
@@ -112,6 +105,7 @@ export class ThemeService extends Extension {
|
||||
* @param color - A color value.
|
||||
* @param fallback - If color value processing fails, it will be used as a fallback.
|
||||
* @param real - If true, it returns the computed style.
|
||||
* @param theme - Target theme, default is the current theme.
|
||||
* @returns - A color property string.
|
||||
*
|
||||
* @example
|
||||
|
||||
@@ -16,6 +16,7 @@ export class SlashMenuViewExtension extends ViewExtensionProvider {
|
||||
|
||||
override setup(context: ViewExtensionContext) {
|
||||
super.setup(context);
|
||||
if (this.isMobile(context.scope)) return;
|
||||
context.register(SlashMenuExtension);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export class ToolbarViewExtension extends ViewExtensionProvider {
|
||||
|
||||
override setup(context: ViewExtensionContext) {
|
||||
super.setup(context);
|
||||
if (this.isMobile(context.scope)) return;
|
||||
context.register(toolbarWidget);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user