pandoc-glance 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oliver MacLaren
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # pandoc-glance
2
+
3
+ `pandoc-glance` renders Markdown and LaTeX files as good-looking pages in your browser. Add `--watch` to refresh the page whenever you save.
4
+
5
+ ![Dark browser preview showing MathML, highlighted TypeScript, and Mermaid](https://raw.githubusercontent.com/omaclaren/pandoc-glance/main/docs/screenshots/dark-browser.png)
6
+
7
+ ## Prerequisites
8
+
9
+ - [Node.js](https://nodejs.org/) 22 or newer
10
+ - [Pandoc](https://pandoc.org/) on `PATH`
11
+
12
+ Install Pandoc on macOS with:
13
+
14
+ ```bash
15
+ brew install pandoc
16
+ ```
17
+
18
+ On Debian/Ubuntu, use `sudo apt install pandoc`. On Windows, use `winget install --id JohnMacFarlane.Pandoc`. If Pandoc is installed elsewhere, set `PANDOC_PATH` to the executable.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install --global pandoc-glance
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```text
29
+ pandoc-glance [options] <file>
30
+ pandoc-glance --watch [options] <file>
31
+ pandoc-glance <file> --watch
32
+ ```
33
+
34
+ | Option | Meaning |
35
+ |---|---|
36
+ | `-w, --watch` | Start a live preview server and re-render after saved changes |
37
+ | `--no-open` | Do not launch a browser; print the generated path or preview URL |
38
+ | `--theme auto\|light\|dark` | Browser theme; default `auto` follows `prefers-color-scheme` |
39
+ | `--format auto\|markdown\|latex` | Input format; default `auto` uses the file extension |
40
+ | `--font-size <px>` | Base document font size from 10 to 24; default 15 |
41
+ | `--port <number>` | Watch-server port; `0` or omission chooses an available port |
42
+ | `-h, --help` | Show help |
43
+ | `-v, --version` | Show the version |
44
+
45
+ Automatic format detection recognizes common Markdown extensions (`.md`, `.markdown`, `.mdown`, `.mkd`, `.qmd`, and `.rmd`) and standalone LaTeX (`.tex` and `.latex`). Use `--format` for another extension.
46
+
47
+ ### One-shot preview
48
+
49
+ ```bash
50
+ pandoc-glance notes.md
51
+ pandoc-glance paper.tex --theme light --font-size 16
52
+ ```
53
+
54
+ One-shot mode writes a cached HTML file and opens it in the default browser. The cache keeps at most 30 previews.
55
+
56
+ For headless or SSH use:
57
+
58
+ ```bash
59
+ pandoc-glance --no-open notes.md
60
+ # HTML: /path/to/cache/<id>.html
61
+ ```
62
+
63
+ ### Watch mode
64
+
65
+ ```bash
66
+ pandoc-glance --watch notes.md
67
+ pandoc-glance notes.md --watch --theme auto
68
+ pandoc-glance --watch --no-open --port 0 notes.md
69
+ ```
70
+
71
+ Watch mode:
72
+
73
+ - Opens one browser tab and updates it after each saved change.
74
+ - Handles ordinary writes and atomic saves.
75
+ - Preserves reading position across reloads.
76
+ - Keeps the last successful preview visible after a render error and recovers on the next valid save.
77
+
78
+ Watch mode sees files on disk, not unsaved editor buffers. Enable autosave for faster updates. If the initial render fails, the error page stays open and waits for a valid save. Stop the server with Ctrl-C.
79
+
80
+ ## Rendering examples
81
+
82
+ ### Math
83
+
84
+ All of these forms are supported in Markdown:
85
+
86
+ ```markdown
87
+ Inline dollar math: $E = mc^2$.
88
+
89
+ Inline parenthesized math: \(a^2 + b^2 = c^2\).
90
+
91
+ $$
92
+ \int_0^1 x^2\,dx = \frac{1}{3}
93
+ $$
94
+
95
+ \[
96
+ \mathbf{A}\mathbf{x} = \mathbf{b}
97
+ \]
98
+ ```
99
+
100
+ Pandoc emits native MathML when possible. The browser loads MathJax only for equations that Pandoc leaves as TeX.
101
+
102
+ ### Mermaid
103
+
104
+ ````markdown
105
+ ```mermaid
106
+ flowchart LR
107
+ Source[Markdown or LaTeX] --> Pandoc
108
+ Pandoc --> Browser
109
+ ```
110
+ ````
111
+
112
+ Mermaid runs in the preview page and loads only when a `mermaid` fence is present. No local Mermaid package or Mermaid CLI (`mmdc`) is required.
113
+
114
+ Flowchart icon nodes support `lucide:*` and `logos:*`. Keep each icon declaration on one source line:
115
+
116
+ ````markdown
117
+ ```mermaid
118
+ flowchart LR
119
+ source@{ icon: "lucide:file-code-2", label: "Source" }
120
+ github@{ icon: "logos:github-icon", label: "GitHub" }
121
+ source --> github
122
+ ```
123
+ ````
124
+
125
+ If Mermaid or an icon pack cannot load, the preview shows the error and the original diagram source.
126
+
127
+ ### Local resources and Obsidian images
128
+
129
+ Relative paths resolve from the source document's directory:
130
+
131
+ ```markdown
132
+ ![Experiment result](figures/result.png)
133
+ ![Path containing spaces](<figures/run 01.png>)
134
+ ![[figures/result.png|Obsidian-style caption]]
135
+ ```
136
+
137
+ Absolute local image paths are also supported. Watch mode uses revisioned, non-cached resource URLs so saved image changes appear immediately.
138
+
139
+ ### Standalone LaTeX
140
+
141
+ A `.tex` or `.latex` file is read as a complete LaTeX document:
142
+
143
+ ```latex
144
+ \documentclass{article}
145
+ \usepackage{amsmath}
146
+ \begin{document}
147
+ \section{Example}
148
+ Inline math $x^2$ and display math
149
+ \[
150
+ \int_0^1 x^2\,dx = \frac{1}{3}.
151
+ \]
152
+ \end{document}
153
+ ```
154
+
155
+ Previewing LaTeX does not run a TeX engine; Pandoc converts supported document structure and equations directly to HTML5/MathML.
156
+
157
+ ## Editor and shell integration
158
+
159
+ Launch watch mode from any editor or task runner that can invoke a shell command:
160
+
161
+ ```bash
162
+ pandoc-glance --watch "/absolute/path/to/current-file.md"
163
+ ```
164
+
165
+ ### Zed
166
+
167
+ Add this task to `.zed/tasks.json` in a project or to Zed's global tasks file:
168
+
169
+ ```json
170
+ [
171
+ {
172
+ "label": "Preview current Markdown/LaTeX file",
173
+ "command": "pandoc-glance",
174
+ "args": ["--watch", "$ZED_FILE"],
175
+ "cwd": "$ZED_WORKTREE_ROOT",
176
+ "use_new_terminal": true,
177
+ "allow_concurrent_runs": false,
178
+ "reveal": "always",
179
+ "save": "current"
180
+ }
181
+ ]
182
+ ```
183
+
184
+ Run **Preview current Markdown/LaTeX file** from Zed's task picker. With autosave enabled, the browser updates shortly after edits.
185
+
186
+ ### Other examples
187
+
188
+ - VS Code tasks can pass `${file}`.
189
+ - Vim and Neovim commands can pass the current buffer's expanded filename after writing it.
190
+ - Over SSH, use `--watch --no-open` and forward the printed port when remote browser access is needed.
191
+ - Scripts and CI checks can use one-shot `--no-open` without launching a GUI.
192
+
193
+ The server binds to `127.0.0.1`, so remote access requires an explicit tunnel such as `ssh -L`.
194
+
195
+ ## Network use
196
+
197
+ Pandoc conversion, styling, native MathML, syntax highlighting, local resources, and live reload are local. The browser downloads these optional components as needed:
198
+
199
+ - Mermaid 11.16 from jsDelivr when the document contains a Mermaid block.
200
+ - Lucide or Logos icon data from unpkg when a diagram uses that pack.
201
+ - MathJax 3 from jsDelivr when Pandoc leaves an equation as TeX.
202
+
203
+ Without network access, the core preview still works. Mermaid remains visible as source with an error, and unsupported equations remain as TeX with a warning.
204
+
205
+ ## Security model
206
+
207
+ Watch mode:
208
+
209
+ - Binds only to `127.0.0.1`.
210
+ - Uses a random 192-bit token in every preview route.
211
+ - Sets `no-store`, `nosniff`, no-referrer, same-origin, and content-security headers.
212
+ - Rejects path traversal, including encoded `..` and symlinks outside the source directory.
213
+ - Serves an outside absolute file only when the current document explicitly references it, through an opaque ID.
214
+
215
+ A failed render keeps the previous HTML and resource allowlist.
216
+
217
+ ## Troubleshooting
218
+
219
+ ### Pandoc was not found
220
+
221
+ Confirm installation:
222
+
223
+ ```bash
224
+ pandoc --version
225
+ ```
226
+
227
+ Or select a binary explicitly:
228
+
229
+ ```bash
230
+ PANDOC_PATH="/custom/path/pandoc" pandoc-glance notes.md
231
+ ```
232
+
233
+ The CLI validates Pandoc before starting and exits nonzero when it is unavailable.
234
+
235
+ ### The browser did not open
236
+
237
+ Use `--no-open` and open the printed HTML path or URL manually:
238
+
239
+ ```bash
240
+ pandoc-glance --no-open notes.md
241
+ pandoc-glance --watch --no-open notes.md
242
+ ```
243
+
244
+ The CLI uses the operating system default-browser command (`open`, `xdg-open`, or Windows `start`); it does not hard-code a browser.
245
+
246
+ ### A local image is missing
247
+
248
+ - Check the path relative to the source file, not the shell's current directory.
249
+ - Put paths containing spaces or parentheses in Markdown angle brackets.
250
+ - In watch mode, a relative symlink whose target is outside the document directory is deliberately blocked; use an explicit absolute path if that file should be allowlisted.
251
+ - Save the image and source file. Resource responses are not browser-cached.
252
+
253
+ ### The selected port is busy
254
+
255
+ Omit `--port` or use `--port 0` to let the operating system choose an available loopback port.
256
+
257
+ ## Development
258
+
259
+ ```bash
260
+ npm install
261
+ npm run typecheck
262
+ npm test
263
+ npm run build
264
+ ```
265
+
266
+ The test suite uses real Pandoc but does not open a browser or access the public network.
267
+
268
+ A representative fixture is available at [`test/fixtures/sample.md`](test/fixtures/sample.md), with a standalone LaTeX companion at [`test/fixtures/sample.tex`](test/fixtures/sample.tex).
269
+
270
+ For a manual headless smoke test:
271
+
272
+ ```bash
273
+ node dist/cli.js --watch --no-open test/fixtures/sample.md
274
+ ```
275
+
276
+ Fetch the printed loopback URL, save an edit to the fixture or a temporary copy, and observe the revision change. Press Ctrl-C to stop it.
277
+
278
+ ## License
279
+
280
+ MIT. The rendering palettes and selected normalization/browser-preview patterns were adapted from the MIT-licensed [`pi-markdown-preview`](https://github.com/omaclaren/pi-markdown-preview) implementation; see [`LICENSE`](LICENSE).
@@ -0,0 +1,3 @@
1
+ /** Open a URL with the operating system's configured default browser. */
2
+ export declare function openInDefaultBrowser(url: string): Promise<void>;
3
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAEA,yEAAyE;AACzE,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BrE"}
@@ -0,0 +1,25 @@
1
+ import { spawn } from "node:child_process";
2
+ /** Open a URL with the operating system's configured default browser. */
3
+ export async function openInDefaultBrowser(url) {
4
+ const command = process.platform === "darwin"
5
+ ? { executable: "open", args: [url] }
6
+ : process.platform === "win32"
7
+ ? { executable: "cmd.exe", args: ["/d", "/s", "/c", "start", "", url] }
8
+ : { executable: "xdg-open", args: [url] };
9
+ await new Promise((resolvePromise, rejectPromise) => {
10
+ const child = spawn(command.executable, command.args, {
11
+ stdio: "ignore",
12
+ detached: true,
13
+ windowsHide: true,
14
+ });
15
+ child.once("error", (error) => {
16
+ rejectPromise(new Error(`Could not open the default browser with ${command.executable}: ${error.message}. `
17
+ + "Retry with --no-open and open the printed URL or HTML path manually.", { cause: error }));
18
+ });
19
+ child.once("spawn", () => {
20
+ child.unref();
21
+ resolvePromise();
22
+ });
23
+ });
24
+ }
25
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,yEAAyE;AACzE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAW;IACpD,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3C,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE;QACrC,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC5B,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE;YACvE,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAE9C,MAAM,IAAI,OAAO,CAAO,CAAC,cAAc,EAAE,aAAa,EAAE,EAAE;QACxD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE;YACpD,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC5B,aAAa,CACX,IAAI,KAAK,CACP,2CAA2C,OAAO,CAAC,UAAU,KAAK,KAAK,CAAC,OAAO,IAAI;kBAC/E,sEAAsE,EAC1E,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACvB,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,cAAc,EAAE,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { type PreviewFormatOption } from "./render.js";
3
+ import type { PreviewTheme } from "./styles.js";
4
+ export declare const VERSION = "0.1.0";
5
+ export declare const DEFAULT_FONT_SIZE_PX = 15;
6
+ export declare const MIN_FONT_SIZE_PX = 10;
7
+ export declare const MAX_FONT_SIZE_PX = 24;
8
+ export interface CliOptions {
9
+ action: "run" | "help" | "version";
10
+ inputPath?: string;
11
+ watch: boolean;
12
+ open: boolean;
13
+ theme: PreviewTheme;
14
+ format: PreviewFormatOption;
15
+ fontSizePx: number;
16
+ port: number;
17
+ }
18
+ export declare class CliArgumentError extends Error {
19
+ constructor(message: string);
20
+ }
21
+ export declare function parseCliArgs(argv: string[]): CliOptions;
22
+ export declare function helpText(): string;
23
+ export declare function runCli(argv: string[]): Promise<number>;
24
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AASA,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,eAAO,MAAM,OAAO,UAAU,CAAC;AAC/B,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAqCD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAyGvD;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAsBjC;AA2ID,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiC5D"}