Skip to content

Commit be7369c

Browse files
feat 5834: add outline - mo.outline() (#6430)
## 📝 Summary Added mo.outline() element to show outline. Fixes [5834](#5834) ## 🔍 Description of Changes <img width="2255" height="1072" alt="image" src="https://github.com/user-attachments/assets/9ad62c34-4857-4b39-ad34-effae5861636" /> --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 3952c54 commit be7369c

File tree

5 files changed

+112
-0
lines changed

5 files changed

+112
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
}

frontend/src/plugins/plugins.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { JsonOutputPlugin } from "./layout/JsonOutputPlugin";
4545
import { LazyPlugin } from "./layout/LazyPlugin";
4646
import { MimeRendererPlugin } from "./layout/MimeRenderPlugin";
4747
import { MermaidPlugin } from "./layout/mermaid/MermaidPlugin";
48+
import { OutlinePlugin } from "./layout/OutlinePlugin";
4849
import { ProgressPlugin } from "./layout/ProgressPlugin";
4950
import { RoutesPlugin } from "./layout/RoutesPlugin";
5051
import { StatPlugin } from "./layout/StatPlugin";
@@ -99,6 +100,7 @@ const LAYOUT_PLUGINS: Array<IStatelessPlugin<unknown>> = [
99100
new MimeRendererPlugin(),
100101
new MermaidPlugin(),
101102
new NavigationMenuPlugin(),
103+
new OutlinePlugin(),
102104
new ProgressPlugin(),
103105
new RoutesPlugin(),
104106
new StatPlugin(),

marimo/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"nav_menu",
6161
"notebook_dir",
6262
"notebook_location",
63+
"outline",
6364
"output",
6465
"pdf",
6566
"persistent_cache",
@@ -98,6 +99,7 @@
9899
from marimo._output.hypertext import Html
99100
from marimo._output.justify import center, left, right
100101
from marimo._output.md import latex, md
102+
from marimo._output.outline import outline
101103
from marimo._output.show_code import show_code
102104
from marimo._plugins import ui
103105
from marimo._plugins.stateless import mpl, status

marimo/_output/outline.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2024 Marimo. All rights reserved.
2+
from __future__ import annotations
3+
4+
from marimo._output.hypertext import Html
5+
from marimo._output.rich_help import mddoc
6+
from marimo._plugins.core.web_component import build_stateless_plugin
7+
8+
9+
@mddoc
10+
def outline(*, label: str = "") -> Html:
11+
"""Display a table of contents outline showing all markdown headers in the notebook.
12+
13+
The outline automatically extracts all markdown headers from executed cells
14+
and displays them in a hierarchical structure with clickable navigation.
15+
16+
Examples:
17+
Basic outline:
18+
```python
19+
mo.outline()
20+
```
21+
22+
With custom label:
23+
```python
24+
mo.outline(label="Table of Contents")
25+
```
26+
27+
Args:
28+
label (str, optional): A descriptive label for the outline. Defaults to "".
29+
30+
Returns:
31+
Html: An HTML object that renders the outline component.
32+
"""
33+
return Html(
34+
build_stateless_plugin(
35+
component_name="marimo-outline",
36+
args={"label": label},
37+
)
38+
)

tests/snapshots/api.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mpl
5151
nav_menu
5252
notebook_dir
5353
notebook_location
54+
outline
5455
output
5556
append
5657
clear

0 commit comments

Comments
 (0)