paseo-ratex-markdown 0.0.0-stage → 0.1.1
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 +21 -0
- package/OVERVIEW.md +23 -0
- package/README.md +165 -2
- package/assets/icon.png +0 -0
- package/assets/screenshot-compact.png +0 -0
- package/assets/screenshot-dark.png +0 -0
- package/assets/screenshot-light.png +0 -0
- package/client/capabilities.ts +10 -0
- package/client/document-context.ts +26 -0
- package/client/document.tsx +295 -0
- package/client/file-panel.tsx +172 -0
- package/client/image.tsx +76 -0
- package/client/links.ts +36 -0
- package/client/markdown/ast.ts +42 -0
- package/client/markdown/front-matter.ts +45 -0
- package/client/markdown/math-syntax.ts +236 -0
- package/client/markdown/parse.ts +326 -0
- package/client/math/formula.tsx +156 -0
- package/client/math/loader.ts +67 -0
- package/client/math/native.ts +117 -0
- package/client/math/svg.ts +139 -0
- package/client/styles.ts +128 -0
- package/client/web.ts +58 -0
- package/client/workspace-panel.tsx +316 -0
- package/index.client.tsx +28 -0
- package/index.server.ts +17 -0
- package/node_modules/entities/LICENSE +11 -0
- package/node_modules/entities/dist/decode-codepoint.d.ts +28 -0
- package/node_modules/entities/dist/decode-codepoint.d.ts.map +1 -0
- package/node_modules/entities/dist/decode-codepoint.js +61 -0
- package/node_modules/entities/dist/decode-codepoint.js.map +1 -0
- package/node_modules/entities/dist/decode.d.ts +227 -0
- package/node_modules/entities/dist/decode.d.ts.map +1 -0
- package/node_modules/entities/dist/decode.js +1132 -0
- package/node_modules/entities/dist/decode.js.map +1 -0
- package/node_modules/entities/dist/encode.d.ts +26 -0
- package/node_modules/entities/dist/encode.d.ts.map +1 -0
- package/node_modules/entities/dist/encode.js +295 -0
- package/node_modules/entities/dist/encode.js.map +1 -0
- package/node_modules/entities/dist/escape.d.ts +60 -0
- package/node_modules/entities/dist/escape.d.ts.map +1 -0
- package/node_modules/entities/dist/escape.js +172 -0
- package/node_modules/entities/dist/escape.js.map +1 -0
- package/node_modules/entities/dist/generated/decode-data-html.d.ts +3 -0
- package/node_modules/entities/dist/generated/decode-data-html.d.ts.map +1 -0
- package/node_modules/entities/dist/generated/decode-data-html.js +5 -0
- package/node_modules/entities/dist/generated/decode-data-html.js.map +1 -0
- package/node_modules/entities/dist/generated/decode-data-xml.d.ts +3 -0
- package/node_modules/entities/dist/generated/decode-data-xml.d.ts.map +1 -0
- package/node_modules/entities/dist/generated/decode-data-xml.js +7 -0
- package/node_modules/entities/dist/generated/decode-data-xml.js.map +1 -0
- package/node_modules/entities/dist/generated/encode-html.d.ts +3 -0
- package/node_modules/entities/dist/generated/encode-html.d.ts.map +1 -0
- package/node_modules/entities/dist/generated/encode-html.js +3 -0
- package/node_modules/entities/dist/generated/encode-html.js.map +1 -0
- package/node_modules/entities/dist/index.d.ts +89 -0
- package/node_modules/entities/dist/index.d.ts.map +1 -0
- package/node_modules/entities/dist/index.js +87 -0
- package/node_modules/entities/dist/index.js.map +1 -0
- package/node_modules/entities/dist/internal/bin-trie-flags.d.ts +48 -0
- package/node_modules/entities/dist/internal/bin-trie-flags.d.ts.map +1 -0
- package/node_modules/entities/dist/internal/bin-trie-flags.js +49 -0
- package/node_modules/entities/dist/internal/bin-trie-flags.js.map +1 -0
- package/node_modules/entities/dist/internal/decode-shared.d.ts +38 -0
- package/node_modules/entities/dist/internal/decode-shared.d.ts.map +1 -0
- package/node_modules/entities/dist/internal/decode-shared.js +195 -0
- package/node_modules/entities/dist/internal/decode-shared.js.map +1 -0
- package/node_modules/entities/package.json +83 -0
- package/node_modules/entities/readme.md +131 -0
- package/node_modules/entities/src/decode-codepoint.ts +69 -0
- package/node_modules/entities/src/decode.ts +1368 -0
- package/node_modules/entities/src/encode.ts +337 -0
- package/node_modules/entities/src/escape.ts +187 -0
- package/node_modules/entities/src/generated/decode-data-html.ts +12 -0
- package/node_modules/entities/src/generated/decode-data-xml.ts +7 -0
- package/node_modules/entities/src/generated/encode-html.ts +3 -0
- package/node_modules/entities/src/index.ts +160 -0
- package/node_modules/entities/src/internal/bin-trie-flags.ts +47 -0
- package/node_modules/entities/src/internal/decode-shared.ts +209 -0
- package/node_modules/linkify-it/LICENSE +22 -0
- package/node_modules/linkify-it/README.md +158 -0
- package/node_modules/linkify-it/build/index.cjs.js +651 -0
- package/node_modules/linkify-it/build/index.cjs.js.map +1 -0
- package/node_modules/linkify-it/build/index.d.ts +236 -0
- package/node_modules/linkify-it/build/index.mjs +538 -0
- package/node_modules/linkify-it/build/index.mjs.map +1 -0
- package/node_modules/linkify-it/package.json +66 -0
- package/node_modules/markdown-it/LICENSE +22 -0
- package/node_modules/markdown-it/README.md +44 -0
- package/node_modules/markdown-it/bin/markdown-it.mjs +107 -0
- package/node_modules/markdown-it/dist/browser/markdown-it.esm.min.mjs +3064 -0
- package/node_modules/markdown-it/dist/browser/markdown-it.esm.min.mjs.map +1 -0
- package/node_modules/markdown-it/dist/browser/markdown-it.umd.min.js +14 -0
- package/node_modules/markdown-it/dist/browser/markdown-it.umd.min.js.map +1 -0
- package/node_modules/markdown-it/dist/markdown-it.cjs.js +4562 -0
- package/node_modules/markdown-it/dist/markdown-it.cjs.js.map +1 -0
- package/node_modules/markdown-it/dist/markdown-it.d.cts +19 -0
- package/node_modules/markdown-it/dist/markdown-it.d.mts +951 -0
- package/node_modules/markdown-it/dist/markdown-it.mjs +3935 -0
- package/node_modules/markdown-it/dist/markdown-it.mjs.map +1 -0
- package/node_modules/markdown-it/node_modules/argparse/LICENSE +47 -0
- package/node_modules/markdown-it/node_modules/argparse/README.md +89 -0
- package/node_modules/markdown-it/node_modules/argparse/lib/_colorize.js +72 -0
- package/node_modules/markdown-it/node_modules/argparse/lib/argparse.d.ts +292 -0
- package/node_modules/markdown-it/node_modules/argparse/lib/argparse.js +3820 -0
- package/node_modules/markdown-it/node_modules/argparse/lib/difflib.js +190 -0
- package/node_modules/markdown-it/node_modules/argparse/lib/sub.js +67 -0
- package/node_modules/markdown-it/node_modules/argparse/lib/textwrap.js +440 -0
- package/node_modules/markdown-it/node_modules/argparse/package.json +38 -0
- package/node_modules/markdown-it/package.json +111 -0
- package/node_modules/markdown-it-footnote/LICENSE +22 -0
- package/node_modules/markdown-it-footnote/README.md +135 -0
- package/node_modules/markdown-it-footnote/dist/index.cjs.js +304 -0
- package/node_modules/markdown-it-footnote/dist/markdown-it-footnote.js +299 -0
- package/node_modules/markdown-it-footnote/dist/markdown-it-footnote.min.js +2 -0
- package/node_modules/markdown-it-footnote/index.mjs +353 -0
- package/node_modules/markdown-it-footnote/package.json +48 -0
- package/node_modules/mdurl/LICENSE +45 -0
- package/node_modules/mdurl/README.md +102 -0
- package/node_modules/mdurl/build/index.cjs.js +534 -0
- package/node_modules/mdurl/index.mjs +11 -0
- package/node_modules/mdurl/lib/decode.mjs +112 -0
- package/node_modules/mdurl/lib/encode.mjs +89 -0
- package/node_modules/mdurl/lib/format.mjs +21 -0
- package/node_modules/mdurl/lib/index.d.ts +28 -0
- package/node_modules/mdurl/lib/parse.mjs +308 -0
- package/node_modules/mdurl/package.json +43 -0
- package/node_modules/opentype.js/LICENSE +20 -0
- package/node_modules/opentype.js/README.md +577 -0
- package/node_modules/opentype.js/bin/ot +84 -0
- package/node_modules/opentype.js/dist/opentype.js +16333 -0
- package/node_modules/opentype.js/dist/opentype.min.js +4 -0
- package/node_modules/opentype.js/dist/opentype.min.js.map +7 -0
- package/node_modules/opentype.js/dist/opentype.min.mjs +3 -0
- package/node_modules/opentype.js/dist/opentype.min.mjs.map +7 -0
- package/node_modules/opentype.js/dist/opentype.mjs +16308 -0
- package/node_modules/opentype.js/package.json +51 -0
- package/node_modules/punycode.js/LICENSE-MIT.txt +20 -0
- package/node_modules/punycode.js/README.md +148 -0
- package/node_modules/punycode.js/package.json +58 -0
- package/node_modules/punycode.js/punycode.es6.js +444 -0
- package/node_modules/punycode.js/punycode.js +443 -0
- package/node_modules/ratex-wasm/LICENSE +85 -0
- package/node_modules/ratex-wasm/README.md +92 -0
- package/node_modules/ratex-wasm/README.zh-CN.md +99 -0
- package/node_modules/ratex-wasm/dist/index.d.ts +68 -0
- package/node_modules/ratex-wasm/dist/index.d.ts.map +1 -0
- package/node_modules/ratex-wasm/dist/index.js +145 -0
- package/node_modules/ratex-wasm/dist/ratex-formula.d.ts +31 -0
- package/node_modules/ratex-wasm/dist/ratex-formula.d.ts.map +1 -0
- package/node_modules/ratex-wasm/dist/ratex-formula.js +135 -0
- package/node_modules/ratex-wasm/dist/renderer.d.ts +21 -0
- package/node_modules/ratex-wasm/dist/renderer.d.ts.map +1 -0
- package/node_modules/ratex-wasm/dist/renderer.js +169 -0
- package/node_modules/ratex-wasm/dist/types.d.ts +87 -0
- package/node_modules/ratex-wasm/dist/types.d.ts.map +1 -0
- package/node_modules/ratex-wasm/dist/types.js +5 -0
- 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 +126 -0
- package/node_modules/ratex-wasm/package.json +50 -0
- package/node_modules/ratex-wasm/pkg/LICENSE +22 -0
- package/node_modules/ratex-wasm/pkg/package.json +22 -0
- package/node_modules/ratex-wasm/pkg/ratex_wasm.d.ts +75 -0
- package/node_modules/ratex-wasm/pkg/ratex_wasm.js +359 -0
- package/node_modules/ratex-wasm/pkg/ratex_wasm_bg.wasm +0 -0
- package/node_modules/ratex-wasm/pkg/ratex_wasm_bg.wasm.d.ts +10 -0
- package/node_modules/ratex-wasm/src/index.ts +223 -0
- package/node_modules/ratex-wasm/src/ratex-formula.ts +154 -0
- package/node_modules/ratex-wasm/src/renderer.ts +204 -0
- package/node_modules/ratex-wasm/src/types.ts +79 -0
- package/node_modules/uc.micro/LICENSE.txt +20 -0
- package/node_modules/uc.micro/README.md +31 -0
- package/node_modules/uc.micro/build/index.cjs.js +15 -0
- package/node_modules/uc.micro/build/index.d.ts +6 -0
- package/node_modules/uc.micro/build/index.mjs +8 -0
- package/node_modules/uc.micro/package.json +32 -0
- package/npm-shrinkwrap.json +4192 -0
- package/package.json +69 -4
- package/paseo-plugin.json +20 -0
- package/scripts/embed-assets.mjs +124 -0
- package/scripts/prepare-plugin.mjs +46 -0
- package/server/files.ts +176 -0
- package/server/glyphs.ts +108 -0
- package/server/ratex.ts +57 -0
- package/server/render.ts +69 -0
- package/server/scene.ts +165 -0
- package/server/vendor/katex-glyphs.ts +45 -0
- package/server/vendor/ratex-wasm-bytes.ts +3 -0
- package/shared/contracts.ts +77 -0
- package/shared/scene.ts +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZedRover
|
|
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/OVERVIEW.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
RaTeX Markdown renders Markdown files with their LaTeX math typeset by RaTeX, a KaTeX-compatible math engine written in Rust. Inline math (`$…$`, `\(…\)`) sits on the text baseline, display math (`$$…$$`, `\[…\]`, `math` code fences) is centered and scrolls sideways when it is wider than the panel, and chemistry with `\ce` works too. The rest of the document renders as well: headings, lists and task lists, quotes, tables, code blocks, footnotes, YAML front matter and images.
|
|
2
|
+
|
|
3
|
+
## Opening a file
|
|
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 in a workspace and pick a file from the list. The open document follows edits on disk.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
The daemon lays out each formula with the bundled `ratex-wasm` module and uses embedded KaTeX glyph outlines, so nothing is downloaded at runtime. The app draws each formula as a vector image that follows the Paseo theme. Selecting text across a formula copies its TeX source. A formula RaTeX cannot parse is shown as source with the error.
|
|
10
|
+
|
|
11
|
+
## Setup and requirements
|
|
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 includes its embedded math engine and glyph outlines; a Git installation prepares them from locked dependencies.
|
|
14
|
+
|
|
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
|
+
|
|
17
|
+
## Data access
|
|
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.
|
|
20
|
+
|
|
21
|
+
## Limitations
|
|
22
|
+
|
|
23
|
+
Code blocks are not syntax highlighted and Mermaid diagrams appear as code. Images inside links, such as badges, appear as their label. Characters outside the KaTeX fonts, such as CJK in `\text{}`, use the client's system fonts.
|
package/README.md
CHANGED
|
@@ -1,3 +1,166 @@
|
|
|
1
|
-
#
|
|
1
|
+
# RaTeX Markdown for Paseo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Open Markdown files in [Paseo](https://paseo.sh) with their LaTeX math typeset by
|
|
4
|
+
[RaTeX](https://github.com/erweixin/RaTeX), a KaTeX-compatible math engine written in Rust.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Inline math with `$…$`, `\(…\)` and GitHub's `` $`…`$ ``; display math with `$$…$$`, `\[…\]`
|
|
11
|
+
and ```` ```math ```` fences. Prices such as `$5 and $10` stay text.
|
|
12
|
+
- Everything RaTeX supports: KaTeX math syntax, environments (`aligned`, `cases`, matrices),
|
|
13
|
+
chemistry with `\ce` and `\pu`, and `\color`.
|
|
14
|
+
- The rest of the document renders too: headings, emphasis, links, lists and task lists,
|
|
15
|
+
quotes, tables with alignment, code blocks, footnotes, YAML front matter and images.
|
|
16
|
+
- Formulas are vector images drawn from the KaTeX fonts, so they stay sharp at any zoom and
|
|
17
|
+
follow the Paseo theme. Wide equations scroll sideways instead of shrinking.
|
|
18
|
+
- Selecting text across a formula copies its TeX source.
|
|
19
|
+
- Edits on disk show up while the document is open.
|
|
20
|
+
- An invalid formula shows its source and RaTeX's error instead of breaking the page.
|
|
21
|
+
|
|
22
|
+
| Light theme, as a file panel | Phone width |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
|  |  |
|
|
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
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
| Component | Requirement |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| Paseo | 0.11.0 or later, daemon and app |
|
|
45
|
+
| Daemon host | Node.js and npm on the daemon's `PATH`, for npm or Git installation |
|
|
46
|
+
| Clients | Desktop and web render formulas. iOS and Android render them on hosts whose `Icon` draws inline SVG (Paseo 0.13 and later) and show the TeX source on older ones. |
|
|
47
|
+
|
|
48
|
+
Plugins must be enabled on the daemon under **Settings → Plugins → Enable plugins**. Plugins
|
|
49
|
+
are trusted code: the server half runs on the daemon host and the client half inside Paseo.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
From npm:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
paseo plugin install npm:paseo-ratex-markdown
|
|
57
|
+
paseo plugin ls
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
From GitHub (the daemon runs `npm ci` and generates the embedded assets during installation):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
paseo plugin add git:ZedRover/paseo-ratex-markdown
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You can also paste either source into **Settings → Plugins → Plugin source**. The plugin ID,
|
|
67
|
+
the npm package and the repository share one name: update with
|
|
68
|
+
`paseo plugin update paseo-ratex-markdown`.
|
|
69
|
+
|
|
70
|
+
## Math syntax
|
|
71
|
+
|
|
72
|
+
| Written as | Renders as |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `$x^2$`, `\(x^2\)`, `` $`x^2`$ `` | inline math |
|
|
75
|
+
| `$$ … $$` and `\[ … \]` on their own lines | display math |
|
|
76
|
+
| `$$x^2$$` inside a paragraph | display math between the paragraph's halves, as on GitHub |
|
|
77
|
+
| ```` ```math ```` fenced block | display math |
|
|
78
|
+
|
|
79
|
+
Rules that keep prose intact:
|
|
80
|
+
|
|
81
|
+
- An inline `$` must not be followed by a space, the closing `$` must not follow a space or
|
|
82
|
+
precede a digit, and a `$` that starts a word ends the attempt. `$5 and $10` is text.
|
|
83
|
+
Inline math may wrap onto the next line, as formatters such as Prettier wrap prose.
|
|
84
|
+
- `\$` is a literal dollar sign, and `\[1\]` inside a sentence is an escaped bracket, as in
|
|
85
|
+
CommonMark. Code spans and code blocks are never math.
|
|
86
|
+
- Display math cannot cross a blank line, so a stray `$$` does not swallow the document.
|
|
87
|
+
- Formulas longer than 4096 characters are shown as source.
|
|
88
|
+
|
|
89
|
+
## How it works
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
Markdown text ──► markdown-it + math rules (client) ──► document tree
|
|
93
|
+
│ formulas, batched
|
|
94
|
+
▼
|
|
95
|
+
daemon: RaTeX WASM lays out each formula (DisplayList)
|
|
96
|
+
precomputed KaTeX glyph outlines
|
|
97
|
+
│ scene: glyphs, rules, paths
|
|
98
|
+
▼
|
|
99
|
+
web/desktop: one SVG <img> per formula iOS/Android: one inline SVG icon per glyph
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The daemon half embeds the `ratex-wasm` binary and KaTeX glyph outlines, so nothing is fetched at
|
|
103
|
+
runtime. During preparation, opentype.js extracts the outlines from the pinned fonts; the
|
|
104
|
+
runtime reads those paths directly without a font parser. Its SVG output matches RaTeX's own
|
|
105
|
+
`ratex-svg` renderer pixel for pixel on the test formulas. Layouts are cached on both sides,
|
|
106
|
+
so a theme change repaints without new requests,
|
|
107
|
+
and an edit only lays out the formulas that changed.
|
|
108
|
+
|
|
109
|
+
## Privacy and data access
|
|
110
|
+
|
|
111
|
+
- The daemon reads only Markdown files (`.md`, `.markdown`) and images inside the workspace
|
|
112
|
+
directory that Paseo reports for the open workspace, after resolving symlinks. Paths that
|
|
113
|
+
leave the workspace are refused.
|
|
114
|
+
- Formulas are laid out on the daemon; nothing is sent to other services.
|
|
115
|
+
- Images with `http(s)` addresses load directly on the client, as in any Markdown viewer.
|
|
116
|
+
- Links open in the system browser only for `http`, `https` and `mailto`.
|
|
117
|
+
- Raw HTML is never interpreted. HTML blocks show as source; inline formatting tags such as
|
|
118
|
+
`<kbd>` or `<sup>` are dropped and their text kept, `<br>` breaks the line, `<img>` shows its
|
|
119
|
+
label, and any other tag shows as written.
|
|
120
|
+
|
|
121
|
+
## Limitations
|
|
122
|
+
|
|
123
|
+
- Code blocks are not syntax highlighted, and Mermaid diagrams show as code.
|
|
124
|
+
- Inline images inside links or text (such as badges) show as their label; standalone images
|
|
125
|
+
render as blocks.
|
|
126
|
+
- Characters outside the KaTeX fonts, such as CJK inside `\text{}`, are drawn with the
|
|
127
|
+
client's system fonts.
|
|
128
|
+
- 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.
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm ci # installs dependencies and embeds the RaTeX assets (npm run prepare)
|
|
135
|
+
npm run typecheck
|
|
136
|
+
npm test
|
|
137
|
+
paseo plugin install "$PWD" # try it on your local daemon
|
|
138
|
+
paseo plugin reload paseo-ratex-markdown
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`npm run test:browser` renders the plugin in Chrome with react-native-web, the way Paseo
|
|
142
|
+
evaluates client bundles. Install the browser tools outside the plugin and point at them:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm install --prefix /tmp/ratex-browser esbuild puppeteer-core react@19.1.0 react-dom@19.1.0 react-native-web@0.21 @tanstack/react-query zod lucide-static
|
|
146
|
+
RATEX_BROWSER_DEPS=/tmp/ratex-browser CHROME_BIN=/usr/bin/google-chrome npm run test:browser
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Set `CHROME_CDP_URL` instead of `CHROME_BIN` to use a running Chrome, `RATEX_SCREENSHOTS=dir`
|
|
150
|
+
to save screenshots, and `RATEX_CLIENT_BUNDLE=file` to test a bundle taken from a daemon's
|
|
151
|
+
plugin catalog.
|
|
152
|
+
|
|
153
|
+
`npm run check:package` checks the files `npm pack` would publish, including the size of
|
|
154
|
+
reachable runtime dependencies and declarations against the registry's 2 MB budget.
|
|
155
|
+
|
|
156
|
+
## Credits
|
|
157
|
+
|
|
158
|
+
- [RaTeX](https://github.com/erweixin/RaTeX) by erweixin, MIT: parsing and layout (`ratex-wasm`).
|
|
159
|
+
- [KaTeX fonts](https://github.com/KaTeX/KaTeX), SIL Open Font License 1.1, shipped in `ratex-wasm`.
|
|
160
|
+
- [markdown-it](https://github.com/markdown-it/markdown-it) and
|
|
161
|
+
[markdown-it-footnote](https://github.com/markdown-it/markdown-it-footnote), MIT.
|
|
162
|
+
- [opentype.js](https://github.com/opentypejs/opentype.js), MIT.
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
[MIT](LICENSE)
|
package/assets/icon.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as sdk from "@getpaseo/plugin";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether the host's `Icon` draws inline SVG (`{ svg }`), the only way plugin code can draw
|
|
5
|
+
* vectors on iOS and Android. Paseo ships the SVG validator in the same release, so its export
|
|
6
|
+
* from the host's own copy of the SDK marks support.
|
|
7
|
+
*/
|
|
8
|
+
export function hostDrawsSvgIcons(): boolean {
|
|
9
|
+
return typeof (sdk as Record<string, unknown>).findSvgIconProblem === "function";
|
|
10
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PluginTheme } from "@getpaseo/plugin";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import type { DocumentStyles } from "./styles";
|
|
4
|
+
|
|
5
|
+
export type ImageResult = { uri: string } | { error: string };
|
|
6
|
+
|
|
7
|
+
/** What a hosting panel provides to the documents it shows. */
|
|
8
|
+
export type DocumentEnvironment = {
|
|
9
|
+
theme: PluginTheme;
|
|
10
|
+
layout: { compact: boolean; platform: "ios" | "android" | "web" };
|
|
11
|
+
/** Identifies the document, so relative image references are cached per document. */
|
|
12
|
+
documentId: string;
|
|
13
|
+
/** Reads a local image through the daemon; null where the panel cannot reach files. */
|
|
14
|
+
loadImage: ((src: string) => Promise<ImageResult>) | null;
|
|
15
|
+
openLink(href: string): void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type DocumentContextValue = DocumentEnvironment & { styles: DocumentStyles };
|
|
19
|
+
|
|
20
|
+
export const DocumentContext = createContext<DocumentContextValue | null>(null);
|
|
21
|
+
|
|
22
|
+
export function useDocument(): DocumentContextValue {
|
|
23
|
+
const value = useContext(DocumentContext);
|
|
24
|
+
if (!value) throw new Error("Markdown blocks must render inside a MarkdownDocument");
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { Icon } from "@getpaseo/plugin/client/react-native";
|
|
2
|
+
import { useMemo, type ReactNode } from "react";
|
|
3
|
+
import { ScrollView, Text, View } from "react-native";
|
|
4
|
+
import type { Align, Block, FrontMatter, Inline, ListItem } from "./markdown/ast";
|
|
5
|
+
import { parseMarkdown } from "./markdown/parse";
|
|
6
|
+
import { DocumentContext, useDocument, type DocumentContextValue, type DocumentEnvironment } from "./document-context";
|
|
7
|
+
import { DocumentImage } from "./image";
|
|
8
|
+
import { Formula, FormulaLoaderProvider } from "./math/formula";
|
|
9
|
+
import { BODY_FONT_SIZE, documentStyles, headingSize } from "./styles";
|
|
10
|
+
|
|
11
|
+
const BULLETS = ["•", "◦", "▪"];
|
|
12
|
+
|
|
13
|
+
/** A Markdown document with its math typeset by RaTeX. The hosting panel owns scrolling. */
|
|
14
|
+
export function MarkdownDocument({ source, environment }: { source: string; environment: DocumentEnvironment }) {
|
|
15
|
+
const tree = useMemo(() => parseMarkdown(source), [source]);
|
|
16
|
+
const { theme, layout } = environment;
|
|
17
|
+
const styles = useMemo(() => documentStyles(theme.colors, layout.compact), [theme, layout.compact]);
|
|
18
|
+
const context = useMemo<DocumentContextValue>(() => ({ ...environment, styles }), [environment, styles]);
|
|
19
|
+
return (
|
|
20
|
+
<FormulaLoaderProvider>
|
|
21
|
+
<DocumentContext.Provider value={context}>
|
|
22
|
+
<View style={styles.document}>
|
|
23
|
+
{tree.frontMatter ? <FrontMatterView frontMatter={tree.frontMatter} /> : null}
|
|
24
|
+
<Blocks blocks={tree.blocks} depth={0} />
|
|
25
|
+
</View>
|
|
26
|
+
</DocumentContext.Provider>
|
|
27
|
+
</FormulaLoaderProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function Blocks({ blocks, depth, tight = false }: { blocks: Block[]; depth: number; tight?: boolean }) {
|
|
32
|
+
const { styles } = useDocument();
|
|
33
|
+
return (
|
|
34
|
+
<View style={tight ? styles.tightBlocks : styles.blocks}>
|
|
35
|
+
{blocks.map((block, index) => (
|
|
36
|
+
<BlockView key={index} block={block} depth={depth} />
|
|
37
|
+
))}
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function BlockView({ block, depth }: { block: Block; depth: number }) {
|
|
43
|
+
const context = useDocument();
|
|
44
|
+
const { styles, theme, layout } = context;
|
|
45
|
+
switch (block.type) {
|
|
46
|
+
case "heading":
|
|
47
|
+
return (
|
|
48
|
+
<View>
|
|
49
|
+
<Text selectable accessibilityRole="header" style={styles.heading(block.level)}>
|
|
50
|
+
{renderInlines(block.children, context, headingSize(block.level))}
|
|
51
|
+
</Text>
|
|
52
|
+
{block.level <= 2 ? <View style={styles.headingRule} /> : null}
|
|
53
|
+
</View>
|
|
54
|
+
);
|
|
55
|
+
case "paragraph":
|
|
56
|
+
return (
|
|
57
|
+
<Text selectable style={styles.paragraph}>
|
|
58
|
+
{renderInlines(block.children, context, BODY_FONT_SIZE)}
|
|
59
|
+
</Text>
|
|
60
|
+
);
|
|
61
|
+
case "math":
|
|
62
|
+
return (
|
|
63
|
+
<Formula
|
|
64
|
+
latex={block.latex}
|
|
65
|
+
source={block.source}
|
|
66
|
+
display
|
|
67
|
+
fontSize={BODY_FONT_SIZE}
|
|
68
|
+
color={theme.colors.foreground}
|
|
69
|
+
platform={layout.platform}
|
|
70
|
+
styles={styles}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
case "image":
|
|
74
|
+
return <DocumentImage src={block.src} alt={block.alt} title={block.title} />;
|
|
75
|
+
case "code":
|
|
76
|
+
return <CodeBlock language={block.language} text={block.text} />;
|
|
77
|
+
case "blockquote":
|
|
78
|
+
return (
|
|
79
|
+
<View style={styles.blockquote}>
|
|
80
|
+
<Blocks blocks={block.children} depth={depth} />
|
|
81
|
+
</View>
|
|
82
|
+
);
|
|
83
|
+
case "list":
|
|
84
|
+
return (
|
|
85
|
+
<View style={block.loose ? styles.blocks : styles.tightBlocks}>
|
|
86
|
+
{block.items.map((item, index) => (
|
|
87
|
+
<View key={index} style={styles.listItem}>
|
|
88
|
+
<ListMarker item={item} label={block.ordered ? `${block.start + index}.` : BULLETS[depth % BULLETS.length]} />
|
|
89
|
+
<View style={styles.listContent}>
|
|
90
|
+
<Blocks blocks={item.children} depth={depth + 1} tight={!block.loose} />
|
|
91
|
+
</View>
|
|
92
|
+
</View>
|
|
93
|
+
))}
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
case "table":
|
|
97
|
+
return <TableView align={block.align} header={block.header} rows={block.rows} />;
|
|
98
|
+
case "html":
|
|
99
|
+
return (
|
|
100
|
+
<Text selectable style={styles.html}>
|
|
101
|
+
{block.text}
|
|
102
|
+
</Text>
|
|
103
|
+
);
|
|
104
|
+
case "rule":
|
|
105
|
+
return <View style={styles.rule} />;
|
|
106
|
+
case "footnotes":
|
|
107
|
+
return (
|
|
108
|
+
<View style={styles.footnotes}>
|
|
109
|
+
<View style={styles.rule} />
|
|
110
|
+
{block.items.map((item) => (
|
|
111
|
+
<View key={item.label} style={styles.footnote}>
|
|
112
|
+
<Text style={styles.footnoteLabel}>{item.label}.</Text>
|
|
113
|
+
<View style={styles.listContent}>
|
|
114
|
+
<Blocks blocks={item.children} depth={depth} tight />
|
|
115
|
+
</View>
|
|
116
|
+
</View>
|
|
117
|
+
))}
|
|
118
|
+
</View>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function ListMarker({ item, label }: { item: ListItem; label: string }) {
|
|
124
|
+
const { styles, theme } = useDocument();
|
|
125
|
+
return (
|
|
126
|
+
<View style={styles.listMarker}>
|
|
127
|
+
{item.checked === null ? (
|
|
128
|
+
<Text style={styles.listMarkerText}>{label}</Text>
|
|
129
|
+
) : (
|
|
130
|
+
<View style={{ paddingTop: 4 }} accessibilityLabel={item.checked ? "Done" : "Not done"}>
|
|
131
|
+
<Icon
|
|
132
|
+
name={item.checked ? "SquareCheck" : "Square"}
|
|
133
|
+
size={16}
|
|
134
|
+
color={item.checked ? theme.colors.accent : theme.colors.foregroundMuted}
|
|
135
|
+
/>
|
|
136
|
+
</View>
|
|
137
|
+
)}
|
|
138
|
+
</View>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function CodeBlock({ language, text }: { language: string; text: string }) {
|
|
143
|
+
const { styles } = useDocument();
|
|
144
|
+
return (
|
|
145
|
+
<View style={styles.codeBlock}>
|
|
146
|
+
{language ? <Text style={styles.codeLanguage}>{language}</Text> : null}
|
|
147
|
+
<ScrollView horizontal showsHorizontalScrollIndicator>
|
|
148
|
+
<Text selectable style={styles.codeText}>
|
|
149
|
+
{text}
|
|
150
|
+
</Text>
|
|
151
|
+
</ScrollView>
|
|
152
|
+
</View>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function TableView({ align, header, rows }: { align: Align[]; header: Inline[][]; rows: Inline[][][] }) {
|
|
157
|
+
const context = useDocument();
|
|
158
|
+
const { styles } = context;
|
|
159
|
+
const widths = useMemo(() => columnWidths(header, rows), [header, rows]);
|
|
160
|
+
const cell = (content: Inline[], column: number, heading: boolean, first: boolean) => (
|
|
161
|
+
<View
|
|
162
|
+
key={column}
|
|
163
|
+
style={[
|
|
164
|
+
styles.tableCell,
|
|
165
|
+
{ width: widths[column] },
|
|
166
|
+
first && { borderTopWidth: 0 },
|
|
167
|
+
column === header.length - 1 && { borderRightWidth: 0 },
|
|
168
|
+
]}
|
|
169
|
+
>
|
|
170
|
+
<Text selectable style={[heading ? styles.tableHeaderText : styles.paragraph, { textAlign: align[column] ?? "left" }]}>
|
|
171
|
+
{renderInlines(content, context, BODY_FONT_SIZE)}
|
|
172
|
+
</Text>
|
|
173
|
+
</View>
|
|
174
|
+
);
|
|
175
|
+
return (
|
|
176
|
+
<ScrollView horizontal showsHorizontalScrollIndicator>
|
|
177
|
+
<View style={styles.table}>
|
|
178
|
+
<View style={styles.tableHeaderRow}>{header.map((content, column) => cell(content, column, true, true))}</View>
|
|
179
|
+
{rows.map((row, index) => (
|
|
180
|
+
<View key={index} style={styles.tableRow}>
|
|
181
|
+
{header.map((_, column) => cell(row[column] ?? [], column, false, false))}
|
|
182
|
+
</View>
|
|
183
|
+
))}
|
|
184
|
+
</View>
|
|
185
|
+
</ScrollView>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function FrontMatterView({ frontMatter }: { frontMatter: FrontMatter }) {
|
|
190
|
+
const { styles } = useDocument();
|
|
191
|
+
if (!frontMatter.rows) return <CodeBlock language="yaml" text={frontMatter.raw} />;
|
|
192
|
+
if (frontMatter.rows.length === 0) return null;
|
|
193
|
+
return (
|
|
194
|
+
<View style={styles.frontMatter}>
|
|
195
|
+
{frontMatter.rows.map((row, index) => (
|
|
196
|
+
<View key={index} style={[styles.frontMatterRow, index === 0 && { borderTopWidth: 0 }]}>
|
|
197
|
+
<View style={styles.frontMatterKey}>
|
|
198
|
+
<Text selectable style={styles.frontMatterKeyText}>
|
|
199
|
+
{row.key}
|
|
200
|
+
</Text>
|
|
201
|
+
</View>
|
|
202
|
+
<View style={styles.frontMatterValue}>
|
|
203
|
+
<Text selectable style={styles.paragraph}>
|
|
204
|
+
{row.value}
|
|
205
|
+
</Text>
|
|
206
|
+
</View>
|
|
207
|
+
</View>
|
|
208
|
+
))}
|
|
209
|
+
</View>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function renderInlines(nodes: Inline[], context: DocumentContextValue, fontSize: number): ReactNode[] {
|
|
214
|
+
const { styles, theme, layout } = context;
|
|
215
|
+
return nodes.map((node, index) => {
|
|
216
|
+
switch (node.type) {
|
|
217
|
+
case "text":
|
|
218
|
+
return node.text;
|
|
219
|
+
case "softbreak":
|
|
220
|
+
return " ";
|
|
221
|
+
case "break":
|
|
222
|
+
return "\n";
|
|
223
|
+
case "strong":
|
|
224
|
+
case "em":
|
|
225
|
+
case "del":
|
|
226
|
+
return (
|
|
227
|
+
<Text key={index} style={styles[node.type]}>
|
|
228
|
+
{renderInlines(node.children, context, fontSize)}
|
|
229
|
+
</Text>
|
|
230
|
+
);
|
|
231
|
+
case "code":
|
|
232
|
+
return (
|
|
233
|
+
<Text key={index} style={styles.inlineCode}>
|
|
234
|
+
{node.text}
|
|
235
|
+
</Text>
|
|
236
|
+
);
|
|
237
|
+
case "link":
|
|
238
|
+
return (
|
|
239
|
+
<Text key={index} style={styles.link} accessibilityRole="link" onPress={() => context.openLink(node.href)}>
|
|
240
|
+
{renderInlines(node.children, context, fontSize)}
|
|
241
|
+
</Text>
|
|
242
|
+
);
|
|
243
|
+
case "image":
|
|
244
|
+
// Images nested in links or text (badges, icons) keep their place as their label.
|
|
245
|
+
return (
|
|
246
|
+
<Text key={index} style={styles.muted}>
|
|
247
|
+
{`[${node.alt || "image"}]`}
|
|
248
|
+
</Text>
|
|
249
|
+
);
|
|
250
|
+
case "math":
|
|
251
|
+
return (
|
|
252
|
+
<Formula
|
|
253
|
+
key={index}
|
|
254
|
+
latex={node.latex}
|
|
255
|
+
source={node.source}
|
|
256
|
+
display={false}
|
|
257
|
+
fontSize={fontSize}
|
|
258
|
+
color={theme.colors.foreground}
|
|
259
|
+
platform={layout.platform}
|
|
260
|
+
styles={styles}
|
|
261
|
+
/>
|
|
262
|
+
);
|
|
263
|
+
case "footnoteRef":
|
|
264
|
+
return (
|
|
265
|
+
<Text key={index} style={styles.footnoteRef}>
|
|
266
|
+
{`[${node.label}]`}
|
|
267
|
+
</Text>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Columns sized from their longest cell, within limits; a wide table scrolls sideways. */
|
|
274
|
+
function columnWidths(header: Inline[][], rows: Inline[][][]): number[] {
|
|
275
|
+
const widths = header.map(() => 0);
|
|
276
|
+
for (const row of [header, ...rows]) {
|
|
277
|
+
row.forEach((cell, column) => {
|
|
278
|
+
if (column < widths.length) widths[column] = Math.max(widths[column], textLength(cell));
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return widths.map((length) => Math.min(Math.max(length * 7.5 + 24, 64), 320));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function textLength(nodes: Inline[]): number {
|
|
285
|
+
let length = 0;
|
|
286
|
+
for (const node of nodes) {
|
|
287
|
+
if (node.type === "text") length += node.text.length;
|
|
288
|
+
else if (node.type === "code") length += node.text.length * 1.1;
|
|
289
|
+
else if (node.type === "math") length += node.latex.length * 0.6;
|
|
290
|
+
else if (node.type === "image") length += node.alt.length + 2;
|
|
291
|
+
else if ("children" in node) length += textLength(node.children);
|
|
292
|
+
else length += 1;
|
|
293
|
+
}
|
|
294
|
+
return length;
|
|
295
|
+
}
|