Compare commits

...

511 Commits
v3 ... v4.0.8

Author SHA1 Message Date
Jacky Zhao
7b7064ad2b fix: ensure code exists inside pre before adding clipboard 2023-08-20 15:38:37 -07:00
Jacky Zhao
ca17af4ae2 fix: dont show index page for folder in its own listing 2023-08-20 15:02:24 -07:00
Jacky Zhao
71471117c5 fix: ci runs on v4 2023-08-20 14:34:00 -07:00
Jacky Zhao
e65ea48fae fix: add async-mutex to builds on large vaults 2023-08-20 14:27:44 -07:00
Jacky Zhao
b99d4cd8ce recent notes css fixes 2023-08-20 14:05:37 -07:00
Jacky Zhao
1bb00e72bb add docs for recent notes 2023-08-20 13:00:33 -07:00
Jacky Zhao
236130ac22 css fixes, add recent notes, more robust quartz update 2023-08-20 12:46:37 -07:00
Jacky Zhao
5adf3c67a8 add engines field 2023-08-20 08:57:56 -07:00
Jacky Zhao
9d77edaf94 fix description not being used in folder and tag listings 2023-08-20 01:08:18 -07:00
Jacky Zhao
0ef1b5b522 update plausible url 2023-08-20 00:54:13 -07:00
Jacky Zhao
cfb7d1232e docs: update notes for tag and folder listings 2023-08-20 00:52:49 -07:00
Jacky Zhao
03fd62496f docs: note about updating default branch 2023-08-20 00:02:41 -07:00
Jacky Zhao
d205eb5686 docs: make setting upstream more clear, docs on npx quartz restore 2023-08-19 22:19:49 -07:00
Jacky Zhao
96a3bfeafb fix: put quotations around font 2023-08-19 22:04:29 -07:00
Jacky Zhao
95fb6ccfcb readme fix 2023-08-19 21:59:20 -07:00
Jacky Zhao
e262482921 fix: string for aliases being treated as array of chars 2023-08-19 21:59:01 -07:00
Jacky Zhao
eb4d3dc5b4 css: fix scrollbars on windows 2023-08-19 21:55:09 -07:00
Jacky Zhao
90d6c1ed24 add git fetch to migration instructions 2023-08-19 21:38:10 -07:00
Jacky Zhao
443c182890 Merge branch 'v4' of https://github.com/jackyzha0/quartz into v4 2023-08-19 21:16:31 -07:00
Jacky Zhao
791b8e2d9f add sponsors 2023-08-19 21:16:24 -07:00
Matt Dunn
a6236d97cf Adding to Showcase page (#367) 2023-08-19 19:15:14 -07:00
Jacky Zhao
b1debaebff update docs 2023-08-19 18:56:45 -07:00
Jacky Zhao
7b8017413c impl baseDir option for quartz build --serve for local testing 2023-08-19 18:04:17 -07:00
Jacky Zhao
6681f28af0 fix trailing slash causing folder listing to not fetch content correctly 2023-08-19 16:55:36 -07:00
Jacky Zhao
78f4cdbe10 avoid 404 on icon for spa navigations with anchors 2023-08-19 16:40:02 -07:00
Jacky Zhao
dd47be1bc6 improve path resolution stability 2023-08-19 16:28:44 -07:00
Jacky Zhao
c874e7e937 base path refactor to better support subpath hosting 2023-08-19 15:52:25 -07:00
Jacky Zhao
3201f83b70 v4-alpha -> v4 2023-08-18 18:24:09 -07:00
Jacky Zhao
d8bec631b6 update docs on github pages and syncing 2023-08-18 18:22:38 -07:00
Jacky Zhao
6f1f820289 fix typo in docs 2023-08-17 23:39:15 -07:00
Jacky Zhao
8bc7a50dfa format 2023-08-17 21:54:42 -07:00
Jacky Zhao
569beb410b ensure sync includes untracked files 2023-08-17 21:49:58 -07:00
Jacky Zhao
5713d30670 ensure contentfolder is passed to popContentFolder 2023-08-17 21:24:41 -07:00
Jacky Zhao
a130945443 fix when symlink targ is calculated and added npx quartz restore 2023-08-17 21:20:15 -07:00
Jacky Zhao
e10f6da011 format 2023-08-17 21:08:26 -07:00
Jacky Zhao
a7cca3242a deref symlink on quartz sync 2023-08-17 21:07:40 -07:00
Jacky Zhao
0998bc355e fix rebuild debouncing 2023-08-17 01:58:11 -07:00
Jacky Zhao
07a327e05a fix back button in spa not working between two pages that both have hash fragments 2023-08-17 01:34:50 -07:00
Jacky Zhao
58d9dc0528 format 2023-08-17 00:55:52 -07:00
Jacky Zhao
0c199975f2 various path fixes for links to extensions, fix relative paths in links 2023-08-17 00:55:28 -07:00
Jacky Zhao
2dc0ae279c fix import paths 2023-08-16 22:09:11 -07:00
Jacky Zhao
2f6747b166 fix relative path resolution in router and link crawling 2023-08-16 22:04:15 -07:00
Sohaib
232652149a Update hosting.md (#371) 2023-08-14 17:59:47 -07:00
Jacky Zhao
7bde99b4e2 fix: add trailing slash to local serving 2023-08-13 17:47:18 -07:00
vintro
f1c9ca495e docs: note about existing content at same path on different branches 2023-08-13 17:19:50 -07:00
Jacky Zhao
4f4b04eeb4 format docs 2023-08-12 21:18:51 -07:00
Jacky Zhao
d6e73f221c fix relative path resolution logic, add more path tests 2023-08-12 21:16:43 -07:00
Jacky Zhao
6d9ffd6da5 404 page styling on local 2023-08-12 21:16:43 -07:00
Jacky Zhao
c89f8b1a9a fix nested callout folding 2023-08-12 21:16:43 -07:00
Sohaib
8fd496bbef Update hosting.md (#368) 2023-08-12 13:52:16 -07:00
Jacky Zhao
aed3f5fccb fmt 2023-08-12 10:17:07 -07:00
Jacky Zhao
c55d54f068 enable rich text in callout title 2023-08-12 10:16:55 -07:00
Jacky Zhao
7bffc2183e include home page in search 2023-08-12 00:24:30 -07:00
Jacky Zhao
827dd91847 format, make search async 2023-08-12 00:03:11 -07:00
Jacky Zhao
e1dd6aee86 fix wikilinks to anchors in the same document 2023-08-11 23:55:17 -07:00
Jacky Zhao
83269ac26e fix scanning for tags in content 2023-08-11 23:40:06 -07:00
Jacky Zhao
ed62ece491 fix broken tag listing links to tags 2023-08-11 23:27:59 -07:00
Jacky Zhao
736c3981c4 fix emit filepaths, tag emit being overriden by content 2023-08-11 23:25:44 -07:00
Jacky Zhao
79e828696a feature docs 2023-08-11 22:47:50 -07:00
Jacky Zhao
259d0a6d9a more documentation 2023-08-11 00:31:44 -07:00
Jacky Zhao
df02ea20d7 spacing fix 2023-08-10 21:32:11 -07:00
Jacky Zhao
21cc6a5da9 run prettier 2023-08-10 21:29:11 -07:00
Jacky Zhao
cefbca4753 docs on making plugins 2023-08-10 21:16:07 -07:00
Jacky Zhao
ad3f7b2d5f format 2023-08-09 09:18:44 -07:00
Jacky Zhao
ebf3263b7e update npx quartz update script 2023-08-09 09:10:40 -07:00
Jacky Zhao
cea6834fef profiling, better concurrency heuristics 2023-08-09 00:26:33 -07:00
Jacky Zhao
68ccd1d79d format 2023-08-08 22:53:01 -07:00
Jacky Zhao
49bd6bc3ff better concurrency debugging, --concurrency flag for npx quartz build 2023-08-08 22:52:49 -07:00
Jacky Zhao
e4950e06a1 fix getFileExtension missing numeric extensions (e.g. mp4) 2023-08-08 21:31:36 -07:00
Jacky Zhao
e21f0f9bb9 change reading time to content meta 2023-08-08 21:28:09 -07:00
Jacky Zhao
ee9ed4f287 fix head.tsx 2023-08-08 20:36:24 -07:00
Jacky Zhao
2706a137a0 guide to creating components 2023-08-08 20:18:31 -07:00
Jacky Zhao
09d4eb0684 fix notes 2023-08-07 23:57:24 -07:00
Jacky Zhao
533d68e642 most of creating components, increase legibility of bold in article and callouts 2023-08-07 23:56:50 -07:00
Jacky Zhao
774a162850 format 2023-08-07 21:51:23 -07:00
Jacky Zhao
2ac5dd49da fix regression in code block font-size boosting on safari mobile 2023-08-07 21:51:06 -07:00
Jacky Zhao
527ce6546e various css fixes, fix new image loading bug when previewing, path docs 2023-08-07 21:41:18 -07:00
Jacky Zhao
d02af6a8ae architecture, fix vendor prefixing 2023-08-07 17:34:38 -07:00
Jacky Zhao
b4cacd5956 format 2023-08-06 22:07:33 -07:00
Jacky Zhao
cd9dc6ecb5 fix css transforms for mobile 2023-08-06 22:07:08 -07:00
Jacky Zhao
d8d9dd22c9 fix shortest path for non-md files, mobile fix 2023-08-06 20:52:17 -07:00
Jacky Zhao
075ac33474 note formatting 2023-08-06 19:54:11 -07:00
Jacky Zhao
3adc73a703 docs upgrade, ci changes 2023-08-06 19:52:30 -07:00
Jacky Zhao
028bcec62c mobile fixes, fix bug when linking to anchor on home, docs 2023-08-06 17:09:29 -07:00
Jacky Zhao
db6054a8c1 format, remove markdown from being procesed 2023-08-05 18:00:52 -07:00
Jacky Zhao
a0d651d64d reverse query param hack to re-add sourcemap support 2023-08-05 17:53:29 -07:00
Jacky Zhao
1da467d214 non-admonition callout fix 2023-08-05 16:43:50 -07:00
Jacky Zhao
7c09627df4 improve hot reload robustness 2023-08-05 15:34:10 -07:00
Jacky Zhao
c402f0c385 more robust error handling, config hotreload 2023-08-05 11:28:09 -07:00
Jacky Zhao
9e76b257d4 fix mermaid initialization 2023-08-04 22:35:21 -07:00
Jacky Zhao
21a7ec2307 bump mathjax version 2023-08-03 23:36:00 -07:00
Jacky Zhao
6423f85614 fix execsync 2023-08-03 23:28:34 -07:00
Jacky Zhao
3a2eae0a16 fix fetch flags 2023-08-03 23:24:34 -07:00
Jacky Zhao
2acfb9e870 format, add upstream 2023-08-03 23:08:04 -07:00
Jacky Zhao
93986c6e7c update pull strategy 2023-08-03 22:29:46 -07:00
Jacky Zhao
4877a9c934 fix callout aliases not being used properly 2023-08-03 00:08:13 -07:00
Jacky Zhao
6457496b4b readme fixes, force 2023-08-02 23:42:49 -07:00
Jacky Zhao
fdf1e2a41d use checkout for pulling updates 2023-08-02 23:29:28 -07:00
Jacky Zhao
663c41fa41 use posix style paths for all path ops 2023-08-02 23:04:26 -07:00
Jacky Zhao
de72dd4e4a format 2023-08-02 22:16:46 -07:00
Jacky Zhao
5537ca41e0 use autostash and pull 2023-08-02 22:16:32 -07:00
Jacky Zhao
558a509164 format 2023-08-02 22:11:46 -07:00
Jacky Zhao
d7842e0ce7 make path and globbing more platform invariant 2023-08-02 22:10:13 -07:00
Jacky Zhao
264ea3d544 add gitattributes for windows 2023-08-02 20:59:56 -07:00
Jacky Zhao
0a33ff7a82 fix test matrix for ci 2023-08-02 20:56:31 -07:00
Jacky Zhao
429f331c21 make ci also run on windows, re-add css minification 2023-08-02 20:53:13 -07:00
Jacky Zhao
9a0f20012a windows patches 2023-08-02 00:07:41 -07:00
Jacky Zhao
c8c108c7f7 change default strategy to be rebase 2023-08-01 23:29:58 -07:00
Jacky Zhao
aaae7d46c2 Merge branch 'v4-alpha' of https://github.com/jackyzha0/quartz into v4-alpha 2023-08-01 22:48:32 -07:00
Jacky Zhao
a70e846b0a flag to allow ofm replace in html embed 2023-08-01 22:47:16 -07:00
Adam Brangenberg
cbae88fc4e Removing redundant properties (#356) 2023-07-30 21:08:32 -07:00
Jacky Zhao
cc79502670 make layouts simpler to think about 2023-07-25 23:37:24 -07:00
Jacky Zhao
45f9087f03 fix checkbox/tasklist styling 2023-07-25 22:27:59 -07:00
Jacky Zhao
1c1a569023 fix formatting 2023-07-25 21:11:06 -07:00
Jacky Zhao
cee2883c08 nested tag support and tag index page 2023-07-25 21:10:37 -07:00
Jacky Zhao
c0278a8c65 font loading options, optimize css 2023-07-24 21:54:47 -07:00
Jacky Zhao
e82ba97a39 actually add processed tag to frontmatter 2023-07-24 00:07:58 -07:00
Jacky Zhao
041a4ce7bc fix watch-mode batching 2023-07-24 00:04:01 -07:00
Jacky Zhao
569ff1a801 npm i on quartz update 2023-07-23 21:53:34 -07:00
Jacky Zhao
351b4ab13b styling fixes for stacking order and overflow 2023-07-23 21:41:09 -07:00
Jacky Zhao
4811500b1b make component resources a proper emitter 2023-07-23 18:20:43 -07:00
Jacky Zhao
236ba56be1 version bump, update doc 2023-07-23 17:59:44 -07:00
Jacky Zhao
7c2bb4ee4c bundleinfo flag, minify scripts 2023-07-23 17:58:35 -07:00
Jacky Zhao
8fd75ffbfd support attachments folder 2023-07-23 17:42:00 -07:00
Jacky Zhao
55a1fb8c41 format 2023-07-23 17:09:12 -07:00
Jacky Zhao
9e83af04a7 refactor static and asset emission to be actual emitter plugins 2023-07-23 17:07:19 -07:00
Jacky Zhao
000eb4c3c0 update feature list 2023-07-23 15:37:06 -07:00
Jacky Zhao
5599eb590e feat: process tags in content 2023-07-23 14:02:57 -07:00
Jacky Zhao
ae2e3b463a improve error handling while serving 2023-07-23 11:49:26 -07:00
Jacky Zhao
fd7c33c537 style fixes for search bar and title on mobile 2023-07-23 11:19:15 -07:00
Jacky Zhao
76fdb3b4d8 fix styles 2023-07-23 11:04:20 -07:00
Jacky Zhao
27a5f7ef8e various typography and styling fixes 2023-07-23 11:02:45 -07:00
Jacky Zhao
ab228748ab oops actually use npm run check 2023-07-22 17:42:13 -07:00
Jacky Zhao
76fa9bbe00 run prettier on ci 2023-07-22 17:39:10 -07:00
Jacky Zhao
7db2eda76c run prettier 2023-07-22 17:27:41 -07:00
Jacky Zhao
2034b970b6 configure prettier 2023-07-22 17:26:03 -07:00
Jacky Zhao
8dd73704e6 hot content reload 2023-07-22 16:06:36 -07:00
Jacky Zhao
b7966ff7fa update features list 2023-07-20 21:51:55 -07:00
Jacky Zhao
01d7d8e554 fix tag pages to emit to tag/index.html to override content and folder pages 2023-07-19 23:03:59 -07:00
Jacky Zhao
83d47f7aaa rename github action 2023-07-19 22:00:44 -07:00
Jacky Zhao
76c092dcf2 add custom.scss 2023-07-19 21:59:48 -07:00
Jacky Zhao
410fc9c8d3 quartz update and quartz sync 2023-07-19 21:59:39 -07:00
Jacky Zhao
8e0ba45789 add link resolution prompt to quartz create 2023-07-16 10:39:35 -07:00
Jacky Zhao
f82282367e treat _index as index 2023-07-15 23:33:06 -07:00
Jacky Zhao
a3e4c86a4c fix ci, disable strict path type checks by default 2023-07-15 23:05:17 -07:00
Jacky Zhao
3ac6b42e16 finish path refactoring, add sourcemap + better trace support 2023-07-15 23:02:12 -07:00
Jacky Zhao
906f91f8ee base path refactor, more docs 2023-07-13 00:19:35 -07:00
Jacky Zhao
08f8e3b4a4 docs + various polish 2023-07-09 19:32:24 -07:00
Jacky Zhao
b90590b9f4 polish 2023-07-08 14:36:02 -07:00
Jacky Zhao
b3480bdc49 fix styling for bullet points 2023-07-06 19:18:18 -07:00
Jacky Zhao
9cbacca2d4 handle dates as tags 2023-07-06 18:45:38 -07:00
Jacky Zhao
05d1ca01c3 handle string tags 2023-07-06 18:32:48 -07:00
Jacky Zhao
f7bf4038dc fix path parsing 2023-07-06 16:56:30 -07:00
Jacky Zhao
465804a389 basic docs, remove publish, add quartz create 2023-07-05 00:16:06 -07:00
Jacky Zhao
92ca787092 fix default callout state 2023-07-04 18:26:11 -07:00
Jacky Zhao
fe2852ff25 update package 2023-07-04 18:08:36 -07:00
Jacky Zhao
974b0da308 folder and tag descriptions, re-enable relative pathing 2023-07-04 18:02:59 -07:00
Jacky Zhao
2a17431460 fix popover zindex 2023-07-04 17:14:15 -07:00
Jacky Zhao
38cff2d670 more visual polish, adjust colours and spacing 2023-07-04 16:48:36 -07:00
Jacky Zhao
ab9da02c60 fix indexing causing main thread freeze, various polish 2023-07-04 10:08:32 -07:00
Jacky Zhao
e0ebee5aa9 various polish 2023-07-02 13:08:29 -07:00
Jacky Zhao
4c904d88ab rss + sitemap 2023-07-01 13:35:27 -07:00
Jacky Zhao
ba9f243728 tag and folder pages 2023-07-01 00:03:01 -07:00
Jacky Zhao
24348b24a9 fix: parsing wikilinks that have codeblock anchors, scroll to anchor 2023-06-19 22:50:25 -07:00
Jacky Zhao
fd5c8d17d3 basic search implementation 2023-06-19 20:37:45 -07:00
Jacky Zhao
c4cf0dcb02 local and global graph 2023-06-18 10:47:07 -07:00
Jacky Zhao
8bfee04c8c popovers 2023-06-17 16:05:46 -07:00
Jacky Zhao
cb89cce183 basic left,right layout 2023-06-17 14:36:06 -07:00
Jacky Zhao
b587782450 collapsible callout 2023-06-17 13:08:06 -07:00
Jacky Zhao
6d5491fdcb collapsible toc 2023-06-17 12:07:40 -07:00
Jacky Zhao
917d5791ac modern toc tweaks 2023-06-16 19:41:59 -07:00
Jacky Zhao
9d2024b11c taglist, mermaid 2023-06-12 22:41:42 -07:00
Jacky Zhao
2bfe90b7e6 add config to components 2023-06-11 23:46:38 -07:00
Jacky Zhao
352075ae81 refactor plugins to be functions instead of classes 2023-06-11 23:26:43 -07:00
Jacky Zhao
b8c011410d toc 2023-06-09 23:06:02 -07:00
Jacky Zhao
3a29f4c86e add custom spa solution 2023-06-09 19:58:58 -07:00
Jacky Zhao
59109a8c1d add flamethrower router 2023-06-07 22:38:45 -07:00
Jacky Zhao
317cce9314 generic quartz component for layout 2023-06-07 22:27:32 -07:00
Jacky Zhao
dde36fa558 update gh actions 2023-06-07 10:52:53 -07:00
Jacky Zhao
1cb4dadf13 codeblock copy 2023-06-06 21:19:00 -07:00
Jacky Zhao
0813f127a3 fix darkmode script load 2023-06-06 20:58:26 -07:00
Jacky Zhao
4d3579ca98 darkmode scripts 2023-06-06 19:48:37 -07:00
Jacky Zhao
89e0311a98 embeds 2023-06-06 00:00:38 -07:00
Jacky Zhao
700036e84c callouts 2023-06-05 22:14:17 -07:00
Jacky Zhao
1406ee0f05 update spinners 2023-06-04 13:37:43 -04:00
Jacky Zhao
9ad89997a5 multi-core builds 2023-06-04 12:35:45 -04:00
Jacky Zhao
4bdc17d4a1 inline scripts 2023-06-03 15:07:19 -04:00
Jacky Zhao
fcd81353f8 heading linking 2023-06-01 19:48:38 -04:00
Jacky Zhao
04eeb2d10c syntax higlighting 2023-06-01 19:05:14 -04:00
Jacky Zhao
42d3a7de17 scss support 2023-06-01 17:35:31 -04:00
Jacky Zhao
c1c46ad67e obsidian flavored markdown support 2023-06-01 12:33:20 -04:00
Jacky Zhao
3636c052eb link processing 2023-05-31 17:41:44 -04:00
Jacky Zhao
21c007e2fc rendering, link resolution, asset copying 2023-05-31 17:01:23 -04:00
Jacky Zhao
ad6ce0d73f plugin integration round 2 2023-05-30 08:02:20 -07:00
Jacky Zhao
a757521313 base setup 2023-05-28 17:44:08 -07:00
BSD-Yassin
7b1da7a845 i18n: Update fr.toml (#313) 2023-04-27 11:12:56 -07:00
Jacky Zhao
e482fa1097 fix: graph and tooltip sometimes not showing 2023-04-06 15:06:01 -07:00
Mattia Ippoliti
ba7a968881 fix: padding for empty title callouts (#308) 2023-04-01 13:50:08 -07:00
Md Jawad Noor Asif
db27557aa3 fix: search highlight not showing because for trailing slash (#306) 2023-03-30 07:14:06 -04:00
Mike Walton
b7c305e002 adding myself to the showcase (#301) 2023-03-23 00:56:20 -05:00
Daniel Lazaro
74fe4d6813 docs: Update link to callouts documentation (#300) 2023-03-18 09:20:56 -07:00
Jacky Zhao
d6c31595b3 deps: bump hugo-obsidian 2023-03-16 10:33:01 -07:00
Jacky Zhao
aa5ab03d4a docs: update to account for github changes 2023-03-02 09:14:29 -08:00
Jacky Zhao
ecba6071b8 deps: bump hugo-obsidian 2023-02-25 13:04:15 -08:00
Jacky Zhao
983efab94c fix: recent notes partial sorting 2023-02-12 16:46:11 -08:00
Dev Uni
10e41743e5 fix: Bad UI due to head.html (#284) 2023-02-07 08:38:20 -08:00
Simon Späti
bde44fadf2 feat: Adding Twitter and Social image preview including description (#207) 2023-02-07 00:16:15 -08:00
Jacky Zhao
6885651f7b feat: max-width for large screens 2023-02-06 12:58:34 -08:00
Jacky Zhao
7df2bb6f5e fix: fix duplicate link click tracking 2023-02-05 12:01:49 -08:00
Jacky Zhao
11959de11c feat: add more plausible events 2023-02-05 11:34:39 -08:00
Jacky Zhao
a73aca8ed9 feat: switch from GA to Plausible for analytics 2023-02-05 10:39:58 -08:00
Adam Brangenberg
93610e232b feat: Remove leading slash of folders in graph view (#282) 2023-02-01 12:34:18 -08:00
Jacky Zhao
712dab5c8c docs: remove broken links from showcase 2023-01-31 11:00:28 -08:00
Olivér Falvai
77b3907b23 docs: Clarify Obsidian settings (#280) 2023-01-31 10:48:20 -08:00
herrwinfried
8fc63586c4 feat: Added Turkish translation (#275) 2023-01-29 12:14:11 -08:00
Apoorv Khandelwal
24c9777a52 feat: Embedding multimodal assets (#274) 2023-01-21 10:01:05 -08:00
Quadrubo
7a8811a184 added the liveReloadPort as an option for docker (#272) 2023-01-18 08:25:01 -08:00
chaosarium
eb2f6aeca8 Fix callout behaviour inconsistent with Obsidian (closes #168) (#268) 2023-01-09 14:14:11 -08:00
Md Jawad Noor Asif
b78008532f feat: Added Bangla translations (#266) 2023-01-09 14:12:52 -08:00
Md Jawad Noor Asif
c5b103c85f fix: fix unicode broken tags (#261) 2023-01-03 22:10:25 -05:00
Adam Brangenberg
614a6222a1 refactor: General performance/style improvements (#262) 2022-12-29 10:43:41 -05:00
chaosarium
dc43737896 fix edge cases link processing (#258)
Fixes https://github.com/jackyzha0/quartz/issues/176
2022-12-24 12:10:59 -05:00
toof
ea37486309 fix: fix misspelling (#259) 2022-12-24 10:38:49 -05:00
chaosarium
c1b0eafce6 feat: Added simplified Chinese translations (#257) 2022-12-22 10:34:21 -08:00
Jacky Zhao
ce5df837f5 feat: latex in search results 2022-12-03 21:03:12 -08:00
Jacky Zhao
4cd6f7efdf fix: text highlighting 2022-11-30 18:00:12 -08:00
Apoorv Khandelwal
5a7936e23a fix: Replacing "internal-link broken" with link to asset (#232) 2022-11-30 17:41:05 -08:00
Jon Erling Hustadnes
5fd707714f feat: Added Norwegian localization (#242) 2022-11-27 10:55:43 -08:00
Filippo Andrea Sighinolfi
717a13a580 feat: Added italian localization in i18n/it.toml (#239) 2022-11-27 10:55:13 -08:00
Brendan Ang
5f3d430699 feat: add support for mermaid diagrams (#244) 2022-11-27 10:53:52 -08:00
Jacky Zhao
66f3e249fe fix: only run docker publish on main repository 2022-11-23 08:34:19 -08:00
Jacky Zhao
e374e3abd4 fix: jump to search for operand 2022-11-21 23:36:27 -08:00
SafEight
f08a76a738 fix: External links ending in .md don't get trimmed (#236)
Co-authored-by: SAF <saf@saf.saf>
fixes https://github.com/jackyzha0/quartz/issues/229
2022-11-21 13:05:46 -08:00
Morgan Gallant
d80f6946c8 fix: Semantic Search: Use Operand Beta API (#235) 2022-11-21 08:54:45 -08:00
Jacky Zhao
120d104230 update config for search 2022-11-20 15:14:48 -08:00
Jacky Zhao
e9aa6ae9e7 feat: docker docs, semantic search alpha 2022-11-20 15:09:58 -08:00
Apoorv Khandelwal
c12af32a5a feat: Dockerfile and automated container build (#230) 2022-11-20 14:03:53 -08:00
SafEight
de2b6b9a1b feat: Replace == with <mark> (#234)
Co-authored-by: SAF <saf@saf.saf>
2022-11-19 13:17:55 -08:00
Jacky Zhao
7f9f58860d feat: allow enableToc to override default no TOC on a per-page basis 2022-11-19 11:18:57 -08:00
jet457
151b9851d6 docs: add Abhijeet's math-wiki to the showcase (#228) 2022-11-19 11:10:41 -08:00
saucecoat
d56a58044d Added German translation (#223) 2022-10-29 23:08:44 -07:00
Conor
689201bfbd feat: Add French translation (#221) 2022-10-26 09:12:35 -07:00
Jacky Zhao
9b72edcd9c Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-10-25 13:14:13 -07:00
Jacky Zhao
8704edcca2 deps: bump ubuntu version (closes #218) 2022-10-25 13:14:06 -07:00
Evan Cater
0a602eda1b fix euler's identity (#220) 2022-10-24 09:13:35 -07:00
Javier Zaleta Martínez
72571a7588 feat: Add Spanish translation (#217) 2022-10-18 17:25:55 -07:00
Charles Chamberlain
3409a49f15 fix: Apply monospace style to all meta in a popover (#216) 2022-10-16 09:43:43 -07:00
Pavol Komlos
666ffebe90 Decode the heading id from split link (#214) 2022-10-12 08:21:28 -07:00
Seth
8ea1525df4 Add SethMB Work (#203) 2022-10-03 11:45:54 -07:00
Jacky Zhao
dd11d56dd9 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-09-23 10:17:34 -07:00
Jacky Zhao
cd7e2088d5 feat: hide TOC when no headers (closes #204) 2022-09-23 10:17:28 -07:00
Simon Späti
169ef442b9 Adding reference projects (#196)
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2022-09-14 10:05:51 -07:00
DongDong Chen
8e3042df49 add my showcase : oldwinterの数字花园 (#192) 2022-09-14 10:05:20 -07:00
Jacky Zhao
2145e92b00 fix: make latex rendering size more simialr to obsidian 2022-09-12 11:08:07 -07:00
Jacky Zhao
e6c7a4e1e2 fix: latex rendering bugs + patch for #195 2022-09-11 18:03:55 -07:00
Nikola Georgiev
ca84da5b31 feat: Hide full path to file in Wikilinks by default (#195) 2022-09-11 17:05:14 -07:00
Jacky Zhao
0d1670adba Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-08-29 14:23:19 -04:00
Jacky Zhao
5c770f965a Update Quartz version in documentation 2022-08-29 14:23:04 -04:00
Andrii Yefremov
ce55eca73b Add Ukrainian translation (#191) 2022-08-29 14:15:18 -04:00
Jacky Zhao
591c4813ec deps: bump hugo-obsidian version 2022-08-28 01:09:52 -04:00
Jacky Zhao
83e7aec3c9 fix: tag list styling 2022-08-24 00:45:08 -04:00
Youssif Shaaban Alsager
25ba1159ad feat: Add internationalization (i18n) support (#182) 2022-08-23 23:32:40 -04:00
Vincent Huang
e38eaa94d6 Popover preview should show relevant heading (#180) 2022-08-20 21:31:06 -04:00
Jacky Zhao
a78926ede5 feat: link previews to page-list (closes #173) 2022-08-11 11:42:16 -07:00
Jacky Zhao
5c76d8dad9 fix: make callout detection case-insensitive (closes #171) 2022-08-05 11:08:52 -07:00
Jacky Zhao
3dcc1f1106 feat: better graph scaling (closes #170) 2022-08-05 11:04:01 -07:00
Jacky Zhao
ff770927fd style: _callouts.scss simplification (#169) 2022-08-04 14:50:24 -07:00
Jacky Zhao
7ffc907907 fix: CJK search (closes #163) 2022-08-03 23:46:55 -07:00
Jacky Zhao
6dd4c64a4c fix: highlights being stripped in non-semantic search mode 2022-08-01 07:59:49 -07:00
Jacky Zhao
8fc6b8e28e docs: update, re-added debounce 2022-07-31 18:21:17 -07:00
Jacky Zhao
b10b23a47b docs: add documentation for Operand Search, remove debounce 2022-07-31 18:02:06 -07:00
Jacky Zhao
23380d0519 fix: title not being selected properly, bump hugo-obsidian for uri fix 2022-07-31 16:55:25 -07:00
Jacky Zhao
dd047305af deps: bump hugo-obsidian to fix bug of writing to non-existent directory during build 2022-07-31 12:33:36 -07:00
Jacky Zhao
54a8fd4a56 deps: bump hugo-obsidian to properly copy linkmap 2022-07-31 12:24:53 -07:00
Jacky Zhao
5ef9aad501 feat: add support for semantic search using operand 2022-07-31 12:16:36 -07:00
Jacky Zhao
14b89105dc refactor: move search utils to util.js 2022-07-31 10:54:23 -07:00
Jacky Zhao
93d039fe7c deps: bump hugo-obsidian version 2022-07-31 10:14:36 -07:00
Jacky Zhao
234c707a93 docs: improve scss structure and admonition styling, update docs 2022-07-30 18:46:19 -07:00
Emile Bangma
728d8529ec Support Admonition callouts (#166) (closes #88) 2022-07-30 17:29:26 -07:00
Jacky Zhao
e142f37e8d Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-07-19 09:03:26 -07:00
Jacky Zhao
d747b19e61 docs: copy edits 2022-07-19 09:03:19 -07:00
Pranav M
1f3da4b829 feat: edit the clipboard button to change border colour on success (#162)
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2022-07-18 08:45:36 -07:00
Jacky Zhao
e15e39155d fix: give precedence to date created over last modified if defined (#101) 2022-07-15 14:26:31 -07:00
Jacky Zhao
dff5ae0d4d style: improve header anchor styling 2022-07-14 13:09:21 -07:00
Jacky Zhao
b2555ced61 feat: add description section to section/term/taxonomies, fix header margin 2022-07-14 12:02:54 -07:00
Jacky Zhao
7ccff2cf3d fix: styling on page-list for smaller screens 2022-07-14 11:49:47 -07:00
Jacky Zhao
e0b6606d50 fix: make section-li scss more generic 2022-07-14 10:38:34 -07:00
Jacky Zhao
d7a42a2fd7 feat: improve styling for lists, fix anchor offset 2022-07-14 10:30:07 -07:00
Jacky Zhao
422b6cc25b feat: css typography improvements 2022-07-13 23:51:33 -07:00
Jacky Zhao
22c8981bb9 feat: css refactor for easy font change 2022-07-13 23:37:54 -07:00
Jacky Zhao
8b2a82a96a fix: change / to use base url 2022-07-13 22:27:13 -07:00
y1450
81af8c459b fix: remove console log (#159) 2022-07-13 15:02:11 -07:00
Jacky Zhao
ffe22689eb feat: use floating-ui for better popover positioning 2022-07-13 15:01:50 -07:00
Jacky Zhao
c1b8fe1221 feat: restyle search icon 2022-07-13 14:32:32 -07:00
Jacky Zhao
b7a619bbd7 fix: tabsize not being respected 2022-07-12 14:37:10 -07:00
Jacky Zhao
74993d19b7 docs + fix: broken partial and description of enableGitHubEdit 2022-07-05 15:42:57 -07:00
rphla
25a4d3b6e1 Add GitHub "edit" button (#157) 2022-07-05 15:39:29 -07:00
Jacky Zhao
aaf31f419e fix: copy code block logic for non code pages 2022-07-03 11:50:13 -07:00
Geoffrey Garrett
f54df35767 Copy to clipboard feature for code block (#152)
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2022-07-03 11:42:35 -07:00
Aiden Bai 白宇彤
015ed4cfa2 Fix width: auto for SPA routing (#156) 2022-07-02 19:40:18 -07:00
Jacky Zhao
a8137edf24 fix: adjust weird colours for err highlighting 2022-07-02 17:14:17 -07:00
Jacky Zhao
eda370334a fix: image scaling for md-style links (closes #155) 2022-07-01 11:27:50 -07:00
Geoffrey Garrett
d3e20b8b94 Added optional rendering of code block titles (#148) 2022-07-01 11:03:52 -07:00
Jacky Zhao
8d7a7b712f fix: non-SPA fn defs (closes #154) 2022-07-01 11:03:04 -07:00
Jacky Zhao
0896814959 docs: remove test image from hosting 2022-06-29 17:35:29 -07:00
Jacky Zhao
8b2fba895a feat: image scaling (closes #131) 2022-06-29 17:34:05 -07:00
Jacky Zhao
e884f4927f fix: anchor formatting (closes #141) 2022-06-29 17:17:53 -07:00
Jacky Zhao
2b0482ae4c docs: fix page weight 2022-06-29 17:03:41 -07:00
Jacky Zhao
8a100edeb8 docs: polish and update 2022-06-29 16:57:36 -07:00
Jacky Zhao
200c605142 feat: enable raw html by default (fixes #143) 2022-06-29 16:16:06 -07:00
Jacky Zhao
f2078ee621 fix: prefix images with base url for non-root quartz 2022-06-29 16:15:40 -07:00
Jacky Zhao
916c51c19c Merge pull request #150 from aidenybai/bump-million 2022-06-28 23:21:25 -07:00
Aiden Bai
67a7ba37e8 Bump million to 1.11.3 2022-06-28 21:43:28 -07:00
Jacky Zhao
72941965ab Merge pull request #146 from geoffreygarrett/hugo 2022-06-27 16:27:57 -07:00
Geoffrey Garrett
b732293f65 fix(head.html): Adds robustness to config.yaml favicon definitions
Initially assumed that `href` definitions should have `/...` as their
pattern, and `baseURL` would always end with `/`, however the omission
of `/` as the prefix of the former and suffix of the latter
simultaneously, would result in broken favicon paths. Final comment:
`..///...` is not breaking, which is worst case scenario with this fix.
2022-06-28 01:21:22 +02:00
Geoffrey Garrett
7070a1992a docs(config.md): Fixed multi-favicon examples and general favicon explanation throughout 2022-06-28 01:15:33 +02:00
Geoffrey Garrett
997937af5a docs(config.md): Added short explainer on favicons 2022-06-28 00:45:48 +02:00
Geoffrey Garrett
a334b45b17 docs(content/notes/config.md): Adds documentation for the new favicon support 2022-06-27 22:05:35 +02:00
Geoffrey Garrett
473ea2c66f feat(layouts/partials/head.html): Adds general favicon support with dict and string input format 2022-06-27 22:04:32 +02:00
Jacky Zhao
34b0353797 Merge pull request #140 from DhammaCharts/hugo 2022-06-07 08:43:52 -07:00
DhammaCharts
52a185f73b change enableGlobalGraph to false 2022-06-06 16:49:01 +01:00
DhammaCharts
69c74ca6b5 minor adjustment 2022-06-06 16:48:16 +01:00
DhammaCharts
ab809249c8 Update layouts/partials/head.html
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2022-06-06 16:42:53 +01:00
DhammaCharts
84c75d0546 Merge branch 'hugo' into hugo 2022-06-06 12:56:47 +01:00
Jacky Zhao
dbd4fb7595 Merge pull request #139 from aidenybai/prerender-latex 2022-06-03 10:59:18 -07:00
DhammaCharts
a275123be2 better font behaviour 2022-06-02 08:35:28 +01:00
DhammaCharts
c88f31c364 change to object destructuring for drawGraph() arguments 2022-06-02 08:16:02 +01:00
DhammaCharts
d261655d96 remove unnecessary ternary 2022-06-02 07:49:09 +01:00
DhammaCharts
c0800a8749 change baseURL back to original 2022-06-02 07:45:44 +01:00
DhammaCharts
ac0dd50c04 uncomment window.Million 2022-06-01 21:30:40 +01:00
DhammaCharts
e809896338 increase scale 2022-06-01 21:22:31 +01:00
DhammaCharts
19606ba63d add www. 2022-06-01 21:19:03 +01:00
DhammaCharts
1e237ef677 change baseURL 2022-06-01 20:15:44 +01:00
DhammaCharts
5a1fbc9374 Improve graph display, options and ability to have a global graph on the home page, local graphs on subpage. 2022-06-01 13:49:27 +01:00
Aiden Bai
a1293f820a Prerender latex 2022-05-29 20:40:44 -07:00
Jacky Zhao
84c6e1efed Merge pull request #138 from aidenybai/add-footer-config 2022-05-28 23:27:54 -07:00
Aiden Bai
8673a7bc3d Add option to toggle footer 2022-05-28 22:52:18 -07:00
Jacky Zhao
775a1b2490 Merge pull request #137 from aidenybai/fix-non-spa-routing 2022-05-27 19:21:05 -07:00
Aiden Bai
006b74ec6f Fix formatting 2022-05-27 18:45:42 -07:00
Aiden Bai
8aba612a00 Fix non-spa fallback 2022-05-27 18:42:01 -07:00
Jacky Zhao
cbc2bea413 Merge pull request #136 from aidenybai/custom-progress-bar-color 2022-05-27 18:32:49 -07:00
Aiden Bai
ae240ff82c Remove redundant CSS rule 2022-05-27 18:31:36 -07:00
Jacky Zhao
ba586adc76 Merge pull request #135 from aidenybai/bump-million 2022-05-27 17:14:55 -07:00
Aiden Bai
159deabfe1 Bump to 1.9.6 2022-05-27 16:14:17 -07:00
Aiden Bai
44984cdaf4 Add support for progress bar 2022-05-27 13:27:13 -07:00
Aiden Bai
683cb53cbd Bump million to 1.9.5 2022-05-27 13:19:19 -07:00
Jacky Zhao
232bd2f016 Merge pull request #134 from aidenybai/add-prefetching-within-graph 2022-05-27 11:01:20 -07:00
Aiden Bai
e0fd9570d7 Bump million to 1.9.4 2022-05-27 09:49:28 -07:00
Aiden Bai
bc32bbeaed Bump milliomn to 1.9.3 2022-05-27 09:02:01 -07:00
Aiden Bai
efb6c7845f Add prefetch to graph 2022-05-27 08:40:00 -07:00
Aiden Bai
bd316d8249 Bump million to 1.9.2 2022-05-27 08:39:44 -07:00
Jacky Zhao
0293c12217 feat: recent posts section/partial 2022-05-23 22:25:13 -07:00
Jacky Zhao
0439c163a0 fix: js not executing if spa disabled 2022-05-20 16:50:56 -04:00
Jacky Zhao
0b6711c218 fix: tag boxes overlapping for content with many tags (closes #130) 2022-05-14 16:47:50 -04:00
Jacky Zhao
ed9a8efd1f fix inline link highlighting, safer latex render 2022-05-05 21:11:23 -04:00
Jacky Zhao
e302f6c423 fix: more generic style to match bad nesting generated by popover interp 2022-05-05 20:35:32 -04:00
Jacky Zhao
b21b27d1d3 fix: clean wikilinks and render latex in popover 2022-05-05 20:30:55 -04:00
Jacky Zhao
364aee36fc fix: merge conf 2022-05-05 01:03:09 -04:00
Jacky Zhao
cea0f3eb74 feat: contextual backlinks (closes #106) 2022-05-05 00:58:50 -04:00
Jacky Zhao
8b855b522a Merge pull request #125 from aidenybai/fix-latex 2022-05-04 11:40:38 -04:00
Aiden Bai
7b3696b877 Remove pnpm debug log 2022-05-04 08:39:25 -07:00
Aiden Bai
b4ff12ca0b Fix latex 2022-05-04 08:10:59 -07:00
Jacky Zhao
b67a389bea Merge pull request #124 from aidenybai/hugo 2022-05-03 13:59:02 -04:00
Aiden Bai
2b5c03c972 Remove redundant URL construction 2022-05-03 10:55:45 -07:00
Aiden Bai
aaed5dc1f1 Support /path root sites 2022-05-03 10:54:39 -07:00
Aiden Bai
1a5d158fce Support active node with other data at end of url 2022-05-03 10:38:41 -07:00
Jacky Zhao
a09974446d Merge pull request #123 from aidenybai/fix-popover 2022-05-03 13:21:32 -04:00
Aiden Bai
9fc71603ba Merge 2022-05-03 10:18:41 -07:00
Aiden Bai
d38f9bec70 Rename API and generalize router API 2022-05-03 10:16:09 -07:00
Aiden Bai
771ebd8031 Merge 2022-05-03 10:07:38 -07:00
Aiden Bai
e4cc625c33 Add future note about init function 2022-05-03 09:34:27 -07:00
Aiden Bai 白宇彤
3789df80e4 Merge branch 'hugo' into fix-popover 2022-05-03 09:33:00 -07:00
Jacky Zhao
037426217c Merge pull request #122 from aidenybai/fix-active-graph-node 2022-05-03 12:29:26 -04:00
Aiden Bai
e646cdb0be Use explicit regex for trailing slash trim 2022-05-03 09:27:25 -07:00
Aiden Bai
8d092a3a4a Remove unnecessary 'url' argument in graph.html 2022-05-03 09:22:51 -07:00
Aiden Bai
32c79a561f Remove unnecessary 'url' argument in graph.html 2022-05-03 09:21:44 -07:00
Aiden Bai
3c660dd9b5 Remove unnecessary 'url' param in drawGraph 2022-05-03 09:20:01 -07:00
Aiden Bai
4cca3c1f2d Peg router version 2022-05-03 09:04:15 -07:00
Aiden Bai
9d3bbd6076 Fix active node on graph 2022-05-03 08:53:18 -07:00
Aiden Bai
9c71f07355 Enable config for testing 2022-05-03 08:48:35 -07:00
Aiden Bai
77485b754d Fix popover 2022-05-03 08:47:42 -07:00
Jacky Zhao
6e6dd4cb0b fix: trim trailing slash when calculating popover 2022-05-03 10:57:20 -04:00
Jacky Zhao
81fe2d2493 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-05-03 10:44:56 -04:00
Jacky Zhao
24d08d580d cfg: make SPA optional 2022-05-03 10:43:22 -04:00
Jacky Zhao
321e19dc41 Merge pull request #121 from benbohmer/patch-1 2022-05-03 09:48:50 -04:00
Jacky Zhao
12d33619a2 Merge pull request #120 from straightupjac/fix/github-info 2022-05-03 09:47:48 -04:00
benbohmer
97607c3ca5 fix: keep / at end of URL to avoid redirects
Removed strings.TrimRight "/" in line 10 to keep the trailing slash at the end of URLs in regular links. This avoids having every single internal link being a 301 redirect.
2022-05-03 09:10:45 +02:00
straightupjac
4197ad460a fix github info 2022-05-03 01:51:15 -04:00
Jacky Zhao
fc89ff2680 fix: broken semi and graph min-height 2022-05-02 13:00:41 -04:00
Jacky Zhao
e9a33c04b5 fmt: remove semis for good 2022-05-02 12:56:44 -04:00
Jacky Zhao
b0e15e0cbc Merge pull request #118 from aidenybai/add-router 2022-05-02 12:19:26 -04:00
Jacky Zhao
9ba0a4b34f fmt: remove semis :) 2022-05-02 12:14:51 -04:00
Aiden Bai
f1b85fb6d9 Fix clarification comment 2022-05-02 09:10:40 -07:00
Jacky Zhao
66304da027 Merge pull request #119 from aidenybai/add-prettier
Add prettier config
2022-05-02 12:06:57 -04:00
Aiden Bai
40d216759c Expand template 2022-05-02 09:05:02 -07:00
Aiden Bai
5c602ab16f Add clarification comments 2022-05-02 09:04:36 -07:00
Aiden Bai
87144fca21 Use semi: false for prettier config 2022-05-02 08:57:25 -07:00
Aiden Bai
a9523dd39b Add prettier config 2022-05-01 22:08:14 -07:00
Aiden Bai
bcb166c21c Add router 2022-05-01 22:06:33 -07:00
Jacky Zhao
416dc0b85c fix: add update for local hugo-obsidian on make update 2022-04-30 13:13:30 -07:00
Jacky Zhao
b8a660e208 feat: copyable header anchors (fixes #86) 2022-04-30 13:10:12 -07:00
Jacky Zhao
ec86cca97b Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-04-28 15:53:57 -07:00
Jacky Zhao
87b5a7a251 feat: show graph titles on zoom (fixes #92) 2022-04-28 15:49:16 -07:00
Jacky Zhao
c8d390dbc5 fix: always hide popover on mobile (fixes #104) 2022-04-28 13:45:29 -07:00
Jacky Zhao
3c7ece5405 fix: append trailing slash, fixes #111 2022-04-28 10:48:31 -07:00
Jacky Zhao
f7027e7ecd Merge pull request #108 from exu3/patch-1 2022-04-20 09:20:21 -07:00
Ella
0cfd93c57c Fix another typo 2022-04-17 02:11:17 -07:00
Ella
3f8c473678 Fix typo: recomment -> recommend 2022-04-17 01:33:16 -07:00
Jacky Zhao
f05ff5e62d fix: add dropshadow to popover, cleanup animation 2022-04-05 23:19:33 -07:00
Jacky Zhao
12ed9722d8 fix: popover selection wrongly including line breaks 2022-04-05 22:43:11 -07:00
Jacky Zhao
887d4d4f5e deps: bump hugo -> v0.96.0 2022-04-05 21:40:59 -07:00
Jacky Zhao
f9c7cdf928 fix: check for src before attempting to add popover 2022-04-05 20:44:39 -07:00
Jacky Zhao
2d55b6ac2e fix: missing whitespace chomp in link render hook 2022-04-05 18:07:40 -07:00
Jacky Zhao
d5884aedb7 fix: wikilink patch not applying to transformed text like apostrophes 2022-04-05 14:14:19 -07:00
Jacky Zhao
66eaa444a4 fix: wikilink image relURL for images with spaces 2022-04-05 14:08:36 -07:00
Jacky Zhao
0ddc48a452 fix: wikilink-like text in code fences #95, #97 2022-04-05 13:47:24 -07:00
Jacky Zhao
cd19159c53 feat: wikilink img support 2022-04-05 12:47:28 -07:00
Jacky Zhao
7808c66c4d fix: align footer links 2022-04-05 09:41:13 -07:00
Jacky Zhao
a7abc6ab96 docs: make update command and clarify update steps/potential danger 2022-04-05 00:09:56 -07:00
Jacky Zhao
9509a64354 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-04-05 00:02:48 -07:00
Jacky Zhao
53242b1e57 add update target to Makefile 2022-04-05 00:02:37 -07:00
Jacky Zhao
3ce6944c18 Merge pull request #93 from meleu/patch-3 2022-04-04 23:56:28 -07:00
Jacky Zhao
3cec4fd950 update screenshot 2022-04-04 23:30:28 -07:00
Jacky Zhao
e245505082 feat: hide toc for short notes 2022-04-04 23:25:24 -07:00
Jacky Zhao
fc4b9ded76 Merge pull request #94 from meleu/patch-4 2022-04-04 23:20:43 -07:00
meleu
27c4761fe0 link to home goes to baseURL 2022-04-04 20:15:40 -03:00
meleu
3583265f80 docs: warn about possible lost of customization 2022-04-04 17:30:23 -03:00
Jacky Zhao
3781b67707 Merge pull request #91 from meleu/patch-2 2022-04-04 13:08:42 -07:00
meleu
671fe05312 padding and border-radius matching bottom cards
Co-authored-by: Jacky Zhao <j.zhao2k19@gmail.com>
2022-04-04 17:07:43 -03:00
Jacky Zhao
1613511f39 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-04-04 09:45:05 -07:00
Jacky Zhao
acab488784 re-add obsidian file 2022-04-04 09:44:58 -07:00
meleu
ff91dcd196 Merge branch 'jackyzha0:hugo' into patch-2 2022-04-03 22:14:12 -03:00
meleu
a287d11246 add a collapsible ToC 2022-04-03 22:12:55 -03:00
Jacky Zhao
575288ece9 Merge pull request #88 from meleu/patch-2 2022-04-03 17:57:46 -07:00
Jacky Zhao
25b5ac43dd fix: favicon not showing on non-root domain #89 2022-04-03 17:43:37 -07:00
meleu
1d9c0e4a44 use "enableToc: false" 2022-04-03 16:31:29 -03:00
meleu
e62d512d95 disable ToC if frontmatter has "enableToc: false" 2022-04-03 16:29:10 -03:00
meleu
8f15c5f8c1 disable ToC if enableToc: false 2022-04-03 16:22:32 -03:00
Jacky Zhao
efeaf9b49c Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-04-03 11:44:39 -07:00
Jacky Zhao
91c4e3fb3a fix: multiline code block #87 2022-04-03 11:44:33 -07:00
Jacky Zhao
22f11711b2 Merge pull request #85 from meleu/patch-1
Ah my git was being really weird with cases :')) thank you
2022-04-03 08:17:13 -07:00
meleu
5c3ef884c7 duplicated file 2022-04-03 11:19:21 -03:00
Jacky Zhao
16b177ce66 README update 2022-04-02 21:04:20 -07:00
Jacky Zhao
14c6181d24 bump hugo version v0.82 -> v0.92.2 2022-04-02 20:37:42 -07:00
Jacky Zhao
e6e04c03c4 fix latex misrendering 2022-04-02 20:34:55 -07:00
Jacky Zhao
146e975932 bump hugo obsidian, fix backlinks for subpathed quartz, update homepage 2022-04-02 20:21:16 -07:00
Jacky Zhao
c117e38899 feat: wikilinks implementation 2022-04-02 20:06:31 -07:00
Jacky Zhao
4fd983277e fix: cjk support + demo page 2022-04-02 17:38:39 -07:00
Jacky Zhao
cc86136bcb feat: basic latex support 2022-04-02 17:00:14 -07:00
Jacky Zhao
8e083d4a93 Merge pull request #83 from meleu/patch-2 2022-04-02 14:53:05 -07:00
meleu
03b574b160 cleanup 2022-04-02 18:51:45 -03:00
meleu
a469653f75 separate contact links semantically 2022-04-02 18:50:58 -03:00
Jacky Zhao
c51573efa9 feat: grey out broken links 2022-04-02 13:34:26 -07:00
Jacky Zhao
902d0f2a0f Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-04-02 12:59:47 -07:00
Jacky Zhao
1ddd15afc6 fix: non-unicode character in popover and search #67, #68 2022-04-02 12:59:38 -07:00
meleu
16f8cd7100 separate links with &ZeroWidthSpace; 2022-04-02 13:37:12 -03:00
Jacky Zhao
9c5ecccf25 Merge pull request #82 from meleu/patch-1 2022-04-01 14:17:35 -07:00
meleu
e3cd531c53 fix custom.scss path 2022-04-01 18:13:49 -03:00
Jacky Zhao
3674df48b8 fix pagination styling 2022-04-01 10:13:01 -07:00
Jacky Zhao
9e8c5587e4 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-03-31 23:16:00 -07:00
Jacky Zhao
6605b13b86 more troubleshooting, backlinks reference private page fix 2022-03-31 23:15:54 -07:00
Jacky Zhao
6edc979896 Merge pull request #71 from siyangsun/patch-1 2022-03-21 09:15:35 -07:00
Siyang
fc43922445 add to showcase and fix link to file 2022-03-20 22:37:05 -07:00
Jacky Zhao
54a68e6e5c patch image 2022-03-18 10:53:39 -07:00
Jacky Zhao
a6ab2f92ef add update 2022-03-16 17:54:24 -07:00
Jacky Zhao
fda481fbb9 fix: bump hugo-obsidian version to account for contentIndex paths on windows 2022-03-15 01:12:08 -07:00
Jacky Zhao
94e987dab5 feat: better titles for empty pages #61 2022-03-15 00:37:56 -07:00
Jacky Zhao
e981c76ed4 Merge pull request #65 from claudio4/fix-text 2022-03-09 10:11:36 -08:00
Claudio Yanes
f70128a3de Prevent overflow of long links and words
When a word (or any string withtout breakpoints (spaces, dashes....), making links the most common place where this becamoes an issue)  is wider than its container, the text will simply overflow any container, including the viewport. This commit fixes this behaviour by making the word-drap strategy of the browser more aggresive.
2022-03-09 17:58:01 +00:00
Jacky Zhao
651bfc5cd2 Merge pull request #62 from claudio4/hugo 2022-03-07 10:45:07 -08:00
Claudio Yanes
6079420178 Merge branch 'jackyzha0-hugo' into hugo 2022-03-07 18:28:14 +00:00
Claudio Yanes
b96c60edfc Merge branch 'hugo' of https://github.com/jackyzha0/quartz into jackyzha0-hugo 2022-03-07 18:27:45 +00:00
Claudio Yanes
978d5ca1ae Format JS 2022-03-07 18:25:02 +00:00
Jacky Zhao
907270992d fix: hide popover on mobile to prevent overflow 2022-03-04 23:55:07 -08:00
Claudio Yanes
6f9283e95b Update makefile and docs
The artifacts produced by hugo-obsidian are now expected to be placed in
the assets/indices directory. This commit reflects this change in the
Makefile and in the docs.
2022-03-04 22:27:21 +00:00
Claudio Yanes
0fad5570d3 Add .gitkeep to assets/indices 2022-03-04 04:14:42 +00:00
Claudio Yanes
dc9b421e21 Remove unnecessary scrollbars
The margin property can escape the parent node and move it alongside
its child. This happens with singlePage div and the body, resulting in
scrollbars appearing as the body has the size of the viewport but
does not align with it. This phenomenon can be always observed
in the vertical axis and it can also be observed in the horizontal axis
when the viewport it’s not wide enough (mostly in mobile).

Using paddings prevents this “extra space” from scraping and displacing
the body.

Also, the value 100vw does not take into account the space taken by the
vertical scrollbar, thus making the body wider than the actual viewport,
producing a horizontal scrollbar.
2022-03-04 04:12:43 +00:00
Claudio Yanes
8779e72c77 Add attribute property to scripts from jsdelivr
Adding the integrity attribute protects the website (by refusing to load
the script) against malicious modifications of the script
in the case of jsdelivr gets hacked
2022-03-04 03:34:45 +00:00
Claudio Yanes
7f6523337c Move popover to the end of the page
The popover script doesn’t ever start in until the DOM has finished
Loading, so wait for the script to be downloaded and parsed before
Showing the content to the user makes no sense.
2022-03-04 03:24:32 +00:00
Claudio Yanes
7e0f2e4449 Fix fetchData
The fetchData function suffer from a race condition. If the function is
called before the promise finishes, it will result in another pair of
HTTP request. This does not only make the function useless but
Actually, it makes it harmful as the data might be redownloaded twice.

Now fetchData is not a function but rather the promise by itself.
Previous callers are expected to await the variable instead, this
should be not concern as awaiting a promise multiple time in
JavaScript is completely safe.
2022-03-04 02:25:30 +00:00
Claudio Yanes
1313bd9779 Move css and js to appropriate files
Having the CSS and JS in the html template produces pages larger
than necessary, as each page need to contain all the js/css.
Separating them in appropriate files allow the browser to just download
them once and use them for all the pages. This is even more effective
with an aggressive cache policy for the js and css, something that can
be done without fear thanks to the implemented cache-busting.
Also, having then in separate files allows us to use Hugo pipelines
for minimizing the code.
2022-03-04 02:07:51 +00:00
Jacky Zhao
5234fae080 fix backlinks not using baseurl 2022-02-28 08:24:29 -08:00
Jacky Zhao
0ee0855e1c bump hugo-obsidian to support root 2022-02-28 07:30:59 -08:00
Jacky Zhao
e06e341468 fix: explicitly set root as current directory to fix ignore files 2022-02-28 07:14:55 -08:00
Jacky Zhao
73e526a7d5 add screenshot to readme 2022-02-23 12:28:25 -05:00
Jacky Zhao
cdc4f1a840 fix: relink search button (move outside content load listener) 2022-02-22 13:36:08 -05:00
Jacky Zhao
714b4fcfa3 fix links being broken for pages with spaces 2022-02-20 21:40:10 -05:00
Jacky Zhao
9c04ca0266 rtl docs 2022-02-17 10:49:41 -05:00
Jacky Zhao
388a2bf78b docs updates 2022-02-17 10:44:39 -05:00
Jacky Zhao
f192f9a23d fix #54: root all image urls 2022-02-15 23:03:02 -05:00
Jacky Zhao
3b3e6ec3b2 fix relative pathing for dynamic fetch 2022-02-15 22:54:20 -05:00
Jacky Zhao
8e85e274f6 change output to static instead of data 2022-02-15 19:42:45 -05:00
Jacky Zhao
fcd5d2807d feat: dynamically fetch indices 2022-02-15 19:39:14 -05:00
Jacky Zhao
4587b13360 feat: add rtl support as part of #47 2022-02-15 17:12:08 -05:00
Jacky Zhao
fb9ea8dcb8 Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-02-15 16:52:49 -05:00
Jacky Zhao
c520db4882 fix: #50, change css load order 2022-02-15 16:52:32 -05:00
Jacky Zhao
10f9843bb6 Merge pull request #51 from brandonkboswell/patch-1 2022-02-15 14:51:29 -05:00
Jacky Zhao
0dc51ff39c Merge branch 'hugo' of https://github.com/jackyzha0/quartz into hugo 2022-02-15 14:50:34 -05:00
Jacky Zhao
c35086c510 visibility fix 2022-02-15 14:50:25 -05:00
Brandon Boswell
31297b7e5a Added to the Showcase 2022-02-12 22:35:03 -05:00
Jacky Zhao
fa3bc3de92 Merge pull request #48 from earnestma/earne/configurable-page-toc 2022-02-11 17:24:54 -05:00
earnest ma
41c443dbf0 Add disableToc parameter to not show TOC on a page 2022-02-11 17:15:28 -05:00
Jacky Zhao
a271fb9d74 Merge pull request #46 from adube/patch-1 2022-01-31 12:28:40 -08:00
Alexandre Dubé
49cdca5dfc Specify Hugo requires extended Sass/SCSS version
Hugo needs to be installed with its "extended" Sass/SCSS version, otherwise this template does not work.
2022-01-31 15:18:26 -05:00
Jacky Zhao
9645f00317 link fixing 2022-01-27 09:38:28 -08:00
Jacky Zhao
57ebf4c21c underscore fix, fix relative path being weird for graph 2022-01-10 13:08:50 -08:00
Jacky Zhao
54e3e071d1 fix popover regex 2022-01-10 09:00:45 -08:00
Jacky Zhao
d46e223831 revert baseurl fix 2022-01-10 08:51:00 -08:00
Jacky Zhao
6f9a29c174 various path fixes 2022-01-10 08:49:29 -08:00
Jacky Zhao
532bc61025 set relativeUrls to true 2022-01-05 19:42:13 -05:00
Jacky Zhao
99aea48260 docs update 2022-01-04 11:39:22 -05:00
195 changed files with 14893 additions and 2091 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

View File

@@ -1,10 +1,9 @@
---
name: Bug report
about: Something about Quartz isn't working the way you expect
title: ''
title: ""
labels: bug
assignees: ''
assignees: ""
---
**Describe the bug**
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -24,9 +24,10 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Device: [e.g. iPhone6]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,10 +1,9 @@
---
name: Feature request
about: Suggest an idea or improvement for Quartz
title: ''
title: ""
labels: enhancement
assignees: ''
assignees: ""
---
**Is your feature request related to a problem? Please describe.**

44
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Build and Test
on:
push:
branches:
- v4
jobs:
build-and-test:
if: ${{ github.repository == 'jackyzha0/quartz' }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
permissions:
contents: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 18
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
- name: Check types and style
run: npm run check
- name: Test
run: npm test
- name: Ensure Quartz builds
run: npx quartz build

View File

@@ -1,36 +0,0 @@
name: Deploy to GitHub Pages
on:
push:
branches:
- hugo
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Build Link Index
uses: jackyzha0/hugo-obsidian@v2.7
with:
index: true
input: content
output: data
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.82.0'
extended: true
- name: Build
run: hugo --minify
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
publish_branch: master # deploying branch
cname: quartz.jzhao.xyz

12
.gitignore vendored
View File

@@ -1,7 +1,9 @@
.DS_Store
.gitignore
node_modules
public
resources
.idea
content/.obsidian
data/linkIndex.yaml
data/contentIndex.yaml
prof
tsconfig.tsbuildinfo
.obsidian
.quartz-cache
private/

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=true

3
.prettierignore Normal file
View File

@@ -0,0 +1,3 @@
public
node_modules
.quartz-cache

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"printWidth": 100,
"quoteProps": "as-needed",
"trailingComma": "all",
"tabWidth": 2,
"semi": false
}

View File

@@ -20,28 +20,28 @@ If you see someone who is making an extra effort to ensure our community is welc
The following behaviors are expected and requested of all community members:
* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
* Exercise consideration and respect in your speech and actions.
* Attempt collaboration before conflict.
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
- Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
- Exercise consideration and respect in your speech and actions.
- Attempt collaboration before conflict.
- Refrain from demeaning, discriminatory, or harassing behavior and speech.
- Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
- Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
## 4. Unacceptable Behavior
The following behaviors are considered harassment and are unacceptable within our community:
* Violence, threats of violence or violent language directed against another person.
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
* Posting or displaying sexually explicit or violent material.
* Posting or threatening to post other people's personally identifying information ("doxing").
* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
* Inappropriate photography or recording.
* Inappropriate physical contact. You should have someone's consent before touching them.
* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
* Deliberate intimidation, stalking or following (online or in person).
* Advocating for, or encouraging, any of the above behavior.
* Sustained disruption of community events, including talks and presentations.
- Violence, threats of violence or violent language directed against another person.
- Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
- Posting or displaying sexually explicit or violent material.
- Posting or threatening to post other people's personally identifying information ("doxing").
- Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
- Inappropriate photography or recording.
- Inappropriate physical contact. You should have someone's consent before touching them.
- Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
- Deliberate intimidation, stalking or following (online or in person).
- Advocating for, or encouraging, any of the above behavior.
- Sustained disruption of community events, including talks and presentations.
## 5. Weapons Policy
@@ -59,14 +59,11 @@ If a community member engages in unacceptable behavior, the community organizers
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. j.zhao2k19@gmail.com.
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
## 8. Addressing Grievances
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify @jackyzha0 with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify @jackyzha0 with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
## 9. Scope
@@ -80,7 +77,7 @@ j.zhao2k19@gmail.com
## 11. License and attribution
The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).

View File

@@ -1,7 +0,0 @@
.DEFAULT_GOAL := serve
help: ## Show all Makefile targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
serve: ## serve
hugo-obsidian -input=content -output=data -index -root=. && hugo server

View File

@@ -1,6 +1,20 @@
# Quartz
Host your own second brain and digital garden for free.
# Quartz v4
> “[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
🔗 Get Started: https://quartz.jzhao.xyz/
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://four.quartz.jzhao.xyz/
[Join the Discord Community](https://discord.gg/cRFFHYye7t)
## Sponsors
<p align="center">
<a href="https://github.com/sponsors/jackyzha0">
<img src="https://cdn.jsdelivr.net/gh/jackyzha0/jackyzha0/sponsorkit/sponsors.svg" />
</a>
</p>

View File

@@ -1,496 +0,0 @@
:root {
--lt-colours-light: var(--light) !important;
--lt-colours-lightgray: var(--lightgray) !important;
--lt-colours-dark: var(--secondary) !important;
--lt-colours-secondary: var(--tertiary) !important;
--lt-colours-gray: var(--outlinegray) !important;
}
h1, h2, h3, h4, ol, ul, thead {
font-family: Inter;
color: var(--dark);
font-weight: revert;
margin: revert;
padding: revert;
}
p, ul, text {
font-family: 'Source Sans Pro', sans-serif;
color: var(--gray);
fill: var(--gray);
font-weight: revert;
margin: revert;
padding: revert;
}
#TableOfContents > ol {
counter-reset: section;
margin-left: 0em;
padding-left: 1.5em;
& > li {
counter-increment: section;
& > ol {
counter-reset: subsection;
& > li {
counter-increment: subsection;
&::marker {
content: counter(section) "." counter(subsection) " ";
}
}
}
}
& > li::marker {
content: counter(section) " ";
}
& > li::marker, & > li > ol > li::marker {
font-family: Source Sans Pro;
font-weight: 700;
}
}
footer {
margin-top: 4em;
text-align: center;
}
table {
width: 100%;
}
img {
width: 100%;
border-radius: 3px;
margin: 1em 0;
}
p>img+em {
display: block;
transform: translateY(-1em);
}
sup {
line-height: 0
}
p, tbody, li {
font-family: Source Sans Pro;
color: var(--gray);
line-height: 1.5em;
}
h2, h3 {
opacity: 0.9;
}
blockquote {
margin-left: 0em;
border-left: 3px solid var(--secondary);
padding-left: 1em;
transition: border-color 0.2s ease;
&:hover {
border-color: var(--tertiary);
}
}
table {
padding: 1.5em;
}
td, th {
padding: 0.1em 0.5em;
}
.footnotes p {
margin: 0.5em 0;
}
.pagination {
list-style: none;
padding-left: 0;
display: flex;
margin-top: 2em;
gap: 1.5em;
justify-content: center;
& > li {
text-align: center;
display: inline-block;
& a {
background-color: transparent !important;
}
& a[href$="#"] {
opacity: 0.2;
}
}
}
.section {
& h3 > a {
font-weight: 700;
font-family: Inter;
margin: 0;
}
& p {
margin-top: 0;
}
}
article {
& > .meta {
margin: -1.5em 0 1em 0;
opacity: 0.7;
}
& > .tags {
list-style: none;
padding-left: 0;
& .meta {
& > h1 {
margin: 0;
}
& > p {
margin: 0;
}
}
& > li {
display: inline-block;
}
& > li > a {
border-radius: 8px;
border: var(--outlinegray) 1px solid;
padding: 0.2em 0.5em;
&::before {
content: "#";
margin-right: 0.3em;
color: var(--outlinegray);
}
}
}
& a {
font-family: Source Sans Pro;
font-weight: 600;
&.internal-link {
text-decoration: none;
background-color: transparentize(#8f9fa9, 0.85);
padding: 0 0.1em;
margin: auto -0.1em;
border-radius: 3px;
}
}
}
.backlinks a {
font-weight: 600;
font-size: 0.9rem;
}
sup > a {
text-decoration: none;
padding: 0 0.1em 0 0.2em;
}
a {
font-family: Inter, sans-serif;
font-size: 1em;
font-weight: 700;
text-decoration: none;
transition: all 0.2s ease;
color: var(--secondary);
&:hover {
color: var(--tertiary) !important;
}
}
pre {
font-family: 'Fira Code';
padding: 0.75em;
border-radius: 3px;
overflow-x: scroll;
}
code {
font-family: 'Fira Code';
font-size: 0.85em;
padding: 0.15em 0.3em;
border-radius: 5px;
background: var(--lightgray);
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
height: 100vh;
width: 100vw;
overflow-x: hidden;
background-color: var(--light);
}
@keyframes fadeIn {
0% {opacity:0;}
100% {opacity:1;}
}
footer {
margin-top: 4em;
& > a {
font-size: 1em;
color: var(--secondary);
padding: 0 0.5em 3em 0.5em;
}
}
hr {
width: 25%;
margin: 4em auto;
height: 2px;
border-radius: 1px;
border-width: 0;
color: var(--dark);
background-color: var(--dark);
}
.singlePage {
margin: 4em 30vw;
@media all and (max-width: 1200px) {
margin: 25px 5vw;
}
}
.page-end {
display: flex;
flex-direction: row;
gap: 2em;
@media all and (max-width: 780px) {
flex-direction: column;
}
& > * {
flex: 1 0 0;
}
& > .backlinks-container {
& > ul {
list-style: none;
padding-left: 0;
& > li {
margin: 0.5em 0;
padding: 0.25em 1em;
border: var(--outlinegray) 1px solid;
border-radius: 5px
}
}
}
& #graph-container {
border: var(--outlinegray) 1px solid;
border-radius: 5px;
}
}
.centered {
margin-top: 30vh;
}
article > h1 {
font-size: 2em;
}
header {
display: flex;
flex-direction: row;
align-items: center;
& > h1 {
font-size: 2em;
}
& > nav {
@media all and (max-width: 600px) {
display: none;
}
}
& > .spacer {
flex: 1 1 auto;
}
& > svg {
cursor: pointer;
width: 18px;
min-width: 18px;
margin: 0 1em;
&:hover .search-path {
stroke: var(--tertiary);
}
.search-path {
stroke: var(--gray);
stroke-width: 2px;
transition: stroke 0.5s ease;
}
}
}
#search-container {
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100vw;
height: 100%;
overflow: scroll;
display: none;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
& > div {
width: 50%;
margin-top: 15vh;
margin-left: auto;
margin-right: auto;
@media all and (max-width: 1200px) {
width: 90%;
}
& > * {
width: 100%;
border-radius: 4px;
background: var(--light);
box-shadow: 0 14px 50px rgba(27, 33, 48, 0.12), 0 10px 30px rgba(27, 33, 48, 0.16);
margin-bottom: 2em;
}
& > input {
box-sizing: border-box;
padding: 0.5em 1em;
font-family: Inter, sans-serif;
color: var(--dark);
font-size: 1.1em;
border: 1px solid var(--outlinegray);
&:focus {
outline: none;
}
}
& > #results-container {
& > .result-card {
padding: 1em;
cursor: pointer;
transition: background 0.2s ease;
border: 1px solid var(--outlinegray);
border-bottom: none;
width: 100%;
// normalize button props
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
overflow: visible;
text-transform: none;
text-align: left;
background: var(--light);
outline: none;
&:hover, &:focus {
background: rgba(180, 180, 180, 0.15);
}
&:first-of-type {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
&:last-of-type {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom: 1px solid var(--outlinegray);
}
& > h3, & > p {
margin: 0;
}
& .search-highlight {
background-color: #afbfc966;
padding: 0.05em 0.2em;
border-radius: 3px;
}
}
}
}
}
.section-ul {
list-style: none;
padding-left: 0;
& > li {
border: 1px solid var(--outlinegray);
border-radius: 5px;
padding: 0 1em;
margin-bottom: 1em;
& h3 {
opacity: 1;
font-weight: 700;
margin-bottom: 0em;
}
& .meta {
opacity: 0.6;
}
}
}
.popover {
z-index: 999;
position: absolute;
width: 15em;
display: inline;
background-color: var(--light);
padding: 1em;
border: 1px solid var(--outlinegray);
border-radius: 5px;
transform: translate(-50%, 40%);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
transition-delay: 0.3s;
user-select: none;
&.visible {
opacity: 1;
transform: translate(-50%, 20%);
}
& > h3 {
margin: 0.5em 0;
}
& > p {
margin: 0;
font-weight: 400;
}
}

View File

@@ -1,24 +0,0 @@
// Add your own CSS here!
:root {
--light: #faf8f8;
--dark: #141021;
--secondary: #284b63;
--tertiary: #84a59d;
--visited: #afbfc9;
--primary: #f28482;
--gray: #4e4e4e;
--lightgray: #f0f0f0;
--outlinegray: #dadada;
}
[saved-theme="dark"] {
--light: #1e1e21 !important;
--dark: #fbfffe !important;
--secondary: #6b879a !important;
--visited: #4a575e !important;
--tertiary: #84a59d !important;
--primary: #f58382 !important;
--gray: #d4d4d4 !important;
--lightgray: #292633 !important;
--outlinegray: #343434 !important;
}

View File

@@ -1,29 +0,0 @@
const userPref = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
const currentTheme = localStorage.getItem('theme') ?? userPref
if (currentTheme) {
document.documentElement.setAttribute('saved-theme', currentTheme);
}
const switchTheme = (e) => {
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')
}
}
window.addEventListener('DOMContentLoaded', () => {
// Darkmode toggle
const toggleSwitch = document.querySelector('#darkmode-toggle')
// listen for toggle
toggleSwitch.addEventListener('change', switchTheme, false)
if (currentTheme === 'dark') {
toggleSwitch.checked = true
}
})

View File

@@ -1,99 +0,0 @@
/* Background */ .chroma { color: #f8f8f2; background-color: #282a36; overflow: hidden }
/* Other */ .chroma .x { }
/* Error */ .chroma .err { }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* Keyword */ .chroma .k { color: #ff79c6 }
/* KeywordConstant */ .chroma .kc { color: #ff79c6 }
/* KeywordDeclaration */ .chroma .kd { color: #8be9fd; font-style: italic }
/* KeywordNamespace */ .chroma .kn { color: #ff79c6 }
/* KeywordPseudo */ .chroma .kp { color: #ff79c6 }
/* KeywordReserved */ .chroma .kr { color: #ff79c6 }
/* KeywordType */ .chroma .kt { color: #8be9fd }
/* Name */ .chroma .n { }
/* NameAttribute */ .chroma .na { color: #50fa7b }
/* NameBuiltin */ .chroma .nb { color: #8be9fd; font-style: italic }
/* NameBuiltinPseudo */ .chroma .bp { }
/* NameClass */ .chroma .nc { color: #50fa7b }
/* NameConstant */ .chroma .no { }
/* NameDecorator */ .chroma .nd { }
/* NameEntity */ .chroma .ni { }
/* NameException */ .chroma .ne { }
/* NameFunction */ .chroma .nf { color: #50fa7b }
/* NameFunctionMagic */ .chroma .fm { }
/* NameLabel */ .chroma .nl { color: #8be9fd; font-style: italic }
/* NameNamespace */ .chroma .nn { }
/* NameOther */ .chroma .nx { }
/* NameProperty */ .chroma .py { }
/* NameTag */ .chroma .nt { color: #ff79c6 }
/* NameVariable */ .chroma .nv { color: #8be9fd; font-style: italic }
/* NameVariableClass */ .chroma .vc { color: #8be9fd; font-style: italic }
/* NameVariableGlobal */ .chroma .vg { color: #8be9fd; font-style: italic }
/* NameVariableInstance */ .chroma .vi { color: #8be9fd; font-style: italic }
/* NameVariableMagic */ .chroma .vm { }
/* Literal */ .chroma .l { }
/* LiteralDate */ .chroma .ld { }
/* LiteralString */ .chroma .s { color: #f1fa8c }
/* LiteralStringAffix */ .chroma .sa { color: #f1fa8c }
/* LiteralStringBacktick */ .chroma .sb { color: #f1fa8c }
/* LiteralStringChar */ .chroma .sc { color: #f1fa8c }
/* LiteralStringDelimiter */ .chroma .dl { color: #f1fa8c }
/* LiteralStringDoc */ .chroma .sd { color: #f1fa8c }
/* LiteralStringDouble */ .chroma .s2 { color: #f1fa8c }
/* LiteralStringEscape */ .chroma .se { color: #f1fa8c }
/* LiteralStringHeredoc */ .chroma .sh { color: #f1fa8c }
/* LiteralStringInterpol */ .chroma .si { color: #f1fa8c }
/* LiteralStringOther */ .chroma .sx { color: #f1fa8c }
/* LiteralStringRegex */ .chroma .sr { color: #f1fa8c }
/* LiteralStringSingle */ .chroma .s1 { color: #f1fa8c }
/* LiteralStringSymbol */ .chroma .ss { color: #f1fa8c }
/* LiteralNumber */ .chroma .m { color: #bd93f9 }
/* LiteralNumberBin */ .chroma .mb { color: #bd93f9 }
/* LiteralNumberFloat */ .chroma .mf { color: #bd93f9 }
/* LiteralNumberHex */ .chroma .mh { color: #bd93f9 }
/* LiteralNumberInteger */ .chroma .mi { color: #bd93f9 }
/* LiteralNumberIntegerLong */ .chroma .il { color: #bd93f9 }
/* LiteralNumberOct */ .chroma .mo { color: #bd93f9 }
/* Operator */ .chroma .o { color: #ff79c6 }
/* OperatorWord */ .chroma .ow { color: #ff79c6 }
/* Punctuation */ .chroma .p { }
/* Comment */ .chroma .c { color: #6272a4 }
/* CommentHashbang */ .chroma .ch { color: #6272a4 }
/* CommentMultiline */ .chroma .cm { color: #6272a4 }
/* CommentSingle */ .chroma .c1 { color: #6272a4 }
/* CommentSpecial */ .chroma .cs { color: #6272a4 }
/* CommentPreproc */ .chroma .cp { color: #ff79c6 }
/* CommentPreprocFile */ .chroma .cpf { color: #ff79c6 }
/* Generic */ .chroma .g { }
/* GenericDeleted */ .chroma .gd { color: #8b080b }
/* GenericEmph */ .chroma .ge { text-decoration: underline }
/* GenericError */ .chroma .gr { }
/* GenericHeading */ .chroma .gh { font-weight: bold }
/* GenericInserted */ .chroma .gi { font-weight: bold }
/* GenericOutput */ .chroma .go { color: #44475a }
/* GenericPrompt */ .chroma .gp { }
/* GenericStrong */ .chroma .gs { }
/* GenericSubheading */ .chroma .gu { font-weight: bold }
/* GenericTraceback */ .chroma .gt { }
/* GenericUnderline */ .chroma .gl { text-decoration: underline }
/* TextWhitespace */ .chroma .w { }
.lntd:first-of-type > .chroma {
padding-right: 0;
}
.chroma code {
font-family: 'Fira Code' !important;
font-size: 0.85em;
line-height: 1em;
background: none;
padding: 0;
}
.chroma {
border-radius: 3px;
margin: 0;
}

View File

@@ -1,30 +0,0 @@
baseURL = "https://quartz.jzhao.xyz/"
languageCode = "en-us"
googleAnalytics = "G-XYFD95KB4J"
pygmentsUseClasses = true
relativeURLs = false
disablePathToLower = true
ignoreFiles = [
"/content/templates/*",
"/content/private/*",
]
summaryLength = 20
paginate = 10
enableGitInfo = true
[markup]
[markup.tableOfContents]
endLevel = 3
ordered = true
startLevel = 2
[markup.highlight]
anchorLineNos = false
codeFences = true
guessSyntax = true
hl_Lines = ""
lineAnchors = ""
lineNoStart = 1
lineNos = true
lineNumbersInTable = true
style = "dracula"
tabWidth = 4

View File

@@ -1,25 +0,0 @@
---
title: 🪴 Quartz 3
---
Host your second brain and [digital garden](https://jzhao.xyz/posts/digital-gardening) for free. Quartz features
1. Extremely fast full-text search by pressing `/`
2. Display for backlinks of each note
3. Fully customizable local graph view
4. Endlessly powerful page and theme customization using CSS
5. Automatically generated tag and section lists of content
6. Beautiful link previews
## Get Started
> 📚 [Setup your own digital garden using Quartz](notes/setup.md)
Not convinced yet? Look at some [community digital gardens](moc/showcase) built with Quartz, or read about [why I made Quartz](notes/philosophy.md) to begin with!
## Content Lists
If you prefer browsing the contents of this site through a list instead of a graph, you can find content lists here too:
- [All Notes](/notes)
- [Setup-related Notes](/tags/setup)
## Troubleshooting
- 🚧 [Troubleshooting and FAQ](notes/troubleshooting.md)
- 🐛 [Submit an Issue](https://github.com/jackyzha0/quartz/issues)

View File

@@ -0,0 +1,52 @@
---
title: Architecture
---
Quartz is a static site generator. How does it work?
This question is best answered by tracing what happens when a user (you!) runs `npx quartz build` in the command line:
## On the server
1. After running `npx quartz build`, npm will look at `package.json` to find the `bin` entry for `quartz` which points at `./quartz/bootstrap-cli.mjs`.
2. This file has a [shebang](<https://en.wikipedia.org/wiki/Shebang_(Unix)>) line at the top which tells npm to execute it using Node.
3. `bootstrap-cli.mjs` is responsible for a few things:
1. Parsing the command-line arguments using [yargs](http://yargs.js.org/).
2. Transpiling and bundling the rest of Quartz (which is in Typescript) to regular JavaScript using [esbuild](https://esbuild.github.io/). The `esbuild` configuration here is slightly special as it also handles `.scss` file imports using [esbuild-sass-plugin v2](https://www.npmjs.com/package/esbuild-sass-plugin). Additionally, we bundle 'inline' client-side scripts (any `.inline.ts` file) that components declare using a custom `esbuild` plugin that runs another instance of `esbuild` which bundles for the browser instead of `node`. Modules of both types are imported as plain text.
3. Running the local preview server if `--serve` is set. This starts two servers:
1. A WebSocket server on port 3001 to handle hot-reload signals. This tracks all inbound connections and sends a 'rebuild' message a server-side change is detected (either content or configuration).
2. An HTTP file-server on a user defined port (normally 8080) to serve the actual website files.
4. If the `--serve` flag is set, it also starts a file watcher to detect source-code changes (e.g. anything that is `.ts`, `.tsx`, `.scss`, or packager files). On a change, we rebuild the module (step 2 above) using esbuild's [rebuild API](https://esbuild.github.io/api/#rebuild) which drastically reduces the build times.
5. After transpiling the main Quartz build module (`quartz/build.ts`), we write it to a cache file `.quartz-cache/transpiled-build.mjs` and then dynamically import this using `await import(cacheFile)`. However, we need to be pretty smart about how to bust Node's [import cache](https://github.com/nodejs/modules/issues/307) so we add a random query string to fake Node into thinking it's a new module. This does, however, cause memory leaks so we just hope that the user doesn't hot-reload their configuration too many times in a single session :)) (it leaks about ~350kB memory on each reload). After importing the module, we then invoke it, passing in the command line arguments we parsed earlier along with a callback function to signal the client to refresh.
4. In `build.ts`, we start by installing source map support manually to account for the query string cache busting hack we introduced earlier. Then, we start processing content:
1. Clean the output directory.
2. Recursively glob all files in the `content` folder, respecting the `.gitignore`.
3. Parse the Markdown files.
1. Quartz detects the number of threads available and chooses to spawn worker threads if there are >128 pieces of content to parse (rough heuristic). If it needs to spawn workers, it will invoke esbuild again to transpile the worker script `quartz/worker.ts`. Then, a work-stealing [workerpool](https://www.npmjs.com/package/workerpool) is then created and batches of 128 files are assigned to workers.
2. Each worker (or just the main thread if there is no concurrency) creates a [unified](https://github.com/unifiedjs/unified) parser based off of the plugins defined in the [[configuration]].
3. Parsing has three steps:
1. Read the file into a [vfile](https://github.com/vfile/vfile).
2. Applied plugin-defined text transformations over the content.
3. Slugify the file path and store it in the data for the file. See the page on [[paths]] for more details about how path logic works in Quartz (spoiler: its complicated).
4. Markdown parsing using [remark-parse](https://www.npmjs.com/package/remark-parse) (text to [mdast](https://github.com/syntax-tree/mdast)).
5. Apply plugin-defined Markdown-to-Markdown transformations.
6. Convert Markdown into HTML using [remark-rehype](https://github.com/remarkjs/remark-rehype) ([mdast](https://github.com/syntax-tree/mdast) to [hast](https://github.com/syntax-tree/hast)).
7. Apply plugin-defined HTML-to-HTML transformations.
4. Filter out unwanted content using plugins.
5. Emit files using plugins.
1. Gather all the static resources (e.g. external CSS, JS modules, etc.) each emitter plugin declares.
2. Emitters that emit HTML files do a bit of extra work here as they need to transform the [hast](https://github.com/syntax-tree/hast) produced in the parse step to JSX. This is done using [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) with the [Preact](https://preactjs.com/) runtime. Finally, the JSX is rendered to HTML using [preact-render-to-string](https://github.com/preactjs/preact-render-to-string) which statically renders the JSX to HTML (i.e. doesn't care about `useState`, `useEffect`, or any other React/Preact interactive bits). Here, we also do a bunch of fun stuff like assemble the page [[layout]] from `quartz.layout.ts`, assemble all the inline scripts that actually get shipped to the client, and all the transpiled styles. The bulk of this logic can be found in `quartz/components/renderPage.tsx`. Other fun things of note:
1. CSS is minified and transformed using [Lightning CSS](https://github.com/parcel-bundler/lightningcss) to add vendor prefixes and do syntax lowering.
2. Scripts are split into `beforeDOMLoaded` and `afterDOMLoaded` and are inserted in the `<head>` and `<body>` respectively.
3. Finally, each emitter plugin is responsible for emitting and writing it's own emitted files to disk.
6. If the `--serve` flag was detected, we also set up another file watcher to detect content changes (only `.md` files). We keep a content map that tracks the parsed AST and plugin data for each slug and update this on file changes. Newly added or modified paths are rebuilt and added to the content map. Then, all the filters and emitters are run over the resulting content map. This file watcher is debounced with a threshold of 250ms. On success, we send a client refresh signal using the passed in callback function.
## On the client
1. The browser opens a Quartz page and loads the HTML. The `<head>` also links to page styles (emitted to `public/index.css`) and page-critical JS (emitted to `public/prescript.js`)
2. Then, once the body is loaded, the browser loads the non-critical JS (emitted to `public/postscript.js`)
3. Once the page is done loading, the page will then dispatch a custom synthetic browser event `"nav"`. This is used so client-side scripts declared by components can 'setup' anything that requires access to the page DOM.
1. If the [[SPA Routing|enableSPA option]] is enabled in the [[configuration]], this `"nav"` event is also fired on any client-navigation to allow for components to unregister and reregister any event handlers and state.
2. If it's not, we wire up the `"nav"` event to just be fired a single time after page load to allow for consistency across how state is setup across both SPA and non-SPA contexts.
The architecture and design of the plugin system was intentionally left pretty vague here as this is described in much more depth in the guide on [[making plugins|making your own plugin]].

View File

@@ -0,0 +1,233 @@
---
title: Creating your own Quartz components
---
> [!warning]
> This guide assumes you have experience writing JavaScript and are familiar with TypeScript.
Normally on the web, we write layout code using HTML which looks something like the following:
```html
<article>
<h1>An article header</h1>
<p>Some content</p>
</article>
```
This piece of HTML represents an article with a leading header that says "An article header" and a paragraph that contains the text "Some content". This is combined with CSS to style the page and JavaScript to add interactivity.
However, HTML doesn't let you create reusable templates. If you wanted to create a new page, you would need to copy and paste the above snippet and edit the header and content yourself. This isn't great if we have a lot of content on our site that shares a lot of similar layout. The smart people who created React also had similar complaints and invented the concept of Components -- JavaScript functions that return JSX -- to solve the code duplication problem.
In effect, components allow you to write a JavaScript function that takes some data and produces HTML as an output. **While Quartz doesn't use React, it uses the same component concept to allow you to easily express layout templates in your Quartz site.**
## An Example Component
### Constructor
Component files are written in `.tsx` files that live in the `quartz/components` folder. These are re-exported in `quartz/components/index.ts` so you can use them in layouts and other components more easily.
Each component file should have a default export that satisfies the `QuartzComponentConstructor` function signature. It's a function that takes in a single optional parameter `opts` and returns a Quartz Component. The type of the parameters `opts` is defined by the interface `Options` which you as the component creator also decide.
In your component, you can use the values from the configuration option to change the rendering behaviour inside of your component. For example, the component in the code snippet below will not render if the `favouriteNumber` option is below 0.
```tsx {11-17}
interface Options {
favouriteNumber: number
}
const defaultOptions: Options = {
favouriteNumber: 42,
}
export default ((userOpts?: Options) => {
const opts = { ...userOpts, ...defaultOpts }
function YourComponent(props: QuartzComponentProps) {
if (opts.favouriteNumber < 0) {
return null
}
return <p>My favourite number is {opts.favouriteNumber}</p>
}
return YourComponent
}) satisfies QuartzComponentConstructor
```
### Props
The Quartz component itself (lines 11-17 highlighted above) looks like a React component. It takes in properties (sometimes called [props](https://react.dev/learn/passing-props-to-a-component)) and returns JSX.
All Quartz components accept the same set of props:
```tsx title="quartz/components/types.ts"
// simplified for sake of demonstration
export type QuartzComponentProps = {
fileData: QuartzPluginData
cfg: GlobalConfiguration
tree: Node<QuartzPluginData>
allFiles: QuartzPluginData[]
displayClass?: "mobile-only" | "desktop-only"
}
```
- `fileData`: Any metadata [[making plugins|plugins]] may have added to the current page.
- `fileData.slug`: slug of the current page.
- `fileData.frontmatter`: any frontmatter parsed.
- `cfg`: The `configuration` field in `quartz.config.ts`.
- `tree`: the resulting [HTML AST](https://github.com/syntax-tree/hast) after processing and transforming the file. This is useful if you'd like to render the content using [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) (you can find an example of this in `quartz/components/pages/Content.tsx`).
- `allFiles`: Metadata for all files that have been parsed. Useful for doing page listings or figuring out the overall site structure.
- `displayClass`: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting. Helpful if you want to conditionally hide a component on mobile or desktop.
### Styling
Quartz components can also define a `.css` property on the actual function component which will get picked up by Quartz. This is expected to be a CSS string which can either be inlined or imported from a `.scss` file.
Note that inlined styles **must** be plain vanilla CSS:
```tsx {6-10} title="quartz/components/YourComponent.tsx"
export default (() => {
function YourComponent() {
return <p class="red-text">Example Component</p>
}
YourComponent.css = `
p.red-text {
color: red;
}
`
return YourComponent
}) satisfies QuartzComponentConstructor
```
Imported styles, however, can be from SCSS files:
```tsx {1-2,9} title="quartz/components/YourComponent.tsx"
// assuming your stylesheet is in quartz/components/styles/YourComponent.scss
import styles from "./styles/YourComponent.scss"
export default (() => {
function YourComponent() {
return <p>Example Component</p>
}
YourComponent.css = styles
return YourComponent
}) satisfies QuartzComponentConstructor
```
> [!warning]
> Quartz does not use CSS modules so any styles you declare here apply _globally_. If you only want it to apply to your component, make sure you use specific class names and selectors.
### Scripts and Interactivity
What about interactivity? Suppose you want to add an-click handler for example. Like the `.css` property on the component, you can also declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties that are strings that contain the script.
```tsx title="quartz/components/YourComponent.tsx"
export default (() => {
function YourComponent() {
return <button id="btn">Click me</button>
}
YourComponent.beforeDOM = `
console.log("hello from before the page loads!")
`
YourComponent.afterDOM = `
document.getElementById('btn').onclick = () => {
alert('button clicked!')
}
`
return YourComponent
}) satisfies QuartzComponentConstructor
```
> [!hint]
> For those coming from React, Quartz components are different from React components in that it only uses JSX for templating and layout. Hooks like `useEffect`, `useState`, etc. are not rendered and other properties that accept functions like `onClick` handlers will not work. Instead, do it using a regular JS script that modifies the DOM element directly.
As the names suggest, the `.beforeDOMLoaded` scripts are executed _before_ the page is done loading so it doesn't have access to any elements on the page. This is mostly used to prefetch any critical data.
The `.afterDOMLoaded` script executes once the page has been completely loaded. This is a good place to setup anything that should last for the duration of a site visit (e.g. getting something saved from local storage).
If you need to create an `afterDOMLoaded` script that depends on _page specific_ elements that may change when navigating to a new page, you can listen for the `"nav"` event that gets fired whenever a page loads (which may happen on navigation if [[SPA Routing]] is enabled).
```ts
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)
})
```
It is best practice to also unmount any existing event handlers to prevent memory leaks.
#### Importing Code
Of course, it isn't always practical (nor desired!) to write your code as a string literal in the component.
Quartz supports importing component code through `.inline.ts` files.
```tsx title="quartz/components/YourComponent.tsx"
// @ts-ignore: typescript doesn't know about our inline bundling system
// so we need to silence the error
import script from "./scripts/graph.inline"
export default (() => {
function YourComponent() {
return <button id="btn">Click me</button>
}
YourComponent.afterDOM = script
return YourComponent
}) satisfies QuartzComponentConstructor
```
```ts title="quartz/components/scripts/graph.inline.ts"
// any imports here are bundled for the browser
import * as d3 from "d3"
document.getElementById("btn").onclick = () => {
alert("button clicked!")
}
```
Additionally, like what is shown in the example above, you can import packages in `.inline.ts` files. This will be bundled by Quartz and included in the actual script.
### Using a Component
After creating your custom component, re-export it in `quartz/components/index.ts`:
```ts title="quartz/components/index.ts" {4,10}
import ArticleTitle from "./ArticleTitle"
import Content from "./pages/Content"
import Darkmode from "./Darkmode"
import YourComponent from "./YourComponent"
export { ArticleTitle, Content, Darkmode, YourComponent }
```
Then, you can use it like any other component in `quartz.layout.ts` via `Component.YourComponent()`. See the [[configuration#Layout|layout]] section for more details.
As Quartz components are just functions that return React components, you can compositionally use them in other Quartz components.
```tsx title="quartz/components/AnotherComponent.tsx"
import YourComponent from "./YourComponent"
export default (() => {
function AnotherComponent(props: QuartzComponentProps) {
return (
<div>
<p>It's nested!</p>
<YourComponent {...props} />
</div>
)
}
return AnotherComponent
}) satisfies QuartzComponentConstructor
```
> [!hint]
> Look in `quartz/components` for more examples of components in Quartz as reference for your own components!

View File

@@ -0,0 +1,302 @@
---
title: Making your own plugins
---
> [!warning]
> This part of the documentation will assume you have working knowledge in TypeScript and will include code snippets that describe the interface of what Quartz plugins should look like.
Quartz's plugins are a series of transformations over content. This is illustrated in the diagram of the processing pipeline below:
![[quartz transform pipeline.png]]
All plugins are defined as a function that takes in a single parameter for options `type OptionType = object | undefined` and return an object that corresponds to the type of plugin it is.
```ts
type OptionType = object | undefined
type QuartzPlugin<Options extends OptionType = undefined> = (opts?: Options) => QuartzPluginInstance
type QuartzPluginInstance =
| QuartzTransformerPluginInstance
| QuartzFilterPluginInstance
| QuartzEmitterPluginInstance
```
The following sections will go into detail for what methods can be implemented for each plugin type. Before we do that, let's clarify a few more ambiguous types:
- `BuildCtx` is defined in `quartz/ctx.ts`. It consists of
- `argv`: The command line arguments passed to the Quartz [[build]] command
- `cfg`: The full Quartz [[configuration]]
- `allSlugs`: a list of all the valid content slugs (see [[paths]] for more information on what a `ServerSlug` is)
- `StaticResources` is defined in `quartz/resources.tsx`. It consists of
- `css`: a list of URLs for stylesheets that should be loaded
- `js`: a list of scripts that should be loaded. A script is described with the `JSResource` type which is also defined in `quartz/resources.tsx`. It allows you to define a load time (either before or after the DOM has been loaded), whether it should be a module, and either the source URL or the inline content of the script.
## Transformers
Transformers **map** over content, taking a Markdown file and outputting modified content or adding metadata to the file itself.
```ts
export type QuartzTransformerPluginInstance = {
name: string
textTransform?: (ctx: BuildCtx, src: string | Buffer) => string | Buffer
markdownPlugins?: (ctx: BuildCtx) => PluggableList
htmlPlugins?: (ctx: BuildCtx) => PluggableList
externalResources?: (ctx: BuildCtx) => Partial<StaticResources>
}
```
All transformer plugins must define at least a `name` field to register the plugin and a few optional functions that allow you to hook into various parts of transforming a single Markdown file.
- `textTransform` performs a text-to-text transformation _before_ a file is parsed into the [Markdown AST](https://github.com/syntax-tree/mdast).
- `markdownPlugins` defines a list of [remark plugins](https://github.com/remarkjs/remark/blob/main/doc/plugins.md). `remark` is a tool that transforms Markdown to Markdown in a structured way.
- `htmlPlugins` defines a list of [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md). Similar to how `remark` works, `rehype` is a tool that transforms HTML to HTML in a structured way.
- `externalResources` defines any external resources the plugin may need to load on the client-side for it to work properly.
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:
```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 { QuartzTransformerPlugin } from "../types"
interface Options {
renderEngine: "katex" | "mathjax"
}
export const Latex: QuartzTransformerPlugin<Options> = (opts?: Options) => {
const engine = opts?.renderEngine ?? "katex"
return {
name: "Latex",
markdownPlugins() {
return [remarkMath]
},
htmlPlugins() {
if (engine === "katex") {
// if you need to pass options into a plugin, you
// can use a tuple of [plugin, options]
return [[rehypeKatex, { output: "html" }]]
} else {
return [rehypeMathjax]
}
},
externalResources() {
if (engine === "katex") {
return {
css: ["https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css"],
js: [
{
src: "https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/contrib/copy-tex.min.js",
loadTime: "afterDOMReady",
contentType: "external",
},
],
}
} else {
return {}
}
},
}
}
```
Another common thing that transformer plugins will do is parse a file and add extra data for that file:
```ts
export const AddWordCount: QuartzTransformerPlugin = () => {
return {
name: "AddWordCount",
markdownPlugins() {
return [
() => {
return (tree, file) => {
// tree is an `mdast` root element
// file is a `vfile`
const text = file.value
const words = text.split(" ").length
file.data.wordcount = words
}
},
]
},
}
}
// tell typescript about our custom data fields we are adding
// other plugins will then also be aware of this data field
declare module "vfile" {
interface DataMap {
wordcount: number
}
}
```
Finally, you can also perform transformations over Markdown or HTML ASTs using the `visit` function from the `unist-util-visit` package or the `findAndReplace` function from the `mdast-util-find-and-replace` package.
```ts
export const TextTransforms: QuartzTransformerPlugin = () => {
return {
name: "TextTransforms",
markdownPlugins() {
return [() => {
return (tree, file) => {
// replace _text_ with the italics version
findAndReplace(tree, /_(.+)_/, (_value: string, ...capture: string[]) => {
// inner is the text inside of the () of the regex
const [inner] = capture
// return an mdast node
// https://github.com/syntax-tree/mdast
return {
type: "emphasis",
children: [{ type: 'text', value: inner }]
}
})
// remove all links (replace with just the link content)
// match by 'type' field on an mdast node
// https://github.com/syntax-tree/mdast#link in this example
visit(tree, "link", (link: Link) => {
return {
type: "paragraph"
children: [{ type: 'text', value: link.title }]
}
})
}
}]
}
}
}
```
All transformer plugins can be found under `quartz/plugins/transformers`. If you decide to write your own transformer plugin, don't forget to re-export it under `quartz/plugins/transformers/index.ts`
A parting word: transformer plugins are quite complex so don't worry if you don't get them right away. Take a look at the built in transformers and see how they operate over content to get a better sense for how to accomplish what you are trying to do.
## Filters
Filters **filter** content, taking the output of all the transformers and determining what files to actually keep and what to discard.
```ts
export type QuartzFilterPlugin<Options extends OptionType = undefined> = (
opts?: Options,
) => QuartzFilterPluginInstance
export type QuartzFilterPluginInstance = {
name: string
shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean
}
```
A filter plugin must define a `name` field and a `shouldPublish` function that takes in a piece of content that has been processed by all the transformers and returns a `true` or `false` depending on whether it should be passed to the emitter plugins or not.
For example, here is the built-in plugin for removing drafts:
```ts title="quartz/plugins/filters/draft.ts"
import { QuartzFilterPlugin } from "../types"
export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({
name: "RemoveDrafts",
shouldPublish(_ctx, [_tree, vfile]) {
// uses frontmatter parsed from transformers
const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false
return !draftFlag
},
})
```
## Emitters
Emitters **reduce** over content, taking in a list of all the transformed and filtered content and creating output files.
```ts
export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
opts?: Options,
) => QuartzEmitterPluginInstance
export type QuartzEmitterPluginInstance = {
name: string
emit(
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
emitCallback: EmitCallback,
): 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.
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:
```ts
export type EmitCallback = (data: {
// the name of the file to emit (not including the file extension)
slug: ServerSlug
// the file extension
ext: `.${string}` | ""
// the file content to add
content: string
}) => Promise<FilePath>
```
This is a thin wrapper around writing to the appropriate output folder and ensuring that intermediate directories exist. If you choose to use the native Node `fs` APIs, ensure you emit to the `argv.output` folder as well.
If you are creating an emitter plugin that needs to render components, there are three more things to be aware of:
- Your component should use `getQuartzComponents` to declare a list of `QuartzComponents` that it uses to construct the page. See the page on [[creating components]] for more information.
- You can use the `renderPage` function defined in `quartz/components/renderPage.tsx` to render Quartz components into HTML.
- If you need to render an HTML AST to JSX, you can use the `toJsxRuntime` function from `hast-util-to-jsx-runtime` library. An example of this can be found in `quartz/components/pages/Content.tsx`.
For example, the following is a simplified version of the content page plugin that renders every single page.
```tsx title="quartz/plugins/emitters/contentPage.tsx"
export const ContentPage: QuartzEmitterPlugin = () => {
// construct the layout
const layout: FullPageLayout = {
...sharedPageComponents,
...defaultContentPageLayout,
pageBody: Content(),
}
const { head, header, beforeBody, pageBody, left, right, footer } = layout
return {
name: "ContentPage",
getQuartzComponents() {
return [head, ...header, ...beforeBody, pageBody, ...left, ...right, footer]
},
async emit(ctx, content, resources, emit): Promise<FilePath[]> {
const cfg = ctx.cfg.configuration
const fps: FilePath[] = []
const allFiles = content.map((c) => c[1].data)
for (const [tree, file] of content) {
const slug = canonicalizeServer(file.data.slug!)
const externalResources = pageResources(slug, resources)
const componentData: QuartzComponentProps = {
fileData: file.data,
externalResources,
cfg,
children: [],
tree,
allFiles,
}
const content = renderPage(slug, componentData, opts, externalResources)
const fp = await emit({
content,
slug: file.data.slug!,
ext: ".html",
})
fps.push(fp)
}
return fps
},
}
}
```
Note that it takes in a `FullPageLayout` as the options. It's made by combining a `SharedLayout` and a `PageLayout` both of which are provided through the `quartz.layout.ts` file.
> [!hint]
> Look in `quartz/plugins` for more examples of plugins in Quartz as reference for your own plugins!

51
content/advanced/paths.md Normal file
View File

@@ -0,0 +1,51 @@
---
title: Paths in Quartz
---
Paths are pretty complex to reason about because, especially for a static site generator, they can come from so many places.
A full file path to a piece of content? Also a path. What about a slug for a piece of content? Yet another path.
It would be silly to type these all as `string` and call it a day as it's pretty common to accidentally mistake one type of path for another. Unfortunately, TypeScript does not have [nominal types](https://en.wikipedia.org/wiki/Nominal_type_system) for type aliases meaning even if you made custom types of a server-side slug or a client-slug slug, you can still accidentally assign one to another and TypeScript wouldn't catch it.
Luckily, we can mimic nominal typing using [brands](https://www.typescriptlang.org/play#example/nominal-typing).
```typescript
// instead of
type FullSlug = string
// we do
type FullSlug = string & { __brand: "full" }
// that way, the following will fail typechecking
const slug: FullSlug = "some random string"
```
While this prevents most typing mistakes _within_ our nominal typing system (e.g. mistaking a server slug for a client slug), it doesn't prevent us from _accidentally_ mistaking a string for a client slug when we forcibly cast it.
Thus, we still need to be careful when casting from a string to one of these nominal types in the 'entrypoints', illustrated with hexagon shapes in the diagram below.
The following diagram draws the relationships between all the path sources, nominal path types, and what functions in `quartz/path.ts` convert between them.
```mermaid
graph LR
Browser{{Browser}} --> Window{{Body}} & LinkElement{{Link Element}}
Window --"getFullSlug()"--> FullSlug[Full Slug]
LinkElement --".href"--> Relative[Relative URL]
FullSlug --"simplifySlug()" --> SimpleSlug[Simple Slug]
SimpleSlug --"pathToRoot()"--> Relative
SimpleSlug --"resolveRelative()" --> Relative
MD{{Markdown File}} --> FilePath{{File Path}} & Links[Markdown links]
Links --"transformLink()"--> Relative
FilePath --"slugifyFilePath()"--> FullSlug[Full Slug]
style FullSlug stroke-width:4px
```
Here are the main types of slugs with a rough description of each type of path:
- `FilePath`: a real file path to a file on disk. Cannot be relative and must have a file extension.
- `FullSlug`: cannot be relative and may not have leading or trailing slashes. It can have `index` as it's last segment. Use this wherever possible is it's the most 'general' interpretation of a slug.
- `SimpleSlug`: cannot be relative and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path.
- `RelativeURL`: must start with `.` or `..` to indicate it's a relative URL. Shouldn't have `/index` as an ending or a file extension but can contain a trailing slash.
To get a clearer picture of how these relate to each other, take a look at the path tests in `quartz/path.test.ts`.

View File

@@ -0,0 +1,48 @@
---
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.
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.
Got everything setup? Let's [[build]] and preview your Quartz locally!
## Syntax
As Quartz uses Markdown files as the main way of writing content, it fully supports Markdown syntax. By default, Quartz also ships with a few syntax extensions like [Github Flavored Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) (footnotes, strikethrough, tables, tasklists) and [Obsidian Flavored Markdown](https://help.obsidian.md/Editing+and+formatting/Obsidian+Flavored+Markdown) ([[callouts]], [[wikilinks]]).
Additionally, Quartz also allows you to specify additional metadata in your notes called **frontmatter**.
```md title="content/note.md"
---
title: Example Title
draft: false
tags:
- example-tag
---
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.
- `aliases`: Other names for this note. This is a list of strings.
- `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 you're Quartz is at a point you're happy with, you can save your changes to GitHub by doing `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

23
content/build.md Normal file
View File

@@ -0,0 +1,23 @@
---
title: "Building your Quartz"
---
Once you've [[index#🪴 Get Started|initialized]] Quartz, let's see what it looks like locally:
```bash
npx quartz build --serve
```
This will start a local web server to run your Quartz on your computer. Open a web browser and visit `http://localhost:8080/` to view it.
> [!hint] Flags and options
> For full help options, you can run `npx quartz build --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
> - `-o` or `--output`: the output folder. This is normally just `public`
> - `--serve`: run a local hot-reloading server to preview your Quartz
> - `--port`: what port to run the local preview server on
> - `--concurrency`: how many threads to use to parse notes

81
content/configuration.md Normal file
View File

@@ -0,0 +1,81 @@
---
title: Configuration
---
Quartz is meant to be extremely configurable, even if you don't know any coding. Most of the configuration you should need can be done by just editing `quartz.config.ts` or changing [[layout|the layout]] in `quartz.layout.ts`.
> [!tip]
> If you edit Quartz configuration using a text-editor that has TypeScript language support like VSCode, it will warn you when you you've made an error in your configuration, helping you avoid configuration mistakes!
The configuration of Quartz can be broken down into two main parts:
```ts title="quartz.config.ts"
const config: QuartzConfig = {
configuration: { ... },
plugins: { ... },
}
```
## General Configuration
This part of the configuration concerns anything that can affect the whole site. The following is a list breaking down all the things you can configure:
- `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site.
- `enableSPA`: whether to enable [[SPA Routing]] on your 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
- `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`
- 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.
- `theme`: configure how the site looks.
- `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.
- `body`: Font for everything
- `colors`: controls the theming of the site.
- `light`: page background
- `lightgray`: borders
- `gray`: graph links, heavier borders
- `darkgray`: body text
- `dark`: header text and icons
- `secondary`: link colour, current [[graph view|graph]] node
- `tertiary`: hover states and visited [[graph view|graph]] nodes
- `highlight`: internal link background, highlighted text, [[syntax highlighting|highlighted lines of code]]
## Plugins
You can think of Quartz plugins as a series of transformations over content.
![[quartz transform pipeline.png]]
```ts
plugins: {
transformers: [...],
filters: [...],
emitters: [...],
}
```
- [[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)
By adding, removing, and reordering plugins from the `tranformers`, `filters`, and `emitters` fields, you can customize the behaviour of Quartz.
> [!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.
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.
```ts
transformers: [
Plugin.FrontMatter(), // uses default options
Plugin.Latex({ renderEngine: "katex" }), // specify some options
]
```
If you'd like to make your own plugins, read the guide on [[making plugins]] for more information.

63
content/features/Latex.md Normal file
View File

@@ -0,0 +1,63 @@
---
tags:
- plugin/transformer
---
Quartz uses [Katex](https://katex.org/) by default to typeset both inline and block math expressions at build time.
## Syntax
### Block Math
Block math can be rendered by delimiting math expression with `$$`.
```
$$
f(x) = \int_{-\infty}^\infty
f\hat(\xi),e^{2 \pi i \xi x}
\,d\xi
$$
```
$$
f(x) = \int_{-\infty}^\infty
f\hat(\xi),e^{2 \pi i \xi x}
\,d\xi
$$
$$
\begin{aligned}
a &= b + c \\ &= e + f \\
\end{aligned}
$$
$$
\begin{bmatrix}
1 & 2 & 3 \\
a & b & c
\end{bmatrix}
$$
### 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$
### Escaping symbols
There will be cases where you may have more than one `$` in a paragraph at once which may accidentally trigger MathJax/Katex.
To get around this, you can escape the dollar sign by doing `\$` instead.
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
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' })`
## Customization
- Removing Latex support: remove all instances of `Plugin.Latex()` from `quartz.config.ts`.
- Plugin: `quartz/plugins/transformers/latex.ts`

View File

@@ -0,0 +1,28 @@
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()`.
## Syntax
To add a Mermaid diagram, create a mermaid code block.
````
```mermaid
sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!
```
````
```mermaid
sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!
```

View File

@@ -0,0 +1,31 @@
---
tags:
- plugin/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]].
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.
Finally, Quartz also provides `Plugin.CrawlLinks` 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`

View File

@@ -0,0 +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.
## Configuration
- Remove RSS feed: set the `enableRSS` field of `Plugin.ContentIndex` in `quartz.config.ts` to be `false`.

View File

@@ -0,0 +1,7 @@
Single-page-app style rendering. This prevents flashes of unstyled content and improves the smoothness of Quartz.
Under the hood, this is done by hijacking page navigations and instead fetching the HTML via a `GET` request and then diffing and selectively replacing parts of the page using [micromorph](https://github.com/natemoo-re/micromorph). This allows us to change the content of the page without fully refreshing the page, reducing the amount of content that the browser needs to load.
## Configuration
- Disable SPA Routing: set the `enableSPA` field of the [[configuration]] in `quartz.config.ts` to be `false`.

View File

@@ -0,0 +1,14 @@
---
title: Backlinks
tags:
- component
---
A backlink for a note is a link from another note to that note. Links in the backlink pane also feature rich [[popover previews]] if you have that feature enabled.
## Customization
- Removing backlinks: delete all usages of `Component.Backlinks()` from `quartz.layout.ts`.
- Component: `quartz/components/Backlinks.tsx`
- Style: `quartz/components/styles/backlinks.scss`
- Script: `quartz/components/scripts/search.inline.ts`

View File

@@ -0,0 +1,86 @@
---
title: Callouts
tags:
- plugin/transformer
---
Quartz supports the same Admonition-callout syntax as Obsidian.
This includes
- 12 Distinct callout types (each with several aliases)
- Collapsable callouts
```
> [!info] Title
> This is a callout!
```
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()`.
## Customization
- Disable callouts: simply pass `callouts: false` to the plugin: `Plugin.ObsidianFlavoredMarkdown({ callouts: false })`
- Editing icons: `quartz/plugins/transformers/ofm.ts`
## Showcase
> [!info]
> Default title
> [!question]+ Can callouts be nested?
>
> > [!todo]- Yes!, they can.
> >
> > > [!example] You can even use multiple layers of nesting.
> [!EXAMPLE] Examples
>
> Aliases: example
> [!note] Notes
>
> Aliases: note
> [!abstract] Summaries
>
> Aliases: abstract, summary, tldr
> [!info] Info
>
> Aliases: info, todo
> [!tip] Hint
>
> Aliases: tip, hint, important
> [!success] Success
>
> Aliases: success, check, done
> [!question] Question
>
> Aliases: question, help, faq
> [!warning] Warning
>
> Aliases: warning, caution, attention
> [!failure] Failure
>
> Aliases: failure, fail, missing
> [!danger] Error
>
> Aliases: danger, error
> [!bug] Bug
>
> Aliases: bug
> [!quote] Quote
>
> Aliases: quote, cite

View File

@@ -0,0 +1,14 @@
---
title: "Darkmode"
tags:
- component
---
Quartz supports darkmode out of the box that respects the user's theme preference. Any future manual toggles of the darkmode switch will be saved in the browser's local storage so it can be persisted across future page loads.
## Customization
- Removing darkmode: delete all usages of `Component.Darkmode()` from `quartz.layout.ts`.
- Component: `quartz/components/Darkmode.tsx`
- Style: `quartz/components/styles/darkmode.scss`
- Script: `quartz/components/scripts/darkmode.inline.ts`

View File

@@ -0,0 +1,32 @@
---
title: Folder and Tag Listings
tags:
- plugin/emitter
---
Quartz creates 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`.
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.
For example, for the folder `content/posts`, you can add another file `content/posts/index.md` to add a specific description for it.
## Tag Listings
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.
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.
## 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`

View File

@@ -0,0 +1,28 @@
---
title: Full-text Search
tags:
- component
---
Full-text search in Quartz is powered by [Flexsearch](https://github.com/nextapps-de/flexsearch). It's fast enough to return search results in under 10ms for Quartzs as large as half a million words.
It can be opened by either clicking on the search bar or pressing ⌘+K. The top 5 search results are shown on each query. Matching subterms are highlighted and the most relevant 30 words are excerpted. Clicking on a search result will navigate to that page.
This component is also keyboard accessible: Tab and Shift+Tab will cycle forward and backward through search results and Enter will navigate to the highlighted result (first result by default).
> [!info]
> Search requires the `ContentIndex` emitter plugin to be present in the [[configuration]].
### Indexing Behaviour
By default, it indexes every page on the site with **Markdown syntax removed**. This means link URLs for instance are not indexed.
It properly tokenizes Chinese, Korean, and Japenese characters and constructs separate indexes for the title and content, weighing title matches above content matches.
## Customization
- Removing search: delete all usages of `Component.Search()` from `quartz.layout.ts`.
- Component: `quartz/components/Search.tsx`
- Style: `quartz/components/styles/search.scss`
- Script: `quartz/components/scripts/search.inline.ts`
- You can edit `contextWindowWords` or `numSearchResults` to suit your needs

View File

@@ -0,0 +1,59 @@
---
title: "Graph View"
tags:
- component
---
Quartz features a graph-view that can show both a local graph view and a global graph view.
- The local graph view shows files that either link to the current file or are linked from the current file. In other words, it shows all notes that are _at most_ one hop away.
- The global graph view can be toggled by clicking the graph icon on the top-right of the local graph view. It shows _all_ the notes in your graph and how they connect to each other.
By default, the node radius is proportional to the total number of incoming and outgoing internal links from that file.
Additionally, similar to how browsers highlight visited links a different colour, the graph view will also show nodes that you have visited in a different colour.
> [!info]
> Graph View requires the `ContentIndex` emitter plugin to be present in the [[configuration]].
## Customization
Most configuration can be done by passing in options to `Component.Graph()`.
For example, here's what the default configuration looks like:
```typescript title="quartz.layout.ts"
Component.Graph({
localGraph: {
drag: true, // whether to allow panning the view around
zoom: true, // whether to allow zooming in and out
depth: 1, // how many hops of notes to display
scale: 1.1, // default view scale
repelForce: 0.5, // how much nodes should repel each other
centerForce: 0.3, // how much force to use when trying to center the nodes
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?
},
globalGraph: {
drag: true,
zoom: true,
depth: -1,
scale: 0.9,
repelForce: 0.5,
centerForce: 0.3,
linkDistance: 30,
fontSize: 0.6,
opacityScale: 1,
},
})
```
When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field.
Want to customize it even more?
- Removing graph view: delete all usages of `Component.Graph()` from `quartz.layout.ts`.
- Component: `quartz/components/Graph.tsx`
- Style: `quartz/components/styles/graph.scss`
- Script: `quartz/components/scripts/graph.inline.ts`

View File

@@ -0,0 +1,3 @@
---
title: Feature List
---

View File

@@ -0,0 +1,15 @@
---
title: Popover Previews
---
Like Wikipedia, when you hover over a link in Quartz, there is a popup of a page preview that you can scroll to see the entire content. Links to headers will also scroll the popup to show that specific header in view.
By default, Quartz only fetches previews for pages inside your vault due to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). It does this by selecting all HTML elements with the `popover-hint` class. For most pages, this includes the page title, page metadata like words and time to read, tags, and the actual page content.
When [[creating components|creating your own components]], you can include this `popover-hint` class to also include it in the popover.
## Configuration
- Remove popovers: set the `enablePopovers` field in `quartz.config.ts` to be `false`.
- Style: `quartz/components/styles/popover.scss`
- Script: `quartz/components/scripts/popover.inline.ts`

View File

@@ -0,0 +1,27 @@
---
title: Private Pages
tags:
- plugin/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 `drafts: 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.
## `ignoreFiles`
This is a field in `quartz.config.ts` under the main [[configuration]] which allows you to specify a list of patterns to effectively exclude from parsing all together. Any valid [glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) pattern works here.
Common examples include:
- `some/folder`: exclude the entire of `some/folder`
- `*.md`: exclude all files with a `.md` extension
- `!*.md` exclude all files that _don't_ have a `.md` extension
- `**/private`: exclude any files or folders named `private` at any level of nesting
> [!warning]
> Marking something as private via either a plugin or through the `ignoreFiles` pattern will only prevent a page from being included in the final built site. If your GitHub repository is public, also be sure to include an ignore for those in the `.gitignore` of your Quartz. See the `git` [documentation](https://git-scm.com/docs/gitignore#_pattern_format) for more information.

View File

@@ -0,0 +1,16 @@
---
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`.
## Customization
- Changing the title from "Recent notes": pass in an additional parameter to `Component.RecentNotes({ title: "Recent writing" })`
- Changing the number of recent notes: pass in an additional parameter to `Component.RecentNotes({ limit: 5 })`
- Show a 'see more' link: pass in an additional parameter to `Component.RecentNotes({ linkToMore: "tags/components" })`. This field should be a full slug to a page that exists.
- Customize filtering: pass in an additional parameter to `Component.RecentNotes({ filter: someFilterFunction })`. The filter function should be a function that has the signature `(f: QuartzPluginData) => boolean`.
- Customize sorting: pass in an additional parameter to `Component.RecentNotes({ sort: someSortFunction })`. By default, Quartz will sort by date and then tie break lexographically. The sort function should be a function that has the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`. See `byDateAndAlphabetical` in `quartz/components/PageList.tsx` for an example.
- Component: `quartz/components/RecentNotes.tsx`
- Style: `quartz/components/styles/recentNotes.scss`

View File

@@ -0,0 +1,135 @@
---
title: Syntax Highlighting
tags:
- plugin/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.
And, unlike some client-side highlighters, it has a full TextMate parser grammar instead of using Regexes, allowing for highly accurate code highlighting.
In short, it generates HTML that looks exactly like your code in an editor like VS Code. Under the hood, it's powered by [Rehype Pretty Code](https://rehype-pretty-code.netlify.app/) which uses [Shiki](https://github.com/shikijs/shiki).
> [!warning]
> Syntax highlighting does have an impact on build speed if you have a lot of code snippets in your notes.
## Formatting
Text inside `backticks` on a line will be formatted like code.
````
```ts
export function trimPathSuffix(fp: string): string {
fp = clientSideSlug(fp)
let [cleanPath, anchor] = fp.split("#", 2)
anchor = anchor === undefined ? "" : "#" + anchor
return cleanPath + anchor
}
```
````
```ts
export function trimPathSuffix(fp: string): string {
fp = clientSideSlug(fp)
let [cleanPath, anchor] = fp.split("#", 2)
anchor = anchor === undefined ? "" : "#" + anchor
return cleanPath + anchor
}
```
### Titles
Add a file title to your code block, with text inside double quotes (`""`):
````
```js title="..."
```
````
```ts title="quartz/path.ts"
export function trimPathSuffix(fp: string): string {
fp = clientSideSlug(fp)
let [cleanPath, anchor] = fp.split("#", 2)
anchor = anchor === undefined ? "" : "#" + anchor
return cleanPath + anchor
}
```
### Line highlighting
Place a numeric range inside `{}`.
````
```js {1-3,4}
```
````
```ts {2-3,6}
export function trimPathSuffix(fp: string): string {
fp = clientSideSlug(fp)
let [cleanPath, anchor] = fp.split("#", 2)
anchor = anchor === undefined ? "" : "#" + anchor
return cleanPath + anchor
}
```
### Word highlighting
A series of characters, like a literal regex.
````
```js /useState/
const [age, setAge] = useState(50);
const [name, setName] = useState('Taylor');
```
````
```js /useState/
const [age, setAge] = useState(50)
const [name, setName] = useState("Taylor")
```
### Line numbers
Syntax highlighting has line numbers configured automatically. If you want to start line numbers at a specific number, use `showLineNumbers{number}`:
````
```js showLineNumbers{number}
```
````
```ts showLineNumbers{20}
export function trimPathSuffix(fp: string): string {
fp = clientSideSlug(fp)
let [cleanPath, anchor] = fp.split("#", 2)
anchor = anchor === undefined ? "" : "#" + anchor
return cleanPath + anchor
}
```
### Escaping code blocks
You can format a codeblock inside of a codeblock by wrapping it with another level of backtick fences that has one more backtick than the previous fence.
`````
````
```js /useState/
const [age, setAge] = useState(50);
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`

View File

@@ -0,0 +1,24 @@
---
title: "Table of Contents"
tags:
- component
- plugin/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.
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.
> [!info]
> This feature requires both `Plugin.TableOfContents` in your `quartz.config.ts` and `Component.TableOfContents` in your `quartz.layout.ts` to function correctly.
## 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`

View File

@@ -0,0 +1,24 @@
---
draft: true
---
## high priority backlog
- block links: https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+block+in+a+note
- note/header/block transcludes: https://help.obsidian.md/Linking+notes+and+files/Embedding+files
- static dead link detection
- docker support
## misc backlog
- breadcrumbs component
- filetree component
- cursor chat extension
- https://giscus.app/ extension
- sidenotes? https://github.com/capnfabs/paperesque
- direct match in search using double quotes
- https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI
- audio/video embed styling
- Canvas
- parse all images in page: use this for page lists if applicable?
- CV mode? with print stylesheet

View File

@@ -0,0 +1,18 @@
---
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.
This is enabled as a part of [[Obsidian compatibility]] and can be configured and enabled/disabled from that plugin.
## Syntax
- `[[Path to file]]`: produces a link to `Path to file` with the text `Path to file`
- `[[Path to file | Here's the title override]]`: produces a link to `Path to file` 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`
> [!warning]
> Currently, Quartz does not support block references or note embed syntax.

115
content/hosting.md Normal file
View File

@@ -0,0 +1,115 @@
---
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.)
> [!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!
## Cloudflare Pages
1. Log in to the [Cloudflare dashboard](https://dash.cloudflare.com/) and select your account.
2. In Account Home, select **Workers & Pages** > **Create application** > **Pages** > **Connect to Git**.
3. Select the new GitHub repository that you created and, in the **Set up builds and deployments** section, provide the following information:
| Configuration option | Value |
| ---------------------- | ------------------ |
| Production branch | `v4` |
| Framework preset | `None` |
| Build command | `npx quartz build` |
| Build output directory | `public` |
Press "Save and deploy" and Cloudflare should have a deployed version of your site in about a minute. Then, every time you sync your Quartz changes to GitHub, your site should be updated.
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.
In your local Quartz, create a new file `quartz/.github/workflows/deploy.yml`.
```yaml title="quartz/.github/workflows/deploy.yml"
name: Deploy Quartz site to GitHub Pages
on:
push:
branches:
- v4
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Fetch all history for git info
- uses: actions/setup-node@v3
with:
node-version: 18.14
- name: Install Dependencies
run: npm ci
- name: Build Quartz
run: npx quartz build
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: public
deploy:
needs: build
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
```
Then:
1. Head to "Settings" tab of your forked repository and in the sidebar, click "Pages". Under "Source", select "GitHub Actions".
2. Commit these changes by doing `npx quartz sync`. This should deploy your site to `<github-username>.github.io/<repository-name>`.
> [!hint]
> If you get an error about not being allowed to deploy to `github-pages` due to environment protection rules, make sure you remove any existing GitHub pages environments.
>
> 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.
### Custom Domain
Here's how to add a custom domain to your GitHub pages deployment.
1. Head to the "Settings" tab of your forked repository.
2. In the "Code and automation" section of the sidebar, click "Pages".
3. Under "Custom Domain", type your custom domain and click "Save".
4. This next step depends on whether you are using an apex domain (`example.com`) or a subdomain (`subdomain.example.com`).
- If you are using an apex domain, navigate to your DNS provider and create an `A` record that points your apex domain to GitHub's name servers which have the following IP addresses:
- `185.199.108.153`
- `185.199.109.153`
- `185.199.110.153`
- `185.199.111.153`
- If you are using a subdomain, navigate to your DNS provider and create a `CNAME` record that points your subdomain to the default domain for your site. For example, if you want to use the subdomain `quartz.example.com` for your user site, create a `CNAME` record that points `quartz.example.com` to `<github-username>.github.io`.
![[dns records.png]]_The above shows a screenshot of Google Domains configured for both `jzhao.xyz` (an apex domain) and `quartz.jzhao.xyz` (a subdomain)._
See the [GitHub documentation](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-a-subdomain) for more detail about how to setup your own custom domain with GitHub Pages.
> [!question] Why aren't my changes showing up?
> There could be many different reasons why your changes aren't showing up but the most likely reason is that you forgot to push your changes to GitHub.
>
> Make sure you save your changes to Git and sync it to GitHub by doing `npx quartz sync`. This will also make sure to pull any updates you may have made from other devices so you have them locally.

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

46
content/index.md Normal file
View File

@@ -0,0 +1,46 @@
---
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.
## 🪴 Get Started
Quartz requires **at least [Node](https://nodejs.org/) v18.14** to function correctly. Ensure you have this installed on your machine before continuing.
Then, in your terminal of choice, enter the following commands line by line:
```shell
git clone https://github.com/jackyzha0/quartz.git
cd quartz
git checkout v4
npm i
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
2. [[configuration|Configure]] Quartz's behaviour
3. Change Quartz's [[layout]]
4. [[build|Build and preview]] Quartz
5. [[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.
## 🔧 Features
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], 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
- Fully-customizable parsing, filtering, and page generation through [[making plugins|plugins]]
For a comprehensive list of features, visit the [features page](/features). You can read more about the _why_ behind these features on the [[philosophy]] page and a technical overview on the [[architecture]] page.
### 🚧 Troubleshooting + Updating
Having trouble with Quartz? Try searching for your issue using the search feature. If you haven't already, [[upgrading|upgrade]] to the newest version of Quartz to see if this fixes your issue.
If you're still having trouble, feel free to [submit an issue](https://github.com/jackyzha0/quartz/issues) if you feel you found a bug or ask for help in our [Discord Community](https://discord.gg/cRFFHYye7t).

42
content/layout.md Normal file
View File

@@ -0,0 +1,42 @@
---
title: Layout
---
Certain emitters may also output [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) files. To enable easy customization, these emitters allow you to fully rearrange the layout of the page. The default page layouts can be found in `quartz.layout.ts`.
Each page is composed of multiple different sections which contain `QuartzComponents`. The following code snippet lists all of the valid sections that you can add components to:
```typescript title="quartz/cfg.ts"
export interface FullPageLayout {
head: QuartzComponent // single component
header: QuartzComponent[] // laid out horizontally
beforeBody: QuartzComponent[] // laid out vertically
pageBody: QuartzComponent // single component
left: QuartzComponent[] // vertical on desktop, horizontal on mobile
right: QuartzComponent[] // vertical on desktop, horizontal on mobile
footer: QuartzComponent // single component
}
```
These correspond to following parts of the page:
![[quartz layout.png|800]]
> [!note]
> There are two additional layout fields that are _not_ shown in the above diagram.
>
> 1. `head` is a single component that renders the `<head>` [tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) in the HTML. This doesn't appear visually on the page and is only is responsible for metadata about the document like the tab title, scripts, and styles.
> 2. `header` is a set of components that are laid out horizontally and appears _before_ the `beforeBody` section. This enables you to replicate the old Quartz 3 header bar where the title, search bar, and dark mode toggle. By default, Quartz 4 doesn't place any components in the `header`.
Quartz **components**, like plugins, can take in additional properties as configuration options. If you're familiar with React terminology, you can think of them as Higher-order Components.
See [a list of all the components](./tags/component) for all available components along with their configuration options. You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz.
### Style
Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz 4, like Quartz 3, uses [Sass](https://sass-lang.com/guide/) for styling.
You can see the base style sheet in `quartz/styles/base.scss` and write your own in `quartz/styles/custom.scss`.
> [!note]
> Some components may provide their own styling as well! For example, `quartz/components/Darkmode.tsx` imports styles from `quartz/components/styles/darkmode.scss`. If you'd like to customize styling for a specific component, double check the component definition to see how its styles are defined.

View File

@@ -0,0 +1,41 @@
---
title: "Migrating from Quartz 3"
---
As you already have Quartz locally, you don't need to fork or clone it again. Simply just checkout the alpha branch, install the dependencies, and import your old vault.
```bash
git fetch
git checkout v4
git pull upstream v4
npm i
npx quartz create
```
If you get an error like `fatal: 'upstream' does not appear to be a git repository`, make sure you add `upstream` as a remote origin:
```shell
git remote add upstream https://github.com/jackyzha0/quartz.git
```
When running `npx quartz create`, you will be prompted as to how to initialize your content folder. Here, you can choose to import or link your previous content folder and Quartz should work just as you expect it to.
> [!note]
> If the existing content folder you'd like to use is at the _same_ path on a different branch, clone the repo again somewhere at a _different_ path in order to use it.
## Key changes
1. **Removing Hugo and `hugo-obsidian`**: Hugo worked well for earlier versions of Quartz but it also made it hard for people outside of the Golang and Hugo communities to fully understand what Quartz was doing under the hood and be able to properly customize it to their needs. Quartz 4 now uses a Node-based static-site generation process which should lead to a much more helpful error messages and an overall smoother user experience.
2. **Full-hot reload**: The many rough edges of how `hugo-obsidian` integrated with Hugo meant that watch mode didn't re-trigger `hugo-obsidian` to update the content index. This lead to a lot of weird cases where the watch mode output wasn't accurate. Quartz 4 now uses a cohesive parse, filter, and emit pipeline which gets run on every change so hot-reloads are always accurate.
3. **Replacing Go template syntax with JSX**: Quartz 3 used [Go templates](https://pkg.go.dev/text/template) to create layouts for pages. However, the syntax isn't great for doing any sort of complex rendering (like [text processing](https://github.com/jackyzha0/quartz/blob/hugo/layouts/partials/textprocessing.html)) and it got very difficult to make any meaningful layout changes to Quartz 3. Quartz 4 uses an extension of JavaScript syntax called JSX which allows you to write layout code that looks like HTML in JavaScript which is significantly easier to understand and maintain.
4. **A new extensible [[configuration]] and [[configuration#Plugins|plugin]] system**: Quartz 3 was hard to configure without technical knowledge of how Hugo's partials worked. Extensions were even hard to make. Quartz 4's configuration and plugin system is designed to be extended by users while making updating to new versions of Quartz easy.
## Things to update
- You will need to update your deploy scripts. See the [[hosting]] guide for more details.
- Ensure that your default branch on GitHub is updated from `hugo` to `v4`.
- [[folder and tag listings|Folder and tag listings]] have also changed.
- Folder descriptions should go under `content/<folder-name>/index.md` where `<folder-name>` is the name of the folder.
- Tag descriptions should go under `content/tags/<tag-name>.md` where `<tag-name>` is the name of the tag.
- Some HTML layout may not be the same between Quartz 3 and Quartz 4. If you depended on a particular HTML hierarchy or class names, you may need to update your custom CSS to reflect these changes.
- If you customized the layout of Quartz 3, you may need to translate these changes from Go templates back to JSX as Quartz 4 no longer uses Hugo. For components, check out the guide on [[creating components]] for more details on this.

View File

@@ -1,16 +0,0 @@
---
title: "Showcase"
---
Want to see what Quartz can do? Here are some cool community gardens :)
- [Quartz Documentation (this site!)](https://quartz.jzhao.xyz/)
- [Strengthening Online Social Bonds: Research Garden](https://communities.digital/)
- [Jacky Zhao's Garden](https://garden.jzhao.xyz/)
- [Anson Yu's Garden](http://garden.ansonyu.me/)
- [Shihyu's PKM](https://shihyuho.github.io/pkm/)
- [Chloe's Garden](https://garden.chloeabrasada.online/)
- [SlRvb's Site](https://slrvb.github.io/Site/)
- [Course notes for Information Technology Advanced Theory](https://a2itnotes.github.io/quartz/)
If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/hugo/content/moc/showcase.md)!

View File

@@ -1,28 +0,0 @@
---
title: "Obsidian Vault Integration"
tags:
- setup
---
## Setup
Obsidian is the preferred way to use Quartz. You can either create a new Obsidian Vault or link one that your already have.
### New Vault
If you don't have an existing Vault, [download Obsidian](https://obsidian.md/) and create a new Vault in the `/content` folder that you created and cloned during the [setup](notes/setup.md).
### Linking an existing Vault
The easiest way to use an existing Vault is to copy all of our files (directory and hierarchies intact) into the `/content` folder.
## Settings
Great, now that you have your Obsidian linked to your Quartz, let's fix some settings so that they play well.
Under Options > Files and Links, set the New link format to always be Absolute Path in Vault and disabled WikiLinks so Obsidian generates regular Markdown links.
![Obsidian Settings](/notes/images/obsidian-settings.png)*Obsidian Settings*
## Templates
Inserting front matter everytime you want to create a new Note gets annoying really quickly. Luckily, Obsidian supports templates which makes inserting new content really easily.
**If you decide to overwrite the `/content` folder completely, don't remove the `/content/templates` folder!**
Head over to Options > Core Plugins and enable the Templates plugin. Then go to Options > Hotkeys and set a hotkey for 'Insert Template'. That way, when you create a new note, you can just press the hotkey for a new template and be ready to go!

View File

@@ -1,48 +0,0 @@
---
title: "Configuration"
tags:
- setup
---
## Configuration
Quartz is designed to be extremely configurable. You can find the bulk of the configuration scattered throughout the repository depending on how in-depth you'd like to get.
The majority of configuration can be be found under `data/config.yaml`. An annotated example configuration is shown below.
```yaml
name: Your name here! # Shows in the footer
enableToc: true # Whether to show a Table of Contents
description: Page description to show to search engines
page_title: Quartz Example Page # Default Page Title
links: # Links to show in footer
- link_name: Twitter
link: https://twitter.com/_jzhao
- link_name: Github
link: https://github.com/jackyzha0
```
### Graph View
To customize the Interactive Graph view, you can poke around `data/graphConfig.yaml`.
```yaml
enableLegend: false # automatically generate a legend
enableDrag: true # allow dragging nodes in the graph
enableZoom: true # allow zooming and panning the graph
depth: -1 # how many neighbours of the current node to show (-1 is all nodes)
paths: # colour specific nodes path off of their path
- /moc: "#4388cc"
```
## Styling
Want to go even more in-depth? You can add custom CSS styling and change existing colours through editing `assets/custom.scss`. If you'd like to target specific parts of the site, you can add ids and classes to the HTML partials in `/layouts/partials`.
### Partials
Partials are what dictate what actually gets rendered to the page. Want to change how pages are styled and structured? You can edit the appropriate layout in `/layouts`.
For example, the structure of the home page can be edited through `/layouts/index.html`. To customize the footer, you can edit `/layouts/partials/footer.html`
More info about partials on [Hugo's website.](https://gohugo.io/templates/partials/)
Still having problems? Checkout our [FAQ and Troubleshooting guide](notes/troubleshooting.md).

View File

@@ -1,59 +0,0 @@
---
title: "Editing Content in Quartz"
tags:
- setup
---
## Editing
Quartz runs on top of [Hugo](https://gohugo.io/) so all notes are written in [Markdown](https://www.markdownguide.org/getting-started/).
### Obsidian
I *strongly* recommend using [Obsidian](http://obsidian.md/) as a way to edit and grow your digital garden. It comes with a really nice editor and graphical interface to preview all of my local files.
🔗 How to link your Obsidian Vault](notes/Obsidian.md)
Of course, all the files are in Markdown so you could just use your favourite text editor of choice.
### Ignoring Files
Only want to publish a subset of all of your notes? Don't worry, Quartz makes this a simple two-step process.
❌ [Excluding pages from being published](notes/ignore%20notes.md)
### Folder Structure
Here's a rough overview of what's what.
**All content in your garden can found in the `/content` folder.** To make edits, you can open any of the files and make changes directly and save it. You can organize content into any folder you'd like.
**To edit the main home page, open `/content/_index.md`.** This is the home page which is slightly special. You don't need front matter here!
To create a link between notes in your garden, just create a normal link using Markdown pointing to the document in question. Please note that **all links should be relative to the root `/content` path**.
```markdown
For example, I want to link this current document to `notes/config.md`.
[A link to the config page](notes/config.md)
```
### Front Matter
Hugo is picky when it comes to metadata for files. Make sure that your title is double-quoted and that you have a title defined at the top of your file like so:
```markdown
---
title: "Example Title"
---
Rest of your content here...
```
## Previewing Changes
This step is purely optional and mostly for those who want to see the published version of their digital garden locally before opening it up to the internet. This is *highly recommended*.
👀 [Preview Quartz Changes](notes/preview%20changes.md)
For those who like to live life more on the edge, viewing the garden through Obsidian gets you pretty close to the real thing.
## Publishing Changes
Now that you know the basics of managing your digital garden using Quartz, you can publish it to the internet!
🌍 [Hosting Quartz online!](notes/hosting.md)
Having problems? Checkout our [FAQ and Troubleshooting guide](notes/troubleshooting.md).

View File

@@ -1,93 +0,0 @@
---
title: "Deploying Quartz to the Web"
tags:
- setup
---
## GitHub Pages
Quartz is designed to be effortless to deploy. If you forked and cloned Quartz directly from the repository, everything should already be good to go! You can head to `<YOUR-GITHUB-USERNAME.github.io/quartz` to see it live.
### Enable GitHub Actions
By default, GitHub disables workflows from running automatically on Forked Repostories. Head to the 'Actions' tab of your forked repository and Enable Workflows to setup deploying your Quartz site!
![Enable GitHub Actions](/notes/images/github-actions.png)*Enable GitHub Actions*
### Enable GitHub Pages
Head to the 'Settings' tab of your forked repository and go to the 'Pages' tab.
1. Set the source to deploy from `master` using `/ (root)`
2. Set a custom domain here if you have one!
![Enable GitHub Pages](/notes/images/github-pages.png)*Enable GitHub Pages*
### Pushing Changes
To see your changes on the internet, we need to push it them to GitHub. Quartz is essentially a `git` repository so updating it is the same workflow as you would follow as normal.
```shell
# Navigate to Quartz folder
cd <path-to-quartz>
# Commit all changes
git add .
git commit -m "message describing changes"
# Push to GitHub to update site
git push origin hugo
```
### Setting up the Site
Now let's get this site up and running. Never hosted a site before? No problem. Have a fancy custom domain you already own or want to subdomain your Quartz? That's easy too.
Here, we take advantage of GitHub's free page hosting to deploy our site. Change `baseURL` in `/config.toml`. If you don't have a custom domain to use, you can use `<YOUR-USERNAME>.github.io` (which GitHub gives to you for free!) as your domain.
[Reference `config.toml` here](https://github.com/jackyzha0/quartz/blob/hugo/config.toml)
```toml
baseURL = "https://<YOUR-DOMAIN>/"
```
If you are using this under a subdomain (e.g. `<YOUR-GITHUB-USERNAME>.github.io/quartz`), include the trailing path.
```toml
baseURL = "https://<YOUR-GITHUB-USERNAME>.github.io/quartz/"
```
Change `cname` in `/.github/workflows/deploy.yaml`. Again, if you don't have a custom domain to use, you can use `<YOUR-USERNAME>.github.io`.
[Reference `deploy.yaml` here](https://github.com/jackyzha0/quartz/blob/hugo/.github/workflows/deploy.yaml)
```yaml
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }} # this can stay as is, GitHub fills this in for us!
publish_dir: ./public
publish_branch: master
cname: <YOUR-DOMAIN>
```
### Registrar
This step is only applicable if you are using a **custom domain**! If you are using `<YOUR-USERNAME>.github.io`, you can skip this step.
For this last bit to take effect, you also need to create a CNAME record with the DNS provider you register your domain with (i.e. NameCheap, Google Domains).
GitHub has some [documentation on this](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site), but the tldr; is to
1. Go to your forked repository (`github.com/<YOUR-GITHUB-USERNAME>/quartz`) settings page and go to the Pages tab. Under "Custom domain", type your custom domain, then click **Save**.
2. Go to your DNS Provider and create a CNAME record that points from your domain to `<YOUR-GITHUB-USERNAME.github.io.` (yes, with the trailing period).
![Example Configuration for Quartz](/notes/images/google-domains.png)*Example Configuration for Quartz*
3. Wait 30 minutes to an hour for the network changes to kick in.
4. Done!
## External Hosting
Don't want to use GitHub Pages? Hugo builds everything for you! Everything is a packaged set of static files ready to deploy in `/public`. You can then upload this folder to a cloud provider to deploy. Alternatively, most providers also give users the option to link a GitHub repository and a folder to deploy. When doing this, ensure you select `/public` folder under the `master` branch.
---
Now that your Quartz is live, let's figure out how to make Quartz really *yours*!
🎨 [Customizing Quartz](notes/config.md)
Having problems? Checkout our [FAQ and Troubleshooting guide](notes/troubleshooting.md).

View File

@@ -1,31 +0,0 @@
---
title: "Ignoring Notes"
---
### Quartz Ignore
Edit `ignoreFiles` in `config.toml` to include paths you'd like to exclude from being rendered.
```toml
...
ignoreFiles = [
"/content/templates/*",
"/content/private/*",
"<your path here>"
]
```
`ignoreFiles` supports the use of Regular Expressions (RegEx) so you can ignore patterns as well (e.g. ignoring all `.png`s by doing `\\.png$`).
To ignore a specific file, you can also add the tag `draft: true` to the frontmatter of a note.
```markdown
---
title: Some Private Note
draft: true
---
...
```
More details in [Hugo's documentation](https://gohugo.io/getting-started/configuration/#ignore-content-and-data-files-when-rendering).
### Global Ignore
However, just adding to the `ignoreFiles` will only prevent the page from being access through Quartz. If you want to prevent the file from being pushed to GitHub (for example if you have a public repository), you need to also add the path to the `.gitignore` file at the root of the repository.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,28 +0,0 @@
---
title: "Obsidian Vault Integration"
tags:
- setup
---
## Setup
Obsidian is the preferred way to use Quartz. You can either create a new Obsidian Vault or link one that your already have.
### New Vault
If you don't have an existing Vault, [download Obsidian](https://obsidian.md/) and create a new Vault in the `/content` folder that you created and cloned during the [setup](notes/setup.md).
### Linking an existing Vault
The easiest way to use an existing Vault is to copy all of our files (directory and hierarchies intact) into the `/content` folder.
## Settings
Great, now that you have your Obsidian linked to your Quartz, let's fix some settings so that they play well.
Under Options > Files and Links, set the New link format to always be Absolute Path in Vault and disabled WikiLinks so Obsidian generates regular Markdown links.
![Obsidian Settings](/notes/images/obsidian-settings.png)*Obsidian Settings*
## Templates
Inserting front matter everytime you want to create a new Note gets annoying really quickly. Luckily, Obsidian supports templates which makes inserting new content really easily.
**If you decide to overwrite the `/content` folder completely, don't remove the `/content/templates` folder!**
Head over to Options > Core Plugins and enable the Templates plugin. Then go to Options > Hotkeys and set a hotkey for 'Insert Template'. That way, when you create a new note, you can just press the hotkey for a new template and be ready to go!

View File

@@ -1,17 +0,0 @@
---
title: "Quartz Philosophy"
---
> “[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
## Why Quartz?
Hosting a public digital garden isn't easy. There are an overwhelming number of tutorials, resources, and guides for tools like [Notion](https://www.notion.so/), [Roam](https://roamresearch.com/), and [Obsidian](https://obsidian.md/), yet none of them have super easy to use *free* tools to publish that garden to the world.
I've personally found that
1. It's nice to access notes from anywhere
2. Having a public digital garden invites open conversations
3. It makes keeping personal notes and knowledge *playful and fun*
I was really inspired by [Bianca](https://garden.bianca.digital/) and [Joel](https://joelhooks.com/digital-garden)'s digital gardens and wanted to try making my own.
**The goal of Quartz is to make hosting your own public digital garden free and simple.** You don't even need your own website. Quartz does all of that for you and gives your own little corner of the internet.

View File

@@ -1,34 +0,0 @@
---
title: "Preview Changes"
---
If you'd like to preview what your Quartz site looks like before deploying it to the internet, here's exactly how to do that!
## Install `hugo-obsidian`
This step will generate the list of backlinks for Hugo to parse. Ensure you have [Go](https://golang.org/doc/install) (>= 1.16) installed.
```shell
# Install and link `hugo-obsidian` locally
$ go install github.com/jackyzha0/hugo-obsidian
# Navigate to your local Quartz folder
$ cd <location-of-your-local-quartz>
# Scrape all links in your Quartz folder and generate info for Quartz
$ hugo-obsidian -input=content -output=data -index=true
```
Afterwards, start the Hugo server as shown above and your local backlinks and interactive graph should be populated!
## Installing Hugo
Hugo is the static site generator that powers Quartz. If you'd like to preview your site locally, [install Hugo](https://gohugo.io/getting-started/installing/).
```
# Navigate to your local Quartz folder
$ cd <location-of-your-local-quartz>
# Start local server
$ hugo server
# View your site in a browser at http://localhost:1313/
```

View File

@@ -1,31 +0,0 @@
---
title: "Setup"
tags:
- setup
---
## Making your own Quartz
Setting up Quartz requires a basic understanding of `git`. If you are unfamiliar, [this resource](https://resources.nwplus.io/2-beginner/how-to-git-github.html) is a great place to start!
### Forking
> A fork is a copy of a repository. Forking a repository allows you to freely experiment with changes without affecting the original project.
Navigate to the GitHub repository for the Quartz project:
📁 [Quartz Repository](https://github.com/jackyzha0/quartz)
Then, Fork the repository into your own GitHub account. If you don't have an account, you can make on for free [here](https://github.com/join). More details about forking a repo can be found on [GitHub's documentation](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
### Cloning
After you've made a fork of the repository, you need to download the files locally onto your machine. Ensure you have `git`, then type the following command replacing `YOUR-USERNAME` with your GitHub username.
```shell
$ git clone https://github.com/YOUR-USERNAME/quartz
```
## Editing
Great! Now you have everything you need to start editing and growing your digital garden. If you're ready to start writing content already, check out the recommended flow for editing notes in Quartz.
✏️ [Editing Notes in Quartz](notes/editing.md)
Having problems? Checkout our [FAQ and Troubleshooting guide](notes/troubleshooting.md).

View File

@@ -1,70 +0,0 @@
---
title: "Troubleshooting and FAQ"
---
## Common Pitfalls
### Some of my pages have 'January 1, 0001' as the last modified date
This is a problem caused by `git` treating files as case-insensitive by default and some of your posts probably have capitalized file names. You can turn this off in your Quartz by running this command.
```shell
# in the root of your Quartz (same folder as config.toml)
git config core.ignorecase true
# or globally (not recommended)
git config --global core.ignorecase true
```
### Can I publish only a subset of my pages?
Yes! Quartz makes selective publishing really easy. Heres a guide on [excluding pages from being published](notes/ignore%20notes.md).
### Can I host this myself and not on GitHub Pages?
Yes! All built files can be found under `/public` in the `master` branch. More details under [hosting](notes/hosting.md).
### Do I need a website already?
No! Setting up Quartz means you set up a site too :)
### `command not found: hugo-obsidian`
Make sure you set your `GOPATH` correctly! This will allow your terminal to correctly recognize `hugo-obsidian` as an executable.
```shell
# Add the following 2 lines to your ~/.bash_profile
export GOPATH=/Users/$USER/go
export PATH=$GOPATH/bin:$PATH
# In your current terminal, to reload the session
source ~/.bash_profile
```
### How come my notes aren't being rendered?
You probably forgot to include front matter in your Markdown files. You can either setup [Obsidian](notes/Obsidian.md) to do this for you or you need to manually define it. More details in [the 'how to edit' guide](notes/editing.md).
### My custom domain isn't working!
Walk through the steps in [the hosting guide](notes/hosting.md) again. Make sure you wait 30 min to 1 hour for changes to take effect.
### How do I setup Google Analytics?
You can edit it in `config.toml` and either use a V3 (UA-) or V4 (G-) tag.
### How do I change the content on the home page?
To edit the main home page, open `/content/_index.md`.
### How do I change the colours?
You can change the theme by editing `assets/custom.scss`. More details on customization and themeing can be found in the [customization guide](notes/config.md).
### How do I add images?
You can put images anywhere in the `/content` folder. The only caveat is that you should reference them in your Markdown by prefixing it with a `/`.
```markdown
Example image (source is in content/notes/images/example.png)
![Example Image](/content/notes/images/example.png)
```
### My Interactive Graph and Backlinks aren't up to date
By default, the `linkIndex.yaml` (which Quartz needs to generate the Interactive Graph and Backlinks) are not regenerated locally. To set that up, see the guide on [local editing](notes/editing.md)
### Can I use React/Vue/some other framework?
Not out of the box. You could probably make it work by editing `/layouts/_default/single.html` but that's not what Quartz is designed to work with. 99% of things you are trying to do with those frameworks you can accomplish perfectly fine using just vanilla HTML/CSS/JS.
## Still Stuck?
Quartz isn't perfect! If you're still having troubles, file an issue in the GitHub repo with as much information as you can reasonably provide. Alternatively, you can message me on [Twitter](https://twitter.com/_jzhao) and I'll try to get back to you as soon as I can.
🐛 [Submit an Issue](https://github.com/jackyzha0/quartz/issues)

28
content/philosophy.md Normal file
View File

@@ -0,0 +1,28 @@
---
title: Philosophy of Quartz
---
## A garden should be a true hypertext
> The garden is the web as topology. Every walk through the garden creates new paths, new meanings, and when we add things to the garden we add them in a way that allows many future, unpredicted relationships.
>
> _(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.
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.
Quartz embraces the inherent rhizomatic and web-like nature of our thinking and tries to encourage note-taking in a similar form.
---
## A garden should be shared
The goal of digital gardening should be to tap into your networks collective intelligence to create constructive feedback loops. If done well, I have a shareable representation of my thoughts that I can send out into the world and people can respond. Even for my most half-baked thoughts, this helps me create a feedback cycle to strengthen and fully flesh out that idea.
Quartz is designed first and foremost as a tool for publishing [digital gardens](https://jzhao.xyz/posts/networked-thought/) to the web. To me, digital gardening is not just passive knowledge collection. Its a form of expression and sharing.
> “[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.

View File

@@ -1,5 +0,0 @@
---
title: "Private Stuff"
---
This page doesn't get published!

20
content/showcase.md Normal file
View File

@@ -0,0 +1,20 @@
---
title: "Quartz Showcase"
---
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/)
- [Course notes for Information Technology Advanced Theory](https://a2itnotes.github.io/quartz/)
- [Data Dictionary 🧠](https://glossary.airbyte.com/)
- [sspaeti.com's Second Brain](https://brain.sspaeti.com/)
- [oldwinterの数字花园](https://garden.oldwinter.top/)
- [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/)
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/content/showcase.md)!

View File

@@ -0,0 +1,5 @@
---
title: Components
---
Want to create your own custom component? Check out the advanced guide on [[creating components]] for more information.

View File

@@ -1,3 +0,0 @@
---
title: "{{title}}"
---

19
content/upgrading.md Normal file
View File

@@ -0,0 +1,19 @@
---
title: "Upgrading Quartz"
---
> [!note]
> This is specifically a guide for upgrading Quartz 4 version to a more recent update. If you are coming from Quartz 3, check out the [[migrating from Quartz 3|migration guide]] for more info.
To fetch the latest Quartz updates, simply run
```bash
npx quartz update
```
As Quartz uses [git](https://git-scm.com/) under the hood for versioning, updating effectively 'pulls' in the updates from the official Quartz GitHub repository. If you have local changes that might conflict with the updates, you may need to resolve these manually yourself (or, pull manually using `git pull origin upstream`).
> [!hint]
> Quartz will try to cache your content before updating to try and prevent merge conflicts. If you get a conflict mid-merge, you can stop the merge and then run `npx quartz restore` to restore your content from the cache.
If you have the [GitHub desktop app](https://desktop.github.com/), this will automatically open to help you resolve the conflicts. Otherwise, you will need to resolve this in a text editor like VSCode. For more help on resolving conflicts manually, check out the [GitHub guide on resolving merge conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line#competing-line-change-merge-conflicts).

View File

@@ -1,13 +0,0 @@
name: Jacky Zhao
enableToc: true
enableLinkPreview: true
description:
Here is the page description. This is an example Quartz site that details installation,
setup, customization, and troubleshooting for Quartz itself.
page_title:
"🪴 Quartz 3"
links:
- link_name: Twitter
link: https://twitter.com/_jzhao
- link_name: Github
link: https://github.com/jackyzha0

View File

@@ -1,6 +0,0 @@
enableLegend: false
enableDrag: true
enableZoom: true
depth: -1 # set to -1 to show full graph
paths:
- /moc: "#4388cc"

12
globals.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
export declare global {
interface Document {
addEventListener<K extends keyof CustomEventMap>(
type: K,
listener: (this: Document, ev: CustomEventMap[K]) => void,
): void
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void
}
interface Window {
spaNavigate(url: URL, isBack: boolean = false)
}
}

11
index.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
declare module "*.scss" {
const content: string
export = content
}
// dom custom event
interface CustomEventMap {
nav: CustomEvent<{ url: FullSlug }>
}
declare const fetchData: Promise<ContentIndex>

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
<div class="singlePage">
{{partial "darkmode.html" .}}
<div class="centered">
<h1>404.</h1>
<h3>Hey! You look a little lost. This page doesn't exist (or may be private).</h3>
<a href="/">↳ Let's get you home.</a>
</div>
</div>
</body>
</html>

View File

@@ -1,9 +0,0 @@
{{$trimmed := strings.TrimSuffix ".md" (.Destination | safeURL)}}
{{$trimmed = replace $trimmed "%20" "-" }}
{{$external := strings.HasPrefix $trimmed "http" }}
{{- if $external -}}
<a href="{{ $trimmed }}" rel="noopener">{{ .Text | safeHTML }}</a>
{{- else -}}
{{$fixedUrl := (cond (hasPrefix $trimmed "/") $trimmed (print "/" $trimmed)) | urlize}}
<a href="{{$fixedUrl}}" rel="noopener" class="internal-link" data-src="{{$fixedUrl}}">{{ .Text | safeHTML }}</a>
{{- end -}}

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ block "head" . }}
{{ end }}
<body>
{{ block "main" . }}
{{ end }}
</body>
</html>

View File

@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
{{partial "search.html" .}}
<div class="singlePage">
<!-- Begin actual content -->
<header>
<h1 id="page-title"><a href="{{ .Site.BaseURL }}">{{ .Site.Data.config.page_title }}</a></h1>
<svg tabindex="0" id="search-icon" aria-labelledby="title desc" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7"><title id="title">Search Icon</title><desc id="desc">Icon to open search</desc><g class="search-path" fill="none"><path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4"/><circle cx="8" cy="8" r="7"/></g></svg>
<div class="spacer"></div>
{{partial "darkmode.html" .}}
</header>
<article>
<h1>All {{.Title}}</h1>
{{partial "page-list.html" .Paginator.Pages.ByLastmod.Reverse }}
{{ template "_internal/pagination.html" .}}
</article>
{{partial "contact.html" .}}
</div>
</body>
</html>

View File

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
{{partial "search.html" .}}
<div class="singlePage">
<!-- Begin actual content -->
<header>
<h1 id="page-title"><a href="{{ .Site.BaseURL }}">{{ .Site.Data.config.page_title }}</a></h1>
<svg tabindex="0" id="search-icon" aria-labelledby="title desc" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7"><title id="title">Search Icon</title><desc id="desc">Icon to open search</desc><g class="search-path" fill="none"><path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4"/><circle cx="8" cy="8" r="7"/></g></svg>
<div class="spacer"></div>
{{partial "darkmode.html" .}}
</header>
<article>
{{if .Title}}<h1>{{ .Title }}</h1>{{end}}
<p class="meta">
Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}}
</p>
<ul class="tags">
{{ range (.GetTerms "tags") }}
<li><a href="{{ .Permalink }}">{{ .LinkTitle | humanize }}</a></li>
{{ end }}
</ul>
{{if $.Site.Data.config.enableToc}}
<aside class="mainTOC">
<h3>Table of Contents</h3>
{{ .TableOfContents }}
</aside>
{{end}}
{{.Content}}
</article>
{{partial "footer.html" .}}
</div>
</body>
</html>

View File

@@ -1,33 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
{{partial "search.html" .}}
<div class="singlePage">
<!-- Begin actual content -->
<header>
<h1 id="page-title"><a href="{{ .Site.BaseURL }}">{{ .Site.Data.config.page_title }}</a></h1>
<svg tabindex="0" id="search-icon" aria-labelledby="title desc" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7"><title id="title">Search Icon</title><desc id="desc">Icon to open search</desc><g class="search-path" fill="none"><path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4"/><circle cx="8" cy="8" r="7"/></g></svg>
<div class="spacer"></div>
{{partial "darkmode.html" .}}
</header>
<article>
<h1>All {{.Title}}</h1>
<div class="tags">
{{ range .Site.Taxonomies.tags.ByCount }}
<div class="meta">
<h1><a href="{{ .Page.Permalink }}">{{ .Page.Title }}</a></h1>
<p><b>{{ .Count }}</b> notes with this tag {{if gt .Count 2}}(showing first 2 results){{end}}</p>
</div>
{{ with ($.Site.GetPage (printf "/tags/%s" .Page.Title)) }}
{{partial "page-list.html" (first 2 .Pages.ByLastmod.Reverse)}}
{{ end }}
{{ end }}
</div>
</article>
{{partial "contact.html" .}}
</div>
</body>
</html>

View File

@@ -1,24 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
{{partial "search.html" .}}
<div class="singlePage">
<!-- Begin actual content -->
<header>
<h1 id="page-title"><a href="{{ .Site.BaseURL }}">{{ .Site.Data.config.page_title }}</a></h1>
<svg tabindex="0" id="search-icon" aria-labelledby="title desc" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7"><title id="title">Search Icon</title><desc id="desc">Icon to open search</desc><g class="search-path" fill="none"><path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4"/><circle cx="8" cy="8" r="7"/></g></svg>
<div class="spacer"></div>
{{partial "darkmode.html" .}}
</header>
<article>
<h1>Tag: {{.Title | humanize}}</h1>
{{partial "page-list.html" .Paginator.Pages}}
{{ template "_internal/pagination.html" . }}
</article>
{{partial "contact.html" .}}
</div>
</body>
</html>

View File

@@ -1,28 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
{{partial "search.html" .}}
<div class="singlePage">
<!-- Begin actual content -->
<header>
<h1>{{if .Title}}{{ .Title }}{{else}}Untitled{{end}}</h1>
<svg tabindex="0" id="search-icon" aria-labelledby="title desc" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7"><title id="title">Search Icon</title><desc id="desc">Icon to open search</desc><g class="search-path" fill="none"><path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4"/><circle cx="8" cy="8" r="7"/></g></svg>
<div class="spacer"></div>
{{partial "darkmode.html" .}}
</header>
<article>
{{if $.Site.Data.config.enableToc}}
<aside class="mainTOC">
<h3>Table of Contents</h3>
{{ .TableOfContents }}
</aside>
{{end}}
{{- .Content -}}
</article>
{{partial "footer.html" .}}
</div>
</body>
</html>

View File

@@ -1,20 +0,0 @@
<h3>Backlinks</h3>
<ul class="backlinks">
{{$url := urls.Parse .Site.BaseURL }}
{{$host := strings.TrimRight "/" $url.Path }}
{{$curPage := strings.TrimPrefix $host (strings.TrimRight "/" .Page.RelPermalink) }}
{{$inbound := index $.Site.Data.linkIndex.index.backlinks $curPage}}
{{$contentTable := $.Site.Data.contentIndex}}
{{if $inbound}}
{{$cleanedInbound := apply (apply $inbound "index" "." "source") "replace" "." " " "-"}}
{{- range $cleanedInbound | uniq -}}
<li>
<a href="{{.}}">{{index (index $contentTable .) "title"}}</a>
</li>
{{- end -}}
{{else}}
<li>
No backlinks found
</li>
{{end}}
</ul>

View File

@@ -1,12 +0,0 @@
<!-- Contact Info -->
<div id="contact_buttons">
<footer>
<p>Made by {{ $.Site.Data.config.name }} using <a href="https://github.com/jackyzha0/quartz">Quartz</a>, © {{ dateFormat "2006" now }}</p>
{{ if not .IsHome }}
<a href="/">Home</a>
{{end}}
{{- range $.Site.Data.config.links -}}
<a href="{{.link}}">{{.link_name}}</a>
{{- end -}}
</footer>
</div>

View File

@@ -1,15 +0,0 @@
<div class='darkmode'>
<input class='toggle' id='darkmode-toggle' type='checkbox' tabindex="-1">
<label id="toggle-label-light" for='darkmode-toggle' tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="dayIcon" x="0px" y="0px" viewBox="0 0 35 35" style="enable-background:new 0 0 35 35;" xml:space="preserve">
<title>Light Mode</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" />
</svg>
</label>
<label id="toggle-label-dark" for='darkmode-toggle' tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="nightIcon" x="0px" y="0px" viewBox="0 0 100 100" style="enable-background='new 0 0 100 100'" xml:space="preserve">
<title>Dark Mode</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" />
</svg>
</label>
</div>

View File

@@ -1,11 +0,0 @@
<hr/>
<div class="page-end">
<div class="backlinks-container">
{{partial "backlinks.html" .}}
</div>
<div>
{{partial "graph.html" .}}
</div>
</div>
{{partial "contact.html" .}}

View File

@@ -1,236 +0,0 @@
<script src="https://cdn.jsdelivr.net/npm/d3@6"></script>
<h3>Interactive Graph</h3>
<div id="graph-container"></div>
<style>
:root {
--g-node: var(--secondary);
--g-node-active: var(--primary);
--g-node-inactive: var(--visited);
--g-link: var(--outlinegray);
--g-link-active: #5a7282;
}
</style>
<script>
const curPage = {{ strings.TrimRight "/" .Page.RelPermalink }}
const pathColors = {{$.Site.Data.graphConfig.paths}}
let depth = {{$.Site.Data.graphConfig.depth}}
const parseIdsFromLinks = (links) => [...(new Set(links.flatMap(link => ([link.source, link.target]))))]
const neighbours = new Set()
const wl = [curPage || "/", "__SENTINEL"]
if (depth >= 0) {
while (depth >= 0 && wl.length > 0) {
// compute neighbours
const cur = wl.shift()
if (cur === "__SENTINEL") {
depth--
wl.push("__SENTINEL")
} else {
neighbours.add(cur)
const outgoing = index.links[cur] || []
const incoming = index.backlinks[cur] || []
wl.push(...outgoing.map(l => l.target), ...incoming.map(l => l.source))
}
}
} else {
parseIdsFromLinks(links).forEach(id => neighbours.add(id))
}
const data = {
nodes: [...neighbours].map(id => ({id})),
links: links.filter(l => neighbours.has(l.source) && neighbours.has(l.target)),
}
const color = (d) => {
if (d.id === curPage || (d.id === "/" && curPage === "")) {
return "var(--g-node-active)"
}
for (const pathColor of pathColors) {
const path = Object.keys(pathColor)[0]
const colour = pathColor[path]
if (d.id.startsWith(path)) {
return colour
}
}
return "var(--g-node)"
}
const drag = simulation => {
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(1).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event,d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event,d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
const enableDrag = {{$.Site.Data.graphConfig.enableDrag}}
const noop = () => {}
return d3.drag()
.on("start", enableDrag ? dragstarted : noop)
.on("drag", enableDrag ? dragged : noop)
.on("end", enableDrag ? dragended : noop);
}
const height = 250
const width = document.getElementById("graph-container").offsetWidth
const simulation = d3.forceSimulation(data.nodes)
.force("charge", d3.forceManyBody().strength(-30))
.force("link", d3.forceLink(data.links).id(d => d.id))
.force("center", d3.forceCenter());
const svg = d3.select('#graph-container')
.append('svg')
.attr('width', width)
.attr('height', height)
.attr("viewBox", [-width / 2, -height / 2, width, height]);
// legend
const enableLegend = {{$.Site.Data.graphConfig.enableLegend}}
if (enableLegend) {
const legend = [
{"Current": "var(--g-node-active)"},
{"Note": "var(--g-node)"},
...pathColors
]
legend.forEach((legendEntry, i) => {
const key = Object.keys(legendEntry)[0]
const colour = legendEntry[key]
svg.append("circle").attr("cx", -width/2 + 20).attr("cy", height/2 - 30 * (i+1)).attr("r", 6).style("fill", colour)
svg.append("text").attr("x", -width/2 + 40).attr("y", height/2 - 30 * (i+1)).text(key).style("font-size", "15px").attr("alignment-baseline","middle")
})
}
// draw links between nodes
const link = svg.append("g")
.selectAll("line")
.data(data.links)
.join("line")
.attr("class", "link")
.attr("stroke", "var(--g-link)")
.attr("stroke-width", 2)
.attr("data-source", d => d.source.id)
.attr("data-target", d => d.target.id)
// svg groups
const graphNode = svg.append("g")
.selectAll("g")
.data(data.nodes)
.enter().append("g")
// draw individual nodes
const node = graphNode.append("circle")
.attr("class", "node")
.attr("id", (d) => d.id)
.attr("r", (d) => {
const numOut = index.links[d.id]?.length || 0
const numIn = index.backlinks[d.id]?.length || 0
return 3 + (numOut + numIn) / 4
})
.attr("fill", color)
.style("cursor", "pointer")
.on("click", (_, d) => {
window.location.href = {{.Site.BaseURL}} + decodeURI(d.id).replace(/[\s_]+/g, '-')
})
.on("mouseover", function (_, d) {
d3.selectAll(".node")
.transition()
.duration(100)
.attr("fill", "var(--g-node-inactive)")
const neighbours = parseIdsFromLinks([...(index.links[d.id] || []), ...(index.backlinks[d.id] || [])])
const neighbourNodes = d3.selectAll(".node").filter(d => neighbours.includes(d.id))
const currentId = d.id
const linkNodes = d3.selectAll(".link").filter(d => d.source.id === currentId || d.target.id === currentId)
// highlight neighbour nodes
neighbourNodes
.transition()
.duration(200)
.attr("fill", color)
// highlight links
linkNodes
.transition()
.duration(200)
.attr("stroke", "var(--g-link-active)")
// show text for self
d3.select(this.parentNode)
.select("text")
.raise()
.transition()
.duration(200)
.style("opacity", 1)
}).on("mouseleave", function (_,d) {
d3.selectAll(".node")
.transition()
.duration(200)
.attr("fill", color)
const currentId = d.id
const linkNodes = d3.selectAll(".link").filter(d => d.source.id === currentId || d.target.id === currentId)
linkNodes
.transition()
.duration(200)
.attr("stroke", "var(--g-link)")
d3.select(this.parentNode)
.select("text")
.transition()
.duration(200)
.style("opacity", 0)
})
.call(drag(simulation));
// draw labels
const labels = graphNode.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text((d) => content[decodeURI(d.id).replace(/[\s_]+/g, '-')]?.title || "Untitled")
.style("opacity", 0)
.style("pointer-events", "none")
.call(drag(simulation));
// set panning
const enableZoom = {{$.Site.Data.graphConfig.enableZoom}}
if (enableZoom) {
svg.call(d3.zoom()
.extent([[0, 0], [width, height]])
.scaleExtent([0.25, 4])
.on("zoom", ({transform}) => {
link.attr("transform", transform);
node.attr("transform", transform);
labels.attr("transform", transform);
}));
}
// progress the simulation
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
labels
.attr("x", d => d.x)
.attr("y", d => d.y);
});
</script>

View File

@@ -1,35 +0,0 @@
<head>
<!-- Meta tags -->
<meta charset="UTF-8">
<meta name="description" content="{{if .IsHome}}{{$.Site.Data.config.description}}{{else}}{{.Summary}}{{end}}">
<title>{{ if .Title }}{{ .Title }}{{ else }}{{ $.Site.Data.config.page_title }}{{ end }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="/icon.png" />
<!-- CSS Stylesheets and Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Source+Sans+Pro:wght@400;600;700&family=Fira+Code:wght@400;700&display=swap" rel="stylesheet">
{{ $css := slice "custom.scss" "base.scss" "darkmode.scss" "syntax.scss"}}
{{range $css}}
{{$sass := resources.Get . | resources.ToCSS }}
{{with $sass | minify}}
<style>
{{.Content | safeCSS}}
</style>
{{end}}
{{end}}
{{- with resources.Get "darkmode.js" | minify -}}
<script>
{{.Content | safeJS }}
</script>
{{- end -}}
<!-- Preload page vars -->
<script>
const content = {{$.Site.Data.contentIndex}}
const index = {{$.Site.Data.linkIndex.index}}
const links = {{$.Site.Data.linkIndex.links}}
</script>
</head>
{{ template "_internal/google_analytics.html" . }}
{{ partial "popover.html" .}}

View File

@@ -1,15 +0,0 @@
<ul class="section-ul">
{{- range . -}}
<li class="section-li">
<div class="section">
<div class="desc">
<h3><a href="{{ .Permalink }}">{{- .Title -}}</a></h3>
<p>{{- .Summary -}}{{if .Truncated}}...{{end}}</p>
</div>
<p class="meta">
{{ .ReadingTime }} minute read. Last updated {{if ne .Date .Lastmod}}{{ .Lastmod.Format "January 2, 2006" }}{{else}}Unknown{{end}}
</p>
</div>
</li>
{{- end -}}
</ul>

View File

@@ -1,31 +0,0 @@
{{if $.Site.Data.config.enableLinkPreview}}
<script>
function htmlToElement(html) {
const template = document.createElement('template')
html = html.trim()
template.innerHTML = html
return template.content.firstChild
}
document.addEventListener("DOMContentLoaded", () => {
[...document.getElementsByClassName("internal-link")]
.forEach(li => {
const linkDest = content[li.dataset.src]
if (linkDest) {
const popoverElement = `<div class="popover">
<h3>${linkDest.title}</h3>
<p>${removeMarkdown(linkDest.content).split(" ", 15).join(" ")}...</p>
</div>`
const el = htmlToElement(popoverElement)
li.appendChild(el)
li.addEventListener("mouseover", () => {
el.classList.add("visible")
})
li.addEventListener("mouseout", () => {
el.classList.remove("visible")
})
}
})
})
</script>
{{end}}

View File

@@ -1,255 +0,0 @@
<div id="search-container">
<div id="search-space">
<input autocomplete="off" id="search-bar" name="search" type="text" aria-label="Search" placeholder="Search for something...">
<div id="results-container">
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.7.2/dist/flexsearch.bundle.js"></script>
<script>
// code from https://github.com/danestves/markdown-to-text
const removeMarkdown = (
markdown,
options = {
listUnicodeChar: false,
stripListLeaders: true,
gfm: true,
useImgAltText: false,
preserveLinks: false,
}
) => {
let output = markdown || "";
output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, "");
try {
if (options.stripListLeaders) {
if (options.listUnicodeChar)
output = output.replace(
/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm,
options.listUnicodeChar + " $1"
);
else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, "$1");
}
if (options.gfm) {
output = output
.replace(/\n={2,}/g, "\n")
.replace(/~{3}.*\n/g, "")
.replace(/~~/g, "")
.replace(/`{3}.*\n/g, "");
}
if (options.preserveLinks) {
output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, "$1 ($2)")
}
output = output
.replace(/<[^>]*>/g, "")
.replace(/^[=\-]{2,}\s*$/g, "")
.replace(/\[\^.+?\](\: .*?$)?/g, "")
.replace(/\s{0,2}\[.*?\]: .*?$/g, "")
.replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? "$1" : "")
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, "$1")
.replace(/^\s{0,3}>\s?/g, "")
.replace(/(^|\n)\s{0,3}>\s?/g, "\n\n")
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, "")
.replace(
/^(\n)?\s{0,}#{1,6}\s+| {0,}(\n)?\s{0,}#{0,} {0,}(\n)?\s{0,}$/gm,
"$1$2$3"
)
.replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, "$2")
.replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, "$2")
.replace(/(`{3,})(.*?)\1/gm, "$2")
.replace(/`(.+?)`/g, "$1")
.replace(/\n{2,}/g, "\n\n");
} catch (e) {
console.error(e);
return markdown;
}
return output;
};
</script>
<script>
const contentIndex = new FlexSearch.Document({
cache: true,
charset: "latin:extra",
optimize: true,
worker: true,
document: {
index: [{
field: "content",
tokenize: "strict",
context: {
resolution: 5,
depth: 3,
bidirectional: true
},
suggest: true,
}, {
field: "title",
tokenize: "forward",
}]
}
})
for (const [key, value] of Object.entries(content)) {
contentIndex.add({
id: key,
title: value.title,
content: removeMarkdown(value.content),
})
}
const highlight = (content, term) => {
const highlightWindow = 20
const tokenizedTerm = term.split(/\s+/).filter(t => t !== "")
const splitText = content.split(/\s+/).filter(t => t !== "")
const includesCheck = (token) => tokenizedTerm.some(term => token.toLowerCase().startsWith(term.toLowerCase()))
const occurrencesIndices = splitText
.map(includesCheck)
// calculate best index
let bestSum = 0
let bestIndex = 0
for (let i = 0; i < Math.max(occurrencesIndices.length - highlightWindow, 0); i++) {
const window = occurrencesIndices.slice(i, i + highlightWindow)
const windowSum = window.reduce((total, cur) => total + cur, 0)
if (windowSum >= bestSum) {
bestSum = windowSum
bestIndex = i
}
}
const startIndex = Math.max(bestIndex - highlightWindow, 0)
const endIndex = Math.min(startIndex + 2 * highlightWindow, splitText.length)
const mappedText = splitText
.slice(startIndex, endIndex)
.map(token => {
if (includesCheck(token)) {
return `<span class="search-highlight">${token}</span>`
}
return token
})
.join(" ")
.replaceAll('</span> <span class="search-highlight">', " ")
return `${startIndex === 0 ? "" : "..."}${mappedText}${endIndex === splitText.length ? "" : "..."}`
}
const resultToHTML = ({url, title, content, term}) => {
const text = removeMarkdown(content)
const resultTitle = highlight(title, term)
const resultText = highlight(text, term)
return `<button class="result-card" id="${url}">
<h3>${resultTitle}</h3>
<p>${resultText}</p>
</button>`
}
const redir = (id, term) => {
window.location.href = "{{.Site.BaseURL}}" + `${id}#:~:text=${encodeURIComponent(term)}`
}
const fetch = id => ({
id,
url: id,
title: content[id].title,
content: content[id].content
})
const source = document.getElementById('search-bar')
const results = document.getElementById("results-container")
let term
source.addEventListener("keyup", (e) => {
if (e.key === "Enter") {
const anchor = document.getElementsByClassName("result-card")[0]
redir(anchor.id, term)
}
})
source.addEventListener('input', (e) => {
term = e.target.value
contentIndex.search(term, [
{
field: "content",
limit: 10,
suggest: true,
},
{
field: "title",
limit: 5,
}
]).then(searchResults => {
const getByField = field => {
const results = searchResults.filter(x => x.field === field)
if (results.length === 0) {
return []
} else {
return [...results[0].result]
}
}
const allIds = new Set([...getByField('title'), ...getByField('content')])
const finalResults = [...allIds].map(fetch)
// display
if (finalResults.length === 0) {
results.innerHTML = `<button class="result-card">
<h3>No results.</h3>
<p>Try another search term?</p>
</button>`
} else {
results.innerHTML = finalResults
.map(result => resultToHTML({
...result,
term,
}))
.join("\n")
const anchors = document.getElementsByClassName("result-card");
[...anchors].forEach(anchor => {
anchor.onclick = () => redir(anchor.id, term)
})
}
})
})
const searchContainer = document.getElementById("search-container")
function openSearch() {
if (searchContainer.style.display === "none" || searchContainer.style.display === "") {
source.value = ""
results.innerHTML = ""
searchContainer.style.display = "block"
source.focus()
} else {
searchContainer.style.display = "none"
}
}
function closeSearch() {
searchContainer.style.display = "none"
}
document.addEventListener('keydown', (event) => {
if (event.key === "/") {
event.preventDefault()
openSearch()
}
if (event.key === "Escape") {
event.preventDefault()
closeSearch()
}
})
window.addEventListener('DOMContentLoaded', () => {
const searchButton = document.getElementById("search-icon")
searchButton.addEventListener('click', (evt) => {
openSearch()
})
searchButton.addEventListener('keydown', (evt) => {
openSearch()
})
searchContainer.addEventListener('click', (evt) => {
closeSearch()
})
document.getElementById("search-space").addEventListener('click', (evt) => {
evt.stopPropagation()
})
})
</script>

6162
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

105
package.json Normal file
View File

@@ -0,0 +1,105 @@
{
"name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website",
"private": true,
"version": "4.0.8",
"type": "module",
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
"license": "MIT",
"homepage": "https://quartz.jzhao.xyz",
"repository": {
"type": "git",
"url": "https://github.com/jackyzha0/quartz.git"
},
"scripts": {
"check": "tsc --noEmit && npx prettier . --check",
"format": "npx prettier . --write",
"test": "tsx ./quartz/util/path.test.ts",
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
},
"engines": {
"node": ">=18.14"
},
"keywords": [
"site generator",
"ssg",
"digital-garden",
"markdown",
"blog",
"quartz"
],
"bin": {
"quartz": "./quartz/bootstrap-cli.mjs"
},
"dependencies": {
"@clack/prompts": "^0.6.3",
"@floating-ui/dom": "^1.4.0",
"@napi-rs/simple-git": "^0.1.8",
"async-mutex": "^0.4.0",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
"cli-spinner": "^0.2.10",
"d3": "^7.8.5",
"esbuild-sass-plugin": "^2.9.0",
"flexsearch": "0.7.21",
"github-slugger": "^2.0.0",
"globby": "^13.1.4",
"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",
"is-absolute-url": "^4.0.1",
"js-yaml": "^4.1.0",
"lightningcss": "^1.21.5",
"mdast-util-find-and-replace": "^2.2.2",
"mdast-util-to-hast": "^12.3.0",
"mdast-util-to-string": "^3.2.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",
"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-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"remark-smartypants": "^2.0.0",
"rimraf": "^5.0.1",
"serve-handler": "^6.1.5",
"source-map-support": "^0.5.21",
"to-vfile": "^7.2.4",
"unified": "^10.1.2",
"unist-util-visit": "^4.1.2",
"vfile": "^5.3.7",
"workerpool": "^6.4.0",
"ws": "^8.13.0",
"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.18.11",
"prettier": "^3.0.0",
"tsx": "^3.12.7",
"typescript": "^5.0.4"
}
}

75
quartz.config.ts Normal file
View File

@@ -0,0 +1,75 @@
import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
const config: QuartzConfig = {
configuration: {
pageTitle: "🪴 Quartz 4.0",
enableSPA: true,
enablePopovers: true,
analytics: {
provider: "plausible",
},
baseUrl: "quartz.jzhao.xyz",
ignorePatterns: ["private", "templates"],
theme: {
typography: {
header: "Schibsted Grotesk",
body: "Source Sans Pro",
code: "IBM Plex Mono",
},
colors: {
lightMode: {
light: "#faf8f8",
lightgray: "#e5e5e5",
gray: "#b8b8b8",
darkgray: "#4e4e4e",
dark: "#2b2b2b",
secondary: "#284b63",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
},
darkMode: {
light: "#161618",
lightgray: "#393639",
gray: "#646464",
darkgray: "#d4d4d4",
dark: "#ebebec",
secondary: "#7b97aa",
tertiary: "#84a59d",
highlight: "rgba(143, 159, 169, 0.15)",
},
},
},
},
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
}),
Plugin.SyntaxHighlighting(),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
Plugin.Latex({ renderEngine: "katex" }),
Plugin.Description(),
],
filters: [Plugin.RemoveDrafts()],
emitters: [
Plugin.AliasRedirects(),
Plugin.ComponentResources({ fontOrigin: "googleFonts" }),
Plugin.ContentPage(),
Plugin.FolderPage(),
Plugin.TagPage(),
Plugin.ContentIndex({
enableSiteMap: true,
enableRSS: true,
}),
Plugin.Assets(),
Plugin.Static(),
],
},
}
export default config

39
quartz.layout.ts Normal file
View File

@@ -0,0 +1,39 @@
import { PageLayout, SharedLayout } from "./quartz/cfg"
import * as Component from "./quartz/components"
// components shared across all pages
export const sharedPageComponents: SharedLayout = {
head: Component.Head(),
header: [],
footer: Component.Footer({
links: {
GitHub: "https://github.com/jackyzha0/quartz",
"Discord Community": "https://discord.gg/cRFFHYye7t",
},
}),
}
// components for pages that display a single page (e.g. a single note)
export const defaultContentPageLayout: PageLayout = {
beforeBody: [Component.ArticleTitle(), Component.ContentMeta(), Component.TagList()],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Search(),
Component.Darkmode(),
Component.DesktopOnly(Component.TableOfContents()),
],
right: [Component.Graph(), Component.Backlinks()],
}
// components for pages that display lists of pages (e.g. tags or folders)
export const defaultListPageLayout: PageLayout = {
beforeBody: [Component.ArticleTitle()],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Search(),
Component.Darkmode(),
],
right: [],
}

539
quartz/bootstrap-cli.mjs Executable file
View File

@@ -0,0 +1,539 @@
#!/usr/bin/env node
import { promises, readFileSync } from "fs"
import yargs from "yargs"
import path from "path"
import { hideBin } from "yargs/helpers"
import esbuild from "esbuild"
import chalk from "chalk"
import { sassPlugin } from "esbuild-sass-plugin"
import fs from "fs"
import { intro, isCancel, outro, select, text } from "@clack/prompts"
import { rimraf } from "rimraf"
import chokidar from "chokidar"
import prettyBytes from "pretty-bytes"
import { execSync, spawnSync } from "child_process"
import http from "http"
import serveHandler from "serve-handler"
import { WebSocketServer } from "ws"
import { randomUUID } from "crypto"
import { Mutex } from "async-mutex"
const ORIGIN_NAME = "origin"
const UPSTREAM_NAME = "upstream"
const QUARTZ_SOURCE_BRANCH = "v4"
const cwd = process.cwd()
const cacheDir = path.join(cwd, ".quartz-cache")
const cacheFile = "./.quartz-cache/transpiled-build.mjs"
const fp = "./quartz/build.ts"
const { version } = JSON.parse(readFileSync("./package.json").toString())
const contentCacheFolder = path.join(cacheDir, "content-cache")
const CommonArgv = {
directory: {
string: true,
alias: ["d"],
default: "content",
describe: "directory to look for content files",
},
verbose: {
boolean: true,
alias: ["v"],
default: false,
describe: "print out extra logging information",
},
}
const SyncArgv = {
...CommonArgv,
commit: {
boolean: true,
default: true,
describe: "create a git commit for your unsaved changes",
},
push: {
boolean: true,
default: true,
describe: "push updates to your Quartz fork",
},
pull: {
boolean: true,
default: true,
describe: "pull updates from your Quartz fork",
},
}
const BuildArgv = {
...CommonArgv,
output: {
string: true,
alias: ["o"],
default: "public",
describe: "output folder for files",
},
serve: {
boolean: true,
default: false,
describe: "run a local server to live-preview your Quartz",
},
baseDir: {
string: true,
default: "",
describe: "base path to serve your local server on",
},
port: {
number: true,
default: 8080,
describe: "port to serve Quartz on",
},
bundleInfo: {
boolean: true,
default: false,
describe: "show detailed bundle information",
},
concurrency: {
number: true,
describe: "how many threads to use to parse notes",
},
}
function escapePath(fp) {
return fp
.replace(/\\ /g, " ") // unescape spaces
.replace(/^".*"$/, "$1")
.replace(/^'.*"$/, "$1")
.trim()
}
function exitIfCancel(val) {
if (isCancel(val)) {
outro(chalk.red("Exiting"))
process.exit(0)
} else {
return val
}
}
async function stashContentFolder(contentFolder) {
await fs.promises.rm(contentCacheFolder, { force: true, recursive: true })
await fs.promises.cp(contentFolder, contentCacheFolder, {
force: true,
recursive: true,
verbatimSymlinks: true,
preserveTimestamps: true,
})
await fs.promises.rm(contentFolder, { force: true, recursive: true })
}
async function popContentFolder(contentFolder) {
await fs.promises.rm(contentFolder, { force: true, recursive: true })
await fs.promises.cp(contentCacheFolder, contentFolder, {
force: true,
recursive: true,
verbatimSymlinks: true,
preserveTimestamps: true,
})
await fs.promises.rm(contentCacheFolder, { force: true, recursive: true })
}
function gitPull(origin, branch) {
const flags = ["--no-rebase", "--autostash", "-s", "recursive", "-X", "ours", "--no-edit"]
const out = spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" })
if (out.stderr) {
throw new Error(`Error while pulling updates: ${out.stderr}`)
}
}
yargs(hideBin(process.argv))
.scriptName("quartz")
.version(version)
.usage("$0 <cmd> [args]")
.command("create", "Initialize Quartz", CommonArgv, async (argv) => {
console.log()
intro(chalk.bgGreen.black(` Quartz v${version} `))
const contentFolder = path.join(cwd, argv.directory)
const setupStrategy = exitIfCancel(
await select({
message: `Choose how to initialize the content in \`${contentFolder}\``,
options: [
{ value: "new", label: "Empty Quartz" },
{ value: "copy", label: "Copy an existing folder", hint: "overwrites `content`" },
{
value: "symlink",
label: "Symlink an existing folder",
hint: "don't select this unless you know what you are doing!",
},
{ value: "keep", label: "Keep the existing files" },
],
}),
)
async function rmContentFolder() {
const contentStat = await fs.promises.lstat(contentFolder)
if (contentStat.isSymbolicLink()) {
await fs.promises.unlink(contentFolder)
} else {
await rimraf(contentFolder)
}
}
if (setupStrategy === "copy" || setupStrategy === "symlink") {
const originalFolder = escapePath(
exitIfCancel(
await text({
message: "Enter the full path to existing content folder",
placeholder:
"On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path",
validate(fp) {
const fullPath = escapePath(fp)
if (!fs.existsSync(fullPath)) {
return "The given path doesn't exist"
} else if (!fs.lstatSync(fullPath).isDirectory()) {
return "The given path is not a folder"
}
},
}),
),
)
await rmContentFolder()
if (setupStrategy === "copy") {
await fs.promises.cp(originalFolder, contentFolder, {
recursive: true,
preserveTimestamps: true,
})
} else if (setupStrategy === "symlink") {
await fs.promises.symlink(originalFolder, contentFolder, "dir")
}
} else if (setupStrategy === "new") {
await rmContentFolder()
await fs.promises.mkdir(contentFolder)
await fs.promises.writeFile(
path.join(contentFolder, "index.md"),
`---
title: Welcome to Quartz
---
This is a blank Quartz installation.
See the [documentation](https://quartz.jzhao.xyz) for how to get started.
`,
)
}
// get a prefered link resolution strategy
const linkResolutionStrategy = exitIfCancel(
await select({
message: `Choose how Quartz should resolve links in your content. You can change this later in \`quartz.config.ts\`.`,
options: [
{
value: "absolute",
label: "Treat links as absolute path",
hint: "for content made for Quartz 3 and Hugo",
},
{
value: "shortest",
label: "Treat links as shortest path",
hint: "for most Obsidian vaults",
},
{
value: "relative",
label: "Treat links as relative paths",
hint: "for just normal Markdown files",
},
],
}),
)
// now, do config changes
const configFilePath = path.join(cwd, "quartz.config.ts")
let configContent = await fs.promises.readFile(configFilePath, { encoding: "utf-8" })
configContent = configContent.replace(
/markdownLinkResolution: '(.+)'/,
`markdownLinkResolution: '${linkResolutionStrategy}'`,
)
await fs.promises.writeFile(configFilePath, configContent)
outro(`You're all set! Not sure what to do next? Try:
• Customizing Quartz a bit more by editing \`quartz.config.ts\`
• Running \`npx quartz build --serve\` to preview your Quartz locally
• Hosting your Quartz online (see: https://quartz.jzhao.xyz/setup/hosting)
`)
})
.command("update", "Get the latest Quartz updates", CommonArgv, async (argv) => {
const contentFolder = path.join(cwd, argv.directory)
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
console.log("Backing up your content")
execSync(
`git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`,
)
await stashContentFolder(contentFolder)
console.log(
"Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.",
)
gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH)
await popContentFolder(contentFolder)
console.log("Ensuring dependencies are up to date")
spawnSync("npm", ["i"], { stdio: "inherit" })
console.log(chalk.green("Done!"))
})
.command(
"restore",
"Try to restore your content folder from the cache",
CommonArgv,
async (argv) => {
const contentFolder = path.join(cwd, argv.directory)
await popContentFolder(contentFolder)
},
)
.command("sync", "Sync your Quartz to and from GitHub.", SyncArgv, async (argv) => {
const contentFolder = path.join(cwd, argv.directory)
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
console.log("Backing up your content")
if (argv.commit) {
const contentStat = await fs.promises.lstat(contentFolder)
if (contentStat.isSymbolicLink()) {
const linkTarg = await fs.promises.readlink(contentFolder)
console.log(chalk.yellow("Detected symlink, trying to dereference before committing"))
// stash symlink file
await stashContentFolder(contentFolder)
// follow symlink and copy content
await fs.promises.cp(linkTarg, contentFolder, {
recursive: true,
preserveTimestamps: true,
})
}
const currentTimestamp = new Date().toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
})
spawnSync("git", ["add", "."], { stdio: "inherit" })
spawnSync("git", ["commit", "-m", `Quartz sync: ${currentTimestamp}`], { stdio: "inherit" })
if (contentStat.isSymbolicLink()) {
// put symlink back
await popContentFolder(contentFolder)
}
}
await stashContentFolder(contentFolder)
if (argv.pull) {
console.log(
"Pulling updates from your repository. You may need to resolve some `git` conflicts if you've made changes to components or plugins.",
)
gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH)
}
await popContentFolder(contentFolder)
if (argv.push) {
console.log("Pushing your changes")
spawnSync("git", ["push", "-f", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], { stdio: "inherit" })
}
console.log(chalk.green("Done!"))
})
.command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => {
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
const ctx = await esbuild.context({
entryPoints: [fp],
outfile: path.join("quartz", cacheFile),
bundle: true,
keepNames: true,
minifyWhitespace: true,
minifySyntax: true,
platform: "node",
format: "esm",
jsx: "automatic",
jsxImportSource: "preact",
packages: "external",
metafile: true,
sourcemap: true,
sourcesContent: false,
plugins: [
sassPlugin({
type: "css-text",
cssImports: true,
}),
{
name: "inline-script-loader",
setup(build) {
build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => {
let text = await promises.readFile(args.path, "utf8")
// remove default exports that we manually inserted
text = text.replace("export default", "")
text = text.replace("export", "")
const sourcefile = path.relative(path.resolve("."), args.path)
const resolveDir = path.dirname(sourcefile)
const transpiled = await esbuild.build({
stdin: {
contents: text,
loader: "ts",
resolveDir,
sourcefile,
},
write: false,
bundle: true,
platform: "browser",
format: "esm",
})
const rawMod = transpiled.outputFiles[0].text
return {
contents: rawMod,
loader: "text",
}
})
},
},
],
})
const buildMutex = new Mutex()
const timeoutIds = new Set()
const build = async (clientRefresh) => {
await buildMutex.acquire()
const result = await ctx.rebuild().catch((err) => {
console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`)
console.log(`Reason: ${chalk.grey(err)}`)
process.exit(1)
})
if (argv.bundleInfo) {
const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs"
const meta = result.metafile.outputs[outputFileName]
console.log(
`Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes(
meta.bytes,
)})`,
)
console.log(await esbuild.analyzeMetafile(result.metafile, { color: true }))
}
// bypass module cache
// https://github.com/nodejs/modules/issues/307
const { default: buildQuartz } = await import(cacheFile + `?update=${randomUUID()}`)
await buildQuartz(argv, clientRefresh)
clientRefresh()
buildMutex.release()
}
const rebuild = (clientRefresh) => {
timeoutIds.forEach((id) => clearTimeout(id))
timeoutIds.add(setTimeout(() => build(clientRefresh), 250))
}
if (argv.serve) {
const wss = new WebSocketServer({ port: 3001 })
const connections = []
wss.on("connection", (ws) => connections.push(ws))
const clientRefresh = () => connections.forEach((conn) => conn.send("rebuild"))
if (argv.baseDir !== "" && !argv.baseDir.startsWith("/")) {
argv.baseDir = "/" + argv.baseDir
}
await build(clientRefresh)
const server = http.createServer(async (req, res) => {
if (argv.baseDir && !req.url?.startsWith(argv.baseDir)) {
console.log(
chalk.red(
`[404] ${req.url} (warning: link outside of site, this is likely a Quartz bug)`,
),
)
res.writeHead(404)
res.end()
return
}
// strip baseDir prefix
req.url = req.url?.slice(argv.baseDir.length)
const serve = async () => {
await serveHandler(req, res, {
public: argv.output,
directoryListing: false,
})
const status = res.statusCode
const statusString =
status >= 200 && status < 300 ? chalk.green(`[${status}]`) : chalk.red(`[${status}]`)
console.log(statusString + chalk.grey(` ${argv.baseDir}${req.url}`))
}
const redirect = (newFp) => {
newFp = argv.baseDir + newFp
res.writeHead(302, {
Location: newFp,
})
console.log(chalk.yellow("[302]") + chalk.grey(` ${argv.baseDir}${req.url} -> ${newFp}`))
res.end()
}
let fp = req.url?.split("?")[0] ?? "/"
// handle redirects
if (fp.endsWith("/")) {
// /trailing/
// does /trailing/index.html exist? if so, serve it
const indexFp = path.posix.join(fp, "index.html")
if (fs.existsSync(path.posix.join(argv.output, indexFp))) {
req.url = fp
return serve()
}
// does /trailing.html exist? if so, redirect to /trailing
let base = fp.slice(0, -1)
if (path.extname(base) === "") {
base += ".html"
}
if (fs.existsSync(path.posix.join(argv.output, base))) {
return redirect(fp.slice(0, -1))
}
} else {
// /regular
// does /regular.html exist? if so, serve it
let base = fp
if (path.extname(base) === "") {
base += ".html"
}
if (fs.existsSync(path.posix.join(argv.output, base))) {
req.url = fp
return serve()
}
// does /regular/index.html exist? if so, redirect to /regular/
let indexFp = path.posix.join(fp, "index.html")
if (fs.existsSync(path.posix.join(argv.output, indexFp))) {
return redirect(fp + "/")
}
}
return serve()
})
server.listen(argv.port)
console.log(
chalk.cyan(
`Started a Quartz server listening at http://localhost:${argv.port}${argv.baseDir}`,
),
)
console.log("hint: exit with ctrl+c")
chokidar
.watch(["**/*.ts", "**/*.tsx", "**/*.scss", "package.json"], {
ignoreInitial: true,
})
.on("all", async () => {
console.log(chalk.yellow("Detected a source code change, doing a hard rebuild..."))
rebuild(clientRefresh)
})
} else {
await build(() => {})
ctx.dispose()
}
})
.showHelpOnFail(false)
.help()
.strict()
.demandCommand().argv

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
import workerpool from "workerpool"
const cacheFile = "./.quartz-cache/transpiled-worker.mjs"
const { parseFiles } = await import(cacheFile)
workerpool.worker({
parseFiles,
})

172
quartz/build.ts Normal file
View File

@@ -0,0 +1,172 @@
import sourceMapSupport from "source-map-support"
sourceMapSupport.install(options)
import path from "path"
import { PerfTimer } from "./util/perf"
import { rimraf } from "rimraf"
import { 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 chokidar from "chokidar"
import { ProcessedContent } from "./plugins/vfile"
import { Argv, BuildCtx } from "./util/ctx"
import { glob, toPosixPath } from "./util/glob"
import { trace } from "./util/trace"
import { options } from "./util/sourcemap"
import { Mutex } from "async-mutex"
async function buildQuartz(argv: Argv, clientRefresh: () => void) {
const ctx: BuildCtx = {
argv,
cfg,
allSlugs: [],
}
const perf = new PerfTimer()
const output = argv.output
const pluginCount = Object.values(cfg.plugins).flat().length
const pluginNames = (key: "transformers" | "filters" | "emitters") =>
cfg.plugins[key].map((plugin) => plugin.name)
if (argv.verbose) {
console.log(`Loaded ${pluginCount} plugins`)
console.log(` Transformers: ${pluginNames("transformers").join(", ")}`)
console.log(` Filters: ${pluginNames("filters").join(", ")}`)
console.log(` Emitters: ${pluginNames("emitters").join(", ")}`)
}
perf.addEvent("clean")
await rimraf(output)
console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`)
perf.addEvent("glob")
const allFiles = await glob("**/*.*", argv.directory, cfg.configuration.ignorePatterns)
const fps = allFiles.filter((fp) => fp.endsWith(".md"))
console.log(
`Found ${fps.length} input files from \`${argv.directory}\` in ${perf.timeSince("glob")}`,
)
const filePaths = fps.map((fp) => joinSegments(argv.directory, fp) as FilePath)
ctx.allSlugs = allFiles.map((fp) => slugifyFilePath(fp as FilePath))
const parsedFiles = await parseMarkdown(ctx, filePaths)
const filteredContent = filterContent(ctx, parsedFiles)
await emitContent(ctx, filteredContent)
console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
if (argv.serve) {
return startServing(ctx, parsedFiles, clientRefresh)
}
}
// setup watcher for rebuilds
async function startServing(
ctx: BuildCtx,
initialContent: ProcessedContent[],
clientRefresh: () => void,
) {
const { argv } = ctx
const ignored = await isGitIgnored()
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
const buildMutex = new Mutex()
const timeoutIds: Set<ReturnType<typeof setTimeout>> = new Set()
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)
}
timeoutIds.forEach((id) => clearTimeout(id))
// debounce rebuilds every 250ms
timeoutIds.add(
setTimeout(async () => {
await buildMutex.acquire()
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)
}
await rimraf(argv.output)
const parsedFiles = [...contentMap.values()]
const filteredContent = filterContent(ctx, parsedFiles)
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()
buildMutex.release()
}, 250),
)
}
const watcher = chokidar.watch(".", {
persistent: true,
cwd: argv.directory,
ignoreInitial: true,
})
watcher
.on("add", (fp) => rebuild(fp, "add"))
.on("change", (fp) => rebuild(fp, "change"))
.on("unlink", (fp) => rebuild(fp, "delete"))
}
export default async (argv: Argv, clientRefresh: () => void) => {
try {
return await buildQuartz(argv, clientRefresh)
} catch (err) {
trace("\nExiting Quartz due to a fatal error", err as Error)
}
}

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