seemore 1.12.20 → 1.12.22

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
@@ -64,7 +64,7 @@ npx seemore # renders this folder at http://localhost:4040
64
64
 
65
65
  The [seemore website](https://arifszn.github.io/seemore) is a folder of Markdown, rendered by seemore. Your own folder will look the same way.
66
66
 
67
- <details open>
67
+ <details>
68
68
  <summary><strong>Or ask an AI agent to set it up</strong></summary>
69
69
  <br/>
70
70
 
@@ -114,7 +114,7 @@ Point seemore at any Markdown you already have: AI-written notes, project docs,
114
114
  - **Zero config**: no config file, no code, no files to move
115
115
  - **Live preview**: files appear, disappear and reorder as you edit them
116
116
  - **Edit in place**: double-click any block in the preview to fix its Markdown
117
- - **Desktop app**: for macOS, Windows and Linux
117
+ - **Desktop app**: for macOS, Windows and Linux, with an integrated terminal
118
118
  - **Editor integration**: one extension covers VS Code, Cursor, Antigravity and other VS Code-compatible editors, including remote workspaces
119
119
  - **Documentation framework**: `seemore build` renders the whole site to HTML, ready to deploy on any host
120
120
  - **Password protection**: protect the built site with one shared password, with no server required
@@ -136,11 +136,15 @@ Inline editing works only for local previews.
136
136
 
137
137
  ## Open with the desktop app
138
138
 
139
- Download the app from the [download page](https://arifszn.github.io/seemore/download). It opens a folder or a `.md` file as a site in its own window. You do not need a terminal or Node.js.
139
+ Download the app from the [download page](https://arifszn.github.io/seemore/download). It opens a folder or a `.md` file as a site in its own window, with an integrated terminal.
140
+
141
+ <p align="center">
142
+ <img src="https://raw.githubusercontent.com/arifszn/seemore/main/packages/site/assets/desktop-app.png" alt="The seemore desktop app in the black theme, showing a folder of research notes as a site: a paragraph's Markdown source open in the inline editor above a flowchart, and the integrated terminal listing the folder's Markdown files" width="640"/>
143
+ </p>
140
144
 
141
145
  - **Open a folder**: use **File > Open Folder**, or drag the folder onto the app.
142
146
  - **Open a file**: double-click a `.md` file, or use **File > Open File**.
143
- - **Export and build**: use the **File** menu.
147
+ - **Use the integrated terminal**: it opens in the site's folder, so you can run an AI agent, git or a script next to the pages it changes.
144
148
 
145
149
  ## View in your code editor
146
150
 
package/dist/cli/index.js CHANGED
@@ -1159,11 +1159,11 @@ function humanise(slug) {
1159
1159
  // src/node/content/source.ts
1160
1160
  function createSource(options) {
1161
1161
  let cached;
1162
- const read = () => cached ??= scan(options);
1162
+ const read2 = () => cached ??= scan(options);
1163
1163
  const loader = dynamicLoader(
1164
1164
  {
1165
1165
  cache: "custom",
1166
- files: () => read().files,
1166
+ files: () => read2().files,
1167
1167
  invalidate: () => {
1168
1168
  cached = void 0;
1169
1169
  }
@@ -1172,11 +1172,11 @@ function createSource(options) {
1172
1172
  );
1173
1173
  return {
1174
1174
  loader,
1175
- current: read,
1176
- pages: () => read().pages,
1175
+ current: read2,
1176
+ pages: () => read2().pages,
1177
1177
  refresh() {
1178
1178
  loader.invalidate();
1179
- return read();
1179
+ return read2();
1180
1180
  },
1181
1181
  async getPageTree() {
1182
1182
  const output = await loader.get();
@@ -1822,6 +1822,14 @@ function spliceSource(content, request) {
1822
1822
  }
1823
1823
  return { ok: true, content: content.slice(0, start) + withEol(text, dominantEol(content)) + content.slice(end) };
1824
1824
  }
1825
+ function appendSource(content, text) {
1826
+ const body = text.replace(/\r\n/g, "\n").replace(/\s+$/, "");
1827
+ if (body.trim() === "") return void 0;
1828
+ const lf = content.replace(/\r\n/g, "\n");
1829
+ const separator = lf === "" || lf.endsWith("\n\n") ? "" : lf.endsWith("\n") ? "\n" : "\n\n";
1830
+ return content + withEol(`${separator}${body}
1831
+ `, dominantEol(content));
1832
+ }
1825
1833
  function dominantEol(content) {
1826
1834
  const crlf = content.match(/\r\n/g)?.length ?? 0;
1827
1835
  const lf = (content.match(/\n/g)?.length ?? 0) - crlf;
@@ -2018,7 +2026,8 @@ function handleSourceRead(ctx, req, res) {
2018
2026
  if (page === void 0) return send(res, 404, { error: "That file is not part of this site." });
2019
2027
  const start = Number(query.get("start"));
2020
2028
  const end = Number(query.get("end"));
2021
- const content = readFileSync6(page.absPath, "utf8");
2029
+ const content = read(res, page.absPath);
2030
+ if (content === void 0) return;
2022
2031
  if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end > content.length) {
2023
2032
  return send(res, 400, { error: "The requested range is not inside this file." });
2024
2033
  }
@@ -2033,10 +2042,19 @@ async function handleSourceWrite(ctx, req, res) {
2033
2042
  }
2034
2043
  const page = resolvePage(ctx, body.file);
2035
2044
  if (page === void 0) return send(res, 404, { error: "That file is not part of this site." });
2045
+ if (body.append === true) {
2046
+ if (typeof body.text !== "string") return send(res, 400, { error: "`text` is required." });
2047
+ const content2 = read(res, page.absPath);
2048
+ if (content2 === void 0) return;
2049
+ const appended = appendSource(content2, body.text);
2050
+ if (appended === void 0) return send(res, 200, { ok: true });
2051
+ return write(res, page.absPath, appended);
2052
+ }
2036
2053
  if (typeof body.expected !== "string" || typeof body.text !== "string") {
2037
2054
  return send(res, 400, { error: "Both `expected` and `text` are required." });
2038
2055
  }
2039
- const content = readFileSync6(page.absPath, "utf8");
2056
+ const content = read(res, page.absPath);
2057
+ if (content === void 0) return;
2040
2058
  const result = spliceSource(content, {
2041
2059
  start: body.start,
2042
2060
  end: body.end,
@@ -2044,7 +2062,24 @@ async function handleSourceWrite(ctx, req, res) {
2044
2062
  text: body.text
2045
2063
  });
2046
2064
  if (!result.ok) return send(res, result.status, { error: result.error });
2047
- writeFileSync5(page.absPath, result.content, "utf8");
2065
+ return write(res, page.absPath, result.content);
2066
+ }
2067
+ function read(res, file) {
2068
+ try {
2069
+ return readFileSync6(file, "utf8");
2070
+ } catch (error) {
2071
+ const code = error.code;
2072
+ send(res, 500, { error: `The file could not be read${code === void 0 ? "" : ` (${code})`}. Is another program holding it open?` });
2073
+ return void 0;
2074
+ }
2075
+ }
2076
+ function write(res, file, content) {
2077
+ try {
2078
+ writeFileSync5(file, content, "utf8");
2079
+ } catch (error) {
2080
+ const code = error.code;
2081
+ return send(res, 500, { error: `The file could not be written${code === void 0 ? "" : ` (${code})`}. Is another program holding it open?` });
2082
+ }
2048
2083
  return send(res, 200, { ok: true });
2049
2084
  }
2050
2085
  function resolvePage(ctx, file) {