diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 189648d..7f1576d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 2c9c226..e766b49 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -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.** diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2a6e70c..2bc953b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: Build and Test +name: Build and Test on: push: @@ -34,4 +34,4 @@ jobs: run: npm test - name: Ensure Quartz builds - run: npx quartz build + run: npx quartz build diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index c624fd5..887a2c4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -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). diff --git a/content/advanced/creating components.md b/content/advanced/creating components.md index f2fa635..9467ef8 100644 --- a/content/advanced/creating components.md +++ b/content/advanced/creating components.md @@ -2,4 +2,4 @@ title: Creating your own Quartz components --- -See the [component listing](/tags/component) for a full-list of the Quartz built-in components. \ No newline at end of file +See the [component listing](/tags/component) for a full-list of the Quartz built-in components. diff --git a/content/advanced/making plugins.md b/content/advanced/making plugins.md index bdd9194..661b516 100644 --- a/content/advanced/making plugins.md +++ b/content/advanced/making plugins.md @@ -5,6 +5,7 @@ title: Making your own plugins This part of the documentation will assume you have some basic coding knowledge and will include code snippets that describe the interface of what Quartz plugins should look like. ## Transformers + ```ts export type QuartzTransformerPluginInstance = { name: string @@ -17,4 +18,4 @@ export type QuartzTransformerPluginInstance = { ## Filters -## Emitters \ No newline at end of file +## Emitters diff --git a/content/configuration.md b/content/configuration.md index 21d11eb..9f103f4 100644 --- a/content/configuration.md +++ b/content/configuration.md @@ -2,7 +2,7 @@ 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`. +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`. If you edit this file 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. @@ -16,33 +16,35 @@ const config: QuartzConfig = { ``` ## 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`: used as an anchor to return to the home page. 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: }`: use Google Analytics -- `caononicalUrl`: sometimes called `baseURL` in other site generators. 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. `https://quartz.jzhao.xyz/` for this site). 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. +- `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: }`: use Google Analytics +- `caononicalUrl`: sometimes called `baseURL` in other site generators. 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. `https://quartz.jzhao.xyz/` for this site). 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]() patterns that Quartz should ignore and not search through when looking for files inside the `content` folder. - `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]] + - `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]] @@ -62,18 +64,19 @@ plugins: { 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. +> 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 + Plugin.FrontMatter(), // uses default options + Plugin.Latex({ renderEngine: "katex" }), // specify some options ] ``` ### Layout -Certain emitters may also output [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) files. To make sure that + +Certain emitters may also output [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) files. To make sure that ### Components diff --git a/content/features/Latex.md b/content/features/Latex.md index 04bc366..3f523d6 100644 --- a/content/features/Latex.md +++ b/content/features/Latex.md @@ -3,6 +3,7 @@ Quartz uses [Katex](https://katex.org/) by default to typeset both inline and bl ## Formatting ### Block Math + Block math can be rendered by delimiting math expression with `$$`. ``` @@ -20,20 +21,25 @@ f(x) = \int_{-\infty}^\infty $$ ### 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. + +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` \ No newline at end of file +- Plugin: `quartz/plugins/transformers/latex.ts` diff --git a/content/features/Mermaid diagrams.md b/content/features/Mermaid diagrams.md index 2d43406..91d0988 100644 --- a/content/features/Mermaid diagrams.md +++ b/content/features/Mermaid diagrams.md @@ -1,5 +1,5 @@ > [!warning] -> Wondering why Mermaid diagrams may not be showing up even if you have them enabled? You may need to reorder your plugins so that `Plugin.ObsidianFlavoredMarkdown()` is *after* `Plugin.SyntaxHighlighting()`. +> Wondering why Mermaid diagrams may not be showing up even if you have them enabled? You may need to reorder your plugins so that `Plugin.ObsidianFlavoredMarkdown()` is _after_ `Plugin.SyntaxHighlighting()`. ```mermaid sequenceDiagram diff --git a/content/features/SPA Routing.md b/content/features/SPA Routing.md index e46b4c7..9124993 100644 --- a/content/features/SPA Routing.md +++ b/content/features/SPA Routing.md @@ -1 +1 @@ -Single-page-app style rendering. This prevents flashes of unstyled content and improves smoothness of Quartz \ No newline at end of file +Single-page-app style rendering. This prevents flashes of unstyled content and improves smoothness of Quartz diff --git a/content/features/backlinks.md b/content/features/backlinks.md index b889e3b..4df7fd1 100644 --- a/content/features/backlinks.md +++ b/content/features/backlinks.md @@ -1,13 +1,14 @@ --- title: Backlinks tags: -- component + - 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.config.ts`. - Component: `quartz/components/Backlinks.tsx` - Style: `quartz/components/styles/backlinks.scss` -- Script: `quartz/components/scripts/search.inline.ts` \ No newline at end of file +- Script: `quartz/components/scripts/search.inline.ts` diff --git a/content/features/callouts.md b/content/features/callouts.md index 6f33dff..cb5feb9 100644 --- a/content/features/callouts.md +++ b/content/features/callouts.md @@ -3,15 +3,16 @@ title: Callouts --- > [!warning] -> Wondering why callouts may not be showing up even if you have them enabled? You may need to reorder your plugins so that `Plugin.ObsidianFlavoredMarkdown()` is *after* `Plugin.SyntaxHighlighting()`. - +> Wondering why callouts may not be showing up even if you have them enabled? You may need to reorder your plugins so that `Plugin.ObsidianFlavoredMarkdown()` is _after_ `Plugin.SyntaxHighlighting()`. > [!info] > Default title > [!question]+ Can callouts be nested? +> > > [!todo]- Yes!, they can. -> > > [!example] You can even use multiple layers of nesting. +> > +> > > [!example] You can even use multiple layers of nesting. > [!EXAMPLE] Examples > @@ -21,31 +22,31 @@ title: Callouts > > Aliases: note -> [!abstract] Summaries +> [!abstract] Summaries > > Aliases: abstract, summary, tldr -> [!info] Info +> [!info] Info > > Aliases: info, todo -> [!tip] Hint +> [!tip] Hint > > Aliases: tip, hint, important -> [!success] Success +> [!success] Success > > Aliases: success, check, done -> [!question] Question +> [!question] Question > > Aliases: question, help, faq -> [!warning] Warning +> [!warning] Warning > > Aliases: warning, caution, attention -> [!failure] Failure +> [!failure] Failure > > Aliases: failure, fail, missing diff --git a/content/features/full-text search.md b/content/features/full-text search.md index 376e467..cb0567f 100644 --- a/content/features/full-text search.md +++ b/content/features/full-text search.md @@ -1,12 +1,12 @@ --- title: Full-text Search -tags: -- component +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. +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). @@ -14,13 +14,15 @@ This component is also keyboard accessible: Tab and Shift+Tab will cycle forward > 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.config.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 + - You can edit `contextWindowWords` or `numSearchResults` to suit your needs diff --git a/content/features/graph view.md b/content/features/graph view.md index 5b71950..9fb31be 100644 --- a/content/features/graph view.md +++ b/content/features/graph view.md @@ -1,13 +1,13 @@ --- title: "Graph View" tags: -- component + - component --- -Quartz features a graph-view that can show both a local graph view and a global graph view. +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. +- 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. @@ -17,6 +17,7 @@ Additionally, similar to how browsers highlight visited links a different colour > 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: @@ -26,13 +27,13 @@ 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 + 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 + 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? + 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, @@ -43,8 +44,8 @@ Component.Graph({ centerForce: 0.3, linkDistance: 30, fontSize: 0.6, - opacityScale: 1 - } + opacityScale: 1, + }, }) ``` @@ -55,4 +56,4 @@ Want to customize it even more? - Removing graph view: delete all usages of `Component.Graph()` from `quartz.config.ts`. - Component: `quartz/components/Graph.tsx` - Style: `quartz/components/styles/graph.scss` -- Script: `quartz/components/scripts/graph.inline.ts` \ No newline at end of file +- Script: `quartz/components/scripts/graph.inline.ts` diff --git a/content/features/index.md b/content/features/index.md index 05bb9cd..2997b3a 100644 --- a/content/features/index.md +++ b/content/features/index.md @@ -1,3 +1,3 @@ --- title: Feature List ---- \ No newline at end of file +--- diff --git a/content/features/popover previews.md b/content/features/popover previews.md index 5eb69a4..7bf608e 100644 --- a/content/features/popover previews.md +++ b/content/features/popover previews.md @@ -9,6 +9,7 @@ By default, Quartz only fetches previews for pages inside your vault due to [COR When [[creating components|creating your own components]], you can include this `popover-hint` class to also include it in the popover. ## 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` \ No newline at end of file +- Script: `quartz/components/scripts/popover.inline.ts` diff --git a/content/features/syntax highlighting.md b/content/features/syntax highlighting.md index 8f06eae..16d3fa9 100644 --- a/content/features/syntax highlighting.md +++ b/content/features/syntax highlighting.md @@ -12,6 +12,7 @@ In short, it generates HTML that looks exactly like your code in an editor like > 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. ```` @@ -37,11 +38,12 @@ export function trimPathSuffix(fp: string): string { ``` ### Titles + Add a file title to your code block, with text inside double quotes (`""`): ```` ```js title="..." - + ``` ```` @@ -56,11 +58,12 @@ export function trimPathSuffix(fp: string): string { ``` ### Line highlighting + Place a numeric range inside `{}`. ```` ```js {1-3,4} - + ``` ```` @@ -75,6 +78,7 @@ export function trimPathSuffix(fp: string): string { ``` ### Word highlighting + A series of characters, like a literal regex. ```` @@ -85,16 +89,17 @@ const [name, setName] = useState('Taylor'); ```` ```js /useState/ -const [age, setAge] = useState(50); -const [name, setName] = useState('Taylor'); +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} - + ``` ```` @@ -109,6 +114,7 @@ export function trimPathSuffix(fp: string): string { ``` ### 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. ````` @@ -121,6 +127,7 @@ 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` \ No newline at end of file +- Plugin: `quartz/plugins/transformers/syntax.ts` diff --git a/content/features/table of contents.md b/content/features/table of contents.md index 5445771..7019115 100644 --- a/content/features/table of contents.md +++ b/content/features/table of contents.md @@ -1,5 +1,5 @@ --- title: "Table of Contents" tags: -- component ---- \ No newline at end of file + - component +--- diff --git a/content/features/upcoming features.md b/content/features/upcoming features.md index 31bbc4e..6d39bb7 100644 --- a/content/features/upcoming features.md +++ b/content/features/upcoming features.md @@ -12,9 +12,9 @@ draft: true - custom md blocks (e.g. for poetry) - sidenotes? [https://github.com/capnfabs/paperesque](https://github.com/capnfabs/paperesque) - watch mode - - watch for markdown changes and quartz config changes - - markdown changes only involve processing that single markdown file (at least for parsing) and then rerunning the filter and emitters - - config changes rebuild the whole thing + - watch for markdown changes and quartz config changes + - markdown changes only involve processing that single markdown file (at least for parsing) and then rerunning the filter and emitters + - config changes rebuild the whole thing - direct match in search using double quotes - attachments path - [https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI](https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI) @@ -26,8 +26,8 @@ draft: true - audio/video embed styling - Canvas - mermaid styling: [https://mermaid.js.org/config/theming.html#theme-variables-reference-table](https://mermaid.js.org/config/theming.html#theme-variables-reference-table) - - [https://github.com/jackyzha0/quartz/issues/331](https://github.com/jackyzha0/quartz/issues/331) + - [https://github.com/jackyzha0/quartz/issues/331](https://github.com/jackyzha0/quartz/issues/331) - block links: [https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+block+in+a+note](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](https://help.obsidian.md/Linking+notes+and+files/Embedding+files) - parse all images in page: use this for page lists if applicable? -- CV mode? with print stylesheet \ No newline at end of file +- CV mode? with print stylesheet diff --git a/content/index.md b/content/index.md index ff51dab..8e012fa 100644 --- a/content/index.md +++ b/content/index.md @@ -5,6 +5,7 @@ title: Welcome to Quartz 4 Quartz is a fast, batteries-included static-site generator that transforms Markdown content into fully functional websites. Thousands of students, developers, and teachers are [[showcase|already using Quartz]] to publish personal notes, wikis, and [digital gardens](https://jzhao.xyz/posts/networked-thought/) to the web. ## 🪴 Get Started + Quartz requires **at least [Node](https://nodejs.org/) v16** to function correctly. In your terminal of choice, enter the following commands line by line: ```shell @@ -26,7 +27,8 @@ When you're ready, you can edit `quartz.config.ts` to customize and configure Qu - [[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 the *why* behind these features on the [[philosophy]] page. +For a comprehensive list of features, visit the [features page](/features). You can read more the _why_ behind these features on the [[philosophy]] page. ### 🚧 Troubleshooting + Having trouble with Quartz? Try searching for your issue using the search feature. 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). diff --git a/content/philosophy.md b/content/philosophy.md index f7ff6af..ecd856b 100644 --- a/content/philosophy.md +++ b/content/philosophy.md @@ -5,7 +5,7 @@ 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. @@ -20,7 +20,7 @@ Quartz embraces the inherent rhizomatic and web-like nature of our thinking and The goal of digital gardening should be to tap into your network’s 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. It’s a form of expression and sharing. +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. It’s 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 diff --git a/content/tags/component.md b/content/tags/component.md index 5b56fbd..57592e8 100644 --- a/content/tags/component.md +++ b/content/tags/component.md @@ -2,4 +2,4 @@ title: Components --- -Want to create your own custom component? Check out the advanced guide on [[creating components]] for more information. \ No newline at end of file +Want to create your own custom component? Check out the advanced guide on [[creating components]] for more information. diff --git a/globals.d.ts b/globals.d.ts index 6cc2479..4473d59 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -1,8 +1,10 @@ export declare global { interface Document { - addEventListener(type: K, - listener: (this: Document, ev: CustomEventMap[K]) => void): void; - dispatchEvent(ev: CustomEventMap[K]): void; + addEventListener( + type: K, + listener: (this: Document, ev: CustomEventMap[K]) => void, + ): void + dispatchEvent(ev: CustomEventMap[K]): void } interface Window { spaNavigate(url: URL, isBack: boolean = false) diff --git a/index.d.ts b/index.d.ts index b98a5be..4a93f16 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,11 +1,11 @@ -declare module '*.scss' { +declare module "*.scss" { const content: string export = content } // dom custom event interface CustomEventMap { - "nav": CustomEvent<{ url: CanonicalSlug }>; + nav: CustomEvent<{ url: CanonicalSlug }> } declare const fetchData: Promise diff --git a/quartz.config.ts b/quartz.config.ts index cd1e9ef..d6aed2f 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -7,7 +7,7 @@ const generalConfiguration: GlobalConfiguration = { enableSPA: true, enablePopovers: true, analytics: { - provider: 'plausible', + provider: "plausible", }, baseUrl: "quartz.jzhao.xyz", ignorePatterns: ["private", "templates"], @@ -19,27 +19,27 @@ const generalConfiguration: GlobalConfiguration = { }, colors: { lightMode: { - light: '#faf8f8', - lightgray: '#e5e5e5', - gray: '#b8b8b8', - darkgray: '#4e4e4e', - dark: '#2b2b2b', - secondary: '#284b63', - tertiary: '#84a59d', - highlight: 'rgba(143, 159, 169, 0.15)', + 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)', + light: "#161618", + lightgray: "#393639", + gray: "#646464", + darkgray: "#d4d4d4", + dark: "#ebebec", + secondary: "#7b97aa", + tertiary: "#84a59d", + highlight: "rgba(143, 159, 169, 0.15)", }, - } - } + }, + }, } const sharedPageComponents = { @@ -47,18 +47,14 @@ const sharedPageComponents = { header: [], footer: Component.Footer({ links: { - "GitHub": "https://github.com/jackyzha0/quartz", - "Discord Community": "https://discord.gg/cRFFHYye7t" - } - }) + GitHub: "https://github.com/jackyzha0/quartz", + "Discord Community": "https://discord.gg/cRFFHYye7t", + }, + }), } const contentPageLayout: PageLayout = { - beforeBody: [ - Component.ArticleTitle(), - Component.ReadingTime(), - Component.TagList(), - ], + beforeBody: [Component.ArticleTitle(), Component.ReadingTime(), Component.TagList()], left: [ Component.PageTitle(), Component.MobileOnly(Component.Spacer()), @@ -66,21 +62,16 @@ const contentPageLayout: PageLayout = { Component.Darkmode(), Component.DesktopOnly(Component.TableOfContents()), ], - right: [ - Component.Graph(), - Component.Backlinks(), - ], + right: [Component.Graph(), Component.Backlinks()], } const listPageLayout: PageLayout = { - beforeBody: [ - Component.ArticleTitle() - ], + beforeBody: [Component.ArticleTitle()], left: [ Component.PageTitle(), Component.MobileOnly(Component.Spacer()), Component.Search(), - Component.Darkmode() + Component.Darkmode(), ], right: [], } @@ -92,18 +83,16 @@ const config: QuartzConfig = { Plugin.FrontMatter(), Plugin.TableOfContents(), Plugin.CreatedModifiedDate({ - priority: ['frontmatter', 'filesystem'] // you can add 'git' here for last modified from Git but this makes the build slower + priority: ["frontmatter", "filesystem"], // you can add 'git' here for last modified from Git but this makes the build slower }), Plugin.SyntaxHighlighting(), Plugin.ObsidianFlavoredMarkdown(), Plugin.GitHubFlavoredMarkdown(), - Plugin.CrawlLinks({ markdownLinkResolution: 'shortest' }), - Plugin.Latex({ renderEngine: 'katex' }), + Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), + Plugin.Latex({ renderEngine: "katex" }), Plugin.Description(), ], - filters: [ - Plugin.RemoveDrafts(), - ], + filters: [Plugin.RemoveDrafts()], emitters: [ Plugin.AliasRedirects(), Plugin.ContentPage({ @@ -125,7 +114,7 @@ const config: QuartzConfig = { enableSiteMap: true, enableRSS: true, }), - ] + ], }, } diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs index b4bf3a9..f25484b 100755 --- a/quartz/bootstrap-cli.mjs +++ b/quartz/bootstrap-cli.mjs @@ -1,19 +1,19 @@ #!/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 prettyBytes from 'pretty-bytes' -import { spawnSync } from 'child_process' +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 prettyBytes from "pretty-bytes" +import { spawnSync } from "child_process" -const UPSTREAM_NAME = 'upstream' -const QUARTZ_SOURCE_BRANCH = 'v4-alpha' +const UPSTREAM_NAME = "upstream" +const QUARTZ_SOURCE_BRANCH = "v4-alpha" const cwd = process.cwd() const cacheDir = path.join(cwd, ".quartz-cache") const cacheFile = "./.quartz-cache/transpiled-build.mjs" @@ -24,16 +24,16 @@ const contentCacheFolder = path.join(cacheDir, "content-cache") const CommonArgv = { directory: { string: true, - alias: ['d'], - default: 'content', - describe: 'directory to look for content files' + alias: ["d"], + default: "content", + describe: "directory to look for content files", }, verbose: { boolean: true, - alias: ['v'], + alias: ["v"], default: false, - describe: 'print out extra logging information' - } + describe: "print out extra logging information", + }, } const SyncArgv = { @@ -41,47 +41,46 @@ const SyncArgv = { commit: { boolean: true, default: true, - describe: 'create a git commit for your unsaved changes' + describe: "create a git commit for your unsaved changes", }, push: { boolean: true, default: true, - describe: 'push updates to your Quartz fork' + describe: "push updates to your Quartz fork", }, force: { boolean: true, - alias: ['f'], + alias: ["f"], default: true, - describe: 'whether to apply the --force flag to git commands' + describe: "whether to apply the --force flag to git commands", }, pull: { boolean: true, default: true, - describe: 'pull updates from your Quartz fork' - } + describe: "pull updates from your Quartz fork", + }, } const BuildArgv = { ...CommonArgv, output: { string: true, - alias: ['o'], - default: 'public', - describe: 'output folder for files' + alias: ["o"], + default: "public", + describe: "output folder for files", }, serve: { boolean: true, default: false, - describe: 'run a local server to live-preview your Quartz' + describe: "run a local server to live-preview your Quartz", }, port: { number: true, default: 8080, - describe: 'port to serve Quartz on' + describe: "port to serve Quartz on", }, } - function escapePath(fp) { return fp .replace(/\\ /g, " ") // unescape spaces @@ -91,7 +90,6 @@ function escapePath(fp) { } function exitIfCancel(val) { - if (isCancel(val)) { outro(chalk.red("Exiting")) process.exit(0) @@ -101,32 +99,48 @@ function exitIfCancel(val) { } async function stashContentFolder(contentFolder) { - await fs.promises.cp(contentFolder, contentCacheFolder, { force: true, recursive: true, verbatimSymlinks: true, preserveTimestamps: 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.cp(contentCacheFolder, contentFolder, { force: true, recursive: true, verbatimSymlinks: true, preserveTimestamps: true }) + await fs.promises.cp(contentCacheFolder, contentFolder, { + force: true, + recursive: true, + verbatimSymlinks: true, + preserveTimestamps: true, + }) await fs.promises.rm(contentCacheFolder, { force: true, recursive: true }) } yargs(hideBin(process.argv)) .scriptName("quartz") .version(version) - .usage('$0 [args]') - .command('create', 'Initialize Quartz', CommonArgv, async argv => { + .usage("$0 [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: "Replace with 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" }, - ] - })) + const setupStrategy = exitIfCancel( + await select({ + message: `Choose how to initialize the content in \`${contentFolder}\``, + options: [ + { value: "new", label: "Empty Quartz" }, + { value: "copy", label: "Replace with 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) @@ -139,54 +153,77 @@ yargs(hideBin(process.argv)) } } - 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" - } - } - }))) + 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') { + if (setupStrategy === "copy") { await fs.promises.cp(originalFolder, contentFolder, { recursive: true }) - } else if (setupStrategy === 'symlink') { - await fs.promises.symlink(originalFolder, contentFolder, 'dir') + } else if (setupStrategy === "symlink") { + await fs.promises.symlink(originalFolder, contentFolder, "dir") } - } else if (setupStrategy === 'new') { + } else if (setupStrategy === "new") { await rmContentFolder() await fs.promises.mkdir(contentFolder) - await fs.promises.writeFile(path.join(contentFolder, "index.md"), + 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" }, - ] - })) + 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}'`) + 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: @@ -195,105 +232,120 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. • Hosting your Quartz online (see: https://quartz.jzhao.xyz/setup/hosting) `) }) - .command('update', 'Get the latest Quartz updates', CommonArgv, async argv => { + .command("update", "Get the latest Quartz updates", CommonArgv, async (argv) => { const contentFolder = path.join(cwd, argv.directory) console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) - console.log('Backing up your content') + console.log("Backing up your content") await stashContentFolder(contentFolder) - console.log("Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.") - spawnSync('git', ['pull', UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH], { stdio: 'inherit' }) + console.log( + "Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.", + ) + spawnSync("git", ["pull", UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH], { stdio: "inherit" }) await popContentFolder(contentFolder) - console.log(chalk.green('Done!')) + console.log(chalk.green("Done!")) }) - .command('sync', 'Sync your Quartz to and from GitHub.', SyncArgv, async argv => { + .command("sync", "Sync your Quartz to and from GitHub.", SyncArgv, async (argv) => { const contentFolder = path.join(cwd, argv.directory) console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) - console.log('Backing up your content') + console.log("Backing up your content") if (argv.commit) { - const currentTimestamp = new Date().toLocaleString('en-US', { dateStyle: "medium", timeStyle: "short" }) - spawnSync('git', ['commit', '-am', `Quartz sync: ${currentTimestamp}`], { stdio: 'inherit' }) + const currentTimestamp = new Date().toLocaleString("en-US", { + dateStyle: "medium", + timeStyle: "short", + }) + spawnSync("git", ["commit", "-am", `Quartz sync: ${currentTimestamp}`], { stdio: "inherit" }) } 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.") - spawnSync('git', ['pull', 'origin', QUARTZ_SOURCE_BRANCH], { stdio: 'inherit' }) + console.log( + "Pulling updates from your repository. You may need to resolve some `git` conflicts if you've made changes to components or plugins.", + ) + spawnSync("git", ["pull", "origin", QUARTZ_SOURCE_BRANCH], { stdio: "inherit" }) } await popContentFolder(contentFolder) if (argv.push) { console.log("Pushing your changes") - const args = argv.force ? - ['push', '-f', 'origin', QUARTZ_SOURCE_BRANCH] : - ['push', 'origin', QUARTZ_SOURCE_BRANCH] - spawnSync('git', args, { stdio: 'inherit' }) + const args = argv.force + ? ["push", "-f", "origin", QUARTZ_SOURCE_BRANCH] + : ["push", "origin", QUARTZ_SOURCE_BRANCH] + spawnSync("git", args, { stdio: "inherit" }) } - console.log(chalk.green('Done!')) + console.log(chalk.green("Done!")) }) - .command('build', 'Build Quartz into a bundle of static HTML files', BuildArgv, async argv => { - const result = await esbuild.build({ - entryPoints: [fp], - outfile: path.join("quartz", cacheFile), - bundle: true, - keepNames: true, - platform: "node", - format: "esm", - jsx: "automatic", - jsxImportSource: "preact", - packages: "external", - metafile: true, - sourcemap: true, - plugins: [ - sassPlugin({ - type: 'css-text', - }), - { - name: 'inline-script-loader', - setup(build) { - build.onLoad({ filter: /\.inline\.(ts|js)$/ }, async (args) => { - let text = await promises.readFile(args.path, 'utf8') + .command("build", "Build Quartz into a bundle of static HTML files", BuildArgv, async (argv) => { + const result = await esbuild + .build({ + entryPoints: [fp], + outfile: path.join("quartz", cacheFile), + bundle: true, + keepNames: true, + platform: "node", + format: "esm", + jsx: "automatic", + jsxImportSource: "preact", + packages: "external", + metafile: true, + sourcemap: true, + plugins: [ + sassPlugin({ + type: "css-text", + }), + { + 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', '') + // 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 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 rawMod = transpiled.outputFiles[0].text - return { - contents: rawMod, - loader: 'text', - } - }) - } - } - ] - }).catch(err => { - console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`) - console.log(`Reason: ${chalk.grey(err)}`) - console.log("hint: make sure all the required dependencies are installed (run `npm install`)") - process.exit(1) - }) + }, + }, + ], + }) + .catch((err) => { + console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`) + console.log(`Reason: ${chalk.grey(err)}`) + console.log( + "hint: make sure all the required dependencies are installed (run `npm install`)", + ) + process.exit(1) + }) if (argv.verbose) { - const outputFileName = 'quartz/.quartz-cache/transpiled-build.mjs' + 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( + `Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes( + meta.bytes, + )})`, + ) } const { default: buildQuartz } = await import(cacheFile) @@ -302,5 +354,4 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. .showHelpOnFail(false) .help() .strict() - .demandCommand() - .argv + .demandCommand().argv diff --git a/quartz/bootstrap-worker.mjs b/quartz/bootstrap-worker.mjs index 7db24c0..b08689c 100644 --- a/quartz/bootstrap-worker.mjs +++ b/quartz/bootstrap-worker.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node -import workerpool from 'workerpool' +import workerpool from "workerpool" const cacheFile = "./.quartz-cache/transpiled-worker.mjs" const { parseFiles } = await import(cacheFile) workerpool.worker({ - parseFiles + parseFiles, }) diff --git a/quartz/build.ts b/quartz/build.ts index 2ab7654..df83f6e 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -1,4 +1,4 @@ -import 'source-map-support/register.js' +import "source-map-support/register.js" import path from "path" import { PerfTimer } from "./perf" import { rimraf } from "rimraf" @@ -12,8 +12,8 @@ import { emitContent } from "./processors/emit" import cfg from "../quartz.config" import { FilePath } from "./path" import chokidar from "chokidar" -import { ProcessedContent } from './plugins/vfile' -import WebSocket, { WebSocketServer } from 'ws' +import { ProcessedContent } from "./plugins/vfile" +import WebSocket, { WebSocketServer } from "ws" interface Argv { directory: string @@ -29,30 +29,38 @@ export default async function buildQuartz(argv: Argv, version: string) { 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) + 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(", ")}`) + console.log(` Transformers: ${pluginNames("transformers").join(", ")}`) + console.log(` Filters: ${pluginNames("filters").join(", ")}`) + console.log(` Emitters: ${pluginNames("emitters").join(", ")}`) } // clean - perf.addEvent('clean') + perf.addEvent("clean") await rimraf(output) - console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince('clean')}`) + console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`) // glob - perf.addEvent('glob') - const fps = await globby('**/*.md', { + perf.addEvent("glob") + const fps = await globby("**/*.md", { cwd: argv.directory, ignore: cfg.configuration.ignorePatterns, gitignore: true, }) - console.log(`Found ${fps.length} input files from \`${argv.directory}\` in ${perf.timeSince('glob')}`) + console.log( + `Found ${fps.length} input files from \`${argv.directory}\` in ${perf.timeSince("glob")}`, + ) - const filePaths = fps.map(fp => `${argv.directory}${path.sep}${fp}` as FilePath) - const parsedFiles = await parseMarkdown(cfg.plugins.transformers, argv.directory, filePaths, argv.verbose) + const filePaths = fps.map((fp) => `${argv.directory}${path.sep}${fp}` as FilePath) + const parsedFiles = await parseMarkdown( + cfg.plugins.transformers, + argv.directory, + filePaths, + argv.verbose, + ) const filteredContent = filterContent(cfg.plugins.filters, parsedFiles, argv.verbose) await emitContent(argv.directory, output, cfg, filteredContent, argv.serve, argv.verbose) console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`)) @@ -60,7 +68,7 @@ export default async function buildQuartz(argv: Argv, version: string) { if (argv.serve) { const wss = new WebSocketServer({ port: 3001 }) const connections: WebSocket[] = [] - wss.on('connection', ws => connections.push(ws)) + wss.on("connection", (ws) => connections.push(ws)) const ignored = await isGitIgnored() const contentMap = new Map() @@ -69,15 +77,20 @@ export default async function buildQuartz(argv: Argv, version: string) { contentMap.set(vfile.data.filePath!, content) } - async function rebuild(fp: string, action: 'add' | 'change' | 'unlink') { - perf.addEvent('rebuild') + async function rebuild(fp: string, action: "add" | "change" | "unlink") { + perf.addEvent("rebuild") if (!ignored(fp)) { console.log(chalk.yellow(`Detected change in ${fp}, rebuilding...`)) const fullPath = `${argv.directory}${path.sep}${fp}` as FilePath - if (action === 'add' || action === 'change') { - const [parsedContent] = await parseMarkdown(cfg.plugins.transformers, argv.directory, [fullPath], argv.verbose) + if (action === "add" || action === "change") { + const [parsedContent] = await parseMarkdown( + cfg.plugins.transformers, + argv.directory, + [fullPath], + argv.verbose, + ) contentMap.set(fullPath, parsedContent) - } else if (action === 'unlink') { + } else if (action === "unlink") { contentMap.delete(fullPath) } @@ -85,21 +98,21 @@ export default async function buildQuartz(argv: Argv, version: string) { const parsedFiles = [...contentMap.values()] const filteredContent = filterContent(cfg.plugins.filters, parsedFiles, argv.verbose) await emitContent(argv.directory, output, cfg, filteredContent, argv.serve, argv.verbose) - console.log(chalk.green(`Done rebuilding in ${perf.timeSince('rebuild')}`)) - connections.forEach(conn => conn.send('rebuild')) + console.log(chalk.green(`Done rebuilding in ${perf.timeSince("rebuild")}`)) + connections.forEach((conn) => conn.send("rebuild")) } } - const watcher = chokidar.watch('.', { + 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, 'unlink')) + .on("add", (fp) => rebuild(fp, "add")) + .on("change", (fp) => rebuild(fp, "change")) + .on("unlink", (fp) => rebuild(fp, "unlink")) const server = http.createServer(async (req, res) => { await serveHandler(req, res, { @@ -107,15 +120,16 @@ export default async function buildQuartz(argv: Argv, version: string) { directoryListing: false, }) const status = res.statusCode - const statusString = (status >= 200 && status < 300) ? - chalk.green(`[${status}]`) : - (status >= 300 && status < 400) ? - chalk.yellow(`[${status}]`) : - chalk.red(`[${status}]`) + const statusString = + status >= 200 && status < 300 + ? chalk.green(`[${status}]`) + : status >= 300 && status < 400 + ? chalk.yellow(`[${status}]`) + : chalk.red(`[${status}]`) console.log(statusString + chalk.grey(` ${req.url}`)) }) server.listen(argv.port) console.log(chalk.cyan(`Started a Quartz server listening at http://localhost:${argv.port}`)) - console.log('hint: exit with ctrl+c') + console.log("hint: exit with ctrl+c") } } diff --git a/quartz/cfg.ts b/quartz/cfg.ts index 420ffe9..d8a14a3 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -5,43 +5,43 @@ import { Theme } from "./theme" export type Analytics = | null | { - provider: 'plausible' - } + provider: "plausible" + } | { - provider: 'google', - tagId: string - } + provider: "google" + tagId: string + } export interface GlobalConfiguration { - pageTitle: string, + pageTitle: string /** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */ - enableSPA: boolean, + enableSPA: boolean /** Whether to display Wikipedia-style popovers when hovering over links */ - enablePopovers: boolean, + enablePopovers: boolean /** Analytics mode */ analytics: Analytics /** Glob patterns to not search */ - ignorePatterns: string[], + ignorePatterns: string[] /** Base URL to use for CNAME files, sitemaps, and RSS feeds that require an absolute URL. - * Quartz will avoid using this as much as possible and use relative URLs most of the time - */ - baseUrl?: string, + * Quartz will avoid using this as much as possible and use relative URLs most of the time + */ + baseUrl?: string theme: Theme } export interface QuartzConfig { - configuration: GlobalConfiguration, - plugins: PluginTypes, + configuration: GlobalConfiguration + plugins: PluginTypes } export interface FullPageLayout { head: QuartzComponent - header: QuartzComponent[], - beforeBody: QuartzComponent[], - pageBody: QuartzComponent, - left: QuartzComponent[], - right: QuartzComponent[], - footer: QuartzComponent, + header: QuartzComponent[] + beforeBody: QuartzComponent[] + pageBody: QuartzComponent + left: QuartzComponent[] + right: QuartzComponent[] + footer: QuartzComponent } export type PageLayout = Pick diff --git a/quartz/components/Backlinks.tsx b/quartz/components/Backlinks.tsx index 9331387..575e613 100644 --- a/quartz/components/Backlinks.tsx +++ b/quartz/components/Backlinks.tsx @@ -4,15 +4,25 @@ import { canonicalizeServer, resolveRelative } from "../path" function Backlinks({ fileData, allFiles }: QuartzComponentProps) { const slug = canonicalizeServer(fileData.slug!) - const backlinkFiles = allFiles.filter(file => file.links?.includes(slug)) - return + const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug)) + return ( + + ) } Backlinks.css = style diff --git a/quartz/components/Body.tsx b/quartz/components/Body.tsx index 6b1f234..fbb8572 100644 --- a/quartz/components/Body.tsx +++ b/quartz/components/Body.tsx @@ -1,16 +1,13 @@ // @ts-ignore -import clipboardScript from './scripts/clipboard.inline' -import clipboardStyle from './styles/clipboard.scss' +import clipboardScript from "./scripts/clipboard.inline" +import clipboardStyle from "./styles/clipboard.scss" import { QuartzComponentConstructor, QuartzComponentProps } from "./types" function Body({ children }: QuartzComponentProps) { - return
- {children} -
+ return
{children}
} Body.afterDOMLoaded = clipboardScript Body.css = clipboardStyle export default (() => Body) satisfies QuartzComponentConstructor - diff --git a/quartz/components/Darkmode.tsx b/quartz/components/Darkmode.tsx index 49f61c7..afbf2bd 100644 --- a/quartz/components/Darkmode.tsx +++ b/quartz/components/Darkmode.tsx @@ -1,50 +1,48 @@ -// @ts-ignore: this is safe, we don't want to actually make darkmode.inline.ts a module as +// @ts-ignore: this is safe, we don't want to actually make darkmode.inline.ts a module as // modules are automatically deferred and we don't want that to happen for critical beforeDOMLoads // see: https://v8.dev/features/modules#defer import darkmodeScript from "./scripts/darkmode.inline" -import styles from './styles/darkmode.scss' +import styles from "./styles/darkmode.scss" import { QuartzComponentConstructor } from "./types" function Darkmode() { - return
- - - -
+ return ( +
+ + + +
+ ) } Darkmode.beforeDOMLoaded = darkmodeScript diff --git a/quartz/components/Date.tsx b/quartz/components/Date.tsx index 7ea6ad4..16c4544 100644 --- a/quartz/components/Date.tsx +++ b/quartz/components/Date.tsx @@ -3,10 +3,10 @@ interface Props { } export function Date({ date }: Props) { - const formattedDate = date.toLocaleDateString('en-US', { + const formattedDate = date.toLocaleDateString("en-US", { year: "numeric", month: "short", - day: '2-digit' + day: "2-digit", }) return <>{formattedDate} } diff --git a/quartz/components/Footer.tsx b/quartz/components/Footer.tsx index 1ea2ef1..f33617e 100644 --- a/quartz/components/Footer.tsx +++ b/quartz/components/Footer.tsx @@ -1,6 +1,6 @@ import { QuartzComponentConstructor } from "./types" import style from "./styles/footer.scss" -import {version} from "../../package.json" +import { version } from "../../package.json" interface Options { links: Record @@ -10,13 +10,21 @@ export default ((opts?: Options) => { function Footer() { const year = new Date().getFullYear() const links = opts?.links ?? [] - return + return ( +
+
+

+ Created with Quartz v{version}, © {year} +

+
    + {Object.entries(links).map(([text, link]) => ( +
  • + {text} +
  • + ))} +
+
+ ) } Footer.css = style diff --git a/quartz/components/Graph.tsx b/quartz/components/Graph.tsx index 1757991..d8ec6e5 100644 --- a/quartz/components/Graph.tsx +++ b/quartz/components/Graph.tsx @@ -4,19 +4,19 @@ import script from "./scripts/graph.inline" import style from "./styles/graph.scss" export interface D3Config { - drag: boolean, - zoom: boolean, - depth: number, - scale: number, - repelForce: number, - centerForce: number, - linkDistance: number, - fontSize: number, + drag: boolean + zoom: boolean + depth: number + scale: number + repelForce: number + centerForce: number + linkDistance: number + fontSize: number opacityScale: number } interface GraphOptions { - localGraph: Partial | undefined, + localGraph: Partial | undefined globalGraph: Partial | undefined } @@ -30,7 +30,7 @@ const defaultOptions: GraphOptions = { centerForce: 0.3, linkDistance: 30, fontSize: 0.6, - opacityScale: 1 + opacityScale: 1, }, globalGraph: { drag: true, @@ -41,21 +41,32 @@ const defaultOptions: GraphOptions = { centerForce: 0.3, linkDistance: 30, fontSize: 0.6, - opacityScale: 1 - } + opacityScale: 1, + }, } export default ((opts?: GraphOptions) => { function Graph() { const localGraph = { ...opts?.localGraph, ...defaultOptions.localGraph } const globalGraph = { ...opts?.globalGraph, ...defaultOptions.globalGraph } - return
-

Graph View

-
-
- - +

Graph View

+
+
+ + - + s-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z" + /> + +
+
+
+
-
-
-
-
+ ) } Graph.css = style diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index b370054..f57e0e2 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -12,23 +12,29 @@ export default (() => { const iconPath = baseDir + "/static/icon.png" const ogImagePath = baseDir + "/static/og-image.png" - return - {title} - - - - - - - - - - - - - {css.map(href => )} - {js.filter(resource => resource.loadTime === "beforeDOMReady").map(res => JSResourceToScriptElement(res, true))} - + return ( + + {title} + + + + + + + + + + + + + {css.map((href) => ( + + ))} + {js + .filter((resource) => resource.loadTime === "beforeDOMReady") + .map((res) => JSResourceToScriptElement(res, true))} + + ) } return Head diff --git a/quartz/components/Header.tsx b/quartz/components/Header.tsx index 0f13ca2..3fd8eca 100644 --- a/quartz/components/Header.tsx +++ b/quartz/components/Header.tsx @@ -1,9 +1,7 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types" function Header({ children }: QuartzComponentProps) { - return (children.length > 0) ?
- {children} -
: null + return children.length > 0 ?
{children}
: null } Header.css = ` diff --git a/quartz/components/PageList.tsx b/quartz/components/PageList.tsx index da1bf9f..2f08f62 100644 --- a/quartz/components/PageList.tsx +++ b/quartz/components/PageList.tsx @@ -17,32 +17,51 @@ function byDateAndAlphabetical(f1: QuartzPluginData, f2: QuartzPluginData): numb // otherwise, sort lexographically by title const f1Title = f1.frontmatter?.title.toLowerCase() ?? "" const f2Title = f2.frontmatter?.title.toLowerCase() ?? "" - return f1Title.localeCompare(f2Title) + return f1Title.localeCompare(f2Title) } export function PageList({ fileData, allFiles }: QuartzComponentProps) { const slug = canonicalizeServer(fileData.slug!) - return
    - {allFiles.sort(byDateAndAlphabetical).map(page => { - const title = page.frontmatter?.title - const pageSlug = canonicalizeServer(page.slug!) - const tags = page.frontmatter?.tags ?? [] + return ( +
      + {allFiles.sort(byDateAndAlphabetical).map((page) => { + const title = page.frontmatter?.title + const pageSlug = canonicalizeServer(page.slug!) + const tags = page.frontmatter?.tags ?? [] - return
    • -
      - {page.dates &&

      - -

      } -
      -

      {title}

      -
      -
        - {tags.map(tag =>
      • #{tag}
      • )} -
      -
      -
    • - })} -
    + return ( +
  • +
    + {page.dates && ( +

    + +

    + )} + + +
    +
  • + ) + })} +
+ ) } PageList.css = ` diff --git a/quartz/components/PageTitle.tsx b/quartz/components/PageTitle.tsx index 00b2300..f6319ef 100644 --- a/quartz/components/PageTitle.tsx +++ b/quartz/components/PageTitle.tsx @@ -5,7 +5,11 @@ function PageTitle({ fileData, cfg }: QuartzComponentProps) { const title = cfg?.pageTitle ?? "Untitled Quartz" const slug = canonicalizeServer(fileData.slug!) const baseDir = pathToRoot(slug) - return

{title}

+ return ( +

+ {title} +

+ ) } PageTitle.css = ` diff --git a/quartz/components/ReadingTime.tsx b/quartz/components/ReadingTime.tsx index 3e3d4d7..f802c87 100644 --- a/quartz/components/ReadingTime.tsx +++ b/quartz/components/ReadingTime.tsx @@ -5,7 +5,11 @@ function ReadingTime({ fileData }: QuartzComponentProps) { const text = fileData.text if (text) { const { text: timeTaken, words } = readingTime(text) - return

{words} words, {timeTaken}

+ return ( +

+ {words} words, {timeTaken} +

+ ) } else { return null } diff --git a/quartz/components/Search.tsx b/quartz/components/Search.tsx index f8dd804..bd9aa69 100644 --- a/quartz/components/Search.tsx +++ b/quartz/components/Search.tsx @@ -5,27 +5,41 @@ import script from "./scripts/search.inline" export default (() => { function Search() { - return