packages feed

emanote-1.4.0.0: src/Emanote/View/Feed.hs

{-# LANGUAGE NamedFieldPuns #-}

module Emanote.View.Feed where

import Data.Aeson qualified as Aeson
import Data.Aeson.Optics (key, _String)
import Emanote.Model (Model)
import Emanote.Model.Meta (getEffectiveRouteMeta)
import Emanote.Model.Note (Feed (..), Note (..), lookupMeta)
import Emanote.Model.Query (Query, parseQuery, runQuery)
import Emanote.Model.SData (lookupAeson)
import Emanote.Model.Title (toPlain)
import Emanote.Route.SiteRoute
import Emanote.Route.SiteRoute.Class (noteFeedSiteRoute)
import Optics.Operators ((^?))
import Optics.Optic ((%))
import Relude
import Text.Atom.Feed qualified as Atom
import Text.Atom.Feed.Export qualified as Export (textFeed)
import Text.Pandoc.Definition hiding (lookupMeta)

import Emanote.Route.ModelRoute (LMLView (..))
import Text.Blaze.Html (Html, (!))
import Text.Blaze.Html5 qualified as H
import Text.Blaze.Html5.Attributes qualified as A

feedDiscoveryLink :: Model -> Note -> Html
feedDiscoveryLink model note =
  H.link
    ! A.href ("/" <> H.toValue feedUrl)
    ! A.rel "alternate"
    ! A.type_ "application/atom+xml"
    ! A.title "Atom Feed"
  where
    feedUrl = siteRouteUrl model (noteFeedSiteRoute note)

noteToEntry :: Atom.URI -> (Note -> Text) -> Note -> Atom.Entry
noteToEntry baseUrl noteUrl note = entry {Atom.entrySummary, Atom.entryLinks}
  where
    entry = Atom.nullEntry entryUrl noteTitle noteDate
    entryUrl = noteUrl note
    entryLinks = [(Atom.nullLink (baseUrl <> "/" <> entryUrl)) {Atom.linkRel = Just (Left "alternate")}]
    noteDate = getNoteDate note
    noteTitle = Atom.TextString $ toPlain $ _noteTitle note
    entrySummary = Atom.TextString <$> lookupMeta ("page" :| ["description"]) note

getNoteDate :: Note -> Atom.Date
getNoteDate note = fromMaybe "1970-01-01" $ _noteMeta note ^? key "date" % _String

getNoteQuery :: Note -> Either LText Query
getNoteQuery note = case _noteDoc note of
  Pandoc _meta [] -> Left "empty note"
  Pandoc _meta blocks -> go blocks
  where
    go [] = Left "can't find note query"
    go (block : rest) = case block of
      CodeBlock ("", classes, _) txt | "query" `elem` classes -> case parseQuery txt of
        Nothing -> Left ("invalid query: " <> toLazy txt)
        Just query -> case go rest of
          -- Check that only query exists
          Right _ -> Left "multiple ```query found"
          Left _ -> Right query
      _ -> go rest

renderFeed :: Model -> Note -> Either LText LByteString
renderFeed model baseNote = case eFeedText of
  Left err -> Left err
  Right feedText -> Right (encodeUtf8 feedText)
  where
    eFeedText = do
      -- get the note feed
      feed <- maybeToRight "feed attribute missing" $ _noteFeed baseNote

      -- find the query and get the feed notes
      feedQuery <- getNoteQuery baseNote
      notes <- case runQuery (_noteRoute baseNote) model feedQuery of
        [] -> Left "no notes matched the query"
        x : xs -> Right (x :| xs)

      -- lookup the feedUrl
      let feedMeta :: Aeson.Value
          feedMeta = getEffectiveRouteMeta (_noteRoute baseNote) model
      let mFeedUrl :: Maybe Text
          mFeedUrl = lookupAeson Nothing ("page" :| ["siteUrl"]) feedMeta
      feedUrl <- maybeToRight "index.yaml or note doesn't have page.siteUrl" mFeedUrl

      -- process the notes
      let noteUrl note =
            let sr = SiteRoute_ResourceRoute $ ResourceRoute_LML LMLView_Html $ _noteRoute note
             in siteRouteUrl model sr
      let takeNotes = case _feedLimit feed of
            Nothing -> id
            Just x -> take (fromIntegral x)
      let feedEntries = noteToEntry feedUrl noteUrl <$> takeNotes (toList notes)

      -- render the feed
      let feedTitle = fromMaybe (toPlain $ _noteTitle baseNote) (_feedTitle feed)
      let feedName = Atom.TextString feedTitle
      let feedUpdated = getNoteDate (head notes)
      let feedLinks =
            [ (Atom.nullLink (feedUrl <> "/" <> noteUrl baseNote)) {Atom.linkRel = Just (Left "alternate")}
            , (Atom.nullLink (feedUrl <> "/" <> siteRouteUrl model (noteFeedSiteRoute baseNote))) {Atom.linkRel = Just (Left "self")}
            ]
      let atomFeed = (Atom.nullFeed feedUrl feedName feedUpdated) {Atom.feedEntries, Atom.feedLinks}
      maybeToRight "invalid feed" $ Export.textFeed atomFeed