feat: add outline plugin (#3624)

Co-authored-by: codert <codert.sn@gmail.com>
This commit is contained in:
Alex Yang
2023-08-16 02:34:26 -05:00
committed by GitHub
parent 93d352f3d8
commit 6f9dfcc3c1
12 changed files with 312 additions and 71 deletions

View File

@@ -0,0 +1,96 @@
import { Tooltip } from '@affine/component';
import { deleteLayoutAtom, pushLayoutAtom } from '@affine/sdk/entry';
import { TOCNotesPanel } from '@blocksuite/blocks';
import { RightSidebarIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store';
import { IconButton } from '@toeverything/components/button';
import { useAtom, useSetAtom } from 'jotai';
import type { ComponentType, PropsWithChildren } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { blocksuiteRootAtom } from './atom';
const Outline = () => {
const ref = useRef<HTMLDivElement>(null);
const tocPanelRef = useRef<TOCNotesPanel | null>(null);
const [blocksuite] = useAtom(blocksuiteRootAtom);
if (!tocPanelRef.current) {
tocPanelRef.current = new TOCNotesPanel();
}
if (blocksuite?.page !== tocPanelRef.current?.page) {
(tocPanelRef.current as TOCNotesPanel).page = blocksuite?.page as Page;
}
useEffect(() => {
if (!ref.current || !tocPanelRef.current) return;
const container = ref.current;
const tocPanel = tocPanelRef.current as TOCNotesPanel;
container.appendChild(tocPanel);
return () => {
container.removeChild(tocPanel);
};
}, []);
return (
<div
className={`outline-wrapper`}
style={{
height: '100%',
borderLeft: `1px solid var(--affine-border-color)`,
}}
ref={ref}
/>
);
};
export const HeaderItem = ({
Provider,
}: {
Provider: ComponentType<PropsWithChildren>;
}) => {
const [open, setOpen] = useState(false);
const pushLayout = useSetAtom(pushLayoutAtom);
const deleteLayout = useSetAtom(deleteLayoutAtom);
return (
<Tooltip content="Plugin Enabled">
<IconButton
onClick={useCallback(() => {
if (!open) {
setOpen(true);
pushLayout(
'@affine/outline-plugin',
div => {
const root = createRoot(div);
div.style.height = '100%';
root.render(
<Provider>
<Outline />
</Provider>
);
return () => {
root.unmount();
};
},
{
maxWidth: [undefined, 300],
}
);
} else {
setOpen(false);
deleteLayout('@affine/outline-plugin');
}
}, [Provider, deleteLayout, open, pushLayout])}
>
<RightSidebarIcon />
</IconButton>
</Tooltip>
);
};

View File

@@ -0,0 +1,5 @@
import { atom } from 'jotai';
export const blocksuiteRootAtom = atom(() =>
document.querySelector('block-suite-root')
);

View File

@@ -0,0 +1,41 @@
import type { PluginContext } from '@affine/sdk/entry';
import { registerTOCComponents } from '@blocksuite/blocks';
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';
import { HeaderItem } from './app';
export const entry = (context: PluginContext) => {
console.log('register outline');
context.register('headerItem', div => {
registerTOCComponents(components => {
for (const compName in components) {
if (window.customElements.get(compName)) continue;
window.customElements.define(
compName,
components[compName as keyof typeof components]
);
}
});
div.style.height = '100%';
const root = createRoot(div);
root.render(
createElement(
context.utils.PluginProvider,
{},
createElement(HeaderItem, {
Provider: context.utils.PluginProvider,
})
)
);
return () => {
root.unmount();
};
});
return () => {};
};