openmd 1.3.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.
- openmd-1.3.0/LICENSE +21 -0
- openmd-1.3.0/PKG-INFO +163 -0
- openmd-1.3.0/README.md +137 -0
- openmd-1.3.0/openmd.egg-info/PKG-INFO +163 -0
- openmd-1.3.0/openmd.egg-info/SOURCES.txt +10 -0
- openmd-1.3.0/openmd.egg-info/dependency_links.txt +1 -0
- openmd-1.3.0/openmd.egg-info/entry_points.txt +2 -0
- openmd-1.3.0/openmd.egg-info/requires.txt +3 -0
- openmd-1.3.0/openmd.egg-info/top_level.txt +1 -0
- openmd-1.3.0/openmd.py +387 -0
- openmd-1.3.0/pyproject.toml +39 -0
- openmd-1.3.0/setup.cfg +4 -0
openmd-1.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RufusLin
|
|
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.
|
openmd-1.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openmd
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Fast Markdown previewer for macOS with GitHub-dark theme, sidebar TOC, and multi-file tabs
|
|
5
|
+
Author: RufusLin
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/RufusLin/openmd
|
|
8
|
+
Project-URL: Repository, https://github.com/RufusLin/openmd
|
|
9
|
+
Project-URL: Issues, https://github.com/RufusLin/openmd/issues
|
|
10
|
+
Keywords: markdown,preview,viewer,macos,pyside6
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: MacOS X
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: PySide6
|
|
23
|
+
Requires-Dist: Markdown
|
|
24
|
+
Requires-Dist: beautifulsoup4
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# openmd
|
|
28
|
+
|
|
29
|
+
A fast, minimal Markdown previewer for macOS with a GitHub-dark theme, collapsible sidebar TOC, live reload, Mermaid diagrams, KaTeX math, and multi-file tab support.
|
|
30
|
+
Built this for myself because who needs to fire up VS Code or Cursor just to quickly view a pretty printed Markdown file, right?🤣
|
|
31
|
+
|
|
32
|
+
   
|
|
33
|
+
|
|
34
|
+
**GitHub:** [RufusLin/openmd](https://github.com/RufusLin/openmd)
|
|
35
|
+
**Warning - Lazy Maintainer:** Really bad at reading PRs, but will pay attention to issues to fix bugs. Feel free to fork, remember to give credit, please.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Open a single file
|
|
43
|
+
openmd README.md
|
|
44
|
+
|
|
45
|
+
# Open multiple files (each in its own tab)
|
|
46
|
+
openmd doc1.md doc2.md doc3.md
|
|
47
|
+
|
|
48
|
+
# No arguments — interactive picker (choose from .md files in current directory)
|
|
49
|
+
openmd
|
|
50
|
+
|
|
51
|
+
# Glob expansion
|
|
52
|
+
openmd docs/*.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Shell aliases (optional)
|
|
56
|
+
|
|
57
|
+
Add to your `~/.zshrc` or `~/.bashrc` for quick access:
|
|
58
|
+
|
|
59
|
+
```zsh
|
|
60
|
+
# Local preview — opens in background
|
|
61
|
+
localmd() {
|
|
62
|
+
openmd "$@" >/dev/null 2>&1 &
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Remote preview via SSH (requires a 'home' SSH alias in ~/.ssh/config)
|
|
66
|
+
remotemd() {
|
|
67
|
+
local remote_path="$1"
|
|
68
|
+
local filename=$(basename "$remote_path")
|
|
69
|
+
local tmp_file="/tmp/remote_preview_${filename}.md"
|
|
70
|
+
scp "home:$remote_path" "$tmp_file" && \
|
|
71
|
+
openmd "$tmp_file" >/dev/null 2>&1 &
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
- **GitHub-dark theme** — comfortable reading in low-light environments. BUT! Change it to whatever you like in `.openmd.css`
|
|
80
|
+
- **Live reload** — the preview updates instantly when the file is saved; no manual refresh needed
|
|
81
|
+
- **Mermaid diagrams** — fenced ` ```mermaid ` blocks render automatically via CDN
|
|
82
|
+
- **KaTeX math** — Formulae? inline `$…$` and display `$$…$$` expressions render out of the box
|
|
83
|
+
- **Collapsible sidebar TOC** — hierarchical, of course; click any heading to jump to it
|
|
84
|
+
- **Multi-file tabs** — pass multiple `.md` files (even `*.md` globs) and each opens in its own tab, max 6
|
|
85
|
+
- **Interactive file picker** — run with no arguments and choose from `.md` files in the current directory via a curses-based picker — no need to copy and paste file names
|
|
86
|
+
- **Remote preview** — optional `remotemd` shell alias pulls a file from a remote host via `scp` and opens it instantly
|
|
87
|
+
- **Keyboard shortcuts** — `Esc` closes the window; arrow keys navigate the sidebar
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Requirements
|
|
92
|
+
|
|
93
|
+
- macOS (uses PySide6/Qt WebEngine)
|
|
94
|
+
- Python 3.8+
|
|
95
|
+
- [PySide6](https://pypi.org/project/PySide6/)
|
|
96
|
+
- [Markdown](https://pypi.org/project/Markdown/)
|
|
97
|
+
- [BeautifulSoup4](https://pypi.org/project/beautifulsoup4/)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Installation
|
|
102
|
+
|
|
103
|
+
### pip (recommended)
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install openmd
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
After installing, the `openmd` command is available in your shell.
|
|
110
|
+
|
|
111
|
+
### From source
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
git clone https://github.com/RufusLin/openmd.git
|
|
115
|
+
cd openmd
|
|
116
|
+
pip install -e .
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Live reload
|
|
122
|
+
|
|
123
|
+
Openmd watches the opened file for changes using Qt's `QFileSystemWatcher`. Save the file in any editor (vim, neovim, VS Code, etc.) and the preview — including the sidebar TOC — updates instantly with no manual refresh.
|
|
124
|
+
|
|
125
|
+
## Mermaid & KaTeX
|
|
126
|
+
|
|
127
|
+
Mermaid and KaTeX are loaded automatically from CDN on every render. No configuration required.
|
|
128
|
+
|
|
129
|
+
**Mermaid example:**
|
|
130
|
+
````markdown
|
|
131
|
+
```mermaid
|
|
132
|
+
graph TD
|
|
133
|
+
A[Start] --> B{Decision}
|
|
134
|
+
B -->|Yes| C[Do it]
|
|
135
|
+
B -->|No| D[Skip]
|
|
136
|
+
```
|
|
137
|
+
````
|
|
138
|
+
|
|
139
|
+
**KaTeX example:**
|
|
140
|
+
```markdown
|
|
141
|
+
Inline: $E = mc^2$
|
|
142
|
+
|
|
143
|
+
Display:
|
|
144
|
+
$$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> **Note:** Mermaid and KaTeX require an internet connection to load from CDN. Offline rendering is not currently supported (maybe never, come to think of it😉).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Keyboard shortcuts
|
|
152
|
+
|
|
153
|
+
| Key | Action |
|
|
154
|
+
|-----|--------|
|
|
155
|
+
| `Esc` | Close the preview window |
|
|
156
|
+
| `↑` / `↓` | Navigate the sidebar TOC |
|
|
157
|
+
| Click heading | Jump to that section |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
openmd-1.3.0/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# openmd
|
|
2
|
+
|
|
3
|
+
A fast, minimal Markdown previewer for macOS with a GitHub-dark theme, collapsible sidebar TOC, live reload, Mermaid diagrams, KaTeX math, and multi-file tab support.
|
|
4
|
+
Built this for myself because who needs to fire up VS Code or Cursor just to quickly view a pretty printed Markdown file, right?🤣
|
|
5
|
+
|
|
6
|
+
   
|
|
7
|
+
|
|
8
|
+
**GitHub:** [RufusLin/openmd](https://github.com/RufusLin/openmd)
|
|
9
|
+
**Warning - Lazy Maintainer:** Really bad at reading PRs, but will pay attention to issues to fix bugs. Feel free to fork, remember to give credit, please.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Open a single file
|
|
17
|
+
openmd README.md
|
|
18
|
+
|
|
19
|
+
# Open multiple files (each in its own tab)
|
|
20
|
+
openmd doc1.md doc2.md doc3.md
|
|
21
|
+
|
|
22
|
+
# No arguments — interactive picker (choose from .md files in current directory)
|
|
23
|
+
openmd
|
|
24
|
+
|
|
25
|
+
# Glob expansion
|
|
26
|
+
openmd docs/*.md
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Shell aliases (optional)
|
|
30
|
+
|
|
31
|
+
Add to your `~/.zshrc` or `~/.bashrc` for quick access:
|
|
32
|
+
|
|
33
|
+
```zsh
|
|
34
|
+
# Local preview — opens in background
|
|
35
|
+
localmd() {
|
|
36
|
+
openmd "$@" >/dev/null 2>&1 &
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Remote preview via SSH (requires a 'home' SSH alias in ~/.ssh/config)
|
|
40
|
+
remotemd() {
|
|
41
|
+
local remote_path="$1"
|
|
42
|
+
local filename=$(basename "$remote_path")
|
|
43
|
+
local tmp_file="/tmp/remote_preview_${filename}.md"
|
|
44
|
+
scp "home:$remote_path" "$tmp_file" && \
|
|
45
|
+
openmd "$tmp_file" >/dev/null 2>&1 &
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- **GitHub-dark theme** — comfortable reading in low-light environments. BUT! Change it to whatever you like in `.openmd.css`
|
|
54
|
+
- **Live reload** — the preview updates instantly when the file is saved; no manual refresh needed
|
|
55
|
+
- **Mermaid diagrams** — fenced ` ```mermaid ` blocks render automatically via CDN
|
|
56
|
+
- **KaTeX math** — Formulae? inline `$…$` and display `$$…$$` expressions render out of the box
|
|
57
|
+
- **Collapsible sidebar TOC** — hierarchical, of course; click any heading to jump to it
|
|
58
|
+
- **Multi-file tabs** — pass multiple `.md` files (even `*.md` globs) and each opens in its own tab, max 6
|
|
59
|
+
- **Interactive file picker** — run with no arguments and choose from `.md` files in the current directory via a curses-based picker — no need to copy and paste file names
|
|
60
|
+
- **Remote preview** — optional `remotemd` shell alias pulls a file from a remote host via `scp` and opens it instantly
|
|
61
|
+
- **Keyboard shortcuts** — `Esc` closes the window; arrow keys navigate the sidebar
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Requirements
|
|
66
|
+
|
|
67
|
+
- macOS (uses PySide6/Qt WebEngine)
|
|
68
|
+
- Python 3.8+
|
|
69
|
+
- [PySide6](https://pypi.org/project/PySide6/)
|
|
70
|
+
- [Markdown](https://pypi.org/project/Markdown/)
|
|
71
|
+
- [BeautifulSoup4](https://pypi.org/project/beautifulsoup4/)
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
### pip (recommended)
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install openmd
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
After installing, the `openmd` command is available in your shell.
|
|
84
|
+
|
|
85
|
+
### From source
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/RufusLin/openmd.git
|
|
89
|
+
cd openmd
|
|
90
|
+
pip install -e .
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Live reload
|
|
96
|
+
|
|
97
|
+
Openmd watches the opened file for changes using Qt's `QFileSystemWatcher`. Save the file in any editor (vim, neovim, VS Code, etc.) and the preview — including the sidebar TOC — updates instantly with no manual refresh.
|
|
98
|
+
|
|
99
|
+
## Mermaid & KaTeX
|
|
100
|
+
|
|
101
|
+
Mermaid and KaTeX are loaded automatically from CDN on every render. No configuration required.
|
|
102
|
+
|
|
103
|
+
**Mermaid example:**
|
|
104
|
+
````markdown
|
|
105
|
+
```mermaid
|
|
106
|
+
graph TD
|
|
107
|
+
A[Start] --> B{Decision}
|
|
108
|
+
B -->|Yes| C[Do it]
|
|
109
|
+
B -->|No| D[Skip]
|
|
110
|
+
```
|
|
111
|
+
````
|
|
112
|
+
|
|
113
|
+
**KaTeX example:**
|
|
114
|
+
```markdown
|
|
115
|
+
Inline: $E = mc^2$
|
|
116
|
+
|
|
117
|
+
Display:
|
|
118
|
+
$$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> **Note:** Mermaid and KaTeX require an internet connection to load from CDN. Offline rendering is not currently supported (maybe never, come to think of it😉).
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Keyboard shortcuts
|
|
126
|
+
|
|
127
|
+
| Key | Action |
|
|
128
|
+
|-----|--------|
|
|
129
|
+
| `Esc` | Close the preview window |
|
|
130
|
+
| `↑` / `↓` | Navigate the sidebar TOC |
|
|
131
|
+
| Click heading | Jump to that section |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openmd
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Fast Markdown previewer for macOS with GitHub-dark theme, sidebar TOC, and multi-file tabs
|
|
5
|
+
Author: RufusLin
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/RufusLin/openmd
|
|
8
|
+
Project-URL: Repository, https://github.com/RufusLin/openmd
|
|
9
|
+
Project-URL: Issues, https://github.com/RufusLin/openmd/issues
|
|
10
|
+
Keywords: markdown,preview,viewer,macos,pyside6
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: MacOS X
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: PySide6
|
|
23
|
+
Requires-Dist: Markdown
|
|
24
|
+
Requires-Dist: beautifulsoup4
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# openmd
|
|
28
|
+
|
|
29
|
+
A fast, minimal Markdown previewer for macOS with a GitHub-dark theme, collapsible sidebar TOC, live reload, Mermaid diagrams, KaTeX math, and multi-file tab support.
|
|
30
|
+
Built this for myself because who needs to fire up VS Code or Cursor just to quickly view a pretty printed Markdown file, right?🤣
|
|
31
|
+
|
|
32
|
+
   
|
|
33
|
+
|
|
34
|
+
**GitHub:** [RufusLin/openmd](https://github.com/RufusLin/openmd)
|
|
35
|
+
**Warning - Lazy Maintainer:** Really bad at reading PRs, but will pay attention to issues to fix bugs. Feel free to fork, remember to give credit, please.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Open a single file
|
|
43
|
+
openmd README.md
|
|
44
|
+
|
|
45
|
+
# Open multiple files (each in its own tab)
|
|
46
|
+
openmd doc1.md doc2.md doc3.md
|
|
47
|
+
|
|
48
|
+
# No arguments — interactive picker (choose from .md files in current directory)
|
|
49
|
+
openmd
|
|
50
|
+
|
|
51
|
+
# Glob expansion
|
|
52
|
+
openmd docs/*.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Shell aliases (optional)
|
|
56
|
+
|
|
57
|
+
Add to your `~/.zshrc` or `~/.bashrc` for quick access:
|
|
58
|
+
|
|
59
|
+
```zsh
|
|
60
|
+
# Local preview — opens in background
|
|
61
|
+
localmd() {
|
|
62
|
+
openmd "$@" >/dev/null 2>&1 &
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Remote preview via SSH (requires a 'home' SSH alias in ~/.ssh/config)
|
|
66
|
+
remotemd() {
|
|
67
|
+
local remote_path="$1"
|
|
68
|
+
local filename=$(basename "$remote_path")
|
|
69
|
+
local tmp_file="/tmp/remote_preview_${filename}.md"
|
|
70
|
+
scp "home:$remote_path" "$tmp_file" && \
|
|
71
|
+
openmd "$tmp_file" >/dev/null 2>&1 &
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
- **GitHub-dark theme** — comfortable reading in low-light environments. BUT! Change it to whatever you like in `.openmd.css`
|
|
80
|
+
- **Live reload** — the preview updates instantly when the file is saved; no manual refresh needed
|
|
81
|
+
- **Mermaid diagrams** — fenced ` ```mermaid ` blocks render automatically via CDN
|
|
82
|
+
- **KaTeX math** — Formulae? inline `$…$` and display `$$…$$` expressions render out of the box
|
|
83
|
+
- **Collapsible sidebar TOC** — hierarchical, of course; click any heading to jump to it
|
|
84
|
+
- **Multi-file tabs** — pass multiple `.md` files (even `*.md` globs) and each opens in its own tab, max 6
|
|
85
|
+
- **Interactive file picker** — run with no arguments and choose from `.md` files in the current directory via a curses-based picker — no need to copy and paste file names
|
|
86
|
+
- **Remote preview** — optional `remotemd` shell alias pulls a file from a remote host via `scp` and opens it instantly
|
|
87
|
+
- **Keyboard shortcuts** — `Esc` closes the window; arrow keys navigate the sidebar
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Requirements
|
|
92
|
+
|
|
93
|
+
- macOS (uses PySide6/Qt WebEngine)
|
|
94
|
+
- Python 3.8+
|
|
95
|
+
- [PySide6](https://pypi.org/project/PySide6/)
|
|
96
|
+
- [Markdown](https://pypi.org/project/Markdown/)
|
|
97
|
+
- [BeautifulSoup4](https://pypi.org/project/beautifulsoup4/)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Installation
|
|
102
|
+
|
|
103
|
+
### pip (recommended)
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install openmd
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
After installing, the `openmd` command is available in your shell.
|
|
110
|
+
|
|
111
|
+
### From source
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
git clone https://github.com/RufusLin/openmd.git
|
|
115
|
+
cd openmd
|
|
116
|
+
pip install -e .
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Live reload
|
|
122
|
+
|
|
123
|
+
Openmd watches the opened file for changes using Qt's `QFileSystemWatcher`. Save the file in any editor (vim, neovim, VS Code, etc.) and the preview — including the sidebar TOC — updates instantly with no manual refresh.
|
|
124
|
+
|
|
125
|
+
## Mermaid & KaTeX
|
|
126
|
+
|
|
127
|
+
Mermaid and KaTeX are loaded automatically from CDN on every render. No configuration required.
|
|
128
|
+
|
|
129
|
+
**Mermaid example:**
|
|
130
|
+
````markdown
|
|
131
|
+
```mermaid
|
|
132
|
+
graph TD
|
|
133
|
+
A[Start] --> B{Decision}
|
|
134
|
+
B -->|Yes| C[Do it]
|
|
135
|
+
B -->|No| D[Skip]
|
|
136
|
+
```
|
|
137
|
+
````
|
|
138
|
+
|
|
139
|
+
**KaTeX example:**
|
|
140
|
+
```markdown
|
|
141
|
+
Inline: $E = mc^2$
|
|
142
|
+
|
|
143
|
+
Display:
|
|
144
|
+
$$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> **Note:** Mermaid and KaTeX require an internet connection to load from CDN. Offline rendering is not currently supported (maybe never, come to think of it😉).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Keyboard shortcuts
|
|
152
|
+
|
|
153
|
+
| Key | Action |
|
|
154
|
+
|-----|--------|
|
|
155
|
+
| `Esc` | Close the preview window |
|
|
156
|
+
| `↑` / `↓` | Navigate the sidebar TOC |
|
|
157
|
+
| Click heading | Jump to that section |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openmd
|
openmd-1.3.0/openmd.py
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Version: 1.3.0
|
|
3
|
+
# Added hierarchical QTreeWidget TOC sidebar (H1→top, H2→children, H3→grandchildren).
|
|
4
|
+
# Tabs are intentionally preserved — DO NOT remove the QTabWidget multi-file tab view.
|
|
5
|
+
# openmd.py - Simple Markdown previewer with sidebar TOC
|
|
6
|
+
# -------------------------------------------------
|
|
7
|
+
# This script is invoked by shell aliases defined in ~/.zshrc:
|
|
8
|
+
#
|
|
9
|
+
# localmd() {
|
|
10
|
+
# $MD_VIEWER_PY $MD_VIEWER_SCRIPT "$@" >/dev/null 2>&1 &
|
|
11
|
+
# }
|
|
12
|
+
#
|
|
13
|
+
# remotemd() {
|
|
14
|
+
# local remote_path="$1"
|
|
15
|
+
# local filename=$(basename "$remote_path")
|
|
16
|
+
# local tmp_file="/tmp/remote_preview_${filename}.md"
|
|
17
|
+
#
|
|
18
|
+
# # Pull via the 'home' alias, then launch
|
|
19
|
+
# scp "home:$remote_path" "$tmp_file" && \
|
|
20
|
+
# $MD_VIEWER_PY $MD_VIEWER_SCRIPT "$tmp_file" >/dev/null 2>&1 &
|
|
21
|
+
# }
|
|
22
|
+
#
|
|
23
|
+
# The script itself only opens a local file path; remote handling is performed by the
|
|
24
|
+
# `remotemd` alias which copies the file via SSH (scp) to a temporary location and
|
|
25
|
+
# then runs this script on that copy. No glob expansion or remote file fetching is
|
|
26
|
+
# performed inside the Python code.
|
|
27
|
+
# -------------------------------------------------
|
|
28
|
+
|
|
29
|
+
import sys, os, markdown
|
|
30
|
+
|
|
31
|
+
# Try to import curses for file picker; fallback to simple list
|
|
32
|
+
try:
|
|
33
|
+
import curses
|
|
34
|
+
except Exception:
|
|
35
|
+
curses = None
|
|
36
|
+
|
|
37
|
+
from PySide6.QtWidgets import (
|
|
38
|
+
QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
|
|
39
|
+
QSplitter, QTreeWidget, QTreeWidgetItem,
|
|
40
|
+
)
|
|
41
|
+
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
42
|
+
from PySide6.QtCore import QSize, Qt, QFileSystemWatcher
|
|
43
|
+
from bs4 import BeautifulSoup
|
|
44
|
+
|
|
45
|
+
# GitHub-Modern Dark Theme
|
|
46
|
+
CSS = """body {
|
|
47
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
48
|
+
line-height: 1.6; color: #c9d1d9; max-width: 900px; margin: auto; padding: 2rem; background-color: #0d1117;
|
|
49
|
+
}
|
|
50
|
+
pre {
|
|
51
|
+
background-color: #161b22; padding: 16px; border-radius: 6px; border: 1px solid #30363d;
|
|
52
|
+
overflow: auto; font-family: "SFMono-Regular", Consolas, monospace;
|
|
53
|
+
}
|
|
54
|
+
code { background-color: rgba(110,118,129,0.4); padding: 0.2em 0.4em; border-radius: 6px; font-size: 85%; }
|
|
55
|
+
table { border-collapse: collapse; width: 100%; margin: 24px 0; border: 1px solid #30363d; }
|
|
56
|
+
table th, table td { border: 1px solid #30363d; padding: 8px 12px; }
|
|
57
|
+
table tr:nth-child(even) { background-color: #161b22; }
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# Sidebar CSS injected into the preview HTML
|
|
61
|
+
SIDEBAR_CSS = """
|
|
62
|
+
QTreeWidget {
|
|
63
|
+
background: #161b22;
|
|
64
|
+
color: #c9d1d9;
|
|
65
|
+
border-right: 1px solid #30363d;
|
|
66
|
+
font-size: 13px;
|
|
67
|
+
}
|
|
68
|
+
QTreeWidget::item:hover { background: #212730; }
|
|
69
|
+
QTreeWidget::item:selected { background: #1f6feb; color: #ffffff; }
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Per-file preview window: sidebar TOC (QTreeWidget) + QWebEngineView
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# NOTE: This class renders a single markdown file with a collapsible sidebar.
|
|
77
|
+
# The outer QTabWidget (in __main__) wraps multiple FilePreviewWidget instances
|
|
78
|
+
# so that multi-file tab support is preserved. DO NOT collapse these into one.
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
class FilePreviewWidget(QWidget):
|
|
82
|
+
"""A single-file preview pane: left sidebar TOC + right HTML view."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, file_path: str):
|
|
85
|
+
super().__init__()
|
|
86
|
+
self.file_path = file_path
|
|
87
|
+
|
|
88
|
+
# KaTeX CDN snippets — loaded synchronously in the HTML head/body
|
|
89
|
+
# KaTeX renders fine via inline scripts in Qt WebEngine setHtml()
|
|
90
|
+
self._katex_css = '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css">'
|
|
91
|
+
self._katex_script = (
|
|
92
|
+
'<script src="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.js"></script>\n'
|
|
93
|
+
'<script src="https://cdn.jsdelivr.net/npm/katex@0.16/dist/contrib/auto-render.min.js"></script>\n'
|
|
94
|
+
'<script>'
|
|
95
|
+
'document.addEventListener("DOMContentLoaded",function(){'
|
|
96
|
+
'if(window.renderMathInElement)renderMathInElement(document.body,{'
|
|
97
|
+
'delimiters:[{left:"$$",right:"$$",display:true},{left:"$",right:"$",display:false}]'
|
|
98
|
+
'});});'
|
|
99
|
+
'</script>'
|
|
100
|
+
)
|
|
101
|
+
# Mermaid: Qt WebEngine setHtml() does not reliably fire window.onload or
|
|
102
|
+
# DOMContentLoaded for CDN scripts. Instead we load the script in the HTML
|
|
103
|
+
# and trigger mermaid.run() via page().runJavaScript() on the loadFinished signal.
|
|
104
|
+
self._mermaid_script = '<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>'
|
|
105
|
+
|
|
106
|
+
# --- Load and render markdown ---
|
|
107
|
+
html_body, toc_html = self._render_markdown(file_path)
|
|
108
|
+
full_html = self._build_html(html_body)
|
|
109
|
+
|
|
110
|
+
# --- Sidebar (QTreeWidget) ---
|
|
111
|
+
# Collapsible TOC: H1 → top-level, H2 → children, H3 → grandchildren.
|
|
112
|
+
# Arrow keys navigate; Enter jumps to the heading; Esc closes the window.
|
|
113
|
+
# DO NOT remove this sidebar — it is the primary navigation feature.
|
|
114
|
+
self.sidebar = QTreeWidget()
|
|
115
|
+
self.sidebar.setHeaderHidden(True)
|
|
116
|
+
self.sidebar.setIndentation(16)
|
|
117
|
+
self.sidebar.setStyleSheet(SIDEBAR_CSS)
|
|
118
|
+
self.sidebar.setFixedWidth(220)
|
|
119
|
+
self.sidebar.itemClicked.connect(self._jump_to_section)
|
|
120
|
+
self._populate_sidebar(toc_html)
|
|
121
|
+
|
|
122
|
+
# --- Web view ---
|
|
123
|
+
self.view = QWebEngineView()
|
|
124
|
+
# Connect loadFinished to trigger Mermaid rendering after the page and CDN
|
|
125
|
+
# scripts have fully loaded. This is the only reliable way to run Mermaid
|
|
126
|
+
# in Qt WebEngine when content is set via setHtml().
|
|
127
|
+
self.view.loadFinished.connect(self._on_load_finished)
|
|
128
|
+
self.view.setHtml(full_html)
|
|
129
|
+
|
|
130
|
+
# --- Live reload: single watcher per file, connected to _reload ---
|
|
131
|
+
# DO NOT create a second QFileSystemWatcher — one per FilePreviewWidget only.
|
|
132
|
+
self.watcher = QFileSystemWatcher(self)
|
|
133
|
+
self.watcher.addPath(self.file_path)
|
|
134
|
+
self.watcher.fileChanged.connect(self._reload)
|
|
135
|
+
|
|
136
|
+
# --- Layout: sidebar left, preview right ---
|
|
137
|
+
splitter = QSplitter(Qt.Horizontal)
|
|
138
|
+
splitter.addWidget(self.sidebar)
|
|
139
|
+
splitter.addWidget(self.view)
|
|
140
|
+
splitter.setStretchFactor(0, 0) # sidebar: fixed
|
|
141
|
+
splitter.setStretchFactor(1, 1) # preview: stretches
|
|
142
|
+
|
|
143
|
+
layout = QVBoxLayout(self)
|
|
144
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
145
|
+
layout.addWidget(splitter)
|
|
146
|
+
|
|
147
|
+
def _populate_sidebar(self, toc_html: str):
|
|
148
|
+
"""Parse the TOC div from markdown-with-toc output and fill the QTreeWidget."""
|
|
149
|
+
if not toc_html:
|
|
150
|
+
return
|
|
151
|
+
soup = BeautifulSoup(toc_html, "html.parser")
|
|
152
|
+
toc_div = soup.find("div", class_="toc")
|
|
153
|
+
if not toc_div:
|
|
154
|
+
return
|
|
155
|
+
# Top-level <ul> inside the TOC div
|
|
156
|
+
top_ul = toc_div.find("ul", recursive=False)
|
|
157
|
+
if top_ul:
|
|
158
|
+
for li in top_ul.find_all("li", recursive=False):
|
|
159
|
+
self._add_toc_item(li, parent_item=None)
|
|
160
|
+
self.sidebar.expandAll()
|
|
161
|
+
|
|
162
|
+
def _add_toc_item(self, node, parent_item):
|
|
163
|
+
"""Recursively add a <li> node (and its nested <ul> children) to the sidebar."""
|
|
164
|
+
anchor_tag = node.find("a")
|
|
165
|
+
if not anchor_tag:
|
|
166
|
+
return
|
|
167
|
+
title = anchor_tag.get_text()
|
|
168
|
+
anchor_id = anchor_tag.get("name") or anchor_tag.get("href", "").lstrip("#")
|
|
169
|
+
if not anchor_id:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
item = QTreeWidgetItem([title])
|
|
173
|
+
item.setData(0, Qt.UserRole, anchor_id)
|
|
174
|
+
|
|
175
|
+
if parent_item is None:
|
|
176
|
+
self.sidebar.addTopLevelItem(item)
|
|
177
|
+
else:
|
|
178
|
+
parent_item.addChild(item)
|
|
179
|
+
|
|
180
|
+
# Recurse into nested <ul> for sub-headings
|
|
181
|
+
for child_ul in node.find_all("ul", recursive=False):
|
|
182
|
+
for sub_li in child_ul.find_all("li", recursive=False):
|
|
183
|
+
self._add_toc_item(sub_li, item)
|
|
184
|
+
|
|
185
|
+
def _render_markdown(self, file_path: str):
|
|
186
|
+
"""Read and render a markdown file; return (html_body, toc_html).
|
|
187
|
+
|
|
188
|
+
Uses the Markdown class directly so we can access md.toc, which is the
|
|
189
|
+
only reliable way to get the TOC HTML — the markdown.markdown() one-liner
|
|
190
|
+
does NOT generate the <div class='toc'> block.
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
with open(file_path, 'r', encoding='utf-8') as fh:
|
|
194
|
+
raw = fh.read()
|
|
195
|
+
md = markdown.Markdown(
|
|
196
|
+
extensions=['toc', 'extra', 'sane_lists'],
|
|
197
|
+
extension_configs={'toc': {'permalink': False, 'anchorlink': True}},
|
|
198
|
+
)
|
|
199
|
+
html_body = md.convert(raw)
|
|
200
|
+
toc_html = md.toc # e.g. '<div class="toc"><ul>...</ul></div>'
|
|
201
|
+
# Convert markdown-rendered mermaid code blocks to the format Mermaid v10 expects.
|
|
202
|
+
# The markdown library renders ```mermaid as <pre><code class="language-mermaid">...
|
|
203
|
+
# but Mermaid.run() looks for <pre class="mermaid">...</pre>.
|
|
204
|
+
# Use BeautifulSoup to do this safely without affecting other code blocks.
|
|
205
|
+
soup = BeautifulSoup(html_body, 'html.parser')
|
|
206
|
+
for code_tag in soup.find_all('code', class_='language-mermaid'):
|
|
207
|
+
pre_tag = code_tag.parent
|
|
208
|
+
if pre_tag and pre_tag.name == 'pre':
|
|
209
|
+
# Replace <pre><code class="language-mermaid">...</code></pre>
|
|
210
|
+
# with <pre class="mermaid">...</pre>
|
|
211
|
+
new_pre = soup.new_tag('pre', **{'class': 'mermaid'})
|
|
212
|
+
new_pre.string = code_tag.get_text()
|
|
213
|
+
pre_tag.replace_with(new_pre)
|
|
214
|
+
html_body = str(soup)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
html_body = f"<h1>Error loading {os.path.basename(file_path)}</h1><p>{type(e).__name__}: {e}</p>"
|
|
217
|
+
toc_html = ""
|
|
218
|
+
return html_body, toc_html
|
|
219
|
+
|
|
220
|
+
def _build_html(self, html_body: str) -> str:
|
|
221
|
+
"""Wrap rendered markdown body with CSS, Mermaid, and KaTeX."""
|
|
222
|
+
return (
|
|
223
|
+
f"<!DOCTYPE html><html><head><meta charset='utf-8'><style>{CSS}</style>{self._katex_css}</head>"
|
|
224
|
+
f"<body>{html_body}{self._mermaid_script}{self._katex_script}</body></html>"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def _on_load_finished(self, ok: bool):
|
|
228
|
+
"""Called by loadFinished signal after setHtml() completes.
|
|
229
|
+
|
|
230
|
+
Triggers Mermaid rendering via runJavaScript — the only reliable way
|
|
231
|
+
to run CDN-loaded Mermaid in Qt WebEngine after setHtml().
|
|
232
|
+
"""
|
|
233
|
+
if ok:
|
|
234
|
+
self.view.page().runJavaScript(
|
|
235
|
+
"if(window.mermaid){"
|
|
236
|
+
" mermaid.initialize({startOnLoad:false,theme:'dark'});"
|
|
237
|
+
" mermaid.run();"
|
|
238
|
+
"}"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def _reload(self, _path: str = ""):
|
|
242
|
+
"""Called by QFileSystemWatcher when the watched file changes."""
|
|
243
|
+
# Re-add path: some editors (vim, neovim) replace the file atomically,
|
|
244
|
+
# which removes it from the watcher. Re-adding ensures continued watching.
|
|
245
|
+
self.watcher.addPath(self.file_path)
|
|
246
|
+
html_body, toc_html = self._render_markdown(self.file_path)
|
|
247
|
+
self.sidebar.clear()
|
|
248
|
+
self._populate_sidebar(toc_html)
|
|
249
|
+
self.view.setHtml(self._build_html(html_body))
|
|
250
|
+
|
|
251
|
+
def _jump_to_section(self, item: QTreeWidgetItem):
|
|
252
|
+
"""Scroll the web view to the heading whose anchor matches the clicked item."""
|
|
253
|
+
anchor = item.data(0, Qt.UserRole)
|
|
254
|
+
if anchor:
|
|
255
|
+
self.view.page().runJavaScript(
|
|
256
|
+
f"var el = document.getElementById('{anchor}'); if (el) el.scrollIntoView();"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
# Top-level window: wraps one or more FilePreviewWidget tabs
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
# IMPORTANT: The QTabWidget multi-file tab view is intentional and must be
|
|
264
|
+
# preserved. When multiple .md files are passed on the command line, each
|
|
265
|
+
# opens in its own tab (with its own sidebar). DO NOT collapse to single-file.
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
class MDPreviewWindow(QMainWindow):
|
|
269
|
+
def __init__(self, tab_widget: QTabWidget):
|
|
270
|
+
super().__init__()
|
|
271
|
+
self.setCentralWidget(tab_widget)
|
|
272
|
+
self.setWindowTitle("MD Preview")
|
|
273
|
+
self.resize(QSize(1200, 1000))
|
|
274
|
+
self.tab_widget = tab_widget
|
|
275
|
+
|
|
276
|
+
def keyPressEvent(self, event):
|
|
277
|
+
if event.key() == Qt.Key_Escape:
|
|
278
|
+
QApplication.quit()
|
|
279
|
+
else:
|
|
280
|
+
super().keyPressEvent(event)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
# Helpers
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
def is_markdown(path: str) -> bool:
|
|
288
|
+
return path.lower().endswith('.md')
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def pick_file_curses() -> str:
|
|
292
|
+
"""Return a selected .md file from the current directory using curses."""
|
|
293
|
+
md_files = [f for f in os.listdir('.') if is_markdown(f) and os.path.isfile(f)]
|
|
294
|
+
md_files.sort()
|
|
295
|
+
if not md_files:
|
|
296
|
+
sys.exit("No markdown files found in the current directory.")
|
|
297
|
+
selected = 0
|
|
298
|
+
if curses:
|
|
299
|
+
def draw(stdscr):
|
|
300
|
+
nonlocal selected
|
|
301
|
+
stdscr.keypad(True)
|
|
302
|
+
curses.curs_set(0) # hide cursor
|
|
303
|
+
while True:
|
|
304
|
+
stdscr.clear()
|
|
305
|
+
stdscr.addstr(0, 0, "Select a Markdown file (\u2191\u2193 navigate, Enter select, Esc quit)")
|
|
306
|
+
for idx, f in enumerate(md_files):
|
|
307
|
+
y = 2 + idx
|
|
308
|
+
if y >= curses.LINES - 1:
|
|
309
|
+
break
|
|
310
|
+
stdscr.addstr(y, 2, f"{'>' if idx == selected else ' '} {f}")
|
|
311
|
+
stdscr.refresh()
|
|
312
|
+
key = stdscr.getch()
|
|
313
|
+
if key == curses.KEY_UP and selected > 0:
|
|
314
|
+
selected -= 1
|
|
315
|
+
elif key == curses.KEY_DOWN and selected < len(md_files) - 1:
|
|
316
|
+
selected += 1
|
|
317
|
+
elif key in (curses.KEY_ENTER, 10, 13):
|
|
318
|
+
return
|
|
319
|
+
elif key == 27: # Esc
|
|
320
|
+
sys.exit(0)
|
|
321
|
+
try:
|
|
322
|
+
curses.wrapper(draw)
|
|
323
|
+
except SystemExit:
|
|
324
|
+
raise
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
327
|
+
else:
|
|
328
|
+
for i, f in enumerate(md_files, 1):
|
|
329
|
+
print(f"{i}. {f}")
|
|
330
|
+
choice = input("Select number (or ENTER to cancel): ").strip()
|
|
331
|
+
if not choice:
|
|
332
|
+
sys.exit(0)
|
|
333
|
+
try:
|
|
334
|
+
idx = int(choice) - 1
|
|
335
|
+
if 0 <= idx < len(md_files):
|
|
336
|
+
selected = idx
|
|
337
|
+
else:
|
|
338
|
+
sys.exit(0)
|
|
339
|
+
except ValueError:
|
|
340
|
+
sys.exit(0)
|
|
341
|
+
return md_files[selected]
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# ---------------------------------------------------------------------------
|
|
345
|
+
# Entry point
|
|
346
|
+
# ---------------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
def main():
|
|
349
|
+
"""Entry point — used both by direct invocation and by the pip console script."""
|
|
350
|
+
# No arguments → interactive curses picker
|
|
351
|
+
if len(sys.argv) < 2:
|
|
352
|
+
file_path = pick_file_curses()
|
|
353
|
+
md_files = [file_path]
|
|
354
|
+
else:
|
|
355
|
+
# Collect all arguments (shell may have expanded globs)
|
|
356
|
+
files = sys.argv[1:]
|
|
357
|
+
# Filter to markdown files only
|
|
358
|
+
md_files = [f for f in files if is_markdown(f)]
|
|
359
|
+
if not md_files:
|
|
360
|
+
sys.exit("No markdown files matched the given pattern(s).")
|
|
361
|
+
# Limit to 6 files (one tab per file)
|
|
362
|
+
if len(md_files) > 6:
|
|
363
|
+
sys.stderr.write("Warning: more than 6 files supplied; showing first 6.\n")
|
|
364
|
+
md_files = md_files[:6]
|
|
365
|
+
|
|
366
|
+
# -----------------------------------------------------------------
|
|
367
|
+
# Build the tabbed window.
|
|
368
|
+
# Each file gets its own tab containing a FilePreviewWidget (sidebar
|
|
369
|
+
# TOC + web view). The QTabWidget is intentional — DO NOT remove it.
|
|
370
|
+
# Wrapped in a top-level try/except to surface Qt init failures.
|
|
371
|
+
# -----------------------------------------------------------------
|
|
372
|
+
try:
|
|
373
|
+
app = QApplication(sys.argv)
|
|
374
|
+
tab_widget = QTabWidget()
|
|
375
|
+
for f in md_files:
|
|
376
|
+
widget = FilePreviewWidget(f)
|
|
377
|
+
tab_widget.addTab(widget, os.path.basename(f))
|
|
378
|
+
window = MDPreviewWindow(tab_widget)
|
|
379
|
+
window.show()
|
|
380
|
+
sys.exit(app.exec())
|
|
381
|
+
except Exception as e:
|
|
382
|
+
sys.stderr.write(f"Fatal error while building the preview window: {type(e).__name__}: {e}\n")
|
|
383
|
+
sys.exit(1)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
if __name__ == "__main__":
|
|
387
|
+
main()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openmd"
|
|
7
|
+
version = "1.3.0"
|
|
8
|
+
description = "Fast Markdown previewer for macOS with GitHub-dark theme, sidebar TOC, and multi-file tabs"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "RufusLin" }]
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"PySide6",
|
|
15
|
+
"Markdown",
|
|
16
|
+
"beautifulsoup4",
|
|
17
|
+
]
|
|
18
|
+
keywords = ["markdown", "preview", "viewer", "macos", "pyside6"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Environment :: MacOS X",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: MacOS",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Topic :: Text Processing :: Markup",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
openmd = "openmd:main"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/RufusLin/openmd"
|
|
35
|
+
Repository = "https://github.com/RufusLin/openmd"
|
|
36
|
+
Issues = "https://github.com/RufusLin/openmd/issues"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
py-modules = ["openmd"]
|
openmd-1.3.0/setup.cfg
ADDED