rapier-html 1.1.51 → 1.1.54
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/BUILD.json +4 -4
- package/README.md +9 -9
- package/package.json +1 -1
- package/page.mjs +24 -5
- package/rapier.html +565 -33
package/BUILD.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "rapier-html",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.54",
|
|
4
4
|
"path": "rapier.html",
|
|
5
|
-
"bytes":
|
|
6
|
-
"sha256": "
|
|
7
|
-
"builtAt": "2026-10-
|
|
5
|
+
"bytes": 1767512,
|
|
6
|
+
"sha256": "25ba82eb3e89dae0820504205c5d0ffd29c1d14b573c0869bd06404a562e5da2",
|
|
7
|
+
"builtAt": "2026-10-03T21:55:34.551Z",
|
|
8
8
|
"node": "v22.22.2",
|
|
9
9
|
"canonical": true,
|
|
10
10
|
"packing": "zopfli",
|
package/README.md
CHANGED
|
@@ -8,20 +8,20 @@ editor, offline, with the document inside.
|
|
|
8
8
|
- `--return` adds a one-use Send back address from the agent door: the person edits offline, presses Send back,
|
|
9
9
|
and the agent reads the returned copy.
|
|
10
10
|
- `--view draw` opens on Draw; `--base original.md` opens on the diff of a proposed change, and the person keeps
|
|
11
|
-
or drops each change.
|
|
11
|
+
or drops each change; `--by <name>` says on that diff who proposed it and when.
|
|
12
12
|
- `wrap` and `unwrap` carry a document into a page and out again, exactly, for programs.
|
|
13
13
|
|
|
14
14
|
Node 22 or newer.
|
|
15
15
|
|
|
16
16
|
```sh
|
|
17
|
-
npx -- rapier-html@1.1.
|
|
18
|
-
npx -- rapier-html@1.1.
|
|
19
|
-
npx -- rapier-html@1.1.
|
|
20
|
-
npx -- rapier-html@1.1.
|
|
21
|
-
npx -- rapier-html@1.1.
|
|
22
|
-
npx -- rapier-html@1.1.
|
|
23
|
-
npx -- rapier-html@1.1.
|
|
24
|
-
npm install rapier-html@1.1.
|
|
17
|
+
npx -- rapier-html@1.1.54 notes.md # the editor, notes.md inside it, written beside the file
|
|
18
|
+
npx -- rapier-html@1.1.54 notes.md --view draw # opens on Draw: sketch and paint, the document behind it
|
|
19
|
+
npx -- rapier-html@1.1.54 notes.md --view notes # opens the Notes library
|
|
20
|
+
npx -- rapier-html@1.1.54 notes.md --drawing sketch.svg # carries a drawing, opened on Draw over the document
|
|
21
|
+
npx -- rapier-html@1.1.54 proposal.md --base original.md # opens on the diff of a proposed change
|
|
22
|
+
npx -- rapier-html@1.1.54 notes.md --return "$RETURN_URL" --return-expires-at "$RETURN_EXPIRES_AT" # Send back
|
|
23
|
+
npx -- rapier-html@1.1.54 notes.md out.html # a named output
|
|
24
|
+
npm install rapier-html@1.1.54 # as a library: wrap, unwrap
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
The first `--` is for npm, so `--help` reaches the command. It never overwrites a file.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-html",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.54",
|
|
4
4
|
"description": "Give a person a document they can read, edit and keep: one command writes one offline HTML file that is the whole Markdown editor with the document inside; draw, paint, review a diff, Send back to the agent.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
package/page.mjs
CHANGED
|
@@ -23,6 +23,9 @@ function decodeCarried(text) {
|
|
|
23
23
|
}
|
|
24
24
|
export {encodeCarried, decodeCarried};
|
|
25
25
|
|
|
26
|
+
const PROPOSED_AT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
27
|
+
const attribute = text => text.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
28
|
+
const unattribute = text => text.replace(/&(quot|lt|gt|amp);/g, (_, e) => ({quot: '"', lt: '<', gt: '>', amp: '&'})[e]);
|
|
26
29
|
function safeName(name, fallback) {
|
|
27
30
|
return String(name || fallback).replace(/[\\/\0"<>&]/g, '-').slice(0, 255) || fallback;
|
|
28
31
|
}
|
|
@@ -45,9 +48,15 @@ export function wrap(pageHtml, text, name, options) {
|
|
|
45
48
|
const drawingName = safeName(opts.drawingName, 'drawing.svg');
|
|
46
49
|
carriedBlocks = '\n' + DRAWING_START + ' data-name="' + drawingName + '">' + encodeCarried(opts.drawing) + END;
|
|
47
50
|
}
|
|
51
|
+
if ((opts.by != null || opts.at != null) && opts.base == null) throw new Error('a proposer needs the base it proposes over');
|
|
48
52
|
if (opts.base != null) {
|
|
49
53
|
if (typeof opts.base !== 'string') throw new Error('the base must be text');
|
|
50
|
-
|
|
54
|
+
// A proposal page (docs/briefs/ledger.md section 3): who proposed it, in the host's own words, and when.
|
|
55
|
+
const by = opts.by == null ? null : String(opts.by).replace(/[\0-\x1f\x7f]/g, ' ').trim().slice(0, 96);
|
|
56
|
+
const at = opts.at == null ? null : String(opts.at);
|
|
57
|
+
if (at != null && !(PROPOSED_AT.test(at) && Number.isFinite(Date.parse(at)))) throw new Error('the time of a proposal is an ISO 8601 UTC timestamp');
|
|
58
|
+
carriedBlocks += '\n' + BASE_START + ' data-name="' + safeName(opts.baseName, docName) + '"' + (by ? ' data-by="' + attribute(by) + '"' : '') +
|
|
59
|
+
(at ? ' data-at="' + at + '"' : '') + '>' + encodeCarried(opts.base) + END;
|
|
51
60
|
}
|
|
52
61
|
// The agent seed goes after the charset (or <head>), once: a page that already carries it, wrapped again, loses its old one.
|
|
53
62
|
const unseeded = unwrap(pageHtml).html.replace(SEARCH_WORDS, '').split(PAGE_SEED + '\n').join('');
|
|
@@ -91,32 +100,36 @@ export function unwrap(pageHtml) {
|
|
|
91
100
|
html = html.slice(0, d) + html.slice(b + END.length);
|
|
92
101
|
if (d > 0 && html[d - 1] === '\n') html = html.slice(0, d - 1) + html.slice(d);
|
|
93
102
|
}
|
|
94
|
-
let base = null, baseName = null;
|
|
103
|
+
let base = null, baseName = null, by = null, at = null;
|
|
95
104
|
const e = html.indexOf(BASE_START);
|
|
96
105
|
if (e >= 0) {
|
|
97
106
|
const open = html.indexOf('>', e), b = html.indexOf(END, open);
|
|
98
107
|
if (open < 0 || b < 0) throw new Error('a carried base without its end');
|
|
99
108
|
baseName = /data-name="([^"]*)"/.exec(html.slice(e, open))?.[1] ?? null;
|
|
109
|
+
const proposer = /data-by="([^"]*)"/.exec(html.slice(e, open))?.[1];
|
|
110
|
+
by = proposer == null ? null : unattribute(proposer);
|
|
111
|
+
at = /data-at="([^"]*)"/.exec(html.slice(e, open))?.[1] ?? null;
|
|
100
112
|
base = decodeCarried(html.slice(open + 1, b));
|
|
101
113
|
html = html.slice(0, e) + html.slice(b + END.length);
|
|
102
114
|
if (e > 0 && html[e - 1] === '\n') html = html.slice(0, e - 1) + html.slice(e);
|
|
103
115
|
}
|
|
104
|
-
return {html, text, name, view, drawing, drawingName, base, baseName, return: address, return_expires_at: expiry};
|
|
116
|
+
return {html, text, name, view, drawing, drawingName, base, baseName, by, at, return: address, return_expires_at: expiry};
|
|
105
117
|
}
|
|
106
118
|
// Unknown options, a bare --drawing or --base and a third name are refused, never dropped.
|
|
107
|
-
const USAGE = 'usage: rapier-html <document.md> [out.html] [--view draw|notes] [--drawing sketch.svg] [--base original.md] [--return <url> --return-expires-at <timestamp>]\n' +
|
|
119
|
+
const USAGE = 'usage: rapier-html <document.md> [out.html] [--view draw|notes] [--drawing sketch.svg] [--base original.md [--by <your name>]] [--return <url> --return-expires-at <timestamp>]\n' +
|
|
108
120
|
' writes <document>.rapier.html beside the file: Rapier with the document inside it, one file, offline.\n' +
|
|
109
121
|
' It never overwrites: an output that already exists, the document itself included, is refused.\n' +
|
|
110
122
|
' --view opens the page on Draw (sketch and paint, the document behind it) or on Notes\n' +
|
|
111
123
|
' --drawing carries an SVG drawing alongside the document, opened on Draw as the page boots\n' +
|
|
112
124
|
' --base carries the text the document was proposed against; the page opens on their diff\n' +
|
|
125
|
+
' --by with --base: who proposes it, shown on the diff with the time the page was made\n' +
|
|
113
126
|
' --return carries a one-use return URL from document.create_return; Share offers Send back\n' +
|
|
114
127
|
' --return-expires-at carries the same mint\'s expiry; Share offers Save once the return expires\n' +
|
|
115
128
|
' -- everything after it is a filename, even one that starts with a dash\n' +
|
|
116
129
|
' --help this text';
|
|
117
130
|
export async function main(argv, pagesDir, {log = console.log} = {}) {
|
|
118
131
|
const names = [];
|
|
119
|
-
let drawingPath = null, basePath = null, view = null, address = null, expiry = null, onlyNames = false;
|
|
132
|
+
let drawingPath = null, basePath = null, by = null, view = null, address = null, expiry = null, onlyNames = false;
|
|
120
133
|
for (let i = 0; i < argv.length; i++) {
|
|
121
134
|
const arg = argv[i];
|
|
122
135
|
if (onlyNames || !arg.startsWith('-')) names.push(arg);
|
|
@@ -130,6 +143,10 @@ export async function main(argv, pagesDir, {log = console.log} = {}) {
|
|
|
130
143
|
if (basePath !== null || !argv[i + 1] || argv[i + 1].startsWith('-')) throw new Error('--base takes one text file (write a name that starts with a dash as ./-original.md)');
|
|
131
144
|
basePath = argv[++i];
|
|
132
145
|
}
|
|
146
|
+
else if (arg === '--by') {
|
|
147
|
+
if (by !== null || !argv[i + 1] || argv[i + 1].startsWith('-')) throw new Error('--by takes one name');
|
|
148
|
+
by = argv[++i];
|
|
149
|
+
}
|
|
133
150
|
else if (arg === '--return') {
|
|
134
151
|
if (address !== null || !argv[i + 1] || argv[i + 1].startsWith('-')) throw new Error('--return takes one HTTPS return URL');
|
|
135
152
|
address = returnAddress(argv[++i]);
|
|
@@ -150,7 +167,9 @@ export async function main(argv, pagesDir, {log = console.log} = {}) {
|
|
|
150
167
|
const out = named || join(dirname(input), name.replace(/\.(md|markdown|txt)$/i, '') + '.rapier.html');
|
|
151
168
|
const wrapOptions = {};
|
|
152
169
|
if (drawingPath) { wrapOptions.drawing = await readFile(drawingPath, 'utf8'); wrapOptions.drawingName = basename(drawingPath); }
|
|
170
|
+
if (by !== null && !basePath) throw new Error('--by names who proposes a change: give the --base it proposes over');
|
|
153
171
|
if (basePath) { wrapOptions.base = await readFile(basePath, 'utf8'); wrapOptions.baseName = basename(basePath); }
|
|
172
|
+
if (by !== null) { wrapOptions.by = by; wrapOptions.at = new Date().toISOString(); }
|
|
154
173
|
if (view) wrapOptions.view = view;
|
|
155
174
|
if (address) wrapOptions.return = address;
|
|
156
175
|
if (expiry) wrapOptions.return_expires_at = expiry;
|