Skip to content

Commit

Permalink
Merge pull request #375 from srid/inline-tags
Browse files Browse the repository at this point in the history
Add inline tags support
  • Loading branch information
srid authored Sep 7, 2020
2 parents b92fb22 + 4f18d44 commit c36ad90
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Allow specifying time in the `date` metadata propery (#343)
- Add `unlisted` metadata property to hide a zettel from z-index (#318)
- Markdown:
- Inline tags (#189)
- support for [fancy lists](https://github.com/jgm/commonmark-hs/blob/master/commonmark-extensions/test/fancy_lists.md) (#335)
- Fix hard line breaks to actually work (#354)
- CLI
Expand Down
3 changes: 2 additions & 1 deletion guide/2011404.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
Zettel files are written in Markdown[^other], per the [CommonMark](https://commonmark.org/) specification. Neuron uses [commonmark-hs](https://github.com/jgm/commonmark-hs) to parse them into the [Pandoc AST](https://pandoc.org/using-the-pandoc-api.html), as well as provides an extention on top to handle zettel links.

* [[[2011504]]]
* [[[535407ad]]]
* [[[2011505]]]
* [[[2013702]]]
* [[[2013701]]]
* [[[2016401]]]
* Styling elements using Semantic UI ([#176](https://github.com/srid/neuron/issues/176))
* Styling elements using Semantic UI ([\#176](https://github.com/srid/neuron/issues/176))

[^other]: Neuron is designed to be extended with other markup formats as well. Org-mode is currently supported (see the `formats` setting in [[2011701]]) but is [experimental](https://github.com/srid/neuron/issues/275). Neuron recommends Markdown, which is supported everywhere including [[041726b3]].
27 changes: 8 additions & 19 deletions guide/2011505.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,21 @@

Zettels may contain optional metadata in the YAML frontmatter.

## Tags

You can attach one or more tags to your zettels using the "tags" metadata property[^kw]:

```markdown
---
tags:
- science
---
```

Tags can be also be nested; see [[[535407ad]]].

[^kw]: For interoperability with other Zettelkasten apps, neuron also accepts "keywords" as an alternative to "tags" in the YAML frontmatter.
:::

## Date

The date of the zettels can be specified in the "date" metadata field (`neuron new` automatically fills in this field):

```markdown
---
date: 2020-08-21T13:06
tags:
- journal
---
```

This date can be made to display in a query result by using the `timeline` flag (see [[2011506]]).

## Pinning

Zettels can be pinned in the z-index so that they appear at the top. To pin a zettel, add the "pinned" tag to it.
Zettels can be pinned in the z-index so that they appear at the top. To pin a zettel, add the "pinned" tag (see [[535407ad]]) to it.

```markdown
---
Expand All @@ -52,3 +34,10 @@ Sometimes you want to "draft" a note, and as such wish to hide it from the rest
unlisted: true
---
```

## Other metadata

You can explicitly specify a title using the `title` metadata; otherwise, Neuron will infer it from the Markdown body.

The metadata key `tags` or `keywords` can be used to specify tags, although neuron supports inline tags as well (see [[[535407ad]]]).

2 changes: 1 addition & 1 deletion guide/2014401.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# MMark Limitations

{.ui .warning .message}
**NOTE**: This page only applies to neuron version 0.4 or below, which used the mmark Markdown parser. Beginning from neuron version 0.5, however, Pandoc (via commonmark-hs) is used to represent Markdown, where these limitations do not apply. This page is left here for legacy reference. See [issue #137](https://github.com/srid/neuron/issues/137) for details.
**NOTE**: This page only applies to neuron version 0.4 or below, which used the mmark Markdown parser. Beginning from neuron version 0.5, however, Pandoc (via commonmark-hs) is used to represent Markdown, where these limitations do not apply. This page is left here for legacy reference. See [issue \#137](https://github.com/srid/neuron/issues/137) for details.

Zettel Markdown format is limited in a few ways owing to the strict parsing nature of `mmark`, the parser library used by neuron.

Expand Down
21 changes: 20 additions & 1 deletion guide/535407ad.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
# Hierarchical tags
# Tags

Neuron supports Twitter like tags. Tags are added by prefixing them with `#`, and they can appear anywhere in the note text. For example:

```markdown
Gaslighting is enabled by the victim's #cognitive-distortion without which the
victimizer is rendered powerless to influence their victim.
```

In the above example, the note is tagged with `#cognitive-distortion` which also links to the tag page.

Tags can also be specified in the [[2011505]] block. The above tag can be specified alternatively as follows:

```markdown
---
tags:
- cognitive-distortion
```

## Hierarchical tags

Tags can be nested using a "tag/subtag" syntax, to allow a more fine-grained organization of your Zettelkasten, especially when using advanced queries as shown in [[2011506]].

Expand Down
2 changes: 1 addition & 1 deletion neuron/neuron.cabal
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cabal-version: 2.4
name: neuron
-- This version must be in sync with what's in Default.dhall
version: 0.6.6.2
version: 0.6.7.0
license: AGPL-3.0-only
copyright: 2020 Sridhar Ratnakumar
maintainer: [email protected]
Expand Down
34 changes: 31 additions & 3 deletions neuron/src/lib/Neuron/Reader/Markdown.hs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ neuronSpec =
mconcat
[ autoLinkSpec,
wikiLinkSpec,
inlineTagSpec,
gfmExtensionsSansEmoji,
CE.fancyListSpec,
CE.footnoteSpec,
Expand All @@ -124,6 +125,23 @@ neuronSpec =
<> CE.autoIdentifiersSpec
<> CE.taskListSpec

inlineTagSpec ::
(Monad m, CM.IsBlock il bl, CM.IsInline il) =>
CM.SyntaxSpec m il bl
inlineTagSpec =
mempty
{ CM.syntaxInlineParsers = [pInlineTag]
}
where
pInlineTag ::
(Monad m, CM.IsInline il) =>
CM.InlineParser m il
pInlineTag = P.try $ do
_ <- symbol '#'
tag <- CM.untokenize <$> idP
let tagQuery = "z:tag/" <> tag
pure $! cmAutoLink tagQuery

-- | Convert the given wrapped link to a `B.Link`.
autoLinkSpec ::
(Monad m, CM.IsBlock il bl, CM.IsInline il) =>
Expand All @@ -139,8 +157,13 @@ autoLinkSpec =
pLink = P.try $ do
x <- angleBracketLinkP
let url = CM.untokenize x
title = ""
pure $! CM.link url title $ CM.str url
pure $! cmAutoLink url

cmAutoLink :: CM.IsInline a => Text -> a
cmAutoLink url =
CM.link url title $ CM.str url
where
title = ""

wikiLinkSpec ::
(Monad m, CM.IsBlock il bl, CM.IsInline il) =>
Expand Down Expand Up @@ -173,10 +196,15 @@ wikiLinkSpec =
wikiLinkP :: Monad m => Int -> P.ParsecT [CM.Tok] s m [CM.Tok]
wikiLinkP n = do
void $ M.count n $ symbol '['
x <- some (noneOfToks [Symbol ']', Spaces, UnicodeSpace, LineEnd])
x <- idP
void $ M.count n $ symbol ']'
pure x

-- TODO: Unify this with the megaparsec parser from ID.hs
idP :: Monad m => P.ParsecT [CM.Tok] s m [CM.Tok]
idP =
some (noneOfToks [Symbol ']', Spaces, UnicodeSpace, LineEnd])

-- rawHtmlSpec eats angle bracket links as html tags
defaultBlockSpecsSansRawHtml :: (Monad m, CM.IsBlock il bl) => [CM.BlockSpec m il bl]
defaultBlockSpecsSansRawHtml =
Expand Down
17 changes: 12 additions & 5 deletions neuron/src/lib/Neuron/Web/Query/View.hs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ renderQueryResult minner = \case
el "section" $ do
renderQuery $ Some q
renderTagTree $ foldTagTree $ tagTree res
ZettelQuery_TagZettel tag :=> Identity () ->
renderInlineTag tag mempty $ do
text "#"
text $ unTag tag
where
-- TODO: Instead of doing this here, group the results in runQuery itself.
groupZettelsByTagsMatching pats matches =
Expand Down Expand Up @@ -93,6 +97,8 @@ renderQuery someQ =
Some (ZettelQuery_Tags (fmap unTagPattern -> pats)) -> do
let qs = toText $ intercalate ", " pats
text $ "Tags matching '" <> qs <> "'"
Some (ZettelQuery_TagZettel _tag) -> do
blank

-- | Render a link to an individual zettel.
renderZettelLink ::
Expand Down Expand Up @@ -178,11 +184,8 @@ renderTagTree t =
tit = show count <> " zettels tagged"
cls = bool "" "inactive" $ count == 0
divClass "node" $ do
neuronRouteLink
(Some $ Route_Search $ Just tag)
("class" =: cls <> "title" =: tit)
$ do
text $ renderTagNode tagNode
renderInlineTag tag ("class" =: cls <> "title" =: tit) $
text $ renderTagNode tagNode
renderTagNode :: NonEmpty TagNode -> Text
renderTagNode = \case
n :| (nonEmpty -> mrest) ->
Expand All @@ -192,6 +195,10 @@ renderTagTree t =
Just rest ->
unTagNode n <> "/" <> renderTagNode rest

renderInlineTag :: DomBuilder t m => Tag -> Map Text Text -> m () -> NeuronWebT t m ()
renderInlineTag tag attr body =
neuronRouteLink (Some $ Route_Search $ Just tag) attr body

style :: Css
style = do
zettelLinkCss
Expand Down
4 changes: 4 additions & 0 deletions neuron/src/lib/Neuron/Zettelkasten/Query.hs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ runZettelQuery zs = \case
Right allTags
ZettelQuery_Tags pats ->
Right $ Map.filterWithKey (const . tagMatchAny pats) allTags
ZettelQuery_TagZettel _tag ->
Right ()
where
allTags :: Map.Map Tag Natural
allTags =
Expand Down Expand Up @@ -96,6 +98,8 @@ zettelQueryResultJson q er skippedZettels =
toJSON r
ZettelQuery_Tags _ ->
toJSON $ fmap treeToJson . tagTree $ r
ZettelQuery_TagZettel _ ->
toJSON r
treeToJson (Node (tag, count) children) =
object
[ "name" .= tag,
Expand Down
2 changes: 2 additions & 0 deletions neuron/src/lib/Neuron/Zettelkasten/Query/Eval.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ queryConnections Zettel {..} = do
(conn,) <$> res
ZettelQuery_Tags _ :=> _ ->
mempty
ZettelQuery_TagZettel _ :=> _ ->
mempty

runSomeZettelQuery ::
( MonadError QueryResultError m,
Expand Down
6 changes: 5 additions & 1 deletion neuron/src/lib/Neuron/Zettelkasten/Query/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ where

import Control.Monad.Except
import Data.Some
import Data.TagTree (TagPattern, mkTagPattern)
import Data.TagTree (TagNode (..), TagPattern, constructTag, mkTagPattern)
import Neuron.Reader.Type (ZettelFormat (..))
import Neuron.Zettelkasten.Connection
import Neuron.Zettelkasten.ID
Expand Down Expand Up @@ -99,6 +99,10 @@ queryFromURI defConn uri = do
(URI.unRText -> "tags") :| []
| noSlash -> do
pure $ Some $ ZettelQuery_Tags (tagPatterns uri "filter")
-- Parse z:tag/foo
(URI.unRText -> "tag") :| (nonEmpty . fmap (TagNode . URI.unRText) -> Just tagNodes)
| noSlash -> do
pure $ Some $ ZettelQuery_TagZettel (constructTag tagNodes)
_ -> empty

parseQueryZettelID :: MonadError QueryParseError m => URI -> Text -> m ZettelID
Expand Down
1 change: 1 addition & 0 deletions neuron/src/lib/Neuron/Zettelkasten/Zettel.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ data ZettelQuery r where
ZettelQuery_ZettelByID :: ZettelID -> Connection -> ZettelQuery Zettel
ZettelQuery_ZettelsByTag :: [TagPattern] -> Connection -> ZettelsView -> ZettelQuery [Zettel]
ZettelQuery_Tags :: [TagPattern] -> ZettelQuery (Map Tag Natural)
ZettelQuery_TagZettel :: Tag -> ZettelQuery ()

-- | A zettel note
--
Expand Down
13 changes: 12 additions & 1 deletion neuron/src/lib/Neuron/Zettelkasten/Zettel/Parser.hs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoImplicitPrelude #-}

module Neuron.Zettelkasten.Zettel.Parser where

import Control.Monad.Writer
import Data.List (nub)
import Data.Some
import Data.TagTree (Tag)
import qualified Data.Text as T
import Data.Time.DateMayTime (mkDateMayTime)
import Neuron.Reader.Type
Expand Down Expand Up @@ -37,14 +42,16 @@ parseZettel format zreader fn zid s = do
Nothing -> fromMaybe ("Untitled", False) $ do
((,True) . plainify . snd <$> getH1 doc)
<|> ((,False) . takeInitial . plainify <$> getFirstParagraphText doc)
tags = fromMaybe [] $ Meta.tags =<< meta
metaTags = fromMaybe [] $ Meta.tags =<< meta
date = case zid of
-- We ignore the "data" meta field on legacy Date IDs, which encode the
-- creation date in the ID.
ZettelDateID v _ -> Just $ mkDateMayTime $ Left v
ZettelCustomID _ -> Meta.date =<< meta
unlisted = fromMaybe False $ Meta.unlisted =<< meta
(queries, errors) = runWriter $ extractQueries doc
queryTags = getInlineTag `mapMaybe` queries
tags = nub $ metaTags <> queryTags
in Right $ Zettel zid format fn title titleInBody tags date unlisted queries errors doc
where
-- Extract all (valid) queries from the Pandoc document
Expand All @@ -58,6 +65,10 @@ parseZettel format zreader fn zid s = do
pure Nothing
Right v ->
pure v
getInlineTag :: Some ZettelQuery -> Maybe Tag
getInlineTag = \case
Some (ZettelQuery_TagZettel tag) -> Just tag
_ -> Nothing
takeInitial =
(<> " ...") . T.take 18

Expand Down
6 changes: 6 additions & 0 deletions neuron/test/Neuron/Zettelkasten/Query/ParserSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ spec = do
it "z:tags?filter=foo" $ do
queryFromURILink (shortLink "z:tags?filter=foo")
`shouldBe` Right (Just $ Some $ ZettelQuery_Tags [mkTagPattern "foo"])
it "z:tag/foo" $ do
queryFromURILink (shortLink "z:tag/foo")
`shouldBe` Right (Just $ Some $ ZettelQuery_TagZettel (Tag "foo"))
it "z:tag/foo/bar/baz" $ do
queryFromURILink (shortLink "z:tag/foo/bar/baz")
`shouldBe` Right (Just $ Some $ ZettelQuery_TagZettel (Tag "foo/bar/baz"))
let normalLink = mkURILink "some link text"
describe "flexible links (regular markdown)" $ do
it "Default connection type should be cf" $ do
Expand Down

0 comments on commit c36ad90

Please sign in to comment.