|
| 1 | +/* Copyright 2024 Marimo. All rights reserved. */ |
| 2 | +import { Provider, useAtomValue } from "jotai"; |
| 3 | +import type { JSX } from "react"; |
| 4 | +import { z } from "zod"; |
| 5 | +import { notebookOutline } from "@/core/cells/cells"; |
| 6 | +import { store } from "@/core/state/jotai"; |
| 7 | +import { OutlineList } from "../../components/editor/chrome/panels/outline/floating-outline"; |
| 8 | +import { |
| 9 | + findOutlineElements, |
| 10 | + useActiveOutline, |
| 11 | +} from "../../components/editor/chrome/panels/outline/useActiveOutline"; |
| 12 | +import type { |
| 13 | + IStatelessPlugin, |
| 14 | + IStatelessPluginProps, |
| 15 | +} from "../stateless-plugin"; |
| 16 | + |
| 17 | +interface Data { |
| 18 | + label?: string; |
| 19 | +} |
| 20 | + |
| 21 | +const OutlineContent: React.FC<{ label?: string }> = ({ label }) => { |
| 22 | + const { items } = useAtomValue(notebookOutline); |
| 23 | + const headerElements = findOutlineElements(items); |
| 24 | + const { activeHeaderId, activeOccurrences } = |
| 25 | + useActiveOutline(headerElements); |
| 26 | + |
| 27 | + if (items.length === 0) { |
| 28 | + return ( |
| 29 | + <div className="text-muted-foreground text-sm p-4 border border-dashed border-border rounded-lg"> |
| 30 | + No outline found. Add markdown headings to your notebook to create an |
| 31 | + outline. |
| 32 | + </div> |
| 33 | + ); |
| 34 | + } |
| 35 | + |
| 36 | + return ( |
| 37 | + <div className="border border-border rounded-lg"> |
| 38 | + {label && ( |
| 39 | + <div className="px-4 py-2 border-b border-border font-medium text-sm"> |
| 40 | + {label} |
| 41 | + </div> |
| 42 | + )} |
| 43 | + <OutlineList |
| 44 | + className="max-h-[400px]" |
| 45 | + items={items} |
| 46 | + activeHeaderId={activeHeaderId} |
| 47 | + activeOccurrences={activeOccurrences} |
| 48 | + /> |
| 49 | + </div> |
| 50 | + ); |
| 51 | +}; |
| 52 | + |
| 53 | +export class OutlinePlugin implements IStatelessPlugin<Data> { |
| 54 | + tagName = "marimo-outline"; |
| 55 | + |
| 56 | + validator = z.object({ |
| 57 | + label: z.string().optional(), |
| 58 | + }); |
| 59 | + |
| 60 | + render(props: IStatelessPluginProps<Data>): JSX.Element { |
| 61 | + const { label } = props.data; |
| 62 | + |
| 63 | + return ( |
| 64 | + <Provider store={store}> |
| 65 | + <OutlineContent label={label} /> |
| 66 | + </Provider> |
| 67 | + ); |
| 68 | + } |
| 69 | +} |
0 commit comments