rapier-html 1.1.99 → 1.1.102
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 +95 -4
- package/README.md +23 -24
- package/dist/ledger/LICENSE +10 -2
- package/dist/ledger/data.mjs +1 -1
- package/dist/ledger/diff.mjs +610 -0
- package/dist/ledger/format.mjs +12 -5
- package/dist/ledger/hash.mjs +16 -22
- package/dist/ledger/history.mjs +309 -0
- package/dist/ledger/inverse-source.mjs +316 -0
- package/dist/ledger/journal-records.mjs +116 -12
- package/dist/ledger/transport.mjs +123 -19
- package/dist/rapier.html +399 -618
- package/dist/wrap.mjs +28 -4
- package/package.json +1 -1
package/dist/wrap.mjs
CHANGED
|
@@ -10,6 +10,8 @@ const END = '</script>';
|
|
|
10
10
|
// The page's search words (rapier.html's RAPIER_SEO regions: description, canonical address, previews, structured data and the plain guide
|
|
11
11
|
// a crawler reads) are rapier.website's own. A page that carries someone's document never claims that address or that description.
|
|
12
12
|
const SEARCH_WORDS = /<!-- RAPIER_SEO_BEGIN -->[\s\S]*?<!-- RAPIER_SEO_END -->\n?/g;
|
|
13
|
+
const OFFLINE_POLICY = "default-src 'none'; script-src 'unsafe-inline' 'wasm-unsafe-eval' blob:; style-src 'unsafe-inline'; " +
|
|
14
|
+
"img-src data: blob:; media-src data: blob:; font-src data: blob:; connect-src 'none'; worker-src blob:; manifest-src 'none'; base-uri 'none'; form-action 'none'";
|
|
13
15
|
|
|
14
16
|
// One encoding, exactly reversible: no `<` before `/` or `!`, no CR, no NUL. engine.js _rapierDecodeCarried is its
|
|
15
17
|
// inverse.
|
|
@@ -19,6 +21,26 @@ function safeName(name, fallback) {
|
|
|
19
21
|
return [...String(name || fallback).replace(/[\\/\0"<>&]/g, '-')].slice(0, 256).join('').replace(/[\uD800-\uDBFF]$/, '') || fallback;
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
// Only the trusted shell head changes. Carried source and inert application bytes stay exact.
|
|
25
|
+
export function offlineTemplate(pageHtml, address = null) {
|
|
26
|
+
const end = pageHtml.indexOf('</head>');
|
|
27
|
+
if (end < 0) throw new Error('not a Rapier page: no </head>');
|
|
28
|
+
const attribute = (tag, name) => {
|
|
29
|
+
const match = new RegExp('\\b' + name + '\\s*=\\s*(?:"([^"]*)"|\'([^\']*)\'|([^\\s>]+))', 'i').exec(tag);
|
|
30
|
+
return match ? match[1] ?? match[2] ?? match[3] : '';
|
|
31
|
+
};
|
|
32
|
+
const head = pageHtml.slice(0, end).replace(/<meta\b[^>]*>/gi, tag =>
|
|
33
|
+
attribute(tag, 'http-equiv').toLowerCase() === 'content-security-policy' ? '' : tag).replace(/<link\b[^>]*>/gi, tag =>
|
|
34
|
+
attribute(tag, 'rel').toLowerCase().split(/\s+/).some(rel => ['icon', 'apple-touch-icon', 'manifest', 'preconnect', 'dns-prefetch'].includes(rel)) ? '' : tag);
|
|
35
|
+
const anchor = /<meta charset[^>]*>\n?/i.exec(head) || /<head[^>]*>\n?/.exec(head);
|
|
36
|
+
if (!anchor) throw new Error('not a Rapier page: no <head>');
|
|
37
|
+
const at = anchor.index + anchor[0].length;
|
|
38
|
+
// A minted return is an explicit Send back destination; boot and rendering stay local.
|
|
39
|
+
const policy = address == null ? OFFLINE_POLICY : OFFLINE_POLICY.replace("connect-src 'none'", 'connect-src ' + new URL(returnAddress(address)).origin);
|
|
40
|
+
return head.slice(0, at) + '<meta http-equiv="Content-Security-Policy" content="' + policy + '">\n' +
|
|
41
|
+
head.slice(at) + pageHtml.slice(end);
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
export function wrap(pageHtml, text, name, options) {
|
|
23
45
|
if (typeof pageHtml !== 'string' || !pageHtml.includes('</body>')) throw new Error('not a Rapier page: no </body>');
|
|
24
46
|
if (typeof text !== 'string') throw new Error('the document must be text');
|
|
@@ -27,12 +49,13 @@ export function wrap(pageHtml, text, name, options) {
|
|
|
27
49
|
const previous = unwrap(pageHtml);
|
|
28
50
|
const opts = {...saved, ...(options || {})};
|
|
29
51
|
const docName = safeName(name, 'document.md');
|
|
52
|
+
if (opts.kind != null && !['markdown', 'text', 'code'].includes(opts.kind)) throw new Error('invalid document kind');
|
|
30
53
|
// `view`: the view the page opens on with the document behind it, `draw` or `notes`.
|
|
31
54
|
if (opts.view != null && !['draw', 'notes'].includes(opts.view)) throw new Error('the view is draw or notes');
|
|
32
55
|
const address = opts.return == null ? null : returnAddress(opts.return);
|
|
33
56
|
const expiry = opts.return_expires_at == null ? null : returnExpiresAt(opts.return_expires_at);
|
|
34
57
|
if (!!address !== !!expiry) throw new Error('a return needs its URL and return_expires_at from document.create_return');
|
|
35
|
-
const block = START + ' data-name="' + docName + '"' + (opts.view ? ' data-view="' + opts.view + '"' : '') +
|
|
58
|
+
const block = START + ' data-name="' + docName + '"' + (opts.kind ? ' data-kind="' + opts.kind + '"' : '') + (opts.view ? ' data-view="' + opts.view + '"' : '') +
|
|
36
59
|
(address ? ' data-return="' + address + '" data-return-expires-at="' + expiry + '"' : '') + '>' + encodeCarried(text) + END;
|
|
37
60
|
let carriedBlocks = '\n' + writeParts(text, opts);
|
|
38
61
|
if (opts.drawing != null) {
|
|
@@ -60,7 +83,7 @@ export function wrap(pageHtml, text, name, options) {
|
|
|
60
83
|
if (baseline) baseline = readBase({...baseline, ...(opts.by != null ? {by: String(opts.by).trim()} : {}), ...(opts.at != null ? {at: String(opts.at)} : {})});
|
|
61
84
|
carriedBlocks += '\n' + writeBase(text, baseline);
|
|
62
85
|
// The agent seed goes after the charset (or <head>), once: a page that already carries it, wrapped again, loses its old one.
|
|
63
|
-
const unseeded = previous.html.replace(SEARCH_WORDS, '').split(PAGE_SEED + '\n').join('');
|
|
86
|
+
const unseeded = offlineTemplate(previous.html.replace(SEARCH_WORDS, '').split(PAGE_SEED + '\n').join(''), address);
|
|
64
87
|
const headTag = /<meta charset[^>]*>\n?/i.exec(unseeded) || /<head[^>]*>\n?/.exec(unseeded);
|
|
65
88
|
if (!headTag) throw new Error('not a Rapier page: no <head>');
|
|
66
89
|
const seededAt = headTag.index + headTag[0].length;
|
|
@@ -75,12 +98,13 @@ export function wrap(pageHtml, text, name, options) {
|
|
|
75
98
|
return titled.slice(0, at + shift) + '\n' + block + carriedBlocks + titled.slice(at + shift);
|
|
76
99
|
}
|
|
77
100
|
export function unwrap(pageHtml) {
|
|
78
|
-
let html = pageHtml, text = null, name = null, view = null, address = null, expiry = null;
|
|
101
|
+
let html = pageHtml, text = null, name = null, kind = null, view = null, address = null, expiry = null;
|
|
79
102
|
const a = pageHtml.indexOf(START);
|
|
80
103
|
if (a >= 0) {
|
|
81
104
|
const open = pageHtml.indexOf('>', a), b = pageHtml.indexOf(END, open);
|
|
82
105
|
if (open < 0 || b < 0) throw new Error('a carried document without its end');
|
|
83
106
|
name = /data-name="([^"]*)"/.exec(pageHtml.slice(a, open))?.[1] ?? null;
|
|
107
|
+
kind = /data-kind="(markdown|text|code)"/.exec(pageHtml.slice(a, open))?.[1] ?? null;
|
|
84
108
|
view = /data-view="(draw|notes)"/.exec(pageHtml.slice(a, open))?.[1] ?? null;
|
|
85
109
|
const carriedReturn = /data-return="([^"]*)"/.exec(pageHtml.slice(a, open));
|
|
86
110
|
if (carriedReturn) address = returnAddress(carriedReturn[1]);
|
|
@@ -104,5 +128,5 @@ export function unwrap(pageHtml) {
|
|
|
104
128
|
const carriedBase = takeBase(html); html = carriedBase.html;
|
|
105
129
|
const comparisonBase = carriedBase.base;
|
|
106
130
|
const parts = takeParts(html, text);
|
|
107
|
-
return {...parts, text, name, view, drawing, drawingName, comparisonBase, base: comparisonBase?.text ?? null, baseName: comparisonBase?.name ?? null, baseSha256: comparisonBase?.sha256 ?? null, baseRevision: comparisonBase?.revision ?? null, by: comparisonBase?.by ?? null, at: comparisonBase?.at ?? null, return: address, return_expires_at: expiry};
|
|
131
|
+
return {...parts, text, name, kind, view, drawing, drawingName, comparisonBase, base: comparisonBase?.text ?? null, baseName: comparisonBase?.name ?? null, baseSha256: comparisonBase?.sha256 ?? null, baseRevision: comparisonBase?.revision ?? null, by: comparisonBase?.by ?? null, at: comparisonBase?.at ?? null, return: address, return_expires_at: expiry};
|
|
108
132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-html",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.102",
|
|
4
4
|
"description": "One command turns a Markdown file into one offline HTML file: the whole Rapier editor with the document inside. Opens in any browser, phone or desktop, with no install or account. Write, draw and paint watercolor, then Send back the edits to your AI agent.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|