feat(booth): render .md/.txt/.log inline in the gallery, collapsible + closable
Docs used to render as a clumsy link that navigated to a separate page. They now render in place: build_gallery pre-renders each doc (markdown -> HTML, plain text raw) and the gallery shows it inside a native <details open> disclosure that spans the full grid width so prose has a readable measure. The doc bar carries: a collapse chevron (the whole <details> summary toggles, works with JS off), a full-page link (still reaches the standalone viewer), a download link, and a session-close ✕. The ✕ needed stopPropagation + preventDefault because it lives inside <summary> — otherwise its click would toggle the disclosure instead of hiding the item. Close is JS (progressive enhancement); collapse is native. Two design points: - Plain text is returned RAW from build_gallery and escaped by the template inside <pre>. Pre-escaping in Python plus Jinja autoescape would double-encode angle brackets; a test pins the single-escape. - Inlining is bounded by DOC_MAX_BYTES. A doc over the limit keeps the old link-out behaviour rather than being rendered into every index load; a test covers the fallback. The shared .markdown-body / .textview typography moved from doc.html's scoped <style> into base.html so the inline body and the full-page view render identically; doc.html keeps only its page-layout wrapper. Updated the pre-existing test_gallery_links_docs_to_view: it asserted the old link-out behaviour the operator asked to change, so it now asserts the inline render plus the surviving full-page and download affordances. 61 pass. Verified live: markdown renders with headings/table/blockquote/code, txt preserves whitespace and single-escapes, collapse and ✕-close both work.
This commit is contained in:
@@ -105,6 +105,89 @@ def test_build_gallery_folds_caption_sidecars(tmp_path):
|
||||
assert "loose.txt" in by_name # a caption with nothing to caption stays visible
|
||||
|
||||
|
||||
# ---- inline doc rendering ---------------------------------------------------
|
||||
#
|
||||
# .md / .txt / .log docs render INLINE in the gallery (collapsible), not as a
|
||||
# clumsy link to a separate page. build_gallery pre-renders the content so the
|
||||
# template stays logicless.
|
||||
|
||||
|
||||
def test_build_gallery_prerenders_markdown_inline(tmp_path):
|
||||
booth = tmp_path / "b"
|
||||
booth.mkdir()
|
||||
(booth / "report.md").write_text("# Title\n\nsome **bold** text\n")
|
||||
|
||||
it = next(i for i in build_gallery(booth) if i["name"] == "report.md")
|
||||
|
||||
assert it["doc"] == "markdown"
|
||||
assert it["rendered_html"] is True
|
||||
assert "<h1>Title</h1>" in it["rendered"]
|
||||
assert "<strong>bold</strong>" in it["rendered"]
|
||||
|
||||
|
||||
def test_build_gallery_prerenders_text_as_raw(tmp_path):
|
||||
booth = tmp_path / "b"
|
||||
booth.mkdir()
|
||||
(booth / "notes.txt").write_text("plain <not html> line")
|
||||
|
||||
it = next(i for i in build_gallery(booth) if i["name"] == "notes.txt")
|
||||
|
||||
assert it["doc"] == "text"
|
||||
assert it["rendered_html"] is False
|
||||
# Raw text is NOT pre-escaped here — the template escapes it inside <pre>.
|
||||
# Pre-escaping plus template autoescape would double-encode the angle brackets.
|
||||
assert it["rendered"] == "plain <not html> line"
|
||||
|
||||
|
||||
def test_build_gallery_oversize_doc_is_not_inlined(tmp_path):
|
||||
booth = tmp_path / "b"
|
||||
booth.mkdir()
|
||||
big = "x" * (2 * 1024 * 1024 + 10) # over DOC_MAX_BYTES
|
||||
(booth / "huge.log").write_text(big)
|
||||
|
||||
it = next(i for i in build_gallery(booth) if i["name"] == "huge.log")
|
||||
|
||||
assert it["doc"] == "text"
|
||||
assert it["rendered"] is None # too big to inline; template falls back to a link
|
||||
|
||||
|
||||
def test_non_doc_item_has_no_rendered_field(tmp_path):
|
||||
booth = tmp_path / "b"
|
||||
_touch(booth / "shot.png")
|
||||
|
||||
it = next(i for i in build_gallery(booth) if i["name"] == "shot.png")
|
||||
|
||||
assert it["doc"] is None
|
||||
assert it["rendered"] is None
|
||||
|
||||
|
||||
def test_booth_page_renders_markdown_inline_collapsible(client):
|
||||
c, data = client
|
||||
(data / "run1").mkdir()
|
||||
(data / "run1" / "brief.md").write_text("# Heading\n\nbody line\n")
|
||||
|
||||
html = c.get("/b/run1/").text
|
||||
|
||||
# rendered inline, inside a native <details> disclosure — no navigation
|
||||
assert "<details" in html
|
||||
assert "<h1>Heading</h1>" in html
|
||||
# and the raw-file link is still available for download / full view
|
||||
assert "brief.md" in html
|
||||
|
||||
|
||||
def test_booth_page_inlines_txt_without_double_escaping(client):
|
||||
c, data = client
|
||||
(data / "run1").mkdir()
|
||||
(data / "run1" / "log.txt").write_text("value <x> & <y>")
|
||||
|
||||
html = c.get("/b/run1/").text
|
||||
|
||||
# exactly one level of HTML-escaping (template autoescape inside <pre>),
|
||||
# not the double-encoding that pre-escaping in Python would produce
|
||||
assert "value <x> & <y>" in html
|
||||
assert "&lt;" not in html
|
||||
|
||||
|
||||
# ---- HTTP surface -----------------------------------------------------------
|
||||
|
||||
|
||||
@@ -487,15 +570,20 @@ def test_view_text_shows_preformatted(client):
|
||||
assert "attachment" not in r.headers.get("content-disposition", "")
|
||||
|
||||
|
||||
def test_gallery_links_docs_to_view(client):
|
||||
def test_gallery_inlines_docs_with_fullpage_and_download_affordances(client):
|
||||
# Docs now render INLINE in the gallery (see the inline-doc tests above),
|
||||
# not as a link. The full-page viewer stays reachable via the ⤢ affordance,
|
||||
# and the raw file via a download link — but the doc content itself is on
|
||||
# the page, not behind a click.
|
||||
c, data = client
|
||||
d = data / "run1"; d.mkdir()
|
||||
(d / "readme.md").write_text("# hi")
|
||||
(d / "notes.txt").write_text("hello") # loose txt (no media partner) -> own item
|
||||
page = c.get("/b/run1/")
|
||||
assert "view?f=readme.md" in page.text # md -> viewer
|
||||
assert "view?f=notes.txt" in page.text # txt -> viewer
|
||||
assert 'href="readme.md" download' not in page.text # not a forced download
|
||||
page = c.get("/b/run1/").text
|
||||
assert "<h1>hi</h1>" in page # md rendered inline
|
||||
assert "hello" in page # txt shown inline
|
||||
assert "view?f=readme.md" in page # full-page viewer still linked (⤢)
|
||||
assert 'href="readme.md" download' in page # download affordance present
|
||||
|
||||
|
||||
# ---- image viewer prev/next nav ---------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user