seemore 1.12.2 → 1.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -55
- package/package.json +1 -1
- package/src/app/entry.prerender.tsx +12 -2
- package/src/app/export/exportPage.ts +65 -2
- package/src/app/export/standalone.ts +22 -1
package/README.md
CHANGED
|
@@ -33,9 +33,11 @@
|
|
|
33
33
|
</p>
|
|
34
34
|
</p>
|
|
35
35
|
|
|
36
|
-
AI tools write Markdown
|
|
36
|
+
AI tools write Markdown fast. They write specs, notes, guides, READMEs, and whole folders of files. No person can read that much Markdown at that speed.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
A folder of `.md` files has no order. You cannot click a link between files. You cannot search across them.
|
|
39
|
+
|
|
40
|
+
**seemore** points at that folder and renders it as a real site. It does not move your files. It does not need any code.
|
|
39
41
|
|
|
40
42
|
<p align="center">
|
|
41
43
|
<img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/home.png" alt="The seemore site: a terminal typing npx seemore to serve a folder of notes at localhost:4040, with an arrow pointing to the browser preview" width="640"/>
|
|
@@ -43,24 +45,24 @@ AI tools write Markdown faster than anyone can read it: specs, notes, guides, RE
|
|
|
43
45
|
|
|
44
46
|
## Quick start
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
seemore needs [Node.js](https://nodejs.org) 20 or newer. You do not install anything. You do not write a config file. You do not move any files.
|
|
47
49
|
|
|
48
50
|
```bash
|
|
49
51
|
cd my-docs
|
|
50
52
|
npx seemore # renders this folder at http://localhost:4040
|
|
51
53
|
```
|
|
52
54
|
|
|
53
|
-
`npx seemore` runs seemore without adding it to your
|
|
55
|
+
`npx seemore` runs seemore without adding it to your project's dependencies.
|
|
54
56
|
|
|
55
|
-
Open the address it prints.
|
|
57
|
+
Open the address it prints. The site stays live after that. Add, rename, retitle, or delete a file, and the site updates at once. Navigation and search update too.
|
|
56
58
|
|
|
57
|
-
The [seemore website](https://arifszn.github.io/seemore) is
|
|
59
|
+
The [seemore website](https://arifszn.github.io/seemore) is a folder of Markdown, rendered by seemore. Your own folder will look the same way.
|
|
58
60
|
|
|
59
61
|
<details open>
|
|
60
62
|
<summary><strong>Or ask an AI agent to set it up</strong></summary>
|
|
61
63
|
<br/>
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
If you do not want to use a terminal, use the [agent skill](https://github.com/arifszn/seemore-skill) instead. It does the setup for you.
|
|
64
66
|
|
|
65
67
|
### Claude Code (Plugin Marketplace)
|
|
66
68
|
|
|
@@ -74,9 +76,9 @@ Not comfortable in a terminal? There's an [agent skill](https://github.com/arifs
|
|
|
74
76
|
|
|
75
77
|
### Other agents (Codex, OpenCode, Antigravity, Gemini, Cursor, …)
|
|
76
78
|
|
|
77
|
-
This
|
|
79
|
+
This skill works in any agent that supports the `SKILL.md` format. See **[INSTALL.md](https://github.com/arifszn/seemore-skill/blob/main/INSTALL.md)** for the skill path and commands for each agent.
|
|
78
80
|
|
|
79
|
-
Or
|
|
81
|
+
Or paste this to your agent:
|
|
80
82
|
|
|
81
83
|
```
|
|
82
84
|
Fetch and follow the install instructions from
|
|
@@ -85,13 +87,13 @@ https://raw.githubusercontent.com/arifszn/seemore-skill/refs/heads/main/INSTALL.
|
|
|
85
87
|
|
|
86
88
|
### Usage
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
After you install the skill, tell your agent what you want:
|
|
89
91
|
|
|
90
92
|
```
|
|
91
93
|
Turn this folder of notes into a docs site I can read in my browser
|
|
92
94
|
```
|
|
93
95
|
|
|
94
|
-
Or
|
|
96
|
+
Or call the skill by name:
|
|
95
97
|
|
|
96
98
|
```
|
|
97
99
|
/seemore
|
|
@@ -101,49 +103,49 @@ Or invoke the skill by name:
|
|
|
101
103
|
|
|
102
104
|
## Three ways to use it
|
|
103
105
|
|
|
104
|
-
- **[In your browser](#edit-from-the-browser)**: `npx seemore` serves the folder you
|
|
105
|
-
- **[In your code editor](#view-in-your-code-editor)**: an extension
|
|
106
|
-
- **[As a static site](#publish-it-to-the-web)**: `npx seemore build` exports plain HTML you can host anywhere
|
|
106
|
+
- **[In your browser](#edit-from-the-browser)**: `npx seemore` serves the folder you are in. The site updates live as you edit.
|
|
107
|
+
- **[In your code editor](#view-in-your-code-editor)**: an extension shows the same site in a panel next to your file, in VS Code and VS Code-compatible editors like Cursor and Antigravity.
|
|
108
|
+
- **[As a static site](#publish-it-to-the-web)**: `npx seemore build` exports plain HTML you can host anywhere. seemore is a docs framework, not only a preview tool.
|
|
107
109
|
|
|
108
110
|
<p align="center">
|
|
109
|
-
<video src="https://github.com/user-attachments/assets/
|
|
111
|
+
<video src="https://github.com/user-attachments/assets/4238308d-8152-4386-9095-0f135447475f" width="640" controls muted></video>
|
|
110
112
|
</p>
|
|
111
113
|
|
|
112
114
|
## What you get
|
|
113
115
|
|
|
114
|
-
Point seemore at
|
|
116
|
+
Point seemore at any Markdown you already have: AI-written notes, project docs, RFCs, API references, specs, or an engineering handbook. You get:
|
|
115
117
|
|
|
116
|
-
- **Zero config**: no config file, no code, no files to move
|
|
117
|
-
- **Live preview**: files appear, disappear and reorder as you edit them
|
|
118
|
+
- **Zero config**: no config file, no code, no files to move. A plain folder works in the browser, in your editor, and as a static build
|
|
119
|
+
- **Live preview**: files appear, disappear and reorder as you edit them. Navigation and search update too
|
|
118
120
|
- **Edit in place**: double-click any block in the preview to fix its Markdown
|
|
119
|
-
- **Editor integration**: one extension covers VS Code, Cursor, Antigravity and other VS Code-compatible editors, remote workspaces
|
|
120
|
-
- **Documentation framework**: `seemore build`
|
|
121
|
+
- **Editor integration**: one extension covers VS Code, Cursor, Antigravity and other VS Code-compatible editors, including remote workspaces
|
|
122
|
+
- **Documentation framework**: `seemore build` renders the whole site to HTML, ready to deploy on any host
|
|
121
123
|
- **Password protection**: protect the built site with one shared password, with no server required
|
|
122
124
|
- **Page actions**: copy a page as Markdown, or export it as one self-contained HTML file to drop into Slack, email or an AI chat
|
|
123
|
-
- **Search built in**: static full-text search with no server and no account, with shareable highlighted results
|
|
125
|
+
- **Search built in**: static full-text search with no server and no account, with shareable highlighted results. [Algolia](https://algolia.com) and [Orama Cloud](https://orama.com) drop in when you want a hosted index
|
|
124
126
|
- **Rich Markdown**: GitHub Flavoured Markdown, admonitions, steps, `[[wikilinks]]`, [Mermaid](https://mermaid.js.org) and [D2](https://d2lang.com) diagrams, click-to-zoom images, embedded PDFs
|
|
125
127
|
- **Full MDX**: `.mdx` pages take real JSX, your own React components and CSS
|
|
126
|
-
- **12 themes**: dark and light follow the system, with a toggle that remembers your choice
|
|
128
|
+
- **12 themes**: dark and light follow the system, with a toggle that remembers your choice. Your own CSS always wins
|
|
127
129
|
|
|
128
|
-
### How
|
|
130
|
+
### How seemore is different
|
|
129
131
|
|
|
130
|
-
- **Starts where your files already are**: most docs frameworks
|
|
131
|
-
- **Preview first, site generator second**: the same content serves locally, renders beside your editor, and builds to static HTML
|
|
132
|
-
- **Nothing to migrate, nothing to undo**: your files
|
|
132
|
+
- **Starts where your files already are**: most docs frameworks need a project, with a scaffold, a config file, a `docs/` layout and a build step wired into your repo. seemore needs only a folder that already exists.
|
|
133
|
+
- **Preview first, site generator second**: the same content serves locally, renders beside your editor, and builds to static HTML. No separate setup is needed for each one.
|
|
134
|
+
- **Nothing to migrate, nothing to undo**: seemore never moves or rewrites your files. You can stop using it at any time, at no cost.
|
|
133
135
|
|
|
134
136
|
## Edit from the browser
|
|
135
137
|
|
|
136
|
-
The preview is also an editor. Double-click any paragraph, heading, list item, quote or table cell
|
|
138
|
+
The preview is also an editor. Double-click any paragraph, heading, list item, quote or table cell. A small editor opens with that block's **Markdown source**: `**bold**` stays `**bold**`, links stay links, tables stay tables. Fix the text, press **Save**, and seemore writes the change to the file on disk.
|
|
137
139
|
|
|
138
140
|
<p align="center">
|
|
139
141
|
<img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/inline-editor.png" alt="seemore rendering a folder of Markdown in the browser, with a paragraph's Markdown source open in the inline editor" width="640"/>
|
|
140
142
|
</p>
|
|
141
143
|
|
|
142
|
-
Inline editing
|
|
144
|
+
Inline editing works only for local previews.
|
|
143
145
|
|
|
144
146
|
## View in your code editor
|
|
145
147
|
|
|
146
|
-
Install **seemore** from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=arifszn.seemore-vscode) or [Open VSX](https://open-vsx.org/extension/arifszn/seemore-vscode)
|
|
148
|
+
Install **seemore** from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=arifszn.seemore-vscode) or [Open VSX](https://open-vsx.org/extension/arifszn/seemore-vscode). You get the same rendered site as a panel beside your editor, with no terminal, no `npx` and no browser tab to manage. The extension bundles the CLI, so nothing downloads and nothing goes on your PATH. Open VSX also covers VS Code-compatible editors such as Cursor and Antigravity.
|
|
147
149
|
|
|
148
150
|
<p align="center">
|
|
149
151
|
<img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/vscode-extension.png" alt="VS Code with features.md open in the editor and the seemore panel beside it, rendering the same page with a paragraph's Markdown source open in the inline editor" width="640"/>
|
|
@@ -159,10 +161,10 @@ Install **seemore** from the [VS Code Marketplace](https://marketplace.visualstu
|
|
|
159
161
|
npx seemore build # static export to dist/ for any host
|
|
160
162
|
```
|
|
161
163
|
|
|
162
|
-
The result is a `dist/` folder of plain web files
|
|
164
|
+
The result is a `dist/` folder of plain web files. Drop it on [Netlify](https://netlify.com), [Surge](https://surge.sh), [Cloudflare Pages](https://pages.cloudflare.com) or [GitHub Pages](https://pages.github.com), or hand it to any web host. Every page is a separate `index.html`, next to a `404.html` that every static host reads. seemore also writes the files each host needs (`_redirects`, `200.html`, `.nojekyll`).
|
|
163
165
|
|
|
164
166
|
> [!TIP]
|
|
165
|
-
>
|
|
167
|
+
> Do you publish to GitHub Pages at `username.github.io/my-repo/`, not at the root? Set the subpath once with `base: '/my-repo/'`.
|
|
166
168
|
|
|
167
169
|
### Password-protect a site
|
|
168
170
|
|
|
@@ -182,27 +184,27 @@ $env:SEEMORE_PASSWORD='a-long-passphrase'; npx seemore build
|
|
|
182
184
|
<img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/password-protection.png" alt="The lock screen of a password-protected seemore site: the site's icon and title above a password field and an Unlock button" width="560"/>
|
|
183
185
|
</p>
|
|
184
186
|
|
|
185
|
-
By default,
|
|
187
|
+
By default, a visitor stays unlocked for one day after their last visit. Change this with `remember`:
|
|
186
188
|
|
|
187
189
|
```ts
|
|
188
190
|
auth: { remember: '7d' } // or '12h'
|
|
189
191
|
```
|
|
190
192
|
|
|
191
|
-
Password protection works only for `seemore build`. To
|
|
193
|
+
Password protection works only for `seemore build`. To test the lock screen, build the site and serve `dist/` on `localhost`.
|
|
192
194
|
|
|
193
195
|
### Share a single page to Slack, email
|
|
194
196
|
|
|
195
|
-
To share
|
|
197
|
+
To share one page instead of a site, use the **Actions** button above the page. It writes one self-contained HTML file (styles inlined, images embedded, diagrams kept) that opens offline from a double-click. The CLI does the same without a browser:
|
|
196
198
|
|
|
197
199
|
```bash
|
|
198
200
|
npx seemore export docs/spec.md # writes spec.html next to the Markdown
|
|
199
201
|
```
|
|
200
202
|
|
|
201
|
-
|
|
203
|
+
Read more about all three methods on the [publishing](https://arifszn.github.io/seemore/publishing) page.
|
|
202
204
|
|
|
203
205
|
## Configuration
|
|
204
206
|
|
|
205
|
-
Configuration is optional
|
|
207
|
+
Configuration is optional. A folder with no config file builds correctly everywhere. To change settings, create `seemore.config.ts` next to your content:
|
|
206
208
|
|
|
207
209
|
```ts
|
|
208
210
|
// seemore.config.ts
|
|
@@ -225,7 +227,7 @@ export default {
|
|
|
225
227
|
};
|
|
226
228
|
```
|
|
227
229
|
|
|
228
|
-
`features` is a set of switches. Name the
|
|
230
|
+
`features` is a set of switches. Name only the flags you want to change. Every other flag keeps its default value.
|
|
229
231
|
|
|
230
232
|
| Flag | Default | Effect |
|
|
231
233
|
| --- | --- | --- |
|
|
@@ -246,37 +248,37 @@ export default {
|
|
|
246
248
|
| `search.highlight` | on | Highlight the query on the page you land on |
|
|
247
249
|
| `social.cards` | off | Per-page OG images (needs `takumi-js`) |
|
|
248
250
|
|
|
249
|
-
|
|
251
|
+
The [configuration](https://arifszn.github.io/seemore/configuration) and [features](https://arifszn.github.io/seemore/features) pages document every key above.
|
|
250
252
|
|
|
251
253
|
### Themes
|
|
252
254
|
|
|
253
|
-
|
|
255
|
+
seemore has twelve built-in colour presets: `neutral` (default), `black`, `catppuccin`, `dusk`, `ocean`, `purple`, `ruby`, `solar`, `aspen`, `emerald`, `vitepress`, `shadcn`. Dark and light modes follow your system setting, with a toggle that remembers your choice. For anything else, put your own CSS in `css`. seemore appends it last, so it wins.
|
|
254
256
|
|
|
255
257
|
| `neutral` (default) | `black` |
|
|
256
258
|
| :--- | :--- |
|
|
257
259
|
| <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/themes/neutral.png" alt="neutral theme" width="100%"> | <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/themes/black.png" alt="black theme" width="100%"> |
|
|
258
260
|
|
|
259
|
-
|
|
261
|
+
The [themes page](https://arifszn.github.io/seemore/themes) shows all twelve.
|
|
260
262
|
|
|
261
263
|
## Content
|
|
262
264
|
|
|
263
265
|
Works with both `.md` and `.mdx`.
|
|
264
266
|
|
|
265
|
-
- GitHub Flavoured Markdown, admonitions (note
|
|
266
|
-
- `[[wikilinks]]`, including `[[Page|label]]` and `[[Page#Heading]]
|
|
267
|
-
- **[Mermaid](https://mermaid.js.org)** and **[D2](https://d2lang.com)** diagrams, rendered live in the browser
|
|
268
|
-
- Sibling images inlined as hashed assets with click-to-zoom, and PDFs opened inline in the browser's own viewer
|
|
269
|
-
- Page order comes from a `meta.json
|
|
267
|
+
- GitHub Flavoured Markdown, admonitions (note, tip and warning boxes), step-by-step lists, and colour-highlighted code blocks with optional filenames and line numbers
|
|
268
|
+
- `[[wikilinks]]`, including `[[Page|label]]` and `[[Page#Heading]]`. Use these to link pages without getting relative paths right. Plain relative `.md` links also resolve automatically
|
|
269
|
+
- **[Mermaid](https://mermaid.js.org)** and **[D2](https://d2lang.com)** diagrams, rendered live in the browser from a ` ```mermaid ` or ` ```d2 ` code fence
|
|
270
|
+
- Sibling images, inlined as hashed assets with click-to-zoom, and PDFs, opened inline in the browser's own viewer
|
|
271
|
+
- Page order comes from a `meta.json` file, a frontmatter `order` field, or the title, checked in that order
|
|
270
272
|
|
|
271
273
|
<p align="center">
|
|
272
274
|
<img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/diagrams.png" alt="A mermaid flowchart reading Markdown, seemore, Static site rendered live on the Diagrams page, with a D2 diagram of the same chain below it" width="640"/>
|
|
273
275
|
</p>
|
|
274
276
|
|
|
275
|
-
|
|
277
|
+
The [content page](https://arifszn.github.io/seemore/content) renders every syntax above, live.
|
|
276
278
|
|
|
277
279
|
### PDF viewer
|
|
278
280
|
|
|
279
|
-
Reference a PDF with image syntax
|
|
281
|
+
Reference a PDF with image syntax. Use a sibling file or a remote URL. The PDF opens inline, with a download link underneath:
|
|
280
282
|
|
|
281
283
|
```md
|
|
282
284
|

|
|
@@ -294,31 +296,31 @@ seemore build [dir] build a static site into dist/
|
|
|
294
296
|
seemore export <file> export a page as a standalone HTML file
|
|
295
297
|
```
|
|
296
298
|
|
|
297
|
-
Run `seemore --help`
|
|
299
|
+
Run `seemore --help` to see the options, or see the [CLI reference](https://arifszn.github.io/seemore/cli-reference) page.
|
|
298
300
|
|
|
299
301
|
## FAQ
|
|
300
302
|
|
|
301
|
-
**Do I have to install anything?** No
|
|
303
|
+
**Do I have to install anything?** No. Just run `npx seemore`.
|
|
302
304
|
|
|
303
305
|
**Does anything leave my machine?** No. The dev server, the build and the export all run locally, with no account and no telemetry. The only network calls are ones you configure yourself, such as a hosted search provider.
|
|
304
306
|
|
|
305
|
-
**
|
|
307
|
+
**Does seemore move or rewrite my files?** No. seemore reads your folder in place. It writes to a file only when you save an inline edit, or run `build` or `export`.
|
|
306
308
|
|
|
307
|
-
**Is the password protection real?** Yes.
|
|
309
|
+
**Is the password protection real?** Yes. seemore encrypts the site content, so nobody can read a copy of the build without the password. There is no per-person access and no way to revoke one password alone. A short password can be guessed offline, so use a long one.
|
|
308
310
|
|
|
309
|
-
**Is
|
|
311
|
+
**Is seemore a preview tool or a docs framework?** Both, from the same folder: `seemore` previews it, the extension renders it beside your editor, and `seemore build` publishes it.
|
|
310
312
|
|
|
311
313
|
## Under the hood
|
|
312
314
|
|
|
313
|
-
[fumadocs](https://fumadocs.vercel.app) provides the interface presets
|
|
315
|
+
[fumadocs](https://fumadocs.vercel.app) provides the interface presets. [Shiki](https://shiki.style), [Mermaid](https://mermaid.js.org), [D2](https://d2lang.com), [Vite](https://vite.dev) and [React Router](https://reactrouter.com) run underneath it. The code editor extension runs the same CLI as a child process it manages.
|
|
314
316
|
|
|
315
317
|
## Contributing
|
|
316
318
|
|
|
317
|
-
|
|
319
|
+
seemore welcomes bug reports and pull requests. [CONTRIBUTING.md](https://github.com/arifszn/seemore/blob/main/CONTRIBUTING.md) covers how to get the repo running, the checks a pull request must pass, and how releases work.
|
|
318
320
|
|
|
319
321
|
## Star it
|
|
320
322
|
|
|
321
|
-
If seemore saved you
|
|
323
|
+
If seemore saved you time, [give it a star](https://github.com/arifszn/seemore). A star helps other people find it.
|
|
322
324
|
|
|
323
325
|
## Licence
|
|
324
326
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Writable } from 'node:stream';
|
|
2
|
-
import { StrictMode, type ReactNode } from 'react';
|
|
2
|
+
import { StrictMode, type ComponentProps, type ReactNode } from 'react';
|
|
3
3
|
import { renderToPipeableStream } from 'react-dom/server';
|
|
4
4
|
import { MemoryRouter, RouterProvider, createMemoryRouter } from 'react-router';
|
|
5
|
+
import { TabsContent } from 'fumadocs-ui/components/tabs';
|
|
5
6
|
import { config } from 'virtual:seemore/config';
|
|
6
7
|
import { toBasename, withBase } from '../shared/base.js';
|
|
7
8
|
import { ogImagePath } from '../shared/og.js';
|
|
@@ -121,6 +122,11 @@ export interface ExportedArticle {
|
|
|
121
122
|
* single-page export. Rendering the component directly, rather than extracting the
|
|
122
123
|
* article from a full-page render, means no HTML parsing anywhere in the export path.
|
|
123
124
|
*
|
|
125
|
+
* Tabs render force-mounted: Radix keeps only the active panel's content in the tree, and
|
|
126
|
+
* a file with three of its four tabs empty is no export at all. Force-mounted panels hide
|
|
127
|
+
* through fumadocs' own `data-[state=inactive]:hidden` rule, which the stylesheet the
|
|
128
|
+
* export inlines already carries, and the file's runtime does the switching.
|
|
129
|
+
*
|
|
124
130
|
* Diagrams are absent here, as in every prerendered page (see `Mermaid.tsx`); the CLI
|
|
125
131
|
* export inlines a runtime that renders them when the file is opened.
|
|
126
132
|
*/
|
|
@@ -132,12 +138,16 @@ export async function renderArticle(url: string): Promise<ExportedArticle> {
|
|
|
132
138
|
if (page === undefined) throw new Error(`No page at ${url}.`);
|
|
133
139
|
|
|
134
140
|
const Content = page.default;
|
|
141
|
+
const exportComponents = {
|
|
142
|
+
...mdxComponents,
|
|
143
|
+
CodeBlockTab: (props: ComponentProps<typeof TabsContent>) => <TabsContent {...props} forceMount />,
|
|
144
|
+
};
|
|
135
145
|
// A router is still required: content links go through react-router's `Link`, which
|
|
136
146
|
// reads the routing context. A memory router with just this page is the smallest one.
|
|
137
147
|
const { html, failures } = await renderToHtml(
|
|
138
148
|
<StrictMode>
|
|
139
149
|
<MemoryRouter initialEntries={[withBase(config.base, url)]} basename={toBasename(config.base)}>
|
|
140
|
-
<Content components={
|
|
150
|
+
<Content components={exportComponents} />
|
|
141
151
|
</MemoryRouter>
|
|
142
152
|
</StrictMode>,
|
|
143
153
|
);
|
|
@@ -216,6 +216,42 @@ function buildExportToc(article: Element): Element | undefined {
|
|
|
216
216
|
return nav;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Radix Tabs keeps only the active panel's content mounted, so the article's DOM alone
|
|
221
|
+
* misses every inactive tab. Walk each tablist, select each trigger in turn — which also
|
|
222
|
+
* gives any diagram inside the panel its scroll-into-view — and record the panel's HTML
|
|
223
|
+
* while it is up. The first trigger is re-selected so the page is left as it was found.
|
|
224
|
+
*/
|
|
225
|
+
async function captureTabPanels(): Promise<Map<string, string>> {
|
|
226
|
+
const captured = new Map<string, string>();
|
|
227
|
+
const scrollX = window.scrollX;
|
|
228
|
+
const scrollY = window.scrollY;
|
|
229
|
+
|
|
230
|
+
for (const tablist of document.querySelectorAll('[role="tablist"]')) {
|
|
231
|
+
const triggers = Array.from(tablist.querySelectorAll<HTMLButtonElement>('button[role="tab"]'));
|
|
232
|
+
for (const trigger of triggers) {
|
|
233
|
+
// Radix activates a tab on `mousedown`, not `click` — and a React re-render means
|
|
234
|
+
// the panel's content appears a tick later, so both sides are handled by hand.
|
|
235
|
+
trigger.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window, button: 0 }));
|
|
236
|
+
const panel = document.getElementById(trigger.getAttribute('aria-controls') ?? '');
|
|
237
|
+
if (panel === null) continue;
|
|
238
|
+
await waitFor(() => panel.childElementCount > 0, 15_000);
|
|
239
|
+
panel.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
240
|
+
await waitFor(
|
|
241
|
+
() =>
|
|
242
|
+
panel.querySelector('.seemore-mermaid, .seemore-d2') === null ||
|
|
243
|
+
panel.querySelector('svg, .seemore-mermaid-error, .seemore-d2-error') !== null,
|
|
244
|
+
15_000,
|
|
245
|
+
);
|
|
246
|
+
captured.set(panel.id, panel.innerHTML);
|
|
247
|
+
}
|
|
248
|
+
triggers[0]?.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window, button: 0 }));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
window.scrollTo(scrollX, scrollY);
|
|
252
|
+
return captured;
|
|
253
|
+
}
|
|
254
|
+
|
|
219
255
|
/** The article is the only thing taken, but dev leaves editor affordances inside it. */
|
|
220
256
|
function cleanArticleForExport(article: Element): Element {
|
|
221
257
|
const clone = article.cloneNode(true) as Element;
|
|
@@ -237,8 +273,9 @@ function escapeHtml(text: string): string {
|
|
|
237
273
|
}
|
|
238
274
|
|
|
239
275
|
/**
|
|
240
|
-
* The exported file's runtime: theme toggle, code copy, heading anchor copy,
|
|
241
|
-
* the behaviors kept, at roughly a kilobyte instead of the site bundle.
|
|
276
|
+
* The exported file's runtime: theme toggle, code copy, heading anchor copy, tabs,
|
|
277
|
+
* click-to-zoom — the behaviors kept, at roughly a kilobyte instead of the site bundle.
|
|
278
|
+
* Handed to React in
|
|
242
279
|
* hydration on the live page; here each is a few lines against the static DOM.
|
|
243
280
|
*
|
|
244
281
|
* Kept free of `</script>`-shaped sequences by construction: it is inlined verbatim.
|
|
@@ -280,6 +317,27 @@ const RUNTIME = `(function () {
|
|
|
280
317
|
});
|
|
281
318
|
});
|
|
282
319
|
|
|
320
|
+
// Tabs are exported as the static Radix markup the live page hydrated; the file wires
|
|
321
|
+
// them back up by hand — a click selects within its tablist and swaps the panels.
|
|
322
|
+
document.querySelectorAll('[role="tablist"]').forEach(function (tablist) {
|
|
323
|
+
var triggers = [].slice.call(tablist.querySelectorAll('button[role="tab"]'));
|
|
324
|
+
triggers.forEach(function (trigger) {
|
|
325
|
+
trigger.addEventListener('click', function () {
|
|
326
|
+
triggers.forEach(function (other) {
|
|
327
|
+
var active = other === trigger;
|
|
328
|
+
other.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
329
|
+
other.setAttribute('data-state', active ? 'active' : 'inactive');
|
|
330
|
+
other.setAttribute('tabindex', active ? '0' : '-1');
|
|
331
|
+
var panel = document.getElementById(other.getAttribute('aria-controls') || '');
|
|
332
|
+
if (panel) {
|
|
333
|
+
panel.hidden = !active;
|
|
334
|
+
panel.setAttribute('data-state', active ? 'active' : 'inactive');
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
283
341
|
// The live site's TOC follows the reader with fumadocs' own scroll tracking; in the file,
|
|
284
342
|
// the plainest version of the same behaviour — last heading above the fold wins.
|
|
285
343
|
var tocLinks = [].slice.call(document.querySelectorAll('.seemore-export-toc a'));
|
|
@@ -398,7 +456,12 @@ export async function exportPageAsHtml(): Promise<void> {
|
|
|
398
456
|
|
|
399
457
|
await prepareDiagrams();
|
|
400
458
|
|
|
459
|
+
const tabs = await captureTabPanels();
|
|
401
460
|
const clone = cleanArticleForExport(article);
|
|
461
|
+
for (const [id, html] of tabs) {
|
|
462
|
+
const panel = clone.querySelector(`[id="${CSS.escape(id)}"]`);
|
|
463
|
+
if (panel !== null) panel.innerHTML = html;
|
|
464
|
+
}
|
|
402
465
|
await inlineImages(clone);
|
|
403
466
|
|
|
404
467
|
const toc = buildExportToc(clone);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* bundled to an IIFE and inlined into the file, finishes the job when the page is opened:
|
|
7
7
|
* each diagram is rendered from the source `pre` the site's own components leave in
|
|
8
8
|
* prerendered output. It also binds the behaviors the export keeps — theme toggle, code
|
|
9
|
-
* copy, click-to-zoom — so the file behaves like the browser-exported one.
|
|
9
|
+
* copy, tabs, click-to-zoom — so the file behaves like the browser-exported one.
|
|
10
10
|
*
|
|
11
11
|
* The browser export ships a hand-written twin of the behavior half (the `RUNTIME` string
|
|
12
12
|
* in `exportPage.ts`): it needs no diagram half, because its diagrams are already SVG when
|
|
@@ -96,6 +96,27 @@ function bindBehaviors(): void {
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// Tabs are exported as the static Radix markup the live page hydrated; the file wires
|
|
100
|
+
// them back up by hand — a click selects within its tablist and swaps the panels.
|
|
101
|
+
for (const tablist of document.querySelectorAll('[role="tablist"]')) {
|
|
102
|
+
const triggers = Array.from(tablist.querySelectorAll('button[role="tab"]'));
|
|
103
|
+
for (const trigger of triggers) {
|
|
104
|
+
trigger.addEventListener('click', () => {
|
|
105
|
+
for (const other of triggers) {
|
|
106
|
+
const active = other === trigger;
|
|
107
|
+
other.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
108
|
+
other.setAttribute('data-state', active ? 'active' : 'inactive');
|
|
109
|
+
other.setAttribute('tabindex', active ? '0' : '-1');
|
|
110
|
+
const panel = document.getElementById(other.getAttribute('aria-controls') ?? '');
|
|
111
|
+
if (panel !== null) {
|
|
112
|
+
panel.hidden = !active;
|
|
113
|
+
panel.setAttribute('data-state', active ? 'active' : 'inactive');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
// The live site's TOC follows the reader with fumadocs' own scroll tracking; in the file,
|
|
100
121
|
// the plainest version of the same behaviour — last heading above the fold wins.
|
|
101
122
|
const tocLinks = Array.from(document.querySelectorAll<HTMLAnchorElement>('.seemore-export-toc a'));
|