packages feed

heist-extra-0.3.0.0: src/Heist/Extra/Splices/Pandoc/Footnotes.hs

module Heist.Extra.Splices.Pandoc.Footnotes where

import Data.List qualified as List
import Data.Map.Syntax ((##))
import Heist qualified as H
import Heist.Extra (runCustomNode)
import Heist.Extra.Splices.Pandoc.Ctx (RenderCtx (rootNode))
import Heist.Extra.Splices.Pandoc.Render (renderPandocWith)
import Heist.Interpreted qualified as HI
import Text.Pandoc.Builder qualified as B
import Text.Pandoc.Definition (Pandoc (..))
import Text.Pandoc.Walk qualified as W
import Text.XmlHtml qualified as X

type Footnotes = [[B.Block]]

gatherFootnotes :: Pandoc -> Footnotes
gatherFootnotes = List.nub . W.query queryFootnotes
  where
    queryFootnotes = \case
      B.Note footnote ->
        [footnote]
      _ ->
        []

lookupFootnote :: HasCallStack => [B.Block] -> Footnotes -> Int
lookupFootnote note fs =
  fromMaybe (error $ "Missing footnote: " <> show note) $ do
    (+ 1) <$> List.elemIndex note fs

renderFootnotesWith :: RenderCtx -> Footnotes -> HI.Splice Identity
renderFootnotesWith ctx fs' =
  fromMaybe (pure []) $ do
    fs <- viaNonEmpty toList fs'
    renderNode <- viaNonEmpty head $ maybe [] (X.childElementsTag "Note:List") $ rootNode ctx
    let footnotesWithIdx = zip [1 :: Int ..] fs
    Just $
      runCustomNode renderNode $ do
        "footnote" ##
          (HI.runChildrenWith . uncurry (footnoteSplices ctx)) `foldMapM` footnotesWithIdx

footnoteSplices :: RenderCtx -> Int -> [B.Block] -> H.Splices (HI.Splice Identity)
footnoteSplices ctx idx bs = do
  let footnoteDoc = Pandoc mempty $ case bs of
        [B.Para is] ->
          -- Optimize for the most usual case, by discarding the paragraph,
          -- which adds unnecessary styling (thus margins).
          one $ B.Plain is
        _ ->
          bs
  "footnote:idx" ## HI.textSplice (show idx)
  "footnote:content" ## renderPandocWith ctx footnoteDoc

footnoteRefSplice :: RenderCtx -> [[B.Block]] -> B.Inline -> Maybe (HI.Splice Identity)
footnoteRefSplice ctx footnotes inline = do
  B.Note bs <- pure inline
  let idx = lookupFootnote bs footnotes
  renderNode <- viaNonEmpty head $ maybe [] (X.childElementsTag "Note:Ref") (rootNode ctx)
  Just $
    runCustomNode renderNode $
      footnoteSplices ctx idx bs