Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Folder notes can now live in $folder/index.md #512

Merged
merged 10 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/guide/folder-note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
slug: folder-note
---

# Folder notes

For every folder `foo/` in your notebook, if a `foo.md` exists alongside it, it is consider a "folder note" that is associated with the folder. Likewise, an associated `foo.yaml` servers as the [[yaml-config]] for the entire folder and its contents recursively.

The children of folder notes become its [[folgezettel|folgezettel]] children by default.

{#index}
## `index.md`

Instead of `foo.md`, you can also use `foo/index.md` (or `index.org` if using [[orgmode]]) or `foo/index.yaml`. Internally, Emanote will treat them as `foo.md` and `foo.yaml` respectively.
4 changes: 2 additions & 2 deletions docs/guide/folgezettel.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Here is the [[target-note]]#
{#folder}
## Folder notes

By default, Emanote includes any directories in your note's path as vertices in the folgezettel graph. This makes the folder note a folgezettel parent of the child note. The contents of a folder, likewise, become folgezetten children of the folder note. For eg., in `foo/bar/qux.md`, "foo" is a folgezettel parent of "bar", and "bar" is a folgezettel parent of "qux".
By default, Emanote includes any directories in your note's path as vertices in the folgezettel graph. This makes the [[folder-note|folder note]] a folgezettel parent of the child note. The contents of a folder, likewise, become folgezetten children of the [[folder-note|folder note]]. For eg., in `foo/bar/qux.md`, "foo" is a folgezettel parent of "bar", and "bar" is a folgezettel parent of "qux".

>[!note]
> The top-level root folder is not considered a folgezettel by default, unless you specify it (as shown below). This is to support the use-case of the user having a *flat list* of notes *without* subdirectories, but connected through [[folgezettel]].
Expand All @@ -38,4 +38,4 @@ emanote:
```

>[!tip]
> Put this configuration in `foo.md` if you want to disable folder folgezettel for just that folder `foo/`. Put it in `foo.yaml` if you want to disable it for `foo/` and all subfolders under it recursively. Put it in top-level `index.yaml` to disable it for all folders. See [[yaml-config]] for more details.
> Put this configuration in `foo.md` if you want to disable folder folgezettel for just that [[folder-note|folder note]] `foo/`. Put it in `foo.yaml` if you want to disable it for `foo/` and all subfolders under it recursively. Put it in top-level `index.yaml` to disable it for all folders. See [[yaml-config]] for more details.
2 changes: 1 addition & 1 deletion docs/guide/html-template/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ In [[html-template|the default template]], the sidebar is rendered on the left s

- The sidebar tree is collapsed by default. But this can be disabled by setting `template.sidebar.collapsed` to `false` in [[yaml-config]]
- The ordering of children in the tree is determined in the following order (this is also the order in which [[query]] results are rendered by default):
1. if `template.sidebar.folders-first` is set to `true` (default `false`), directories are put before single notes, respecting the other order criteria. Otherwise, directories and notes will be interleaved.
1. if `template.sidebar.folders-first` is set to `true` (default `false`), directories (see [[folder-note]]) are put before single notes, respecting the other order criteria. Otherwise, directories and notes will be interleaved.
2. If the `order` [[yaml-config|frontmatter]] metadata exists, use that as the primary sort key.
3. If the note has a H1 title, use that as the secondary sort key; otherwise, use the note filename as the secondary sort key.

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/yaml-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ tags: [emanote/yaml/demo]

# YAML configuration

Configure your site metadata, rendering configuration and such using YAML configuration. Create a `foo.yaml` alongside `foo.md` or `foo/` folder, and those settings apply only to that route. The YAML structure is the same as your Markdown frontmatter, and vice-versa. Settings in the YAML frontmatter apply onto that Markdown route only; whereas settings in an individual .yaml file apply to that entire sub-route tree. Emanote does a deep-merge of the parent YAML configurations, so you can have children override only what's necessary. This is sometimes known as ["data cascade"](https://www.11ty.dev/docs/data-cascade/). The final merged YAML structure is passed to the HTML templates, of which you have full rendering control over.
Configure your site metadata, rendering configuration and such using YAML configuration. Create a `foo.yaml` alongside `foo.md` (see [[folder-note]]) or `foo/` folder, and those settings apply only to that route. The YAML structure is the same as your Markdown frontmatter, and vice-versa. Settings in the YAML frontmatter apply onto that Markdown route only; whereas settings in an individual .yaml file apply to that entire sub-route tree. Emanote does a deep-merge of the parent YAML configurations, so you can have children override only what's necessary. This is sometimes known as ["data cascade"](https://www.11ty.dev/docs/data-cascade/). The final merged YAML structure is passed to the HTML templates, of which you have full rendering control over.

Notice how this page's sidebar colorscheme has [changed to green]{.greenery}? View [the source of this page](https://github.com/srid/emanote/blob/master/docs/guide/yaml-config.md) to see the magic involved. That CSS greenery you just saw too comes from YAML.

Expand Down
4 changes: 4 additions & 0 deletions emanote/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
- Obsidian-style callouts ([\#466](https://github.com/srid/emanote/pull/466))
- `emanote run --no-ws` option to disable WebSocket monitoring. This is useful for using Emanote to serve the HTML site directly on the internet, without needing to statically generate it.
- Add query syntax for listing folgezetten children & parents ([\#476](https://github.com/srid/emanote/pull/476))
- Native support for combining multiple notebooks
- Resolve ambiguities based on closer common ancestor ([\#498](https://github.com/srid/emanote/pull/498))
- Support for folder "index.md" notes ([\#512](https://github.com/srid/emanote/pull/512))
- Instead of "foo/qux.md", you can now create "foo/qux/index.md"
- **BACKWARDS INCOMPTABILE** changes
- `feed.siteUrl` is now `page.siteUrl`
- A new HTML template layout "default" (unifies and) replaces both "book" and "note" layout. ([\#483](https://github.com/srid/emanote/pull/483))
Expand Down
2 changes: 1 addition & 1 deletion emanote/emanote.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: 2.4
name: emanote
version: 1.3.12.0
version: 1.3.13.0
license: AGPL-3.0-only
copyright: 2022 Sridhar Ratnakumar
maintainer: [email protected]
Expand Down
12 changes: 6 additions & 6 deletions emanote/src/Emanote/Model/Note.hs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ data Feed = Feed

data Note = Note
{ _noteRoute :: R.LMLRoute
, _noteLayerLoc :: Maybe Loc
, _noteSource :: Maybe (Loc, FilePath)
-- ^ The layer from which this note came. Nothing if the note was auto-generated.
, _noteDoc :: Pandoc
, _noteMeta :: Aeson.Value
Expand Down Expand Up @@ -281,12 +281,12 @@ mkEmptyNoteWith someR (Pandoc mempty -> doc) =
where
meta = Aeson.Null

mkNoteWith :: R.LMLRoute -> Maybe Loc -> Pandoc -> Aeson.Value -> [Text] -> Note
mkNoteWith r layerLoc doc' meta errs =
mkNoteWith :: R.LMLRoute -> Maybe (Loc, FilePath) -> Pandoc -> Aeson.Value -> [Text] -> Note
mkNoteWith r src doc' meta errs =
let (doc'', tit) = queryNoteTitle r doc' meta
feed = queryNoteFeed meta
doc = if null errs then doc'' else pandocPrepend (errorDiv errs) doc''
in Note r layerLoc doc meta tit errs feed
in Note r src doc meta tit errs feed
where
-- Prepend to block to the beginning of a Pandoc document (never before H1)
pandocPrepend :: B.Block -> Pandoc -> Pandoc
Expand All @@ -309,14 +309,14 @@ parseNote ::
(Loc, FilePath) ->
Text ->
m Note
parseNote scriptingEngine pluginBaseDir r (layerLoc, fp) s = do
parseNote scriptingEngine pluginBaseDir r src@(_, fp) s = do
((doc, meta), errs) <- runWriterT $ do
case r of
R.LMLRoute_Md _ ->
parseNoteMarkdown scriptingEngine pluginBaseDir fp s
R.LMLRoute_Org _ -> do
parseNoteOrg s
pure $ mkNoteWith r (Just layerLoc) doc meta errs
pure $ mkNoteWith r (Just src) doc meta errs

parseNoteOrg :: (MonadWriter [Text] m) => Text -> m (Pandoc, Aeson.Value)
parseNoteOrg s =
Expand Down
2 changes: 1 addition & 1 deletion emanote/src/Emanote/Model/Stork.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ renderStorkIndex model = do
storkFiles :: Model -> [File]
storkFiles model =
flip mapMaybe (Ix.toList (model ^. M.modelNotes)) $ \note -> do
baseDir <- Loc.locPath <$> note ^. N.noteLayerLoc
baseDir <- Loc.locPath . fst <$> note ^. N.noteSource
let fp = ((baseDir </>) $ R.withLmlRoute R.encodeRoute $ note ^. N.noteRoute)
ft = case note ^. N.noteRoute of
R.LMLRoute_Md _ -> FileType_Markdown
Expand Down
4 changes: 2 additions & 2 deletions emanote/src/Emanote/Route/ModelRoute.hs
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,5 @@ mkLMLRouteFromFilePath fp =
mkLMLRouteFromKnownFilePath :: LML -> FilePath -> Maybe LMLRoute
mkLMLRouteFromKnownFilePath lmlType fp =
case lmlType of
Md -> fmap LMLRoute_Md (R.mkRouteFromFilePath fp)
Org -> fmap LMLRoute_Org (R.mkRouteFromFilePath fp)
Md -> fmap LMLRoute_Md (R.mkRouteFromFilePath' True fp)
Org -> fmap LMLRoute_Org (R.mkRouteFromFilePath' True fp)
15 changes: 12 additions & 3 deletions emanote/src/Emanote/Route/R.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,19 @@ instance (HasExt ext) => Show (R ext) where

-- | Convert foo/bar.<ext> to a @R@
mkRouteFromFilePath :: forall a (ext :: FileType a). (HasExt ext) => FilePath -> Maybe (R ext)
mkRouteFromFilePath fp = do
mkRouteFromFilePath = mkRouteFromFilePath' False

{- | Like `mkRouteFromFilePath` but drops the last slug if it's "index"

Behaves like `mkRouteFromFilePath` for top-level files.
-}
mkRouteFromFilePath' :: forall a (ext :: FileType a). (HasExt ext) => Bool -> FilePath -> Maybe (R ext)
mkRouteFromFilePath' dropIndex fp = do
base <- withoutKnownExt @_ @ext fp
let slugs = fromString . toString . T.dropWhileEnd (== '/') . toText <$> splitPath base
viaNonEmpty R slugs
slugs <- nonEmpty $ fromString . toString . T.dropWhileEnd (== '/') . toText <$> splitPath base
if dropIndex && length slugs > 1 && last slugs == "index"
then viaNonEmpty R $ init slugs
else pure $ R slugs

mkRouteFromSlugs :: NonEmpty Slug -> R ext
mkRouteFromSlugs =
Expand Down
2 changes: 1 addition & 1 deletion emanote/src/Emanote/Source/Patch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ patchModel' layers noteF storkIndexTVar scriptingEngine fpType fp action = do
log $ "Removing note: " <> toText fp
pure $ M.modelDeleteNote r
R.Yaml ->
case R.mkRouteFromFilePath fp of
case R.mkRouteFromFilePath' True fp of
Nothing ->
pure id
Just r -> case action of
Expand Down
5 changes: 4 additions & 1 deletion emanote/src/Emanote/View/Template.hs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ renderLmlHtml :: Model -> MN.Note -> LByteString
renderLmlHtml model note = do
let r = note ^. MN.noteRoute
meta = patchMeta $ Meta.getEffectiveRouteMetaWith (note ^. MN.noteMeta) r model
sourcePath = fromMaybe (R.withLmlRoute R.encodeRoute r) $ do
fmap snd $ note ^. MN.noteSource
-- Force a doctype into the generated HTML as a workaround for Heist
-- discarding it. See: https://github.com/srid/emanote/issues/216
withDoctype = ("<!DOCTYPE html>\n" <>)
Expand All @@ -169,7 +171,8 @@ renderLmlHtml model note = do
"ema:note:title" ##
C.titleSplice ctx (note ^. MN.noteTitle)
"ema:note:source-path" ##
HI.textSplice (toText . R.withLmlRoute R.encodeRoute $ r)
HI.textSplice
$ toText sourcePath
"ema:note:url" ##
HI.textSplice (SR.siteRouteUrl model . SR.lmlSiteRoute $ (R.LMLView_Html, r))
"emaNoteFeedUrl" ##
Expand Down
9 changes: 9 additions & 0 deletions emanote/test/Emanote/Route/RSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ mkRouteFromFilePathSpec = describe "mkRouteFromFilePath" $ do
mkRouteFromFilePath "foo/bar.md" === Just r2
it "three slugs" . hedgehog $ do
mkRouteFromFilePath "foo/bar/qux.md" === Just r3
describe "dropIndex" $ do
it "index route" . hedgehog $ do
mkRouteFromFilePath' True "index.md" === Just rIndex
it "single slug" . hedgehog $ do
mkRouteFromFilePath' True "foo.md" === Just r1
it "two slugs" . hedgehog $ do
mkRouteFromFilePath' True "foo/index.md" === Just r1
it "three slugs" . hedgehog $ do
mkRouteFromFilePath' True "foo/bar/index.md" === Just r2

routeInitsSpec :: Spec
routeInitsSpec = describe "routeInits" $ do
Expand Down
Loading