mkdocs-katex-ssr 0.1.0__tar.gz
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.
- mkdocs_katex_ssr-0.1.0/LICENSE +21 -0
- mkdocs_katex_ssr-0.1.0/PKG-INFO +10 -0
- mkdocs_katex_ssr-0.1.0/README.md +110 -0
- mkdocs_katex_ssr-0.1.0/mkdocs_katex_ssr/plugin.py +272 -0
- mkdocs_katex_ssr-0.1.0/mkdocs_katex_ssr/renderer.js +58 -0
- mkdocs_katex_ssr-0.1.0/mkdocs_katex_ssr.egg-info/PKG-INFO +10 -0
- mkdocs_katex_ssr-0.1.0/mkdocs_katex_ssr.egg-info/SOURCES.txt +11 -0
- mkdocs_katex_ssr-0.1.0/mkdocs_katex_ssr.egg-info/dependency_links.txt +1 -0
- mkdocs_katex_ssr-0.1.0/mkdocs_katex_ssr.egg-info/entry_points.txt +2 -0
- mkdocs_katex_ssr-0.1.0/mkdocs_katex_ssr.egg-info/requires.txt +2 -0
- mkdocs_katex_ssr-0.1.0/mkdocs_katex_ssr.egg-info/top_level.txt +1 -0
- mkdocs_katex_ssr-0.1.0/pyproject.toml +25 -0
- mkdocs_katex_ssr-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RainPPR
|
|
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.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mkdocs-katex-ssr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A MkDocs plugin for server-side rendering of KaTeX math.
|
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: mkdocs>=1.1.0
|
|
9
|
+
Requires-Dist: beautifulsoup4>=4.9.0
|
|
10
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# MkDocs KaTeX SSR Plugin
|
|
2
|
+
|
|
3
|
+
A MkDocs plugin that renders LaTeX math on the server side using [KaTeX](https://katex.org/), offering faster load times, layout stability, and optional offline support.
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> **Maintenance Philosophy**: This project is self-maintained. I welcome bug reports and corrections, but please note that I may not have the bandwidth to accept significant feature requests or major functional additions. Pull requests for bug fixes are appreciated.
|
|
7
|
+
>
|
|
8
|
+
> *This project's code and documentation were generated by Antigravity (Google DeepMind) and verified by the maintainer.*
|
|
9
|
+
|
|
10
|
+
## Why Server-Side Rendering (SSR)?
|
|
11
|
+
|
|
12
|
+
Traditional client-side rendering relies on JavaScript in the browser to convert LaTeX strings (e.g., `$\sqrt{a^2 + b^2}$`) into HTML. This can lead to:
|
|
13
|
+
|
|
14
|
+
- **Layout Shift**: Content jumps as math loads.
|
|
15
|
+
- **Slower Performance**: The browser must download and parse the KaTeX library before rendering.
|
|
16
|
+
- **Dependency**: Requires client-side JavaScript execution.
|
|
17
|
+
|
|
18
|
+
**MkDocs KaTeX SSR** pre-renders all math during the `mkdocs build` process using a local Node.js process. The resulting HTML contains ready-to-view markup.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **High Performance**: Uses a persistent Node.js process to render equations efficiently without spawning a new process for every item.
|
|
23
|
+
- **Offline Support**: Optional "Offline Mode" copies all necessary CSS, fonts, and scripts to your site directory, removing external CDN dependencies.
|
|
24
|
+
- **Smart Asset Management**: Separate configuration for server-side processing scripts (like `mhchem`) and client-side interactive scripts (like `copy-tex`).
|
|
25
|
+
- **Clean Output**: Aggressive warning suppression for a quieter build log.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Prerequisites
|
|
30
|
+
|
|
31
|
+
- **Node.js**: Must be installed and available in your system PATH.
|
|
32
|
+
- **Python**: 3.8+
|
|
33
|
+
|
|
34
|
+
### Install Plugin
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install mkdocs-katex-ssr
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
Add the plugin to your `mkdocs.yml`:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
markdown_extensions:
|
|
46
|
+
- pymdownx.arithmatex:
|
|
47
|
+
generic: true
|
|
48
|
+
|
|
49
|
+
plugins:
|
|
50
|
+
- katex-ssr:
|
|
51
|
+
# --- Basic Configuration ---
|
|
52
|
+
katex_dist: "https://cdn.jsdelivr.net/npm/katex@latest/dist/"
|
|
53
|
+
add_katex_css: true
|
|
54
|
+
katex_css_filename: "katex-swap.min.css" # Use swap version for better font-display behavior
|
|
55
|
+
|
|
56
|
+
# --- Script Loading ---
|
|
57
|
+
# Scripts needed for rendering (Server-Side). e.g., chemical formulas.
|
|
58
|
+
# These are NOT sent to the browser.
|
|
59
|
+
ssr_contribs:
|
|
60
|
+
- mhchem
|
|
61
|
+
|
|
62
|
+
# Scripts needed for interaction (Client-Side). e.g., clipboard copy.
|
|
63
|
+
# These ARE injected into the HTML.
|
|
64
|
+
client_scripts:
|
|
65
|
+
- copy-tex
|
|
66
|
+
|
|
67
|
+
# --- KaTeX Options ---
|
|
68
|
+
katex_options:
|
|
69
|
+
macros:
|
|
70
|
+
"\\RR": "\\mathbb{R}"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Offline Mode (Self-Contained)
|
|
74
|
+
|
|
75
|
+
If you need your documentation to work without an internet connection (or just want to host everything yourself), enable `embed_assets`.
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
plugins:
|
|
79
|
+
- katex-ssr:
|
|
80
|
+
embed_assets: true
|
|
81
|
+
# You can specify a local path if auto-detection fails
|
|
82
|
+
# katex_dist: "./node_modules/katex/dist/"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**What this does:**
|
|
86
|
+
|
|
87
|
+
1. **Copies Files**: It copies `katex.min.css` (or your chosen filename), the `fonts/` directory, and any specified `client_scripts` from your local `node_modules` to `site/assets/katex`.
|
|
88
|
+
2. **Relative Linking**: It updates all HTML citations to point to these local files using correct relative paths (e.g., `../assets/katex/katex.min.css`).
|
|
89
|
+
|
|
90
|
+
## Advanced Configuration Options
|
|
91
|
+
|
|
92
|
+
| Option | Type | Default | Description |
|
|
93
|
+
| :--- | :--- | :--- | :--- |
|
|
94
|
+
| `katex_dist` | str | jsDelivr | Base URL for CDN, or local file path to KaTeX distribution. |
|
|
95
|
+
| `add_katex_css` | bool | `true` | Whether to inject the CSS link tag. |
|
|
96
|
+
| `katex_css_filename` | str | `katex.min.css` | The specific CSS file to load. `katex-swap.min.css` is recommended. |
|
|
97
|
+
| `embed_assets` | bool | `false` | If true, copies assets to output dir and links locally. |
|
|
98
|
+
| `ssr_contribs` | list | `[]` | List of KaTeX `contrib` libraries to load in the Node.js renderer (e.g., `mhchem`). |
|
|
99
|
+
| `client_scripts` | list | `[]` | List of KaTeX `contrib` libraries to load in the browser (e.g., `copy-tex`). |
|
|
100
|
+
| `copy_assets_to` | str | `assets/katex` | Destination directory for copied assets (relative to site_dir). |
|
|
101
|
+
| `katex_options` | dict | `{}` | Standard options passed to `katex.renderToString` (macros, etc.). |
|
|
102
|
+
|
|
103
|
+
## Troubleshooting
|
|
104
|
+
|
|
105
|
+
- **`katex` module not found**: Ensure `npm install katex` (or `pnpm`/`yarn`) has been run in your project root, or specific the path via `katex_dist`.
|
|
106
|
+
- **Node.js error**: The plugin requires `node` to be in your PATH. On Windows, ensure you can run `node --version` in your terminal.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import subprocess
|
|
4
|
+
import threading
|
|
5
|
+
import warnings
|
|
6
|
+
import logging
|
|
7
|
+
import requests
|
|
8
|
+
import shutil
|
|
9
|
+
from mkdocs.plugins import BasePlugin
|
|
10
|
+
from mkdocs.config import config_options
|
|
11
|
+
from mkdocs.utils import get_relative_url
|
|
12
|
+
from bs4 import BeautifulSoup
|
|
13
|
+
|
|
14
|
+
# Extremely aggressive global warning suppression
|
|
15
|
+
warnings.filterwarnings("ignore")
|
|
16
|
+
os.environ["PYTHONWARNINGS"] = "ignore"
|
|
17
|
+
|
|
18
|
+
class WarningFilter(logging.Filter):
|
|
19
|
+
def filter(self, record):
|
|
20
|
+
msg = record.getMessage().lower()
|
|
21
|
+
if "pkg_resources" in msg or "jieba" in msg or "deprecationwarning" in msg or "userwarning" in msg:
|
|
22
|
+
return False
|
|
23
|
+
return True
|
|
24
|
+
|
|
25
|
+
# Apply filter to all potential loggers
|
|
26
|
+
for logger_name in ["mkdocs", "mkdocs.plugins", "py.warnings", ""]:
|
|
27
|
+
logger = logging.getLogger(logger_name)
|
|
28
|
+
logger.addFilter(WarningFilter())
|
|
29
|
+
|
|
30
|
+
logging.captureWarnings(True)
|
|
31
|
+
|
|
32
|
+
class KatexSsrPlugin(BasePlugin):
|
|
33
|
+
config_scheme = (
|
|
34
|
+
('katex_dist', config_options.Type(str, default='https://cdn.jsdelivr.net/npm/katex@latest/dist/')),
|
|
35
|
+
('katex_css_filename', config_options.Type(str, default='katex.min.css')),
|
|
36
|
+
('add_katex_css', config_options.Type(bool, default=True)),
|
|
37
|
+
('embed_assets', config_options.Type(bool, default=False)),
|
|
38
|
+
('copy_assets_to', config_options.Type(str, default='assets/katex')),
|
|
39
|
+
('ssr_contribs', config_options.Type(list, default=[])),
|
|
40
|
+
('client_scripts', config_options.Type(list, default=[])),
|
|
41
|
+
# Legacy/Alias for ssr_contribs to maintain some compat, though behavior changes
|
|
42
|
+
('contrib_scripts', config_options.Type(list, default=[])),
|
|
43
|
+
('katex_options', config_options.Type(dict, default={})),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.process = None
|
|
48
|
+
self.lock = threading.Lock()
|
|
49
|
+
self._asset_cache = {}
|
|
50
|
+
self._local_dist_path = None
|
|
51
|
+
|
|
52
|
+
def _ensure_trailing_slash(self, path):
|
|
53
|
+
if not path.endswith('/') and not path.endswith('\\'):
|
|
54
|
+
return path + '/'
|
|
55
|
+
return path
|
|
56
|
+
|
|
57
|
+
def _resolve_url(self, base, path):
|
|
58
|
+
base = base.replace('\\', '/')
|
|
59
|
+
if base.startswith('http'):
|
|
60
|
+
return base.rstrip('/') + '/' + path.lstrip('/')
|
|
61
|
+
else:
|
|
62
|
+
return os.path.normpath(os.path.join(base, path))
|
|
63
|
+
|
|
64
|
+
def on_config(self, config):
|
|
65
|
+
self.config['katex_dist'] = self._ensure_trailing_slash(self.config['katex_dist'])
|
|
66
|
+
|
|
67
|
+
project_dir = os.path.dirname(config['config_file_path'])
|
|
68
|
+
|
|
69
|
+
# Merge legacy contrib_scripts into ssr_contribs if used
|
|
70
|
+
if self.config['contrib_scripts']:
|
|
71
|
+
# Append unique items
|
|
72
|
+
for script in self.config['contrib_scripts']:
|
|
73
|
+
if script not in self.config['ssr_contribs']:
|
|
74
|
+
self.config['ssr_contribs'].append(script)
|
|
75
|
+
|
|
76
|
+
# Asset resolution logic
|
|
77
|
+
possible_dist = self._resolve_url(project_dir, self.config['katex_dist'])
|
|
78
|
+
if os.path.isdir(possible_dist):
|
|
79
|
+
self._local_dist_path = possible_dist
|
|
80
|
+
else:
|
|
81
|
+
node_modules = os.path.join(project_dir, 'node_modules')
|
|
82
|
+
dist = os.path.join(node_modules, 'katex', 'dist')
|
|
83
|
+
if os.path.isdir(dist):
|
|
84
|
+
self._local_dist_path = dist
|
|
85
|
+
|
|
86
|
+
# Start Node.js process
|
|
87
|
+
renderer_path = os.path.join(os.path.dirname(__file__), 'renderer.js')
|
|
88
|
+
|
|
89
|
+
use_shell = os.name == 'nt'
|
|
90
|
+
cmd = ['node', renderer_path]
|
|
91
|
+
if use_shell:
|
|
92
|
+
cmd = f'node "{renderer_path}"'
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
env = os.environ.copy()
|
|
96
|
+
node_modules = os.path.join(project_dir, 'node_modules')
|
|
97
|
+
if 'NODE_PATH' in env:
|
|
98
|
+
env['NODE_PATH'] = node_modules + os.pathsep + env['NODE_PATH']
|
|
99
|
+
else:
|
|
100
|
+
env['NODE_PATH'] = node_modules
|
|
101
|
+
|
|
102
|
+
self.process = subprocess.Popen(
|
|
103
|
+
cmd,
|
|
104
|
+
cwd=project_dir,
|
|
105
|
+
stdin=subprocess.PIPE,
|
|
106
|
+
stdout=subprocess.PIPE,
|
|
107
|
+
stderr=subprocess.PIPE,
|
|
108
|
+
shell=use_shell,
|
|
109
|
+
env=env
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Send ONLY ssr_contribs to Node
|
|
113
|
+
node_contribs = [c for c in self.config['ssr_contribs'] if '://' not in c]
|
|
114
|
+
setup_payload = {
|
|
115
|
+
'type': 'setup',
|
|
116
|
+
'contribs': node_contribs
|
|
117
|
+
}
|
|
118
|
+
try:
|
|
119
|
+
line = (json.dumps(setup_payload) + '\n').encode('utf-8')
|
|
120
|
+
self.process.stdin.write(line)
|
|
121
|
+
self.process.stdin.flush()
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f"Error during KaTeX setup: {e}")
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Error starting KaTeX renderer: {e}")
|
|
127
|
+
self.process = None
|
|
128
|
+
|
|
129
|
+
return config
|
|
130
|
+
|
|
131
|
+
def _render_latex(self, latex, display_mode=False):
|
|
132
|
+
if not self.process:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
with self.lock:
|
|
136
|
+
payload = {
|
|
137
|
+
'type': 'render',
|
|
138
|
+
'latex': latex,
|
|
139
|
+
'displayMode': display_mode,
|
|
140
|
+
'options': self.config['katex_options']
|
|
141
|
+
}
|
|
142
|
+
try:
|
|
143
|
+
line = (json.dumps(payload) + '\n').encode('utf-8')
|
|
144
|
+
self.process.stdin.write(line)
|
|
145
|
+
self.process.stdin.flush()
|
|
146
|
+
|
|
147
|
+
response_line = self.process.stdout.readline()
|
|
148
|
+
if not response_line:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
result = json.loads(response_line.decode('utf-8'))
|
|
152
|
+
if result.get('status') == 'success':
|
|
153
|
+
return result.get('html')
|
|
154
|
+
else:
|
|
155
|
+
print(f"KaTeX error: {result.get('message')}")
|
|
156
|
+
except Exception as e:
|
|
157
|
+
if self.process and self.process.poll() is not None:
|
|
158
|
+
stderr_content = self.process.stderr.read()
|
|
159
|
+
if stderr_content:
|
|
160
|
+
print(f"Renderer died with: {stderr_content.decode('utf-8', errors='replace')}")
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
def on_post_page(self, output, page, config):
|
|
164
|
+
if not self.process:
|
|
165
|
+
return output
|
|
166
|
+
|
|
167
|
+
soup = BeautifulSoup(output, 'html.parser')
|
|
168
|
+
math_elements = soup.find_all(class_='arithmatex')
|
|
169
|
+
for el in math_elements:
|
|
170
|
+
content = el.get_text().strip()
|
|
171
|
+
display_mode = False
|
|
172
|
+
|
|
173
|
+
if content.startswith('\\(') and content.endswith('\\)'):
|
|
174
|
+
latex = content[2:-2]
|
|
175
|
+
elif content.startswith('\\[') and content.endswith('\\]'):
|
|
176
|
+
latex = content[2:-2]
|
|
177
|
+
display_mode = True
|
|
178
|
+
elif content.startswith('$') and content.endswith('$'):
|
|
179
|
+
latex = content[1:-1]
|
|
180
|
+
elif content.startswith('$$') and content.endswith('$$'):
|
|
181
|
+
latex = content[2:-2]
|
|
182
|
+
display_mode = True
|
|
183
|
+
else:
|
|
184
|
+
latex = content
|
|
185
|
+
|
|
186
|
+
rendered_html = self._render_latex(latex, display_mode)
|
|
187
|
+
if rendered_html:
|
|
188
|
+
new_soup = BeautifulSoup(rendered_html, 'html.parser')
|
|
189
|
+
el.replace_with(new_soup)
|
|
190
|
+
|
|
191
|
+
# Assets Injection
|
|
192
|
+
css_file = self.config['katex_css_filename']
|
|
193
|
+
if self.config['add_katex_css']:
|
|
194
|
+
if self.config['embed_assets'] and self._local_dist_path:
|
|
195
|
+
dest_path = self.config['copy_assets_to']
|
|
196
|
+
css_dest_file = f"{dest_path}/{css_file}"
|
|
197
|
+
css_url = get_relative_url(css_dest_file, page.url)
|
|
198
|
+
css_link = soup.new_tag('link', rel='stylesheet', href=css_url)
|
|
199
|
+
if soup.head:
|
|
200
|
+
soup.head.append(css_link)
|
|
201
|
+
else:
|
|
202
|
+
soup.insert(0, css_link)
|
|
203
|
+
else:
|
|
204
|
+
css_url = self._resolve_url(self.config['katex_dist'], css_file)
|
|
205
|
+
css_link = soup.new_tag('link', rel='stylesheet', href=css_url)
|
|
206
|
+
if soup.head:
|
|
207
|
+
soup.head.append(css_link)
|
|
208
|
+
else:
|
|
209
|
+
soup.insert(0, css_link)
|
|
210
|
+
|
|
211
|
+
# Inject ONLY client_scripts
|
|
212
|
+
for script_name in self.config['client_scripts']:
|
|
213
|
+
if '://' in script_name or script_name.endswith('.js'):
|
|
214
|
+
script_url = script_name
|
|
215
|
+
else:
|
|
216
|
+
if self.config['embed_assets'] and self._local_dist_path:
|
|
217
|
+
dest_path = self.config['copy_assets_to']
|
|
218
|
+
script_dest_file = f"{dest_path}/contrib/{script_name}.min.js"
|
|
219
|
+
script_url = get_relative_url(script_dest_file, page.url)
|
|
220
|
+
else:
|
|
221
|
+
script_url = self._resolve_url(self.config['katex_dist'], f'contrib/{script_name}.min.js')
|
|
222
|
+
|
|
223
|
+
script_tag = soup.new_tag('script', src=script_url)
|
|
224
|
+
if soup.body:
|
|
225
|
+
soup.body.append(script_tag)
|
|
226
|
+
else:
|
|
227
|
+
soup.append(script_tag)
|
|
228
|
+
|
|
229
|
+
return str(soup)
|
|
230
|
+
|
|
231
|
+
def on_post_build(self, config):
|
|
232
|
+
if self.process:
|
|
233
|
+
self.process.terminate()
|
|
234
|
+
self.process.wait()
|
|
235
|
+
|
|
236
|
+
# Copy assets if requested
|
|
237
|
+
if self.config['embed_assets'] and self._local_dist_path:
|
|
238
|
+
dest_dir = os.path.join(config['site_dir'], self.config['copy_assets_to'])
|
|
239
|
+
if not os.path.exists(dest_dir):
|
|
240
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
241
|
+
|
|
242
|
+
# Copy katex CSS (filename depends on config)
|
|
243
|
+
css_file = self.config['katex_css_filename']
|
|
244
|
+
src_css = os.path.join(self._local_dist_path, css_file)
|
|
245
|
+
if os.path.exists(src_css):
|
|
246
|
+
shutil.copy2(src_css, dest_dir)
|
|
247
|
+
else:
|
|
248
|
+
print(f"Warning: Could not find {css_file} at {src_css}")
|
|
249
|
+
|
|
250
|
+
# Copy fonts
|
|
251
|
+
src_fonts = os.path.join(self._local_dist_path, 'fonts')
|
|
252
|
+
dest_fonts = os.path.join(dest_dir, 'fonts')
|
|
253
|
+
if os.path.exists(src_fonts):
|
|
254
|
+
if os.path.exists(dest_fonts):
|
|
255
|
+
shutil.rmtree(dest_fonts)
|
|
256
|
+
shutil.copytree(src_fonts, dest_fonts)
|
|
257
|
+
|
|
258
|
+
# Copy requested client_scripts
|
|
259
|
+
dest_contrib = os.path.join(dest_dir, 'contrib')
|
|
260
|
+
if not os.path.exists(dest_contrib):
|
|
261
|
+
os.makedirs(dest_contrib, exist_ok=True)
|
|
262
|
+
|
|
263
|
+
# Note: We technically might need to copy items from ssr_contribs IF the user wanted them
|
|
264
|
+
# but we decided they are separate. However, if 'mhchem' is in ssr_contribs only,
|
|
265
|
+
# we don't copy it. If user wants it on client, they MUST put it in client_scripts.
|
|
266
|
+
for script_name in self.config['client_scripts']:
|
|
267
|
+
if '://' not in script_name and not script_name.endswith('.js'):
|
|
268
|
+
src_script = os.path.join(self._local_dist_path, 'contrib', f'{script_name}.min.js')
|
|
269
|
+
if os.path.exists(src_script):
|
|
270
|
+
shutil.copy2(src_script, dest_contrib)
|
|
271
|
+
|
|
272
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const katex = require('katex');
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
|
|
4
|
+
// Store loaded contrib names to avoid reloading
|
|
5
|
+
const loadedContribs = new Set();
|
|
6
|
+
|
|
7
|
+
const rl = readline.createInterface({
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stdout,
|
|
10
|
+
terminal: false
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
rl.on('line', (line) => {
|
|
14
|
+
if (!line.trim()) return;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const data = JSON.parse(line);
|
|
18
|
+
|
|
19
|
+
if (data.type === 'setup') {
|
|
20
|
+
if (data.contribs && Array.isArray(data.contribs)) {
|
|
21
|
+
data.contribs.forEach(name => {
|
|
22
|
+
if (!loadedContribs.has(name)) {
|
|
23
|
+
try {
|
|
24
|
+
// Try to load from katex/dist/contrib/
|
|
25
|
+
require(`katex/dist/contrib/${name}.js`);
|
|
26
|
+
loadedContribs.add(name);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
// Also try without .js or direct name if it's external
|
|
29
|
+
try {
|
|
30
|
+
require(name);
|
|
31
|
+
loadedContribs.add(name);
|
|
32
|
+
} catch (e2) {
|
|
33
|
+
console.error(`Failed to load contrib: ${name}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return; // No response needed for setup
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (data.type === 'render' || !data.type) {
|
|
43
|
+
const { latex, displayMode, options } = data;
|
|
44
|
+
|
|
45
|
+
const html = katex.renderToString(latex, {
|
|
46
|
+
displayMode: displayMode || false,
|
|
47
|
+
throwOnError: false,
|
|
48
|
+
...options
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
process.stdout.write(JSON.stringify({ status: 'success', html }) + '\n');
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
process.stdout.write(JSON.stringify({ status: 'error', message: err.message }) + '\n');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.error('KaTeX SSR Renderer started');
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mkdocs-katex-ssr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A MkDocs plugin for server-side rendering of KaTeX math.
|
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: mkdocs>=1.1.0
|
|
9
|
+
Requires-Dist: beautifulsoup4>=4.9.0
|
|
10
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
mkdocs_katex_ssr/plugin.py
|
|
5
|
+
mkdocs_katex_ssr/renderer.js
|
|
6
|
+
mkdocs_katex_ssr.egg-info/PKG-INFO
|
|
7
|
+
mkdocs_katex_ssr.egg-info/SOURCES.txt
|
|
8
|
+
mkdocs_katex_ssr.egg-info/dependency_links.txt
|
|
9
|
+
mkdocs_katex_ssr.egg-info/entry_points.txt
|
|
10
|
+
mkdocs_katex_ssr.egg-info/requires.txt
|
|
11
|
+
mkdocs_katex_ssr.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mkdocs_katex_ssr
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mkdocs-katex-ssr"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A MkDocs plugin for server-side rendering of KaTeX math."
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "Your Name", email = "your.email@example.com"}
|
|
11
|
+
]
|
|
12
|
+
license = {text = "MIT"}
|
|
13
|
+
dependencies = [
|
|
14
|
+
"mkdocs>=1.1.0",
|
|
15
|
+
"beautifulsoup4>=4.9.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.entry-points."mkdocs.plugins"]
|
|
19
|
+
katex-ssr = "mkdocs_katex_ssr.plugin:KatexSsrPlugin"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
packages = ["mkdocs_katex_ssr"]
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.package-data]
|
|
25
|
+
mkdocs_katex_ssr = ["renderer.js"]
|