mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-24 09:52:49 +08:00
feat(editor): add experimental feature adapter panel to AFFiNE canary (#12489)
Closes: [BS-2539](https://linear.app/affine-design/issue/BS-2539/为-affine-添加-ef,并且支持在-affine-预览对应的功能) > [!warning] > This feature is only available in the canary build and is intended for debugging purposes. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced an "Adapter Panel" feature with a new sidebar tab for previewing document content in multiple formats (Markdown, PlainText, HTML, Snapshot), controllable via a feature flag. - Added a fully integrated adapter panel component with reactive UI elements for selecting adapters, toggling HTML preview modes, and updating content. - Provided a customizable adapter panel for both main app and playground environments, supporting content transformation pipelines and export previews. - Enabled seamless toggling and live updating of adapter panel content through intuitive menus and controls. - **Localization** - Added English translations and descriptive settings for the Adapter Panel feature. - **Chores** - Added new package and workspace dependencies along with TypeScript project references to support the Adapter Panel modules and components. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -18,6 +18,7 @@ import { TrashPageFooter } from '@affine/core/components/pure/trash-page-footer'
|
||||
import { TopTip } from '@affine/core/components/top-tip';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { RecentDocsService } from '@affine/core/modules/quicksearch';
|
||||
@@ -36,6 +37,7 @@ import { DisposableGroup } from '@blocksuite/affine/global/disposable';
|
||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/inlines/reference';
|
||||
import {
|
||||
AiIcon,
|
||||
ExportIcon,
|
||||
FrameIcon,
|
||||
PropertyIcon,
|
||||
TocIcon,
|
||||
@@ -57,6 +59,7 @@ import { PageNotFound } from '../../404';
|
||||
import * as styles from './detail-page.css';
|
||||
import { DetailPageHeader } from './detail-page-header';
|
||||
import { DetailPageWrapper } from './detail-page-wrapper';
|
||||
import { EditorAdapterPanel } from './tabs/adapter';
|
||||
import { EditorChatPanel } from './tabs/chat';
|
||||
import { EditorFramePanel } from './tabs/frame';
|
||||
import { EditorJournalPanel } from './tabs/journal';
|
||||
@@ -103,6 +106,11 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
|
||||
const enableAI = useEnableAI();
|
||||
|
||||
const featureFlagService = useService(FeatureFlagService);
|
||||
const enableAdapterPanel = useLiveData(
|
||||
featureFlagService.flags.enable_adapter_panel.$
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActiveView) {
|
||||
setActiveBlockSuiteEditor(editorContainer);
|
||||
@@ -360,6 +368,16 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
</Scrollable.Root>
|
||||
</ViewSidebarTab>
|
||||
|
||||
{enableAdapterPanel && (
|
||||
<ViewSidebarTab tabId="adapter" icon={<ExportIcon />}>
|
||||
<Scrollable.Root className={styles.sidebarScrollArea}>
|
||||
<Scrollable.Viewport>
|
||||
<EditorAdapterPanel host={editorContainer?.host ?? null} />
|
||||
</Scrollable.Viewport>
|
||||
</Scrollable.Root>
|
||||
</ViewSidebarTab>
|
||||
)}
|
||||
|
||||
<GlobalPageHistoryModal />
|
||||
{/* FIXME: wait for better ai, <PageAIOnboarding /> */}
|
||||
</FrameworkScope>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
export const root = style({
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { AdapterPanel } from '@blocksuite/affine/fragments/adapter-panel';
|
||||
import {
|
||||
customImageProxyMiddleware,
|
||||
docLinkBaseURLMiddlewareBuilder,
|
||||
embedSyncedDocMiddleware,
|
||||
titleMiddleware,
|
||||
} from '@blocksuite/affine/shared/adapters';
|
||||
import type { EditorHost } from '@blocksuite/affine/std';
|
||||
import type { TransformerMiddleware } from '@blocksuite/affine/store';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
import * as styles from './adapter.css';
|
||||
|
||||
const createImageProxyUrl = (baseUrl: string) => {
|
||||
try {
|
||||
return new URL(BUILD_CONFIG.imageProxyUrl, baseUrl).toString();
|
||||
} catch (error) {
|
||||
console.error('Failed to create image proxy url', error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const createMiddlewares = (
|
||||
host: EditorHost,
|
||||
baseUrl: string
|
||||
): TransformerMiddleware[] => {
|
||||
const imageProxyUrl = createImageProxyUrl(baseUrl);
|
||||
|
||||
return [
|
||||
docLinkBaseURLMiddlewareBuilder(baseUrl, host.store.workspace.id).get(),
|
||||
titleMiddleware(host.store.workspace.meta.docMetas),
|
||||
embedSyncedDocMiddleware('content'),
|
||||
customImageProxyMiddleware(imageProxyUrl),
|
||||
];
|
||||
};
|
||||
|
||||
const getTransformerMiddlewares = (
|
||||
host: EditorHost | null,
|
||||
baseUrl: string
|
||||
) => {
|
||||
if (!host) return [];
|
||||
return createMiddlewares(host, baseUrl);
|
||||
};
|
||||
|
||||
// A wrapper for AdapterPanel
|
||||
export const EditorAdapterPanel = ({ host }: { host: EditorHost | null }) => {
|
||||
const server = useService(ServerService).server;
|
||||
const adapterPanelRef = useRef<AdapterPanel | null>(null);
|
||||
|
||||
const onRefChange = useCallback(
|
||||
(container: HTMLDivElement | null) => {
|
||||
if (container && host && container.children.length === 0) {
|
||||
adapterPanelRef.current = new AdapterPanel();
|
||||
adapterPanelRef.current.store = host.store;
|
||||
adapterPanelRef.current.transformerMiddlewares =
|
||||
getTransformerMiddlewares(host, server.baseUrl);
|
||||
container.append(adapterPanelRef.current);
|
||||
}
|
||||
},
|
||||
[host, server]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (host && adapterPanelRef.current) {
|
||||
adapterPanelRef.current.store = host.store;
|
||||
adapterPanelRef.current.transformerMiddlewares =
|
||||
getTransformerMiddlewares(host, server.baseUrl);
|
||||
}
|
||||
}, [host, server]);
|
||||
|
||||
return <div className={styles.root} ref={onRefChange} />;
|
||||
};
|
||||
@@ -327,6 +327,15 @@ export const AFFINE_FLAGS = {
|
||||
configurable: isCanaryBuild,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_adapter_panel: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-adapter-panel.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-adapter-panel.description',
|
||||
configurable: isCanaryBuild,
|
||||
defaultState: false,
|
||||
},
|
||||
} satisfies { [key in string]: FlagInfo };
|
||||
|
||||
// oxlint-disable-next-line no-redeclare
|
||||
|
||||
@@ -5890,6 +5890,14 @@ export function useAFFiNEI18N(): {
|
||||
* `Once enabled, you can preview HTML in code block.`
|
||||
*/
|
||||
["com.affine.settings.workspace.experimental-features.enable-code-block-html-preview.description"](): string;
|
||||
/**
|
||||
* `Adapter Panel`
|
||||
*/
|
||||
["com.affine.settings.workspace.experimental-features.enable-adapter-panel.name"](): string;
|
||||
/**
|
||||
* `Once enabled, you can preview adapter export content in the right side bar.`
|
||||
*/
|
||||
["com.affine.settings.workspace.experimental-features.enable-adapter-panel.description"](): string;
|
||||
/**
|
||||
* `Only an owner can edit the workspace avatar and name. Changes will be shown for everyone.`
|
||||
*/
|
||||
|
||||
@@ -1471,6 +1471,8 @@
|
||||
"com.affine.settings.workspace.experimental-features.enable-table-virtual-scroll.description": "Once enabled, switch table view to virtual scroll mode in Database Block.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-code-block-html-preview.name": "Code block HTML preview",
|
||||
"com.affine.settings.workspace.experimental-features.enable-code-block-html-preview.description": "Once enabled, you can preview HTML in code block.",
|
||||
"com.affine.settings.workspace.experimental-features.enable-adapter-panel.name": "Adapter Panel",
|
||||
"com.affine.settings.workspace.experimental-features.enable-adapter-panel.description": "Once enabled, you can preview adapter export content in the right side bar.",
|
||||
"com.affine.settings.workspace.not-owner": "Only an owner can edit the workspace avatar and name. Changes will be shown for everyone.",
|
||||
"com.affine.settings.workspace.preferences": "Preference",
|
||||
"com.affine.settings.workspace.billing": "Team's Billing",
|
||||
|
||||
Reference in New Issue
Block a user