Web dashboard step 2: full issue pages, article chapters, POST /rate
Signed-in issue page with the Brief, downloads, index and colophon; article, World Briefing and Behind the paper pages under the login guard; the rating widget with its fetch enhancement; the admin-only POST /rate writing dashboard-attributed rating events; the HMAC confirmation page links to the site. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
This commit is contained in:
+26
-2
@@ -11,11 +11,29 @@ a { color:var(--accent); }
|
||||
main { min-height:70vh; }
|
||||
.reading { max-width:72ch; margin:2rem auto; }
|
||||
.narrow { max-width:34rem; }
|
||||
.dateline,.stats,.byline,.comments,.strap { color:var(--muted); }
|
||||
.dateline,.stats,.byline,.comments,.strap,.meta,.social,.index-meta { color:var(--muted); }
|
||||
.issue section { border-top:1px solid var(--rule); margin-top:2rem; }
|
||||
.issue article { border-bottom:1px solid var(--rule); padding:.4rem 0 .8rem; }
|
||||
.issue .lead h3 { font-size:1.45rem; }
|
||||
.comments a,.downloads a { margin-right:.8rem; }
|
||||
.downloads { display:flex; flex-wrap:wrap; gap:.6rem; margin:1.5rem 0; }
|
||||
.button { border:1px solid currentColor; padding:.45rem .7rem; text-decoration:none; }
|
||||
.index-list { list-style:none; padding:0; }
|
||||
.index-entry { border-bottom:1px solid var(--rule); padding:.5rem 0 1rem; }
|
||||
.index-entry h3 { margin-bottom:.1rem; }
|
||||
.index-entry p { margin:.35rem 0; }
|
||||
.issue-chapters { border-block:1px solid var(--rule); margin:2rem 0; padding:1rem 0; text-align:center; font-size:1.15rem; }
|
||||
.colophon { max-width:none; margin:2rem 0; text-align:left; }
|
||||
.rule { border:0; border-top:1px solid var(--rule); margin:1.5rem 0; }
|
||||
.article-header h1 a { color:inherit; }
|
||||
.article-body img { display:block; max-width:100%; height:auto; margin:1rem auto; }
|
||||
.article-body pre { max-width:100%; overflow-x:auto; }
|
||||
.discussion { border-top:1px solid var(--rule); margin-top:2rem; padding-top:1rem; }
|
||||
.discussion blockquote { border-left:2px solid var(--rule); margin-left:.5rem; padding-left:1rem; }
|
||||
.discussion blockquote.reply { margin-left:1.5rem; }
|
||||
.comment-meta { color:var(--muted); font-size:.9rem; }
|
||||
.prev-next { display:grid; grid-template-columns:1fr 1fr; gap:1rem; margin:1.5rem 0; }
|
||||
.prev-next a:last-child { text-align:right; }
|
||||
form { display:grid; gap:.8rem; margin:1.5rem 0; }
|
||||
label { display:grid; gap:.25rem; }
|
||||
input,textarea,button { font:inherit; padding:.45rem; }
|
||||
@@ -31,5 +49,11 @@ thead { position:sticky; top:0; background:var(--bg); }
|
||||
.kv { display:grid; grid-template-columns:minmax(10rem,1fr) 3fr; } .kv dt { color:var(--muted); }
|
||||
.funnel > * { background:var(--good); min-width:1px; margin:.2rem 0; }
|
||||
.spark { max-width:100%; height:auto; }
|
||||
.rating { display:flex; flex-wrap:wrap; gap:.3rem; }
|
||||
.rating { display:flex; flex-wrap:wrap; align-items:center; gap:.3rem; }
|
||||
.rating button { border:1px solid var(--rule); background:transparent; color:var(--fg); cursor:pointer; }
|
||||
.rating button.active { background:var(--fg); color:var(--bg); border-color:var(--fg); }
|
||||
.rating button.clear { border:0; color:var(--muted); padding-inline:.25rem; text-decoration:underline; }
|
||||
.rating button.clear.active { background:transparent; color:var(--fg); font-weight:700; }
|
||||
.rating-prompt { margin-right:.25rem; }
|
||||
.rating-note { flex-basis:100%; }
|
||||
@media (max-width:40rem) { .masthead { font-size:1.55rem; } .kv { display:block; } }
|
||||
|
||||
+31
-1
@@ -1,5 +1,35 @@
|
||||
document.addEventListener("submit", (event) => {
|
||||
const message = event.target.dataset.confirm;
|
||||
const form = event.target;
|
||||
if (form.matches("form.rating") && event.submitter) {
|
||||
event.preventDefault();
|
||||
const submitted = event.submitter;
|
||||
const body = new URLSearchParams(new FormData(form));
|
||||
body.set(submitted.name, submitted.value);
|
||||
fetch(form.action, {
|
||||
method: "POST",
|
||||
body,
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" },
|
||||
}).then((response) => {
|
||||
if (!response.ok) throw new Error("rating request failed");
|
||||
return response.json();
|
||||
}).then((result) => {
|
||||
form.querySelectorAll("button[data-label]").forEach((button) => {
|
||||
const active = button.dataset.label === result.label;
|
||||
button.classList.toggle("active", active);
|
||||
button.setAttribute("aria-pressed", active ? "true" : "false");
|
||||
});
|
||||
}).catch(() => {
|
||||
const label = document.createElement("input");
|
||||
label.type = "hidden";
|
||||
label.name = submitted.name;
|
||||
label.value = submitted.value;
|
||||
form.appendChild(label);
|
||||
form.submit();
|
||||
});
|
||||
return;
|
||||
}
|
||||
const message = form.dataset.confirm;
|
||||
if (message && !window.confirm(message)) event.preventDefault();
|
||||
});
|
||||
document.querySelectorAll("details[id]").forEach((details) => {
|
||||
|
||||
Reference in New Issue
Block a user