jsmdcui 0.3.0 → 0.5.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 +137 -0
- package/README.md +56 -2
- package/package.json +1 -1
- package/runmd.mjs +71 -7
- package/runtime/help/help.md +56 -2
- package/runtime/syntax/markdown.yaml +12 -0
- package/src/buffer/backup.js +8 -3
- package/src/config/config.js +3 -0
- package/src/config/defaults.js +2 -2
- package/src/cui/rpc.mjs +137 -2
- package/src/index.js +301 -47
- package/src/plugins/js-bridge.js +168 -0
- package/testapp.md +23 -0
- package/tests/backup.test.js +25 -0
- package/tests/cat-markdown.test.js +79 -0
- package/tests/config-settings.test.js +52 -0
- package/tests/demo.test.js +24 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,143 @@
|
|
|
2
2
|
|
|
3
3
|
All notable user-visible changes to jsmdcui are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.5.0] - 2026-07-16
|
|
6
|
+
|
|
7
|
+
This update adds cross-environment form-like text blocks and a small
|
|
8
|
+
jQuery-style selector API, while keeping the same Markdown source usable in
|
|
9
|
+
both the terminal and browser interfaces.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Add a minimal global `$` API to terminal frontend evaluation, JavaScript
|
|
14
|
+
plugins, generated browser frontend modules, and `javascript:` links.
|
|
15
|
+
- Support tag, ID, class, and combined selectors such as `$('text')`,
|
|
16
|
+
`$('#answer')`, `$('.field')`, and `$('text#answer.field')`.
|
|
17
|
+
- Add `.val()` and `.val(value)` for reading and replacing block contents.
|
|
18
|
+
Getters always return a string and use an empty string for missing elements,
|
|
19
|
+
invalid selectors, or lookup errors. Setters support chaining and resize
|
|
20
|
+
terminal blocks to fit multiline values.
|
|
21
|
+
- Recognize both raw Markdown fenced blocks and rendered Bun ANSI blocks,
|
|
22
|
+
including ASCII and Unicode frame characters.
|
|
23
|
+
- Add editable terminal `text` blocks. Content after the protected `│ ` or
|
|
24
|
+
`| ` prefix can be edited, while frame characters, line joins, inserted
|
|
25
|
+
newlines, and multiline pastes remain protected.
|
|
26
|
+
- Add interactive terminal text-block resizing. Activating the lower-left
|
|
27
|
+
frame corner adds an empty content row; activating the upper-left corner
|
|
28
|
+
removes only a trailing empty row and never deletes non-empty content.
|
|
29
|
+
- Add the optional frontend lifecycle callback
|
|
30
|
+
`onMdcuiExit({ reason, path, $ })`. The terminal awaits it before closing an
|
|
31
|
+
mdcui buffer and invokes it at most once per buffer.
|
|
32
|
+
- Add Markdown syntax completions for fenced block language identifiers.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Convert `text` and `textarea` fenced blocks into native browser
|
|
37
|
+
`<textarea>` elements while preserving their ID, classes, original language
|
|
38
|
+
metadata, and selector compatibility.
|
|
39
|
+
- Prefer native `document.querySelector()` in the browser `$` implementation,
|
|
40
|
+
then fall back to mdcui metadata and the original `pre > code` structure.
|
|
41
|
+
- Automatically wrap and resize generated browser textareas on page load,
|
|
42
|
+
input, `.val(value)`, and window resize. Initial `rows` and `cols` are
|
|
43
|
+
derived from the Markdown content.
|
|
44
|
+
- Generate complete HTML5 documents for WUI output, including `<!doctype
|
|
45
|
+
html>`, UTF-8 metadata, a responsive viewport, a document title, and
|
|
46
|
+
`lang="zh-TW"`.
|
|
47
|
+
- Enable `softwrap` by default.
|
|
48
|
+
- Close modified mdcui buffers without displaying a save prompt. Applications
|
|
49
|
+
can use `onMdcuiExit` to collect edited field values or call backend RPC
|
|
50
|
+
functions before closing.
|
|
51
|
+
- Update the bundled example with editable text fields, selector API examples,
|
|
52
|
+
answer validation, and manual text-box resizing instructions.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- Keep terminal text-block frame prefixes intact during editing and prevent
|
|
57
|
+
Delete at the end of a content row from merging it with the next row.
|
|
58
|
+
- Preserve browser textarea IDs and classes during Markdown-to-HTML
|
|
59
|
+
conversion, including compatibility metadata for original `text` selectors.
|
|
60
|
+
- Keep README and built-in help lifecycle documentation synchronized.
|
|
61
|
+
|
|
62
|
+
## [0.4.0] - 2026-07-16
|
|
63
|
+
|
|
64
|
+
This release separates executable Markdown UI views from ordinary editable
|
|
65
|
+
buffers, makes encoding behavior predictable across opens, tabs, splits, and
|
|
66
|
+
reopens, and adds explicit demo and trusted-remote execution modes.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- Add `--demo`. It outputs and overwrites `./testapp.md`, opens it as a terminal
|
|
71
|
+
Markdown UI, and writes the five generated companion files beside it.
|
|
72
|
+
- Add `--allow-url` for trusted remote Markdown apps. It downloads an HTTP(S)
|
|
73
|
+
resource into the current directory, opens the downloaded copy through the
|
|
74
|
+
normal local Markdown UI pipeline, writes the five generated companion files,
|
|
75
|
+
and permits its embedded frontend and backend code to run.
|
|
76
|
+
- Add regression coverage for global encoding handling, mdcui backup isolation,
|
|
77
|
+
the bundled demo workflow, implicit Markdown rendering, and explicit UTF-8
|
|
78
|
+
editing.
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- Treat `mdcui` buffers as independent, derived, read-only views. Every mdcui
|
|
83
|
+
open creates a new buffer instead of entering the same-path editable-buffer
|
|
84
|
+
cache.
|
|
85
|
+
- Continue sharing ordinary buffers opened from the same absolute path.
|
|
86
|
+
UTF-8 and other non-mdcui views share content, modification state, saves, and
|
|
87
|
+
reopens.
|
|
88
|
+
- Re-evaluate buffer sharing after `reopen`:
|
|
89
|
+
- reopening an mdcui view as a normal encoding joins an existing same-path
|
|
90
|
+
editable buffer, or registers itself when no such buffer exists;
|
|
91
|
+
- reopening a shared editable view as mdcui detaches only the current pane
|
|
92
|
+
into a new mdcui buffer and leaves the other shared panes unchanged.
|
|
93
|
+
- Make interactive `open`, `tab`, `vsplit`, and `hsplit` operations perform
|
|
94
|
+
fresh `.md` automatic detection for files that are not already represented
|
|
95
|
+
by a normal cached buffer.
|
|
96
|
+
- Ignore a top-level global `encoding` value read from `settings.json`.
|
|
97
|
+
Encoding is selected per invocation or buffer; command-line encoding options
|
|
98
|
+
remain effective for the current run and are not persisted.
|
|
99
|
+
- Organize `--testapp.md` and `--demo` under a dedicated `Demo` section in
|
|
100
|
+
`--help`, and document commands that overwrite source or generated files.
|
|
101
|
+
|
|
102
|
+
### Fixed
|
|
103
|
+
|
|
104
|
+
- Fix `.md` files opened from inside the editor failing to enter mdcui mode.
|
|
105
|
+
- Fix encoding changes leaking between independent mdcui tabs.
|
|
106
|
+
- Fix `reopen mdcui` mutating every pane that shared the original editable
|
|
107
|
+
buffer.
|
|
108
|
+
- Fix mdcui-to-UTF-8 reopens remaining outside the normal same-path buffer
|
|
109
|
+
cache.
|
|
110
|
+
- Delay applying an explicit reopen encoding until the reopen is confirmed, so
|
|
111
|
+
canceling the save prompt does not alter the shared buffer.
|
|
112
|
+
|
|
113
|
+
### Security
|
|
114
|
+
|
|
115
|
+
- Exclude mdcui buffers completely from crash-recovery backups: they do not
|
|
116
|
+
create, apply, prompt for, or remove backups. This prevents a derived
|
|
117
|
+
read-only view from consuming or deleting a backup belonging to an editable
|
|
118
|
+
view of the same Markdown source.
|
|
119
|
+
- Remote HTTP(S) Markdown remains non-executable and does not write companion
|
|
120
|
+
files by default.
|
|
121
|
+
- `--allow-url` is an explicit trust boundary. It writes downloaded content and
|
|
122
|
+
generated JavaScript/HTML files into the current directory, may overwrite
|
|
123
|
+
files with the same names, and runs code from the remote document with the
|
|
124
|
+
permissions of the jsmdcui process. Use it only with trusted URLs and in a
|
|
125
|
+
directory where those writes are safe.
|
|
126
|
+
|
|
127
|
+
## [0.3.1] - 2026-07-16
|
|
128
|
+
|
|
129
|
+
### Added
|
|
130
|
+
|
|
131
|
+
- Add `--edit` to open Markdown files as editable UTF-8 source instead of
|
|
132
|
+
automatically entering mdcui mode.
|
|
133
|
+
- Add `--cat` regression coverage for implicit mdcui rendering and explicit
|
|
134
|
+
UTF-8 overrides.
|
|
135
|
+
|
|
136
|
+
### Fixed
|
|
137
|
+
|
|
138
|
+
- Prevent an explicit encoding choice from being replaced by automatic mdcui
|
|
139
|
+
detection.
|
|
140
|
+
- Correct terminal raw-mode handling around log and prompt output.
|
|
141
|
+
|
|
5
142
|
## [0.3.0] - 2026-07-16
|
|
6
143
|
|
|
7
144
|
Initial usable release, based on the bunmicro terminal editor.
|
package/README.md
CHANGED
|
@@ -78,8 +78,11 @@ The command table below uses the cloned-source form. If you use npx, replace
|
|
|
78
78
|
| Command | Result |
|
|
79
79
|
| --- | --- |
|
|
80
80
|
| `bun src/index.js app.md` | Render `app.md` as a read-only terminal UI and write five generated files beside it. |
|
|
81
|
+
| `bun src/index.js --edit app.md` | Open `app.md` as editable UTF-8 source, overriding automatic Markdown UI detection. |
|
|
81
82
|
| `bun src/index.js --cat app.md` | Render the terminal version to stdout, write five generated files beside it, and exit. |
|
|
82
83
|
| `bun src/index.js --testapp.md` | Write the bundled `testapp.md` source to stdout and exit. |
|
|
84
|
+
| `bun src/index.js --demo` | Outputs & overwrites `./testapp.md`, opens it in the terminal UI, and writes 5 generated files beside it. |
|
|
85
|
+
| `bun src/index.js --allow-url URL.md` | Download HTTP(S) Markdown to the current directory, write 5 generated files, and allow its embedded code to run. Only use trusted URLs. |
|
|
83
86
|
| `bun src/index.js --wui` | Use local `testapp.md` when present, otherwise use the bundled demo; write five generated files in the current directory, then print and serve a random URL. |
|
|
84
87
|
| `bun src/index.js --wui app.md` | Write five generated files beside `app.md`, then print and serve a random URL. |
|
|
85
88
|
| `PORT=8080 bun src/index.js --wui app.md` | Start the browser UI on another port. |
|
|
@@ -97,6 +100,11 @@ Create `app.md`:
|
|
|
97
100
|
|
|
98
101
|
- [Say hello](javascript:sayHello())
|
|
99
102
|
- [Get server time](javascript:showServerTime())
|
|
103
|
+
- [Update text box](javascript:updateText())
|
|
104
|
+
|
|
105
|
+
```text#myid.myclass
|
|
106
|
+
Editable in both TUI and WUI
|
|
107
|
+
```
|
|
100
108
|
|
|
101
109
|
```js front
|
|
102
110
|
export function sayHello() {
|
|
@@ -108,6 +116,10 @@ export async function showServerTime() {
|
|
|
108
116
|
const time = await rpc.getServerTime();
|
|
109
117
|
alert(time);
|
|
110
118
|
}
|
|
119
|
+
|
|
120
|
+
export function updateText() {
|
|
121
|
+
$('#myid').val($('.myclass').val() + ' ✓');
|
|
122
|
+
}
|
|
111
123
|
```
|
|
112
124
|
|
|
113
125
|
```js back
|
|
@@ -124,12 +136,37 @@ bun src/index.js app.md
|
|
|
124
136
|
bun src/index.js --wui app.md
|
|
125
137
|
```
|
|
126
138
|
|
|
139
|
+
### Text blocks
|
|
140
|
+
|
|
141
|
+
Both `text` and `textarea` fenced blocks define editable text fields:
|
|
142
|
+
|
|
143
|
+
````md
|
|
144
|
+
```text#message.note
|
|
145
|
+
Initial value
|
|
146
|
+
```
|
|
147
|
+
````
|
|
148
|
+
|
|
149
|
+
The same Markdown works in both interfaces. In the browser WUI it becomes a
|
|
150
|
+
native `<textarea>` with the declared ID and classes. Long text wraps
|
|
151
|
+
automatically, and the field height is recalculated when the user types, the
|
|
152
|
+
window is resized, or frontend code calls `.val(value)`.
|
|
153
|
+
|
|
154
|
+
In the terminal TUI, only content after the protected `│ ` or `| ` prefix can
|
|
155
|
+
be edited. The frame prefix cannot be deleted, Enter cannot insert a newline,
|
|
156
|
+
Delete at the end of a row cannot join the next row, and multiline paste is
|
|
157
|
+
blocked. Activate the lower-left frame corner to add a row. Activate the
|
|
158
|
+
upper-left frame corner to remove the trailing row only when it is empty;
|
|
159
|
+
non-empty content is never removed.
|
|
160
|
+
|
|
127
161
|
The three UI building blocks are:
|
|
128
162
|
|
|
129
163
|
- Regular Markdown provides headings, text, lists, task checkboxes, code, and
|
|
130
164
|
links.
|
|
131
165
|
- A `js front` block contains UI code. Exported functions can use
|
|
132
166
|
`alert`, `confirm`, `prompt`, and the generated `rpc` client.
|
|
167
|
+
- A front module may export `async function onMdcuiExit({ reason, path, $ })`.
|
|
168
|
+
The terminal UI awaits it before closing an `mdcui` buffer. Modified
|
|
169
|
+
`mdcui` buffers close without a save prompt.
|
|
133
170
|
- A `js back` block exports trusted backend functions. In the browser WUI,
|
|
134
171
|
`rpc` publishes only exported functions whose exported names do not start
|
|
135
172
|
with `_`. Call a published function from the front end with
|
|
@@ -146,6 +183,22 @@ Use a `javascript:` Markdown link to run front-end code:
|
|
|
146
183
|
[Button label](javascript:exportedFunction())
|
|
147
184
|
```
|
|
148
185
|
|
|
186
|
+
Use `onMdcuiExit` when a terminal Markdown app needs to submit or otherwise
|
|
187
|
+
process edited fields before it closes:
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
export async function onMdcuiExit({ reason, path, $ }) {
|
|
191
|
+
await rpc.saveDraft({
|
|
192
|
+
reason,
|
|
193
|
+
path,
|
|
194
|
+
message: $('#message').val(),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The callback is optional, may be asynchronous, and is called at most once for
|
|
200
|
+
each mdcui buffer.
|
|
201
|
+
|
|
149
202
|
The front and back code blocks are extracted and are not shown in the rendered
|
|
150
203
|
UI. In the terminal, `rpc` calls the generated backend module directly. In the
|
|
151
204
|
browser, it sends the call to the relative `rpc` endpoint under the printed
|
|
@@ -153,8 +206,9 @@ UUID URL (`/<uuid>/rpc`).
|
|
|
153
206
|
|
|
154
207
|
## Terminal interaction
|
|
155
208
|
|
|
156
|
-
Markdown files automatically use `mdcui` mode.
|
|
157
|
-
|
|
209
|
+
Markdown files automatically use `mdcui` mode. Most rendered content remains
|
|
210
|
+
protected, while `text` block content rows can be edited. Navigation,
|
|
211
|
+
selection, search, and copy remain available.
|
|
158
212
|
|
|
159
213
|
| Input | Result |
|
|
160
214
|
| --- | --- |
|
package/package.json
CHANGED
package/runmd.mjs
CHANGED
|
@@ -17,6 +17,7 @@ const TEST_COL=5
|
|
|
17
17
|
|
|
18
18
|
function logWroteFile(label,path)
|
|
19
19
|
{
|
|
20
|
+
if(!process.stdin.isRaw)
|
|
20
21
|
csl(mda(`- Wrote to ${label} file: ${path}`))
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -188,6 +189,54 @@ export function createTui(md,TERMINAL_WIDTH=30) // ANSI Colors
|
|
|
188
189
|
)
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
function parseWuiControlIdentity(info)
|
|
193
|
+
{
|
|
194
|
+
const match = String(info ?? "").match(/^([A-Za-z_][\w:-]*)(?:#([A-Za-z_][\w:-]*))?((?:\.[A-Za-z_][\w:-]*)*)$/);
|
|
195
|
+
if (!match || !["text", "textarea"].includes(match[1])) return null;
|
|
196
|
+
return {
|
|
197
|
+
tag: match[1],
|
|
198
|
+
id: match[2] || "",
|
|
199
|
+
classes: match[3] ? match[3].slice(1).split(".") : [],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function escapeHtmlAttribute(value)
|
|
204
|
+
{
|
|
205
|
+
return String(value)
|
|
206
|
+
.replaceAll("&", "&")
|
|
207
|
+
.replaceAll('"', """)
|
|
208
|
+
.replaceAll("<", "<")
|
|
209
|
+
.replaceAll(">", ">");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function convertWuiTextareas(html)
|
|
213
|
+
{
|
|
214
|
+
return String(html).replace(
|
|
215
|
+
/<pre><code class="language-([^"]+)">([^]*?)<\/code><\/pre>/g,
|
|
216
|
+
(whole, info, content) => {
|
|
217
|
+
const identity = parseWuiControlIdentity(info);
|
|
218
|
+
if (!identity) return whole;
|
|
219
|
+
const value = content.replace(/\n$/, "");
|
|
220
|
+
const contentLines = value.split("\n");
|
|
221
|
+
const cols = Math.max(1, ...contentLines.map(line => [...line].length));
|
|
222
|
+
const attrs = [
|
|
223
|
+
`data-mdcui-tag="${identity.tag}"`,
|
|
224
|
+
`data-mdcui-language="${escapeHtmlAttribute(info)}"`,
|
|
225
|
+
identity.id ? `id="${escapeHtmlAttribute(identity.id)}"` : "",
|
|
226
|
+
`class="${escapeHtmlAttribute([
|
|
227
|
+
`language-${info}`,
|
|
228
|
+
...identity.classes,
|
|
229
|
+
].join(" "))}"`,
|
|
230
|
+
`rows="${Math.max(1, contentLines.length)}"`,
|
|
231
|
+
`cols="${cols}"`,
|
|
232
|
+
'wrap="soft"',
|
|
233
|
+
'style="box-sizing:border-box;max-width:100%;width:100%;resize:vertical;overflow-y:hidden"',
|
|
234
|
+
].filter(Boolean).join(" ");
|
|
235
|
+
return `<textarea ${attrs}>${value}</textarea>`;
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
191
240
|
export async function createWui(md,mdpath) // HTML
|
|
192
241
|
{
|
|
193
242
|
|
|
@@ -208,16 +257,31 @@ export async function createWui(md,mdpath) // HTML
|
|
|
208
257
|
'class="task-list-item-checkbox" disabled',
|
|
209
258
|
'class="task-list-item-checkbox"'
|
|
210
259
|
)
|
|
260
|
+
|
|
261
|
+
md = convertWuiTextareas(md)
|
|
211
262
|
|
|
212
263
|
const mdb = path.basename(mdpath);
|
|
213
264
|
|
|
214
|
-
|
|
215
|
-
md
|
|
216
|
-
|
|
217
|
-
<
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
265
|
+
const moduleScript = `<scr`+`ipt type="module" src="./${mdb}.front.js"></scr`+`ipt>`;
|
|
266
|
+
if (!/^\s*<!doctype html>/i.test(md)) {
|
|
267
|
+
md = `<!doctype html>
|
|
268
|
+
<html lang="zh-TW">
|
|
269
|
+
<head>
|
|
270
|
+
<meta charset="utf-8">
|
|
271
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
272
|
+
<title>${escapeHtmlAttribute(mdb)}</title>
|
|
273
|
+
</head>
|
|
274
|
+
<body>
|
|
275
|
+
${md}
|
|
276
|
+
${moduleScript}
|
|
277
|
+
</body>
|
|
278
|
+
</html>
|
|
279
|
+
`;
|
|
280
|
+
} else if (/<\/body\s*>/i.test(md)) {
|
|
281
|
+
md = md.replace(/<\/body\s*>/i, `${moduleScript}\n</body>`);
|
|
282
|
+
} else {
|
|
283
|
+
md += `\n${moduleScript}\n`;
|
|
284
|
+
}
|
|
221
285
|
|
|
222
286
|
|
|
223
287
|
await Bun.write(mdpath+".html",md)
|
package/runtime/help/help.md
CHANGED
|
@@ -78,8 +78,11 @@ The command table below uses the cloned-source form. If you use npx, replace
|
|
|
78
78
|
| Command | Result |
|
|
79
79
|
| --- | --- |
|
|
80
80
|
| `bun src/index.js app.md` | Render `app.md` as a read-only terminal UI and write five generated files beside it. |
|
|
81
|
+
| `bun src/index.js --edit app.md` | Open `app.md` as editable UTF-8 source, overriding automatic Markdown UI detection. |
|
|
81
82
|
| `bun src/index.js --cat app.md` | Render the terminal version to stdout, write five generated files beside it, and exit. |
|
|
82
83
|
| `bun src/index.js --testapp.md` | Write the bundled `testapp.md` source to stdout and exit. |
|
|
84
|
+
| `bun src/index.js --demo` | Outputs & overwrites `./testapp.md`, opens it in the terminal UI, and writes 5 generated files beside it. |
|
|
85
|
+
| `bun src/index.js --allow-url URL.md` | Download HTTP(S) Markdown to the current directory, write 5 generated files, and allow its embedded code to run. Only use trusted URLs. |
|
|
83
86
|
| `bun src/index.js --wui` | Use local `testapp.md` when present, otherwise use the bundled demo; write five generated files in the current directory, then print and serve a random URL. |
|
|
84
87
|
| `bun src/index.js --wui app.md` | Write five generated files beside `app.md`, then print and serve a random URL. |
|
|
85
88
|
| `PORT=8080 bun src/index.js --wui app.md` | Start the browser UI on another port. |
|
|
@@ -97,6 +100,11 @@ Create `app.md`:
|
|
|
97
100
|
|
|
98
101
|
- [Say hello](javascript:sayHello())
|
|
99
102
|
- [Get server time](javascript:showServerTime())
|
|
103
|
+
- [Update text box](javascript:updateText())
|
|
104
|
+
|
|
105
|
+
```text#myid.myclass
|
|
106
|
+
Editable in both TUI and WUI
|
|
107
|
+
```
|
|
100
108
|
|
|
101
109
|
```js front
|
|
102
110
|
export function sayHello() {
|
|
@@ -108,6 +116,10 @@ export async function showServerTime() {
|
|
|
108
116
|
const time = await rpc.getServerTime();
|
|
109
117
|
alert(time);
|
|
110
118
|
}
|
|
119
|
+
|
|
120
|
+
export function updateText() {
|
|
121
|
+
$('#myid').val($('.myclass').val() + ' ✓');
|
|
122
|
+
}
|
|
111
123
|
```
|
|
112
124
|
|
|
113
125
|
```js back
|
|
@@ -124,12 +136,37 @@ bun src/index.js app.md
|
|
|
124
136
|
bun src/index.js --wui app.md
|
|
125
137
|
```
|
|
126
138
|
|
|
139
|
+
### Text blocks
|
|
140
|
+
|
|
141
|
+
Both `text` and `textarea` fenced blocks define editable text fields:
|
|
142
|
+
|
|
143
|
+
````md
|
|
144
|
+
```text#message.note
|
|
145
|
+
Initial value
|
|
146
|
+
```
|
|
147
|
+
````
|
|
148
|
+
|
|
149
|
+
The same Markdown works in both interfaces. In the browser WUI it becomes a
|
|
150
|
+
native `<textarea>` with the declared ID and classes. Long text wraps
|
|
151
|
+
automatically, and the field height is recalculated when the user types, the
|
|
152
|
+
window is resized, or frontend code calls `.val(value)`.
|
|
153
|
+
|
|
154
|
+
In the terminal TUI, only content after the protected `│ ` or `| ` prefix can
|
|
155
|
+
be edited. The frame prefix cannot be deleted, Enter cannot insert a newline,
|
|
156
|
+
Delete at the end of a row cannot join the next row, and multiline paste is
|
|
157
|
+
blocked. Activate the lower-left frame corner to add a row. Activate the
|
|
158
|
+
upper-left frame corner to remove the trailing row only when it is empty;
|
|
159
|
+
non-empty content is never removed.
|
|
160
|
+
|
|
127
161
|
The three UI building blocks are:
|
|
128
162
|
|
|
129
163
|
- Regular Markdown provides headings, text, lists, task checkboxes, code, and
|
|
130
164
|
links.
|
|
131
165
|
- A `js front` block contains UI code. Exported functions can use
|
|
132
166
|
`alert`, `confirm`, `prompt`, and the generated `rpc` client.
|
|
167
|
+
- A front module may export `async function onMdcuiExit({ reason, path, $ })`.
|
|
168
|
+
The terminal UI awaits it before closing an `mdcui` buffer. Modified
|
|
169
|
+
`mdcui` buffers close without a save prompt.
|
|
133
170
|
- A `js back` block exports trusted backend functions. In the browser WUI,
|
|
134
171
|
`rpc` publishes only exported functions whose exported names do not start
|
|
135
172
|
with `_`. Call a published function from the front end with
|
|
@@ -146,6 +183,22 @@ Use a `javascript:` Markdown link to run front-end code:
|
|
|
146
183
|
[Button label](javascript:exportedFunction())
|
|
147
184
|
```
|
|
148
185
|
|
|
186
|
+
Use `onMdcuiExit` when a terminal Markdown app needs to submit or otherwise
|
|
187
|
+
process edited fields before it closes:
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
export async function onMdcuiExit({ reason, path, $ }) {
|
|
191
|
+
await rpc.saveDraft({
|
|
192
|
+
reason,
|
|
193
|
+
path,
|
|
194
|
+
message: $('#message').val(),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The callback is optional, may be asynchronous, and is called at most once for
|
|
200
|
+
each mdcui buffer.
|
|
201
|
+
|
|
149
202
|
The front and back code blocks are extracted and are not shown in the rendered
|
|
150
203
|
UI. In the terminal, `rpc` calls the generated backend module directly. In the
|
|
151
204
|
browser, it sends the call to the relative `rpc` endpoint under the printed
|
|
@@ -153,8 +206,9 @@ UUID URL (`/<uuid>/rpc`).
|
|
|
153
206
|
|
|
154
207
|
## Terminal interaction
|
|
155
208
|
|
|
156
|
-
Markdown files automatically use `mdcui` mode.
|
|
157
|
-
|
|
209
|
+
Markdown files automatically use `mdcui` mode. Most rendered content remains
|
|
210
|
+
protected, while `text` block content rows can be edited. Navigation,
|
|
211
|
+
selection, search, and copy remain available.
|
|
158
212
|
|
|
159
213
|
| Input | Result |
|
|
160
214
|
| --- | --- |
|
|
@@ -47,3 +47,15 @@ rules:
|
|
|
47
47
|
start: "`"
|
|
48
48
|
end: "`"
|
|
49
49
|
rules: []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# keywords
|
|
53
|
+
# javascript alert confirm prompt rpc
|
|
54
|
+
# front back
|
|
55
|
+
# console log error JSON stringify parse
|
|
56
|
+
# async await Promise
|
|
57
|
+
# textarea text password search
|
|
58
|
+
# email url tel number range color
|
|
59
|
+
# date time datetime-local month week
|
|
60
|
+
# checkbox radio file hidden image button submit reset
|
|
61
|
+
|
package/src/buffer/backup.js
CHANGED
|
@@ -5,9 +5,14 @@ import { existsSync, statSync, unlinkSync } from "node:fs";
|
|
|
5
5
|
import { mkdir, writeFile, readFile, rename } from "node:fs/promises";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
+
import { isMdcuiEncoding } from "../runtime/encodings.js";
|
|
8
9
|
|
|
9
10
|
export const BACKUP_SUFFIX = ".micro-backup";
|
|
10
11
|
|
|
12
|
+
function isMdcuiBuffer(buf) {
|
|
13
|
+
return isMdcuiEncoding(buf?.encoding ?? buf?.Settings?.encoding);
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
export function getBackupDir(buf, configDir) {
|
|
12
17
|
const raw = String(buf?.Settings?.backupdir ?? "");
|
|
13
18
|
if (!raw) return join(configDir, "backups");
|
|
@@ -49,7 +54,7 @@ export function determineBackupPath(backupDirPath, absPath) {
|
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export async function writeBackup(buf, configDir, path = buf?.AbsPath ?? buf?.path, { force = false } = {}) {
|
|
52
|
-
if ((!force && !buf?.Settings?.backup) || !path || buf.type !== "default") return false;
|
|
57
|
+
if (isMdcuiBuffer(buf) || (!force && !buf?.Settings?.backup) || !path || buf.type !== "default") return false;
|
|
53
58
|
const dir = getBackupDir(buf, configDir);
|
|
54
59
|
await mkdir(dir, { recursive: true });
|
|
55
60
|
const { name, resolveName } = determineBackupPath(dir, path);
|
|
@@ -66,7 +71,7 @@ export async function writeBackup(buf, configDir, path = buf?.AbsPath ?? buf?.pa
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
export function removeBackup(buf, configDir, path = buf?.AbsPath ?? buf?.path) {
|
|
69
|
-
if (buf?.Settings?.permbackup || buf?._forceKeepBackup) return;
|
|
74
|
+
if (isMdcuiBuffer(buf) || buf?.Settings?.permbackup || buf?._forceKeepBackup) return;
|
|
70
75
|
if (!path || buf.type !== "default") return;
|
|
71
76
|
const dir = getBackupDir(buf, configDir);
|
|
72
77
|
const { name, resolveName } = determineBackupPath(dir, path);
|
|
@@ -77,7 +82,7 @@ export function removeBackup(buf, configDir, path = buf?.AbsPath ?? buf?.path) {
|
|
|
77
82
|
// promptFn(msg) -> Promise<string>
|
|
78
83
|
// Returns { recovered: bool, abort: bool }
|
|
79
84
|
export async function applyBackup(buf, configDir, promptFn) {
|
|
80
|
-
if (!buf?.Settings?.backup || buf?.Settings?.permbackup) return { recovered: false, abort: false };
|
|
85
|
+
if (isMdcuiBuffer(buf) || !buf?.Settings?.backup || buf?.Settings?.permbackup) return { recovered: false, abort: false };
|
|
81
86
|
if (!buf.path || buf.type !== "default") return { recovered: false, abort: false };
|
|
82
87
|
|
|
83
88
|
const dir = getBackupDir(buf, configDir);
|
package/src/config/config.js
CHANGED
|
@@ -27,6 +27,9 @@ export class Config {
|
|
|
27
27
|
const text = await readFile(path, "utf8");
|
|
28
28
|
if (text.trimStart().startsWith("null") || text.trim() === "") return;
|
|
29
29
|
this.parsedSettings = Bun.JSON5.parse(text);
|
|
30
|
+
// jsmdcui chooses the input encoding per invocation/buffer. A persisted
|
|
31
|
+
// global encoding must neither affect startup nor survive the next save.
|
|
32
|
+
delete this.parsedSettings.encoding;
|
|
30
33
|
this.applyParsedSettings(this.parsedSettings);
|
|
31
34
|
}
|
|
32
35
|
|
package/src/config/defaults.js
CHANGED
|
@@ -38,7 +38,7 @@ export const DEFAULT_COMMON_SETTINGS = {
|
|
|
38
38
|
scrollspeed: 2,
|
|
39
39
|
showchars: "",
|
|
40
40
|
smartpaste: true,
|
|
41
|
-
softwrap:
|
|
41
|
+
softwrap: true,
|
|
42
42
|
splitbottom: true,
|
|
43
43
|
splitright: true,
|
|
44
44
|
statusformatl: "$(filename) $(modified)$(overwrite)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
|
|
@@ -98,7 +98,7 @@ export const OPTION_CHOICES = {
|
|
|
98
98
|
|
|
99
99
|
// Settings that are buffer-local only and must never be written to the global config file.
|
|
100
100
|
// Mirrors Go micro's config.LocalSettings.
|
|
101
|
-
export const LOCAL_SETTINGS = new Set(["readonly", "filetype", "fileformat"]);
|
|
101
|
+
export const LOCAL_SETTINGS = new Set(["readonly", "filetype", "fileformat", "encoding"]);
|
|
102
102
|
|
|
103
103
|
export function defaultAllSettings() {
|
|
104
104
|
return { ...DEFAULT_COMMON_SETTINGS, ...DEFAULT_GLOBAL_ONLY_SETTINGS };
|