seemore 1.1.5 → 1.3.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 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/packages/vscode/assets/icon.png" alt="seemore" width="40" height="40">
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/themes/neutral.png" alt="seemore rendering a folder of Markdown in the browser, neutral theme" width="640"/>
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
- Open a terminal in your folder of Markdown files, run `npx seemore`, 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, navigation included.
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. No terminal, no `npx`, no browser tab to manage. Open VSX also covers VS Code-compatible editors — Cursor, Antigravity, and others.
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,7 +99,19 @@ 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. |
103
+
104
+ ## Edit your files from the browser
105
+
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.
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
+ });
114
+ ```
92
115
 
93
116
  ## Publish it to the web
94
117
 
@@ -96,19 +119,14 @@ Install seemore from the [VS Code Marketplace](https://marketplace.visualstudio.
96
119
  npx seemore build # static export to dist/ for any host
97
120
  ```
98
121
 
99
- 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.
100
-
101
- **On GitHub Pages:** project sites live under `username.github.io/my-repo/`, not the root, so set `base` once:
102
-
103
- ```ts
104
- export default defineConfig({ base: '/my-repo/' });
105
- ```
122
+ 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.
106
123
 
107
- (or `--base /my-repo/` on the CLI). Building under GitHub Actions without it set prints the exact line to add.
124
+ > [!TIP]
125
+ > 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
126
 
109
127
  ## Configuration
110
128
 
111
- Optional. A folder with no config file builds correctly in the browser, in your code editor, and when built for publishing. If you want to adjust things, create `seemore.config.ts` next to your content:
129
+ Optional a folder with no config file builds correctly everywhere. To adjust things, create `seemore.config.ts` next to your content:
112
130
 
113
131
  ```ts
114
132
  // seemore.config.ts
@@ -130,6 +148,10 @@ export default defineConfig({
130
148
  });
131
149
  ```
132
150
 
151
+ ### Search
152
+
153
+ 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.
154
+
133
155
  ### Themes
134
156
 
135
157
  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 +182,7 @@ Twelve built-in colour presets: `neutral` (default), `black`, `catppuccin`, `dus
160
182
 
161
183
  ### Features
162
184
 
163
- A flat list of switches for readers who want fine control, set as an array on the `features` key in `seemore.config.ts`. To turn on something that's off by default, add its flag name. To turn off something that's on by default, add its flag name prefixed with `!`.
185
+ 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
186
 
165
187
  ```ts
166
188
  // seemore.config.ts
@@ -172,8 +194,6 @@ export default defineConfig({
172
194
  });
173
195
  ```
174
196
 
175
- Flags you don't mention are left at their default, so you only ever list the ones you're changing.
176
-
177
197
  | Flag | Default | Effect |
178
198
  | --- | --- | --- |
179
199
  | `navigation.instant.prefetch` | on | Load the target page on hover |
@@ -187,6 +207,7 @@ Flags you don't mention are left at their default, so you only ever list the one
187
207
  | `toc.integrate` | off | Merge the table of contents into the sidebar |
188
208
  | `content.code.copy` | on | Copy button on code blocks |
189
209
  | `content.action.edit` | on with `editLink` | Edit-this-page link |
210
+ | `content.edit` | on (dev only) | Double-click a block to edit its Markdown in place |
190
211
  | `content.image.zoom` | on | Click-to-zoom on content images |
191
212
  | `search.suggest` | on | Inline query completion |
192
213
  | `search.highlight` | on | Highlight the query on the page you land on |
@@ -202,8 +223,8 @@ Supports `.md` and `.mdx` both.
202
223
  - `[[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
224
  - Relative `.md` links are resolved to working links automatically
204
225
  - **[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 (the `key: value` block at the top of a file) is validated, with errors that name the file and the field
226
+ - Sibling images inlined as hashed assets with click-to-zoom, sibling PDFs open in the browser's own viewer
227
+ - Frontmatter keys are validated
207
228
 
208
229
  ### Code blocks
209
230
 
@@ -230,7 +251,7 @@ The marker follows the language's own comment syntax, so `# [!code highlight]` i
230
251
 
231
252
  ### Components
232
253
 
233
- An `.mdx` file can use `<Callout>`, `<Card>`, `<Cards>`, `<CodeBlockTabs>`, `<Mermaid>`, `<D2>` and `<Pdf>` without importing anything. The set is deliberately small: Markdown has no imports, so every component is one seemore ships to every site whether it is used or not, and these are the ones that pair with something Markdown already expresses. Anything else — fumadocs' `<Tabs>`, `<Accordions>`, `<Files>` among them — 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 details.
254
+ 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
255
 
235
256
  Numbered headings — `## 1. Install it`, `## 2. Point it at a folder` — become a numbered sequence.
236
257
 
@@ -244,8 +265,6 @@ Numbered headings — `## 1. Install it`, `## 2. Point it at a folder` — becom
244
265
  | `guide/index.md` | `/guide` |
245
266
  | `guide/Deep Dive.md` | `/guide/deep-dive` |
246
267
 
247
- Both `/guide` and `/guide/` work on every host.
248
-
249
268
  ### Ordering
250
269
 
251
270
  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 writeFileSync4 } from "fs";
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 writeFileSync3 } from "fs";
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
- writeFileSync3(target, png);
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
- writeFileSync4(join8(outDir, "api", "search.json"), index, "utf8");
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);