book.html: let a book be named, because a filename is not a title

Uploading "Memory Road interior 12-28.pdf" produced a book, a run and a
storyboard all headed "Memory Road interior 12-28". The title is derived from
the filename because that is the only thing an upload carries, and publishers
name files after print runs.

Guessing harder is the wrong fix - stripping "interior 12-28" would be a rule
that works on one file. Letting the author type the title takes eight lines and
is right for every file. The author field goes in at the same time; it costs
nothing and a trailer should know whose book it is.
This commit is contained in:
gator 2026-09-17 02:43:12 +00:00
parent c6788afba5
commit fa1992f200

View file

@ -25,6 +25,9 @@
border:1px dashed var(--line);border-radius:12px;padding:18px} border:1px dashed var(--line);border-radius:12px;padding:18px}
.pick.on{border-color:var(--accent);border-style:solid} .pick.on{border-color:var(--accent);border-style:solid}
.pick .or{color:var(--muted);font-size:13px} .pick .or{color:var(--muted);font-size:13px}
.rename{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.rename input{font:inherit;font-size:13px;background:transparent;color:var(--ink);
border:1px solid var(--line);border-radius:8px;padding:6px 9px}
.writers{display:flex;flex-wrap:wrap;gap:10px;margin-top:8px} .writers{display:flex;flex-wrap:wrap;gap:10px;margin-top:8px}
.wr{display:flex;gap:8px;align-items:center;border:1px solid var(--line); .wr{display:flex;gap:8px;align-items:center;border:1px solid var(--line);
border-radius:10px;padding:9px 12px;cursor:pointer} border-radius:10px;padding:9px 12px;cursor:pointer}
@ -125,7 +128,7 @@ function readFileB64(file) {
if (!email) return; if (!email) return;
navBar("trailer"); navBar("trailer");
var BOOKS = [], PICKED = null, WRITERS = [], CHOSEN = {}, LEAD = ""; var BOOKS = [], PICKED = null, WRITERS = [], CHOSEN = {}, LEAD = "", EDIT = null;
var pollTimer = null; var pollTimer = null;
// ---------------------------------------------------------------- books // ---------------------------------------------------------------- books
@ -141,17 +144,30 @@ function readFileB64(file) {
} else { } else {
sub = "reading the manuscript…"; sub = "reading the manuscript…";
} }
// A filename is not a title. Publishers name files after print runs, so
// the derived title is a starting point the author can correct.
var rename = EDIT === b.id
? '<div class="rename">'
+ '<input id="rt" value="' + esc(b.title || "") + '" placeholder="Title">'
+ '<input id="ra" value="' + esc(b.author || "") + '" placeholder="Author">'
+ '<button type="button" class="btn" data-save="' + esc(b.id) + '">Save</button>'
+ '<button type="button" class="btn" data-cancel="1">Cancel</button>'
+ '</div>'
: '';
return '<div class="bk" data-id="' + esc(b.id) + '">' return '<div class="bk" data-id="' + esc(b.id) + '">'
+ '<div class="meta">' + '<div class="meta">'
+ '<h3>' + esc(b.title || "Untitled") + '</h3>' + '<h3>' + esc(b.title || "Untitled") + '</h3>'
+ '<div class="sub">' + sub + (b.created_at ? ' &middot; ' + esc(when(b.created_at)) : '') + '</div>' + '<div class="sub">' + (b.author ? esc(b.author) + ' &middot; ' : '') + sub
+ (b.created_at ? ' &middot; ' + esc(when(b.created_at)) : '') + '</div>'
+ (st === "failed" && b.note ? '<div class="why">' + esc(b.note) + '</div>' : '') + (st === "failed" && b.note ? '<div class="why">' + esc(b.note) + '</div>' : '')
+ rename
+ '</div>' + '</div>'
+ '<div class="acts">' + '<div class="acts">'
+ '<span class="state ' + esc(st) + '">' + esc(st) + '</span>' + '<span class="state ' + esc(st) + '">' + esc(st) + '</span>'
+ (st === "ready" + (st === "ready"
? '<button type="button" class="btn" data-pick="' + esc(b.id) + '">' ? '<button type="button" class="btn" data-pick="' + esc(b.id) + '">'
+ (PICKED === b.id ? "Selected" : "Use this book") + '</button>' + (PICKED === b.id ? "Selected" : "Use this book") + '</button>'
+ '<button type="button" class="btn" data-edit="' + esc(b.id) + '">Rename</button>'
: '') : '')
+ '<button type="button" class="btn" data-del="' + esc(b.id) + '">Remove</button>' + '<button type="button" class="btn" data-del="' + esc(b.id) + '">Remove</button>'
+ '</div></div>'; + '</div></div>';
@ -179,13 +195,26 @@ function readFileB64(file) {
} }
document.getElementById("books").addEventListener("click", async function (e) { document.getElementById("books").addEventListener("click", async function (e) {
var pick = e.target.getAttribute("data-pick"); var t = e.target;
var del = e.target.getAttribute("data-del"); var pick = t.getAttribute("data-pick"), del = t.getAttribute("data-del");
var edit = t.getAttribute("data-edit"), save = t.getAttribute("data-save");
if (t.getAttribute("data-cancel")) { EDIT = null; paintBooks(); return; }
if (edit) { EDIT = edit; paintBooks(); return; }
if (save) {
var title = document.getElementById("rt").value.trim();
var author = document.getElementById("ra").value.trim();
if (!title) { showMsg("msg", "A book needs a title."); return; }
t.disabled = true;
var u = await api("update_book", { book_id: save, title: title, author: author });
if (!u.ok) { showMsg("msg", u.error || "That did not save."); t.disabled = false; return; }
hideMsg("msg"); EDIT = null; loadBooks();
return;
}
if (pick) { PICKED = pick; hideMsg("msg"); paintBooks(); paintQuote(); return; } if (pick) { PICKED = pick; hideMsg("msg"); paintBooks(); paintQuote(); return; }
if (del) { if (del) {
e.target.disabled = true; t.disabled = true;
var r = await api("delete_book", { book_id: del }); var r = await api("delete_book", { book_id: del });
if (!r.ok) { showMsg("msg", r.error || "Could not remove that."); e.target.disabled = false; return; } if (!r.ok) { showMsg("msg", r.error || "Could not remove that."); t.disabled = false; return; }
if (PICKED === del) PICKED = null; if (PICKED === del) PICKED = null;
loadBooks(); loadBooks();
} }
@ -227,6 +256,9 @@ function readFileB64(file) {
resetPick(); resetPick();
if (!r.ok) { showMsg("msg", r.error || "That upload did not go through."); return; } if (!r.ok) { showMsg("msg", r.error || "That upload did not go through."); return; }
PICKED = null; PICKED = null;
// Straight into rename: the derived title is a guess and this is the one
// moment the author is looking at it.
EDIT = r.book && r.book.id;
loadBooks(); loadBooks();
} }
@ -275,7 +307,7 @@ function readFileB64(file) {
// Writing is the cheap half. Drawing the storyboards is priced separately, // Writing is the cheap half. Drawing the storyboards is priced separately,
// per writer, once you have read the scripts. // per writer, once you have read the scripts.
el.innerHTML = n + " writer" + (n > 1 ? "s" : "") el.innerHTML = n + " writer" + (n > 1 ? "s" : "")
+ " &middot; about $" + (n * 0.6).toFixed(2) + " to write" + " &middot; about $" + (n * 0.85).toFixed(2) + " to write a full-length novel"
+ " &middot; storyboards are priced separately once you have read them"; + " &middot; storyboards are priced separately once you have read them";
} }