jsmdcui 0.6.3 → 0.8.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/CHANGELOG.md +121 -0
- package/README.md +216 -33
- package/clean.sh +9 -0
- package/demo.resized.jpg +0 -0
- package/{image-processor.md → demos/image-processor.md} +1 -0
- package/{image-processor.zh-TW.md → demos/image-processor.zh-TW.md} +1 -0
- package/demos/maze.md +144 -0
- package/{select.md → demos/select.md} +2 -0
- package/demos/todo-zh.md +190 -0
- package/demos/todo.md +191 -0
- package/edit +9 -0
- package/package.json +1 -1
- package/runmd.mjs +77 -16
- package/runtime/help/help.md +216 -33
- package/runtime/jsplugins/cdp/cdp.js +5 -2
- package/runtime/jsplugins/chapter/chapter.js +4 -2
- package/runtime/jsplugins/example/example.js +10 -7
- package/runtime/syntax/markdown.yaml +1 -0
- package/single-exe/packAssets.sh +1 -1
- package/src/cui/fence-events.mjs +106 -0
- package/src/cui/id-collision.mjs +10 -28
- package/src/cui/kitty-debug.mjs +25 -0
- package/src/cui/kitty-images.mjs +200 -0
- package/src/cui/rpc.mjs +169 -10
- package/src/cui/server.mjs +14 -3
- package/src/cui/task-checkbox.mjs +44 -0
- package/src/index.js +615 -101
- package/src/platform/terminal.js +5 -0
- package/src/plugins/js-bridge.js +354 -21
- package/src/screen/screen.js +108 -1
- package/tests/cat-markdown.test.js +6 -5
- package/tests/demo.test.js +99 -9
- package/tests/fence-events.test.js +405 -0
- package/tests/heading-list-selector.test.js +346 -0
- package/tests/id-collision.test.js +14 -2
- package/tests/js-plugin-prompts.test.js +46 -0
- package/tests/key-event.md +10 -0
- package/tests/kitty-demo.md +184 -0
- package/tests/kitty-images.test.js +169 -0
- package/tests/platform-terminal.test.js +8 -0
- package/tests/task-checkbox.test.js +33 -0
- package/tests/textarea.md +8 -0
- package/tests/wui-responsive-images.test.js +35 -0
- package/tests/wui.test.js +16 -1
- package/tui +4 -2
- package/wui +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,127 @@
|
|
|
2
2
|
|
|
3
3
|
All notable user-visible changes to jsmdcui are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.8.0] - 2026-07-20
|
|
6
|
+
|
|
7
|
+
This update adds portable inline keydown handling to Markdown text controls,
|
|
8
|
+
including mobile software-keyboard fallback behavior, and makes interactive
|
|
9
|
+
prompts safe across every TUI frontend action path.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Add quoted `@keydown="..."` attributes to named `text` and `textarea`
|
|
14
|
+
fences. The TUI preserves source event metadata and evaluates it before key
|
|
15
|
+
handling; the WUI emits a native inline event attribute. Add `.prevent` to
|
|
16
|
+
apply `event.preventDefault()` before the handler runs. Keyup is intentionally
|
|
17
|
+
not exposed because traditional terminals do not report key releases
|
|
18
|
+
reliably.
|
|
19
|
+
- In the WUI, automatically map `beforeinput.data` back through `onkeydown`
|
|
20
|
+
when a mobile software keyboard reports `event.key` as `Unidentified`.
|
|
21
|
+
Unidentified events are hidden from application handlers, while identified
|
|
22
|
+
desktop keydowns are marked briefly to prevent duplicate calls.
|
|
23
|
+
- Add native multiline editing to TUI `textarea` controls. Enter splits a body
|
|
24
|
+
row and expands the frame; Backspace at the start of a later row and Delete
|
|
25
|
+
at the end of a row join adjacent body rows without exposing frame borders.
|
|
26
|
+
- Add the `bun ./edit` launcher as a short equivalent of
|
|
27
|
+
`bun src/index.js --edit`, opening Markdown as ordinary editable UTF-8 source.
|
|
28
|
+
- Add the automatically discovered `--demo-maze` example, demonstrating
|
|
29
|
+
portable `@keydown.prevent` controls with keyboard and arrow-key navigation;
|
|
30
|
+
add `Ctrl-R` as a keyboard shortcut for resetting the maze.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Install protected `alert`, `confirm`, and `prompt` globals once for the TUI
|
|
35
|
+
lifetime and share them across OSC 8 actions, fenced keyboard handlers, and
|
|
36
|
+
JS plugins. Restore Bun's original globals when the TUI stops.
|
|
37
|
+
- Make `micro.alert()`, `micro.confirm()`, and `micro.prompt()` synchronous like
|
|
38
|
+
their native counterparts, and update the bundled JS plugin examples so they
|
|
39
|
+
do not `await` these calls.
|
|
40
|
+
- Add TUI `.val(value)` replacements to Undo/Redo history, including multiline
|
|
41
|
+
layout, ANSI rendering metadata, image positions, and task-list anchors.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- Prevent `alert()` called from a keydown handler from competing with the TUI
|
|
46
|
+
for stdin by suspending raw mode and screen rendering around native prompts.
|
|
47
|
+
- Restore the terminal's previous raw-mode and input-listener state after a
|
|
48
|
+
protected prompt, including setup, prompt, and cleanup error paths.
|
|
49
|
+
- Index fenced keyboard-event regions once and use binary lookup for each key,
|
|
50
|
+
instead of rescanning the rendered Markdown document on every keypress.
|
|
51
|
+
- Open Windows controlling-terminal input through read-only `CONIN$` when stdin
|
|
52
|
+
is redirected, so piped Markdown retains keyboard and protected-prompt input.
|
|
53
|
+
- Preserve `ctrlKey`, `shiftKey`, `altKey`, and `metaKey` when the WUI maps an
|
|
54
|
+
unidentified mobile keydown through its `beforeinput` fallback.
|
|
55
|
+
- Recover modified A-Z keys in the WUI from key codes 65-90 or physical
|
|
56
|
+
`KeyA`-`KeyZ` codes, including Android browsers that transform Alt-S into
|
|
57
|
+
`ß`; exclude AltGraph so international text composition remains intact.
|
|
58
|
+
- Add matching non-enumerable `event.toJSON()` methods to TUI and WUI keydown
|
|
59
|
+
events, allowing `JSON.stringify(event)` to produce portable event details.
|
|
60
|
+
- Keep fenced-block IDs visible to collision checking when the info string also
|
|
61
|
+
contains inline event attributes such as `@keydown="..."`.
|
|
62
|
+
- When the WUI's default port 3000 is already in use, retry with port 0 so the
|
|
63
|
+
operating system selects an available port, and print that actual port in the
|
|
64
|
+
generated URL. Other server startup errors continue to fail normally.
|
|
65
|
+
|
|
66
|
+
## [0.7.0] - 2026-07-20
|
|
67
|
+
|
|
68
|
+
This update adds native Kitty image rendering, turns heading task lists into
|
|
69
|
+
Array-style collections in both interfaces, and keeps TUI layout, interaction,
|
|
70
|
+
and checkbox styling consistent at every terminal width.
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
|
|
74
|
+
- Display local Markdown images at their rendered TUI positions with the
|
|
75
|
+
Kitty graphics protocol. Image rows are reserved according to intrinsic
|
|
76
|
+
dimensions, and placements follow scrolling, resizing, and split panes.
|
|
77
|
+
- With `--allow-url` and Kitty mode enabled, download HTTP(S) Markdown images
|
|
78
|
+
through the existing `curl`, `wget`, then Bun `fetch` fallback chain. Relative
|
|
79
|
+
image URLs in downloaded Markdown resolve against the original document URL.
|
|
80
|
+
- Keep Bun's normal linked `📷` fallback for missing, unsupported, and
|
|
81
|
+
unauthorized remote images, and scope Kitty cleanup to image IDs owned by
|
|
82
|
+
jsmdcui.
|
|
83
|
+
- Set Kitty's `C=1` placement flag so displaying an image cannot advance the
|
|
84
|
+
terminal cursor and trigger an unwanted scroll at the bottom of the screen.
|
|
85
|
+
- Add `.push()`, `.pop()`, `.shift()`, `.unshift()`, and `.splice()` to heading
|
|
86
|
+
selectors in both the TUI and WUI. The methods follow their
|
|
87
|
+
`Array.prototype` argument and return-value conventions, operate on direct
|
|
88
|
+
items in the first task list belonging to the heading, and remove nested
|
|
89
|
+
content together with its parent item.
|
|
90
|
+
- Allow inserted task items to be passed as strings for unchecked items or as
|
|
91
|
+
`{ value, checked }` objects, and preserve an emptied TUI list's insertion
|
|
92
|
+
point so later mutations still target the same list.
|
|
93
|
+
- Add read-only heading `.slice(start, end)`, returning fresh
|
|
94
|
+
`{ value, checked }` snapshots that include both checked and unchecked direct
|
|
95
|
+
task items.
|
|
96
|
+
- Add `demos/todo.md` and `demos/todo-zh.md`, runnable Todo examples that use
|
|
97
|
+
text controls and the new list methods to add and remove items and display
|
|
98
|
+
completed or pending tasks.
|
|
99
|
+
- Move secondary examples under `demos/` and add automatic
|
|
100
|
+
`--demo-<filename>` discovery, so newly bundled `demos/<filename>.md` files
|
|
101
|
+
need no parser changes. Keep `--demo` mapped to the root `testapp.md` and
|
|
102
|
+
retain the existing image-processor aliases.
|
|
103
|
+
- Add `--demo-list` to list the root demo and every automatically discovered
|
|
104
|
+
Markdown example from bundled assets or the source tree's `demos/` directory.
|
|
105
|
+
- Add `clean.sh` as a convenience helper for removing generated Markdown
|
|
106
|
+
companion files from the project directory.
|
|
107
|
+
|
|
108
|
+
### Changed
|
|
109
|
+
|
|
110
|
+
- Document the Array-style heading task-list API, its return values, nested-item
|
|
111
|
+
behavior, existing-list requirement, and non-persistent rendered-state
|
|
112
|
+
semantics in the README and bundled help.
|
|
113
|
+
- Use the complete pane content width consistently for Markdown rendering,
|
|
114
|
+
soft wrapping, cursor and mouse mapping, and Kitty image sizing instead of
|
|
115
|
+
reserving the terminal's final column.
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- Fix soft wrapping at affected terminal widths shifting the visual cursor by
|
|
120
|
+
one row, which could activate the preceding Markdown action and leave the
|
|
121
|
+
final action unresponsive.
|
|
122
|
+
- Update the Bun Markdown ANSI glyph and style together when toggling a task
|
|
123
|
+
checkbox, so checked items become green and unchecked items no longer retain
|
|
124
|
+
the checked color.
|
|
125
|
+
|
|
5
126
|
## [0.6.3] - 2026-07-18
|
|
6
127
|
|
|
7
128
|
This update enforces unique heading IDs at the Markdown source level and
|
package/README.md
CHANGED
|
@@ -47,9 +47,10 @@ On other platforms, follow the [official Bun installation guide](https://bun.com
|
|
|
47
47
|
Choose either of these two ways to run jsmdcui.
|
|
48
48
|
|
|
49
49
|
> **Important:** Opening or rendering a local `.md` file writes or overwrites
|
|
50
|
-
> 5 generated files beside it.
|
|
51
|
-
>
|
|
52
|
-
>
|
|
50
|
+
> 5 generated files beside it.
|
|
51
|
+
> Starting `--wui` without a file or starting all the `--demo`s writes the
|
|
52
|
+
> 5 files generated from `testapp.md` in the current directory.
|
|
53
|
+
> The source Markdown is not changed, but you should run the demo in a directory where
|
|
53
54
|
> overwriting generated files is safe.
|
|
54
55
|
|
|
55
56
|
### Route 1: Run with npx
|
|
@@ -72,9 +73,10 @@ npx jsmdcui
|
|
|
72
73
|
npx jsmdcui --demo
|
|
73
74
|
```
|
|
74
75
|
|
|
76
|
+
List every bundled demo and its command-line option:
|
|
77
|
+
|
|
75
78
|
```sh
|
|
76
|
-
|
|
77
|
-
npx jsmdcui --demo-select
|
|
79
|
+
npx jsmdcui --demo-list
|
|
78
80
|
```
|
|
79
81
|
|
|
80
82
|
```sh
|
|
@@ -87,6 +89,11 @@ npx jsmdcui --demo-imgtool
|
|
|
87
89
|
npx jsmdcui --demo-imgtool-zh
|
|
88
90
|
```
|
|
89
91
|
|
|
92
|
+
```sh
|
|
93
|
+
# Maze game demo
|
|
94
|
+
npx jsmdcui --demo-maze
|
|
95
|
+
```
|
|
96
|
+
|
|
90
97
|
```sh
|
|
91
98
|
# WUI(Web User Interface) Demo
|
|
92
99
|
npx jsmdcui --wui
|
|
@@ -121,22 +128,31 @@ bun src/index.js --wui testapp.md
|
|
|
121
128
|
|
|
122
129
|
### Usage table
|
|
123
130
|
|
|
124
|
-
- The command table below
|
|
131
|
+
- The command table below assumes you're running from a cloned repository
|
|
125
132
|
- If you use npx, replace `bun src/index.js` with `npx jsmdcui`
|
|
126
133
|
|
|
134
|
+
- I've also provided short aliases
|
|
135
|
+
* bun ./tui = bun src/index.js
|
|
136
|
+
* bun ./wui = bun src/index.js --wui
|
|
137
|
+
|
|
127
138
|
| Command | Result |
|
|
128
139
|
| --- | --- |
|
|
129
140
|
| `bun src/index.js app.md` | Render `app.md` as a read-only terminal UI and write five generated files beside it. |
|
|
141
|
+
| `bun src/index.js --kitty app.md` | Display Markdown images with Kitty graphics and the jsgotty MIME extension. |
|
|
142
|
+
| `bun src/index.js --kitty-compat app.md` | Convert Markdown images to PNG with `Bun.Image` and display them using the standard Kitty graphics protocol without the non-standard MIME `U` field. |
|
|
143
|
+
| `bun src/index.js --kitty --allow-url URL.md` | Download trusted HTTP(S) Markdown and its HTTP(S) images, then display supported images with Kitty graphics. |
|
|
144
|
+
| `JSMDCUI_KITTY_DEBUG=1 bun src/index.js --kitty app.md` | Enable Kitty image placement logging to `kitty-placement.log`. |
|
|
130
145
|
| `bun src/index.js --check app.md` | Check heading and fenced-block IDs for collisions, print line-by-line details, and exit. |
|
|
131
146
|
| `bun src/index.js --edit app.md` | Open `app.md` as editable UTF-8 source, overriding automatic Markdown UI detection. |
|
|
132
147
|
| `bun src/index.js --cat app.md` | Render the terminal version to stdout, write five generated files beside it, and exit. |
|
|
133
148
|
| `bun src/index.js --testapp.md` | Write the bundled `testapp.md` source to stdout and exit. |
|
|
134
149
|
| `bun src/index.js --export-readme` | Write or overwrite `./README.md` with the bundled README source and exit. |
|
|
150
|
+
| `bun src/index.js --demo-list` | List `testapp.md` and every bundled `demos/*.md` example with its command-line option, then exit. |
|
|
135
151
|
| `bun src/index.js --demo` | Use local `testapp.md` when present, otherwise write the bundled demo; open it in the terminal UI and write five generated files beside it. |
|
|
136
|
-
| `bun src/index.js --demo
|
|
137
|
-
| `bun src/index.js --demo-imgtool` |
|
|
138
|
-
| `bun src/index.js --demo-imgtool-zh` |
|
|
139
|
-
| `bun src/index.js --allow-url URL.md` | Download HTTP(S) Markdown to the current directory, write 5 generated files
|
|
152
|
+
| `bun src/index.js --demo-<filename>` | Load `demos/<filename>.md`; preserve an existing local copy or write the bundled copy, then open it and generate its five companion files. New files added under `demos/` work automatically. |
|
|
153
|
+
| `bun src/index.js --demo-imgtool` | Compatibility alias for `--demo-image-processor`. |
|
|
154
|
+
| `bun src/index.js --demo-imgtool-zh` | Compatibility alias for `--demo-image-processor.zh-TW`. |
|
|
155
|
+
| `bun src/index.js --allow-url URL.md` | Download HTTP(S) Markdown to the current directory and, with Kitty mode enabled, download its HTTP(S) images; write 5 generated files and allow embedded code to run. Only use trusted URLs. |
|
|
140
156
|
| `bun src/index.js --wui` | Use local `testapp.md` when present, otherwise write the bundled demo; write five generated files in the current directory, then print and serve a random URL. |
|
|
141
157
|
| `bun src/index.js --wui app.md` | Write five generated files beside `app.md`, then print and serve a random URL. |
|
|
142
158
|
| `PORT=8080 bun src/index.js --wui app.md` | Start the browser UI on another port. |
|
|
@@ -228,20 +244,90 @@ native `<textarea>` with the declared ID and classes. Long text wraps
|
|
|
228
244
|
automatically, and the field height is recalculated when the user types, the
|
|
229
245
|
window is resized, or frontend code calls `.val(value)`.
|
|
230
246
|
|
|
247
|
+
In the TUI, `text` remains a single-line control while `textarea` supports
|
|
248
|
+
native multiline editing. Enter splits the current body row and grows the
|
|
249
|
+
frame, Backspace at the start of a later row joins it to the previous row, and
|
|
250
|
+
Delete at the end of a row joins the following row. When the expanded control
|
|
251
|
+
does not fit on screen, the document viewport scrolls to keep the cursor
|
|
252
|
+
visible. The closing border and following Markdown content move with the
|
|
253
|
+
resized control.
|
|
254
|
+
|
|
255
|
+
A named `text` or `textarea` block can run inline front-end code before it
|
|
256
|
+
handles a key by placing a quoted HTML-style `@keydown` attribute after its
|
|
257
|
+
identity:
|
|
258
|
+
|
|
259
|
+
````md
|
|
260
|
+
```text#command.field @keydown="handleCommand(event)"
|
|
261
|
+
Initial value
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```js front
|
|
265
|
+
export function handleCommand(event) {
|
|
266
|
+
if (event.key !== 'Enter') return;
|
|
267
|
+
event.preventDefault();
|
|
268
|
+
alert(`Command: ${$('#command').val()}`);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
````
|
|
272
|
+
|
|
273
|
+
The block must have a unique ID. In the WUI, jsmdcui writes the code directly
|
|
274
|
+
to the generated textarea's native `onkeydown` attribute. Mobile browsers may
|
|
275
|
+
report ordinary software-keyboard letters as `Unidentified`, so the WUI also
|
|
276
|
+
generates an `onbeforeinput` fallback. The unidentified event is hidden from
|
|
277
|
+
the handler; `beforeinput.data` becomes `event.key`, then the same keydown code
|
|
278
|
+
runs once. The fallback also preserves `ctrlKey`, `shiftKey`, `altKey`, and
|
|
279
|
+
`metaKey` from the unidentified keydown. A short zero-delay timer distinguishes
|
|
280
|
+
it from the `beforeinput` that normally follows an already identified keydown,
|
|
281
|
+
preventing duplicate calls.
|
|
282
|
+
|
|
283
|
+
On Android browsers, `Alt-E`, `Alt-N`, `Alt-U`, and `Alt-I` may be consumed or
|
|
284
|
+
transformed by the software keyboard and therefore cannot always be observed
|
|
285
|
+
reliably by a keydown handler. Avoid relying on these combinations for portable
|
|
286
|
+
WUI controls.
|
|
287
|
+
|
|
288
|
+
In the TUI, the full source info string is kept in an event table before Bun
|
|
289
|
+
renders it. The keydown statements run with a synthetic `event` before the
|
|
290
|
+
text control handles the key. Both interfaces expose `event.key`, modifier
|
|
291
|
+
flags, `event.target.id`, and `event.target.value`. Use `event`, the native
|
|
292
|
+
inline-handler variable, rather than Vue's `$event` alias. Double quotes
|
|
293
|
+
delimit the handler; use single-quoted JavaScript strings inside it or escape
|
|
294
|
+
an embedded double quote as `\"`.
|
|
295
|
+
|
|
296
|
+
Both interfaces add a non-enumerable `event.toJSON()` method with matching
|
|
297
|
+
keyboard, modifier, prevention, and target fields. `JSON.stringify(event)`
|
|
298
|
+
calls it automatically, so the resulting JSON is portable between the TUI and
|
|
299
|
+
WUI.
|
|
300
|
+
|
|
301
|
+
A keydown handler can call `event.preventDefault()` to stop text insertion or
|
|
302
|
+
cursor movement. The `.prevent` modifier applies this automatically:
|
|
303
|
+
|
|
304
|
+
````md
|
|
305
|
+
```text#command @keydown.prevent="handleCommand(event)"
|
|
306
|
+
```
|
|
307
|
+
````
|
|
308
|
+
|
|
309
|
+
`@keydown` is the only keyboard event exposed by jsmdcui. Traditional terminal
|
|
310
|
+
input does not report physical key releases reliably, so jsmdcui does not
|
|
311
|
+
provide or emulate `@keyup` in either interface.
|
|
312
|
+
|
|
231
313
|
In the terminal TUI, only content after the protected `│ ` or `| ` prefix can
|
|
232
|
-
be edited
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
314
|
+
be edited, and the frame prefix cannot be deleted. Multiline paste remains
|
|
315
|
+
blocked. For single-line `text` controls, activate the lower-left frame corner
|
|
316
|
+
to add a row. Activate the upper-left frame corner to remove the trailing row
|
|
317
|
+
only when it is empty; non-empty content is never removed.
|
|
318
|
+
|
|
319
|
+
TUI text-control changes made through `.val(value)` participate in the same
|
|
320
|
+
history as direct editing. `Ctrl-Z` undoes the replacement and `Ctrl-Y` redoes
|
|
321
|
+
it, including multiline frame resizing and its associated rendered metadata.
|
|
237
322
|
|
|
238
323
|
### Heading task lists and selector API
|
|
239
324
|
|
|
240
325
|
Headings can act as form-group selectors. jsmdcui uses the IDs generated by
|
|
241
326
|
`Bun.markdown.html(..., { headings: { ids: true } })`; for example,
|
|
242
|
-
`## Select Color` becomes `#select-color`. A heading selection reads
|
|
243
|
-
task list
|
|
244
|
-
|
|
327
|
+
`## Select Color` becomes `#select-color`. A heading selection reads direct
|
|
328
|
+
task items from the first list following that heading and stops at the next
|
|
329
|
+
heading. In the TUI, the first rendered `☐` or `☒` establishes that list and
|
|
330
|
+
its indentation. Nested task items are not included in the outer list's value.
|
|
245
331
|
|
|
246
332
|
Heading IDs share the same selector namespace as all explicitly named fenced
|
|
247
333
|
blocks, not only `text` and `textarea`, so avoid name collisions between them.
|
|
@@ -306,14 +392,65 @@ $('#select-color').val() // "Green"
|
|
|
306
392
|
$('#features').val() // ["Search", "Offline mode"]
|
|
307
393
|
```
|
|
308
394
|
|
|
309
|
-
The
|
|
395
|
+
The first direct task list belonging to a heading can also be changed with
|
|
396
|
+
Array-style methods. String arguments create unchecked items. Pass an object
|
|
397
|
+
to choose the initial checked state:
|
|
398
|
+
|
|
399
|
+
```js
|
|
400
|
+
$('#features').push('Export')
|
|
401
|
+
$('#features').unshift({ value: 'Import', checked: true })
|
|
402
|
+
$('#features').splice(1, 2, 'Replacement')
|
|
403
|
+
$('#features').slice(0, 2)
|
|
404
|
+
$('#features').pop()
|
|
405
|
+
$('#features').shift()
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Like the corresponding `Array.prototype` methods, `.push(...items)` and
|
|
409
|
+
`.unshift(...items)` accept multiple items and return the new number of direct
|
|
410
|
+
items. `.pop()` and `.shift()` return the removed item's visible label, or
|
|
411
|
+
`undefined` when the list is empty. Nested task items are part of their parent
|
|
412
|
+
item: they are not counted separately, and are removed together with that
|
|
413
|
+
parent. These methods change the rendered TUI/WUI state; they do not rewrite
|
|
414
|
+
the source Markdown file. A heading must already have a task list before items
|
|
415
|
+
can be added.
|
|
416
|
+
|
|
417
|
+
`.splice(start, deleteCount, ...items)` follows `Array.prototype.splice()`:
|
|
418
|
+
negative indexes count from the end, omitting `deleteCount` removes through the
|
|
419
|
+
end, and the return value is an array containing the removed visible labels.
|
|
420
|
+
|
|
421
|
+
`.slice(start, end)` is read-only and follows `Array.prototype.slice()`. It
|
|
422
|
+
returns fresh item snapshots, including unchecked items, so changing the
|
|
423
|
+
returned array or its objects does not change the rendered list:
|
|
424
|
+
|
|
425
|
+
```js
|
|
426
|
+
$('#features').slice()
|
|
427
|
+
// [
|
|
428
|
+
// { value: 'Search', checked: true },
|
|
429
|
+
// { value: 'Notifications', checked: false },
|
|
430
|
+
// ]
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
`demos/todo.md` and `demos/todo-zh.md` are runnable Todo examples that
|
|
434
|
+
demonstrate `.push()`,
|
|
435
|
+
`.splice()`, `.slice()`, and `{ value, checked }` snapshots using editable text
|
|
436
|
+
controls instead of `prompt()` dialogs. Materialize one in the current
|
|
437
|
+
directory with its demo flag, or open the source-tree copy directly:
|
|
438
|
+
|
|
439
|
+
```sh
|
|
440
|
+
bun src/index.js --demo-todo
|
|
441
|
+
bun src/index.js --demo-todo-zh
|
|
442
|
+
bun src/index.js demos/todo.md
|
|
443
|
+
bun src/index.js --wui demos/todo-zh.md
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
The bundled `demos/select.md` is a multilevel runnable example. Use `--demo-select`
|
|
310
447
|
to write it into the current directory when missing and open it in the TUI, or
|
|
311
448
|
open it explicitly in either interface:
|
|
312
449
|
|
|
313
450
|
```sh
|
|
314
451
|
bun src/index.js --demo-select
|
|
315
|
-
bun src/index.js select.md
|
|
316
|
-
bun src/index.js --wui select.md
|
|
452
|
+
bun src/index.js demos/select.md
|
|
453
|
+
bun src/index.js --wui demos/select.md
|
|
317
454
|
```
|
|
318
455
|
|
|
319
456
|
The available selector methods are:
|
|
@@ -321,23 +458,29 @@ The available selector methods are:
|
|
|
321
458
|
| Method | TUI | WUI |
|
|
322
459
|
| --- | --- | --- |
|
|
323
460
|
| `.val()` | Read text blocks or heading task-list values. | Read textareas/controls or heading task-list values. |
|
|
324
|
-
| `.val(value)` | Replace text-block contents
|
|
461
|
+
| `.val(value)` | Replace text-block contents, resize multiline values, and record Undo/Redo history. | Set textarea/control values and resize textareas. |
|
|
325
462
|
| `.html()` | Return a selected heading's inner HTML. | Return any successfully selected DOM element's `innerHTML`. |
|
|
326
463
|
| `.line()` | Return a heading's current 1-based TUI row, or `0` if missing. | Not available. |
|
|
464
|
+
| `.push(...items)` | Append unchecked strings or `{ value, checked }` task items; return the new direct-item count. | Same. |
|
|
465
|
+
| `.pop()` | Remove and return the last direct task item's label, or `undefined`. | Same. |
|
|
466
|
+
| `.shift()` | Remove and return the first direct task item's label, or `undefined`. | Same. |
|
|
467
|
+
| `.unshift(...items)` | Prepend unchecked strings or `{ value, checked }` task items; return the new direct-item count. | Same. |
|
|
468
|
+
| `.splice(start, deleteCount, ...items)` | Remove and insert direct task items; return the removed labels as an array. | Same. |
|
|
469
|
+
| `.slice(start, end)` | Return `{ value, checked }` snapshots without changing the direct task items. | Same. |
|
|
327
470
|
|
|
328
471
|
TUI heading rows are recalculated after text-block rows are added, removed, or
|
|
329
472
|
replaced with multiline `.val(value)` content.
|
|
330
473
|
|
|
331
|
-
The
|
|
474
|
+
The 3 UI building blocks are:
|
|
332
475
|
|
|
333
|
-
- Regular Markdown provides headings, text, lists, task checkboxes, code, and
|
|
476
|
+
- 1. `Regular Markdown` provides headings, text, lists, task checkboxes, code, and
|
|
334
477
|
links.
|
|
335
|
-
-
|
|
478
|
+
- 2. `js front` block contains UI code. Exported functions can use
|
|
336
479
|
`alert`, `confirm`, `prompt`, and the generated `rpc` client.
|
|
337
|
-
|
|
338
|
-
The terminal UI awaits it before closing an `mdcui` buffer.
|
|
339
|
-
`mdcui` buffers close without a save prompt.
|
|
340
|
-
-
|
|
480
|
+
* A front module may export `async function onMdcuiExit({ reason, path, $ })`.
|
|
481
|
+
* The terminal UI awaits it before closing an `mdcui` buffer.
|
|
482
|
+
* Modified `mdcui` buffers close without a save prompt.
|
|
483
|
+
- 3. `js back` block exports trusted backend functions. In the browser WUI,
|
|
341
484
|
`rpc` publishes only exported functions whose exported names do not start
|
|
342
485
|
with `_`. Call a published function from the front end with
|
|
343
486
|
`await rpc.functionName(arg1, arg2)`.
|
|
@@ -398,6 +541,19 @@ The terminal automatically reflows the Markdown when its width changes.
|
|
|
398
541
|
Only `javascript:` links execute in the TUI; ordinary web links behave as
|
|
399
542
|
normal links in the browser.
|
|
400
543
|
|
|
544
|
+
Local Markdown images are displayed automatically in terminals that support
|
|
545
|
+
the Kitty graphics protocol. Relative image paths are resolved from the
|
|
546
|
+
Markdown file's directory. jsmdcui reads the image dimensions, reserves the
|
|
547
|
+
corresponding terminal rows, and updates the placement when the document is
|
|
548
|
+
scrolled, resized, or shown in a split pane. Unsupported or missing images, as
|
|
549
|
+
well as remote images not authorized with `--allow-url`, retain Bun's normal
|
|
550
|
+
linked `📷` fallback. To download trusted remote Markdown and display its
|
|
551
|
+
supported HTTP(S) images with Kitty graphics, combine the options:
|
|
552
|
+
|
|
553
|
+
```sh
|
|
554
|
+
bun src/index.js --kitty --allow-url https://example.com/app.md
|
|
555
|
+
```
|
|
556
|
+
|
|
401
557
|
## Browser interaction
|
|
402
558
|
|
|
403
559
|
The WUI uses normal browser mouse and keyboard behavior. Clicking a
|
|
@@ -407,15 +563,35 @@ its associated text. `alert`, `confirm`, and `prompt` use the browser's built-in
|
|
|
407
563
|
dialogs. Checkbox changes exist only in the current page: refreshing does not
|
|
408
564
|
preserve them and does not update the Markdown file.
|
|
409
565
|
|
|
410
|
-
The WUI
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
566
|
+
The WUI first tries port `3000` and accepts connections through the machine's
|
|
567
|
+
available network interfaces. If port `3000` is already in use, the operating
|
|
568
|
+
system selects an available port instead; the printed URL always contains the
|
|
569
|
+
actual port. Set `PORT` to request another fixed port. The printed `localhost`
|
|
570
|
+
URL is for the same machine. From another device on the same network, replace
|
|
571
|
+
`localhost` with the server machine's IP address and keep the printed port and
|
|
572
|
+
full path.
|
|
414
573
|
|
|
415
574
|
Each server start prints a new random path. The old URL stops working after the
|
|
416
575
|
server is stopped or restarted. Keep the process running while using the page,
|
|
417
576
|
and press `Ctrl-C` in its terminal to stop it.
|
|
418
577
|
|
|
578
|
+
## Editing Markdown source
|
|
579
|
+
|
|
580
|
+
From a cloned repository, use the `edit` launcher to open a Markdown file as
|
|
581
|
+
ordinary editable UTF-8 source instead of rendering it as a Markdown UI:
|
|
582
|
+
|
|
583
|
+
```sh
|
|
584
|
+
bun ./edit app.md
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
This is equivalent to:
|
|
588
|
+
|
|
589
|
+
```sh
|
|
590
|
+
bun src/index.js --edit app.md
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Additional file and cursor arguments are forwarded unchanged.
|
|
594
|
+
|
|
419
595
|
## Generated files
|
|
420
596
|
|
|
421
597
|
Opening a local Markdown UI generates these files beside the source file:
|
|
@@ -431,6 +607,13 @@ app.md-server.js
|
|
|
431
607
|
They are regenerated from `app.md`, so edit the Markdown source rather than the
|
|
432
608
|
generated files. The source directory must be writable.
|
|
433
609
|
|
|
610
|
+
From the project directory, remove generated `*.md.*` and `*.md-*` companion
|
|
611
|
+
files while keeping the Markdown source files:
|
|
612
|
+
|
|
613
|
+
```sh
|
|
614
|
+
bun ./clean.sh
|
|
615
|
+
```
|
|
616
|
+
|
|
434
617
|
### Generated heading sections
|
|
435
618
|
|
|
436
619
|
WUI output wraps each h1-h6 and its content in a hierarchical `<section>`.
|
package/clean.sh
ADDED
package/demo.resized.jpg
ADDED
|
Binary file
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env jsmdcui
|
|
2
|
+
|
|
2
3
|
# Bun.Image Processor
|
|
3
4
|
|
|
4
5
|
Paste the path to a local image below (for example, `/home/me/photo.jpg`). The output file will be written next to the source as `original.resized.jpg` or `original.resized.png`; the source image will not be overwritten.
|
package/demos/maze.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
```text#character
|
|
2
|
+
🧱😀🧱🧱🧱🧱🧱🧱🧱🧱🧱🧱
|
|
3
|
+
🧱 🧱 🧱
|
|
4
|
+
🧱 🧱🧱🧱 🧱 🧱🧱🧱🧱
|
|
5
|
+
🧱 🧱 🧱 🧱
|
|
6
|
+
🧱🧱🧱 🧱🧱🧱🧱🧱 🧱🧱
|
|
7
|
+
🧱 🧱 🧱 🧱
|
|
8
|
+
🧱 🧱🧱🧱 🧱 🧱 🧱🧱
|
|
9
|
+
🧱 🧱 🧱 🧱
|
|
10
|
+
🧱 🧱🧱🧱🧱🧱🧱🧱 🧱🧱
|
|
11
|
+
🧱 🧱 🧱
|
|
12
|
+
🧱 🧱 🧱🧱🧱 🧱🧱🧱🧱
|
|
13
|
+
🧱 🧱 🧱
|
|
14
|
+
🧱🧱🧱🧱🧱🧱🧱🧱🧱 🧱🧱
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```text#controls @keydown.prevent="handle(event)"
|
|
18
|
+
Put the cursor here, then press:
|
|
19
|
+
將遊標放在這裡,然後按:
|
|
20
|
+
|
|
21
|
+
L't half of QWERTY ← move left
|
|
22
|
+
R't half of QWERTY → move right
|
|
23
|
+
T / Y ↑ move up
|
|
24
|
+
Space ↓ move down
|
|
25
|
+
|
|
26
|
+
鍵盤左半邊 ← 向左移動
|
|
27
|
+
鍵盤右半邊 → 向右移動
|
|
28
|
+
T / Y 向上移動
|
|
29
|
+
空白鍵向下移動
|
|
30
|
+
|
|
31
|
+
Arrow keys ←↑→↓ / 正常方向移動
|
|
32
|
+
Ctrl-R Reset maze / 重新開始
|
|
33
|
+
|
|
34
|
+
Do not hit 🧱 / 不能撞牆
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```text#last-key
|
|
38
|
+
Waiting for input / 等待輸入
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
[🔄 Reset maze / 重新開始](javascript:reset())
|
|
42
|
+
|
|
43
|
+
```js front
|
|
44
|
+
const LEFT_KEYS = new Set([
|
|
45
|
+
...'`12345qwerasdfgzxcvb',
|
|
46
|
+
'escape', 'tab',
|
|
47
|
+
]);
|
|
48
|
+
const RIGHT_KEYS = new Set([
|
|
49
|
+
...'67890-=uiop[]\\hjkl;\'nm,./',
|
|
50
|
+
'backspace', 'enter', 'delete',
|
|
51
|
+
'home', 'end', 'pageup', 'pagedown',
|
|
52
|
+
]);
|
|
53
|
+
const MAZE = [
|
|
54
|
+
'# ##########',
|
|
55
|
+
'# # #',
|
|
56
|
+
'# ### # ####',
|
|
57
|
+
'# # # #',
|
|
58
|
+
'### ##### ##',
|
|
59
|
+
'# # # #',
|
|
60
|
+
'# ### # # ##',
|
|
61
|
+
'# # # #',
|
|
62
|
+
'# ####### ##',
|
|
63
|
+
'# # #',
|
|
64
|
+
'# # ### ####',
|
|
65
|
+
'# # #',
|
|
66
|
+
'######### ##',
|
|
67
|
+
];
|
|
68
|
+
const GOAL = { row: MAZE.length - 1, col: 9 };
|
|
69
|
+
const player = { row: 0, col: 1 };
|
|
70
|
+
let completed = false;
|
|
71
|
+
|
|
72
|
+
function drawMaze() {
|
|
73
|
+
const screen = MAZE.map((line, row) => [...line].map((cell, col) => {
|
|
74
|
+
if (row === player.row && col === player.col) return '😀';
|
|
75
|
+
return cell === '#' ? '🧱' : ' ';
|
|
76
|
+
}).join('')).join('\n');
|
|
77
|
+
$('#character').val(screen);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function reset() {
|
|
81
|
+
player.row = 0;
|
|
82
|
+
player.col = 1;
|
|
83
|
+
completed = false;
|
|
84
|
+
drawMaze();
|
|
85
|
+
$('#last-key').val('Waiting for input / 等待輸入');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function handle(event) {
|
|
89
|
+
const key = String(event.key ?? '').toLowerCase();
|
|
90
|
+
if (event.ctrlKey && key === 'r') {
|
|
91
|
+
reset();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (completed) return;
|
|
95
|
+
|
|
96
|
+
let row = player.row;
|
|
97
|
+
let col = player.col;
|
|
98
|
+
let direction;
|
|
99
|
+
|
|
100
|
+
if (key === 't' || key === 'y' || key === 'arrowup') {
|
|
101
|
+
row--;
|
|
102
|
+
direction = 'up / 上';
|
|
103
|
+
} else if (key === ' ' || key === 'arrowdown') {
|
|
104
|
+
row++;
|
|
105
|
+
direction = 'down / 下';
|
|
106
|
+
} else if (key === 'arrowleft' || LEFT_KEYS.has(key)) {
|
|
107
|
+
col--;
|
|
108
|
+
direction = 'left / 左';
|
|
109
|
+
} else if (key === 'arrowright' || RIGHT_KEYS.has(key)) {
|
|
110
|
+
col++;
|
|
111
|
+
direction = 'right / 右';
|
|
112
|
+
} else {
|
|
113
|
+
$('#last-key').val(`Ignored / 忽略:${event.key}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (MAZE[row]?.[col] !== ' ') {
|
|
118
|
+
$('#last-key').val(`${event.key === ' ' ? 'Space' : event.key} → 🧱 Wall / 撞牆`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
player.row = row;
|
|
123
|
+
player.col = col;
|
|
124
|
+
drawMaze();
|
|
125
|
+
|
|
126
|
+
if (row === GOAL.row && col === GOAL.col) {
|
|
127
|
+
completed = true;
|
|
128
|
+
$('#last-key').val('🎉🏆🌟 Escaped the maze! / 成功走出迷宮!🌟🏆🎉');
|
|
129
|
+
alert([
|
|
130
|
+
'🎆🎇🎉 恭 喜 過 關 ! 🎉🎇🎆',
|
|
131
|
+
'',
|
|
132
|
+
'🥳 你成功逃出迷宮了! 🥳',
|
|
133
|
+
'👑 🏆 ⭐ 🌟 💫 🌈 🦄',
|
|
134
|
+
'👏👏👏 AMAZING! 👏👏👏',
|
|
135
|
+
'🎈 🎊 🎁 🪩 🎁 🎊 🎈',
|
|
136
|
+
'',
|
|
137
|
+
'🚀 ESCAPED THE MAZE! 🚀',
|
|
138
|
+
].join('\n'));
|
|
139
|
+
} else {
|
|
140
|
+
const label = event.key === ' ' ? 'Space' : event.key;
|
|
141
|
+
$('#last-key').val(`${label} → ${direction} (${row}, ${col})`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|