From cb0f3a6b1f810619fc9f234756eb865bcd081ac4 Mon Sep 17 00:00:00 2001 From: gator Date: Tue, 22 Sep 2026 08:34:01 +0000 Subject: [PATCH] manuscript: Baum's own words shown in green --- manuscript.html | 53 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/manuscript.html b/manuscript.html index 0e553f0..7164532 100644 --- a/manuscript.html +++ b/manuscript.html @@ -24,6 +24,10 @@ .para.sel{border-color:var(--accent);background:#10141d} .para.changed{border-left:3px solid #3fae74} .para .pn{position:absolute;left:-30px;top:14px;font:11px var(--mono,monospace);color:var(--muted)} + .orig{color:#5fd38d} + .new{color:var(--ink)} + .legend{font-size:12px;color:var(--muted);margin:0 0 8px} + .legend .orig{font-weight:600} mark{border-radius:4px;padding:0 2px;cursor:pointer;color:inherit} mark.open{background:#5a4a12} mark.kept{background:#2a2f3a} @@ -189,14 +193,55 @@ POLL = setTimeout(function () { openChapter(CH.n, PAGE); }, 4000); } + // Which words of the paragraph are Baum's. Longest common subsequence on + // words (punctuation ignored for the match, kept in the output). + function tok(t) { return String(t || "").split(/(\s+)/).filter(function (x) { return x.length; }); } + function key(w) { return w.toLowerCase().replace(/[^a-z0-9']/g, ""); } + function originalMask(p) { + if (p.text === p.original) return null; + var a = tok(p.original).filter(function (x) { return !/^\s+$/.test(x); }), b = tok(p.text); + var bw = b.map(function (x, i) { return /^\s+$/.test(x) ? null : i; }).filter(function (x) { return x !== null; }); + var A = a.map(key), B = bw.map(function (i) { return key(b[i]); }); + var n = A.length, m = B.length, L = []; + for (var i = 0; i <= n; i++) { L.push(new Uint16Array(m + 1)); } + for (i = n - 1; i >= 0; i--) for (var j = m - 1; j >= 0; j--) L[i][j] = A[i] === B[j] ? L[i + 1][j + 1] + 1 : Math.max(L[i + 1][j], L[i][j + 1]); + var mask = b.map(function () { return false; }); + i = 0; j = 0; + while (i < n && j < m) { + if (A[i] === B[j]) { mask[bw[j]] = true; i++; j++; } + else if (L[i + 1][j] >= L[i][j + 1]) i++; else j++; + } + for (var k = 0; k < b.length; k++) if (/^\s+$/.test(b[k])) mask[k] = (k > 0 && mask[k - 1]) && (k + 1 < b.length && mask[k + 1]); + return { parts: b, mask: mask }; + } + function greened(p) { + var om = originalMask(p); + if (!om) return '' + esc(p.text) + ''; + var out = "", cur = null, buf = ""; + om.parts.forEach(function (w, i) { + var cls = om.mask[i] ? "orig" : "new"; + if (cls !== cur) { if (cur !== null) out += '' + esc(buf) + ''; cur = cls; buf = ""; } + buf += w; + }); + if (cur !== null) out += '' + esc(buf) + ''; + return out; + } function marked(p) { - var text = p.text, html = esc(text), refs = REFS.filter(function (r) { return r.para === p.i; }); + var text = p.text, html = greened(p), refs = REFS.filter(function (r) { return r.para === p.i; }); refs.forEach(function (r) { - var q = esc(r.quote), at = html.indexOf(q); - if (at >= 0) html = html.slice(0, at) + '' + q + '' + html.slice(at + q.length); + html = markIn(html, r.quote, esc(r.status), esc(r.id), esc(r.why || "")); }); return html; } + // Wrapping a quote in inside the green spans: the quote is found in + // the plain text; the coloured html is searched for the same run of words. + function markIn(html, q, cls, id, title) { + var plain = q.split(/\s+/).map(function (w) { return esc(w).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }).join('(?:\\s|<\\/?span[^>]*>)+'); + var rx = new RegExp(plain); + var m = rx.exec(html); + if (!m) return html; + return html.slice(0, m.index) + '' + m[0] + '' + html.slice(m.index + m[0].length); + } function paintChapter() { $("chtitle").textContent = (CH.n ? "Chapter " + CH.n + " — " : "") + CH.title; @@ -218,7 +263,7 @@ $("pagebar").innerHTML = bar.replace('left in the chapter', 'left in the chapter' + printSpan + ''); var platesHere = PLATES.filter(function (x) { return pp.length && x.pg >= Math.min.apply(null, pp) && x.pg <= Math.max.apply(null, pp); }); var sceneOf = function (i) { var sc = SCENES.find(function (x) { return i >= x.from && i <= x.to; }); return sc ? 'Scene ' + sc.n + (sc.title ? ' · ' + esc(sc.title) : '') + '' : ''; }; - $("paras").innerHTML = (platesHere.length ? '
' + platesHere.map(function (x) { return '' + esc(x.alt) + ''; }).join("") + '
' : '') + here.map(function (p) { + $("paras").innerHTML = '

Green is Baum\'s own text, word for word. White is what has been written for this adaptation.

' + (platesHere.length ? '
' + platesHere.map(function (x) { return '' + esc(x.alt) + ''; }).join("") + '
' : '') + here.map(function (p) { var changed = p.text !== p.original; return (SCENES.some(function (x) { return x.from === p.i; }) ? sceneOf(p.i) : '') + '
' + (p.i + 1) + (p.print_page ? '
p' + p.print_page : '') + '
' + marked(p) + (changed ? '
As Baum wrote it: ' + esc(p.original) + '
changed ' + esc(when(p.changed_at)) + ' by ' + esc(p.changed_by || "") + ' · ' + esc(p.how || "") + ' · revision ' + p.rev + '
' : '')