emanote 1.0.0.0 → 1.0.2.0
raw patch · 48 files changed
+676/−544 lines, 48 filesdep +deriving-aesonPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: deriving-aeson
API changes (from Hackage documentation)
- Emanote.Model.Stork.Index: newtype Input
+ Emanote.Model.Stork.Index: Config :: Input -> Config
+ Emanote.Model.Stork.Index: data Handling
+ Emanote.Model.Stork.Index: data Input
+ Emanote.Model.Stork.Index: instance Data.Aeson.Types.FromJSON.FromJSON Emanote.Model.Stork.Index.Handling
+ Emanote.Model.Stork.Index: instance Data.Default.Class.Default Emanote.Model.Stork.Index.Handling
+ Emanote.Model.Stork.Index: instance GHC.Classes.Eq Emanote.Model.Stork.Index.Config
+ Emanote.Model.Stork.Index: instance GHC.Classes.Eq Emanote.Model.Stork.Index.Handling
+ Emanote.Model.Stork.Index: instance GHC.Generics.Generic Emanote.Model.Stork.Index.Handling
+ Emanote.Model.Stork.Index: instance GHC.Show.Show Emanote.Model.Stork.Index.Config
+ Emanote.Model.Stork.Index: instance GHC.Show.Show Emanote.Model.Stork.Index.Handling
+ Emanote.Model.Stork.Index: newtype Config
- Emanote.Model.Stork.Index: Input :: [File] -> Input
+ Emanote.Model.Stork.Index: Input :: [File] -> Handling -> Input
- Emanote.Model.Stork.Index: readOrBuildStorkIndex :: (MonadIO m, MonadLoggerIO m) => IndexVar -> Input -> m LByteString
+ Emanote.Model.Stork.Index: readOrBuildStorkIndex :: (MonadIO m, MonadLoggerIO m) => IndexVar -> Config -> m LByteString
- Emanote.Model.Type: modelCompileTailwind :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) Bool
+ Emanote.Model.Type: modelCompileTailwind :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) Bool
- Emanote.Model.Type: modelEmaCLIAction :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) (Some Action)
+ Emanote.Model.Type: modelEmaCLIAction :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) (Some Action)
- Emanote.Model.Type: modelHeistTemplate :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) TemplateState
+ Emanote.Model.Type: modelHeistTemplate :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) TemplateState
- Emanote.Model.Type: modelInstanceID :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) UUID
+ Emanote.Model.Type: modelInstanceID :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) UUID
- Emanote.Model.Type: modelLayers :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) (Set Loc)
+ Emanote.Model.Type: modelLayers :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) (Set Loc)
- Emanote.Model.Type: modelNav :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) [Tree Slug]
+ Emanote.Model.Type: modelNav :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) [Tree Slug]
- Emanote.Model.Type: modelNotes :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) IxNote
+ Emanote.Model.Type: modelNotes :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) IxNote
- Emanote.Model.Type: modelPandocRenderers :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) (EmanotePandocRenderers Model LMLRoute)
+ Emanote.Model.Type: modelPandocRenderers :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) (EmanotePandocRenderers Model LMLRoute)
- Emanote.Model.Type: modelRels :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) IxRel
+ Emanote.Model.Type: modelRels :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) IxRel
- Emanote.Model.Type: modelRoutePrism :: forall encF_a2sEA encF_a2thF. Lens (ModelT encF_a2sEA) (ModelT encF_a2thF) (encF_a2sEA (Prism' FilePath SiteRoute)) (encF_a2thF (Prism' FilePath SiteRoute))
+ Emanote.Model.Type: modelRoutePrism :: forall encF_a1yQJ encF_a1ztS. Lens (ModelT encF_a1yQJ) (ModelT encF_a1ztS) (encF_a1yQJ (Prism' FilePath SiteRoute)) (encF_a1ztS (Prism' FilePath SiteRoute))
- Emanote.Model.Type: modelSData :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) IxSData
+ Emanote.Model.Type: modelSData :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) IxSData
- Emanote.Model.Type: modelStaticFiles :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) IxStaticFile
+ Emanote.Model.Type: modelStaticFiles :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) IxStaticFile
- Emanote.Model.Type: modelStatus :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) Status
+ Emanote.Model.Type: modelStatus :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) Status
- Emanote.Model.Type: modelStorkIndex :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) IndexVar
+ Emanote.Model.Type: modelStorkIndex :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) IndexVar
- Emanote.Model.Type: modelTasks :: forall encF_a2sEA. Lens' (ModelT encF_a2sEA) IxTask
+ Emanote.Model.Type: modelTasks :: forall encF_a1yQJ. Lens' (ModelT encF_a1yQJ) IxTask
Files
- CHANGELOG.md +17/−1
- README.md +6/−5
- default/_emanote-bin/compile-css +0/−4
- default/_emanote-static/stork/stork.js.map +1/−0
- default/index.yaml +4/−0
- default/templates/components/stork/stork-search-head.tpl +43/−8
- emanote.cabal +4/−3
- src/Emanote.hs +18/−19
- src/Emanote/CLI.hs +14/−15
- src/Emanote/Model/Calendar.hs +8/−2
- src/Emanote/Model/Graph.hs +8/−7
- src/Emanote/Model/Link/Rel.hs +24/−20
- src/Emanote/Model/Meta.hs +9/−9
- src/Emanote/Model/Note.hs +33/−33
- src/Emanote/Model/SData.hs +6/−5
- src/Emanote/Model/StaticFile.hs +4/−4
- src/Emanote/Model/Stork.hs +19/−6
- src/Emanote/Model/Stork/Index.hs +80/−31
- src/Emanote/Model/Task.hs +5/−5
- src/Emanote/Model/Title.hs +11/−12
- src/Emanote/Model/Type.hs +42/−39
- src/Emanote/Pandoc/BuiltinFilters.hs +4/−5
- src/Emanote/Pandoc/ExternalLink.hs +3/−4
- src/Emanote/Pandoc/Link.hs +7/−5
- src/Emanote/Pandoc/Markdown/Parser.hs +14/−15
- src/Emanote/Pandoc/Markdown/Syntax/HashTag.hs +17/−18
- src/Emanote/Pandoc/Markdown/Syntax/Highlight.hs +3/−4
- src/Emanote/Pandoc/Renderer.hs +27/−27
- src/Emanote/Pandoc/Renderer/Embed.hs +10/−10
- src/Emanote/Pandoc/Renderer/Query.hs +4/−5
- src/Emanote/Pandoc/Renderer/Url.hs +9/−10
- src/Emanote/Prelude.hs +3/−2
- src/Emanote/Route/Ext.hs +13/−10
- src/Emanote/Route/ModelRoute.hs +26/−25
- src/Emanote/Route/R.hs +4/−3
- src/Emanote/Route/SiteRoute.hs +11/−12
- src/Emanote/Route/SiteRoute/Class.hs +15/−16
- src/Emanote/Route/SiteRoute/Type.hs +17/−17
- src/Emanote/Source/Dynamic.hs +20/−20
- src/Emanote/Source/Loc.hs +21/−20
- src/Emanote/Source/Patch.hs +16/−16
- src/Emanote/Source/Pattern.hs +18/−12
- src/Emanote/View/Common.hs +18/−18
- src/Emanote/View/Export.hs +15/−16
- src/Emanote/View/LiveServerFiles.hs +5/−6
- src/Emanote/View/TagIndex.hs +17/−17
- src/Emanote/View/Template.hs +2/−2
- test/Emanote/Pandoc/ExternalLinkSpec.hs +1/−1
CHANGELOG.md view
@@ -1,10 +1,26 @@ # Revision history for emanote +## 1.0.2.0 (2023-01-29)++- Nix+ - Reduce Emanote's Nix runtime closure size+- UI+ - Add source map for Stork [\#391](https://github.com/srid/emanote/pull/391)+ - Workaround for Prism.js and Tailwind CSS both using `table` class [\#320](https://github.com/srid/emanote/pull/396)+ - Add option to include YAML frontmatter in the Stork index [\#398](https://github.com/srid/emanote/pull/398)+- Features+ - Timeline backlinks recognize flexible daily notes suffixed with arbitrary string [\#395](https://github.com/srid/emanote/issues/395)+- Performance+ - Address client-side memory leak due to Stork search in live server [\#411](https://github.com/srid/emanote/issues/411#issuecomment-1402056235)+- Misc+ - Ignore toplevel `flake.{nix,lock}` by default.+ - Remove deprecated `_emanote-bin/compile-css` script+ ## 1.0.0.0 (2022-12-04) - UI - Index pages are no longer marked as 'experimental'- - Add external link icon to external links (this behaviour is customizable). [\#189](https://github.com/EmaApps/emanote/pull/189)+ - Add external link icon to external links (this behaviour is customizable). [\#189](https://github.com/srid/emanote/pull/189) - `js.mermaid` snippet uses the "module" script tag approach, which is added to end of `<body>`. See [here](https://mermaid-js.github.io/mermaid/#/n00b-gettingStarted?id=_3-calling-the-javascript-api). - Align dates in timeline queries - Configuration
README.md view
@@ -29,6 +29,10 @@ See [architecture.md](docs/architecture.md) for a high-level overview of the codebase. +### PR contribution guidelines++Run `nix build .#check -L` when opening a PR.+ ## Discussion To discuss the emanote project, [join Matrix][matrix] or post in [GitHub Discussions][ghdiscuss].@@ -36,11 +40,8 @@ [matrix]: https://matrix.to/#/#ema:matrix.org [ghdiscuss]: https://github.com/srid/emanote/discussions -## Related projects--- https://github.com/squidfunk/mkdocs-material- [^licenses]: Emanote uses software and resources that are licensed differently, viz.: - [Logo](https://www.svgrepo.com/svg/267765/paper-plane) - [Stork search](https://github.com/jameslittle230/stork/blob/master/license.txt)- - Various SVG icons are from [Heroicons](https://heroicons.com/)+ - [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss/blob/master/LICENSE)+ - Various SVG icons are from [Heroicons](https://github.com/tailwindlabs/heroicons/blob/master/LICENSE)
− default/_emanote-bin/compile-css
@@ -1,4 +0,0 @@-#!/usr/bin/env bash-set -xe--echo "Deprecation Warning: this script should not be used anymore"
+ default/_emanote-static/stork/stork.js.map view
@@ -0,0 +1,1 @@+{"version":3,"file":"stork.js","mappings":"kDAmBA,IAAIA,E,mGAEJ,MAAMC,EAAO,IAAIC,MAAM,IAAIC,UAAKC,GAIhC,SAASC,EAAUC,GAAO,OAAOL,EAAKK,GAFtCL,EAAKM,UAAKH,EAAW,MAAM,GAAM,GAIjC,IAAII,EAAYP,EAAKQ,OAcrB,IAAIC,EAAkB,EAElBC,EAAuB,KAC3B,SAASC,IAIL,OAH6B,OAAzBD,GAAiCA,EAAqBE,SAAWb,EAAKc,OAAOD,SAC7EF,EAAuB,IAAII,WAAWf,EAAKc,OAAOD,SAE/CF,EAGX,IAAIK,EAAoB,IAAIC,YAAY,SAExC,MAAMC,EAAwD,mBAAjCF,EAAkBG,WACzC,SAAUC,EAAKC,GACjB,OAAOL,EAAkBG,WAAWC,EAAKC,IAEvC,SAAUD,EAAKC,GACjB,MAAMC,EAAMN,EAAkBO,OAAOH,GAErC,OADAC,EAAKG,IAAIF,GACF,CACHG,KAAML,EAAIX,OACViB,QAASJ,EAAIb,SAIrB,SAASkB,EAAkBP,EAAKQ,EAAQC,GAEpC,QAAgBzB,IAAZyB,EAAuB,CACvB,MAAMP,EAAMN,EAAkBO,OAAOH,GAC/BU,EAAMF,EAAON,EAAIb,QAGvB,OAFAG,IAAkBmB,SAASD,EAAKA,EAAMR,EAAIb,QAAQe,IAAIF,GACtDZ,EAAkBY,EAAIb,OACfqB,EAGX,IAAIE,EAAMZ,EAAIX,OACVqB,EAAMF,EAAOI,GAEjB,MAAMC,EAAMrB,IAEZ,IAAIsB,EAAS,EAEb,KAAOA,EAASF,EAAKE,IAAU,CAC3B,MAAMC,EAAOf,EAAIgB,WAAWF,GAC5B,GAAIC,EAAO,IAAM,MACjBF,EAAIH,EAAMI,GAAUC,EAGxB,GAAID,IAAWF,EAAK,CACD,IAAXE,IACAd,EAAMA,EAAIiB,MAAMH,IAEpBJ,EAAMD,EAAQC,EAAKE,EAAKA,EAAME,EAAsB,EAAbd,EAAIX,QAC3C,MAAMY,EAAOT,IAAkBmB,SAASD,EAAMI,EAAQJ,EAAME,GAG5DE,GAFYhB,EAAaE,EAAKC,GAEhBK,QAIlB,OADAhB,EAAkBwB,EACXJ,EAUX,IAAIQ,EAAuB,KAC3B,SAASC,IAIL,OAH6B,OAAzBD,GAAiCA,EAAqBzB,SAAWb,EAAKc,OAAOD,SAC7EyB,EAAuB,IAAIE,WAAWxC,EAAKc,OAAOD,SAE/CyB,EAGX,IAAIG,EAAoB,IAAIC,YAAY,QAAS,CAAEC,WAAW,EAAMC,OAAO,IAI3E,SAASC,EAAmBf,EAAKE,GAC7B,OAAOS,EAAkBK,OAAOlC,IAAkBmB,SAASD,EAAKA,EAAME,IAOnE,SAASe,EAAoBC,EAAMC,GACtC,IACI,MAAMC,EAASlD,EAAKmD,iCAAiC,IACrD,IAAIC,EAAOzB,EAAkBqB,EAAMhD,EAAKqD,kBAAmBrD,EAAKsD,oBAC5DC,EAAO7C,EACP8C,EAhCZ,SAA2BpC,EAAKQ,GAC5B,MAAME,EAAMF,EAAoB,EAAbR,EAAIX,QAGvB,OAFAG,IAAkBY,IAAIJ,EAAKU,EAAM,GACjCpB,EAAkBU,EAAIX,OACfqB,EA4BQ2B,CAAkBR,EAAMjD,EAAKqD,mBACpCK,EAAOhD,EACXV,EAAK+C,oBAAoBG,EAAQE,EAAMG,EAAMC,EAAME,GACnD,IAAIC,EAAKpB,IAAkBW,EAAS,EAAI,GACpCU,EAAKrB,IAAkBW,EAAS,EAAI,GACxC,OAAOL,EAAmBc,EAAIC,GAChC,QACE5D,EAAKmD,gCAAgC,IACrCnD,EAAK6D,gBAAgBF,EAAIC,IAS1B,SAASE,EAAYd,EAAMe,GAC9B,IACI,MAAMb,EAASlD,EAAKmD,iCAAiC,IACrD,IAAIC,EAAOzB,EAAkBqB,EAAMhD,EAAKqD,kBAAmBrD,EAAKsD,oBAC5DC,EAAO7C,EACP8C,EAAO7B,EAAkBoC,EAAO/D,EAAKqD,kBAAmBrD,EAAKsD,oBAC7DI,EAAOhD,EACXV,EAAK8D,YAAYZ,EAAQE,EAAMG,EAAMC,EAAME,GAC3C,IAAIC,EAAKpB,IAAkBW,EAAS,EAAI,GACpCU,EAAKrB,IAAkBW,EAAS,EAAI,GACxC,OAAOL,EAAmBc,EAAIC,GAChC,QACE5D,EAAKmD,gCAAgC,IACrCnD,EAAK6D,gBAAgBF,EAAIC,IAO1B,SAASI,IACZ,IACI,MAAMd,EAASlD,EAAKmD,iCAAiC,IACrDnD,EAAKgE,mBAAmBd,GACxB,IAAIS,EAAKpB,IAAkBW,EAAS,EAAI,GACpCU,EAAKrB,IAAkBW,EAAS,EAAI,GACxC,OAAOL,EAAmBc,EAAIC,GAChC,QACE5D,EAAKmD,gCAAgC,IACrCnD,EAAK6D,gBAAgBF,EAAIC,IA7DjCnB,EAAkBK,SAmJlB,QA1CAmB,eAAeC,EAAKC,QACK,IAAVA,IACPA,EAAQ,IAAIC,IAAI,gBAnOlB,SAAwBC,GACtB,MAAMC,EAAa,IAEnB,IAAIC,EAAM,GAYV,QAVKD,GAAcA,EAAWE,QAAQ,OAAS,KAC7CD,GAAOE,OAAOC,SAASC,SAAW,KAAOF,OAAOC,SAASE,MAIzDL,GADED,GAGK,IAGFC,EAAMF,EAoN4BQ,CAAe,wCAE5D,MAAMC,EAAU,CAChBA,IAAc,IACdA,EAAQC,IAAIC,2BAA6B,WAErC,OAhDR,SAAuBC,GACfzE,IAAcP,EAAKQ,QAAQR,EAAKM,KAAKN,EAAKQ,OAAS,GACvD,MAAMH,EAAME,EAIZ,OAHAA,EAAYP,EAAKK,GAEjBL,EAAKK,GAAO2E,EACL3E,EA0CI4E,CADG,IAAIC,QAGlBL,EAAQC,IAAIK,6BAA+B,SAASC,EAAMC,GACtD,IACIlC,EAAOzB,EADDtB,EAAUiF,GAAMC,MACQvF,EAAKqD,kBAAmBrD,EAAKsD,oBAC3DC,EAAO7C,EACX6B,IAAkB8C,EAAO,EAAI,GAAK9B,EAClChB,IAAkB8C,EAAO,EAAI,GAAKjC,GAEtC0B,EAAQC,IAAIS,6BAA+B,SAASH,EAAMC,GACtD,IACIG,QAAQC,MAAM7C,EAAmBwC,EAAMC,IACzC,QACEtF,EAAK6D,gBAAgBwB,EAAMC,KAGnCR,EAAQC,IAAIY,2BAA6B,SAASN,IAvNtD,SAAoB/E,GAChB,MAAMsF,EAAMvF,EAAUC,IAP1B,SAAoBA,GACZA,EAAM,KACVL,EAAKK,GAAOE,EACZA,EAAYF,IAKZuF,CAAWvF,GAsNPwF,CAAWT,KAGM,iBAAVlB,GAA0C,mBAAZ4B,SAA0B5B,aAAiB4B,SAA4B,mBAAR3B,KAAsBD,aAAiBC,OAC3ID,EAAQ6B,MAAM7B,IAKlB,MAAM,SAAE8B,EAAQ,OAAEC,SAjEtBjC,eAAoBiC,EAAQpB,GACxB,GAAwB,mBAAbqB,UAA2BD,aAAkBC,SAAU,CAC9D,GAAgD,mBAArCC,YAAYC,qBACnB,IACI,aAAaD,YAAYC,qBAAqBH,EAAQpB,GAExD,MAAOwB,GACL,GAA0C,oBAAtCJ,EAAOK,QAAQC,IAAI,gBAInB,MAAMF,EAHNb,QAAQgB,KAAK,oMAAqMH,GAQ9N,MAAMI,QAAcR,EAAOS,cAC3B,aAAaP,YAAYQ,YAAYF,EAAO5B,GAEzC,CACH,MAAMmB,QAAiBG,YAAYQ,YAAYV,EAAQpB,GAEvD,OAAImB,aAAoBG,YAAYS,SACzB,CAAEZ,SAAAA,EAAUC,OAAAA,GAGZD,GAuCoBa,OAAW3C,EAAOW,GAKrD,OAHA9E,EAAOiG,EAASc,QAChB7C,EAAK8C,uBAAyBd,EAEvBlG,I,kHCzQX,aACA,SAiBa,EAAAiH,cAAyC,CACpDC,cAAc,EACdC,gBAAgB,EAChBC,YAAY,EACZC,iBAAiB,EACjBC,mBAAoB,EACpBC,gBAAgB,EAChBC,WAAY,CAAEC,SAAU,OAAQC,OAAQ,SACxCC,mBAAevH,EACfwH,sBAAkBxH,EAClByH,qBAAiBzH,EACjB0H,oBAAgB1H,EAChB2H,mBAAoB,SAAAxD,GAAO,OAAAA,IAG7B,qCACEyD,GAEA,IAAMC,GAAgB,IAAAC,YACpBC,OAAOC,KAAKJ,GACZG,OAAOC,KAAK,EAAAnB,gBAGd,GAAIgB,EAAcxH,OAAS,EAAG,CAC5B,IAAM2H,GAAO,IAAAV,QAAOO,EAAcxH,OAAQ,MAAO,QAC3C4H,EAAcC,KAAKC,UAAUN,GACnC,OAAO,IAAI,UAAW,kBAAWG,EAAI,8BAAsBC,IAK7D,IAFA,IAAMG,EAAwBL,OAAOM,OAAO,GAAI,EAAAxB,eAE9B,MAAAkB,OAAOC,KAAK,EAAAnB,eAAZ,eAA0D,CAAvE,IAAMyB,EAAG,KACNC,EAAcX,EAAUU,QACVtI,IAAhBuI,IAGFH,EAAOE,GAAOC,GAIlB,OAAOH,I,mIC1DT,kBACExF,EACA4F,GAEA,IAAMC,EAAOC,SAASC,cAAc/F,GAIpC,OAHI4F,EAAWI,YACbH,EAAKI,aAAa,QAASL,EAAWI,WAAWE,KAAK,MAEjDL,GAGT,eACEA,EACAnE,EACAyE,GAEAA,EAAUC,sBAAsB1E,EAAUmE,IAG5C,iBAAsBA,GACpB,KAAOA,GAAQA,EAAKQ,YAClBR,EAAKS,YAAYT,EAAKQ,aAI1B,mBAAwBR,EAA0BU,GAChD,IAAMC,EAAWV,SAASW,eAAeF,GACrCV,GAAQA,EAAKQ,WACfR,EAAKa,aAAaF,EAAUX,EAAKQ,YACxBR,GACTA,EAAKc,YAAYH,IAIrB,uCACEX,EACAe,GAEA,IAAMC,EAAkBhB,EAAKiB,wBACvBC,EAAuBH,EAAUE,wBAEvC,OACED,EAAgBG,OAASD,EAAqBC,QAC9CH,EAAgBI,IAAMF,EAAqBE,M,+EC1C/C,aACA,SACA,SACA,SAIA,aAmBE,WAAYjH,EAAcuB,EAAa2F,GAAvC,WAdQ,KAAAC,OAAsB,cAE9B,KAAAC,iBAAmB,EAGnB,KAAAC,QAAyB,GACzB,KAAAC,iBAAmB,EAGnB,KAAAC,uBAA6D,GAC7D,KAAAC,kBAAoB,EACpB,KAAAC,gBAAiB,EACjB,KAAAC,oBAAqB,EA+HrB,KAAAC,oBAAsB,SAACC,GACrB,EAAKC,MAAQ,UACb,EAAKT,iBAAmBQ,EACpB,EAAKV,OAAOhD,cACd,EAAK4D,UAhIPC,KAAK/H,KAAOA,EACZ+H,KAAKxG,IAAMA,EACXwG,KAAKb,OAASA,EA+JlB,OA5JE,sBAAW,oBAAK,C,IAAhB,WACE,OAAOa,KAAKZ,Q,IAGd,SAAiBa,GACfD,KAAKZ,OAASa,EACdD,KAAKD,U,gCAGC,YAAAG,kBAAR,WACE,IAAKF,KAAKG,WAAY,OAAO,KAC7B,IAAMnH,EAAQgH,KAAKG,WAAWC,WAC9B,MAAmB,UAAfJ,KAAKF,MACA,oCACgB,SAAdE,KAAKF,MACP,cACE9G,MAAAA,OAAK,EAALA,EAAOtD,QAASsK,KAAKb,OAAO5C,mBAC9B,eACEyD,KAAKV,QACgB,IAA1BU,KAAKT,iBACA,aAAMS,KAAKb,OAAO1C,WAAWE,OAAM,WACP,IAA1BqD,KAAKT,iBACP,YAAKS,KAAKb,OAAO1C,WAAWC,SAAQ,WAEpC,UAAGsD,KAAKT,iBAAgB,YAAIS,KAAKb,OAAO1C,WAAWE,OAAM,WAI7D,MAGD,YAAA0D,qBAAR,WACE,MAAO,CACLf,QAASU,KAAKV,QACdI,gBAAgB,EAChBrD,WAAY2D,KAAKb,OAAO9C,WACxBiE,QAASN,KAAKE,oBACd/D,aAAc6D,KAAKb,OAAOhD,aAC1BoE,SAAUP,KAAKX,iBACfS,MAAOE,KAAKF,QAIR,YAAAC,OAAR,WACOC,KAAKG,YACVH,KAAKG,WAAWJ,OAAOC,KAAKK,yBAG9B,YAAAG,cAAA,SAActI,GAAd,WACE,OAAO,IAAIuI,SAAQ,SAACC,EAASC,GAC3B,IAAMC,EAAYrD,KAAKsD,OAAM,IAAA7I,qBAAoB,EAAKC,KAAMC,IACxD0I,EAAUjG,MACZgG,EAAO,IAAI,UAAWC,EAAUjG,SAE5B,EAAKwE,OAAO/C,gBACd1B,QAAQoG,IAAIF,GAGd,EAAKd,MAAQ,QACbY,EAAQE,QAKd,YAAAG,YAAA,WACEf,KAAKG,WAAa,IAAI,EAAAa,UAAUhB,KAAK/H,KAAM+H,MAC3CA,KAAKD,UAGP,YAAAkB,iBAAA,SAAiB/I,GAAjB,WACE8H,KAAKV,QAAUpH,EAAKoH,QACpBU,KAAKT,iBAAmBrH,EAAKgJ,gBAC7BlB,KAAKP,kBAAoB,EAGzB,IAAM0B,EAAYjJ,EAAKkJ,YAAc,GACrCpB,KAAKV,QAAQ+B,KAAI,SAAAC,GACf,IAAIC,EAAY,GAEVC,EAA2BF,EAAEG,SAChCJ,KAAI,SAAA9F,GAAK,OAAAA,EAAEmG,wBACXC,QAAO,SAAAC,GAAM,QAAEA,KAAI,GAEtB,GAAIJ,GAA4BA,EAAyB,GAAI,CAC3D,IAAMK,EAAgBL,EAAyB,GACb,iBAAvBK,EAAiB,IAC1BN,GAAaM,EAAiB,GAMhCP,EAAEG,UACFH,EAAEG,SAAS,IACXH,EAAEG,SAAS,GAAGC,sBACdJ,EAAEG,SAAS,GAAGC,qBAAqB,IACnCJ,EAAEG,SAAS,GAAGC,qBAAqB,GAAM,GACa,iBAA/CJ,EAAEG,SAAS,GAAGC,qBAAqB,GAAM,IAEhDH,EAAYD,EAAEG,SAAS,GAAGC,qBAAqB,GAAM,GAEvDJ,EAAEQ,MAAMtI,IAAM,EAAK2F,OAAOnC,mBAAmB,UAAGmE,GAAS,OAAGG,EAAEQ,MAAMtI,KAAG,OAAG+H,OAG5EvB,KAAKD,UAGC,YAAAgC,oBAAR,WACE,IAAMzC,EAAUU,KAAKV,QAQrB,OAPAA,EAAQ+B,KAAI,SAAAW,UACHA,EAAOC,uBACdD,EAAOP,SAASJ,KAAI,SAAAa,UACXA,EAAQC,wBACRD,EAAQR,2BAGZpC,GAWT,YAAA8C,iBAAA,WACEpC,KAAKF,MAAQ,SAGf,YAAAuC,cAAA,SAAcrJ,GACZ,GAAmB,UAAfgH,KAAKF,MAAT,CAKA,GAAI9G,EAAMtD,OAASsK,KAAKb,OAAO5C,mBAG7B,OAFAyD,KAAKV,QAAU,QACfU,KAAKD,SAIP,IACE,IAAM7H,GAAO,IAAAoK,eAActC,KAAK/H,KAAMe,GACtC,IAAKd,EAAM,OAEX8H,KAAKiB,iBAAiB/I,GAElB8H,KAAKb,OAAOvC,eACdoD,KAAKb,OAAOvC,cAAc5D,EAAOgH,KAAK+B,uBAExC,MAAOpH,GACPD,QAAQC,MAAMA,SApBdqF,KAAKD,UAuBX,EArLA,GAAa,EAAAwC,OAAAA,G,kFCNb,aAQA,OAsBMC,EAA0C,CAC9ClD,QAAS,GACTI,gBAAgB,EAChBrD,YAAY,EACZiE,QAAS,KACTnE,cAAc,EACdoE,SAAU,EACVT,MAAO,SAGT,aAUE,WAAY7H,EAAcwK,GAA1B,I,IAAA,OAFA,KAAAC,kBAAqC,MAGnC1C,KAAKyC,OAASA,EAEd,IAWM,EAXO,CACX,CACEE,SAAU,4BAAqB1K,EAAI,MACnC2K,YAAa,SAEf,CACED,SAAU,0BAAmB1K,EAAI,aACjC2K,YAAa,WAIYvB,KAAI,SAAAwB,GAC/B,IAAMC,EAAU/E,SAASgF,cAAcF,EAAEF,UACzC,IAAKG,EACH,MAAM,IAAI1I,MACR,yCAAkCnC,EAAI,cAAM4K,EAAED,YAAW,gFAAyEC,EAAEF,SAAQ,MAIhJ,OAAOG,KARF1J,EAAK,KAAEqE,EAAM,KAWpBuC,KAAKgD,SAAW,CACd5J,MAAOA,EACPqE,OAAQA,EACRwF,MAAM,IAAAC,QAAO,KAAM,CAAEjF,WAAY,CAAC,mBAClCkF,aAAa,IAAAD,QAAO,MAAO,CACzBjF,WAAY,CAAC,uBAEfsC,UAAU,IAAA2C,QAAO,MAAO,CAAEjF,WAAY,CAAC,oBACvCqC,SAAS,IAAA4C,QAAO,MAAO,CAAEjF,WAAY,CAAC,mBACtCmF,aAAa,IAAAF,QAAO,SAAU,CAC5BjF,WAAY,CAAC,yBAOjB+B,KAAKgD,SAAS5J,MAAMiK,oBAClB,QACArD,KAAKyC,OAAOjD,uBAAuB8D,iBAGrCtD,KAAKgD,SAAS5J,MAAMiK,oBAClB,UACArD,KAAKyC,OAAOjD,uBAAuB+D,mBAMrCvD,KAAKyC,OAAOjD,uBAAyB,CACnC8D,gBAAiB,SAAC/H,GAChB,EAAKiI,iBAAiBjI,IAGxBgI,kBAAmB,SAAChI,GAClB,EAAKkI,mBAAmBlI,KAK5ByE,KAAKgD,SAAS5J,MAAMsK,iBAClB,QACA1D,KAAKyC,OAAOjD,uBAAuB8D,iBAGrCtD,KAAKgD,SAAS5J,MAAMsK,iBAClB,UACA1D,KAAKyC,OAAOjD,uBAAuB+D,mBAKnB,QAAlB,EAAAvD,KAAKgD,SAASC,YAAI,SAAES,iBAAiB,aAAa,WAChD,EAAK/D,oBAAqB,KAG5BK,KAAKgD,SAASG,YAAYQ,UACxB,0DAEF3D,KAAKgD,SAASI,YAAYO,UAAY,sZAUlC3D,KAAKyC,OAAOtD,OAAOhD,eACrB,IAAAyH,KAAI5D,KAAKgD,SAASzC,SAAU,WAAYP,KAAKgD,SAAS5J,OAG/B,QAAzB,EAAA4G,KAAKgD,SAASI,mBAAW,SAAEM,iBAAiB,SAAS,WACnD,EAAKV,SAAS5J,MAAM6G,MAAQ,GAC5B,EAAK+C,SAAS5J,MAAMyK,QACpB,EAAK9D,OAAOyC,GACN,MAAS,CACb,EAAKC,OAAOtD,OAAOpC,eACnB,EAAK0F,OAAOtD,OAAOrC,iBAFdgH,EAAC,KAAEC,EAAC,KAIXD,GAAIA,IACJC,GAAIA,OA8NV,OA1NU,YAAAC,SAAR,W,OACE,IAAAC,OAAMjE,KAAKgD,SAASvF,SACpB,IAAAwG,OAAMjE,KAAKgD,SAASC,MACK,QAAzB,EAAAjD,KAAKgD,SAASI,mBAAW,SAAEc,SAC3BlE,KAAKgD,SAASvF,OAAO0G,UAAUD,OAAO,yBAGxC,YAAAnE,OAAA,SAAOD,GAAP,I,EAAA,OACQ9G,EAASgH,KAAKgD,SAAS5J,MAA2B6G,MAIxD,GAHAD,KAAKgE,WACLhE,KAAKoE,gBAAkBtE,EAEnBA,EAAM3D,aAAc,CACtB,IAWMoE,EAXkB,WACtB,OAAQT,EAAMA,OACZ,IAAK,QACL,IAAK,QACH,OAAO,EACT,IAAK,cACL,IAAK,UACH,MAAwB,GAAjBA,EAAMS,SAAiB,KAInB8D,GAEb9D,EAAW,GACbP,KAAKgD,SAASzC,SAAS+D,MAAMC,MAAQ,UAAc,IAAXhE,EAAc,KACtDP,KAAKgD,SAASzC,SAAS+D,MAAME,QAAU,MAEvCxE,KAAKgD,SAASzC,SAAS+D,MAAMC,MAAQ,OACrCvE,KAAKgD,SAASzC,SAAS+D,MAAME,QAAU,KAiB3C,GAboB,UAAhB1E,EAAMA,OACRE,KAAKgD,SAAS5J,MAAM+K,UAAUP,IAAI,eAGhC5D,KAAKI,WAAW1K,OAAS,GAAKoK,EAAMJ,iBACtCM,KAAKgD,SAASvF,OAAO0G,UAAUP,IAAI,yBACnC,IAAAA,KAAI5D,KAAKgD,SAAS1C,QAAS,YAAaN,KAAKgD,SAASvF,SAGpDqC,EAAMQ,UACR,IAAAmE,SAAQzE,KAAKgD,SAAS1C,QAASR,EAAMQ,UAGtB,QAAb,EAAAR,EAAMR,eAAO,eAAE5J,QAAS,GAAKoK,EAAMJ,eAAgB,EACrD,IAAAkE,KAAI5D,KAAKgD,SAASC,KAAM,YAAajD,KAAKgD,SAASvF,QAEnD,I,eAASiH,GACP,IAAM1C,EAASlC,EAAMR,QAAQoF,GACvBC,EAA0C,CAC9CC,SAAUF,IAAM,EAAKjF,kBACrBpD,WAAYyD,EAAMzD,YAGdwI,GAAW,IAAAC,kBAAiB9C,EAAQ2C,IAC1C,IAAAf,KAAIiB,EAAyB,YAAa,EAAK7B,SAASC,MAExD4B,EAASnB,iBAAiB,aAAa,WACjC,EAAK/D,oBACH+E,IAAM,EAAKjF,mBACb,EAAKsF,wBAAwB,CAAEC,GAAIN,EAAGO,gBAAgB,OAK5DJ,EAASnB,iBAAiB,cAAc,WAClC,EAAK/D,oBACH+E,IAAM,EAAKjF,mBACb,EAAKsF,wBAAwB,CAAEC,IAAK,EAAGC,gBAAgB,OAK7DJ,EAASnB,iBAAiB,SAAS,SAAAnI,GACjCA,EAAE2J,iBACF,EAAKC,mB,OA5BAT,EAAI,EAAGA,EAAI5E,EAAMR,QAAQ5J,OAAQgP,I,EAAjCA,IAgCT,IAAAd,KAAI5D,KAAKgD,SAASG,YAAa,YAAanD,KAAKgD,SAASvF,UAGvDzE,MAAAA,OAAK,EAALA,EAAOtD,SAAU,GAAK,GAAKsK,KAAKyC,OAAOtD,OAAO7C,kBACjD,IAAAsH,KAAI5D,KAAKgD,SAASI,YAAa,WAAYpD,KAAKgD,SAAS5J,QAIrD,YAAA+L,aAAR,WACE,GAA8B,MAA1BnF,KAAKP,kBAA2B,CAClC,IAAM,EAASO,KAAKyC,OAAOnD,QAAQU,KAAKP,mBACpCO,KAAKyC,OAAOtD,OAAOtC,iBACrB4D,QAAQC,QACNV,KAAKyC,OAAOtD,OAAOtC,iBAAiBmD,KAAKI,WAAY,IACrDgF,SAAQ,WACR1L,OAAOC,SAAS+D,OAAO,EAAOoE,MAAMtI,QAGtCE,OAAOC,SAAS+D,OAAO,EAAOoE,MAAMtI,OAK1C,YAAAuL,wBAAA,SAAwBM,G,MAIhBC,EAAgBtF,KAAKP,kBAErB8F,EAAcC,KAAKC,KACtB,EACDD,KAAKE,IAAI1F,KAAKyC,OAAOnD,QAAQ5J,OAAS,EAAG2P,EAAQL,KAGnDhF,KAAKP,kBAAoB8F,EACzBvF,KAAK0C,mBACF4C,GAAiB,GAAKC,EAAc,MAAQ,QAI/C,IAFA,IAAII,EAAoB,KAEfjB,EAAI,EAAGA,EAAI1E,KAAKyC,OAAOnD,QAAQ5J,OAAQgP,IAAK,CACnD,IAAM5B,EAA4B,QAAlB,EAAA9C,KAAKgD,SAASC,YAAI,eAAE2C,SAASlB,GAC7C,GAAK5B,EAAL,CAIA,IAAM+C,EAAuB,WAEzBnB,GAAKa,GACPzC,EAAQqB,UAAUP,IAAIiC,GACtBF,EAAoB7C,GAEpBA,EAAQqB,UAAUD,OAAO2B,IAsB7B,OAlBIR,EAAQJ,iBACVjF,KAAKL,oBAAqB,EACtBgG,IAEA,IAAAG,6BACEH,EACA3F,KAAKgD,SAASC,OAGf0C,EAAkCI,eAAe,CAChDC,SAAU,SACVC,MAAOjG,KAAK0C,kBACZwD,OAAQ,aAMTX,GAGT,YAAA9B,mBAAA,SAAmB0C,GAMjB,OAAQA,EAAMC,SACZ,KALW,GAMT,GAA8B,MAA1BpG,KAAKP,kBACPO,KAAK+E,wBAAwB,CAAEC,GAAI,EAAGC,gBAAgB,QACjD,CACL,IAAMoB,EAASb,KAAKE,IAClB1F,KAAKP,kBAAoB,EACzBO,KAAKyC,OAAOnD,QAAQ5J,OAAS,GAE/BsK,KAAK+E,wBAAwB,CAAEC,GAAIqB,EAAQpB,gBAAgB,IAE7D,MAGF,KAnBS,GAoBuB,MAA1BjF,KAAKP,oBACD4G,EAASb,KAAKC,IAAI,EAAGzF,KAAKP,kBAAoB,GACpDO,KAAK+E,wBAAwB,CAAEC,GAAIqB,EAAQpB,gBAAgB,KAE7D,MAGF,KAzBa,GA0BXjF,KAAKmF,eACL,MAEF,KA5BU,GA6BR,GAAInF,KAAKoE,gBAAgB1E,eACvBM,KAAKD,OAAOyC,IACNsB,EAAI9D,KAAKyC,OAAOtD,OAAOrC,kBACzBgH,SACC,GAAI9D,KAAKgD,SAAS5J,MAAM6G,MAAMvK,OAAS,EAAG,CAG/C,IAAMoO,EAFN9D,KAAKgD,SAAS5J,MAAM6G,MAAQ,GAC5BD,KAAKD,OAAOyC,IACNsB,EAAI9D,KAAKyC,OAAOtD,OAAOpC,iBACzB+G,IAGN,MAEF,QACE,SAIN,YAAAN,iBAAA,SAAiB2C,GACfnG,KAAKyC,OAAOJ,cAAe8D,EAAME,OAA4BpG,QAG/D,YAAAG,SAAA,WACE,OAAQJ,KAAKgD,SAAS5J,MAA2B6G,OAErD,EApVA,GAAa,EAAAe,UAAAA,G,sWC1Cb,aACA,SACA,SACA,SACA,SAEMsF,EAAmC,GAkEhC,EAAAC,SAhEQ,SACftO,EACAuB,EACAgN,GAEA,OAAO,IAAI/F,SAAQ,SAACgG,EAAKC,GACvB,IAAMC,GAAa,IAAAC,2BAA0BJ,GAC7C,GAAIG,aAAsB,UACxBD,EAAIC,OADN,CAKIL,EAASrO,KAAU0O,EAAWnK,gBAChCkK,EACE,IAAI,UACF,6CAAuCzO,EAAI,yIAKjD,IAAMwK,EAAS,IAAI,EAAAF,OAAOtK,EAAMuB,EAAKmN,GACrCL,EAASrO,GAAQwK,GAEjB,IAAAoE,kBAAiBrN,EAAK,CACpB+G,SAAU,SAAAV,GACR4C,EAAO7C,oBAAoBC,IAG7B9D,KAAM,SAAA+K,IACJ,IAAAC,qBACE,WACEtE,EAAOjC,cAAc,IAAIxK,WAAW8Q,IAAWE,KAAKP,GAAKQ,MAAMP,MAEjE,WACEjE,EAAO3C,MAAQ,YAKrBnF,MAAO,WACL8H,EAAOL,mBACPsE,YAuBW,EAAA3F,YAjBC,SAAC9I,GACnB,IAAKqO,EAASrO,GACZ,MAAM,IAAImC,MAAM,gBAASnC,EAAI,8BAG/BqO,EAASrO,GAAM8I,eAYe,EAAAmG,cATV,SAACjP,G,MACrB,MAAiC,WAAZ,QAAd,EAAAqO,EAASrO,UAAK,eAAE6H,QAQsB,EAAAqH,MALjC,WAA+B,OAC3Cb,SAAU,EAAF,GAAOA,GACfc,cAAed,EAAS5Q,U,uFC/D1B,4BACE8D,EACA6N,GAEA,IAAM/F,EAAI,IAAIgG,eAEdhG,EAAEoC,iBAAiB,QAAQ,SAAAnI,GACnB,MAAuBA,EAAE8K,OAAvBkB,EAAM,SAAET,EAAQ,WAGT,IAAXS,EAKAA,EAAS,KAAOA,EAAS,IAC3BF,EAAU1M,QAIZ0M,EAAUtL,KAAK+K,GATbO,EAAU9G,SAAShF,EAAEiM,OAASjM,EAAEkM,UAYpCnG,EAAEoC,iBAAiB,SAAS,WAC1B2D,EAAU1M,WAGZ2G,EAAEoC,iBAAiB,YAAY,SAAAnI,GAC7B8L,EAAU9G,SAAShF,EAAEiM,OAASjM,EAAEkM,UAGlCnG,EAAEoG,aAAe,cACjBpG,EAAEqG,KAAK,MAAOnO,GACd8H,EAAEsG,S,uXCtCJ,aAMA,SACA,SACA,SACA,QACA,SAEA,SAASC,EAAWC,GAClB,YADkB,IAAAA,IAAAA,EAAA,OACX,IAAAC,UAASD,GAAiBd,MAAK,eAKxC,SAASgB,EAAc/P,EAAcuB,EAAa2F,GAChD,YADgD,IAAAA,IAAAA,EAAA,IACzC,IAAIsB,SAAQ,SAACgG,EAAKC,GACvB,IAAMuB,GAAkB,IAAAC,qBAAoBjQ,EAAMuB,GAC9CyO,EACFvB,EAAIuB,IAIN,IAAA1B,UAAetO,EAAMuB,EAAK2F,GAAQ6H,KAAKP,GAAKQ,MAAMP,MAItD,SAASyB,EAAOlQ,GACd,KACE,IAAA8I,aAAY9I,GACZ,MAAOsD,GACP,MAAM,IAAI,UAAWA,EAAE+E,UA4ClB,EAAAuH,WAAAA,EAAY,EAAAG,cAAAA,EAAe,EAAAG,OAAAA,EAAgB,EAAA5B,SAxCpD,SACEtO,EACAuB,EACA2F,QAAA,IAAAA,IAAAA,EAAA,IAEA,IAAMiJ,EAAcP,IACdQ,EAAkBL,EAAc/P,EAAMuB,EAAK2F,GAMjD,OALAgJ,EAAOlQ,GAKAwI,QAAQ6H,IAAI,CAACF,EAAaC,IAAkBrB,QA4BT,EAAAuB,OAzB5C,SAAgBtQ,EAAce,GAC5B,IAAKf,IAASe,EACZ,MAAM,IAAI,UACR,6FAIJ,KAAK,IAAAkO,eAAcjP,GACjB,MAAM,IAAI,UACR,gHAIJ,OAAO,IAAAqK,eAAcrK,EAAMe,IAYiC,EAAAmO,MAT9D,WACE,OAAO,EAAP,QACK,eACA,cAAa,CAChBqB,eAAgB,QAChBC,iBAAkB,EAAAxP,uB,gFCvEtB,qBACEuF,EACA2D,GAEA,SAASuG,EAAOC,EAAaC,EAAe3I,GAC1C,OAAO0I,EAAIE,OAAO,EAAGD,GAAS3I,EAAQ0I,EAAIE,OAAOD,GAKnD,IAFA,IAAIE,EAAyB,EAET,MAAA3G,EAAA,eAAkB,CAAjC,IAAM4G,EAAK,KACRC,EAAqB,iCACrBC,EAAe,UAErBzK,EAAOkK,EACLlK,EACAuK,EAAMG,UAAYJ,EAClBE,GAEFF,GAA0BE,EAAmBtT,OAE7C8I,EAAOkK,EAAOlK,EAAMuK,EAAMI,IAAML,EAAwBG,GACxDH,GAA0BG,EAAavT,OAGzC,OAAO8I,I,uFC7BT,aAQA,4BACEwD,EACAqD,GAEA,IAAM+D,EAAWrL,SAASC,cAAc,YA4BxC,OA3BAoL,EAASzF,UAAY,mCACE0B,EAAQT,SAAW,YAAc,GAAE,0BAC/C5C,EAAOF,MAAMtI,IAAG,wDAElB,IAAA6P,WACHrH,EAAOF,MAAMwH,MACbtH,EAAOC,wBAA0B,IAClC,uBACCoD,EAAQhJ,WAAa,mBAAY2F,EAAOuH,MAAK,eAAgB,GAAE,6BAGjEvH,EAAOP,SAAS/L,OAAS,EACrB,0CACA,GAAE,iBAENsM,EAAOP,SACNJ,KACC,SAAA9F,GAAK,4DACA,IAAA8N,WAAU9N,EAAEiD,KAAMjD,EAAE4G,kBAAoB,IAAG,sCAE9CkD,EAAQhJ,WAAa,gBAASd,EAAEgO,MAAK,WAAY,GAAE,uBAGtDpL,KAAK,IAAG,qBACL6D,EAAOP,SAAS/L,OAAS,EAAI,SAAW,GAAE,mBAG3C0T,EAASI,QAAQC,oB,sFCxC1B,aACA,SAkCA,yBAA8BxR,EAAce,GAC1C,IAAI0Q,EAAe,KACfxR,EAAO,KAEX,IACEwR,GAAe,IAAA3Q,aAAYd,EAAMe,GAGjCd,EAAOqF,KAAKsD,MAAM6I,GAClB,MAAOnO,GAGP,MAAM,IAAI,UACR,sGACEmO,GAIN,IAAKxR,EACH,MAAM,IAAI,UAAW,4BAGvB,GAAIA,EAAKyC,MACP,MAAM,IAAI,UAAW,8QAKnBzC,EAAKyC,QAGT,OAAOzC,I,kiBClET,kBACE,WAAYoI,GAAZ,MACE,YAAMA,IAAQ,K,OACd,EAAKrI,KAAO,a,EAEhB,OALyB,OAKzB,EALA,CAAyBmC,OAOzB,UAAeuP,G,0GCPf,yBAA8BC,GAC5B,IAAMR,EAAWrL,SAASC,cAAc,YAGxC,OAFA4L,EAAOA,EAAKC,OACZT,EAASzF,UAAYiG,EACdR,EAASI,QAAQlL,YAG1B,sBAA8BwL,EAAgBC,GAC5C,IAAMC,EAAO,IAAIC,IAAIH,GACfI,EAAO,IAAID,IAAIF,GACfI,EAAO,IAAIF,IAAI9U,MAAMiV,KAAKJ,GAAMrI,QAAO,SAAA0I,GAAK,OAACH,EAAKI,IAAID,OAC5D,OAAOlV,MAAMiV,KAAKD,IAGP,EAAAxN,OAAS,SACpB4N,EACA7N,EACAC,GACW,OAAU,GAAT4N,EAAa7N,EAAWC,I,2FClBtC,aAEa,EAAAuL,oBAAsB,SACjCjQ,EACAuB,GAEA,MAAoB,iBAATvB,EACF,IAAI,UAAW,6CAGL,iBAARuB,EACF,IAAI,UAAW,yBAGjB,O,8GCdT,aACA,SAGMgR,EACF,mDAFY,QAEuC,eAGnDC,EAA+B,KAC/BC,EAAiD,KAEjDC,EAAwB,GACxBC,EAA6B,GA0EJ,EAAA7C,SAxEZ,SACf8C,GAGA,QAHA,IAAAA,IAAAA,EAAA,MAGIH,EACF,OAAOA,EAGT,IAAMlR,EAAMqR,GAAeL,EAC3BC,EAAgBjR,EAEhB,IAAMsR,GAAI,aAAKtR,GACZwN,MAAK,WAEJ,OADA+D,IACOvR,KAERyN,OAAM,WAEL,MADA+D,IACM,IAAI,UAAW,sCAA+BxR,OAIxD,OADAkR,EAAkBI,EACXA,GAkDA,EAAA/D,mBApCkB,SACzBkE,EACAC,GAEA,OAAKR,GAOHA,EAAgB1D,MAAK,WAAM,OAAAiE,OAAMhE,OAAM,WAAM,OAAAiE,OACtCR,IAPPC,EAAMnV,KAAKyV,GACXL,EAAWpV,KAAK0V,GACT,OASX,IAAMH,EAAQ,WACZJ,EAAMQ,SAAQ,SAAAF,GACZA,OAEFN,EAAQ,IAGJK,EAAa,WACjBJ,EAAWO,SAAQ,SAAAF,GACjBA,OAEFL,EAAa,IASwB,EAAAzD,MANzB,WAA+B,OAC3CsD,cAAa,EACbC,gBAAe,EACfU,YAAaT,EAAMjV,WClFjB2V,EAA2B,GAG/B,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBlW,IAAjBmW,EACH,OAAOA,EAAaxP,QAGrB,IAAIb,EAASkQ,EAAyBE,GAAY,CAGjDvP,QAAS,IAOV,OAHAyP,EAAoBF,GAAUG,KAAKvQ,EAAOa,QAASb,EAAQA,EAAOa,QAASsP,GAGpEnQ,EAAOa,QCpBfsP,EAAoBzI,EAAI,CAAC7G,EAAS2P,KACjC,IAAI,IAAIhO,KAAOgO,EACXL,EAAoBM,EAAED,EAAYhO,KAAS2N,EAAoBM,EAAE5P,EAAS2B,IAC5EP,OAAOyO,eAAe7P,EAAS2B,EAAK,CAAEmO,YAAY,EAAMrQ,IAAKkQ,EAAWhO,MCJ3E2N,EAAoBS,EAAI,WACvB,GAA0B,iBAAfC,WAAyB,OAAOA,WAC3C,IACC,OAAOhM,MAAQ,IAAIiM,SAAS,cAAb,GACd,MAAO1Q,GACR,GAAsB,iBAAX7B,OAAqB,OAAOA,QALjB,GCAxB4R,EAAoBM,EAAI,CAAC1R,EAAKgS,IAAU9O,OAAO+O,UAAUC,eAAeV,KAAKxR,EAAKgS,GCClFZ,EAAoBhK,EAAKtF,IACH,oBAAXqQ,QAA0BA,OAAOC,aAC1ClP,OAAOyO,eAAe7P,EAASqQ,OAAOC,YAAa,CAAErM,MAAO,WAE7D7C,OAAOyO,eAAe7P,EAAS,aAAc,CAAEiE,OAAO,K,MCLvD,IAAIsM,EACAjB,EAAoBS,EAAES,gBAAeD,EAAYjB,EAAoBS,EAAEpS,SAAW,IACtF,IAAIoE,EAAWuN,EAAoBS,EAAEhO,SACrC,IAAKwO,GAAaxO,IACbA,EAAS0O,gBACZF,EAAYxO,EAAS0O,cAAcC,MAC/BH,GAAW,CACf,IAAII,EAAU5O,EAAS6O,qBAAqB,UACzCD,EAAQjX,SAAQ6W,EAAYI,EAAQA,EAAQjX,OAAS,GAAGgX,KAK7D,IAAKH,EAAW,MAAM,IAAInS,MAAM,yDAChCmS,EAAYA,EAAUM,QAAQ,OAAQ,IAAIA,QAAQ,QAAS,IAAIA,QAAQ,YAAa,KACpFvB,EAAoBR,EAAIyB,G,GCZxB,IAAIO,EAAsBxB,EAAoB,K","sources":["webpack://stork/./node_modules/stork-search/stork.js","webpack://stork/./js/config.ts","webpack://stork/./js/dom.ts","webpack://stork/./js/entity.ts","webpack://stork/./js/entityDom.ts","webpack://stork/./js/entityManager.ts","webpack://stork/./js/loaders/indexLoader.ts","webpack://stork/./js/main.ts","webpack://stork/./js/pencil.ts","webpack://stork/./js/resultToListItem.ts","webpack://stork/./js/searchData.ts","webpack://stork/./js/storkError.ts","webpack://stork/./js/util.ts","webpack://stork/./js/validators/indexParamValidator.ts","webpack://stork/./js/wasmManager.ts","webpack://stork/webpack/bootstrap","webpack://stork/webpack/runtime/define property getters","webpack://stork/webpack/runtime/global","webpack://stork/webpack/runtime/hasOwnProperty shorthand","webpack://stork/webpack/runtime/make namespace object","webpack://stork/webpack/runtime/publicPath","webpack://stork/webpack/startup"],"sourcesContent":["\n function getAbsoluteUrl(relativeUrl) {\n const publicPath = __webpack_public_path__;\n\n let url = '';\n\n if (!publicPath || publicPath.indexOf('://') < 0) {\n url += window.location.protocol + '//' + window.location.host;\n }\n\n if (publicPath) {\n url += publicPath;\n } else {\n url += '/';\n }\n\n return url + relativeUrl;\n }\n\nlet wasm;\n\nconst heap = new Array(32).fill(undefined);\n\nheap.push(undefined, null, true, false);\n\nfunction getObject(idx) { return heap[idx]; }\n\nlet heap_next = heap.length;\n\nfunction dropObject(idx) {\n if (idx < 36) return;\n heap[idx] = heap_next;\n heap_next = idx;\n}\n\nfunction takeObject(idx) {\n const ret = getObject(idx);\n dropObject(idx);\n return ret;\n}\n\nlet WASM_VECTOR_LEN = 0;\n\nlet cachegetUint8Memory0 = null;\nfunction getUint8Memory0() {\n if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {\n cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);\n }\n return cachegetUint8Memory0;\n}\n\nlet cachedTextEncoder = new TextEncoder('utf-8');\n\nconst encodeString = (typeof cachedTextEncoder.encodeInto === 'function'\n ? function (arg, view) {\n return cachedTextEncoder.encodeInto(arg, view);\n}\n : function (arg, view) {\n const buf = cachedTextEncoder.encode(arg);\n view.set(buf);\n return {\n read: arg.length,\n written: buf.length\n };\n});\n\nfunction passStringToWasm0(arg, malloc, realloc) {\n\n if (realloc === undefined) {\n const buf = cachedTextEncoder.encode(arg);\n const ptr = malloc(buf.length);\n getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);\n WASM_VECTOR_LEN = buf.length;\n return ptr;\n }\n\n let len = arg.length;\n let ptr = malloc(len);\n\n const mem = getUint8Memory0();\n\n let offset = 0;\n\n for (; offset < len; offset++) {\n const code = arg.charCodeAt(offset);\n if (code > 0x7F) break;\n mem[ptr + offset] = code;\n }\n\n if (offset !== len) {\n if (offset !== 0) {\n arg = arg.slice(offset);\n }\n ptr = realloc(ptr, len, len = offset + arg.length * 3);\n const view = getUint8Memory0().subarray(ptr + offset, ptr + len);\n const ret = encodeString(arg, view);\n\n offset += ret.written;\n }\n\n WASM_VECTOR_LEN = offset;\n return ptr;\n}\n\nfunction passArray8ToWasm0(arg, malloc) {\n const ptr = malloc(arg.length * 1);\n getUint8Memory0().set(arg, ptr / 1);\n WASM_VECTOR_LEN = arg.length;\n return ptr;\n}\n\nlet cachegetInt32Memory0 = null;\nfunction getInt32Memory0() {\n if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {\n cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);\n }\n return cachegetInt32Memory0;\n}\n\nlet cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });\n\ncachedTextDecoder.decode();\n\nfunction getStringFromWasm0(ptr, len) {\n return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));\n}\n/**\n* @param {string} name\n* @param {Uint8Array} data\n* @returns {string}\n*/\nexport function wasm_register_index(name, data) {\n try {\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\n var ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n var len0 = WASM_VECTOR_LEN;\n var ptr1 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);\n var len1 = WASM_VECTOR_LEN;\n wasm.wasm_register_index(retptr, ptr0, len0, ptr1, len1);\n var r0 = getInt32Memory0()[retptr / 4 + 0];\n var r1 = getInt32Memory0()[retptr / 4 + 1];\n return getStringFromWasm0(r0, r1);\n } finally {\n wasm.__wbindgen_add_to_stack_pointer(16);\n wasm.__wbindgen_free(r0, r1);\n }\n}\n\n/**\n* @param {string} name\n* @param {string} query\n* @returns {string}\n*/\nexport function wasm_search(name, query) {\n try {\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\n var ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n var len0 = WASM_VECTOR_LEN;\n var ptr1 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n var len1 = WASM_VECTOR_LEN;\n wasm.wasm_search(retptr, ptr0, len0, ptr1, len1);\n var r0 = getInt32Memory0()[retptr / 4 + 0];\n var r1 = getInt32Memory0()[retptr / 4 + 1];\n return getStringFromWasm0(r0, r1);\n } finally {\n wasm.__wbindgen_add_to_stack_pointer(16);\n wasm.__wbindgen_free(r0, r1);\n }\n}\n\n/**\n* @returns {string}\n*/\nexport function wasm_stork_version() {\n try {\n const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);\n wasm.wasm_stork_version(retptr);\n var r0 = getInt32Memory0()[retptr / 4 + 0];\n var r1 = getInt32Memory0()[retptr / 4 + 1];\n return getStringFromWasm0(r0, r1);\n } finally {\n wasm.__wbindgen_add_to_stack_pointer(16);\n wasm.__wbindgen_free(r0, r1);\n }\n}\n\nfunction addHeapObject(obj) {\n if (heap_next === heap.length) heap.push(heap.length + 1);\n const idx = heap_next;\n heap_next = heap[idx];\n\n heap[idx] = obj;\n return idx;\n}\n\nasync function load(module, imports) {\n if (typeof Response === 'function' && module instanceof Response) {\n if (typeof WebAssembly.instantiateStreaming === 'function') {\n try {\n return await WebAssembly.instantiateStreaming(module, imports);\n\n } catch (e) {\n if (module.headers.get('Content-Type') != 'application/wasm') {\n console.warn(\"`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n\", e);\n\n } else {\n throw e;\n }\n }\n }\n\n const bytes = await module.arrayBuffer();\n return await WebAssembly.instantiate(bytes, imports);\n\n } else {\n const instance = await WebAssembly.instantiate(module, imports);\n\n if (instance instanceof WebAssembly.Instance) {\n return { instance, module };\n\n } else {\n return instance;\n }\n }\n}\n\nasync function init(input) {\n if (typeof input === 'undefined') {\n input = new URL('stork_bg.wasm', ({ url: getAbsoluteUrl('node_modules/stork-search/stork.js') }).url);\n }\n const imports = {};\n imports.wbg = {};\n imports.wbg.__wbg_new_693216e109162396 = function() {\n var ret = new Error();\n return addHeapObject(ret);\n };\n imports.wbg.__wbg_stack_0ddaca5d1abfb52f = function(arg0, arg1) {\n var ret = getObject(arg1).stack;\n var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);\n var len0 = WASM_VECTOR_LEN;\n getInt32Memory0()[arg0 / 4 + 1] = len0;\n getInt32Memory0()[arg0 / 4 + 0] = ptr0;\n };\n imports.wbg.__wbg_error_09919627ac0992f5 = function(arg0, arg1) {\n try {\n console.error(getStringFromWasm0(arg0, arg1));\n } finally {\n wasm.__wbindgen_free(arg0, arg1);\n }\n };\n imports.wbg.__wbindgen_object_drop_ref = function(arg0) {\n takeObject(arg0);\n };\n\n if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {\n input = fetch(input);\n }\n\n\n\n const { instance, module } = await load(await input, imports);\n\n wasm = instance.exports;\n init.__wbindgen_wasm_module = module;\n\n return wasm;\n}\n\nexport default init;\n\n","import StorkError from \"./storkError\";\nimport { difference, plural } from \"./util\";\n\nexport interface Configuration {\n showProgress: boolean;\n printIndexInfo: boolean;\n showScores: boolean;\n showCloseButton: boolean;\n minimumQueryLength: number;\n forceOverwrite: boolean;\n resultNoun: { singular: string; plural: string };\n onQueryUpdate?: (query: string, results: unknown) => unknown;\n onResultSelected?: (query: string, result: unknown) => unknown;\n onResultsHidden?: () => unknown;\n onInputCleared?: () => unknown;\n transformResultUrl: (url: string) => string;\n}\n\nexport const defaultConfig: Readonly<Configuration> = {\n showProgress: true,\n printIndexInfo: false,\n showScores: false,\n showCloseButton: true,\n minimumQueryLength: 3,\n forceOverwrite: false,\n resultNoun: { singular: \"file\", plural: \"files\" },\n onQueryUpdate: undefined,\n onResultSelected: undefined,\n onResultsHidden: undefined,\n onInputCleared: undefined,\n transformResultUrl: url => url,\n};\n\nexport function calculateOverriddenConfig(\n overrides: Partial<Configuration>\n): Configuration | StorkError {\n const configKeyDiff = difference(\n Object.keys(overrides),\n Object.keys(defaultConfig)\n );\n\n if (configKeyDiff.length > 0) {\n const keys = plural(configKeyDiff.length, \"key\", \"keys\");\n const invalidKeys = JSON.stringify(configKeyDiff);\n return new StorkError(`Invalid ${keys} in config object: ${invalidKeys}`);\n }\n\n const output: Configuration = Object.assign({}, defaultConfig);\n\n for (const key of Object.keys(defaultConfig) as Array<keyof Configuration>) {\n const overrideVal = overrides[key];\n if (overrideVal !== undefined) {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n output[key] = overrideVal;\n }\n }\n\n return output;\n}\n","export function create(\n name: string,\n attributes: Record<string, Array<string>>\n): HTMLElement {\n const elem = document.createElement(name);\n if (attributes.classNames) {\n elem.setAttribute(\"class\", attributes.classNames.join(\" \"));\n }\n return elem;\n}\n\nexport function add(\n elem: HTMLElement,\n location: InsertPosition,\n reference: HTMLElement\n): void {\n reference.insertAdjacentElement(location, elem);\n}\n\nexport function clear(elem: HTMLElement | null): void {\n while (elem && elem.firstChild) {\n elem.removeChild(elem.firstChild);\n }\n}\n\nexport function setText(elem: HTMLElement | null, text: string): void {\n const textNode = document.createTextNode(text);\n if (elem && elem.firstChild) {\n elem.replaceChild(textNode, elem.firstChild);\n } else if (elem) {\n elem.appendChild(textNode);\n }\n}\n\nexport function existsBeyondContainerBounds(\n elem: HTMLElement,\n container: HTMLElement\n): boolean {\n const elemBoundingBox = elem.getBoundingClientRect();\n const containerBoundingBox = container.getBoundingClientRect();\n\n return (\n elemBoundingBox.bottom > containerBoundingBox.bottom ||\n elemBoundingBox.top < containerBoundingBox.top\n );\n}\n","import { Configuration } from \"./config\";\nimport { Result, SearchData, resolveSearch } from \"./searchData\";\nimport { EntityDom, RenderState } from \"./entityDom\";\nimport { wasm_register_index } from \"stork-search\";\nimport StorkError from \"./storkError\";\n\nexport type EntityState = \"initialized\" | \"loading\" | \"ready\" | \"error\";\n\nexport class Entity {\n readonly name: string;\n readonly url: string;\n readonly config: Configuration;\n\n private _state: EntityState = \"initialized\";\n\n downloadProgress = 0;\n\n index: Uint8Array;\n results: Array<Result> = [];\n totalResultCount = 0;\n\n domManager: EntityDom | null;\n eventListenerFunctions: Record<string, (e: Event) => void> = {};\n highlightedResult = 0;\n resultsVisible = false;\n hoverSelectEnabled = true;\n\n constructor(name: string, url: string, config: Configuration) {\n this.name = name;\n this.url = url;\n this.config = config;\n }\n\n public get state(): EntityState {\n return this._state;\n }\n\n public set state(value: EntityState) {\n this._state = value;\n this.render();\n }\n\n private getCurrentMessage(): string | null {\n if (!this.domManager) return null;\n const query = this.domManager.getQuery();\n if (this.state === \"error\") {\n return \"Error! Check the browser console.\";\n } else if (this.state != \"ready\") {\n return \"Loading...\";\n } else if (query?.length < this.config.minimumQueryLength) {\n return \"Filtering...\";\n } else if (this.results) {\n if (this.totalResultCount === 0) {\n return `No ${this.config.resultNoun.plural} found.`;\n } else if (this.totalResultCount === 1) {\n return `1 ${this.config.resultNoun.singular} found.`;\n } else {\n return `${this.totalResultCount} ${this.config.resultNoun.plural} found.`;\n }\n }\n\n return null;\n }\n\n private generateRenderConfig(): RenderState {\n return {\n results: this.results,\n resultsVisible: true,\n showScores: this.config.showScores,\n message: this.getCurrentMessage(),\n showProgress: this.config.showProgress,\n progress: this.downloadProgress,\n state: this.state\n };\n }\n\n private render() {\n if (!this.domManager) return;\n this.domManager.render(this.generateRenderConfig());\n }\n\n registerIndex(data: Uint8Array): Promise<void> {\n return new Promise((resolve, reject) => {\n const indexInfo = JSON.parse(wasm_register_index(this.name, data));\n if (indexInfo.error) {\n reject(new StorkError(indexInfo.error));\n } else {\n if (this.config.printIndexInfo) {\n console.log(indexInfo);\n }\n\n this.state = \"ready\";\n resolve(indexInfo);\n }\n });\n }\n\n attachToDom(): void {\n this.domManager = new EntityDom(this.name, this);\n this.render();\n }\n\n injestSearchData(data: SearchData): void {\n this.results = data.results;\n this.totalResultCount = data.total_hit_count;\n this.highlightedResult = 0;\n\n // Mutate the result URL, like we do when there's a url prefix or suffix\n const urlPrefix = data.url_prefix || \"\";\n this.results.map(r => {\n let urlSuffix = \"\";\n\n const firstInternalAnnotations = r.excerpts\n .map(e => e.internal_annotations)\n .filter(ia => !!ia)[0];\n\n if (firstInternalAnnotations && firstInternalAnnotations[0]) {\n const annotationMap = firstInternalAnnotations[0];\n if (typeof annotationMap[\"a\"] === \"string\") {\n urlSuffix += annotationMap[\"a\"];\n }\n }\n\n // oof\n if (\n r.excerpts &&\n r.excerpts[0] &&\n r.excerpts[0].internal_annotations &&\n r.excerpts[0].internal_annotations[0] &&\n r.excerpts[0].internal_annotations[0][\"a\"] &&\n typeof r.excerpts[0].internal_annotations[0][\"a\"] === \"string\"\n ) {\n urlSuffix = r.excerpts[0].internal_annotations[0][\"a\"];\n }\n r.entry.url = this.config.transformResultUrl(`${urlPrefix}${r.entry.url}${urlSuffix}`);\n });\n\n this.render();\n }\n\n private getSanitizedResults() {\n const results = this.results;\n results.map(result => {\n delete result.title_highlight_ranges;\n result.excerpts.map(excerpt => {\n delete excerpt.highlight_ranges;\n delete excerpt.internal_annotations;\n });\n });\n return results;\n }\n\n setDownloadProgress = (percentage: number): void => {\n this.state = \"loading\";\n this.downloadProgress = percentage;\n if (this.config.showProgress) {\n this.render();\n }\n };\n\n setDownloadError(): void {\n this.state = \"error\";\n }\n\n performSearch(query: string): void {\n if (this.state !== \"ready\") {\n this.render();\n return;\n }\n\n if (query.length < this.config.minimumQueryLength) {\n this.results = [];\n this.render();\n return;\n }\n\n try {\n const data = resolveSearch(this.name, query);\n if (!data) return;\n\n this.injestSearchData(data);\n\n if (this.config.onQueryUpdate) {\n this.config.onQueryUpdate(query, this.getSanitizedResults());\n }\n } catch (error) {\n console.error(error);\n }\n }\n}\n","import { Result } from \"./searchData\";\n\nimport {\n create,\n add,\n clear,\n setText,\n existsBeyondContainerBounds\n} from \"./dom\";\nimport { Entity, EntityState } from \"./entity\";\nimport { ListItemDisplayOptions, resultToListItem } from \"./resultToListItem\";\n\ninterface ElementMap {\n input: HTMLInputElement;\n output: HTMLDivElement;\n progress: HTMLElement;\n list: HTMLElement;\n message: HTMLElement;\n attribution: HTMLElement;\n closeButton: HTMLElement;\n}\n\nexport interface RenderState {\n results: Array<Result>;\n resultsVisible: boolean;\n showScores: boolean;\n message: string | null;\n showProgress: boolean;\n progress: number;\n state: EntityState;\n}\n\nconst hiddenInterfaceRenderState: RenderState = {\n results: [],\n resultsVisible: false,\n showScores: false,\n message: null,\n showProgress: false,\n progress: 1,\n state: \"ready\"\n};\n\nexport class EntityDom {\n readonly elements: ElementMap;\n readonly entity: Entity;\n\n highlightedResult?: number;\n hoverSelectEnabled: boolean;\n lastRenderState: RenderState;\n\n scrollAnchorPoint: \"start\" | \"end\" = \"end\";\n\n constructor(name: string, entity: Entity) {\n this.entity = entity;\n\n const data = [\n {\n selector: `input[data-stork=\"${name}\"]`,\n elementName: \"input\"\n },\n {\n selector: `div[data-stork=\"${name}-output\"]`,\n elementName: \"output\"\n }\n ];\n\n const [input, output] = data.map(d => {\n const element = document.querySelector(d.selector);\n if (!element) {\n throw new Error(\n `Could not register search box \"${name}\": ${d.elementName} element not found. Make sure an element matches the query selector \\`${d.selector}\\``\n );\n }\n\n return element;\n }) as [HTMLInputElement, HTMLDivElement];\n\n this.elements = {\n input: input,\n output: output,\n list: create(\"ul\", { classNames: [\"stork-results\"] }),\n attribution: create(\"div\", {\n classNames: [\"stork-attribution\"]\n }),\n progress: create(\"div\", { classNames: [\"stork-progress\"] }),\n message: create(\"div\", { classNames: [\"stork-message\"] }),\n closeButton: create(\"button\", {\n classNames: [\"stork-close-button\"]\n })\n };\n\n // First, remove saved event listener functions from the element, if they exist.\n // This makes the EntityDom constructor safe to call multiple times, even if\n // the elements on the page haven't changed.\n this.elements.input.removeEventListener(\n \"input\",\n this.entity.eventListenerFunctions.inputInputEvent\n );\n\n this.elements.input.removeEventListener(\n \"keydown\",\n this.entity.eventListenerFunctions.inputKeydownEvent\n );\n\n // Then, save new event listener functions to the entity so that we can\n // delete those listeners from the corresponding elements when the\n // EntityDom object is recreated.\n this.entity.eventListenerFunctions = {\n inputInputEvent: (e: InputEvent) => {\n this.handleInputEvent(e as InputEvent);\n },\n\n inputKeydownEvent: (e: KeyboardEvent) => {\n this.handleKeyDownEvent(e as KeyboardEvent);\n }\n };\n\n // Then, add those newly saved functions as event listeners on the elements.\n this.elements.input.addEventListener(\n \"input\",\n this.entity.eventListenerFunctions.inputInputEvent\n );\n\n this.elements.input.addEventListener(\n \"keydown\",\n this.entity.eventListenerFunctions.inputKeydownEvent\n );\n\n // We didn't have to do the remove/add dance with this one because\n // this listener behavior is already idempotent.\n this.elements.list?.addEventListener(\"mousemove\", () => {\n this.hoverSelectEnabled = true;\n });\n\n this.elements.attribution.innerHTML =\n 'Powered by <a href=\"https://stork-search.net\">Stork</a>';\n\n this.elements.closeButton.innerHTML = `\n<svg height=\"0.8em\" viewBox=\"0 0 23 24\" xmlns=\"http://www.w3.org/2000/svg\">\n<g fill=\"none\" fill-rule=\"evenodd\" stroke-linecap=\"round\">\n<g transform=\"translate(-700 -149)\" stroke=\"currentcolor\" stroke-width=\"4\">\n<line id=\"a\" x1=\"702.5\" x2=\"720\" y1=\"152.5\" y2=\"170\"/>\n<line transform=\"translate(711 161) rotate(-90) translate(-711 -161)\" x1=\"702.5\" x2=\"720\" y1=\"152.5\" y2=\"170\"/>\n</g>\n</g>\n</svg>`;\n\n if (this.entity.config.showProgress) {\n add(this.elements.progress, \"afterend\", this.elements.input);\n }\n\n this.elements.closeButton?.addEventListener(\"click\", () => {\n this.elements.input.value = \"\";\n this.elements.input.focus();\n this.render(hiddenInterfaceRenderState);\n const [m, n] = [\n this.entity.config.onInputCleared,\n this.entity.config.onResultsHidden\n ];\n m ? m() : null;\n n ? n() : null;\n });\n }\n\n private clearDom() {\n clear(this.elements.output);\n clear(this.elements.list);\n this.elements.closeButton?.remove();\n this.elements.output.classList.remove(\"stork-output-visible\");\n }\n\n render(state: RenderState): void {\n const query = (this.elements.input as HTMLInputElement).value;\n this.clearDom();\n this.lastRenderState = state;\n\n if (state.showProgress) {\n const getFakeProgress = (): number => {\n switch (state.state) {\n case \"ready\":\n case \"error\":\n return 1;\n case \"initialized\":\n case \"loading\":\n return state.progress * 0.9 + 0.05;\n }\n };\n\n const progress = getFakeProgress();\n\n if (progress < 1) {\n this.elements.progress.style.width = `${progress * 100}%`;\n this.elements.progress.style.opacity = \"1\";\n } else {\n this.elements.progress.style.width = `100%`;\n this.elements.progress.style.opacity = \"0\";\n }\n }\n\n if (state.state === \"error\") {\n this.elements.input.classList.add(\"stork-error\");\n }\n\n if (this.getQuery().length > 0 && state.resultsVisible) {\n this.elements.output.classList.add(\"stork-output-visible\");\n add(this.elements.message, \"beforeend\", this.elements.output);\n }\n\n if (state.message) {\n setText(this.elements.message, state.message);\n }\n\n if (state.results?.length > 0 && state.resultsVisible) {\n add(this.elements.list, \"beforeend\", this.elements.output);\n\n for (let i = 0; i < state.results.length; i++) {\n const result = state.results[i];\n const generateOptions: ListItemDisplayOptions = {\n selected: i === this.highlightedResult,\n showScores: state.showScores\n };\n\n const listItem = resultToListItem(result, generateOptions);\n add(listItem as HTMLElement, \"beforeend\", this.elements.list);\n\n listItem.addEventListener(\"mousemove\", () => {\n if (this.hoverSelectEnabled) {\n if (i !== this.highlightedResult) {\n this.changeHighlightedResult({ to: i, shouldScrollTo: false });\n }\n }\n });\n\n listItem.addEventListener(\"mouseleave\", () => {\n if (this.hoverSelectEnabled) {\n if (i === this.highlightedResult) {\n this.changeHighlightedResult({ to: -1, shouldScrollTo: false });\n }\n }\n });\n\n listItem.addEventListener(\"click\", e => {\n e.preventDefault();\n this.selectResult();\n });\n }\n\n add(this.elements.attribution, \"beforeend\", this.elements.output);\n }\n\n if ((query?.length || 0) > 0 && this.entity.config.showCloseButton) {\n add(this.elements.closeButton, \"afterend\", this.elements.input);\n }\n }\n\n private selectResult() {\n if (this.highlightedResult != null) {\n const result = this.entity.results[this.highlightedResult];\n if (this.entity.config.onResultSelected) {\n Promise.resolve(\n this.entity.config.onResultSelected(this.getQuery(), result)\n ).finally(() => {\n window.location.assign(result.entry.url);\n });\n } else {\n window.location.assign(result.entry.url);\n }\n }\n }\n\n changeHighlightedResult(options: {\n to: number;\n shouldScrollTo: boolean;\n }): number {\n const previousValue = this.highlightedResult;\n\n const resolvedIdx = Math.max(\n -1, // `to` will be -1 if we want to clear the highlight\n Math.min(this.entity.results.length - 1, options.to)\n );\n\n this.highlightedResult = resolvedIdx;\n this.scrollAnchorPoint =\n (previousValue || 0) < resolvedIdx ? \"end\" : \"start\";\n\n let targetForScrollTo = null;\n\n for (let i = 0; i < this.entity.results.length; i++) {\n const element = this.elements.list?.children[i];\n if (!element) {\n continue;\n }\n\n const highlightedClassName = \"selected\";\n\n if (i == resolvedIdx) {\n element.classList.add(highlightedClassName);\n targetForScrollTo = element;\n } else {\n element.classList.remove(highlightedClassName);\n }\n }\n\n if (options.shouldScrollTo) {\n this.hoverSelectEnabled = false;\n if (targetForScrollTo) {\n if (\n existsBeyondContainerBounds(\n targetForScrollTo as HTMLElement,\n this.elements.list\n )\n ) {\n (targetForScrollTo as HTMLElement).scrollIntoView({\n behavior: \"smooth\",\n block: this.scrollAnchorPoint,\n inline: \"nearest\"\n });\n }\n }\n }\n\n return resolvedIdx;\n }\n\n handleKeyDownEvent(event: KeyboardEvent): void {\n const UP = 38;\n const DOWN = 40;\n const RETURN = 13;\n const ESC = 27;\n\n switch (event.keyCode) {\n case DOWN: {\n if (this.highlightedResult == null) {\n this.changeHighlightedResult({ to: 0, shouldScrollTo: true });\n } else {\n const target = Math.min(\n this.highlightedResult + 1,\n this.entity.results.length - 1\n );\n this.changeHighlightedResult({ to: target, shouldScrollTo: true });\n }\n break;\n }\n\n case UP: {\n if (this.highlightedResult != null) {\n const target = Math.max(0, this.highlightedResult - 1);\n this.changeHighlightedResult({ to: target, shouldScrollTo: true });\n }\n break;\n }\n\n case RETURN:\n this.selectResult();\n break;\n\n case ESC:\n if (this.lastRenderState.resultsVisible) {\n this.render(hiddenInterfaceRenderState);\n const m = this.entity.config.onResultsHidden;\n m ? m() : null;\n } else if (this.elements.input.value.length > 0) {\n this.elements.input.value = \"\";\n this.render(hiddenInterfaceRenderState); // To clear [x] button\n const m = this.entity.config.onInputCleared;\n m ? m() : null;\n }\n\n break;\n\n default:\n return;\n }\n }\n\n handleInputEvent(event: InputEvent): void {\n this.entity.performSearch((event.target as HTMLInputElement).value);\n }\n\n getQuery(): string {\n return (this.elements.input as HTMLInputElement).value;\n }\n}\n","import { Entity } from \"./entity\";\nimport { Configuration, calculateOverriddenConfig } from \"./config\";\nimport { loadIndexFromUrl } from \"./loaders/indexLoader\";\nimport { runAfterWasmLoaded } from \"./wasmManager\";\nimport StorkError from \"./storkError\";\n\nconst entities: Record<string, Entity> = {};\n\nconst register = (\n name: string,\n url: string,\n partialConfig: Partial<Configuration>\n): Promise<void> => {\n return new Promise((res, rej) => {\n const fullConfig = calculateOverriddenConfig(partialConfig);\n if (fullConfig instanceof StorkError) {\n rej(fullConfig);\n return;\n }\n\n if (entities[name] && !fullConfig.forceOverwrite) {\n rej(\n new StorkError(\n `You're registering an index named \\`${name}\\`, but that already exists. If this is expected, set forceOverwrite to true in your Javascript config to allow overwriting indexes.`\n )\n );\n }\n\n const entity = new Entity(name, url, fullConfig);\n entities[name] = entity;\n\n loadIndexFromUrl(url, {\n progress: percentage => {\n entity.setDownloadProgress(percentage);\n },\n\n load: response => {\n runAfterWasmLoaded(\n () => {\n entity.registerIndex(new Uint8Array(response)).then(res).catch(rej);\n },\n () => {\n entity.state = \"error\";\n }\n );\n },\n\n error: () => {\n entity.setDownloadError();\n rej();\n }\n });\n });\n};\n\nconst attachToDom = (name: string): void => {\n if (!entities[name]) {\n throw new Error(`Index ${name} has not been registered!`);\n }\n\n entities[name].attachToDom();\n};\n\nconst entityIsReady = (name: string): boolean => {\n return entities[name]?.state === \"ready\";\n};\n\nconst debug = (): Record<string, unknown> => ({\n entities: { ...entities },\n entitiesCount: entities.length\n});\n\nexport { register, attachToDom, entityIsReady, debug };\n","interface IndexLoaderCallbacks {\n load: (response: ArrayBufferLike) => void;\n progress: (percentage: number) => void;\n error: () => void;\n}\n\nexport function loadIndexFromUrl(\n url: string,\n callbacks: IndexLoaderCallbacks\n): void {\n const r = new XMLHttpRequest();\n\n r.addEventListener(\"load\", e => {\n const { status, response } = e.target as XMLHttpRequest;\n\n // This shouldn't happen on the `load` event, but handle it safely if it does\n if (status === 0) {\n callbacks.progress(e.loaded / e.total);\n return;\n }\n\n if (status < 200 || status > 299) {\n callbacks.error();\n return;\n }\n\n callbacks.load(response);\n });\n\n r.addEventListener(\"error\", () => {\n callbacks.error();\n });\n\n r.addEventListener(\"progress\", e => {\n callbacks.progress(e.loaded / e.total);\n });\n\n r.responseType = \"arraybuffer\";\n r.open(\"GET\", url);\n r.send();\n}\n","import { Configuration } from \"./config\";\nimport {\n register as registerEntity,\n attachToDom,\n entityIsReady,\n debug as entityDebug\n} from \"./entityManager\";\nimport { loadWasm, debug as wasmDebug } from \"./wasmManager\";\nimport { resolveSearch, SearchData } from \"./searchData\";\nimport StorkError from \"./storkError\";\nimport { validateIndexParams } from \"./validators/indexParamValidator\";\nimport { wasm_stork_version } from \"stork-search\";\n\nfunction initialize(wasmOverrideUrl: string | null = null): Promise<void> {\n return loadWasm(wasmOverrideUrl).then(() => {\n return;\n });\n}\n\nfunction downloadIndex(name: string, url: string, config = {}): Promise<void> {\n return new Promise((res, rej) => {\n const validationError = validateIndexParams(name, url);\n if (validationError) {\n rej(validationError);\n return;\n }\n\n registerEntity(name, url, config).then(res).catch(rej);\n });\n}\n\nfunction attach(name: string): void {\n try {\n attachToDom(name);\n } catch (e) {\n throw new StorkError(e.message);\n }\n}\n\nfunction register(\n name: string,\n url: string,\n config: Partial<Configuration> = {}\n): Promise<void> {\n const initPromise = initialize();\n const downloadPromise = downloadIndex(name, url, config);\n attach(name);\n\n // This silly `then` call turns a [(void), (void)] into a (void), which is\n // only necessary to make Typescript happy.\n // You begin to wonder if you write Typescript code, or if Typescript code writes you.\n return Promise.all([initPromise, downloadPromise]).then();\n}\n\nfunction search(name: string, query: string): SearchData {\n if (!name || !query) {\n throw new StorkError(\n \"Make sure to call stork.search() with two arguments: the index name and the search query.\"\n );\n }\n\n if (!entityIsReady(name)) {\n throw new StorkError(\n \"Couldn't find index. Make sure the stork.downloadIndex() promise has resolved before calling stork.search().\"\n );\n }\n\n return resolveSearch(name, query);\n}\n\nfunction debug(): Record<string, unknown> {\n return {\n ...wasmDebug(),\n ...entityDebug(),\n jsStorkVersion: process.env.VERSION,\n wasmStorkVersion: wasm_stork_version\n };\n}\n\nexport { initialize, downloadIndex, attach, search, register, debug };\n","// It's like Handlebars, but smaller.\n\nimport { HighlightRange } from \"./searchData\";\n\nexport function highlight(\n text: string,\n highlight_ranges: Array<HighlightRange>\n): string {\n function insert(str: string, index: number, value: string) {\n return str.substr(0, index) + value + str.substr(index);\n }\n\n let charactersAlreadyAdded = 0;\n\n for (const range of highlight_ranges) {\n const beginningInsertion = `<mark class=\"stork-highlight\">`;\n const endInsertion = `</mark>`;\n\n text = insert(\n text,\n range.beginning + charactersAlreadyAdded,\n beginningInsertion\n );\n charactersAlreadyAdded += beginningInsertion.length;\n\n text = insert(text, range.end + charactersAlreadyAdded, endInsertion);\n charactersAlreadyAdded += endInsertion.length;\n }\n\n return text;\n}\n","import { highlight } from \"./pencil\";\nimport { Result } from \"./searchData\";\n\nexport interface ListItemDisplayOptions {\n selected: boolean;\n showScores: boolean;\n}\n\nexport function resultToListItem(\n result: Result,\n options: ListItemDisplayOptions\n): ChildNode {\n const template = document.createElement(\"template\");\n template.innerHTML = `\n<li class=\"stork-result${options.selected ? \" selected\" : \"\"}\">\n <a href=\"${result.entry.url}\">\n <div class=\"stork-title\">\n <p>${highlight(\n result.entry.title,\n result.title_highlight_ranges || []\n )}</p>\n ${options.showScores ? `<code><b>${result.score}</b></code>` : \"\"}\n </div>\n ${\n result.excerpts.length > 0\n ? '<div class=\"stork-excerpt-container\" />'\n : \"\"\n }\n ${result.excerpts\n .map(\n e => `<div class=\"stork-excerpt\"><p>\n ...${highlight(e.text, e.highlight_ranges || [])}...\n </p>\n ${options.showScores ? `<code>${e.score}</code>` : \"\"}\n </div>`\n )\n .join(\"\")}\n ${result.excerpts.length > 0 ? \"</div>\" : \"\"}\n </a>\n</li>`;\n return template.content.firstElementChild as ChildNode;\n}\n","import { wasm_search } from \"stork-search\";\nimport StorkError from \"./storkError\";\n\nexport interface HighlightRange {\n beginning: number;\n end: number;\n}\n\nexport interface Entry {\n fields: Record<string, unknown>;\n title: string;\n url: string;\n}\n\nexport interface Excerpt {\n fields: Record<string, unknown>;\n internal_annotations?: Array<Record<string, unknown>>;\n highlight_ranges?: Array<HighlightRange>;\n score: number;\n text: string;\n}\n\nexport interface Result {\n entry: Entry;\n excerpts: Array<Excerpt>;\n score: number;\n title_highlight_ranges?: Array<HighlightRange>;\n}\n\nexport interface SearchData {\n results: Array<Result>;\n total_hit_count: number;\n url_prefix: string;\n}\n\nexport function resolveSearch(name: string, query: string): SearchData {\n let searchOutput = null;\n let data = null;\n\n try {\n searchOutput = wasm_search(name, query);\n // If wasm_search returns an error, it will return a JSON blob. Look for\n // data.error to see if this is the case.\n data = JSON.parse(searchOutput);\n } catch (e) {\n // Data has come back improperly, even beyond an error in Rust-land.\n // analytics.log(e)\n throw new StorkError(\n \"Could not parse data from wasm_search. If you see this, please file a bug: https://jil.im/storkbug \" +\n searchOutput\n );\n }\n\n if (!data) {\n throw new StorkError(\"Data was an empty object\");\n }\n\n if (data.error) {\n throw new StorkError(`Could not perform search: the WASM binary failed to return search results.\n You might not be serving your search index properly.\n If you think this is an error, please file a bug: https://jil.im/storkbug\n \n The WASM binary came back with:\n ${data.error}`);\n }\n\n return data;\n}\n","class StorkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"StorkError\";\n }\n}\n\nexport default StorkError;\n","export function htmlToElement(html: string): ChildNode | null {\n const template = document.createElement(\"template\");\n html = html.trim(); // Never return a text node of whitespace as the result\n template.innerHTML = html;\n return template.content.firstChild;\n}\n\nexport function difference<T>(arr1: Array<T>, arr2: Array<T>): Array<T> {\n const set1 = new Set(arr1);\n const set2 = new Set(arr2);\n const diff = new Set(Array.from(set1).filter(x => !set2.has(x)));\n return Array.from(diff);\n}\n\nexport const plural = (\n count: number,\n singular: string,\n plural: string\n): string => (count == 1 ? singular : plural);\n","import StorkError from \"../storkError\";\n\nexport const validateIndexParams = (\n name: string,\n url: string\n): StorkError | null => {\n if (typeof name !== \"string\") {\n return new StorkError(\"Index registration name must be a string.\");\n }\n\n if (typeof url !== \"string\") {\n return new StorkError(\"URL must be a string.\");\n }\n\n return null;\n};\n","import init from \"stork-search\";\nimport StorkError from \"./storkError\";\n\nconst version = process.env.VERSION;\nconst DEFAULT_WASM_URL = version\n ? `https://files.stork-search.net/releases/v${version}/stork.wasm`\n : `https://files.stork-search.net/stork.wasm`;\n\nlet wasmSourceUrl: string | null = null; // only for debug output\nlet wasmLoadPromise: Promise<string | void> | null = null;\n\nlet queue: { (): void }[] = [];\nlet errorQueue: { (): void }[] = [];\n\nconst loadWasm = (\n overrideUrl: string | null = null\n): Promise<string | void> => {\n // If there's a WASM load in flight or complete, don't try to call init again\n if (wasmLoadPromise) {\n return wasmLoadPromise;\n }\n\n const url = overrideUrl || DEFAULT_WASM_URL;\n wasmSourceUrl = url;\n\n const p = init(url)\n .then(() => {\n flush();\n return url;\n })\n .catch(() => {\n errorFlush();\n throw new StorkError(`Error while loading WASM at ${url}`);\n });\n\n wasmLoadPromise = p;\n return p;\n};\n\n/**\n * Caller should use this to queue up a function to be run only when the\n * WASM is loaded. If the WASM is already loaded when this method is called,\n * the function will run immediately.\n *\n * @param fn Function to be run once WASM is loaded\n *\n * @returns a promise if loadWasm has been called, or undefined if loadWasm\n * has not been called. If loadWasm has been called, the promise will resolve\n * when the WASM has been loaded and when the function has been run.\n */\nconst runAfterWasmLoaded = (\n fn: () => void,\n err: () => void\n): Promise<string | void> | null => {\n if (!wasmLoadPromise) {\n queue.push(fn);\n errorQueue.push(err);\n return null;\n } else {\n // We have a wasmLoadPromise, but we don't know if it's resolved.\n // Let's wait for it to resolve, then run the function.\n wasmLoadPromise.then(() => fn()).catch(() => err());\n return wasmLoadPromise;\n }\n};\n\nconst flush = () => {\n queue.forEach(fn => {\n fn();\n });\n queue = [];\n};\n\nconst errorFlush = () => {\n errorQueue.forEach(fn => {\n fn();\n });\n errorQueue = [];\n};\n\nconst debug = (): Record<string, unknown> => ({\n wasmSourceUrl,\n wasmLoadPromise,\n queueLength: queue.length\n});\n\nexport { runAfterWasmLoaded, loadWasm, debug };\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) scriptUrl = scripts[scripts.length - 1].src\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(480);\n"],"names":["wasm","heap","Array","fill","undefined","getObject","idx","push","heap_next","length","WASM_VECTOR_LEN","cachegetUint8Memory0","getUint8Memory0","buffer","memory","Uint8Array","cachedTextEncoder","TextEncoder","encodeString","encodeInto","arg","view","buf","encode","set","read","written","passStringToWasm0","malloc","realloc","ptr","subarray","len","mem","offset","code","charCodeAt","slice","cachegetInt32Memory0","getInt32Memory0","Int32Array","cachedTextDecoder","TextDecoder","ignoreBOM","fatal","getStringFromWasm0","decode","wasm_register_index","name","data","retptr","__wbindgen_add_to_stack_pointer","ptr0","__wbindgen_malloc","__wbindgen_realloc","len0","ptr1","passArray8ToWasm0","len1","r0","r1","__wbindgen_free","wasm_search","query","wasm_stork_version","async","init","input","URL","relativeUrl","publicPath","url","indexOf","window","location","protocol","host","getAbsoluteUrl","imports","wbg","__wbg_new_693216e109162396","obj","addHeapObject","Error","__wbg_stack_0ddaca5d1abfb52f","arg0","arg1","stack","__wbg_error_09919627ac0992f5","console","error","__wbindgen_object_drop_ref","ret","dropObject","takeObject","Request","fetch","instance","module","Response","WebAssembly","instantiateStreaming","e","headers","get","warn","bytes","arrayBuffer","instantiate","Instance","load","exports","__wbindgen_wasm_module","defaultConfig","showProgress","printIndexInfo","showScores","showCloseButton","minimumQueryLength","forceOverwrite","resultNoun","singular","plural","onQueryUpdate","onResultSelected","onResultsHidden","onInputCleared","transformResultUrl","overrides","configKeyDiff","difference","Object","keys","invalidKeys","JSON","stringify","output","assign","key","overrideVal","attributes","elem","document","createElement","classNames","setAttribute","join","reference","insertAdjacentElement","firstChild","removeChild","text","textNode","createTextNode","replaceChild","appendChild","container","elemBoundingBox","getBoundingClientRect","containerBoundingBox","bottom","top","config","_state","downloadProgress","results","totalResultCount","eventListenerFunctions","highlightedResult","resultsVisible","hoverSelectEnabled","setDownloadProgress","percentage","state","render","this","value","getCurrentMessage","domManager","getQuery","generateRenderConfig","message","progress","registerIndex","Promise","resolve","reject","indexInfo","parse","log","attachToDom","EntityDom","injestSearchData","total_hit_count","urlPrefix","url_prefix","map","r","urlSuffix","firstInternalAnnotations","excerpts","internal_annotations","filter","ia","annotationMap","entry","getSanitizedResults","result","title_highlight_ranges","excerpt","highlight_ranges","setDownloadError","performSearch","resolveSearch","Entity","hiddenInterfaceRenderState","entity","scrollAnchorPoint","selector","elementName","d","element","querySelector","elements","list","create","attribution","closeButton","removeEventListener","inputInputEvent","inputKeydownEvent","handleInputEvent","handleKeyDownEvent","addEventListener","innerHTML","add","focus","m","n","clearDom","clear","remove","classList","lastRenderState","getFakeProgress","style","width","opacity","setText","i","generateOptions","selected","listItem","resultToListItem","changeHighlightedResult","to","shouldScrollTo","preventDefault","selectResult","finally","options","previousValue","resolvedIdx","Math","max","min","targetForScrollTo","children","highlightedClassName","existsBeyondContainerBounds","scrollIntoView","behavior","block","inline","event","keyCode","target","entities","register","partialConfig","res","rej","fullConfig","calculateOverriddenConfig","loadIndexFromUrl","response","runAfterWasmLoaded","then","catch","entityIsReady","debug","entitiesCount","callbacks","XMLHttpRequest","status","loaded","total","responseType","open","send","initialize","wasmOverrideUrl","loadWasm","downloadIndex","validationError","validateIndexParams","attach","initPromise","downloadPromise","all","search","jsStorkVersion","wasmStorkVersion","insert","str","index","substr","charactersAlreadyAdded","range","beginningInsertion","endInsertion","beginning","end","template","highlight","title","score","content","firstElementChild","searchOutput","StorkError","html","trim","arr1","arr2","set1","Set","set2","diff","from","x","has","count","DEFAULT_WASM_URL","wasmSourceUrl","wasmLoadPromise","queue","errorQueue","overrideUrl","p","flush","errorFlush","fn","err","forEach","queueLength","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__","call","definition","o","defineProperty","enumerable","g","globalThis","Function","prop","prototype","hasOwnProperty","Symbol","toStringTag","scriptUrl","importScripts","currentScript","src","scripts","getElementsByTagName","replace","__webpack_exports__"],"sourceRoot":""}
default/index.yaml view
@@ -36,6 +36,9 @@ # Whether this node in the sidebar tree should remain collapsed by default # (unless a route in it is active) collapsed: true+ stork:+ # https://stork-search.net/docs/config-ref#frontmatter_handling+ frontmatter-handling: omit pandoc: # Rewrite the class specified in Pandoc's Div and Span nodes. You can specify the class using@@ -95,6 +98,7 @@ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/themes/prism-tomorrow.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/prism.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.28.0/plugins/autoloader/prism-autoloader.min.js"></script>+ <style>code span.token.table{display:inline;}</style> <!-- Tailwind and Prism both use the .table class. This resets the display:table property set by Tailwind. https://github.com/srid/emanote/issues/320 --> # Syntax highlighting using highlight.js highlightjs: | <!-- highlight.js -->
default/templates/components/stork/stork-search-head.tpl view
@@ -15,11 +15,13 @@ <script src="${ema:emanoteStaticLayerUrl}/stork/stork.js"></script> <ema:metadata> <with var="template">- <script data-emanote-base-url="${value:baseUrl}">+ <script id="emanote-stork" data-emanote-base-url="${value:baseUrl}"> window.emanote = {}; window.emanote.stork = { searchShown: false,+ indexIsStale: false, toggleSearch: function () {+ window.emanote.stork.refreshIndex(); document.getElementById('stork-search-container').classList.toggle('hidden'); window.emanote.stork.searchShown = document.body.classList.toggle('stork-overflow-hidden-important'); if (window.emanote.stork.searchShown) {@@ -32,14 +34,25 @@ window.emanote.stork.searchShown = false; }, - init: function () {+ getBaseUrl: function () {+ const baseUrl = document.getElementById("emanote-stork").getAttribute('data-emanote-base-url') || '/';+ return baseUrl;+ },++ registerIndex: function (options) { const indexName = 'emanote-search'; // used to match input[data-stork] attribute value- const baseUrl = document.currentScript.getAttribute('data-emanote-base-url') || '/';- const indexUrl = baseUrl + '-/stork.st';+ const indexUrl = window.emanote.stork.getBaseUrl() + '-/stork.st';+ stork.register(+ indexName,+ indexUrl,+ options);+ },++ init: function () { if (document.readyState !== 'complete') { window.addEventListener('load', function () {- stork.initialize(baseUrl + '_emanote-static/stork/stork.wasm');- stork.register(indexName, indexUrl);+ stork.initialize(window.emanote.stork.getBaseUrl() + '_emanote-static/stork/stork.wasm');+ window.emanote.stork.registerIndex(); }); document.addEventListener('keydown', event => {@@ -52,10 +65,32 @@ } }); } else {- // Override existing on Ema's hot-reload- stork.register(indexName, indexUrl, { forceOverwrite: true });+ // This section is called during Ema's hot reload.+ // + // Mark the current index as stale, and refresh it *only when* the+ // user actually invokes search.+ // + // We do not refresh the index *right away*, as that will cause+ // memory leaks in the browser. See+ // https://github.com/srid/emanote/issues/411#issuecomment-1402056235+ console.log("stork: Marking index as stale");+ window.emanote.stork.markIndexAsStale(); }+ },++ markIndexAsStale: function () {+ window.emanote.stork.indexIsStale = true;+ },++ refreshIndex: function () {+ if (window.emanote.stork.indexIsStale) {+ console.log("stork: Reloading index");+ window.emanote.stork.indexIsStale = false;+ // NOTE: This will leak memory. See the comment above.+ window.emanote.stork.registerIndex({ forceOverwrite: true });+ } }+ }; window.emanote.stork.init();
emanote.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: emanote-version: 1.0.0.0+version: 1.0.2.0 license: AGPL-3.0-only copyright: 2022 Sridhar Ratnakumar maintainer: srid@srid.ca@@ -11,7 +11,7 @@ Create beautiful websites -- such as personal webpage, blog, wiki, Zettelkasten, notebook, knowledge-base, documentation, etc. from future-proof plain-text notes and arbitrary data -- with live preview that updates in real-time. -- A URL where users can report bugs.-bug-reports: https://github.com/EmaApps/emanote/issues+bug-reports: https://github.com/srid/emanote/issues extra-source-files: CHANGELOG.md LICENSE@@ -19,13 +19,13 @@ data-dir: default data-files:- _emanote-bin/compile-css _emanote-live-server/**/*.css _emanote-static/**/*.ttf _emanote-static/*.css _emanote-static/*.svg _emanote-static/stork/flat.css _emanote-static/stork/stork.js+ _emanote-static/stork/stork.js.map _emanote-static/stork/stork.wasm favicon.svg index.yaml@@ -104,6 +104,7 @@ , containers , data-default , dependent-sum+ , deriving-aeson , directory , ema >=0.9 , filepath
src/Emanote.hs view
@@ -1,11 +1,10 @@ {-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -Wno-orphans #-} -module Emanote- ( run,- defaultEmanoteConfig,- )-where+module Emanote (+ run,+ defaultEmanoteConfig,+) where import Control.Monad.Logger (LogLevel (LevelError), runStderrLoggingT, runStdoutLoggingT) import Control.Monad.Logger.Extras (Logger (Logger), logToStderr, runLoggerLoggingT)@@ -13,13 +12,13 @@ import Data.Default (def) import Data.Dependent.Sum (DSum ((:=>))) import Data.Map.Strict qualified as Map-import Ema- ( EmaSite (..),- IsRoute (..),- fromPrism_,- runSiteWithCli,- toPrism_,- )+import Ema (+ EmaSite (..),+ IsRoute (..),+ fromPrism_,+ runSiteWithCli,+ toPrism_,+ ) import Ema.CLI qualified import Ema.Dynamic (Dynamic (Dynamic)) import Emanote.CLI qualified as CLI@@ -139,17 +138,17 @@ defaultEmanotePandocRenderers = let blockRenderers = PandocRenderers- [ PF.embedInlineWikiLinkResolvingSplice, -- embedInlineWikiLinkResolvingSplice should be first to recognize inline Link elements first- PF.urlResolvingSplice+ [ PF.embedInlineWikiLinkResolvingSplice -- embedInlineWikiLinkResolvingSplice should be first to recognize inline Link elements first+ , PF.urlResolvingSplice ]- [ PF.embedBlockWikiLinkResolvingSplice,- PF.embedBlockRegularLinkResolvingSplice,- PF.queryResolvingSplice+ [ PF.embedBlockWikiLinkResolvingSplice+ , PF.embedBlockRegularLinkResolvingSplice+ , PF.queryResolvingSplice ] inlineRenderers = PandocRenderers- [ PF.embedInlineWikiLinkResolvingSplice, -- embedInlineWikiLinkResolvingSplice should be first to recognize inline Link elements first- PF.urlResolvingSplice+ [ PF.embedInlineWikiLinkResolvingSplice -- embedInlineWikiLinkResolvingSplice should be first to recognize inline Link elements first+ , PF.urlResolvingSplice ] mempty linkInlineRenderers =
src/Emanote/CLI.hs view
@@ -1,13 +1,12 @@ {-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE RecordWildCards #-} -module Emanote.CLI- ( Cli (..),- Cmd (..),- parseCli,- cliParser,- )-where+module Emanote.CLI (+ Cli (..),+ Cmd (..),+ parseCli,+ cliParser,+) where import Data.Text qualified as T import Data.Version (showVersion)@@ -18,9 +17,9 @@ import UnliftIO.Directory (getCurrentDirectory) data Cli = Cli- { layers :: NonEmpty FilePath,- allowBrokenLinks :: Bool,- cmd :: Cmd+ { layers :: NonEmpty FilePath+ , allowBrokenLinks :: Bool+ , cmd :: Cmd } data Cmd@@ -39,11 +38,11 @@ pathList defaultPath = do option pathListReader $ mconcat- [ long "layers",- short 'L',- metavar "LAYERS",- value defaultPath,- help "List of (semicolon delimited) notebook folders to 'union mount', with the left-side folders being overlaid on top of the right-side ones. The default layer is implicitly included at the end of this list."+ [ long "layers"+ , short 'L'+ , metavar "LAYERS"+ , value defaultPath+ , help "List of (semicolon delimited) notebook folders to 'union mount', with the left-side folders being overlaid on top of the right-side ones. The default layer is implicitly included at the end of this list." ] pathListReader :: ReadM (NonEmpty FilePath) pathListReader =
src/Emanote/Model/Calendar.hs view
@@ -1,5 +1,6 @@--- | A primitive module to eventually pave way towards first-class "calendar"--- (daily notes, etc.) support in Emanote; either built-in or as plugin.+{- | A primitive module to eventually pave way towards first-class "calendar"+ (daily notes, etc.) support in Emanote; either built-in or as plugin.+-} module Emanote.Model.Calendar where import Data.Time.Calendar (Day, fromGregorianValid)@@ -42,5 +43,10 @@ void $ M.string "-" -- Day day <- asInt =<< replicateM 2 M.digitChar+ -- Optional suffix (ignored)+ void $+ optional $ do+ void $ M.oneOf ['-', '_', ' ']+ void M.takeRest maybe (fail "Not a date") pure $ fromGregorianValid year (fromInteger month) (fromInteger day)
src/Emanote/Model/Graph.hs view
@@ -41,9 +41,9 @@ parentLmlRoute model lmlR folgezettelParents = mconcat- [ folgezettelBacklinks,- folgezettelFrontlinks,- folgezettelFolder+ [ folgezettelBacklinks+ , folgezettelFrontlinks+ , folgezettelFolder ] fmap catMaybes . forM folgezettelParents $ \parentR -> do let parentModelR = R.ModelRoute_LML parentR@@ -71,10 +71,11 @@ Rel.RRTFound x -> Just x _ -> Nothing --- | Return the route to parent folder (unless indexRoute is passed).------ This will return the existing note (.org or .md) if possible. Otherwise--- fallback to .md even if missing.+{- | Return the route to parent folder (unless indexRoute is passed).++ This will return the existing note (.org or .md) if possible. Otherwise+ fallback to .md even if missing.+-} parentLmlRoute :: Model -> R.LMLRoute -> Maybe R.LMLRoute parentLmlRoute model r = do pr <- do
src/Emanote/Model/Link/Rel.hs view
@@ -21,25 +21,27 @@ import Text.Pandoc.Definition qualified as B import Text.Pandoc.LinkContext qualified as LC --- | A relation from one note to anywhere in the model.------ Target will remain unresolved in the `Rel`, and can be resolved at a latter--- time (eg: during rendering).+{- | A relation from one note to anywhere in the model.++ Target will remain unresolved in the `Rel`, and can be resolved at a latter+ time (eg: during rendering).+-} data Rel = Rel { -- The note containing this relation- _relFrom :: LMLRoute,- -- The target of the relation (can be a note or anything)- _relTo :: UnresolvedRelTarget,- -- | The relation context in LML- _relCtx :: [B.Block]+ _relFrom :: LMLRoute+ , -- The target of the relation (can be a note or anything)+ _relTo :: UnresolvedRelTarget+ , _relCtx :: [B.Block]+ -- ^ The relation context in LML } deriving stock (Eq, Ord, Show) --- | A link target that has not been resolved (using model) yet.------ Resolving this may or may not result in a resource in the model. The ADT--- constructors capture the different possible types of links the user is--- allowed to link to.+{- | A link target that has not been resolved (using model) yet.++ Resolving this may or may not result in a resource in the model. The ADT+ constructors capture the different possible types of links the user is+ allowed to link to.+-} data UnresolvedRelTarget = URTWikiLink (WL.WikiLinkType, WL.WikiLink) | URTResource ModelRoute@@ -79,9 +81,10 @@ in (URTWikiLink <$> toList wls) <> [URTResource r] --- | Parse a relative URL string for later resolution.------ TODO: Need tests for this function.+{- | Parse a relative URL string for later resolution.++ TODO: Need tests for this function.+-} parseUnresolvedRelTarget :: Maybe (R.R 'R.Folder) -> [(Text, Text)] -> Text -> Maybe (UnresolvedRelTarget, Maybe WL.Anchor) parseUnresolvedRelTarget baseDir attrs url = do (wlRes, manchor) <- WL.delineateLink attrs url@@ -124,9 +127,10 @@ x -> maybe [] (go 0) $ nonEmpty $ NEL.drop n x in toString . T.intercalate "/" . maybe [] (reverse . go 0 . NEL.reverse) . nonEmpty . T.splitOn "/" . toText --- | An `UnresolvedRelTarget` that has been resolved.------ See @Model.Link.Resolve@ for actual resolution logic.+{- | An `UnresolvedRelTarget` that has been resolved.++ See @Model.Link.Resolve@ for actual resolution logic.+-} data ResolvedRelTarget a = RRTMissing | RRTAmbiguous (NonEmpty a)
src/Emanote/Model/Meta.hs view
@@ -1,9 +1,8 @@-module Emanote.Model.Meta- ( lookupRouteMeta,- getEffectiveRouteMeta,- getEffectiveRouteMetaWith,- )-where+module Emanote.Model.Meta (+ lookupRouteMeta,+ getEffectiveRouteMeta,+ getEffectiveRouteMetaWith,+) where import Data.Aeson (FromJSON) import Data.Aeson qualified as Aeson@@ -21,8 +20,9 @@ lookupRouteMeta x k r = SData.lookupAeson x k . getEffectiveRouteMeta r --- | Get the (final) metadata of a note at the given route, by merging it with--- the defaults specified in parent routes all the way upto index.yaml.+{- | Get the (final) metadata of a note at the given route, by merging it with+ the defaults specified in parent routes all the way upto index.yaml.+-} getEffectiveRouteMeta :: R.LMLRoute -> ModelT f -> Aeson.Value getEffectiveRouteMeta mr model = let mNote = modelLookupNoteByRoute mr model@@ -30,7 +30,7 @@ getEffectiveRouteMetaWith :: Aeson.Value -> R.LMLRoute -> ModelT f -> Aeson.Value getEffectiveRouteMetaWith frontmatter mr model =- let defaultFiles = R.routeInits @'R.Yaml (R.withLmlRoute coerce mr)+ let defaultFiles = R.routeInits @('R.Yaml) (R.withLmlRoute coerce mr) defaults = flip mapMaybe (toList defaultFiles) $ \r -> do v <- getYamlMeta r model guard $ v /= Aeson.Null
src/Emanote/Model/Note.hs view
@@ -35,11 +35,11 @@ import Text.Pandoc.Walk qualified as W data Note = Note- { _noteRoute :: R.LMLRoute,- _noteDoc :: Pandoc,- _noteMeta :: Aeson.Value,- _noteTitle :: Tit.Title,- _noteErrors :: [Text]+ { _noteRoute :: R.LMLRoute+ , _noteDoc :: Pandoc+ , _noteMeta :: Aeson.Value+ , _noteTitle :: Tit.Title+ , _noteErrors :: [Text] } deriving stock (Eq, Ord, Show, Generic) deriving anyclass (Aeson.ToJSON)@@ -50,18 +50,18 @@ type NoteIxs = '[ -- Route to this note- R.LMLRoute,- -- Allowed ways to wiki-link to this note.- WL.WikiLink,- -- HTML route for this note- R 'R.Html,- -- Ancestor folder routes- RAncestor,- -- Parent folder- R 'R.Folder,- -- Tag- HT.Tag,- -- Alias route for this note. Can be "foo" or "foo/bar".+ R.LMLRoute+ , -- Allowed ways to wiki-link to this note.+ WL.WikiLink+ , -- HTML route for this note+ R 'R.Html+ , -- Ancestor folder routes+ RAncestor+ , -- Parent folder+ R 'R.Folder+ , -- Tag+ HT.Tag+ , -- Alias route for this note. Can be "foo" or "foo/bar". NonEmpty Slug ] @@ -107,7 +107,7 @@ noteSlug :: Note -> Maybe (NonEmpty Slug) noteSlug note = do slugPath :: Text <- lookupMeta (one "slug") note- fmap R.unRoute $ R.mkRouteFromFilePath @_ @'R.AnyExt $ toString slugPath+ fmap R.unRoute $ R.mkRouteFromFilePath @_ @('R.AnyExt) $ toString slugPath lookupMeta :: Aeson.FromJSON a => NonEmpty Text -> Note -> Maybe a lookupMeta k =@@ -169,13 +169,13 @@ ancestorPlaceholderNote :: R.R 'Folder -> Note ancestorPlaceholderNote r = let placeHolder =- [ folderListingQuery,- -- TODO: Ideally, we should use semantic tags, like <aside> (rather+ [ folderListingQuery+ , -- TODO: Ideally, we should use semantic tags, like <aside> (rather -- than <div>), to render these non-relevant content. B.Div (cls "emanote:placeholder-message") . one . B.Para $ [ B.Str- "Note: To override the auto-generated content here, create a file named one of: ",- -- TODO: or, .org+ "Note: To override the auto-generated content here, create a file named one of: "+ , -- TODO: or, .org B.Span (cls "font-mono text-sm") $ one $ B.Str $@@ -196,9 +196,9 @@ mkEmptyNoteWith (R.defaultLmlRoute route404) $ one $ B.Para- [ B.Str "No note has the URL ",- B.Code B.nullAttr $ "/" <> urlPath,- -- TODO: org+ [ B.Str "No note has the URL "+ , B.Code B.nullAttr $ "/" <> urlPath+ , -- TODO: org B.Span (cls "font-mono text-sm") $ one $ B.Str $@@ -215,9 +215,9 @@ ambiguousNoteURL urlPath rs = mkEmptyNoteWith (head rs) $ [ B.Para- [ B.Str "The URL ",- B.Code B.nullAttr $ toText urlPath,- B.Str " is ambiguous, as more than one note (see list below) use it. To fix this, specify a different slug for these notes:"+ [ B.Str "The URL "+ , B.Code B.nullAttr $ toText urlPath+ , B.Str " is ambiguous, as more than one note (see list below) use it. To fix this, specify a different slug for these notes:" ] ] <> one candidates@@ -226,8 +226,8 @@ candidates = B.BulletList $ toList rs <&> \(R.lmlRouteCase -> r) ->- [ B.Plain $ one $ B.Str " ",- B.Plain $ one $ B.Code B.nullAttr $ show r+ [ B.Plain $ one $ B.Str " "+ , B.Plain $ one $ B.Code B.nullAttr $ show r ] mkEmptyNoteWith :: R.LMLRoute -> [B.Block] -> Note@@ -317,9 +317,9 @@ frontmatter & AO.key "tags" % AO._Array .~ ( fromList . fmap Aeson.toJSON $- ordNub $- SData.lookupAeson @[HT.Tag] mempty (one "tags") frontmatter- <> HT.inlineTagsInPandoc doc+ ordNub $+ SData.lookupAeson @[HT.Tag] mempty (one "tags") frontmatter+ <> HT.inlineTagsInPandoc doc ) addDescriptionFromBody = overrideAesonText ("page" :| ["description"]) $ \case
src/Emanote/Model/SData.hs view
@@ -14,12 +14,13 @@ import Optics.TH (makeLenses) import Relude --- | `S` for "structured". Refers to a per-route data file represented by Aeson--- value. Example: /foo/bar.yaml file+{- | `S` for "structured". Refers to a per-route data file represented by Aeson+ value. Example: /foo/bar.yaml file+-} data SData = SData- { _sdataValue :: Aeson.Value,- -- | Location of this data file- _sdataRoute :: R.R 'R.Yaml+ { _sdataValue :: Aeson.Value+ , _sdataRoute :: R.R 'R.Yaml+ -- ^ Location of this data file } deriving stock (Eq, Ord, Data, Show, Generic) deriving anyclass (Aeson.ToJSON)
src/Emanote/Model/StaticFile.hs view
@@ -12,10 +12,10 @@ import Relude data StaticFile = StaticFile- { _staticFileRoute :: R.R 'R.AnyExt,- _staticFilePath :: FilePath,- -- | Indicates that this file was updated no latter than the given time.- _staticFileTime :: UTCTime+ { _staticFileRoute :: R.R 'R.AnyExt+ , _staticFilePath :: FilePath+ , _staticFileTime :: UTCTime+ -- ^ Indicates that this file was updated no latter than the given time. } deriving stock (Eq, Ord, Show, Generic) deriving anyclass (Aeson.ToJSON)
src/Emanote/Model/Stork.hs view
@@ -1,12 +1,19 @@-module Emanote.Model.Stork- ( renderStorkIndex,- )-where+module Emanote.Model.Stork (+ renderStorkIndex,+) where import Control.Monad.Logger (MonadLoggerIO)+import Data.Default (Default (def)) import Data.IxSet.Typed qualified as Ix+import Emanote.Model.Meta (lookupRouteMeta) import Emanote.Model.Note qualified as N-import Emanote.Model.Stork.Index (File (File), Input (Input), readOrBuildStorkIndex)+import Emanote.Model.Stork.Index (+ Config (Config),+ File (File),+ Handling,+ Input (Input),+ readOrBuildStorkIndex,+ ) import Emanote.Model.Title qualified as Tit import Emanote.Model.Type (Model) import Emanote.Model.Type qualified as M@@ -19,7 +26,8 @@ renderStorkIndex :: (MonadIO m, MonadLoggerIO m) => Model -> m LByteString renderStorkIndex model = do- readOrBuildStorkIndex (model ^. M.modelStorkIndex) (Input $ storkFiles model)+ let config = Config $ Input (storkFiles model) (frontmatterHandling model)+ readOrBuildStorkIndex (model ^. M.modelStorkIndex) config storkFiles :: Model -> [File] storkFiles model =@@ -29,3 +37,8 @@ ((baseDir </>) $ R.withLmlRoute R.encodeRoute $ note ^. N.noteRoute) (SR.siteRouteUrl model $ SR.lmlSiteRoute $ note ^. N.noteRoute) (Tit.toPlain $ note ^. N.noteTitle)++frontmatterHandling :: Model -> Handling+frontmatterHandling model =+ let indexRoute = M.modelIndexRoute model+ in lookupRouteMeta def ("template" :| ["stork", "frontmatter-handling"]) indexRoute model
src/Emanote/Model/Stork/Index.hs view
@@ -1,25 +1,28 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE TemplateHaskell #-} -module Emanote.Model.Stork.Index- ( IndexVar,- newIndex,- clearStorkIndex,- readOrBuildStorkIndex,- File (File),- Input (Input),- )-where+module Emanote.Model.Stork.Index (+ IndexVar,+ newIndex,+ clearStorkIndex,+ readOrBuildStorkIndex,+ File (File),+ Input (Input),+ Config (Config),+ Handling,+) where import Control.Monad.Logger (MonadLoggerIO)+import Data.Default (Default (..)) import Data.Text qualified as T import Data.Time (NominalDiffTime, diffUTCTime, getCurrentTime)+import Deriving.Aeson import Emanote.Prelude (log, logD, logW) import Numeric (showGFloat) import Relude import System.Process.ByteString (readProcessWithExitCode) import System.Which (staticWhich)-import Toml (TomlCodec, encode, list, string, text, (.=))+import Toml (Key, TomlCodec, diwrap, encode, list, string, table, text, textBy, (.=)) -- | In-memory Stork index tracked in a @TVar@ newtype IndexVar = IndexVar (TVar (Maybe LByteString))@@ -31,8 +34,8 @@ clearStorkIndex :: (MonadIO m) => IndexVar -> m () clearStorkIndex (IndexVar var) = atomically $ writeTVar var mempty -readOrBuildStorkIndex :: (MonadIO m, MonadLoggerIO m) => IndexVar -> Input -> m LByteString-readOrBuildStorkIndex (IndexVar indexVar) input = do+readOrBuildStorkIndex :: (MonadIO m, MonadLoggerIO m) => IndexVar -> Config -> m LByteString+readOrBuildStorkIndex (IndexVar indexVar) config = do readTVarIO indexVar >>= \case Just index -> do logD "STORK: Returning cached search index"@@ -41,7 +44,7 @@ -- TODO: What if there are concurrent reads? We probably need a lock. -- And we want to encapsulate this whole thing. logW "STORK: Generating search index (this may be expensive)"- (diff, !index) <- timeIt $ runStork input+ (diff, !index) <- timeIt $ runStork config log $ toText $ "STORK: Done generating search index in " <> showGFloat (Just 2) diff "" <> " seconds" atomically $ modifyTVar' indexVar $ \_ -> Just index pure index@@ -57,9 +60,9 @@ storkBin :: FilePath storkBin = $(staticWhich "stork") -runStork :: MonadIO m => Input -> m LByteString-runStork input = do- let storkToml = handleTomlandBug $ Toml.encode inputCodec input+runStork :: MonadIO m => Config -> m LByteString+runStork config = do+ let storkToml = handleTomlandBug $ Toml.encode configCodec config (_, !index, _) <- liftIO $ readProcessWithExitCode@@ -72,33 +75,79 @@ where handleTomlandBug = -- HACK: Deal with tomland's bug.- -- https://github.com/EmaApps/emanote/issues/336+ -- https://github.com/srid/emanote/issues/336 -- https://github.com/kowainik/tomland/issues/408 -- -- This could be problematic if the user literally uses \\U in their note -- title (but why would they?) T.replace "\\\\U" "\\U" -newtype Input = Input+newtype Config = Config+ { configInput :: Input+ }+ deriving stock (Eq, Show)++data Input = Input { inputFiles :: [File]+ , inputFrontmatterHandling :: Handling } deriving stock (Eq, Show) data File = File- { filePath :: FilePath,- fileUrl :: Text,- fileTitle :: Text+ { filePath :: FilePath+ , fileUrl :: Text+ , fileTitle :: Text } deriving stock (Eq, Show) -fileCodec :: TomlCodec File-fileCodec =- File- <$> Toml.string "path" .= filePath- <*> Toml.text "url" .= fileUrl- <*> Toml.text "title" .= fileTitle+data Handling+ = Handling_Ignore+ | Handling_Omit+ | Handling_Parse+ deriving stock (Eq, Show, Generic)+ deriving+ (FromJSON)+ via CustomJSON+ '[ ConstructorTagModifier '[StripPrefix "Handling_", CamelToSnake]+ ]+ Handling -inputCodec :: TomlCodec Input-inputCodec =- Input- <$> Toml.list fileCodec "input.files" .= inputFiles+instance Default Handling where+ def = Handling_Omit++configCodec :: TomlCodec Config+configCodec =+ Config+ <$> Toml.table inputCodec "input"+ .= configInput+ where+ inputCodec :: TomlCodec Input+ inputCodec =+ Input+ <$> Toml.list fileCodec "files"+ .= inputFiles+ <*> Toml.diwrap (handlingCodec "frontmatter_handling")+ .= inputFrontmatterHandling+ fileCodec :: TomlCodec File+ fileCodec =+ File+ <$> Toml.string "path"+ .= filePath+ <*> Toml.text "url"+ .= fileUrl+ <*> Toml.text "title"+ .= fileTitle+ handlingCodec :: Toml.Key -> TomlCodec Handling+ handlingCodec = textBy showHandling parseHandling+ where+ showHandling :: Handling -> Text+ showHandling handling = case handling of+ Handling_Ignore -> "Ignore"+ Handling_Omit -> "Omit"+ Handling_Parse -> "Parse"+ parseHandling :: Text -> Either Text Handling+ parseHandling handling = case handling of+ "Ignore" -> Right Handling_Ignore+ "Omit" -> Right Handling_Omit+ "Parse" -> Right Handling_Parse+ other -> Left $ "Unsupported value for frontmatter handling: " <> other
src/Emanote/Model/Task.hs view
@@ -16,12 +16,12 @@ import Text.Pandoc.Builder qualified as B data Task = Task- { _taskRoute :: R.LMLRoute,- -- Index of this task within the containing note. Used to sort tasks by+ { _taskRoute :: R.LMLRoute+ , -- Index of this task within the containing note. Used to sort tasks by -- their original order of appearance in the Markdown file.- _taskNum :: Word,- _taskDescription :: [B.Inline],- _taskChecked :: Bool+ _taskNum :: Word+ , _taskDescription :: [B.Inline]+ , _taskChecked :: Bool } deriving stock (Eq, Show, Generic) deriving anyclass (Aeson.ToJSON)
src/Emanote/Model/Title.hs view
@@ -1,19 +1,18 @@ {-# LANGUAGE DeriveAnyClass #-} -module Emanote.Model.Title- ( Title,+module Emanote.Model.Title (+ Title, - -- * Title conversion- fromRoute,- fromInlines,- toInlines,+ -- * Title conversion+ fromRoute,+ fromInlines,+ toInlines, - -- * Rendering a Title- titleSplice,- titleSpliceNoHtml,- toPlain,- )-where+ -- * Rendering a Title+ titleSplice,+ titleSpliceNoHtml,+ toPlain,+) where import Commonmark.Extensions.WikiLink (plainify) import Data.Aeson (ToJSON)
src/Emanote/Model/Type.hs view
@@ -18,16 +18,16 @@ import Ema.CLI qualified import Emanote.Model.Link.Rel (IxRel) import Emanote.Model.Link.Rel qualified as Rel-import Emanote.Model.Note- ( IxNote,- Note,- )+import Emanote.Model.Note (+ IxNote,+ Note,+ ) import Emanote.Model.Note qualified as N import Emanote.Model.SData (IxSData, SData, sdataRoute)-import Emanote.Model.StaticFile- ( IxStaticFile,- StaticFile (StaticFile),- )+import Emanote.Model.StaticFile (+ IxStaticFile,+ StaticFile (StaticFile),+ ) import Emanote.Model.Stork.Index qualified as Stork import Emanote.Model.Task (IxTask) import Emanote.Model.Task qualified as Task@@ -49,31 +49,32 @@ deriving stock (Eq, Show) data ModelT encF = Model- { _modelStatus :: Status,- _modelLayers :: Set Loc,- _modelEmaCLIAction :: Some Ema.CLI.Action,- _modelRoutePrism :: encF (Prism' FilePath SiteRoute),- -- | Dictates how exactly to render `Pandoc` to Heist nodes.- _modelPandocRenderers :: EmanotePandocRenderers Model LMLRoute,- _modelCompileTailwind :: Bool,- -- | An unique ID for this process's model. ID changes across processes.- _modelInstanceID :: UUID,- _modelNotes :: IxNote,- _modelRels :: IxRel,- _modelSData :: IxSData,- _modelStaticFiles :: IxStaticFile,- _modelTasks :: IxTask,- _modelNav :: [Tree Slug],- _modelHeistTemplate :: TemplateState,- _modelStorkIndex :: Stork.IndexVar+ { _modelStatus :: Status+ , _modelLayers :: Set Loc+ , _modelEmaCLIAction :: Some Ema.CLI.Action+ , _modelRoutePrism :: encF (Prism' FilePath SiteRoute)+ , _modelPandocRenderers :: EmanotePandocRenderers Model LMLRoute+ -- ^ Dictates how exactly to render `Pandoc` to Heist nodes.+ , _modelCompileTailwind :: Bool+ , _modelInstanceID :: UUID+ -- ^ An unique ID for this process's model. ID changes across processes.+ , _modelNotes :: IxNote+ , _modelRels :: IxRel+ , _modelSData :: IxSData+ , _modelStaticFiles :: IxStaticFile+ , _modelTasks :: IxTask+ , _modelNav :: [Tree Slug]+ , _modelHeistTemplate :: TemplateState+ , _modelStorkIndex :: Stork.IndexVar } deriving stock (Generic) type Model = ModelT Identity --- | A bare version of `Model` that is managed by the Ema app.------ The only difference is that this one has no `RouteEncoder`.+{- | A bare version of `Model` that is managed by the Ema app.++ The only difference is that this one has no `RouteEncoder`.+-} type ModelEma = ModelT (Const ()) deriving stock instance Generic ModelEma@@ -108,9 +109,9 @@ modelInsertNote note = modelNotes %~ ( Ix.updateIx r note- -- Insert folder placeholder automatically for ancestor paths- >>> injectAncestors (N.noteAncestors note)- >>> dropRedundantAncestor r+ -- Insert folder placeholder automatically for ancestor paths+ >>> injectAncestors (N.noteAncestors note)+ >>> dropRedundantAncestor r ) >>> modelRels %~ updateIxMulti r (Rel.noteRels note)@@ -121,8 +122,9 @@ where r = note ^. N.noteRoute --- | If a placeholder route was added already, but the newly added note is a--- non-Markdown, removce that markdown placeholder route.+{- | If a placeholder route was added already, but the newly added note is a+ non-Markdown, removce that markdown placeholder route.+-} dropRedundantAncestor :: LMLRoute -> IxNote -> IxNote dropRedundantAncestor recentNoteRoute ns = case recentNoteRoute of@@ -169,7 +171,7 @@ model & modelNotes %~ ( Ix.deleteIx k- >>> restoreAncestor (N.RAncestor <$> mFolderR)+ >>> restoreAncestor (N.RAncestor <$> mFolderR) ) & modelRels %~ deleteIxMulti k@@ -281,9 +283,10 @@ guard $ not $ null errs pure (note ^. N.noteRoute, errs) --- | Return the most suitable index LML route------ If index.org exist, use that. Otherwise, fallback to index.md.+{- | Return the most suitable index LML route++ If index.org exist, use that. Otherwise, fallback to index.md.+-} modelIndexRoute :: ModelT f -> LMLRoute modelIndexRoute model = do resolveLmlRoute model R.indexRoute@@ -298,7 +301,7 @@ -- TODO: Refactor using `[minBound..maxBound] :: [LML]` note <- asum- [ N.lookupNotesByRoute (R.LMLRoute_Org $ coerce r) notes,- N.lookupNotesByRoute (R.LMLRoute_Md $ coerce r) notes+ [ N.lookupNotesByRoute (R.LMLRoute_Org $ coerce r) notes+ , N.lookupNotesByRoute (R.LMLRoute_Md $ coerce r) notes ] pure $ note ^. N.noteRoute
src/Emanote/Pandoc/BuiltinFilters.hs view
@@ -1,8 +1,7 @@-module Emanote.Pandoc.BuiltinFilters- ( prepareNoteDoc,- preparePandoc,- )-where+module Emanote.Pandoc.BuiltinFilters (+ prepareNoteDoc,+ preparePandoc,+) where import Emanote.Model.Note qualified as N import Emanote.Pandoc.ExternalLink (setExternalLinkIcon)
src/Emanote/Pandoc/ExternalLink.hs view
@@ -1,7 +1,6 @@-module Emanote.Pandoc.ExternalLink- ( setExternalLinkIcon,- )-where+module Emanote.Pandoc.ExternalLink (+ setExternalLinkIcon,+) where import Relude import Text.Pandoc.Definition qualified as B
src/Emanote/Pandoc/Link.hs view
@@ -4,9 +4,10 @@ import Relude import Text.Pandoc.Definition qualified as B --- | A Pandoc inline node that refers to something else.------ There are, currently, only two possible nodes: link & image.+{- | A Pandoc inline node that refers to something else.++ There are, currently, only two possible nodes: link & image.+-} data InlineRef = InlineLink | InlineImage@@ -21,8 +22,9 @@ _ -> Nothing --- | Given an inline that is known to be an InlineRef, reconstruct and return--- its orginal Markdown source.+{- | Given an inline that is known to be an InlineRef, reconstruct and return+ its orginal Markdown source.+-} unParseLink :: HasCallStack => B.Inline -> Text unParseLink inl = case WL.wikiLinkInlineRendered inl of
src/Emanote/Pandoc/Markdown/Parser.hs view
@@ -1,7 +1,6 @@-module Emanote.Pandoc.Markdown.Parser- ( parseMarkdown,- )-where+module Emanote.Pandoc.Markdown.Parser (+ parseMarkdown,+) where import Commonmark qualified as CM import Commonmark.Extensions qualified as CE@@ -31,17 +30,17 @@ where baseExtsSansPipeTable = mconcat- [ CE.fancyListSpec,- CE.footnoteSpec,- CE.mathSpec,- CE.smartPunctuationSpec,- CE.definitionListSpec,- CE.attributesSpec,- CE.rawAttributeSpec,- CE.fencedDivSpec,- CE.bracketedSpanSpec,- CE.autolinkSpec,- CM.defaultSyntaxSpec+ [ CE.fancyListSpec+ , CE.footnoteSpec+ , CE.mathSpec+ , CE.smartPunctuationSpec+ , CE.definitionListSpec+ , CE.attributesSpec+ , CE.rawAttributeSpec+ , CE.fencedDivSpec+ , CE.bracketedSpanSpec+ , CE.autolinkSpec+ , CM.defaultSyntaxSpec ] gfmExtensionsSansPipeTable = CE.emojiSpec
src/Emanote/Pandoc/Markdown/Syntax/HashTag.hs view
@@ -1,17 +1,16 @@-module Emanote.Pandoc.Markdown.Syntax.HashTag- ( hashTagSpec,- inlineTagsInPandoc,- getTagFromInline,- TT.Tag (..),- TT.TagPattern (..),- TT.TagNode (..),- TT.mkTagPattern,- TT.tagMatch,- TT.constructTag,- TT.deconstructTag,- TT.tagTree,- )-where+module Emanote.Pandoc.Markdown.Syntax.HashTag (+ hashTagSpec,+ inlineTagsInPandoc,+ getTagFromInline,+ TT.Tag (..),+ TT.TagPattern (..),+ TT.TagNode (..),+ TT.mkTagPattern,+ TT.tagMatch,+ TT.constructTag,+ TT.deconstructTag,+ TT.tagTree,+) where import Commonmark (TokType (..)) import Commonmark qualified as CM@@ -42,12 +41,12 @@ instance HasHashTag (CP.Cm b B.Inlines) where hashTag (TT.Tag tag) = let attrs =- [ ("title", "Tag"),- (tagDataAttr, tag)+ [ ("title", "Tag")+ , (tagDataAttr, tag) ] classes =- [ "emanote:inline-tag",- -- This must be placed *after* the class above, to allow the user to+ [ "emanote:inline-tag"+ , -- This must be placed *after* the class above, to allow the user to -- override generic styles (of the class above) "emanote:inline-tag:" <> tag ]
src/Emanote/Pandoc/Markdown/Syntax/Highlight.hs view
@@ -1,7 +1,6 @@-module Emanote.Pandoc.Markdown.Syntax.Highlight- ( highlightSpec,- )-where+module Emanote.Pandoc.Markdown.Syntax.Highlight (+ highlightSpec,+) where import Commonmark qualified as CM import Commonmark.Inlines qualified as CM
src/Emanote/Pandoc/Renderer.hs view
@@ -1,23 +1,23 @@ {-# LANGUAGE RecordWildCards #-} --- | Types for custom render extensions to Pandoc AST nodes.------ Note that unlike Pandoc *filters* (which operate on entire document), these--- are modeled based on Text.Pandoc.Walk, ie. fine-grained on individual inline--- and block processing. We do this only so as to render a specific node during--- recursion (cf. `rpBlock` and `rpInline` in Render.hs).------ So we expect the extensions to be in Haskell, however external script may be--- supported using a traditional whole-AST extension API.-module Emanote.Pandoc.Renderer- ( PandocRenderers (PandocRenderers),- PandocInlineRenderer,- PandocBlockRenderer,- mkRenderCtxWithPandocRenderers,- EmanotePandocRenderers (..),- )-where+{- | Types for custom render extensions to Pandoc AST nodes. + Note that unlike Pandoc *filters* (which operate on entire document), these+ are modeled based on Text.Pandoc.Walk, ie. fine-grained on individual inline+ and block processing. We do this only so as to render a specific node during+ recursion (cf. `rpBlock` and `rpInline` in Render.hs).++ So we expect the extensions to be in Haskell, however external script may be+ supported using a traditional whole-AST extension API.+-}+module Emanote.Pandoc.Renderer (+ PandocRenderers (PandocRenderers),+ PandocInlineRenderer,+ PandocBlockRenderer,+ mkRenderCtxWithPandocRenderers,+ EmanotePandocRenderers (..),+) where+ import Heist (HeistT) import Heist.Extra.Splices.Pandoc qualified as Splices import Heist.Extra.Splices.Pandoc.Ctx qualified as Splices@@ -39,8 +39,8 @@ type PandocBlockRenderer model route = PandocRenderF model route B.Block data PandocRenderers model route = PandocRenderers- { pandocInlineRenderers :: [PandocInlineRenderer model route],- pandocBlockRenderers :: [PandocBlockRenderer model route]+ { pandocInlineRenderers :: [PandocInlineRenderer model route]+ , pandocBlockRenderers :: [PandocBlockRenderer model route] } mkRenderCtxWithPandocRenderers ::@@ -66,12 +66,12 @@ ) data EmanotePandocRenderers a r = EmanotePandocRenderers- { blockRenderers :: PandocRenderers a r,- -- | Like `blockRenderers` but for use in inline contexts.- --- -- Backlinks and titles constitute an example of inline context, where we don't- -- care about block elements.- inlineRenderers :: PandocRenderers a r,- -- | Like `inlineRenderers` but suitable for use inside links (<a> tags).- linkInlineRenderers :: PandocRenderers a r+ { blockRenderers :: PandocRenderers a r+ , inlineRenderers :: PandocRenderers a r+ -- ^ Like `blockRenderers` but for use in inline contexts.+ --+ -- Backlinks and titles constitute an example of inline context, where we don't+ -- care about block elements.+ , linkInlineRenderers :: PandocRenderers a r+ -- ^ Like `inlineRenderers` but suitable for use inside links (<a> tags). }
src/Emanote/Pandoc/Renderer/Embed.hs view
@@ -93,18 +93,18 @@ imageExts :: [Text] imageExts =- [ ".jpg",- ".jpeg",- ".png",- ".svg",- ".gif",- ".bmp",- ".webp"+ [ ".jpg"+ , ".jpeg"+ , ".png"+ , ".svg"+ , ".gif"+ , ".bmp"+ , ".webp" ] videoExts :: [Text] videoExts =- [ ".mp4",- ".webm",- ".ogv"+ [ ".mp4"+ , ".webm"+ , ".ogv" ]
src/Emanote/Pandoc/Renderer/Query.hs view
@@ -1,8 +1,7 @@-module Emanote.Pandoc.Renderer.Query- ( queryResolvingSplice,- noteSpliceMap,- )-where+module Emanote.Pandoc.Renderer.Query (+ queryResolvingSplice,+ noteSpliceMap,+) where import Data.List qualified as List import Data.Map.Syntax ((##))
src/Emanote/Pandoc/Renderer/Url.hs view
@@ -1,9 +1,8 @@-module Emanote.Pandoc.Renderer.Url- ( urlResolvingSplice,- plainifyWikiLinkSplice,- renderSomeInlineRefWith,- )-where+module Emanote.Pandoc.Renderer.Url (+ urlResolvingSplice,+ plainifyWikiLinkSplice,+ renderSomeInlineRefWith,+) where import Commonmark.Extensions.WikiLink qualified as WL import Data.Text qualified as T@@ -72,8 +71,8 @@ ctx ( tooltip "Link is broken"- [ B.Strikeout $ one $ B.Str $ Link.unParseLink origInl,- B.Str "❌"+ [ B.Strikeout $ one $ B.Str $ Link.unParseLink origInl+ , B.Str "❌" ] ) details <-@@ -133,8 +132,8 @@ ([B.Inline], Text) -> ([B.Inline], (Text, Bool)) replaceLinkNodeWithRoute model r (inner, url) =- ( inlinesWithWikiLinksPlainified $ nonEmptyLinkInlines model url (Just r) inner,- let linkUrl = SR.siteRouteUrl model r+ ( inlinesWithWikiLinksPlainified $ nonEmptyLinkInlines model url (Just r) inner+ , let linkUrl = SR.siteRouteUrl model r in (linkUrl, "?" `T.isInfixOf` linkUrl) ) where
src/Emanote/Prelude.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE DeriveAnyClass #-} --- | We use `relude` as our prelude; any extra Prelude-like functionality is put--- here.+{- | We use `relude` as our prelude; any extra Prelude-like functionality is put+ here.+-} module Emanote.Prelude where import Control.Monad.Logger (MonadLogger, logDebugNS, logErrorNS, logInfoNS, logWarnNS)
src/Emanote/Route/Ext.hs view
@@ -27,18 +27,20 @@ deriving stock instance Ord a => Ord (FileType a) --- | A lightweight markup language------ https://en.wikipedia.org/wiki/Lightweight_markup_language------ This type exists simply because we may support more formats (eg: org-mode) in--- the future.+{- | A lightweight markup language++ https://en.wikipedia.org/wiki/Lightweight_markup_language++ This type exists simply because we may support more formats (eg: org-mode) in+ the future.+-} data LML = Md | Org deriving stock (Generic, Eq, Ord, Typeable, Data, Enum, Bounded) deriving anyclass (ToJSON) --- | The `HasExt` class's responsibility is to allow dealing with basepath sans--- extension (and vice-versa).+{- | The `HasExt` class's responsibility is to allow dealing with basepath sans+ extension (and vice-versa).+-} class HasExt (ext :: FileType a) where fileType :: FileType a @@ -78,8 +80,9 @@ withExt = id withoutKnownExt = pure --- | The AnyExt instance ignores explicitly dealing with extensions, expecting--- the user to explicitly encode the extension in their value tpye.+{- | The AnyExt instance ignores explicitly dealing with extensions, expecting+ the user to explicitly encode the extension in their value tpye.+-} instance HasExt 'AnyExt where fileType = AnyExt withExt = id
src/Emanote/Route/ModelRoute.hs view
@@ -1,27 +1,27 @@ {-# LANGUAGE DeriveAnyClass #-} --- | Route types representing the resources in our `Model`.------ See also: `Emanote.Route.SiteRoute`.-module Emanote.Route.ModelRoute- ( -- Some route in a generated site- ModelRoute (..),- modelRouteCase,- mkModelRouteFromFilePath,- -- Only LML routes- LMLRoute (..),- defaultLmlRoute,- possibleLmlRoutes,- lmlRouteCase,- withLmlRoute,- mkLMLRouteFromFilePath,- mkLMLRouteFromKnownFilePath,- isMdRoute,- -- Static file routes- StaticFileRoute,- )-where+{- | Route types representing the resources in our `Model`. + See also: `Emanote.Route.SiteRoute`.+-}+module Emanote.Route.ModelRoute (+ -- Some route in a generated site+ ModelRoute (..),+ modelRouteCase,+ mkModelRouteFromFilePath,+ -- Only LML routes+ LMLRoute (..),+ defaultLmlRoute,+ possibleLmlRoutes,+ lmlRouteCase,+ withLmlRoute,+ mkLMLRouteFromFilePath,+ mkLMLRouteFromKnownFilePath,+ isMdRoute,+ -- Static file routes+ StaticFileRoute,+) where+ import Data.Aeson.Types (ToJSON) import Emanote.Route.Ext (FileType (AnyExt, LMLType), HasExt, LML (Md, Org)) import Emanote.Route.R (R)@@ -50,8 +50,8 @@ possibleLmlRoutes :: R (ext :: FileType a) -> [LMLRoute] possibleLmlRoutes r =- [ LMLRoute_Md (coerce r),- LMLRoute_Org (coerce r)+ [ LMLRoute_Md (coerce r)+ , LMLRoute_Org (coerce r) ] lmlRouteCase ::@@ -86,8 +86,9 @@ mkLMLRouteFromKnownFilePath Md fp <|> mkLMLRouteFromKnownFilePath Org fp --- | Like `mkLMLRouteFromFilePath`, but when the file extension is known ahead--- to be of `lmlType`.+{- | Like `mkLMLRouteFromFilePath`, but when the file extension is known ahead+ to be of `lmlType`.+-} mkLMLRouteFromKnownFilePath :: LML -> FilePath -> Maybe LMLRoute mkLMLRouteFromKnownFilePath lmlType fp = case lmlType of
src/Emanote/Route/R.hs view
@@ -11,8 +11,9 @@ import System.FilePath (splitPath) import Text.Show qualified (Show (show)) --- | Represents the relative path to some file (or its isomporphic URL--- represetation).+{- | Represents the relative path to some file (or its isomporphic URL+ represetation).+-} newtype R (ext :: FileType a) = R {unRoute :: NonEmpty Slug} deriving stock (Eq, Ord, Typeable, Data) @@ -106,4 +107,4 @@ decodeAnyRoute :: FilePath -> Maybe (R 'AnyExt) decodeAnyRoute =- mkRouteFromFilePath @_ @'AnyExt+ mkRouteFromFilePath @_ @('AnyExt)
src/Emanote/Route/SiteRoute.hs view
@@ -1,15 +1,14 @@-module Emanote.Route.SiteRoute- ( SiteRoute (..),- VirtualRoute (..),- ResourceRoute (..),- decodeVirtualRoute,- noteFileSiteRoute,- staticFileSiteRoute,- lmlSiteRoute,- siteRouteUrl,- siteRouteUrlStatic,- )-where+module Emanote.Route.SiteRoute (+ SiteRoute (..),+ VirtualRoute (..),+ ResourceRoute (..),+ decodeVirtualRoute,+ noteFileSiteRoute,+ staticFileSiteRoute,+ lmlSiteRoute,+ siteRouteUrl,+ siteRouteUrlStatic,+) where import Emanote.Route.SiteRoute.Class import Emanote.Route.SiteRoute.Type
src/Emanote/Route/SiteRoute/Class.hs view
@@ -1,20 +1,19 @@-module Emanote.Route.SiteRoute.Class- ( decodeVirtualRoute,- noteFileSiteRoute,- staticFileSiteRoute,- lmlSiteRoute,- indexRoute,- tagIndexRoute,- taskIndexRoute,- siteRouteUrl,- siteRouteUrlStatic,- urlStrategySuffix,+module Emanote.Route.SiteRoute.Class (+ decodeVirtualRoute,+ noteFileSiteRoute,+ staticFileSiteRoute,+ lmlSiteRoute,+ indexRoute,+ tagIndexRoute,+ taskIndexRoute,+ siteRouteUrl,+ siteRouteUrlStatic,+ urlStrategySuffix, - -- * Ema stuff- emanoteRouteEncoder,- emanoteGeneratableRoutes,- )-where+ -- * Ema stuff+ emanoteRouteEncoder,+ emanoteGeneratableRoutes,+) where import Data.IxSet.Typed qualified as Ix import Data.List.NonEmpty qualified as NE
src/Emanote/Route/SiteRoute/Type.hs view
@@ -1,14 +1,13 @@ {-# LANGUAGE DeriveAnyClass #-} -module Emanote.Route.SiteRoute.Type- ( SiteRoute (..),- VirtualRoute (..),- ResourceRoute (..),- decodeVirtualRoute,- encodeVirtualRoute,- encodeTagIndexR,- )-where+module Emanote.Route.SiteRoute.Type (+ SiteRoute (..),+ VirtualRoute (..),+ ResourceRoute (..),+ decodeVirtualRoute,+ encodeVirtualRoute,+ encodeTagIndexR,+) where import Data.Aeson (ToJSON) import Emanote.Pandoc.Markdown.Syntax.HashTag qualified as HT@@ -29,10 +28,11 @@ deriving stock (Eq, Ord, Show, Generic) deriving anyclass (ToJSON) --- | A route to a resource in `Model`------ This is *mostly isomorphic* to `ModelRoute`, except for containing the--- absolute path to the static file.+{- | A route to a resource in `Model`++ This is *mostly isomorphic* to `ModelRoute`, except for containing the+ absolute path to the static file.+-} data ResourceRoute = ResourceRoute_StaticFile StaticFileRoute FilePath | ResourceRoute_LML LMLRoute@@ -101,13 +101,13 @@ VirtualRoute_TagIndex tagNodes -> R.encodeRoute $ encodeTagIndexR tagNodes VirtualRoute_Index ->- R.encodeRoute $ R.R @() @'Ext.Html $ "-" :| ["all"]+ R.encodeRoute $ R.R @() @('Ext.Html) $ "-" :| ["all"] VirtualRoute_Export ->- R.encodeRoute $ R.R @Ext.SourceExt @'Ext.AnyExt $ "-" :| ["export.json"]+ R.encodeRoute $ R.R @Ext.SourceExt @('Ext.AnyExt) $ "-" :| ["export.json"] VirtualRoute_StorkIndex ->- R.encodeRoute $ R.R @Ext.SourceExt @'Ext.AnyExt $ "-" :| ["stork.st"]+ R.encodeRoute $ R.R @Ext.SourceExt @('Ext.AnyExt) $ "-" :| ["stork.st"] VirtualRoute_TaskIndex ->- R.encodeRoute $ R.R @() @'Ext.Html $ "-" :| ["tasks"]+ R.encodeRoute $ R.R @() @('Ext.Html) $ "-" :| ["tasks"] encodeTagIndexR :: [HT.TagNode] -> R.R 'Ext.Html encodeTagIndexR tagNodes =
src/Emanote/Source/Dynamic.hs view
@@ -1,15 +1,14 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} -module Emanote.Source.Dynamic- ( emanoteSiteInput,- EmanoteConfig (..),- emanoteCompileTailwind,- emanoteConfigCli,- emanoteConfigNoteFn,- emanoteConfigPandocRenderers,- )-where+module Emanote.Source.Dynamic (+ emanoteSiteInput,+ EmanoteConfig (..),+ emanoteCompileTailwind,+ emanoteConfigCli,+ emanoteConfigNoteFn,+ emanoteConfigPandocRenderers,+) where import Control.Monad.Logger (MonadLogger, MonadLoggerIO) import Data.Map.Strict qualified as Map@@ -37,19 +36,20 @@ -- | Everything that's required to run an Emanote site. data EmanoteConfig = EmanoteConfig- { -- | CLI arguments (includes layers)- _emanoteConfigCli :: CLI.Cli,- -- | A function to filter the `Note` before it gets added to the model.- _emanoteConfigNoteFn :: Note -> Note,- -- | How to render Pandoc to Heist HTML.- _emanoteConfigPandocRenderers :: EmanotePandocRenderers Model.Model LMLRoute,- -- | Whether to replace Tailwind2 CDN with a minimized Tailwind3 CSS file.- _emanoteCompileTailwind :: Bool+ { _emanoteConfigCli :: CLI.Cli+ -- ^ CLI arguments (includes layers)+ , _emanoteConfigNoteFn :: Note -> Note+ -- ^ A function to filter the `Note` before it gets added to the model.+ , _emanoteConfigPandocRenderers :: EmanotePandocRenderers Model.Model LMLRoute+ -- ^ How to render Pandoc to Heist HTML.+ , _emanoteCompileTailwind :: Bool+ -- ^ Whether to replace Tailwind2 CDN with a minimized Tailwind3 CSS file. } --- | Make an Ema `Dynamic` for the Emanote model.------ The bulk of logic for building the Dynamic is in `Patch.hs`.+{- | Make an Ema `Dynamic` for the Emanote model.++ The bulk of logic for building the Dynamic is in `Patch.hs`.+-} emanoteSiteInput :: (MonadUnliftIO m, MonadLoggerIO m) => Some Ema.CLI.Action -> EmanoteConfig -> m (Dynamic m Model.ModelEma) emanoteSiteInput cliAct EmanoteConfig {..} = do defaultLayer <- Loc.defaultLayer <$> liftIO Paths_emanote.getDataDir
src/Emanote/Source/Loc.hs view
@@ -1,29 +1,29 @@ -- | Notebook location-module Emanote.Source.Loc- ( -- * Type- Loc (..),+module Emanote.Source.Loc (+ -- * Type+ Loc (..), - -- * Making a `Loc`- defaultLayer,- userLayers,+ -- * Making a `Loc`+ defaultLayer,+ userLayers, - -- * Using a `Loc`- locResolve,- locPath,+ -- * Using a `Loc`+ locResolve,+ locPath, - -- * Dealing with layers of locs- LocLayers,- primaryLayer,- )-where+ -- * Dealing with layers of locs+ LocLayers,+ primaryLayer,+) where import Data.Set qualified as Set import Relude import System.FilePath ((</>)) --- | Location of the notebook------ The order here matters. Top = higher precedence.+{- | Location of the notebook++ The order here matters. Top = higher precedence.+-} data Loc = -- | The Int argument specifies the precedence (lower value = higher precedence) LocUser Int FilePath@@ -33,9 +33,10 @@ type LocLayers = Set Loc --- | Return the "primary" `LocUser` layer (that which are not overrides).------ Assumes that the user has put it always by last; i.e, `-L foo;primary/layer`.+{- | Return the "primary" `LocUser` layer (that which are not overrides).++ Assumes that the user has put it always by last; i.e, `-L foo;primary/layer`.+-} primaryLayer :: HasCallStack => LocLayers -> Loc primaryLayer = Set.findMax . Set.filter isUserLayer
src/Emanote/Source/Patch.hs view
@@ -1,10 +1,9 @@ -- | Patch model state depending on file change event.-module Emanote.Source.Patch- ( patchModel,- filePatterns,- ignorePatterns,- )-where+module Emanote.Source.Patch (+ patchModel,+ filePatterns,+ ignorePatterns,+) where import Control.Exception (throwIO) import Control.Monad.Logger (LoggingT (runLoggingT), MonadLogger, MonadLoggerIO (askLoggerIO))@@ -16,11 +15,11 @@ import Emanote.Model.SData qualified as SD import Emanote.Model.Stork.Index qualified as Stork import Emanote.Model.Type (ModelEma)-import Emanote.Prelude- ( BadInput (BadInput),- log,- logD,- )+import Emanote.Prelude (+ BadInput (BadInput),+ log,+ logD,+ ) import Emanote.Route qualified as R import Emanote.Source.Loc (Loc, LocLayers, locPath, locResolve, primaryLayer) import Emanote.Source.Pattern (filePatterns, ignorePatterns)@@ -159,11 +158,12 @@ _ -> readFileFollowingFsnotify fp --- | Like `readFileBS` but accounts for file truncation due to us responding--- *immediately* to a fsnotify modify event (which is triggered even before the--- writer *finishes* writing the new contents). We solve this "glitch" by--- delaying the read retry, expecting (hoping really) that *this time* the new--- non-empty contents will come through. 'tis a bit of a HACK though.+{- | Like `readFileBS` but accounts for file truncation due to us responding+ *immediately* to a fsnotify modify event (which is triggered even before the+ writer *finishes* writing the new contents). We solve this "glitch" by+ delaying the read retry, expecting (hoping really) that *this time* the new+ non-empty contents will come through. 'tis a bit of a HACK though.+-} readFileFollowingFsnotify :: (MonadIO m, MonadLogger m) => FilePath -> m ByteString readFileFollowingFsnotify fp = do log $ "Reading file: " <> toText fp
src/Emanote/Source/Pattern.hs view
@@ -13,10 +13,9 @@ R.withExt @_ @('R.LMLType 'R.Org) $ "**/*" R.Yaml ->- R.withExt @_ @'R.Yaml $- "**/*"+ R.withExt @_ @('R.Yaml) "**/*" R.HeistTpl ->- R.withExt @_ @'R.HeistTpl $+ R.withExt @_ @('R.HeistTpl) $ "**/*" R.AnyExt -> "**"@@ -24,19 +23,26 @@ filePatterns :: [(R.FileType R.SourceExt, FilePattern)] filePatterns = (id &&& filePattern)- <$> [ R.LMLType R.Md,- R.LMLType R.Org,- R.Yaml,- R.HeistTpl,- R.AnyExt+ <$> [ R.LMLType R.Md+ , R.LMLType R.Org+ , R.Yaml+ , R.HeistTpl+ , R.AnyExt ] ignorePatterns :: [FilePattern] ignorePatterns = [ -- Ignore all dotfile directories (eg: .git, .vscode)- "**/.*/**",- -- Ignore vi/vim/neovim writebackup files (see ":help writebackup")- "**/*~",- -- /Top-level ./-/ directory is reserved by Emanote+ "**/.*/**"+ , -- Ignore vi/vim/neovim writebackup files (see ":help writebackup")+ "**/*~"+ , -- /Top-level ./-/ directory is reserved by Emanote "-/**"+ , -- Special files that are not meant to be rendered+ -- NOTE: We must hardcode this only because there is no user-controllable+ -- `.emanoteignore` setting yet.{-# ANN annotation #-}+ --+ -- Any top-level Nix flake files+ "flake.nix"+ , "flake.lock" ]
src/Emanote/View/Common.hs view
@@ -1,17 +1,16 @@ {-# LANGUAGE RecordWildCards #-} -module Emanote.View.Common- ( commonSplices,- renderModelTemplate,- routeBreadcrumbs,- generatedCssFile,+module Emanote.View.Common (+ commonSplices,+ renderModelTemplate,+ routeBreadcrumbs,+ generatedCssFile, - -- * Render context- TemplateRenderCtx (..),- mkTemplateRenderCtx,- defaultRouteMeta,- )-where+ -- * Render context+ TemplateRenderCtx (..),+ mkTemplateRenderCtx,+ defaultRouteMeta,+) where import Data.Aeson.Types qualified as Aeson import Data.Map.Syntax ((##))@@ -48,14 +47,15 @@ import Text.Blaze.Renderer.XmlHtml qualified as RX data TemplateRenderCtx n = TemplateRenderCtx- { withInlineCtx :: (RenderCtx -> HI.Splice Identity) -> HI.Splice Identity,- withBlockCtx :: (RenderCtx -> HI.Splice Identity) -> HI.Splice Identity,- withLinkInlineCtx :: (RenderCtx -> HI.Splice Identity) -> HI.Splice Identity,- titleSplice :: Tit.Title -> HI.Splice Identity+ { withInlineCtx :: (RenderCtx -> HI.Splice Identity) -> HI.Splice Identity+ , withBlockCtx :: (RenderCtx -> HI.Splice Identity) -> HI.Splice Identity+ , withLinkInlineCtx :: (RenderCtx -> HI.Splice Identity) -> HI.Splice Identity+ , titleSplice :: Tit.Title -> HI.Splice Identity } --- | Create the context in which Heist templates (notably `pandoc.tpl`) will be--- rendered.+{- | Create the context in which Heist templates (notably `pandoc.tpl`) will be+ rendered.+-} mkTemplateRenderCtx :: -- | Current model. Model ->@@ -172,7 +172,7 @@ fromMaybe (error "no _emanote-static?") $ M.modelLookupStaticFile (staticFolder </> "inverted-tree.css") model staticFolderUrl = fst $ T.breakOn "/inverted-tree.css" itUrl- -- Deal with a silly Firefox bug https://github.com/EmaApps/emanote/issues/340+ -- Deal with a silly Firefox bug https://github.com/srid/emanote/issues/340 -- -- Firefox deduces an incorrect <base> after doing morphdom -- patching, unless the <base> is absolute (i.e., starts with a '/').
src/Emanote/View/Export.hs view
@@ -1,11 +1,10 @@ {-# LANGUAGE DeriveAnyClass #-} -module Emanote.View.Export- ( renderGraphExport,- Link (..),- modelRels,- )-where+module Emanote.View.Export (+ renderGraphExport,+ Link (..),+ modelRels,+) where import Data.Aeson (ToJSON) import Data.Aeson qualified as Aeson@@ -24,8 +23,8 @@ import Relude data Export = Export- { version :: Word,- files :: Map Text SourceFile+ { version :: Word+ , files :: Map Text SourceFile } deriving stock (Generic, Show) deriving anyclass (ToJSON)@@ -35,19 +34,19 @@ -- | A source file in `Model` data SourceFile = SourceFile- { title :: Text,- filePath :: Text,- parentNote :: Maybe Text,- url :: Text,- meta :: Aeson.Value,- links :: [Link]+ { title :: Text+ , filePath :: Text+ , parentNote :: Maybe Text+ , url :: Text+ , meta :: Aeson.Value+ , links :: [Link] } deriving stock (Generic, Show) deriving anyclass (ToJSON) data Link = Link- { unresolvedRelTarget :: Rel.UnresolvedRelTarget,- resolvedRelTarget :: Rel.ResolvedRelTarget Text+ { unresolvedRelTarget :: Rel.UnresolvedRelTarget+ , resolvedRelTarget :: Rel.ResolvedRelTarget Text } deriving stock (Generic, Eq, Ord, Show) deriving anyclass (ToJSON)
src/Emanote/View/LiveServerFiles.hs view
@@ -1,9 +1,8 @@-module Emanote.View.LiveServerFiles- ( isLiveServerFile,- tailwindFullCssPath,- tailwindCssFile,- )-where+module Emanote.View.LiveServerFiles (+ isLiveServerFile,+ tailwindFullCssPath,+ tailwindCssFile,+) where import Data.Text qualified as T import Emanote.Model.StaticFile (StaticFile)
src/Emanote/View/TagIndex.hs view
@@ -12,13 +12,13 @@ import Emanote.Pandoc.Markdown.Syntax.HashTag qualified as HT import Emanote.Pandoc.Renderer.Query qualified as PF import Emanote.Route.SiteRoute.Class qualified as SR-import Emanote.View.Common- ( TemplateRenderCtx (withInlineCtx),- commonSplices,- defaultRouteMeta,- mkTemplateRenderCtx,- renderModelTemplate,- )+import Emanote.View.Common (+ TemplateRenderCtx (withInlineCtx),+ commonSplices,+ defaultRouteMeta,+ mkTemplateRenderCtx,+ renderModelTemplate,+ ) import Heist.Extra.Splices.List qualified as Splices import Heist.Extra.Splices.Pandoc.Ctx (emptyRenderCtx) import Heist.Interpreted qualified as HI@@ -26,16 +26,16 @@ -- An index view into the notebook indexed by the given tag path. data TagIndex = TagIndex- { -- | The tag path under which this index is creatd- tagIndexPath :: [HT.TagNode],- -- | User descriptive title of this index- tagIndexTitle :: Text,- -- | All notes tagged precisely with this tag path- tagIndexNotes :: [MN.Note],- -- | Tags immediately under this tag path.- --- -- If the tag path being index is "foo/bar", this will contain "foo/bar/qux".- tagIndexChildren :: [(NonEmpty HT.TagNode, [MN.Note])]+ { tagIndexPath :: [HT.TagNode]+ -- ^ The tag path under which this index is creatd+ , tagIndexTitle :: Text+ -- ^ User descriptive title of this index+ , tagIndexNotes :: [MN.Note]+ -- ^ All notes tagged precisely with this tag path+ , tagIndexChildren :: [(NonEmpty HT.TagNode, [MN.Note])]+ -- ^ Tags immediately under this tag path.+ --+ -- If the tag path being index is "foo/bar", this will contain "foo/bar/qux". } deriving stock (Eq)
src/Emanote/View/Template.hs view
@@ -192,8 +192,8 @@ "ema:route-tree" ## ( let tree = PathTree.treeDeleteChild "index" $ model ^. M.modelNav getOrder tr =- ( Meta.lookupRouteMeta @Int 0 (one "order") tr model,- tr+ ( Meta.lookupRouteMeta @Int 0 (one "order") tr model+ , tr ) getCollapsed tr = Meta.lookupRouteMeta @Bool True ("template" :| ["sidebar", "collapsed"]) tr model
test/Emanote/Pandoc/ExternalLinkSpec.hs view
@@ -28,7 +28,7 @@ getDataLinkIconAttrs "#tag" === Right [] getDataLinkIconAttrs "[[abc|def]]" === Right [] it "adds attribute if link is external and its description contains text, code or math" . hedgehog $ do- getDataLinkIconAttrs "[Emanote](https://github.com/EmaApps/emanote)" === Right ["external"]+ getDataLinkIconAttrs "[Emanote](https://github.com/srid/emanote)" === Right ["external"] getDataLinkIconAttrs "[`text`](http://somehost:1234/test)" === Right ["external"] getDataLinkIconAttrs "[$$E=mc^2$$](ssh://user@host.abc/~/path/)" === Right ["external"] getDataLinkIconAttrs "[![[picture.png]] ~~**`code`**~~](scheme://host:port/path?query)" === Right ["external"]