paseo-ratex-markdown 0.1.1 → 0.1.3
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/OVERVIEW.md +3 -3
- package/README.md +39 -19
- package/client/active-file.ts +86 -0
- package/client/workspace-panel.tsx +19 -6
- package/index.client.tsx +6 -2
- package/npm-shrinkwrap.json +8 -10
- package/package.json +5 -7
- package/scripts/embed-assets.mjs +39 -4
- package/scripts/prepare-plugin.mjs +5 -6
- package/server/ratex.ts +2 -2
- package/server/scene.ts +1 -1
- package/{node_modules/ratex-wasm/dist/types.d.ts → server/vendor/ratex-types.d.ts} +2 -0
- package/{node_modules/ratex-wasm/pkg → server/vendor}/ratex_wasm.d.ts +2 -0
- package/{node_modules/ratex-wasm/pkg → server/vendor}/ratex_wasm.js +2 -0
- package/node_modules/opentype.js/LICENSE +0 -20
- package/node_modules/opentype.js/README.md +0 -577
- package/node_modules/opentype.js/bin/ot +0 -84
- package/node_modules/opentype.js/dist/opentype.js +0 -16333
- package/node_modules/opentype.js/dist/opentype.min.js +0 -4
- package/node_modules/opentype.js/dist/opentype.min.js.map +0 -7
- package/node_modules/opentype.js/dist/opentype.min.mjs +0 -3
- package/node_modules/opentype.js/dist/opentype.min.mjs.map +0 -7
- package/node_modules/opentype.js/dist/opentype.mjs +0 -16308
- package/node_modules/opentype.js/package.json +0 -51
- package/node_modules/ratex-wasm/LICENSE +0 -85
- package/node_modules/ratex-wasm/README.md +0 -92
- package/node_modules/ratex-wasm/README.zh-CN.md +0 -99
- package/node_modules/ratex-wasm/dist/index.d.ts +0 -68
- package/node_modules/ratex-wasm/dist/index.d.ts.map +0 -1
- package/node_modules/ratex-wasm/dist/index.js +0 -145
- package/node_modules/ratex-wasm/dist/ratex-formula.d.ts +0 -31
- package/node_modules/ratex-wasm/dist/ratex-formula.d.ts.map +0 -1
- package/node_modules/ratex-wasm/dist/ratex-formula.js +0 -135
- package/node_modules/ratex-wasm/dist/renderer.d.ts +0 -21
- package/node_modules/ratex-wasm/dist/renderer.d.ts.map +0 -1
- package/node_modules/ratex-wasm/dist/renderer.js +0 -169
- package/node_modules/ratex-wasm/dist/types.d.ts.map +0 -1
- package/node_modules/ratex-wasm/dist/types.js +0 -5
- package/node_modules/ratex-wasm/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Bold.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Italic.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Math-Italic.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Script-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/node_modules/ratex-wasm/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/node_modules/ratex-wasm/fonts.css +0 -126
- package/node_modules/ratex-wasm/package.json +0 -50
- package/node_modules/ratex-wasm/pkg/LICENSE +0 -22
- package/node_modules/ratex-wasm/pkg/package.json +0 -22
- package/node_modules/ratex-wasm/pkg/ratex_wasm_bg.wasm +0 -0
- package/node_modules/ratex-wasm/pkg/ratex_wasm_bg.wasm.d.ts +0 -10
- package/node_modules/ratex-wasm/src/index.ts +0 -223
- package/node_modules/ratex-wasm/src/ratex-formula.ts +0 -154
- package/node_modules/ratex-wasm/src/renderer.ts +0 -204
- package/node_modules/ratex-wasm/src/types.ts +0 -79
package/OVERVIEW.md
CHANGED
|
@@ -2,7 +2,7 @@ RaTeX Markdown renders Markdown files with their LaTeX math typeset by RaTeX, a
|
|
|
2
2
|
|
|
3
3
|
## Opening a file
|
|
4
4
|
|
|
5
|
-
On Paseo 0.13 and later, opening a `.md` or `.markdown` file offers **Markdown (RaTeX)** alongside Paseo's own Markdown and Source panels, and **Don't ask me again** makes it the default. On Paseo 0.11 and 0.12, which have no file panels, run **Open Markdown preview (RaTeX)** from the Command Center
|
|
5
|
+
On Paseo 0.13 and later, opening a `.md` or `.markdown` file offers **Markdown (RaTeX)** alongside Paseo's own Markdown and Source panels, and **Don't ask me again** makes it the default. On Paseo 0.11 and 0.12, which have no file panels, open a Markdown file and run **Open Markdown preview (RaTeX)** from the Command Center: the panel shows that file. Run from any other tab, it lists the workspace's Markdown files to pick from. The open document follows edits on disk.
|
|
6
6
|
|
|
7
7
|
## How it works
|
|
8
8
|
|
|
@@ -10,13 +10,13 @@ The daemon lays out each formula with the bundled `ratex-wasm` module and uses e
|
|
|
10
10
|
|
|
11
11
|
## Setup and requirements
|
|
12
12
|
|
|
13
|
-
Paseo 0.11.0 or later. Installing from npm or Git needs Node.js and npm on the daemon host. The npm package
|
|
13
|
+
Paseo 0.11.0 or later. Installing from npm or Git needs Node.js and npm on the daemon host. The npm package and the repository both include the embedded math engine and glyph outlines; a Git installation only installs the locked Markdown parser.
|
|
14
14
|
|
|
15
15
|
Desktop and web clients render formulas. iOS and Android clients render them where Paseo draws inline SVG icons (0.13 and later) and show the TeX source otherwise.
|
|
16
16
|
|
|
17
17
|
## Data access
|
|
18
18
|
|
|
19
|
-
The daemon reads only Markdown and image files inside the directory of the workspace being viewed, after resolving symlinks, and refuses paths outside it. It makes no network requests. Images with `http` or `https` addresses in a document are loaded by the app directly. Links open in the system browser only for `http`, `https` and `mailto` addresses. Raw HTML in a document is never interpreted: HTML blocks show as source, and inline tags are either dropped with their text kept (formatting such as `<kbd>`) or shown as written.
|
|
19
|
+
The daemon reads only Markdown and image files inside the directory of the workspace being viewed, after resolving symlinks, and refuses paths outside it. It makes no network requests. Images with `http` or `https` addresses in a document are loaded by the app directly. Links open in the system browser only for `http`, `https` and `mailto` addresses. Raw HTML in a document is never interpreted: HTML blocks show as source, and inline tags are either dropped with their text kept (formatting such as `<kbd>`) or shown as written. On Paseo 0.11 and 0.12 the preview command reads the tab layout the app keeps in its local storage on web and desktop, only to find the open Markdown file.
|
|
20
20
|
|
|
21
21
|
## Limitations
|
|
22
22
|
|
package/README.md
CHANGED
|
@@ -23,20 +23,6 @@ Open Markdown files in [Paseo](https://paseo.sh) with their LaTeX math typeset b
|
|
|
23
23
|
| --- | --- |
|
|
24
24
|
|  |  |
|
|
25
25
|
|
|
26
|
-
## How it opens Markdown
|
|
27
|
-
|
|
28
|
-
Paseo's plugin API decides where the plugin can draw:
|
|
29
|
-
|
|
30
|
-
- **Paseo 0.13 and later** ([file panels](https://github.com/getpaseo/paseo/pull/7411), merged
|
|
31
|
-
after 0.12.0): opening a
|
|
32
|
-
`.md` or `.markdown` file offers **Markdown (RaTeX)** next to Paseo's own Markdown and Source
|
|
33
|
-
panels. Pick it and check **Don't ask me again** to make it the default for Markdown files.
|
|
34
|
-
- **Paseo 0.11 and 0.12**: press <kbd>⌘K</kbd> / <kbd>Ctrl+K</kbd> in a workspace and run
|
|
35
|
-
**Open Markdown preview (RaTeX)**. The panel lists the workspace's Markdown files; pick one to
|
|
36
|
-
read it. Relative links to other Markdown files open in the same panel.
|
|
37
|
-
|
|
38
|
-
The plugin detects file panel support at runtime, so the same release works on both.
|
|
39
|
-
|
|
40
26
|
## Requirements
|
|
41
27
|
|
|
42
28
|
| Component | Requirement |
|
|
@@ -57,7 +43,7 @@ paseo plugin install npm:paseo-ratex-markdown
|
|
|
57
43
|
paseo plugin ls
|
|
58
44
|
```
|
|
59
45
|
|
|
60
|
-
From GitHub (the daemon
|
|
46
|
+
From GitHub (the daemon installs the locked Markdown parser with `npm ci` during installation):
|
|
61
47
|
|
|
62
48
|
```bash
|
|
63
49
|
paseo plugin add git:ZedRover/paseo-ratex-markdown
|
|
@@ -67,6 +53,33 @@ You can also paste either source into **Settings → Plugins → Plugin source**
|
|
|
67
53
|
the npm package and the repository share one name: update with
|
|
68
54
|
`paseo plugin update paseo-ratex-markdown`.
|
|
69
55
|
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
Paseo's plugin API decides where the plugin can draw. The plugin detects file panel support at
|
|
59
|
+
runtime, so the same release works on every version below.
|
|
60
|
+
|
|
61
|
+
### Paseo 0.11 and 0.12
|
|
62
|
+
|
|
63
|
+
1. Open a `.md` or `.markdown` file in the workspace.
|
|
64
|
+
2. Press <kbd>⌘K</kbd> / <kbd>Ctrl+K</kbd> and run **Open Markdown preview (RaTeX)**.
|
|
65
|
+
3. A **Markdown (RaTeX)** tab opens in the same pane and shows that file with its math typeset.
|
|
66
|
+
|
|
67
|
+
Run the command again from another Markdown tab to switch the preview to that file. Run from any
|
|
68
|
+
other tab, the preview lists the workspace's Markdown files instead; pick one to read it. iOS and
|
|
69
|
+
Android always start from this list.
|
|
70
|
+
|
|
71
|
+
In the preview, the file name at the top opens the list again with a filter, and the reload button
|
|
72
|
+
reads the file again. Relative links to other Markdown files open in the same preview.
|
|
73
|
+
|
|
74
|
+
### Paseo 0.13 and later
|
|
75
|
+
|
|
76
|
+
These releases add [file panels](https://github.com/getpaseo/paseo/pull/7411), merged after 0.12.0.
|
|
77
|
+
|
|
78
|
+
1. Open a `.md` or `.markdown` file. Paseo offers **Markdown (RaTeX)** next to its own Markdown
|
|
79
|
+
and Source panels.
|
|
80
|
+
2. Pick **Markdown (RaTeX)**. Check **Don't ask me again** to make it the default for Markdown
|
|
81
|
+
files.
|
|
82
|
+
|
|
70
83
|
## Math syntax
|
|
71
84
|
|
|
72
85
|
| Written as | Renders as |
|
|
@@ -100,8 +113,11 @@ Markdown text ──► markdown-it + math rules (client) ──► document tre
|
|
|
100
113
|
```
|
|
101
114
|
|
|
102
115
|
The daemon half embeds the `ratex-wasm` binary and KaTeX glyph outlines, so nothing is fetched at
|
|
103
|
-
runtime.
|
|
104
|
-
|
|
116
|
+
runtime. `npm run prepare` generates them in `server/vendor` from the pinned `ratex-wasm`
|
|
117
|
+
package, with opentype.js extracting the outlines from its fonts; the runtime reads those paths
|
|
118
|
+
directly without a font parser. The generated files are committed, and CI fails when they no
|
|
119
|
+
longer match the pinned package, so neither the npm package nor a Git installation carries
|
|
120
|
+
`ratex-wasm` itself. Its SVG output matches RaTeX's own
|
|
105
121
|
`ratex-svg` renderer pixel for pixel on the test formulas. Layouts are cached on both sides,
|
|
106
122
|
so a theme change repaints without new requests,
|
|
107
123
|
and an edit only lays out the formulas that changed.
|
|
@@ -112,6 +128,8 @@ and an edit only lays out the formulas that changed.
|
|
|
112
128
|
directory that Paseo reports for the open workspace, after resolving symlinks. Paths that
|
|
113
129
|
leave the workspace are refused.
|
|
114
130
|
- Formulas are laid out on the daemon; nothing is sent to other services.
|
|
131
|
+
- To find the open Markdown file on Paseo 0.11 and 0.12, the command reads the tab layout the
|
|
132
|
+
app keeps in its local storage on web and desktop. It reads nothing else there and writes nothing.
|
|
115
133
|
- Images with `http(s)` addresses load directly on the client, as in any Markdown viewer.
|
|
116
134
|
- Links open in the system browser only for `http`, `https` and `mailto`.
|
|
117
135
|
- Raw HTML is never interpreted. HTML blocks show as source; inline formatting tags such as
|
|
@@ -126,12 +144,14 @@ and an edit only lays out the formulas that changed.
|
|
|
126
144
|
- Characters outside the KaTeX fonts, such as CJK inside `\text{}`, are drawn with the
|
|
127
145
|
client's system fonts.
|
|
128
146
|
- Paseo 0.12 and earlier have no file panels, so there documents open in the plugin's own
|
|
129
|
-
panel instead of the file tab.
|
|
147
|
+
panel instead of the file tab. Their plugin API does not say which file is open either: the
|
|
148
|
+
command reads the app's saved layout, which iOS and Android keep out of reach, so there it
|
|
149
|
+
always shows the file list.
|
|
130
150
|
|
|
131
151
|
## Development
|
|
132
152
|
|
|
133
153
|
```bash
|
|
134
|
-
npm ci # installs dependencies
|
|
154
|
+
npm ci # installs dependencies; npm run prepare refreshes server/vendor
|
|
135
155
|
npm run typecheck
|
|
136
156
|
npm test
|
|
137
157
|
paseo plugin install "$PWD" # try it on your local daemon
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Markdown file open in Paseo's own file tab, so the preview command can show it.
|
|
3
|
+
*
|
|
4
|
+
* Paseo 0.11 and 0.12 tell plugins nothing about open tabs. On web and desktop the app persists
|
|
5
|
+
* its workspace layout in localStorage, and plugin code runs in the app's page, so this reads that
|
|
6
|
+
* private state. Anything it does not recognize means no file: the panel then keeps its own.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type KeyValueStorage = { getItem(key: string): string | null };
|
|
10
|
+
|
|
11
|
+
const LAYOUT_STORAGE_KEY = "workspace-layout-state";
|
|
12
|
+
|
|
13
|
+
type StoredTab = { tabId?: unknown; createdAt?: unknown; target?: { kind?: unknown; path?: unknown } };
|
|
14
|
+
type StoredPane = { id?: unknown; focusedTabId?: unknown; tabs?: unknown };
|
|
15
|
+
type StoredNode = { kind?: unknown; pane?: StoredPane; group?: { children?: unknown } };
|
|
16
|
+
type StoredLayout = { focusedPaneId?: unknown; root?: StoredNode };
|
|
17
|
+
|
|
18
|
+
/** The app's localStorage, or null on native clients and where storage is blocked. */
|
|
19
|
+
export function hostStorage(): KeyValueStorage | null {
|
|
20
|
+
try {
|
|
21
|
+
const storage = (globalThis as { localStorage?: KeyValueStorage }).localStorage;
|
|
22
|
+
return typeof storage?.getItem === "function" ? storage : null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The workspace-relative path of the Markdown file in the focused tab. When focus sits elsewhere,
|
|
30
|
+
* such as the file tree, the newest Markdown file showing in any pane.
|
|
31
|
+
*/
|
|
32
|
+
export function focusedMarkdownFile(
|
|
33
|
+
storage: KeyValueStorage | null,
|
|
34
|
+
workspace: { id: string; directory: string },
|
|
35
|
+
): string | null {
|
|
36
|
+
for (const layout of storedLayouts(storage, workspace.id)) {
|
|
37
|
+
const panes = collectPanes(layout.root);
|
|
38
|
+
const focused = panes.find((pane) => pane.id === layout.focusedPaneId);
|
|
39
|
+
const shown = panes.map(visibleTab).filter((tab): tab is StoredTab => markdownPath(tab) !== null);
|
|
40
|
+
shown.sort((a, b) => createdAt(b) - createdAt(a));
|
|
41
|
+
const path = markdownPath(focused && visibleTab(focused)) ?? markdownPath(shown[0]);
|
|
42
|
+
if (path) return workspaceRelative(path, workspace.directory);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Layouts are keyed `serverId:workspaceId`; the plugin knows only the workspace. */
|
|
48
|
+
function storedLayouts(storage: KeyValueStorage | null, workspaceId: string): StoredLayout[] {
|
|
49
|
+
let state: unknown;
|
|
50
|
+
try {
|
|
51
|
+
state = JSON.parse(storage?.getItem(LAYOUT_STORAGE_KEY) ?? "null")?.state;
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const layouts = (state as { layoutByWorkspace?: unknown } | null)?.layoutByWorkspace;
|
|
56
|
+
if (!layouts || typeof layouts !== "object") return [];
|
|
57
|
+
return Object.entries(layouts)
|
|
58
|
+
.filter(([key, layout]) => key.endsWith(`:${workspaceId}`) && layout && typeof layout === "object")
|
|
59
|
+
.map(([, layout]) => layout as StoredLayout);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function collectPanes(node: StoredNode | undefined): StoredPane[] {
|
|
63
|
+
if (node?.kind === "pane" && node.pane) return [node.pane];
|
|
64
|
+
const children = node?.kind === "group" ? node.group?.children : null;
|
|
65
|
+
return Array.isArray(children) ? children.flatMap((child: StoredNode) => collectPanes(child)) : [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function visibleTab(pane: StoredPane): StoredTab | undefined {
|
|
69
|
+
return Array.isArray(pane.tabs) ? pane.tabs.find((tab: StoredTab) => tab?.tabId === pane.focusedTabId) : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function markdownPath(tab: StoredTab | undefined): string | null {
|
|
73
|
+
const path = tab?.target?.kind === "file" ? tab.target.path : null;
|
|
74
|
+
return typeof path === "string" && /\.(md|markdown)$/i.test(path) ? path : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function createdAt(tab: StoredTab): number {
|
|
78
|
+
return typeof tab.createdAt === "number" ? tab.createdAt : 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** File tabs hold workspace-relative paths, but an absolute one inside the workspace is trimmed too. */
|
|
82
|
+
function workspaceRelative(file: string, directory: string): string {
|
|
83
|
+
const path = file.replace(/\\/g, "/");
|
|
84
|
+
const root = directory.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
85
|
+
return root && path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
|
|
86
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { PluginWorkspacePanelProps } from "@getpaseo/plugin/client";
|
|
|
2
2
|
import { openExternalUrl, useRpc } from "@getpaseo/plugin/client";
|
|
3
3
|
import { FlatList, Icon, Modal, TextInput } from "@getpaseo/plugin/client/react-native";
|
|
4
4
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { useCallback, useMemo, useState } from "react";
|
|
5
|
+
import { useCallback, useMemo, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { ActivityIndicator, Pressable, ScrollView, Text, View } from "react-native";
|
|
7
7
|
import { listMarkdownFilesRpc, readImageRpc, readMarkdownFileRpc } from "../shared/contracts";
|
|
8
8
|
import { MarkdownDocument } from "./document";
|
|
@@ -12,8 +12,22 @@ import { isExternalLink, resolveMarkdownLink } from "./links";
|
|
|
12
12
|
/** How often an open document checks the disk; unchanged files are not sent again. */
|
|
13
13
|
const POLL_INTERVAL = 2000;
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The file each workspace shows, outside the panel: reopening the panel returns to it, and the
|
|
17
|
+
* command can switch a panel that is already open.
|
|
18
|
+
*/
|
|
19
|
+
const shownFiles = new Map<string, string>();
|
|
20
|
+
const shownFileListeners = new Set<() => void>();
|
|
21
|
+
|
|
22
|
+
export function showMarkdownFile(workspaceId: string, path: string): void {
|
|
23
|
+
shownFiles.set(workspaceId, path);
|
|
24
|
+
shownFileListeners.forEach((listener) => listener());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function subscribeShownFiles(listener: () => void): () => void {
|
|
28
|
+
shownFileListeners.add(listener);
|
|
29
|
+
return () => shownFileListeners.delete(listener);
|
|
30
|
+
}
|
|
17
31
|
|
|
18
32
|
type Theme = PluginWorkspacePanelProps["theme"];
|
|
19
33
|
type Layout = PluginWorkspacePanelProps["layout"];
|
|
@@ -24,15 +38,14 @@ type DocumentData = { text: string; revision: string };
|
|
|
24
38
|
* file panels get this instead of rendering inside the file tab itself.
|
|
25
39
|
*/
|
|
26
40
|
export function MarkdownPreviewPanel({ workspaceId, theme, layout }: PluginWorkspacePanelProps) {
|
|
27
|
-
const
|
|
41
|
+
const path = useSyncExternalStore(subscribeShownFiles, () => shownFiles.get(workspaceId) ?? null);
|
|
28
42
|
const [picking, setPicking] = useState(false);
|
|
29
43
|
const queryClient = useQueryClient();
|
|
30
44
|
const styles = useMemo(() => panelStyles(theme, layout.compact), [theme, layout.compact]);
|
|
31
45
|
|
|
32
46
|
const open = useCallback(
|
|
33
47
|
(next: string) => {
|
|
34
|
-
|
|
35
|
-
setPath(next);
|
|
48
|
+
showMarkdownFile(workspaceId, next);
|
|
36
49
|
setPicking(false);
|
|
37
50
|
},
|
|
38
51
|
[workspaceId],
|
package/index.client.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
2
|
+
import { focusedMarkdownFile, hostStorage } from "./client/active-file";
|
|
2
3
|
import { registerFilePanel } from "./client/file-panel";
|
|
3
|
-
import { MarkdownPreviewPanel } from "./client/workspace-panel";
|
|
4
|
+
import { MarkdownPreviewPanel, showMarkdownFile } from "./client/workspace-panel";
|
|
4
5
|
|
|
5
6
|
export default function contribute(client: PluginClientContext) {
|
|
6
7
|
// Where the host can open Markdown files with a plugin panel, that is the whole integration.
|
|
@@ -20,7 +21,10 @@ export default function contribute(client: PluginClientContext) {
|
|
|
20
21
|
icon: "Sigma",
|
|
21
22
|
keywords: ["markdown", "math", "latex", "ratex", "preview"],
|
|
22
23
|
context: "workspace",
|
|
23
|
-
onSelect({ openPanel }) {
|
|
24
|
+
onSelect({ workspace, openPanel }) {
|
|
25
|
+
// Run from a Markdown file tab, the preview opens on that file.
|
|
26
|
+
const file = focusedMarkdownFile(hostStorage(), workspace);
|
|
27
|
+
if (file) showMarkdownFile(workspace.id, file);
|
|
24
28
|
openPanel("preview");
|
|
25
29
|
},
|
|
26
30
|
});
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paseo-ratex-markdown",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "paseo-ratex-markdown",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.3",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"markdown-it",
|
|
12
|
-
"markdown-it-footnote"
|
|
13
|
-
"opentype.js",
|
|
14
|
-
"ratex-wasm"
|
|
12
|
+
"markdown-it-footnote"
|
|
15
13
|
],
|
|
16
14
|
"license": "MIT",
|
|
17
15
|
"dependencies": {
|
|
18
16
|
"markdown-it": "15.0.3",
|
|
19
|
-
"markdown-it-footnote": "4.0.0"
|
|
20
|
-
"opentype.js": "2.0.0",
|
|
21
|
-
"ratex-wasm": "0.1.15"
|
|
17
|
+
"markdown-it-footnote": "4.0.0"
|
|
22
18
|
},
|
|
23
19
|
"devDependencies": {
|
|
24
20
|
"@getpaseo/plugin": "0.11.2",
|
|
25
21
|
"@tanstack/react-query": "^5.90.11",
|
|
26
22
|
"@types/node": "^22.19.0",
|
|
27
23
|
"@types/react": "~19.2.0",
|
|
24
|
+
"opentype.js": "2.0.0",
|
|
25
|
+
"ratex-wasm": "0.1.15",
|
|
28
26
|
"react": "19.1.0",
|
|
29
27
|
"react-native": "0.81.5",
|
|
30
28
|
"typescript": "^5.9.3",
|
|
@@ -3169,7 +3167,7 @@
|
|
|
3169
3167
|
"version": "2.0.0",
|
|
3170
3168
|
"resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-2.0.0.tgz",
|
|
3171
3169
|
"integrity": "sha512-kCyjv6xdDY1W/jLWZ/L3QhhTlKUqDZMQ5+Jdlw12b3dXkKNpYBqqlMMj0YDQPShWFTMwgZI1hG14kN3XUDSg/A==",
|
|
3172
|
-
"
|
|
3170
|
+
"dev": true,
|
|
3173
3171
|
"license": "MIT",
|
|
3174
3172
|
"bin": {
|
|
3175
3173
|
"ot": "bin/ot"
|
|
@@ -3336,7 +3334,7 @@
|
|
|
3336
3334
|
"version": "0.1.15",
|
|
3337
3335
|
"resolved": "https://registry.npmjs.org/ratex-wasm/-/ratex-wasm-0.1.15.tgz",
|
|
3338
3336
|
"integrity": "sha512-8+WuJFrx2l6dMVib07neaKnWlujQfXdLPatYX58/IiSy6BBZAIWP73DDfjzHZvi9PgU02YumsP6I78j7aMVw7w==",
|
|
3339
|
-
"
|
|
3337
|
+
"dev": true,
|
|
3340
3338
|
"license": "MIT"
|
|
3341
3339
|
},
|
|
3342
3340
|
"node_modules/react": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paseo-ratex-markdown",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Opens Markdown files in Paseo with LaTeX math typeset by RaTeX.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,21 +48,19 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"markdown-it": "15.0.3",
|
|
51
|
-
"markdown-it-footnote": "4.0.0"
|
|
52
|
-
"opentype.js": "2.0.0",
|
|
53
|
-
"ratex-wasm": "0.1.15"
|
|
51
|
+
"markdown-it-footnote": "4.0.0"
|
|
54
52
|
},
|
|
55
53
|
"bundleDependencies": [
|
|
56
54
|
"markdown-it",
|
|
57
|
-
"markdown-it-footnote"
|
|
58
|
-
"opentype.js",
|
|
59
|
-
"ratex-wasm"
|
|
55
|
+
"markdown-it-footnote"
|
|
60
56
|
],
|
|
61
57
|
"devDependencies": {
|
|
62
58
|
"@getpaseo/plugin": "0.11.2",
|
|
63
59
|
"@tanstack/react-query": "^5.90.11",
|
|
64
60
|
"@types/node": "^22.19.0",
|
|
65
61
|
"@types/react": "~19.2.0",
|
|
62
|
+
"opentype.js": "2.0.0",
|
|
63
|
+
"ratex-wasm": "0.1.15",
|
|
66
64
|
"react": "19.1.0",
|
|
67
65
|
"react-native": "0.81.5",
|
|
68
66
|
"typescript": "^5.9.3",
|
package/scripts/embed-assets.mjs
CHANGED
|
@@ -2,12 +2,14 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { brotliCompressSync, constants } from "node:zlib";
|
|
5
|
+
import { brotliCompressSync, brotliDecompressSync, constants } from "node:zlib";
|
|
6
6
|
import opentype from "opentype.js";
|
|
7
7
|
|
|
8
8
|
// The Paseo server bundle is evaluated from a string: it has no plugin directory to read
|
|
9
9
|
// files from at runtime. The RaTeX WASM binary and precomputed KaTeX outlines therefore
|
|
10
10
|
// travel inside the bundle as Brotli-compressed base64. Font parsing happens only here.
|
|
11
|
+
// The output is committed with the bindings it needs, so installations never run this and
|
|
12
|
+
// ratex-wasm stays a development dependency. CI checks that the output is current.
|
|
11
13
|
|
|
12
14
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
13
15
|
const packageDir = path.join(root, "node_modules", "ratex-wasm");
|
|
@@ -23,7 +25,7 @@ const FONT_IDS = [
|
|
|
23
25
|
|
|
24
26
|
const manifest = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
|
|
25
27
|
const installed = JSON.parse(await readFile(path.join(packageDir, "package.json"), "utf8"));
|
|
26
|
-
const pinned = manifest.
|
|
28
|
+
const pinned = manifest.devDependencies["ratex-wasm"];
|
|
27
29
|
if (installed.version !== pinned) {
|
|
28
30
|
throw new Error(`ratex-wasm ${installed.version} is installed, but package.json pins ${pinned}. Run npm ci.`);
|
|
29
31
|
}
|
|
@@ -45,14 +47,46 @@ function encode(bytes) {
|
|
|
45
47
|
const header = (source) =>
|
|
46
48
|
`// Generated by scripts/embed-assets.mjs from ratex-wasm@${installed.version}/${source}. Do not edit.\n`;
|
|
47
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Brotli output may differ between Node releases. Keep a committed file whose comments and
|
|
52
|
+
* payloads already describe the same bytes, so only a ratex-wasm update rewrites it.
|
|
53
|
+
*/
|
|
54
|
+
async function writeEncoded(file, text, payloads) {
|
|
55
|
+
const existing = await readFile(file, "utf8").catch(() => null);
|
|
56
|
+
if (existing !== null) {
|
|
57
|
+
const comments = (source) => source.split("\n").filter((line) => line.startsWith("//")).join("\n");
|
|
58
|
+
const found = [...existing.matchAll(/"([A-Za-z0-9+/]{64,}={0,2})"/g)].map((match) => match[1]);
|
|
59
|
+
const unchanged =
|
|
60
|
+
comments(existing) === comments(text) &&
|
|
61
|
+
found.length === payloads.length &&
|
|
62
|
+
found.every((base64, index) => {
|
|
63
|
+
const bytes = brotliDecompressSync(Buffer.from(base64, "base64"));
|
|
64
|
+
return createHash("sha256").update(bytes).digest("hex") === payloads[index].sha256;
|
|
65
|
+
});
|
|
66
|
+
if (unchanged) return;
|
|
67
|
+
}
|
|
68
|
+
await writeFile(file, text);
|
|
69
|
+
}
|
|
70
|
+
|
|
48
71
|
await mkdir(outDir, { recursive: true });
|
|
49
72
|
|
|
73
|
+
// The wasm-bindgen bindings and the display list types, so the runtime needs no ratex-wasm.
|
|
74
|
+
for (const [source, target] of [
|
|
75
|
+
["pkg/ratex_wasm.js", "ratex_wasm.js"],
|
|
76
|
+
["pkg/ratex_wasm.d.ts", "ratex_wasm.d.ts"],
|
|
77
|
+
["dist/types.d.ts", "ratex-types.d.ts"],
|
|
78
|
+
]) {
|
|
79
|
+
const content = await readFile(path.join(packageDir, source), "utf8");
|
|
80
|
+
await writeFile(path.join(outDir, target), header(source) + "// ratex-wasm: MIT License, Copyright (c) erweixin.\n" + content);
|
|
81
|
+
}
|
|
82
|
+
|
|
50
83
|
const wasm = encode(await readFile(path.join(packageDir, "pkg", "ratex_wasm_bg.wasm")));
|
|
51
|
-
await
|
|
84
|
+
await writeEncoded(
|
|
52
85
|
path.join(outDir, "ratex-wasm-bytes.ts"),
|
|
53
86
|
header("pkg/ratex_wasm_bg.wasm") +
|
|
54
87
|
`// ${wasm.size} bytes, sha256 ${wasm.sha256}; Brotli-compressed, then base64.\n` +
|
|
55
88
|
`export const RATEX_WASM_BROTLI_BASE64 = "${wasm.base64}";\n`,
|
|
89
|
+
[wasm],
|
|
56
90
|
);
|
|
57
91
|
|
|
58
92
|
const fonts = [];
|
|
@@ -67,7 +101,7 @@ for (const id of FONT_IDS) {
|
|
|
67
101
|
}
|
|
68
102
|
fonts.push({ id, ...encode(Buffer.from(JSON.stringify({ characters, outlines }))) });
|
|
69
103
|
}
|
|
70
|
-
await
|
|
104
|
+
await writeEncoded(
|
|
71
105
|
path.join(outDir, "katex-glyphs.ts"),
|
|
72
106
|
header("fonts/KaTeX_*.ttf") +
|
|
73
107
|
"// KaTeX fonts: SIL Open Font License 1.1, see the LICENSE file in ratex-wasm.\n" +
|
|
@@ -76,6 +110,7 @@ await writeFile(
|
|
|
76
110
|
"export const KATEX_GLYPHS_BROTLI_BASE64: Readonly<Record<string, string>> = {\n" +
|
|
77
111
|
fonts.map(({ id, base64 }) => ` "${id}": "${base64}",\n`).join("") +
|
|
78
112
|
"};\n",
|
|
113
|
+
fonts,
|
|
79
114
|
);
|
|
80
115
|
// Remove the pre-0.1.1 generated asset when rebuilding an existing checkout.
|
|
81
116
|
await rm(path.join(outDir, "katex-fonts.ts"), { force: true });
|
|
@@ -24,21 +24,20 @@ function run(command, args) {
|
|
|
24
24
|
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
if (!(await exists("
|
|
27
|
+
// Both channels carry the embedded RaTeX assets in server/vendor. npm also carries the
|
|
28
|
+
// bundled dependencies; a fresh Git checkout installs the locked production tree without
|
|
29
|
+
// lifecycle scripts.
|
|
30
|
+
if (!(await exists("node_modules/markdown-it/package.json"))) {
|
|
31
31
|
if (!(await exists("npm-shrinkwrap.json"))) throw new Error("Missing dependency lock. Reinstall the plugin.");
|
|
32
32
|
run(process.platform === "win32" ? "npm.cmd" : "npm", [
|
|
33
33
|
"ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund",
|
|
34
34
|
]);
|
|
35
|
-
run(process.execPath, ["scripts/embed-assets.mjs"]);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
for (const file of [
|
|
39
38
|
"server/vendor/ratex-wasm-bytes.ts",
|
|
40
39
|
"server/vendor/katex-glyphs.ts",
|
|
41
|
-
"
|
|
40
|
+
"server/vendor/ratex_wasm.js",
|
|
42
41
|
"node_modules/markdown-it/package.json",
|
|
43
42
|
"node_modules/markdown-it-footnote/package.json",
|
|
44
43
|
]) {
|
package/server/ratex.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import { brotliDecompressSync } from "node:zlib";
|
|
3
|
-
import type { DisplayList } from "ratex-
|
|
4
|
-
import { initSync, renderLatexWithOptions } from "
|
|
3
|
+
import type { DisplayList } from "./vendor/ratex-types.js";
|
|
4
|
+
import { initSync, renderLatexWithOptions } from "./vendor/ratex_wasm.js";
|
|
5
5
|
import { RATEX_WASM_BROTLI_BASE64 } from "./vendor/ratex-wasm-bytes.ts";
|
|
6
6
|
|
|
7
7
|
/**
|
package/server/scene.ts
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2020 Frederik De Bleser
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
-
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
-
the Software without restriction, including without limitation the rights to
|
|
8
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
-
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
-
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, FITNESS
|
|
17
|
-
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
-
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|