mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
feat(core): outline viewer (quick toc) (#7614)
Close: [BS-949](https://linear.app/affine-design/issue/BS-949/outline-viewer-加入到affine) Details are in this PR: https://github.com/toeverything/blocksuite/pull/7704
This commit is contained in:
@@ -80,11 +80,13 @@ const ExperimentalFeaturesItem = ({
|
||||
isMutating,
|
||||
checked,
|
||||
onChange,
|
||||
testId,
|
||||
}: {
|
||||
title: React.ReactNode;
|
||||
isMutating?: boolean;
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
testId?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.switchRow}>
|
||||
@@ -93,6 +95,7 @@ const ExperimentalFeaturesItem = ({
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
className={isMutating ? styles.switchDisabled : ''}
|
||||
data-testid={testId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -121,6 +124,26 @@ const SplitViewSettingRow = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const OutlineViewerSettingRow = () => {
|
||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||
|
||||
const onToggle = useCallback(
|
||||
(checked: boolean) => {
|
||||
updateSettings('enableOutlineViewer', checked);
|
||||
},
|
||||
[updateSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
<ExperimentalFeaturesItem
|
||||
title="Outline Viewer"
|
||||
checked={appSettings.enableOutlineViewer}
|
||||
onChange={onToggle}
|
||||
testId="outline-viewer-switch"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// feature flag -> display name
|
||||
const blocksuiteFeatureFlags: Partial<Record<keyof BlockSuiteFlags, string>> = {
|
||||
enable_expand_database_block: 'Enable Expand Database Block',
|
||||
@@ -177,6 +200,7 @@ const ExperimentalFeaturesMain = () => {
|
||||
>
|
||||
<SplitViewSettingRow />
|
||||
<BlocksuiteFeatureFlagSettings />
|
||||
<OutlineViewerSettingRow />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -63,10 +63,11 @@ import { performanceRenderLogger } from '../../../shared';
|
||||
import { PageNotFound } from '../../404';
|
||||
import * as styles from './detail-page.css';
|
||||
import { DetailPageHeader } from './detail-page-header';
|
||||
import { EditorOutlineViewer } from './outline-viewer';
|
||||
import { EditorChatPanel } from './tabs/chat';
|
||||
import { EditorFramePanel } from './tabs/frame';
|
||||
import { EditorJournalPanel } from './tabs/journal';
|
||||
import { EditorOutline } from './tabs/outline';
|
||||
import { EditorOutlinePanel } from './tabs/outline';
|
||||
|
||||
const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
@@ -206,6 +207,11 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
[jumpToPageBlock, docCollection.id, openPage, jumpToTag, workspace.id]
|
||||
);
|
||||
|
||||
const openOutlinePanel = useCallback(() => {
|
||||
workbench.openSidebar();
|
||||
view.activeSidebarTab('outline');
|
||||
}, [workbench, view]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewHeader>
|
||||
@@ -239,6 +245,12 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
</AffineErrorBoundary>
|
||||
{isInTrash ? <TrashPageFooter /> : null}
|
||||
</div>
|
||||
{appSettings.enableOutlineViewer && (
|
||||
<EditorOutlineViewer
|
||||
editor={editor}
|
||||
toggleOutlinePanel={openOutlinePanel}
|
||||
/>
|
||||
)}
|
||||
</ViewBody>
|
||||
|
||||
<ViewSidebarTab tabId="chat" icon={<AiIcon />} unmountOnInactive={false}>
|
||||
@@ -250,7 +262,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
||||
</ViewSidebarTab>
|
||||
|
||||
<ViewSidebarTab tabId="outline" icon={<TocIcon />}>
|
||||
<EditorOutline editor={editor} />
|
||||
<EditorOutlinePanel editor={editor} />
|
||||
</ViewSidebarTab>
|
||||
|
||||
<ViewSidebarTab tabId="frame" icon={<FrameIcon />}>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const root = style({
|
||||
position: 'fixed',
|
||||
top: 256,
|
||||
right: 22,
|
||||
maxHeight: 'calc(100% - 16px)',
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import { OutlineViewer } from '@blocksuite/presets';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import * as styles from './outline-viewer.css';
|
||||
|
||||
export const EditorOutlineViewer = ({
|
||||
editor,
|
||||
toggleOutlinePanel,
|
||||
}: {
|
||||
editor: AffineEditorContainer | null;
|
||||
toggleOutlinePanel: () => void;
|
||||
}) => {
|
||||
const outlineViewerRef = useRef<OutlineViewer | null>(null);
|
||||
|
||||
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
||||
if (container) {
|
||||
if (outlineViewerRef.current === null) {
|
||||
console.error('outline viewer should be initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
container.append(outlineViewerRef.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!outlineViewerRef.current) {
|
||||
outlineViewerRef.current = new OutlineViewer();
|
||||
(outlineViewerRef.current as OutlineViewer).editor = editor;
|
||||
(outlineViewerRef.current as OutlineViewer).toggleOutlinePanel =
|
||||
toggleOutlinePanel;
|
||||
}
|
||||
|
||||
return <div className={styles.root} ref={onRefChange} />;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||
import { OutlinePanel } from '@blocksuite/presets';
|
||||
import { useCallback, useRef } from 'react';
|
||||
@@ -6,7 +5,7 @@ import { useCallback, useRef } from 'react';
|
||||
import * as styles from './outline.css';
|
||||
|
||||
// A wrapper for TOCNotesPanel
|
||||
export const EditorOutline = ({
|
||||
export const EditorOutlinePanel = ({
|
||||
editor,
|
||||
}: {
|
||||
editor: AffineEditorContainer | null;
|
||||
@@ -15,7 +14,10 @@ export const EditorOutline = ({
|
||||
|
||||
const onRefChange = useCallback((container: HTMLDivElement | null) => {
|
||||
if (container) {
|
||||
assertExists(outlinePanelRef.current, 'toc panel should be initialized');
|
||||
if (outlinePanelRef.current === null) {
|
||||
console.error('outline panel should be initialized');
|
||||
return;
|
||||
}
|
||||
container.append(outlinePanelRef.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user