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:
L-Sun
2024-07-29 10:19:57 +00:00
parent 1b4d65fd64
commit 622715d2f3
7 changed files with 139 additions and 5 deletions

View File

@@ -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>
</>
);

View File

@@ -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 />}>

View File

@@ -0,0 +1,8 @@
import { style } from '@vanilla-extract/css';
export const root = style({
position: 'fixed',
top: 256,
right: 22,
maxHeight: 'calc(100% - 16px)',
});

View File

@@ -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} />;
};

View File

@@ -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);
}
}, []);