Squashed commit of the following:
commit76f2664277
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Nov 13 22:57:05 2023 -0800 versioning: bump to v4.1.1 commit74777118a7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Nov 13 22:51:40 2023 -0800 feat: header and full-page transcludes (closes #557) commit8223465bda
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Nov 12 14:33:19 2023 -0800 fix: make :has img selector direct commitcf6ab9e933
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Nov 12 14:27:53 2023 -0800 feat: option to specify npx quartz sync message (closes #583) commit74c63e448e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 21:13:10 2023 -0800 fix(style): dont internal-link highlight when image (closes #581) commit43d638a6de
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 21:06:37 2023 -0800 perf: compute mapping of folder name to file data for faster breadcrumbs commitd1551872ff
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 20:46:57 2023 -0800 fix: check if popover exists after fetching and before inserting commit275bea3051
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 20:46:29 2023 -0800 style + cfg: resolve breadcrumb titles by default and change arrow character commitbc02791734
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 20:27:51 2023 -0800 fix: .date.getTime() based sort commitbf603c49c2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 12:08:54 2023 -0800 fix: sort rss feed by date commitf67356c3d2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 12:02:34 2023 -0800 lint: format commit5d666d1860
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 11:59:05 2023 -0800 fix: normalize relative urls (closes #569) commit22b7cf135e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 11:41:44 2023 -0800 types: cast in jsx.tsx to avoid @ts-ignore commit50a87d0d86
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 11:39:56 2023 -0800 style: scrollable tables commit134b6ed582
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 10:11:31 2023 -0800 fix: anchors links shouldnt cause reload (closes #574) commit99e8f5944f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 11 09:56:30 2023 -0800 fix: trailing slash aliases (closes #577) commite9f4e28a2d
Author: Yes365 <ninfovores365@gmail.com> Date: Fri Nov 10 11:44:16 2023 +0800 fix: adapt vercel cleanurls (#487) Co-authored-by: Harrison <Harrison@fanruan.com> commit2a6b9a9ea0
Author: Niklas Schröder <33390735+lnschroeder@users.noreply.github.com> Date: Tue Nov 7 18:16:48 2023 +0100 docs: fix property name for ToC toggle (#573) commite806c30fa1
Author: Mau Camargo <52770775+camargomau@users.noreply.github.com> Date: Sun Nov 5 13:30:10 2023 -0600 docs: Add Mau Camargo's Notkesto to showcase (#570) commitaac7b7e97d
Author: Anson Yu <ansonyu24@gmail.com> Date: Sat Nov 4 17:20:16 2023 -0400 docs: Update making plugins.md (#567) :) commit101e9946bd
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 4 12:11:42 2023 -0700 feat: add collapseByDefault option to TableOfContents (closes #566) commita62a97c7ab
Author: Emil Rofors <emirof@gmail.com> Date: Fri Nov 3 16:40:43 2023 -0700 docs: add GitLab pages CI (#549) * add .gitlab-ci.yml * move GitLab CI to hosting.md * remove extra folder name Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * remove test from gitlab instructions * run prettier --------- Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commit923b72fb67
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Nov 1 10:03:45 2023 -0700 feat: auto-tag releases (closes #560) commit05a1c34c6f
Author: Florence <59734957+Pydes-boop@users.noreply.github.com> Date: Wed Nov 1 17:57:32 2023 +0100 docs: remove dead link (#561) commit06ccb89cd7
Author: Blue Rose <134471273+bluerosegarden@users.noreply.github.com> Date: Tue Oct 31 15:53:49 2023 -0500 docs: clarifications about globs (#559) * Add note about fast-glob * Add warning about non-markdown files Also added a glob pattern to filter out all non-markdown files outside of a specified folder. * run npm format --------- Co-authored-by: wych <wychwitchcraft@gmail.com> commit01fc8e4640
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Oct 25 09:40:43 2023 -0700 fix: disable semi-broken flexsearch cache commit7c01e8dde0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 22 09:54:12 2023 -0700 feat: openLinksInNewTab option for link transformer commitb7ae7a99db
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Oct 21 21:12:11 2023 -0700 fix: styling for nested popover tag in page list commit60b3bc34cb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Oct 21 21:05:46 2023 -0700 fix: catch html to jsx errors (closes #547) commitdc834015d0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Oct 21 20:27:49 2023 -0700 fix(style): tag float orientation for long tags on page listing commit1e357ef5ac
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Oct 21 20:09:49 2023 -0700 fix(style): prioritize base and custom scss over component css commit54e722a55d
Author: freenandes <42041153+freenandes@users.noreply.github.com> Date: Wed Oct 18 03:43:41 2023 +0100 docs: Update showcase.md (#540) changed URL commit86d16b12a2
Author: Thomas <65691606+NotTacoz@users.noreply.github.com> Date: Wed Oct 18 10:43:20 2023 +0800 docs(explorer): Fixed small typo with extra } in explorer.md (#541) commited971800c0
Author: freenandes <42041153+freenandes@users.noreply.github.com> Date: Tue Oct 17 16:58:28 2023 +0100 Update showcase.md (#539) commitaf9ddadc4d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Oct 14 13:45:56 2023 -0700 fix(css): import base from custom instead of the other way around (#536) commitda0a062c05
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 8 09:59:18 2023 -0700 feat: docker support for v4 (closes #530) commitf66d2c23ac
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 8 09:15:06 2023 -0700 fix: ctrl+click with spa enabled commit3268d45a20
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Oct 5 13:48:52 2023 -0700 css: make article relative commitafa163f2fe
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Oct 5 13:30:06 2023 -0700 style: styling for codeblocks without langs (#527) commitcec4877adb
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Thu Oct 5 18:19:56 2023 +0200 fix(breadcrumbs): problem with folder whitespace (#522) * fix(breadcrumbs): problem with folder whitespace use slugs for folder hrefs so folder paths get resolved properly * feat: only use `slug` for constructing crumbs * fix: remove capitalization commitcf0c090e3c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Oct 4 09:23:56 2023 -0700 specify minimum npm version commitc8f5dbbad3
Author: Luca Salvarani <lucasalvarani99@gmail.com> Date: Mon Oct 2 02:20:55 2023 +0200 fix: Fix `Backlinks` not applying the display class (#519) * fix: Fix `Backlinks` not applying the display class Fix #518 * fix: Apply `displayClass` to all layout components * refactor: Use same style * fix: Remove `undefined` class using coalescing operator commitab5efac75f
Author: bfahrenfort <59982409+bfahrenfort@users.noreply.github.com> Date: Sun Oct 1 11:47:22 2023 -0500 Fix: RSS title escaping (#521) * Fix title escaping * npm run format commit2f99339dcf
Author: Hrishikesh Barman <geekodour@users.noreply.github.com> Date: Sat Sep 30 00:05:26 2023 +0530 feat: add transformations for latex in oxhugofm (#510) ox-hugo currently supports the following syntax for latex equations: - https://orgmode.org/manual/LaTeX-fragments.html - https://ox-hugo.scripter.co/doc/equations This syntax is supported by mathjax as is mentioned in the ox-hugo documentation. But quartz uses remark-math which has some issues with the \( \) syntax. See https://github.com/remarkjs/remark-math/issues/39 This change adds few more transformations to the OxHugoFlavouredMarkdown plugin, which makes a best effort conversion of this syntax into what the Quartz Latex transformer plugin supports. With these changes, the generated files show latex formatting with default quartz configuration. Sidenote on `\_` escape by ox-hugo: ox-hugo escapes, _ using \_, we match against it after we transform equations into what quartz supports($$ and $). This could be achieved using lookaround like regex as follows ```js (?<=(\$|\$\$)[\s\S]*) -> Positive lookbehind for $ or $$ \\_ -> Matches \_ (?=[\s\S]*(?:\1)) Positive lookahead for $ or $$ if matched const escapedUnderscoreRegex = new RegExp(/(?<=(\$|\$\$)[\s\S]*)\\_(?=[\s\S]*(?:\1))/, "g") ```` But since lookahead/behind can slow things down on large files, we just look up all equations with $ and $$ delimiters and then try replacing \_ commit5232d09af5
Author: ArtfulAzeria <146041757+ArtfulAzeria@users.noreply.github.com> Date: Fri Sep 29 20:17:48 2023 +0200 feat: Better and more responsive tag behavior (#515) * fix(explorer): default sortFn implementation (#511) * fix: use `numeric` + `base` for localeCompare * docs(explorer): update default sortFn * fix: better and more responsive tag behavior * tags css moved to TagList.tsx * used npm run format * merged tag declarations --------- Co-authored-by: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> commit0138085c16
Author: Catchears <57631841+Catchears@users.noreply.github.com> Date: Fri Sep 29 17:19:10 2023 +0200 docs: fix typo in breadcrumbs documentation (#513) commit0b61f6fbfd
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Fri Sep 29 10:26:15 2023 +0200 feat: implement breadcrumb component (#508) * feat: implement breadcrumbs * style: fix styling, move breadcrumbs to top * refactor: move `capitalize to `lang.ts`` * refactor: clean breadcrumb generation * feat: add options to breadcrumbs * feat: implement `resolveFrontmatterTitle` * feat: add `hideOnRoot` option * feat(consistency): capitalize every crumb * style: add `flex-wrap` to parent container * refactor: clean `Breadcrumbs.tsx` * feat(accessibility): use `nav`, add aria label * style: improve look in popovers by adding margin * docs: write docs for breadcrumb component * refactor: collapse `if` condition for hideOnRoot * chore: add todo for perf optimization * docs: update introduction commitd4c122646c
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Thu Sep 28 17:39:44 2023 +0200 fix(explorer): default sortFn implementation (#511) * fix: use `numeric` + `base` for localeCompare * docs(explorer): update default sortFn commitd22c3c107a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Sep 25 18:15:55 2023 -0700 fix: coerce title to string commit697bffdb8b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Sep 24 14:47:30 2023 -0700 fix: treat the 0 time as invalid too commitea5742c328
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Sep 24 10:31:47 2023 -0700 fix: mermaid copy source position commit95eec5b49d
Author: Chad Lee <git@chadly.net> Date: Sun Sep 24 12:27:42 2023 -0500 add site to showcase (#504) commitc5b9137f12
Author: Vince Imbat <96913392+vinceimbat@users.noreply.github.com> Date: Sat Sep 23 10:39:02 2023 +0800 docs: Adds Vince Imbat to showcase (#501) commit13c8673226
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Sep 22 10:04:37 2023 -0700 feat: add warning for invalid date format commita897cc1f53
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Sep 22 09:43:34 2023 -0700 feat: add warning for missing home page commitd93599364a
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Fri Sep 22 17:20:19 2023 +0200 docs(showcase): fix pull request redirect link (#500) commitfa69c2a565
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Thu Sep 21 19:35:11 2023 +0200 fix(explorer): increase consistency, explicitly use font-family (#496) * fix(explorer): display name for folders without `index` file * docs(explorer): add section for folder display names * docs(explorer): fix broken wikilink * fix(consistency): explicitly set font + label/link fix Use consistent styling between folders with `folderClickBehavior: "link"` and `"collapse` * Update quartz/components/styles/explorer.scss Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * Update quartz/components/styles/explorer.scss Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> --------- Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commit8eb1554b13
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Thu Sep 21 18:54:33 2023 +0200 fix(explorer): display names for folders without frontmatter (#494) * fix(explorer): display name for folders without `index` file * docs(explorer): add section for folder display names commitdcdeae4e7b
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Thu Sep 21 18:53:19 2023 +0200 docs(explorer): update default config + new example (#493) commit48452231d5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 20 16:09:18 2023 -0700 perf: memoize filetree computation (#490) * perf: memoize filetree computation * format * var -> let commit16d33fb771
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 20 16:08:54 2023 -0700 feat: display name for folders, expand explorer a little bit (#489) * feat: display name for folders, expand explorer a little bit * update docs commitb029eeadab
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Wed Sep 20 22:55:29 2023 +0200 feat(explorer): improve accessibility and consistency (+ bug fix) (#488) * feat(consistency): use `all: unset` on button * style: improve accessibility and consistency for explorer * fix: localStorage bug with folder name changes * chore: bump quartz version commit6a9e6352e8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 20 13:52:45 2023 -0700 Revert "feat: Making Quartz available offline by making it a PWA (#465)" This reverts commitd6301fae90
. commit70e029d151
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 20 13:52:29 2023 -0700 Revert "docs: wording changes for offline support" This reverts commit52a172d1a4
. commit0bad3ce799
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 20 11:58:52 2023 -0700 docs: document enableToc commit52a172d1a4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 20 11:40:36 2023 -0700 docs: wording changes for offline support commitd6301fae90
Author: Adam Brangenberg <adambrangenberg@proton.me> Date: Wed Sep 20 20:38:13 2023 +0200 feat: Making Quartz available offline by making it a PWA (#465) * Adding PWA and chaching for offline aviability * renamed workbox config to fit Quartz' scheme * Documenting new configuration * Added missig umami documentation * Fixed formatting so the build passes, thank you prettier :) * specified caching strategies to improve performance * formatting... * fixing "404 manifest.json not found" on subdirectories by adding a / to manifestpath * turning it into a plugin * Removed Workbox-cli and updated @types/node * Added Serviceworkercode to offline.ts * formatting * Removing workbox from docs * applied suggestions * Removed path.join for sw path Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * Removed path.join for manifest path Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * Removing path module import * Added absolute path to manifests start_url and manifest "import" using baseUrl * Adding protocol to baseurl Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * Adding protocol to start_url too then * formatting... * Adding fallback page * Documenting offline plugin * formatting... * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * merge suggestion Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * formatting... * Fixing manifest path, all these nits hiding the actual issues .-. * Offline fallback page through plugins, most things taken from 404 Plugin * adding Offline Plugin to config * formatting... * Turned offline off as default and removed offline.md --------- Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commit27a6087dd5
Author: rwutscher <richard.wutscher@gmail.com> Date: Tue Sep 19 21:26:30 2023 +0200 fix: tag regex no longer includes purely numerical 'tags' (#485) * fix: tag regex no longer includes purely numerical 'tags' * fix: formatting * fix: use guard in findAndReplace() instead of expanding the regex commit1bf7e3d8b3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Sep 19 10:22:39 2023 -0700 fix(nit): make defaultOptions on explorer not a function commitcc31a40b0c
Author: David Fischer <david@konst.fish> Date: Tue Sep 19 18:25:51 2023 +0200 feat: support changes in system theme (#484) * feat: support changes in system theme * fix: run prettier * fix: add content/.gitkeep commit0d3cf29226
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Mon Sep 18 23:32:00 2023 +0200 docs: fix explorer example (#483) commit6a2e0b3ad3
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Sun Sep 17 22:04:44 2023 +0200 fix: bad visibility for last explorer item (#478) * fix: bad visibility for last explorer item * feat(explorer): add pseudo element for observer commite67f409ec1
Merge:af41f34b
4afb099b
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Sun Sep 17 21:36:04 2023 +0200 Merge pull request #479 from benschlegel/explorer-config feat(explorer): add config for custom sort/map/filter functions commit4afb099bf3
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sun Sep 17 21:32:23 2023 +0200 docs: fix examples commit6914d4b40c
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sun Sep 17 21:20:09 2023 +0200 docs: fix intra page links commitaf41f34bfd
Author: Christian Gill <gillchristiang@gmail.com> Date: Sun Sep 17 20:02:00 2023 +0200 fix(slug): Handle question mark (#481) commit7ac772fca8
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Sun Sep 17 19:29:20 2023 +0200 fix: darkmode scroll bars (#480) commit5cc9253c41
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sun Sep 17 16:41:23 2023 +0200 docs(explorer): write docs for new features commit94a04ab1c9
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sun Sep 17 15:51:08 2023 +0200 fix(explorer): filter function in `ExplorerNode` commit9358f73f1c
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sun Sep 17 12:41:06 2023 +0200 fix: display name for file nodes commitf7029012df
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sat Sep 16 21:58:38 2023 +0200 feat: black magic add config for `order` array, which determines the order in which all passed config functions for explorer will get executed in. functions will now dynamically be called on `fileTree` via array accessor (e.g. fileTree["sort"].call(...)) with corresponding function from options being passed to call) commitfea352849c
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sat Sep 16 19:45:21 2023 +0200 fix: create deep copy of file passed into tree commit3d8c470c0d
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sat Sep 16 19:35:27 2023 +0200 feat(explorer): implement `map` fn argument Add a function for mapping over all FileNodes as an option for `Explorer` commit31d16fbd2c
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sat Sep 16 19:18:59 2023 +0200 feat(explorer): integrate filter option commit036a33f70b
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sat Sep 16 17:47:44 2023 +0200 fix: use correct import for `QuartzPluginData` commit58aea1cb07
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sat Sep 16 17:28:58 2023 +0200 feat: implement filter function for explorer commitc7d3474ba8
Author: Ben Schlegel <ben5.schlegel@gmail.com> Date: Sat Sep 16 12:40:19 2023 +0200 feat(explorer): add config to support custom sort fn commit422ba5c365
Author: Yuto Nagata <38714187+mouse484@users.noreply.github.com> Date: Sat Sep 16 11:17:20 2023 +0900 fix: umami analytics date attribute (#477) commit9ae6343dd0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Sep 15 10:33:38 2023 -0700 Revert "fix: use git dates by default, @napi/git is fast enough" This reverts commit5dcb7e83fc
. commit5dcb7e83fc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Sep 15 09:46:06 2023 -0700 fix: use git dates by default, @napi/git is fast enough commit91f9ae2d71
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Fri Sep 15 18:39:16 2023 +0200 feat: implement file explorer component (closes #201) (#452) * feat: add basic explorer structure„ * feat: integrate new component/plugin * feat: add basic explorer structure * feat: add sort to FileNodes * style: improve style for explorer * refactor: remove unused explorer plugin * refactor: clean explorer structure, fix base (toc) * refactor: clean css, respect displayClass * style: add styling to chevron * refactor: clean up debug statements * refactor: remove unused import * fix: clicking folder icon sometimes turns invisible * refactor: clean css * feat(explorer): add config for title * feat: add config for folder click behavior * fix: `no-pointer` not being set for all elements new approach, have one `no-pointer` class, that removes pointer events and one `clickable` class on the svg and button (everything that can normally be clicked). then, find all children with `clickable` and toggle `no-pointer` * fix: bug where nested folders got incorrect height this fixes the bug where nested folders weren't calculating their total height correctly. done by adding class to main container of all children and calculating total * feat: introduce `folderDefaultState` config * feat: store depth for explorer nodes * feat: implement option for collapsed state + bug fixes folderBehavior: "link" still has bad styling, but major bugs with pointers fixed (not clean yet, but working) * fix: default folder icon rotation * fix: hitbox problem with folder links, fix style * fix: redirect url for nested folders * fix: inconsistent behavior with 'collapseFolders' opt * chore: add comments to `ExplorerNode` * feat: save explorer state to local storage (not clean) * feat: rework `getFolders()`, fix localstorage read + write * feat: set folder state from localStorage needs serious refactoring but functional (except folder icon orientation) * fix: folder icon orientation after local storage * feat: add config for `useSavedState` * refactor: clean `explorer.inline.ts` remove unused functions, comments, unused code, add types to EventHandler * refactor: clean explorer merge `isSvg` paths, remove console logs * refactor: add documentation, remove unused funcs * feat: rework folder collapse logic use grids instead of jank scuffed solution with calculating total heights * refactor: remove depth arg from insert * feat: restore collapse functionality to clicks allow folder icon + folder label to collapse folders again * refactor: remove `pointer-event` jank * feat: improve svg viewbox + remove unused props * feat: use css selector to toggle icon rework folder icon to work purely with css instead of JS manipulation * refactor: remove unused cfg * feat: move TOC to right sidebar * refactor: clean css * style: fix overflow + overflow margin * fix: use `resolveRelative` to resolve file paths * fix: `defaultFolderState` config option * refactor: rename import, rename `folderLi` + ul * fix: use `QuartzPluginData` type * docs: add explorer documentation commit14cbbdb8a2
Author: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Thu Sep 14 05:55:59 2023 +0200 feat: display tag in graph view (#466) * feat: tags in graph view * fix: revert changing graph forces * fix: run prettier commitcce389c81d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 13 11:28:53 2023 -0700 feat: note transclusion (#475) * basic transclude * feat: note transclusion commit4461748a85
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 13 09:43:14 2023 -0700 fix dont show html in search when rssFullHtml is true (closes #474) commit6ecdcb5e24
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Sep 12 22:55:50 2023 -0700 feat: resolve block references in obsidian markdown commite3b879741b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Sep 12 21:44:03 2023 -0700 feat: rich html rss (closes #460) commit60a3c54339
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Sep 12 21:29:57 2023 -0700 fix: 404 page styling for nested pages (closes #458) commit71d81bde1d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Sep 12 19:18:44 2023 -0700 feat: rss limit (closes #459) commita19df64be8
Author: hcplantern <38579760+HCPlantern@users.noreply.github.com> Date: Tue Sep 12 14:00:21 2023 +0800 fix: callout parsing (#469) commit4e23e67244
Author: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Mon Sep 11 08:11:42 2023 +0200 feat: plugin for remark-breaks (#467) * feat: plugin for remark-breaks * fix: update package-lock.json * fix: styling Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * Update linebreaks.ts * Update index.ts --------- Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commita66c239797
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Sep 10 23:07:17 2023 -0700 ci: print bundleInfo commit53f1c88738
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Sep 8 09:29:57 2023 -0700 fix: more lenient date parsing for templates commit06df00b186
Author: Stefano Cecere <stefano.cecere@krur.com> Date: Thu Sep 7 17:13:41 2023 +0200 typo (it's draft, not drafts) (#456) commit2525bfbab5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 6 22:24:15 2023 -0700 fix: links to index not showing in graph (closes #450) commit828aa71fe3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 6 21:47:59 2023 -0700 fix: escape encoding for titles in rss commitef1ead31dc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 6 21:31:01 2023 -0700 fix: encodeuri for slugs in rss commit989bee5979
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 6 21:08:08 2023 -0700 docs: correct field for ignorePatterns commit8d6029b7b8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 6 21:02:21 2023 -0700 feat: 404 page emitter commit2d52eba413
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Sep 6 20:25:38 2023 -0700 fix: dont transform external links commit6ef4246cf1
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Mon Sep 4 07:36:30 2023 +0200 docs: update `full-text-search.md` (#447) commit616a7f148a
Author: Dr Kim Foale <kim@gfsc.studio> Date: Mon Sep 4 05:29:58 2023 +0100 docs: Make it clearer that wikilinks go to paths not page titles (#448) commite8a04efaf1
Author: Adam Brangenberg <adambrangenberg@proton.me> Date: Mon Sep 4 06:28:57 2023 +0200 feat(analytics): Support for Umami (#449) commit7e42be8e46
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Sun Sep 3 18:32:46 2023 +0200 feat(search): add arrow key navigation (#442) * feat(search): add arrow navigation * chore: format * refactor: simplify arrow navigation * chore: remove comment * feat: rework arrow navigation to work without state * feat: make pressing enter work with arrow navigation * fix: remove unused css class * chore: correct comment * refactor(search): use optional chaining commit8c354f6261
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Sun Sep 3 18:06:05 2023 +0200 fix: clipboard button visible in search (#445) commit505673acd7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Sep 2 18:07:26 2023 -0700 feat: pluralize things in lists commit23f43045c4
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Fri Sep 1 23:12:32 2023 +0200 fix(search): matches getting highlighted in title (#440) commit90dac31216
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Fri Sep 1 19:09:58 2023 +0200 feat: Implement search for tags (#436) * Quartz sync: Aug 29, 2023, 10:17 PM * style: add basic style to tags in search * feat: add SearchType + tags to search preview * feat: support multiple matches * style(search): add style to matching tags * feat(search): add content to preview for tag search * fix: only display tags on tag search * feat: support basic + tag search * refactor: extract common `fillDocument`, format * feat: add hotkey to search for tags * chore: remove logs * fix: dont render empty `<ul>` if tags not present * fix(search-tag): make case insensitive * refactor: clean `hideSearch` and `showSearch` * feat: trim content similar to `description.ts` * fix(search-tag): hotkey for windows * perf: re-use main index for tag search commit2d6dc176c3
Author: Pelayo Arbués <gonzalezpelayo@gmail.com> Date: Thu Aug 31 21:12:06 2023 +0200 Adds Pelayo Arbues to showcase (#435) commitb213ba45e2
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Thu Aug 31 20:55:04 2023 +0200 fix: regex for matching highlights (closes #437) (#438) * fix: regex for matching highlights * fix: regex for empty highlights commit5fa6fc9789
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 29 10:37:00 2023 -0700 fix: aliasredirects not using full path, add permalink support commit1cc09ef76d
Author: Jeffrey Fabian <jeffrey.fabian61@gmail.com> Date: Tue Aug 29 13:14:54 2023 -0400 feat: support kebab-case and nested tags in Obsidian-flavored Markdown tag-in-content parsing (#425) * enhancement: support kebab-case and nested tags in ofm transformer * update regex/capture groups to allow for (arbitrarily) nested values and tags of only -/_ * Update quartz/plugins/transformers/ofm.ts --------- Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commitc35cd422c6
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Mon Aug 28 19:00:49 2023 +0200 fix: correct graph labels for `index.md` nodes (#431) commit082fdf2e80
Author: Jeremy Press <jeremypress1@gmail.com> Date: Sun Aug 27 20:57:19 2023 -0700 Fix typo :) (#430) commitb6b1dabde0
Author: Jeremy Press <jeremypress1@gmail.com> Date: Sun Aug 27 17:39:42 2023 -0700 feat: support configurable ws port and remote development (#429) Co-authored-by: Jeremy Press <jeremy@replit.com> Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commit4b89202f7e
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Mon Aug 28 00:59:51 2023 +0200 cleanup: rework cli to allow invoking create and build outside of cli (#428) * refactor: move `bootstrap-cli.mjs` tp cli also update reference in docs * refactor(cli): move build handler to `cli-functions` * refactor(cli): move create to handler + helpers * refactor(cli): extract arg definitions * refactor: rename handlers and helpers * refactor(cli): move update, await handlers * refactor(cli): create constants, migrate to helpers * refactor(cli): migrate `restore` * refactor(cli): migrate `sync` * format * refactor(cli): remove old imports/functions * refactor(cli): remove unused imports + format * chore: remove old log statement * fix: fix imports, clean duplicate code * fix: relative import * fix: simplified cacheFile path * fix: update cacheFile import path * refactor: move bootstrap-cli to quartz * format * revert: revert path to bootstrap-cli * ci: re-run * ci: fix execution permission commit52ca312f41
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 27 12:27:42 2023 -0700 fix: slugify tag on page before adding (closes #411) commitc91e62c376
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Sun Aug 27 02:19:45 2023 +0200 Fix search bar after navigate (#424) commitad4145fb10
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Sat Aug 26 22:21:44 2023 +0200 feat: support CLI arguments for `npx quartz create` (#421) * feat(cli): add new args for content + link resolve * feat(cli): validate cmd args * feat(cli): add chalk + error code to errors * feat(cli): support for setup/link via args * refactor(cli): use yargs choices instead of manual Scrap manual check if arguments are valid, use yargs "choices" field instead. * feat(cli): add in-dir argument+ handle errors add new "in-directory" argument, used if "setup" is "copy" or "symlink" to determine source. add error handling for invalid permutations of arguments or non existent path * feat(cli): dynamically use cli or provided args use "in-directory" arg as `originalFolder` if available, otherwise get it from manual cli process * run format * fix: use process.exit instead of return * refactor: split CommonArgv and CreateArgv * refactor(cli): rename create args, use ${} syntax * fix(cli): fix link resolution strategy arg * format * feat(consistency): allow partial cmd args commit74c3ebb7bd
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 26 10:48:34 2023 -0700 style: fix mulitline callout styling commite3265f8416
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 26 10:42:55 2023 -0700 docs: simplify oxhugo page commitbc543f81d9
Author: Hrishikesh Barman <geekodour@users.noreply.github.com> Date: Sat Aug 26 11:22:23 2023 +0530 feat(plugins): add OxHugoFlavouredMarkdown (#419) * feat(plugins): add OxHugoFlavouredMarkdown ox-hugo is an org exporter backend that exports org files to hugo-compatible markdown in an opinionated way. This plugin adds some tweaks to the generated markdown to make it compatible with quartz but the list of changes applied it is not extensive. In the future however, we could leapfrog ox-hugo altogether and create a quartz site directly out of org-roam files. That way we won't have to do all the ritual dancing that this plugin has to perform. See https://github.com/k2052/org-to-markdown * fix: add toml to remarkFrontmatter configuration * docs: add docs for OxHugoFlavouredMarkdown * fixup! docs: add docs for OxHugoFlavouredMarkdown commit5c6d1e27ba
Author: Hrishikesh Barman <geekodour@users.noreply.github.com> Date: Fri Aug 25 22:55:46 2023 +0530 feat(plugins): add toml support for frontmatter (#418) * feat(plugins): add toml support for frontmatter Currently frontmatter is expected to be yaml, with delimiter set to "---". This might not always be the case, for example ox-hugo(a hugo exporter for org-mode files) exports in toml format with the delimiter set to "+++" by default. With this change, the users will be able use frontmatter plugin to support this toml frontmatter format. Example usage: `Plugin.FrontMatter({delims: "+++", language: 'toml'})` - [0] https://ox-hugo.scripter.co/doc/org-meta-data-to-hugo-front-matter/ * fixup! feat(plugins): add toml support for frontmatter commit340e3ef511
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Fri Aug 25 18:03:49 2023 +0200 feat(consistency): Add `.obsidian` to ignorePatterns (#420) commit953ef29f4e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 24 12:31:15 2023 -0700 format, ensure ci runs on prs commit94ce0883e7
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Thu Aug 24 21:28:06 2023 +0200 style: integrate tertiary color to text-select (#413) commit8cf7280614
Author: Zero King <l2dy@icloud.com> Date: Fri Aug 25 02:41:20 2023 +0800 feat: reproducible build (#412) for sitemap, RSS and contentIndex.json. commitc8412a5b0a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 24 10:03:14 2023 -0700 format commitfc4b8f3d3f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 24 09:38:00 2023 -0700 fix: ensure recentnotes uses proper date commit6cd0612d40
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 24 09:17:43 2023 -0700 fix: add better warning when defaultDateType is not set due to upgrade commit9851697b58
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 24 09:05:19 2023 -0700 version bump to 4.0.10 commitc36a9f3fb7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 24 08:56:40 2023 -0700 feat: add defaultDateType config commit98d82415dc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 24 08:31:06 2023 -0700 fix: lock to never read when site is building commit9d2340e90b
Author: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Thu Aug 24 17:14:52 2023 +0200 docs: fix typo in `authoring content.md` (#408) commit8200c8d040
Author: bfahrenfort <59982409+bfahrenfort@users.noreply.github.com> Date: Thu Aug 24 00:57:49 2023 -0500 Revert contentIndex to RSS 2.0 (#407) commit2e0e518f5d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 15:16:04 2023 -0700 format commit632c27b7ec
Author: Zane Helton <me@zaaane.com> Date: Wed Aug 23 18:14:23 2023 -0400 docs: update `hosting.md` with Vercel hosting instructions (#406) * Update hosting.md with Vercel hosting instructions * Update docs/hosting.md Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * Update docs/hosting.md Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> * Run npm run format --------- Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commitbfb416b35a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 13:10:23 2023 -0700 fix: text wrap in popover commit960c1814d0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 12:23:49 2023 -0700 docs: make incompability of trailing slashes clear commiteed4472aee
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 12:18:50 2023 -0700 fix: use proper full base for links.ts commitb99eb7ebce
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 12:11:16 2023 -0700 docs: whitespace commit0aaf88b852
Author: kanpov <71177577+kanpov@users.noreply.github.com> Date: Wed Aug 23 22:09:04 2023 +0300 Fix #403 by moving documentation to separate directory to avoid merge conflicts (#405) commita1a1e7e1e0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 11:36:34 2023 -0700 fix: builds should no accumulate on repeated changes (closes #404) commit3209f7c3b7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 09:19:00 2023 -0700 deps: native addons for lightningcss commitcde1e26129
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 09:16:44 2023 -0700 deps: install exact commit1128efcf23
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 23 09:10:30 2023 -0700 deps: esbuild and esbuild-sass-plugin commitd2f5254995
Author: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Wed Aug 23 12:05:01 2023 -0400 fix(esbuild): conflict with esbuild-sass-plugin (#402) commit3064839c2d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 22 23:37:02 2023 -0700 version bump to 4.0.9 commitb444c5c13b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 22 23:33:58 2023 -0700 fix: percent-encoding for files with %, contentIndex for non-latin chars (closes #397, closes #399) commit36548d5986
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 22 22:41:50 2023 -0700 fix: toc for cyrillic and other non-latin alphabets (closes #396) commit99dbe525d9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 22 22:27:41 2023 -0700 fix: properly lock across source and content refresh by sharing a mutex commit8b63ff882a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 22 22:14:16 2023 -0700 fix: tag support for non-latin alphabets (fixes #398) commitb991cf2ee8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 22 21:30:31 2023 -0700 fix: spa hijacks back button (closes #400) commitbb677840fc
Author: 松浦 知也 Matsuura Tomoya <me@matsuuratomoya.com> Date: Wed Aug 23 01:16:55 2023 +0900 fixed broken CJK links (#390) commitc60b3d5e34
Author: Ikko Eltociear Ashimine <eltociear@gmail.com> Date: Wed Aug 23 01:16:21 2023 +0900 fix: typo in bootstrap-cli.mjs (#394) commite10de3febf
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 21 17:01:18 2023 -0700 fix: server-handler crash from filename (closes #386) commitb69556c918
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 21 16:43:22 2023 -0700 fix: async-mutex not exclusively locking correectly commitce70571072
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 21 09:14:47 2023 -0700 docs: use canonical quartz.jzhao.xyz, update bootstrap script to point to correct hosting link commit8c943f47d6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 21 09:00:13 2023 -0700 format, update default sidepanel width commit2774e976d2
Author: 松浦 知也 Matsuura Tomoya <me@matsuuratomoya.com> Date: Tue Aug 22 00:45:47 2023 +0900 fix: opts being overriden in graph option (#384) commitbb93ac1c83
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 23:50:19 2023 -0700 docs: fix links to networked thought commit777ff51c7a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 20:48:35 2023 -0700 format commit4e42d52e16
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 20:47:07 2023 -0700 fix: ctrl + k breaking after page nav commitd0f67d9935
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 18:41:37 2023 -0700 move wss server start after http commit952d6cb3dd
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 18:08:44 2023 -0700 fix: nav event with spa off, anchor nav refresh page commit173ec240d2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 17:50:56 2023 -0700 fix: jump to anchor on deployed site triggering spa refresh commit425c9789a4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 16:59:25 2023 -0700 remove checkout step from instructions as v4 is the default branch commit7b7064ad2b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 15:38:37 2023 -0700 fix: ensure code exists inside pre before adding clipboard commitca17af4ae2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 15:02:24 2023 -0700 fix: dont show index page for folder in its own listing commit71471117c5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 14:34:00 2023 -0700 fix: ci runs on v4 commite65ea48fae
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 14:27:44 2023 -0700 fix: add async-mutex to builds on large vaults commitb99d4cd8ce
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 14:05:37 2023 -0700 recent notes css fixes commit1bb00e72bb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 13:00:33 2023 -0700 add docs for recent notes commit236130ac22
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 12:46:37 2023 -0700 css fixes, add recent notes, more robust quartz update commit5adf3c67a8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 08:57:56 2023 -0700 add engines field commit9d77edaf94
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 01:08:18 2023 -0700 fix description not being used in folder and tag listings commit0ef1b5b522
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 00:54:13 2023 -0700 update plausible url commitcfb7d1232e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 00:52:49 2023 -0700 docs: update notes for tag and folder listings commit03fd62496f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 20 00:02:41 2023 -0700 docs: note about updating default branch commitd205eb5686
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 22:19:49 2023 -0700 docs: make setting upstream more clear, docs on npx quartz restore commit96a3bfeafb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 22:04:29 2023 -0700 fix: put quotations around font commit95fb6ccfcb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 21:59:20 2023 -0700 readme fix commite262482921
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 21:59:01 2023 -0700 fix: string for aliases being treated as array of chars commiteb4d3dc5b4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 21:55:09 2023 -0700 css: fix scrollbars on windows commit90d6c1ed24
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 21:38:10 2023 -0700 add git fetch to migration instructions commit443c182890
Merge:791b8e2d
a6236d97
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 21:16:31 2023 -0700 Merge branch 'v4' of https://github.com/jackyzha0/quartz into v4 commit791b8e2d9f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 21:16:24 2023 -0700 add sponsors commita6236d97cf
Author: Matt Dunn <55315824+TheRealMattDunn@users.noreply.github.com> Date: Sun Aug 20 03:15:14 2023 +0100 Adding to Showcase page (#367) commitb1debaebff
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 18:56:45 2023 -0700 update docs commit7b8017413c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 18:04:17 2023 -0700 impl baseDir option for quartz build --serve for local testing commit6681f28af0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 16:55:36 2023 -0700 fix trailing slash causing folder listing to not fetch content correctly commit78f4cdbe10
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 16:40:02 2023 -0700 avoid 404 on icon for spa navigations with anchors commitdd47be1bc6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 16:28:44 2023 -0700 improve path resolution stability commitc874e7e937
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 19 15:52:25 2023 -0700 base path refactor to better support subpath hosting commit3201f83b70
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 18 18:24:09 2023 -0700 v4-alpha -> v4 commitd8bec631b6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 18 18:22:38 2023 -0700 update docs on github pages and syncing commit6f1f820289
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 23:39:15 2023 -0700 fix typo in docs commit8bc7a50dfa
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 21:54:42 2023 -0700 format commit569beb410b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 21:49:58 2023 -0700 ensure sync includes untracked files commit5713d30670
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 21:24:41 2023 -0700 ensure contentfolder is passed to popContentFolder commita130945443
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 21:20:15 2023 -0700 fix when symlink targ is calculated and added npx quartz restore commite10f6da011
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 21:08:26 2023 -0700 format commita7cca3242a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 21:07:40 2023 -0700 deref symlink on quartz sync commit0998bc355e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 01:58:11 2023 -0700 fix rebuild debouncing commit07a327e05a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 01:34:50 2023 -0700 fix back button in spa not working between two pages that both have hash fragments commit58d9dc0528
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 00:55:52 2023 -0700 format commit0c199975f2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 17 00:55:28 2023 -0700 various path fixes for links to extensions, fix relative paths in links commit2dc0ae279c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 16 22:09:11 2023 -0700 fix import paths commit2f6747b166
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 16 22:04:15 2023 -0700 fix relative path resolution in router and link crawling commit232652149a
Author: Sohaib <98542228+sohaibology@users.noreply.github.com> Date: Mon Aug 14 20:59:47 2023 -0400 Update hosting.md (#371) commit7bde99b4e2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 13 17:47:07 2023 -0700 fix: add trailing slash to local serving commitf1c9ca495e
Author: vintro <77507980+vintrocode@users.noreply.github.com> Date: Sun Aug 13 20:19:50 2023 -0400 docs: note about existing content at same path on different branches commit4f4b04eeb4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 12 21:18:51 2023 -0700 format docs commitd6e73f221c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 12 21:16:34 2023 -0700 fix relative path resolution logic, add more path tests commit6d9ffd6da5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 12 17:44:35 2023 -0700 404 page styling on local commitc89f8b1a9a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 12 10:33:57 2023 -0700 fix nested callout folding commit8fd496bbef
Author: Sohaib <98542228+sohaibology@users.noreply.github.com> Date: Sat Aug 12 16:52:16 2023 -0400 Update hosting.md (#368) commitaed3f5fccb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 12 10:17:07 2023 -0700 fmt commitc55d54f068
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 12 10:16:55 2023 -0700 enable rich text in callout title commit7bffc2183e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 12 00:24:30 2023 -0700 include home page in search commit827dd91847
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 12 00:03:11 2023 -0700 format, make search async commite1dd6aee86
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 11 23:55:17 2023 -0700 fix wikilinks to anchors in the same document commit83269ac26e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 11 23:40:06 2023 -0700 fix scanning for tags in content commited62ece491
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 11 23:27:59 2023 -0700 fix broken tag listing links to tags commit736c3981c4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 11 23:25:44 2023 -0700 fix emit filepaths, tag emit being overriden by content commit79e828696a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 11 22:47:50 2023 -0700 feature docs commit259d0a6d9a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 11 00:31:44 2023 -0700 more documentation commitdf02ea20d7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 10 21:32:11 2023 -0700 spacing fix commit21cc6a5da9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 10 21:29:11 2023 -0700 run prettier commitcefbca4753
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 10 21:16:07 2023 -0700 docs on making plugins commitad3f7b2d5f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 9 09:18:44 2023 -0700 format commitebf3263b7e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 9 09:10:40 2023 -0700 update npx quartz update script commitcea6834fef
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 9 00:26:33 2023 -0700 profiling, better concurrency heuristics commit68ccd1d79d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 8 22:53:01 2023 -0700 format commit49bd6bc3ff
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 8 22:52:49 2023 -0700 better concurrency debugging, --concurrency flag for npx quartz build commite4950e06a1
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 8 21:31:36 2023 -0700 fix getFileExtension missing numeric extensions (e.g. mp4) commite21f0f9bb9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 8 21:28:09 2023 -0700 change reading time to content meta commitee9ed4f287
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 8 20:36:24 2023 -0700 fix head.tsx commit2706a137a0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 8 20:18:31 2023 -0700 guide to creating components commit09d4eb0684
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 7 23:57:24 2023 -0700 fix notes commit533d68e642
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 7 23:56:50 2023 -0700 most of creating components, increase legibility of bold in article and callouts commit774a162850
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 7 21:51:23 2023 -0700 format commit2ac5dd49da
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 7 21:51:06 2023 -0700 fix regression in code block font-size boosting on safari mobile commit527ce6546e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 7 21:41:18 2023 -0700 various css fixes, fix new image loading bug when previewing, path docs commitd02af6a8ae
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 7 17:34:38 2023 -0700 architecture, fix vendor prefixing commitb4cacd5956
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 6 22:07:33 2023 -0700 format commitcd9dc6ecb5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 6 22:07:08 2023 -0700 fix css transforms for mobile commitd8d9dd22c9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 6 20:52:17 2023 -0700 fix shortest path for non-md files, mobile fix commit075ac33474
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 6 19:54:11 2023 -0700 note formatting commit3adc73a703
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 6 19:52:30 2023 -0700 docs upgrade, ci changes commit028bcec62c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 6 17:09:29 2023 -0700 mobile fixes, fix bug when linking to anchor on home, docs commitdb6054a8c1
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 5 18:00:52 2023 -0700 format, remove markdown from being procesed commita0d651d64d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 5 17:53:29 2023 -0700 reverse query param hack to re-add sourcemap support commit1da467d214
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 5 16:43:50 2023 -0700 non-admonition callout fix commit7c09627df4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 5 15:34:10 2023 -0700 improve hot reload robustness commitc402f0c385
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Aug 5 11:28:09 2023 -0700 more robust error handling, config hotreload commit9e76b257d4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 4 22:35:21 2023 -0700 fix mermaid initialization commit21a7ec2307
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 3 23:36:00 2023 -0700 bump mathjax version commit6423f85614
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 3 23:28:34 2023 -0700 fix execsync commit3a2eae0a16
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 3 23:24:34 2023 -0700 fix fetch flags commit2acfb9e870
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 3 23:08:04 2023 -0700 format, add upstream commit93986c6e7c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 3 22:29:46 2023 -0700 update pull strategy commit4877a9c934
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 3 00:08:13 2023 -0700 fix callout aliases not being used properly commit6457496b4b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 23:42:49 2023 -0700 readme fixes, force commitfdf1e2a41d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 23:29:28 2023 -0700 use checkout for pulling updates commit663c41fa41
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 23:04:26 2023 -0700 use posix style paths for all path ops commitde72dd4e4a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 22:16:46 2023 -0700 format commit5537ca41e0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 22:16:32 2023 -0700 use autostash and pull commit558a509164
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 22:11:46 2023 -0700 format commitd7842e0ce7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 22:10:13 2023 -0700 make path and globbing more platform invariant commit264ea3d544
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 20:59:56 2023 -0700 add gitattributes for windows commit0a33ff7a82
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 20:56:31 2023 -0700 fix test matrix for ci commit429f331c21
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 20:53:13 2023 -0700 make ci also run on windows, re-add css minification commit9a0f20012a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 2 00:07:41 2023 -0700 windows patches commitc8c108c7f7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 1 23:29:58 2023 -0700 change default strategy to be rebase commitaaae7d46c2
Merge:a70e846b
cbae88fc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 1 22:48:32 2023 -0700 Merge branch 'v4-alpha' of https://github.com/jackyzha0/quartz into v4-alpha commita70e846b0a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 1 22:47:16 2023 -0700 flag to allow ofm replace in html embed commitcbae88fc4e
Author: Adam Brangenberg <adambrangenberg@proton.me> Date: Mon Jul 31 05:08:32 2023 +0100 Removing redundant properties (#356) commitcc79502670
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 25 23:37:24 2023 -0700 make layouts simpler to think about commit45f9087f03
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 25 22:27:59 2023 -0700 fix checkbox/tasklist styling commit1c1a569023
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 25 21:11:06 2023 -0700 fix formatting commitcee2883c08
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 25 21:10:37 2023 -0700 nested tag support and tag index page commitc0278a8c65
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jul 24 21:54:47 2023 -0700 font loading options, optimize css commite82ba97a39
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jul 24 00:07:58 2023 -0700 actually add processed tag to frontmatter commit041a4ce7bc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jul 24 00:04:01 2023 -0700 fix watch-mode batching commit569ff1a801
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 21:53:34 2023 -0700 npm i on quartz update commit351b4ab13b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 21:41:09 2023 -0700 styling fixes for stacking order and overflow commit4811500b1b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 18:20:43 2023 -0700 make component resources a proper emitter commit236ba56be1
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 17:59:44 2023 -0700 version bump, update doc commit7c2bb4ee4c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 17:58:35 2023 -0700 bundleinfo flag, minify scripts commit8fd75ffbfd
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 17:42:00 2023 -0700 support attachments folder commit55a1fb8c41
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 17:09:12 2023 -0700 format commit9e83af04a7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 17:07:19 2023 -0700 refactor static and asset emission to be actual emitter plugins commit000eb4c3c0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 15:37:06 2023 -0700 update feature list commit5599eb590e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 14:02:57 2023 -0700 feat: process tags in content commitae2e3b463a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 11:49:26 2023 -0700 improve error handling while serving commitfd7c33c537
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 11:19:15 2023 -0700 style fixes for search bar and title on mobile commit76fdb3b4d8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 11:04:20 2023 -0700 fix styles commit27a5f7ef8e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 23 11:02:45 2023 -0700 various typography and styling fixes commitab228748ab
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 22 17:42:13 2023 -0700 oops actually use npm run check commit76fa9bbe00
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 22 17:39:10 2023 -0700 run prettier on ci commit7db2eda76c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 22 17:27:41 2023 -0700 run prettier commit2034b970b6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 22 17:26:03 2023 -0700 configure prettier commit8dd73704e6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 22 16:06:36 2023 -0700 hot content reload commitb7966ff7fa
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 20 21:51:55 2023 -0700 update features list commit01d7d8e554
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 19 23:03:59 2023 -0700 fix tag pages to emit to tag/index.html to override content and folder pages commit83d47f7aaa
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 19 22:00:44 2023 -0700 rename github action commit76c092dcf2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 19 21:59:48 2023 -0700 add custom.scss commit410fc9c8d3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 19 21:59:39 2023 -0700 quartz update and quartz sync commit8e0ba45789
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 16 10:39:35 2023 -0700 add link resolution prompt to quartz create commitf82282367e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 15 23:33:06 2023 -0700 treat _index as index commita3e4c86a4c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 15 23:05:17 2023 -0700 fix ci, disable strict path type checks by default commit3ac6b42e16
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 15 23:02:12 2023 -0700 finish path refactoring, add sourcemap + better trace support commit906f91f8ee
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 13 00:19:35 2023 -0700 base path refactor, more docs commit08f8e3b4a4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 9 19:32:24 2023 -0700 docs + various polish commitb90590b9f4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 8 14:36:02 2023 -0700 polish commitb3480bdc49
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 6 19:18:18 2023 -0700 fix styling for bullet points commit9cbacca2d4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 6 18:45:38 2023 -0700 handle dates as tags commit05d1ca01c3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 6 18:32:48 2023 -0700 handle string tags commitf7bf4038dc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 6 16:56:30 2023 -0700 fix path parsing commit465804a389
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 5 00:16:06 2023 -0700 basic docs, remove publish, add quartz create commit92ca787092
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 4 18:26:11 2023 -0700 fix default callout state commitfe2852ff25
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 4 18:08:36 2023 -0700 update package commit974b0da308
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 4 18:02:59 2023 -0700 folder and tag descriptions, re-enable relative pathing commit2a17431460
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 4 17:14:15 2023 -0700 fix popover zindex commit38cff2d670
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 4 16:48:36 2023 -0700 more visual polish, adjust colours and spacing commitab9da02c60
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 4 10:08:32 2023 -0700 fix indexing causing main thread freeze, various polish commite0ebee5aa9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 2 13:08:29 2023 -0700 various polish commit4c904d88ab
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 1 13:35:27 2023 -0700 rss + sitemap commitba9f243728
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 1 00:03:01 2023 -0700 tag and folder pages commit24348b24a9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jun 19 22:50:25 2023 -0700 fix: parsing wikilinks that have codeblock anchors, scroll to anchor commitfd5c8d17d3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jun 19 20:37:45 2023 -0700 basic search implementation commitc4cf0dcb02
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jun 18 10:47:07 2023 -0700 local and global graph commit8bfee04c8c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jun 17 16:05:46 2023 -0700 popovers commitcb89cce183
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jun 17 14:36:06 2023 -0700 basic left,right layout commitb587782450
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jun 17 13:08:06 2023 -0700 collapsible callout commit6d5491fdcb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jun 17 12:07:40 2023 -0700 collapsible toc commit917d5791ac
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Jun 16 19:41:59 2023 -0700 modern toc tweaks commit9d2024b11c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jun 12 22:41:42 2023 -0700 taglist, mermaid commit2bfe90b7e6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jun 11 23:46:38 2023 -0700 add config to components commit352075ae81
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jun 11 23:26:43 2023 -0700 refactor plugins to be functions instead of classes commitb8c011410d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Jun 9 23:06:02 2023 -0700 toc commit3a29f4c86e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Jun 9 19:58:58 2023 -0700 add custom spa solution commit59109a8c1d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 7 22:38:45 2023 -0700 add flamethrower router commit317cce9314
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 7 22:27:32 2023 -0700 generic quartz component for layout commitdde36fa558
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 7 10:52:53 2023 -0700 update gh actions commit1cb4dadf13
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jun 6 21:19:00 2023 -0700 codeblock copy commit0813f127a3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jun 6 20:58:26 2023 -0700 fix darkmode script load commit4d3579ca98
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jun 6 19:48:37 2023 -0700 darkmode scripts commit89e0311a98
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jun 6 00:00:38 2023 -0700 embeds commit700036e84c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jun 5 22:14:17 2023 -0700 callouts commit1406ee0f05
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jun 4 13:37:43 2023 -0400 update spinners commit9ad89997a5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jun 4 12:35:45 2023 -0400 multi-core builds commit4bdc17d4a1
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jun 3 15:07:19 2023 -0400 inline scripts commitfcd81353f8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jun 1 19:48:38 2023 -0400 heading linking commit04eeb2d10c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jun 1 19:05:14 2023 -0400 syntax higlighting commit42d3a7de17
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jun 1 17:35:31 2023 -0400 scss support commitc1c46ad67e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jun 1 12:33:20 2023 -0400 obsidian flavored markdown support commit3636c052eb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed May 31 17:41:44 2023 -0400 link processing commit21c007e2fc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed May 31 17:01:23 2023 -0400 rendering, link resolution, asset copying commitad6ce0d73f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 30 08:02:20 2023 -0700 plugin integration round 2 commita757521313
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun May 28 17:44:08 2023 -0700 base setup commit7b1da7a845
Author: BSD-Yassin <103321053+BSD-Yassin@users.noreply.github.com> Date: Thu Apr 27 20:12:56 2023 +0200 i18n: Update fr.toml (#313) commite482fa1097
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Apr 6 15:06:01 2023 -0700 fix: graph and tooltip sometimes not showing commitba7a968881
Author: Mattia Ippoliti <Ippolitimattia@gmail.com> Date: Sat Apr 1 22:50:08 2023 +0200 fix: padding for empty title callouts (#308) commitdb27557aa3
Author: Md Jawad Noor Asif <jawad.asif.bd@gmail.com> Date: Thu Mar 30 17:14:06 2023 +0600 fix: search highlight not showing because for trailing slash (#306) commitb7c305e002
Author: Mike Walton <walton.myke@gmail.com> Date: Wed Mar 22 22:56:20 2023 -0700 adding myself to the showcase (#301) commit74fe4d6813
Author: Daniel Lazaro <daniel@dlazaro.ca> Date: Sat Mar 18 12:20:56 2023 -0400 docs: Update link to callouts documentation (#300) commitd6c31595b3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Mar 16 10:33:01 2023 -0700 deps: bump hugo-obsidian commitaa5ab03d4a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Mar 2 09:14:29 2023 -0800 docs: update to account for github changes commitecba6071b8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Feb 25 13:04:15 2023 -0800 deps: bump hugo-obsidian commit983efab94c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Feb 12 16:46:11 2023 -0800 fix: recent notes partial sorting commit10e41743e5
Author: Dev Uni <wlwhsvkdlxh@gmail.com> Date: Wed Feb 8 01:38:20 2023 +0900 fix: Bad UI due to head.html (#284) commitbde44fadf2
Author: Simon Späti <simu@sspaeti.com> Date: Tue Feb 7 09:16:15 2023 +0100 feat: Adding Twitter and Social image preview including description (#207) commit6885651f7b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Feb 6 12:58:34 2023 -0800 feat: max-width for large screens commit7df2bb6f5e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Feb 5 12:01:49 2023 -0800 fix: fix duplicate link click tracking commit11959de11c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Feb 5 11:34:39 2023 -0800 feat: add more plausible events commita73aca8ed9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Feb 5 10:39:58 2023 -0800 feat: switch from GA to Plausible for analytics commit93610e232b
Author: Adam Brangenberg <adambrangenberg@proton.me> Date: Wed Feb 1 21:34:18 2023 +0100 feat: Remove leading slash of folders in graph view (#282) commit712dab5c8c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jan 31 11:00:28 2023 -0800 docs: remove broken links from showcase commit77b3907b23
Author: Olivér Falvai <ofalvai@gmail.com> Date: Tue Jan 31 19:48:20 2023 +0100 docs: Clarify Obsidian settings (#280) commit8fc63586c4
Author: herrwinfried <ozgurarslln@icloud.com> Date: Sun Jan 29 23:14:11 2023 +0300 feat: Added Turkish translation (#275) commit24c9777a52
Author: Apoorv Khandelwal <mail@apoorvkh.com> Date: Sat Jan 21 10:01:05 2023 -0800 feat: Embedding multimodal assets (#274) commit7a8811a184
Author: Quadrubo <71718414+Quadrubo@users.noreply.github.com> Date: Wed Jan 18 17:25:01 2023 +0100 added the liveReloadPort as an option for docker (#272) commiteb2f6aeca8
Author: chaosarium <38693485+chaosarium@users.noreply.github.com> Date: Mon Jan 9 17:14:11 2023 -0500 Fix callout behaviour inconsistent with Obsidian (closes #168) (#268) commitb78008532f
Author: Md Jawad Noor Asif <jawad.asif.bd@gmail.com> Date: Tue Jan 10 04:12:52 2023 +0600 feat: Added Bangla translations (#266) commitc5b103c85f
Author: Md Jawad Noor Asif <jawad.asif.bd@gmail.com> Date: Wed Jan 4 09:10:25 2023 +0600 fix: fix unicode broken tags (#261) commit614a6222a1
Author: Adam Brangenberg <adambrangenberg@proton.me> Date: Thu Dec 29 16:43:41 2022 +0100 refactor: General performance/style improvements (#262) commitdc43737896
Author: chaosarium <38693485+chaosarium@users.noreply.github.com> Date: Sat Dec 24 12:10:59 2022 -0500 fix edge cases link processing (#258) Fixes https://github.com/jackyzha0/quartz/issues/176 commitea37486309
Author: toof <toof@toof.jp> Date: Sun Dec 25 00:38:49 2022 +0900 fix: fix misspelling (#259) commitc1b0eafce6
Author: chaosarium <38693485+chaosarium@users.noreply.github.com> Date: Thu Dec 22 13:34:21 2022 -0500 feat: Added simplified Chinese translations (#257) commitce5df837f5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Dec 3 21:03:12 2022 -0800 feat: latex in search results commit4cd6f7efdf
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Nov 30 18:00:12 2022 -0800 fix: text highlighting commit5a7936e23a
Author: Apoorv Khandelwal <mail@apoorvkh.com> Date: Wed Nov 30 17:41:05 2022 -0800 fix: Replacing "internal-link broken" with link to asset (#232) commit5fd707714f
Author: Jon Erling Hustadnes <jonerling.hustadnes@gmail.com> Date: Sun Nov 27 19:55:43 2022 +0100 feat: Added Norwegian localization (#242) commit717a13a580
Author: Filippo Andrea Sighinolfi <83777862+Sighi-04@users.noreply.github.com> Date: Sun Nov 27 19:55:13 2022 +0100 feat: Added italian localization in i18n/it.toml (#239) commit5f3d430699
Author: Brendan Ang <53790951+bbawj@users.noreply.github.com> Date: Mon Nov 28 02:53:52 2022 +0800 feat: add support for mermaid diagrams (#244) commit66f3e249fe
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Nov 23 08:34:19 2022 -0800 fix: only run docker publish on main repository commite374e3abd4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Nov 21 23:36:27 2022 -0800 fix: jump to search for operand commitf08a76a738
Author: SafEight <43656822+SafEight@users.noreply.github.com> Date: Mon Nov 21 21:05:46 2022 +0000 fix: External links ending in .md don't get trimmed (#236) Co-authored-by: SAF <saf@saf.saf> fixes https://github.com/jackyzha0/quartz/issues/229 commitd80f6946c8
Author: Morgan Gallant <morgan@morgangallant.com> Date: Tue Nov 22 01:54:45 2022 +0900 fix: Semantic Search: Use Operand Beta API (#235) commit120d104230
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Nov 20 15:14:48 2022 -0800 update config for search commite9aa6ae9e7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Nov 20 15:09:58 2022 -0800 feat: docker docs, semantic search alpha commitc12af32a5a
Author: Apoorv Khandelwal <apoorv.khand@gmail.com> Date: Sun Nov 20 17:03:53 2022 -0500 feat: Dockerfile and automated container build (#230) commitde2b6b9a1b
Author: SafEight <43656822+SafEight@users.noreply.github.com> Date: Sat Nov 19 21:17:55 2022 +0000 feat: Replace == with <mark> (#234) Co-authored-by: SAF <saf@saf.saf> commit7f9f58860d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 19 11:18:57 2022 -0800 feat: allow enableToc to override default no TOC on a per-page basis commit151b9851d6
Author: jet457 <abhmul@gmail.com> Date: Sat Nov 19 13:10:41 2022 -0600 docs: add Abhijeet's math-wiki to the showcase (#228) commitd56a58044d
Author: saucecoat <43880196+saucecoat@users.noreply.github.com> Date: Sun Oct 30 06:08:44 2022 +0000 Added German translation (#223) commit689201bfbd
Author: Conor <hzk@konor.fr> Date: Wed Oct 26 18:12:35 2022 +0200 feat: Add French translation (#221) commit9b72edcd9c
Merge:8704edcc
0a602eda
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Oct 25 13:14:13 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit8704edcca2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Oct 25 13:14:06 2022 -0700 deps: bump ubuntu version (closes #218) commit0a602eda1b
Author: Evan Cater <evan.ecater@gmail.com> Date: Mon Oct 24 12:13:35 2022 -0400 fix euler's identity (#220) commit72571a7588
Author: Javier Zaleta Martínez <94091554+javierzaleta@users.noreply.github.com> Date: Tue Oct 18 19:25:55 2022 -0500 feat: Add Spanish translation (#217) commit3409a49f15
Author: Charles Chamberlain <charlesetc@users.noreply.github.com> Date: Sun Oct 16 12:43:43 2022 -0400 fix: Apply monospace style to all meta in a popover (#216) commit666ffebe90
Author: Pavol Komlos <62595149+plundration@users.noreply.github.com> Date: Wed Oct 12 17:21:28 2022 +0200 Decode the heading id from split link (#214) commit8ea1525df4
Author: Seth <37915796+iSaluki@users.noreply.github.com> Date: Mon Oct 3 19:45:54 2022 +0100 Add SethMB Work (#203) commitdd11d56dd9
Merge:cd7e2088
169ef442
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Sep 23 10:17:34 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commitcd7e2088d5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Sep 23 10:17:28 2022 -0700 feat: hide TOC when no headers (closes #204) commit169ef442b9
Author: Simon Späti <simon@airbyte.io> Date: Wed Sep 14 19:05:51 2022 +0200 Adding reference projects (#196) Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commit8e3042df49
Author: DongDong Chen <cdd2zju@gmail.com> Date: Thu Sep 15 01:05:20 2022 +0800 add my showcase : oldwinterの数字花园 (#192) commit2145e92b00
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Sep 12 11:08:07 2022 -0700 fix: make latex rendering size more simialr to obsidian commite6c7a4e1e2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Sep 11 18:03:55 2022 -0700 fix: latex rendering bugs + patch for #195 commitca84da5b31
Author: Nikola Georgiev <42315052+nikolageorgiev2000@users.noreply.github.com> Date: Mon Sep 12 01:05:14 2022 +0100 feat: Hide full path to file in Wikilinks by default (#195) commit0d1670adba
Merge:5c770f96
ce55eca7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 29 14:23:19 2022 -0400 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit5c770f965a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 29 14:23:04 2022 -0400 Update Quartz version in documentation commitce55eca73b
Author: Andrii Yefremov <56955307+decatetsu@users.noreply.github.com> Date: Mon Aug 29 21:15:18 2022 +0300 Add Ukrainian translation (#191) commit591c4813ec
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Aug 28 01:09:52 2022 -0400 deps: bump hugo-obsidian version commit83e7aec3c9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 24 00:45:08 2022 -0400 fix: tag list styling commit25ba1159ad
Author: Youssif Shaaban Alsager <ysh-alsager@hotmail.com> Date: Wed Aug 24 05:32:40 2022 +0200 feat: Add internationalization (i18n) support (#182) commite38eaa94d6
Author: Vincent Huang <vincenthuang75025@yahoo.com> Date: Sat Aug 20 20:31:06 2022 -0500 Popover preview should show relevant heading (#180) commita78926ede5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 11 11:42:16 2022 -0700 feat: link previews to page-list (closes #173) commit5c76d8dad9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 5 11:08:52 2022 -0700 fix: make callout detection case-insensitive (closes #171) commit3dcc1f1106
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 5 11:04:01 2022 -0700 feat: better graph scaling (closes #170) commitff770927fd
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 4 14:50:24 2022 -0700 style: _callouts.scss simplification (#169) commit7ffc907907
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Aug 3 23:46:55 2022 -0700 fix: CJK search (closes #163) commit6dd4c64a4c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Aug 1 07:59:49 2022 -0700 fix: highlights being stripped in non-semantic search mode commit8fc6b8e28e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 31 18:21:17 2022 -0700 docs: update, re-added debounce commitb10b23a47b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 31 18:02:06 2022 -0700 docs: add documentation for Operand Search, remove debounce commit23380d0519
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 31 16:55:25 2022 -0700 fix: title not being selected properly, bump hugo-obsidian for uri fix commitdd047305af
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 31 12:33:36 2022 -0700 deps: bump hugo-obsidian to fix bug of writing to non-existent directory during build commit54a8fd4a56
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 31 12:24:53 2022 -0700 deps: bump hugo-obsidian to properly copy linkmap commit5ef9aad501
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 31 12:16:36 2022 -0700 feat: add support for semantic search using operand commit14b89105dc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 31 10:54:23 2022 -0700 refactor: move search utils to util.js commit93d039fe7c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 31 10:14:36 2022 -0700 deps: bump hugo-obsidian version commit234c707a93
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 30 18:46:19 2022 -0700 docs: improve scss structure and admonition styling, update docs commit728d8529ec
Author: Emile Bangma <ewjbangma@hotmail.com> Date: Sun Jul 31 02:29:26 2022 +0200 Support Admonition callouts (#166) (closes #88) commite142f37e8d
Merge:d747b19e
1f3da4b8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 19 09:03:26 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commitd747b19e61
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 19 09:03:19 2022 -0700 docs: copy edits commit1f3da4b829
Author: Pranav M <pranavm7@outlook.com> Date: Mon Jul 18 11:45:36 2022 -0400 feat: edit the clipboard button to change border colour on success (#162) Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commite15e39155d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Jul 15 14:26:31 2022 -0700 fix: give precedence to date created over last modified if defined (#101) commitdff5ae0d4d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 14 13:09:21 2022 -0700 style: improve header anchor styling commitb2555ced61
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 14 12:02:35 2022 -0700 feat: add description section to section/term/taxonomies, fix header margin commit7ccff2cf3d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 14 11:49:47 2022 -0700 fix: styling on page-list for smaller screens commite0b6606d50
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 14 10:38:34 2022 -0700 fix: make section-li scss more generic commitd7a42a2fd7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jul 14 10:30:07 2022 -0700 feat: improve styling for lists, fix anchor offset commit422b6cc25b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 13 23:51:33 2022 -0700 feat: css typography improvements commit22c8981bb9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 13 23:37:54 2022 -0700 feat: css refactor for easy font change commit8b2a82a96a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 13 22:27:13 2022 -0700 fix: change / to use base url commit81af8c459b
Author: y1450 <107429941+y1450@users.noreply.github.com> Date: Thu Jul 14 00:02:11 2022 +0200 fix: remove console log (#159) commitffe22689eb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 13 15:01:50 2022 -0700 feat: use floating-ui for better popover positioning commitc1b8fe1221
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jul 13 14:32:32 2022 -0700 feat: restyle search icon commitb7a619bbd7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 12 14:37:10 2022 -0700 fix: tabsize not being respected commit74993d19b7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jul 5 15:42:57 2022 -0700 docs + fix: broken partial and description of enableGitHubEdit commit25a4d3b6e1
Author: rphla <101242699+rphla@users.noreply.github.com> Date: Wed Jul 6 06:39:29 2022 +0800 Add GitHub "edit" button (#157) commitaaf31f419e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jul 3 11:50:13 2022 -0700 fix: copy code block logic for non code pages commitf54df35767
Author: Geoffrey Garrett <g.h.garrett13@gmail.com> Date: Sun Jul 3 20:42:35 2022 +0200 Copy to clipboard feature for code block (#152) Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commit015ed4cfa2
Author: Aiden Bai 白宇彤 <aiden.bai05@gmail.com> Date: Sat Jul 2 19:40:18 2022 -0700 Fix `width: auto` for SPA routing (#156) commita8137edf24
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Jul 2 17:14:17 2022 -0700 fix: adjust weird colours for err highlighting commiteda370334a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Jul 1 11:27:50 2022 -0700 fix: image scaling for md-style links (closes #155) commitd3e20b8b94
Author: Geoffrey Garrett <g.h.garrett13@gmail.com> Date: Fri Jul 1 20:03:52 2022 +0200 Added optional rendering of code block titles (#148) commit8d7a7b712f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Jul 1 11:02:42 2022 -0700 fix: non-SPA fn defs (closes #154) commit0896814959
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 29 17:35:29 2022 -0700 docs: remove test image from hosting commit8b2fba895a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 29 17:34:05 2022 -0700 feat: image scaling (closes #131) commite884f4927f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 29 17:17:53 2022 -0700 fix: anchor formatting (closes #141) commit2b0482ae4c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 29 17:03:41 2022 -0700 docs: fix page weight commit8a100edeb8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 29 16:57:36 2022 -0700 docs: polish and update commit200c605142
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 29 16:16:06 2022 -0700 feat: enable raw html by default (fixes #143) commitf2078ee621
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jun 29 16:12:33 2022 -0700 fix: prefix images with base url for non-root quartz commit916c51c19c
Merge:72941965
67a7ba37
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jun 28 23:21:25 2022 -0700 Merge pull request #150 from aidenybai/bump-million commit67a7ba37e8
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue Jun 28 21:43:28 2022 -0700 Bump million to 1.11.3 commit72941965ab
Merge:34b03537
b732293f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jun 27 16:27:57 2022 -0700 Merge pull request #146 from geoffreygarrett/hugo commitb732293f65
Author: Geoffrey Garrett <g.h.garrett13@gmail.com> Date: Tue Jun 28 01:21:22 2022 +0200 fix(head.html): Adds robustness to `config.yaml` favicon definitions Initially assumed that `href` definitions should have `/...` as their pattern, and `baseURL` would always end with `/`, however the omission of `/` as the prefix of the former and suffix of the latter simultaneously, would result in broken favicon paths. Final comment: `..///...` is not breaking, which is worst case scenario with this fix. commit7070a1992a
Author: Geoffrey Garrett <g.h.garrett13@gmail.com> Date: Tue Jun 28 01:15:33 2022 +0200 docs(config.md): Fixed multi-favicon examples and general favicon explanation throughout commit997937af5a
Author: Geoffrey Garrett <g.h.garrett13@gmail.com> Date: Tue Jun 28 00:45:48 2022 +0200 docs(config.md): Added short explainer on favicons commita334b45b17
Author: Geoffrey Garrett <g.h.garrett13@gmail.com> Date: Mon Jun 27 22:05:35 2022 +0200 docs(content/notes/config.md): Adds documentation for the new favicon support commit473ea2c66f
Author: Geoffrey Garrett <g.h.garrett13@gmail.com> Date: Mon Jun 27 22:04:32 2022 +0200 feat(layouts/partials/head.html): Adds general favicon support with dict and string input format commit34b0353797
Merge:dbd4fb75
52a185f7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jun 7 08:43:52 2022 -0700 Merge pull request #140 from DhammaCharts/hugo commit52a185f73b
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Mon Jun 6 16:49:01 2022 +0100 change enableGlobalGraph to false commit69c74ca6b5
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Mon Jun 6 16:48:16 2022 +0100 minor adjustment commitab809249c8
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Mon Jun 6 16:42:53 2022 +0100 Update layouts/partials/head.html Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commit84c75d0546
Merge:a275123b
dbd4fb75
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Mon Jun 6 12:56:47 2022 +0100 Merge branch 'hugo' into hugo commitdbd4fb7595
Merge:84c6e1ef
a1293f82
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Jun 3 10:59:18 2022 -0700 Merge pull request #139 from aidenybai/prerender-latex commita275123be2
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Thu Jun 2 08:35:28 2022 +0100 better font behaviour commitc88f31c364
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Thu Jun 2 08:16:02 2022 +0100 change to object destructuring for drawGraph() arguments commitd261655d96
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Thu Jun 2 07:49:09 2022 +0100 remove unnecessary ternary commitc0800a8749
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Thu Jun 2 07:45:44 2022 +0100 change baseURL back to original commitac0dd50c04
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Wed Jun 1 21:30:40 2022 +0100 uncomment window.Million commite809896338
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Wed Jun 1 21:22:31 2022 +0100 increase scale commit19606ba63d
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Wed Jun 1 21:19:03 2022 +0100 add www. commit1e237ef677
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Wed Jun 1 20:15:44 2022 +0100 change baseURL commit5a1fbc9374
Author: DhammaCharts <100090806+DhammaCharts@users.noreply.github.com> Date: Wed Jun 1 13:49:27 2022 +0100 Improve graph display, options and ability to have a global graph on the home page, local graphs on subpage. commita1293f820a
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Sun May 29 20:40:44 2022 -0700 Prerender latex commit84c6e1efed
Merge:775a1b24
8673a7bc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat May 28 23:27:54 2022 -0700 Merge pull request #138 from aidenybai/add-footer-config commit8673a7bc3d
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Sat May 28 22:52:18 2022 -0700 Add option to toggle footer commit775a1b2490
Merge:cbc2bea4
006b74ec
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri May 27 19:21:05 2022 -0700 Merge pull request #137 from aidenybai/fix-non-spa-routing commit006b74ec6f
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 18:45:42 2022 -0700 Fix formatting commit8aba612a00
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 18:42:01 2022 -0700 Fix non-spa fallback commitcbc2bea413
Merge:ba586adc
ae240ff8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri May 27 18:32:49 2022 -0700 Merge pull request #136 from aidenybai/custom-progress-bar-color commitae240ff82c
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 18:31:36 2022 -0700 Remove redundant CSS rule commitba586adc76
Merge:232bd2f0
159deabf
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri May 27 17:14:55 2022 -0700 Merge pull request #135 from aidenybai/bump-million commit159deabfe1
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 16:14:17 2022 -0700 Bump to 1.9.6 commit44984cdaf4
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 13:27:13 2022 -0700 Add support for progress bar commit683cb53cbd
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 13:19:19 2022 -0700 Bump million to 1.9.5 commit232bd2f016
Merge:0293c122
e0fd9570
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri May 27 11:01:20 2022 -0700 Merge pull request #134 from aidenybai/add-prefetching-within-graph commite0fd9570d7
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 09:49:28 2022 -0700 Bump million to 1.9.4 commitbc32bbeaed
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 09:02:01 2022 -0700 Bump milliomn to 1.9.3 commitefb6c7845f
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 08:40:00 2022 -0700 Add prefetch to graph commitbd316d8249
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Fri May 27 08:39:44 2022 -0700 Bump million to 1.9.2 commit0293c12217
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon May 23 22:25:13 2022 -0700 feat: recent posts section/partial commit0439c163a0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri May 20 16:50:56 2022 -0400 fix: js not executing if spa disabled commit0b6711c218
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat May 14 16:47:50 2022 -0400 fix: tag boxes overlapping for content with many tags (closes #130) commited9a8efd1f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu May 5 21:11:23 2022 -0400 fix inline link highlighting, safer latex render commite302f6c423
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu May 5 20:35:32 2022 -0400 fix: more generic style to match bad nesting generated by popover interp commitb21b27d1d3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu May 5 20:30:55 2022 -0400 fix: clean wikilinks and render latex in popover commit364aee36fc
Merge:cea0f3eb
8b855b52
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu May 5 01:03:09 2022 -0400 fix: merge conf commitcea0f3eb74
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu May 5 00:58:50 2022 -0400 feat: contextual backlinks (closes #106) commit8b855b522a
Merge:b67a389b
7b3696b8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed May 4 11:40:38 2022 -0400 Merge pull request #125 from aidenybai/fix-latex commit7b3696b877
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Wed May 4 08:39:25 2022 -0700 Remove pnpm debug log commitb4ff12ca0b
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Wed May 4 08:10:59 2022 -0700 Fix latex commitb67a389bea
Merge:a0997444
2b5c03c9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 3 13:59:02 2022 -0400 Merge pull request #124 from aidenybai/hugo commit2b5c03c972
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 10:55:45 2022 -0700 Remove redundant URL construction commitaaed5dc1f1
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 10:54:39 2022 -0700 Support /path root sites commit1a5d158fce
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 10:38:41 2022 -0700 Support active node with other data at end of url commita09974446d
Merge:03742621
9fc71603
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 3 13:21:32 2022 -0400 Merge pull request #123 from aidenybai/fix-popover commit9fc71603ba
Merge:d38f9bec
3789df80
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 10:18:41 2022 -0700 Merge commitd38f9bec70
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 10:16:09 2022 -0700 Rename API and generalize router API commit771ebd8031
Merge:e4cc625c
03742621
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 10:07:38 2022 -0700 Merge commite4cc625c33
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 09:34:27 2022 -0700 Add future note about init function commit3789df80e4
Merge:32c79a56
03742621
Author: Aiden Bai 白宇彤 <aiden.bai05@gmail.com> Date: Tue May 3 09:33:00 2022 -0700 Merge branch 'hugo' into fix-popover commit037426217c
Merge:6e6dd4cb
e646cdb0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 3 12:29:26 2022 -0400 Merge pull request #122 from aidenybai/fix-active-graph-node commite646cdb0be
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 09:27:25 2022 -0700 Use explicit regex for trailing slash trim commit8d092a3a4a
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 09:22:51 2022 -0700 Remove unnecessary 'url' argument in graph.html commit32c79a561f
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 09:21:44 2022 -0700 Remove unnecessary 'url' argument in graph.html commit3c660dd9b5
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 09:20:01 2022 -0700 Remove unnecessary 'url' param in drawGraph commit4cca3c1f2d
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 09:04:15 2022 -0700 Peg router version commit9d3bbd6076
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 08:53:18 2022 -0700 Fix active node on graph commit9c71f07355
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 08:48:35 2022 -0700 Enable config for testing commit77485b754d
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Tue May 3 08:47:42 2022 -0700 Fix popover commit6e6dd4cb0b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 3 10:57:20 2022 -0400 fix: trim trailing slash when calculating popover commit81fe2d2493
Merge:24d08d58
321e19dc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 3 10:44:56 2022 -0400 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit24d08d580d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 3 10:43:22 2022 -0400 cfg: make SPA optional commit321e19dc41
Merge:12d33619
97607c3c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 3 09:48:50 2022 -0400 Merge pull request #121 from benbohmer/patch-1 commit12d33619a2
Merge:fc89ff26
4197ad46
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue May 3 09:47:48 2022 -0400 Merge pull request #120 from straightupjac/fix/github-info commit97607c3ca5
Author: benbohmer <103453816+benbohmer@users.noreply.github.com> Date: Tue May 3 09:10:45 2022 +0200 fix: keep / at end of URL to avoid redirects Removed strings.TrimRight "/" in line 10 to keep the trailing slash at the end of URLs in regular links. This avoids having every single internal link being a 301 redirect. commit4197ad460a
Author: straightupjac <jdc.jaclyn@gmail.com> Date: Tue May 3 01:51:15 2022 -0400 fix github info commitfc89ff2680
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon May 2 13:00:41 2022 -0400 fix: broken semi and graph min-height commite9a33c04b5
Merge:9ba0a4b3
b0e15e0c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon May 2 12:56:44 2022 -0400 fmt: remove semis for good commitb0e15e0cbc
Merge:66304da0
f1b85fb6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon May 2 12:19:26 2022 -0400 Merge pull request #118 from aidenybai/add-router commit9ba0a4b34f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon May 2 12:14:51 2022 -0400 fmt: remove semis :) commitf1b85fb6d9
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Mon May 2 09:10:40 2022 -0700 Fix clarification comment commit66304da027
Merge:416dc0b8
87144fca
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon May 2 12:06:57 2022 -0400 Merge pull request #119 from aidenybai/add-prettier Add prettier config commit40d216759c
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Mon May 2 09:05:02 2022 -0700 Expand template commit5c602ab16f
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Mon May 2 09:04:36 2022 -0700 Add clarification comments commit87144fca21
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Mon May 2 08:57:25 2022 -0700 Use semi: false for prettier config commita9523dd39b
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Sun May 1 22:08:14 2022 -0700 Add prettier config commitbcb166c21c
Author: Aiden Bai <aiden.bai05@gmail.com> Date: Sun May 1 22:06:33 2022 -0700 Add router commit416dc0b85c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 30 13:13:30 2022 -0700 fix: add update for local hugo-obsidian on make update commitb8a660e208
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 30 13:10:12 2022 -0700 feat: copyable header anchors (fixes #86) commitec86cca97b
Merge:87b5a7a2
f7027e7e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Apr 28 15:53:57 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit87b5a7a251
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Apr 28 15:49:16 2022 -0700 feat: show graph titles on zoom (fixes #92) commitc8d390dbc5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Apr 28 13:45:29 2022 -0700 fix: always hide popover on mobile (fixes #104) commit3c7ece5405
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Apr 28 10:48:31 2022 -0700 fix: append trailing slash, fixes #111 commitf7027e7ecd
Merge:f05ff5e6
0cfd93c5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Apr 20 09:20:21 2022 -0700 Merge pull request #108 from exu3/patch-1 commit0cfd93c57c
Author: Ella <git@ella.cx> Date: Sun Apr 17 02:11:17 2022 -0700 Fix another typo commit3f8c473678
Author: Ella <git@ella.cx> Date: Sun Apr 17 01:33:16 2022 -0700 Fix typo: recomment -> recommend commitf05ff5e62d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 23:19:33 2022 -0700 fix: add dropshadow to popover, cleanup animation commit12ed9722d8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 22:43:11 2022 -0700 fix: popover selection wrongly including line breaks commit887d4d4f5e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 21:40:59 2022 -0700 deps: bump hugo -> v0.96.0 commitf9c7cdf928
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 20:44:39 2022 -0700 fix: check for src before attempting to add popover commit2d55b6ac2e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 18:07:40 2022 -0700 fix: missing whitespace chomp in link render hook commitd5884aedb7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 14:14:19 2022 -0700 fix: wikilink patch not applying to transformed text like apostrophes commit66eaa444a4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 14:08:36 2022 -0700 fix: wikilink image relURL for images with spaces commit0ddc48a452
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 13:47:24 2022 -0700 fix: wikilink-like text in code fences #95, #97 commitcd19159c53
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 12:47:28 2022 -0700 feat: wikilink img support commit7808c66c4d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 09:41:13 2022 -0700 fix: align footer links commita7abc6ab96
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 00:09:56 2022 -0700 docs: make update command and clarify update steps/potential danger commit9509a64354
Merge:53242b1e
3ce6944c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 00:02:48 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit53242b1e57
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Apr 5 00:02:37 2022 -0700 add update target to Makefile commit3ce6944c18
Merge:e2455050
3583265f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Apr 4 23:56:28 2022 -0700 Merge pull request #93 from meleu/patch-3 commit3cec4fd950
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Apr 4 23:30:28 2022 -0700 update screenshot commite245505082
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Apr 4 23:25:24 2022 -0700 feat: hide toc for short notes commitfc4b9ded76
Merge:3781b677
27c4761f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Apr 4 23:20:43 2022 -0700 Merge pull request #94 from meleu/patch-4 commit27c4761fe0
Author: meleu <meleu@users.noreply.github.com> Date: Mon Apr 4 20:15:40 2022 -0300 link to home goes to baseURL commit3583265f80
Author: meleu <meleu@users.noreply.github.com> Date: Mon Apr 4 17:30:23 2022 -0300 docs: warn about possible lost of customization commit3781b67707
Merge:1613511f
671fe053
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Apr 4 13:08:42 2022 -0700 Merge pull request #91 from meleu/patch-2 commit671fe05312
Author: meleu <meleu@users.noreply.github.com> Date: Mon Apr 4 17:07:43 2022 -0300 padding and border-radius matching bottom cards Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com> commit1613511f39
Merge:acab4887
575288ec
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Apr 4 09:45:05 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commitacab488784
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Apr 4 09:44:58 2022 -0700 re-add obsidian file commitff91dcd196
Merge:a287d112
575288ec
Author: meleu <meleu@users.noreply.github.com> Date: Sun Apr 3 22:14:12 2022 -0300 Merge branch 'jackyzha0:hugo' into patch-2 commita287d11246
Author: meleu <meleu.dev@gmail.com> Date: Sun Apr 3 22:12:55 2022 -0300 add a collapsible ToC commit575288ece9
Merge:25b5ac43
1d9c0e4a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Apr 3 17:57:46 2022 -0700 Merge pull request #88 from meleu/patch-2 commit25b5ac43dd
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Apr 3 17:43:37 2022 -0700 fix: favicon not showing on non-root domain #89 commit1d9c0e4a44
Author: meleu <meleu.dev@gmail.com> Date: Sun Apr 3 16:31:29 2022 -0300 use "enableToc: false" commite62d512d95
Author: meleu <meleu.dev@gmail.com> Date: Sun Apr 3 16:29:10 2022 -0300 disable ToC if frontmatter has "enableToc: false" commit8f15c5f8c1
Author: meleu <meleu@users.noreply.github.com> Date: Sun Apr 3 16:22:32 2022 -0300 disable ToC if enableToc: false commitefeaf9b49c
Merge:91c4e3fb
22f11711
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Apr 3 11:44:39 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit91c4e3fb3a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Apr 3 11:42:42 2022 -0700 fix: multiline code block #87 commit22f11711b2
Merge:16b177ce
5c3ef884
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Apr 3 08:17:13 2022 -0700 Merge pull request #85 from meleu/patch-1 Ah my git was being really weird with cases :')) thank you commit5c3ef884c7
Author: meleu <meleu@users.noreply.github.com> Date: Sun Apr 3 11:19:21 2022 -0300 duplicated file commit16b177ce66
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 21:04:20 2022 -0700 README update commit14c6181d24
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 20:37:42 2022 -0700 bump hugo version v0.82 -> v0.92.2 commite6e04c03c4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 20:34:55 2022 -0700 fix latex misrendering commit146e975932
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 20:21:16 2022 -0700 bump hugo obsidian, fix backlinks for subpathed quartz, update homepage commitc117e38899
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 20:06:31 2022 -0700 feat: wikilinks implementation commit4fd983277e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 17:38:39 2022 -0700 fix: cjk support + demo page commitcc86136bcb
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 17:00:14 2022 -0700 feat: basic latex support commit8e083d4a93
Merge:c51573ef
03b574b1
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 14:53:05 2022 -0700 Merge pull request #83 from meleu/patch-2 commit03b574b160
Author: meleu <meleu.dev@gmail.com> Date: Sat Apr 2 18:51:45 2022 -0300 cleanup commita469653f75
Author: meleu <meleu.dev@gmail.com> Date: Sat Apr 2 18:50:58 2022 -0300 separate contact links semantically commitc51573efa9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 13:34:26 2022 -0700 feat: grey out broken links commit902d0f2a0f
Merge:1ddd15af
9c5ecccf
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 12:59:47 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit1ddd15afc6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Apr 2 12:59:38 2022 -0700 fix: non-unicode character in popover and search #67, #68 commit16f8cd7100
Author: meleu <meleu@users.noreply.github.com> Date: Sat Apr 2 13:37:12 2022 -0300 separate links with ​ commit9c5ecccf25
Merge:3674df48
e3cd531c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Apr 1 14:17:35 2022 -0700 Merge pull request #82 from meleu/patch-1 commite3cd531c53
Author: meleu <meleu@users.noreply.github.com> Date: Fri Apr 1 18:13:49 2022 -0300 fix custom.scss path commit3674df48b8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Apr 1 10:13:01 2022 -0700 fix pagination styling commit9e8c5587e4
Merge:6605b13b
6edc9798
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Mar 31 23:16:00 2022 -0700 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit6605b13b86
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Mar 31 23:15:54 2022 -0700 more troubleshooting, backlinks reference private page fix commit6edc979896
Merge:54a68e6e
fc439224
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Mar 21 09:15:35 2022 -0700 Merge pull request #71 from siyangsun/patch-1 commitfc43922445
Author: Siyang <siyangsun2007@gmail.com> Date: Sun Mar 20 22:37:05 2022 -0700 add to showcase and fix link to file commit54a68e6e5c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Mar 18 10:53:39 2022 -0700 patch image commita6ab2f92ef
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Mar 16 17:54:24 2022 -0700 add update commitfda481fbb9
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Mar 15 01:12:08 2022 -0700 fix: bump hugo-obsidian version to account for contentIndex paths on windows commit94e987dab5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Mar 15 00:37:56 2022 -0700 feat: better titles for empty pages #61 commite981c76ed4
Merge:651bfc5c
f70128a3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Mar 9 10:11:36 2022 -0800 Merge pull request #65 from claudio4/fix-text commitf70128a3de
Author: Claudio Yanes <me@claudio4.com> Date: Wed Mar 9 17:58:01 2022 +0000 Prevent overflow of long links and words When a word (or any string withtout breakpoints (spaces, dashes....), making links the most common place where this becamoes an issue) is wider than its container, the text will simply overflow any container, including the viewport. This commit fixes this behaviour by making the word-drap strategy of the browser more aggresive. commit651bfc5cd2
Merge:90727099
60794201
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Mar 7 10:45:07 2022 -0800 Merge pull request #62 from claudio4/hugo commit6079420178
Merge:978d5ca1
b96c60ed
Author: Claudio Yanes <me@claudio4.com> Date: Mon Mar 7 18:28:14 2022 +0000 Merge branch 'jackyzha0-hugo' into hugo commitb96c60edfc
Merge:978d5ca1
90727099
Author: Claudio Yanes <me@claudio4.com> Date: Mon Mar 7 18:27:45 2022 +0000 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into jackyzha0-hugo commit978d5ca1ae
Author: Claudio Yanes <me@claudio4.com> Date: Mon Mar 7 18:25:02 2022 +0000 Format JS commit907270992d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Mar 4 23:55:07 2022 -0800 fix: hide popover on mobile to prevent overflow commit6f9283e95b
Author: Claudio Yanes <me@claudio4.com> Date: Fri Mar 4 22:27:21 2022 +0000 Update makefile and docs The artifacts produced by hugo-obsidian are now expected to be placed in the assets/indices directory. This commit reflects this change in the Makefile and in the docs. commit0fad5570d3
Author: Claudio Yanes <me@claudio4.com> Date: Fri Mar 4 04:14:42 2022 +0000 Add .gitkeep to assets/indices commitdc9b421e21
Author: Claudio Yanes <me@claudio4.com> Date: Fri Mar 4 04:12:43 2022 +0000 Remove unnecessary scrollbars The margin property can escape the parent node and move it alongside its child. This happens with singlePage div and the body, resulting in scrollbars appearing as the body has the size of the viewport but does not align with it. This phenomenon can be always observed in the vertical axis and it can also be observed in the horizontal axis when the viewport it’s not wide enough (mostly in mobile). Using paddings prevents this “extra space” from scraping and displacing the body. Also, the value 100vw does not take into account the space taken by the vertical scrollbar, thus making the body wider than the actual viewport, producing a horizontal scrollbar. commit8779e72c77
Author: Claudio Yanes <me@claudio4.com> Date: Fri Mar 4 03:34:45 2022 +0000 Add attribute property to scripts from jsdelivr Adding the integrity attribute protects the website (by refusing to load the script) against malicious modifications of the script in the case of jsdelivr gets hacked commit7f6523337c
Author: Claudio Yanes <me@claudio4.com> Date: Fri Mar 4 03:24:32 2022 +0000 Move popover to the end of the page The popover script doesn’t ever start in until the DOM has finished Loading, so wait for the script to be downloaded and parsed before Showing the content to the user makes no sense. commit7e0f2e4449
Author: Claudio Yanes <me@claudio4.com> Date: Fri Mar 4 02:25:30 2022 +0000 Fix fetchData The fetchData function suffer from a race condition. If the function is called before the promise finishes, it will result in another pair of HTTP request. This does not only make the function useless but Actually, it makes it harmful as the data might be redownloaded twice. Now fetchData is not a function but rather the promise by itself. Previous callers are expected to await the variable instead, this should be not concern as awaiting a promise multiple time in JavaScript is completely safe. commit1313bd9779
Author: Claudio Yanes <me@claudio4.com> Date: Fri Mar 4 02:07:51 2022 +0000 Move css and js to appropriate files Having the CSS and JS in the html template produces pages larger than necessary, as each page need to contain all the js/css. Separating them in appropriate files allow the browser to just download them once and use them for all the pages. This is even more effective with an aggressive cache policy for the js and css, something that can be done without fear thanks to the implemented cache-busting. Also, having then in separate files allows us to use Hugo pipelines for minimizing the code. commit5234fae080
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Feb 28 08:24:29 2022 -0800 fix backlinks not using baseurl commit0ee0855e1c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Feb 28 07:30:59 2022 -0800 bump hugo-obsidian to support root commite06e341468
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Feb 28 07:14:55 2022 -0800 fix: explicitly set root as current directory to fix ignore files commit73e526a7d5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Feb 23 12:28:25 2022 -0500 add screenshot to readme commitcdc4f1a840
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 22 13:36:08 2022 -0500 fix: relink search button (move outside content load listener) commit714b4fcfa3
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Feb 20 21:40:10 2022 -0500 fix links being broken for pages with spaces commit9c04ca0266
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Feb 17 10:49:41 2022 -0500 rtl docs commit388a2bf78b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Feb 17 10:44:39 2022 -0500 docs updates commitf192f9a23d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 23:03:02 2022 -0500 fix #54: root all image urls commit3b3e6ec3b2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 22:54:20 2022 -0500 fix relative pathing for dynamic fetch commit8e85e274f6
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 19:42:45 2022 -0500 change output to static instead of data commitfcd5d2807d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 19:39:14 2022 -0500 feat: dynamically fetch indices commit4587b13360
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 17:12:08 2022 -0500 feat: add rtl support as part of #47 commitfb9ea8dcb8
Merge:c520db48
10f9843b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 16:52:49 2022 -0500 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commitc520db4882
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 16:52:32 2022 -0500 fix: #50, change css load order commit10f9843bb6
Merge:0dc51ff3
31297b7e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 14:51:29 2022 -0500 Merge pull request #51 from brandonkboswell/patch-1 commit0dc51ff39c
Merge:c35086c5
fa3bc3de
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 14:50:34 2022 -0500 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commitc35086c510
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Feb 15 14:50:25 2022 -0500 visibility fix commit31297b7e5a
Author: Brandon Boswell <brandonkboswell@gmail.com> Date: Sat Feb 12 22:35:03 2022 -0500 Added to the Showcase commitfa3bc3de92
Merge:a271fb9d
41c443db
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Feb 11 17:24:54 2022 -0500 Merge pull request #48 from earnestma/earne/configurable-page-toc commit41c443dbf0
Author: earnest ma <me@earne.link> Date: Fri Feb 11 17:05:38 2022 -0500 Add disableToc parameter to not show TOC on a page commita271fb9d74
Merge:9645f003
49cdca5d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 31 12:28:40 2022 -0800 Merge pull request #46 from adube/patch-1 commit49cdca5dfc
Author: Alexandre Dubé <adube@mapgears.com> Date: Mon Jan 31 15:18:26 2022 -0500 Specify Hugo requires extended Sass/SCSS version Hugo needs to be installed with its "extended" Sass/SCSS version, otherwise this template does not work. commit9645f00317
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Jan 27 09:38:28 2022 -0800 link fixing commit57ebf4c21c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 10 13:08:50 2022 -0800 underscore fix, fix relative path being weird for graph commit54e3e071d1
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 10 09:00:45 2022 -0800 fix popover regex commitd46e223831
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 10 08:51:00 2022 -0800 revert baseurl fix commit6f9a29c174
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 10 08:49:29 2022 -0800 various path fixes commit532bc61025
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Jan 5 19:42:13 2022 -0500 set relativeUrls to true commit99aea48260
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Jan 4 11:39:22 2022 -0500 docs update commit4a3766db56
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 3 16:37:24 2022 -0500 update featurelist commit4e639979f8
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 3 15:36:58 2022 -0500 fix copy selection commite49a1ac9db
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 3 13:22:04 2022 -0500 made link preview optional commit4a3c4fdef5
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Jan 3 13:18:31 2022 -0500 popover implementation commit2b432d7f0b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jan 2 20:02:47 2022 -0500 fix flex gap commit7507fd2991
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Jan 2 19:49:41 2022 -0500 fix search styling commitca886e4075
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Dec 28 14:28:08 2021 -0500 fix render link for apostrophe commit3722e600ee
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 20:52:30 2021 -0500 bump hugo-obsidian commitefeaf0f4e4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 20:16:21 2021 -0500 add pagination to section, fix graph linking commit1a8cdaad24
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 19:43:01 2021 -0500 remove console.log commite4caa0d1d7
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 19:35:42 2021 -0500 add taxonomy and term lists commita45856d788
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 17:53:33 2021 -0500 fix last modified not working for capitalized pages commitdbe9b338cc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 17:44:39 2021 -0500 fix capitalization commit000fcdbf99
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 17:43:27 2021 -0500 fix casing commit612c44d719
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 17:34:53 2021 -0500 modify obsidian commite1911a58ff
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 17:28:53 2021 -0500 enable last modified info commitb4e2697116
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 15:59:19 2021 -0500 content section commit094ab9d064
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 13:15:10 2021 -0500 dedupe backlinks commit39592347cc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Dec 27 13:06:58 2021 -0500 add graph depth config commit165d33810d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Dec 26 21:13:21 2021 -0500 base tags commit6fbfa7170b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Dec 26 00:09:15 2021 -0500 various font and colour fixes commit43837f9e2e
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Dec 25 23:45:30 2021 -0500 add makefile, fix link padding, test capitalization commit2ba01c8311
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Dec 24 15:51:37 2021 -0500 fix untitled #36 commit114b7ca913
Merge:5bd5642c
091be704
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Dec 24 09:48:41 2021 -0500 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit5bd5642c99
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Dec 24 09:48:22 2021 -0500 add toLowerCase to id commit48d01810c4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Dec 23 14:40:59 2021 -0800 fix config setting, fix font size for h1 in article commit3a98c8b554
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Dec 23 14:32:47 2021 -0800 actually display site title commit69c86e407f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Dec 23 14:21:39 2021 -0800 update subdomain docs commit56d2382c28
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Dec 23 14:05:27 2021 -0800 fix relative link styling, change graph and backlinks to refer to name rather than path commit091be7040b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Dec 23 13:37:29 2021 -0800 Create CODE_OF_CONDUCT.md commit09b5522a48
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Dec 23 13:22:22 2021 -0800 Update issue templates commitb9d7adafcc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Dec 23 13:18:03 2021 -0800 Create FUNDING.yml commitafeb18212d
Merge:c64322ad
bc909559
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Dec 2 20:06:18 2021 -0800 Merge pull request #28 from juaoose/overflow commitbc90955959
Author: Juaoose <jjrg1994@gmail.com> Date: Thu Dec 2 22:58:34 2021 -0500 remove horizontal scrollbar commitc64322ad3f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 20 22:55:53 2021 -0800 remove bad wikilink commit48eb9ebc5f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Nov 20 22:53:26 2021 -0800 better search, fix spacing support, bump hugo-obsidian commit82ba843e42
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Nov 15 15:54:18 2021 -0800 search styling commit8ca31df3f2
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 31 09:59:38 2021 -0700 search patch commitdf23b99951
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sat Oct 30 23:27:33 2021 -0700 more search improvements commit6005a2e0a0
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Wed Oct 27 20:10:04 2021 -0700 css fixes commitde940d6a4b
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Oct 26 17:06:00 2021 -0700 update graph redir commit806d11f874
Merge:03bb3a3b
1fc2da4f
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Oct 26 17:03:07 2021 -0700 Merge pull request #23 from bur3ku/hugo commit1fc2da4fe2
Merge:9292de63
24776624
Author: Blake Allen <blakesnake100@gmail.com> Date: Tue Oct 26 16:58:37 2021 -0700 Merge branch 'hugo' of https://github.com/bur3ku/quartz into hugo commit9292de6333
Author: Blake Allen <blakesnake100@gmail.com> Date: Tue Oct 26 16:58:08 2021 -0700 remove unnecessary regex, use encodeuri for label instead of replace commit2477662404
Merge:a14d06aa
03bb3a3b
Author: Blake Allen <blake.edward.allen@gmail.com> Date: Tue Oct 26 12:46:03 2021 -0700 Merge branch 'hugo' into hugo commita14d06aa3d
Author: Blake Allen <blakesnake100@gmail.com> Date: Tue Oct 26 12:44:25 2021 -0700 fix conflict fix commite0535dbe32
Author: Blake Allen <blakesnake100@gmail.com> Date: Tue Oct 26 12:43:55 2021 -0700 fix conflict commit8eca1e60f7
Author: Blake Allen <blakesnake100@gmail.com> Date: Tue Oct 26 12:36:20 2021 -0700 change %20 in node labels to whitespace, change %20 in node hrefs to hyphen commit03bb3a3bae
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Oct 25 15:06:29 2021 -0700 normalize search styling commitf7b89db8ee
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Mon Oct 25 15:00:55 2021 -0700 search fix commit1835b97a7a
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 24 23:45:55 2021 -0700 better homepage commitf56642f13c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 24 23:32:55 2021 -0700 forgot string lol commit22a9c0ddfc
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 24 23:31:09 2021 -0700 docs updates, add search to main page, fix redir bug commitc1c061fbea
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 24 23:17:20 2021 -0700 bump docs commit6fd19069de
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 24 23:17:13 2021 -0700 search improvements commit299533a4f4
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Sun Oct 24 23:17:00 2021 -0700 bump hugo-obsidian version commite1366ecb61
Author: Blake Allen <blakesnake100@gmail.com> Date: Fri Oct 22 18:56:26 2021 -0700 fix accidental code commit776ef084c9
Author: Blake Allen <blakesnake100@gmail.com> Date: Fri Oct 22 18:32:57 2021 -0700 fix last commit commitfc00ad5bff
Author: Blake Allen <blakesnake100@gmail.com> Date: Fri Oct 22 14:04:09 2021 -0700 fix for notes with spaces not linking properly commit228f96e74d
Merge:ae2f7efd
071984a1
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Tue Aug 31 18:32:14 2021 -0400 Merge pull request #14 from juaoose/hugo fix product typo in external hosting section commit071984a12d
Author: Juan José Rodríguez <juaoose@users.noreply.github.com> Date: Tue Aug 31 16:40:31 2021 -0500 fix product typo in external hosting section commitae2f7efde0
Author: jackyzha0 <j.zhao2k19@gmail.com> Date: Sat Aug 28 20:58:14 2021 -0400 update showcase commitcb38667c1d
Merge:1c851271
27c33f83
Author: jackyzha0 <j.zhao2k19@gmail.com> Date: Fri Aug 27 14:08:18 2021 -0400 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo commit27c33f8334
Merge:8850976d
f9920f6d
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Fri Aug 13 17:45:32 2021 -0400 Merge pull request #9 from brechtcs/template Execute darkmode script before first render commitf9920f6d73
Author: Brecht Savelkoul <brecht.savelkoul@alumni.lse.ac.uk> Date: Fri Aug 13 22:46:00 2021 +0200 Execute darkmode script before first render commit8850976d8d
Merge:9b427faa
bb6a1e8c
Author: Jacky Zhao <j.zhao2k19@gmail.com> Date: Thu Aug 12 23:49:05 2021 -0400 Merge pull request #8 from SlRvb/patch-1 Add SlRvb Site to Showcase commitbb6a1e8c34
Author: SlRvb <54087190+SlRvb@users.noreply.github.com> Date: Thu Aug 12 20:46:23 2021 -0700 Add SlRvb Site to Showcase
This commit is contained in:
parent
f62ff28044
commit
70376e7584
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,3 +7,5 @@ tsconfig.tsbuildinfo
|
|||||||
content/.obsidian/workspace.json
|
content/.obsidian/workspace.json
|
||||||
.quartz-cache
|
.quartz-cache
|
||||||
private/
|
private/
|
||||||
|
.replit
|
||||||
|
replit.nix
|
||||||
|
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM node:20-slim as builder
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY package.json .
|
||||||
|
COPY package-lock.json* .
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
FROM node:20-slim
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY --from=builder /usr/src/app/ /usr/src/app/
|
||||||
|
COPY . .
|
||||||
|
CMD ["npx", "quartz", "build", "--serve"]
|
BIN
docs/images/dns records.png
Normal file
BIN
docs/images/dns records.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
BIN
docs/images/quartz layout.png
Normal file
BIN
docs/images/quartz layout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
BIN
docs/images/quartz transform pipeline.png
Normal file
BIN
docs/images/quartz transform pipeline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
38
package-lock.json
generated
38
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@jackyzha0/quartz",
|
"name": "@jackyzha0/quartz",
|
||||||
"version": "4.0.10",
|
"version": "4.0.11",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@jackyzha0/quartz",
|
"name": "@jackyzha0/quartz",
|
||||||
"version": "4.0.10",
|
"version": "4.0.11",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^0.6.3",
|
"@clack/prompts": "^0.6.3",
|
||||||
@ -45,6 +45,7 @@
|
|||||||
"rehype-raw": "^6.1.1",
|
"rehype-raw": "^6.1.1",
|
||||||
"rehype-slug": "^5.1.0",
|
"rehype-slug": "^5.1.0",
|
||||||
"remark": "^14.0.2",
|
"remark": "^14.0.2",
|
||||||
|
"remark-breaks": "^3.0.3",
|
||||||
"remark-frontmatter": "^4.0.1",
|
"remark-frontmatter": "^4.0.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"remark-math": "^5.1.1",
|
"remark-math": "^5.1.1",
|
||||||
@ -55,6 +56,7 @@
|
|||||||
"serve-handler": "^6.1.5",
|
"serve-handler": "^6.1.5",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"to-vfile": "^7.2.4",
|
"to-vfile": "^7.2.4",
|
||||||
|
"toml": "^3.0.0",
|
||||||
"unified": "^10.1.2",
|
"unified": "^10.1.2",
|
||||||
"unist-util-visit": "^4.1.2",
|
"unist-util-visit": "^4.1.2",
|
||||||
"vfile": "^5.3.7",
|
"vfile": "^5.3.7",
|
||||||
@ -3809,6 +3811,19 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mdast-util-newline-to-break": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-491LcYv3gbGhhCrLoeALncQmega2xPh+m3gbsIhVsOX4sw85+ShLFPvPyibxc1Swx/6GtzxgVodq+cGa/47ULg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mdast": "^3.0.0",
|
||||||
|
"mdast-util-find-and-replace": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mdast-util-phrasing": {
|
"node_modules/mdast-util-phrasing": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz",
|
||||||
@ -4902,6 +4917,20 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/remark-breaks": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-C7VkvcUp1TPUc2eAYzsPdaUh8Xj4FSbQnYA5A9f80diApLZscTDeG7efiWP65W8hV2sEy3JuGVU0i6qr5D8Hug==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/mdast": "^3.0.0",
|
||||||
|
"mdast-util-newline-to-break": "^1.0.0",
|
||||||
|
"unified": "^10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark-frontmatter": {
|
"node_modules/remark-frontmatter": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-4.0.1.tgz",
|
||||||
@ -5548,6 +5577,11 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/toml": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="
|
||||||
|
},
|
||||||
"node_modules/tough-cookie": {
|
"node_modules/tough-cookie": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "@jackyzha0/quartz",
|
"name": "@jackyzha0/quartz",
|
||||||
"description": "🌱 publish your digital garden and notes as a website",
|
"description": "🌱 publish your digital garden and notes as a website",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "4.0.10",
|
"version": "4.1.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -19,6 +19,7 @@
|
|||||||
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
|
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
"npm": ">=9.3.1",
|
||||||
"node": ">=18.14"
|
"node": ">=18.14"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@ -69,6 +70,7 @@
|
|||||||
"rehype-raw": "^6.1.1",
|
"rehype-raw": "^6.1.1",
|
||||||
"rehype-slug": "^5.1.0",
|
"rehype-slug": "^5.1.0",
|
||||||
"remark": "^14.0.2",
|
"remark": "^14.0.2",
|
||||||
|
"remark-breaks": "^3.0.3",
|
||||||
"remark-frontmatter": "^4.0.1",
|
"remark-frontmatter": "^4.0.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"remark-math": "^5.1.1",
|
"remark-math": "^5.1.1",
|
||||||
@ -79,6 +81,7 @@
|
|||||||
"serve-handler": "^6.1.5",
|
"serve-handler": "^6.1.5",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"to-vfile": "^7.2.4",
|
"to-vfile": "^7.2.4",
|
||||||
|
"toml": "^3.0.0",
|
||||||
"unified": "^10.1.2",
|
"unified": "^10.1.2",
|
||||||
"unist-util-visit": "^4.1.2",
|
"unist-util-visit": "^4.1.2",
|
||||||
"vfile": "^5.3.7",
|
"vfile": "^5.3.7",
|
||||||
|
@ -10,7 +10,7 @@ const config: QuartzConfig = {
|
|||||||
provider: "plausible",
|
provider: "plausible",
|
||||||
},
|
},
|
||||||
baseUrl: "garden.matsuuratomoya.com",
|
baseUrl: "garden.matsuuratomoya.com",
|
||||||
ignorePatterns: ["private", "templates"],
|
ignorePatterns: ["private", "templates",".obsidian"],
|
||||||
defaultDateType: "created",
|
defaultDateType: "created",
|
||||||
theme: {
|
theme: {
|
||||||
typography: {
|
typography: {
|
||||||
@ -69,6 +69,7 @@ const config: QuartzConfig = {
|
|||||||
}),
|
}),
|
||||||
Plugin.Assets(),
|
Plugin.Assets(),
|
||||||
Plugin.Static(),
|
Plugin.Static(),
|
||||||
|
Plugin.NotFoundPage(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,550 +1,39 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { promises, readFileSync } from "fs"
|
|
||||||
import yargs from "yargs"
|
import yargs from "yargs"
|
||||||
import path from "path"
|
|
||||||
import { hideBin } from "yargs/helpers"
|
import { hideBin } from "yargs/helpers"
|
||||||
import esbuild from "esbuild"
|
import {
|
||||||
import chalk from "chalk"
|
handleBuild,
|
||||||
import { sassPlugin } from "esbuild-sass-plugin"
|
handleCreate,
|
||||||
import fs from "fs"
|
handleUpdate,
|
||||||
import { intro, isCancel, outro, select, text } from "@clack/prompts"
|
handleRestore,
|
||||||
import { rimraf } from "rimraf"
|
handleSync,
|
||||||
import chokidar from "chokidar"
|
} from "./cli/handlers.js"
|
||||||
import prettyBytes from "pretty-bytes"
|
import { CommonArgv, BuildArgv, CreateArgv, SyncArgv } from "./cli/args.js"
|
||||||
import { execSync, spawnSync } from "child_process"
|
import { version } from "./cli/constants.js"
|
||||||
import http from "http"
|
|
||||||
import serveHandler from "serve-handler"
|
|
||||||
import { WebSocketServer } from "ws"
|
|
||||||
import { randomUUID } from "crypto"
|
|
||||||
import { Mutex } from "async-mutex"
|
|
||||||
|
|
||||||
const ORIGIN_NAME = "origin"
|
|
||||||
const UPSTREAM_NAME = "upstream"
|
|
||||||
const QUARTZ_SOURCE_BRANCH = "v4"
|
|
||||||
const cwd = process.cwd()
|
|
||||||
const cacheDir = path.join(cwd, ".quartz-cache")
|
|
||||||
const cacheFile = "./.quartz-cache/transpiled-build.mjs"
|
|
||||||
const fp = "./quartz/build.ts"
|
|
||||||
const { version } = JSON.parse(readFileSync("./package.json").toString())
|
|
||||||
const contentCacheFolder = path.join(cacheDir, "content-cache")
|
|
||||||
|
|
||||||
const CommonArgv = {
|
|
||||||
directory: {
|
|
||||||
string: true,
|
|
||||||
alias: ["d"],
|
|
||||||
default: "content",
|
|
||||||
describe: "directory to look for content files",
|
|
||||||
},
|
|
||||||
verbose: {
|
|
||||||
boolean: true,
|
|
||||||
alias: ["v"],
|
|
||||||
default: false,
|
|
||||||
describe: "print out extra logging information",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const SyncArgv = {
|
|
||||||
...CommonArgv,
|
|
||||||
commit: {
|
|
||||||
boolean: true,
|
|
||||||
default: true,
|
|
||||||
describe: "create a git commit for your unsaved changes",
|
|
||||||
},
|
|
||||||
push: {
|
|
||||||
boolean: true,
|
|
||||||
default: true,
|
|
||||||
describe: "push updates to your Quartz fork",
|
|
||||||
},
|
|
||||||
pull: {
|
|
||||||
boolean: true,
|
|
||||||
default: true,
|
|
||||||
describe: "pull updates from your Quartz fork",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const BuildArgv = {
|
|
||||||
...CommonArgv,
|
|
||||||
output: {
|
|
||||||
string: true,
|
|
||||||
alias: ["o"],
|
|
||||||
default: "public",
|
|
||||||
describe: "output folder for files",
|
|
||||||
},
|
|
||||||
serve: {
|
|
||||||
boolean: true,
|
|
||||||
default: false,
|
|
||||||
describe: "run a local server to live-preview your Quartz",
|
|
||||||
},
|
|
||||||
baseDir: {
|
|
||||||
string: true,
|
|
||||||
default: "",
|
|
||||||
describe: "base path to serve your local server on",
|
|
||||||
},
|
|
||||||
port: {
|
|
||||||
number: true,
|
|
||||||
default: 8080,
|
|
||||||
describe: "port to serve Quartz on",
|
|
||||||
},
|
|
||||||
bundleInfo: {
|
|
||||||
boolean: true,
|
|
||||||
default: false,
|
|
||||||
describe: "show detailed bundle information",
|
|
||||||
},
|
|
||||||
concurrency: {
|
|
||||||
number: true,
|
|
||||||
describe: "how many threads to use to parse notes",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapePath(fp) {
|
|
||||||
return fp
|
|
||||||
.replace(/\\ /g, " ") // unescape spaces
|
|
||||||
.replace(/^".*"$/, "$1")
|
|
||||||
.replace(/^'.*"$/, "$1")
|
|
||||||
.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
function exitIfCancel(val) {
|
|
||||||
if (isCancel(val)) {
|
|
||||||
outro(chalk.red("Exiting"))
|
|
||||||
process.exit(0)
|
|
||||||
} else {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stashContentFolder(contentFolder) {
|
|
||||||
await fs.promises.rm(contentCacheFolder, { force: true, recursive: true })
|
|
||||||
await fs.promises.cp(contentFolder, contentCacheFolder, {
|
|
||||||
force: true,
|
|
||||||
recursive: true,
|
|
||||||
verbatimSymlinks: true,
|
|
||||||
preserveTimestamps: true,
|
|
||||||
})
|
|
||||||
await fs.promises.rm(contentFolder, { force: true, recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function popContentFolder(contentFolder) {
|
|
||||||
await fs.promises.rm(contentFolder, { force: true, recursive: true })
|
|
||||||
await fs.promises.cp(contentCacheFolder, contentFolder, {
|
|
||||||
force: true,
|
|
||||||
recursive: true,
|
|
||||||
verbatimSymlinks: true,
|
|
||||||
preserveTimestamps: true,
|
|
||||||
})
|
|
||||||
await fs.promises.rm(contentCacheFolder, { force: true, recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
function gitPull(origin, branch) {
|
|
||||||
const flags = ["--no-rebase", "--autostash", "-s", "recursive", "-X", "ours", "--no-edit"]
|
|
||||||
const out = spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" })
|
|
||||||
if (out.stderr) {
|
|
||||||
throw new Error(`Error while pulling updates: ${out.stderr}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yargs(hideBin(process.argv))
|
yargs(hideBin(process.argv))
|
||||||
.scriptName("quartz")
|
.scriptName("quartz")
|
||||||
.version(version)
|
.version(version)
|
||||||
.usage("$0 <cmd> [args]")
|
.usage("$0 <cmd> [args]")
|
||||||
.command("create", "Initialize Quartz", CommonArgv, async (argv) => {
|
.command("create", "Initialize Quartz", CreateArgv, async (argv) => {
|
||||||
console.log()
|
await handleCreate(argv)
|
||||||
intro(chalk.bgGreen.black(` Quartz v${version} `))
|
|
||||||
const contentFolder = path.join(cwd, argv.directory)
|
|
||||||
const setupStrategy = exitIfCancel(
|
|
||||||
await select({
|
|
||||||
message: `Choose how to initialize the content in \`${contentFolder}\``,
|
|
||||||
options: [
|
|
||||||
{ value: "new", label: "Empty Quartz" },
|
|
||||||
{ value: "copy", label: "Copy an existing folder", hint: "overwrites `content`" },
|
|
||||||
{
|
|
||||||
value: "symlink",
|
|
||||||
label: "Symlink an existing folder",
|
|
||||||
hint: "don't select this unless you know what you are doing!",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
async function rmContentFolder() {
|
|
||||||
const contentStat = await fs.promises.lstat(contentFolder)
|
|
||||||
if (contentStat.isSymbolicLink()) {
|
|
||||||
await fs.promises.unlink(contentFolder)
|
|
||||||
} else {
|
|
||||||
await rimraf(contentFolder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.promises.unlink(path.join(contentFolder, ".gitkeep"))
|
|
||||||
if (setupStrategy === "copy" || setupStrategy === "symlink") {
|
|
||||||
const originalFolder = escapePath(
|
|
||||||
exitIfCancel(
|
|
||||||
await text({
|
|
||||||
message: "Enter the full path to existing content folder",
|
|
||||||
placeholder:
|
|
||||||
"On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path",
|
|
||||||
validate(fp) {
|
|
||||||
const fullPath = escapePath(fp)
|
|
||||||
if (!fs.existsSync(fullPath)) {
|
|
||||||
return "The given path doesn't exist"
|
|
||||||
} else if (!fs.lstatSync(fullPath).isDirectory()) {
|
|
||||||
return "The given path is not a folder"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
await rmContentFolder()
|
|
||||||
if (setupStrategy === "copy") {
|
|
||||||
await fs.promises.cp(originalFolder, contentFolder, {
|
|
||||||
recursive: true,
|
|
||||||
preserveTimestamps: true,
|
|
||||||
})
|
|
||||||
} else if (setupStrategy === "symlink") {
|
|
||||||
await fs.promises.symlink(originalFolder, contentFolder, "dir")
|
|
||||||
}
|
|
||||||
} else if (setupStrategy === "new") {
|
|
||||||
await fs.promises.writeFile(
|
|
||||||
path.join(contentFolder, "index.md"),
|
|
||||||
`---
|
|
||||||
title: Welcome to Quartz
|
|
||||||
---
|
|
||||||
|
|
||||||
This is a blank Quartz installation.
|
|
||||||
See the [documentation](https://quartz.jzhao.xyz) for how to get started.
|
|
||||||
`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get a preferred link resolution strategy
|
|
||||||
const linkResolutionStrategy = exitIfCancel(
|
|
||||||
await select({
|
|
||||||
message: `Choose how Quartz should resolve links in your content. You can change this later in \`quartz.config.ts\`.`,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
value: "absolute",
|
|
||||||
label: "Treat links as absolute path",
|
|
||||||
hint: "for content made for Quartz 3 and Hugo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "shortest",
|
|
||||||
label: "Treat links as shortest path",
|
|
||||||
hint: "for most Obsidian vaults",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "relative",
|
|
||||||
label: "Treat links as relative paths",
|
|
||||||
hint: "for just normal Markdown files",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
// now, do config changes
|
|
||||||
const configFilePath = path.join(cwd, "quartz.config.ts")
|
|
||||||
let configContent = await fs.promises.readFile(configFilePath, { encoding: "utf-8" })
|
|
||||||
configContent = configContent.replace(
|
|
||||||
/markdownLinkResolution: '(.+)'/,
|
|
||||||
`markdownLinkResolution: '${linkResolutionStrategy}'`,
|
|
||||||
)
|
|
||||||
await fs.promises.writeFile(configFilePath, configContent)
|
|
||||||
|
|
||||||
outro(`You're all set! Not sure what to do next? Try:
|
|
||||||
• Customizing Quartz a bit more by editing \`quartz.config.ts\`
|
|
||||||
• Running \`npx quartz build --serve\` to preview your Quartz locally
|
|
||||||
• Hosting your Quartz online (see: https://quartz.jzhao.xyz/hosting)
|
|
||||||
`)
|
|
||||||
})
|
})
|
||||||
.command("update", "Get the latest Quartz updates", CommonArgv, async (argv) => {
|
.command("update", "Get the latest Quartz updates", CommonArgv, async (argv) => {
|
||||||
const contentFolder = path.join(cwd, argv.directory)
|
await handleUpdate(argv)
|
||||||
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
|
||||||
console.log("Backing up your content")
|
|
||||||
execSync(
|
|
||||||
`git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`,
|
|
||||||
)
|
|
||||||
await stashContentFolder(contentFolder)
|
|
||||||
console.log(
|
|
||||||
"Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.",
|
|
||||||
)
|
|
||||||
gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH)
|
|
||||||
await popContentFolder(contentFolder)
|
|
||||||
console.log("Ensuring dependencies are up to date")
|
|
||||||
spawnSync("npm", ["i"], { stdio: "inherit" })
|
|
||||||
console.log(chalk.green("Done!"))
|
|
||||||
})
|
})
|
||||||
.command(
|
.command(
|
||||||
"restore",
|
"restore",
|
||||||
"Try to restore your content folder from the cache",
|
"Try to restore your content folder from the cache",
|
||||||
CommonArgv,
|
CommonArgv,
|
||||||
async (argv) => {
|
async (argv) => {
|
||||||
const contentFolder = path.join(cwd, argv.directory)
|
await handleRestore(argv)
|
||||||
await popContentFolder(contentFolder)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.command("sync", "Sync your Quartz to and from GitHub.", SyncArgv, async (argv) => {
|
.command("sync", "Sync your Quartz to and from GitHub.", SyncArgv, async (argv) => {
|
||||||
const contentFolder = path.join(cwd, argv.directory)
|
await handleSync(argv)
|
||||||
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
|
||||||
console.log("Backing up your content")
|
|
||||||
|
|
||||||
if (argv.commit) {
|
|
||||||
const contentStat = await fs.promises.lstat(contentFolder)
|
|
||||||
if (contentStat.isSymbolicLink()) {
|
|
||||||
const linkTarg = await fs.promises.readlink(contentFolder)
|
|
||||||
console.log(chalk.yellow("Detected symlink, trying to dereference before committing"))
|
|
||||||
|
|
||||||
// stash symlink file
|
|
||||||
await stashContentFolder(contentFolder)
|
|
||||||
|
|
||||||
// follow symlink and copy content
|
|
||||||
await fs.promises.cp(linkTarg, contentFolder, {
|
|
||||||
recursive: true,
|
|
||||||
preserveTimestamps: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTimestamp = new Date().toLocaleString("en-US", {
|
|
||||||
dateStyle: "medium",
|
|
||||||
timeStyle: "short",
|
|
||||||
})
|
|
||||||
spawnSync("git", ["add", "."], { stdio: "inherit" })
|
|
||||||
spawnSync("git", ["commit", "-m", `Quartz sync: ${currentTimestamp}`], { stdio: "inherit" })
|
|
||||||
|
|
||||||
if (contentStat.isSymbolicLink()) {
|
|
||||||
// put symlink back
|
|
||||||
await popContentFolder(contentFolder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await stashContentFolder(contentFolder)
|
|
||||||
|
|
||||||
if (argv.pull) {
|
|
||||||
console.log(
|
|
||||||
"Pulling updates from your repository. You may need to resolve some `git` conflicts if you've made changes to components or plugins.",
|
|
||||||
)
|
|
||||||
gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
await popContentFolder(contentFolder)
|
|
||||||
if (argv.push) {
|
|
||||||
console.log("Pushing your changes")
|
|
||||||
spawnSync("git", ["push", "-f", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], { stdio: "inherit" })
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(chalk.green("Done!"))
|
|
||||||
})
|
})
|
||||||
.command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => {
|
.command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => {
|
||||||
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
await handleBuild(argv)
|
||||||
const ctx = await esbuild.context({
|
|
||||||
entryPoints: [fp],
|
|
||||||
outfile: path.join("quartz", cacheFile),
|
|
||||||
bundle: true,
|
|
||||||
keepNames: true,
|
|
||||||
minifyWhitespace: true,
|
|
||||||
minifySyntax: true,
|
|
||||||
platform: "node",
|
|
||||||
format: "esm",
|
|
||||||
jsx: "automatic",
|
|
||||||
jsxImportSource: "preact",
|
|
||||||
packages: "external",
|
|
||||||
metafile: true,
|
|
||||||
sourcemap: true,
|
|
||||||
sourcesContent: false,
|
|
||||||
plugins: [
|
|
||||||
sassPlugin({
|
|
||||||
type: "css-text",
|
|
||||||
cssImports: true,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: "inline-script-loader",
|
|
||||||
setup(build) {
|
|
||||||
build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => {
|
|
||||||
let text = await promises.readFile(args.path, "utf8")
|
|
||||||
|
|
||||||
// remove default exports that we manually inserted
|
|
||||||
text = text.replace("export default", "")
|
|
||||||
text = text.replace("export", "")
|
|
||||||
|
|
||||||
const sourcefile = path.relative(path.resolve("."), args.path)
|
|
||||||
const resolveDir = path.dirname(sourcefile)
|
|
||||||
const transpiled = await esbuild.build({
|
|
||||||
stdin: {
|
|
||||||
contents: text,
|
|
||||||
loader: "ts",
|
|
||||||
resolveDir,
|
|
||||||
sourcefile,
|
|
||||||
},
|
|
||||||
write: false,
|
|
||||||
bundle: true,
|
|
||||||
platform: "browser",
|
|
||||||
format: "esm",
|
|
||||||
})
|
|
||||||
const rawMod = transpiled.outputFiles[0].text
|
|
||||||
return {
|
|
||||||
contents: rawMod,
|
|
||||||
loader: "text",
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const buildMutex = new Mutex()
|
|
||||||
let lastBuildMs = 0
|
|
||||||
let cleanupBuild = null
|
|
||||||
const build = async (clientRefresh) => {
|
|
||||||
const buildStart = new Date().getTime()
|
|
||||||
lastBuildMs = buildStart
|
|
||||||
const release = await buildMutex.acquire()
|
|
||||||
if (lastBuildMs > buildStart) {
|
|
||||||
release()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanupBuild) {
|
|
||||||
await cleanupBuild()
|
|
||||||
console.log(chalk.yellow("Detected a source code change, doing a hard rebuild..."))
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await ctx.rebuild().catch((err) => {
|
|
||||||
console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`)
|
|
||||||
console.log(`Reason: ${chalk.grey(err)}`)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
release()
|
|
||||||
|
|
||||||
if (argv.bundleInfo) {
|
|
||||||
const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs"
|
|
||||||
const meta = result.metafile.outputs[outputFileName]
|
|
||||||
console.log(
|
|
||||||
`Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes(
|
|
||||||
meta.bytes,
|
|
||||||
)})`,
|
|
||||||
)
|
|
||||||
console.log(await esbuild.analyzeMetafile(result.metafile, { color: true }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// bypass module cache
|
|
||||||
// https://github.com/nodejs/modules/issues/307
|
|
||||||
const { default: buildQuartz } = await import(cacheFile + `?update=${randomUUID()}`)
|
|
||||||
cleanupBuild = await buildQuartz(argv, buildMutex, clientRefresh)
|
|
||||||
clientRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv.serve) {
|
|
||||||
const connections = []
|
|
||||||
const clientRefresh = () => connections.forEach((conn) => conn.send("rebuild"))
|
|
||||||
|
|
||||||
if (argv.baseDir !== "" && !argv.baseDir.startsWith("/")) {
|
|
||||||
argv.baseDir = "/" + argv.baseDir
|
|
||||||
}
|
|
||||||
|
|
||||||
await build(clientRefresh)
|
|
||||||
const server = http.createServer(async (req, res) => {
|
|
||||||
if (argv.baseDir && !req.url?.startsWith(argv.baseDir)) {
|
|
||||||
console.log(
|
|
||||||
chalk.red(
|
|
||||||
`[404] ${req.url} (warning: link outside of site, this is likely a Quartz bug)`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
res.writeHead(404)
|
|
||||||
res.end()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip baseDir prefix
|
|
||||||
req.url = req.url?.slice(argv.baseDir.length)
|
|
||||||
|
|
||||||
const serve = async () => {
|
|
||||||
const release = await buildMutex.acquire()
|
|
||||||
await serveHandler(req, res, {
|
|
||||||
public: argv.output,
|
|
||||||
directoryListing: false,
|
|
||||||
headers: [
|
|
||||||
{
|
|
||||||
source: "**/*.html",
|
|
||||||
headers: [{ key: "Content-Disposition", value: "inline" }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
const status = res.statusCode
|
|
||||||
const statusString =
|
|
||||||
status >= 200 && status < 300 ? chalk.green(`[${status}]`) : chalk.red(`[${status}]`)
|
|
||||||
console.log(statusString + chalk.grey(` ${argv.baseDir}${req.url}`))
|
|
||||||
release()
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirect = (newFp) => {
|
|
||||||
newFp = argv.baseDir + newFp
|
|
||||||
res.writeHead(302, {
|
|
||||||
Location: newFp,
|
|
||||||
})
|
|
||||||
console.log(chalk.yellow("[302]") + chalk.grey(` ${argv.baseDir}${req.url} -> ${newFp}`))
|
|
||||||
res.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
let fp = req.url?.split("?")[0] ?? "/"
|
|
||||||
|
|
||||||
// handle redirects
|
|
||||||
if (fp.endsWith("/")) {
|
|
||||||
// /trailing/
|
|
||||||
// does /trailing/index.html exist? if so, serve it
|
|
||||||
const indexFp = path.posix.join(fp, "index.html")
|
|
||||||
if (fs.existsSync(path.posix.join(argv.output, indexFp))) {
|
|
||||||
req.url = fp
|
|
||||||
return serve()
|
|
||||||
}
|
|
||||||
|
|
||||||
// does /trailing.html exist? if so, redirect to /trailing
|
|
||||||
let base = fp.slice(0, -1)
|
|
||||||
if (path.extname(base) === "") {
|
|
||||||
base += ".html"
|
|
||||||
}
|
|
||||||
if (fs.existsSync(path.posix.join(argv.output, base))) {
|
|
||||||
return redirect(fp.slice(0, -1))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// /regular
|
|
||||||
// does /regular.html exist? if so, serve it
|
|
||||||
let base = fp
|
|
||||||
if (path.extname(base) === "") {
|
|
||||||
base += ".html"
|
|
||||||
}
|
|
||||||
if (fs.existsSync(path.posix.join(argv.output, base))) {
|
|
||||||
req.url = fp
|
|
||||||
return serve()
|
|
||||||
}
|
|
||||||
|
|
||||||
// does /regular/index.html exist? if so, redirect to /regular/
|
|
||||||
let indexFp = path.posix.join(fp, "index.html")
|
|
||||||
if (fs.existsSync(path.posix.join(argv.output, indexFp))) {
|
|
||||||
return redirect(fp + "/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return serve()
|
|
||||||
})
|
|
||||||
server.listen(argv.port)
|
|
||||||
const wss = new WebSocketServer({ port: 3001 })
|
|
||||||
wss.on("connection", (ws) => connections.push(ws))
|
|
||||||
console.log(
|
|
||||||
chalk.cyan(
|
|
||||||
`Started a Quartz server listening at http://localhost:${argv.port}${argv.baseDir}`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
console.log("hint: exit with ctrl+c")
|
|
||||||
chokidar
|
|
||||||
.watch(["**/*.ts", "**/*.tsx", "**/*.scss", "package.json"], {
|
|
||||||
ignoreInitial: true,
|
|
||||||
})
|
|
||||||
.on("all", async () => {
|
|
||||||
build(clientRefresh)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
await build(() => {})
|
|
||||||
ctx.dispose()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.showHelpOnFail(false)
|
.showHelpOnFail(false)
|
||||||
.help()
|
.help()
|
||||||
|
@ -12,6 +12,10 @@ export type Analytics =
|
|||||||
provider: "google"
|
provider: "google"
|
||||||
tagId: string
|
tagId: string
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
provider: "umami"
|
||||||
|
websiteId: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface GlobalConfiguration {
|
export interface GlobalConfiguration {
|
||||||
pageTitle: string
|
pageTitle: string
|
||||||
|
103
quartz/cli/args.js
Normal file
103
quartz/cli/args.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
export const CommonArgv = {
|
||||||
|
directory: {
|
||||||
|
string: true,
|
||||||
|
alias: ["d"],
|
||||||
|
default: "content",
|
||||||
|
describe: "directory to look for content files",
|
||||||
|
},
|
||||||
|
verbose: {
|
||||||
|
boolean: true,
|
||||||
|
alias: ["v"],
|
||||||
|
default: false,
|
||||||
|
describe: "print out extra logging information",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateArgv = {
|
||||||
|
...CommonArgv,
|
||||||
|
source: {
|
||||||
|
string: true,
|
||||||
|
alias: ["s"],
|
||||||
|
describe: "source directory to copy/create symlink from",
|
||||||
|
},
|
||||||
|
strategy: {
|
||||||
|
string: true,
|
||||||
|
alias: ["X"],
|
||||||
|
choices: ["new", "copy", "symlink"],
|
||||||
|
describe: "strategy for content folder setup",
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
string: true,
|
||||||
|
alias: ["l"],
|
||||||
|
choices: ["absolute", "shortest", "relative"],
|
||||||
|
describe: "strategy to resolve links",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SyncArgv = {
|
||||||
|
...CommonArgv,
|
||||||
|
commit: {
|
||||||
|
boolean: true,
|
||||||
|
default: true,
|
||||||
|
describe: "create a git commit for your unsaved changes",
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
string: true,
|
||||||
|
alias: ["m"],
|
||||||
|
describe: "option to override the default Quartz commit message",
|
||||||
|
},
|
||||||
|
push: {
|
||||||
|
boolean: true,
|
||||||
|
default: true,
|
||||||
|
describe: "push updates to your Quartz fork",
|
||||||
|
},
|
||||||
|
pull: {
|
||||||
|
boolean: true,
|
||||||
|
default: true,
|
||||||
|
describe: "pull updates from your Quartz fork",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BuildArgv = {
|
||||||
|
...CommonArgv,
|
||||||
|
output: {
|
||||||
|
string: true,
|
||||||
|
alias: ["o"],
|
||||||
|
default: "public",
|
||||||
|
describe: "output folder for files",
|
||||||
|
},
|
||||||
|
serve: {
|
||||||
|
boolean: true,
|
||||||
|
default: false,
|
||||||
|
describe: "run a local server to live-preview your Quartz",
|
||||||
|
},
|
||||||
|
baseDir: {
|
||||||
|
string: true,
|
||||||
|
default: "",
|
||||||
|
describe: "base path to serve your local server on",
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
number: true,
|
||||||
|
default: 8080,
|
||||||
|
describe: "port to serve Quartz on",
|
||||||
|
},
|
||||||
|
wsPort: {
|
||||||
|
number: true,
|
||||||
|
default: 3001,
|
||||||
|
describe: "port to use for WebSocket-based hot-reload notifications",
|
||||||
|
},
|
||||||
|
remoteDevHost: {
|
||||||
|
string: true,
|
||||||
|
default: "",
|
||||||
|
describe: "A URL override for the websocket connection if you are not developing on localhost",
|
||||||
|
},
|
||||||
|
bundleInfo: {
|
||||||
|
boolean: true,
|
||||||
|
default: false,
|
||||||
|
describe: "show detailed bundle information",
|
||||||
|
},
|
||||||
|
concurrency: {
|
||||||
|
number: true,
|
||||||
|
describe: "how many threads to use to parse notes",
|
||||||
|
},
|
||||||
|
}
|
15
quartz/cli/constants.js
Normal file
15
quartz/cli/constants.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import path from "path"
|
||||||
|
import { readFileSync } from "fs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All constants relating to helpers or handlers
|
||||||
|
*/
|
||||||
|
export const ORIGIN_NAME = "origin"
|
||||||
|
export const UPSTREAM_NAME = "upstream"
|
||||||
|
export const QUARTZ_SOURCE_BRANCH = "v4"
|
||||||
|
export const cwd = process.cwd()
|
||||||
|
export const cacheDir = path.join(cwd, ".quartz-cache")
|
||||||
|
export const cacheFile = "./quartz/.quartz-cache/transpiled-build.mjs"
|
||||||
|
export const fp = "./quartz/build.ts"
|
||||||
|
export const { version } = JSON.parse(readFileSync("./package.json").toString())
|
||||||
|
export const contentCacheFolder = path.join(cacheDir, "content-cache")
|
512
quartz/cli/handlers.js
Normal file
512
quartz/cli/handlers.js
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
import { promises } from "fs"
|
||||||
|
import path from "path"
|
||||||
|
import esbuild from "esbuild"
|
||||||
|
import chalk from "chalk"
|
||||||
|
import { sassPlugin } from "esbuild-sass-plugin"
|
||||||
|
import fs from "fs"
|
||||||
|
import { intro, outro, select, text } from "@clack/prompts"
|
||||||
|
import { rimraf } from "rimraf"
|
||||||
|
import chokidar from "chokidar"
|
||||||
|
import prettyBytes from "pretty-bytes"
|
||||||
|
import { execSync, spawnSync } from "child_process"
|
||||||
|
import http from "http"
|
||||||
|
import serveHandler from "serve-handler"
|
||||||
|
import { WebSocketServer } from "ws"
|
||||||
|
import { randomUUID } from "crypto"
|
||||||
|
import { Mutex } from "async-mutex"
|
||||||
|
import { CreateArgv } from "./args.js"
|
||||||
|
import {
|
||||||
|
exitIfCancel,
|
||||||
|
escapePath,
|
||||||
|
gitPull,
|
||||||
|
popContentFolder,
|
||||||
|
stashContentFolder,
|
||||||
|
} from "./helpers.js"
|
||||||
|
import {
|
||||||
|
UPSTREAM_NAME,
|
||||||
|
QUARTZ_SOURCE_BRANCH,
|
||||||
|
ORIGIN_NAME,
|
||||||
|
version,
|
||||||
|
fp,
|
||||||
|
cacheFile,
|
||||||
|
cwd,
|
||||||
|
} from "./constants.js"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles `npx quartz create`
|
||||||
|
* @param {*} argv arguments for `create`
|
||||||
|
*/
|
||||||
|
export async function handleCreate(argv) {
|
||||||
|
console.log()
|
||||||
|
intro(chalk.bgGreen.black(` Quartz v${version} `))
|
||||||
|
const contentFolder = path.join(cwd, argv.directory)
|
||||||
|
let setupStrategy = argv.strategy?.toLowerCase()
|
||||||
|
let linkResolutionStrategy = argv.links?.toLowerCase()
|
||||||
|
const sourceDirectory = argv.source
|
||||||
|
|
||||||
|
// If all cmd arguments were provided, check if theyre valid
|
||||||
|
if (setupStrategy && linkResolutionStrategy) {
|
||||||
|
// If setup isn't, "new", source argument is required
|
||||||
|
if (setupStrategy !== "new") {
|
||||||
|
// Error handling
|
||||||
|
if (!sourceDirectory) {
|
||||||
|
outro(
|
||||||
|
chalk.red(
|
||||||
|
`Setup strategies (arg '${chalk.yellow(
|
||||||
|
`-${CreateArgv.strategy.alias[0]}`,
|
||||||
|
)}') other than '${chalk.yellow(
|
||||||
|
"new",
|
||||||
|
)}' require content folder argument ('${chalk.yellow(
|
||||||
|
`-${CreateArgv.source.alias[0]}`,
|
||||||
|
)}') to be set`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
} else {
|
||||||
|
if (!fs.existsSync(sourceDirectory)) {
|
||||||
|
outro(
|
||||||
|
chalk.red(
|
||||||
|
`Input directory to copy/symlink 'content' from not found ('${chalk.yellow(
|
||||||
|
sourceDirectory,
|
||||||
|
)}', invalid argument "${chalk.yellow(`-${CreateArgv.source.alias[0]}`)})`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
} else if (!fs.lstatSync(sourceDirectory).isDirectory()) {
|
||||||
|
outro(
|
||||||
|
chalk.red(
|
||||||
|
`Source directory to copy/symlink 'content' from is not a directory (found file at '${chalk.yellow(
|
||||||
|
sourceDirectory,
|
||||||
|
)}', invalid argument ${chalk.yellow(`-${CreateArgv.source.alias[0]}`)}")`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use cli process if cmd args werent provided
|
||||||
|
if (!setupStrategy) {
|
||||||
|
setupStrategy = exitIfCancel(
|
||||||
|
await select({
|
||||||
|
message: `Choose how to initialize the content in \`${contentFolder}\``,
|
||||||
|
options: [
|
||||||
|
{ value: "new", label: "Empty Quartz" },
|
||||||
|
{ value: "copy", label: "Copy an existing folder", hint: "overwrites `content`" },
|
||||||
|
{
|
||||||
|
value: "symlink",
|
||||||
|
label: "Symlink an existing folder",
|
||||||
|
hint: "don't select this unless you know what you are doing!",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rmContentFolder() {
|
||||||
|
const contentStat = await fs.promises.lstat(contentFolder)
|
||||||
|
if (contentStat.isSymbolicLink()) {
|
||||||
|
await fs.promises.unlink(contentFolder)
|
||||||
|
} else {
|
||||||
|
await rimraf(contentFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.promises.unlink(path.join(contentFolder, ".gitkeep"))
|
||||||
|
if (setupStrategy === "copy" || setupStrategy === "symlink") {
|
||||||
|
let originalFolder = sourceDirectory
|
||||||
|
|
||||||
|
// If input directory was not passed, use cli
|
||||||
|
if (!sourceDirectory) {
|
||||||
|
originalFolder = escapePath(
|
||||||
|
exitIfCancel(
|
||||||
|
await text({
|
||||||
|
message: "Enter the full path to existing content folder",
|
||||||
|
placeholder:
|
||||||
|
"On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path",
|
||||||
|
validate(fp) {
|
||||||
|
const fullPath = escapePath(fp)
|
||||||
|
if (!fs.existsSync(fullPath)) {
|
||||||
|
return "The given path doesn't exist"
|
||||||
|
} else if (!fs.lstatSync(fullPath).isDirectory()) {
|
||||||
|
return "The given path is not a folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
await rmContentFolder()
|
||||||
|
if (setupStrategy === "copy") {
|
||||||
|
await fs.promises.cp(originalFolder, contentFolder, {
|
||||||
|
recursive: true,
|
||||||
|
preserveTimestamps: true,
|
||||||
|
})
|
||||||
|
} else if (setupStrategy === "symlink") {
|
||||||
|
await fs.promises.symlink(originalFolder, contentFolder, "dir")
|
||||||
|
}
|
||||||
|
} else if (setupStrategy === "new") {
|
||||||
|
await fs.promises.writeFile(
|
||||||
|
path.join(contentFolder, "index.md"),
|
||||||
|
`---
|
||||||
|
title: Welcome to Quartz
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a blank Quartz installation.
|
||||||
|
See the [documentation](https://quartz.jzhao.xyz) for how to get started.
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use cli process if cmd args werent provided
|
||||||
|
if (!linkResolutionStrategy) {
|
||||||
|
// get a preferred link resolution strategy
|
||||||
|
linkResolutionStrategy = exitIfCancel(
|
||||||
|
await select({
|
||||||
|
message: `Choose how Quartz should resolve links in your content. You can change this later in \`quartz.config.ts\`.`,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "absolute",
|
||||||
|
label: "Treat links as absolute path",
|
||||||
|
hint: "for content made for Quartz 3 and Hugo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "shortest",
|
||||||
|
label: "Treat links as shortest path",
|
||||||
|
hint: "for most Obsidian vaults",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "relative",
|
||||||
|
label: "Treat links as relative paths",
|
||||||
|
hint: "for just normal Markdown files",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now, do config changes
|
||||||
|
const configFilePath = path.join(cwd, "quartz.config.ts")
|
||||||
|
let configContent = await fs.promises.readFile(configFilePath, { encoding: "utf-8" })
|
||||||
|
configContent = configContent.replace(
|
||||||
|
/markdownLinkResolution: '(.+)'/,
|
||||||
|
`markdownLinkResolution: '${linkResolutionStrategy}'`,
|
||||||
|
)
|
||||||
|
await fs.promises.writeFile(configFilePath, configContent)
|
||||||
|
|
||||||
|
outro(`You're all set! Not sure what to do next? Try:
|
||||||
|
• Customizing Quartz a bit more by editing \`quartz.config.ts\`
|
||||||
|
• Running \`npx quartz build --serve\` to preview your Quartz locally
|
||||||
|
• Hosting your Quartz online (see: https://quartz.jzhao.xyz/hosting)
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles `npx quartz build`
|
||||||
|
* @param {*} argv arguments for `build`
|
||||||
|
*/
|
||||||
|
export async function handleBuild(argv) {
|
||||||
|
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
||||||
|
const ctx = await esbuild.context({
|
||||||
|
entryPoints: [fp],
|
||||||
|
outfile: cacheFile,
|
||||||
|
bundle: true,
|
||||||
|
keepNames: true,
|
||||||
|
minifyWhitespace: true,
|
||||||
|
minifySyntax: true,
|
||||||
|
platform: "node",
|
||||||
|
format: "esm",
|
||||||
|
jsx: "automatic",
|
||||||
|
jsxImportSource: "preact",
|
||||||
|
packages: "external",
|
||||||
|
metafile: true,
|
||||||
|
sourcemap: true,
|
||||||
|
sourcesContent: false,
|
||||||
|
plugins: [
|
||||||
|
sassPlugin({
|
||||||
|
type: "css-text",
|
||||||
|
cssImports: true,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "inline-script-loader",
|
||||||
|
setup(build) {
|
||||||
|
build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => {
|
||||||
|
let text = await promises.readFile(args.path, "utf8")
|
||||||
|
|
||||||
|
// remove default exports that we manually inserted
|
||||||
|
text = text.replace("export default", "")
|
||||||
|
text = text.replace("export", "")
|
||||||
|
|
||||||
|
const sourcefile = path.relative(path.resolve("."), args.path)
|
||||||
|
const resolveDir = path.dirname(sourcefile)
|
||||||
|
const transpiled = await esbuild.build({
|
||||||
|
stdin: {
|
||||||
|
contents: text,
|
||||||
|
loader: "ts",
|
||||||
|
resolveDir,
|
||||||
|
sourcefile,
|
||||||
|
},
|
||||||
|
write: false,
|
||||||
|
bundle: true,
|
||||||
|
platform: "browser",
|
||||||
|
format: "esm",
|
||||||
|
})
|
||||||
|
const rawMod = transpiled.outputFiles[0].text
|
||||||
|
return {
|
||||||
|
contents: rawMod,
|
||||||
|
loader: "text",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildMutex = new Mutex()
|
||||||
|
let lastBuildMs = 0
|
||||||
|
let cleanupBuild = null
|
||||||
|
const build = async (clientRefresh) => {
|
||||||
|
const buildStart = new Date().getTime()
|
||||||
|
lastBuildMs = buildStart
|
||||||
|
const release = await buildMutex.acquire()
|
||||||
|
if (lastBuildMs > buildStart) {
|
||||||
|
release()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanupBuild) {
|
||||||
|
await cleanupBuild()
|
||||||
|
console.log(chalk.yellow("Detected a source code change, doing a hard rebuild..."))
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await ctx.rebuild().catch((err) => {
|
||||||
|
console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`)
|
||||||
|
console.log(`Reason: ${chalk.grey(err)}`)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
release()
|
||||||
|
|
||||||
|
if (argv.bundleInfo) {
|
||||||
|
const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs"
|
||||||
|
const meta = result.metafile.outputs[outputFileName]
|
||||||
|
console.log(
|
||||||
|
`Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes(
|
||||||
|
meta.bytes,
|
||||||
|
)})`,
|
||||||
|
)
|
||||||
|
console.log(await esbuild.analyzeMetafile(result.metafile, { color: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
// bypass module cache
|
||||||
|
// https://github.com/nodejs/modules/issues/307
|
||||||
|
const { default: buildQuartz } = await import(`../../${cacheFile}?update=${randomUUID()}`)
|
||||||
|
// ^ this import is relative, so base "cacheFile" path can't be used
|
||||||
|
|
||||||
|
cleanupBuild = await buildQuartz(argv, buildMutex, clientRefresh)
|
||||||
|
clientRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv.serve) {
|
||||||
|
const connections = []
|
||||||
|
const clientRefresh = () => connections.forEach((conn) => conn.send("rebuild"))
|
||||||
|
|
||||||
|
if (argv.baseDir !== "" && !argv.baseDir.startsWith("/")) {
|
||||||
|
argv.baseDir = "/" + argv.baseDir
|
||||||
|
}
|
||||||
|
|
||||||
|
await build(clientRefresh)
|
||||||
|
const server = http.createServer(async (req, res) => {
|
||||||
|
if (argv.baseDir && !req.url?.startsWith(argv.baseDir)) {
|
||||||
|
console.log(
|
||||||
|
chalk.red(
|
||||||
|
`[404] ${req.url} (warning: link outside of site, this is likely a Quartz bug)`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
res.writeHead(404)
|
||||||
|
res.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip baseDir prefix
|
||||||
|
req.url = req.url?.slice(argv.baseDir.length)
|
||||||
|
|
||||||
|
const serve = async () => {
|
||||||
|
const release = await buildMutex.acquire()
|
||||||
|
await serveHandler(req, res, {
|
||||||
|
public: argv.output,
|
||||||
|
directoryListing: false,
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
source: "**/*.html",
|
||||||
|
headers: [{ key: "Content-Disposition", value: "inline" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
const status = res.statusCode
|
||||||
|
const statusString =
|
||||||
|
status >= 200 && status < 300 ? chalk.green(`[${status}]`) : chalk.red(`[${status}]`)
|
||||||
|
console.log(statusString + chalk.grey(` ${argv.baseDir}${req.url}`))
|
||||||
|
release()
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirect = (newFp) => {
|
||||||
|
newFp = argv.baseDir + newFp
|
||||||
|
res.writeHead(302, {
|
||||||
|
Location: newFp,
|
||||||
|
})
|
||||||
|
console.log(chalk.yellow("[302]") + chalk.grey(` ${argv.baseDir}${req.url} -> ${newFp}`))
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
let fp = req.url?.split("?")[0] ?? "/"
|
||||||
|
|
||||||
|
// handle redirects
|
||||||
|
if (fp.endsWith("/")) {
|
||||||
|
// /trailing/
|
||||||
|
// does /trailing/index.html exist? if so, serve it
|
||||||
|
const indexFp = path.posix.join(fp, "index.html")
|
||||||
|
if (fs.existsSync(path.posix.join(argv.output, indexFp))) {
|
||||||
|
req.url = fp
|
||||||
|
return serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// does /trailing.html exist? if so, redirect to /trailing
|
||||||
|
let base = fp.slice(0, -1)
|
||||||
|
if (path.extname(base) === "") {
|
||||||
|
base += ".html"
|
||||||
|
}
|
||||||
|
if (fs.existsSync(path.posix.join(argv.output, base))) {
|
||||||
|
return redirect(fp.slice(0, -1))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// /regular
|
||||||
|
// does /regular.html exist? if so, serve it
|
||||||
|
let base = fp
|
||||||
|
if (path.extname(base) === "") {
|
||||||
|
base += ".html"
|
||||||
|
}
|
||||||
|
if (fs.existsSync(path.posix.join(argv.output, base))) {
|
||||||
|
req.url = fp
|
||||||
|
return serve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// does /regular/index.html exist? if so, redirect to /regular/
|
||||||
|
let indexFp = path.posix.join(fp, "index.html")
|
||||||
|
if (fs.existsSync(path.posix.join(argv.output, indexFp))) {
|
||||||
|
return redirect(fp + "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serve()
|
||||||
|
})
|
||||||
|
server.listen(argv.port)
|
||||||
|
const wss = new WebSocketServer({ port: argv.wsPort })
|
||||||
|
wss.on("connection", (ws) => connections.push(ws))
|
||||||
|
console.log(
|
||||||
|
chalk.cyan(
|
||||||
|
`Started a Quartz server listening at http://localhost:${argv.port}${argv.baseDir}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
console.log("hint: exit with ctrl+c")
|
||||||
|
chokidar
|
||||||
|
.watch(["**/*.ts", "**/*.tsx", "**/*.scss", "package.json"], {
|
||||||
|
ignoreInitial: true,
|
||||||
|
})
|
||||||
|
.on("all", async () => {
|
||||||
|
build(clientRefresh)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await build(() => {})
|
||||||
|
ctx.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles `npx quartz update`
|
||||||
|
* @param {*} argv arguments for `update`
|
||||||
|
*/
|
||||||
|
export async function handleUpdate(argv) {
|
||||||
|
const contentFolder = path.join(cwd, argv.directory)
|
||||||
|
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
||||||
|
console.log("Backing up your content")
|
||||||
|
execSync(
|
||||||
|
`git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`,
|
||||||
|
)
|
||||||
|
await stashContentFolder(contentFolder)
|
||||||
|
console.log(
|
||||||
|
"Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.",
|
||||||
|
)
|
||||||
|
gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH)
|
||||||
|
await popContentFolder(contentFolder)
|
||||||
|
console.log("Ensuring dependencies are up to date")
|
||||||
|
spawnSync("npm", ["i"], { stdio: "inherit" })
|
||||||
|
console.log(chalk.green("Done!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles `npx quartz restore`
|
||||||
|
* @param {*} argv arguments for `restore`
|
||||||
|
*/
|
||||||
|
export async function handleRestore(argv) {
|
||||||
|
const contentFolder = path.join(cwd, argv.directory)
|
||||||
|
await popContentFolder(contentFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles `npx quartz sync`
|
||||||
|
* @param {*} argv arguments for `sync`
|
||||||
|
*/
|
||||||
|
export async function handleSync(argv) {
|
||||||
|
const contentFolder = path.join(cwd, argv.directory)
|
||||||
|
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
||||||
|
console.log("Backing up your content")
|
||||||
|
|
||||||
|
if (argv.commit) {
|
||||||
|
const contentStat = await fs.promises.lstat(contentFolder)
|
||||||
|
if (contentStat.isSymbolicLink()) {
|
||||||
|
const linkTarg = await fs.promises.readlink(contentFolder)
|
||||||
|
console.log(chalk.yellow("Detected symlink, trying to dereference before committing"))
|
||||||
|
|
||||||
|
// stash symlink file
|
||||||
|
await stashContentFolder(contentFolder)
|
||||||
|
|
||||||
|
// follow symlink and copy content
|
||||||
|
await fs.promises.cp(linkTarg, contentFolder, {
|
||||||
|
recursive: true,
|
||||||
|
preserveTimestamps: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTimestamp = new Date().toLocaleString("en-US", {
|
||||||
|
dateStyle: "medium",
|
||||||
|
timeStyle: "short",
|
||||||
|
})
|
||||||
|
const commitMessage = argv.message ?? `Quartz sync: ${currentTimestamp}`
|
||||||
|
spawnSync("git", ["add", "."], { stdio: "inherit" })
|
||||||
|
spawnSync("git", ["commit", "-m", commitMessage], { stdio: "inherit" })
|
||||||
|
|
||||||
|
if (contentStat.isSymbolicLink()) {
|
||||||
|
// put symlink back
|
||||||
|
await popContentFolder(contentFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await stashContentFolder(contentFolder)
|
||||||
|
|
||||||
|
if (argv.pull) {
|
||||||
|
console.log(
|
||||||
|
"Pulling updates from your repository. You may need to resolve some `git` conflicts if you've made changes to components or plugins.",
|
||||||
|
)
|
||||||
|
gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
await popContentFolder(contentFolder)
|
||||||
|
if (argv.push) {
|
||||||
|
console.log("Pushing your changes")
|
||||||
|
spawnSync("git", ["push", "-f", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], { stdio: "inherit" })
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.green("Done!"))
|
||||||
|
}
|
52
quartz/cli/helpers.js
Normal file
52
quartz/cli/helpers.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { isCancel, outro } from "@clack/prompts"
|
||||||
|
import chalk from "chalk"
|
||||||
|
import { contentCacheFolder } from "./constants.js"
|
||||||
|
import { spawnSync } from "child_process"
|
||||||
|
import fs from "fs"
|
||||||
|
|
||||||
|
export function escapePath(fp) {
|
||||||
|
return fp
|
||||||
|
.replace(/\\ /g, " ") // unescape spaces
|
||||||
|
.replace(/^".*"$/, "$1")
|
||||||
|
.replace(/^'.*"$/, "$1")
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exitIfCancel(val) {
|
||||||
|
if (isCancel(val)) {
|
||||||
|
outro(chalk.red("Exiting"))
|
||||||
|
process.exit(0)
|
||||||
|
} else {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function stashContentFolder(contentFolder) {
|
||||||
|
await fs.promises.rm(contentCacheFolder, { force: true, recursive: true })
|
||||||
|
await fs.promises.cp(contentFolder, contentCacheFolder, {
|
||||||
|
force: true,
|
||||||
|
recursive: true,
|
||||||
|
verbatimSymlinks: true,
|
||||||
|
preserveTimestamps: true,
|
||||||
|
})
|
||||||
|
await fs.promises.rm(contentFolder, { force: true, recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gitPull(origin, branch) {
|
||||||
|
const flags = ["--no-rebase", "--autostash", "-s", "recursive", "-X", "ours", "--no-edit"]
|
||||||
|
const out = spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" })
|
||||||
|
if (out.stderr) {
|
||||||
|
throw new Error(`Error while pulling updates: ${out.stderr}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function popContentFolder(contentFolder) {
|
||||||
|
await fs.promises.rm(contentFolder, { force: true, recursive: true })
|
||||||
|
await fs.promises.cp(contentCacheFolder, contentFolder, {
|
||||||
|
force: true,
|
||||||
|
recursive: true,
|
||||||
|
verbatimSymlinks: true,
|
||||||
|
preserveTimestamps: true,
|
||||||
|
})
|
||||||
|
await fs.promises.rm(contentCacheFolder, { force: true, recursive: true })
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
|
||||||
function ArticleTitle({ fileData }: QuartzComponentProps) {
|
function ArticleTitle({ fileData, displayClass }: QuartzComponentProps) {
|
||||||
const title = fileData.frontmatter?.title
|
const title = fileData.frontmatter?.title
|
||||||
if (title) {
|
if (title) {
|
||||||
return <h1 class="article-title">{title}</h1>
|
return <h1 class={`article-title ${displayClass ?? ""}`}>{title}</h1>
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
|||||||
import style from "./styles/backlinks.scss"
|
import style from "./styles/backlinks.scss"
|
||||||
import { resolveRelative, simplifySlug } from "../util/path"
|
import { resolveRelative, simplifySlug } from "../util/path"
|
||||||
|
|
||||||
function Backlinks({ fileData, allFiles }: QuartzComponentProps) {
|
function Backlinks({ fileData, allFiles, displayClass }: QuartzComponentProps) {
|
||||||
const slug = simplifySlug(fileData.slug!)
|
const slug = simplifySlug(fileData.slug!)
|
||||||
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
|
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
|
||||||
return (
|
return (
|
||||||
<div class="backlinks">
|
<div class={`backlinks ${displayClass ?? ""}`}>
|
||||||
<h3>Backlinks</h3>
|
<h3>Backlinks</h3>
|
||||||
<ul class="overflow">
|
<ul class="overflow">
|
||||||
{backlinkFiles.length > 0 ? (
|
{backlinkFiles.length > 0 ? (
|
||||||
|
116
quartz/components/Breadcrumbs.tsx
Normal file
116
quartz/components/Breadcrumbs.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
import breadcrumbsStyle from "./styles/breadcrumbs.scss"
|
||||||
|
import { FullSlug, SimpleSlug, resolveRelative } from "../util/path"
|
||||||
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
|
|
||||||
|
type CrumbData = {
|
||||||
|
displayName: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BreadcrumbOptions {
|
||||||
|
/**
|
||||||
|
* Symbol between crumbs
|
||||||
|
*/
|
||||||
|
spacerSymbol: string
|
||||||
|
/**
|
||||||
|
* Name of first crumb
|
||||||
|
*/
|
||||||
|
rootName: string
|
||||||
|
/**
|
||||||
|
* wether to look up frontmatter title for folders (could cause performance problems with big vaults)
|
||||||
|
*/
|
||||||
|
resolveFrontmatterTitle: boolean
|
||||||
|
/**
|
||||||
|
* Wether to display breadcrumbs on root `index.md`
|
||||||
|
*/
|
||||||
|
hideOnRoot: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: BreadcrumbOptions = {
|
||||||
|
spacerSymbol: "❯",
|
||||||
|
rootName: "Home",
|
||||||
|
resolveFrontmatterTitle: true,
|
||||||
|
hideOnRoot: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData {
|
||||||
|
return {
|
||||||
|
displayName: displayName.replaceAll("-", " "),
|
||||||
|
path: resolveRelative(baseSlug, currentSlug),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ((opts?: Partial<BreadcrumbOptions>) => {
|
||||||
|
// Merge options with defaults
|
||||||
|
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
|
||||||
|
|
||||||
|
// computed index of folder name to its associated file data
|
||||||
|
let folderIndex: Map<string, QuartzPluginData> | undefined
|
||||||
|
|
||||||
|
function Breadcrumbs({ fileData, allFiles, displayClass }: QuartzComponentProps) {
|
||||||
|
// Hide crumbs on root if enabled
|
||||||
|
if (options.hideOnRoot && fileData.slug === "index") {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format entry for root element
|
||||||
|
const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug)
|
||||||
|
const crumbs: CrumbData[] = [firstEntry]
|
||||||
|
|
||||||
|
if (!folderIndex && options.resolveFrontmatterTitle) {
|
||||||
|
folderIndex = new Map()
|
||||||
|
// construct the index for the first time
|
||||||
|
for (const file of allFiles) {
|
||||||
|
if (file.slug?.endsWith("index")) {
|
||||||
|
const folderParts = file.filePath?.split("/")
|
||||||
|
if (folderParts) {
|
||||||
|
const folderName = folderParts[folderParts?.length - 2]
|
||||||
|
folderIndex.set(folderName, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split slug into hierarchy/parts
|
||||||
|
const slugParts = fileData.slug?.split("/")
|
||||||
|
if (slugParts) {
|
||||||
|
// full path until current part
|
||||||
|
let currentPath = ""
|
||||||
|
for (let i = 0; i < slugParts.length - 1; i++) {
|
||||||
|
let curPathSegment = slugParts[i]
|
||||||
|
|
||||||
|
// Try to resolve frontmatter folder title
|
||||||
|
const currentFile = folderIndex?.get(curPathSegment)
|
||||||
|
if (currentFile) {
|
||||||
|
curPathSegment = currentFile.frontmatter!.title
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add current slug to full path
|
||||||
|
currentPath += slugParts[i] + "/"
|
||||||
|
|
||||||
|
// Format and add current crumb
|
||||||
|
const crumb = formatCrumb(curPathSegment, fileData.slug!, currentPath as SimpleSlug)
|
||||||
|
crumbs.push(crumb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add current file to crumb (can directly use frontmatter title)
|
||||||
|
crumbs.push({
|
||||||
|
displayName: fileData.frontmatter!.title,
|
||||||
|
path: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<nav class={`breadcrumb-container ${displayClass ?? ""}`} aria-label="breadcrumbs">
|
||||||
|
{crumbs.map((crumb, index) => (
|
||||||
|
<div class="breadcrumb-element">
|
||||||
|
<a href={crumb.path}>{crumb.displayName}</a>
|
||||||
|
{index !== crumbs.length - 1 && <p>{` ${options.spacerSymbol} `}</p>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Breadcrumbs.css = breadcrumbsStyle
|
||||||
|
return Breadcrumbs
|
||||||
|
}) satisfies QuartzComponentConstructor
|
@ -3,7 +3,7 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
|||||||
import readingTime from "reading-time"
|
import readingTime from "reading-time"
|
||||||
|
|
||||||
export default (() => {
|
export default (() => {
|
||||||
function ContentMetadata({ cfg, fileData }: QuartzComponentProps) {
|
function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) {
|
||||||
const text = fileData.text
|
const text = fileData.text
|
||||||
if (text) {
|
if (text) {
|
||||||
const segments: string[] = []
|
const segments: string[] = []
|
||||||
@ -14,7 +14,7 @@ export default (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
segments.push(timeTaken)
|
segments.push(timeTaken)
|
||||||
return <p class="content-meta">{segments.join(", ")}</p>
|
return <p class={`content-meta ${displayClass ?? ""}`}>{segments.join(", ")}</p>
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
// see: https://v8.dev/features/modules#defer
|
// see: https://v8.dev/features/modules#defer
|
||||||
import darkmodeScript from "./scripts/darkmode.inline"
|
import darkmodeScript from "./scripts/darkmode.inline"
|
||||||
import styles from "./styles/darkmode.scss"
|
import styles from "./styles/darkmode.scss"
|
||||||
import { QuartzComponentConstructor } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
|
||||||
function Darkmode() {
|
function Darkmode({ displayClass }: QuartzComponentProps) {
|
||||||
return (
|
return (
|
||||||
<div class="darkmode">
|
<div class={`darkmode ${displayClass ?? ""}`}>
|
||||||
<input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
|
<input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
|
||||||
<label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
|
<label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
|
||||||
<svg
|
<svg
|
||||||
|
126
quartz/components/Explorer.tsx
Normal file
126
quartz/components/Explorer.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
import explorerStyle from "./styles/explorer.scss"
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import script from "./scripts/explorer.inline"
|
||||||
|
import { ExplorerNode, FileNode, Options } from "./ExplorerNode"
|
||||||
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
|
|
||||||
|
// Options interface defined in `ExplorerNode` to avoid circular dependency
|
||||||
|
const defaultOptions = {
|
||||||
|
title: "Explorer",
|
||||||
|
folderClickBehavior: "collapse",
|
||||||
|
folderDefaultState: "collapsed",
|
||||||
|
useSavedState: true,
|
||||||
|
sortFn: (a, b) => {
|
||||||
|
// Sort order: folders first, then files. Sort folders and files alphabetically
|
||||||
|
if ((!a.file && !b.file) || (a.file && b.file)) {
|
||||||
|
// numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10"
|
||||||
|
// sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A
|
||||||
|
return a.displayName.localeCompare(b.displayName, undefined, {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: "base",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (a.file && !b.file) {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filterFn: (node) => node.name !== "tags",
|
||||||
|
order: ["filter", "map", "sort"],
|
||||||
|
} satisfies Options
|
||||||
|
|
||||||
|
export default ((userOpts?: Partial<Options>) => {
|
||||||
|
// Parse config
|
||||||
|
const opts: Options = { ...defaultOptions, ...userOpts }
|
||||||
|
|
||||||
|
// memoized
|
||||||
|
let fileTree: FileNode
|
||||||
|
let jsonTree: string
|
||||||
|
|
||||||
|
function constructFileTree(allFiles: QuartzPluginData[]) {
|
||||||
|
if (!fileTree) {
|
||||||
|
// Construct tree from allFiles
|
||||||
|
fileTree = new FileNode("")
|
||||||
|
allFiles.forEach((file) => fileTree.add(file, 1))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keys of this object must match corresponding function name of `FileNode`,
|
||||||
|
* while values must be the argument that will be passed to the function.
|
||||||
|
*
|
||||||
|
* e.g. entry for FileNode.sort: `sort: opts.sortFn` (value is sort function from options)
|
||||||
|
*/
|
||||||
|
const functions = {
|
||||||
|
map: opts.mapFn,
|
||||||
|
sort: opts.sortFn,
|
||||||
|
filter: opts.filterFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied)
|
||||||
|
if (opts.order) {
|
||||||
|
// Order is important, use loop with index instead of order.map()
|
||||||
|
for (let i = 0; i < opts.order.length; i++) {
|
||||||
|
const functionName = opts.order[i]
|
||||||
|
if (functions[functionName]) {
|
||||||
|
// for every entry in order, call matching function in FileNode and pass matching argument
|
||||||
|
// e.g. i = 0; functionName = "filter"
|
||||||
|
// converted to: (if opts.filterFn) => fileTree.filter(opts.filterFn)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
// typescript cant statically check these dynamic references, so manually make sure reference is valid and ignore warning
|
||||||
|
fileTree[functionName].call(fileTree, functions[functionName])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all folders of tree. Initialize with collapsed state
|
||||||
|
const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
|
||||||
|
|
||||||
|
// Stringify to pass json tree as data attribute ([data-tree])
|
||||||
|
jsonTree = JSON.stringify(folders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
|
||||||
|
constructFileTree(allFiles)
|
||||||
|
return (
|
||||||
|
<div class={`explorer ${displayClass ?? ""}`}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="explorer"
|
||||||
|
data-behavior={opts.folderClickBehavior}
|
||||||
|
data-collapsed={opts.folderDefaultState}
|
||||||
|
data-savestate={opts.useSavedState}
|
||||||
|
data-tree={jsonTree}
|
||||||
|
>
|
||||||
|
<h1>{opts.title}</h1>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="5 8 14 8"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="fold"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div id="explorer-content">
|
||||||
|
<ul class="overflow" id="explorer-ul">
|
||||||
|
<ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
|
||||||
|
<li id="explorer-end" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Explorer.css = explorerStyle
|
||||||
|
Explorer.afterDOMLoaded = script
|
||||||
|
return Explorer
|
||||||
|
}) satisfies QuartzComponentConstructor
|
224
quartz/components/ExplorerNode.tsx
Normal file
224
quartz/components/ExplorerNode.tsx
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
|
import { resolveRelative } from "../util/path"
|
||||||
|
|
||||||
|
type OrderEntries = "sort" | "filter" | "map"
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
title: string
|
||||||
|
folderDefaultState: "collapsed" | "open"
|
||||||
|
folderClickBehavior: "collapse" | "link"
|
||||||
|
useSavedState: boolean
|
||||||
|
sortFn: (a: FileNode, b: FileNode) => number
|
||||||
|
filterFn?: (node: FileNode) => boolean
|
||||||
|
mapFn?: (node: FileNode) => void
|
||||||
|
order?: OrderEntries[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataWrapper = {
|
||||||
|
file: QuartzPluginData
|
||||||
|
path: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FolderState = {
|
||||||
|
path: string
|
||||||
|
collapsed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structure to add all files into a tree
|
||||||
|
export class FileNode {
|
||||||
|
children: FileNode[]
|
||||||
|
name: string
|
||||||
|
displayName: string
|
||||||
|
file: QuartzPluginData | null
|
||||||
|
depth: number
|
||||||
|
|
||||||
|
constructor(name: string, file?: QuartzPluginData, depth?: number) {
|
||||||
|
this.children = []
|
||||||
|
this.name = name
|
||||||
|
this.displayName = name
|
||||||
|
this.file = file ? structuredClone(file) : null
|
||||||
|
this.depth = depth ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private insert(file: DataWrapper) {
|
||||||
|
if (file.path.length === 1) {
|
||||||
|
if (file.path[0] !== "index.md") {
|
||||||
|
this.children.push(new FileNode(file.file.frontmatter!.title, file.file, this.depth + 1))
|
||||||
|
} else {
|
||||||
|
const title = file.file.frontmatter?.title
|
||||||
|
if (title && title !== "index" && file.path[0] === "index.md") {
|
||||||
|
this.displayName = title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const next = file.path[0]
|
||||||
|
file.path = file.path.splice(1)
|
||||||
|
for (const child of this.children) {
|
||||||
|
if (child.name === next) {
|
||||||
|
child.insert(file)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newChild = new FileNode(next, undefined, this.depth + 1)
|
||||||
|
newChild.insert(file)
|
||||||
|
this.children.push(newChild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new file to tree
|
||||||
|
add(file: QuartzPluginData, splice: number = 0) {
|
||||||
|
this.insert({ file, path: file.filePath!.split("/").splice(splice) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print tree structure (for debugging)
|
||||||
|
print(depth: number = 0) {
|
||||||
|
let folderChar = ""
|
||||||
|
if (!this.file) folderChar = "|"
|
||||||
|
console.log("-".repeat(depth), folderChar, this.name, this.depth)
|
||||||
|
this.children.forEach((e) => e.print(depth + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter FileNode tree. Behaves similar to `Array.prototype.filter()`, but modifies tree in place
|
||||||
|
* @param filterFn function to filter tree with
|
||||||
|
*/
|
||||||
|
filter(filterFn: (node: FileNode) => boolean) {
|
||||||
|
this.children = this.children.filter(filterFn)
|
||||||
|
this.children.forEach((child) => child.filter(filterFn))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter FileNode tree. Behaves similar to `Array.prototype.map()`, but modifies tree in place
|
||||||
|
* @param mapFn function to use for mapping over tree
|
||||||
|
*/
|
||||||
|
map(mapFn: (node: FileNode) => void) {
|
||||||
|
mapFn(this)
|
||||||
|
|
||||||
|
this.children.forEach((child) => child.map(mapFn))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get folder representation with state of tree.
|
||||||
|
* Intended to only be called on root node before changes to the tree are made
|
||||||
|
* @param collapsed default state of folders (collapsed by default or not)
|
||||||
|
* @returns array containing folder state for tree
|
||||||
|
*/
|
||||||
|
getFolderPaths(collapsed: boolean): FolderState[] {
|
||||||
|
const folderPaths: FolderState[] = []
|
||||||
|
|
||||||
|
const traverse = (node: FileNode, currentPath: string) => {
|
||||||
|
if (!node.file) {
|
||||||
|
const folderPath = currentPath + (currentPath ? "/" : "") + node.name
|
||||||
|
if (folderPath !== "") {
|
||||||
|
folderPaths.push({ path: folderPath, collapsed })
|
||||||
|
}
|
||||||
|
node.children.forEach((child) => traverse(child, folderPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(this, "")
|
||||||
|
|
||||||
|
return folderPaths
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort order: folders first, then files. Sort folders and files alphabetically
|
||||||
|
/**
|
||||||
|
* Sorts tree according to sort/compare function
|
||||||
|
* @param sortFn compare function used for `.sort()`, also used recursively for children
|
||||||
|
*/
|
||||||
|
sort(sortFn: (a: FileNode, b: FileNode) => number) {
|
||||||
|
this.children = this.children.sort(sortFn)
|
||||||
|
this.children.forEach((e) => e.sort(sortFn))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExplorerNodeProps = {
|
||||||
|
node: FileNode
|
||||||
|
opts: Options
|
||||||
|
fileData: QuartzPluginData
|
||||||
|
fullPath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodeProps) {
|
||||||
|
// Get options
|
||||||
|
const folderBehavior = opts.folderClickBehavior
|
||||||
|
const isDefaultOpen = opts.folderDefaultState === "open"
|
||||||
|
|
||||||
|
// Calculate current folderPath
|
||||||
|
let pathOld = fullPath ? fullPath : ""
|
||||||
|
let folderPath = ""
|
||||||
|
if (node.name !== "") {
|
||||||
|
folderPath = `${pathOld}/${node.name}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
{node.file ? (
|
||||||
|
// Single file node
|
||||||
|
<li key={node.file.slug}>
|
||||||
|
<a href={resolveRelative(fileData.slug!, node.file.slug!)} data-for={node.file.slug}>
|
||||||
|
{node.displayName}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{node.name !== "" && (
|
||||||
|
// Node with entire folder
|
||||||
|
// Render svg button + folder name, then children
|
||||||
|
<div class="folder-container">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="5 8 14 8"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="folder-icon"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
{/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */}
|
||||||
|
<div key={node.name} data-folderpath={folderPath}>
|
||||||
|
{folderBehavior === "link" ? (
|
||||||
|
<a href={`${folderPath}`} data-for={node.name} class="folder-title">
|
||||||
|
{node.displayName}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<button class="folder-button">
|
||||||
|
<p class="folder-title">{node.displayName}</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Recursively render children of folder */}
|
||||||
|
<div class={`folder-outer ${node.depth === 0 || isDefaultOpen ? "open" : ""}`}>
|
||||||
|
<ul
|
||||||
|
// Inline style for left folder paddings
|
||||||
|
style={{
|
||||||
|
paddingLeft: node.name !== "" ? "1.4rem" : "0",
|
||||||
|
}}
|
||||||
|
class="content"
|
||||||
|
data-folderul={folderPath}
|
||||||
|
>
|
||||||
|
{node.children.map((childNode, i) => (
|
||||||
|
<ExplorerNode
|
||||||
|
node={childNode}
|
||||||
|
key={i}
|
||||||
|
opts={opts}
|
||||||
|
fullPath={folderPath}
|
||||||
|
fileData={fileData}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { QuartzComponentConstructor } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
import style from "./styles/footer.scss"
|
import style from "./styles/footer.scss"
|
||||||
import { version } from "../../package.json"
|
import { version } from "../../package.json"
|
||||||
|
|
||||||
@ -7,11 +7,11 @@ interface Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default ((opts?: Options) => {
|
export default ((opts?: Options) => {
|
||||||
function Footer() {
|
function Footer({ displayClass }: QuartzComponentProps) {
|
||||||
const year = new Date().getFullYear()
|
const year = new Date().getFullYear()
|
||||||
const links = opts?.links ?? []
|
const links = opts?.links ?? []
|
||||||
return (
|
return (
|
||||||
<footer>
|
<footer class={`${displayClass ?? ""}`}>
|
||||||
<hr />
|
<hr />
|
||||||
<p>
|
<p>
|
||||||
Created with <a href="https://quartz.jzhao.xyz/">Quartz v{version}</a>, © {year}
|
Created with <a href="https://quartz.jzhao.xyz/">Quartz v{version}</a>, © {year}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { QuartzComponentConstructor } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import script from "./scripts/graph.inline"
|
import script from "./scripts/graph.inline"
|
||||||
import style from "./styles/graph.scss"
|
import style from "./styles/graph.scss"
|
||||||
@ -13,6 +13,8 @@ export interface D3Config {
|
|||||||
linkDistance: number
|
linkDistance: number
|
||||||
fontSize: number
|
fontSize: number
|
||||||
opacityScale: number
|
opacityScale: number
|
||||||
|
removeTags: string[]
|
||||||
|
showTags: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GraphOptions {
|
interface GraphOptions {
|
||||||
@ -31,6 +33,8 @@ const defaultOptions: GraphOptions = {
|
|||||||
linkDistance: 30,
|
linkDistance: 30,
|
||||||
fontSize: 0.6,
|
fontSize: 0.6,
|
||||||
opacityScale: 1,
|
opacityScale: 1,
|
||||||
|
showTags: true,
|
||||||
|
removeTags: [],
|
||||||
},
|
},
|
||||||
globalGraph: {
|
globalGraph: {
|
||||||
drag: true,
|
drag: true,
|
||||||
@ -42,15 +46,17 @@ const defaultOptions: GraphOptions = {
|
|||||||
linkDistance: 30,
|
linkDistance: 30,
|
||||||
fontSize: 0.6,
|
fontSize: 0.6,
|
||||||
opacityScale: 1,
|
opacityScale: 1,
|
||||||
|
showTags: true,
|
||||||
|
removeTags: [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ((opts?: GraphOptions) => {
|
export default ((opts?: GraphOptions) => {
|
||||||
function Graph() {
|
function Graph({ displayClass }: QuartzComponentProps) {
|
||||||
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
|
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
|
||||||
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
|
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
|
||||||
return (
|
return (
|
||||||
<div class="graph">
|
<div class={`graph ${displayClass ?? ""}`}>
|
||||||
<h3>Graph View</h3>
|
<h3>Graph View</h3>
|
||||||
<div class="graph-outer">
|
<div class="graph-outer">
|
||||||
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
|
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { joinSegments, pathToRoot } from "../util/path"
|
import { FullSlug, _stripSlashes, joinSegments, pathToRoot } from "../util/path"
|
||||||
import { JSResourceToScriptElement } from "../util/resources"
|
import { JSResourceToScriptElement } from "../util/resources"
|
||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
|
||||||
@ -7,7 +7,11 @@ export default (() => {
|
|||||||
const title = fileData.frontmatter?.title ?? "Untitled"
|
const title = fileData.frontmatter?.title ?? "Untitled"
|
||||||
const description = fileData.description?.trim() ?? "No description provided"
|
const description = fileData.description?.trim() ?? "No description provided"
|
||||||
const { css, js } = externalResources
|
const { css, js } = externalResources
|
||||||
const baseDir = pathToRoot(fileData.slug!)
|
|
||||||
|
const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`)
|
||||||
|
const path = url.pathname as FullSlug
|
||||||
|
const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!)
|
||||||
|
|
||||||
const iconPath = joinSegments(baseDir, "static/icon.png")
|
const iconPath = joinSegments(baseDir, "static/icon.png")
|
||||||
const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png`
|
const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png`
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { pathToRoot } from "../util/path"
|
import { pathToRoot } from "../util/path"
|
||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
|
||||||
function PageTitle({ fileData, cfg }: QuartzComponentProps) {
|
function PageTitle({ fileData, cfg, displayClass }: QuartzComponentProps) {
|
||||||
const title = cfg?.pageTitle ?? "Untitled Quartz"
|
const title = cfg?.pageTitle ?? "Untitled Quartz"
|
||||||
const baseDir = pathToRoot(fileData.slug!)
|
const baseDir = pathToRoot(fileData.slug!)
|
||||||
return (
|
return (
|
||||||
<h1 class="page-title">
|
<h1 class={`page-title ${displayClass ?? ""}`}>
|
||||||
<a href={baseDir}>{title}</a>
|
<a href={baseDir}>{title}</a>
|
||||||
</h1>
|
</h1>
|
||||||
)
|
)
|
||||||
|
@ -23,13 +23,12 @@ const defaultOptions = (cfg: GlobalConfiguration): Options => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export default ((userOpts?: Partial<Options>) => {
|
export default ((userOpts?: Partial<Options>) => {
|
||||||
function RecentNotes(props: QuartzComponentProps) {
|
function RecentNotes({ allFiles, fileData, displayClass, cfg }: QuartzComponentProps) {
|
||||||
const { allFiles, fileData, displayClass, cfg } = props
|
|
||||||
const opts = { ...defaultOptions(cfg), ...userOpts }
|
const opts = { ...defaultOptions(cfg), ...userOpts }
|
||||||
const pages = allFiles.filter(opts.filter).sort(opts.sort)
|
const pages = allFiles.filter(opts.filter).sort(opts.sort)
|
||||||
const remaining = Math.max(0, pages.length - opts.limit)
|
const remaining = Math.max(0, pages.length - opts.limit)
|
||||||
return (
|
return (
|
||||||
<div class={`recent-notes ${displayClass}`}>
|
<div class={`recent-notes ${displayClass ?? ""}`}>
|
||||||
<h3>{opts.title}</h3>
|
<h3>{opts.title}</h3>
|
||||||
<ul class="recent-ul">
|
<ul class="recent-ul">
|
||||||
{pages.slice(0, opts.limit).map((page) => {
|
{pages.slice(0, opts.limit).map((page) => {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { QuartzComponentConstructor } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
import style from "./styles/search.scss"
|
import style from "./styles/search.scss"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import script from "./scripts/search.inline"
|
import script from "./scripts/search.inline"
|
||||||
|
|
||||||
export default (() => {
|
export default (() => {
|
||||||
function Search() {
|
function Search({ displayClass }: QuartzComponentProps) {
|
||||||
return (
|
return (
|
||||||
<div class="search">
|
<div class={`search ${displayClass ?? ""}`}>
|
||||||
<div id="search-icon">
|
<div id="search-icon">
|
||||||
<p>Search</p>
|
<p>Search</p>
|
||||||
<div></div>
|
<div></div>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
|
||||||
function Spacer({ displayClass }: QuartzComponentProps) {
|
function Spacer({ displayClass }: QuartzComponentProps) {
|
||||||
const className = displayClass ? `spacer ${displayClass}` : "spacer"
|
return <div class={`spacer ${displayClass ?? ""}`}></div>
|
||||||
return <div class={className}></div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (() => Spacer) satisfies QuartzComponentConstructor
|
export default (() => Spacer) satisfies QuartzComponentConstructor
|
||||||
|
@ -19,8 +19,8 @@ function TableOfContents({ fileData, displayClass }: QuartzComponentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`toc ${displayClass}`}>
|
<div class={`toc ${displayClass ?? ""}`}>
|
||||||
<button type="button" id="toc">
|
<button type="button" id="toc" class={fileData.collapseToc ? "collapsed" : ""}>
|
||||||
<h3>Table of Contents</h3>
|
<h3>Table of Contents</h3>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -60,7 +60,7 @@ function LegacyTableOfContents({ fileData }: QuartzComponentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<details id="toc" open>
|
<details id="toc" open={!fileData.collapseToc}>
|
||||||
<summary>
|
<summary>
|
||||||
<h3>Table of Contents</h3>
|
<h3>Table of Contents</h3>
|
||||||
</summary>
|
</summary>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { pathToRoot, slugTag } from "../util/path"
|
import { pathToRoot, slugTag } from "../util/path"
|
||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
|
||||||
function TagList({ fileData }: QuartzComponentProps) {
|
function TagList({ fileData, displayClass }: QuartzComponentProps) {
|
||||||
const tags = fileData.frontmatter?.tags
|
const tags = fileData.frontmatter?.tags
|
||||||
const baseDir = pathToRoot(fileData.slug!)
|
const baseDir = pathToRoot(fileData.slug!)
|
||||||
if (tags && tags.length > 0) {
|
if (tags && tags.length > 0) {
|
||||||
return (
|
return (
|
||||||
<ul class="tags">
|
<ul class={`tags ${displayClass ?? ""}`}>
|
||||||
{tags.map((tag) => {
|
{tags.map((tag) => {
|
||||||
const display = `#${tag}`
|
const display = `#${tag}`
|
||||||
const linkDest = baseDir + `/tags/${slugTag(tag)}`
|
const linkDest = baseDir + `/tags/${slugTag(tag)}`
|
||||||
@ -32,6 +32,12 @@ TagList.css = `
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-li > .section > .tags {
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags > li {
|
.tags > li {
|
||||||
@ -41,7 +47,7 @@ TagList.css = `
|
|||||||
overflow-wrap: normal;
|
overflow-wrap: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.tag-link {
|
a.internal.tag-link {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: var(--highlight);
|
background-color: var(--highlight);
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import ArticleTitle from "./ArticleTitle"
|
|
||||||
import Content from "./pages/Content"
|
import Content from "./pages/Content"
|
||||||
import TagContent from "./pages/TagContent"
|
import TagContent from "./pages/TagContent"
|
||||||
import FolderContent from "./pages/FolderContent"
|
import FolderContent from "./pages/FolderContent"
|
||||||
|
import NotFound from "./pages/404"
|
||||||
|
import ArticleTitle from "./ArticleTitle"
|
||||||
import Darkmode from "./Darkmode"
|
import Darkmode from "./Darkmode"
|
||||||
import Head from "./Head"
|
import Head from "./Head"
|
||||||
import PageTitle from "./PageTitle"
|
import PageTitle from "./PageTitle"
|
||||||
import ContentMeta from "./ContentMeta"
|
import ContentMeta from "./ContentMeta"
|
||||||
import Spacer from "./Spacer"
|
import Spacer from "./Spacer"
|
||||||
import TableOfContents from "./TableOfContents"
|
import TableOfContents from "./TableOfContents"
|
||||||
|
import Explorer from "./Explorer"
|
||||||
import TagList from "./TagList"
|
import TagList from "./TagList"
|
||||||
import Graph from "./Graph"
|
import Graph from "./Graph"
|
||||||
import Backlinks from "./Backlinks"
|
import Backlinks from "./Backlinks"
|
||||||
@ -16,6 +18,7 @@ import Footer from "./Footer"
|
|||||||
import DesktopOnly from "./DesktopOnly"
|
import DesktopOnly from "./DesktopOnly"
|
||||||
import MobileOnly from "./MobileOnly"
|
import MobileOnly from "./MobileOnly"
|
||||||
import RecentNotes from "./RecentNotes"
|
import RecentNotes from "./RecentNotes"
|
||||||
|
import Breadcrumbs from "./Breadcrumbs"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ArticleTitle,
|
ArticleTitle,
|
||||||
@ -28,6 +31,7 @@ export {
|
|||||||
ContentMeta,
|
ContentMeta,
|
||||||
Spacer,
|
Spacer,
|
||||||
TableOfContents,
|
TableOfContents,
|
||||||
|
Explorer,
|
||||||
TagList,
|
TagList,
|
||||||
Graph,
|
Graph,
|
||||||
Backlinks,
|
Backlinks,
|
||||||
@ -36,4 +40,6 @@ export {
|
|||||||
DesktopOnly,
|
DesktopOnly,
|
||||||
MobileOnly,
|
MobileOnly,
|
||||||
RecentNotes,
|
RecentNotes,
|
||||||
|
NotFound,
|
||||||
|
Breadcrumbs,
|
||||||
}
|
}
|
||||||
|
12
quartz/components/pages/404.tsx
Normal file
12
quartz/components/pages/404.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { QuartzComponentConstructor } from "../types"
|
||||||
|
|
||||||
|
function NotFound() {
|
||||||
|
return (
|
||||||
|
<article class="popover-hint">
|
||||||
|
<h1>404</h1>
|
||||||
|
<p>Either this page is private or doesn't exist.</p>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (() => NotFound) satisfies QuartzComponentConstructor
|
@ -1,10 +1,8 @@
|
|||||||
|
import { htmlToJsx } from "../../util/jsx"
|
||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
|
||||||
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
|
|
||||||
|
|
||||||
function Content({ tree }: QuartzComponentProps) {
|
function Content({ fileData, tree }: QuartzComponentProps) {
|
||||||
// @ts-ignore (preact makes it angry)
|
const content = htmlToJsx(fileData.filePath!, tree)
|
||||||
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
|
||||||
return <article class="popover-hint">{content}</article>
|
return <article class="popover-hint">{content}</article>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
|
||||||
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
import style from "../styles/listPage.scss"
|
import style from "../styles/listPage.scss"
|
||||||
import { PageList } from "../PageList"
|
import { PageList } from "../PageList"
|
||||||
import { _stripSlashes, simplifySlug } from "../../util/path"
|
import { _stripSlashes, simplifySlug } from "../../util/path"
|
||||||
import { Root } from "hast"
|
import { Root } from "hast"
|
||||||
|
import { pluralize } from "../../util/lang"
|
||||||
|
import { htmlToJsx } from "../../util/jsx"
|
||||||
|
|
||||||
function FolderContent(props: QuartzComponentProps) {
|
function FolderContent(props: QuartzComponentProps) {
|
||||||
const { tree, fileData, allFiles } = props
|
const { tree, fileData, allFiles } = props
|
||||||
@ -28,15 +28,14 @@ function FolderContent(props: QuartzComponentProps) {
|
|||||||
const content =
|
const content =
|
||||||
(tree as Root).children.length === 0
|
(tree as Root).children.length === 0
|
||||||
? fileData.description
|
? fileData.description
|
||||||
: // @ts-ignore
|
: htmlToJsx(fileData.filePath!, tree)
|
||||||
toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="popover-hint">
|
<div class="popover-hint">
|
||||||
<article>
|
<article>
|
||||||
<p>{content}</p>
|
<p>{content}</p>
|
||||||
</article>
|
</article>
|
||||||
<p>{allPagesInFolder.length} items under this folder.</p>
|
<p>{pluralize(allPagesInFolder.length, "item")} under this folder.</p>
|
||||||
<div>
|
<div>
|
||||||
<PageList {...listProps} />
|
<PageList {...listProps} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
|
||||||
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
|
|
||||||
import style from "../styles/listPage.scss"
|
import style from "../styles/listPage.scss"
|
||||||
import { PageList } from "../PageList"
|
import { PageList } from "../PageList"
|
||||||
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
|
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
|
||||||
import { QuartzPluginData } from "../../plugins/vfile"
|
import { QuartzPluginData } from "../../plugins/vfile"
|
||||||
import { Root } from "hast"
|
import { Root } from "hast"
|
||||||
|
import { pluralize } from "../../util/lang"
|
||||||
|
import { htmlToJsx } from "../../util/jsx"
|
||||||
|
|
||||||
const numPages = 10
|
const numPages = 10
|
||||||
function TagContent(props: QuartzComponentProps) {
|
function TagContent(props: QuartzComponentProps) {
|
||||||
@ -25,8 +25,7 @@ function TagContent(props: QuartzComponentProps) {
|
|||||||
const content =
|
const content =
|
||||||
(tree as Root).children.length === 0
|
(tree as Root).children.length === 0
|
||||||
? fileData.description
|
? fileData.description
|
||||||
: // @ts-ignore
|
: htmlToJsx(fileData.filePath!, tree)
|
||||||
toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
|
||||||
|
|
||||||
if (tag === "") {
|
if (tag === "") {
|
||||||
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
|
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
|
||||||
@ -54,13 +53,13 @@ function TagContent(props: QuartzComponentProps) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>
|
<h2>
|
||||||
<a class="internal tag-link" href={`./${tag}`}>
|
<a class="internal tag-link" href={`../tags/${tag}`}>
|
||||||
#{tag}
|
#{tag}
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
{content && <p>{content}</p>}
|
{content && <p>{content}</p>}
|
||||||
<p>
|
<p>
|
||||||
{pages.length} items with this tag.{" "}
|
{pluralize(pages.length, "item")} with this tag.{" "}
|
||||||
{pages.length > numPages && `Showing first ${numPages}.`}
|
{pages.length > numPages && `Showing first ${numPages}.`}
|
||||||
</p>
|
</p>
|
||||||
<PageList limit={numPages} {...listProps} />
|
<PageList limit={numPages} {...listProps} />
|
||||||
@ -80,7 +79,7 @@ function TagContent(props: QuartzComponentProps) {
|
|||||||
return (
|
return (
|
||||||
<div class="popover-hint">
|
<div class="popover-hint">
|
||||||
<article>{content}</article>
|
<article>{content}</article>
|
||||||
<p>{pages.length} items with this tag.</p>
|
<p>{pluralize(pages.length, "item")} with this tag.</p>
|
||||||
<div>
|
<div>
|
||||||
<PageList {...listProps} />
|
<PageList {...listProps} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,9 @@ import { QuartzComponent, QuartzComponentProps } from "./types"
|
|||||||
import HeaderConstructor from "./Header"
|
import HeaderConstructor from "./Header"
|
||||||
import BodyConstructor from "./Body"
|
import BodyConstructor from "./Body"
|
||||||
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
|
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
|
||||||
import { FullSlug, joinSegments, pathToRoot } from "../util/path"
|
import { FullSlug, RelativeURL, joinSegments } from "../util/path"
|
||||||
|
import { visit } from "unist-util-visit"
|
||||||
|
import { Root, Element, ElementContent } from "hast"
|
||||||
|
|
||||||
interface RenderComponents {
|
interface RenderComponents {
|
||||||
head: QuartzComponent
|
head: QuartzComponent
|
||||||
@ -15,9 +17,10 @@ interface RenderComponents {
|
|||||||
footer: QuartzComponent
|
footer: QuartzComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pageResources(slug: FullSlug, staticResources: StaticResources): StaticResources {
|
export function pageResources(
|
||||||
const baseDir = pathToRoot(slug)
|
baseDir: FullSlug | RelativeURL,
|
||||||
|
staticResources: StaticResources,
|
||||||
|
): StaticResources {
|
||||||
const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json")
|
const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json")
|
||||||
const contentIndexScript = `const fetchData = fetch(\`${contentIndexPath}\`).then(data => data.json())`
|
const contentIndexScript = `const fetchData = fetch(\`${contentIndexPath}\`).then(data => data.json())`
|
||||||
|
|
||||||
@ -52,6 +55,99 @@ export function renderPage(
|
|||||||
components: RenderComponents,
|
components: RenderComponents,
|
||||||
pageResources: StaticResources,
|
pageResources: StaticResources,
|
||||||
): string {
|
): string {
|
||||||
|
// process transcludes in componentData
|
||||||
|
visit(componentData.tree as Root, "element", (node, _index, _parent) => {
|
||||||
|
if (node.tagName === "blockquote") {
|
||||||
|
const classNames = (node.properties?.className ?? []) as string[]
|
||||||
|
if (classNames.includes("transclude")) {
|
||||||
|
const inner = node.children[0] as Element
|
||||||
|
const transcludeTarget = inner.properties?.["data-slug"] as FullSlug
|
||||||
|
|
||||||
|
// TODO: avoid this expensive find operation and construct an index ahead of time
|
||||||
|
const page = componentData.allFiles.find((f) => f.slug === transcludeTarget)
|
||||||
|
if (!page) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let blockRef = node.properties?.dataBlock as string | undefined
|
||||||
|
if (blockRef?.startsWith("^")) {
|
||||||
|
// block transclude
|
||||||
|
blockRef = blockRef.slice(1)
|
||||||
|
let blockNode = page.blocks?.[blockRef]
|
||||||
|
if (blockNode) {
|
||||||
|
if (blockNode.tagName === "li") {
|
||||||
|
blockNode = {
|
||||||
|
type: "element",
|
||||||
|
tagName: "ul",
|
||||||
|
children: [blockNode],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.children = [
|
||||||
|
blockNode,
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
tagName: "a",
|
||||||
|
properties: { href: inner.properties?.href, class: ["internal"] },
|
||||||
|
children: [{ type: "text", value: `Link to original` }],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else if (blockRef?.startsWith("#") && page.htmlAst) {
|
||||||
|
// header transclude
|
||||||
|
blockRef = blockRef.slice(1)
|
||||||
|
let startIdx = undefined
|
||||||
|
let endIdx = undefined
|
||||||
|
for (const [i, el] of page.htmlAst.children.entries()) {
|
||||||
|
if (el.type === "element" && el.tagName.match(/h[1-6]/)) {
|
||||||
|
if (endIdx) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIdx) {
|
||||||
|
endIdx = i
|
||||||
|
} else if (el.properties?.id === blockRef) {
|
||||||
|
startIdx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startIdx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
node.children = [
|
||||||
|
...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]),
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
tagName: "a",
|
||||||
|
properties: { href: inner.properties?.href, class: ["internal"] },
|
||||||
|
children: [{ type: "text", value: `Link to original` }],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else if (page.htmlAst) {
|
||||||
|
// page transclude
|
||||||
|
node.children = [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
tagName: "h1",
|
||||||
|
children: [
|
||||||
|
{ type: "text", value: page.frontmatter?.title ?? `Transclude of ${page.slug}` },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...(page.htmlAst.children as ElementContent[]),
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
tagName: "a",
|
||||||
|
properties: { href: inner.properties?.href, class: ["internal"] },
|
||||||
|
children: [{ type: "text", value: `Link to original` }],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
head: Head,
|
head: Head,
|
||||||
header,
|
header,
|
||||||
|
@ -20,4 +20,13 @@ document.addEventListener("nav", () => {
|
|||||||
if (currentTheme === "dark") {
|
if (currentTheme === "dark") {
|
||||||
toggleSwitch.checked = true
|
toggleSwitch.checked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for changes in prefers-color-scheme
|
||||||
|
const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
colorSchemeMediaQuery.addEventListener("change", (e) => {
|
||||||
|
const newTheme = e.matches ? "dark" : "light"
|
||||||
|
document.documentElement.setAttribute("saved-theme", newTheme)
|
||||||
|
localStorage.setItem("theme", newTheme)
|
||||||
|
toggleSwitch.checked = e.matches
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
164
quartz/components/scripts/explorer.inline.ts
Normal file
164
quartz/components/scripts/explorer.inline.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { FolderState } from "../ExplorerNode"
|
||||||
|
|
||||||
|
// Current state of folders
|
||||||
|
let explorerState: FolderState[]
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
// If last element is observed, remove gradient of "overflow" class so element is visible
|
||||||
|
const explorer = document.getElementById("explorer-ul")
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
explorer?.classList.add("no-background")
|
||||||
|
} else {
|
||||||
|
explorer?.classList.remove("no-background")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function toggleExplorer(this: HTMLElement) {
|
||||||
|
// Toggle collapsed state of entire explorer
|
||||||
|
this.classList.toggle("collapsed")
|
||||||
|
const content = this.nextElementSibling as HTMLElement
|
||||||
|
content.classList.toggle("collapsed")
|
||||||
|
content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFolder(evt: MouseEvent) {
|
||||||
|
evt.stopPropagation()
|
||||||
|
|
||||||
|
// Element that was clicked
|
||||||
|
const target = evt.target as HTMLElement
|
||||||
|
|
||||||
|
// Check if target was svg icon or button
|
||||||
|
const isSvg = target.nodeName === "svg"
|
||||||
|
|
||||||
|
// corresponding <ul> element relative to clicked button/folder
|
||||||
|
let childFolderContainer: HTMLElement
|
||||||
|
|
||||||
|
// <li> element of folder (stores folder-path dataset)
|
||||||
|
let currentFolderParent: HTMLElement
|
||||||
|
|
||||||
|
// Get correct relative container and toggle collapsed class
|
||||||
|
if (isSvg) {
|
||||||
|
childFolderContainer = target.parentElement?.nextSibling as HTMLElement
|
||||||
|
currentFolderParent = target.nextElementSibling as HTMLElement
|
||||||
|
|
||||||
|
childFolderContainer.classList.toggle("open")
|
||||||
|
} else {
|
||||||
|
childFolderContainer = target.parentElement?.parentElement?.nextElementSibling as HTMLElement
|
||||||
|
currentFolderParent = target.parentElement as HTMLElement
|
||||||
|
|
||||||
|
childFolderContainer.classList.toggle("open")
|
||||||
|
}
|
||||||
|
if (!childFolderContainer) return
|
||||||
|
|
||||||
|
// Collapse folder container
|
||||||
|
const isCollapsed = childFolderContainer.classList.contains("open")
|
||||||
|
setFolderState(childFolderContainer, !isCollapsed)
|
||||||
|
|
||||||
|
// Save folder state to localStorage
|
||||||
|
const clickFolderPath = currentFolderParent.dataset.folderpath as string
|
||||||
|
|
||||||
|
// Remove leading "/"
|
||||||
|
const fullFolderPath = clickFolderPath.substring(1)
|
||||||
|
toggleCollapsedByPath(explorerState, fullFolderPath)
|
||||||
|
|
||||||
|
const stringifiedFileTree = JSON.stringify(explorerState)
|
||||||
|
localStorage.setItem("fileTree", stringifiedFileTree)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupExplorer() {
|
||||||
|
// Set click handler for collapsing entire explorer
|
||||||
|
const explorer = document.getElementById("explorer")
|
||||||
|
|
||||||
|
// Get folder state from local storage
|
||||||
|
const storageTree = localStorage.getItem("fileTree")
|
||||||
|
|
||||||
|
// Convert to bool
|
||||||
|
const useSavedFolderState = explorer?.dataset.savestate === "true"
|
||||||
|
|
||||||
|
if (explorer) {
|
||||||
|
// Get config
|
||||||
|
const collapseBehavior = explorer.dataset.behavior
|
||||||
|
|
||||||
|
// Add click handlers for all folders (click handler on folder "label")
|
||||||
|
if (collapseBehavior === "collapse") {
|
||||||
|
Array.prototype.forEach.call(
|
||||||
|
document.getElementsByClassName("folder-button"),
|
||||||
|
function (item) {
|
||||||
|
item.removeEventListener("click", toggleFolder)
|
||||||
|
item.addEventListener("click", toggleFolder)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click handler to main explorer
|
||||||
|
explorer.removeEventListener("click", toggleExplorer)
|
||||||
|
explorer.addEventListener("click", toggleExplorer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up click handlers for each folder (click handler on folder "icon")
|
||||||
|
Array.prototype.forEach.call(document.getElementsByClassName("folder-icon"), function (item) {
|
||||||
|
item.removeEventListener("click", toggleFolder)
|
||||||
|
item.addEventListener("click", toggleFolder)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (storageTree && useSavedFolderState) {
|
||||||
|
// Get state from localStorage and set folder state
|
||||||
|
explorerState = JSON.parse(storageTree)
|
||||||
|
explorerState.map((folderUl) => {
|
||||||
|
// grab <li> element for matching folder path
|
||||||
|
const folderLi = document.querySelector(
|
||||||
|
`[data-folderpath='/${folderUl.path}']`,
|
||||||
|
) as HTMLElement
|
||||||
|
|
||||||
|
// Get corresponding content <ul> tag and set state
|
||||||
|
if (folderLi) {
|
||||||
|
const folderUL = folderLi.parentElement?.nextElementSibling
|
||||||
|
if (folderUL) {
|
||||||
|
setFolderState(folderUL as HTMLElement, folderUl.collapsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// If tree is not in localStorage or config is disabled, use tree passed from Explorer as dataset
|
||||||
|
explorerState = JSON.parse(explorer?.dataset.tree as string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", setupExplorer)
|
||||||
|
document.addEventListener("nav", () => {
|
||||||
|
setupExplorer()
|
||||||
|
|
||||||
|
const explorerContent = document.getElementById("explorer-ul")
|
||||||
|
// select pseudo element at end of list
|
||||||
|
const lastItem = document.getElementById("explorer-end")
|
||||||
|
|
||||||
|
observer.disconnect()
|
||||||
|
observer.observe(lastItem as Element)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the state of a given folder
|
||||||
|
* @param folderElement <div class="folder-outer"> Element of folder (parent)
|
||||||
|
* @param collapsed if folder should be set to collapsed or not
|
||||||
|
*/
|
||||||
|
function setFolderState(folderElement: HTMLElement, collapsed: boolean) {
|
||||||
|
if (collapsed) {
|
||||||
|
folderElement?.classList.remove("open")
|
||||||
|
} else {
|
||||||
|
folderElement?.classList.add("open")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles visibility of a folder
|
||||||
|
* @param array array of FolderState (`fileTree`, either get from local storage or data attribute)
|
||||||
|
* @param path path to folder (e.g. 'advanced/more/more2')
|
||||||
|
*/
|
||||||
|
function toggleCollapsedByPath(array: FolderState[], path: string) {
|
||||||
|
const entry = array.find((item) => item.path === path)
|
||||||
|
if (entry) {
|
||||||
|
entry.collapsed = !entry.collapsed
|
||||||
|
}
|
||||||
|
}
|
@ -42,19 +42,38 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
linkDistance,
|
linkDistance,
|
||||||
fontSize,
|
fontSize,
|
||||||
opacityScale,
|
opacityScale,
|
||||||
|
removeTags,
|
||||||
|
showTags,
|
||||||
} = JSON.parse(graph.dataset["cfg"]!)
|
} = JSON.parse(graph.dataset["cfg"]!)
|
||||||
|
|
||||||
const data = await fetchData
|
const data = await fetchData
|
||||||
|
|
||||||
const links: LinkData[] = []
|
const links: LinkData[] = []
|
||||||
|
const tags: SimpleSlug[] = []
|
||||||
|
|
||||||
|
const validLinks = new Set(Object.keys(data).map((slug) => simplifySlug(slug as FullSlug)))
|
||||||
|
|
||||||
for (const [src, details] of Object.entries<ContentDetails>(data)) {
|
for (const [src, details] of Object.entries<ContentDetails>(data)) {
|
||||||
const source = simplifySlug(src as FullSlug)
|
const source = simplifySlug(src as FullSlug)
|
||||||
const outgoing = details.links ?? []
|
const outgoing = details.links ?? []
|
||||||
|
|
||||||
for (const dest of outgoing) {
|
for (const dest of outgoing) {
|
||||||
if (dest in data) {
|
if (validLinks.has(dest)) {
|
||||||
links.push({ source, target: dest })
|
links.push({ source, target: dest })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showTags) {
|
||||||
|
const localTags = details.tags
|
||||||
|
.filter((tag) => !removeTags.includes(tag))
|
||||||
|
.map((tag) => simplifySlug(("tags/" + tag) as FullSlug))
|
||||||
|
|
||||||
|
tags.push(...localTags.filter((tag) => !tags.includes(tag)))
|
||||||
|
|
||||||
|
for (const tag of localTags) {
|
||||||
|
links.push({ source, target: tag })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const neighbourhood = new Set<SimpleSlug>()
|
const neighbourhood = new Set<SimpleSlug>()
|
||||||
@ -75,14 +94,18 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Object.keys(data).forEach((id) => neighbourhood.add(simplifySlug(id as FullSlug)))
|
Object.keys(data).forEach((id) => neighbourhood.add(simplifySlug(id as FullSlug)))
|
||||||
|
if (showTags) tags.forEach((tag) => neighbourhood.add(tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
const graphData: { nodes: NodeData[]; links: LinkData[] } = {
|
const graphData: { nodes: NodeData[]; links: LinkData[] } = {
|
||||||
nodes: [...neighbourhood].map((url) => ({
|
nodes: [...neighbourhood].map((url) => {
|
||||||
|
const text = url.startsWith("tags/") ? "#" + url.substring(5) : data[url]?.title ?? url
|
||||||
|
return {
|
||||||
id: url,
|
id: url,
|
||||||
text: data[url]?.title ?? url,
|
text: text,
|
||||||
tags: data[url]?.tags ?? [],
|
tags: data[url]?.tags ?? [],
|
||||||
})),
|
}
|
||||||
|
}),
|
||||||
links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)),
|
links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +149,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
const isCurrent = d.id === slug
|
const isCurrent = d.id === slug
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
return "var(--secondary)"
|
return "var(--secondary)"
|
||||||
} else if (visited.has(d.id)) {
|
} else if (visited.has(d.id) || d.id.startsWith("tags/")) {
|
||||||
return "var(--tertiary)"
|
return "var(--tertiary)"
|
||||||
} else {
|
} else {
|
||||||
return "var(--gray)"
|
return "var(--gray)"
|
||||||
@ -230,9 +253,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
|
|||||||
.attr("dx", 0)
|
.attr("dx", 0)
|
||||||
.attr("dy", (d) => -nodeRadius(d) + "px")
|
.attr("dy", (d) => -nodeRadius(d) + "px")
|
||||||
.attr("text-anchor", "middle")
|
.attr("text-anchor", "middle")
|
||||||
.text(
|
.text((d) => d.text)
|
||||||
(d) => data[d.id]?.title || (d.id.charAt(1).toUpperCase() + d.id.slice(2)).replace("-", " "),
|
|
||||||
)
|
|
||||||
.style("opacity", (opacityScale - 1) / 3.75)
|
.style("opacity", (opacityScale - 1) / 3.75)
|
||||||
.style("pointer-events", "none")
|
.style("pointer-events", "none")
|
||||||
.style("font-size", fontSize + "em")
|
.style("font-size", fontSize + "em")
|
||||||
|
@ -28,8 +28,11 @@ async function mouseEnterHandler(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasAlreadyBeenFetched = () =>
|
||||||
|
[...link.children].some((child) => child.classList.contains("popover"))
|
||||||
|
|
||||||
// dont refetch if there's already a popover
|
// dont refetch if there's already a popover
|
||||||
if ([...link.children].some((child) => child.classList.contains("popover"))) {
|
if (hasAlreadyBeenFetched()) {
|
||||||
return setPosition(link.lastChild as HTMLElement)
|
return setPosition(link.lastChild as HTMLElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +52,11 @@ async function mouseEnterHandler(
|
|||||||
console.error(err)
|
console.error(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// bailout if another popover exists
|
||||||
|
if (hasAlreadyBeenFetched()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!contents) return
|
if (!contents) return
|
||||||
const html = p.parseFromString(contents, "text/html")
|
const html = p.parseFromString(contents, "text/html")
|
||||||
normalizeRelativeURLs(html, targetUrl)
|
normalizeRelativeURLs(html, targetUrl)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Document } from "flexsearch"
|
import { Document, SimpleDocumentSearchResultSetUnit } from "flexsearch"
|
||||||
import { ContentDetails } from "../../plugins/emitters/contentIndex"
|
import { ContentDetails } from "../../plugins/emitters/contentIndex"
|
||||||
import { registerEscapeHandler, removeAllChildren } from "./util"
|
import { registerEscapeHandler, removeAllChildren } from "./util"
|
||||||
import { FullSlug, resolveRelative } from "../../util/path"
|
import { FullSlug, resolveRelative } from "../../util/path"
|
||||||
@ -8,12 +8,20 @@ interface Item {
|
|||||||
slug: FullSlug
|
slug: FullSlug
|
||||||
title: string
|
title: string
|
||||||
content: string
|
content: string
|
||||||
|
tags: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
let index: Document<Item> | undefined = undefined
|
let index: Document<Item> | undefined = undefined
|
||||||
|
|
||||||
|
// Can be expanded with things like "term" in the future
|
||||||
|
type SearchType = "basic" | "tags"
|
||||||
|
|
||||||
|
// Current searchType
|
||||||
|
let searchType: SearchType = "basic"
|
||||||
|
|
||||||
const contextWindowWords = 30
|
const contextWindowWords = 30
|
||||||
const numSearchResults = 5
|
const numSearchResults = 5
|
||||||
|
const numTagResults = 3
|
||||||
function highlight(searchTerm: string, text: string, trim?: boolean) {
|
function highlight(searchTerm: string, text: string, trim?: boolean) {
|
||||||
// try to highlight longest tokens first
|
// try to highlight longest tokens first
|
||||||
const tokenizedTerms = searchTerm
|
const tokenizedTerms = searchTerm
|
||||||
@ -74,6 +82,7 @@ document.addEventListener("nav", async (e: unknown) => {
|
|||||||
const searchIcon = document.getElementById("search-icon")
|
const searchIcon = document.getElementById("search-icon")
|
||||||
const searchBar = document.getElementById("search-bar") as HTMLInputElement | null
|
const searchBar = document.getElementById("search-bar") as HTMLInputElement | null
|
||||||
const results = document.getElementById("results-container")
|
const results = document.getElementById("results-container")
|
||||||
|
const resultCards = document.getElementsByClassName("result-card")
|
||||||
const idDataMap = Object.keys(data) as FullSlug[]
|
const idDataMap = Object.keys(data) as FullSlug[]
|
||||||
|
|
||||||
function hideSearch() {
|
function hideSearch() {
|
||||||
@ -87,9 +96,12 @@ document.addEventListener("nav", async (e: unknown) => {
|
|||||||
if (results) {
|
if (results) {
|
||||||
removeAllChildren(results)
|
removeAllChildren(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchType = "basic" // reset search type after closing
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSearch() {
|
function showSearch(searchTypeNew: SearchType) {
|
||||||
|
searchType = searchTypeNew
|
||||||
if (sidebar) {
|
if (sidebar) {
|
||||||
sidebar.style.zIndex = "1"
|
sidebar.style.zIndex = "1"
|
||||||
}
|
}
|
||||||
@ -98,36 +110,123 @@ document.addEventListener("nav", async (e: unknown) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shortcutHandler(e: HTMLElementEventMap["keydown"]) {
|
function shortcutHandler(e: HTMLElementEventMap["keydown"]) {
|
||||||
if (e.key === "k" && (e.ctrlKey || e.metaKey)) {
|
if (e.key === "k" && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const searchBarOpen = container?.classList.contains("active")
|
const searchBarOpen = container?.classList.contains("active")
|
||||||
searchBarOpen ? hideSearch() : showSearch()
|
searchBarOpen ? hideSearch() : showSearch("basic")
|
||||||
|
} else if (e.shiftKey && (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
|
||||||
|
// Hotkey to open tag search
|
||||||
|
e.preventDefault()
|
||||||
|
const searchBarOpen = container?.classList.contains("active")
|
||||||
|
searchBarOpen ? hideSearch() : showSearch("tags")
|
||||||
|
|
||||||
|
// add "#" prefix for tag search
|
||||||
|
if (searchBar) searchBar.value = "#"
|
||||||
} else if (e.key === "Enter") {
|
} else if (e.key === "Enter") {
|
||||||
|
// If result has focus, navigate to that one, otherwise pick first result
|
||||||
|
if (results?.contains(document.activeElement)) {
|
||||||
|
const active = document.activeElement as HTMLInputElement
|
||||||
|
active.click()
|
||||||
|
} else {
|
||||||
const anchor = document.getElementsByClassName("result-card")[0] as HTMLInputElement | null
|
const anchor = document.getElementsByClassName("result-card")[0] as HTMLInputElement | null
|
||||||
if (anchor) {
|
anchor?.click()
|
||||||
anchor.click()
|
}
|
||||||
|
} else if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault()
|
||||||
|
// When first pressing ArrowDown, results wont contain the active element, so focus first element
|
||||||
|
if (!results?.contains(document.activeElement)) {
|
||||||
|
const firstResult = resultCards[0] as HTMLInputElement | null
|
||||||
|
firstResult?.focus()
|
||||||
|
} else {
|
||||||
|
// If an element in results-container already has focus, focus next one
|
||||||
|
const nextResult = document.activeElement?.nextElementSibling as HTMLInputElement | null
|
||||||
|
nextResult?.focus()
|
||||||
|
}
|
||||||
|
} else if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault()
|
||||||
|
if (results?.contains(document.activeElement)) {
|
||||||
|
// If an element in results-container already has focus, focus previous one
|
||||||
|
const prevResult = document.activeElement?.previousElementSibling as HTMLInputElement | null
|
||||||
|
prevResult?.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function trimContent(content: string) {
|
||||||
|
// works without escaping html like in `description.ts`
|
||||||
|
const sentences = content.replace(/\s+/g, " ").split(".")
|
||||||
|
let finalDesc = ""
|
||||||
|
let sentenceIdx = 0
|
||||||
|
|
||||||
|
// Roughly estimate characters by (words * 5). Matches description length in `description.ts`.
|
||||||
|
const len = contextWindowWords * 5
|
||||||
|
while (finalDesc.length < len) {
|
||||||
|
const sentence = sentences[sentenceIdx]
|
||||||
|
if (!sentence) break
|
||||||
|
finalDesc += sentence + "."
|
||||||
|
sentenceIdx++
|
||||||
|
}
|
||||||
|
|
||||||
|
// If more content would be available, indicate it by finishing with "..."
|
||||||
|
if (finalDesc.length < content.length) {
|
||||||
|
finalDesc += ".."
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalDesc
|
||||||
|
}
|
||||||
|
|
||||||
const formatForDisplay = (term: string, id: number) => {
|
const formatForDisplay = (term: string, id: number) => {
|
||||||
const slug = idDataMap[id]
|
const slug = idDataMap[id]
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
slug,
|
slug,
|
||||||
title: highlight(term, data[slug].title ?? ""),
|
title: searchType === "tags" ? data[slug].title : highlight(term, data[slug].title ?? ""),
|
||||||
content: highlight(term, data[slug].content ?? "", true),
|
// if searchType is tag, display context from start of file and trim, otherwise use regular highlight
|
||||||
|
content:
|
||||||
|
searchType === "tags"
|
||||||
|
? trimContent(data[slug].content)
|
||||||
|
: highlight(term, data[slug].content ?? "", true),
|
||||||
|
tags: highlightTags(term, data[slug].tags),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultToHTML = ({ slug, title, content }: Item) => {
|
function highlightTags(term: string, tags: string[]) {
|
||||||
|
if (tags && searchType === "tags") {
|
||||||
|
// Find matching tags
|
||||||
|
const termLower = term.toLowerCase()
|
||||||
|
let matching = tags.filter((str) => str.includes(termLower))
|
||||||
|
|
||||||
|
// Substract matching from original tags, then push difference
|
||||||
|
if (matching.length > 0) {
|
||||||
|
let difference = tags.filter((x) => !matching.includes(x))
|
||||||
|
|
||||||
|
// Convert to html (cant be done later as matches/term dont get passed to `resultToHTML`)
|
||||||
|
matching = matching.map((tag) => `<li><p class="match-tag">#${tag}</p></li>`)
|
||||||
|
difference = difference.map((tag) => `<li><p>#${tag}</p></li>`)
|
||||||
|
matching.push(...difference)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow max of `numTagResults` in preview
|
||||||
|
if (tags.length > numTagResults) {
|
||||||
|
matching.splice(numTagResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
return matching
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultToHTML = ({ slug, title, content, tags }: Item) => {
|
||||||
|
const htmlTags = tags.length > 0 ? `<ul>${tags.join("")}</ul>` : ``
|
||||||
const button = document.createElement("button")
|
const button = document.createElement("button")
|
||||||
button.classList.add("result-card")
|
button.classList.add("result-card")
|
||||||
button.id = slug
|
button.id = slug
|
||||||
button.innerHTML = `<h3>${title}</h3><p>${content}</p>`
|
button.innerHTML = `<h3>${title}</h3>${htmlTags}<p>${content}</p>`
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
const targ = resolveRelative(currentSlug, slug)
|
const targ = resolveRelative(currentSlug, slug)
|
||||||
window.spaNavigate(new URL(targ, window.location.toString()))
|
window.spaNavigate(new URL(targ, window.location.toString()))
|
||||||
|
hideSearch()
|
||||||
})
|
})
|
||||||
return button
|
return button
|
||||||
}
|
}
|
||||||
@ -147,15 +246,45 @@ document.addEventListener("nav", async (e: unknown) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onType(e: HTMLElementEventMap["input"]) {
|
async function onType(e: HTMLElementEventMap["input"]) {
|
||||||
const term = (e.target as HTMLInputElement).value
|
let term = (e.target as HTMLInputElement).value
|
||||||
const searchResults = (await index?.searchAsync(term, numSearchResults)) ?? []
|
let searchResults: SimpleDocumentSearchResultSetUnit[]
|
||||||
|
|
||||||
|
if (term.toLowerCase().startsWith("#")) {
|
||||||
|
searchType = "tags"
|
||||||
|
} else {
|
||||||
|
searchType = "basic"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (searchType) {
|
||||||
|
case "tags": {
|
||||||
|
term = term.substring(1)
|
||||||
|
searchResults =
|
||||||
|
(await index?.searchAsync({ query: term, limit: numSearchResults, index: ["tags"] })) ??
|
||||||
|
[]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "basic":
|
||||||
|
default: {
|
||||||
|
searchResults =
|
||||||
|
(await index?.searchAsync({
|
||||||
|
query: term,
|
||||||
|
limit: numSearchResults,
|
||||||
|
index: ["title", "content"],
|
||||||
|
})) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getByField = (field: string): number[] => {
|
const getByField = (field: string): number[] => {
|
||||||
const results = searchResults.filter((x) => x.field === field)
|
const results = searchResults.filter((x) => x.field === field)
|
||||||
return results.length === 0 ? [] : ([...results[0].result] as number[])
|
return results.length === 0 ? [] : ([...results[0].result] as number[])
|
||||||
}
|
}
|
||||||
|
|
||||||
// order titles ahead of content
|
// order titles ahead of content
|
||||||
const allIds: Set<number> = new Set([...getByField("title"), ...getByField("content")])
|
const allIds: Set<number> = new Set([
|
||||||
|
...getByField("title"),
|
||||||
|
...getByField("content"),
|
||||||
|
...getByField("tags"),
|
||||||
|
])
|
||||||
const finalResults = [...allIds].map((id) => formatForDisplay(term, id))
|
const finalResults = [...allIds].map((id) => formatForDisplay(term, id))
|
||||||
displayResults(finalResults)
|
displayResults(finalResults)
|
||||||
}
|
}
|
||||||
@ -166,15 +295,14 @@ document.addEventListener("nav", async (e: unknown) => {
|
|||||||
|
|
||||||
document.addEventListener("keydown", shortcutHandler)
|
document.addEventListener("keydown", shortcutHandler)
|
||||||
prevShortcutHandler = shortcutHandler
|
prevShortcutHandler = shortcutHandler
|
||||||
searchIcon?.removeEventListener("click", showSearch)
|
searchIcon?.removeEventListener("click", () => showSearch("basic"))
|
||||||
searchIcon?.addEventListener("click", showSearch)
|
searchIcon?.addEventListener("click", () => showSearch("basic"))
|
||||||
searchBar?.removeEventListener("input", onType)
|
searchBar?.removeEventListener("input", onType)
|
||||||
searchBar?.addEventListener("input", onType)
|
searchBar?.addEventListener("input", onType)
|
||||||
|
|
||||||
// setup index if it hasn't been already
|
// setup index if it hasn't been already
|
||||||
if (!index) {
|
if (!index) {
|
||||||
index = new Document({
|
index = new Document({
|
||||||
cache: true,
|
|
||||||
charset: "latin:extra",
|
charset: "latin:extra",
|
||||||
optimize: true,
|
optimize: true,
|
||||||
encode: encoder,
|
encode: encoder,
|
||||||
@ -189,10 +317,27 @@ document.addEventListener("nav", async (e: unknown) => {
|
|||||||
field: "content",
|
field: "content",
|
||||||
tokenize: "reverse",
|
tokenize: "reverse",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: "tags",
|
||||||
|
tokenize: "reverse",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fillDocument(index, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// register handlers
|
||||||
|
registerEscapeHandler(container, hideSearch)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills flexsearch document with data
|
||||||
|
* @param index index to fill
|
||||||
|
* @param data data to fill index with
|
||||||
|
*/
|
||||||
|
async function fillDocument(index: Document<Item, false>, data: any) {
|
||||||
let id = 0
|
let id = 0
|
||||||
for (const [slug, fileData] of Object.entries<ContentDetails>(data)) {
|
for (const [slug, fileData] of Object.entries<ContentDetails>(data)) {
|
||||||
await index.addAsync(id, {
|
await index.addAsync(id, {
|
||||||
@ -200,11 +345,8 @@ document.addEventListener("nav", async (e: unknown) => {
|
|||||||
slug: slug as FullSlug,
|
slug: slug as FullSlug,
|
||||||
title: fileData.title,
|
title: fileData.title,
|
||||||
content: fileData.content,
|
content: fileData.content,
|
||||||
|
tags: fileData.tags,
|
||||||
})
|
})
|
||||||
id++
|
id++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// register handlers
|
|
||||||
registerEscapeHandler(container, hideSearch)
|
|
||||||
})
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import micromorph from "micromorph"
|
import micromorph from "micromorph"
|
||||||
import { FullSlug, RelativeURL, getFullSlug } from "../../util/path"
|
import { FullSlug, RelativeURL, getFullSlug } from "../../util/path"
|
||||||
|
import { normalizeRelativeURLs } from "./popover.inline"
|
||||||
|
|
||||||
// adapted from `micromorph`
|
// adapted from `micromorph`
|
||||||
// https://github.com/natemoo-re/micromorph
|
// https://github.com/natemoo-re/micromorph
|
||||||
@ -18,8 +19,15 @@ const isLocalUrl = (href: string) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSamePage = (url: URL): boolean => {
|
||||||
|
const sameOrigin = url.origin === window.location.origin
|
||||||
|
const samePath = url.pathname === window.location.pathname
|
||||||
|
return sameOrigin && samePath
|
||||||
|
}
|
||||||
|
|
||||||
const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => {
|
const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => {
|
||||||
if (!isElement(target)) return
|
if (!isElement(target)) return
|
||||||
|
if (target.attributes.getNamedItem("target")?.value === "_blank") return
|
||||||
const a = target.closest("a")
|
const a = target.closest("a")
|
||||||
if (!a) return
|
if (!a) return
|
||||||
if ("routerIgnore" in a.dataset) return
|
if ("routerIgnore" in a.dataset) return
|
||||||
@ -45,6 +53,8 @@ async function navigate(url: URL, isBack: boolean = false) {
|
|||||||
if (!contents) return
|
if (!contents) return
|
||||||
|
|
||||||
const html = p.parseFromString(contents, "text/html")
|
const html = p.parseFromString(contents, "text/html")
|
||||||
|
normalizeRelativeURLs(html, url)
|
||||||
|
|
||||||
let title = html.querySelector("title")?.textContent
|
let title = html.querySelector("title")?.textContent
|
||||||
if (title) {
|
if (title) {
|
||||||
document.title = title
|
document.title = title
|
||||||
@ -92,8 +102,16 @@ function createRouter() {
|
|||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
window.addEventListener("click", async (event) => {
|
window.addEventListener("click", async (event) => {
|
||||||
const { url } = getOpts(event) ?? {}
|
const { url } = getOpts(event) ?? {}
|
||||||
if (!url) return
|
// dont hijack behaviour, just let browser act normally
|
||||||
|
if (!url || event.ctrlKey || event.metaKey) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (isSamePage(url) && url.hash) {
|
||||||
|
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
|
||||||
|
el?.scrollIntoView()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
navigate(url, false)
|
navigate(url, false)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -24,8 +24,9 @@ function toggleToc(this: HTMLElement) {
|
|||||||
function setupToc() {
|
function setupToc() {
|
||||||
const toc = document.getElementById("toc")
|
const toc = document.getElementById("toc")
|
||||||
if (toc) {
|
if (toc) {
|
||||||
|
const collapsed = toc.classList.contains("collapsed")
|
||||||
const content = toc.nextElementSibling as HTMLElement
|
const content = toc.nextElementSibling as HTMLElement
|
||||||
content.style.maxHeight = content.scrollHeight + "px"
|
content.style.maxHeight = collapsed ? "0px" : content.scrollHeight + "px"
|
||||||
toc.removeEventListener("click", toggleToc)
|
toc.removeEventListener("click", toggleToc)
|
||||||
toc.addEventListener("click", toggleToc)
|
toc.addEventListener("click", toggleToc)
|
||||||
}
|
}
|
||||||
|
22
quartz/components/styles/breadcrumbs.scss
Normal file
22
quartz/components/styles/breadcrumbs.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.breadcrumb-container {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-element {
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
padding: 0;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
@ -10,7 +10,6 @@
|
|||||||
background-color: var(--light);
|
background-color: var(--light);
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
z-index: 1;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
|
|
||||||
|
@ -21,6 +21,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root[saved-theme="dark"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[saved-theme="light"] {
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
|
||||||
:root[saved-theme="dark"] .toggle ~ label {
|
:root[saved-theme="dark"] .toggle ~ label {
|
||||||
& > #dayIcon {
|
& > #dayIcon {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
146
quartz/components/styles/explorer.scss
Normal file
146
quartz/components/styles/explorer.scss
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
button#explorer {
|
||||||
|
all: unset;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
color: var(--dark);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& h1 {
|
||||||
|
font-size: 1rem;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .fold {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed .fold {
|
||||||
|
transform: rotateZ(-90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-outer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
transition: grid-template-rows 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-outer.open {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-outer > ul {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#explorer-content {
|
||||||
|
list-style: none;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: none;
|
||||||
|
transition: max-height 0.35s ease;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
|
||||||
|
&.collapsed > .overflow::after {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0.08rem 0;
|
||||||
|
padding: 0;
|
||||||
|
transition:
|
||||||
|
max-height 0.35s ease,
|
||||||
|
transform 0.35s ease,
|
||||||
|
opacity 0.2s ease;
|
||||||
|
& li > a {
|
||||||
|
color: var(--dark);
|
||||||
|
opacity: 0.75;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
& > polyline {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-container {
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
& div > a {
|
||||||
|
color: var(--secondary);
|
||||||
|
font-family: var(--headerFont);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
& div > a:hover {
|
||||||
|
color: var(--tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
& div > button {
|
||||||
|
color: var(--dark);
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-family: var(--headerFont);
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
color: var(--secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
backface-visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
div:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-icon:hover {
|
||||||
|
color: var(--tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-background::after {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#explorer-end {
|
||||||
|
// needs height so IntersectionObserver gets triggered
|
||||||
|
height: 4px;
|
||||||
|
// remove default margin from li
|
||||||
|
margin: 0;
|
||||||
|
}
|
@ -19,11 +19,6 @@ li.section-li {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .tags {
|
|
||||||
justify-self: end;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .desc > h3 > a {
|
& > .desc > h3 > a {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,44 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > ul > li {
|
||||||
|
margin: 0;
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 0;
|
||||||
|
overflow-wrap: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > ul {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
padding-left: 0;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.45rem;
|
||||||
|
// Offset border radius
|
||||||
|
margin-left: -2px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-clip: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > ul > li > p {
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--highlight);
|
||||||
|
overflow: hidden;
|
||||||
|
background-clip: border-box;
|
||||||
|
padding: 0.03rem 0.4rem;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--secondary);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > ul > li > .match-tag {
|
||||||
|
color: var(--tertiary);
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
59
quartz/plugins/emitters/404.tsx
Normal file
59
quartz/plugins/emitters/404.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
|
import { QuartzComponentProps } from "../../components/types"
|
||||||
|
import BodyConstructor from "../../components/Body"
|
||||||
|
import { pageResources, renderPage } from "../../components/renderPage"
|
||||||
|
import { FullPageLayout } from "../../cfg"
|
||||||
|
import { FilePath, FullSlug } from "../../util/path"
|
||||||
|
import { sharedPageComponents } from "../../../quartz.layout"
|
||||||
|
import { NotFound } from "../../components"
|
||||||
|
import { defaultProcessedContent } from "../vfile"
|
||||||
|
|
||||||
|
export const NotFoundPage: QuartzEmitterPlugin = () => {
|
||||||
|
const opts: FullPageLayout = {
|
||||||
|
...sharedPageComponents,
|
||||||
|
pageBody: NotFound(),
|
||||||
|
beforeBody: [],
|
||||||
|
left: [],
|
||||||
|
right: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const { head: Head, pageBody, footer: Footer } = opts
|
||||||
|
const Body = BodyConstructor()
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "404Page",
|
||||||
|
getQuartzComponents() {
|
||||||
|
return [Head, Body, pageBody, Footer]
|
||||||
|
},
|
||||||
|
async emit(ctx, _content, resources, emit): Promise<FilePath[]> {
|
||||||
|
const cfg = ctx.cfg.configuration
|
||||||
|
const slug = "404" as FullSlug
|
||||||
|
|
||||||
|
const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`)
|
||||||
|
const path = url.pathname as FullSlug
|
||||||
|
const externalResources = pageResources(path, resources)
|
||||||
|
const [tree, vfile] = defaultProcessedContent({
|
||||||
|
slug,
|
||||||
|
text: "Not Found",
|
||||||
|
description: "Not Found",
|
||||||
|
frontmatter: { title: "Not Found", tags: [] },
|
||||||
|
})
|
||||||
|
const componentData: QuartzComponentProps = {
|
||||||
|
fileData: vfile.data,
|
||||||
|
externalResources,
|
||||||
|
cfg,
|
||||||
|
children: [],
|
||||||
|
tree,
|
||||||
|
allFiles: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
await emit({
|
||||||
|
content: renderPage(slug, componentData, opts, externalResources),
|
||||||
|
slug,
|
||||||
|
ext: ".html",
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { FilePath, FullSlug, resolveRelative, simplifySlug } from "../../util/path"
|
import { FilePath, FullSlug, joinSegments, resolveRelative, simplifySlug } from "../../util/path"
|
||||||
import { QuartzEmitterPlugin } from "../types"
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
@ -12,15 +12,25 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({
|
|||||||
|
|
||||||
for (const [_tree, file] of content) {
|
for (const [_tree, file] of content) {
|
||||||
const ogSlug = simplifySlug(file.data.slug!)
|
const ogSlug = simplifySlug(file.data.slug!)
|
||||||
const dir = path.posix.relative(argv.directory, file.dirname ?? argv.directory)
|
const dir = path.posix.relative(argv.directory, path.dirname(file.data.filePath!))
|
||||||
|
|
||||||
let aliases: FullSlug[] = file.data.frontmatter?.aliases ?? file.data.frontmatter?.alias ?? []
|
let aliases: FullSlug[] = file.data.frontmatter?.aliases ?? file.data.frontmatter?.alias ?? []
|
||||||
if (typeof aliases === "string") {
|
if (typeof aliases === "string") {
|
||||||
aliases = [aliases]
|
aliases = [aliases]
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const alias of aliases) {
|
const slugs: FullSlug[] = aliases.map((alias) => path.posix.join(dir, alias) as FullSlug)
|
||||||
const slug = path.posix.join(dir, alias) as FullSlug
|
const permalink = file.data.frontmatter?.permalink
|
||||||
|
if (typeof permalink === "string") {
|
||||||
|
slugs.push(permalink as FullSlug)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let slug of slugs) {
|
||||||
|
// fix any slugs that have trailing slash
|
||||||
|
if (slug.endsWith("/")) {
|
||||||
|
slug = joinSegments(slug, "index") as FullSlug
|
||||||
|
}
|
||||||
|
|
||||||
const redirUrl = resolveRelative(slug, file.data.slug!)
|
const redirUrl = resolveRelative(slug, file.data.slug!)
|
||||||
const fp = await emit({
|
const fp = await emit({
|
||||||
content: `
|
content: `
|
||||||
|
@ -7,7 +7,7 @@ import spaRouterScript from "../../components/scripts/spa.inline"
|
|||||||
import plausibleScript from "../../components/scripts/plausible.inline"
|
import plausibleScript from "../../components/scripts/plausible.inline"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import popoverScript from "../../components/scripts/popover.inline"
|
import popoverScript from "../../components/scripts/popover.inline"
|
||||||
import styles from "../../styles/base.scss"
|
import styles from "../../styles/custom.scss"
|
||||||
import popoverStyle from "../../components/styles/popover.scss"
|
import popoverStyle from "../../components/styles/popover.scss"
|
||||||
import { BuildCtx } from "../../util/ctx"
|
import { BuildCtx } from "../../util/ctx"
|
||||||
import { StaticResources } from "../../util/resources"
|
import { StaticResources } from "../../util/resources"
|
||||||
@ -96,6 +96,15 @@ function addGlobalPageResources(
|
|||||||
});`)
|
});`)
|
||||||
} else if (cfg.analytics?.provider === "plausible") {
|
} else if (cfg.analytics?.provider === "plausible") {
|
||||||
componentResources.afterDOMLoaded.push(plausibleScript)
|
componentResources.afterDOMLoaded.push(plausibleScript)
|
||||||
|
} else if (cfg.analytics?.provider === "umami") {
|
||||||
|
componentResources.afterDOMLoaded.push(`
|
||||||
|
const umamiScript = document.createElement("script")
|
||||||
|
umamiScript.src = "https://analytics.umami.is/script.js"
|
||||||
|
umamiScript.setAttribute("data-website-id", "${cfg.analytics.websiteId}")
|
||||||
|
umamiScript.async = true
|
||||||
|
|
||||||
|
document.head.appendChild(umamiScript)
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.enableSPA) {
|
if (cfg.enableSPA) {
|
||||||
@ -107,12 +116,18 @@ function addGlobalPageResources(
|
|||||||
document.dispatchEvent(event)`)
|
document.dispatchEvent(event)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wsUrl = `ws://localhost:${ctx.argv.wsPort}`
|
||||||
|
|
||||||
|
if (ctx.argv.remoteDevHost) {
|
||||||
|
wsUrl = `wss://${ctx.argv.remoteDevHost}:${ctx.argv.wsPort}`
|
||||||
|
}
|
||||||
|
|
||||||
if (reloadScript) {
|
if (reloadScript) {
|
||||||
staticResources.js.push({
|
staticResources.js.push({
|
||||||
loadTime: "afterDOMReady",
|
loadTime: "afterDOMReady",
|
||||||
contentType: "inline",
|
contentType: "inline",
|
||||||
script: `
|
script: `
|
||||||
const socket = new WebSocket('ws://localhost:3001')
|
const socket = new WebSocket('${wsUrl}')
|
||||||
socket.addEventListener('message', () => document.location.reload())
|
socket.addEventListener('message', () => document.location.reload())
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
@ -149,7 +164,7 @@ export const ComponentResources: QuartzEmitterPlugin<Options> = (opts?: Partial<
|
|||||||
|
|
||||||
addGlobalPageResources(ctx, resources, componentResources)
|
addGlobalPageResources(ctx, resources, componentResources)
|
||||||
|
|
||||||
const stylesheet = joinStyles(ctx.cfg.configuration.theme, styles, ...componentResources.css)
|
const stylesheet = joinStyles(ctx.cfg.configuration.theme, ...componentResources.css, styles)
|
||||||
const prescript = joinScripts(componentResources.beforeDOMLoaded)
|
const prescript = joinScripts(componentResources.beforeDOMLoaded)
|
||||||
const postscript = joinScripts(componentResources.afterDOMLoaded)
|
const postscript = joinScripts(componentResources.afterDOMLoaded)
|
||||||
const fps = await Promise.all([
|
const fps = await Promise.all([
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
import { Root } from "hast"
|
||||||
import { GlobalConfiguration } from "../../cfg"
|
import { GlobalConfiguration } from "../../cfg"
|
||||||
import { getDate } from "../../components/Date"
|
import { getDate } from "../../components/Date"
|
||||||
|
import { escapeHTML } from "../../util/escape"
|
||||||
import { FilePath, FullSlug, SimpleSlug, simplifySlug } from "../../util/path"
|
import { FilePath, FullSlug, SimpleSlug, simplifySlug } from "../../util/path"
|
||||||
import { QuartzEmitterPlugin } from "../types"
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
|
import { toHtml } from "hast-util-to-html"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
export type ContentIndex = Map<FullSlug, ContentDetails>
|
export type ContentIndex = Map<FullSlug, ContentDetails>
|
||||||
@ -10,6 +13,7 @@ export type ContentDetails = {
|
|||||||
links: SimpleSlug[]
|
links: SimpleSlug[]
|
||||||
tags: string[]
|
tags: string[]
|
||||||
content: string
|
content: string
|
||||||
|
richContent?: string
|
||||||
date?: Date
|
date?: Date
|
||||||
description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
@ -17,19 +21,23 @@ export type ContentDetails = {
|
|||||||
interface Options {
|
interface Options {
|
||||||
enableSiteMap: boolean
|
enableSiteMap: boolean
|
||||||
enableRSS: boolean
|
enableRSS: boolean
|
||||||
|
rssLimit?: number
|
||||||
|
rssFullHtml: boolean
|
||||||
includeEmptyFiles: boolean
|
includeEmptyFiles: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
enableSiteMap: true,
|
enableSiteMap: true,
|
||||||
enableRSS: true,
|
enableRSS: true,
|
||||||
|
rssLimit: 10,
|
||||||
|
rssFullHtml: false,
|
||||||
includeEmptyFiles: true,
|
includeEmptyFiles: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string {
|
function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string {
|
||||||
const base = cfg.baseUrl ?? ""
|
const base = cfg.baseUrl ?? ""
|
||||||
const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<url>
|
const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<url>
|
||||||
<loc>https://${base}/${slug}</loc>
|
<loc>https://${base}/${encodeURI(slug)}</loc>
|
||||||
<lastmod>${content.date?.toISOString()}</lastmod>
|
<lastmod>${content.date?.toISOString()}</lastmod>
|
||||||
</url>`
|
</url>`
|
||||||
const urls = Array.from(idx)
|
const urls = Array.from(idx)
|
||||||
@ -38,27 +46,42 @@ function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string {
|
|||||||
return `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">${urls}</urlset>`
|
return `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">${urls}</urlset>`
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex): string {
|
function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: number): string {
|
||||||
const base = cfg.baseUrl ?? ""
|
const base = cfg.baseUrl ?? ""
|
||||||
const root = `https://${base}`
|
const root = `https://${base}`
|
||||||
|
|
||||||
const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<item>
|
const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<item>
|
||||||
<title>${content.title}</title>
|
<title>${escapeHTML(content.title)}</title>
|
||||||
<link>${root}/${slug}</link>
|
<link>${root}/${encodeURI(slug)}</link>
|
||||||
<guid>${root}/${slug}</guid>
|
<guid>${root}/${encodeURI(slug)}</guid>
|
||||||
<description>${content.description}</description>
|
<description>${content.richContent ?? content.description}</description>
|
||||||
<pubDate>${content.date?.toUTCString()}</pubDate>
|
<pubDate>${content.date?.toUTCString()}</pubDate>
|
||||||
</item>`
|
</item>`
|
||||||
|
|
||||||
const items = Array.from(idx)
|
const items = Array.from(idx)
|
||||||
|
.sort(([_, f1], [__, f2]) => {
|
||||||
|
if (f1.date && f2.date) {
|
||||||
|
return f2.date.getTime() - f1.date.getTime()
|
||||||
|
} else if (f1.date && !f2.date) {
|
||||||
|
return -1
|
||||||
|
} else if (!f1.date && f2.date) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return f1.title.localeCompare(f2.title)
|
||||||
|
})
|
||||||
.map(([slug, content]) => createURLEntry(simplifySlug(slug), content))
|
.map(([slug, content]) => createURLEntry(simplifySlug(slug), content))
|
||||||
|
.slice(0, limit ?? idx.size)
|
||||||
.join("")
|
.join("")
|
||||||
|
|
||||||
return `<?xml version="1.0" encoding="UTF-8" ?>
|
return `<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<rss version="2.0">
|
<rss version="2.0">
|
||||||
<channel>
|
<channel>
|
||||||
<title>${cfg.pageTitle}</title>
|
<title>${escapeHTML(cfg.pageTitle)}</title>
|
||||||
<link>${root}</link>
|
<link>${root}</link>
|
||||||
<description>Recent content on ${cfg.pageTitle}</description>
|
<description>${!!limit ? `Last ${limit} notes` : "Recent notes"} on ${escapeHTML(
|
||||||
|
cfg.pageTitle,
|
||||||
|
)}</description>
|
||||||
<generator>Quartz -- quartz.jzhao.xyz</generator>
|
<generator>Quartz -- quartz.jzhao.xyz</generator>
|
||||||
${items}
|
${items}
|
||||||
</channel>
|
</channel>
|
||||||
@ -73,7 +96,7 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
|
|||||||
const cfg = ctx.cfg.configuration
|
const cfg = ctx.cfg.configuration
|
||||||
const emitted: FilePath[] = []
|
const emitted: FilePath[] = []
|
||||||
const linkIndex: ContentIndex = new Map()
|
const linkIndex: ContentIndex = new Map()
|
||||||
for (const [_tree, file] of content) {
|
for (const [tree, file] of content) {
|
||||||
const slug = file.data.slug!
|
const slug = file.data.slug!
|
||||||
const date = getDate(ctx.cfg.configuration, file.data) ?? new Date()
|
const date = getDate(ctx.cfg.configuration, file.data) ?? new Date()
|
||||||
if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) {
|
if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) {
|
||||||
@ -82,6 +105,9 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
|
|||||||
links: file.data.links ?? [],
|
links: file.data.links ?? [],
|
||||||
tags: file.data.frontmatter?.tags ?? [],
|
tags: file.data.frontmatter?.tags ?? [],
|
||||||
content: file.data.text ?? "",
|
content: file.data.text ?? "",
|
||||||
|
richContent: opts?.rssFullHtml
|
||||||
|
? escapeHTML(toHtml(tree as Root, { allowDangerousHtml: true }))
|
||||||
|
: undefined,
|
||||||
date: date,
|
date: date,
|
||||||
description: file.data.description ?? "",
|
description: file.data.description ?? "",
|
||||||
})
|
})
|
||||||
@ -101,7 +127,7 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
|
|||||||
if (opts?.enableRSS) {
|
if (opts?.enableRSS) {
|
||||||
emitted.push(
|
emitted.push(
|
||||||
await emit({
|
await emit({
|
||||||
content: generateRSSFeed(cfg, linkIndex),
|
content: generateRSSFeed(cfg, linkIndex, opts.rssLimit),
|
||||||
slug: "index" as FullSlug,
|
slug: "index" as FullSlug,
|
||||||
ext: ".xml",
|
ext: ".xml",
|
||||||
}),
|
}),
|
||||||
|
@ -4,9 +4,10 @@ import HeaderConstructor from "../../components/Header"
|
|||||||
import BodyConstructor from "../../components/Body"
|
import BodyConstructor from "../../components/Body"
|
||||||
import { pageResources, renderPage } from "../../components/renderPage"
|
import { pageResources, renderPage } from "../../components/renderPage"
|
||||||
import { FullPageLayout } from "../../cfg"
|
import { FullPageLayout } from "../../cfg"
|
||||||
import { FilePath } from "../../util/path"
|
import { FilePath, pathToRoot } from "../../util/path"
|
||||||
import { defaultContentPageLayout, sharedPageComponents } from "../../../quartz.layout"
|
import { defaultContentPageLayout, sharedPageComponents } from "../../../quartz.layout"
|
||||||
import { Content } from "../../components"
|
import { Content } from "../../components"
|
||||||
|
import chalk from "chalk"
|
||||||
|
|
||||||
export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOpts) => {
|
export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOpts) => {
|
||||||
const opts: FullPageLayout = {
|
const opts: FullPageLayout = {
|
||||||
@ -29,9 +30,15 @@ export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOp
|
|||||||
const cfg = ctx.cfg.configuration
|
const cfg = ctx.cfg.configuration
|
||||||
const fps: FilePath[] = []
|
const fps: FilePath[] = []
|
||||||
const allFiles = content.map((c) => c[1].data)
|
const allFiles = content.map((c) => c[1].data)
|
||||||
|
|
||||||
|
let containsIndex = false
|
||||||
for (const [tree, file] of content) {
|
for (const [tree, file] of content) {
|
||||||
const slug = file.data.slug!
|
const slug = file.data.slug!
|
||||||
const externalResources = pageResources(slug, resources)
|
if (slug === "index") {
|
||||||
|
containsIndex = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const externalResources = pageResources(pathToRoot(slug), resources)
|
||||||
const componentData: QuartzComponentProps = {
|
const componentData: QuartzComponentProps = {
|
||||||
fileData: file.data,
|
fileData: file.data,
|
||||||
externalResources,
|
externalResources,
|
||||||
@ -50,6 +57,15 @@ export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOp
|
|||||||
|
|
||||||
fps.push(fp)
|
fps.push(fp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!containsIndex) {
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(
|
||||||
|
`\nWarning: you seem to be missing an \`index.md\` home page file at the root of your \`${ctx.argv.directory}\` folder. This may cause errors when deploying.`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return fps
|
return fps
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
SimpleSlug,
|
SimpleSlug,
|
||||||
_stripSlashes,
|
_stripSlashes,
|
||||||
joinSegments,
|
joinSegments,
|
||||||
|
pathToRoot,
|
||||||
simplifySlug,
|
simplifySlug,
|
||||||
} from "../../util/path"
|
} from "../../util/path"
|
||||||
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
|
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
|
||||||
@ -69,7 +70,7 @@ export const FolderPage: QuartzEmitterPlugin<FullPageLayout> = (userOpts) => {
|
|||||||
|
|
||||||
for (const folder of folders) {
|
for (const folder of folders) {
|
||||||
const slug = joinSegments(folder, "index") as FullSlug
|
const slug = joinSegments(folder, "index") as FullSlug
|
||||||
const externalResources = pageResources(slug, resources)
|
const externalResources = pageResources(pathToRoot(slug), resources)
|
||||||
const [tree, file] = folderDescriptions[folder]
|
const [tree, file] = folderDescriptions[folder]
|
||||||
const componentData: QuartzComponentProps = {
|
const componentData: QuartzComponentProps = {
|
||||||
fileData: file.data,
|
fileData: file.data,
|
||||||
|
@ -6,3 +6,4 @@ export { AliasRedirects } from "./aliases"
|
|||||||
export { Assets } from "./assets"
|
export { Assets } from "./assets"
|
||||||
export { Static } from "./static"
|
export { Static } from "./static"
|
||||||
export { ComponentResources } from "./componentResources"
|
export { ComponentResources } from "./componentResources"
|
||||||
|
export { NotFoundPage } from "./404"
|
||||||
|
@ -5,7 +5,13 @@ import BodyConstructor from "../../components/Body"
|
|||||||
import { pageResources, renderPage } from "../../components/renderPage"
|
import { pageResources, renderPage } from "../../components/renderPage"
|
||||||
import { ProcessedContent, defaultProcessedContent } from "../vfile"
|
import { ProcessedContent, defaultProcessedContent } from "../vfile"
|
||||||
import { FullPageLayout } from "../../cfg"
|
import { FullPageLayout } from "../../cfg"
|
||||||
import { FilePath, FullSlug, getAllSegmentPrefixes, joinSegments } from "../../util/path"
|
import {
|
||||||
|
FilePath,
|
||||||
|
FullSlug,
|
||||||
|
getAllSegmentPrefixes,
|
||||||
|
joinSegments,
|
||||||
|
pathToRoot,
|
||||||
|
} from "../../util/path"
|
||||||
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
|
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
|
||||||
import { TagContent } from "../../components"
|
import { TagContent } from "../../components"
|
||||||
|
|
||||||
@ -62,7 +68,7 @@ export const TagPage: QuartzEmitterPlugin<FullPageLayout> = (userOpts) => {
|
|||||||
|
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
const slug = joinSegments("tags", tag) as FullSlug
|
const slug = joinSegments("tags", tag) as FullSlug
|
||||||
const externalResources = pageResources(slug, resources)
|
const externalResources = pageResources(pathToRoot(slug), resources)
|
||||||
const [tree, file] = tagDescriptions[tag]
|
const [tree, file] = tagDescriptions[tag]
|
||||||
const componentData: QuartzComponentProps = {
|
const componentData: QuartzComponentProps = {
|
||||||
fileData: file.data,
|
fileData: file.data,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Root as HTMLRoot } from "hast"
|
import { Root as HTMLRoot } from "hast"
|
||||||
import { toString } from "hast-util-to-string"
|
import { toString } from "hast-util-to-string"
|
||||||
import { QuartzTransformerPlugin } from "../types"
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
|
import { escapeHTML } from "../../util/escape"
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
descriptionLength: number
|
descriptionLength: number
|
||||||
@ -10,15 +11,6 @@ const defaultOptions: Options = {
|
|||||||
descriptionLength: 150,
|
descriptionLength: 150,
|
||||||
}
|
}
|
||||||
|
|
||||||
const escapeHTML = (unsafe: string) => {
|
|
||||||
return unsafe
|
|
||||||
.replaceAll("&", "&")
|
|
||||||
.replaceAll("<", "<")
|
|
||||||
.replaceAll(">", ">")
|
|
||||||
.replaceAll('"', """)
|
|
||||||
.replaceAll("'", "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Description: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
export const Description: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||||
const opts = { ...defaultOptions, ...userOpts }
|
const opts = { ...defaultOptions, ...userOpts }
|
||||||
return {
|
return {
|
||||||
|
@ -2,14 +2,17 @@ import matter from "gray-matter"
|
|||||||
import remarkFrontmatter from "remark-frontmatter"
|
import remarkFrontmatter from "remark-frontmatter"
|
||||||
import { QuartzTransformerPlugin } from "../types"
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
import yaml from "js-yaml"
|
import yaml from "js-yaml"
|
||||||
|
import toml from "toml"
|
||||||
import { slugTag } from "../../util/path"
|
import { slugTag } from "../../util/path"
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
delims: string | string[]
|
delims: string | string[]
|
||||||
|
language: "yaml" | "toml"
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
delims: "---",
|
delims: "---",
|
||||||
|
language: "yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||||
@ -18,13 +21,14 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
|
|||||||
name: "FrontMatter",
|
name: "FrontMatter",
|
||||||
markdownPlugins() {
|
markdownPlugins() {
|
||||||
return [
|
return [
|
||||||
remarkFrontmatter,
|
[remarkFrontmatter, ["yaml", "toml"]],
|
||||||
() => {
|
() => {
|
||||||
return (_, file) => {
|
return (_, file) => {
|
||||||
const { data } = matter(file.value, {
|
const { data } = matter(file.value, {
|
||||||
...opts,
|
...opts,
|
||||||
engines: {
|
engines: {
|
||||||
yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object,
|
yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object,
|
||||||
|
toml: (s) => toml.parse(s) as object,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -33,6 +37,11 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
|
|||||||
data.tags = data.tag
|
data.tags = data.tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// coerce title to string
|
||||||
|
if (data.title) {
|
||||||
|
data.title = data.title.toString()
|
||||||
|
}
|
||||||
|
|
||||||
if (data.tags && !Array.isArray(data.tags)) {
|
if (data.tags && !Array.isArray(data.tags)) {
|
||||||
data.tags = data.tags
|
data.tags = data.tags
|
||||||
.toString()
|
.toString()
|
||||||
|
@ -5,5 +5,7 @@ export { Latex } from "./latex"
|
|||||||
export { Description } from "./description"
|
export { Description } from "./description"
|
||||||
export { CrawlLinks } from "./links"
|
export { CrawlLinks } from "./links"
|
||||||
export { ObsidianFlavoredMarkdown } from "./ofm"
|
export { ObsidianFlavoredMarkdown } from "./ofm"
|
||||||
|
export { OxHugoFlavouredMarkdown } from "./oxhugofm"
|
||||||
export { SyntaxHighlighting } from "./syntax"
|
export { SyntaxHighlighting } from "./syntax"
|
||||||
export { TableOfContents } from "./toc"
|
export { TableOfContents } from "./toc"
|
||||||
|
export { HardLineBreaks } from "./linebreaks"
|
||||||
|
@ -2,6 +2,7 @@ import fs from "fs"
|
|||||||
import path from "path"
|
import path from "path"
|
||||||
import { Repository } from "@napi-rs/simple-git"
|
import { Repository } from "@napi-rs/simple-git"
|
||||||
import { QuartzTransformerPlugin } from "../types"
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
|
import chalk from "chalk"
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
priority: ("frontmatter" | "git" | "filesystem")[]
|
priority: ("frontmatter" | "git" | "filesystem")[]
|
||||||
@ -11,6 +12,20 @@ const defaultOptions: Options = {
|
|||||||
priority: ["frontmatter", "git", "filesystem"],
|
priority: ["frontmatter", "git", "filesystem"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function coerceDate(fp: string, d: any): Date {
|
||||||
|
const dt = new Date(d)
|
||||||
|
const invalidDate = isNaN(dt.getTime()) || dt.getTime() === 0
|
||||||
|
if (invalidDate && d !== undefined) {
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(
|
||||||
|
`\nWarning: found invalid date "${d}" in \`${fp}\`. Supported formats: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidDate ? new Date() : dt
|
||||||
|
}
|
||||||
|
|
||||||
type MaybeDate = undefined | string | number
|
type MaybeDate = undefined | string | number
|
||||||
export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options> | undefined> = (
|
export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options> | undefined> = (
|
||||||
userOpts,
|
userOpts,
|
||||||
@ -27,10 +42,11 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options> | und
|
|||||||
let modified: MaybeDate = undefined
|
let modified: MaybeDate = undefined
|
||||||
let published: MaybeDate = undefined
|
let published: MaybeDate = undefined
|
||||||
|
|
||||||
const fp = path.posix.join(file.cwd, file.data.filePath as string)
|
const fp = file.data.filePath!
|
||||||
|
const fullFp = path.posix.join(file.cwd, fp)
|
||||||
for (const source of opts.priority) {
|
for (const source of opts.priority) {
|
||||||
if (source === "filesystem") {
|
if (source === "filesystem") {
|
||||||
const st = await fs.promises.stat(fp)
|
const st = await fs.promises.stat(fullFp)
|
||||||
created ||= st.birthtimeMs
|
created ||= st.birthtimeMs
|
||||||
modified ||= st.mtimeMs
|
modified ||= st.mtimeMs
|
||||||
} else if (source === "frontmatter" && file.data.frontmatter) {
|
} else if (source === "frontmatter" && file.data.frontmatter) {
|
||||||
@ -49,9 +65,9 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options> | und
|
|||||||
}
|
}
|
||||||
|
|
||||||
file.data.dates = {
|
file.data.dates = {
|
||||||
created: created ? new Date(created) : new Date(),
|
created: coerceDate(fp, created),
|
||||||
modified: modified ? new Date(modified) : new Date(),
|
modified: coerceDate(fp, modified),
|
||||||
published: published ? new Date(published) : new Date(),
|
published: coerceDate(fp, published),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
11
quartz/plugins/transformers/linebreaks.ts
Normal file
11
quartz/plugins/transformers/linebreaks.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
|
import remarkBreaks from "remark-breaks"
|
||||||
|
|
||||||
|
export const HardLineBreaks: QuartzTransformerPlugin = () => {
|
||||||
|
return {
|
||||||
|
name: "HardLineBreaks",
|
||||||
|
markdownPlugins() {
|
||||||
|
return [remarkBreaks]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ import {
|
|||||||
SimpleSlug,
|
SimpleSlug,
|
||||||
TransformOptions,
|
TransformOptions,
|
||||||
_stripSlashes,
|
_stripSlashes,
|
||||||
joinSegments,
|
|
||||||
simplifySlug,
|
simplifySlug,
|
||||||
splitAnchor,
|
splitAnchor,
|
||||||
transformLink,
|
transformLink,
|
||||||
@ -19,11 +18,13 @@ interface Options {
|
|||||||
markdownLinkResolution: TransformOptions["strategy"]
|
markdownLinkResolution: TransformOptions["strategy"]
|
||||||
/** Strips folders from a link so that it looks nice */
|
/** Strips folders from a link so that it looks nice */
|
||||||
prettyLinks: boolean
|
prettyLinks: boolean
|
||||||
|
openLinksInNewTab: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
markdownLinkResolution: "absolute",
|
markdownLinkResolution: "absolute",
|
||||||
prettyLinks: true,
|
prettyLinks: true,
|
||||||
|
openLinksInNewTab: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||||
@ -53,8 +54,13 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
|
|||||||
node.properties.className ??= []
|
node.properties.className ??= []
|
||||||
node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal")
|
node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal")
|
||||||
|
|
||||||
|
if (opts.openLinksInNewTab) {
|
||||||
|
node.properties.target = "_blank"
|
||||||
|
}
|
||||||
|
|
||||||
// don't process external links or intra-document anchors
|
// don't process external links or intra-document anchors
|
||||||
if (!(isAbsoluteUrl(dest) || dest.startsWith("#"))) {
|
const isInternal = !(isAbsoluteUrl(dest) || dest.startsWith("#"))
|
||||||
|
if (isInternal) {
|
||||||
dest = node.properties.href = transformLink(
|
dest = node.properties.href = transformLink(
|
||||||
file.data.slug!,
|
file.data.slug!,
|
||||||
dest,
|
dest,
|
||||||
@ -72,11 +78,13 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
|
|||||||
simplifySlug(destCanonical as FullSlug),
|
simplifySlug(destCanonical as FullSlug),
|
||||||
) as SimpleSlug
|
) as SimpleSlug
|
||||||
outgoing.add(simple)
|
outgoing.add(simple)
|
||||||
|
node.properties["data-slug"] = simple
|
||||||
}
|
}
|
||||||
|
|
||||||
// rewrite link internals if prettylinks is on
|
// rewrite link internals if prettylinks is on
|
||||||
if (
|
if (
|
||||||
opts.prettyLinks &&
|
opts.prettyLinks &&
|
||||||
|
isInternal &&
|
||||||
node.children.length === 1 &&
|
node.children.length === 1 &&
|
||||||
node.children[0].type === "text" &&
|
node.children[0].type === "text" &&
|
||||||
!node.children[0].value.startsWith("#")
|
!node.children[0].value.startsWith("#")
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { PluggableList } from "unified"
|
import { PluggableList } from "unified"
|
||||||
import { QuartzTransformerPlugin } from "../types"
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
|
import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
|
||||||
|
import { Element, Literal, Root as HtmlRoot } from "hast"
|
||||||
import { Replace, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
|
import { Replace, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
|
||||||
import { slug as slugAnchor } from "github-slugger"
|
import { slug as slugAnchor } from "github-slugger"
|
||||||
import rehypeRaw from "rehype-raw"
|
import rehypeRaw from "rehype-raw"
|
||||||
@ -13,6 +14,7 @@ import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../util/path"
|
|||||||
import { toHast } from "mdast-util-to-hast"
|
import { toHast } from "mdast-util-to-hast"
|
||||||
import { toHtml } from "hast-util-to-html"
|
import { toHtml } from "hast-util-to-html"
|
||||||
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
|
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
|
||||||
|
import { capitalize } from "../../util/lang"
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
comments: boolean
|
comments: boolean
|
||||||
@ -21,6 +23,7 @@ export interface Options {
|
|||||||
callouts: boolean
|
callouts: boolean
|
||||||
mermaid: boolean
|
mermaid: boolean
|
||||||
parseTags: boolean
|
parseTags: boolean
|
||||||
|
parseBlockReferences: boolean
|
||||||
enableInHtmlEmbed: boolean
|
enableInHtmlEmbed: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,6 +34,7 @@ const defaultOptions: Options = {
|
|||||||
callouts: true,
|
callouts: true,
|
||||||
mermaid: true,
|
mermaid: true,
|
||||||
parseTags: true,
|
parseTags: true,
|
||||||
|
parseBlockReferences: true,
|
||||||
enableInHtmlEmbed: false,
|
enableInHtmlEmbed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +73,8 @@ const callouts = {
|
|||||||
const calloutMapping: Record<string, keyof typeof callouts> = {
|
const calloutMapping: Record<string, keyof typeof callouts> = {
|
||||||
note: "note",
|
note: "note",
|
||||||
abstract: "abstract",
|
abstract: "abstract",
|
||||||
|
summary: "abstract",
|
||||||
|
tldr: "abstract",
|
||||||
info: "info",
|
info: "info",
|
||||||
todo: "todo",
|
todo: "todo",
|
||||||
tip: "tip",
|
tip: "tip",
|
||||||
@ -96,11 +102,7 @@ const calloutMapping: Record<string, keyof typeof callouts> = {
|
|||||||
|
|
||||||
function canonicalizeCallout(calloutName: string): keyof typeof callouts {
|
function canonicalizeCallout(calloutName: string): keyof typeof callouts {
|
||||||
let callout = calloutName.toLowerCase() as keyof typeof calloutMapping
|
let callout = calloutName.toLowerCase() as keyof typeof calloutMapping
|
||||||
return calloutMapping[callout] ?? calloutName
|
return calloutMapping[callout] ?? "note"
|
||||||
}
|
|
||||||
|
|
||||||
const capitalize = (s: string): string => {
|
|
||||||
return s.substring(0, 1).toUpperCase() + s.substring(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// !? -> optional embedding
|
// !? -> optional embedding
|
||||||
@ -109,14 +111,17 @@ const capitalize = (s: string): string => {
|
|||||||
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
|
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
|
||||||
// (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias)
|
// (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias)
|
||||||
const wikilinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)?(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g")
|
const wikilinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)?(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g")
|
||||||
const highlightRegex = new RegExp(/==(.+)==/, "g")
|
const highlightRegex = new RegExp(/==([^=]+)==/, "g")
|
||||||
const commentRegex = new RegExp(/%%(.+)%%/, "g")
|
const commentRegex = new RegExp(/%%(.+)%%/, "g")
|
||||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
||||||
const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/)
|
const calloutRegex = new RegExp(/^\[\!(\w+)\]([+-]?)/)
|
||||||
const calloutLineRegex = new RegExp(/^> *\[\!\w+\][+-]?.*$/, "gm")
|
const calloutLineRegex = new RegExp(/^> *\[\!\w+\][+-]?.*$/, "gm")
|
||||||
// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line
|
// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line
|
||||||
// #(\w+) -> tag itself is # followed by a string of alpha-numeric characters
|
// #(...) -> capturing group, tag itself must start with #
|
||||||
const tagRegex = new RegExp(/(?:^| )#(\p{L}+)/, "gu")
|
// (?:[-_\p{L}])+ -> non-capturing group, non-empty string of (Unicode-aware) alpha-numeric characters, hyphens and/or underscores
|
||||||
|
// (?:\/[-_\p{L}]+)*) -> non-capturing group, matches an arbitrary number of tag strings separated by "/"
|
||||||
|
const tagRegex = new RegExp(/(?:^| )#((?:[-_\p{L}\d])+(?:\/[-_\p{L}\d]+)*)/, "gu")
|
||||||
|
const blockReferenceRegex = new RegExp(/\^([A-Za-z0-9]+)$/, "g")
|
||||||
|
|
||||||
export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = (
|
export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = (
|
||||||
userOpts,
|
userOpts,
|
||||||
@ -230,8 +235,16 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
|||||||
value: `<iframe src="${url}"></iframe>`,
|
value: `<iframe src="${url}"></iframe>`,
|
||||||
}
|
}
|
||||||
} else if (ext === "") {
|
} else if (ext === "") {
|
||||||
// TODO: note embed
|
const block = anchor
|
||||||
|
return {
|
||||||
|
type: "html",
|
||||||
|
data: { hProperties: { transclude: true } },
|
||||||
|
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}"><a href="${
|
||||||
|
url + anchor
|
||||||
|
}" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// otherwise, fall through to regular link
|
// otherwise, fall through to regular link
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,13 +396,18 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
|||||||
return (tree: Root, file) => {
|
return (tree: Root, file) => {
|
||||||
const base = pathToRoot(file.data.slug!)
|
const base = pathToRoot(file.data.slug!)
|
||||||
findAndReplace(tree, tagRegex, (_value: string, tag: string) => {
|
findAndReplace(tree, tagRegex, (_value: string, tag: string) => {
|
||||||
|
// Check if the tag only includes numbers
|
||||||
|
if (/^\d+$/.test(tag)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tag = slugTag(tag)
|
||||||
if (file.data.frontmatter && !file.data.frontmatter.tags.includes(tag)) {
|
if (file.data.frontmatter && !file.data.frontmatter.tags.includes(tag)) {
|
||||||
file.data.frontmatter.tags.push(tag)
|
file.data.frontmatter.tags.push(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "link",
|
type: "link",
|
||||||
url: base + `/tags/${slugTag(tag)}`,
|
url: base + `/tags/${tag}`,
|
||||||
data: {
|
data: {
|
||||||
hProperties: {
|
hProperties: {
|
||||||
className: ["tag-link"],
|
className: ["tag-link"],
|
||||||
@ -406,11 +424,64 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugins
|
return plugins
|
||||||
},
|
},
|
||||||
htmlPlugins() {
|
htmlPlugins() {
|
||||||
return [rehypeRaw]
|
const plugins = [rehypeRaw]
|
||||||
|
|
||||||
|
if (opts.parseBlockReferences) {
|
||||||
|
plugins.push(() => {
|
||||||
|
const inlineTagTypes = new Set(["p", "li"])
|
||||||
|
const blockTagTypes = new Set(["blockquote"])
|
||||||
|
return (tree, file) => {
|
||||||
|
file.data.blocks = {}
|
||||||
|
file.data.htmlAst = tree
|
||||||
|
|
||||||
|
visit(tree, "element", (node, index, parent) => {
|
||||||
|
if (blockTagTypes.has(node.tagName)) {
|
||||||
|
const nextChild = parent?.children.at(index! + 2) as Element
|
||||||
|
if (nextChild && nextChild.tagName === "p") {
|
||||||
|
const text = nextChild.children.at(0) as Literal
|
||||||
|
if (text && text.value && text.type === "text") {
|
||||||
|
const matches = text.value.match(blockReferenceRegex)
|
||||||
|
if (matches && matches.length >= 1) {
|
||||||
|
parent!.children.splice(index! + 2, 1)
|
||||||
|
const block = matches[0].slice(1)
|
||||||
|
|
||||||
|
if (!Object.keys(file.data.blocks!).includes(block)) {
|
||||||
|
node.properties = {
|
||||||
|
...node.properties,
|
||||||
|
id: block,
|
||||||
|
}
|
||||||
|
file.data.blocks![block] = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (inlineTagTypes.has(node.tagName)) {
|
||||||
|
const last = node.children.at(-1) as Literal
|
||||||
|
if (last && last.value && typeof last.value === "string") {
|
||||||
|
const matches = last.value.match(blockReferenceRegex)
|
||||||
|
if (matches && matches.length >= 1) {
|
||||||
|
last.value = last.value.slice(0, -matches[0].length)
|
||||||
|
const block = matches[0].slice(1)
|
||||||
|
|
||||||
|
if (!Object.keys(file.data.blocks!).includes(block)) {
|
||||||
|
node.properties = {
|
||||||
|
...node.properties,
|
||||||
|
id: block,
|
||||||
|
}
|
||||||
|
file.data.blocks![block] = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugins
|
||||||
},
|
},
|
||||||
externalResources() {
|
externalResources() {
|
||||||
const js: JSResource[] = []
|
const js: JSResource[] = []
|
||||||
@ -449,3 +520,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "vfile" {
|
||||||
|
interface DataMap {
|
||||||
|
blocks: Record<string, Element>
|
||||||
|
htmlAst: HtmlRoot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
108
quartz/plugins/transformers/oxhugofm.ts
Normal file
108
quartz/plugins/transformers/oxhugofm.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
/** Replace {{ relref }} with quartz wikilinks []() */
|
||||||
|
wikilinks: boolean
|
||||||
|
/** Remove pre-defined anchor (see https://ox-hugo.scripter.co/doc/anchors/) */
|
||||||
|
removePredefinedAnchor: boolean
|
||||||
|
/** Remove hugo shortcode syntax */
|
||||||
|
removeHugoShortcode: boolean
|
||||||
|
/** Replace <figure/> with ![]() */
|
||||||
|
replaceFigureWithMdImg: boolean
|
||||||
|
|
||||||
|
/** Replace org latex fragments with $ and $$ */
|
||||||
|
replaceOrgLatex: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: Options = {
|
||||||
|
wikilinks: true,
|
||||||
|
removePredefinedAnchor: true,
|
||||||
|
removeHugoShortcode: true,
|
||||||
|
replaceFigureWithMdImg: true,
|
||||||
|
replaceOrgLatex: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const relrefRegex = new RegExp(/\[([^\]]+)\]\(\{\{< relref "([^"]+)" >\}\}\)/, "g")
|
||||||
|
const predefinedHeadingIdRegex = new RegExp(/(.*) {#(?:.*)}/, "g")
|
||||||
|
const hugoShortcodeRegex = new RegExp(/{{(.*)}}/, "g")
|
||||||
|
const figureTagRegex = new RegExp(/< ?figure src="(.*)" ?>/, "g")
|
||||||
|
// \\\\\( -> matches \\(
|
||||||
|
// (.+?) -> Lazy match for capturing the equation
|
||||||
|
// \\\\\) -> matches \\)
|
||||||
|
const inlineLatexRegex = new RegExp(/\\\\\((.+?)\\\\\)/, "g")
|
||||||
|
// (?:\\begin{equation}|\\\\\(|\\\\\[) -> start of equation
|
||||||
|
// ([\s\S]*?) -> Matches the block equation
|
||||||
|
// (?:\\\\\]|\\\\\)|\\end{equation}) -> end of equation
|
||||||
|
const blockLatexRegex = new RegExp(
|
||||||
|
/(?:\\begin{equation}|\\\\\(|\\\\\[)([\s\S]*?)(?:\\\\\]|\\\\\)|\\end{equation})/,
|
||||||
|
"g",
|
||||||
|
)
|
||||||
|
// \$\$[\s\S]*?\$\$ -> Matches block equations
|
||||||
|
// \$.*?\$ -> Matches inline equations
|
||||||
|
const quartzLatexRegex = new RegExp(/\$\$[\s\S]*?\$\$|\$.*?\$/, "g")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ox-hugo is an org exporter backend that exports org files to hugo-compatible
|
||||||
|
* markdown in an opinionated way. This plugin adds some tweaks to the generated
|
||||||
|
* markdown to make it compatible with quartz but the list of changes applied it
|
||||||
|
* is not exhaustive.
|
||||||
|
* */
|
||||||
|
export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = (
|
||||||
|
userOpts,
|
||||||
|
) => {
|
||||||
|
const opts = { ...defaultOptions, ...userOpts }
|
||||||
|
return {
|
||||||
|
name: "OxHugoFlavouredMarkdown",
|
||||||
|
textTransform(_ctx, src) {
|
||||||
|
if (opts.wikilinks) {
|
||||||
|
src = src.toString()
|
||||||
|
src = src.replaceAll(relrefRegex, (value, ...capture) => {
|
||||||
|
const [text, link] = capture
|
||||||
|
return `[${text}](${link})`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.removePredefinedAnchor) {
|
||||||
|
src = src.toString()
|
||||||
|
src = src.replaceAll(predefinedHeadingIdRegex, (value, ...capture) => {
|
||||||
|
const [headingText] = capture
|
||||||
|
return headingText
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.removeHugoShortcode) {
|
||||||
|
src = src.toString()
|
||||||
|
src = src.replaceAll(hugoShortcodeRegex, (value, ...capture) => {
|
||||||
|
const [scContent] = capture
|
||||||
|
return scContent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.replaceFigureWithMdImg) {
|
||||||
|
src = src.toString()
|
||||||
|
src = src.replaceAll(figureTagRegex, (value, ...capture) => {
|
||||||
|
const [src] = capture
|
||||||
|
return `![](${src})`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.replaceOrgLatex) {
|
||||||
|
src = src.toString()
|
||||||
|
src = src.replaceAll(inlineLatexRegex, (value, ...capture) => {
|
||||||
|
const [eqn] = capture
|
||||||
|
return `$${eqn}$`
|
||||||
|
})
|
||||||
|
src = src.replaceAll(blockLatexRegex, (value, ...capture) => {
|
||||||
|
const [eqn] = capture
|
||||||
|
return `$$${eqn}$$`
|
||||||
|
})
|
||||||
|
|
||||||
|
// ox-hugo escapes _ as \_
|
||||||
|
src = src.replaceAll(quartzLatexRegex, (value) => {
|
||||||
|
return value.replaceAll("\\_", "_")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return src
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -8,12 +8,14 @@ export interface Options {
|
|||||||
maxDepth: 1 | 2 | 3 | 4 | 5 | 6
|
maxDepth: 1 | 2 | 3 | 4 | 5 | 6
|
||||||
minEntries: 1
|
minEntries: 1
|
||||||
showByDefault: boolean
|
showByDefault: boolean
|
||||||
|
collapseByDefault: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
maxDepth: 3,
|
maxDepth: 3,
|
||||||
minEntries: 1,
|
minEntries: 1,
|
||||||
showByDefault: true,
|
showByDefault: true,
|
||||||
|
collapseByDefault: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TocEntry {
|
interface TocEntry {
|
||||||
@ -54,6 +56,7 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
|
|||||||
...entry,
|
...entry,
|
||||||
depth: entry.depth - highestDepth,
|
depth: entry.depth - highestDepth,
|
||||||
}))
|
}))
|
||||||
|
file.data.collapseToc = opts.collapseByDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,5 +69,6 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
|
|||||||
declare module "vfile" {
|
declare module "vfile" {
|
||||||
interface DataMap {
|
interface DataMap {
|
||||||
toc: TocEntry[]
|
toc: TocEntry[]
|
||||||
|
collapseToc: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
@use "./custom.scss";
|
@use "./variables.scss" as *;
|
||||||
@use "./syntax.scss";
|
@use "./syntax.scss";
|
||||||
@use "./callouts.scss";
|
@use "./callouts.scss";
|
||||||
@use "./variables.scss" as *;
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
@ -65,7 +64,7 @@ a {
|
|||||||
color: var(--tertiary) !important;
|
color: var(--tertiary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.internal {
|
&.internal:not(:has(> img)) {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: var(--highlight);
|
background-color: var(--highlight);
|
||||||
padding: 0 0.1rem;
|
padding: 0 0.1rem;
|
||||||
@ -95,6 +94,8 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
& article {
|
& article {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
& > h1 {
|
& > h1 {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
@ -389,23 +390,33 @@ p {
|
|||||||
line-height: 1.6rem;
|
line-height: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
& > table {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
min-width: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 0.7rem;
|
||||||
border-bottom: 2px solid var(--gray);
|
border-bottom: 2px solid var(--gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 0.2rem 1rem;
|
padding: 0.2rem 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
@ -446,7 +457,7 @@ video {
|
|||||||
|
|
||||||
ul.overflow,
|
ul.overflow,
|
||||||
ol.overflow {
|
ol.overflow {
|
||||||
height: 300px;
|
max-height: 400;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
// clearfix
|
// clearfix
|
||||||
@ -454,7 +465,7 @@ ol.overflow {
|
|||||||
clear: both;
|
clear: both;
|
||||||
|
|
||||||
& > li:last-of-type {
|
& > li:last-of-type {
|
||||||
margin-bottom: 50px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
&:after {
|
||||||
@ -470,3 +481,9 @@ ol.overflow {
|
|||||||
background: linear-gradient(transparent 0px, var(--light));
|
background: linear-gradient(transparent 0px, var(--light));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.transclude {
|
||||||
|
ul {
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -82,7 +82,6 @@
|
|||||||
|
|
||||||
.callout-title {
|
.callout-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
@ -103,6 +102,8 @@
|
|||||||
.callout-icon {
|
.callout-icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
flex: 0 0 18px;
|
||||||
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.callout-title-inner {
|
.callout-title-inner {
|
||||||
|
@ -7,6 +7,8 @@ export interface Argv {
|
|||||||
output: string
|
output: string
|
||||||
serve: boolean
|
serve: boolean
|
||||||
port: number
|
port: number
|
||||||
|
wsPort: number
|
||||||
|
remoteDevHost?: string
|
||||||
concurrency?: number
|
concurrency?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
8
quartz/util/escape.ts
Normal file
8
quartz/util/escape.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const escapeHTML = (unsafe: string) => {
|
||||||
|
return unsafe
|
||||||
|
.replaceAll("&", "&")
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll(">", ">")
|
||||||
|
.replaceAll('"', """)
|
||||||
|
.replaceAll("'", "'")
|
||||||
|
}
|
28
quartz/util/jsx.tsx
Normal file
28
quartz/util/jsx.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Components, Jsx, toJsxRuntime } from "hast-util-to-jsx-runtime"
|
||||||
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
|
import { Node, Root } from "hast"
|
||||||
|
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
||||||
|
import { trace } from "./trace"
|
||||||
|
import { type FilePath } from "./path"
|
||||||
|
|
||||||
|
const customComponents: Components = {
|
||||||
|
table: (props) => (
|
||||||
|
<div class="table-container">
|
||||||
|
<table {...props} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export function htmlToJsx(fp: FilePath, tree: Node<QuartzPluginData>) {
|
||||||
|
try {
|
||||||
|
return toJsxRuntime(tree as Root, {
|
||||||
|
Fragment,
|
||||||
|
jsx: jsx as Jsx,
|
||||||
|
jsxs: jsxs as Jsx,
|
||||||
|
elementAttributeNameCase: "html",
|
||||||
|
components: customComponents,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
trace(`Failed to parse Markdown in \`${fp}\` into JSX`, e as Error)
|
||||||
|
}
|
||||||
|
}
|
11
quartz/util/lang.ts
Normal file
11
quartz/util/lang.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export function pluralize(count: number, s: string): string {
|
||||||
|
if (count === 1) {
|
||||||
|
return `1 ${s}`
|
||||||
|
} else {
|
||||||
|
return `${count} ${s}s`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function capitalize(s: string): string {
|
||||||
|
return s.substring(0, 1).toUpperCase() + s.substring(1)
|
||||||
|
}
|
@ -52,7 +52,7 @@ export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {
|
|||||||
|
|
||||||
let slug = withoutFileExt
|
let slug = withoutFileExt
|
||||||
.split("/")
|
.split("/")
|
||||||
.map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent")) // slugify all segments
|
.map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent").replace(/\?/g, "-q")) // slugify all segments
|
||||||
.join("/") // always use / as sep
|
.join("/") // always use / as sep
|
||||||
.replace(/\/$/, "") // remove trailing slash
|
.replace(/\/$/, "") // remove trailing slash
|
||||||
|
|
||||||
@ -123,7 +123,10 @@ export function slugTag(tag: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function joinSegments(...args: string[]): string {
|
export function joinSegments(...args: string[]): string {
|
||||||
return args.filter((segment) => segment !== "").join("/")
|
return args
|
||||||
|
.filter((segment) => segment !== "")
|
||||||
|
.join("/")
|
||||||
|
.replace(/\/\/+/g, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllSegmentPrefixes(tags: string): string[] {
|
export function getAllSegmentPrefixes(tags: string): string[] {
|
||||||
|
@ -4,7 +4,7 @@ import { isMainThread } from "workerpool"
|
|||||||
|
|
||||||
const rootFile = /.*at file:/
|
const rootFile = /.*at file:/
|
||||||
export function trace(msg: string, err: Error) {
|
export function trace(msg: string, err: Error) {
|
||||||
const stack = err.stack
|
let stack = err.stack ?? ""
|
||||||
|
|
||||||
const lines: string[] = []
|
const lines: string[] = []
|
||||||
|
|
||||||
@ -12,15 +12,11 @@ export function trace(msg: string, err: Error) {
|
|||||||
lines.push(
|
lines.push(
|
||||||
"\n" +
|
"\n" +
|
||||||
chalk.bgRed.black.bold(" ERROR ") +
|
chalk.bgRed.black.bold(" ERROR ") +
|
||||||
"\n" +
|
"\n\n" +
|
||||||
chalk.red(` ${msg}`) +
|
chalk.red(` ${msg}`) +
|
||||||
(err.message.length > 0 ? `: ${err.message}` : ""),
|
(err.message.length > 0 ? `: ${err.message}` : ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!stack) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let reachedEndOfLegibleTrace = false
|
let reachedEndOfLegibleTrace = false
|
||||||
for (const line of stack.split("\n").slice(1)) {
|
for (const line of stack.split("\n").slice(1)) {
|
||||||
if (reachedEndOfLegibleTrace) {
|
if (reachedEndOfLegibleTrace) {
|
||||||
|
Loading…
Reference in New Issue
Block a user