import json import html as _esc import httpx from fastapi import APIRouter from fastapi.responses import HTMLResponse from editor_page import _API_URL router = APIRouter() # ── Google Fonts ──────────────────────────────────────────────────────────── _GFONTS = ( "https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400;0,700;1,400;1,700" "&family=Caladea:ital,wght@0,400;0,700;1,400;1,700" "&family=Cormorant+Garamond:ital,wght@0,400;0,700;1,400;1,700" "&family=Cousine:ital,wght@0,400;0,700;1,400;1,700" "&family=Crimson+Text:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700" "&family=EB+Garamond:ital,wght@0,400;0,700;1,400;1,700" "&family=Fira+Code:wght@400;700" "&family=Inter:wght@400;500;700" "&family=JetBrains+Mono:ital,wght@0,400;0,700;1,400;1,700" "&family=Josefin+Sans:ital,wght@0,400;0,700;1,400;1,700" "&family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700" "&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400" "&family=Libre+Franklin:ital,wght@0,400;0,700;1,400;1,700" "&family=Lora:ital,wght@0,400;0,700;1,400;1,700" "&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700" "&family=Nunito:ital,wght@0,400;0,700;1,400;1,700" "&family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700" "&family=Oswald:wght@400;500;700" "&family=Outfit:wght@400;700" "&family=Patrick+Hand" "&family=Playfair+Display:ital,wght@0,400;0,700;1,400;1,700" "&family=Poppins:ital,wght@0,400;0,700;1,400;1,700" "&family=Roboto+Mono:ital,wght@0,400;0,700;1,400;1,700" "&family=Roboto:ital,wght@0,400;0,700;1,400;1,700" "&family=Source+Code+Pro:ital,wght@0,400;0,700;1,400;1,700" "&family=Tinos:ital,wght@0,400;0,700;1,400;1,700" "&display=swap" ) # ── HTML template ─────────────────────────────────────────────────────────── # Placeholders use %%NAME%% — never clashes with CSS/JS { } braces. _TMPL = """\
{excerpt_text}
' if excerpt_text else "" ) tags = a.get("tags") or [] tags_html = "" if tags: chips = "".join( f'#{_esc.escape(t["name"])}' for t in tags ) tags_html = f'' # Content is safely embedded as a JS string via json.dumps — # no risk of HTML injection or format-string collisions. content_json = json.dumps(a.get("content") or "") return ( _TMPL .replace("%%GFONTS%%", _GFONTS) .replace("%%TITLE%%", title) .replace("%%EXCERPT_TEXT%%", excerpt_text) .replace("%%FONT_FAMILY%%", font_family) .replace("%%PUB_DATE%%", pub_date) .replace("%%VIEW_COUNT%%", view_count) .replace("%%COVER_HTML%%", cover_html) .replace("%%CAT_HTML%%", cat_html) .replace("%%META_SEP%%", meta_sep) .replace("%%EXCERPT_HTML%%", excerpt_html) .replace("%%TAGS_HTML%%", tags_html) .replace("%%CONTENT_JSON%%", content_json) ) @router.get("/article/{slug}", response_class=HTMLResponse) async def article_page(slug: str): async with httpx.AsyncClient(timeout=10.0) as client: r = await client.get(f"{_API_URL}/news/{slug}") if r.status_code == 404: return HTMLResponse( content='