levineuwirth.org/build/Dingbat.hs

80 lines
3.0 KiB
Haskell

{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Section-break ornament selection.
--
-- Every page exposes a @$dingbat$@ template variable naming the ornament to
-- use in place of the standard @<hr>@. The template renders this as
-- @data-dingbat="..."@ on @<body>@; CSS attribute selectors then swap the
-- @hr::after@ glyph.
--
-- Resolution order:
--
-- 1. @dingbat:@ frontmatter key on the page (must be in 'knownDingbats').
-- 2. Section default derived from the item's route ('sectionDefault').
-- 3. Fallback ('fallbackDingbat').
--
-- Best practice: set @dingbat:@ explicitly in frontmatter. The section
-- defaults are a safety net, not a substitute.
--
-- Adding a new ornament:
--
-- 1. Add its name to 'knownDingbats'.
-- 2. Optionally assign a section default in 'sectionDefault'.
-- 3. Add a matching @body[data-dingbat="…"]@ rule in typography.css.
module Dingbat
( dingbatField
, knownDingbats
) where
import Data.List (isPrefixOf)
import Data.Maybe (fromMaybe)
import Hakyll
-- | Curated palette. Extend here when adding a new ornament.
knownDingbats :: [String]
knownDingbats =
[ "asterism" -- ⁂ typographic asterism (neutral fallback)
, "asterisks" -- * * * spaced asterisks (classic scene break)
, "fleuron" -- Aldine leaf (literary essays)
, "trefoil" -- three-lobed ornament (poetry)
, "lozenge" -- diamond/rhombus (blog)
, "clef" -- musical ornament (music)
, "memento" -- mourning ornament (memento-mori)
, "tech" -- tech ornament
, "ai" -- AI ornament (the cute robot)
]
-- | Last-resort default when neither frontmatter nor section rule applies.
fallbackDingbat :: String
fallbackDingbat = "asterism"
-- | Section defaults matched against the item's route prefix.
-- First matching prefix wins. Unmatched routes use 'fallbackDingbat'.
sectionDefault :: String -> String
sectionDefault r
| "essays/" `isPrefixOf` r = "fleuron"
| "blog/" `isPrefixOf` r = "lozenge"
| "poetry/" `isPrefixOf` r = "trefoil"
| "fiction/" `isPrefixOf` r = "asterisks"
| "music/" `isPrefixOf` r = "clef"
| "memento-mori/" `isPrefixOf` r = "memento"
| otherwise = fallbackDingbat
-- | @$dingbat$@: name of the ornament to use on this page.
dingbatField :: Context a
dingbatField = field "dingbat" $ \item -> do
meta <- getMetadata (itemIdentifier item)
r <- fromMaybe "" <$> getRoute (itemIdentifier item)
let sectionD = sectionDefault r
case lookupString "dingbat" meta of
Nothing -> return sectionD
Just name
| name `elem` knownDingbats -> return name
| otherwise -> do
let ident = toFilePath (itemIdentifier item)
unsafeCompiler $ putStrLn $
"[Dingbat] " ++ ident ++ ": unknown dingbat \""
++ name ++ "\" — using \"" ++ sectionD ++ "\""
return sectionD