seemore 1.1.5 → 1.2.0
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 +51 -29
- package/dist/cli/index.js +123 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/app/features/edit.tsx +285 -0
- package/src/app/index.html +5 -2
- package/src/app/layout/DocsLayout.tsx +6 -1
- package/src/app/styles/globals.css +64 -0
- package/src/shared/types.ts +1 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<br/>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<img src="https://raw.githubusercontent.com/arifszn/seemore/main/
|
|
4
|
+
<img src="https://raw.githubusercontent.com/arifszn/seemore/main/assets/icon.png" alt="seemore" width="40" height="40">
|
|
5
5
|
<h1 align="center">seemore</h1>
|
|
6
6
|
<h4 align="center">Let AI write the Markdown. Let seemore show it better — zero config documentation framework.</h4>
|
|
7
7
|
<p align="center">
|
|
@@ -42,11 +42,9 @@
|
|
|
42
42
|
</p>
|
|
43
43
|
</p>
|
|
44
44
|
|
|
45
|
-
AI tools write Markdown — specs, notes, guides, READMEs, whole folders of it, faster than
|
|
46
|
-
anyone can read. A pile of `.md` files is write-only memory: nothing to click, nothing to
|
|
47
|
-
search, no order.
|
|
45
|
+
AI tools write Markdown — specs, notes, guides, READMEs, whole folders of it, faster than anyone can read. A pile of `.md` files is write-only memory: nothing to click, nothing to search, no order.
|
|
48
46
|
|
|
49
|
-
seemore points at that folder and renders it as a proper site instead, without you moving a single file or writing any code.
|
|
47
|
+
**seemore** points at that folder and renders it as a proper site instead, without you moving a single file or writing any code.
|
|
50
48
|
|
|
51
49
|
Three ways to use it:
|
|
52
50
|
|
|
@@ -54,26 +52,39 @@ Three ways to use it:
|
|
|
54
52
|
- **[In your code editor](#view-in-your-code-editor)**: an extension puts the same site in a panel next to the file you're editing — VS Code and VS Code-compatible editors like Cursor and Antigravity.
|
|
55
53
|
- **[As a static site](#publish-it-to-the-web)**: `npx seemore build` exports plain HTML you can host anywhere, so it doubles as a docs framework, not just a preview tool.
|
|
56
54
|
|
|
55
|
+
And whichever preview is open, the page is also an editor: **double-click any paragraph to fix its Markdown in place**, and the change is written straight back to the file.
|
|
56
|
+
|
|
57
57
|
<p align="center">
|
|
58
|
-
<img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/
|
|
58
|
+
<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"/>
|
|
59
59
|
</p>
|
|
60
60
|
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- **Zero config** — no config file, no code, no files to move; a plain folder of Markdown works in the browser, in your editor, and as a static build
|
|
64
|
+
- **Live preview** — add, rename, retitle or delete a file and the site updates immediately, navigation and search included
|
|
65
|
+
- **Edit in place** — double-click any block in the preview to fix its Markdown; saves are surgical, so `git diff` shows the sentence you changed and nothing else
|
|
66
|
+
- **Static export** — `seemore build` prerenders every page to its own HTML file, `404.html` included, and adds the conventions individual hosts look for (`_redirects`, `200.html`, `.nojekyll`)
|
|
67
|
+
- **Search built in** — static, zero-setup full-text search out of the box, with shareable highlighted results; [Algolia](https://algolia.com) and [Orama Cloud](https://orama.com) for hosted indexes
|
|
68
|
+
- **12 themes** — dark and light follow the system, with a toggle that remembers your choice; your own CSS always wins
|
|
69
|
+
- **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
|
|
70
|
+
- **First-class code blocks** — build-time [Shiki](https://shiki.style) highlighting in the theme's own colours, with titles, line numbers, diff markers and focus
|
|
71
|
+
- **MDX components** — `<Callout>`, `<Card>`, `<CodeBlockTabs>` and friends, with no imports to write
|
|
72
|
+
- **Editor integration** — one extension covers VS Code, Cursor, Antigravity and other VS Code-compatible editors, remote workspaces included
|
|
73
|
+
|
|
61
74
|
## View in your browser
|
|
62
75
|
|
|
63
76
|
```bash
|
|
64
77
|
npx seemore # view the current folder in your browser, live, no setup
|
|
65
78
|
```
|
|
66
79
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
With no folder given, seemore serves the folder you're standing in. Point it at a subfolder with `npx seemore docs` if that's where your files live.
|
|
80
|
+
Run it in your folder of Markdown files (Node.js 20 or newer) and open the address it prints — `http://localhost:4040` by default. From there it's live: add, rename, retitle or delete a file and the site updates immediately. Point it at a subfolder with `npx seemore docs` if that's where your files live.
|
|
70
81
|
|
|
71
82
|
## View in your code editor
|
|
72
83
|
|
|
73
|
-
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) to get the same rendered site as a panel beside your editor
|
|
84
|
+
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) to get the same rendered site as a panel beside your editor — no terminal, no `npx`, no browser tab to manage. The extension bundles the CLI, so nothing is downloaded or put on your PATH. Open VSX also covers VS Code-compatible editors — Cursor, Antigravity, and others.
|
|
74
85
|
|
|
75
86
|
1. Open any Markdown file.
|
|
76
|
-
2. Click the seemore icon in the editor's title bar, or right-click a folder in the explorer and choose **Open Folder in seemore**.
|
|
87
|
+
2. Click the **seemore** icon in the editor's title bar, or right-click a folder in the explorer and choose **Open Folder in seemore**.
|
|
77
88
|
3. The rendered site opens beside your editor, scoped to that file's folder.
|
|
78
89
|
|
|
79
90
|
### Commands
|
|
@@ -88,27 +99,37 @@ Install seemore from the [VS Code Marketplace](https://marketplace.visualstudio.
|
|
|
88
99
|
|
|
89
100
|
| Setting | Default | Effect |
|
|
90
101
|
| --- | --- | --- |
|
|
91
|
-
| `seemore.path` | *(bundled copy)* | Path to a `seemore` CLI entry point to use instead of the version bundled with the extension. Leave empty unless you're developing seemore itself. |
|
|
102
|
+
| `seemore.path` | *(bundled copy)* | Path to a `seemore` CLI entry point to use instead of the version bundled with the extension. Leave empty unless you're developing **seemore** itself. |
|
|
92
103
|
|
|
93
|
-
##
|
|
104
|
+
## Edit your files from the browser
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
The preview is not just for reading — it is the fastest way to fix what you are reading. Double-click any paragraph, heading, list item, quote or table cell and it opens in a small editor holding that block's **Markdown source**: `**bold**` stays `**bold**`, links stay links, tables stay tables. Fix the text and hit **Save** and the change is written to the file on disk. Nothing is written until you say so: clicking away leaves the editor open rather than saving behind your back. It works the same in the VS Code panel, which runs the same dev server.
|
|
107
|
+
|
|
108
|
+
Inline editing is for local previews only — `seemore build` output is static, so nothing is emitted there. It is on by default in dev; switch it off with the `!` prefix:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
export default defineConfig({
|
|
112
|
+
features: ['!content.edit'],
|
|
113
|
+
});
|
|
97
114
|
```
|
|
98
115
|
|
|
99
|
-
|
|
116
|
+
> [!NOTE]
|
|
117
|
+
> The dev server binds to localhost, so only your machine can reach the endpoint that writes. If you serve the site to your network with `--host`, anyone who can open the site can also edit your files — pass `features: ['!content.edit']` when you do that.
|
|
100
118
|
|
|
101
|
-
|
|
119
|
+
## Publish it to the web
|
|
102
120
|
|
|
103
|
-
```
|
|
104
|
-
|
|
121
|
+
```bash
|
|
122
|
+
npx seemore build # static export to dist/ for any host
|
|
105
123
|
```
|
|
106
124
|
|
|
107
|
-
|
|
125
|
+
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 prerendered to its own `index.html`, next to a `404.html` that every static host honours. On top of that, the small conventions individual hosts look for — `_redirects` for Netlify and Cloudflare Pages, `200.html` for Surge, `.nojekyll` for GitHub Pages — are written for you.
|
|
126
|
+
|
|
127
|
+
> [!TIP]
|
|
128
|
+
> Project sites on GitHub Pages live under `username.github.io/my-repo/`, not the root, so set `base` once: `defineConfig({ base: '/my-repo/' })` (or `--base /my-repo/` on the CLI). Building under GitHub Actions without it set prints the exact line to add.
|
|
108
129
|
|
|
109
130
|
## Configuration
|
|
110
131
|
|
|
111
|
-
Optional
|
|
132
|
+
Optional — a folder with no config file builds correctly everywhere. To adjust things, create `seemore.config.ts` next to your content:
|
|
112
133
|
|
|
113
134
|
```ts
|
|
114
135
|
// seemore.config.ts
|
|
@@ -130,6 +151,10 @@ export default defineConfig({
|
|
|
130
151
|
});
|
|
131
152
|
```
|
|
132
153
|
|
|
154
|
+
### Search
|
|
155
|
+
|
|
156
|
+
The default `search: 'static'` needs no setup, no server and no account: the index is built from your Markdown and queried in the browser, in a Web Worker. `search.suggest` completes your query inline, and `search.highlight` carries the query onto the page you land on (`?h=…`), so search-result links are shareable. Hosted indexes are a drop-in swap whenever you want one: [Orama Cloud](https://orama.com) (`@orama/core`) or [Algolia](https://algolia.com) (`algoliasearch`), each needing nothing but its SDK installed.
|
|
157
|
+
|
|
133
158
|
### Themes
|
|
134
159
|
|
|
135
160
|
Twelve built-in colour presets: `neutral` (default), `black`, `catppuccin`, `dusk`, `ocean`, `purple`, `ruby`, `solar`, `aspen`, `emerald`, `vitepress`, `shadcn`. For anything else, put your own CSS in `css`; it's appended last, so it wins. Dark and light follow your system setting, with a toggle that remembers your choice — `black` is built for dark mode, shown below with the toggle on.
|
|
@@ -160,7 +185,7 @@ Twelve built-in colour presets: `neutral` (default), `black`, `catppuccin`, `dus
|
|
|
160
185
|
|
|
161
186
|
### Features
|
|
162
187
|
|
|
163
|
-
A flat list of switches for
|
|
188
|
+
A flat list of switches for fine control, set as an array on the `features` key. Turn something on by adding its flag name, turn something off with the `!` prefix; flags you don't mention keep their default.
|
|
164
189
|
|
|
165
190
|
```ts
|
|
166
191
|
// seemore.config.ts
|
|
@@ -172,8 +197,6 @@ export default defineConfig({
|
|
|
172
197
|
});
|
|
173
198
|
```
|
|
174
199
|
|
|
175
|
-
Flags you don't mention are left at their default, so you only ever list the ones you're changing.
|
|
176
|
-
|
|
177
200
|
| Flag | Default | Effect |
|
|
178
201
|
| --- | --- | --- |
|
|
179
202
|
| `navigation.instant.prefetch` | on | Load the target page on hover |
|
|
@@ -187,6 +210,7 @@ Flags you don't mention are left at their default, so you only ever list the one
|
|
|
187
210
|
| `toc.integrate` | off | Merge the table of contents into the sidebar |
|
|
188
211
|
| `content.code.copy` | on | Copy button on code blocks |
|
|
189
212
|
| `content.action.edit` | on with `editLink` | Edit-this-page link |
|
|
213
|
+
| `content.edit` | on (dev only) | Double-click a block to edit its Markdown in place |
|
|
190
214
|
| `content.image.zoom` | on | Click-to-zoom on content images |
|
|
191
215
|
| `search.suggest` | on | Inline query completion |
|
|
192
216
|
| `search.highlight` | on | Highlight the query on the page you land on |
|
|
@@ -202,8 +226,8 @@ Supports `.md` and `.mdx` both.
|
|
|
202
226
|
- `[[wikilinks]]`, including `[[Page|label]]` and `[[Page#Heading]]`, the easiest way for you or your AI to link pages without relative paths to get right
|
|
203
227
|
- Relative `.md` links are resolved to working links automatically
|
|
204
228
|
- **[Mermaid](https://mermaid.js.org)** and **[D2](https://d2lang.com)** diagrams, both rendered live in the browser straight from a ` ```mermaid ` or ` ```d2 ` code fence
|
|
205
|
-
- Sibling images inlined as hashed assets, sibling PDFs open in the browser's own viewer
|
|
206
|
-
- Frontmatter
|
|
229
|
+
- Sibling images inlined as hashed assets with click-to-zoom, sibling PDFs open in the browser's own viewer
|
|
230
|
+
- Frontmatter keys are validated
|
|
207
231
|
|
|
208
232
|
### Code blocks
|
|
209
233
|
|
|
@@ -230,7 +254,7 @@ The marker follows the language's own comment syntax, so `# [!code highlight]` i
|
|
|
230
254
|
|
|
231
255
|
### Components
|
|
232
256
|
|
|
233
|
-
An `.mdx` file can use `<Callout>`, `<Card>`, `<Cards>`, `<CodeBlockTabs>`, `<Mermaid>`, `<D2>` and `<Pdf>` without importing anything.
|
|
257
|
+
An `.mdx` file can use `<Callout>`, `<Card>`, `<Cards>`, `<CodeBlockTabs>`, `<Mermaid>`, `<D2>` and `<Pdf>` without importing anything. Anything else fails the build, naming the file and the component. In a plain `.md` file a tag is not JSX at all: it is dropped and its text kept, so components need the `.mdx` extension. The [Content page](https://arifszn.github.io/seemore/content) has the full syntax for each.
|
|
234
258
|
|
|
235
259
|
Numbered headings — `## 1. Install it`, `## 2. Point it at a folder` — become a numbered sequence.
|
|
236
260
|
|
|
@@ -244,8 +268,6 @@ Numbered headings — `## 1. Install it`, `## 2. Point it at a folder` — becom
|
|
|
244
268
|
| `guide/index.md` | `/guide` |
|
|
245
269
|
| `guide/Deep Dive.md` | `/guide/deep-dive` |
|
|
246
270
|
|
|
247
|
-
Both `/guide` and `/guide/` work on every host.
|
|
248
|
-
|
|
249
271
|
### Ordering
|
|
250
272
|
|
|
251
273
|
Pages are ordered by:
|
package/dist/cli/index.js
CHANGED
|
@@ -75,7 +75,7 @@ import { parseArgs } from "util";
|
|
|
75
75
|
import pc4 from "picocolors";
|
|
76
76
|
|
|
77
77
|
// src/cli/build.ts
|
|
78
|
-
import { mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync as
|
|
78
|
+
import { mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync5 } from "fs";
|
|
79
79
|
import { createRequire as createRequire3 } from "module";
|
|
80
80
|
import { tmpdir as tmpdir2 } from "os";
|
|
81
81
|
import { isAbsolute as isAbsolute2, join as join8, relative as relative2, resolve as resolve5 } from "path";
|
|
@@ -124,6 +124,7 @@ var FEATURES = [
|
|
|
124
124
|
"toc.integrate",
|
|
125
125
|
"content.code.copy",
|
|
126
126
|
"content.action.edit",
|
|
127
|
+
"content.edit",
|
|
127
128
|
"content.image.zoom",
|
|
128
129
|
"search.suggest",
|
|
129
130
|
"search.highlight",
|
|
@@ -144,6 +145,10 @@ var FEATURE_DEFAULTS = {
|
|
|
144
145
|
"content.code.copy": true,
|
|
145
146
|
// Implicitly on when `editLink` is configured; there is nothing to link to otherwise.
|
|
146
147
|
"content.action.edit": false,
|
|
148
|
+
// On by default, but only ever active in dev: the stamping that makes a block editable is
|
|
149
|
+
// not emitted by `seemore build`, and the endpoint that writes is registered only by the
|
|
150
|
+
// dev server. Switch it off with '!content.edit'.
|
|
151
|
+
"content.edit": true,
|
|
147
152
|
"content.image.zoom": true,
|
|
148
153
|
"search.suggest": true,
|
|
149
154
|
"search.highlight": true,
|
|
@@ -965,6 +970,22 @@ function virtualPath(contentRoot, file) {
|
|
|
965
970
|
return toPosix(relative(contentRoot, file.path));
|
|
966
971
|
}
|
|
967
972
|
|
|
973
|
+
// src/node/vite/positions.ts
|
|
974
|
+
import { visit as visit2 } from "unist-util-visit";
|
|
975
|
+
var POSITION_ATTRIBUTE = "data-seemore-pos";
|
|
976
|
+
var EDITABLE = /* @__PURE__ */ new Set(["p", "h1", "h2", "h3", "h4", "h5", "h6", "li", "blockquote", "td", "th"]);
|
|
977
|
+
function rehypeSeemorePositions() {
|
|
978
|
+
return (tree) => {
|
|
979
|
+
visit2(tree, "element", (node) => {
|
|
980
|
+
if (!EDITABLE.has(node.tagName)) return;
|
|
981
|
+
const { start, end } = node.position ?? {};
|
|
982
|
+
if (start?.offset === void 0 || end?.offset === void 0) return;
|
|
983
|
+
node.properties ??= {};
|
|
984
|
+
node.properties[POSITION_ATTRIBUTE] = `${start.offset}:${end.offset}`;
|
|
985
|
+
});
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
968
989
|
// src/node/vite/mdx.ts
|
|
969
990
|
function createRemarkPlugins(options) {
|
|
970
991
|
return [
|
|
@@ -997,18 +1018,21 @@ function createRemarkPlugins(options) {
|
|
|
997
1018
|
() => remarkSeemoreLinks(options)
|
|
998
1019
|
];
|
|
999
1020
|
}
|
|
1000
|
-
function createRehypePlugins() {
|
|
1021
|
+
function createRehypePlugins(options = {}) {
|
|
1001
1022
|
return [
|
|
1002
1023
|
// A fence in a language Shiki has no grammar for (anything an AI dreamt up) is plain code
|
|
1003
1024
|
// on the page, not a dead one: `plaintext` is special-cased by Shiki and never needs
|
|
1004
1025
|
// loading.
|
|
1005
1026
|
[rehypeCode, { fallbackLanguage: "plaintext" }],
|
|
1006
|
-
rehypeToc
|
|
1027
|
+
rehypeToc,
|
|
1028
|
+
// After `rehype-code`, so a fence Shiki rebuilt is passed over rather than stamped with
|
|
1029
|
+
// the position of whatever it replaced.
|
|
1030
|
+
...options.positions === true ? [rehypeSeemorePositions] : []
|
|
1007
1031
|
];
|
|
1008
1032
|
}
|
|
1009
1033
|
|
|
1010
1034
|
// src/node/vite/plugin.ts
|
|
1011
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
1035
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1012
1036
|
import { createRequire } from "module";
|
|
1013
1037
|
import { dirname as dirname6 } from "path";
|
|
1014
1038
|
|
|
@@ -1066,6 +1090,33 @@ function formatBytes(bytes) {
|
|
|
1066
1090
|
|
|
1067
1091
|
// src/node/vite/plugin.ts
|
|
1068
1092
|
init_paths();
|
|
1093
|
+
|
|
1094
|
+
// src/node/content/edit.ts
|
|
1095
|
+
function spliceSource(content, request) {
|
|
1096
|
+
const { start, end, expected, text } = request;
|
|
1097
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end > content.length) {
|
|
1098
|
+
return { ok: false, status: 400, error: "The edited range is not inside this file." };
|
|
1099
|
+
}
|
|
1100
|
+
if (content.slice(start, end) !== expected) {
|
|
1101
|
+
return {
|
|
1102
|
+
ok: false,
|
|
1103
|
+
status: 409,
|
|
1104
|
+
error: "This file changed since the page was rendered. Reload and try the edit again."
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
return { ok: true, content: content.slice(0, start) + withEol(text, dominantEol(content)) + content.slice(end) };
|
|
1108
|
+
}
|
|
1109
|
+
function dominantEol(content) {
|
|
1110
|
+
const crlf = content.match(/\r\n/g)?.length ?? 0;
|
|
1111
|
+
const lf = (content.match(/\n/g)?.length ?? 0) - crlf;
|
|
1112
|
+
return crlf > lf ? "\r\n" : "\n";
|
|
1113
|
+
}
|
|
1114
|
+
function withEol(text, eol) {
|
|
1115
|
+
const normalised = text.replace(/\r\n/g, "\n");
|
|
1116
|
+
return eol === "\n" ? normalised : normalised.replace(/\n/g, "\r\n");
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/node/vite/plugin.ts
|
|
1069
1120
|
var VIRTUAL = {
|
|
1070
1121
|
tree: "virtual:seemore/tree",
|
|
1071
1122
|
routes: "virtual:seemore/routes",
|
|
@@ -1165,6 +1216,13 @@ function seemorePlugin({ ctx, serveSearch = false }) {
|
|
|
1165
1216
|
res.setHeader("Content-Type", "application/json");
|
|
1166
1217
|
res.end(JSON.stringify({ url: withBase(ctx.config.base, page.url) }));
|
|
1167
1218
|
});
|
|
1219
|
+
if (ctx.config.features["content.edit"]) {
|
|
1220
|
+
devServer.middlewares.use((req, res, next) => {
|
|
1221
|
+
const path = (req.url ?? "").split("?")[0] ?? "";
|
|
1222
|
+
if (path !== SOURCE_ENDPOINT && path !== withBase(ctx.config.base, SOURCE_ENDPOINT)) return next();
|
|
1223
|
+
void handleSource(ctx, req, res).catch(next);
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1168
1226
|
},
|
|
1169
1227
|
/** Called by the watcher after a rescan. */
|
|
1170
1228
|
api: {
|
|
@@ -1179,6 +1237,63 @@ function seemorePlugin({ ctx, serveSearch = false }) {
|
|
|
1179
1237
|
}
|
|
1180
1238
|
};
|
|
1181
1239
|
}
|
|
1240
|
+
var SOURCE_ENDPOINT = "/__seemore/source";
|
|
1241
|
+
async function handleSource(ctx, req, res) {
|
|
1242
|
+
if (req.method === "GET") return handleSourceRead(ctx, req, res);
|
|
1243
|
+
if (req.method === "PUT") return handleSourceWrite(ctx, req, res);
|
|
1244
|
+
res.setHeader("Allow", "GET, PUT");
|
|
1245
|
+
return send(res, 405, { error: `${req.method ?? "This method"} is not allowed here.` });
|
|
1246
|
+
}
|
|
1247
|
+
function handleSourceRead(ctx, req, res) {
|
|
1248
|
+
const query = new URLSearchParams((req.url ?? "").split("?")[1] ?? "");
|
|
1249
|
+
const page = resolvePage(ctx, query.get("file"));
|
|
1250
|
+
if (page === void 0) return send(res, 404, { error: "That file is not part of this site." });
|
|
1251
|
+
const start = Number(query.get("start"));
|
|
1252
|
+
const end = Number(query.get("end"));
|
|
1253
|
+
const content = readFileSync4(page.absPath, "utf8");
|
|
1254
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end > content.length) {
|
|
1255
|
+
return send(res, 400, { error: "The requested range is not inside this file." });
|
|
1256
|
+
}
|
|
1257
|
+
return send(res, 200, { text: content.slice(start, end) });
|
|
1258
|
+
}
|
|
1259
|
+
async function handleSourceWrite(ctx, req, res) {
|
|
1260
|
+
let body;
|
|
1261
|
+
try {
|
|
1262
|
+
body = JSON.parse(await readBody(req));
|
|
1263
|
+
} catch {
|
|
1264
|
+
return send(res, 400, { error: "The request body was not valid JSON." });
|
|
1265
|
+
}
|
|
1266
|
+
const page = resolvePage(ctx, body.file);
|
|
1267
|
+
if (page === void 0) return send(res, 404, { error: "That file is not part of this site." });
|
|
1268
|
+
if (typeof body.expected !== "string" || typeof body.text !== "string") {
|
|
1269
|
+
return send(res, 400, { error: "Both `expected` and `text` are required." });
|
|
1270
|
+
}
|
|
1271
|
+
const content = readFileSync4(page.absPath, "utf8");
|
|
1272
|
+
const result = spliceSource(content, {
|
|
1273
|
+
start: body.start,
|
|
1274
|
+
end: body.end,
|
|
1275
|
+
expected: body.expected,
|
|
1276
|
+
text: body.text
|
|
1277
|
+
});
|
|
1278
|
+
if (!result.ok) return send(res, result.status, { error: result.error });
|
|
1279
|
+
writeFileSync3(page.absPath, result.content, "utf8");
|
|
1280
|
+
return send(res, 200, { ok: true });
|
|
1281
|
+
}
|
|
1282
|
+
function resolvePage(ctx, file) {
|
|
1283
|
+
if (typeof file !== "string" || file === "") return void 0;
|
|
1284
|
+
const absFile = canonicalise(file);
|
|
1285
|
+
return ctx.pages().find((page) => page.absPath === absFile);
|
|
1286
|
+
}
|
|
1287
|
+
async function readBody(req) {
|
|
1288
|
+
const chunks = [];
|
|
1289
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
1290
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1291
|
+
}
|
|
1292
|
+
function send(res, status, payload) {
|
|
1293
|
+
res.statusCode = status;
|
|
1294
|
+
res.setHeader("Content-Type", "application/json");
|
|
1295
|
+
res.end(JSON.stringify(payload));
|
|
1296
|
+
}
|
|
1182
1297
|
function hotStoreModule(suffix, value) {
|
|
1183
1298
|
return `const state = import.meta.hot
|
|
1184
1299
|
? (import.meta.hot.data.seemore${suffix} ||= { listeners: new Set() })
|
|
@@ -1341,7 +1456,7 @@ function createViteConfig({ ctx, mode, outDir, ssrOutDir }) {
|
|
|
1341
1456
|
getResolver: () => ctx.resolver(),
|
|
1342
1457
|
onWarning: (message) => ctx.warnings.add(message)
|
|
1343
1458
|
}),
|
|
1344
|
-
rehypePlugins: createRehypePlugins(),
|
|
1459
|
+
rehypePlugins: createRehypePlugins({ positions: mode === "dev" && ctx.config.features["content.edit"] }),
|
|
1345
1460
|
// MDX compiles its own JSX. Vite's builtin transform infers a file's language from its
|
|
1346
1461
|
// extension and does not know `.md`/`.mdx`, so leaving JSX in the output would fail to
|
|
1347
1462
|
// parse. Fast Refresh is unaffected: it is a separate transform, applied to these files
|
|
@@ -1464,7 +1579,7 @@ async function loadPrerenderModule(ctx, ssrOutDir) {
|
|
|
1464
1579
|
}
|
|
1465
1580
|
|
|
1466
1581
|
// src/node/social/cards.ts
|
|
1467
|
-
import { mkdirSync as mkdirSync2, writeFileSync as
|
|
1582
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1468
1583
|
import { dirname as dirname7, join as join7 } from "path";
|
|
1469
1584
|
|
|
1470
1585
|
// src/shared/og.ts
|
|
@@ -1490,7 +1605,7 @@ async function generateSocialCards(ctx, outDir) {
|
|
|
1490
1605
|
if (png === void 0) continue;
|
|
1491
1606
|
const target = join7(outDir, ogImagePath(page.url));
|
|
1492
1607
|
mkdirSync2(dirname7(target), { recursive: true });
|
|
1493
|
-
|
|
1608
|
+
writeFileSync4(target, png);
|
|
1494
1609
|
written++;
|
|
1495
1610
|
}
|
|
1496
1611
|
return written;
|
|
@@ -1558,7 +1673,7 @@ async function runBuild(options) {
|
|
|
1558
1673
|
if (config.search.provider === "static") {
|
|
1559
1674
|
const index = await buildSearchIndex(ctx);
|
|
1560
1675
|
mkdirSync3(join8(outDir, "api"), { recursive: true });
|
|
1561
|
-
|
|
1676
|
+
writeFileSync5(join8(outDir, "api", "search.json"), index, "utf8");
|
|
1562
1677
|
const size = measureIndex(index);
|
|
1563
1678
|
console.log(pc2.dim(`seemore search index ${formatBytes(size.gzipped)} gzipped`));
|
|
1564
1679
|
if (size.warning !== void 0) ctx.warnings.add(size.warning);
|