mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
refactor(editor): move editor components to frontend core (#10335)
### TL;DR Moved editor components from BlockSuite presets to AFFiNE core and updated imports accordingly. ### What changed? - Relocated `EdgelessEditor` and `PageEditor` components from BlockSuite presets to AFFiNE core - Removed basic editor examples from playground - Updated import paths across the codebase to reference new component locations - Added editor effects registration in AFFiNE core - Removed editor exports from BlockSuite presets ### How to test? 1. Launch the application 2. Verify both page and edgeless editors load correctly 3. Confirm editor functionality remains intact including: - Document editing - Mode switching - Editor toolbars and controls - Multiple editor instances ### Why make this change? This change better aligns with AFFiNE's architecture by moving editor components closer to where they are used. It reduces coupling with BlockSuite presets and gives AFFiNE more direct control over editor customization and implementation.
This commit is contained in:
107
packages/frontend/core/src/blocksuite/editors/edgeless-editor.ts
Normal file
107
packages/frontend/core/src/blocksuite/editors/edgeless-editor.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { BlockStdScope, ShadowlessElement } from '@blocksuite/affine/block-std';
|
||||
import {
|
||||
EdgelessEditorBlockSpecs,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/utils';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
import { css, html, nothing, type TemplateResult } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
|
||||
export class EdgelessEditor extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
edgeless-editor {
|
||||
font-family: var(--affine-font-family);
|
||||
background: var(--affine-background-primary-color);
|
||||
}
|
||||
|
||||
edgeless-editor * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media print {
|
||||
edgeless-editor {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.affine-edgeless-viewport {
|
||||
display: block;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: clip;
|
||||
container-name: viewport;
|
||||
container-type: inline-size;
|
||||
}
|
||||
`;
|
||||
|
||||
get host() {
|
||||
try {
|
||||
return this.std.host;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._disposables.add(
|
||||
this.doc.slots.rootAdded.on(() => this.requestUpdate())
|
||||
);
|
||||
this.std = new BlockStdScope({
|
||||
store: this.doc,
|
||||
extensions: this.specs,
|
||||
});
|
||||
}
|
||||
|
||||
override async getUpdateComplete(): Promise<boolean> {
|
||||
const result = await super.getUpdateComplete();
|
||||
await this.host?.updateComplete;
|
||||
return result;
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this.doc.root) return nothing;
|
||||
|
||||
const std = this.std;
|
||||
const theme = std.get(ThemeProvider).edgeless$.value;
|
||||
return html`
|
||||
<div class="affine-edgeless-viewport" data-theme=${theme}>
|
||||
${guard([std], () => std.render())}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
override willUpdate(
|
||||
changedProperties: Map<string | number | symbol, unknown>
|
||||
) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has('doc')) {
|
||||
this.std = new BlockStdScope({
|
||||
store: this.doc,
|
||||
extensions: this.specs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor doc!: Store;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor editor!: TemplateResult;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor specs = EdgelessEditorBlockSpecs;
|
||||
|
||||
@state()
|
||||
accessor std!: BlockStdScope;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'edgeless-editor': EdgelessEditor;
|
||||
}
|
||||
}
|
||||
10
packages/frontend/core/src/blocksuite/editors/index.ts
Normal file
10
packages/frontend/core/src/blocksuite/editors/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { EdgelessEditor } from './edgeless-editor';
|
||||
import { PageEditor } from './page-editor';
|
||||
|
||||
export * from './edgeless-editor';
|
||||
export * from './page-editor';
|
||||
|
||||
export function effects() {
|
||||
customElements.define('page-editor', PageEditor);
|
||||
customElements.define('edgeless-editor', EdgelessEditor);
|
||||
}
|
||||
116
packages/frontend/core/src/blocksuite/editors/page-editor.ts
Normal file
116
packages/frontend/core/src/blocksuite/editors/page-editor.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
BlockStdScope,
|
||||
EditorHost,
|
||||
ShadowlessElement,
|
||||
} from '@blocksuite/affine/block-std';
|
||||
import { PageEditorBlockSpecs, ThemeProvider } from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
noop,
|
||||
SignalWatcher,
|
||||
WithDisposable,
|
||||
} from '@blocksuite/affine/global/utils';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { property, state } from 'lit/decorators.js';
|
||||
import { guard } from 'lit/directives/guard.js';
|
||||
|
||||
noop(EditorHost);
|
||||
|
||||
export class PageEditor extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
page-editor {
|
||||
font-family: var(--affine-font-family);
|
||||
background: var(--affine-background-primary-color);
|
||||
}
|
||||
|
||||
page-editor * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media print {
|
||||
page-editor {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.affine-page-viewport {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
container-name: viewport;
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.page-editor-container {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
get host() {
|
||||
try {
|
||||
return this.std.host;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._disposables.add(
|
||||
this.doc.slots.rootAdded.on(() => this.requestUpdate())
|
||||
);
|
||||
this.std = new BlockStdScope({
|
||||
store: this.doc,
|
||||
extensions: this.specs,
|
||||
});
|
||||
}
|
||||
|
||||
override async getUpdateComplete(): Promise<boolean> {
|
||||
const result = await super.getUpdateComplete();
|
||||
await this.host?.updateComplete;
|
||||
return result;
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this.doc.root) return nothing;
|
||||
|
||||
const std = this.std;
|
||||
const theme = std.get(ThemeProvider).app$.value;
|
||||
return html`
|
||||
<div data-theme=${theme} class="page-editor-container">
|
||||
${guard([std], () => std.render())}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
override willUpdate(
|
||||
changedProperties: Map<string | number | symbol, unknown>
|
||||
) {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has('doc')) {
|
||||
this.std = new BlockStdScope({
|
||||
store: this.doc,
|
||||
extensions: this.specs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor doc!: Store;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor specs = PageEditorBlockSpecs;
|
||||
|
||||
@state()
|
||||
accessor std!: BlockStdScope;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'page-editor': PageEditor;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import type {
|
||||
EdgelessEditor,
|
||||
PageEditor,
|
||||
} from '@affine/core/blocksuite/editors';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import type { BlockStdScope, EditorHost } from '@blocksuite/affine/block-std';
|
||||
import {
|
||||
appendParagraphCommand,
|
||||
type DocMode,
|
||||
type DocTitle,
|
||||
focusBlockEnd,
|
||||
getLastNoteBlock,
|
||||
type RootBlockModel,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import type {
|
||||
AffineEditorContainer,
|
||||
EdgelessEditor,
|
||||
PageEditor,
|
||||
} from '@blocksuite/affine/presets';
|
||||
import { type Store } from '@blocksuite/affine/store';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
@@ -37,6 +38,18 @@ interface BlocksuiteEditorContainerProps {
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface AffineEditorContainer extends HTMLElement {
|
||||
page: Store;
|
||||
doc: Store;
|
||||
docTitle: DocTitle;
|
||||
host: EditorHost;
|
||||
model: RootBlockModel | null;
|
||||
updateComplete: Promise<boolean>;
|
||||
mode: DocMode;
|
||||
origin: HTMLDivElement;
|
||||
std: BlockStdScope;
|
||||
}
|
||||
|
||||
export const BlocksuiteEditorContainer = forwardRef<
|
||||
AffineEditorContainer,
|
||||
BlocksuiteEditorContainerProps
|
||||
@@ -66,9 +79,11 @@ export const BlocksuiteEditorContainer = forwardRef<
|
||||
return docTitleRef.current;
|
||||
},
|
||||
get host() {
|
||||
return mode === 'page'
|
||||
? docRef.current?.host
|
||||
: edgelessRef.current?.host;
|
||||
return (
|
||||
(mode === 'page'
|
||||
? docRef.current?.host
|
||||
: edgelessRef.current?.host) ?? null
|
||||
);
|
||||
},
|
||||
get model() {
|
||||
return page.root as any;
|
||||
@@ -110,7 +125,7 @@ export const BlocksuiteEditorContainer = forwardRef<
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
}) as unknown as AffineEditorContainer & { origin: HTMLDivElement };
|
||||
}) as AffineEditorContainer;
|
||||
|
||||
return proxy;
|
||||
}, [mode, page]);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
LinkPreviewerService,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/affine/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
@@ -21,7 +20,10 @@ import type { CSSProperties } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { DefaultOpenProperty } from '../../doc-properties';
|
||||
import { BlocksuiteEditorContainer } from './blocksuite-editor-container';
|
||||
import {
|
||||
type AffineEditorContainer,
|
||||
BlocksuiteEditorContainer,
|
||||
} from './blocksuite-editor-container';
|
||||
import { NoPageRootError } from './no-page-error';
|
||||
|
||||
export type EditorProps = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { effects as editorEffects } from '@affine/core/blocksuite/editors';
|
||||
import { registerBlocksuitePresetsCustomComponents } from '@affine/core/blocksuite/presets/effects';
|
||||
import { effects as bsEffects } from '@blocksuite/affine/effects';
|
||||
|
||||
@@ -6,7 +7,9 @@ import { effects as patchEffects } from './specs/preview';
|
||||
|
||||
bsEffects();
|
||||
patchEffects();
|
||||
editorEffects();
|
||||
edgelessEffects();
|
||||
registerBlocksuitePresetsCustomComponents();
|
||||
|
||||
export * from './blocksuite-editor';
|
||||
export * from './blocksuite-editor-container';
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
useConfirmModal,
|
||||
useLitPortalFactory,
|
||||
} from '@affine/component';
|
||||
import { EdgelessEditor, PageEditor } from '@affine/core/blocksuite/editors';
|
||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||
import { DocService, DocsService } from '@affine/core/modules/doc';
|
||||
import type {
|
||||
@@ -27,7 +28,6 @@ import {
|
||||
slashMenuWidget,
|
||||
surfaceRefToolbarWidget,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { EdgelessEditor, PageEditor } from '@blocksuite/affine/presets';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
import {
|
||||
useFramework,
|
||||
|
||||
@@ -21,12 +21,12 @@ import {
|
||||
titleMiddleware,
|
||||
ZipTransformer,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import { type Store, Transformer } from '@blocksuite/affine/store';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import type { AffineEditorContainer } from '../../blocksuite/block-suite-editor/blocksuite-editor-container';
|
||||
import { useAsyncCallback } from '../affine-async-hooks';
|
||||
|
||||
type ExportType = 'pdf' | 'html' | 'png' | 'markdown' | 'snapshot';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import type { SetStateAction } from 'jotai';
|
||||
import { atom, useAtom } from 'jotai';
|
||||
|
||||
import type { AffineEditorContainer } from '../blocksuite/block-suite-editor';
|
||||
|
||||
const activeEditorContainerAtom = atom<AffineEditorContainer | null>(null);
|
||||
|
||||
export function useActiveBlocksuiteEditor(): [
|
||||
|
||||
@@ -9,7 +9,6 @@ import { UrlService } from '@affine/core/modules/url';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { TextSelection } from '@blocksuite/affine/block-std';
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import { useService, useServiceOptional } from '@toeverything/infra';
|
||||
import { useStore } from 'jotai';
|
||||
import { useTheme } from 'next-themes';
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils';
|
||||
import { EditorSettingService } from '../../modules/editor-setting';
|
||||
import { CMDKQuickSearchService } from '../../modules/quicksearch/services/cmdk';
|
||||
import type { AffineEditorContainer } from '../blocksuite/block-suite-editor';
|
||||
import { useActiveBlocksuiteEditor } from './use-block-suite-editor';
|
||||
import { useNavigateHelper } from './use-navigate-helper';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import './page-detail-editor.css';
|
||||
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect } from 'react';
|
||||
@@ -8,6 +7,7 @@ import { useEffect } from 'react';
|
||||
import { DocService } from '../modules/doc';
|
||||
import { EditorService } from '../modules/editor';
|
||||
import { EditorSettingService } from '../modules/editor-setting';
|
||||
import type { AffineEditorContainer } from './blocksuite/block-suite-editor';
|
||||
import { BlockSuiteEditor } from './blocksuite/block-suite-editor';
|
||||
import * as styles from './page-detail-editor.css';
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
||||
import type { ChatPanel } from '@affine/core/blocksuite/presets';
|
||||
import { AIProvider } from '@affine/core/blocksuite/presets';
|
||||
import { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding';
|
||||
import type { AffineEditorContainer } from '@affine/core/components/blocksuite/block-suite-editor';
|
||||
import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-viewer';
|
||||
import { DocPropertySidebar } from '@affine/core/components/doc-properties/sidebar';
|
||||
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
type Disposable,
|
||||
DisposableGroup,
|
||||
} from '@blocksuite/affine/global/utils';
|
||||
import { type AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import {
|
||||
AiIcon,
|
||||
FrameIcon,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChatPanel } from '@affine/core/blocksuite/presets';
|
||||
import type { AffineEditorContainer } from '@affine/core/components/blocksuite/block-suite-editor';
|
||||
import { createPageModePreviewSpecs } from '@affine/core/components/blocksuite/block-suite-editor/specs/preview';
|
||||
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
|
||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
DocModeProvider,
|
||||
RefNodeSlotsProvider,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import { useFramework } from '@toeverything/infra';
|
||||
import { forwardRef, useEffect, useRef } from 'react';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Scrollable } from '@affine/component';
|
||||
import type { AffineEditorContainer } from '@affine/core/components/blocksuite/block-suite-editor';
|
||||
import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-viewer';
|
||||
import { useActiveBlocksuiteEditor } from '@affine/core/components/hooks/use-block-suite-editor';
|
||||
import { usePageDocumentTitle } from '@affine/core/components/hooks/use-global-state';
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
RefNodeSlotsProvider,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/affine/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import { Logo1Icon } from '@blocksuite/icons/rc';
|
||||
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useThemeColorV2 } from '@affine/component';
|
||||
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
||||
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
|
||||
import type { AffineEditorContainer } from '@affine/core/components/blocksuite/block-suite-editor';
|
||||
import { useActiveBlocksuiteEditor } from '@affine/core/components/hooks/use-block-suite-editor';
|
||||
import { usePageDocumentTitle } from '@affine/core/components/hooks/use-global-state';
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
@@ -28,7 +29,6 @@ import {
|
||||
RefNodeSlotsProvider,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/affine/global/utils';
|
||||
import { type AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import {
|
||||
FrameworkScope,
|
||||
useLiveData,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AffineEditorContainer } from '@affine/core/components/blocksuite/block-suite-editor';
|
||||
import type { DefaultOpenProperty } from '@affine/core/components/doc-properties';
|
||||
import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx';
|
||||
import {
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
HighlightSelection,
|
||||
type ReferenceParams,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import type { InlineEditor } from '@blocksuite/inline';
|
||||
import { effect } from '@preact/signals-core';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Scrollable } from '@affine/component';
|
||||
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
||||
import { AIProvider } from '@affine/core/blocksuite/presets';
|
||||
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
|
||||
import type { AffineEditorContainer } from '@affine/core/components/blocksuite/block-suite-editor';
|
||||
import { EditorOutlineViewer } from '@affine/core/components/blocksuite/outline-viewer';
|
||||
import { PageNotFound } from '@affine/core/desktop/pages/404';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
type Disposable,
|
||||
DisposableGroup,
|
||||
} from '@blocksuite/affine/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import {
|
||||
FrameworkScope,
|
||||
useLiveData,
|
||||
|
||||
Reference in New Issue
Block a user