Skip to content

Commit 559ee92

Browse files
TMHSDigitalclaude
andcommitted
Stack pages: add every pick to the shortlist in one click (#272)
A button under the stack's lead merges the pick ids into the shortlist's storage key without duplicates and says how many it added. The catalog and other tabs read the change from storage. Checked in a browser: 11 picks added, a second click adds none, the catalog shows 11. Closes #272 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpkeXQPaUhxjgqdqnk6BrJ
1 parent dd149be commit 559ee92

3 files changed

Lines changed: 35 additions & 3 deletions

File tree

‎site/lib/lib.test.mjs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,9 @@ has("stack title escaped", spage, "<title>Make a &quot;thing&quot; &lt;/script&g
452452
has("stack canonical", spage, '<link rel="canonical" href="https://x.test/s/stack/s1/" />');
453453
eq("stack one h1", (spage.match(/<h1[\s>]/g) || []).length, 1);
454454
has("stack pick links its entry page", spage, 'href="../../entry/a/"');
455+
has("stack: add-all carries every pick once (#272)", sp({ sections: [{ name: "Art", rows: [{ need: "Tiles", entry: eA, whyHtml: "" }, { need: "More", entry: eA, whyHtml: "" }, { need: "Sounds", entry: eC, whyHtml: "" }] }] }), `data-shortlist-all="${eA.id},${eC.id}"`);
456+
has("stack: add-all names the count", spage, "Add all 2 picks to my shortlist");
457+
has("stack: loads stack.js for the button", sp({ owed: owes([]) }), 'src="../../stack.js"');
455458
has("stack credit line escaped", spage, "Art by &quot;A&quot; &lt;x&gt;");
456459
has("copy all carries every canned line", spage, 'data-copy="Art by &quot;A&quot; &lt;x&gt;\nMusic by B"');
457460
has("no canned line note", spage, "No canned credit line: see the entry.");
@@ -460,7 +463,7 @@ has("needs-review flag on the pick", spage, 'class="pick-flag"');
460463
has("gaps rendered", spage, "<li>No parallax.</li>");
461464
has("walked line", spage, "Stack walked 2026-09-20. Each licence is only as current as its entry's verified date.");
462465
has("copy script with canned lines", spage, '<script src="../../stack.js" defer></script>');
463-
lacks("no copy script without canned lines", sp({ owed: owes([{ need: "Sounds", entry: eC }]) }), "stack.js");
466+
lacks("no script without canned lines or picks", sp({ owed: owes([{ need: "Sounds", entry: eC }]), sections: [] }), "stack.js");
464467
const sld = spage.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/)[1];
465468
lacks("stack json-ld cannot close its script", sld, "</script");
466469
eq("stack json-ld lists the picks", JSON.parse(sld).itemListElement[0].url, "https://x.test/s/entry/a/");

‎site/lib/stack-page.mjs‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,12 @@ export function stackPageHtml({ stack, sections, gapsHtml, leadHtml, owed, site,
113113
<ul class="gap-list">${gapsHtml.map((g) => `<li>${g}</li>`).join("")}</ul>
114114
</section>`
115115
: "";
116-
const script = copyAllText(owed) ? '<script src="../../stack.js" defer></script>' : "";
116+
// Every pick's id, once, for "Add all to shortlist" (stack.js; #272).
117+
const pickIds = [...new Set(sections.flatMap((s) => s.rows.map((r) => r.entry.id)))];
118+
const shortlistAll = pickIds.length
119+
? `<p class="copy-all shortlist-all"><button type="button" class="btn-ghost" data-shortlist-all="${esc(pickIds.join(","))}" hidden>Add all ${pickIds.length} picks to my shortlist</button> <span class="copy-status" role="status"></span></p>`
120+
: "";
121+
const script = copyAllText(owed) || pickIds.length ? '<script src="../../stack.js" defer></script>' : "";
117122
return `<!DOCTYPE html>
118123
<html lang="en">
119124
<head>
@@ -136,6 +141,7 @@ export function stackPageHtml({ stack, sections, gapsHtml, leadHtml, owed, site,
136141
</nav>
137142
<h1>${esc(meta.title)}</h1>
138143
<div class="entry-lead"><p>${esc(meta.task)}</p>${leadHtml}</div>
144+
${shortlistAll}
139145
<section class="owes" aria-labelledby="owes-title">
140146
<h2 id="owes-title">What this stack owes</h2>
141147
${owesHtml(owed)}

‎site/public/stack.js‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** Copy buttons on a stack page. The page works without them. */
1+
/** Copy buttons and "Add all to shortlist" on a stack page. The page works without them. */
22
(() => {
33
for (const btn of document.querySelectorAll("button[data-copy]")) {
44
btn.hidden = false;
@@ -23,4 +23,27 @@
2323
}
2424
});
2525
}
26+
27+
// Every pick into the shortlist (#272), under shortlist.js's storage key;
28+
// the catalog and other open tabs pick the change up from storage.
29+
const KEY = "fgda:shortlist";
30+
for (const btn of document.querySelectorAll("button[data-shortlist-all]")) {
31+
btn.hidden = false;
32+
btn.addEventListener("click", () => {
33+
const status = btn.parentElement.querySelector(".copy-status");
34+
const picks = btn.getAttribute("data-shortlist-all").split(",").filter(Boolean);
35+
try {
36+
let ids = JSON.parse(localStorage.getItem(KEY) || "[]");
37+
if (!Array.isArray(ids)) ids = [];
38+
ids = ids.filter((id) => typeof id === "string");
39+
const added = picks.filter((id) => !ids.includes(id));
40+
localStorage.setItem(KEY, JSON.stringify([...ids, ...added]));
41+
status.textContent = added.length
42+
? `Added ${added.length} of ${picks.length} to your shortlist (${ids.length + added.length} in all).`
43+
: `All ${picks.length} are already on your shortlist.`;
44+
} catch {
45+
status.textContent = "This browser is not keeping site data, so the shortlist cannot be saved.";
46+
}
47+
});
48+
}
2649
})();

0 commit comments

Comments
 (0)