Selects join the design system at the element; entities decode at the parser
Eric, correctly: "Didn't learn from last time I see :)". The container select in the where-it-lives card was native — because the previous fix styled .editform select, the container, not the element. The rule is now bare `select` (every page here is ours), so the fourth dropdown gets it for free, and the CSS comment records the lesson. Same screenshot, same class of miss: the About card showed raw ’ entities — the export got the double-encoding fix, the app never did. Fixed at the PARSER this time (models.parse_things_full decodes once, so every consumer — app, export, games.json — receives plain text), with a render-time decode covering entries enriched before the fix. Verified in-browser: "Great Britain's history", apostrophe and all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
co-authored by
Claude Fable 5
parent
fb611e0c87
commit
f435cffea0
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import html
|
||||||
import warnings
|
import warnings
|
||||||
import xml.etree.ElementTree as ET # parsing itself goes via defusedxml
|
import xml.etree.ElementTree as ET # parsing itself goes via defusedxml
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@@ -228,7 +229,12 @@ def parse_things_full(xml_text: str) -> list[dict]:
|
|||||||
"type": item.get("type", "boardgame"),
|
"type": item.get("type", "boardgame"),
|
||||||
"name": name.get("value", "") if name is not None else "",
|
"name": name.get("value", "") if name is not None else "",
|
||||||
"year": _attr_int(item.find("yearpublished")),
|
"year": _attr_int(item.find("yearpublished")),
|
||||||
"description": (item.findtext("description") or "").strip(),
|
# BGG serves descriptions with entities still encoded
|
||||||
|
# (’ etc): decode ONCE here so every consumer —
|
||||||
|
# app, export, games.json — gets plain text
|
||||||
|
"description": html.unescape(
|
||||||
|
(item.findtext("description") or "").strip()
|
||||||
|
),
|
||||||
"image": (item.findtext("image") or "").strip(),
|
"image": (item.findtext("image") or "").strip(),
|
||||||
"thumbnail": (item.findtext("thumbnail") or "").strip(),
|
"thumbnail": (item.findtext("thumbnail") or "").strip(),
|
||||||
"min_players": _attr_int(item.find("minplayers")),
|
"min_players": _attr_int(item.find("minplayers")),
|
||||||
|
|||||||
@@ -528,11 +528,16 @@ a.game:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
#pickfooter .edithint { flex: 1 1 100%; }
|
#pickfooter .edithint { flex: 1 1 100%; }
|
||||||
.editform select {
|
/* selects join the design system EVERYWHERE — the third time a native
|
||||||
|
dropdown showed up in a card, the lesson finally took: style the
|
||||||
|
element, not each container it appears in */
|
||||||
|
select {
|
||||||
font: inherit; font-size: .85rem; color: var(--ink);
|
font: inherit; font-size: .85rem; color: var(--ink);
|
||||||
border: 2px solid var(--board-edge); border-radius: var(--radius);
|
border: 2px solid var(--board-edge); border-radius: var(--radius);
|
||||||
padding: .25rem .45rem; background: #fff; max-width: 24rem;
|
padding: .25rem .45rem; background: #fff; max-width: 24rem;
|
||||||
}
|
}
|
||||||
|
#storedform { display: flex; gap: .5rem; align-items: center; }
|
||||||
|
#storedform select { flex: 1 1 auto; }
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
background: var(--board); border: 2px dashed var(--board-edge);
|
background: var(--board); border: 2px dashed var(--board-edge);
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
const KEY = decodeURIComponent(location.pathname.replace(/^\/library\/game\//, ""));
|
const KEY = decodeURIComponent(location.pathname.replace(/^\/library\/game\//, ""));
|
||||||
|
|
||||||
|
// entries enriched before descriptions were decoded at parse still hold
|
||||||
|
// ’-style entities: decode once before escaping for display
|
||||||
|
function decodeEntities(s) {
|
||||||
|
const ta = document.createElement("textarea");
|
||||||
|
ta.innerHTML = s;
|
||||||
|
return ta.value;
|
||||||
|
}
|
||||||
|
|
||||||
function chips(label, values) {
|
function chips(label, values) {
|
||||||
if (!values || !values.length) return "";
|
if (!values || !values.length) return "";
|
||||||
return `<div class="factrow"><b>${esc(label)}</b>
|
return `<div class="factrow"><b>${esc(label)}</b>
|
||||||
@@ -228,7 +236,7 @@ function render(g) {
|
|||||||
${contains}
|
${contains}
|
||||||
${photos}
|
${photos}
|
||||||
${g.description && !local ? `<h2>About</h2>
|
${g.description && !local ? `<h2>About</h2>
|
||||||
<div class="card prose"><p class="gdesc">${esc(g.description)}</p></div>` : ""}`;
|
<div class="card prose"><p class="gdesc">${esc(decodeEntities(g.description))}</p></div>` : ""}`;
|
||||||
wireLocal(g);
|
wireLocal(g);
|
||||||
wireStored(g);
|
wireStored(g);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user