packages feed

neuron-0.6.0.0: src/app/Data/Structured/Breadcrumb.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE NoImplicitPrelude #-}

-- | JSON for Google's structured data breadcrumbs
--
-- https://developers.google.com/search/docs/data-types/breadcrumb
module Data.Structured.Breadcrumb where

import Data.Aeson
import Data.Tree
import Reflex.Dom.Core hiding (mapMaybe)
import Relude
import Text.URI (URI)
import qualified Text.URI as URI

data Item = Item
  { name :: Text,
    url :: Maybe URI
  }
  deriving (Eq, Show, Generic)

newtype Breadcrumb = Breadcrumb {unBreadcrumb :: NonEmpty Item}
  deriving (Eq, Show, Generic)

renderBreadcrumbs :: DomBuilder t m => [Breadcrumb] -> m ()
renderBreadcrumbs bs =
  elAttr "script" ("type" =: "application/ld+json") $ text $ decodeUtf8 $ encode bs

instance ToJSON Breadcrumb where
  toJSON (Breadcrumb (toList -> crumbs)) =
    toJSON $
      object
        [ "@context" .= toJSON context,
          "@type" .= ("BreadcrumbList" :: Text),
          "itemListElement" .= toJSON (uncurry itemJson <$> zip [1 :: Int ..] crumbs)
        ]
    where
      context = "https://schema.org" :: Text
      itemJson pos Item {..} =
        object
          [ "@type" .= toJSON ("ListItem" :: Text),
            "position" .= toJSON pos,
            "name" .= toJSON name,
            "item" .= toJSON (fmap URI.render url)
          ]

fromForest :: Forest Item -> [Breadcrumb]
fromForest =
  fmap Breadcrumb . mapMaybe (nonEmpty . reverse) . concatMap (foldTree f)
  where
    f :: Item -> [[[Item]]] -> [[Item]]
    f parent = \case
      [] -> [[parent]]
      childPaths ->
        fmap (parent :) `concatMap` childPaths