Compare commits

..

317 Commits

Author SHA1 Message Date
Jacky Zhao
d0c0daa4aa ci: fix autotag 2024-02-23 19:00:47 -08:00
Jacky Zhao
ea7122dd5a pkg: bump to 4.2.3 2024-02-23 18:52:28 -08:00
Jacky Zhao
2c74b05d1b fix(ci): autotag 2024-02-23 18:48:25 -08:00
kabirgh
a6417c447a fix(fast rebuild): handle added an deleted markdown correctly (#921)
* Handle added files correctly

* Handle deletes properly

* addGraph renamed to mergeGraph
2024-02-23 18:40:42 -08:00
Jacky Zhao
6be1ed1ea2 docs(latex): mhchem 2024-02-23 17:45:41 -08:00
Eiko Wagenknecht
1929241a62 docs: update plugin documentation (#888)
* docs: first few plugins documented

* docs: move plugin info

* docs: move plugin docs to tag based system

* docs: update latex example code snippet

* docs: fix spelling of latex in title

* docs: add missing linebreak

* docs: remove plugin tag from feature pages

* docs: shorten titles

* docs: refine wording

* docs: move plugin details for frontmatter

* docs: add features/* tags

* docs: update latex example

* docs: make references more explicit

* docs: add stubs for the remaining plugins

* docs: more descriptions

* docs: fix feature tags

* docs: descriptions

* docs: new plugin pages

* docs: update configuration page

* docs: more plugin work

* docs: run prettier

* docs: remove comments in config file and add link to docs

* docs: minor fixes

* docs: run prettier

* docs: spelling

* docs: update docs/plugins/AliasRedirects.md

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>

* docs: update docs/plugins/Assets.md

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>

* docs: update docs/plugins/CNAME.md

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>

* docs: update docs/plugins/Static.md

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>

* docs: update docs

* docs: update docs/features/Mermaid diagrams.md

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>

* docs: update docs/plugins/RemoveDrafts.md

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* docs: update docs/plugins/Assets.md

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* docs: update docs/configuration.md

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* docs: update docs/configuration.md

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* docs: update docs/configuration.md

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* docs: some updates

* docs: work in review comments

---------

Signed-off-by: Eiko Wagenknecht <git@eiko-wagenknecht.de>
Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-02-23 12:07:53 -08:00
Jacky Zhao
421718958f fix(callouts): use user provided title instead of canonical for default title 2024-02-23 11:20:35 -08:00
Jacky Zhao
be9b6b3a1e fix(docs): make docs accurate to callout behaviour (closes #920) 2024-02-23 09:32:22 -08:00
KylinDC
fb66ae2838 deps(highlighting): migrate to shiki as shikiji has been archived (#918) 2024-02-22 21:56:26 -08:00
Aaron Pham
129e878b29 chore(img): return targetUrl as given href (#916)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-22 21:55:35 -08:00
Aaron Pham
96c7076fb5 feat(popover): add support for PDF (#913)
* feat(popover): add support for PDF

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: split pdf by ';'

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: remove unnecessary check

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-22 22:16:40 -05:00
Aaron Pham
345c347a56 chore: passing additional buildCtx to componentData (#914)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-22 17:51:07 -08:00
Aster Hu
916aedce40 docs: Add Aster's notebook to showcase.md (#912) 2024-02-21 19:04:36 -08:00
kon-foo
7dd596ebce docs: Fix in explorer.md (#911) 2024-02-21 08:18:44 -08:00
Eiko Wagenknecht
1c3f3d03e1 fix(toc): correct type for minEntries param (#909) 2024-02-20 09:06:53 -08:00
Eiko Wagenknecht
3b266ee7d0 fix: add space and missing dot for listing pages (#907) 2024-02-20 09:45:10 -05:00
JONG HWAN KIM
fc5fa48bf1 feat(i18n): change itemsUnderFolder, itemsUnderTag translation of ko-KR (#905)
* feat(i18n): add Korean

* feat(i18n): add Korean

* feat(i18n): change itemsUnderFolder, itemsUnderTag translation of ko-KR
2024-02-19 22:36:54 -08:00
Eiko Wagenknecht
b6cf3df84f fix: correctly parse falsy js as title (#900) 2024-02-19 13:49:07 -08:00
dependabot[bot]
779c501d9e chore(deps): bump preact from 10.19.4 to 10.19.5 (#898)
Bumps [preact](https://github.com/preactjs/preact) from 10.19.4 to 10.19.5.
- [Release notes](https://github.com/preactjs/preact/releases)
- [Commits](https://github.com/preactjs/preact/compare/10.19.4...10.19.5)

---
updated-dependencies:
- dependency-name: preact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-19 13:47:07 -08:00
dependabot[bot]
f1619620d5 chore(deps): bump globby from 14.0.0 to 14.0.1 (#897)
Bumps [globby](https://github.com/sindresorhus/globby) from 14.0.0 to 14.0.1.
- [Release notes](https://github.com/sindresorhus/globby/releases)
- [Commits](https://github.com/sindresorhus/globby/compare/v14.0.0...v14.0.1)

---
updated-dependencies:
- dependency-name: globby
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-19 13:47:00 -08:00
dependabot[bot]
637e336cda chore(deps-dev): bump @types/node from 20.11.16 to 20.11.19 (#899)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.16 to 20.11.19.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-19 13:46:27 -08:00
kon-foo
0493942c79 fix: remove assets via globs to avoid volume mount lock (#877)
* Fix docker volume lock issue by altering asset cleanup method
Modified build process to prevent the deletion of the output directory.

* Add fsOps utility for filesystem operations

* Use cleanDirectory in build process to fix volume lock issue

* applied prettier

* handle ENOENT error when output dir does not exist

* remove native function in favor of rimraf

* use path.join to concatenate paths
2024-02-19 11:04:27 -08:00
kabirgh
a67a8d7aa9 feat: implement getDependencyGraph for TagPage (#872)
* feat: implement getDependencyGraph for TagPage

* Only add file to dg if it has at least 1 tag
2024-02-19 13:58:15 -05:00
KylinDC
e85ea49000 feat(i18n): add Simplified Chinese (#896) 2024-02-19 13:31:09 -05:00
kon-foo
3e09b05468 docs: add self-hosting section (#883)
* Add Self-Hosting section
Add Nginx section

* run prettier
2024-02-19 12:50:40 -05:00
Leonardo Ledda
d9e8ffc78c feat(i18n): Add Italian (#893)
Signed-off-by: Leonardo Ledda <leonardoledda@gmail.com>
2024-02-19 12:50:01 -05:00
Eiko Wagenknecht
efd46f84de fix(frontmatter): delimiters parameter was not passed (#885)
* fix: delimiters parameter was not passed

Signed-off-by: Eiko Wagenknecht <git@eiko-wagenknecht.de>

* fix: remove unneeded undefined

---------

Signed-off-by: Eiko Wagenknecht <git@eiko-wagenknecht.de>
2024-02-19 00:08:36 -08:00
s-crypt
739c2e2cc8 perf(cdn): CDNJS instead of JSDelivr (#891) 2024-02-18 20:26:04 -08:00
JONG HWAN KIM
b1a105371b feat(i18n): add Korean (#889)
* feat(i18n): add Korean

* feat(i18n): add Korean
2024-02-18 17:37:59 -05:00
makondratev
8c5c5f9130 feat(i18n): add Russian (#886) 2024-02-18 13:54:37 -05:00
Jacky Zhao
aa24a62ae7 fix(breadcrumbs): calculate trailing slash for tag hierarchies (closes #873) 2024-02-17 11:12:35 -08:00
Jacky Zhao
a6690c6503 fix(style): bold should use semibold 2024-02-17 10:57:59 -08:00
Jacky Zhao
06e3f8b93d fix(style): introduce semiBoldWeight and various improvements to reduce CLS 2024-02-17 10:34:51 -08:00
Silviu Lorenț
fa2ea2896f feat: add user-defined config for syntax highlighting plugin (#869)
* feat: add user-defined options to syntax highlighting plugin

* feat: add default syntax highlighting config to `quartz.config.ts`

* chore: refactor according to @aarnphm's review

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>

* chore: run Prettier on `quartz/plugins/transformers/syntax.ts`

* Update quartz/plugins/transformers/syntax.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* Update syntax.ts

---------

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-02-17 10:23:45 -08:00
kabirgh
5af707ea20 fix/feat(fast rebuild): re-render transclusions in normal and fastRebuild mode (#842)
* Re-render transclusions in normal watch mode

* Include transclusions in ContentPage getDependencyGraph

* Address PR comments
2024-02-17 09:45:01 -08:00
kabirgh
823d952922 feat: implement getDependencyGraph for AliasRedirects emitter (#860) 2024-02-15 19:50:48 -05:00
kabirgh
78a408c96a feat: implement getDependencyGraph for FolderPage (#849) 2024-02-15 19:50:33 -05:00
David Fischer
6c8023463d Add support for image popovers (#854)
* feat(popover): Add support for images

* fix: run prettier

* feat(popover): use switch logic for content types & adjust styles

* feat(popover): Add content type data tag for popover-inner class
2024-02-14 15:41:13 -05:00
Aaron Bull Schaefer
2041341d9f docs: workaround for shallow clones on Cloudflare Pages (#868)
Rather than recommend a different hosting provider, Cloudflare Pages
users that prioritize the `git` method for their `CreatedModifiedDate`
configuration can preface the build command with a means of fetching the
required repository history.

See:
- https://gohugo.io/methods/page/gitinfo/#hosting-considerations
2024-02-14 09:41:44 -08:00
Aaron Pham
21c6bbf302 chore(types): add additional hint for LSP support (#864)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-13 23:53:44 -05:00
Jacky Zhao
b87a701ff7 fix: base.com not being resolved properly with joinSegments 2024-02-13 01:27:27 -08:00
Lin
880a9511b6 fix: incorrect link resolution for transclusion in root index file (#853)
Co-authored-by: Lauréline Nevin <laureline.nevin@unicaen.fr>
2024-02-13 03:11:16 -05:00
dependabot[bot]
a31e3f9458 chore(deps): bump @floating-ui/dom from 1.6.1 to 1.6.3 (#857)
Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.6.1 to 1.6.3.
- [Release notes](https://github.com/floating-ui/floating-ui/releases)
- [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/dom/CHANGELOG.md)
- [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/dom@1.6.3/packages/dom)

---
updated-dependencies:
- dependency-name: "@floating-ui/dom"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 00:21:45 -05:00
dependabot[bot]
2c06e68ba6 chore(deps): bump preact from 10.19.3 to 10.19.4 (#858)
Bumps [preact](https://github.com/preactjs/preact) from 10.19.3 to 10.19.4.
- [Release notes](https://github.com/preactjs/preact/releases)
- [Commits](https://github.com/preactjs/preact/compare/10.19.3...10.19.4)

---
updated-dependencies:
- dependency-name: preact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 00:21:30 -05:00
dependabot[bot]
4a28d0e5d1 chore(deps-dev): bump tsx from 4.7.0 to 4.7.1 (#859)
Bumps [tsx](https://github.com/privatenumber/tsx) from 4.7.0 to 4.7.1.
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/develop/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.7.0...v4.7.1)

---
updated-dependencies:
- dependency-name: tsx
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 00:21:13 -05:00
Jacky Zhao
a7325eadc1 fix(analytics): umami custom host should be a string (closes #852) 2024-02-12 09:01:05 -08:00
Emile Bangma
5dc4f21a4b feat(i18n): localize the min read string for the nl-NL locale (#850)
* Update min read translation

* Added nl_BE to Dutch

Added Flemish (nl_BE) to point to nl.

* Removed period to match other translations
2024-02-12 08:58:00 -08:00
Jacky Zhao
76f295620c feat: add transclude-src to transclude 'link to original' 2024-02-12 08:52:00 -08:00
kabirgh
226891b9b1 fix(fast rebuild): call only required emitters, don't always copy assets (#845)
* fix(fast rebuild): call only required emitters, don't always copy assets

* Type function
2024-02-11 12:20:44 -08:00
Jacky Zhao
389f2e8bee fix(ofm): allow diacretic marks in tag regex (closes #830) 2024-02-11 12:12:01 -08:00
dependabot[bot]
998198cffb chore(deps): bump esbuild-sass-plugin from 2.16.0 to 2.16.1 (#778)
Bumps [esbuild-sass-plugin](https://github.com/glromeo/esbuild-sass-plugin) from 2.16.0 to 2.16.1.
- [Release notes](https://github.com/glromeo/esbuild-sass-plugin/releases)
- [Commits](https://github.com/glromeo/esbuild-sass-plugin/compare/v2.16.0...v2.16.1)

---
updated-dependencies:
- dependency-name: esbuild-sass-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-11 11:27:16 -08:00
Jacky Zhao
4a6a44950f fix(breadcrumbs): folder index by full path rather than folder name (closes #676) 2024-02-11 11:26:24 -08:00
Silviu Lorenț
2b39bd93f3 feat(i18n): localize the min read string for the ro-RO locale (#847)
* feat(i18n): localize `min read` string for `ro-RO` locale

* chore: run Prettier on `quartz/i18n/locales/ro-RO.ts`
2024-02-11 11:23:58 -08:00
Neel Shah
b5295e0f26 fix: breadcrumbs displayName issue for file names ending with index (#839) 2024-02-11 11:08:12 -08:00
Jacky Zhao
ab0e20b4d0 chore: refactor out and export endsWith 2024-02-11 10:57:24 -08:00
Silviu Lorenț
af5f5abad4 docs: add documentation for Umami analytics integration (#846) 2024-02-11 10:51:10 -08:00
Alq
3518ca9e2a feat(i18n): localize the min read string (#838)
* feat(i18n): localize the min read string fixes #825

* chore: format
2024-02-11 10:43:08 -08:00
Aaron Pham
ab80eba794 chore(callouts): remove unnecessary whitespaces after class name (#833)
Though we should have a plugins that just strip whitespace in all node
class.

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-10 17:19:17 -05:00
Aaron Pham
6ae0bb0908 chore: move fonts all into static folder (#835)
* chore: move fonts all into static folder

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* chore: update formatter

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-02-10 17:17:41 -05:00
Aaron Pham
db5e701810 feat(i18n): support parsing callouts (#834)
* feat(i18n): support parsing callouts

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: move callout into components

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: update arabic translation

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: make sure to use correct items

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-10 17:09:57 -05:00
Alq
a0d6daa3b4 feat(i18n): add Arabic translation (#837)
* feat(i18n): add Arabic translation

* chore: format
2024-02-10 09:02:28 -08:00
kabirgh
fe353d946b feat(experimental): partial rebuilds (#716) 2024-02-09 10:07:32 -05:00
Jacky Zhao
a87704cd05 fix: set default locale for lang attribute 2024-02-08 09:31:36 -08:00
Silviu Lorenț
fd785ada56 feat(i18n): use Romanian translation for ro-MD locale (#828) 2024-02-08 08:48:13 -08:00
Serhii Stets
e186811c9c added Ukrainian to i18n (#829) 2024-02-08 08:47:12 -08:00
Aaron Pham
51818efc38 fix(umami): format correct string from custom hosts (#826) 2024-02-08 08:45:20 -08:00
Aaron Pham
330e322e48 feat(fonts): fetch before build (#817)
* feat: fetch google fonts before build

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* Update quartz/plugins/emitters/componentResources.ts

* fix: fetching wolff2

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: remove request stylesheet

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: race condition

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: remove preconnect for static fonts

since we are already downloading fonts into public folder

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: remove deadcode

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: add options to gate for cdn caching

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* chore: apply jacky's suggestion

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* chore: add docs and only use one promise

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: fmt

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: remove deadcode

* chore: final touches

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* revert: changes in theme.ts

* fix: styles and remove deadcode

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-02-08 02:52:55 -05:00
Miguel Pimentel
ca284778b2 add Spanish translations (#822)
* add Spanish translations

* format with prettier

* clears npm ci, formatted w/ prettier
2024-02-07 09:57:14 -08:00
Aaron Pham
2578597f7e chore(lang): lang element based on frontmatter or default locale (#819)
default locale

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-07 09:29:47 -08:00
Silviu Lorenț
ce413b4bae feat(i18n): add Romanian to i18n (#821) 2024-02-07 11:26:45 -05:00
Aaron Pham
d2fb50b83c fix(links): show backdrop on links highlighted in headers alias (#816)
* fix: assign specific classes based on parent node

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: use custom role for anchor icone

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: allow color on links 😄

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: unify search inner container

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-06 02:06:19 -05:00
Aaron Pham
52ef6d1b6f fix(search): set background-color for icon within preview panel (#815)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-06 01:12:31 -05:00
Jacky Zhao
34334eabed perf: don't load mermaid if its not on the page 2024-02-05 20:36:31 -08:00
Jacky Zhao
bec726b666 fix(i18n): forgot a string 2024-02-05 16:40:39 -08:00
Jacky Zhao
2b9659a1c2 fix(i18n): add default locale 2024-02-05 14:19:21 -08:00
dependabot[bot]
19fc53854f chore(deps-dev): bump @types/node from 20.11.14 to 20.11.16 (#811)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.14 to 20.11.16.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 16:19:49 -05:00
dependabot[bot]
479cbb6d91 chore(deps): bump @napi-rs/simple-git from 0.1.14 to 0.1.16 (#810)
Bumps [@napi-rs/simple-git](https://github.com/Brooooooklyn/simple-git) from 0.1.14 to 0.1.16.
- [Release notes](https://github.com/Brooooooklyn/simple-git/releases)
- [Commits](https://github.com/Brooooooklyn/simple-git/compare/v0.1.14...v0.1.16)

---
updated-dependencies:
- dependency-name: "@napi-rs/simple-git"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-05 16:19:34 -05:00
Emile Bangma
b169a5880f feat(i18n): Add Dutch to i18n (#813)
* Create nl-NL.ts

* Update index.ts

* Update nl-NL.ts
2024-02-05 13:12:54 -08:00
松浦 知也 Matsuura Tomoya
ba836dd3e0 feat(i18n): Add Japanese to i18n (#809)
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-02-05 08:58:31 -08:00
Mats Fangohr
b061b1b6a2 feat(i18n): German translation (#808) 2024-02-05 09:59:58 -05:00
Aaron Pham
e58c217de1 feat: support checkbox (closes #646) (#799)
* feat: support checkbox

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: apply review from jacky

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-04 22:19:25 -08:00
Aaron Pham
90725688a7 style(search): increase width on mobile view (#796)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-04 21:52:24 -08:00
Jacky Zhao
c891ad8ff5 pkg: bump to 4.2.2 2024-02-04 21:23:17 -08:00
Jacky Zhao
06ee73e006 fix(path): properly path encode & 2024-02-04 21:22:57 -08:00
Jacky Zhao
36e4cc41a9 chore(i18n): refactor and cleanup (#805)
* checkpoint

* finish

* docs
2024-02-04 20:57:10 -08:00
Mats Fangohr
dff4b06313 fix(i18n): backlinks naming in mapping (#800) 2024-02-04 09:48:31 -05:00
Aaron Pham
5b90fbd0d0 feat(ofm): parsing all type of arrow (#797)
* feat(ofm): parsing all type of arrow

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: use html value instead of decimal

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: skip parsing arrow if it is not a valid supported mapping

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-04 00:51:55 -05:00
Mara-Li
dbbc672c67 feat: Adding support for i18n (closes #462) (#738)
* fix: alt error mix with height/width

More granular detection of alt and resize in image

* fix: format

* feat: init i18n

* feat: add translation

* style: prettier for test

* fix: build-up the locale to fusion with dateLocale

* style: run prettier

* remove cursed file

* refactor: remove i18n library and use locale way instead

* format with prettier

* forgot to remove test

* prevent merging error

* format

* format

* fix: allow string for locale
- Check during translation if valid / existing locale
- Allow to use "en" and "en-US" for example
- Add fallback directly in the function
- Add default key in the function
- Add docstring to cfg.ts

* forgot item translation

* remove unused locale variable

* forgot to remove fr-FR testing

* format
2024-02-03 19:55:24 -08:00
Jacky Zhao
3fb3930df8 fix: calculate heading after latex (closes #719) 2024-02-03 19:44:24 -08:00
Jacky Zhao
742b883256 fix(search): flex basis and card highlighting 2024-02-02 12:18:02 -08:00
Jacky Zhao
9ff1fdd280 fix(search): oops restore ability to preview on hover lol 2024-02-02 10:52:51 -08:00
Jacky Zhao
a2c46f442d fix(search): dont rely on mouse to manipulate focus 2024-02-02 10:44:19 -08:00
Jacky Zhao
260498a96b fix(style): prevent callout icon from shrinking on long titles (closes #792) 2024-02-02 10:23:24 -08:00
Jacky Zhao
0a3379a853 fix(search): null checks and focus fixes 2024-02-02 10:10:25 -08:00
Luis Michaelis
bece8fcab6 fix: properly handle absolute paths in CreatedModifiedDate (#790)
When providing an absolute path to the content directory (e.g. when using an Obsidian Vault in another directory), the build step would fail with

    Failed to process `/absolute/path/to/file.md`: ENOENT: no such file or directory, stat '/current/working/directory/absolute/path/'

This problem originated in the `CreatedModifiedDate` transformer which tries to construct a native filesystem path to the file to call `fs.stat` on. It did not however, account for the original file path contained in the received `VFile` being an absolute path and so, just concatenated the current working directory with the absolute path producing a nonexistent one.

This patch adds a simple fix for this issue by checking if the original file path is already absolute before concatenating with the current working directory.
2024-02-02 09:51:34 -08:00
Jacky Zhao
18745a9dc6 fix(style): correctly collapse on mobile 2024-02-02 09:36:36 -08:00
Jacky Zhao
34a8dfcd55 pkg: bump to 4.2.1 2024-02-02 01:45:28 -08:00
Jacky Zhao
44da82467e fix(style): remove redundant selector 2024-02-02 01:45:15 -08:00
Jacky Zhao
3231ce6e79 fix: search async ordering, scroll offset 2024-02-02 01:36:17 -08:00
Jacky Zhao
a0b927da4a fix: use display instead of visibility for click handling pasthrough 2024-02-02 01:24:40 -08:00
Jacky Zhao
5ab922f316 fix(revert): font aliasing 2024-02-02 01:15:10 -08:00
Jacky Zhao
d11a0e71a8 fix: font smoothing defaults 2024-02-02 01:01:04 -08:00
Jacky Zhao
2b57a68e1f fix: font weight consistency 2024-02-02 00:53:09 -08:00
Jacky Zhao
18cd58617d fix: parallelize search indexing 2024-02-02 00:53:09 -08:00
Aaron Pham
ee868b2d79 fix(search): set correct attribute on hover icon (#787)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-02 00:35:53 -08:00
Jacky Zhao
5a36e5b68d fix(style): reasonable page width for rich search preview 2024-02-02 00:29:45 -08:00
Jacky Zhao
0416c03ae6 fix: be more eager about constructing search index 2024-02-02 00:25:05 -08:00
Jacky Zhao
3b596c9311 fix: flatmap children when highlighting rich preview to avoid body 2024-02-02 00:19:19 -08:00
Jacky Zhao
970a30a139 chore: fmt 2024-02-01 23:57:17 -08:00
Jacky Zhao
dc62aeb213 pkg: bump to 4.2.0 2024-02-01 23:55:40 -08:00
Jacky Zhao
9b8e0c9d1a chore(cleanup): misc refactoring for cleanup, fix some search bugs 2024-02-01 23:55:11 -08:00
Jacky Zhao
45b93a80f4 fix: index setup, styling fixes 2024-02-01 22:22:06 -08:00
Jacky Zhao
e9fb0ecb96 fix: border radius on search preview 2024-02-01 21:19:51 -08:00
Jacky Zhao
c0c0b24138 feat: improve search preview styling and tokenization 2024-02-01 21:19:51 -08:00
Jacky Zhao
c00089bd57 chore: add window.addCleanup() for cleaning up handlers 2024-02-01 21:19:51 -08:00
Justin Fowler
8a6ebd1939 docs: clarity for RecentNotes (#786)
- Removed a word for clarity
- added reference to layout file
2024-02-01 23:17:21 -05:00
Aaron Pham
f78b512436 chore(search): check for input type and assignment of focus (#785)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-01 19:25:45 -08:00
Aaron Pham
295b8fc914 fix(search): increase size on fullPageWidth viewport (#784)
* fix(search): increase size on fullPageWidth viewport

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: fix width size to be consistent on multiple views

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: set layout to 0 if there is no term

remove flashing by setting max-height

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-01 19:44:33 -05:00
Aaron Pham
756acc7f97 feat(search): highlight on preview (#783)
* feat: primitive full-text search on preview

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: remove invalid regex and unused code path

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-02-01 16:48:27 -05:00
Aaron Pham
9aa6a18be2 fix(search): improve more general usability (closes #781) (#782)
* fix(search): improve more general usability

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: revert naming

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: correct check for enter event on no-match cases

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* Update quartz/components/scripts/search.inline.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* chore: remove unecessary class for tracking mouse

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-02-01 15:56:42 -05:00
dependabot[bot]
444e05ee21 chore(deps-dev): bump @types/hast from 3.0.3 to 3.0.4 (#780)
Bumps [@types/hast](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/hast) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/hast)

---
updated-dependencies:
- dependency-name: "@types/hast"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 18:35:29 -08:00
dependabot[bot]
1c175b2d09 chore(deps): bump mdast-util-to-hast from 13.0.2 to 13.1.0 (#776)
Bumps [mdast-util-to-hast](https://github.com/syntax-tree/mdast-util-to-hast) from 13.0.2 to 13.1.0.
- [Release notes](https://github.com/syntax-tree/mdast-util-to-hast/releases)
- [Commits](https://github.com/syntax-tree/mdast-util-to-hast/compare/13.0.2...13.1.0)

---
updated-dependencies:
- dependency-name: mdast-util-to-hast
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 15:27:30 -05:00
dependabot[bot]
7b2ce8b4a3 chore(deps): bump async-mutex from 0.4.0 to 0.4.1 (#777)
Bumps [async-mutex](https://github.com/DirtyHairy/async-mutex) from 0.4.0 to 0.4.1.
- [Changelog](https://github.com/DirtyHairy/async-mutex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/DirtyHairy/async-mutex/compare/v0.4.0...v0.4.1)

---
updated-dependencies:
- dependency-name: async-mutex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 15:26:57 -05:00
dependabot[bot]
f2e93c3314 chore(deps-dev): bump @types/node from 20.11.11 to 20.11.14 (#779)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.11 to 20.11.14.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 15:26:34 -05:00
Jacky Zhao
25e6869d38 deps: reduce dependabot frequency 2024-01-31 12:24:25 -08:00
Jacky Zhao
bfd877133b fix: regression in formatted callout titles 2024-01-31 12:09:04 -08:00
Aaron Pham
422986c98b fix(search): remove background with mouseEvent (#775)
* fix(search): remove background with mouseEvent

make sure when mouseenter we remove all existing background

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: update logics from suggestions

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* revert: class is evicted

* fix: address correct type

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-31 15:00:19 -05:00
Jacky Zhao
75d64eac91 fix: fmt 2024-01-31 11:58:54 -08:00
Jacky Zhao
355aa22318 docs: fix outdated comment on rebuild debounce behaviour 2024-01-31 11:52:10 -08:00
Jacky Zhao
7cb1c291c8 fix: allow formatting in callout titles 2024-01-31 11:41:27 -08:00
Jacky Zhao
22de92f6c4 pkg: bump to 4.1.6 2024-01-31 10:01:40 -08:00
Jacky Zhao
e1f12e6cb7 fix(style): search preview consistency 2024-01-31 09:55:23 -08:00
Aaron Pham
50bb1ffd8a feat(usability): update functions for search (#774)
* feat(usability): update functions for search

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* perf: slightly cleaner variables

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-31 09:38:42 -08:00
Aaron Pham
fee3ef9b3a chore(deps): bump katex to 0.16.9 (#772)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-31 09:25:16 -08:00
Aaron Pham
a29fadb046 feat(search): experimental telescope layout (closes #718) (#722)
* feat(search): telescope-style search

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore(search): cleanup some basis and borders

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix(search): make sure to set overflow-y

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* feat(search): shows preview on desktop only search

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* perf: add options to control layout through config

cache memoize results to avoid fetching

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: use the default configuration

* fix: correct minor type for search

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: use datasets to query for preview

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: layout changes

show preview on normal layout, and only show previous layout in list page.

* fix(type): annotate search with types

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: apply jacky's suggestion

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* chore: using map API and scss

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: styling on search container view on phones

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* Update quartz.layout.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-31 01:16:14 -08:00
Jacky Zhao
4e5643fb49 fix: properly parse tags in body 2024-01-30 23:51:21 -08:00
LUCASTUCIOUS
072ee64127 feat: Feature/custom callout icon (#727)
* Add icons as masks

To handle a simple way to add custom icons, i made it pure css. Icon are now a mask for the callout-icon div, so they always follow the --color form the current callout.

Now to add a custom icon, you simply add

```css
.callout {
  &[data-callout="custom"] {
    --color: #customcolor;
    --border: #custombordercolor;
    --bg: #custombg;
    --callout-icon: url('data:image/svg+xml; utf8, <custom formatted svg>');

  }
```

to custom.scss

* remove now unused code

* Make callouts an enum

* docs: update instructions for custom callouts

* Prettier & run format

* dynamic matching

For maintainability, make dynamic mathching. If we or Obsidian want to support more callouts, we simply add it to the enum

* callout mapping const

Getting ride of the enum entierly as it's not worth here?

* fix callout icon styling

* Add forgotten icons

* Rebase

* harmonize callout icon and fold icon

* fix docs + prettier

* Update docs/features/callouts.md

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* Update quartz/plugins/transformers/ofm.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* Suggestions fix

* remove unecessary rules

* comment is always nice

* Update docs/features/callouts.md

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-30 22:10:13 -08:00
dependabot[bot]
90043cd582 chore(deps): bump lightningcss from 1.22.1 to 1.23.0 (#765)
Bumps [lightningcss](https://github.com/parcel-bundler/lightningcss) from 1.22.1 to 1.23.0.
- [Release notes](https://github.com/parcel-bundler/lightningcss/releases)
- [Commits](https://github.com/parcel-bundler/lightningcss/compare/v1.22.1...v1.23.0)

---
updated-dependencies:
- dependency-name: lightningcss
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 22:05:17 -08:00
dependabot[bot]
e21d50c711 chore(deps): bump @floating-ui/dom from 1.5.3 to 1.6.1 (#766)
Bumps [@floating-ui/dom](https://github.com/floating-ui/floating-ui/tree/HEAD/packages/dom) from 1.5.3 to 1.6.1.
- [Release notes](https://github.com/floating-ui/floating-ui/releases)
- [Changelog](https://github.com/floating-ui/floating-ui/blob/master/packages/dom/CHANGELOG.md)
- [Commits](https://github.com/floating-ui/floating-ui/commits/@floating-ui/dom@1.6.1/packages/dom)

---
updated-dependencies:
- dependency-name: "@floating-ui/dom"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 22:04:53 -08:00
dependabot[bot]
f3c7211bf0 chore(deps-dev): bump @types/node from 20.3.3 to 20.11.11 (#767)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.3.3 to 20.11.11.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 22:03:54 -08:00
dependabot[bot]
ead7ee2f50 chore(deps-dev): bump prettier from 3.1.1 to 3.2.4 (#768)
* chore(deps-dev): bump prettier from 3.1.1 to 3.2.4

Bumps [prettier](https://github.com/prettier/prettier) from 3.1.1 to 3.2.4.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.1.1...3.2.4)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* format

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-30 22:03:33 -08:00
1900
6ba138b4fa feat: support selfhost umami (#764)
* feat: support selfhsot umami

* Update quartz/plugins/emitters/componentResources.ts

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>

* Update quartz/plugins/emitters/componentResources.ts

Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
Co-authored-by: Aaron Pham <29749331+aarnphm@users.noreply.github.com>
2024-01-30 09:58:09 -08:00
Justin Fowler
6ce754bda2 fix(css): improve wrapping when right sidebar has more than two items (#762)
* improve wrapping when right sidebar has more than two items, particularly on mobile

* Adjusted min-width
2024-01-29 21:56:59 -08:00
Aaron Pham
8df74185e9 fix(type): annotate event for nav (#761)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-29 21:55:10 -08:00
Aaron Pham
37c6231e79 fix(div): update class name to remove weird space afterwards (#763)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-29 21:51:13 -08:00
Aaron Pham
9555407f65 fix(type): make sure dispatchEvent also accept UIEvent (#760)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-29 16:26:47 -08:00
dependabot[bot]
fbb4d7e399 chore(deps): bump workerpool from 8.0.0 to 9.1.0 (#757)
* chore(deps): bump workerpool from 8.0.0 to 9.1.0

Bumps [workerpool](https://github.com/josdejong/workerpool) from 8.0.0 to 9.1.0.
- [Changelog](https://github.com/josdejong/workerpool/blob/master/HISTORY.md)
- [Commits](https://github.com/josdejong/workerpool/compare/v8.0.0...v9.1.0)

---
updated-dependencies:
- dependency-name: workerpool
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* remove @types/workerpool

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-29 12:45:37 -08:00
dependabot[bot]
5f624edb38 chore(deps): bump remark-rehype from 11.0.0 to 11.1.0 (#758)
Bumps [remark-rehype](https://github.com/remarkjs/remark-rehype) from 11.0.0 to 11.1.0.
- [Release notes](https://github.com/remarkjs/remark-rehype/releases)
- [Commits](https://github.com/remarkjs/remark-rehype/compare/11.0.0...11.1.0)

---
updated-dependencies:
- dependency-name: remark-rehype
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 12:41:51 -08:00
dependabot[bot]
b8ddf53aa8 chore(deps): bump rfdc from 1.3.0 to 1.3.1 (#759)
Bumps [rfdc](https://github.com/davidmarkclements/rfdc) from 1.3.0 to 1.3.1.
- [Commits](https://github.com/davidmarkclements/rfdc/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: rfdc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 12:41:33 -08:00
dependabot[bot]
b85a3543f4 chore(deps): bump @napi-rs/simple-git from 0.1.11 to 0.1.14 (#756)
Bumps [@napi-rs/simple-git](https://github.com/Brooooooklyn/simple-git) from 0.1.11 to 0.1.14.
- [Release notes](https://github.com/Brooooooklyn/simple-git/releases)
- [Commits](https://github.com/Brooooooklyn/simple-git/compare/v0.1.11...v0.1.14)

---
updated-dependencies:
- dependency-name: "@napi-rs/simple-git"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 12:40:55 -08:00
Jacky Zhao
ebf429a9c6 fix: fmt 2024-01-29 09:38:14 -08:00
Jacky Zhao
2d727443b3 fix: implement regex fix for alt in image wikilinks (closes #753) 2024-01-29 09:36:36 -08:00
Jacky Zhao
76be137283 fix: attempt to merge cached folder state between builds (closes #691) 2024-01-29 00:56:20 -08:00
Aaron Pham
f68872c09f feat(icon): update content for gfm links (#751)
* feat(icon): update content for gfm links

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: remove unused var

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: inherit display to remove additional spacing

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* revert: remove redundant svg attribute

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-28 23:38:59 -08:00
Mara-Li
b7152f743b feat: div that encapsulate PageList component (#750)
* feat: div that encapsulate PageList component

* change class to follow review

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* apply page-listing div to TagContent

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-28 22:52:04 -08:00
Mara-Li
603c181ad2 feat: allow to config a translation for date (#739)
* fix: alt error mix with height/width

More granular detection of alt and resize in image

* fix: format

* feat: allow to translate the date displayed

* style: format

* fix: rename to fusion dateLocale with locale (i18n support)

* Update quartz/components/PageList.tsx

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* remove default key as it was already set

* add docstring for locale

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-28 22:13:59 -08:00
Mara-Li
16adbd3011 fix: cssclasses was not applied on folder note (index) (#749)
* docs: improve first-time git setup

* fix: cssClasses was not applied on index page

* refactor: remove vscode files

* fix: format

* fix: cssClasses should be applied on the entire div, not only the article

* feat: support cssClasses for tag-listing

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-28 22:12:48 -08:00
Jacky Zhao
b014d060f3 fix: content-disposition inline should apply to all resource types (closes #728) 2024-01-28 22:12:01 -08:00
Jacky Zhao
85f05ea99b fix: revert parsing dates in frontmatter 2024-01-28 21:27:16 -08:00
Jacky Zhao
bf5a556cc1 docs: improve first-time git setup 2024-01-28 00:20:08 -08:00
Jacky Zhao
c4b756c817 style: remove redundant webkit prefix 2024-01-27 23:13:17 -08:00
Jacky Zhao
211f95c527 fix: allow alt to be defined in wikilinks alongside dims 2024-01-27 22:49:57 -08:00
Jacky Zhao
ba40516c54 fix: fmt 2024-01-27 22:24:13 -08:00
LUCASTUCIOUS
a70078ccdc feat: Option to mask folder count (#734)
* Option to mask folder count

* Update quartz/components/pages/FolderContent.tsx

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-27 22:21:32 -08:00
Jacky Zhao
2b62e29282 fix: revert bad tsconfig change 2024-01-27 22:19:37 -08:00
Jacky Zhao
efdce070e1 deps: bump flexsearch 2024-01-27 22:15:25 -08:00
dependabot[bot]
2739457c86 chore(deps): bump shikiji from 0.9.9 to 0.10.2 (#742)
Bumps [shikiji](https://github.com/antfu/shikiji/tree/HEAD/packages/shikiji) from 0.9.9 to 0.10.2.
- [Release notes](https://github.com/antfu/shikiji/releases)
- [Commits](https://github.com/antfu/shikiji/commits/v0.10.2/packages/shikiji)

---
updated-dependencies:
- dependency-name: shikiji
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 22:01:43 -08:00
dependabot[bot]
7695df69e5 chore(deps): bump rehype-mathjax from 5.0.0 to 6.0.0 (#745)
Bumps [rehype-mathjax](https://github.com/remarkjs/remark-math) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/remarkjs/remark-math/releases)
- [Commits](https://github.com/remarkjs/remark-math/compare/rehype-mathjax@5.0.0...rehype-mathjax@6.0.0)

---
updated-dependencies:
- dependency-name: rehype-mathjax
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 22:00:38 -08:00
dependabot[bot]
319dec4245 chore(deps): bump @napi-rs/simple-git from 0.1.9 to 0.1.11 (#746)
Bumps [@napi-rs/simple-git](https://github.com/Brooooooklyn/simple-git) from 0.1.9 to 0.1.11.
- [Release notes](https://github.com/Brooooooklyn/simple-git/releases)
- [Commits](https://github.com/Brooooooklyn/simple-git/compare/v0.1.9...v0.1.11)

---
updated-dependencies:
- dependency-name: "@napi-rs/simple-git"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 21:59:02 -08:00
dependabot[bot]
bebd6320b7 chore(deps-dev): bump tsx from 4.6.2 to 4.7.0 (#743)
Bumps [tsx](https://github.com/privatenumber/tsx) from 4.6.2 to 4.7.0.
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/develop/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.6.2...v4.7.0)

---
updated-dependencies:
- dependency-name: tsx
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 21:56:51 -08:00
dependabot[bot]
0a2d746e38 chore(deps): bump rehype-pretty-code from 0.12.3 to 0.12.6 (#741)
Bumps [rehype-pretty-code](https://github.com/atomiks/rehype-pretty-code) from 0.12.3 to 0.12.6.
- [Release notes](https://github.com/atomiks/rehype-pretty-code/releases)
- [Commits](https://github.com/atomiks/rehype-pretty-code/compare/v0.12.3...v0.12.6)

---
updated-dependencies:
- dependency-name: rehype-pretty-code
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 21:47:04 -08:00
Jacky Zhao
b11fefbbbe feat: enable dependabot 2024-01-27 21:44:38 -08:00
Jacky Zhao
42ee069c1c fix: generalize frontmatter parsing and coercing 2024-01-27 21:39:16 -08:00
LUCASTUCIOUS
b211d49922 feat: Handling cssclasses properties in Quartz (#711)
* Add cssclasses to article

* Prettier

* Update quartz/components/pages/Content.tsx

* Update quartz/components/pages/Content.tsx

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-27 18:34:21 -08:00
Jacky Zhao
af3a4ff9cd docs: i can't type 2024-01-26 20:23:43 -08:00
Jacky Zhao
448ba008e0 docs: fix phrasing 2024-01-26 20:16:54 -08:00
Jacky Zhao
8fa1a1e7b9 fix: allow partial when specifiying layout for emitter plugins 2024-01-26 13:40:37 -08:00
Jacky Zhao
b87c6cd5c7 docs: add nicole van der hoeven's setup guide 2024-01-26 10:55:59 -08:00
Jacky Zhao
a8e1c4abc2 docs: rearrange showcase 2024-01-25 22:22:07 -08:00
Xinyang Yu
d90199c8db fix: code block overflow scroll (#729) 2024-01-25 09:56:26 -08:00
LUCASTUCIOUS
d5b40279bd feat: Enable custom callout (#724)
* Enable custom callout

make a callout custom defaulted to a note one.

* Add a comment

* remove comment from quartz/plugins/transformers/ofm.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* Update quartz/plugins/transformers/ofm.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-24 23:54:24 -08:00
Jacky Zhao
b22bcd17b4 fix: border-box result-card 2024-01-23 20:20:35 -08:00
Jacky Zhao
fa6c02d321 fix: make search result card block 2024-01-23 17:08:56 -08:00
Jacky Zhao
5fb203a6df fix(style): make a not inline-block 2024-01-23 17:08:56 -08:00
kabirgh
0a76707062 feat: Emit custom event when theme changes (#723)
* Emit custom event when theme changes

* Type themechange custom event

* Update darkmode docs
2024-01-23 14:52:41 -08:00
kabirgh
1ce12fc1fc cleanup: Move rebuild function outside startServing function (#715)
* Move rebuild function outside `startServing`

* Move toRebuild and toRemove inside rebuild func

* Revert "Move toRebuild and toRemove inside rebuild func"

This reverts commit 8c4dbb13c7.

* Rename func to rebuildFromEntrypoint
2024-01-23 10:55:37 -08:00
Aaron Pham
eb302c05b8 fix(search): update no results to be a (#721)
Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-23 10:53:28 -08:00
Jacky Zhao
c9ac2a7507 pkg: bump to 4.1.5 2024-01-22 10:56:58 -08:00
Jacky Zhao
7ca491bc1d fix: add polyfill for broken tabindex on mac 2024-01-22 10:55:15 -08:00
Jacky Zhao
4edd27d3f9 fix: font weight in search 2024-01-22 10:48:23 -08:00
Jacky Zhao
2c8d0f8ab6 fix: more robust ofm comment handling 2024-01-22 10:29:57 -08:00
Jacky Zhao
cd826fb477 fix: process comments at a text level rather than a markdown level 2024-01-22 10:03:59 -08:00
Jacky Zhao
273931d25c fix: breadcrumbs on non-folder pages 2024-01-21 21:14:16 -08:00
Aaron Pham
0403fa70aa fix(search): use anchor element (closes #698) (#717)
* fix(search): use anchor element

This addresses #698 to allow search title to include links for SPA

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* fix: formatter

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: move itemTile to `a`

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore: remove nested a title

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* chore(search): remove spaNavigate

since now searchResult is an `a` item

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-21 20:50:00 -08:00
Jacky Zhao
015b4f6a15 fix: remove quartz 3 references, update font style in popovers 2024-01-21 12:39:20 -08:00
Aaron Pham
4d338cec13 feat(ofm): add options to parse arrows (#713)
* feat(ofm): add options to parse arrows

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

* feat(ofm): add options to parse arrows

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>

---------

Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com>
2024-01-21 11:33:32 -08:00
LUCASTUCIOUS
c11395e7bc feat: Add an option to display or not reading time from notes (#707)
* add an option to display or not reading time from notes

* Prettier (?)

* Remove ContentMeta override from quartz.layout.ts

* Make it positive ! 🌞

* Update quartz/components/ContentMeta.tsx

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-20 13:18:35 -08:00
Jacky Zhao
1f2ea96ae0 fix: allow dashes and underscores in block references (closes #712) 2024-01-20 00:33:14 -08:00
kabirgh
ce3dd0923b refactor: move emit from callback to helper file function (#704)
* Change emit from callback to helpers file function

* Update docs, remove commented code, improve type sig
2024-01-18 10:56:14 -08:00
Jacky Zhao
af811d824f style: make internal link have less visual padding (closes #706) 2024-01-17 20:03:14 -08:00
Jacky Zhao
129e0c60a9 fix: remove extra console log 2024-01-17 09:46:01 -08:00
Jacky Zhao
d7d5d8253c fix: clean up ofm code for video parsing 2024-01-17 09:45:05 -08:00
Matthew Bailin
f6299da182 feat: add ofm option to transform <img> tags with video exts into <video> (closes #463) (#664)
* enableVideoEmbed plugin

* enableVideoEmbed plugin

* enableVideoEmbed plugin

* enableVideoEmbed plugin

* enableVideoEmbed plugin

* cleaned up index validation, regex, conditional, no autoplay

* Update quartz/plugins/transformers/ofm.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* Update quartz/plugins/transformers/ofm.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* Update quartz/plugins/transformers/ofm.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

* Update ofm.ts

* Update ofm.ts

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-17 09:32:02 -08:00
kabirgh
e17ff20244 fix: use joinSegments for contentIndex.json file path (#702) 2024-01-16 08:24:01 -08:00
sean
107d9b8dff fix: external link icon shouldn't be vertical aligned (#699) 2024-01-16 08:18:55 -08:00
sean
fa7d139ce5 feat: External link icons (#697) 2024-01-15 23:55:32 -08:00
Jacky Zhao
f31cabbbf9 fix: dont use default callout title if theres additional title children left (closes #693) 2024-01-15 12:37:56 -08:00
kabirgh
30640e3441 Revert "fix: rebuild errors on windows (#692)" (#695)
This reverts commit 8eec47c340.
2024-01-15 11:51:46 -08:00
kabirgh
8eec47c340 fix: rebuild errors on windows (#692) 2024-01-15 08:39:16 -08:00
Jacky Zhao
f36376503a fix: allow transcludes of notes with dots (closes #682) 2024-01-13 14:47:39 -08:00
Jacky Zhao
a40dbd55a4 fix: unbork search shortcut 2024-01-13 13:56:03 -08:00
Jacky Zhao
e70312320f feat: improve default layout 2024-01-13 09:47:56 -08:00
Jacky Zhao
4e82b0d8ce docs: add sidneys artist handbook to showcase 2024-01-13 09:37:24 -08:00
Jacky Zhao
783b9b219c fix: dont hijack handlers when search is not focused (closes #680) 2024-01-13 09:29:43 -08:00
Jacky Zhao
4014c4d6d6 fix: add another test for notes with dots 2024-01-13 09:27:00 -08:00
Jacky Zhao
6babb788ed fix: sluggify pound (closes #681) 2024-01-13 09:22:27 -08:00
ikorihn
0a8c38dc21 fix: small typos (#686) 2024-01-13 09:09:41 -08:00
ikorihn
52e6c03730 fix: broken RSS item's link, which were set to https:/${base}. (#687) 2024-01-13 09:08:21 -08:00
Jacky Zhao
1a8aedf5f5 docs: clarify git only sets modified 2024-01-07 15:39:38 -08:00
Aaron Pham
a4d6f701bf fix(showcase): markdown link (#673) 2024-01-07 11:47:53 -08:00
Aaron Pham
60017164ad chore: add my garden 😃 (#672) 2024-01-07 11:35:52 -08:00
Jacky Zhao
5ccc48a172 style: div -> li for explorer 2024-01-04 11:05:05 -08:00
Nate Silva
707124cbd6 fix: allow publish property to be a string (ExplicitPublish) (#667)
* fix: allow publish property to be a string (ExplicitPublish)

Previously, the ExplicitPublish filter would publish if the `publish`
property was truthy.

The filter expects the `publish` property to be a boolean:

```
---
publish: true
---
```

However, Obsidian only shows the above if you are viewing a page in
“Source” mode.

If you are not in Source view, and you choose Three Dots Menu (...),
“Add file property”, you will get a string, not a boolean. It seems
likely that many users will do this and get:

```
publish: "true"
```

Notice that `"true"` is a string, not the boolean value `true`. If the
user changes this to `"false"`, the page will still be published:

```
publish: "false"
```

That is because the string value `"false"` is truthy.

This PR does the following:

- Allows the `publish` property to be either a boolean or a string.
- If it’s a string, it’s considered `true` if the string is `"true"`
  (not case-sensitive; it will also work if it is `"True"`, `"TRUE"`,
  etc.)
- Guarantees that the returned value from `shouldPublish` is a `boolean`
  -- previously it could be any truthy value even though it was cast to
  `boolean`

* style: use double-quotes everywhere

* style: format according to project style guide
2024-01-02 15:19:19 -08:00
jeff
88194ac348 feat: allow embedding youtube videos with the obsidian markdown syntax (#665)
* Add option to allow embedding YouTube videos with Obsidian Markdown syntax

* Update Obsidian compatability doc page

* Switch to converting YT links as an html plugin
2024-01-02 10:49:14 -08:00
Olivér Falvai
65d75b8bdc feat: support modification date reading from parent git repo (#661)
* feat: support modification date reading from parent git repo

* Print warning

* Fix formatting

* Update quartz/plugins/transformers/lastmod.ts

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

---------

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2024-01-02 09:23:28 -08:00
Mats Fangohr
6e34844114 feat: embed webp images (#666) 2024-01-02 08:03:05 -08:00
Jacky Zhao
b33f13ccaf fix: dont show last page if folder 2024-01-01 14:20:34 -08:00
Jimmy He
002bbc37b1 fix: Continue setup even if a file to delete is not found (#663)
* Continue setup even if a file to delete is not found

For various reasons, `.gitkeep` may be deleted already.

(In my case, even though I followed the [Getting Started](https://quartz.jzhao.xyz) instructions exactly, my first run resulted in an `fatal: 'upstream' does not appear to be a git repository`)

If we try to delete `.gitkeep` again and don't ignore `ENOENT`, then the whole setup fails.

* Use fs.existsSync
2024-01-01 14:14:37 -08:00
Jacky Zhao
e603d7396b fix: parse emoji tags in body (closes #659) 2024-01-01 08:58:25 -08:00
Jacky Zhao
40cfccdc77 style: relative back on pre 2023-12-28 15:07:59 -08:00
Jacky Zhao
e758cbe1ee pkg: bump version to 4.1.4 2023-12-28 14:00:15 -08:00
Jacky Zhao
4b6c7aeffe feat: lazyLoading specifier in link transformer 2023-12-28 13:56:20 -08:00
Jacky Zhao
e277ed5c30 fix: use joinSegment instead of joining via slash in sitemap (closes #658) 2023-12-28 08:54:09 -08:00
Olivér Falvai
68f53352e7 feat: Self-hosted Plausible support (#656)
* Self-hosted Plausible support

* Remove leftover import
2023-12-28 08:49:35 -08:00
Jacky Zhao
359484c139 fix: more robust tags parsing 2023-12-28 08:48:14 -08:00
Jacky Zhao
dafc9f318e feat: minify js scripts (closes #655) (#657) 2023-12-28 08:02:04 -08:00
Sidney
e1b6a0014c docs: add explorer example for advanced sortFn (#564)
* Added doc example to explorer sortFn

* Prettier fixed formatting

* Let Prettier fix the formatting of the entire markdown file

* Updated example

* Added extra commentary and fixed example

* Update docs/features/explorer.md

* doc fixes

* docs: remove leftover TODO

* docs: move example to `advanced`

---------

Co-authored-by: Sidney <85735034+Epicrex@users.noreply.github.com>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
Co-authored-by: Ben Schlegel <ben5.schlegel@gmail.com>
2023-12-28 12:04:15 +01:00
Hydrophobefireman
233d4b2f2c fix: fix invalid html output (#642)
* fix: fix invalid html output

* fix: HTML structure w/ nested <li>
2023-12-28 11:20:07 +01:00
Jacky Zhao
504b447162 fix: use slugs instead of title as basis for explorer (#652)
* use slugs instead of title as basis for explorer

* fix folder persist state, better default behaviour

* use relative path instead of full path as full path is affected by -d

* dont use title in breadcrumb if it's just index lol
2023-12-27 16:44:14 -08:00
Jacky Zhao
63bf1e14b5 style: remove relative from base pre 2023-12-20 19:55:28 -08:00
migueltorrescosta
be76da9e95 docs: Add CollapsedWave to showcase.md (#643)
Thank you so much for a beautiful setup
2023-12-20 12:09:48 -08:00
Jacky Zhao
8fe37cc5e5 docs: update issue template 2023-12-20 10:05:00 -08:00
Jacky Zhao
2e9896c893 fix: deep clone before relativizing urls in transclude (closes #640) 2023-12-20 09:52:17 -08:00
Jacky Zhao
7bcf27241f fix: latex before syntax highlighting 2023-12-19 19:03:40 -08:00
Jacky Zhao
b44a79eeba fix: wikilinks should allow external links (closes #639) 2023-12-19 11:40:59 -08:00
Jacky Zhao
9b9d86474b fix: mermaid rendering fix from upstream 2023-12-19 11:01:55 -08:00
Jacky Zhao
4c83251f8e feat: -v flag should log exact error on parse failure 2023-12-19 09:07:52 -08:00
Jacky Zhao
984ab1c578 fix: change backtick to regular after making script loading less hacky 2023-12-18 23:13:37 -08:00
Jacky Zhao
443cd53a1a fix: mermaid rendering broken after rehype-pretty-code bump (closes #638) 2023-12-18 23:09:49 -08:00
Jacky Zhao
5152d32fbd pkg: bump version to 4.1.3 2023-12-18 09:50:14 -08:00
Jacky Zhao
ea6208c1f0 deps: bump everything (closes #635) (#636)
* deps: bump ws

* deps: bump lightningcss

* deps: workerpool

* deps: various types

* deps: chalk

* deps: globby

* deps: preact

* deps: tsx

* deps: @floating-ui/dom

* deps: esbuild

* deps: types + prettier

* deps: rimraf, typescript

* deps: remark/rehype/unified ecosystem

* format
2023-12-18 09:48:40 -08:00
Jacky Zhao
78b33fc2fb fix: release build lock before client refresh 2023-12-17 16:46:17 -08:00
Jacky Zhao
d2be097b76 feat: include tag hierarchies in tag listing, sort tag listing 2023-12-17 15:09:51 -08:00
Jacky Zhao
ad1f964a5f docs: graph view tag options 2023-12-17 13:19:03 -08:00
Jacky Zhao
150050f379 docs: agentic computing in quartz philosophy 2023-12-17 13:01:44 -08:00
Jacky Zhao
d979331dc7 fix: remove whitespace unicode from tag regex 2023-12-17 12:54:52 -08:00
Jacky Zhao
972cf0a887 feat: support emoji tags (closes #634) 2023-12-17 12:28:28 -08:00
Jacky Zhao
14e6b13ff1 docs: dont pull on first sync 2023-12-17 09:57:46 -08:00
Jacky Zhao
3c01b92cc4 docs: note embeds and update git hint 2023-12-16 11:04:18 -08:00
Jacky Zhao
ed9bd43d9f docs: update showcase 2023-12-15 12:18:29 -08:00
Jacky Zhao
c35818c336 fix: set upstream in sync handler, cleanup docs around setting up github 2023-12-14 16:48:09 -08:00
Jacky Zhao
a464ae5029 fix: format 2023-12-13 16:47:22 -08:00
Jacky Zhao
66e297c0ea css: make article no longer relative to prevent z-fighting 2023-12-13 16:40:24 -08:00
Jacky Zhao
4442847b37 fix: internal link selector specificity 2023-12-13 16:07:44 -08:00
Jacky Zhao
e6b5ca33c9 re-add gitkeep to content 2023-12-11 15:34:21 -08:00
Jacky Zhao
1b92440009 fix: better error handling on spawnsync failures 2023-12-11 10:38:55 -08:00
Jacky Zhao
c6546903f2 fix: reland string coercion in title 2023-12-10 06:19:29 -08:00
Jacky Zhao
2c69b0c97d fix: frontmatter coercion (empty string is falsy) 2023-12-08 16:55:40 -08:00
Sam Stokes
a7e20804f5 feat: Support space-delimited tags in FrontMatter transformer (#620) 2023-12-04 18:18:47 -08:00
Jacky Zhao
5196f3b9db docs: github setup and hosting fixes 2023-12-03 23:25:40 -08:00
Jimin Kim
f0ec6c9b92 fix: tag index page (#616) 2023-12-03 14:56:30 -08:00
Jacky Zhao
9c88d5967f fix: don't show popovers on heading anchors 2023-12-03 09:22:16 -08:00
Jacky Zhao
0d8c025d6a deps: version bump 2023-12-02 17:00:06 -08:00
Jacky Zhao
54b4a5567c fix: fmt 2023-12-02 16:55:38 -08:00
Jacky Zhao
610b04406f fix: incorrect test 2023-12-02 16:54:09 -08:00
Jacky Zhao
82bd08d14a fix: transcludes and relative paths 2023-12-02 16:51:03 -08:00
mancuoj
649090de1b docs: add deploy with netlify (#613) 2023-12-01 22:59:02 -08:00
Jacky Zhao
b5fec6c87f feat: allow popovers on intrapage links (closes #243) 2023-12-01 09:00:47 -08:00
Jacky Zhao
0d314db1f8 fix(style): overflow on toc 2023-11-29 10:50:47 -08:00
Odaimoko
660aae62e0 docs: add Imk&Cc's homepage to showcase.md (#595)
* add Imk&Cc's homepage to showcase.md

* Update showcase.md

* Update showcase.md
2023-11-27 23:05:18 -08:00
Rune Antonsen
9a599aebea feat(breadcrumbs): add option to hide current page (#601)
* feat(breadcrumbs): add option to hide current page

* Remove debug lines

Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>

---------

Co-authored-by: ruant <ruant@ruant.net>
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2023-11-20 08:28:16 -08:00
Jacky Zhao
296c1cf83f fix: spa shouldn't use popover script directly 2023-11-18 18:46:58 -08:00
Jacky Zhao
516d9a27e7 fix: explicit undefined check in header transclude 2023-11-18 18:27:44 -08:00
Jacky Zhao
6a05fa777c fix: bad transform in wikilink pre-transform (closes #598) 2023-11-17 14:00:49 -08:00
Jacky Zhao
3f0be7fbe4 fix: check content-type before applying spa patch (closes #597) 2023-11-17 10:46:23 -08:00
Jacky Zhao
ea08c0511a fix: dont run explorer scripts on non-explorer pages (closes #596) 2023-11-17 10:29:24 -08:00
Matt Vogel
727b9b5d72 feat: add class alias to aliases (#585) 2023-11-17 10:23:39 -08:00
Zijing Zhang
50f0ba29a2 feat: cname emitter (#590)
* feat: cname emitter

* feat: impl cname.ts

* Update cname.ts

* Update index.ts

* Update cname.ts

* Update cname.ts

* Update cname.ts

* Update cname.ts
2023-11-16 15:31:20 -08:00
Jacky Zhao
95b1141b9d fix: include anchor when normalizing urls for spa/popovers 2023-11-15 20:35:45 -08:00
Jacky Zhao
a26eb59392 feat: scrub link formatting from toc entries 2023-11-15 20:13:28 -08:00
Jacky Zhao
5befcf4780 fix: format 2023-11-15 19:32:25 -08:00
Jacky Zhao
f861a7c160 fix: regression where clicking anchors on the same page wouldn't set the anchor in the url 2023-11-15 19:31:18 -08:00
Jacky Zhao
06426c8f7e feat: support repeated anchor tag (closes #592) 2023-11-15 19:27:54 -08:00
Jacky Zhao
8fc7b9f4c6 feat: deref symlinks when copying static assets (closes #588) 2023-11-15 09:43:30 -08:00
Jacky Zhao
2de48b267a fix: set htmlAst after walking tree in ofm (closes #589) 2023-11-14 20:01:48 -08:00
Jacky Zhao
76f2664277 versioning: bump to v4.1.1 2023-11-13 22:57:05 -08:00
Jacky Zhao
74777118a7 feat: header and full-page transcludes (closes #557) 2023-11-13 22:51:40 -08:00
Jacky Zhao
8223465bda fix: make :has img selector direct 2023-11-12 14:33:19 -08:00
Jacky Zhao
cf6ab9e933 feat: option to specify npx quartz sync message (closes #583) 2023-11-12 14:27:53 -08:00
Jacky Zhao
74c63e448e fix(style): dont internal-link highlight when image (closes #581) 2023-11-11 21:13:10 -08:00
Jacky Zhao
43d638a6de perf: compute mapping of folder name to file data for faster breadcrumbs 2023-11-11 21:06:37 -08:00
Jacky Zhao
d1551872ff fix: check if popover exists after fetching and before inserting 2023-11-11 20:46:57 -08:00
Jacky Zhao
275bea3051 style + cfg: resolve breadcrumb titles by default and change arrow character 2023-11-11 20:46:29 -08:00
Jacky Zhao
bc02791734 fix: .date.getTime() based sort 2023-11-11 20:28:26 -08:00
Jacky Zhao
bf603c49c2 fix: sort rss feed by date 2023-11-11 12:08:54 -08:00
Jacky Zhao
f67356c3d2 lint: format 2023-11-11 12:02:34 -08:00
Jacky Zhao
5d666d1860 fix: normalize relative urls (closes #569) 2023-11-11 11:59:05 -08:00
Jacky Zhao
22b7cf135e types: cast in jsx.tsx to avoid @ts-ignore 2023-11-11 11:41:44 -08:00
Jacky Zhao
50a87d0d86 style: scrollable tables 2023-11-11 11:39:56 -08:00
Jacky Zhao
134b6ed582 fix: anchors links shouldnt cause reload (closes #574) 2023-11-11 10:11:31 -08:00
Jacky Zhao
99e8f5944f fix: trailing slash aliases (closes #577) 2023-11-11 09:56:30 -08:00
Yes365
e9f4e28a2d fix: adapt vercel cleanurls (#487)
Co-authored-by: Harrison <Harrison@fanruan.com>
2023-11-09 19:44:16 -08:00
Niklas Schröder
2a6b9a9ea0 docs: fix property name for ToC toggle (#573) 2023-11-07 09:16:48 -08:00
Mau Camargo
e806c30fa1 docs: Add Mau Camargo's Notkesto to showcase (#570) 2023-11-05 11:30:10 -08:00
Anson Yu
aac7b7e97d docs: Update making plugins.md (#567)
:)
2023-11-04 14:20:16 -07:00
Jacky Zhao
101e9946bd feat: add collapseByDefault option to TableOfContents (closes #566) 2023-11-04 12:11:42 -07:00
Emil Rofors
a62a97c7ab 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>
2023-11-03 16:40:43 -07:00
170 changed files with 7488 additions and 3445 deletions

View File

@@ -20,12 +20,19 @@ Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
**Screenshots and Source**
If applicable, add screenshots to help explain your problem.
You can help speed up fixing the problem by either
1. providing a simple reproduction
2. linking to your Quartz repository where the problem can be observed
**Desktop (please complete the following information):**
- Device: [e.g. iPhone6]
- Quartz Version: [e.g. v4.1.2]
- `node` Version: [e.g. v18.16]
- `npm` version: [e.g. v10.1.0]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -46,8 +46,14 @@ jobs:
- name: Ensure Quartz builds, check bundle info
run: npx quartz build --bundleInfo
- name: Get package version
run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV
- name: Create release tag
uses: Klemensas/action-autotag@stable
uses: pkgdeps/git-tag-action@v2
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
tag_prefix: "v"
github_token: ${{ secrets.GITHUB_TOKEN }}
github_repo: ${{ github.repository }}
version: ${{ env.PACKAGE_VERSION }}
git_commit_sha: ${{ github.sha }}
git_tag_prefix: "v"

View File

@@ -5,8 +5,6 @@
Quartz is a set of tools that helps you publish your [digital garden](https://jzhao.xyz/posts/networked-thought) and notes as a website for free.
Quartz v4 features a from-the-ground rewrite focusing on end-user extensibility and ease-of-use.
**If you are looking for Quartz v3, you can find it on the [`hugo` branch](https://github.com/jackyzha0/quartz/tree/hugo).**
🔗 Read the documentation and get started: https://quartz.jzhao.xyz/
[Join the Discord Community](https://discord.gg/cRFFHYye7t)

View File

@@ -156,12 +156,13 @@ document.addEventListener("nav", () => {
// do page specific logic here
// e.g. attach event listeners
const toggleSwitch = document.querySelector("#switch") as HTMLInputElement
toggleSwitch.removeEventListener("change", switchTheme)
toggleSwitch.addEventListener("change", switchTheme)
window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme))
})
```
It is best practice to also unmount any existing event handlers to prevent memory leaks.
It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks.
This will get called on page navigation.
#### Importing Code

View File

@@ -53,12 +53,12 @@ All transformer plugins must define at least a `name` field to register the plug
Normally for both `remark` and `rehype`, you can find existing plugins that you can use to . If you'd like to create your own `remark` or `rehype` plugin, checkout the [guide to creating a plugin](https://unifiedjs.com/learn/guide/create-a-plugin/) using `unified` (the underlying AST parser and transformer library).
A good example of a transformer plugin that borrows from the `remark` and `rehype` ecosystems is the [[Latex]] plugin:
A good example of a transformer plugin that borrows from the `remark` and `rehype` ecosystems is the [[plugins/Latex|Latex]] plugin:
```ts title="quartz/plugins/transformers/latex.ts"
import remarkMath from "remark-math"
import rehypeKatex from "rehype-katex"
import rehypeMathjax from "rehype-mathjax/svg.js"
import rehypeMathjax from "rehype-mathjax/svg"
import { QuartzTransformerPlugin } from "../types"
interface Options {
@@ -84,10 +84,14 @@ export const Latex: QuartzTransformerPlugin<Options> = (opts?: Options) => {
externalResources() {
if (engine === "katex") {
return {
css: ["https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css"],
css: [
// base css
"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css",
],
js: [
{
src: "https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/contrib/copy-tex.min.js",
// fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md
src: "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/copy-tex.min.js",
loadTime: "afterDOMReady",
contentType: "external",
},
@@ -216,22 +220,19 @@ export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
export type QuartzEmitterPluginInstance = {
name: string
emit(
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
emitCallback: EmitCallback,
): Promise<FilePath[]>
emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise<FilePath[]>
getQuartzComponents(ctx: BuildCtx): QuartzComponent[]
}
```
An emitter plugin must define a `name` field an `emit` function and a `getQuartzComponents` function. `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created.
An emitter plugin must define a `name` field, an `emit` function, and a `getQuartzComponents` function. `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created.
Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `emitCallback` if you are creating files that contain text. The `emitCallback` function is the 4th argument of the emit function. It's interface looks something like this:
Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `quartz/plugins/emitters/helpers.ts` if you are creating files that contain text. `write` has the following signature:
```ts
export type EmitCallback = (data: {
export type WriteOptions = (data: {
// the build context
ctx: BuildCtx
// the name of the file to emit (not including the file extension)
slug: ServerSlug
// the file extension
@@ -281,7 +282,7 @@ export const ContentPage: QuartzEmitterPlugin = () => {
allFiles,
}
const content = renderPage(slug, componentData, opts, externalResources)
const content = renderPage(cfg, slug, componentData, opts, externalResources)
const fp = await emit({
content,
slug: file.data.slug!,

View File

@@ -2,7 +2,7 @@
title: Authoring Content
---
All of the content in your Quartz should go in the `/content` folder. The content for the home page of your Quartz lives in `content/index.md`. If you've [[index#🪴 Get Started|setup Quartz]] already, this folder should already be initailized. Any Markdown in this folder will get processed by Quartz.
All of the content in your Quartz should go in the `/content` folder. The content for the home page of your Quartz lives in `content/index.md`. If you've [[index#🪴 Get Started|setup Quartz]] already, this folder should already be initialized. Any Markdown in this folder will get processed by Quartz.
It is recommended that you use [Obsidian](https://obsidian.md/) as a way to edit and maintain your Quartz. It comes with a nice editor and graphical interface to preview, edit, and link your local files and attachments.
@@ -28,21 +28,17 @@ The rest of your content lives here. You can use **Markdown** here :)
Some common frontmatter fields that are natively supported by Quartz:
- `title`: Title of the page. If it isn't provided, Quartz will use the name of the file as the title.
- `description`: Description of the page used for link previews.
- `aliases`: Other names for this note. This is a list of strings.
- `tags`: Tags for this note.
- `draft`: Whether to publish the page or not. This is one way to make [[private pages|pages private]] in Quartz.
- `date`: A string representing the day the note was published. Normally uses `YYYY-MM-DD` format.
## Syncing your Content
When your Quartz is at a point you're happy with, you can save your changes to GitHub by doing `npx quartz sync`.
When your Quartz is at a point you're happy with, you can save your changes to GitHub.
First, make sure you've [[setting up your GitHub repository|already setup your GitHub repository]] and then do `npx quartz sync`.
> [!hint] Flags and options
> For full help options, you can run `npx quartz sync --help`.
>
> Most of these have sensible defaults but you can override them if you have a custom setup:
>
> - `-d` or `--directory`: the content folder. This is normally just `content`
> - `-v` or `--verbose`: print out extra logging information
> - `--commit` or `--no-commit`: whether to make a `git` commit for your changes
> - `--push` or `--no-push`: whether to push updates to your GitHub fork of Quartz
> - `--pull` or `--no-pull`: whether to try and pull in any updates from your GitHub fork (i.e. from other devices) before pushing
## Customization
Frontmatter parsing for `title`, `tags`, `aliases` and `cssclasses` is a functionality of the [[Frontmatter]] plugin, `date` is handled by the [[CreatedModifiedDate]] plugin and `description` by the [[Description]] plugin. See the plugin pages for customization options.

View File

@@ -25,14 +25,17 @@ This part of the configuration concerns anything that can affect the whole site.
- `enablePopovers`: whether to enable [[popover previews]] on your site.
- `analytics`: what to use for analytics on your site. Values can be
- `null`: don't use analytics;
- `{ provider: 'plausible' }`: use [Plausible](https://plausible.io/), a privacy-friendly alternative to Google Analytics; or
- `{ provider: 'google', tagId: <your-google-tag> }`: use Google Analytics
- `{ provider: 'google', tagId: '<your-google-tag>' }`: use Google Analytics;
- `{ provider: 'plausible' }` (managed) or `{ provider: 'plausible', host: '<your-plausible-host>' }` (self-hosted): use [Plausible](https://plausible.io/);
- `{ provider: 'umami', host: '<your-umami-host>', websiteId: '<your-umami-website-id>' }`: use [Umami](https://umami.is/);
- `locale`: used for [[i18n]] and date formatting
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`.
- Note that Quartz 4 will avoid using this as much as possible and use relative URLs whenever it can to make sure your site works no matter _where_ you end up actually deploying it.
- `ignorePatterns`: a list of [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) patterns that Quartz should ignore and not search through when looking for files inside the `content` folder. See [[private pages]] for more details.
- `defaultDateType`: whether to use created, modified, or published as the default date to display on pages and page listings.
- `theme`: configure how the site looks.
- `cdnCaching`: If `true` (default), use Google CDN to cache the fonts. This will generally will be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained.
- `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here.
- `header`: Font to use for headers
- `code`: Font for inline and block quotes.
@@ -53,7 +56,7 @@ You can think of Quartz plugins as a series of transformations over content.
![[quartz transform pipeline.png]]
```ts
```ts title="quartz.config.ts"
plugins: {
transformers: [...],
filters: [...],
@@ -61,22 +64,40 @@ plugins: {
}
```
- [[making plugins#Transformers|Transformers]] **map** over content (e.g. parsing frontmatter, generating a description)
- [[making plugins#Filters|Filters]] **filter** content (e.g. filtering out drafts)
- [[making plugins#Emitters|Emitters]] **reduce** over content (e.g. creating an RSS feed or pages that list all files with a specific tag)
- [[tags/plugin/transformer|Transformers]] **map** over content (e.g. parsing frontmatter, generating a description)
- [[tags/plugin/filter|Filters]] **filter** content (e.g. filtering out drafts)
- [[tags/plugin/emitter|Emitters]] **reduce** over content (e.g. creating an RSS feed or pages that list all files with a specific tag)
By adding, removing, and reordering plugins from the `tranformers`, `filters`, and `emitters` fields, you can customize the behaviour of Quartz.
You can customize the behaviour of Quartz by adding, removing and reordering plugins in the `transformers`, `filters` and `emitters` fields.
> [!note]
> Each node is modified by every transformer _in order_. Some transformers are position-sensitive so you may need to take special note of whether it needs come before or after any other particular plugins.
> Each node is modified by every transformer _in order_. Some transformers are position sensitive, so you may need to pay particular attention to whether they need to come before or after certain other plugins.
Additionally, plugins may also have their own configuration settings that you can pass in. For example, the [[Latex]] plugin allows you to pass in a field specifying the `renderEngine` to choose between Katex and MathJax.
You should take care to add the plugin to the right entry corresponding to its plugin type. For example, to add the [[ExplicitPublish]] plugin (a [[tags/plugin/transformer|Transformer]], you would add the following line:
```ts
```ts title="quartz.config.ts"
transformers: [
Plugin.FrontMatter(), // uses default options
Plugin.Latex({ renderEngine: "katex" }), // specify some options
...
Plugin.ExplicitPublish(),
...
],
```
To remove a plugin, you should remove all occurrences of it in the `quartz.config.ts`.
To customize plugins further, some plugins may also have their own configuration settings that you can pass in. If you do not pass in a configuration, the plugin will use its default settings.
For example, the [[plugins/Latex|Latex]] plugin allows you to pass in a field specifying the `renderEngine` to choose between Katex and MathJax.
```ts title="quartz.config.ts"
transformers: [
Plugin.FrontMatter(), // use default options
Plugin.Latex({ renderEngine: "katex" }), // set some custom options
]
```
If you'd like to make your own plugins, read the guide on [[making plugins]] for more information.
Some plugins are included by default in the[ `quartz.config.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz.config.ts), but there are more available.
You can see a list of all plugins and their configuration options [[tags/plugin|here]].
If you'd like to make your own plugins, see the [[making plugins|making custom plugins]] guide.

View File

@@ -1,6 +1,7 @@
---
title: LaTeX
tags:
- plugin/transformer
- feature/transformer
---
Quartz uses [Katex](https://katex.org/) by default to typeset both inline and block math expressions at build time.
@@ -38,6 +39,9 @@ a & b & c
\end{bmatrix}
$$
> [!warn]
> Due to limitations in the [underlying parsing library](https://github.com/remarkjs/remark-math), block math in Quartz requires the `$$` delimiters to be on newlines like above.
### Inline Math
Similarly, inline math can be rendered by delimiting math expression with a single `$`. For example, `$e^{i\pi} = -1$` produces $e^{i\pi} = -1$
@@ -53,11 +57,15 @@ For example:
- Incorrect: `I have $1 and you have $2` produces I have $1 and you have $2
- Correct: `I have \$1 and you have \$2` produces I have \$1 and you have \$2
## MathJax
### Using mhchem
In `quartz.config.ts`, you can configure Quartz to use [MathJax SVG rendering](https://docs.mathjax.org/en/latest/output/svg.html) by replacing `Plugin.Latex({ renderEngine: 'katex' })` with `Plugin.Latex({ renderEngine: 'mathjax' })`
Add the following import to the top of `quartz/plugins/transformers/latex.ts` (before all the other
imports):
```ts title="quartz/plugins/transformers/latex.ts"
import "katex/contrib/mhchem"
```
## Customization
- Removing Latex support: remove all instances of `Plugin.Latex()` from `quartz.config.ts`.
- Plugin: `quartz/plugins/transformers/latex.ts`
Latex parsing is a functionality of the [[plugins/Latex|Latex]] plugin. See the plugin page for customization options.

View File

@@ -1,9 +1,15 @@
---
title: "Mermaid Diagrams"
tags:
- feature/transformer
---
Quartz supports Mermaid which allows you to add diagrams and charts to your notes. Mermaid supports a range of diagrams, such as [flow charts](https://mermaid.js.org/syntax/flowchart.html), [sequence diagrams](https://mermaid.js.org/syntax/sequenceDiagram.html), and [timelines](https://mermaid.js.org/syntax/timeline.html). This is enabled as a part of [[Obsidian compatibility]] and can be configured and enabled/disabled from that plugin.
By default, Quartz will render Mermaid diagrams to match the site theme.
> [!warning]
> Wondering why Mermaid diagrams may not be showing up even if you have them enabled? You may need to reorder your plugins so that `Plugin.ObsidianFlavoredMarkdown()` is _after_ `Plugin.SyntaxHighlighting()`.
> Wondering why Mermaid diagrams may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]].
## Syntax

View File

@@ -1,31 +1,17 @@
---
title: "Obsidian Compatibility"
tags:
- plugin/transformer
- feature/transformer
---
Quartz was originally designed as a tool to publish Obsidian vaults as websites. Even as the scope of Quartz has widened over time, it hasn't lost the ability to seamlessly interoperate with Obsidian.
By default, Quartz ships with `Plugin.ObsidianFlavoredMarkdown` which is a transformer plugin that adds support for [Obsidian Flavored Markdown](https://help.obsidian.md/Editing+and+formatting/Obsidian+Flavored+Markdown). This includes support for features like [[wikilinks]] and [[Mermaid diagrams]].
By default, Quartz ships with the [[ObsidianFlavoredMarkdown]] plugin, which is a transformer plugin that adds support for [Obsidian Flavored Markdown](https://help.obsidian.md/Editing+and+formatting/Obsidian+Flavored+Markdown). This includes support for features like [[wikilinks]] and [[Mermaid diagrams]].
It also ships with support for [frontmatter parsing](https://help.obsidian.md/Editing+and+formatting/Properties) with the same fields that Obsidian uses through the `Plugin.FrontMatter` transformer plugin.
It also ships with support for [frontmatter parsing](https://help.obsidian.md/Editing+and+formatting/Properties) with the same fields that Obsidian uses through the [[Frontmatter]] transformer plugin.
Finally, Quartz also provides `Plugin.CrawlLinks` which allows you to customize Quartz's link resolution behaviour to match Obsidian.
Finally, Quartz also provides [[CrawlLinks]] plugin, which allows you to customize Quartz's link resolution behaviour to match Obsidian.
## Configuration
- Frontmatter parsing:
- Disabling: remove all instances of `Plugin.FrontMatter()` from `quartz.config.ts`.
- Customize default values for frontmatter: edit `quartz/plugins/transformers/frontmatter.ts`
- Obsidian Flavored Markdown:
- Disabling: remove all instances of `Plugin.ObsidianFlavoredMarkdown()` from `quartz.config.ts`
- Customizing features: `Plugin.ObsidianFlavoredMarkdown` has several other options to toggle on and off:
- `comments`: whether to enable `%%` style Obsidian comments. Defaults to `true`
- `highlight`: whether to enable `==` style highlights. Defaults to `true`
- `wikilinks`: whether to enable turning [[wikilinks]] into regular links. Defaults to `true`
- `callouts`: whether to enable [[callouts]]. Defaults to `true`
- `mermaid`: whether to enable [[Mermaid diagrams]]. Defaults to `true`
- `parseTags`: whether to try and parse tags in the content body. Defaults to `true`
- `enableInHtmlEmbed`: whether to try and parse Obsidian flavoured markdown in raw HTML. Defaults to `false`
- Link resolution behaviour:
- Disabling: remove all instances of `Plugin.CrawlLinks()` from `quartz.config.ts`
- Changing link resolution preference: set `markdownLinkResolution` to one of `absolute`, `relative` or `shortest`
This functionality is provided by the [[ObsidianFlavoredMarkdown]], [[Frontmatter]] and [[CrawlLinks]] plugins. See the plugin pages for customization options.

View File

@@ -1,11 +1,12 @@
---
title: "OxHugo Compatibility"
tags:
- plugin/transformer
- feature/transformer
---
[org-roam](https://www.orgroam.com/) is a plain-text personal knowledge management system for [emacs](https://en.wikipedia.org/wiki/Emacs). [ox-hugo](https://github.com/kaushalmodi/ox-hugo) is org exporter backend that exports `org-mode` files to [Hugo](https://gohugo.io/) compatible Markdown.
Because the Markdown generated by ox-hugo is not pure Markdown but Hugo specific, we need to transform it to fit into Quartz. This is done by `Plugin.OxHugoFlavouredMarkdown`. Even though this [[making plugins|plugin]] was written with `ox-hugo` in mind, it should work for any Hugo specific Markdown.
Because the Markdown generated by ox-hugo is not pure Markdown but Hugo specific, we need to transform it to fit into Quartz. This is done by the [[OxHugoFlavoredMarkdown]] plugin. Even though this plugin was written with `ox-hugo` in mind, it should work for any Hugo specific Markdown.
```typescript title="quartz.config.ts"
plugins: {
@@ -25,15 +26,4 @@ Quartz by default doesn't understand `org-roam` files as they aren't Markdown. Y
## Configuration
- Link resolution
- `wikilinks`: Whether to replace `{{ relref }}` with Quartz [[wikilinks]]
- `removePredefinedAnchor`: Whether to remove [pre-defined anchor set by ox-hugo](https://ox-hugo.scripter.co/doc/anchors/).
- Image handling
- `replaceFigureWithMdImg`: Whether to replace `<figure/>` with `![]()`
- Formatting
- `removeHugoShortcode`: Whether to remove hugo shortcode syntax (`{{}}`)
- `replaceOrgLatex`: Whether to replace org-mode formatting for latex fragments with what `Plugin.Latex` supports.
> [!warning]
>
> While you can use `Plugin.OxHugoFlavoredMarkdown` and `Plugin.ObsidianFlavoredMarkdown` together, it's not recommended because it might mutate the file in unexpected ways. Use with caution.
This functionality is provided by the [[OxHugoFlavoredMarkdown]] plugin. See the plugin page for customization options.

View File

@@ -1,7 +1,5 @@
Quartz creates an RSS feed for all the content on your site by generating an `index.xml` file that RSS readers can subscribe to. Because of the RSS spec, this requires the `baseUrl` property in your [[configuration]] to be set properly for RSS readers to pick it up properly.
Quartz emits an RSS feed for all the content on your site by generating an `index.xml` file that RSS readers can subscribe to. Because of the RSS spec, this requires the `baseUrl` property in your [[configuration]] to be set properly for RSS readers to pick it up properly.
## Configuration
- Remove RSS feed: set the `enableRSS` field of `Plugin.ContentIndex` in `quartz.config.ts` to be `false`.
- Change number of entries: set the `rssLimit` field of `Plugin.ContentIndex` to be the desired value. It defaults to latest 10 items.
- Use rich HTML output in RSS: set `rssFullHtml` field of `Plugin.ContentIndex` to be `true`.
This functionality is provided by the [[ContentIndex]] plugin. See the plugin page for customization options.

View File

@@ -16,10 +16,11 @@ For example, here's what the default configuration looks like:
```typescript title="quartz.layout.ts"
Component.Breadcrumbs({
spacerSymbol: ">", // symbol between crumbs
spacerSymbol: "", // symbol between crumbs
rootName: "Home", // name of first/root element
resolveFrontmatterTitle: false, // wether to resolve folder names through frontmatter titles (more computationally expensive)
hideOnRoot: true, // wether to hide breadcrumbs on root `index.md` page
resolveFrontmatterTitle: true, // whether to resolve folder names through frontmatter titles
hideOnRoot: true, // whether to hide breadcrumbs on root `index.md` page
showCurrentPage: true, // whether to display the current page in the breadcrumbs
})
```

View File

@@ -1,7 +1,7 @@
---
title: Callouts
tags:
- plugin/transformer
- feature/transformer
---
Quartz supports the same Admonition-callout syntax as Obsidian.
@@ -19,68 +19,78 @@ This includes
See [documentation on supported types and syntax here](https://help.obsidian.md/Editing+and+formatting/Callouts).
> [!warning]
> Wondering why callouts may not be showing up even if you have them enabled? You may need to reorder your plugins so that `Plugin.ObsidianFlavoredMarkdown()` is _after_ `Plugin.SyntaxHighlighting()`.
> Wondering why callouts may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]].
## Customization
- Disable callouts: simply pass `callouts: false` to the plugin: `Plugin.ObsidianFlavoredMarkdown({ callouts: false })`
- Editing icons: `quartz/plugins/transformers/ofm.ts`
The callouts are a functionality of the [[ObsidianFlavoredMarkdown]] plugin. See the plugin page for how to enable or disable them.
You can edit the icons by customizing `quartz/styles/callouts.scss`.
### Add custom callouts
By default, custom callouts are handled by applying the `note` style. To make fancy ones, you have to add these lines to `custom.scss`.
```scss title="quartz/styles/custom.scss"
.callout {
&[data-callout="custom"] {
--color: #customcolor;
--border: #custombordercolor;
--bg: #custombg;
--callout-icon: url("data:image/svg+xml; utf8, <custom formatted svg>"); //SVG icon code
}
}
```
> [!warning]
> Don't forget to ensure that the SVG is URL encoded before putting it in the CSS. You can use tools like [this one](https://yoksel.github.io/url-encoder/) to help you do that.
## Showcase
> [!info]
> Default title
> [!question]+ Can callouts be nested?
> [!question]+ Can callouts be _nested_?
>
> > [!todo]- Yes!, they can.
> > [!todo]- Yes!, they can. And collapsed!
> >
> > > [!example] You can even use multiple layers of nesting.
> [!EXAMPLE] Examples
>
> Aliases: example
> [!note]
> Aliases: "note"
> [!note] Notes
>
> Aliases: note
> [!abstract]
> Aliases: "abstract", "summary", "tldr"
> [!abstract] Summaries
>
> Aliases: abstract, summary, tldr
> [!info]
> Aliases: "info"
> [!info] Info
>
> Aliases: info, todo
> [!todo]
> Aliases: "todo"
> [!tip] Hint
>
> Aliases: tip, hint, important
> [!tip]
> Aliases: "tip", "hint", "important"
> [!success] Success
>
> Aliases: success, check, done
> [!success]
> Aliases: "success", "check", "done"
> [!question] Question
>
> Aliases: question, help, faq
> [!question]
> Aliases: "question", "help", "faq"
> [!warning] Warning
>
> Aliases: warning, caution, attention
> [!warning]
> Aliases: "warning", "attention", "caution"
> [!failure] Failure
>
> Aliases: failure, fail, missing
> [!failure]
> Aliases: "failure", "missing", "fail"
> [!danger] Error
>
> Aliases: danger, error
> [!danger]
> Aliases: "danger", "error"
> [!bug] Bug
>
> Aliases: bug
> [!bug]
> Aliases: "bug"
> [!quote] Quote
>
> Aliases: quote, cite
> [!example]
> Aliases: "example"
> [!quote]
> Aliases: "quote", "cite"

View File

@@ -12,3 +12,12 @@ Quartz supports darkmode out of the box that respects the user's theme preferenc
- Component: `quartz/components/Darkmode.tsx`
- Style: `quartz/components/styles/darkmode.scss`
- Script: `quartz/components/scripts/darkmode.inline.ts`
You can also listen to the `themechange` event to perform any custom logic when the theme changes.
```js
document.addEventListener("themechange", (e) => {
console.log("Theme changed to " + e.detail.theme) // either "light" or "dark"
// your logic here
})
```

View File

@@ -26,7 +26,7 @@ Component.Explorer({
title: "Explorer", // title of the explorer component
folderClickBehavior: "collapse", // what happens when you click a folder ("link" to navigate to folder page on click or "collapse" to collapse folder on click)
folderDefaultState: "collapsed", // default state of folders ("collapsed" or "open")
useSavedState: true, // wether to use local storage to save "state" (which folders are opened) of explorer
useSavedState: true, // whether to use local storage to save "state" (which folders are opened) of explorer
// Sort order: folders first, then files. Sort folders and files alphabetically
sortFn: (a, b) => {
... // default implementation shown later
@@ -42,7 +42,7 @@ When passing in your own options, you can omit any or all of these fields if you
Want to customize it even more?
- Removing table of contents: remove `Component.Explorer()` from `quartz.layout.ts`
- Removing explorer: remove `Component.Explorer()` from `quartz.layout.ts`
- (optional): After removing the explorer component, you can move the [[table of contents | Table of Contents]] component back to the `left` part of the layout
- Changing `sort`, `filter` and `map` behavior: explained in [[#Advanced customization]]
- Component:
@@ -179,6 +179,34 @@ Component.Explorer({
## Advanced examples
> [!tip]
> When writing more complicated functions, the `layout` file can start to look very cramped.
> You can fix this by defining your functions in another file.
>
> ```ts title="functions.ts"
> import { Options } from "./quartz/components/ExplorerNode"
> export const mapFn: Options["mapFn"] = (node) => {
> // implement your function here
> }
> export const filterFn: Options["filterFn"] = (node) => {
> // implement your function here
> }
> export const sortFn: Options["sortFn"] = (a, b) => {
> // implement your function here
> }
> ```
>
> You can then import them like this:
>
> ```ts title="quartz.layout.ts"
> import { mapFn, filterFn, sortFn } from "./functions.ts"
> Component.Explorer({
> mapFn: mapFn,
> filterFn: filterFn,
> sortFn: sortFn,
> })
> ```
### Add emoji prefix
To add emoji prefixes (📁 for folders, 📄 for files), you could use a map function like this:
@@ -216,30 +244,63 @@ Notice how we customized the `order` array here. This is done because the defaul
To fix this, we just changed around the order and apply the `sort` function before changing the display names in the `map` function.
> [!tip]
> When writing more complicated functions, the `layout` file can start to look very cramped.
> You can fix this by defining your functions in another file.
>
> ```ts title="functions.ts"
> import { Options } from "./quartz/components/ExplorerNode"
> export const mapFn: Options["mapFn"] = (node) => {
> // implement your function here
> }
> export const filterFn: Options["filterFn"] = (node) => {
> // implement your function here
> }
> export const sortFn: Options["sortFn"] = (a, b) => {
> // implement your function here
> }
> ```
>
> You can then import them like this:
>
> ```ts title="quartz.layout.ts"
> import { mapFn, filterFn, sortFn } from "./functions.ts"
> Component.Explorer({
> mapFn: mapFn,
> filterFn: filterFn,
> sortFn: sortFn,
> })
> ```
### Use `sort` with pre-defined sort order
Here's another example where a map containing file/folder names (as slugs) is used to define the sort order of the explorer in quartz. All files/folders that aren't listed inside of `nameOrderMap` will appear at the top of that folders hierarchy level.
It's also worth mentioning, that the smaller the number set in `nameOrderMap`, the higher up the entry will be in the explorer. Incrementing every folder/file by 100, makes ordering files in their folders a lot easier. Lastly, this example still allows you to use a `mapFn` or frontmatter titles to change display names, as it uses slugs for `nameOrderMap` (which is unaffected by display name changes).
```ts title="quartz.layout.ts"
Component.Explorer({
sortFn: (a, b) => {
const nameOrderMap: Record<string, number> = {
"poetry-folder": 100,
"essay-folder": 200,
"research-paper-file": 201,
"dinosaur-fossils-file": 300,
"other-folder": 400,
}
let orderA = 0
let orderB = 0
if (a.file && a.file.slug) {
orderA = nameOrderMap[a.file.slug] || 0
} else if (a.name) {
orderA = nameOrderMap[a.name] || 0
}
if (b.file && b.file.slug) {
orderB = nameOrderMap[b.file.slug] || 0
} else if (b.name) {
orderB = nameOrderMap[b.name] || 0
}
return orderA - orderB
},
})
```
For reference, this is how the quartz explorer window would look like with that example:
```
📖 Poetry Folder
📑 Essay Folder
⚗️ Research Paper File
🦴 Dinosaur Fossils File
🔮 Other Folder
```
And this is how the file structure would look like:
```
index.md
poetry-folder
index.md
essay-folder
index.md
research-paper-file.md
dinosaur-fossils-file.md
other-folder
index.md
```

View File

@@ -1,18 +1,20 @@
---
title: Folder and Tag Listings
tags:
- plugin/emitter
- feature/emitter
---
Quartz creates listing pages for any folders and tags you have.
Quartz emits listing pages for any folders and tags you have.
## Folder Listings
Quartz will generate an index page for all the pages under that folder. This includes any content that is multiple levels deep.
Additionally, Quartz will also generate pages for subfolders. Say you have a note in a nested folder `content/abc/def/note.md`. Then, Quartz would generate a page for all the notes under `abc` _and_ a page for all the notes under `abc/def`.
Additionally, Quartz will also generate pages for subfolders. Say you have a note in a nested folder `content/abc/def/note.md`. Then Quartz would generate a page for all the notes under `abc` _and_ a page for all the notes under `abc/def`.
By default, Quartz will title the page `Folder: <name of folder>` and no description. You can override this by creating an `index.md` file in the folder with the `title` [[authoring content#Syntax|frontmatter]] field. Any content you write in this file will also be used in the description of the folder.
You can link to the folder listing by referencing its name, plus a trailing slash, like this: `[[advanced/]]` (results in [[advanced/]]).
By default, Quartz will title the page `Folder: <folder name>` and no description. You can override this by creating an `index.md` file in the folder with the `title` [[authoring content#Syntax|frontmatter]] field. Any content you write in this file will also be used in the folder description.
For example, for the folder `content/posts`, you can add another file `content/posts/index.md` to add a specific description for it.
@@ -20,13 +22,12 @@ For example, for the folder `content/posts`, you can add another file `content/p
Quartz will also create an index page for each unique tag in your vault and render a list of all notes with that tag.
Quartz also supports tag hierarchies as well (e.g. `plugin/emitter`) and will also render a separate tag page for each layer of the tag hierarchy. It will also create a default global tag index page at `/tags` that displays a list of all the tags in your Quartz.
Quartz also supports tag hierarchies as well (e.g. `plugin/emitter`) and will also render a separate tag page for each level of the tag hierarchy. It will also create a default global tag index page at `/tags` that displays a list of all the tags in your Quartz.
Like folder listings, you can also provide a description and title for a tag page by creating a file for each tag. For example, if you wanted to create a custom description for the #component tag, you would create a file at `content/tags/component.md` with a title and description.
You can link to the tag listing by referencing its name with a `tag/` prefix, like this: `[[tags/plugin]]` (results in [[tags/plugin]]).
As with folder listings, you can also provide a description and title for a tag page by creating a file for each tag. For example, if you wanted to create a custom description for the #component tag, you would create a file at `content/tags/component.md` with a title and description.
## Customization
The layout for both the folder and content pages can be customized. By default, they use the `defaultListPageLayout` in `quartz.layouts.ts`. If you'd like to make more involved changes to the layout and don't mind editing some [[creating components|Quartz components]], you can take a look at `quartz/components/pages/FolderContent.tsx` and `quartz/components/pages/TagContent.tsx` respectively.
- Removing folder listings: remove `Plugin.FolderPage()` from `emitters` in `quartz.config.ts`
- Removing tag listings: remove `Plugin.TagPage()` from `emitters` in `quartz.config.ts`
The folder listings are a functionality of the [[FolderPage]] plugin, the tag listings of the [[TagPage]] plugin. See the plugin pages for customization options.

View File

@@ -34,6 +34,8 @@ Component.Graph({
linkDistance: 30, // how long should the links be by default?
fontSize: 0.6, // what size should the node labels be?
opacityScale: 1, // how quickly do we fade out the labels when zooming out?
removeTags: [], // what tags to remove from the graph
showTags: true, // whether to show tags in the graph
},
globalGraph: {
drag: true,
@@ -45,6 +47,8 @@ Component.Graph({
linkDistance: 30,
fontSize: 0.6,
opacityScale: 1,
removeTags: [], // what tags to remove from the graph
showTags: true, // whether to show tags in the graph
},
})
```

18
docs/features/i18n.md Normal file
View File

@@ -0,0 +1,18 @@
---
title: Internationalization
---
Internationalization allows users to translate text in the Quartz interface into various supported languages without needing to make extensive code changes. This can be changed via the `locale` [[configuration]] field in `quartz.config.ts`.
The locale field generally follows a certain format: `{language}-{REGION}`
- `{language}` is usually a [2-letter lowercase language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes).
- `{REGION}` is usually a [2-letter uppercase region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
> [!tip] Interested in contributing?
> We [gladly welcome translation PRs](https://github.com/jackyzha0/quartz/tree/v4/quartz/i18n/locales)! To contribute a translation, do the following things:
>
> 1. In the `quartz/i18n/locales` folder, copy the `en-US.ts` file.
> 2. Rename it to `{language}-{REGION}.ts` so it matches a locale of the format shown above.
> 3. Fill in the translations!
> 4. Add the entry under `TRANSLATIONS` in `quartz/i18n/index.ts`.

View File

@@ -8,6 +8,8 @@ By default, Quartz only fetches previews for pages inside your vault due to [COR
When [[creating components|creating your own components]], you can include this `popover-hint` class to also include it in the popover.
Similar to Obsidian, [[quartz layout.png|images referenced using wikilinks]] can also be viewed as popups.
## Configuration
- Remove popovers: set the `enablePopovers` field in `quartz.config.ts` to be `false`.

View File

@@ -1,16 +1,16 @@
---
title: Private Pages
tags:
- plugin/filter
- feature/filter
---
There may be some notes you want to avoid publishing as a website. Quartz supports this through two mechanisms which can be used in conjunction:
## Filter Plugins
[[making plugins#Filters|Filter plugins]] are plugins that filter out content based off of certain criteria. By default, Quartz uses the `Plugin.RemoveDrafts` plugin which filters out any note that has `draft: true` in the frontmatter.
[[making plugins#Filters|Filter plugins]] are plugins that filter out content based off of certain criteria. By default, Quartz uses the [[RemoveDrafts]] plugin which filters out any note that has `draft: true` in the frontmatter.
If you'd like to only publish a select number of notes, you can instead use `Plugin.ExplicitPublish` which will filter out all notes except for any that have `publish: true` in the frontmatter.
If you'd like to only publish a select number of notes, you can instead use [[ExplicitPublish]] which will filter out all notes except for any that have `publish: true` in the frontmatter.
> [!warning]
> Regardless of the filter plugin used, **all non-markdown files will be emitted and available publically in the final build.** This includes files such as images, voice recordings, PDFs, etc. One way to prevent this and still be able to embed local images is to create a folder specifically for public media and add the following two patterns to the ignorePatterns array.

View File

@@ -3,7 +3,7 @@ title: Recent Notes
tags: component
---
Quartz can generate a list of recent notes for based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Component.RecentNotes`.
Quartz can generate a list of recent notes based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Component.RecentNotes` in `quartz.layout.ts`.
## Customization

View File

@@ -1,7 +1,7 @@
---
title: Syntax Highlighting
tags:
- plugin/transformer
- feature/transformer
---
Syntax highlighting in Quartz is completely done at build-time. This means that Quartz only ships pre-calculated CSS to highlight the right words so there is no heavy client-side bundle that does the syntax highlighting.
@@ -130,6 +130,4 @@ const [name, setName] = useState('Taylor');
## Customization
- Removing syntax highlighting: delete all usages of `Plugin.SyntaxHighlighting()` from `quartz.config.ts`.
- Style: By default, Quartz uses derivatives of the GitHub light and dark themes. You can customize the colours in the `quartz/styles/syntax.scss` file.
- Plugin: `quartz/plugins/transformers/syntax.ts`
Syntax highlighting is a functionality of the [[SyntaxHighlighting]] plugin. See the plugin page for customization options.

View File

@@ -2,24 +2,17 @@
title: "Table of Contents"
tags:
- component
- plugin/transformer
- feature/transformer
---
Quartz can automatically generate a table of contents from a list of headings on each page. It will also show you your current scroll position on the site by marking headings you've scrolled through with a different colour.
Quartz can automatically generate a table of contents (TOC) from a list of headings on each page. It will also show you your current scrolling position on the page by highlighting headings you've scrolled through with a different color.
By default, it will show all headers from H1 (`# Title`) all the way to H3 (`### Title`) and will only show the table of contents if there is more than 1 header on the page.
You can also hide the table of contents on a page by adding `showToc: false` to the frontmatter for that page.
You can hide the TOC on a page by adding `enableToc: false` to the frontmatter for that page.
> [!info]
> This feature requires both `Plugin.TableOfContents` in your `quartz.config.ts` and `Component.TableOfContents` in your `quartz.layout.ts` to function correctly.
By default, the TOC shows all headings from H1 (`# Title`) to H3 (`### Title`) and is only displayed if there is more than one heading on the page.
## Customization
- Removing table of contents: remove all instances of `Plugin.TableOfContents()` from `quartz.config.ts`. and `Component.TableOfContents()` from `quartz.layout.ts`
- Changing the max depth: pass in a parameter to `Plugin.TableOfContents({ maxDepth: 4 })`
- Changing the minimum number of entries in the Table of Contents before it renders: pass in a parameter to `Plugin.TableOfContents({ minEntries: 3 })`
- Component: `quartz/components/TableOfContents.tsx`
- Style:
- Modern (default): `quartz/components/styles/toc.scss`
- Legacy Quartz 3 style: `quartz/components/styles/legacyToc.scss`
- Script: `quartz/components/scripts/toc.inline.ts`
The table of contents is a functionality of the [[TableOfContents]] plugin. See the plugin page for more customization options.
It also needs the `TableOfContents` component, which is displayed in the right sidebar by default. You can change this by customizing the [[layout]]. The TOC component can be configured with the `layout` parameter, which can either be `modern` (default) or `legacy`.

View File

@@ -4,7 +4,7 @@ title: Wikilinks
Wikilinks were pioneered by earlier internet wikis to make it easier to write links across pages without needing to write Markdown or HTML links each time.
Quartz supports Wikilinks by default and these links are resolved by Quartz using `Plugin.CrawlLinks`. See the [Obsidian Help page on Internal Links](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for more information on Wikilink syntax.
Quartz supports Wikilinks by default and these links are resolved by Quartz using the [[CrawlLinks]] plugin. See the [Obsidian Help page on Internal Links](https://help.obsidian.md/Linking+notes+and+files/Internal+links) for more information on Wikilink syntax.
This is enabled as a part of [[Obsidian compatibility]] and can be configured and enabled/disabled from that plugin.
@@ -14,3 +14,11 @@ This is enabled as a part of [[Obsidian compatibility]] and can be configured an
- `[[Path to file | Here's the title override]]`: produces a link to `Path to file.md` with the text `Here's the title override`
- `[[Path to file#Anchor]]`: produces a link to the anchor `Anchor` in the file `Path to file.md`
- `[[Path to file#^block-ref]]`: produces a link to the specific block `block-ref` in the file `Path to file.md`
### Embeds
- `![[Path to image]]`: embeds an image into the page
- `![[Path to image|100x145]]`: embeds an image into the page with dimensions 100px by 145px
- `![[Path to file]]`: transclude an entire page
- `![[Path to file#Anchor]]`: transclude everything under the header `Anchor`
- `![[Path to file#^b15695]]`: transclude block with ID `^b15695`

View File

@@ -4,7 +4,10 @@ title: Hosting
Quartz effectively turns your Markdown files and other resources into a bundle of HTML, JS, and CSS files (a website!).
However, if you'd like to publish your site to the world, you need a way to host it online. This guide will detail how to deploy with either GitHub Pages or Cloudflare pages but any service that allows you to deploy static HTML should work as well (e.g. Netlify, Replit, etc.)
However, if you'd like to publish your site to the world, you need a way to host it online. This guide will detail how to deploy with common hosting providers but any service that allows you to deploy static HTML should work as well.
> [!warning]
> The rest of this guide assumes that you've already created your own GitHub repository for Quartz. If you haven't already, [[setting up your GitHub repository|make sure you do so]].
> [!hint]
> Some Quartz features (like [[RSS Feed]] and sitemap generation) require `baseUrl` to be configured properly in your [[configuration]] to work properly. Make sure you set this before deploying!
@@ -26,12 +29,10 @@ Press "Save and deploy" and Cloudflare should have a deployed version of your si
To add a custom domain, check out [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/custom-domains/).
## GitHub Pages
Like Quartz 3, you can deploy the site generated by Quartz 4 via GitHub Pages.
> [!warning]
> Quartz generates files in the format of `file.html` instead of `file/index.html` which means the trailing slashes for _non-folder paths_ are dropped. As GitHub pages does not do this redirect, this may cause existing links to your site that use trailing slashes to break. If not breaking existing links is important to you, consider using [[#Cloudflare Pages]].
> Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz build`).
## GitHub Pages
In your local Quartz, create a new file `quartz/.github/workflows/deploy.yml`.
@@ -93,6 +94,9 @@ Then:
>
> You can do this by going to your Settings page on your GitHub fork and going to the Environments tab and pressing the trash icon. The GitHub action will recreate the environment for you correctly the next time you sync your Quartz.
> [!info]
> Quartz generates files in the format of `file.html` instead of `file/index.html` which means the trailing slashes for _non-folder paths_ are dropped. As GitHub pages does not do this redirect, this may cause existing links to your site that use trailing slashes to break. If not breaking existing links is important to you (e.g. you are migrating from Quartz 3), consider using [[#Cloudflare Pages]].
### Custom Domain
Here's how to add a custom domain to your GitHub pages deployment.
@@ -166,3 +170,83 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c
3. Go to the [Vercel Dashboard](https://vercel.com/dashboard) and select your Quartz project.
4. Go to the Settings tab and then click Domains in the sidebar
5. Enter your subdomain into the field and press Add
## Netlify
1. Log in to the [Netlify dashboard](https://app.netlify.com/) and click "Add new site".
2. Select your Git provider and repository containing your Quartz project.
3. Under "Build command", enter `npx quartz build`.
4. Under "Publish directory", enter `public`.
5. Press Deploy. Once it's live, you'll have a `*.netlify.app` URL to view the page.
6. To add a custom domain, check "Domain management" in the left sidebar, just like with Vercel.
## GitLab Pages
In your local Quartz, create a new file `.gitlab-ci.yaml`.
```yaml title=".gitlab-ci.yaml"
stages:
- build
- deploy
variables:
NODE_VERSION: "18.14"
build:
stage: build
rules:
- if: '$CI_COMMIT_REF_NAME == "v4"'
before_script:
- apt-get update -q && apt-get install -y nodejs npm
- npm install -g n
- n $NODE_VERSION
- hash -r
- npm ci
script:
- npx quartz build
artifacts:
paths:
- public
cache:
paths:
- ~/.npm/
key: "${CI_COMMIT_REF_SLUG}-node-${CI_COMMIT_REF_NAME}"
tags:
- docker
pages:
stage: deploy
rules:
- if: '$CI_COMMIT_REF_NAME == "v4"'
script:
- echo "Deploying to GitLab Pages..."
artifacts:
paths:
- public
```
When `.gitlab-ci.yaml` is committed, GitLab will build and deploy the website as a GitLab Page. You can find the url under `Deploy > Pages` in the sidebar.
By default, the page is private and only visible when logged in to a GitLab account with access to the repository but can be opened in the settings under `Deploy` -> `Pages`.
## Self-Hosting
Copy the `public` directory to your web server and configure it to serve the files. You can use any web server to host your site. Since Quartz generates links that do not include the `.html` extension, you need to let your web server know how to deal with it.
### Using Nginx
Here's an example of how to do this with Nginx:
```nginx title="nginx.conf"
server {
listen 80;
server_name example.com;
root /path/to/quartz/public;
index index.html;
error_page 404 /404.html;
location / {
try_files $uri $uri.html $uri/ =404;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -2,7 +2,7 @@
title: Welcome to Quartz 4
---
Quartz is a fast, batteries-included static-site generator that transforms Markdown content into fully functional websites. Thousands of students, developers, and teachers are [[showcase|already using Quartz]] to publish personal notes, wikis, and [digital gardens](https://jzhao.xyz/posts/networked-thought) to the web.
Quartz is a fast, batteries-included static-site generator that transforms Markdown content into fully functional websites. Thousands of students, developers, and teachers are [[showcase|already using Quartz]] to publish personal notes, websites, and [digital gardens](https://jzhao.xyz/posts/networked-thought) to the web.
## 🪴 Get Started
@@ -19,18 +19,19 @@ npx quartz create
This will guide you through initializing your Quartz with content. Once you've done so, see how to:
1. [[authoring content|Author content]] in Quartz
1. [[authoring content|Writing content]] in Quartz
2. [[configuration|Configure]] Quartz's behaviour
3. Change Quartz's [[layout]]
4. [[build|Build and preview]] Quartz
5. [[hosting|Host]] Quartz online
5. Sync your changes with [[setting up your GitHub repository|GitHub]]
6. [[hosting|Host]] Quartz online
> [!info]
> Coming from Quartz 3? See the [[migrating from Quartz 3|migration guide]] for the differences between Quartz 3 and Quartz 4 and how to migrate.
If you prefer instructions in a video format you can try following Nicole van der Hoeven's
[video guide on how to set up Quartz!](https://www.youtube.com/watch?v=6s6DT1yN4dw&t=227s)
## 🔧 Features
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], and [many more](./features) right out of the box
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]] and [many more](./features) right out of the box
- Hot-reload for both configuration and content
- Simple JSX layouts and [[creating components|page components]]
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes

View File

@@ -8,7 +8,9 @@ title: Philosophy of Quartz
>
> _(The Garden and the Stream)_
The problem with the file cabinet is that it focuses on efficiency of access and interoperability rather than generativity and creativity. Thinking is not linear, nor is it hierarchical. In fact, not many things are linear or hierarchical at all. Then why is it that most tools and thinking strategies assume a nice chronological or hierarchical order for my thought processes? The ideal tool for thought for me would embrace the messiness of my mind, and organically help insights emerge from chaos instead of forcing an artificial order. A rhizomatic, not arboresecent, form of note taking.
The problem with the file cabinet is that it focuses on efficiency of access and interoperability rather than generativity and creativity. Thinking is not linear, nor is it hierarchical. In fact, not many things are linear or hierarchical at all. Then why is it that most tools and thinking strategies assume a nice chronological or hierarchical order for my thought processes?
The ideal tool for thought for me would embrace the messiness of my mind, and organically help insights emerge from chaos instead of forcing an artificial order. A rhizomatic, not arboresecent, form of note taking.
My goal with a digital garden is not purely as an organizing system and information store (though it works nicely for that). I want my digital garden to be a playground for new ways ideas can connect together. As a result, existing formal organizing systems like Zettelkasten or the hierarchical folder structures of Notion dont work well for me. There is way too much upfront friction that by the time Ive thought about how to organize my thought into folders categories, Ive lost it.
@@ -25,4 +27,21 @@ Quartz is designed first and foremost as a tool for publishing [digital gardens]
> “[One] who works with the door open gets all kinds of interruptions, but [they] also occasionally gets clues as to what the world is and what might be important.”
> — Richard Hamming
**The goal of Quartz is to make sharing your digital garden free and simple.** At its core, Quartz is designed to be easy to use enough for non-technical people to get going but also powerful enough that senior developers can tweak it to work how they'd like it to work.
**The goal of Quartz is to make sharing your digital garden free and simple.**
---
## A garden should be your own
At its core, Quartz is designed to be easy to use enough for non-technical people to get going but also powerful enough that senior developers can tweak it to work how they'd like it to work.
1. If you like the default configuration of Quartz and just want to change the content, the only thing that you need to change is the contents of the `content` folder.
2. If you'd like to make basic configuration tweaks but don't want to edit source code, one can tweak the plugins and components in `quartz.config.ts` and `quartz.layout.ts` in a guided manner to their liking.
3. If you'd like to tweak the actual source code of the underlying plugins, components, or even build process, Quartz purposefully ships its full source code to the end user to allow customization at this level too.
Most software either confines you to either
1. Makes it easy to tweak content but not the presentation
2. Gives you too many knobs to tune the presentation without good opinionated defaults
**Quartz should feel powerful but ultimately be an intuitive tool fully within your control.** It should be a piece of [agentic software](https://jzhao.xyz/posts/agentic-computing). Ultimately, it should have the right affordances to nudge users towards good defaults but never dictate what the 'correct' way of using it is.

View File

@@ -0,0 +1,37 @@
---
title: AliasRedirects
tags:
- plugin/emitter
---
This plugin emits HTML redirect pages for aliases and permalinks defined in the frontmatter of content files.
For example, A `foo.md` has the following frontmatter
```md title="foo.md"
---
title: "Foo"
alias:
- "bar"
---
```
The target `host.me/bar` will be redirected to `host.me/foo`
Note that these are permanent redirect.
The emitter supports the following aliases:
- `aliases`
- `alias`
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Emitter
- Function name: `Plugin.AliasRedirects()`.
- Source: [`quartz/plugins/emitters/aliases.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/aliases.ts).

20
docs/plugins/Assets.md Normal file
View File

@@ -0,0 +1,20 @@
---
title: Assets
tags:
- plugin/emitter
---
This plugin emits all non-Markdown static assets in your content folder (like images, videos, HTML, etc). The plugin respects the `ignorePatterns` in the global [[configuration]].
Note that all static assets will then be accessible through its path on your generated site, i.e: `host.me/path/to/static.pdf`
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Emitter
- Function name: `Plugin.Assets()`.
- Source: [`quartz/plugins/emitters/assets.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/assets.ts).

22
docs/plugins/CNAME.md Normal file
View File

@@ -0,0 +1,22 @@
---
title: CNAME
tags:
- plugin/emitter
---
This plugin emits a `CNAME` record that points your subdomain to the default domain of your site.
If you want to use a custom domain name like `quartz.example.com` for the site, then this is needed.
See [[Hosting]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Emitter
- Function name: `Plugin.CNAME()`.
- Source: [`quartz/plugins/emitters/cname.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/cname.ts).

View File

@@ -0,0 +1,18 @@
---
title: ComponentResources
tags:
- plugin/emitter
---
This plugin manages and emits the static resources required for the Quartz framework. This includes CSS stylesheets and JavaScript scripts that enhance the functionality and aesthetics of the generated site. See also the `cdnCaching` option in the `theme` section of the [[configuration]].
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Emitter
- Function name: `Plugin.ComponentResources()`.
- Source: [`quartz/plugins/emitters/componentResources.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/componentResources.ts).

View File

@@ -0,0 +1,26 @@
---
title: ContentIndex
tags:
- plugin/emitter
---
This plugin emits both RSS and an XML sitemap for your site. The [[RSS Feed]] allows users to subscribe to content on your site and the sitemap allows search engines to better index your site. The plugin also emits a `contentIndex.json` file which is used by dynamic frontend components like search and graph.
This plugin emits a comprehensive index of the site's content, generating additional resources such as a sitemap, an RSS feed, and a
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `enableSiteMap`: If `true` (default), generates a sitemap XML file (`sitemap.xml`) listing all site URLs for search engines in content discovery.
- `enableRSS`: If `true` (default), produces an RSS feed (`index.xml`) with recent content updates.
- `rssLimit`: Defines the maximum number of entries to include in the RSS feed, helping to focus on the most recent or relevant content. Defaults to `10`.
- `rssFullHtml`: If `true`, the RSS feed includes full HTML content. Otherwise it includes just summaries.
- `includeEmptyFiles`: If `true` (default), content files with no body text are included in the generated index and resources.
## API
- Category: Emitter
- Function name: `Plugin.ContentIndex()`.
- Source: [`quartz/plugins/emitters/contentIndex.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/contentIndex.ts).

View File

@@ -0,0 +1,18 @@
---
title: ContentPage
tags:
- plugin/emitter
---
This plugin is a core component of the Quartz framework. It generates the HTML pages for each piece of Markdown content. It emits the full-page [[layout]], including headers, footers, and body content, among others.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Emitter
- Function name: `Plugin.ContentPage()`.
- Source: [`quartz/plugins/emitters/contentPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/contentPage.tsx).

View File

@@ -0,0 +1,30 @@
---
title: CrawlLinks
tags:
- plugin/transformer
---
This plugin parses links and processes them to point to the right places. It is also needed for embedded links (like images). See [[Obsidian compatibility]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `markdownLinkResolution`: Sets the strategy for resolving Markdown paths, can be `"absolute"` (default), `"relative"` or `"shortest"`. You should use the same setting here as in [[Obsidian compatibility|Obsidian]].
- `absolute`: Path relative to the root of the content folder.
- `relative`: Path relative to the file you are linking from.
- `shortest`: Name of the file. If this isn't enough to identify the file, use the full absolute path.
- `prettyLinks`: If `true` (default), simplifies links by removing folder paths, making them more user friendly (e.g. `folder/deeply/nested/note` becomes `note`).
- `openLinksInNewTab`: If `true`, configures external links to open in a new tab. Defaults to `false`.
- `lazyLoad`: If `true`, adds lazy loading to resource elements (`img`, `video`, etc.) to improve page load performance. Defaults to `false`.
- `externalLinkIcon`: Adds an icon next to external links when `true` (default) to visually distinguishing them from internal links.
> [!warning]
> Removing this plugin is _not_ recommended and will likely break the page.
## API
- Category: Transformer
- Function name: `Plugin.CrawlLinks()`.
- Source: [`quartz/plugins/transformers/links.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/links.ts).

View File

@@ -0,0 +1,25 @@
---
title: "CreatedModifiedDate"
tags:
- plugin/transformer
---
This plugin determines the created, modified, and published dates for a document using three potential data sources: frontmatter metadata, Git history, and the filesystem. See [[authoring content#Syntax]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `priority`: The data sources to consult for date information. Highest priority first. Possible values are `"frontmatter"`, `"git"`, and `"filesystem"`. Defaults to `"frontmatter", "git", "filesystem"]`.
> [!warning]
> If you rely on `git` for dates, make sure `defaultDateType` is set to `modified` in `quartz.config.ts`.
>
> Depending on how you [[hosting|host]] your Quartz, the `filesystem` dates of your local files may not match the final dates. In these cases, it may be better to use `git` or `frontmatter` to guarantee correct dates.
## API
- Category: Transformer
- Function name: `Plugin.CreatedModifiedDate()`.
- Source: [`quartz/plugins/transformers/lastmod.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/lastmod.ts).

View File

@@ -0,0 +1,22 @@
---
title: Description
tags:
- plugin/transformer
---
This plugin generates descriptions that are used as metadata for the HTML `head`, the [[RSS Feed]] and in [[folder and tag listings]] if there is no main body content, the description is used as the text between the title and the listing.
If the frontmatter contains a `description` property, it is used (see [[authoring content#Syntax]]). Otherwise, the plugin will do its best to use the first few sentences of the content to reach the target description length.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `descriptionLength`: the maximum length of the generated description. Default is 150 characters. The cut off happens after the first _sentence_ that ends after the given length.
## API
- Category: Transformer
- Function name: `Plugin.Description()`.
- Source: [`quartz/plugins/transformers/description.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/description.ts).

View File

@@ -0,0 +1,18 @@
---
title: ExplicitPublish
tags:
- plugin/filter
---
This plugin filters content based on an explicit `publish` flag in the frontmatter, allowing only content that is explicitly marked for publication to pass through. It's the opt-in version of [[RemoveDrafts]]. See [[private pages]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Emitter
- Function name: `Plugin.ExplicitPublish()`.
- Source: [`quartz/plugins/filters/explicit.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/filters/explicit.ts).

View File

@@ -0,0 +1,22 @@
---
title: FolderPage
tags:
- plugin/emitter
---
This plugin generates index pages for folders, creating a listing page for each folder that contains multiple content files. See [[folder and tag listings]] for more information.
Example: [[advanced/|Advanced]]
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts`. For the content, the `FolderContent` component is used. If you want to modify the layout, you must edit it directly (`quartz/components/pages/FolderContent.tsx`).
## API
- Category: Emitter
- Function name: `Plugin.FolderPage()`.
- Source: [`quartz/plugins/emitters/folderPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/folderPage.tsx).

View File

@@ -0,0 +1,24 @@
---
title: "Frontmatter"
tags:
- plugin/transformer
---
This plugin parses the frontmatter of the page using the [gray-matter](https://github.com/jonschlinkert/gray-matter) library. See [[authoring content#Syntax]], [[Obsidian compatibility]] and [[OxHugo compatibility]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `delimiters`: the delimiters to use for the frontmatter. Can have one value (e.g. `"---"`) or separate values for opening and closing delimiters (e.g. `["---", "~~~"]`). Defaults to `"---"`.
- `language`: the language to use for parsing the frontmatter. Can be `yaml` (default) or `toml`.
> [!warning]
> This plugin must not be removed, otherwise Quartz will break.
## API
- Category: Transformer
- Function name: `Plugin.Frontmatter()`.
- Source: [`quartz/plugins/transformers/frontmatter.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/frontmatter.ts).

View File

@@ -0,0 +1,23 @@
---
title: GitHubFlavoredMarkdown
tags:
- plugin/transformer
---
This plugin enhances Markdown processing to support GitHub Flavored Markdown (GFM) which adds features like autolink literals, footnotes, strikethrough, tables and tasklists.
In addition, this plugin adds optional features for typographic refinement (such as converting straight quotes to curly quotes, dashes to en-dashes/em-dashes, and ellipses) and automatic heading links as a symbol that appears next to the heading on hover.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `enableSmartyPants`: When true, enables typographic enhancements. Default is true.
- `linkHeadings`: When true, automatically adds links to headings. Default is true.
## API
- Category: Transformer
- Function name: `Plugin.GitHubFlavoredMarkdown()`.
- Source: [`quartz/plugins/transformers/gfm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/gfm.ts).

View File

@@ -0,0 +1,18 @@
---
title: HardLineBreaks
tags:
- plugin/transformer
---
This plugin automatically converts single line breaks in Markdown text into hard line breaks in the HTML output. This plugin is not enabled by default as this doesn't follow the semantics of actual Markdown but you may enable it if you'd like parity with [[Obsidian compatibility|Obsidian]].
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Transformer
- Function name: `Plugin.HardLineBreaks()`.
- Source: [`quartz/plugins/transformers/linebreaks.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/linebreaks.ts).

20
docs/plugins/Latex.md Normal file
View File

@@ -0,0 +1,20 @@
---
title: "Latex"
tags:
- plugin/transformer
---
This plugin adds LaTeX support to Quartz. See [[features/Latex|Latex]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `renderEngine`: the engine to use to render LaTeX equations. Can be `"katex"` for [KaTeX](https://katex.org/) or `"mathjax"` for [MathJax](https://www.mathjax.org/) [SVG rendering](https://docs.mathjax.org/en/latest/output/svg.html). Defaults to KaTeX.
## API
- Category: Transformer
- Function name: `Plugin.Latex()`.
- Source: [`quartz/plugins/transformers/latex.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/latex.ts).

View File

@@ -0,0 +1,18 @@
---
title: NotFoundPage
tags:
- plugin/emitter
---
This plugin emits a 404 (Not Found) page for broken or non-existent URLs.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Emitter
- Function name: `Plugin.NotFoundPage()`.
- Source: [`quartz/plugins/emitters/404.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/404.tsx).

View File

@@ -0,0 +1,34 @@
---
title: ObsidianFlavoredMarkdown
tags:
- plugin/transformer
---
This plugin provides support for [[Obsidian compatibility]].
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `comments`: If `true` (default), enables parsing of `%%` style Obsidian comment blocks.
- `highlight`: If `true` (default), enables parsing of `==` style highlights within content.
- `wikilinks`:If `true` (default), turns [[wikilinks]] into regular links.
- `callouts`: If `true` (default), adds support for [[callouts|callout]] blocks for emphasizing content.
- `mermaid`: If `true` (default), enables [[Mermaid diagrams|Mermaid diagram]] rendering within Markdown files.
- `parseTags`: If `true` (default), parses and links tags within the content.
- `parseArrows`: If `true` (default), transforms arrow symbols into their HTML character equivalents.
- `parseBlockReferences`: If `true` (default), handles block references, linking to specific content blocks.
- `enableInHtmlEmbed`: If `true`, allows embedding of content directly within HTML. Defaults to `false`.
- `enableYouTubeEmbed`: If `true` (default), enables the embedding of YouTube videos using external image Markdown syntax.
- `enableVideoEmbed`: If `true` (default), enables the embedding of video files.
- `enableCheckbox`: If `true`, adds support for interactive checkboxes in content. Defaults to `false`.
> [!warning]
> Don't remove this plugin if you're using [[Obsidian compatibility|Obsidian]] to author the content!
## API
- Category: Transformer
- Function name: `Plugin.ObsidianFlavoredMarkdown()`.
- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts).

View File

@@ -0,0 +1,29 @@
---
title: OxHugoFlavoredMarkdown
tags:
- plugin/transformer
---
This plugin provides support for [ox-hugo](https://github.com/kaushalmodi/ox-hugo) compatibility. See [[OxHugo compatibility]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `wikilinks`: If `true` (default), converts Hugo `{{ relref }}` shortcodes to Quartz [[wikilinks]].
- `removePredefinedAnchor`: If `true` (default), strips predefined anchors from headings.
- `removeHugoShortcode`: If `true` (default), removes Hugo shortcode syntax (`{{}}`) from the content.
- `replaceFigureWithMdImg`: If `true` (default), replaces `<figure/>` with `![]()`.
- `replaceOrgLatex`: If `true` (default), converts Org-mode [[features/Latex|Latex]] fragments to Quartz-compatible LaTeX wrapped in `$` (for inline) and `$$` (for block equations).
> [!warning]
> While you can use this together with [[ObsidianFlavoredMarkdown]], it's not recommended because it might mutate the file in unexpected ways. Use with caution.
>
> If you use `toml` frontmatter, make sure to configure the [[Frontmatter]] plugin accordingly. See [[OxHugo compatibility]] for an example.
## API
- Category: Transformer
- Function name: `Plugin.OxHugoFlavoredMarkdown()`.
- Source: [`quartz/plugins/transformers/oxhugofm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/oxhugofm.ts).

View File

@@ -0,0 +1,18 @@
---
title: RemoveDrafts
tags:
- plugin/filter
---
This plugin filters out content from your vault, so that only finalized content is made available. This prevents [[private pages]] from being published. By default, it filters out all pages with `draft: true` in the frontmatter and leaves all other pages intact.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Filter
- Function name: `Plugin.RemoveDrafts()`.
- Source: [`quartz/plugins/filters/draft.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/filters/draft.ts).

21
docs/plugins/Static.md Normal file
View File

@@ -0,0 +1,21 @@
---
title: Static
tags:
- plugin/emitter
---
This plugin emits all static resources needed by Quartz. This is used, for example, for fonts and images that need a stable position, such as banners and icons. The plugin respects the `ignorePatterns` in the global [[configuration]].
> [!important]
> This is different from [[Assets]]. The resources from the [[Static]] plugin are located under `quartz/static`, whereas [[Assets]] renders all static resources under `content` and is used for images, videos, audio, etc. that are directly referenced by your markdown content.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
## API
- Category: Emitter
- Function name: `Plugin.Static()`.
- Source: [`quartz/plugins/emitters/static.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/static.ts).

View File

@@ -0,0 +1,23 @@
---
title: "SyntaxHighlighting"
tags:
- plugin/transformer
---
This plugin is used to add syntax highlighting to code blocks in Quartz. See [[syntax highlighting]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `theme`: a separate id of one of the [themes bundled with Shikiji](https://shikiji.netlify.app/themes). One for light mode and one for dark mode. Defaults to `theme: { light: "github-light", dark: "github-dark" }`.
- `keepBackground`: If set to `true`, the background of the Shikiji theme will be used. With `false` (default) the Quartz theme color for background will be used instead.
In addition, you can further override the colours in the `quartz/styles/syntax.scss` file.
## API
- Category: Transformer
- Function name: `Plugin.SyntaxHighlighting()`.
- Source: [`quartz/plugins/transformers/syntax.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/syntax.ts).

View File

@@ -0,0 +1,26 @@
---
title: TableOfContents
tags:
- plugin/transformer
---
This plugin generates a table of contents (TOC) for Markdown documents. See [[table of contents]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin accepts the following configuration options:
- `maxDepth`: Limits the depth of headings included in the TOC, ranging from `1` (top level headings only) to `6` (all heading levels). Default is `3`.
- `minEntries`: The minimum number of heading entries required for the TOC to be displayed. Default is `1`.
- `showByDefault`: If `true` (default), the TOC should be displayed by default. Can be overridden by frontmatter settings.
- `collapseByDefault`: If `true`, the TOC will start in a collapsed state. Default is `false`.
> [!warning]
> This plugin needs the `Component.TableOfContents` component in `quartz.layout.ts` to determine where to display the TOC. Without it, nothing will be displayed. They should always be added or removed together.
## API
- Category: Transformer
- Function name: `Plugin.TableOfContents()`.
- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts).

20
docs/plugins/TagPage.md Normal file
View File

@@ -0,0 +1,20 @@
---
title: TagPage
tags:
- plugin/emitter
---
This plugin emits dedicated pages for each tag used in the content. See [[folder and tag listings]] for more information.
> [!note]
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
This plugin has no configuration options.
The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts`. For the content, the `TagContent` component is used. If you want to modify the layout, you must edit it directly (`quartz/components/pages/TagContent.tsx`).
## API
- Category: Emitter
- Function name: `Plugin.AliasRedirects()`.
- Source: [`quartz/plugins/emitters/tagPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/tagPage.tsx).

3
docs/plugins/index.md Normal file
View File

@@ -0,0 +1,3 @@
---
title: Plugins
---

View File

@@ -0,0 +1,48 @@
---
title: Setting up your GitHub repository
---
First, make sure you have Quartz [[index#🪴 Get Started|cloned and setup locally]].
Then, create a new repository on GitHub.com. Do **not** initialize the new repository with `README`, license, or `gitignore` files.
![[github-init-repo-options.png]]
At the top of your repository on GitHub.com's Quick Setup page, click the clipboard to copy the remote repository URL.
![[github-quick-setup.png]]
In your terminal of choice, navigate to the root of your Quartz folder. Then, run the following commands, replacing `REMOTE-URL` with the URL you just copied from the previous step.
```bash
# list all the repositories that are tracked
git remote -v
# if the origin doesn't match your own repository, set your repository as the origin
git remote set-url origin REMOTE-URL
# if you don't have upstream as a remote, add it so updates work
git remote add upstream https://github.com/jackyzha0/quartz.git
```
Then, you can sync the content to upload it to your repository. This is a helper command that will do the initial push of your content to your repository.
```bash
npx quartz sync --no-pull
```
> [!warning]- `fatal: --[no-]autostash option is only valid with --rebase`
> You may have an outdated version of `git`. Updating `git` should fix this issue.
In future updates, you can simply run `npx quartz sync` every time you want to push updates to your repository.
> [!hint] Flags and options
> For full help options, you can run `npx quartz sync --help`.
>
> Most of these have sensible defaults but you can override them if you have a custom setup:
>
> - `-d` or `--directory`: the content folder. This is normally just `content`
> - `-v` or `--verbose`: print out extra logging information
> - `--commit` or `--no-commit`: whether to make a `git` commit for your changes
> - `--push` or `--no-push`: whether to push updates to your GitHub fork of Quartz
> - `--pull` or `--no-pull`: whether to try and pull in any updates from your GitHub fork (i.e. from other devices) before pushing

View File

@@ -6,18 +6,25 @@ Want to see what Quartz can do? Here are some cool community gardens:
- [Quartz Documentation (this site!)](https://quartz.jzhao.xyz/)
- [Jacky Zhao's Garden](https://jzhao.xyz/)
- [Brandon Boswell's Garden](https://brandonkboswell.com)
- [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/)
- [AWAGMI Intern Notes](https://notes.awagmi.xyz/)
- [Data Dictionary 🧠](https://glossary.airbyte.com/)
- [sspaeti.com's Second Brain](https://brain.sspaeti.com/)
- [Socratica Toolbox](https://toolbox.socratica.info/)
- [oldwinter の数字花园](https://garden.oldwinter.top/)
- [Aaron Pham's Garden](https://aarnphm.xyz/)
- [The Quantum Garden](https://quantumgardener.blog/)
- [Abhijeet's Math Wiki](https://abhmul.github.io/quartz/Math-Wiki/)
- [Mike's AI Garden 🤖🪴](https://mwalton.me/)
- [Matt Dunn's Second Brain](https://mattdunn.info/)
- [Pelayo Arbues' Notes](https://pelayoarbues.github.io/)
- [Vince Imbat's Talahardin](https://vinceimbat.com/)
- [🧠🌳 Chad's Mind Garden](https://www.chadly.net/)
- [Pedro MC Fernandes's Topo da Mente](https://www.pmcf.xyz/topo-da-mente/)
- [Mau Camargo's Notkesto](https://notes.camargomau.com/)
- [Caicai's Novels](https://imoko.cc/blog/caicai/)
- [🌊 Collapsed Wave](https://collapsedwave.com/)
- [Sideny's 3D Artist's Handbook](https://sidney-eliot.github.io/3d-artists-handbook/)
- [Mike's AI Garden 🤖🪴](https://mwalton.me/)
- [Brandon Boswell's Garden](https://brandonkboswell.com)
- [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/)
- [Data Dictionary 🧠](https://glossary.airbyte.com/)
- [sspaeti.com's Second Brain](https://brain.sspaeti.com/)
- [🪴Aster's notebook](https://notes.asterhu.com)
If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)!

3
docs/tags/plugin.md Normal file
View File

@@ -0,0 +1,3 @@
---
title: Plugins
---

3
globals.d.ts vendored
View File

@@ -4,9 +4,10 @@ export declare global {
type: K,
listener: (this: Document, ev: CustomEventMap[K]) => void,
): void
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K] | UIEvent): void
}
interface Window {
spaNavigate(url: URL, isBack: boolean = false)
addCleanup(fn: (...args: any[]) => void)
}
}

1
index.d.ts vendored
View File

@@ -6,6 +6,7 @@ declare module "*.scss" {
// dom custom event
interface CustomEventMap {
nav: CustomEvent<{ url: FullSlug }>
themechange: CustomEvent<{ theme: "light" | "dark" }>
}
declare const fetchData: Promise<ContentIndex>

3632
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website",
"private": true,
"version": "4.1.0",
"version": "4.2.3",
"type": "module",
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
"license": "MIT",
@@ -15,7 +15,7 @@
"docs": "npx quartz build --serve -d docs",
"check": "tsc --noEmit && npx prettier . --check",
"format": "npx prettier . --write",
"test": "tsx ./quartz/util/path.test.ts",
"test": "tsx ./quartz/util/path.test.ts && tsx ./quartz/depgraph.test.ts",
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
},
"engines": {
@@ -34,76 +34,75 @@
"quartz": "./quartz/bootstrap-cli.mjs"
},
"dependencies": {
"@clack/prompts": "^0.6.3",
"@floating-ui/dom": "^1.4.0",
"@napi-rs/simple-git": "0.1.9",
"async-mutex": "^0.4.0",
"chalk": "^4.1.2",
"@clack/prompts": "^0.7.0",
"@floating-ui/dom": "^1.6.3",
"@napi-rs/simple-git": "0.1.16",
"async-mutex": "^0.4.1",
"chalk": "^5.3.0",
"chokidar": "^3.5.3",
"cli-spinner": "^0.2.10",
"d3": "^7.8.5",
"esbuild-sass-plugin": "^2.12.0",
"flexsearch": "0.7.21",
"esbuild-sass-plugin": "^2.16.1",
"flexsearch": "0.7.43",
"github-slugger": "^2.0.0",
"globby": "^13.1.4",
"globby": "^14.0.1",
"gray-matter": "^4.0.3",
"hast-util-to-html": "^8.0.4",
"hast-util-to-jsx-runtime": "^1.2.0",
"hast-util-to-string": "^2.0.0",
"hast-util-to-html": "^9.0.0",
"hast-util-to-jsx-runtime": "^2.3.0",
"hast-util-to-string": "^3.0.0",
"is-absolute-url": "^4.0.1",
"js-yaml": "^4.1.0",
"lightningcss": "1.21.7",
"mdast-util-find-and-replace": "^2.2.2",
"mdast-util-to-hast": "^12.3.0",
"mdast-util-to-string": "^3.2.0",
"lightningcss": "^1.23.0",
"mdast-util-find-and-replace": "^3.0.1",
"mdast-util-to-hast": "^13.1.0",
"mdast-util-to-string": "^4.0.0",
"micromorph": "^0.4.5",
"plausible-tracker": "^0.3.8",
"preact": "^10.14.1",
"preact-render-to-string": "^6.0.3",
"pretty-bytes": "^6.1.0",
"preact": "^10.19.5",
"preact-render-to-string": "^6.3.1",
"pretty-bytes": "^6.1.1",
"pretty-time": "^1.1.0",
"reading-time": "^1.5.0",
"rehype-autolink-headings": "^6.1.1",
"rehype-katex": "^6.0.3",
"rehype-mathjax": "^4.0.3",
"rehype-pretty-code": "^0.10.0",
"rehype-raw": "^6.1.1",
"rehype-slug": "^5.1.0",
"remark": "^14.0.2",
"remark-breaks": "^3.0.3",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-katex": "^7.0.0",
"rehype-mathjax": "^6.0.0",
"rehype-pretty-code": "^0.13.0",
"rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0",
"remark": "^15.0.1",
"remark-breaks": "^4.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.0",
"remark-smartypants": "^2.0.0",
"rimraf": "^5.0.1",
"rfdc": "^1.3.1",
"rimraf": "^5.0.5",
"serve-handler": "^6.1.5",
"shiki": "^1.1.6",
"source-map-support": "^0.5.21",
"to-vfile": "^7.2.4",
"to-vfile": "^8.0.0",
"toml": "^3.0.0",
"unified": "^10.1.2",
"unist-util-visit": "^4.1.2",
"vfile": "^5.3.7",
"workerpool": "^6.4.0",
"ws": "^8.13.0",
"unified": "^11.0.4",
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.1",
"workerpool": "^9.1.0",
"ws": "^8.15.1",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/cli-spinner": "^0.2.1",
"@types/d3": "^7.4.0",
"@types/flexsearch": "^0.7.3",
"@types/hast": "^2.3.4",
"@types/js-yaml": "^4.0.5",
"@types/node": "^20.1.2",
"@types/pretty-time": "^1.1.2",
"@types/source-map-support": "^0.5.6",
"@types/workerpool": "^6.4.0",
"@types/ws": "^8.5.5",
"@types/yargs": "^17.0.24",
"esbuild": "0.19.2",
"prettier": "^3.0.0",
"tsx": "^3.12.7",
"typescript": "^5.0.4"
"@types/cli-spinner": "^0.2.3",
"@types/d3": "^7.4.3",
"@types/hast": "^3.0.4",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.11.19",
"@types/pretty-time": "^1.1.5",
"@types/source-map-support": "^0.5.10",
"@types/ws": "^8.5.10",
"@types/yargs": "^17.0.32",
"esbuild": "^0.19.9",
"prettier": "^3.2.4",
"tsx": "^4.7.1",
"typescript": "^5.3.3"
}
}

View File

@@ -1,6 +1,11 @@
import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
/**
* Quartz 4.0 Configuration
*
* See https://quartz.jzhao.xyz/configuration for more information.
*/
const config: QuartzConfig = {
configuration: {
pageTitle: "🪴 Quartz 4.0",
@@ -9,10 +14,12 @@ const config: QuartzConfig = {
analytics: {
provider: "plausible",
},
locale: "en-US",
baseUrl: "quartz.jzhao.xyz",
ignorePatterns: ["private", "templates", ".obsidian"],
defaultDateType: "created",
theme: {
cdnCaching: true,
typography: {
header: "Schibsted Grotesk",
body: "Source Sans Pro",
@@ -45,15 +52,21 @@ const config: QuartzConfig = {
plugins: {
transformers: [
Plugin.FrontMatter(),
Plugin.TableOfContents(),
Plugin.CreatedModifiedDate({
priority: ["frontmatter", "filesystem"], // you can add 'git' here for last modified from Git but this makes the build slower
priority: ["frontmatter", "filesystem"],
}),
Plugin.Latex({ renderEngine: "katex" }),
Plugin.SyntaxHighlighting({
theme: {
light: "github-light",
dark: "github-dark",
},
keepBackground: false,
}),
Plugin.SyntaxHighlighting(),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(),
Plugin.TableOfContents(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
Plugin.Latex({ renderEngine: "katex" }),
Plugin.Description(),
],
filters: [Plugin.RemoveDrafts()],

View File

@@ -37,12 +37,13 @@ export const defaultContentPageLayout: PageLayout = {
// components for pages that display lists of pages (e.g. tags or folders)
export const defaultListPageLayout: PageLayout = {
beforeBody: [Component.ArticleTitle()],
beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta()],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Search(),
Component.Darkmode(),
Component.DesktopOnly(Component.Explorer()),
],
right: [],
}

View File

@@ -3,13 +3,13 @@ sourceMapSupport.install(options)
import path from "path"
import { PerfTimer } from "./util/perf"
import { rimraf } from "rimraf"
import { isGitIgnored } from "globby"
import { GlobbyFilterFunction, isGitIgnored } from "globby"
import chalk from "chalk"
import { parseMarkdown } from "./processors/parse"
import { filterContent } from "./processors/filter"
import { emitContent } from "./processors/emit"
import cfg from "../quartz.config"
import { FilePath, joinSegments, slugifyFilePath } from "./util/path"
import { FilePath, FullSlug, joinSegments, slugifyFilePath } from "./util/path"
import chokidar from "chokidar"
import { ProcessedContent } from "./plugins/vfile"
import { Argv, BuildCtx } from "./util/ctx"
@@ -17,6 +17,26 @@ import { glob, toPosixPath } from "./util/glob"
import { trace } from "./util/trace"
import { options } from "./util/sourcemap"
import { Mutex } from "async-mutex"
import DepGraph from "./depgraph"
import { getStaticResourcesFromPlugins } from "./plugins"
type Dependencies = Record<string, DepGraph<FilePath> | null>
type BuildData = {
ctx: BuildCtx
ignored: GlobbyFilterFunction
mut: Mutex
initialSlugs: FullSlug[]
// TODO merge contentMap and trackedAssets
contentMap: Map<FilePath, ProcessedContent>
trackedAssets: Set<FilePath>
toRebuild: Set<FilePath>
toRemove: Set<FilePath>
lastBuildMs: number
dependencies: Dependencies
}
type FileEvent = "add" | "change" | "delete"
async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
const ctx: BuildCtx = {
@@ -40,7 +60,7 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
const release = await mut.acquire()
perf.addEvent("clean")
await rimraf(output)
await rimraf(path.join(output, "*"), { glob: true })
console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`)
perf.addEvent("glob")
@@ -55,12 +75,24 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
const parsedFiles = await parseMarkdown(ctx, filePaths)
const filteredContent = filterContent(ctx, parsedFiles)
const dependencies: Record<string, DepGraph<FilePath> | null> = {}
// Only build dependency graphs if we're doing a fast rebuild
if (argv.fastRebuild) {
const staticResources = getStaticResourcesFromPlugins(ctx)
for (const emitter of cfg.plugins.emitters) {
dependencies[emitter.name] =
(await emitter.getDependencyGraph?.(ctx, filteredContent, staticResources)) ?? null
}
}
await emitContent(ctx, filteredContent)
console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
release()
if (argv.serve) {
return startServing(ctx, mut, parsedFiles, clientRefresh)
return startServing(ctx, mut, parsedFiles, clientRefresh, dependencies)
}
}
@@ -70,92 +102,28 @@ async function startServing(
mut: Mutex,
initialContent: ProcessedContent[],
clientRefresh: () => void,
dependencies: Dependencies, // emitter name: dep graph
) {
const { argv } = ctx
const ignored = await isGitIgnored()
// cache file parse results
const contentMap = new Map<FilePath, ProcessedContent>()
for (const content of initialContent) {
const [_tree, vfile] = content
contentMap.set(vfile.data.filePath!, content)
}
const initialSlugs = ctx.allSlugs
let lastBuildMs = 0
const toRebuild: Set<FilePath> = new Set()
const toRemove: Set<FilePath> = new Set()
const trackedAssets: Set<FilePath> = new Set()
async function rebuild(fp: string, action: "add" | "change" | "delete") {
// don't do anything for gitignored files
if (ignored(fp)) {
return
}
// dont bother rebuilding for non-content files, just track and refresh
fp = toPosixPath(fp)
const filePath = joinSegments(argv.directory, fp) as FilePath
if (path.extname(fp) !== ".md") {
if (action === "add" || action === "change") {
trackedAssets.add(filePath)
} else if (action === "delete") {
trackedAssets.delete(filePath)
}
clientRefresh()
return
}
if (action === "add" || action === "change") {
toRebuild.add(filePath)
} else if (action === "delete") {
toRemove.add(filePath)
}
// debounce rebuilds every 250ms
const buildStart = new Date().getTime()
lastBuildMs = buildStart
const release = await mut.acquire()
if (lastBuildMs > buildStart) {
release()
return
}
const perf = new PerfTimer()
console.log(chalk.yellow("Detected change, rebuilding..."))
try {
const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp))
const trackedSlugs = [...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets])]
.filter((fp) => !toRemove.has(fp))
.map((fp) => slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath))
ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])]
const parsedContent = await parseMarkdown(ctx, filesToRebuild)
for (const content of parsedContent) {
const [_tree, vfile] = content
contentMap.set(vfile.data.filePath!, content)
}
for (const fp of toRemove) {
contentMap.delete(fp)
}
const parsedFiles = [...contentMap.values()]
const filteredContent = filterContent(ctx, parsedFiles)
// TODO: we can probably traverse the link graph to figure out what's safe to delete here
// instead of just deleting everything
await rimraf(argv.output)
await emitContent(ctx, filteredContent)
console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`))
} catch {
console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`))
}
clientRefresh()
toRebuild.clear()
toRemove.clear()
release()
const buildData: BuildData = {
ctx,
mut,
dependencies,
contentMap,
ignored: await isGitIgnored(),
initialSlugs: ctx.allSlugs,
toRebuild: new Set<FilePath>(),
toRemove: new Set<FilePath>(),
trackedAssets: new Set<FilePath>(),
lastBuildMs: 0,
}
const watcher = chokidar.watch(".", {
@@ -164,16 +132,274 @@ async function startServing(
ignoreInitial: true,
})
const buildFromEntry = argv.fastRebuild ? partialRebuildFromEntrypoint : rebuildFromEntrypoint
watcher
.on("add", (fp) => rebuild(fp, "add"))
.on("change", (fp) => rebuild(fp, "change"))
.on("unlink", (fp) => rebuild(fp, "delete"))
.on("add", (fp) => buildFromEntry(fp, "add", clientRefresh, buildData))
.on("change", (fp) => buildFromEntry(fp, "change", clientRefresh, buildData))
.on("unlink", (fp) => buildFromEntry(fp, "delete", clientRefresh, buildData))
return async () => {
await watcher.close()
}
}
async function partialRebuildFromEntrypoint(
filepath: string,
action: FileEvent,
clientRefresh: () => void,
buildData: BuildData, // note: this function mutates buildData
) {
const { ctx, ignored, dependencies, contentMap, mut, toRemove } = buildData
const { argv, cfg } = ctx
// don't do anything for gitignored files
if (ignored(filepath)) {
return
}
const buildStart = new Date().getTime()
buildData.lastBuildMs = buildStart
const release = await mut.acquire()
if (buildData.lastBuildMs > buildStart) {
release()
return
}
const perf = new PerfTimer()
console.log(chalk.yellow("Detected change, rebuilding..."))
// UPDATE DEP GRAPH
const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath
const staticResources = getStaticResourcesFromPlugins(ctx)
let processedFiles: ProcessedContent[] = []
switch (action) {
case "add":
// add to cache when new file is added
processedFiles = await parseMarkdown(ctx, [fp])
processedFiles.forEach(([tree, vfile]) => contentMap.set(vfile.data.filePath!, [tree, vfile]))
// update the dep graph by asking all emitters whether they depend on this file
for (const emitter of cfg.plugins.emitters) {
const emitterGraph =
(await emitter.getDependencyGraph?.(ctx, processedFiles, staticResources)) ?? null
if (emitterGraph) {
const existingGraph = dependencies[emitter.name]
if (existingGraph !== null) {
existingGraph.mergeGraph(emitterGraph)
} else {
// might be the first time we're adding a mardown file
dependencies[emitter.name] = emitterGraph
}
}
}
break
case "change":
// invalidate cache when file is changed
processedFiles = await parseMarkdown(ctx, [fp])
processedFiles.forEach(([tree, vfile]) => contentMap.set(vfile.data.filePath!, [tree, vfile]))
// only content files can have added/removed dependencies because of transclusions
if (path.extname(fp) === ".md") {
for (const emitter of cfg.plugins.emitters) {
// get new dependencies from all emitters for this file
const emitterGraph =
(await emitter.getDependencyGraph?.(ctx, processedFiles, staticResources)) ?? null
// only update the graph if the emitter plugin uses the changed file
// eg. Assets plugin ignores md files, so we skip updating the graph
if (emitterGraph?.hasNode(fp)) {
// merge the new dependencies into the dep graph
dependencies[emitter.name]?.updateIncomingEdgesForNode(emitterGraph, fp)
}
}
}
break
case "delete":
toRemove.add(fp)
break
}
if (argv.verbose) {
console.log(`Updated dependency graphs in ${perf.timeSince()}`)
}
// EMIT
perf.addEvent("rebuild")
let emittedFiles = 0
for (const emitter of cfg.plugins.emitters) {
const depGraph = dependencies[emitter.name]
// emitter hasn't defined a dependency graph. call it with all processed files
if (depGraph === null) {
if (argv.verbose) {
console.log(
`Emitter ${emitter.name} doesn't define a dependency graph. Calling it with all files...`,
)
}
const files = [...contentMap.values()].filter(
([_node, vfile]) => !toRemove.has(vfile.data.filePath!),
)
const emittedFps = await emitter.emit(ctx, files, staticResources)
if (ctx.argv.verbose) {
for (const file of emittedFps) {
console.log(`[emit:${emitter.name}] ${file}`)
}
}
emittedFiles += emittedFps.length
continue
}
// only call the emitter if it uses this file
if (depGraph.hasNode(fp)) {
// re-emit using all files that are needed for the downstream of this file
// eg. for ContentIndex, the dep graph could be:
// a.md --> contentIndex.json
// b.md ------^
//
// if a.md changes, we need to re-emit contentIndex.json,
// and supply [a.md, b.md] to the emitter
const upstreams = [...depGraph.getLeafNodeAncestors(fp)] as FilePath[]
const upstreamContent = upstreams
// filter out non-markdown files
.filter((file) => contentMap.has(file))
// if file was deleted, don't give it to the emitter
.filter((file) => !toRemove.has(file))
.map((file) => contentMap.get(file)!)
const emittedFps = await emitter.emit(ctx, upstreamContent, staticResources)
if (ctx.argv.verbose) {
for (const file of emittedFps) {
console.log(`[emit:${emitter.name}] ${file}`)
}
}
emittedFiles += emittedFps.length
}
}
console.log(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`)
// CLEANUP
const destinationsToDelete = new Set<FilePath>()
for (const file of toRemove) {
// remove from cache
contentMap.delete(file)
Object.values(dependencies).forEach((depGraph) => {
// remove the node from dependency graphs
depGraph?.removeNode(file)
// remove any orphan nodes. eg if a.md is deleted, a.html is orphaned and should be removed
const orphanNodes = depGraph?.removeOrphanNodes()
orphanNodes?.forEach((node) => {
// only delete files that are in the output directory
if (node.startsWith(argv.output)) {
destinationsToDelete.add(node)
}
})
})
}
await rimraf([...destinationsToDelete])
toRemove.clear()
release()
clientRefresh()
}
async function rebuildFromEntrypoint(
fp: string,
action: FileEvent,
clientRefresh: () => void,
buildData: BuildData, // note: this function mutates buildData
) {
const { ctx, ignored, mut, initialSlugs, contentMap, toRebuild, toRemove, trackedAssets } =
buildData
const { argv } = ctx
// don't do anything for gitignored files
if (ignored(fp)) {
return
}
// dont bother rebuilding for non-content files, just track and refresh
fp = toPosixPath(fp)
const filePath = joinSegments(argv.directory, fp) as FilePath
if (path.extname(fp) !== ".md") {
if (action === "add" || action === "change") {
trackedAssets.add(filePath)
} else if (action === "delete") {
trackedAssets.delete(filePath)
}
clientRefresh()
return
}
if (action === "add" || action === "change") {
toRebuild.add(filePath)
} else if (action === "delete") {
toRemove.add(filePath)
}
const buildStart = new Date().getTime()
buildData.lastBuildMs = buildStart
const release = await mut.acquire()
// there's another build after us, release and let them do it
if (buildData.lastBuildMs > buildStart) {
release()
return
}
const perf = new PerfTimer()
console.log(chalk.yellow("Detected change, rebuilding..."))
try {
const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp))
const trackedSlugs = [...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets])]
.filter((fp) => !toRemove.has(fp))
.map((fp) => slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath))
ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])]
const parsedContent = await parseMarkdown(ctx, filesToRebuild)
for (const content of parsedContent) {
const [_tree, vfile] = content
contentMap.set(vfile.data.filePath!, content)
}
for (const fp of toRemove) {
contentMap.delete(fp)
}
const parsedFiles = [...contentMap.values()]
const filteredContent = filterContent(ctx, parsedFiles)
// TODO: we can probably traverse the link graph to figure out what's safe to delete here
// instead of just deleting everything
await rimraf(path.join(argv.output, ".*"), { glob: true })
await emitContent(ctx, filteredContent)
console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`))
} catch (err) {
console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`))
if (argv.verbose) {
console.log(chalk.red(err))
}
}
release()
clientRefresh()
toRebuild.clear()
toRemove.clear()
}
export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => {
try {
return await buildQuartz(argv, mut, clientRefresh)

View File

@@ -1,5 +1,6 @@
import { ValidDateType } from "./components/Date"
import { QuartzComponent } from "./components/types"
import { ValidLocale } from "./i18n"
import { PluginTypes } from "./plugins/types"
import { Theme } from "./util/theme"
@@ -7,6 +8,7 @@ export type Analytics =
| null
| {
provider: "plausible"
host?: string
}
| {
provider: "google"
@@ -15,6 +17,7 @@ export type Analytics =
| {
provider: "umami"
websiteId: string
host?: string
}
export interface GlobalConfiguration {
@@ -34,6 +37,15 @@ export interface GlobalConfiguration {
*/
baseUrl?: string
theme: Theme
/**
* Allow to translate the date in the language of your choice.
* Also used for UI translation (default: en-US)
* Need to be formated following BCP 47: https://en.wikipedia.org/wiki/IETF_language_tag
* The first part is the language (en) and the second part is the script/region (US)
* Language Codes: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
* Region Codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
*/
locale: ValidLocale
}
export interface QuartzConfig {

View File

@@ -41,6 +41,11 @@ export const SyncArgv = {
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,
@@ -66,6 +71,11 @@ export const BuildArgv = {
default: false,
describe: "run a local server to live-preview your Quartz",
},
fastRebuild: {
boolean: true,
default: false,
describe: "[experimental] rebuild only the changed files",
},
baseDir: {
string: true,
default: "",

View File

@@ -113,7 +113,10 @@ export async function handleCreate(argv) {
}
}
await fs.promises.unlink(path.join(contentFolder, ".gitkeep"))
const gitkeepPath = path.join(contentFolder, ".gitkeep")
if (fs.existsSync(gitkeepPath)) {
await fs.promises.unlink(gitkeepPath)
}
if (setupStrategy === "copy" || setupStrategy === "symlink") {
let originalFolder = sourceDirectory
@@ -165,22 +168,20 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started.
// 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\`.`,
message: `Choose how Quartz should resolve links in your content. This should match Obsidian's link format. 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",
hint: "(default)",
},
{
value: "absolute",
label: "Treat links as absolute path",
},
{
value: "relative",
label: "Treat links as relative paths",
hint: "for just normal Markdown files",
},
],
}),
@@ -196,6 +197,12 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started.
)
await fs.promises.writeFile(configFilePath, configContent)
// setup remote
execSync(
`git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`,
{ stdio: "ignore" },
)
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
@@ -250,6 +257,7 @@ export async function handleBuild(argv) {
},
write: false,
bundle: true,
minify: true,
platform: "browser",
format: "esm",
})
@@ -339,7 +347,7 @@ export async function handleBuild(argv) {
directoryListing: false,
headers: [
{
source: "**/*.html",
source: "**/*.*",
headers: [{ key: "Content-Disposition", value: "inline" }],
},
],
@@ -438,11 +446,23 @@ export async function handleUpdate(argv) {
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)
try {
gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH)
} catch {
console.log(chalk.red("An error occurred above while pulling updates."))
await popContentFolder(contentFolder)
return
}
await popContentFolder(contentFolder)
console.log("Ensuring dependencies are up to date")
spawnSync("npm", ["i"], { stdio: "inherit" })
console.log(chalk.green("Done!"))
const res = spawnSync("npm", ["i"], { stdio: "inherit" })
if (res.status === 0) {
console.log(chalk.green("Done!"))
} else {
console.log(chalk.red("An error occurred above while installing dependencies."))
}
}
/**
@@ -483,8 +503,9 @@ export async function handleSync(argv) {
dateStyle: "medium",
timeStyle: "short",
})
const commitMessage = argv.message ?? `Quartz sync: ${currentTimestamp}`
spawnSync("git", ["add", "."], { stdio: "inherit" })
spawnSync("git", ["commit", "-m", `Quartz sync: ${currentTimestamp}`], { stdio: "inherit" })
spawnSync("git", ["commit", "-m", commitMessage], { stdio: "inherit" })
if (contentStat.isSymbolicLink()) {
// put symlink back
@@ -498,13 +519,25 @@ export async function handleSync(argv) {
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)
try {
gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH)
} catch {
console.log(chalk.red("An error occurred above while pulling updates."))
await popContentFolder(contentFolder)
return
}
}
await popContentFolder(contentFolder)
if (argv.push) {
console.log("Pushing your changes")
spawnSync("git", ["push", "-f", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], { stdio: "inherit" })
const res = spawnSync("git", ["push", "-uf", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], {
stdio: "inherit",
})
if (res.status !== 0) {
console.log(chalk.red(`An error occurred above while pushing to remote ${ORIGIN_NAME}.`))
return
}
}
console.log(chalk.green("Done!"))

View File

@@ -36,7 +36,9 @@ 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}`)
throw new Error(chalk.red(`Error while pulling updates: ${out.stderr}`))
} else if (out.status !== 0) {
throw new Error(chalk.red("Error while pulling updates"))
}
}

View File

@@ -1,13 +1,15 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { classNames } from "../util/lang"
function ArticleTitle({ fileData, displayClass }: QuartzComponentProps) {
const ArticleTitle: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
const title = fileData.frontmatter?.title
if (title) {
return <h1 class={`article-title ${displayClass ?? ""}`}>{title}</h1>
return <h1 class={classNames(displayClass, "article-title")}>{title}</h1>
} else {
return null
}
}
ArticleTitle.css = `
.article-title {
margin: 2rem 0 0 0;

View File

@@ -1,13 +1,20 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/backlinks.scss"
import { resolveRelative, simplifySlug } from "../util/path"
import { i18n } from "../i18n"
import { classNames } from "../util/lang"
function Backlinks({ fileData, allFiles, displayClass }: QuartzComponentProps) {
const Backlinks: QuartzComponent = ({
fileData,
allFiles,
displayClass,
cfg,
}: QuartzComponentProps) => {
const slug = simplifySlug(fileData.slug!)
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
return (
<div class={`backlinks ${displayClass ?? ""}`}>
<h3>Backlinks</h3>
<div class={classNames(displayClass, "backlinks")}>
<h3>{i18n(cfg.locale).components.backlinks.title}</h3>
<ul class="overflow">
{backlinkFiles.length > 0 ? (
backlinkFiles.map((f) => (
@@ -18,7 +25,7 @@ function Backlinks({ fileData, allFiles, displayClass }: QuartzComponentProps) {
</li>
))
) : (
<li>No backlinks found</li>
<li>{i18n(cfg.locale).components.backlinks.noBacklinksFound}</li>
)}
</ul>
</div>

View File

@@ -1,9 +1,9 @@
// @ts-ignore
import clipboardScript from "./scripts/clipboard.inline"
import clipboardStyle from "./styles/clipboard.scss"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
function Body({ children }: QuartzComponentProps) {
const Body: QuartzComponent = ({ children }: QuartzComponentProps) => {
return <div id="quartz-body">{children}</div>
}

View File

@@ -1,7 +1,8 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import breadcrumbsStyle from "./styles/breadcrumbs.scss"
import { FullSlug, SimpleSlug, resolveRelative } from "../util/path"
import { FullSlug, SimpleSlug, joinSegments, resolveRelative } from "../util/path"
import { QuartzPluginData } from "../plugins/vfile"
import { classNames } from "../util/lang"
type CrumbData = {
displayName: string
@@ -18,20 +19,25 @@ interface BreadcrumbOptions {
*/
rootName: string
/**
* wether to look up frontmatter title for folders (could cause performance problems with big vaults)
* Whether to look up frontmatter title for folders (could cause performance problems with big vaults)
*/
resolveFrontmatterTitle: boolean
/**
* Wether to display breadcrumbs on root `index.md`
* Whether to display breadcrumbs on root `index.md`
*/
hideOnRoot: boolean
/**
* Whether to display the current page in the breadcrumbs.
*/
showCurrentPage: boolean
}
const defaultOptions: BreadcrumbOptions = {
spacerSymbol: ">",
spacerSymbol: "",
rootName: "Home",
resolveFrontmatterTitle: false,
resolveFrontmatterTitle: true,
hideOnRoot: true,
showCurrentPage: true,
}
function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData {
@@ -41,26 +47,18 @@ function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: Simpl
}
}
// given a folderName (e.g. "features"), search for the corresponding `index.md` file
function findCurrentFile(allFiles: QuartzPluginData[], folderName: string) {
return allFiles.find((file) => {
if (file.slug?.endsWith("index")) {
const folderParts = file.filePath?.split("/")
if (folderParts) {
const name = folderParts[folderParts?.length - 2]
if (name === folderName) {
return true
}
}
}
})
}
export default ((opts?: Partial<BreadcrumbOptions>) => {
// Merge options with defaults
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
function Breadcrumbs({ fileData, allFiles, displayClass }: QuartzComponentProps) {
// computed index of folder name to its associated file data
let folderIndex: Map<string, QuartzPluginData> | undefined
const Breadcrumbs: QuartzComponent = ({
fileData,
allFiles,
displayClass,
}: QuartzComponentProps) => {
// Hide crumbs on root if enabled
if (options.hideOnRoot && fileData.slug === "index") {
return <></>
@@ -70,39 +68,62 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
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) {
const folderParts = file.slug?.split("/")
if (folderParts?.at(-1) === "index") {
folderIndex.set(folderParts.slice(0, -1).join("/"), file)
}
}
}
// Split slug into hierarchy/parts
const slugParts = fileData.slug?.split("/")
if (slugParts) {
// is tag breadcrumb?
const isTagPath = slugParts[0] === "tags"
// full path until current part
let currentPath = ""
for (let i = 0; i < slugParts.length - 1; i++) {
let currentTitle = slugParts[i]
// TODO: performance optimizations/memoizing
for (let i = 0; i < slugParts.length - 1; i++) {
let curPathSegment = slugParts[i]
// Try to resolve frontmatter folder title
if (options?.resolveFrontmatterTitle) {
// try to find file for current path
const currentFile = findCurrentFile(allFiles, currentTitle)
if (currentFile) {
currentTitle = currentFile.frontmatter!.title
const currentFile = folderIndex?.get(slugParts.slice(0, i + 1).join("/"))
if (currentFile) {
const title = currentFile.frontmatter!.title
if (title !== "index") {
curPathSegment = title
}
}
// Add current slug to full path
currentPath += slugParts[i] + "/"
currentPath = joinSegments(currentPath, slugParts[i])
const includeTrailingSlash = !isTagPath || i < 1
// Format and add current crumb
const crumb = formatCrumb(currentTitle, fileData.slug!, currentPath as SimpleSlug)
const crumb = formatCrumb(
curPathSegment,
fileData.slug!,
(currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug,
)
crumbs.push(crumb)
}
// Add current file to crumb (can directly use frontmatter title)
crumbs.push({
displayName: fileData.frontmatter!.title,
path: "",
})
if (options.showCurrentPage && slugParts.at(-1) !== "index") {
crumbs.push({
displayName: fileData.frontmatter!.title,
path: "",
})
}
}
return (
<nav class={`breadcrumb-container ${displayClass ?? ""}`} aria-label="breadcrumbs">
<nav class={classNames(displayClass, "breadcrumb-container")} aria-label="breadcrumbs">
{crumbs.map((crumb, index) => (
<div class="breadcrumb-element">
<a href={crumb.path}>{crumb.displayName}</a>
@@ -113,5 +134,6 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
)
}
Breadcrumbs.css = breadcrumbsStyle
return Breadcrumbs
}) satisfies QuartzComponentConstructor

View File

@@ -1,20 +1,44 @@
import { formatDate, getDate } from "./Date"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import readingTime from "reading-time"
import { classNames } from "../util/lang"
import { i18n } from "../i18n"
interface ContentMetaOptions {
/**
* Whether to display reading time
*/
showReadingTime: boolean
}
const defaultOptions: ContentMetaOptions = {
showReadingTime: true,
}
export default ((opts?: Partial<ContentMetaOptions>) => {
// Merge options with defaults
const options: ContentMetaOptions = { ...defaultOptions, ...opts }
export default (() => {
function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) {
const text = fileData.text
if (text) {
const segments: string[] = []
const { text: timeTaken, words: _words } = readingTime(text)
if (fileData.dates) {
segments.push(formatDate(getDate(cfg, fileData)!))
segments.push(formatDate(getDate(cfg, fileData)!, cfg.locale))
}
segments.push(timeTaken)
return <p class={`content-meta ${displayClass ?? ""}`}>{segments.join(", ")}</p>
// Display reading time if enabled
if (options.showReadingTime) {
const { minutes, words: _words } = readingTime(text)
const displayedTime = i18n(cfg.locale).components.contentMeta.readingTime({
minutes: Math.ceil(minutes),
})
segments.push(displayedTime)
}
return <p class={classNames(displayClass, "content-meta")}>{segments.join(", ")}</p>
} else {
return null
}

View File

@@ -3,11 +3,13 @@
// see: https://v8.dev/features/modules#defer
import darkmodeScript from "./scripts/darkmode.inline"
import styles from "./styles/darkmode.scss"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { i18n } from "../i18n"
import { classNames } from "../util/lang"
function Darkmode({ displayClass }: QuartzComponentProps) {
const Darkmode: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
return (
<div class={`darkmode ${displayClass ?? ""}`}>
<div class={classNames(displayClass, "darkmode")}>
<input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
<label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
<svg
@@ -18,10 +20,10 @@ function Darkmode({ displayClass }: QuartzComponentProps) {
x="0px"
y="0px"
viewBox="0 0 35 35"
style="enable-background:new 0 0 35 35;"
style="enable-background:new 0 0 35 35"
xmlSpace="preserve"
>
<title>Light mode</title>
<title>{i18n(cfg.locale).components.themeToggle.darkMode}</title>
<path d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5 S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5 C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6 C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9 c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44 l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5 c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06 L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2 C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29 c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7 C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7z M17.5,25c-4.136,0-7.5-3.364-7.5-7.5 c0-4.136,3.364-7.5,7.5-7.5c4.136,0,7.5,3.364,7.5,7.5C25,21.636,21.636,25,17.5,25z"></path>
</svg>
</label>
@@ -34,10 +36,10 @@ function Darkmode({ displayClass }: QuartzComponentProps) {
x="0px"
y="0px"
viewBox="0 0 100 100"
style="enable-background='new 0 0 100 100'"
style="enable-background:new 0 0 100 100"
xmlSpace="preserve"
>
<title>Dark mode</title>
<title>{i18n(cfg.locale).components.themeToggle.lightMode}</title>
<path d="M96.76,66.458c-0.853-0.852-2.15-1.064-3.23-0.534c-6.063,2.991-12.858,4.571-19.655,4.571 C62.022,70.495,50.88,65.88,42.5,57.5C29.043,44.043,25.658,23.536,34.076,6.47c0.532-1.08,0.318-2.379-0.534-3.23 c-0.851-0.852-2.15-1.064-3.23-0.534c-4.918,2.427-9.375,5.619-13.246,9.491c-9.447,9.447-14.65,22.008-14.65,35.369 c0,13.36,5.203,25.921,14.65,35.368s22.008,14.65,35.368,14.65c13.361,0,25.921-5.203,35.369-14.65 c3.872-3.871,7.064-8.328,9.491-13.246C97.826,68.608,97.611,67.309,96.76,66.458z"></path>
</svg>
</label>

View File

@@ -1,8 +1,10 @@
import { GlobalConfiguration } from "../cfg"
import { ValidLocale } from "../i18n"
import { QuartzPluginData } from "../plugins/vfile"
interface Props {
date: Date
locale?: ValidLocale
}
export type ValidDateType = keyof Required<QuartzPluginData>["dates"]
@@ -16,14 +18,14 @@ export function getDate(cfg: GlobalConfiguration, data: QuartzPluginData): Date
return data.dates?.[cfg.defaultDateType]
}
export function formatDate(d: Date): string {
return d.toLocaleDateString("en-US", {
export function formatDate(d: Date, locale: ValidLocale = "en-US"): string {
return d.toLocaleDateString(locale, {
year: "numeric",
month: "short",
day: "2-digit",
})
}
export function Date({ date }: Props) {
return <>{formatDate(date)}</>
export function Date({ date, locale }: Props) {
return <>{formatDate(date, locale)}</>
}

View File

@@ -3,7 +3,7 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } fro
export default ((component?: QuartzComponent) => {
if (component) {
const Component = component
function DesktopOnly(props: QuartzComponentProps) {
const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => {
return <Component displayClass="desktop-only" {...props} />
}

View File

@@ -1,17 +1,21 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, 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"
import { classNames } from "../util/lang"
import { i18n } from "../i18n"
// Options interface defined in `ExplorerNode` to avoid circular dependency
const defaultOptions = {
title: "Explorer",
folderClickBehavior: "collapse",
folderDefaultState: "collapsed",
useSavedState: true,
mapFn: (node) => {
return node
},
sortFn: (a, b) => {
// Sort order: folders first, then files. Sort folders and files alphabetically
if ((!a.file && !b.file) || (a.file && b.file)) {
@@ -22,6 +26,7 @@ const defaultOptions = {
sensitivity: "base",
})
}
if (a.file && !b.file) {
return 1
} else {
@@ -41,52 +46,44 @@ export default ((userOpts?: Partial<Options>) => {
let jsonTree: string
function constructFileTree(allFiles: QuartzPluginData[]) {
if (!fileTree) {
// Construct tree from allFiles
fileTree = new FileNode("")
allFiles.forEach((file) => fileTree.add(file, 1))
if (fileTree) {
return
}
/**
* 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,
}
// Construct tree from allFiles
fileTree = new FileNode("")
allFiles.forEach((file) => fileTree.add(file))
// 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])
}
// 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 (functionName === "map") {
fileTree.map(opts.mapFn)
} else if (functionName === "sort") {
fileTree.sort(opts.sortFn)
} else if (functionName === "filter") {
fileTree.filter(opts.filterFn)
}
}
// 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)
}
// Get all folders of tree. Initialize with collapsed state
// Stringify to pass json tree as data attribute ([data-tree])
const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
jsonTree = JSON.stringify(folders)
}
function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
const Explorer: QuartzComponent = ({
cfg,
allFiles,
displayClass,
fileData,
}: QuartzComponentProps) => {
constructFileTree(allFiles)
return (
<div class={`explorer ${displayClass ?? ""}`}>
<div class={classNames(displayClass, "explorer")}>
<button
type="button"
id="explorer"
@@ -95,7 +92,7 @@ export default ((userOpts?: Partial<Options>) => {
data-savestate={opts.useSavedState}
data-tree={jsonTree}
>
<h1>{opts.title}</h1>
<h1>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h1>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
@@ -120,6 +117,7 @@ export default ((userOpts?: Partial<Options>) => {
</div>
)
}
Explorer.css = explorerStyle
Explorer.afterDOMLoaded = script
return Explorer

View File

@@ -1,18 +1,25 @@
// @ts-ignore
import { QuartzPluginData } from "../plugins/vfile"
import { resolveRelative } from "../util/path"
import {
joinSegments,
resolveRelative,
clone,
simplifySlug,
SimpleSlug,
FilePath,
} from "../util/path"
type OrderEntries = "sort" | "filter" | "map"
export interface Options {
title: string
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[]
filterFn: (node: FileNode) => boolean
mapFn: (node: FileNode) => void
order: OrderEntries[]
}
type DataWrapper = {
@@ -25,59 +32,74 @@ export type FolderState = {
collapsed: boolean
}
function getPathSegment(fp: FilePath | undefined, idx: number): string | undefined {
if (!fp) {
return undefined
}
return fp.split("/").at(idx)
}
// Structure to add all files into a tree
export class FileNode {
children: FileNode[]
name: string
children: Array<FileNode>
name: string // this is the slug segment
displayName: string
file: QuartzPluginData | null
depth: number
constructor(name: string, file?: QuartzPluginData, depth?: number) {
constructor(slugSegment: string, displayName?: string, file?: QuartzPluginData, depth?: number) {
this.children = []
this.name = name
this.displayName = name
this.file = file ? structuredClone(file) : null
this.name = slugSegment
this.displayName = displayName ?? file?.frontmatter?.title ?? slugSegment
this.file = file ? clone(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") {
private insert(fileData: DataWrapper) {
if (fileData.path.length === 0) {
return
}
const nextSegment = fileData.path[0]
// base case, insert here
if (fileData.path.length === 1) {
if (nextSegment === "") {
// index case (we are the root and we just found index.md), set our data appropriately
const title = fileData.file.frontmatter?.title
if (title && title !== "index") {
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
}
} else {
// direct child
this.children.push(new FileNode(nextSegment, undefined, fileData.file, this.depth + 1))
}
const newChild = new FileNode(next, undefined, this.depth + 1)
newChild.insert(file)
this.children.push(newChild)
return
}
// find the right child to insert into
fileData.path = fileData.path.splice(1)
const child = this.children.find((c) => c.name === nextSegment)
if (child) {
child.insert(fileData)
return
}
const newChild = new FileNode(
nextSegment,
getPathSegment(fileData.file.relativePath, this.depth),
undefined,
this.depth + 1,
)
newChild.insert(fileData)
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))
add(file: QuartzPluginData) {
this.insert({ file: file, path: simplifySlug(file.slug!).split("/") })
}
/**
@@ -95,7 +117,6 @@ export class FileNode {
*/
map(mapFn: (node: FileNode) => void) {
mapFn(this)
this.children.forEach((child) => child.map(mapFn))
}
@@ -110,16 +131,16 @@ export class FileNode {
const traverse = (node: FileNode, currentPath: string) => {
if (!node.file) {
const folderPath = currentPath + (currentPath ? "/" : "") + node.name
const folderPath = joinSegments(currentPath, node.name)
if (folderPath !== "") {
folderPaths.push({ path: folderPath, collapsed })
}
node.children.forEach((child) => traverse(child, folderPath))
}
}
traverse(this, "")
return folderPaths
}
@@ -147,14 +168,13 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
const isDefaultOpen = opts.folderDefaultState === "open"
// Calculate current folderPath
let pathOld = fullPath ? fullPath : ""
let folderPath = ""
if (node.name !== "") {
folderPath = `${pathOld}/${node.name}`
folderPath = joinSegments(fullPath ?? "", node.name)
}
return (
<li>
<>
{node.file ? (
// Single file node
<li key={node.file.slug}>
@@ -163,7 +183,7 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
</a>
</li>
) : (
<div>
<li>
{node.name !== "" && (
// Node with entire folder
// Render svg button + folder name, then children
@@ -185,12 +205,16 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
{/* 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">
<a
href={resolveRelative(fileData.slug!, folderPath as SimpleSlug)}
data-for={node.name}
class="folder-title"
>
{node.displayName}
</a>
) : (
<button class="folder-button">
<p class="folder-title">{node.displayName}</p>
<span class="folder-title">{node.displayName}</span>
</button>
)}
</div>
@@ -217,8 +241,8 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
))}
</ul>
</div>
</div>
</li>
)}
</li>
</>
)
}

View File

@@ -1,20 +1,22 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/footer.scss"
import { version } from "../../package.json"
import { i18n } from "../i18n"
interface Options {
links: Record<string, string>
}
export default ((opts?: Options) => {
function Footer({ displayClass }: QuartzComponentProps) {
const Footer: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
const year = new Date().getFullYear()
const links = opts?.links ?? []
return (
<footer class={`${displayClass ?? ""}`}>
<hr />
<p>
Created with <a href="https://quartz.jzhao.xyz/">Quartz v{version}</a>, © {year}
{i18n(cfg.locale).components.footer.createdWith}{" "}
<a href="https://quartz.jzhao.xyz/">Quartz v{version}</a> © {year}
</p>
<ul>
{Object.entries(links).map(([text, link]) => (

View File

@@ -1,7 +1,9 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
// @ts-ignore
import script from "./scripts/graph.inline"
import style from "./styles/graph.scss"
import { i18n } from "../i18n"
import { classNames } from "../util/lang"
export interface D3Config {
drag: boolean
@@ -52,12 +54,12 @@ const defaultOptions: GraphOptions = {
}
export default ((opts?: GraphOptions) => {
function Graph({ displayClass }: QuartzComponentProps) {
const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
return (
<div class={`graph ${displayClass ?? ""}`}>
<h3>Graph View</h3>
<div class={classNames(displayClass, "graph")}>
<h3>{i18n(cfg.locale).components.graph.title}</h3>
<div class="graph-outer">
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
<svg

View File

@@ -1,11 +1,13 @@
import { FullSlug, _stripSlashes, joinSegments, pathToRoot } from "../util/path"
import { i18n } from "../i18n"
import { FullSlug, joinSegments, pathToRoot } from "../util/path"
import { JSResourceToScriptElement } from "../util/resources"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
export default (() => {
function Head({ cfg, fileData, externalResources }: QuartzComponentProps) {
const title = fileData.frontmatter?.title ?? "Untitled"
const description = fileData.description?.trim() ?? "No description provided"
const Head: QuartzComponent = ({ cfg, fileData, externalResources }: QuartzComponentProps) => {
const title = fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
const description =
fileData.description?.trim() ?? i18n(cfg.locale).propertyDefaults.description
const { css, js } = externalResources
const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`)
@@ -19,6 +21,12 @@ export default (() => {
<head>
<title>{title}</title>
<meta charSet="utf-8" />
{cfg.theme.cdnCaching && (
<>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
</>
)}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
@@ -28,8 +36,6 @@ export default (() => {
<link rel="icon" href={iconPath} />
<meta name="description" content={description} />
<meta name="generator" content="Quartz" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
{css.map((href) => (
<link key={href} href={href} rel="stylesheet" type="text/css" spa-preserve />
))}

View File

@@ -1,6 +1,6 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
function Header({ children }: QuartzComponentProps) {
const Header: QuartzComponent = ({ children }: QuartzComponentProps) => {
return children.length > 0 ? <header>{children}</header> : null
}

View File

@@ -3,7 +3,7 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } fro
export default ((component?: QuartzComponent) => {
if (component) {
const Component = component
function MobileOnly(props: QuartzComponentProps) {
const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => {
return <Component displayClass="mobile-only" {...props} />
}

View File

@@ -1,7 +1,7 @@
import { FullSlug, resolveRelative } from "../util/path"
import { QuartzPluginData } from "../plugins/vfile"
import { Date, getDate } from "./Date"
import { QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentProps } from "./types"
import { GlobalConfiguration } from "../cfg"
export function byDateAndAlphabetical(
@@ -29,7 +29,7 @@ type Props = {
limit?: number
} & QuartzComponentProps
export function PageList({ cfg, fileData, allFiles, limit }: Props) {
export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit }: Props) => {
let list = allFiles.sort(byDateAndAlphabetical(cfg))
if (limit) {
list = list.slice(0, limit)
@@ -46,7 +46,7 @@ export function PageList({ cfg, fileData, allFiles, limit }: Props) {
<div class="section">
{page.dates && (
<p class="meta">
<Date date={getDate(cfg, page)!} />
<Date date={getDate(cfg, page)!} locale={cfg.locale} />
</p>
)}
<div class="desc">

View File

@@ -1,11 +1,13 @@
import { pathToRoot } from "../util/path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { classNames } from "../util/lang"
import { i18n } from "../i18n"
function PageTitle({ fileData, cfg, displayClass }: QuartzComponentProps) {
const title = cfg?.pageTitle ?? "Untitled Quartz"
const PageTitle: QuartzComponent = ({ fileData, cfg, displayClass }: QuartzComponentProps) => {
const title = cfg?.pageTitle ?? i18n(cfg.locale).propertyDefaults.title
const baseDir = pathToRoot(fileData.slug!)
return (
<h1 class={`page-title ${displayClass ?? ""}`}>
<h1 class={classNames(displayClass, "page-title")}>
<a href={baseDir}>{title}</a>
</h1>
)

View File

@@ -1,13 +1,15 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { FullSlug, SimpleSlug, resolveRelative } from "../util/path"
import { QuartzPluginData } from "../plugins/vfile"
import { byDateAndAlphabetical } from "./PageList"
import style from "./styles/recentNotes.scss"
import { Date, getDate } from "./Date"
import { GlobalConfiguration } from "../cfg"
import { i18n } from "../i18n"
import { classNames } from "../util/lang"
interface Options {
title: string
title?: string
limit: number
linkToMore: SimpleSlug | false
filter: (f: QuartzPluginData) => boolean
@@ -15,7 +17,6 @@ interface Options {
}
const defaultOptions = (cfg: GlobalConfiguration): Options => ({
title: "Recent Notes",
limit: 3,
linkToMore: false,
filter: () => true,
@@ -23,16 +24,21 @@ const defaultOptions = (cfg: GlobalConfiguration): Options => ({
})
export default ((userOpts?: Partial<Options>) => {
function RecentNotes({ allFiles, fileData, displayClass, cfg }: QuartzComponentProps) {
const RecentNotes: QuartzComponent = ({
allFiles,
fileData,
displayClass,
cfg,
}: QuartzComponentProps) => {
const opts = { ...defaultOptions(cfg), ...userOpts }
const pages = allFiles.filter(opts.filter).sort(opts.sort)
const remaining = Math.max(0, pages.length - opts.limit)
return (
<div class={`recent-notes ${displayClass ?? ""}`}>
<h3>{opts.title}</h3>
<div class={classNames(displayClass, "recent-notes")}>
<h3>{opts.title ?? i18n(cfg.locale).components.recentNotes.title}</h3>
<ul class="recent-ul">
{pages.slice(0, opts.limit).map((page) => {
const title = page.frontmatter?.title
const title = page.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
const tags = page.frontmatter?.tags ?? []
return (
@@ -47,7 +53,7 @@ export default ((userOpts?: Partial<Options>) => {
</div>
{page.dates && (
<p class="meta">
<Date date={getDate(cfg, page)!} />
<Date date={getDate(cfg, page)!} locale={cfg.locale} />
</p>
)}
<ul class="tags">
@@ -69,7 +75,9 @@ export default ((userOpts?: Partial<Options>) => {
</ul>
{opts.linkToMore && remaining > 0 && (
<p>
<a href={resolveRelative(fileData.slug!, opts.linkToMore)}>See {remaining} more </a>
<a href={resolveRelative(fileData.slug!, opts.linkToMore)}>
{i18n(cfg.locale).components.recentNotes.seeRemainingMore({ remaining })}
</a>
</p>
)}
</div>

View File

@@ -1,14 +1,26 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/search.scss"
// @ts-ignore
import script from "./scripts/search.inline"
import { classNames } from "../util/lang"
import { i18n } from "../i18n"
export default (() => {
function Search({ displayClass }: QuartzComponentProps) {
export interface SearchOptions {
enablePreview: boolean
}
const defaultOptions: SearchOptions = {
enablePreview: true,
}
export default ((userOpts?: Partial<SearchOptions>) => {
const Search: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
const opts = { ...defaultOptions, ...userOpts }
const searchPlaceholder = i18n(cfg.locale).components.search.searchBarPlaceholder
return (
<div class={`search ${displayClass ?? ""}`}>
<div class={classNames(displayClass, "search")}>
<div id="search-icon">
<p>Search</p>
<p>{i18n(cfg.locale).components.search.title}</p>
<div></div>
<svg
tabIndex={0}
@@ -32,10 +44,10 @@ export default (() => {
id="search-bar"
name="search"
type="text"
aria-label="Search for something"
placeholder="Search for something"
aria-label={searchPlaceholder}
placeholder={searchPlaceholder}
/>
<div id="results-container"></div>
<div id="search-layout" data-preview={opts.enablePreview}></div>
</div>
</div>
</div>

View File

@@ -1,7 +1,8 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { classNames } from "../util/lang"
function Spacer({ displayClass }: QuartzComponentProps) {
return <div class={`spacer ${displayClass ?? ""}`}></div>
return <div class={classNames(displayClass, "spacer")}></div>
}
export default (() => Spacer) satisfies QuartzComponentConstructor

View File

@@ -1,9 +1,11 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import legacyStyle from "./styles/legacyToc.scss"
import modernStyle from "./styles/toc.scss"
import { classNames } from "../util/lang"
// @ts-ignore
import script from "./scripts/toc.inline"
import { i18n } from "../i18n"
interface Options {
layout: "modern" | "legacy"
@@ -13,15 +15,19 @@ const defaultOptions: Options = {
layout: "modern",
}
function TableOfContents({ fileData, displayClass }: QuartzComponentProps) {
const TableOfContents: QuartzComponent = ({
fileData,
displayClass,
cfg,
}: QuartzComponentProps) => {
if (!fileData.toc) {
return null
}
return (
<div class={`toc ${displayClass ?? ""}`}>
<button type="button" id="toc">
<h3>Table of Contents</h3>
<div class={classNames(displayClass, "toc")}>
<button type="button" id="toc" class={fileData.collapseToc ? "collapsed" : ""}>
<h3>{i18n(cfg.locale).components.tableOfContents.title}</h3>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@@ -54,15 +60,14 @@ function TableOfContents({ fileData, displayClass }: QuartzComponentProps) {
TableOfContents.css = modernStyle
TableOfContents.afterDOMLoaded = script
function LegacyTableOfContents({ fileData }: QuartzComponentProps) {
const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => {
if (!fileData.toc) {
return null
}
return (
<details id="toc" open>
<details id="toc" open={!fileData.collapseToc}>
<summary>
<h3>Table of Contents</h3>
<h3>{i18n(cfg.locale).components.tableOfContents.title}</h3>
</summary>
<ul>
{fileData.toc.map((tocEntry) => (

View File

@@ -1,12 +1,13 @@
import { pathToRoot, slugTag } from "../util/path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { classNames } from "../util/lang"
function TagList({ fileData, displayClass }: QuartzComponentProps) {
const TagList: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
const tags = fileData.frontmatter?.tags
const baseDir = pathToRoot(fileData.slug!)
if (tags && tags.length > 0) {
return (
<ul class={`tags ${displayClass ?? ""}`}>
<ul class={classNames(displayClass, "tags")}>
{tags.map((tag) => {
const display = `#${tag}`
const linkDest = baseDir + `/tags/${slugTag(tag)}`

View File

@@ -1,10 +1,11 @@
import { QuartzComponentConstructor } from "../types"
import { i18n } from "../../i18n"
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
function NotFound() {
function NotFound({ cfg }: QuartzComponentProps) {
return (
<article class="popover-hint">
<h1>404</h1>
<p>Either this page is private or doesn't exist.</p>
<p>{i18n(cfg.locale).pages.error.notFound}</p>
</article>
)
}

View File

@@ -3,7 +3,9 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
function Content({ fileData, tree }: QuartzComponentProps) {
const content = htmlToJsx(fileData.filePath!, tree)
return <article class="popover-hint">{content}</article>
const classes: string[] = fileData.frontmatter?.cssclasses ?? []
const classString = ["popover-hint", ...classes].join(" ")
return <article class={classString}>{content}</article>
}
export default (() => Content) satisfies QuartzComponentConstructor

View File

@@ -3,45 +3,69 @@ import path from "path"
import style from "../styles/listPage.scss"
import { PageList } from "../PageList"
import { _stripSlashes, simplifySlug } from "../../util/path"
import { stripSlashes, simplifySlug } from "../../util/path"
import { Root } from "hast"
import { pluralize } from "../../util/lang"
import { htmlToJsx } from "../../util/jsx"
import { i18n } from "../../i18n"
function FolderContent(props: QuartzComponentProps) {
const { tree, fileData, allFiles } = props
const folderSlug = _stripSlashes(simplifySlug(fileData.slug!))
const allPagesInFolder = allFiles.filter((file) => {
const fileSlug = _stripSlashes(simplifySlug(file.slug!))
const prefixed = fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug
const folderParts = folderSlug.split(path.posix.sep)
const fileParts = fileSlug.split(path.posix.sep)
const isDirectChild = fileParts.length === folderParts.length + 1
return prefixed && isDirectChild
})
const listProps = {
...props,
allFiles: allPagesInFolder,
}
const content =
(tree as Root).children.length === 0
? fileData.description
: htmlToJsx(fileData.filePath!, tree)
return (
<div class="popover-hint">
<article>
<p>{content}</p>
</article>
<p>{pluralize(allPagesInFolder.length, "item")} under this folder.</p>
<div>
<PageList {...listProps} />
</div>
</div>
)
interface FolderContentOptions {
/**
* Whether to display number of folders
*/
showFolderCount: boolean
}
FolderContent.css = style + PageList.css
export default (() => FolderContent) satisfies QuartzComponentConstructor
const defaultOptions: FolderContentOptions = {
showFolderCount: true,
}
export default ((opts?: Partial<FolderContentOptions>) => {
const options: FolderContentOptions = { ...defaultOptions, ...opts }
function FolderContent(props: QuartzComponentProps) {
const { tree, fileData, allFiles, cfg } = props
const folderSlug = stripSlashes(simplifySlug(fileData.slug!))
const allPagesInFolder = allFiles.filter((file) => {
const fileSlug = stripSlashes(simplifySlug(file.slug!))
const prefixed = fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug
const folderParts = folderSlug.split(path.posix.sep)
const fileParts = fileSlug.split(path.posix.sep)
const isDirectChild = fileParts.length === folderParts.length + 1
return prefixed && isDirectChild
})
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
const classes = ["popover-hint", ...cssClasses].join(" ")
const listProps = {
...props,
allFiles: allPagesInFolder,
}
const content =
(tree as Root).children.length === 0
? fileData.description
: htmlToJsx(fileData.filePath!, tree)
return (
<div class={classes}>
<article>
<p>{content}</p>
</article>
<div class="page-listing">
{options.showFolderCount && (
<p>
{i18n(cfg.locale).pages.folderContent.itemsUnderFolder({
count: allPagesInFolder.length,
})}
</p>
)}
<div>
<PageList {...listProps} />
</div>
</div>
</div>
)
}
FolderContent.css = style + PageList.css
return FolderContent
}) satisfies QuartzComponentConstructor

View File

@@ -4,12 +4,12 @@ import { PageList } from "../PageList"
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
import { QuartzPluginData } from "../../plugins/vfile"
import { Root } from "hast"
import { pluralize } from "../../util/lang"
import { htmlToJsx } from "../../util/jsx"
import { i18n } from "../../i18n"
const numPages = 10
function TagContent(props: QuartzComponentProps) {
const { tree, fileData, allFiles } = props
const { tree, fileData, allFiles, cfg } = props
const slug = fileData.slug
if (!(slug?.startsWith("tags/") || slug === "tags")) {
@@ -26,20 +26,24 @@ function TagContent(props: QuartzComponentProps) {
(tree as Root).children.length === 0
? fileData.description
: htmlToJsx(fileData.filePath!, tree)
if (tag === "") {
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
const classes = ["popover-hint", ...cssClasses].join(" ")
if (tag === "/") {
const tags = [
...new Set(
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
),
].sort((a, b) => a.localeCompare(b))
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
for (const tag of tags) {
tagItemMap.set(tag, allPagesWithTag(tag))
}
return (
<div class="popover-hint">
<div class={classes}>
<article>
<p>{content}</p>
</article>
<p>Found {tags.length} total tags.</p>
<p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p>
<div>
{tags.map((tag) => {
const pages = tagItemMap.get(tag)!
@@ -53,16 +57,25 @@ function TagContent(props: QuartzComponentProps) {
return (
<div>
<h2>
<a class="internal tag-link" href={`./${tag}`}>
<a class="internal tag-link" href={`../tags/${tag}`}>
#{tag}
</a>
</h2>
{content && <p>{content}</p>}
<p>
{pluralize(pages.length, "item")} with this tag.{" "}
{pages.length > numPages && `Showing first ${numPages}.`}
</p>
<PageList limit={numPages} {...listProps} />
<div class="page-listing">
<p>
{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}
{pages.length > numPages && (
<>
{" "}
<span>
{i18n(cfg.locale).pages.tagContent.showingFirst({ count: numPages })}
</span>
</>
)}
</p>
<PageList limit={numPages} {...listProps} />
</div>
</div>
)
})}
@@ -77,11 +90,13 @@ function TagContent(props: QuartzComponentProps) {
}
return (
<div class="popover-hint">
<div class={classes}>
<article>{content}</article>
<p>{pluralize(pages.length, "item")} with this tag.</p>
<div>
<PageList {...listProps} />
<div class="page-listing">
<p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p>
<div>
<PageList {...listProps} />
</div>
</div>
</div>
)

View File

@@ -3,9 +3,11 @@ import { QuartzComponent, QuartzComponentProps } from "./types"
import HeaderConstructor from "./Header"
import BodyConstructor from "./Body"
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
import { FullSlug, RelativeURL, joinSegments } from "../util/path"
import { clone, FullSlug, RelativeURL, joinSegments, normalizeHastElement } from "../util/path"
import { visit } from "unist-util-visit"
import { Root, Element } from "hast"
import { Root, Element, ElementContent } from "hast"
import { GlobalConfiguration } from "../cfg"
import { i18n } from "../i18n"
interface RenderComponents {
head: QuartzComponent
@@ -22,7 +24,7 @@ export function pageResources(
staticResources: StaticResources,
): StaticResources {
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())`
return {
css: [joinSegments(baseDir, "index.css"), ...staticResources.css],
@@ -50,38 +52,119 @@ export function pageResources(
}
export function renderPage(
cfg: GlobalConfiguration,
slug: FullSlug,
componentData: QuartzComponentProps,
components: RenderComponents,
pageResources: StaticResources,
): string {
// make a deep copy of the tree so we don't remove the transclusion references
// for the file cached in contentMap in build.ts
const root = clone(componentData.tree) as Root
// process transcludes in componentData
visit(componentData.tree as Root, "element", (node, _index, _parent) => {
visit(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 blockSlug = inner.properties?.["data-slug"] as FullSlug
const blockRef = node.properties!.dataBlock as string
const transcludeTarget = inner.properties["data-slug"] as FullSlug
const page = componentData.allFiles.find((f) => f.slug === transcludeTarget)
if (!page) {
return
}
// TODO: avoid this expensive find operation and construct an index ahead of time
let blockNode = componentData.allFiles.find((f) => f.slug === blockSlug)?.blocks?.[blockRef]
if (blockNode) {
if (blockNode.tagName === "li") {
blockNode = {
type: "element",
tagName: "ul",
children: [blockNode],
let blockRef = node.properties.dataBlock as string | undefined
if (blockRef?.startsWith("#^")) {
// block transclude
blockRef = blockRef.slice("#^".length)
let blockNode = page.blocks?.[blockRef]
if (blockNode) {
if (blockNode.tagName === "li") {
blockNode = {
type: "element",
tagName: "ul",
properties: {},
children: [blockNode],
}
}
node.children = [
normalizeHastElement(blockNode, slug, transcludeTarget),
{
type: "element",
tagName: "a",
properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] },
children: [
{ type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal },
],
},
]
}
} 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 !== undefined) {
endIdx = i
} else if (el.properties?.id === blockRef) {
startIdx = i
}
}
}
if (startIdx === undefined) {
return
}
node.children = [
blockNode,
...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]).map((child) =>
normalizeHastElement(child as Element, slug, transcludeTarget),
),
{
type: "element",
tagName: "a",
properties: { href: inner.properties?.href, class: ["internal"] },
children: [{ type: "text", value: `Link to original` }],
properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] },
children: [
{ type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal },
],
},
]
} else if (page.htmlAst) {
// page transclude
node.children = [
{
type: "element",
tagName: "h1",
properties: {},
children: [
{
type: "text",
value:
page.frontmatter?.title ??
i18n(cfg.locale).components.transcludes.transcludeOf({
targetSlug: page.slug!,
}),
},
],
},
...(page.htmlAst.children as ElementContent[]).map((child) =>
normalizeHastElement(child as Element, slug, transcludeTarget),
),
{
type: "element",
tagName: "a",
properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] },
children: [
{ type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal },
],
},
]
}
@@ -89,6 +172,9 @@ export function renderPage(
}
})
// set componentData.tree to the edited html that has transclusions rendered
componentData.tree = root
const {
head: Head,
header,
@@ -117,8 +203,9 @@ export function renderPage(
</div>
)
const lang = componentData.frontmatter?.lang ?? cfg.locale?.split("-")[0] ?? "en"
const doc = (
<html>
<html lang={lang}>
<Head {...componentData} />
<body data-slug={slug}>
<div id="quartz-root" class="page">

View File

@@ -1,21 +1,21 @@
function toggleCallout(this: HTMLElement) {
const outerBlock = this.parentElement!
outerBlock.classList.toggle(`is-collapsed`)
const collapsed = outerBlock.classList.contains(`is-collapsed`)
outerBlock.classList.toggle("is-collapsed")
const collapsed = outerBlock.classList.contains("is-collapsed")
const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight
outerBlock.style.maxHeight = height + `px`
outerBlock.style.maxHeight = height + "px"
// walk and adjust height of all parents
let current = outerBlock
let parent = outerBlock.parentElement
while (parent) {
if (!parent.classList.contains(`callout`)) {
if (!parent.classList.contains("callout")) {
return
}
const collapsed = parent.classList.contains(`is-collapsed`)
const collapsed = parent.classList.contains("is-collapsed")
const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight
parent.style.maxHeight = height + `px`
parent.style.maxHeight = height + "px"
current = parent
parent = parent.parentElement
@@ -30,15 +30,15 @@ function setupCallout() {
const title = div.firstElementChild
if (title) {
title.removeEventListener(`click`, toggleCallout)
title.addEventListener(`click`, toggleCallout)
title.addEventListener("click", toggleCallout)
window.addCleanup(() => title.removeEventListener("click", toggleCallout))
const collapsed = div.classList.contains(`is-collapsed`)
const collapsed = div.classList.contains("is-collapsed")
const height = collapsed ? title.scrollHeight : div.scrollHeight
div.style.maxHeight = height + `px`
div.style.maxHeight = height + "px"
}
}
}
document.addEventListener(`nav`, setupCallout)
window.addEventListener(`resize`, setupCallout)
document.addEventListener("nav", setupCallout)
window.addEventListener("resize", setupCallout)

View File

@@ -0,0 +1,23 @@
import { getFullSlug } from "../../util/path"
const checkboxId = (index: number) => `${getFullSlug(window)}-checkbox-${index}`
document.addEventListener("nav", () => {
const checkboxes = document.querySelectorAll(
"input.checkbox-toggle",
) as NodeListOf<HTMLInputElement>
checkboxes.forEach((el, index) => {
const elId = checkboxId(index)
const switchState = (e: Event) => {
const newCheckboxState = (e.target as HTMLInputElement)?.checked ? "true" : "false"
localStorage.setItem(elId, newCheckboxState)
}
el.addEventListener("change", switchState)
window.addCleanup(() => el.removeEventListener("change", switchState))
if (localStorage.getItem(elId) === "true") {
el.checked = true
}
})
})

View File

@@ -14,7 +14,7 @@ document.addEventListener("nav", () => {
button.type = "button"
button.innerHTML = svgCopy
button.ariaLabel = "Copy source"
button.addEventListener("click", () => {
function onClick() {
navigator.clipboard.writeText(source).then(
() => {
button.blur()
@@ -26,7 +26,9 @@ document.addEventListener("nav", () => {
},
(error) => console.error(error),
)
})
}
button.addEventListener("click", onClick)
window.addCleanup(() => button.removeEventListener("click", onClick))
els[i].prepend(button)
}
}

View File

@@ -2,31 +2,39 @@ const userPref = window.matchMedia("(prefers-color-scheme: light)").matches ? "l
const currentTheme = localStorage.getItem("theme") ?? userPref
document.documentElement.setAttribute("saved-theme", currentTheme)
const emitThemeChangeEvent = (theme: "light" | "dark") => {
const event: CustomEventMap["themechange"] = new CustomEvent("themechange", {
detail: { theme },
})
document.dispatchEvent(event)
}
document.addEventListener("nav", () => {
const switchTheme = (e: any) => {
if (e.target.checked) {
document.documentElement.setAttribute("saved-theme", "dark")
localStorage.setItem("theme", "dark")
} else {
document.documentElement.setAttribute("saved-theme", "light")
localStorage.setItem("theme", "light")
}
const switchTheme = (e: Event) => {
const newTheme = (e.target as HTMLInputElement)?.checked ? "dark" : "light"
document.documentElement.setAttribute("saved-theme", newTheme)
localStorage.setItem("theme", newTheme)
emitThemeChangeEvent(newTheme)
}
const themeChange = (e: MediaQueryListEvent) => {
const newTheme = e.matches ? "dark" : "light"
document.documentElement.setAttribute("saved-theme", newTheme)
localStorage.setItem("theme", newTheme)
toggleSwitch.checked = e.matches
emitThemeChangeEvent(newTheme)
}
// Darkmode toggle
const toggleSwitch = document.querySelector("#darkmode-toggle") as HTMLInputElement
toggleSwitch.removeEventListener("change", switchTheme)
toggleSwitch.addEventListener("change", switchTheme)
window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme))
if (currentTheme === "dark") {
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
})
colorSchemeMediaQuery.addEventListener("change", themeChange)
window.addCleanup(() => colorSchemeMediaQuery.removeEventListener("change", themeChange))
})

Some files were not shown because too many files have changed in this diff Show More