Sidenotes: emit the section.footnotes fallback the CSS expects
The filter consumes every Pandoc Note, so the "standard Pandoc- generated section.footnotes" its doc claimed as the no-JS fallback never existed — below 1500px with JS disabled, footnote content was simply invisible (AUDIT §2.3). The filter now collects consumed notes and appends the section itself: letter labels, jump targets for the in-text refs (which now point at the visible fallback item), and doc-backlink returns. sidenotes.js pairs ref/note by element id and preventDefaults clicks, so behavior with JS is unchanged. Verified in output: per-page item count matches inline sidenote count; refs target #fn-<label>; backlinks target #snref-<label>. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4e28c82e4c
commit
8ca22a45d2
|
|
@ -4,12 +4,23 @@
|
|||
--
|
||||
-- Each footnote becomes:
|
||||
-- * A @<sup class="sidenote-ref">@ anchor in the body text.
|
||||
-- * An @<aside class="sidenote">@ immediately following it, containing
|
||||
-- * A @<span class="sidenote">@ immediately following it, containing
|
||||
-- the rendered note content.
|
||||
--
|
||||
-- On wide viewports, sidenotes.css floats asides into the right margin.
|
||||
-- On narrow viewports they are hidden; the standard Pandoc-generated
|
||||
-- @<section class="footnotes">@ at the document end serves as fallback.
|
||||
-- Additionally, every consumed note is re-emitted in a
|
||||
-- @<section class="footnotes">@ appended at the document end. The
|
||||
-- filter swallows Pandoc's own @Note@ inlines, so Pandoc's writer
|
||||
-- never produces that section itself — without this re-emission,
|
||||
-- narrow viewports with JavaScript disabled (where sidenotes.css
|
||||
-- hides @.sidenote@ and sidenotes.js's bottom sheet never runs)
|
||||
-- would lose footnote content entirely.
|
||||
--
|
||||
-- On wide viewports, sidenotes.css floats the spans into the right
|
||||
-- margin and hides @section.footnotes@; on narrow viewports the
|
||||
-- spans are hidden and the section is shown. The in-text anchor
|
||||
-- targets the footnotes item (the only target visible on narrow
|
||||
-- no-JS viewports); sidenotes.js intercepts clicks and pairs
|
||||
-- ref\/note by element id, so the href is purely the no-JS path.
|
||||
module Filters.Sidenotes (apply) where
|
||||
|
||||
import Control.Monad.State.Strict
|
||||
|
|
@ -23,17 +34,53 @@ import Text.Pandoc.Options (WriterOptions (..),
|
|||
import Text.Pandoc.Walk (walkM)
|
||||
import Text.Pandoc.Writers.HTML (writeHtml5String)
|
||||
|
||||
-- | Transform all @Note@ inlines in the document to inline sidenote HTML.
|
||||
apply :: Pandoc -> Pandoc
|
||||
apply doc = evalState (walkM convertNote doc) (1 :: Int)
|
||||
-- | Accumulator: next label counter plus collected notes
|
||||
-- (newest-first; reversed before rendering the fallback section).
|
||||
type NoteState = (Int, [(Text, [Block])])
|
||||
|
||||
convertNote :: Inline -> State Int Inline
|
||||
-- | Transform all @Note@ inlines in the document to inline sidenote
|
||||
-- HTML, and append the collected notes as a @section.footnotes@
|
||||
-- fallback block.
|
||||
apply :: Pandoc -> Pandoc
|
||||
apply doc =
|
||||
let (Pandoc m blocks, (_, collected)) =
|
||||
runState (walkM convertNote doc) (1, [])
|
||||
notes = reverse collected
|
||||
in Pandoc m $
|
||||
if null notes
|
||||
then blocks
|
||||
else blocks ++ [footnotesSection notes]
|
||||
|
||||
convertNote :: Inline -> State NoteState Inline
|
||||
convertNote (Note blocks) = do
|
||||
n <- get
|
||||
put (n + 1)
|
||||
(n, acc) <- get
|
||||
put (n + 1, (toLabel n, blocks) : acc)
|
||||
return $ RawInline "html" (renderNote n blocks)
|
||||
convertNote x = return x
|
||||
|
||||
-- | The end-of-document fallback list. Letter labels are rendered
|
||||
-- explicitly (an @<ol>@'s automatic numbering would disagree with
|
||||
-- the in-text letters), so the list itself is unstyled.
|
||||
footnotesSection :: [(Text, [Block])] -> Block
|
||||
footnotesSection notes = RawBlock "html" $ T.concat $
|
||||
[ "<section class=\"footnotes\" role=\"doc-endnotes\">"
|
||||
, "<ol class=\"footnotes-list\">"
|
||||
]
|
||||
++ map item notes ++
|
||||
[ "</ol>"
|
||||
, "</section>"
|
||||
]
|
||||
where
|
||||
item (lbl, blocks) = T.concat
|
||||
[ "<li id=\"fn-", lbl, "\" class=\"footnote-item\">"
|
||||
, "<span class=\"footnote-label\" aria-hidden=\"true\">", lbl, "</span>"
|
||||
, blocksToHtml blocks
|
||||
, "<a href=\"#snref-", lbl
|
||||
, "\" class=\"footnote-back\" role=\"doc-backlink\""
|
||||
, " aria-label=\"Back to reference ", lbl, "\">\x21a9\xfe0e</a>"
|
||||
, "</li>"
|
||||
]
|
||||
|
||||
-- | Convert a 1-based counter to a letter label using base-26 expansion
|
||||
-- (Excel-column style): 1→a, 2→b, … 26→z, 27→aa, 28→ab, … 52→az,
|
||||
-- 53→ba, … 702→zz, 703→aaa. Guarantees a unique label per counter so
|
||||
|
|
@ -54,8 +101,14 @@ renderNote n blocks =
|
|||
let inner = blocksToInlineHtml blocks
|
||||
lbl = toLabel n
|
||||
in T.concat
|
||||
-- href targets the footnotes-section item: on narrow no-JS
|
||||
-- viewports that is the only visible rendering of the note
|
||||
-- (the adjacent .sidenote span is display:none there, and on
|
||||
-- wide viewports the note is already visible in the margin).
|
||||
-- sidenotes.js pairs ref/note by id and preventDefaults the
|
||||
-- click, so the href only ever navigates without JS.
|
||||
[ "<sup class=\"sidenote-ref\" id=\"snref-", lbl, "\">"
|
||||
, "<a href=\"#sn-", lbl, "\">", lbl, "</a>"
|
||||
, "<a href=\"#fn-", lbl, "\">", lbl, "</a>"
|
||||
, "</sup>"
|
||||
, "<span class=\"sidenote\" id=\"sn-", lbl, "\">"
|
||||
, "<sup class=\"sidenote-num\">", lbl, "</sup>\x00a0"
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@
|
|||
For an inline <span> inside a <p>, this is roughly the line containing
|
||||
the sidenote reference, giving correct vertical alignment without JS.
|
||||
|
||||
On narrow viewports the <span> is hidden and the Pandoc-generated
|
||||
<section class="footnotes"> at document end is shown instead.
|
||||
On narrow viewports the <span> is hidden and the
|
||||
<section class="footnotes"> the Sidenotes filter appends at document
|
||||
end is shown instead (Pandoc's own footnote section never exists —
|
||||
the filter consumes every Note, and re-emits this fallback itself).
|
||||
*/
|
||||
|
||||
/* ============================================================
|
||||
|
|
@ -137,22 +139,54 @@
|
|||
|
||||
|
||||
/* ============================================================
|
||||
FOOTNOTE REFERENCES — shown on narrow viewports alongside
|
||||
section.footnotes
|
||||
FOOTNOTES FALLBACK LIST — the section the Sidenotes filter
|
||||
appends at document end; visible on narrow viewports only
|
||||
(see the media queries above). Letter labels are rendered
|
||||
explicitly because an <ol>'s automatic numbers would disagree
|
||||
with the in-text letter refs.
|
||||
============================================================ */
|
||||
|
||||
a.footnote-ref {
|
||||
text-decoration: none;
|
||||
color: var(--text-faint);
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
section.footnotes .footnotes-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.footnote-item {
|
||||
position: relative;
|
||||
top: -0.4em;
|
||||
padding-left: 1.5rem;
|
||||
margin-bottom: 0.85rem;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.6;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.footnote-label {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0.15em;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.75em;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
/* First paragraph flows on the label's line; later ones stack. */
|
||||
.footnote-item > p {
|
||||
margin: 0 0 0.5em;
|
||||
}
|
||||
.footnote-item > p:first-of-type {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.footnote-back {
|
||||
margin-left: 0.35em;
|
||||
text-decoration: none;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--text-faint);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
a.footnote-ref:hover {
|
||||
.footnote-back:hover {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue