From a94c567942fe740c18e767e606ed6000d7d70a63 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 9 Feb 2024 10:51:23 -0500 Subject: [PATCH 01/10] wip --- docs/{guide.md => guide/index.md} | 0 docs/{start.md => start/index.md} | 0 emanote/src/Emanote/Route/ModelRoute.hs | 4 ++-- emanote/src/Emanote/Route/R.hs | 11 ++++++++--- 4 files changed, 10 insertions(+), 5 deletions(-) rename docs/{guide.md => guide/index.md} (100%) rename docs/{start.md => start/index.md} (100%) diff --git a/docs/guide.md b/docs/guide/index.md similarity index 100% rename from docs/guide.md rename to docs/guide/index.md diff --git a/docs/start.md b/docs/start/index.md similarity index 100% rename from docs/start.md rename to docs/start/index.md diff --git a/emanote/src/Emanote/Route/ModelRoute.hs b/emanote/src/Emanote/Route/ModelRoute.hs index d890408b6..aa344395e 100644 --- a/emanote/src/Emanote/Route/ModelRoute.hs +++ b/emanote/src/Emanote/Route/ModelRoute.hs @@ -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) diff --git a/emanote/src/Emanote/Route/R.hs b/emanote/src/Emanote/Route/R.hs index 0b7b86d6c..9c4a26878 100644 --- a/emanote/src/Emanote/Route/R.hs +++ b/emanote/src/Emanote/Route/R.hs @@ -29,10 +29,15 @@ instance (HasExt ext) => Show (R ext) where -- | Convert foo/bar. to a @R@ mkRouteFromFilePath :: forall a (ext :: FileType a). (HasExt ext) => FilePath -> Maybe (R ext) -mkRouteFromFilePath fp = do +mkRouteFromFilePath = mkRouteFromFilePath' False + +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 = From b2f566b4051683c26924b8f332f06aab9a162d4c Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 9 Feb 2024 11:02:44 -0500 Subject: [PATCH 02/10] yaml --- emanote/src/Emanote/Source/Patch.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emanote/src/Emanote/Source/Patch.hs b/emanote/src/Emanote/Source/Patch.hs index 0dcd849ee..17a6863e9 100644 --- a/emanote/src/Emanote/Source/Patch.hs +++ b/emanote/src/Emanote/Source/Patch.hs @@ -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 From 8335727d96a59dd5f25ae7144127eae84d185743 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 9 Feb 2024 14:07:32 -0500 Subject: [PATCH 03/10] Store note fiepath in `Note` Along with `Loc`. Thus, unify the fields. --- emanote/src/Emanote/Model/Note.hs | 12 ++++++------ emanote/src/Emanote/Model/Stork.hs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/emanote/src/Emanote/Model/Note.hs b/emanote/src/Emanote/Model/Note.hs index 97307bdc8..3c81c6648 100644 --- a/emanote/src/Emanote/Model/Note.hs +++ b/emanote/src/Emanote/Model/Note.hs @@ -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 @@ -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 @@ -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 = diff --git a/emanote/src/Emanote/Model/Stork.hs b/emanote/src/Emanote/Model/Stork.hs index 848ed386e..801ad59c4 100644 --- a/emanote/src/Emanote/Model/Stork.hs +++ b/emanote/src/Emanote/Model/Stork.hs @@ -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 From d73f795a5eb83279270957c7a41237234101ea09 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 9 Feb 2024 14:07:54 -0500 Subject: [PATCH 04/10] use actual source path in edit url --- emanote/src/Emanote/View/Template.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/emanote/src/Emanote/View/Template.hs b/emanote/src/Emanote/View/Template.hs index aa3809d39..72453e5a3 100644 --- a/emanote/src/Emanote/View/Template.hs +++ b/emanote/src/Emanote/View/Template.hs @@ -169,7 +169,9 @@ 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 $ fromMaybe (toText $ R.withLmlRoute R.encodeRoute r) $ do + (_, fp) <- note ^. MN.noteSource + pure $ toText fp "ema:note:url" ## HI.textSplice (SR.siteRouteUrl model . SR.lmlSiteRoute $ (R.LMLView_Html, r)) "emaNoteFeedUrl" ## From 42597fe7cd85d5df616deafd770e2ba6917cfc5e Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 9 Feb 2024 14:12:17 -0500 Subject: [PATCH 05/10] changelog --- emanote/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/emanote/CHANGELOG.md b/emanote/CHANGELOG.md index dfdf4f77a..20cad4a10 100644 --- a/emanote/CHANGELOG.md +++ b/emanote/CHANGELOG.md @@ -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)) From 66b5eed368616fdf588dac2f810f23cd57dc47a3 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Fri, 9 Feb 2024 14:12:25 -0500 Subject: [PATCH 06/10] ver --- emanote/emanote.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emanote/emanote.cabal b/emanote/emanote.cabal index 8fb8ce874..f692fb7e8 100644 --- a/emanote/emanote.cabal +++ b/emanote/emanote.cabal @@ -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: srid@srid.ca From cf357641a947a4c71e1f60eb05c0e32269e2f4e7 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Sat, 10 Feb 2024 01:29:21 -0500 Subject: [PATCH 07/10] refactor and tests --- emanote/src/Emanote/Route/R.hs | 4 ++++ emanote/src/Emanote/View/Template.hs | 7 ++++--- emanote/test/Emanote/Route/RSpec.hs | 9 +++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/emanote/src/Emanote/Route/R.hs b/emanote/src/Emanote/Route/R.hs index 9c4a26878..84aa9fe1b 100644 --- a/emanote/src/Emanote/Route/R.hs +++ b/emanote/src/Emanote/Route/R.hs @@ -31,6 +31,10 @@ instance (HasExt ext) => Show (R ext) where mkRouteFromFilePath :: forall a (ext :: FileType a). (HasExt ext) => FilePath -> Maybe (R ext) 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 diff --git a/emanote/src/Emanote/View/Template.hs b/emanote/src/Emanote/View/Template.hs index 72453e5a3..f92334363 100644 --- a/emanote/src/Emanote/View/Template.hs +++ b/emanote/src/Emanote/View/Template.hs @@ -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 = ("\n" <>) @@ -169,9 +171,8 @@ renderLmlHtml model note = do "ema:note:title" ## C.titleSplice ctx (note ^. MN.noteTitle) "ema:note:source-path" ## - HI.textSplice $ fromMaybe (toText $ R.withLmlRoute R.encodeRoute r) $ do - (_, fp) <- note ^. MN.noteSource - pure $ toText fp + HI.textSplice + $ toText sourcePath "ema:note:url" ## HI.textSplice (SR.siteRouteUrl model . SR.lmlSiteRoute $ (R.LMLView_Html, r)) "emaNoteFeedUrl" ## diff --git a/emanote/test/Emanote/Route/RSpec.hs b/emanote/test/Emanote/Route/RSpec.hs index c7b643aa8..f561cf2d5 100644 --- a/emanote/test/Emanote/Route/RSpec.hs +++ b/emanote/test/Emanote/Route/RSpec.hs @@ -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 From 5232610c1479d10529f788e12264b34be9a74edc Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Sat, 10 Feb 2024 01:37:47 -0500 Subject: [PATCH 08/10] docs --- docs/guide/folder-note.md | 14 ++++++++++++++ docs/guide/folgezettel.md | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 docs/guide/folder-note.md diff --git a/docs/guide/folder-note.md b/docs/guide/folder-note.md new file mode 100644 index 000000000..cb636a457 --- /dev/null +++ b/docs/guide/folder-note.md @@ -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. \ No newline at end of file diff --git a/docs/guide/folgezettel.md b/docs/guide/folgezettel.md index 536a3f982..5d78b01b2 100644 --- a/docs/guide/folgezettel.md +++ b/docs/guide/folgezettel.md @@ -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]]. From 61f2e9a113cd4bdd2cd6f400694a0da0b746f4f1 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Sat, 10 Feb 2024 01:44:26 -0500 Subject: [PATCH 09/10] undo renames --- docs/{guide/index.md => guide.md} | 0 docs/{start/index.md => start.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/{guide/index.md => guide.md} (100%) rename docs/{start/index.md => start.md} (100%) diff --git a/docs/guide/index.md b/docs/guide.md similarity index 100% rename from docs/guide/index.md rename to docs/guide.md diff --git a/docs/start/index.md b/docs/start.md similarity index 100% rename from docs/start/index.md rename to docs/start.md From c89515c69336bf2aa60e26432989027e49a6c910 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Sat, 10 Feb 2024 01:46:25 -0500 Subject: [PATCH 10/10] links --- docs/guide/folgezettel.md | 2 +- docs/guide/html-template/sidebar.md | 2 +- docs/guide/yaml-config.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guide/folgezettel.md b/docs/guide/folgezettel.md index 5d78b01b2..aa8f89352 100644 --- a/docs/guide/folgezettel.md +++ b/docs/guide/folgezettel.md @@ -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. \ No newline at end of file +> 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. \ No newline at end of file diff --git a/docs/guide/html-template/sidebar.md b/docs/guide/html-template/sidebar.md index 73c1f7e25..da02630a2 100644 --- a/docs/guide/html-template/sidebar.md +++ b/docs/guide/html-template/sidebar.md @@ -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. diff --git a/docs/guide/yaml-config.md b/docs/guide/yaml-config.md index 246e61833..5695c1cb5 100644 --- a/docs/guide/yaml-config.md +++ b/docs/guide/yaml-config.md @@ -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.