@commte/mdbrowse 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ja.md +178 -0
- package/README.md +178 -0
- package/assets/head.html +295 -0
- package/bin/mdbrowse +108 -0
- package/package.json +42 -0
- package/sample.md +97 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 commte
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ja.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
[English](README.md) | 日本語
|
|
2
|
+
|
|
3
|
+
# mdbrowse
|
|
4
|
+
|
|
5
|
+
編集中の Markdown を、自分の CSS でブラウザに表示する。シェルコマンドを実行できるエディタなら何でも使える
|
|
6
|
+
|
|
7
|
+
サーバーもポートも常駐プロセスも要らない。ショートカットを押すと現在のファイルが固定パスの HTML に変換され、開いたままのブラウザのタブがそれを拾う
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## 何のために作ったか
|
|
12
|
+
|
|
13
|
+
エディタ内蔵のプレビューは、エディタの中で描画されるので見た目に手を入れられない。サーバーを立てるツールなら自由になるが、常駐とポートを引き受けることになる。これはどちらでもない。pandoc の上に乗せたシェルスクリプトと、スタイルを収めた HTML が1枚あるだけ
|
|
14
|
+
|
|
15
|
+
出力先が常に同じなので、ファイルを切り替えてもタブは増えない。すでに開いているタブの中身が入れ替わる
|
|
16
|
+
|
|
17
|
+
## 必要なもの
|
|
18
|
+
|
|
19
|
+
- [pandoc](https://pandoc.org/) macOS は `brew install pandoc`、Debian/Ubuntu は `apt install pandoc`
|
|
20
|
+
- 現在開いているファイルのパスを渡してシェルコマンドを実行できるエディタ
|
|
21
|
+
|
|
22
|
+
## インストール
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install -g @commte/mdbrowse
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
インストールせずに試す場合
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npx @commte/mdbrowse file.md
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
スタイルシートはパッケージに同梱されている。自分で編集したい場合は `~/.config/mdbrowse/head.html` に取り出す。以後はそちらが同梱版より優先される
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
mdbrowse --eject
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary>ソースからインストールする場合</summary>
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
git clone https://github.com/commte/mdbrowse.git
|
|
45
|
+
cd mdbrowse
|
|
46
|
+
./install.sh
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`mdbrowse` が `~/.local/bin` に、スタイルシートが `~/.config/mdbrowse/head.html` に入る。再インストールしても自分で編集したスタイルシートは上書きされない。配布時の状態に戻したいときは `--force` を付ける
|
|
50
|
+
|
|
51
|
+
</details>
|
|
52
|
+
|
|
53
|
+
プレビュー用のタブを1回だけ開いて、そのままにしておく
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
mdbrowse --open
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 使い方
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
mdbrowse file.md # 変換する(出力 HTML を上書きする)
|
|
63
|
+
mdbrowse --open # プレビュー用のタブを開く
|
|
64
|
+
mdbrowse --path # 出力先の HTML のパスを表示する
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| 環境変数 | 既定値 | |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `MDBROWSE_OUT` | `/tmp/mdbrowse.html` | 出力先の HTML |
|
|
70
|
+
| `MDBROWSE_HEAD` | `~/.config/mdbrowse/head.html` | スタイルとブラウザ側のスクリプト |
|
|
71
|
+
|
|
72
|
+
## ページ内の操作
|
|
73
|
+
|
|
74
|
+
右上に小さなバーが出る。テーマ、文字サイズ、本文幅、目次の表示を切り替えられる。選択はブラウザに保存されるので、再読み込みしても次に開くファイルにも引き継がれる。ディスクには何も書かず、サーバーも使わない
|
|
75
|
+
|
|
76
|
+
右サイドバーの目次は、そのファイルの見出しから組み立てられ、スクロール位置に追従する。見出しが3つ以上あるファイルで表示される
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
## エディタの設定
|
|
81
|
+
|
|
82
|
+
### Zed
|
|
83
|
+
|
|
84
|
+
`install.sh` が `~/.config/zed/tasks.json` を作る。既存のファイルがある場合は上書きせず、貼り付ける内容を表示する。あとは `~/.config/zed/keymap.json` にキーを追加する
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"bindings": {
|
|
89
|
+
"cmd-shift-m": ["task::Spawn", { "task_name": "Preview in browser" }]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
つまずきやすい点
|
|
95
|
+
|
|
96
|
+
- 単独のキーの組み合わせにする。`cmd-k` から始まる2段のキーは、Zed 自身が前置きキーとして使っているため安定しない
|
|
97
|
+
- `"context"` を書くなら `Workspace` にする。`Editor` に書くと `task::Spawn` は発火しない
|
|
98
|
+
|
|
99
|
+
### VS Code / Cursor / Windsurf
|
|
100
|
+
|
|
101
|
+
`.vscode/tasks.json`
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"version": "2.0.0",
|
|
106
|
+
"tasks": [
|
|
107
|
+
{
|
|
108
|
+
"label": "Preview in browser",
|
|
109
|
+
"type": "shell",
|
|
110
|
+
"command": "mdbrowse",
|
|
111
|
+
"args": ["${file}"],
|
|
112
|
+
"presentation": { "reveal": "never" },
|
|
113
|
+
"problemMatcher": []
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`keybindings.json` で `workbench.action.tasks.runTask` に割り当てる
|
|
120
|
+
|
|
121
|
+
### Neovim
|
|
122
|
+
|
|
123
|
+
```lua
|
|
124
|
+
vim.keymap.set("n", "<leader>mp", function()
|
|
125
|
+
vim.fn.jobstart({ "mdbrowse", vim.fn.expand("%:p") })
|
|
126
|
+
end)
|
|
127
|
+
|
|
128
|
+
-- 保存のたびに更新する場合
|
|
129
|
+
vim.api.nvim_create_autocmd("BufWritePost", {
|
|
130
|
+
pattern = "*.md",
|
|
131
|
+
callback = function() vim.fn.jobstart({ "mdbrowse", vim.fn.expand("%:p") }) end,
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### JetBrains 系
|
|
136
|
+
|
|
137
|
+
設定 → ツール → 外部ツール で `mdbrowse` を追加し、引数に `$FilePath$` を指定する。Keymap でショートカットを割り当てる
|
|
138
|
+
|
|
139
|
+
### Emacs
|
|
140
|
+
|
|
141
|
+
```elisp
|
|
142
|
+
(defun mdbrowse ()
|
|
143
|
+
(interactive)
|
|
144
|
+
(start-process "mdbrowse" nil "mdbrowse" (buffer-file-name)))
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### それ以外
|
|
148
|
+
|
|
149
|
+
`mdbrowse /path/to/current/file.md` を実行できるエディタなら動く
|
|
150
|
+
|
|
151
|
+
## 見た目を変える
|
|
152
|
+
|
|
153
|
+
見た目に関わるものは `~/.config/mdbrowse/head.html` に集まっている。先頭の設定ブロック、それを使う CSS、バーと更新検知のスクリプトという構成で、配色とタイポグラフィは GitHub(Primer)に合わせてある。このファイルを直せば全ファイルのプレビューに反映される。バーで変更した値は、その人のブラウザ側で既定値を上書きする
|
|
154
|
+
|
|
155
|
+
表示確認用に `sample.md` が入っている
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
mdbrowse sample.md
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 仕組み
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
エディタのショートカット → mdbrowse <file> → pandoc → /tmp/mdbrowse.html
|
|
165
|
+
→ /tmp/mdbrowse-stamp.js
|
|
166
|
+
↑
|
|
167
|
+
ブラウザがスタンプを見て、変化したときだけ再読み込み
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
変換のたびに1行のスタンプファイルも書き出す。ブラウザは無条件に再読み込みせず、このスタンプを見て、新しく変換されたときだけ読み直す。画像のあるページで定期的にちらつかないのはこのため。スクロール中と印刷中、バーを操作している間はスタンプの確認を止める。画像の寸法はセッションに記憶して、読み込み中にレイアウトがずれないようにしている
|
|
171
|
+
|
|
172
|
+
元ファイルからの相対パス(画像や隣のファイルへのリンク)は、変換時に絶対 `file://` へ書き換える。HTML が `/tmp` にあっても画像が表示されるのはこのため。絶対パス、`http(s)`、`data:`、`mailto:`、ページ内アンカーはそのまま残す
|
|
173
|
+
|
|
174
|
+
ページは静的なので、待ち受けているものは何も無く、終了させる必要もない
|
|
175
|
+
|
|
176
|
+
## ライセンス
|
|
177
|
+
|
|
178
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
English | [日本語](README.ja.md)
|
|
2
|
+
|
|
3
|
+
# mdbrowse
|
|
4
|
+
|
|
5
|
+
Preview the Markdown file you are editing in a real browser, with your own CSS, from any editor that can run a shell command.
|
|
6
|
+
|
|
7
|
+
No server. No port. No daemon. One shortcut renders the current file to a fixed HTML path, and the browser tab you already have open picks it up.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
Editors render Markdown previews inside their own window and give you little control over the result. Server-based previewers give you control but ask you to keep a process and a port around. This does neither: it is a shell script on top of `pandoc`, plus one HTML file holding the styles.
|
|
14
|
+
|
|
15
|
+
Because the output path never changes, switching between files does not open new tabs. The tab you already have simply shows the new file.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- [pandoc](https://pandoc.org/) — macOS: `brew install pandoc`, Debian/Ubuntu: `apt install pandoc`
|
|
20
|
+
- An editor that can run a shell command with the path of the active file
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install -g @commte/mdbrowse
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or without installing anything:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npx @commte/mdbrowse file.md
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The stylesheet ships with the package. To customize it, copy it to `~/.config/mdbrowse/head.html` — that copy wins over the bundled one from then on:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
mdbrowse --eject
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary>Install from source instead</summary>
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
git clone https://github.com/commte/mdbrowse.git
|
|
45
|
+
cd mdbrowse
|
|
46
|
+
./install.sh
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This installs `mdbrowse` into `~/.local/bin` and the stylesheet into `~/.config/mdbrowse/head.html`. Your stylesheet is never overwritten on reinstall; pass `--force` when you do want the shipped one back.
|
|
50
|
+
|
|
51
|
+
</details>
|
|
52
|
+
|
|
53
|
+
Open the preview tab once and leave it open:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
mdbrowse --open
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
mdbrowse file.md # render (overwrites the output HTML)
|
|
63
|
+
mdbrowse --open # open the preview tab
|
|
64
|
+
mdbrowse --path # print the output HTML path
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Variable | Default | |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `MDBROWSE_OUT` | `/tmp/mdbrowse.html` | output HTML path |
|
|
70
|
+
| `MDBROWSE_HEAD` | `~/.config/mdbrowse/head.html` | stylesheet and browser script |
|
|
71
|
+
|
|
72
|
+
## In-page controls
|
|
73
|
+
|
|
74
|
+
A small bar sits in the top-right corner: theme, font size, content width, and a toggle for the table of contents. Choices are stored in the browser, so they survive reloads and apply to every file you preview afterwards. Nothing is written to disk and no server is involved.
|
|
75
|
+
|
|
76
|
+
The table of contents is built from the headings of the current file and follows your position as you scroll. It appears when a file has at least three headings.
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
## Editor setup
|
|
81
|
+
|
|
82
|
+
### Zed
|
|
83
|
+
|
|
84
|
+
`install.sh` writes `~/.config/zed/tasks.json` for you — it will not overwrite an existing one; it prints the entries to paste instead. Then bind a key in `~/.config/zed/keymap.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"bindings": {
|
|
89
|
+
"cmd-shift-m": ["task::Spawn", { "task_name": "Preview in browser" }]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Two things worth knowing:
|
|
95
|
+
|
|
96
|
+
- Use a single key combination. `cmd-k`-style chords are unreliable here, because `cmd-k` is already a chord prefix in Zed's own keymaps.
|
|
97
|
+
- If you scope the binding with `"context"`, use `Workspace`. `task::Spawn` does not fire from an `Editor` context binding.
|
|
98
|
+
|
|
99
|
+
### VS Code / Cursor / Windsurf
|
|
100
|
+
|
|
101
|
+
`.vscode/tasks.json`:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"version": "2.0.0",
|
|
106
|
+
"tasks": [
|
|
107
|
+
{
|
|
108
|
+
"label": "Preview in browser",
|
|
109
|
+
"type": "shell",
|
|
110
|
+
"command": "mdbrowse",
|
|
111
|
+
"args": ["${file}"],
|
|
112
|
+
"presentation": { "reveal": "never" },
|
|
113
|
+
"problemMatcher": []
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Bind it in `keybindings.json` with `workbench.action.tasks.runTask`.
|
|
120
|
+
|
|
121
|
+
### Neovim
|
|
122
|
+
|
|
123
|
+
```lua
|
|
124
|
+
vim.keymap.set("n", "<leader>mp", function()
|
|
125
|
+
vim.fn.jobstart({ "mdbrowse", vim.fn.expand("%:p") })
|
|
126
|
+
end)
|
|
127
|
+
|
|
128
|
+
-- or update the preview on every save
|
|
129
|
+
vim.api.nvim_create_autocmd("BufWritePost", {
|
|
130
|
+
pattern = "*.md",
|
|
131
|
+
callback = function() vim.fn.jobstart({ "mdbrowse", vim.fn.expand("%:p") }) end,
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### JetBrains IDEs
|
|
136
|
+
|
|
137
|
+
Settings → Tools → External Tools → add `mdbrowse` with `$FilePath$` as the argument, then assign a shortcut under Keymap.
|
|
138
|
+
|
|
139
|
+
### Emacs
|
|
140
|
+
|
|
141
|
+
```elisp
|
|
142
|
+
(defun mdbrowse ()
|
|
143
|
+
(interactive)
|
|
144
|
+
(start-process "mdbrowse" nil "mdbrowse" (buffer-file-name)))
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Anything else
|
|
148
|
+
|
|
149
|
+
If your editor can run `mdbrowse /path/to/the/current/file.md`, it works.
|
|
150
|
+
|
|
151
|
+
## Styling
|
|
152
|
+
|
|
153
|
+
Everything visual lives in `~/.config/mdbrowse/head.html`: a settings block at the top, the rules that use it, and the script that draws the bar and runs the reload loop. Colors and typography follow GitHub (Primer) by default. Edit that one file and every preview follows; values changed in the bar override them per browser.
|
|
154
|
+
|
|
155
|
+
`sample.md` in this repository exercises headings, lists, task lists, quotes, code blocks, tables and links — render it to check your styling:
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
mdbrowse sample.md
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## How it works
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
editor shortcut → mdbrowse <file> → pandoc → /tmp/mdbrowse.html
|
|
165
|
+
→ /tmp/mdbrowse-stamp.js
|
|
166
|
+
↑
|
|
167
|
+
browser polls the stamp, reloads only on change
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Relative paths in the source file (images, links to neighbouring files) are rewritten to absolute `file://` URLs during conversion, so images show up even though the HTML lives in `/tmp`. Absolute paths, `http(s)`, `data:`, `mailto:` and in-page anchors are left alone.
|
|
171
|
+
|
|
172
|
+
Each render also writes a one-line stamp file. The page polls that stamp instead of reloading blindly, so it refreshes only when you actually preview something new — no periodic flicker on pages with images. Polling pauses while you scroll, while you print, and while the pointer is on the bar. Image dimensions are remembered per session, so a refresh does not shift the layout while images load.
|
|
173
|
+
|
|
174
|
+
The page is static, so nothing is listening and nothing needs to be shut down.
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/assets/head.html
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
/* ==== 設定 ===========================================================
|
|
3
|
+
GitHub(Primer)の配色とタイポグラフィに合わせてある。
|
|
4
|
+
ライト bg #ffffff / canvas-subtle #f6f8fa / border #d1d9e0 / fg #1f2328
|
|
5
|
+
muted #59636e / link #0969da
|
|
6
|
+
ダーク bg #0d1117 / canvas-subtle #151b23 / border #3d444d / fg #e6edf3
|
|
7
|
+
muted #9198a1 / link #4493f8
|
|
8
|
+
==================================================================== */
|
|
9
|
+
:root {
|
|
10
|
+
color-scheme: light;
|
|
11
|
+
|
|
12
|
+
/* レイアウト(文字と行間は GitHub と同じ 16px / 1.5) */
|
|
13
|
+
--content-width: 800px;
|
|
14
|
+
--font-size: 16px;
|
|
15
|
+
--line-height: 1.5;
|
|
16
|
+
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
|
|
17
|
+
Helvetica, Arial, "Hiragino Sans", "Noto Sans JP", sans-serif;
|
|
18
|
+
--code-font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
19
|
+
|
|
20
|
+
/* 色(ライト) */
|
|
21
|
+
--bg: #ffffff;
|
|
22
|
+
--surface: #f6f8fa;
|
|
23
|
+
--border: #d1d9e0;
|
|
24
|
+
--fg: #1f2328;
|
|
25
|
+
--muted: #59636e;
|
|
26
|
+
--link: #0969da;
|
|
27
|
+
--inline-code-bg: rgba(129, 139, 152, .12);
|
|
28
|
+
|
|
29
|
+
/* 右上のバー */
|
|
30
|
+
--bar-bg: #ffffff;
|
|
31
|
+
--bar-border: #d1d9e0;
|
|
32
|
+
--bar-shadow: 0 3px 8px rgba(31, 35, 40, .18), 0 1px 2px rgba(31, 35, 40, .12);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
:root[data-theme="dark"] {
|
|
36
|
+
color-scheme: dark;
|
|
37
|
+
--bg: #0d1117; /* canvas-default */
|
|
38
|
+
--surface: #151b23; /* canvas-subtle */
|
|
39
|
+
--border: #3d444d; /* border-default */
|
|
40
|
+
--fg: #f0f6fc; /* fg-default */
|
|
41
|
+
--muted: #9198a1; /* fg-muted */
|
|
42
|
+
--link: #4493f8; /* accent-fg */
|
|
43
|
+
--inline-code-bg: rgba(101, 108, 118, .2);
|
|
44
|
+
|
|
45
|
+
/* 右上のバー */
|
|
46
|
+
--bar-bg: #212830; /* canvas-inset より一段明るい面 */
|
|
47
|
+
--bar-border: #3d444d;
|
|
48
|
+
--bar-shadow: 0 3px 8px rgba(1, 4, 9, .85), 0 1px 2px rgba(1, 4, 9, .6);
|
|
49
|
+
}
|
|
50
|
+
/* ==== ここから下は組み方 ============================================ */
|
|
51
|
+
html { background: var(--bg); }
|
|
52
|
+
body {
|
|
53
|
+
max-width: var(--content-width);
|
|
54
|
+
margin: 0 auto;
|
|
55
|
+
padding: 32px 32px 160px;
|
|
56
|
+
font-size: var(--font-size);
|
|
57
|
+
line-height: var(--line-height);
|
|
58
|
+
font-family: var(--font-family);
|
|
59
|
+
color: var(--fg);
|
|
60
|
+
background: var(--bg);
|
|
61
|
+
word-wrap: break-word;
|
|
62
|
+
}
|
|
63
|
+
h1, h2, h3, h4, h5, h6 { line-height: 1.25; font-weight: 600; margin: 24px 0 16px; }
|
|
64
|
+
h1 { font-size: 2em; padding-bottom: .3em; border-bottom: 1px solid var(--border); }
|
|
65
|
+
h2 { font-size: 1.5em; padding-bottom: .3em; border-bottom: 1px solid var(--border); }
|
|
66
|
+
h3 { font-size: 1.25em; }
|
|
67
|
+
h4 { font-size: 1em; }
|
|
68
|
+
h1:first-child { margin-top: 0; }
|
|
69
|
+
p, ul, ol, blockquote, table, pre { margin: 0 0 16px; }
|
|
70
|
+
a { color: var(--link); text-decoration: none; }
|
|
71
|
+
a:hover { text-decoration: underline; }
|
|
72
|
+
code {
|
|
73
|
+
font-family: var(--code-font-family); font-size: 85%;
|
|
74
|
+
background: var(--inline-code-bg); padding: .2em .4em; border-radius: 6px;
|
|
75
|
+
}
|
|
76
|
+
pre {
|
|
77
|
+
background: var(--surface); padding: 16px; border-radius: 6px;
|
|
78
|
+
overflow-x: auto; font-size: 85%; line-height: 1.45;
|
|
79
|
+
}
|
|
80
|
+
pre code { background: none; padding: 0; font-size: 100%; }
|
|
81
|
+
blockquote {
|
|
82
|
+
padding: 0 1em; color: var(--muted);
|
|
83
|
+
border-left: .25em solid var(--border);
|
|
84
|
+
}
|
|
85
|
+
table { border-collapse: collapse; display: block; width: max-content; max-width: 100%; overflow: auto; }
|
|
86
|
+
th, td { border: 1px solid var(--border); padding: 6px 13px; text-align: left; }
|
|
87
|
+
th { font-weight: 600; }
|
|
88
|
+
tr:nth-child(2n) { background: var(--surface); }
|
|
89
|
+
img { max-width: 100%; }
|
|
90
|
+
hr { height: .25em; padding: 0; margin: 24px 0; background: var(--border); border: 0; }
|
|
91
|
+
ul, ol { padding-left: 2em; }
|
|
92
|
+
li + li { margin-top: .25em; }
|
|
93
|
+
li input[type="checkbox"] { margin: 0 .2em .25em -1.4em; vertical-align: middle; }
|
|
94
|
+
|
|
95
|
+
/* ==== 右上のバー ==================================================== */
|
|
96
|
+
#mdbar {
|
|
97
|
+
position: fixed; top: 16px; right: 16px; z-index: 10;
|
|
98
|
+
display: flex; align-items: center; gap: 2px;
|
|
99
|
+
padding: 4px; border-radius: 8px;
|
|
100
|
+
background: var(--bar-bg); color: var(--fg);
|
|
101
|
+
border: 1px solid var(--bar-border);
|
|
102
|
+
box-shadow: var(--bar-shadow);
|
|
103
|
+
font: 500 12px/1 var(--font-family);
|
|
104
|
+
}
|
|
105
|
+
#mdbar button {
|
|
106
|
+
all: unset; cursor: pointer; user-select: none;
|
|
107
|
+
padding: 6px 10px; border-radius: 6px; color: var(--fg);
|
|
108
|
+
font: inherit; min-width: 1.4em; text-align: center;
|
|
109
|
+
}
|
|
110
|
+
#mdbar button:hover { background: var(--surface); }
|
|
111
|
+
#mdbar button:active { background: var(--border); }
|
|
112
|
+
/* ==== 目次(右サイドバー) ========================================== */
|
|
113
|
+
#mdtoc {
|
|
114
|
+
position: fixed; top: 64px; right: 16px; z-index: 9;
|
|
115
|
+
width: 248px; max-height: calc(100vh - 96px); overflow-y: auto;
|
|
116
|
+
padding: 4px 0; font: 13.5px/1.7 var(--font-family); color: var(--muted);
|
|
117
|
+
}
|
|
118
|
+
#mdtoc a {
|
|
119
|
+
display: block; padding: 4px 10px; border-radius: 6px;
|
|
120
|
+
color: var(--muted); text-decoration: none;
|
|
121
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
122
|
+
}
|
|
123
|
+
#mdtoc a:hover { background: var(--surface); color: var(--fg); text-decoration: none; }
|
|
124
|
+
#mdtoc a.is-current { background: var(--surface); color: var(--fg); font-weight: 600; }
|
|
125
|
+
#mdtoc a[data-level="3"] { padding-left: 22px; font-size: 12.5px; }
|
|
126
|
+
/* 目次は幅に関係なく出す。その分の余白をページ右に確保する */
|
|
127
|
+
html.has-toc { padding-right: 280px; }
|
|
128
|
+
|
|
129
|
+
/* ==== 印刷(PDF 保存) ============================================== */
|
|
130
|
+
@page { margin: 18mm; }
|
|
131
|
+
@media print {
|
|
132
|
+
#mdbar, #mdtoc { display: none; }
|
|
133
|
+
/* テーマに関係なく紙はライトで刷る */
|
|
134
|
+
:root, :root[data-theme="dark"] {
|
|
135
|
+
--bg: #ffffff; --surface: #f6f8fa; --border: #d1d9e0;
|
|
136
|
+
--fg: #1f2328; --muted: #59636e; --link: #0969da;
|
|
137
|
+
--inline-code-bg: rgba(129, 139, 152, .12);
|
|
138
|
+
}
|
|
139
|
+
html, body { background: #ffffff; color: #1f2328; }
|
|
140
|
+
body { max-width: none; padding: 0; font-size: 10.5pt; }
|
|
141
|
+
pre, blockquote, table, img, li { break-inside: avoid; }
|
|
142
|
+
h1, h2, h3, h4 { break-after: avoid; }
|
|
143
|
+
pre { white-space: pre-wrap; word-break: break-word; }
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
146
|
+
<script>
|
|
147
|
+
// 保存済みの設定を body 描画前に当てる(再読み込み時のちらつき防止)
|
|
148
|
+
(function () {
|
|
149
|
+
var s = {};
|
|
150
|
+
try { s = JSON.parse(localStorage.getItem('mdbrowse') || '{}'); } catch (e) {}
|
|
151
|
+
var r = document.documentElement;
|
|
152
|
+
r.dataset.theme = (s.theme === 'dark') ? 'dark' : 'light';
|
|
153
|
+
if (s.fontSize) r.style.setProperty('--font-size', s.fontSize + 'px');
|
|
154
|
+
if (s.width) r.style.setProperty('--content-width', s.width + 'px');
|
|
155
|
+
window.__mdSettings = s;
|
|
156
|
+
})();
|
|
157
|
+
|
|
158
|
+
addEventListener('DOMContentLoaded', function () {
|
|
159
|
+
var r = document.documentElement;
|
|
160
|
+
var s = window.__mdSettings || {};
|
|
161
|
+
var THEMES = ['light', 'dark'];
|
|
162
|
+
var WIDTHS = [800, 680, 960, 1200];
|
|
163
|
+
var paused = false;
|
|
164
|
+
var printing = false;
|
|
165
|
+
addEventListener('beforeprint', function () { printing = true; });
|
|
166
|
+
addEventListener('afterprint', function () { printing = false; });
|
|
167
|
+
|
|
168
|
+
function save() { try { localStorage.setItem('mdbrowse', JSON.stringify(s)); } catch (e) {} }
|
|
169
|
+
function fontSize() { return s.fontSize || parseInt(getComputedStyle(r).getPropertyValue('--font-size')) || 16; }
|
|
170
|
+
|
|
171
|
+
function applyToc() {
|
|
172
|
+
var el = document.getElementById('mdtoc');
|
|
173
|
+
var on = (s.toc !== false) && !!el;
|
|
174
|
+
if (el) el.style.display = on ? '' : 'none';
|
|
175
|
+
r.classList.toggle('has-toc', on);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
var bar = document.createElement('div');
|
|
179
|
+
bar.id = 'mdbar';
|
|
180
|
+
bar.innerHTML =
|
|
181
|
+
'<button data-act="theme" title="テーマ"></button>' +
|
|
182
|
+
'<button data-act="dec" title="文字を小さく">A-</button>' +
|
|
183
|
+
'<button data-act="inc" title="文字を大きく">A+</button>' +
|
|
184
|
+
'<button data-act="width" title="本文の幅">↔</button>' +
|
|
185
|
+
'<button data-act="toc" title="目次">☰</button>';
|
|
186
|
+
|
|
187
|
+
function render() { bar.querySelector('[data-act=theme]').textContent = r.dataset.theme; }
|
|
188
|
+
|
|
189
|
+
bar.addEventListener('click', function (e) {
|
|
190
|
+
var act = e.target.dataset && e.target.dataset.act;
|
|
191
|
+
if (!act) return;
|
|
192
|
+
if (act === 'theme') {
|
|
193
|
+
s.theme = (r.dataset.theme === 'dark') ? 'light' : 'dark';
|
|
194
|
+
r.dataset.theme = s.theme;
|
|
195
|
+
} else if (act === 'inc' || act === 'dec') {
|
|
196
|
+
s.fontSize = Math.min(24, Math.max(12, fontSize() + (act === 'inc' ? 1 : -1)));
|
|
197
|
+
r.style.setProperty('--font-size', s.fontSize + 'px');
|
|
198
|
+
} else if (act === 'toc') {
|
|
199
|
+
s.toc = (s.toc === false);
|
|
200
|
+
applyToc();
|
|
201
|
+
} else if (act === 'width') {
|
|
202
|
+
var i = WIDTHS.indexOf(s.width || 780);
|
|
203
|
+
s.width = WIDTHS[(i + 1) % WIDTHS.length];
|
|
204
|
+
r.style.setProperty('--content-width', s.width + 'px');
|
|
205
|
+
}
|
|
206
|
+
save(); render();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// バーを操作している間は再読み込みを止める
|
|
210
|
+
bar.addEventListener('pointerenter', function () { paused = true; });
|
|
211
|
+
bar.addEventListener('pointerleave', function () { paused = false; });
|
|
212
|
+
|
|
213
|
+
document.body.appendChild(bar);
|
|
214
|
+
render();
|
|
215
|
+
|
|
216
|
+
// 目次(右サイドバー)。見出しが少ないファイルでは出さない
|
|
217
|
+
var heads = [].slice.call(document.querySelectorAll('h2[id], h3[id]'));
|
|
218
|
+
if (heads.length >= 3) {
|
|
219
|
+
var toc = document.createElement('nav');
|
|
220
|
+
toc.id = 'mdtoc';
|
|
221
|
+
var links = {};
|
|
222
|
+
heads.forEach(function (h) {
|
|
223
|
+
var a = document.createElement('a');
|
|
224
|
+
a.href = '#' + h.id;
|
|
225
|
+
a.textContent = h.textContent;
|
|
226
|
+
a.title = h.textContent;
|
|
227
|
+
a.dataset.level = (h.tagName === 'H3') ? '3' : '2';
|
|
228
|
+
a.addEventListener('click', function (e) {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
h.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
231
|
+
});
|
|
232
|
+
toc.appendChild(a);
|
|
233
|
+
links[h.id] = a;
|
|
234
|
+
});
|
|
235
|
+
document.body.appendChild(toc);
|
|
236
|
+
applyToc();
|
|
237
|
+
|
|
238
|
+
// いま読んでいる見出しを目立たせる
|
|
239
|
+
var visible = [];
|
|
240
|
+
var io = new IntersectionObserver(function (entries) {
|
|
241
|
+
entries.forEach(function (en) {
|
|
242
|
+
var id = en.target.id;
|
|
243
|
+
var i = visible.indexOf(id);
|
|
244
|
+
if (en.isIntersecting) { if (i < 0) visible.push(id); }
|
|
245
|
+
else if (i >= 0) { visible.splice(i, 1); }
|
|
246
|
+
});
|
|
247
|
+
visible.sort(function (a, b) { return heads.findIndex(function (h) { return h.id === a; }) - heads.findIndex(function (h) { return h.id === b; }); });
|
|
248
|
+
var current = visible[0];
|
|
249
|
+
Object.keys(links).forEach(function (id) { links[id].classList.toggle('is-current', id === current); });
|
|
250
|
+
}, { rootMargin: '0px 0px -75% 0px' });
|
|
251
|
+
heads.forEach(function (h) { io.observe(h); });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// スクロール位置はブラウザ標準の復元に任せる(手で保存するとカクつく)
|
|
255
|
+
var lastScroll = 0;
|
|
256
|
+
addEventListener('scroll', function () { lastScroll = Date.now(); }, { passive: true });
|
|
257
|
+
|
|
258
|
+
// 画像の寸法を覚えておき、次の描画では読み込み前に場所を確保する(レイアウトシフト対策)
|
|
259
|
+
var dims = {};
|
|
260
|
+
try { dims = JSON.parse(sessionStorage.getItem('mdImgDims') || '{}'); } catch (e) {}
|
|
261
|
+
Array.prototype.forEach.call(document.images, function (img) {
|
|
262
|
+
var key = img.getAttribute('src');
|
|
263
|
+
var d = dims[key];
|
|
264
|
+
if (d) { img.style.aspectRatio = d.w + ' / ' + d.h; img.style.width = 'min(100%, ' + d.w + 'px)'; }
|
|
265
|
+
img.addEventListener('load', function () {
|
|
266
|
+
if (!img.naturalWidth) return;
|
|
267
|
+
dims[key] = { w: img.naturalWidth, h: img.naturalHeight };
|
|
268
|
+
try { sessionStorage.setItem('mdImgDims', JSON.stringify(dims)); } catch (e) {}
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// 更新の判定。mdbrowse が書き出すスタンプを読み、変わったときだけ再読み込みする。
|
|
273
|
+
// 毎秒リロードすると画像が読み直されてちらつくため
|
|
274
|
+
var stampUrl = location.pathname.replace(/\.html$/, '-stamp.js');
|
|
275
|
+
var known = null;
|
|
276
|
+
function checkStamp() {
|
|
277
|
+
var el = document.createElement('script');
|
|
278
|
+
el.src = stampUrl + '?t=' + Date.now();
|
|
279
|
+
el.onload = function () {
|
|
280
|
+
el.remove();
|
|
281
|
+
var v = window.__mdbrowseStamp;
|
|
282
|
+
if (known === null) { known = v; return; }
|
|
283
|
+
if (v !== known) location.reload();
|
|
284
|
+
};
|
|
285
|
+
el.onerror = function () { el.remove(); };
|
|
286
|
+
document.head.appendChild(el);
|
|
287
|
+
}
|
|
288
|
+
checkStamp();
|
|
289
|
+
setInterval(function () {
|
|
290
|
+
if (paused || printing) return;
|
|
291
|
+
if (Date.now() - lastScroll < 900) return;
|
|
292
|
+
checkStamp();
|
|
293
|
+
}, 1000);
|
|
294
|
+
});
|
|
295
|
+
</script>
|
package/bin/mdbrowse
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# mdbrowse — render a Markdown file to a fixed HTML path so the
|
|
3
|
+
# browser tab can stay open and just swap its contents.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
VERSION="0.1.0"
|
|
7
|
+
OUT="${MDBROWSE_OUT:-/tmp/mdbrowse.html}"
|
|
8
|
+
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/mdbrowse"
|
|
9
|
+
|
|
10
|
+
# スタイルシートの探索順
|
|
11
|
+
# 1. MDBROWSE_HEAD
|
|
12
|
+
# 2. ユーザー設定 ~/.config/mdbrowse/head.html
|
|
13
|
+
# 3. 同梱のもの(npm でインストールした場合はここが使われる)
|
|
14
|
+
resolve_dir() {
|
|
15
|
+
local src="$1" dir
|
|
16
|
+
while [ -L "$src" ]; do
|
|
17
|
+
dir="$(cd -P "$(dirname "$src")" && pwd)"
|
|
18
|
+
src="$(readlink "$src")"
|
|
19
|
+
case "$src" in /*) ;; *) src="$dir/$src" ;; esac
|
|
20
|
+
done
|
|
21
|
+
(cd -P "$(dirname "$src")" && pwd)
|
|
22
|
+
}
|
|
23
|
+
BUNDLED_HEAD="$(resolve_dir "$0")/../assets/head.html"
|
|
24
|
+
|
|
25
|
+
if [ -n "${MDBROWSE_HEAD:-}" ]; then
|
|
26
|
+
HEAD="$MDBROWSE_HEAD"
|
|
27
|
+
elif [ -f "$CONFIG_DIR/head.html" ]; then
|
|
28
|
+
HEAD="$CONFIG_DIR/head.html"
|
|
29
|
+
else
|
|
30
|
+
HEAD="$BUNDLED_HEAD"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
usage() {
|
|
34
|
+
cat <<'USAGE'
|
|
35
|
+
mdbrowse — render Markdown to a fixed HTML file for browser preview
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
mdbrowse <file.md> render the file (overwrites the output HTML)
|
|
39
|
+
mdbrowse --open open the preview tab in the default browser
|
|
40
|
+
mdbrowse --eject copy the bundled stylesheet into your config directory
|
|
41
|
+
mdbrowse --path print the output HTML path
|
|
42
|
+
mdbrowse --version print the version
|
|
43
|
+
|
|
44
|
+
Environment:
|
|
45
|
+
MDBROWSE_OUT output HTML path (default: /tmp/mdbrowse.html)
|
|
46
|
+
MDBROWSE_HEAD stylesheet/header (default: $XDG_CONFIG_HOME/mdbrowse/head.html)
|
|
47
|
+
|
|
48
|
+
Rendering also writes a stamp file next to the output. The page watches that
|
|
49
|
+
stamp and reloads only when you render something new, so pointing the editor at
|
|
50
|
+
a different file swaps the contents of the same tab instead of opening one.
|
|
51
|
+
USAGE
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
open_url() {
|
|
55
|
+
if command -v open >/dev/null 2>&1; then open "file://$OUT"
|
|
56
|
+
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "file://$OUT"
|
|
57
|
+
else echo "file://$OUT"
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
case "${1:-}" in
|
|
62
|
+
-h|--help) usage; exit 0 ;;
|
|
63
|
+
-v|--version) echo "mdbrowse $VERSION"; exit 0 ;;
|
|
64
|
+
--path) echo "$OUT"; exit 0 ;;
|
|
65
|
+
--eject)
|
|
66
|
+
mkdir -p "$CONFIG_DIR"
|
|
67
|
+
if [ -f "$CONFIG_DIR/head.html" ] && [ "${2:-}" != "--force" ]; then
|
|
68
|
+
echo "mdbrowse: $CONFIG_DIR/head.html already exists (pass --force to overwrite)" >&2
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
cp "$BUNDLED_HEAD" "$CONFIG_DIR/head.html"
|
|
72
|
+
echo "$CONFIG_DIR/head.html"
|
|
73
|
+
exit 0 ;;
|
|
74
|
+
--open)
|
|
75
|
+
[ -f "$OUT" ] || printf '<!doctype html><meta charset="utf-8"><p>No preview yet.' > "$OUT"
|
|
76
|
+
[ -f "${OUT%.html}-stamp.js" ] || printf 'window.__mdbrowseStamp="0";\n' > "${OUT%.html}-stamp.js"
|
|
77
|
+
open_url; exit 0 ;;
|
|
78
|
+
"") usage; exit 2 ;;
|
|
79
|
+
esac
|
|
80
|
+
|
|
81
|
+
src="$1"
|
|
82
|
+
|
|
83
|
+
if ! command -v pandoc >/dev/null 2>&1; then
|
|
84
|
+
echo "mdbrowse: pandoc not found. Install it first (macOS: brew install pandoc)" >&2
|
|
85
|
+
exit 127
|
|
86
|
+
fi
|
|
87
|
+
if [ ! -f "$src" ]; then
|
|
88
|
+
echo "mdbrowse: no such file: $src" >&2
|
|
89
|
+
exit 66
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
args=(--standalone --from gfm --variable "pagetitle=$(basename "$src")" --output "$OUT")
|
|
93
|
+
[ -f "$HEAD" ] && args+=(--include-in-header "$HEAD")
|
|
94
|
+
|
|
95
|
+
pandoc "$src" "${args[@]}"
|
|
96
|
+
|
|
97
|
+
# 出力先が /tmp なので、元ファイルからの相対パス(画像・ローカルリンク)が
|
|
98
|
+
# そのままでは解決できない。ソースのあるディレクトリ基準の file:// に書き換える。
|
|
99
|
+
# http(s) / data: / mailto: / 絶対パス / ページ内アンカーはそのまま残す
|
|
100
|
+
src_dir="$(cd "$(dirname "$src")" && pwd)"
|
|
101
|
+
sed -E \
|
|
102
|
+
-e 's#(src|href)="(https?://|data:|mailto:|file://|/|\#)#\1="@@KEEP@@\2#g' \
|
|
103
|
+
-e "s#(src|href)=\"#\1=\"file://$src_dir/#g" \
|
|
104
|
+
-e 's#(src|href)="file://[^"]*@@KEEP@@#\1="#g' \
|
|
105
|
+
"$OUT" > "$OUT.tmp" && mv "$OUT.tmp" "$OUT"
|
|
106
|
+
|
|
107
|
+
# 更新の目印。ブラウザはこれを見て、中身が変わったときだけ読み直す
|
|
108
|
+
printf 'window.__mdbrowseStamp="%s";\n' "$(date +%s)-$RANDOM" > "${OUT%.html}-stamp.js"
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@commte/mdbrowse",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Preview the Markdown file you are editing in a real browser, with your own CSS, from any editor that can run a shell command. No server, no port.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"mdbrowse": "bin/mdbrowse"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/mdbrowse",
|
|
10
|
+
"assets/head.html",
|
|
11
|
+
"sample.md",
|
|
12
|
+
"README.md",
|
|
13
|
+
"README.ja.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"markdown",
|
|
18
|
+
"preview",
|
|
19
|
+
"pandoc",
|
|
20
|
+
"editor",
|
|
21
|
+
"zed",
|
|
22
|
+
"neovim",
|
|
23
|
+
"cli"
|
|
24
|
+
],
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/commte/mdbrowse.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/commte/mdbrowse#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/commte/mdbrowse/issues"
|
|
32
|
+
},
|
|
33
|
+
"author": "commte",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"os": [
|
|
36
|
+
"darwin",
|
|
37
|
+
"linux"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/sample.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Markdown 記法サンプル
|
|
2
|
+
|
|
3
|
+
このファイルは mdbrowse の表示確認用です。見出し、リスト、表、コードなど、日常的に使う記法をひと通り並べてあります。
|
|
4
|
+
|
|
5
|
+
## 見出し
|
|
6
|
+
|
|
7
|
+
見出しは `#` の数で階層になります。このページには h1 から h4 まで入っています。
|
|
8
|
+
|
|
9
|
+
### 見出し 3
|
|
10
|
+
|
|
11
|
+
#### 見出し 4
|
|
12
|
+
|
|
13
|
+
## 段落と強調
|
|
14
|
+
|
|
15
|
+
段落は空行で区切ります。文中の強調は **太字**、*斜体*、~~打ち消し~~、`インラインコード` が使えます。
|
|
16
|
+
|
|
17
|
+
改行だけしたいときは、行末に空白を2つ置くか、`<br>` を入れます。
|
|
18
|
+
|
|
19
|
+
## リスト
|
|
20
|
+
|
|
21
|
+
箇条書きです。
|
|
22
|
+
|
|
23
|
+
- 親の項目
|
|
24
|
+
- ネストもできます
|
|
25
|
+
- 子の項目
|
|
26
|
+
- もう1つの子
|
|
27
|
+
- 孫の項目
|
|
28
|
+
- 3つ目
|
|
29
|
+
|
|
30
|
+
番号付きは数字で書きます。
|
|
31
|
+
|
|
32
|
+
1. 最初の手順
|
|
33
|
+
2. 次の手順
|
|
34
|
+
3. 最後の手順
|
|
35
|
+
|
|
36
|
+
タスクリストです。
|
|
37
|
+
|
|
38
|
+
- [x] 完了した項目
|
|
39
|
+
- [ ] まだの項目
|
|
40
|
+
- [ ] 長めのテキストが入った項目。折り返しの見え方もここで確認できます
|
|
41
|
+
|
|
42
|
+
## 引用
|
|
43
|
+
|
|
44
|
+
> 引用ブロックです。返信の引用や、原文の一部を示すときに使います。
|
|
45
|
+
>
|
|
46
|
+
> 空行を挟めば段落を分けられます。
|
|
47
|
+
|
|
48
|
+
## コード
|
|
49
|
+
|
|
50
|
+
インラインは `mdbrowse sample.md` のように書きます。
|
|
51
|
+
|
|
52
|
+
ブロックは3つのバッククォートで囲みます。言語名を書けます。
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
#!/usr/bin/env bash
|
|
56
|
+
set -euo pipefail
|
|
57
|
+
|
|
58
|
+
for f in *.md; do
|
|
59
|
+
mdbrowse "$f"
|
|
60
|
+
done
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"label": "Preview in browser",
|
|
66
|
+
"command": "mdbrowse",
|
|
67
|
+
"args": ["$ZED_FILE"]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
横に長い行は、はみ出さずにブロックの中でスクロールします。
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
pandoc input.md --standalone --from gfm --variable "pagetitle=input.md" --include-in-header head.html --output /tmp/mdbrowse.html
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 表
|
|
78
|
+
|
|
79
|
+
| 項目 | 既定値 | 説明 |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `MDBROWSE_OUT` | `/tmp/mdbrowse.html` | 出力先の HTML |
|
|
82
|
+
| `MDBROWSE_HEAD` | `~/.config/mdbrowse/head.html` | スタイルとリロード用スクリプト |
|
|
83
|
+
| `--content-width` | `800px` | 本文の幅 |
|
|
84
|
+
|
|
85
|
+
## リンク
|
|
86
|
+
|
|
87
|
+
[通常のリンク](https://pandoc.org/) と、自動リンク https://github.com/ の見え方です。
|
|
88
|
+
|
|
89
|
+
## 区切り線
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 長い本文
|
|
94
|
+
|
|
95
|
+
日本語の本文が続いたときの行間と1行の文字数を見るための段落です。読みやすさは、文字サイズ、行間、本文幅の3つの組み合わせで決まります。右上のバーで文字サイズと幅を変えられるので、実際に切り替えながら好みの値を探せます。設定はブラウザに保存され、次に開いたときも維持されます。
|
|
96
|
+
|
|
97
|
+
段落をもう1つ置きます。行送りが詰まりすぎていないか、逆に空きすぎて視線が迷わないかを、この2段落で確認できます。
|