diff --git a/src/bggpipe/models.py b/src/bggpipe/models.py index 5324cb7..d41ad69 100644 --- a/src/bggpipe/models.py +++ b/src/bggpipe/models.py @@ -2,6 +2,7 @@ from __future__ import annotations +import html import warnings import xml.etree.ElementTree as ET # parsing itself goes via defusedxml from dataclasses import dataclass, field @@ -228,7 +229,12 @@ def parse_things_full(xml_text: str) -> list[dict]: "type": item.get("type", "boardgame"), "name": name.get("value", "") if name is not None else "", "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(), "thumbnail": (item.findtext("thumbnail") or "").strip(), "min_players": _attr_int(item.find("minplayers")), diff --git a/src/bggpipe/static/app.css b/src/bggpipe/static/app.css index 4ecd4ce..4d3d4c3 100644 --- a/src/bggpipe/static/app.css +++ b/src/bggpipe/static/app.css @@ -528,11 +528,16 @@ a.game:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; } flex-wrap: wrap; } #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); border: 2px solid var(--board-edge); border-radius: var(--radius); padding: .25rem .45rem; background: #fff; max-width: 24rem; } +#storedform { display: flex; gap: .5rem; align-items: center; } +#storedform select { flex: 1 1 auto; } .empty { background: var(--board); border: 2px dashed var(--board-edge); diff --git a/src/bggpipe/templates/pages/librarygame.html b/src/bggpipe/templates/pages/librarygame.html index ea0c53f..823d7e1 100644 --- a/src/bggpipe/templates/pages/librarygame.html +++ b/src/bggpipe/templates/pages/librarygame.html @@ -8,6 +8,14 @@ "use strict"; 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) { if (!values || !values.length) return ""; return `
${esc(label)} @@ -228,7 +236,7 @@ function render(g) { ${contains} ${photos} ${g.description && !local ? `

About

-

${esc(g.description)}

` : ""}`; +

${esc(decodeEntities(g.description))}

` : ""}`; wireLocal(g); wireStored(g); }