diff --git a/build/Archive.hs b/build/Archive.hs index 6940f54..693c83e 100644 --- a/build/Archive.hs +++ b/build/Archive.hs @@ -265,8 +265,17 @@ loadArchiveEntries = do removed <- readRemovedUrls validateManifestEntries manifest removed provByUrl <- readProvenances + -- Join on normalised URLs, like every other URL comparison in the + -- archive system: editing a manifest URL to a normalisation- + -- equivalent form (http->https, trailing slash, tracking params) + -- must keep matching its provenance — an exact-string join would + -- silently unpublish the page while ArchiveIndex's normalised + -- filter keeps links pointing at it. Key collisions can't occur: + -- validateManifestEntries rejects normalised duplicates. + let normKey = T.unpack . normalizeUrl . T.pack + provByNorm = Map.mapKeys normKey provByUrl fmap catMaybes $ forM manifest $ \me -> - case Map.lookup (meUrl me) provByUrl of + case Map.lookup (normKey (meUrl me)) provByNorm of Nothing -> return Nothing Just (slug, pv) -> do let dir = "archive/" ++ slug diff --git a/build/Backlinks.hs b/build/Backlinks.hs index 205355d..a6b3abc 100644 --- a/build/Backlinks.hs +++ b/build/Backlinks.hs @@ -213,18 +213,28 @@ splitSentences = go [] -- For every internal link in a paragraph, emit an entry carrying the HTML -- of the sentence containing the link (default display) and the HTML of -- the full paragraph (hover/popup context). --- Recurses into Div, BlockQuote, BulletList, and OrderedList. +-- Recurses into Div, BlockQuote, BulletList, OrderedList, and +-- DefinitionList. @Plain@ matters as much as @Para@: Pandoc renders +-- tight list items (the default @- item@ Markdown form) as @Plain@ +-- blocks, so without it every link written in a tight list would be +-- invisible to the backlinks system. extractLinksWithContext :: Pandoc -> [LinkEntry] extractLinksWithContext (Pandoc _ blocks) = concatMap go blocks where go :: Block -> [LinkEntry] go (Para inlines) = paraEntries inlines + go (Plain inlines) = paraEntries inlines go (BlockQuote bs) = concatMap go bs go (Div _ bs) = concatMap go bs go (BulletList items) = concatMap (concatMap go) items go (OrderedList _ items) = concatMap (concatMap go) items + go (DefinitionList defs) = concatMap defEntries defs go _ = [] + defEntries :: ([Inline], [[Block]]) -> [LinkEntry] + defEntries (term, bodies) = + paraEntries term ++ concatMap (concatMap go) bodies + paraEntries :: [Inline] -> [LinkEntry] paraEntries inlines = let paraHtml = renderInlines inlines diff --git a/build/Filters/Sidenotes.hs b/build/Filters/Sidenotes.hs index dfb6e41..a7a2bae 100644 --- a/build/Filters/Sidenotes.hs +++ b/build/Filters/Sidenotes.hs @@ -18,7 +18,8 @@ import Data.Text (Text) import qualified Data.Text as T import Text.Pandoc.Class (runPure) import Text.Pandoc.Definition -import Text.Pandoc.Options (WriterOptions) +import Text.Pandoc.Options (WriterOptions (..), + HTMLMathMethod (KaTeX)) import Text.Pandoc.Walk (walkM) import Text.Pandoc.Writers.HTML (writeHtml5String) @@ -84,16 +85,25 @@ blocksToInlineHtml = T.concat . map renderOne renderOne b = blocksToHtml [b] +-- | Writer options for note bodies. Must agree with the math method in +-- 'Compilers.writerOpts' (KaTeX), or math inside a footnote silently +-- degrades to the writer default (PlainMath -> italics) and the +-- client-side KaTeX pass never sees it. Defined locally because +-- importing Compilers from here would create a module cycle +-- (Compilers -> Filters -> Filters.Sidenotes). +noteWriterOpts :: WriterOptions +noteWriterOpts = def { writerHTMLMathMethod = KaTeX "" } + -- | Render a list of inlines to HTML (no surrounding @
@).
inlinesToHtml :: [Inline] -> Text
inlinesToHtml inlines =
- case runPure (writeHtml5String (def :: WriterOptions) (Pandoc mempty [Plain inlines])) of
+ case runPure (writeHtml5String noteWriterOpts (Pandoc mempty [Plain inlines])) of
Left _ -> T.empty
Right t -> t
-- | Render a list of Pandoc blocks to an HTML fragment via a pure writer run.
blocksToHtml :: [Block] -> Text
blocksToHtml blocks =
- case runPure (writeHtml5String (def :: WriterOptions) (Pandoc mempty blocks)) of
+ case runPure (writeHtml5String noteWriterOpts (Pandoc mempty blocks)) of
Left _ -> T.empty
Right t -> t
diff --git a/build/Photography.hs b/build/Photography.hs
index 9e8f5b5..95bef5c 100644
--- a/build/Photography.hs
+++ b/build/Photography.hs
@@ -27,7 +27,7 @@ import Data.Maybe (mapMaybe, fromMaybe, catMaybes)
import qualified Data.Set as Set
import Data.Set (Set)
import Data.Ord (Down (..), comparing)
-import System.FilePath (takeDirectory, takeFileName, replaceExtension)
+import System.FilePath (takeBaseName, takeDirectory, takeFileName, replaceExtension)
import qualified Data.Aeson as Aeson
import Data.Aeson (Value (..), (.=))
import qualified Data.Aeson.KeyMap as KM
@@ -305,10 +305,11 @@ stripIndexHtml r
-- * @exact@: 4 decimal places (~10 m)
-- * @km@ : 2 decimal places (~1 km)
-- * @city@ : 1 decimal place (~10 km) — default
--- * other : treated as @city@
+-- * other : treated as @city@ (defensive only — 'buildPin' validates
+-- the precision and fails closed before consulting this function)
--
--- @hidden@ is handled at the call site by skipping the pin entirely;
--- this function is not consulted in that case.
+-- @hidden@ and unrecognised values are handled at the call site by
+-- skipping the pin entirely; this function is not consulted then.
roundCoord :: String -> Double -> Double
roundCoord prec x =
let n = case prec of
@@ -336,7 +337,10 @@ parseGeo meta = case KM.lookup "geo" meta of
-- | Build a single pin object from a photo entry. Returns 'Nothing'
-- when:
-- * the entry has no @geo:@ frontmatter, or
--- * it has @geo-precision: hidden@, or
+-- * @geo-precision:@ is anything other than @exact@/@km@/@city@ —
+-- @hidden@ and unrecognised values (typos, wrong case) alike.
+-- Failing closed means a typo'd \"hidden\" can never publish
+-- coordinates the author meant to suppress.
-- * the entry has no resolvable route (shouldn't happen for
-- photographyPattern items, but be defensive).
buildPin :: Item String -> Compiler (Maybe Value)
@@ -345,13 +349,21 @@ buildPin item = do
meta <- getMetadata ident
mRoute <- getRoute ident
case (parseGeo meta, lookupString "geo-precision" meta, mRoute) of
- (_, Just "hidden", _) -> return Nothing
- (Just (lat, lon), prec, Just r) ->
+ (Just (lat, lon), prec, Just r)
+ | maybe True (`elem` ["exact", "km", "city"]) prec ->
let prec' = fromMaybe "city" prec
rLat = roundCoord prec' lat
rLon = roundCoord prec' lon
fp = toFilePath ident
- slug = takeFileName (takeDirectory fp)
+ -- Directory entries (