neuron-1.0.0.0: src/app/Neuron/Web/StructuredData.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Neuron.Web.StructuredData
( renderStructuredData,
)
where
import Data.Structured.Breadcrumb (Breadcrumb)
import qualified Data.Structured.Breadcrumb as Breadcrumb
import Data.Structured.OpenGraph
import Data.Structured.OpenGraph.Render (renderOpenGraph)
import qualified Data.Text as T
import Neuron.Config.Type (Config (..), getSiteBaseUrl)
import Neuron.Web.Generate.Route (routeUri)
import Neuron.Web.Route
import Neuron.Zettelkasten.Connection
import Neuron.Zettelkasten.Graph (ZettelGraph)
import qualified Neuron.Zettelkasten.Graph as G
import Neuron.Zettelkasten.Zettel
import Reflex.Dom.Core hiding ((&))
import Relude
import Text.Pandoc (runPure, writePlain)
import Text.Pandoc.Definition (Block (Plain), Inline (Image), Pandoc (..))
import Text.Pandoc.Util (getFirstParagraphText)
import Text.Pandoc.Walk (query)
import qualified Text.URI as URI
renderStructuredData :: DomBuilder t m => Config -> Route a -> (ZettelGraph, a) -> m ()
renderStructuredData config route val = do
renderOpenGraph $ routeOpenGraph config (snd val) route
Breadcrumb.renderBreadcrumbs $ routeStructuredData config val route
routeStructuredData :: Config -> (ZettelGraph, a) -> Route a -> [Breadcrumb]
routeStructuredData cfg (graph, v) = \case
Route_Zettel _ ->
case (either fail id $ getSiteBaseUrl cfg) of
Nothing -> []
Just baseUrl ->
let mkCrumb :: Zettel -> Breadcrumb.Item
mkCrumb Zettel {..} =
Breadcrumb.Item zettelTitle (Just $ routeUri baseUrl $ Route_Zettel zettelID)
in Breadcrumb.fromForest $ fmap mkCrumb <$> G.backlinkForest Folgezettel (sansContent v) graph
_ ->
[]
routeOpenGraph :: Config -> a -> Route a -> OpenGraph
routeOpenGraph cfg@Config {siteTitle, author} v r =
OpenGraph
{ _openGraph_title = routeTitle' v r,
_openGraph_siteName = siteTitle,
_openGraph_description = case r of
Route_Redirect _ -> Nothing
Route_ZIndex -> Just "Zettelkasten Index"
(Route_Search _mtag) -> Just "Search Zettelkasten"
Route_Zettel _ -> do
doc <- getPandocDoc v
para <- getFirstParagraphText doc
paraText <- renderPandocAsText para
pure $ T.take 300 paraText,
_openGraph_author = author,
_openGraph_type = case r of
Route_Zettel _ -> Just $ OGType_Article (Article Nothing Nothing Nothing Nothing mempty)
_ -> Just OGType_Website,
_openGraph_image = case r of
Route_Zettel _ -> do
doc <- getPandocDoc v
image <- URI.mkURI =<< getFirstImg doc
baseUrl <- either fail id $ getSiteBaseUrl cfg
URI.relativeTo image baseUrl
_ -> Nothing,
_openGraph_url = do
baseUrl <- either fail id $ getSiteBaseUrl cfg
pure $ routeUri baseUrl r
}
where
getPandocDoc = either (const Nothing) (Just . zettelContent)
getFirstImg ::
Pandoc ->
-- | Relative URL path to the image
Maybe Text
getFirstImg (Pandoc _ bs) = listToMaybe $
flip query bs $ \case
Image _ _ (url, _) -> [toText url]
_ -> []
renderPandocAsText :: [Inline] -> Maybe Text
renderPandocAsText =
either (const Nothing) Just . runPure . writePlain def . Pandoc mempty . pure . Plain