Fix image .margin positioning where caption and image got that layout instead of the figure wrapper getting it.

This commit is contained in:
2026-09-03 14:12:35 +00:00
parent 4413c7efdf
commit b133ad6dd6
3 changed files with 31 additions and 14 deletions
+12 -9
View File
@@ -847,7 +847,7 @@ article.multicol {
article.multicol .margin, article.multicol .margin,
article.multicol .aside, article.multicol .aside,
article.multicol figure:has(.margin) { article.multicol figure.margin {
position: absolute; position: absolute;
left: 0; left: 0;
width: 14rem; width: 14rem;
@@ -898,7 +898,7 @@ article.multicol {
body:has(#sidebar):has(.multicol):not(.editing) article.multicol .margin, body:has(#sidebar):has(.multicol):not(.editing) article.multicol .margin,
body:has(#sidebar):has(.multicol):not(.editing) article.multicol .aside, body:has(#sidebar):has(.multicol):not(.editing) article.multicol .aside,
body:has(#sidebar):has(.multicol):not(.editing) article.multicol figure:has(.margin) { body:has(#sidebar):has(.multicol):not(.editing) article.multicol figure.margin {
position: absolute; position: absolute;
/* Attached to the article's left border (1.25rem gap), hanging into /* Attached to the article's left border (1.25rem gap), hanging into
the left lane and growing leftward with it: 12rem when the lane is the left lane and growing leftward with it: 12rem when the lane is
@@ -916,7 +916,7 @@ article.multicol {
/* A shrink-wrapped figure (explicit image width) centers in the plain /* A shrink-wrapped figure (explicit image width) centers in the plain
layout; inside a column the centering looks adrift — left-align. layout; inside a column the centering looks adrift — left-align.
Floated figures keep their own margins (the text gap). */ Floated figures keep their own margins (the text gap). */
.multicol .colseg.cols figure:has(img[width]):not(:has(.left), :has(.right), :has(.margin)) { .multicol .colseg.cols figure:has(img[width]):not(:has(.left), :has(.right), .margin) {
margin-inline: 0; margin-inline: 0;
} }
@@ -1094,7 +1094,8 @@ blockquote p + p {
} }
/* Side boxes: ::: aside is a muted floated box; {.margin} / ::: margin /* Side boxes: ::: aside is a muted floated box; {.margin} / ::: margin
is a plainer margin note, and figures take {.margin} like {.left}. is a plainer margin note, and figures take the class directly on the
<figure> (the renderer moves it off the img), floating like {.left}.
Where the layout has room for a side zone — multicol pages, the Where the layout has room for a side zone — multicol pages, the
sidebar's track, the wide single-column gutter (see the article sidebar's track, the wide single-column gutter (see the article
section and the figure rules below) — the boxes are taken out of flow section and the figure rules below) — the boxes are taken out of flow
@@ -1287,7 +1288,8 @@ dd {
Markdown images standing alone in a paragraph render as a block Markdown images standing alone in a paragraph render as a block
<figure> (with <figcaption> when the image has a title); the <figure> (with <figcaption> when the image has a title); the
brace-attribute positioning class ({.left}, {.right}, {.wide}) lives brace-attribute positioning class ({.left}, {.right}, {.wide}) lives
on the img inside, but only the figure is ever positioned, so the on the img inside — except {.margin}, which the renderer moves onto
the figure itself — but only the figure is ever positioned, so the
caption stays below the image. Raw <img> HTML written by the author caption stays below the image. Raw <img> HTML written by the author
stays inline and unstyled beyond these defaults. */ stays inline and unstyled beyond these defaults. */
img { img {
@@ -1353,9 +1355,10 @@ figure:has(img[width]) {
width: fit-content; width: fit-content;
} }
/* {.margin} figures float left like {.left} ones — until they fall into /* {.margin} figures (the class moves onto the figure wrapper at render —
see markdown.py) float left like {.left} ones — until they fall into
the side zone (see the composition rules up in the article section). */ the side zone (see the composition rules up in the article section). */
figure:has(.margin) { figure.margin {
float: left; float: left;
width: 30%; width: 30%;
max-width: 50%; max-width: 50%;
@@ -1445,7 +1448,7 @@ article figure img {
@media (min-width: 104rem) { @media (min-width: 104rem) {
body:not(.editing):not(:has(.multicol)) article .margin, body:not(.editing):not(:has(.multicol)) article .margin,
body:not(.editing):not(:has(.multicol)) article .aside, body:not(.editing):not(:has(.multicol)) article .aside,
body:not(.editing):not(:has(.multicol)) article figure:has(.margin) { body:not(.editing):not(:has(.multicol)) article figure.margin {
position: absolute; position: absolute;
--box-w: min(18rem, (100vw - 78rem) / 2 - 1.25rem); --box-w: min(18rem, (100vw - 78rem) / 2 - 1.25rem);
width: var(--box-w); width: var(--box-w);
@@ -1668,7 +1671,7 @@ article h2 {
figure:has(.right), figure:has(.right),
figure:has(.left), figure:has(.left),
figure:has(.margin) { figure.margin {
float: none; float: none;
width: 100%; width: 100%;
max-width: none; max-width: none;
+3 -2
View File
@@ -30,6 +30,7 @@ walking the tree (``resolve``), moves are slot detach/attach
import asyncio import asyncio
import logging import logging
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
@@ -39,12 +40,12 @@ from pagerite import api, files, pages, tracking, translate
from pagerite.__main__ import DEVMODE from pagerite.__main__ import DEVMODE
from pagerite.files import file_store from pagerite.files import file_store
from pagerite.state import HOSTNAME, analytics_store, data, frontend, kanta from pagerite.state import HOSTNAME, analytics_store, data, frontend, kanta
from collections.abc import AsyncGenerator
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@asynccontextmanager @asynccontextmanager
async def lifespan(_app: FastAPI): async def lifespan(_app: FastAPI) -> AsyncGenerator:
"""Open the database (migrations run inside kanta.open), load assets, load GeoIP.""" """Open the database (migrations run inside kanta.open), load assets, load GeoIP."""
async with kanta: async with kanta:
translate.log_service_urls(data.translate_keys, HOSTNAME) translate.log_service_urls(data.translate_keys, HOSTNAME)
+16 -3
View File
@@ -156,14 +156,27 @@ def _image_rule(
page = env.get("page_path", "") page = env.get("page_path", "")
token.attrs["src"] = f"/{page}/{src}" if page else f"/{src}" token.attrs["src"] = f"/{page}/{src}" if page else f"/{src}"
token.attrs["alt"] = self.renderInlineAsText(token.children, options, env) token.attrs["alt"] = self.renderInlineAsText(token.children, options, env)
img = self.renderToken(tokens, idx, options, env)
if len(tokens) == 1: if len(tokens) == 1:
# The only inline content of its paragraph: render as a block # The only inline content of its paragraph: render as a block
# figure, captioned when titled. (The <p> wrapper is dropped by # figure, captioned when titled. (The <p> wrapper is dropped by
# _unwrap_lone_figures below.) # _unwrap_lone_figures below.) {.margin} positions the whole
# figure, so it moves from the img onto the figure wrapper — left
# on the img, the margin-breakout CSS would pull the image out of
# the figure (and mostly off-screen), leaving the caption behind.
classes = (token.attrs.get("class") or "").split()
figure_class = ""
if "margin" in classes:
classes.remove("margin")
if classes:
token.attrs["class"] = " ".join(classes)
else:
del token.attrs["class"]
figure_class = ' class="margin"'
img = self.renderToken(tokens, idx, options, env)
title = token.attrs.get("title") title = token.attrs.get("title")
caption = f"<figcaption>{escapeHtml(title)}</figcaption>" if title else "" caption = f"<figcaption>{escapeHtml(title)}</figcaption>" if title else ""
return f"<figure>{img}{caption}</figure>" return f"<figure{figure_class}>{img}{caption}</figure>"
img = self.renderToken(tokens, idx, options, env)
# Inline with other content: a plain inline image. # Inline with other content: a plain inline image.
return img return img