manuscript: Denslow plates, first-edition pages, scenes, report of changes, simulated entries shown

This commit is contained in:
gator 2026-09-22 00:19:55 +00:00
parent 85043303b4
commit dd5eae01f7

View file

@ -70,9 +70,26 @@
<div class="tabs2"> <div class="tabs2">
<button type="button" data-tab="read" class="on">The manuscript</button> <button type="button" data-tab="read" class="on">The manuscript</button>
<button type="button" data-tab="decisions">My creative decisions</button> <button type="button" data-tab="decisions">My creative decisions</button>
<button type="button" data-tab="scenes">Scenes</button>
<button type="button" data-tab="report">Report of changes</button>
<button type="button" data-tab="log">The record</button> <button type="button" data-tab="log">The record</button>
</div> </div>
<div id="tab-scenes" style="display:none" class="card">
<h2 style="margin-top:0">Scenes in this chapter</h2>
<p class="tiny">How the chapter divides into scenes for filming: which paragraphs each covers, and so which pages. Recorded as yours.</p>
<div id="scenelist"></div>
<textarea id="scenesjson" class="tiny" style="width:100%;box-sizing:border-box;min-height:120px;font-family:var(--mono,monospace)" placeholder='[{"title":"The prairie","from":0,"to":3,"place":"EXT. FARM - DAY"}]'></textarea>
<div class="row" style="display:flex;gap:8px;margin-top:8px"><button class="btn small" type="button" id="scenesave">Save the scenes</button></div>
</div>
<div id="tab-report" style="display:none" class="card">
<h2 style="margin-top:0">Report of changes from the source</h2>
<p class="tiny">Key changes in this chapter, who made them, and how. For internal records.</p>
<div class="row" style="display:flex;gap:8px;margin:8px 0 14px"><button class="btn small" type="button" id="reportsave">Save this report as a file</button></div>
<div id="report"></div>
</div>
<div id="tab-decisions" style="display:none" class="card"> <div id="tab-decisions" style="display:none" class="card">
<h2 style="margin-top:0">My creative decisions</h2> <h2 style="margin-top:0">My creative decisions</h2>
<p class="tiny">The standing choices that shape the whole adaptation, in your own words. Each is dated and kept in the record. Anything drafted for you is drafted <em>from</em> these.</p> <p class="tiny">The standing choices that shape the whole adaptation, in your own words. Each is dated and kept in the record. Anything drafted for you is drafted <em>from</em> these.</p>
@ -114,6 +131,7 @@
navBar("book"); navBar("book");
if (!Session.get()) { location.href = "/register.html?next=" + encodeURIComponent(location.pathname + location.search); return; } if (!Session.get()) { location.href = "/register.html?next=" + encodeURIComponent(location.pathname + location.search); return; }
var Q = new URLSearchParams(location.search); var Q = new URLSearchParams(location.search);
var PLATES = [], SCENES = [], EDITION = "";
var AID = Q.get("book") || "", AD = null, TOC = [], DEC = [], CH = null, PARAS = [], REFS = [], PAGE = 1, SEL = null, LOGCHAP = true, POLL = null; var AID = Q.get("book") || "", AD = null, TOC = [], DEC = [], CH = null, PARAS = [], REFS = [], PAGE = 1, SEL = null, LOGCHAP = true, POLL = null;
function $(id) { return document.getElementById(id); } function $(id) { return document.getElementById(id); }
function msg(t, kind) { var m = $("msg"); m.className = "msg " + (kind || "err"); m.textContent = t; if (!t) m.className = "msg"; } function msg(t, kind) { var m = $("msg"); m.className = "msg " + (kind || "err"); m.textContent = t; if (!t) m.className = "msg"; }
@ -162,7 +180,7 @@
async function openChapter(n, page) { async function openChapter(n, page) {
var r = await api("adapt_chapter", { adaptation_id: AID, chapter: n }); var r = await api("adapt_chapter", { adaptation_id: AID, chapter: n });
if (!r.ok) { msg(r.error || "That chapter did not load."); return; } if (!r.ok) { msg(r.error || "That chapter did not load."); return; }
CH = r.chapter; PARAS = r.paragraphs || []; REFS = r.refs || []; DEC = r.decisions || DEC; CH = r.chapter; PARAS = r.paragraphs || []; REFS = r.refs || []; DEC = r.decisions || DEC; PLATES = r.plates || []; SCENES = r.scenes || []; EDITION = r.edition || "";
PAGE = Math.max(1, Math.min(page || PAGE || 1, CH.pages)); SEL = null; PAGE = Math.max(1, Math.min(page || PAGE || 1, CH.pages)); SEL = null;
history.replaceState(null, "", "?book=" + encodeURIComponent(AID) + "&ch=" + n + "&p=" + PAGE); history.replaceState(null, "", "?book=" + encodeURIComponent(AID) + "&ch=" + n + "&p=" + PAGE);
paintToc(); paintChapter(); paintToc(); paintChapter();
@ -187,7 +205,7 @@
+ '<span>page <b>' + PAGE + '/' + CH.pages + '</b> of this chapter &middot; <b>' + bookPage + '/' + total + '</b> of the book &middot; ' + (CH.pages - PAGE) + ' left in the chapter</span>' + '<span>page <b>' + PAGE + '/' + CH.pages + '</b> of this chapter &middot; <b>' + bookPage + '/' + total + '</b> of the book &middot; ' + (CH.pages - PAGE) + ' left in the chapter</span>'
+ '<span class="meter"><i style="width:' + Math.round(100 * bookPage / total) + '%"></i></span>' + '<span class="meter"><i style="width:' + Math.round(100 * bookPage / total) + '%"></i></span>'
+ '<button class="btn small ghost" type="button" data-pg="next">Next &#9654;</button>'; + '<button class="btn small ghost" type="button" data-pg="next">Next &#9654;</button>';
$("pagebar").innerHTML = bar; $("pagebar2").innerHTML = bar; $("pagebar2").innerHTML = bar;
var open = REFS.filter(function (r) { return r.status === "open"; }).length; var open = REFS.filter(function (r) { return r.status === "open"; }).length;
$("chtools").innerHTML = (CH.refs_state === "working" $("chtools").innerHTML = (CH.refs_state === "working"
? '<span class="tiny">Reading the chapter for references to ' + esc(AD.written || "its own time") + '&hellip;</span>' ? '<span class="tiny">Reading the chapter for references to ' + esc(AD.written || "its own time") + '&hellip;</span>'
@ -195,9 +213,14 @@
+ (REFS.length ? '<span class="tiny">' + REFS.length + ' found &middot; <b style="color:#f5c04b">' + open + ' for you to decide</b> &middot; yellow = undecided, green = rewritten by you, grey = kept as written. Click one.</span>' : '') + (REFS.length ? '<span class="tiny">' + REFS.length + ' found &middot; <b style="color:#f5c04b">' + open + ' for you to decide</b> &middot; yellow = undecided, green = rewritten by you, grey = kept as written. Click one.</span>' : '')
+ (/^failed/.test(CH.refs_state || "") ? '<span class="warn">' + esc(CH.refs_state) + '</span>' : ''); + (/^failed/.test(CH.refs_state || "") ? '<span class="warn">' + esc(CH.refs_state) + '</span>' : '');
var here = PARAS.filter(function (p) { return p.page === PAGE; }); var here = PARAS.filter(function (p) { return p.page === PAGE; });
$("paras").innerHTML = here.map(function (p) { var pp = here.map(function (p) { return p.print_page; }).filter(Boolean);
var printSpan = pp.length ? ' &middot; first edition p. ' + Math.min.apply(null, pp) + (Math.max.apply(null, pp) > Math.min.apply(null, pp) ? '&ndash;' + Math.max.apply(null, pp) : '') : '';
$("pagebar").innerHTML = bar.replace('left in the chapter</span>', 'left in the chapter' + printSpan + '</span>');
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 ? '<span class="tiny" style="display:block;color:#f5c04b">Scene ' + sc.n + (sc.title ? ' &middot; ' + esc(sc.title) : '') + '</span>' : ''; };
$("paras").innerHTML = (platesHere.length ? '<div style="display:flex;gap:10px;flex-wrap:wrap;margin:0 0 12px">' + platesHere.map(function (x) { return '<a href="' + esc(x.url) + '" target="_blank" title="' + esc(x.alt) + ' (Denslow, 1900, p.' + x.pg + ')"><img src="' + esc(x.url) + '" style="height:150px;border-radius:6px;background:#fff" alt="' + esc(x.alt) + '"></a>'; }).join("") + '</div>' : '') + here.map(function (p) {
var changed = p.text !== p.original; var changed = p.text !== p.original;
return '<div class="para' + (changed ? ' changed' : '') + (SEL === p.i ? ' sel' : '') + '" data-para="' + p.i + '"><span class="pn">' + (p.i + 1) + '</span>' + marked(p) return (SCENES.some(function (x) { return x.from === p.i; }) ? sceneOf(p.i) : '') + '<div class="para' + (changed ? ' changed' : '') + (SEL === p.i ? ' sel' : '') + '" data-para="' + p.i + '" title="' + (p.print_page ? 'first edition p. ' + p.print_page : '') + '"><span class="pn">' + (p.i + 1) + (p.print_page ? '<br>p' + p.print_page : '') + '</span>' + marked(p)
+ (changed ? '<div class="was"><b>As Baum wrote it:</b> ' + esc(p.original) + '<br><span class="tiny">changed ' + esc(when(p.changed_at)) + ' by ' + esc(p.changed_by || "") + ' &middot; ' + esc(p.how || "") + ' &middot; revision ' + p.rev + '</span></div>' : '') + (changed ? '<div class="was"><b>As Baum wrote it:</b> ' + esc(p.original) + '<br><span class="tiny">changed ' + esc(when(p.changed_at)) + ' by ' + esc(p.changed_by || "") + ' &middot; ' + esc(p.how || "") + ' &middot; revision ' + p.rev + '</span></div>' : '')
+ '</div>' + (SEL === p.i ? workHtml(p) : ''); }).join("") || '<p class="tiny">Nothing on this page.</p>'; + '</div>' + (SEL === p.i ? workHtml(p) : ''); }).join("") || '<p class="tiny">Nothing on this page.</p>';
} }
@ -226,8 +249,10 @@
var t = ev.target, el, r; var t = ev.target, el, r;
if ((el = t.closest("[data-tab]"))) { if ((el = t.closest("[data-tab]"))) {
document.querySelectorAll(".tabs2 button").forEach(function (b) { b.classList.toggle("on", b === el); }); document.querySelectorAll(".tabs2 button").forEach(function (b) { b.classList.toggle("on", b === el); });
["read", "decisions", "log"].forEach(function (k) { $("tab-" + k).style.display = el.getAttribute("data-tab") === k ? "" : "none"; }); ["read", "decisions", "log", "scenes", "report"].forEach(function (k) { $("tab-" + k).style.display = el.getAttribute("data-tab") === k ? "" : "none"; });
if (el.getAttribute("data-tab") === "log") loadLog(); if (el.getAttribute("data-tab") === "log") loadLog();
if (el.getAttribute("data-tab") === "scenes") paintScenes();
if (el.getAttribute("data-tab") === "report") loadReport();
return; return;
} }
if ((el = t.closest("[data-ch]"))) { ev.preventDefault(); openChapter(parseInt(el.getAttribute("data-ch"), 10), 1); window.scrollTo(0, 0); return; } if ((el = t.closest("[data-ch]"))) { ev.preventDefault(); openChapter(parseInt(el.getAttribute("data-ch"), 10), 1); window.scrollTo(0, 0); return; }
@ -289,13 +314,43 @@
} }
}); });
function paintScenes() {
$("scenelist").innerHTML = SCENES.length ? '<ol>' + SCENES.map(function (x) { return '<li><b>' + esc(x.title || ("Scene " + x.n)) + '</b> <span class="tiny">' + esc(x.place || "") + ' &middot; paragraphs ' + (x.from + 1) + '&ndash;' + (x.to + 1) + ' &middot; pages ' + (x.pages || []).join(", ") + (x.print_pages && x.print_pages.length ? ' &middot; first edition p. ' + x.print_pages[0] + (x.print_pages.length > 1 ? '&ndash;' + x.print_pages[x.print_pages.length - 1] : '') : '') + '</span>' + (x.note ? '<br><span class="tiny">' + esc(x.note) + '</span>' : '') + '</li>'; }).join("") + '</ol>' : '<p class="tiny">No scenes yet for this chapter.</p>';
$("scenesjson").value = SCENES.length ? JSON.stringify(SCENES.map(function (x) { return { title: x.title, from: x.from, to: x.to, place: x.place, note: x.note }; }), null, 1) : "";
}
var REPORT = null;
async function loadReport() {
var r = await api("adapt_report", { adaptation_id: AID, chapter: CH.n });
if (!r.ok) { msg(r.error); return; }
REPORT = r;
var people = Object.keys(r.by_person || {}).map(function (k) { var v = r.by_person[k]; return '<li><b>' + esc(k.replace(/^human:/, "").replace(/^sim:/, "")) + '</b>' + (/^sim:/.test(k) ? ' <span class="tiny">(entries made by the assistant at this person's instruction, as a simulation)</span>' : '') + ' &mdash; ' + v.decisions + ' creative decisions, ' + v.rewrites + ' rewrites, ' + v.kept + ' kept as written, ' + v.flags + ' flagged, ' + v.reverts + ' put back</li>'; }).join("");
$("report").innerHTML = '<p><b>' + esc(r.adaptation.title) + '</b> by ' + esc(r.adaptation.author) + ' &middot; ' + esc(r.adaptation.source) + (r.adaptation.edition ? '<br><span class="tiny">' + esc(r.adaptation.edition) + '</span>' : '') + '<br><span class="tiny">source fingerprint ' + esc(r.adaptation.source_sha256) + ' &middot; record chain ' + (r.chain_ok ? 'intact' : 'BROKEN at ' + r.chain_broken_at) + ' &middot; made ' + esc(when(r.made_at)) + '</span></p>'
+ '<h3>Chapter ' + r.chapter.n + ' &mdash; ' + esc(r.chapter.title) + '</h3><p class="tiny">' + r.chapter.changed + ' of ' + r.chapter.paragraphs + ' paragraphs changed &middot; ' + r.chapter.words_original + ' words in the source, ' + r.chapter.words_final + ' now</p>'
+ '<h3>By person</h3><ul>' + (people || '<li class="tiny">nobody yet</li>') + '</ul>'
+ '<h3>Standing creative decisions</h3><ol>' + (r.decisions || []).map(function (d) { return '<li>' + esc(d.text) + ' <span class="tiny">&mdash; ' + esc(d.by) + (d.simulated ? ' (simulated)' : '') + ', ' + esc(when(d.at)) + (d.status === "idea" ? ', idea' : '') + '</span></li>'; }).join("") + '</ol>'
+ '<h3>Changed paragraphs</h3>' + (r.changed || []).map(function (c) { return '<div class="work"><b>Paragraph ' + (c.para + 1) + '</b> <span class="tiny">page ' + c.page + (c.print_page ? ' &middot; first edition p. ' + c.print_page : '') + ' &middot; ' + esc(c.by || "") + ' &middot; ' + esc(c.how || "") + ' &middot; ' + c.revisions + ' revision' + (c.revisions === 1 ? '' : 's') + '</span>' + (c.directions.length ? '<p class="tiny"><b>Direction:</b> ' + c.directions.map(esc).join(' / ') + '</p>' : '') + '<p class="tiny"><b>Source:</b> ' + esc(c.original) + '</p><p><b>Now:</b> ' + esc(c.final) + '</p></div>'; }).join("")
+ (r.kept.length ? '<h3>Kept as written, by decision</h3><ul>' + r.kept.map(function (k) { return '<li>&ldquo;' + esc(k.quote) + '&rdquo; <span class="tiny">paragraph ' + (k.para + 1) + (k.reason ? ' &mdash; ' + esc(k.reason) : '') + ' &mdash; ' + esc(k.by || "") + '</span></li>'; }).join("") + '</ul>' : '')
+ (r.scenes.length ? '<h3>Scenes</h3><ol>' + r.scenes.map(function (x) { return '<li>' + esc(x.title || "") + ' <span class="tiny">' + esc(x.place || "") + ' &middot; paragraphs ' + (x.from + 1) + '&ndash;' + (x.to + 1) + '</span></li>'; }).join("") + '</ol>' : '');
}
document.addEventListener("click", async function (ev) {
var t = ev.target, r;
if (t.id === "scenesave") {
var list; try { list = JSON.parse($("scenesjson").value || "[]"); } catch (e) { msg("The scenes are not valid JSON."); return; }
r = await api("adapt_scenes", { adaptation_id: AID, chapter: CH.n, scenes: list });
if (!r.ok) { msg(r.error); return; } SCENES = r.scenes; paintScenes(); msg("Scenes saved.", "ok"); return;
}
if (t.id === "reportsave" && REPORT) {
var blob = new Blob([JSON.stringify(REPORT, null, 1)], { type: "application/json" });
var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = (AD.title || "adaptation").replace(/[^a-z0-9]+/gi, "-").toLowerCase() + "-ch" + CH.n + "-report.json"; a.click();
}
});
var NAMES = { import: "brought in", creative_decision: "creative decision", ai_flag: "found by the reader (AI)", human_flag: "found by you", human_keep: "kept as written", human_direction: "your direction", ai_draft: "draft made from your direction (AI)", human_rewrite: "your wording saved", human_revert: "put back" }; var NAMES = { import: "brought in", creative_decision: "creative decision", ai_flag: "found by the reader (AI)", human_flag: "found by you", human_keep: "kept as written", human_direction: "your direction", ai_draft: "draft made from your direction (AI)", human_rewrite: "your wording saved", human_revert: "put back" };
async function loadLog() { async function loadLog() {
var r = await api("adapt_log", LOGCHAP && CH ? { adaptation_id: AID, chapter: CH.n } : { adaptation_id: AID }); var r = await api("adapt_log", LOGCHAP && CH ? { adaptation_id: AID, chapter: CH.n } : { adaptation_id: AID });
if (!r.ok) { msg(r.error); return; } if (!r.ok) { msg(r.error); return; }
$("chain").innerHTML = (r.chain_ok ? "Every entry carries the fingerprint of the one before it, and the chain is intact" : "<b style='color:#e0685f'>The chain is broken at entry " + r.chain_broken_at + "</b>") + " &middot; " + r.events.length + " entries" + (LOGCHAP && CH ? " for chapter " + CH.n : "") + ". Times are as recorded by the server (UTC), shown in your local time."; $("chain").innerHTML = (r.chain_ok ? "Every entry carries the fingerprint of the one before it, and the chain is intact" : "<b style='color:#e0685f'>The chain is broken at entry " + r.chain_broken_at + "</b>") + " &middot; " + r.events.length + " entries" + (LOGCHAP && CH ? " for chapter " + CH.n : "") + ". Times are as recorded by the server (UTC), shown in your local time.";
$("logtable").innerHTML = '<tr><th>#</th><th>when</th><th>who</th><th>what</th><th>where</th><th>detail</th></tr>' + r.events.map(function (e) { $("logtable").innerHTML = '<tr><th>#</th><th>when</th><th>who</th><th>what</th><th>where</th><th>detail</th></tr>' + r.events.map(function (e) {
var who = String(e.actor || "").replace(/^human:/, "").replace(/^ai:/, "AI · "); var who = String(e.actor || "").replace(/^human:/, "").replace(/^ai:/, "AI · ").replace(/^sim:(.*)$/, "$1 (simulated by the assistant)");
var detail = (e.direction ? '<b>Direction:</b> ' + esc(e.direction) + '<br>' : '') var detail = (e.direction ? '<b>Direction:</b> ' + esc(e.direction) + '<br>' : '')
+ (e.kind === "human_rewrite" || e.kind === "ai_draft" || e.kind === "human_revert" ? '<span class="tiny">before:</span> ' + esc(e.before_text) + '<br><span class="tiny">after:</span> ' + esc(e.after_text) + '<br>' : '') + (e.kind === "human_rewrite" || e.kind === "ai_draft" || e.kind === "human_revert" ? '<span class="tiny">before:</span> ' + esc(e.before_text) + '<br><span class="tiny">after:</span> ' + esc(e.after_text) + '<br>' : '')
+ (e.kind === "creative_decision" ? esc(e.after_text) + '<br>' : '') + (e.kind === "creative_decision" ? esc(e.after_text) + '<br>' : '')