neuron-0.6.0.0: src/lib/Neuron/Web/Query/View.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Neuron.Web.Query.View
( renderQueryResult,
renderZettelLink,
renderZettelLinkIDOnly,
zettelUrl,
tagUrl,
style,
)
where
import qualified Clay as C
import Clay ((?), Css, em)
import Control.Monad.Except
import Data.Default
import Data.Dependent.Sum
import qualified Data.Map.Strict as Map
import Data.Some
import Data.TagTree (Tag (..), TagNode (..), TagPattern (..), constructTag, foldTagTree, tagMatchAny, tagTree)
import qualified Data.Text as T
import Data.Tree
import Neuron.Web.Route
import Neuron.Web.Widget
import Neuron.Zettelkasten.Connection
import Neuron.Zettelkasten.ID
import Neuron.Zettelkasten.Query.Theme (LinkView (..), ZettelsView (..))
import Neuron.Zettelkasten.Zettel
import Reflex.Dom.Core hiding (count, tag)
import Relude
-- | Render the query results.
renderQueryResult ::
DomBuilder t m => DSum ZettelQuery Identity -> NeuronWebT t m ()
renderQueryResult = \case
ZettelQuery_ZettelByID _zid (fromMaybe def -> conn) :=> Identity target -> do
renderZettelLink (Just conn) Nothing target
q@(ZettelQuery_ZettelsByTag pats (fromMaybe def -> conn) view) :=> Identity res -> do
el "section" $ do
renderQuery $ Some q
case zettelsViewGroupByTag view of
False ->
el "ul" $ forM_ res $ \z -> do
el "li" $
renderZettelLink (Just conn) (Just $ zettelsViewLinkView view) z
True ->
forM_ (Map.toList $ groupZettelsByTagsMatching pats res) $ \(tag, zettelGrp) -> do
el "section" $ do
elClass "span" "ui basic pointing below grey label" $ do
semanticIcon "tag"
text $ unTag tag
el "ul" $ forM_ zettelGrp $ \z ->
el "li" $
renderZettelLink (Just conn) (Just $ zettelsViewLinkView view) z
q@(ZettelQuery_Tags _) :=> Identity res -> do
el "section" $ do
renderQuery $ Some q
renderTagTree $ foldTagTree $ tagTree res
where
-- TODO: Instead of doing this here, group the results in runQuery itself.
groupZettelsByTagsMatching pats matches =
fmap sortZettelsReverseChronological $ Map.fromListWith (<>) $ flip concatMap matches $ \z ->
flip concatMap (zettelTags z) $ \t -> [(t, [z]) | tagMatchAny pats t]
renderQuery :: DomBuilder t m => Some ZettelQuery -> m ()
renderQuery someQ =
elAttr "div" ("class" =: "ui horizontal divider" <> "title" =: "Neuron ZettelQuery") $ do
case someQ of
Some (ZettelQuery_ZettelByID _ _) ->
blank
Some (ZettelQuery_ZettelsByTag [] _mconn _mview) ->
text "All zettels"
Some (ZettelQuery_ZettelsByTag (fmap unTagPattern -> pats) _mconn _mview) -> do
let qs = toText $ intercalate ", " pats
desc = toText $ "Zettels tagged '" <> qs <> "'"
elAttr "span" ("class" =: "ui basic pointing below black label" <> "title" =: desc) $ do
semanticIcon "tags"
text qs
Some (ZettelQuery_Tags []) ->
text "All tags"
Some (ZettelQuery_Tags (fmap unTagPattern -> pats)) -> do
let qs = toText $ intercalate ", " pats
text $ "Tags matching '" <> qs <> "'"
-- | Render a link to an individual zettel.
renderZettelLink :: DomBuilder t m => Maybe Connection -> Maybe LinkView -> Zettel -> NeuronWebT t m ()
renderZettelLink conn (fromMaybe def -> linkView) Zettel {..} = do
let connClass = show <$> conn
rawClass = either (const $ Just "raw") (const Nothing) zettelError
mextra =
case linkView of
LinkView_Default ->
Nothing
LinkView_ShowDate ->
elTime <$> zettelDay
LinkView_ShowID ->
Just $ el "tt" $ text $ zettelIDText zettelID
classes :: [Text] = catMaybes $ [Just "zettel-link-container"] <> [connClass, rawClass]
elClass "span" (T.intercalate " " classes) $ do
forM_ mextra $ \extra ->
elAttr "span" ("class" =: "extra monoFont" <> noSnippet) $ do
extra
-- The extra space is so that double clicking on this extra text
-- doesn't select the title next.
text " "
let linkTooltip =
if null zettelTags
then Nothing
else Just $ "Tags: " <> T.intercalate "; " (unTag <$> zettelTags)
elAttr "span" ("class" =: "zettel-link" <> withTooltip linkTooltip) $ do
neuronRouteLink (Some $ Route_Zettel zettelID) mempty $ text zettelTitle
where
-- Prevent this element from appearing in Google search results
-- https://developers.google.com/search/reference/robots_meta_tag#data-nosnippet-attr
noSnippet :: Map Text Text
noSnippet = "data-nosnippet" =: ""
withTooltip :: Maybe Text -> Map Text Text
withTooltip = \case
Nothing -> mempty
Just s ->
( "data-tooltip" =: s
<> "data-inverted" =: ""
<> "data-position" =: "right center"
)
-- | Like `renderZettelLink` but when we only have ID in hand.
renderZettelLinkIDOnly :: DomBuilder t m => ZettelID -> NeuronWebT t m ()
renderZettelLinkIDOnly zid =
elClass "span" "zettel-link-container" $ do
elClass "span" "zettel-link" $ do
neuronRouteLink (Some $ Route_Zettel zid) mempty $ text $ zettelIDText zid
renderTagTree :: forall t m. DomBuilder t m => Forest (NonEmpty TagNode, Natural) -> m ()
renderTagTree t =
divClass "tag-tree" $
renderForest mempty t
where
renderForest :: [TagNode] -> Forest (NonEmpty TagNode, Natural) -> m ()
renderForest ancestors forest =
el "ul" $ do
forM_ forest $ \tree ->
el "li" $ renderTree ancestors tree
renderTree :: [TagNode] -> Tree (NonEmpty TagNode, Natural) -> m ()
renderTree ancestors (Node (tagNode, count) children) = do
renderTag ancestors (tagNode, count)
renderForest (ancestors <> toList tagNode) $ toList children
renderTag :: [TagNode] -> (NonEmpty TagNode, Natural) -> m ()
renderTag ancestors (tagNode, count) = do
let tag = constructTag $ maybe tagNode (<> tagNode) $ nonEmpty ancestors
tit = show count <> " zettels tagged"
cls = bool "" "inactive" $ count == 0
divClass "node" $ do
elAttr "a" ("class" =: cls <> "title" =: tit <> "href" =: tagUrl tag) $ do
text $ renderTagNode tagNode
renderTagNode :: NonEmpty TagNode -> Text
renderTagNode = \case
n :| (nonEmpty -> mrest) ->
case mrest of
Nothing ->
unTagNode n
Just rest ->
unTagNode n <> "/" <> renderTagNode rest
-- TODO: not using Rib for ghcjs, but factorize this
zettelUrl :: ZettelID -> Text
zettelUrl zid =
zettelIDText zid <> ".html"
tagUrl :: Tag -> Text
tagUrl (Tag s) =
"search.html?tag=" <> s
style :: Css
style = do
zettelLinkCss
"div.tag-tree" ? do
"div.node" ? do
C.fontWeight C.bold
"a.inactive" ? do
C.color "#555"
zettelLinkCss :: Css
zettelLinkCss = do
"span.zettel-link-container span.zettel-link a" ? do
C.fontWeight C.bold
C.textDecoration C.none
"span.zettel-link-container span.extra" ? do
C.color C.auto
"span.zettel-link-container.folgezettel::after" ? do
C.paddingLeft $ em 0.3
C.content $ C.stringContent "ᛦ"
"span.zettel-link-container.raw" ? do
C.border C.solid (C.px 1) C.red
"[data-tooltip]:after" ? do
C.fontSize $ em 0.7