packages feed

emanote-1.0.2.0: src/Emanote/Model/Title.hs

{-# LANGUAGE DeriveAnyClass #-}

module Emanote.Model.Title (
  Title,

  -- * Title conversion
  fromRoute,
  fromInlines,
  toInlines,

  -- * Rendering a Title
  titleSplice,
  titleSpliceNoHtml,
  toPlain,
) where

import Commonmark.Extensions.WikiLink (plainify)
import Data.Aeson (ToJSON)
import Emanote.Route qualified as R
import Heist.Extra.Splices.Pandoc qualified as HP
import Heist.Interpreted qualified as HI
import Relude
import Text.Pandoc.Definition qualified as B
import Text.Pandoc.Walk qualified as W

data Title
  = TitlePlain Text
  | TitlePandoc [B.Inline]
  deriving stock (Show, Generic)
  deriving anyclass (ToJSON)

instance Eq Title where
  (==) =
    -- Use toPlain here, rather than toInlines, because the same text can have
    -- different inlines structure. For example, "Foo Bar" can be represented as
    --   [Str "Foo", Space, Str "Bar"],
    -- or as,
    --   [Str "Foo Bar"]
    on (==) toPlain

instance Ord Title where
  compare =
    on compare toPlain

instance Semigroup Title where
  TitlePlain a <> TitlePlain b =
    TitlePlain (a <> b)
  x <> y =
    TitlePandoc $ on (<>) toInlines x y

instance IsString Title where
  fromString = TitlePlain . toText

fromRoute :: R.LMLRoute -> Title
fromRoute =
  TitlePlain . R.withLmlRoute R.routeBaseName

fromInlines :: [B.Inline] -> Title
fromInlines = TitlePandoc

toInlines :: Title -> [B.Inline]
toInlines = \case
  TitlePlain s -> one (B.Str s)
  TitlePandoc is -> is

toPlain :: Title -> Text
toPlain = \case
  TitlePlain s -> s
  TitlePandoc is -> plainify is

titleSplice ::
  forall b.
  (W.Walkable B.Inline b, b ~ [B.Inline]) =>
  HP.RenderCtx ->
  (b -> b) ->
  Title ->
  HI.Splice Identity
titleSplice ctx f = \case
  TitlePlain x ->
    HI.textSplice x
  TitlePandoc is -> do
    let titleDoc = B.Pandoc mempty $ one $ B.Plain $ f is
    HP.pandocSplice ctx titleDoc

titleSpliceNoHtml :: Title -> HI.Splice Identity
titleSpliceNoHtml =
  HI.textSplice . toPlain