diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 69bad3c22..a4a96dd8b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,22 +6,18 @@ labels: bug assignees: "" --- -**Describe the bug** -A clear and concise description of what the bug is. +**Describe the bug** A clear and concise description of what the bug is. -**To Reproduce** -Steps to reproduce the behavior: +**To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** -A clear and concise description of what you expected to happen. +**Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots and Source** -If applicable, add screenshots to help explain your problem. +**Screenshots and Source** If applicable, add screenshots to help explain your problem. You can help speed up fixing the problem by either @@ -30,11 +26,10 @@ You can help speed up fixing the problem by either **Desktop (please complete the following information):** -- Quartz Version: [e.g. v4.1.2] -- `node` Version: [e.g. v18.16] -- `npm` version: [e.g. v10.1.0] -- OS: [e.g. iOS] -- Browser [e.g. chrome, safari] +- Quartz Version: [e.g. v4.1.2] +- `node` Version: [e.g. v18.16] +- `npm` version: [e.g. v10.1.0] +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] -**Additional context** -Add any other context about the problem here. +**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 e766b49b8..cd56f9f1a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,14 +6,10 @@ labels: enhancement assignees: "" --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +**Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -**Describe the solution you'd like** -A clear and concise description of what you want to happen. +**Describe the solution you'd like** A clear and concise description of what you want to happen. -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. +**Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. -**Additional context** -Add any other context or screenshots about the feature request here. +**Additional context** Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f94fbb333..42adb4474 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/build-only.yml b/.github/workflows/build-only.yml index a9ad54c65..3beb58d73 100644 --- a/.github/workflows/build-only.yml +++ b/.github/workflows/build-only.yml @@ -1,32 +1,32 @@ name: Quartz - Build only on: - workflow_dispatch: - pull_request: - # push: - # branches: - # - main + workflow_dispatch: + pull_request: + # push: + # branches: + # - main permissions: - contents: read - pages: write - id-token: write + contents: read + pages: write + id-token: write concurrency: - group: "pages" - cancel-in-progress: false + group: "pages" + cancel-in-progress: false jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch all history for git info - - uses: actions/setup-node@v4 - with: - node-version: 18 - - name: Install Dependencies - run: npm i - - name: Build Quartz - run: npx quartz build + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for git info + - uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install Dependencies + run: npm i + - name: Build Quartz + run: npx quartz build diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b0fbc7472..82ac46d23 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,71 +1,71 @@ name: Quartz - CI Test on: - pull_request: - branches: - - v4 - push: - branches: - - v4 + pull_request: + branches: + - v4 + 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 + 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: 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- + - 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 + - run: npm ci - - name: Check types and style - run: npm run check + - name: Check types and style + run: npm run check - - name: Test - run: npm test + - name: Test + run: npm test - - name: Ensure Quartz builds, check bundle info - run: npx quartz build --bundleInfo + - name: Ensure Quartz builds, check bundle info + run: npx quartz build --bundleInfo - publish-tag: - if: ${{ github.repository == 'jackyzha0/quartz' }} - runs-on: ubuntu-latest - 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: Get package version - run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV - - name: Create release tag - uses: pkgdeps/git-tag-action@v2 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - github_repo: ${{ github.repository }} - version: ${{ env.PACKAGE_VERSION }} - git_commit_sha: ${{ github.sha }} - git_tag_prefix: "v" + publish-tag: + if: ${{ github.repository == 'jackyzha0/quartz' }} + runs-on: ubuntu-latest + 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: Get package version + run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV + - name: Create release tag + uses: pkgdeps/git-tag-action@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + github_repo: ${{ github.repository }} + version: ${{ env.PACKAGE_VERSION }} + git_commit_sha: ${{ github.sha }} + git_tag_prefix: "v" diff --git a/.github/workflows/deploy-npm.yml b/.github/workflows/deploy-npm.yml index b104af606..45bd6f7b5 100644 --- a/.github/workflows/deploy-npm.yml +++ b/.github/workflows/deploy-npm.yml @@ -1,52 +1,52 @@ name: Quartz - Deploy to GitHub Pages (npm) on: - workflow_dispatch: - # push: - # branches: - # - main + workflow_dispatch: + # push: + # branches: + # - main permissions: - contents: read - pages: write - id-token: write + contents: read + pages: write + id-token: write concurrency: - group: "pages" - cancel-in-progress: false + group: "pages" + cancel-in-progress: false defaults: - run: - shell: bash + run: + shell: bash jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch all history for git info - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 18 - - name: Install Dependencies - run: npm i - - name: Build Quartz - run: npm run build - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: public + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for git info + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install Dependencies + run: npm i + - name: Build Quartz + run: npm run build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + 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@v4 + 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@v4 diff --git a/.github/workflows/deploy-pnpm.yml b/.github/workflows/deploy-pnpm.yml index 0ba7df563..a1a30afeb 100644 --- a/.github/workflows/deploy-pnpm.yml +++ b/.github/workflows/deploy-pnpm.yml @@ -1,63 +1,63 @@ name: Quartz - Deploy to GitHub Pages (pnpm) on: - workflow_dispatch: - push: - branches: - - main + workflow_dispatch: + push: + branches: + - main permissions: - contents: read - pages: write - id-token: write + contents: read + pages: write + id-token: write concurrency: - group: "pages" - cancel-in-progress: false + group: "pages" + cancel-in-progress: false defaults: - run: - shell: bash + run: + shell: bash jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch all history for git info - - name: Setup PNPM - uses: pnpm/action-setup@v3 - with: - version: 8 - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - name: Install dependencies - run: pnpm install - - name: Build Quartz Site - run: pnpm run build - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: public + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for git info + - name: Setup PNPM + uses: pnpm/action-setup@v3 + with: + version: 8 + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + - name: Install dependencies + run: pnpm install + - name: Build Quartz Site + run: pnpm run build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + 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 - uses: actions/deploy-pages@v4 - id: deployment + deploy: + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 + id: deployment diff --git a/.prettierrc b/.prettierrc index af98918ef..ab1256170 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,16 +1,16 @@ { - "printWidth": 80, - "trailingComma": "all", - "tabWidth": 2, - "semi": false, - "useTabs": false, - "singleQuote": false, - "quoteProps": "consistent", - "bracketSpacing": false, - "bracketSameLine": true, - "arrowParens": "always", - "proseWrap": "never", - "endOfLine": "lf", - "embeddedLanguageFormatting": "auto", - "singleAttributePerLine": false + "printWidth": 80, + "trailingComma": "all", + "tabWidth": 2, + "semi": false, + "useTabs": false, + "singleQuote": false, + "quoteProps": "consistent", + "bracketSpacing": false, + "bracketSameLine": true, + "arrowParens": "always", + "proseWrap": "never", + "endOfLine": "lf", + "embeddedLanguageFormatting": "auto", + "singleAttributePerLine": false } diff --git a/README.md b/README.md index 18cf32421..ead223f2b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # 🌱 Forgetful Notes -![code size](https://img.shields.io/github/languages/code-size/semanticdata/forgetful-notes) -![repository size](https://img.shields.io/github/repo-size/semanticdata/forgetful-notes) -![commits](https://img.shields.io/github/commit-activity/t/semanticdata/forgetful-notes) -![last commit](https://img.shields.io/github/last-commit/semanticdata/forgetful-notes) -![is website up?](https://img.shields.io/website/https/forgetfulnotes.com.svg) +![code size](https://img.shields.io/github/languages/code-size/semanticdata/forgetful-notes) ![repository size](https://img.shields.io/github/repo-size/semanticdata/forgetful-notes) ![commits](https://img.shields.io/github/commit-activity/t/semanticdata/forgetful-notes) ![last commit](https://img.shields.io/github/last-commit/semanticdata/forgetful-notes) ![is website up?](https://img.shields.io/website/https/forgetfulnotes.com.svg) This repository holds the source code for [Forgetful Notes](https://forgetfulnotes.com)—my digital garden of knowledge. It serves as a platform for my learning and creative endeavours. A space for thinking through, building upon, and coming back to. @@ -15,21 +11,21 @@ It is powered by [Quartz](https://github.com/jackyzha0/quartz/) and [Obsidian](h
Show/Hide -- [🌱 Forgetful Notes](#-forgetful-notes) - - [Contents](#contents) - - [Screenshots](#screenshots) - - [Full Width](#full-width) - - [Slim (light)](#slim-light) - - [Slim (dark)](#slim-dark) - - [Features](#features) - - [Background](#background) - - [Technology](#technology) - - [Useful Commands](#useful-commands) - - [Customization](#customization) - - [Stylesheets](#stylesheets) - - [Fonts](#fonts) - - [Folder Structure](#folder-structure) - - [License](#license) +- [🌱 Forgetful Notes](#-forgetful-notes) + - [Contents](#contents) + - [Screenshots](#screenshots) + - [Full Width](#full-width) + - [Slim (light)](#slim-light) + - [Slim (dark)](#slim-dark) + - [Features](#features) + - [Background](#background) + - [Technology](#technology) + - [Useful Commands](#useful-commands) + - [Customization](#customization) + - [Stylesheets](#stylesheets) + - [Fonts](#fonts) + - [Folder Structure](#folder-structure) + - [License](#license)
@@ -54,12 +50,12 @@ It is powered by [Quartz](https://github.com/jackyzha0/quartz/) and [Obsidian](h ## Features -- Fast Natural-Language Search -- Bidirectional Backlinks -- Floating Link Previews -- Admonition-style Callouts -- Markdown Links and Wikilinks Support -- Latex Support +- Fast Natural-Language Search +- Bidirectional Backlinks +- Floating Link Previews +- Admonition-style Callouts +- Markdown Links and Wikilinks Support +- Latex Support ## Background @@ -106,11 +102,11 @@ You can add custom CSS code within `/quartz/styles/custom.scss`. You will then n ### Fonts -| Used in: | Font Family | Previous Font | -| -------- | :------------------------------------------------------: | :----------------------------------------------------------------------: | -| Headers | [Bitter](https://fonts.google.com/specimen/Bitter) | [Schibsted Grotesk](https://fonts.google.com/specimen/Schibsted+Grotesk) | -| Body | [Bitter](https://fonts.google.com/specimen/Bitter) | [Source Sans Pro](https://fonts.google.com/specimen/Source+Sans+3) | -| Code | [Fira Mono](https://fonts.google.com/specimen/Fira+Mono) | [IBM Plex Mono](https://fonts.google.com/specimen/IBM+Plex+Mono) | +| Used in: | Font Family | Previous Font | +| --- | :-: | :-: | +| Headers | [Bitter](https://fonts.google.com/specimen/Bitter) | [Schibsted Grotesk](https://fonts.google.com/specimen/Schibsted+Grotesk) | +| Body | [Bitter](https://fonts.google.com/specimen/Bitter) | [Source Sans Pro](https://fonts.google.com/specimen/Source+Sans+3) | +| Code | [Fira Mono](https://fonts.google.com/specimen/Fira+Mono) | [IBM Plex Mono](https://fonts.google.com/specimen/IBM+Plex+Mono) | ## Folder Structure diff --git a/content/About.md b/content/About.md index 3f73e5ea8..5bda56ff2 100644 --- a/content/About.md +++ b/content/About.md @@ -26,16 +26,16 @@ Here are some places you can find me on the web. You'll probably be able to find ### Past Titles and Roles -| Title | Description | -| :------------------------------------- | :-------------------------------------------------------------------------------------------------- | -| Civil Engineering Technician | Work under Engineers and Architects providing daily site visits and technical design assistance. | -| Crew Lead | Lead small construction crew throughout mountain road culverts installation. | -| Construction Materials Technician | Materials Lab - Asphalt, Concrete, and Soil testing. Nuclear gauge certified. | -| Drafting Specialist | Custom commercial furniture designer. | -| Chief Field Inspector | Lead inspector in several residential streets public works projects. | -| Council Authorized Representative | Metropolitan Council's authorized representative during the Construction phase of several projects. | -| Assistant Contract Administrator | Take infrastructure and technology assets projects, from procurement through contract closesure. | -| Emergency Medical Technician | Nationally Certified EMT. Experience in urban emergency response. | -| Licensed Firefighter | Minnesota Licensed Firefighter. Residential firefighting experience. | -| Fire Motor Operator | Driving, Tiling, Interim Officer, and Pumping fire apparatus operation. | -| Public Works Safety Committee Co-chair | Served as Co-chair for the Bloomington Public Works Safety committee co-chair. | +| Title | Description | +| :-- | :-- | +| Civil Engineering Technician | Work under Engineers and Architects providing daily site visits and technical design assistance. | +| Crew Lead | Lead small construction crew throughout mountain road culverts installation. | +| Construction Materials Technician | Materials Lab - Asphalt, Concrete, and Soil testing. Nuclear gauge certified. | +| Drafting Specialist | Custom commercial furniture designer. | +| Chief Field Inspector | Lead inspector in several residential streets public works projects. | +| Council Authorized Representative | Metropolitan Council's authorized representative during the Construction phase of several projects. | +| Assistant Contract Administrator | Take infrastructure and technology assets projects, from procurement through contract closesure. | +| Emergency Medical Technician | Nationally Certified EMT. Experience in urban emergency response. | +| Licensed Firefighter | Minnesota Licensed Firefighter. Residential firefighting experience. | +| Fire Motor Operator | Driving, Tiling, Interim Officer, and Pumping fire apparatus operation. | +| Public Works Safety Committee Co-chair | Served as Co-chair for the Bloomington Public Works Safety committee co-chair. | diff --git a/content/Atomic Notes.md b/content/Atomic Notes.md index 5bcd04f4d..60411bc85 100644 --- a/content/Atomic Notes.md +++ b/content/Atomic Notes.md @@ -13,15 +13,14 @@ They can be connected to another atomic note or idea in some way. By breaking do ## Example Atomic Note -Title: Atomic Note: Importance of Exercise -Tags: #exercise #health #wellness +Title: Atomic Note: Importance of Exercise Tags: #exercise #health #wellness Regular exercise confers numerous health benefits, including: -- Improved cardiovascular health -- Increased strength and flexibility -- Weight management -- Reduced risk of chronic diseases +- Improved cardiovascular health +- Increased strength and flexibility +- Weight management +- Reduced risk of chronic diseases It is recommended to engage in at least 150 minutes of moderate-intensity exercise or 75 minutes of vigorous exercise per week. Exercise should be a combination of aerobic activity, strength training, and flexibility exercises. diff --git a/content/Aurora Borealis Sighting.md b/content/Aurora Borealis Sighting.md index e07671ea0..0bbb71836 100644 --- a/content/Aurora Borealis Sighting.md +++ b/content/Aurora Borealis Sighting.md @@ -35,10 +35,10 @@ Viewing the aurora depends on four important factors. If the geomagnetic field is active, then the aurora will be brighter and further from the poles. Geomagnetic activity is driven by solar activity and solar coronal holes and thus it waxes and wanes with time. The level of geomagnetic activity is indicated by the planetary K index or Kp. The Kp index ranges from 0 to 9. -- For Kp in the range 0 to 2, the aurora will be far north, quite dim in intensity, and not very active. -- For Kp in the range of 3 to 5, the aurora will move further from the poles, it will become brighter and there will be more auroral activity (motion and formations). If you are in the right place, these aurora can be quite pleasing to look at. -- For Kp in the range 6 to 7, the aurora will move even further from the poles and will become quite bright and active. At this geomagnetic activity level, it might be possible to see the aurora from the northern edge of the United States. -- For Kp in the range 8 to 9, the aurora will move even further towards the equator and it will become very bright and very active. These are the events that create the best aurora and the extended auroral oval will be observable by the most people. At these levels, aurora may be seen directly overhead from the northern states of the USA. +- For Kp in the range 0 to 2, the aurora will be far north, quite dim in intensity, and not very active. +- For Kp in the range of 3 to 5, the aurora will move further from the poles, it will become brighter and there will be more auroral activity (motion and formations). If you are in the right place, these aurora can be quite pleasing to look at. +- For Kp in the range 6 to 7, the aurora will move even further from the poles and will become quite bright and active. At this geomagnetic activity level, it might be possible to see the aurora from the northern edge of the United States. +- For Kp in the range 8 to 9, the aurora will move even further towards the equator and it will become very bright and very active. These are the events that create the best aurora and the extended auroral oval will be observable by the most people. At these levels, aurora may be seen directly overhead from the northern states of the USA. It should be noted that the relationship between Kp and auroral latitude are approximate and represent averages. There will be times when these relationships do not hold up exactly. @@ -64,8 +64,7 @@ Below are maps showing the most southern extent of where aurora might be observa ### July 12th, 2023 -![NOAA Space Weather Prediction Center - G-Kp](https://www.swpc.noaa.gov/sites/default/files/images/u2/Aurora_Kp_MapNorthAm.png) -G is NOAA Geomagnetic Storm Index (0–5) +![NOAA Space Weather Prediction Center - G-Kp](https://www.swpc.noaa.gov/sites/default/files/images/u2/Aurora_Kp_MapNorthAm.png) G is NOAA Geomagnetic Storm Index (0–5) Kp is Planetary K Index (0–9) ### September 18th, 2023 @@ -74,7 +73,7 @@ Kp is Planetary K Index (0–9) ## Additional Resources -- [Aurora Forecast](https://www.gi.alaska.edu/monitors/aurora-forecast) -- [Aurora Dashboard](https://www.swpc.noaa.gov/content/aurora-dashboard-experimental) -- [Space Weather Phenomena](https://www.swpc.noaa.gov/phenomena) -- [Dive Deeper Into the Science of the Aurora](https://www.swpc.noaa.gov/content/aurora-tutorial) +- [Aurora Forecast](https://www.gi.alaska.edu/monitors/aurora-forecast) +- [Aurora Dashboard](https://www.swpc.noaa.gov/content/aurora-dashboard-experimental) +- [Space Weather Phenomena](https://www.swpc.noaa.gov/phenomena) +- [Dive Deeper Into the Science of the Aurora](https://www.swpc.noaa.gov/content/aurora-tutorial) diff --git a/content/Books.md b/content/Books.md index 0471caf08..736d1f673 100644 --- a/content/Books.md +++ b/content/Books.md @@ -11,11 +11,11 @@ Non-inclusive, non-comprehensive list of books I've read. ## John Green -- [An Abundance of Katherines](https://www.librarything.com/work/2569212) -- [Looking for Alaska](https://www.librarything.com/work/30329846) -- [Paper Towns](https://www.librarything.com/work/5105584) -- [The Fault in Our Stars](https://www.librarything.com/work/11456497) -- [Will Grayson, Will Grayson](https://www.librarything.com/work/8463786) +- [An Abundance of Katherines](https://www.librarything.com/work/2569212) +- [Looking for Alaska](https://www.librarything.com/work/30329846) +- [Paper Towns](https://www.librarything.com/work/5105584) +- [The Fault in Our Stars](https://www.librarything.com/work/11456497) +- [Will Grayson, Will Grayson](https://www.librarything.com/work/8463786) ## Janet Evanovich @@ -59,4 +59,4 @@ Non-inclusive, non-comprehensive list of books I've read. ## Robert Pinsky -- [The Sounds of Poetry: A Brief Guide](https://www.librarything.com/work/121193) +- [The Sounds of Poetry: A Brief Guide](https://www.librarything.com/work/121193) diff --git a/content/CSS.md b/content/CSS.md index 18b03a420..1a770ebd6 100644 --- a/content/CSS.md +++ b/content/CSS.md @@ -31,9 +31,9 @@ All _mobile-first-designs_ media queries and 1 _desktop-first-design_ media quer ```scss @mixin screen-min($min) { - @media (min-width: $min) { - @content; - } + @media (min-width: $min) { + @content; + } } ``` @@ -41,9 +41,9 @@ All _mobile-first-designs_ media queries and 1 _desktop-first-design_ media quer ```scss @mixin screen-max($max) { - @media (max-width: $max - 1) { - @content; - } + @media (max-width: $max - 1) { + @content; + } } ``` @@ -51,9 +51,9 @@ All _mobile-first-designs_ media queries and 1 _desktop-first-design_ media quer ```scss @mixin screen-minmax($min, $max) { - @media (min-width: $min) and (max-width: $max - 1) { - @content; - } + @media (min-width: $min) and (max-width: $max - 1) { + @content; + } } ``` @@ -64,22 +64,22 @@ All _mobile-first-designs_ media queries and 1 _desktop-first-design_ media quer ------------------------------------------------------------------ .container { - margin: 0 auto; - width: 100%; - @include screen-min(768px) { - max-width: 750px; - } - @include screen-min(992px) { - max-width: 970px; - } - @include screen-min(1200px) { - max-width: 1170px; - } - @include screen-min(1400px) { - max-width: 1370px; - } - @include screen-min(1600px) { - max-width: 1570px; - } + margin: 0 auto; + width: 100%; + @include screen-min(768px) { + max-width: 750px; + } + @include screen-min(992px) { + max-width: 970px; + } + @include screen-min(1200px) { + max-width: 1170px; + } + @include screen-min(1400px) { + max-width: 1370px; + } + @include screen-min(1600px) { + max-width: 1570px; + } } ``` diff --git a/content/Digital Garden.md b/content/Digital Garden.md index f9c40d474..ce73597ec 100644 --- a/content/Digital Garden.md +++ b/content/Digital Garden.md @@ -17,6 +17,6 @@ Digital Gardens are explorable rather than structured as a strictly linear strea ## Kinds of Notes -- 🌱 *Seedlings* for very rough and early ideas. -- 🌿 *Budding* for work I've cleaned up and clarified. -- 🌳 *Evergreen* for work that is reasonably complete (though I still tend these over time). +- 🌱 *Seedlings* for very rough and early ideas. +- 🌿 *Budding* for work I've cleaned up and clarified. +- 🌳 *Evergreen* for work that is reasonably complete (though I still tend these over time). diff --git a/content/Emmet Cheat Sheet.md b/content/Emmet Cheat Sheet.md index 5e8947974..9e3d5ca81 100644 --- a/content/Emmet Cheat Sheet.md +++ b/content/Emmet Cheat Sheet.md @@ -4,8 +4,8 @@ updated: 2024-02-08 compartir: true --- -- [Documentation](https://docs.emmet.io/) -- [Documentation](https://code.visualstudio.com/docs/editor/emmet) for Emmet in VS Code +- [Documentation](https://docs.emmet.io/) +- [Documentation](https://code.visualstudio.com/docs/editor/emmet) for Emmet in VS Code ## Notes on Abbreviation Formatting @@ -13,8 +13,8 @@ When you get familiar with Emmet's abbreviations syntax, you may want to use som This is why Emmet needs some indicators (like spaces) where it should stop parsing to not expand anything that you don't need. If you're still thinking that such formatting is required for complex abbreviations to make them more readable: -- Abbreviations are not a template language, they don't have to be "readable", they have to be "quickly expandable and removable". -- You don't really need to write complex abbreviations. Stop thinking that "typing" is the slowest process in web-development. You'll quickly find out that constructing a single complex abbreviation is much slower and error-prone than constructing and typing a few short ones. +- Abbreviations are not a template language, they don't have to be "readable", they have to be "quickly expandable and removable". +- You don't really need to write complex abbreviations. Stop thinking that "typing" is the slowest process in web-development. You'll quickly find out that constructing a single complex abbreviation is much slower and error-prone than constructing and typing a few short ones. ## HTML + CSS Emmet Short Guide @@ -28,9 +28,9 @@ div>ul>li ```html
- +
``` @@ -76,7 +76,7 @@ div+div>p>span+em^^^bq ```html
-

+

``` @@ -89,11 +89,11 @@ ul>li*5 ```html ``` @@ -105,15 +105,15 @@ div>(header>ul>li*2>a)+footer>p ```html
-
- -
- +
+ +
+
``` @@ -123,17 +123,17 @@ div>(header>ul>li*2>a)+footer>p ```html
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
``` @@ -167,11 +167,11 @@ ul>li.item$*5 ```html ``` @@ -181,11 +181,11 @@ ul>li.item$$$*5 ```html ``` @@ -223,11 +223,11 @@ ul>li.item$@-3*5 ```html ``` diff --git a/content/Espanso Cheat Sheet.md b/content/Espanso Cheat Sheet.md index 7c20a5a0e..c62196423 100644 --- a/content/Espanso Cheat Sheet.md +++ b/content/Espanso Cheat Sheet.md @@ -12,21 +12,21 @@ Visit the [Documentation](https://espanso.org/docs/get-started/). ## Features -- Works on **Windows**, **macOS** and **Linux** -- Works with almost **any program** -- Works with **Emojis** 😄 -- Works with **Images** -- Includes a powerful **Search Bar** 🔎 -- **Date** expansion support -- **Custom scripts** support -- **Shell commands** support -- **App-specific** configurations -- Support [Forms](https://espanso.org/docs/matches/forms/) -- Expandable with **packages** -- Built-in **package manager** for [espanso hub](https://hub.espanso.org/) -- File based configuration -- Support Regex triggers -- Experimental Wayland support +- Works on **Windows**, **macOS** and **Linux** +- Works with almost **any program** +- Works with **Emojis** 😄 +- Works with **Images** +- Includes a powerful **Search Bar** 🔎 +- **Date** expansion support +- **Custom scripts** support +- **Shell commands** support +- **App-specific** configurations +- Support [Forms](https://espanso.org/docs/matches/forms/) +- Expandable with **packages** +- Built-in **package manager** for [espanso hub](https://hub.espanso.org/) +- File based configuration +- Support Regex triggers +- Experimental Wayland support ## Static Matches @@ -52,10 +52,10 @@ Visit the [Documentation](https://espanso.org/docs/get-started/). - trigger: "now" replace: "It's {{mytime}}" vars: - - name: mytime - type: date - params: - format: "%H:%M" + - name: mytime + type: date + params: + format: "%H:%M" ``` ### Preconfigured Choices @@ -76,8 +76,8 @@ Visit the [Documentation](https://espanso.org/docs/get-started/). - name: getip type: shell params: - cmd: "curl ifconfig.me" - shell: cmd + cmd: "curl ifconfig.me" + shell: cmd ``` ## Global Variables @@ -86,14 +86,14 @@ Visit the [Documentation](https://espanso.org/docs/get-started/). ```yml global_vars: - - name: "global1" - type: "shell" - params: - cmd: "echo global var" - - name: "greet" - type: "echo" - params: - echo: "Hey" + - name: "global1" + type: "shell" + params: + cmd: "echo global var" + - name: "greet" + type: "echo" + params: + echo: "Hey" ``` ### Usage @@ -122,9 +122,9 @@ global_vars: word: true ``` -- If you write `alh`, the match will be expanded to `although`. -- If you write `Alh`, the match will be expanded to `Although`. -- If you write `ALH`, the match will be expanded to `ALTHOUGH`. +- If you write `alh`, the match will be expanded to `although`. +- If you write `Alh`, the match will be expanded to `Although`. +- If you write `ALH`, the match will be expanded to `ALTHOUGH`. ## Cursor Hints diff --git a/content/Free Facts.md b/content/Free Facts.md index b016afca5..a3a51d6c1 100644 --- a/content/Free Facts.md +++ b/content/Free Facts.md @@ -32,8 +32,7 @@ The MV Derbyshire was a British ore-bulk-oil combination carrier built in 1976. ## Rogue Waves -Rogue waves are unusually large, unpredictable, and suddenly appearing surface waves that can be extremely dangerous to ships, even to large ones. -In oceanography, rogue waves are more precisely defined as waves whose height is more than twice the significant wave height (Hs or SWH), which is itself defined as the mean of the largest third of waves in a wave record. Therefore, rogue waves are not necessarily the biggest waves found on the water; they are, rather, unusually large waves for a given sea state. +Rogue waves are unusually large, unpredictable, and suddenly appearing surface waves that can be extremely dangerous to ships, even to large ones. In oceanography, rogue waves are more precisely defined as waves whose height is more than twice the significant wave height (Hs or SWH), which is itself defined as the mean of the largest third of waves in a wave record. Therefore, rogue waves are not necessarily the biggest waves found on the water; they are, rather, unusually large waves for a given sea state. [Source](https://en.wikipedia.org/wiki/Rogue_wave) ## Sea Sickness diff --git a/content/Guitar.md b/content/Guitar.md index 4ee98e158..c0390407a 100644 --- a/content/Guitar.md +++ b/content/Guitar.md @@ -15,23 +15,13 @@ When you are looking at a tab, you will see six horizontal lines. These lines re An [[./Arpeggio|arpeggio]] is a type of [[./Chords|broken chord]] in which the notes that compose a chord are individually sounded in a progressive rising or descending order. Arpeggios on keyboard instruments may be called _rolled chords_. ```md -e|--------2-----------------| -B|------3---3---------------| -G|----2-------2-------------| -D|--0-----------------------| -A|--------------------------| -E|--------------------------| +e|--------2-----------------| B|------3---3---------------| G|----2-------2-------------| D|--0-----------------------| A|--------------------------| E|--------------------------| ``` ### Metallica – Enter the Sandman (Intro) ```md -e|---------------------|------------------|---------------|--------------------| -B|---------------------|------------------|---------------|--------------------| -G|---------------------|------------------|---------------|--------------------| -D|-------5-------------|----5-------------|----5----------|--------------------| -A|----7-----------7----|-7-----------7----|-7-----------7-|--------------------| -E|-0--------6--5-----0-|-------6--5-----0-|-------6--5----|--------------------| +e|---------------------|------------------|---------------|--------------------| B|---------------------|------------------|---------------|--------------------| G|---------------------|------------------|---------------|--------------------| D|-------5-------------|----5-------------|----5----------|--------------------| A|----7-----------7----|-7-----------7----|-7-----------7-|--------------------| E|-0--------6--5-----0-|-------6--5-----0-|-------6--5----|--------------------| ``` ### Pasted @@ -42,5 +32,5 @@ You can then finish out the riff by grabbing the 6th fret on the 6th string with ## Sources -- [ultimate-guitar.com](https://tabs.ultimate-guitar.com/tab/metallica/enter-sandman-tabs-8595) -- [guitarlessons.org](https://www.guitarlessons.org/lessons/read-guitar-tabs/) +- [ultimate-guitar.com](https://tabs.ultimate-guitar.com/tab/metallica/enter-sandman-tabs-8595) +- [guitarlessons.org](https://www.guitarlessons.org/lessons/read-guitar-tabs/) diff --git a/content/Inspirations.md b/content/Inspirations.md index 32efeb6b6..84199c0d0 100644 --- a/content/Inspirations.md +++ b/content/Inspirations.md @@ -9,20 +9,20 @@ _These are people and projects have resonated with me. Inspiring me to do things ## People Who Inspire Me -- [Andy Bell](https://andy-bell.co.uk/) -- [Anjana Vakil](https://anjana.dev/) -- [Derek Sivers](https://sive.rs/) -- [Drew DeVault](https://drewdevault.com/) -- [Eric Bower](https://erock.prose.sh/) -- [Herman Martinus](https://herman.bearblog.dev/) -- [Jacky Zha](https://github.com/jackyzha0?tab=repositories) +- [Andy Bell](https://andy-bell.co.uk/) +- [Anjana Vakil](https://anjana.dev/) +- [Derek Sivers](https://sive.rs/) +- [Drew DeVault](https://drewdevault.com/) +- [Eric Bower](https://erock.prose.sh/) +- [Herman Martinus](https://herman.bearblog.dev/) +- [Jacky Zha](https://github.com/jackyzha0?tab=repositories) ## Projects That Inspire Me -- [Anemone](https://github.com/Speyll/anemone) – Clean Zola theme. Integrates public journals. -- [Bearblog](https://github.com/HermanMartinus/bearblog) – Blogging Platform. -- [Duotone Theme](https://github.com/Hussseinkizz/duotone-theme-v2-official) – VS Code Theme (Supports Ligaments). -- [Fira Code iScript](https://github.com/kencrocken/FiraCodeiScript) – Font Family (incl. ligations and cursive italics). -- [mataroa](https://github.com/mataroa-blog/mataroa) – Minimal Blogging Platform. -- [SorryTennesee](https://github.com/vpicone/SorryTennesee) – Remove Tennesee from drop-down menus. -- [wttr.in](https://github.com/chubin/wttr.in) – Plain Text Weather. +- [Anemone](https://github.com/Speyll/anemone) – Clean Zola theme. Integrates public journals. +- [Bearblog](https://github.com/HermanMartinus/bearblog) – Blogging Platform. +- [Duotone Theme](https://github.com/Hussseinkizz/duotone-theme-v2-official) – VS Code Theme (Supports Ligaments). +- [Fira Code iScript](https://github.com/kencrocken/FiraCodeiScript) – Font Family (incl. ligations and cursive italics). +- [mataroa](https://github.com/mataroa-blog/mataroa) – Minimal Blogging Platform. +- [SorryTennesee](https://github.com/vpicone/SorryTennesee) – Remove Tennesee from drop-down menus. +- [wttr.in](https://github.com/chubin/wttr.in) – Plain Text Weather. diff --git a/content/Markdown Showcase.md b/content/Markdown Showcase.md index 99e5d5f3b..cf7e68f15 100644 --- a/content/Markdown Showcase.md +++ b/content/Markdown Showcase.md @@ -59,8 +59,7 @@ You can [link](https://example.dom/) to external pages. and other internal [[./M > 1. This is the first list item. > 2. This is the second list item. > -> Here's some example code: -> `Markdown.generate();` +> Here's some example code: `Markdown.generate();` ## Lists @@ -76,22 +75,22 @@ In arcu magna, aliquet vel pretium et, molestie et arcu. Mauris lobortis nulla e In arcu magna, aliquet vel pretium et, molestie et arcu. Mauris lobortis nulla et felis ullamcorper bibendum. Phasellus et hendrerit mauris. -- List item -- Another item -- And another item +- List item +- Another item +- And another item ### Nested List In arcu magna, aliquet vel pretium et, molestie et arcu. Mauris lobortis nulla et felis ullamcorper bibendum. Phasellus et hendrerit mauris. -- Item - 1. First Sub-item - 2. Second Sub-item +- Item + 1. First Sub-item + 2. Second Sub-item 1. Numbered Item 2. Another one - 1. Sub-item - - Unordered again + 1. Sub-item + - Unordered again ## Code @@ -103,26 +102,26 @@ Let us use some `inline code` and check out how it `looks`. Here's some `more`. ```html - -
- Example -
- + +
+ Example +
+ ``` ```css .niceClass { - color: blue; - background-color: #fff; + color: blue; + background-color: #fff; } ``` ```js // Javascript code with syntax highlighting. var fun = function lang(l) { - dateformat.i18n = require("./lang/" + l) - return true + dateformat.i18n = require("./lang/" + l) + return true } ``` @@ -139,27 +138,27 @@ In arcu magna, aliquet vel pretium et, molestie et arcu. Mauris lobortis nulla e ### Simple Example -| Title 1 | Title 2 | Title 3 | Title 4 | -| --------------------- | --------------------- | --------------------- | --------------------- | -| lorem | lorem ipsum | lorem ipsum dolor | lorem ipsum dolor sit | +| Title 1 | Title 2 | Title 3 | Title 4 | +| --- | --- | --- | --- | +| lorem | lorem ipsum | lorem ipsum dolor | lorem ipsum dolor sit | | lorem ipsum dolor sit | lorem ipsum dolor sit | lorem ipsum dolor sit | lorem ipsum dolor sit | | lorem ipsum dolor sit | lorem ipsum dolor sit | lorem ipsum dolor sit | lorem ipsum dolor sit | | lorem ipsum dolor sit | lorem ipsum dolor sit | lorem ipsum dolor sit | lorem ipsum dolor sit | ### Longer Example -| Title 1 | Title 2 | Title 3 | Title 4 | -| -------------------------- | -------------------------------------- | -------------------------- | -------------------------------------- | -| lorem | lorem ipsum | lorem ipsum dolor | lorem ipsum dolor sit | -| lorem ipsum dolor sit amet | lorem ipsum dolor sit amet consectetur | lorem ipsum dolor sit amet | lorem ipsum dolor sit | -| lorem ipsum dolor | lorem ipsum | lorem | lorem ipsum | -| lorem ipsum dolor | lorem ipsum dolor sit | lorem ipsum dolor sit amet | lorem ipsum dolor sit amet consectetur | +| Title 1 | Title 2 | Title 3 | Title 4 | +| --- | --- | --- | --- | +| lorem | lorem ipsum | lorem ipsum dolor | lorem ipsum dolor sit | +| lorem ipsum dolor sit amet | lorem ipsum dolor sit amet consectetur | lorem ipsum dolor sit amet | lorem ipsum dolor sit | +| lorem ipsum dolor | lorem ipsum | lorem | lorem ipsum | +| lorem ipsum dolor | lorem ipsum dolor sit | lorem ipsum dolor sit amet | lorem ipsum dolor sit amet consectetur | ### Inline Markdown Within Tables -| Inline    | Markdown    | In    | Table | -| ------------------------ | -------------------------- | ----------------------------------- | ------ | -| _italics_ | **bold** | ~~strikethrough~~    | `code` | +| Inline    | Markdown    | In    | Table | +| --- | --- | --- | --- | +| _italics_ | **bold** | ~~strikethrough~~    | `code` | ## Horizontal Rule @@ -167,16 +166,16 @@ In arcu magna, aliquet vel pretium et, molestie et arcu. Mauris lobortis nulla e ## Tasks and Custom Todos -- [ ] Pending Task -- [x] Completed Task +- [ ] Pending Task +- [x] Completed Task -* [-] Won't Do Task -* [/] In Progress Task -* [*] You are a star. -* [!] Exclamation Mark! -* [?] Question Mark? -* [<] Scheduled Task -* [>] Forwarded Task +* [-] Won't Do Task +* [/] In Progress Task +* [*] You are a star. +* [!] Exclamation Mark! +* [?] Question Mark? +* [<] Scheduled Task +* [>] Forwarded Task ## Images diff --git a/content/Markdown.md b/content/Markdown.md index 3531e854e..22d407d29 100644 --- a/content/Markdown.md +++ b/content/Markdown.md @@ -9,8 +9,7 @@ tags: [stub] Markdown is a lightweight [[./Markup Language|Markup Language]] that you can use to add formatting elements to plaintext text documents. Created by [John Gruber](https://daringfireball.net/projects/markdown/) in 2004, Markdown is now one of the world's most popular markup languages. -> [!Note] -> This website's content is exclusively written in Markdown. +> [!Note] This website's content is exclusively written in Markdown. ## Markdown Flavors diff --git a/content/Meta.md b/content/Meta.md index 52e3f9f5f..4fd7bf9dd 100644 --- a/content/Meta.md +++ b/content/Meta.md @@ -24,12 +24,12 @@ The [source code](https://github.com/semanticdata/forgetful-notes) is hosted in ## Features -- Fast Natural-Language Search -- Bidirectional Backlinks -- Floating Link Previews -- Admonition-style Callouts -- Markdown Links and Wikilinks Support -- Latex Support +- Fast Natural-Language Search +- Bidirectional Backlinks +- Floating Link Previews +- Admonition-style Callouts +- Markdown Links and Wikilinks Support +- Latex Support ## File Structure diff --git a/content/Move Your Body.md b/content/Move Your Body.md index 291f071fd..9983227aa 100644 --- a/content/Move Your Body.md +++ b/content/Move Your Body.md @@ -7,9 +7,9 @@ tags: [stub] Move your body every day. Benefits include: -- Improved sleep quality. -- Less risk of chronic disease. -- Increased productivity. -- Reduced anxiety. +- Improved sleep quality. +- Less risk of chronic disease. +- Increased productivity. +- Reduced anxiety. The "every day" part is important, because [[./Consistency|Consistency]] is key to most things worth doing. diff --git a/content/NeoVim.md b/content/NeoVim.md index 7624fec1a..c252bfbfa 100644 --- a/content/NeoVim.md +++ b/content/NeoVim.md @@ -79,24 +79,24 @@ NeoVim is a fork of Vim focused on extensibility and usability. This is my short ## Plugins -| Author/Plugin | Description | -| ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| [akinsho/bufferline.nvim](https://github.com/akinsho/bufferline.nvim) | A snazzy bufferline for Neovim. | -| [akinsho/toggleterm.nvim](https://github.com/akinsho/toggleterm.nvim) | A neovim lua plugin to help easily manage multiple terminal windows. | -| [ap/vim-css-color](https://github.com/ap/vim-css-color) | Preview colours in source code while editing. | -| [editorconfig/editorconfig-vim](https://github.com/editorconfig/editorconfig-vim) | EditorConfig plugin for Vim. | -| [folke/tokyonight.nvim](https://github.com/folke/tokyonight.nvim) | Theme | -| [kyazdani42/nvim-tree.lua](https://github.com/kyazdani42/nvim-tree.lua) | A file explorer tree for neovim written in lua. | -| [kyazdani42/nvim-web-devicons](https://github.com/kyazdani42/nvim-web-devicons) | Lua "fork" of vim-web-devicons for neovim. | -| [lewis6991/gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim) | Git integration for buffers. | -| [lukas-reineke/indent-blankline.nvim](https://github.com/lukas-reineke/indent-blankline.nvim) | Indent guides for Neovim. | -| [numToStr/Comment.nvim](https://github.com/numToStr/Comment.nvim) | Smart and powerful comment plugin for neovim. | -| [nvim-lua/plenary.nvim](https://github.com/nvim-lua/plenary.nvim) | All the lua functions I [they] don't want to write twice. | -| [nvim-lualine/lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) | neovim statusline plugin written in pure lua. | -| [nvim-telescope/telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) | Find, Filter, Preview, Pick. All lua, all the time. | -| [nvim-treesitter/nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects) | Syntax aware text-objects, select, move, swap, and peek support. | -| [nvim-treesitter/nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) | Nvim Treesitter configurations and abstraction layer. | -| [ThePrimeagen/vim-be-good](https://github.com/ThePrimeagen/vim-be-good) | Nvim plugin designed to make you better at Vim Movements. | -| [tpope/vim-fugitive](https://github.com/tpope/vim-fugitive) | A Git wrapper so awesome, it should be illegal. | -| [vim-telescope/telescope-fzf-native.nvim](https://github.com/nvim-telescope/telescope-fzf-native.nvim) | Find, Filter, Preview, Pick. All lua, all the time. | -| [wellle/targets.vim](https://github.com/wellle/targets.vim) | Vim plugin that provides additional text objects. | +| Author/Plugin | Description | +| --- | --- | +| [akinsho/bufferline.nvim](https://github.com/akinsho/bufferline.nvim) | A snazzy bufferline for Neovim. | +| [akinsho/toggleterm.nvim](https://github.com/akinsho/toggleterm.nvim) | A neovim lua plugin to help easily manage multiple terminal windows. | +| [ap/vim-css-color](https://github.com/ap/vim-css-color) | Preview colours in source code while editing. | +| [editorconfig/editorconfig-vim](https://github.com/editorconfig/editorconfig-vim) | EditorConfig plugin for Vim. | +| [folke/tokyonight.nvim](https://github.com/folke/tokyonight.nvim) | Theme | +| [kyazdani42/nvim-tree.lua](https://github.com/kyazdani42/nvim-tree.lua) | A file explorer tree for neovim written in lua. | +| [kyazdani42/nvim-web-devicons](https://github.com/kyazdani42/nvim-web-devicons) | Lua "fork" of vim-web-devicons for neovim. | +| [lewis6991/gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim) | Git integration for buffers. | +| [lukas-reineke/indent-blankline.nvim](https://github.com/lukas-reineke/indent-blankline.nvim) | Indent guides for Neovim. | +| [numToStr/Comment.nvim](https://github.com/numToStr/Comment.nvim) | Smart and powerful comment plugin for neovim. | +| [nvim-lua/plenary.nvim](https://github.com/nvim-lua/plenary.nvim) | All the lua functions I [they] don't want to write twice. | +| [nvim-lualine/lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) | neovim statusline plugin written in pure lua. | +| [nvim-telescope/telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) | Find, Filter, Preview, Pick. All lua, all the time. | +| [nvim-treesitter/nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects) | Syntax aware text-objects, select, move, swap, and peek support. | +| [nvim-treesitter/nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) | Nvim Treesitter configurations and abstraction layer. | +| [ThePrimeagen/vim-be-good](https://github.com/ThePrimeagen/vim-be-good) | Nvim plugin designed to make you better at Vim Movements. | +| [tpope/vim-fugitive](https://github.com/tpope/vim-fugitive) | A Git wrapper so awesome, it should be illegal. | +| [vim-telescope/telescope-fzf-native.nvim](https://github.com/nvim-telescope/telescope-fzf-native.nvim) | Find, Filter, Preview, Pick. All lua, all the time. | +| [wellle/targets.vim](https://github.com/wellle/targets.vim) | Vim plugin that provides additional text objects. | diff --git a/content/Outlines.md b/content/Outlines.md index 3009d5971..137de0227 100644 --- a/content/Outlines.md +++ b/content/Outlines.md @@ -10,29 +10,29 @@ Learning how to write with brevity (short-form), how to write long-form, how to ## Questions to Be Answered by Post -- What is an outline -- Why should I use one -- How do I make one +- What is an outline +- Why should I use one +- How do I make one ## What Needs to Be Mentioned -- Essential tips. Must do when creating an outline -- Provide an example of an outline -- Outlines are not barriers. They are a tool to structure your writing. +- Essential tips. Must do when creating an outline +- Provide an example of an outline +- Outlines are not barriers. They are a tool to structure your writing. ## Logical Order -- Define the purpose of the essay -- Define the audience of your essay -- Write your thesis statement (consequential thesis statement) -- Brainstorm all of the ideas you want to include in your essay -- Group related ideas -- Label your ideas with headings and sub-headings -- Write a draft of your outline -- Write an intro and conclusion. Purposely last. +- Define the purpose of the essay +- Define the audience of your essay +- Write your thesis statement (consequential thesis statement) +- Brainstorm all of the ideas you want to include in your essay +- Group related ideas +- Label your ideas with headings and sub-headings +- Write a draft of your outline +- Write an intro and conclusion. Purposely last. ## Links to Reference Material -- [How to Outline // Purdue Writing Lab](https://owl.purdue.edu/owl/general_writing/the_writing_process/developing_an_outline/how_to_outline.html) -- [How to Write an Outline: 4 Ways to Organize Your Thoughts | Grammarly](https://www.grammarly.com/blog/how-to-write-outline/) -- [How to Write a Blog Post Outline: A Simple Formula to Follow](https://blog.hubspot.com/marketing/how-to-write-blog-post-outline) +- [How to Outline // Purdue Writing Lab](https://owl.purdue.edu/owl/general_writing/the_writing_process/developing_an_outline/how_to_outline.html) +- [How to Write an Outline: 4 Ways to Organize Your Thoughts | Grammarly](https://www.grammarly.com/blog/how-to-write-outline/) +- [How to Write a Blog Post Outline: A Simple Formula to Follow](https://blog.hubspot.com/marketing/how-to-write-blog-post-outline) diff --git a/content/Poetry.md b/content/Poetry.md index a17b87787..b0731eb83 100644 --- a/content/Poetry.md +++ b/content/Poetry.md @@ -10,9 +10,9 @@ enableToc: true Poetry is a broad literary category that covers everything from bawdy limericks to unforgettable song lyrics to the sentimental couplets inside greeting cards. A **poem** is a singular piece of poetry. -- Show, don't tell. The goal is to provoke an emotion in the reader. -- Less can be more. While it's perfectly acceptable to write long, flowery verse, using simple, concise language is also powerful. Word choice and poem length are up to you. -- It's OK to break grammatical rules when doing so helps you express yourself. +- Show, don't tell. The goal is to provoke an emotion in the reader. +- Less can be more. While it's perfectly acceptable to write long, flowery verse, using simple, concise language is also powerful. Word choice and poem length are up to you. +- It's OK to break grammatical rules when doing so helps you express yourself. The key elements that distinguish poetry from other kinds of literature include sound, rhythm, rhyme, and format. One thing poetry has in common with other kinds of literature is its use of literary devices. Poems, like other kinds of creative writing, often make use of allegories and other kinds of figurative language to communicate themes. @@ -22,10 +22,7 @@ Sometimes poetry is most impactful when it's listened to rather than read. Take > [!quote] The Cold Wind Blows by Kelly Roper > -> Who knows why the cold wind blows -> Or where it goes, or what it knows. -> It only flows in passionate throes -> Until it finally slows and settles in repose. +> Who knows why the cold wind blows Or where it goes, or what it knows. It only flows in passionate throes Until it finally slows and settles in repose. Poets create _sound_ in a variety of ways, like alliteration, assonance, and consonance. @@ -41,15 +38,7 @@ Stressed and unstressed syllables aren't the only way you can create rhythm in y > [!quote] Still I Rise by Maya Angelou > -> Leaving behind nights of terror and fear -> I rise -> Into a daybreak that's wondrously clear -> I rise -> Bringing the gifts that my ancestors gave, -> I am the dream and the hope of the slave. -> I rise -> I rise -> I rise. +> Leaving behind nights of terror and fear I rise Into a daybreak that's wondrously clear I rise Bringing the gifts that my ancestors gave, I am the dream and the hope of the slave. I rise I rise I rise. ## Time to Rhyme @@ -57,10 +46,7 @@ With poetry, rhythm and rhyme go hand in hand. Both create musicality in the poe > [!quote] Jabberwocky by Lewis Carrol > -> One, two! One, two! And through and through -> The vorpal blade went snicker-snack! -> He left it dead, and with its head -> He went galumphing back. +> One, two! One, two! And through and through The vorpal blade went snicker-snack! He left it dead, and with its head He went galumphing back. ## Formatting @@ -70,15 +56,15 @@ A **stanza** is the poetic equivalent of a paragraph. It's a group of lines that ## Literary Devices -- Figurative language -- Juxtaposition -- Onomatopoeia -- Simile -- Metaphor -- Puns -- Chiasmus -- Imagery -- Hyperbole -- Mood -- Motif -- Personification +- Figurative language +- Juxtaposition +- Onomatopoeia +- Simile +- Metaphor +- Puns +- Chiasmus +- Imagery +- Hyperbole +- Mood +- Motif +- Personification diff --git a/content/Quotes.md b/content/Quotes.md index be2c919d4..ca18c2eab 100644 --- a/content/Quotes.md +++ b/content/Quotes.md @@ -39,10 +39,7 @@ compartir: true ## Ashtavakra Gita -> "The wise man knows the Self, -> And he plays the game of life. -> But the fool lives in the world -> Like a beast of burden." +> "The wise man knows the Self, And he plays the game of life. But the fool lives in the world Like a beast of burden." ## Big Mouth @@ -130,8 +127,7 @@ compartir: true > > I don't know. The only thing I do know… is that we have to be kind. > -> Please. Be kind… especially when we don't know what's going on. -> I know you see yourself as a fighter. Well, I see myself as one too. This is how I fight." +> Please. Be kind… especially when we don't know what's going on. I know you see yourself as a fighter. Well, I see myself as one too. This is how I fight." ## Yuval Noah Harari diff --git a/content/Rhizomatic Learning.md b/content/Rhizomatic Learning.md index 191dd0f43..0a9a1b0c7 100644 --- a/content/Rhizomatic Learning.md +++ b/content/Rhizomatic Learning.md @@ -9,8 +9,7 @@ tags: [stub] Rhizomatic learning is a variety of pedagogical practices informed by the work of Gilles Deleuze and Félix Guattari. It takes it's name from the rhizome. -> [!Rhizome] -> Underground stem in which various plants asexually reproduce via budding. +> [!Rhizome] Underground stem in which various plants asexually reproduce via budding. ## Background diff --git a/content/SSD NVMe Comparison.md b/content/SSD NVMe Comparison.md index 1c5a0f97c..cb68e0499 100644 --- a/content/SSD NVMe Comparison.md +++ b/content/SSD NVMe Comparison.md @@ -12,23 +12,23 @@ tags: [archived] ### 3D NAND -- The most basic of modern SSD technologies. Great for throwing on cheap systems, home servers, anything non-critical really. -- It is not recommended to host your Operating System on 3D NAND, or QLC. +- The most basic of modern SSD technologies. Great for throwing on cheap systems, home servers, anything non-critical really. +- It is not recommended to host your Operating System on 3D NAND, or QLC. ### Quad Level Cell (QLC) -- QLC (Quad Level Cell) is cheaper to manufacture than TLC (Triple Level Cell). -- QLC is much slower and less durable to constant writing than TLC. -- It is not recommended to host your Operating System on 3D NAND, or QLC. +- QLC (Quad Level Cell) is cheaper to manufacture than TLC (Triple Level Cell). +- QLC is much slower and less durable to constant writing than TLC. +- It is not recommended to host your Operating System on 3D NAND, or QLC. ### Triple Level Cell (TLC) -- TLC is more reliable when compared to QLC. -- MLC is a Triple Level Cell based Samsung technology. +- TLC is more reliable when compared to QLC. +- MLC is a Triple Level Cell based Samsung technology. ### Multi Level Cell (MLC) -- MLC is a Triple Level Cell based Samsung technology. +- MLC is a Triple Level Cell based Samsung technology. Let's break it down: @@ -42,23 +42,22 @@ Let's break it down: ### Storage -- 1 point per GB - - Less accurate the bigger the SSD - - 3 TB and higher drives scale exp/log instead of linearly. +- 1 point per GB + - Less accurate the bigger the SSD + - 3 TB and higher drives scale exp/log instead of linearly. ### Price -Based on price per $1. -Selected $0.10 as the baseline after averaging some calculations. +Based on price per $1. Selected $0.10 as the baseline after averaging some calculations. -- 1 point for every $0.01 / GB below $0.10 +- 1 point for every $0.01 / GB below $0.10 ### Technology Coefficient -- 3D NAND Coefficient = 0.5 (Big Penalty) -- QLC Coefficient = 0.75 (Small Penalty) -- TLC Coefficient = 1.0 (No Change) -- MLC V-NAND coefficient = 1.25 (Small Advantage) +- 3D NAND Coefficient = 0.5 (Big Penalty) +- QLC Coefficient = 0.75 (Small Penalty) +- TLC Coefficient = 1.0 (No Change) +- MLC V-NAND coefficient = 1.25 (Small Advantage) ## NVMe M.2 2280 M Key @@ -119,24 +118,24 @@ Selected $0.10 as the baseline after averaging some calculations. ### SSD -| Description | $ / GB | 1 per cent | 1 per GB | Coefficient | Score | -| ------------------------- | :----: | :--------: | :------: | :---------: | :---: | -| Inland 1TB $50 TLC | 0.050 | 5 | 1000 | 1 | 1005 | -| Inland 512GB $25 TLC | 0.049 | 5.1 | 512 | 1 | 517 | -| Platinum 2TB $80 TLC | 0.040 | 6 | 2000 | 1 | 2006 | -| Platinum 1TB $43 TLC | 0.043 | 5.7 | 1000 | 1 | 1006 | -| Professional 256GB $20 3D | 0.078 | 2.2 | 256 | 0.5 | 129 | -| Professional 125GB $15 3D | 0.120 | 0 | 125 | 0.5 | 63 | -| 870 EVO 1TB $50 MLC | 0.050 | 5 | 1000 | 1.25 | 1256 | -| 870 EVO 4TB $220 MLC | 0.055 | 4.5 | 4000 | 1.25 | 5006 | -| 870 EVO 500GB $40 MLC | 0.020 | 8 | 500 | 1.25 | 635 | -| 870 QVO 1TB $70 QLC | 0.070 | 3 | 1000 | 0.75 | 753 | +| Description | $ / GB | 1 per cent | 1 per GB | Coefficient | Score | +| --- | :-: | :-: | :-: | :-: | :-: | +| Inland 1TB $50 TLC | 0.050 | 5 | 1000 | 1 | 1005 | +| Inland 512GB $25 TLC | 0.049 | 5.1 | 512 | 1 | 517 | +| Platinum 2TB $80 TLC | 0.040 | 6 | 2000 | 1 | 2006 | +| Platinum 1TB $43 TLC | 0.043 | 5.7 | 1000 | 1 | 1006 | +| Professional 256GB $20 3D | 0.078 | 2.2 | 256 | 0.5 | 129 | +| Professional 125GB $15 3D | 0.120 | 0 | 125 | 0.5 | 63 | +| 870 EVO 1TB $50 MLC | 0.050 | 5 | 1000 | 1.25 | 1256 | +| 870 EVO 4TB $220 MLC | 0.055 | 4.5 | 4000 | 1.25 | 5006 | +| 870 EVO 500GB $40 MLC | 0.020 | 8 | 500 | 1.25 | 635 | +| 870 QVO 1TB $70 QLC | 0.070 | 3 | 1000 | 0.75 | 753 | \*_Higher is better._ ## Conclusions -- Cheap system? Get **any** of these. -- Secondary drive? Get any **QLC** or better. -- OS Drive? Get any **TLC** or better. -- Extra cash? Premium for reliability? Get any **Samsung** from the list. +- Cheap system? Get **any** of these. +- Secondary drive? Get any **QLC** or better. +- OS Drive? Get any **TLC** or better. +- Extra cash? Premium for reliability? Get any **Samsung** from the list. diff --git a/content/Sans-serif.md b/content/Sans-serif.md index 3bd48424c..b66acc542 100644 --- a/content/Sans-serif.md +++ b/content/Sans-serif.md @@ -13,6 +13,6 @@ In typography and lettering, a "sans-serif", "sans serif", "gothic", or simply " ```css font-family: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir, - "Nimbus Sans L", Roboto, Noto, "Segoe UI", Arial, Helvetica, - "Helvetica Neue", sans-serif; + "Nimbus Sans L", Roboto, Noto, "Segoe UI", Arial, Helvetica, "Helvetica Neue", + sans-serif; ``` diff --git a/content/Scope of Work.md b/content/Scope of Work.md index b0c021088..483361547 100644 --- a/content/Scope of Work.md +++ b/content/Scope of Work.md @@ -10,10 +10,10 @@ You might create an SOW when multiple parties are working together on the projec Usually, a scope of work has these standard components: -- Project goals: This includes all goals intended for the team to reach as the project progresses, as well as when it completes. -- Timeline: This includes the exact timeframe for the project's beginning and end, including when specific tasks should start and complete. -- Expected results: This includes specific outcomes intended to take place by the completion of the project. -- Deliverables: This includes the exact product or products the project should deliver by its completion. -- Conditions: This includes any stipulations the team should abide by or work under or requirements to meet. -- Financial information: This includes accounting data, such as how much the project may cost to complete, how much each team member is going to earn and how and when they're going to be paid. -- Management: This includes information regarding administrative details, such as who's responsible for approving financial decisions or who can agree to specific terms. +- Project goals: This includes all goals intended for the team to reach as the project progresses, as well as when it completes. +- Timeline: This includes the exact timeframe for the project's beginning and end, including when specific tasks should start and complete. +- Expected results: This includes specific outcomes intended to take place by the completion of the project. +- Deliverables: This includes the exact product or products the project should deliver by its completion. +- Conditions: This includes any stipulations the team should abide by or work under or requirements to meet. +- Financial information: This includes accounting data, such as how much the project may cost to complete, how much each team member is going to earn and how and when they're going to be paid. +- Management: This includes information regarding administrative details, such as who's responsible for approving financial decisions or who can agree to specific terms. diff --git a/content/Serif.md b/content/Serif.md index f6c7652c2..ec3b73ea0 100644 --- a/content/Serif.md +++ b/content/Serif.md @@ -13,5 +13,5 @@ In typography, a serif (/ˈsɛrɪf/) is a small line or stroke regularly attache ```css font-family: Constantia, "Lucida Bright", Lucidabright, "Lucida Serif", Lucida, - "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif; + "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif; ``` diff --git a/content/Static Site Generators.md b/content/Static Site Generators.md index 77c104ad4..e79dd7023 100644 --- a/content/Static Site Generators.md +++ b/content/Static Site Generators.md @@ -9,6 +9,6 @@ Static site generators (SSGs) are engines that use text input files (such as [[. Popular choices in SSGs include: -- [Jekyll](https://jekyllrb.com/) -- [Hugo](https://gohugo.io/) -- [[./Zola|Zola]] +- [Jekyll](https://jekyllrb.com/) +- [Hugo](https://gohugo.io/) +- [[./Zola|Zola]] diff --git a/content/Tools.md b/content/Tools.md index 9267ba3bd..02c731dae 100644 --- a/content/Tools.md +++ b/content/Tools.md @@ -9,47 +9,47 @@ A non-comprehensive list of the hardware and software I use on a day-to-day basi ## Hardware -- [Lenovo Thinkpad T480s](https://www.notebookcheck.net/Lenovo-ThinkPad-T480s-20L8S02D00.294734.0.html) – Laptop -- [Logi M575](https://www.logitech.com/en-us/products/mice/m575-ergo-wireless-trackball) – Mouse -- [Skullcandy Crusher ANC](https://info.skullcandy.com/Support?Dest=hc%2Fen-us%2Farticles%2F360034534854-Crusher-ANC-Wireless) – Headphones +- [Lenovo Thinkpad T480s](https://www.notebookcheck.net/Lenovo-ThinkPad-T480s-20L8S02D00.294734.0.html) – Laptop +- [Logi M575](https://www.logitech.com/en-us/products/mice/m575-ergo-wireless-trackball) – Mouse +- [Skullcandy Crusher ANC](https://info.skullcandy.com/Support?Dest=hc%2Fen-us%2Farticles%2F360034534854-Crusher-ANC-Wireless) – Headphones ## Software ### Daily Drivers -- [Obsidian](https://obsidian.md/) – Markdown Text Editor -- [Visual Studio Code](https://code.visualstudio.com/) – Code Editor -- [Raindrop](https://raindrop.io/) – Bookmarks Manager -- [Syncthing](https://github.com/syncthing/syncthing) – Open source continuous file synchronization. -- [Espanso](https://github.com/espanso/espanso) – Cross-platform text expander written in Rust. +- [Obsidian](https://obsidian.md/) – Markdown Text Editor +- [Visual Studio Code](https://code.visualstudio.com/) – Code Editor +- [Raindrop](https://raindrop.io/) – Bookmarks Manager +- [Syncthing](https://github.com/syncthing/syncthing) – Open source continuous file synchronization. +- [Espanso](https://github.com/espanso/espanso) – Cross-platform text expander written in Rust. ### Notable Visual Studio Code Extensions -- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) – Opinionated Code Formatter. -- [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) – Markdown Support. -- [Markwhen](https://marketplace.visualstudio.com/items?itemName=Markwhen.markwhen) – View/Edit Markwhen documents. -- [Markmap](https://marketplace.visualstudio.com/items?itemName=gera2ld.markmap-vscode) – Preview markdown files as Markmap. -- [GistPad](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs) – Edit GitHub Gists and Repositories remotely. -- [Project Manager](https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager) – Easily change current Project/Folder. -- [shift shift](https://marketplace.visualstudio.com/items?itemName=ahgood.shift-shift) – Provides shortcuts for `shift + shift` and `ctrl + ctrl`. -- [CSS Peek](https://marketplace.visualstudio.com/items?itemName=pranaygp.vscode-css-peek) – Introduces `Go To Definition` and `Go To Symbol in Workspace` support. +- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) – Opinionated Code Formatter. +- [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) – Markdown Support. +- [Markwhen](https://marketplace.visualstudio.com/items?itemName=Markwhen.markwhen) – View/Edit Markwhen documents. +- [Markmap](https://marketplace.visualstudio.com/items?itemName=gera2ld.markmap-vscode) – Preview markdown files as Markmap. +- [GistPad](https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs) – Edit GitHub Gists and Repositories remotely. +- [Project Manager](https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager) – Easily change current Project/Folder. +- [shift shift](https://marketplace.visualstudio.com/items?itemName=ahgood.shift-shift) – Provides shortcuts for `shift + shift` and `ctrl + ctrl`. +- [CSS Peek](https://marketplace.visualstudio.com/items?itemName=pranaygp.vscode-css-peek) – Introduces `Go To Definition` and `Go To Symbol in Workspace` support. ### Notable Obsidian Extensions -- [Omnisearch](https://github.com/scambier/obsidian-omnisearch) – Better Search Engine. -- [Obsidian Git](https://github.com/denolehov/obsidian-git) – Run Git commands from Obsidian. -- [Natural Language Dates](https://github.com/argenos/nldates-obsidian) – Adds `@today` format to dates. -- [Linter](https://github.com/platers/obsidian-linter) – Customizable Markdown Linter similar to Prettier. -- [Editor Shortcuts](https://github.com/timhor/obsidian-editor-shortcuts) – Adds shortcuts usually found in Code Editors. -- [GitHub Publisher](https://github.com/ObsidianPublisher/obsidian-github-publisher) – Publish your notes to a preconfigured repository. -- [DoubleShift](https://github.com/Qwyntex/doubleshift) – Provides shortcuts for `shift + shift` and `ctrl + ctrl`. -- [Contextual Typography](https://github.com/mgmeyers/obsidian-contextual-typography) – Allows for custom insertion of code at the block level. +- [Omnisearch](https://github.com/scambier/obsidian-omnisearch) – Better Search Engine. +- [Obsidian Git](https://github.com/denolehov/obsidian-git) – Run Git commands from Obsidian. +- [Natural Language Dates](https://github.com/argenos/nldates-obsidian) – Adds `@today` format to dates. +- [Linter](https://github.com/platers/obsidian-linter) – Customizable Markdown Linter similar to Prettier. +- [Editor Shortcuts](https://github.com/timhor/obsidian-editor-shortcuts) – Adds shortcuts usually found in Code Editors. +- [GitHub Publisher](https://github.com/ObsidianPublisher/obsidian-github-publisher) – Publish your notes to a preconfigured repository. +- [DoubleShift](https://github.com/Qwyntex/doubleshift) – Provides shortcuts for `shift + shift` and `ctrl + ctrl`. +- [Contextual Typography](https://github.com/mgmeyers/obsidian-contextual-typography) – Allows for custom insertion of code at the block level. ### Notable Windows Programs -- [Everything](https://www.voidtools.com/) – Locate files and folders by name instantly. -- [QuickLook](https://github.com/QL-Win/QuickLook) – Bring macOS "Quick Look" feature to Windows. -- [PowerToys](https://github.com/microsoft/PowerToys) – Windows system utilities to maximize productivity. -- [EarTrumpet](https://github.com/File-New-Project/EarTrumpet) – Volume control, multi-channel discovery, default playback device management. -- [PDFsam](https://github.com/torakiki/pdfsam) – Desktop application to split, merge, mix, rotate PDF files and extract pages. -- [TaskbarX](https://github.com/ChrisAnd1998/TaskbarX) – Center Windows taskbar icons with a variety of animations and options. +- [Everything](https://www.voidtools.com/) – Locate files and folders by name instantly. +- [QuickLook](https://github.com/QL-Win/QuickLook) – Bring macOS "Quick Look" feature to Windows. +- [PowerToys](https://github.com/microsoft/PowerToys) – Windows system utilities to maximize productivity. +- [EarTrumpet](https://github.com/File-New-Project/EarTrumpet) – Volume control, multi-channel discovery, default playback device management. +- [PDFsam](https://github.com/torakiki/pdfsam) – Desktop application to split, merge, mix, rotate PDF files and extract pages. +- [TaskbarX](https://github.com/ChrisAnd1998/TaskbarX) – Center Windows taskbar icons with a variety of animations and options. diff --git a/content/Words.md b/content/Words.md index 81a316154..496278682 100644 --- a/content/Words.md +++ b/content/Words.md @@ -14,8 +14,7 @@ compartir: true ### Anodyne (adjective) 1. Capable of soothing or eliminating pain. -2. Not likely to offend or arouse tensions. - Also used as a _noun_ to describe something that soothes, calms, or comforts. +2. Not likely to offend or arouse tensions. Also used as a _noun_ to describe something that soothes, calms, or comforts. ### Arete (noun) @@ -63,7 +62,7 @@ compartir: true ### Dogfooding (slang) -- _From [Wikipedia](https://en.wikipedia.org/wiki/Eating_your_own_dog_food)_: Eating your own dog food or "_dogfooding_" is the practice of using one's own products or services. +- _From [Wikipedia](https://en.wikipedia.org/wiki/Eating_your_own_dog_food)_: Eating your own dog food or "_dogfooding_" is the practice of using one's own products or services. ### Excoriate (verb) diff --git a/content/index.md b/content/index.md index 608e393d5..3a87a2596 100644 --- a/content/index.md +++ b/content/index.md @@ -13,12 +13,12 @@ You will find within a wide range of topics, expanding and exploring ideas acros Like me, all notes contained within should be considered work-in-progress. Expect changes at all content levels. That said, I try not to let perfectionism get in the way. That means what you read here is not authoritative or complete, and is not representative of my best work. Please keep that in mind as you navigate around the garden. I'm glad you are here. Enjoy your visit! -- Want to learn more _about me_? - Check out the [[./About|About]] page. -- Want to read more of my material? - Visit my [Blog](https://miguelpimentel.do/). -- Want to learn more _about the site_? - Review the site's [[./Meta|Meta]] page. +- Want to learn more _about me_? + Check out the [[./About|About]] page. +- Want to read more of my material? + Visit my [Blog](https://miguelpimentel.do/). +- Want to learn more _about the site_? + Review the site's [[./Meta|Meta]] page. I leave you with some fun quotes. Feel free to stop by the [[./Quotes|Quotes]]. diff --git a/docs/advanced/architecture.md b/docs/advanced/architecture.md index be4bc74ca..33da89d90 100644 --- a/docs/advanced/architecture.md +++ b/docs/advanced/architecture.md @@ -11,42 +11,42 @@ This question is best answered by tracing what happens when a user (you!) runs ` 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]() 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. + 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 `` and `` 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. + 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 `` and `` 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 `` 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. + 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]]. diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md index 2c62bbacc..549c130a3 100644 --- a/docs/advanced/creating components.md +++ b/docs/advanced/creating components.md @@ -2,15 +2,14 @@ title: Creating your own Quartz components --- -> [!warning] -> This guide assumes you have experience writing JavaScript and are familiar with TypeScript. +> [!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
-

An article header

-

Some content

+

An article header

+

Some content

``` @@ -32,24 +31,24 @@ In your component, you can use the values from the configuration option to chang ```tsx {11-17} interface Options { - favouriteNumber: number + favouriteNumber: number } const defaultOptions: Options = { - favouriteNumber: 42, + favouriteNumber: 42, } export default ((userOpts?: Options) => { - const opts = { ...userOpts, ...defaultOpts } - function YourComponent(props: QuartzComponentProps) { - if (opts.favouriteNumber < 0) { - return null - } - - return

My favourite number is {opts.favouriteNumber}

+ const opts = {...userOpts, ...defaultOpts} + function YourComponent(props: QuartzComponentProps) { + if (opts.favouriteNumber < 0) { + return null } - return YourComponent + return

My favourite number is {opts.favouriteNumber}

+ } + + return YourComponent }) satisfies QuartzComponentConstructor ``` @@ -62,21 +61,21 @@ 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 - allFiles: QuartzPluginData[] - displayClass?: "mobile-only" | "desktop-only" + fileData: QuartzPluginData + cfg: GlobalConfiguration + tree: Node + 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. +- `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 @@ -86,17 +85,17 @@ Note that inlined styles **must** be plain vanilla CSS: ```tsx {6-10} title="quartz/components/YourComponent.tsx" export default (() => { - function YourComponent() { - return

Example Component

- } + function YourComponent() { + return

Example Component

+ } - YourComponent.css = ` + YourComponent.css = ` p.red-text { color: red; } ` - return YourComponent + return YourComponent }) satisfies QuartzComponentConstructor ``` @@ -107,17 +106,16 @@ Imported styles, however, can be from SCSS files: import styles from "./styles/YourComponent.scss" export default (() => { - function YourComponent() { - return

Example Component

- } + function YourComponent() { + return

Example Component

+ } - YourComponent.css = styles - return YourComponent + 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. +> [!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 @@ -125,25 +123,24 @@ What about interactivity? Suppose you want to add an-click handler for example. ```tsx title="quartz/components/YourComponent.tsx" export default (() => { - function YourComponent() { - return - } + function YourComponent() { + return + } - YourComponent.beforeDOM = ` + YourComponent.beforeDOM = ` console.log("hello from before the page loads!") ` - YourComponent.afterDOM = ` + YourComponent.afterDOM = ` document.getElementById('btn').onclick = () => { alert('button clicked!') } ` - return YourComponent + 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. +> [!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. @@ -153,16 +150,15 @@ If you need to create an `afterDOMLoaded` script that depends on _page specific_ ```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, { passive: true }) + // 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, {passive: true}) }) ``` -It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. -This will get called on page navigation. +It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. This will get called on page navigation. #### Importing Code @@ -176,12 +172,12 @@ Quartz supports importing component code through `.inline.ts` files. import script from "./scripts/graph.inline" export default (() => { - function YourComponent() { - return - } + function YourComponent() { + return + } - YourComponent.afterDOM = script - return YourComponent + YourComponent.afterDOM = script + return YourComponent }) satisfies QuartzComponentConstructor ``` @@ -190,7 +186,7 @@ export default (() => { import * as d3 from "d3" document.getElementById("btn").onclick = () => { - alert("button clicked!") + alert("button clicked!") } ``` @@ -206,7 +202,7 @@ import Content from "./pages/Content" import Darkmode from "./Darkmode" import YourComponent from "./YourComponent" -export { ArticleTitle, Content, Darkmode, 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. @@ -217,18 +213,17 @@ As Quartz components are just functions that return React components, you can co import YourComponent from "./YourComponent" export default (() => { - function AnotherComponent(props: QuartzComponentProps) { - return ( -
-

It's nested!

- -
- ) - } + function AnotherComponent(props: QuartzComponentProps) { + return ( +
+

It's nested!

+ +
+ ) + } - return AnotherComponent + return AnotherComponent }) satisfies QuartzComponentConstructor ``` -> [!hint] -> Look in `quartz/components` for more examples of components in Quartz as reference for your own components! +> [!hint] Look in `quartz/components` for more examples of components in Quartz as reference for your own components! diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index 748aa6ff1..d8aa2af9a 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -2,8 +2,7 @@ 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. +> [!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: @@ -14,23 +13,23 @@ All plugins are defined as a function that takes in a single parameter for optio ```ts type OptionType = object | undefined type QuartzPlugin = ( - opts?: Options, + opts?: Options, ) => QuartzPluginInstance type QuartzPluginInstance = - | QuartzTransformerPluginInstance - | QuartzFilterPluginInstance - | QuartzEmitterPluginInstance + | 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. +- `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 @@ -38,20 +37,20 @@ Transformers **map** over content, taking a Markdown file and outputting modifie ```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 + name: string + textTransform?: (ctx: BuildCtx, src: string | Buffer) => string | Buffer + markdownPlugins?: (ctx: BuildCtx) => PluggableList + htmlPlugins?: (ctx: BuildCtx) => PluggableList + externalResources?: (ctx: BuildCtx) => Partial } ``` 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. +- `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). @@ -61,49 +60,49 @@ A good example of a transformer plugin that borrows from the `remark` and `rehyp import remarkMath from "remark-math" import rehypeKatex from "rehype-katex" import rehypeMathjax from "rehype-mathjax/svg" -import { QuartzTransformerPlugin } from "../types" +import {QuartzTransformerPlugin} from "../types" interface Options { - renderEngine: "katex" | "mathjax" + renderEngine: "katex" | "mathjax" } export const Latex: QuartzTransformerPlugin = (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: [ - // base css - "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css", - ], - js: [ - { - // fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md - src: "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/copy-tex.min.js", - loadTime: "afterDOMReady", - contentType: "external", - }, - ], - } - } else { - return {} - } - }, - } + 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: [ + // base css + "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css", + ], + js: [ + { + // fix copy behaviour: https://github.com/KaTeX/KaTeX/blob/main/contrib/copy-tex/README.md + src: "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/copy-tex.min.js", + loadTime: "afterDOMReady", + contentType: "external", + }, + ], + } + } else { + return {} + } + }, + } } ``` @@ -111,30 +110,30 @@ Another common thing that transformer plugins will do is parse a file and add ex ```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 - } - }, - ] + 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 - } + interface DataMap { + wordcount: number + } } ``` @@ -185,12 +184,12 @@ Filters **filter** content, taking the output of all the transformers and determ ```ts export type QuartzFilterPlugin = ( - opts?: Options, + opts?: Options, ) => QuartzFilterPluginInstance export type QuartzFilterPluginInstance = { - name: string - shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean + name: string + shouldPublish(ctx: BuildCtx, content: ProcessedContent): boolean } ``` @@ -199,15 +198,15 @@ A filter plugin must define a `name` field and a `shouldPublish` function that t For example, here is the built-in plugin for removing drafts: ```ts title="quartz/plugins/filters/draft.ts" -import { QuartzFilterPlugin } from "../types" +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 - }, + name: "RemoveDrafts", + shouldPublish(_ctx, [_tree, vfile]) { + // uses frontmatter parsed from transformers + const draftFlag: boolean = vfile.data?.frontmatter?.draft ?? false + return !draftFlag + }, }) ``` @@ -217,17 +216,17 @@ Emitters **reduce** over content, taking in a list of all the transformed and fi ```ts export type QuartzEmitterPlugin = ( - opts?: Options, + opts?: Options, ) => QuartzEmitterPluginInstance export type QuartzEmitterPluginInstance = { - name: string - emit( - ctx: BuildCtx, - content: ProcessedContent[], - resources: StaticResources, - ): Promise - getQuartzComponents(ctx: BuildCtx): QuartzComponent[] + name: string + emit( + ctx: BuildCtx, + content: ProcessedContent[], + resources: StaticResources, + ): Promise + getQuartzComponents(ctx: BuildCtx): QuartzComponent[] } ``` @@ -237,14 +236,14 @@ Creating new files can be done via regular Node [fs module](https://nodejs.org/a ```ts export type WriteOptions = (data: { - // the build context - ctx: BuildCtx - // the name of the file to emit (not including the file extension) - slug: ServerSlug - // the file extension - ext: `.${string}` | "" - // the file content to add - content: string + // the build context + ctx: BuildCtx + // 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 ``` @@ -252,72 +251,71 @@ This is a thin wrapper around writing to the appropriate output folder and ensur 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 `htmlToJsx` function from `quartz/util/jsx.ts`. An example of this can be found in `quartz/components/pages/Content.tsx`. +- 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 `htmlToJsx` function from `quartz/util/jsx.ts`. 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 { - 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, - } + // 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 { + 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( - cfg, - slug, - componentData, - opts, - externalResources, - ) - const fp = await emit({ - content, - slug: file.data.slug!, - ext: ".html", - }) + const content = renderPage( + cfg, + slug, + componentData, + opts, + externalResources, + ) + const fp = await emit({ + content, + slug: file.data.slug!, + ext: ".html", + }) - fps.push(fp) - } - return fps - }, - } + 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! +> [!hint] Look in `quartz/plugins` for more examples of plugins in Quartz as reference for your own plugins! diff --git a/docs/advanced/paths.md b/docs/advanced/paths.md index d19af777f..7ac874759 100644 --- a/docs/advanced/paths.md +++ b/docs/advanced/paths.md @@ -15,7 +15,7 @@ Luckily, we can mimic nominal typing using [brands](https://www.typescriptlang.o type FullSlug = string // we do -type FullSlug = string & { __brand: "full" } +type FullSlug = string & {__brand: "full"} // that way, the following will fail typechecking const slug: FullSlug = "some random string" @@ -43,9 +43,9 @@ graph LR 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. +- `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`. diff --git a/docs/authoring content.md b/docs/authoring content.md index bb17e7dfd..96b1eaddb 100644 --- a/docs/authoring content.md +++ b/docs/authoring content.md @@ -19,7 +19,7 @@ Additionally, Quartz also allows you to specify additional metadata in your note title: Example Title draft: false tags: - - example-tag + - example-tag --- The rest of your content lives here. You can use **Markdown** here :) @@ -27,17 +27,16 @@ The rest of your content lives here. You can use **Markdown** here :) Some common frontmatter fields that are natively supported by Quartz: -- `title`: Title of the page. If it isn't provided, Quartz will use the name of the file as the title. -- `description`: Description of the page used for link previews. -- `aliases`: Other names for this note. This is a list of strings. -- `tags`: Tags for this note. -- `draft`: Whether to publish the page or not. This is one way to make [[private pages|pages private]] in Quartz. -- `date`: A string representing the day the note was published. Normally uses `YYYY-MM-DD` format. +- `title`: Title of the page. If it isn't provided, Quartz will use the name of the file as the title. +- `description`: Description of the page used for link previews. +- `aliases`: Other names for this note. This is a list of strings. +- `tags`: Tags for this note. +- `draft`: Whether to publish the page or not. This is one way to make [[private pages|pages private]] in Quartz. +- `date`: A string representing the day the note was published. Normally uses `YYYY-MM-DD` format. ## Syncing your Content -When your Quartz is at a point you're happy with, you can save your changes to GitHub. -First, make sure you've [[setting up your GitHub repository|already setup your GitHub repository]] and then do `npx quartz sync`. +When your Quartz is at a point you're happy with, you can save your changes to GitHub. First, make sure you've [[setting up your GitHub repository|already setup your GitHub repository]] and then do `npx quartz sync`. ## Customization diff --git a/docs/build.md b/docs/build.md index 689e120f9..f147964ec 100644 --- a/docs/build.md +++ b/docs/build.md @@ -10,14 +10,13 @@ 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`. +> [!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 +> - `-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 diff --git a/docs/configuration.md b/docs/configuration.md index 34e9be699..03f1690c3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,8 +4,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` 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! +> [!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: @@ -20,36 +19,36 @@ const config: QuartzConfig = { 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: 'google', tagId: '' }`: use Google Analytics; - - `{ provider: 'plausible' }` (managed) or `{ provider: 'plausible', host: '' }` (self-hosted): use [Plausible](https://plausible.io/); - - `{ provider: 'umami', host: '', websiteId: '' }`: use [Umami](https://umami.is/); - - `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id' }` (managed) or `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id', host: 'my-goatcounter-domain.com', scriptSrc: 'https://my-url.to/counter.js' }` (self-hosted) use [GoatCounter](https://goatcounter.com) -- `locale`: used for [[i18n]] and date formatting -- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes. - - This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`. - - 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. See [[private pages]] for more details. -- `defaultDateType`: whether to use created, modified, or published as the default date to display on pages and page listings. -- `theme`: configure how the site looks. - - `cdnCaching`: If `true` (default), use Google CDN to cache the fonts. This will generally will be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained. - - `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here. - - `header`: Font to use for headers - - `code`: Font for inline and block quotes. - - `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]] +- `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: 'google', tagId: '' }`: use Google Analytics; + - `{ provider: 'plausible' }` (managed) or `{ provider: 'plausible', host: '' }` (self-hosted): use [Plausible](https://plausible.io/); + - `{ provider: 'umami', host: '', websiteId: '' }`: use [Umami](https://umami.is/); + - `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id' }` (managed) or `{ provider: 'goatcounter', websiteId: 'my-goatcounter-id', host: 'my-goatcounter-domain.com', scriptSrc: 'https://my-url.to/counter.js' }` (self-hosted) use [GoatCounter](https://goatcounter.com) +- `locale`: used for [[i18n]] and date formatting +- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes. + - This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`. + - 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. See [[private pages]] for more details. +- `defaultDateType`: whether to use created, modified, or published as the default date to display on pages and page listings. +- `theme`: configure how the site looks. + - `cdnCaching`: If `true` (default), use Google CDN to cache the fonts. This will generally will be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained. + - `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here. + - `header`: Font to use for headers + - `code`: Font for inline and block quotes. + - `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 @@ -65,14 +64,13 @@ plugins: { } ``` -- [[tags/plugin/transformer|Transformers]] **map** over content (e.g. parsing frontmatter, generating a description) -- [[tags/plugin/filter|Filters]] **filter** content (e.g. filtering out drafts) -- [[tags/plugin/emitter|Emitters]] **reduce** over content (e.g. creating an RSS feed or pages that list all files with a specific tag) +- [[tags/plugin/transformer|Transformers]] **map** over content (e.g. parsing frontmatter, generating a description) +- [[tags/plugin/filter|Filters]] **filter** content (e.g. filtering out drafts) +- [[tags/plugin/emitter|Emitters]] **reduce** over content (e.g. creating an RSS feed or pages that list all files with a specific tag) You can customize the behaviour of Quartz by adding, removing and reordering plugins in the `transformers`, `filters` and `emitters` fields. -> [!note] -> Each node is modified by every transformer _in order_. Some transformers are position sensitive, so you may need to pay particular attention to whether they need to come before or after certain other plugins. +> [!note] Each node is modified by every transformer _in order_. Some transformers are position sensitive, so you may need to pay particular attention to whether they need to come before or after certain other plugins. You should take care to add the plugin to the right entry corresponding to its plugin type. For example, to add the [[ExplicitPublish]] plugin (a [[tags/plugin/filter|Filter]]), you would add the following line: @@ -92,8 +90,8 @@ For example, the [[plugins/Latex|Latex]] plugin allows you to pass in a field sp ```ts title="quartz.config.ts" transformers: [ - Plugin.FrontMatter(), // use default options - Plugin.Latex({ renderEngine: "katex" }), // set some custom options + Plugin.FrontMatter(), // use default options + Plugin.Latex({renderEngine: "katex"}), // set some custom options ] ``` diff --git a/docs/features/Latex.md b/docs/features/Latex.md index aeaa0af8a..3a9b4f14b 100644 --- a/docs/features/Latex.md +++ b/docs/features/Latex.md @@ -1,7 +1,7 @@ --- title: LaTeX tags: - - feature/transformer + - feature/transformer --- Quartz uses [Katex](https://katex.org/) by default to typeset both inline and block math expressions at build time. @@ -39,8 +39,7 @@ a & b & c \end{bmatrix} $$ -> [!warn] -> Due to limitations in the [underlying parsing library](https://github.com/remarkjs/remark-math), block math in Quartz requires the `$$` delimiters to be on newlines like above. +> [!warn] Due to limitations in the [underlying parsing library](https://github.com/remarkjs/remark-math), block math in Quartz requires the `$$` delimiters to be on newlines like above. ### Inline Math @@ -54,13 +53,12 @@ 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 +- 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 ### Using mhchem -Add the following import to the top of `quartz/plugins/transformers/latex.ts` (before all the other -imports): +Add the following import to the top of `quartz/plugins/transformers/latex.ts` (before all the other imports): ```ts title="quartz/plugins/transformers/latex.ts" import "katex/contrib/mhchem" diff --git a/docs/features/Mermaid diagrams.md b/docs/features/Mermaid diagrams.md index 733f6e65b..284596d17 100644 --- a/docs/features/Mermaid diagrams.md +++ b/docs/features/Mermaid diagrams.md @@ -1,15 +1,14 @@ --- title: "Mermaid Diagrams" tags: - - feature/transformer + - feature/transformer --- Quartz supports Mermaid which allows you to add diagrams and charts to your notes. Mermaid supports a range of diagrams, such as [flow charts](https://mermaid.js.org/syntax/flowchart.html), [sequence diagrams](https://mermaid.js.org/syntax/sequenceDiagram.html), and [timelines](https://mermaid.js.org/syntax/timeline.html). This is enabled as a part of [[Obsidian compatibility]] and can be configured and enabled/disabled from that plugin. By default, Quartz will render Mermaid diagrams to match the site theme. -> [!warning] -> Wondering why Mermaid diagrams may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]]. +> [!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 [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]]. ## Syntax diff --git a/docs/features/Obsidian compatibility.md b/docs/features/Obsidian compatibility.md index 2ed85d5cd..e469f4866 100644 --- a/docs/features/Obsidian compatibility.md +++ b/docs/features/Obsidian compatibility.md @@ -1,7 +1,7 @@ --- title: "Obsidian Compatibility" tags: - - feature/transformer + - feature/transformer --- Quartz was originally designed as a tool to publish Obsidian vaults as websites. Even as the scope of Quartz has widened over time, it hasn't lost the ability to seamlessly interoperate with Obsidian. diff --git a/docs/features/OxHugo compatibility.md b/docs/features/OxHugo compatibility.md index d9c479cf5..e22051146 100644 --- a/docs/features/OxHugo compatibility.md +++ b/docs/features/OxHugo compatibility.md @@ -1,7 +1,7 @@ --- title: "OxHugo Compatibility" tags: - - feature/transformer + - feature/transformer --- [org-roam](https://www.orgroam.com/) is a plain-text personal knowledge management system for [emacs](https://en.wikipedia.org/wiki/Emacs). [ox-hugo](https://github.com/kaushalmodi/ox-hugo) is org exporter backend that exports `org-mode` files to [Hugo](https://gohugo.io/) compatible Markdown. diff --git a/docs/features/SPA Routing.md b/docs/features/SPA Routing.md index 0d0f0b335..3004af977 100644 --- a/docs/features/SPA Routing.md +++ b/docs/features/SPA Routing.md @@ -4,4 +4,4 @@ Under the hood, this is done by hijacking page navigations and instead fetching ## Configuration -- Disable SPA Routing: set the `enableSPA` field of the [[configuration]] in `quartz.config.ts` to be `false`. +- Disable SPA Routing: set the `enableSPA` field of the [[configuration]] in `quartz.config.ts` to be `false`. diff --git a/docs/features/backlinks.md b/docs/features/backlinks.md index d2f60ca81..f558f4a5d 100644 --- a/docs/features/backlinks.md +++ b/docs/features/backlinks.md @@ -1,14 +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.layout.ts`. -- Component: `quartz/components/Backlinks.tsx` -- Style: `quartz/components/styles/backlinks.scss` -- Script: `quartz/components/scripts/search.inline.ts` +- 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` diff --git a/docs/features/breadcrumbs.md b/docs/features/breadcrumbs.md index f29744471..a70185836 100644 --- a/docs/features/breadcrumbs.md +++ b/docs/features/breadcrumbs.md @@ -1,7 +1,7 @@ --- title: "Breadcrumbs" tags: - - component + - component --- Breadcrumbs provide a way to navigate a hierarchy of pages within your site using a list of its parent folders. @@ -16,11 +16,11 @@ For example, here's what the default configuration looks like: ```typescript title="quartz.layout.ts" Component.Breadcrumbs({ - spacerSymbol: "❯", // symbol between crumbs - rootName: "Home", // name of first/root element - resolveFrontmatterTitle: true, // whether to resolve folder names through frontmatter titles - hideOnRoot: true, // whether to hide breadcrumbs on root `index.md` page - showCurrentPage: true, // whether to display the current page in the breadcrumbs + spacerSymbol: "❯", // symbol between crumbs + rootName: "Home", // name of first/root element + resolveFrontmatterTitle: true, // whether to resolve folder names through frontmatter titles + hideOnRoot: true, // whether to hide breadcrumbs on root `index.md` page + showCurrentPage: true, // whether to display the current page in the breadcrumbs }) ``` @@ -30,7 +30,7 @@ You can also adjust where the breadcrumbs will be displayed by adjusting the [[l Want to customize it even more? -- Removing breadcrumbs: delete all usages of `Component.Breadcrumbs()` from `quartz.layout.ts`. -- Component: `quartz/components/Breadcrumbs.tsx` -- Style: `quartz/components/styles/breadcrumbs.scss` -- Script: inline at `quartz/components/Breadcrumbs.tsx` +- Removing breadcrumbs: delete all usages of `Component.Breadcrumbs()` from `quartz.layout.ts`. +- Component: `quartz/components/Breadcrumbs.tsx` +- Style: `quartz/components/styles/breadcrumbs.scss` +- Script: inline at `quartz/components/Breadcrumbs.tsx` diff --git a/docs/features/callouts.md b/docs/features/callouts.md index f1a5a892b..0eae10f48 100644 --- a/docs/features/callouts.md +++ b/docs/features/callouts.md @@ -1,15 +1,15 @@ --- title: Callouts tags: - - feature/transformer + - feature/transformer --- Quartz supports the same Admonition-callout syntax as Obsidian. This includes -- 12 Distinct callout types (each with several aliases) -- Collapsable callouts +- 12 Distinct callout types (each with several aliases) +- Collapsable callouts ``` > [!info] Title @@ -18,8 +18,7 @@ This includes See [documentation on supported types and syntax here](https://help.obsidian.md/Editing+and+formatting/Callouts). -> [!warning] -> Wondering why callouts may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]]. +> [!warning] Wondering why callouts may not be showing up even if you have them enabled? You may need to reorder your plugins so that [[ObsidianFlavoredMarkdown]] is _after_ [[SyntaxHighlighting]]. ## Customization @@ -33,22 +32,20 @@ By default, custom callouts are handled by applying the `note` style. To make fa ```scss title="quartz/styles/custom.scss" .callout { - &[data-callout="custom"] { - --color: #customcolor; - --border: #custombordercolor; - --bg: #custombg; - --callout-icon: url("data:image/svg+xml; utf8, "); //SVG icon code - } + &[data-callout="custom"] { + --color: #customcolor; + --border: #custombordercolor; + --bg: #custombg; + --callout-icon: url("data:image/svg+xml; utf8, "); //SVG icon code + } } ``` -> [!warning] -> Don't forget to ensure that the SVG is URL encoded before putting it in the CSS. You can use tools like [this one](https://yoksel.github.io/url-encoder/) to help you do that. +> [!warning] Don't forget to ensure that the SVG is URL encoded before putting it in the CSS. You can use tools like [this one](https://yoksel.github.io/url-encoder/) to help you do that. ## Showcase -> [!info] -> Default title +> [!info] Default title > [!question]+ Can callouts be _nested_? > @@ -56,41 +53,28 @@ By default, custom callouts are handled by applying the `note` style. To make fa > > > > > [!example] You can even use multiple layers of nesting. -> [!note] -> Aliases: "note" +> [!note] Aliases: "note" -> [!abstract] -> Aliases: "abstract", "summary", "tldr" +> [!abstract] Aliases: "abstract", "summary", "tldr" -> [!info] -> Aliases: "info" +> [!info] Aliases: "info" -> [!todo] -> Aliases: "todo" +> [!todo] Aliases: "todo" -> [!tip] -> Aliases: "tip", "hint", "important" +> [!tip] Aliases: "tip", "hint", "important" -> [!success] -> Aliases: "success", "check", "done" +> [!success] Aliases: "success", "check", "done" -> [!question] -> Aliases: "question", "help", "faq" +> [!question] Aliases: "question", "help", "faq" -> [!warning] -> Aliases: "warning", "attention", "caution" +> [!warning] Aliases: "warning", "attention", "caution" -> [!failure] -> Aliases: "failure", "missing", "fail" +> [!failure] Aliases: "failure", "missing", "fail" -> [!danger] -> Aliases: "danger", "error" +> [!danger] Aliases: "danger", "error" -> [!bug] -> Aliases: "bug" +> [!bug] Aliases: "bug" -> [!example] -> Aliases: "example" +> [!example] Aliases: "example" -> [!quote] -> Aliases: "quote", "cite" +> [!quote] Aliases: "quote", "cite" diff --git a/docs/features/darkmode.md b/docs/features/darkmode.md index 49abc6b84..dff75b44d 100644 --- a/docs/features/darkmode.md +++ b/docs/features/darkmode.md @@ -1,23 +1,23 @@ --- title: "Darkmode" tags: - - component + - 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` +- 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` You can also listen to the `themechange` event to perform any custom logic when the theme changes. ```js document.addEventListener("themechange", (e) => { - console.log("Theme changed to " + e.detail.theme) // either "light" or "dark" - // your logic here + console.log("Theme changed to " + e.detail.theme) // either "light" or "dark" + // your logic here }) ``` diff --git a/docs/features/explorer.md b/docs/features/explorer.md index aefdc74ce..22a163111 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -1,7 +1,7 @@ --- title: "Explorer" tags: - - component + - component --- Quartz features an explorer that allows you to navigate all files and folders on your site. It supports nested folders and is highly customizable. @@ -10,8 +10,7 @@ By default, it shows all folders and files on your page. To display the explorer Display names for folders get determined by the `title` frontmatter field in `folder/index.md` (more detail in [[authoring content | Authoring Content]]). If this file does not exist or does not contain frontmatter, the local folder name will be used instead. -> [!info] -> The explorer uses local storage by default to save the state of your explorer. This is done to ensure a smooth experience when navigating to different pages. +> [!info] The explorer uses local storage by default to save the state of your explorer. This is done to ensure a smooth experience when navigating to different pages. > > To clear/delete the explorer state from local storage, delete the `fileTree` entry (guide on how to delete a key from local storage in chromium based browsers can be found [here](https://docs.devolutions.net/kb/general-knowledge-base/clear-browser-local-storage/clear-chrome-local-storage/)). You can disable this by passing `useSavedState: false` as an argument. @@ -42,19 +41,18 @@ When passing in your own options, you can omit any or all of these fields if you Want to customize it even more? -- Removing explorer: remove `Component.Explorer()` from `quartz.layout.ts` - - (optional): After removing the explorer component, you can move the [[table of contents | Table of Contents]] component back to the `left` part of the layout -- Changing `sort`, `filter` and `map` behavior: explained in [[#Advanced customization]] -- Component: - - Wrapper (Outer component, generates file tree, etc): `quartz/components/Explorer.tsx` - - Explorer node (recursive, either a folder or a file): `quartz/components/ExplorerNode.tsx` -- Style: `quartz/components/styles/explorer.scss` -- Script: `quartz/components/scripts/explorer.inline.ts` +- Removing explorer: remove `Component.Explorer()` from `quartz.layout.ts` + - (optional): After removing the explorer component, you can move the [[table of contents | Table of Contents]] component back to the `left` part of the layout +- Changing `sort`, `filter` and `map` behavior: explained in [[#Advanced customization]] +- Component: + - Wrapper (Outer component, generates file tree, etc): `quartz/components/Explorer.tsx` + - Explorer node (recursive, either a folder or a file): `quartz/components/ExplorerNode.tsx` +- Style: `quartz/components/styles/explorer.scss` +- Script: `quartz/components/scripts/explorer.inline.ts` ## Advanced customization -This component allows you to fully customize all of its behavior. You can pass a custom `sort`, `filter` and `map` function. -All functions you can pass work with the `FileNode` class, which has the following properties: +This component allows you to fully customize all of its behavior. You can pass a custom `sort`, `filter` and `map` function. All functions you can pass work with the `FileNode` class, which has the following properties: ```ts title="quartz/components/ExplorerNode.tsx" {2-5} export class FileNode { @@ -73,21 +71,21 @@ Every function you can pass is optional. By default, only a `sort` function will ```ts title="Default sort function" // Sort order: folders first, then files. Sort folders and files alphabetically Component.Explorer({ - sortFn: (a, b) => { - if ((!a.file && !b.file) || (a.file && b.file)) { - // sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A - // numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10" - return a.displayName.localeCompare(b.displayName, undefined, { - numeric: true, - sensitivity: "base", - }) - } - if (a.file && !b.file) { - return 1 - } else { - return -1 - } - }, + sortFn: (a, b) => { + if ((!a.file && !b.file) || (a.file && b.file)) { + // sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A + // numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10" + return a.displayName.localeCompare(b.displayName, undefined, { + numeric: true, + sensitivity: "base", + }) + } + if (a.file && !b.file) { + return 1 + } else { + return -1 + } + }, }) ``` @@ -105,14 +103,13 @@ filterFn: (node: FileNode) => boolean mapFn: (node: FileNode) => void ``` -> [!tip] -> You can check if a `FileNode` is a folder or a file like this: +> [!tip] You can check if a `FileNode` is a folder or a file like this: > > ```ts > if (node.file) { -> // node is a file +> // node is a file > } else { -> // node is a folder +> // node is a folder > } > ``` @@ -126,16 +123,16 @@ Using this example, the explorer will alphabetically sort everything, but put al ```ts title="quartz.layout.ts" Component.Explorer({ - sortFn: (a, b) => { - if ((!a.file && !b.file) || (a.file && b.file)) { - return a.displayName.localeCompare(b.displayName) - } - if (a.file && !b.file) { - return -1 - } else { - return 1 - } - }, + sortFn: (a, b) => { + if ((!a.file && !b.file) || (a.file && b.file)) { + return a.displayName.localeCompare(b.displayName) + } + if (a.file && !b.file) { + return -1 + } else { + return 1 + } + }, }) ``` @@ -145,9 +142,9 @@ Using this example, the display names of all `FileNodes` (folders + files) will ```ts title="quartz.layout.ts" Component.Explorer({ - mapFn: (node) => { - node.displayName = node.displayName.toUpperCase() - }, + mapFn: (node) => { + node.displayName = node.displayName.toUpperCase() + }, }) ``` @@ -157,11 +154,11 @@ Using this example, you can remove elements from your explorer by providing an a ```ts title="quartz.layout.ts" Component.Explorer({ - filterFn: (node) => { - // set containing names of everything you want to filter out - const omit = new Set(["authoring content", "tags", "hosting"]) - return !omit.has(node.name.toLowerCase()) - }, + filterFn: (node) => { + // set containing names of everything you want to filter out + const omit = new Set(["authoring content", "tags", "hosting"]) + return !omit.has(node.name.toLowerCase()) + }, }) ``` @@ -173,12 +170,10 @@ You can access the frontmatter of a file by `node.file?.frontmatter?`. This allo ```ts title="quartz.layout.ts" Component.Explorer({ - filterFn: (node) => { - // exclude files with the tag "explorerexclude" - return ( - node.file?.frontmatter?.tags?.includes("explorerexclude") !== true - ) - }, + filterFn: (node) => { + // exclude files with the tag "explorerexclude" + return node.file?.frontmatter?.tags?.includes("explorerexclude") !== true + }, }) ``` @@ -188,37 +183,35 @@ To override the default filter function that removes the `tags` folder from the ```ts title="quartz.layout.ts" Component.Explorer({ - filterFn: undefined, // apply no filter function, every file and folder will visible + filterFn: undefined, // apply no filter function, every file and folder will visible }) ``` ## Advanced examples -> [!tip] -> When writing more complicated functions, the `layout` file can start to look very cramped. -> You can fix this by defining your functions in another file. +> [!tip] When writing more complicated functions, the `layout` file can start to look very cramped. You can fix this by defining your functions in another file. > > ```ts title="functions.ts" -> import { Options } from "./quartz/components/ExplorerNode" +> import {Options} from "./quartz/components/ExplorerNode" > export const mapFn: Options["mapFn"] = (node) => { -> // implement your function here +> // implement your function here > } > export const filterFn: Options["filterFn"] = (node) => { -> // implement your function here +> // implement your function here > } > export const sortFn: Options["sortFn"] = (a, b) => { -> // implement your function here +> // implement your function here > } > ``` > > You can then import them like this: > > ```ts title="quartz.layout.ts" -> import { mapFn, filterFn, sortFn } from "./functions.ts" +> import {mapFn, filterFn, sortFn} from "./functions.ts" > Component.Explorer({ -> mapFn: mapFn, -> filterFn: filterFn, -> sortFn: sortFn, +> mapFn: mapFn, +> filterFn: filterFn, +> sortFn: sortFn, > }) > ``` @@ -228,17 +221,17 @@ To add emoji prefixes (📁 for folders, 📄 for files), you could use a map fu ```ts title="quartz.layout.ts" Component.Explorer({ - mapFn: (node) => { - // dont change name of root node - if (node.depth > 0) { - // set emoji for file/folder - if (node.file) { - node.displayName = "📄 " + node.displayName - } else { - node.displayName = "📁 " + node.displayName - } - } - }, + mapFn: (node) => { + // dont change name of root node + if (node.depth > 0) { + // set emoji for file/folder + if (node.file) { + node.displayName = "📄 " + node.displayName + } else { + node.displayName = "📁 " + node.displayName + } + } + }, }) ``` @@ -248,10 +241,10 @@ In this example, we're going to customize the explorer by using functions from e ```ts title="quartz.layout.ts" Component.Explorer({ - filterFn: sampleFilterFn, - mapFn: sampleMapFn, - sortFn: sampleSortFn, - order: ["filter", "sort", "map"], + filterFn: sampleFilterFn, + mapFn: sampleMapFn, + sortFn: sampleSortFn, + order: ["filter", "sort", "map"], }) ``` @@ -267,32 +260,32 @@ It's also worth mentioning, that the smaller the number set in `nameOrderMap`, t ```ts title="quartz.layout.ts" Component.Explorer({ - sortFn: (a, b) => { - const nameOrderMap: Record = { - "poetry-folder": 100, - "essay-folder": 200, - "research-paper-file": 201, - "dinosaur-fossils-file": 300, - "other-folder": 400, - } + sortFn: (a, b) => { + const nameOrderMap: Record = { + "poetry-folder": 100, + "essay-folder": 200, + "research-paper-file": 201, + "dinosaur-fossils-file": 300, + "other-folder": 400, + } - let orderA = 0 - let orderB = 0 + let orderA = 0 + let orderB = 0 - if (a.file && a.file.slug) { - orderA = nameOrderMap[a.file.slug] || 0 - } else if (a.name) { - orderA = nameOrderMap[a.name] || 0 - } + if (a.file && a.file.slug) { + orderA = nameOrderMap[a.file.slug] || 0 + } else if (a.name) { + orderA = nameOrderMap[a.name] || 0 + } - if (b.file && b.file.slug) { - orderB = nameOrderMap[b.file.slug] || 0 - } else if (b.name) { - orderB = nameOrderMap[b.name] || 0 - } + if (b.file && b.file.slug) { + orderB = nameOrderMap[b.file.slug] || 0 + } else if (b.name) { + orderB = nameOrderMap[b.name] || 0 + } - return orderA - orderB - }, + return orderA - orderB + }, }) ``` diff --git a/docs/features/folder and tag listings.md b/docs/features/folder and tag listings.md index a04a94b77..d330f1479 100644 --- a/docs/features/folder and tag listings.md +++ b/docs/features/folder and tag listings.md @@ -1,7 +1,7 @@ --- title: Folder and Tag Listings tags: - - feature/emitter + - feature/emitter --- Quartz emits listing pages for any folders and tags you have. diff --git a/docs/features/full-text search.md b/docs/features/full-text search.md index 1b79af3f3..9bf8352bc 100644 --- a/docs/features/full-text search.md +++ b/docs/features/full-text search.md @@ -1,7 +1,7 @@ --- title: Full-text Search tags: - - component + - 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. @@ -12,8 +12,7 @@ To search content by tags, you can either press `⌘`/`ctrl` + `shift` + `K` or 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). You are also able to navigate search results using `ArrowUp` and `ArrowDown`. -> [!info] -> Search requires the `ContentIndex` emitter plugin to be present in the [[configuration]]. +> [!info] Search requires the `ContentIndex` emitter plugin to be present in the [[configuration]]. ### Indexing Behaviour @@ -23,8 +22,8 @@ It properly tokenizes Chinese, Korean, and Japenese characters and constructs se ## 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`, `numSearchResults` or `numTagResults` to suit your needs +- 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`, `numSearchResults` or `numTagResults` to suit your needs diff --git a/docs/features/graph view.md b/docs/features/graph view.md index 7298d1f41..579973dfb 100644 --- a/docs/features/graph view.md +++ b/docs/features/graph view.md @@ -1,20 +1,19 @@ --- title: "Graph View" tags: - - component + - 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. +- 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]]. +> [!info] Graph View requires the `ContentIndex` emitter plugin to be present in the [[configuration]]. ## Customization @@ -24,32 +23,32 @@ 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? - removeTags: [], // what tags to remove from the graph - showTags: true, // whether to show tags in the graph - }, - globalGraph: { - drag: true, - zoom: true, - depth: -1, - scale: 0.9, - repelForce: 0.5, - centerForce: 0.3, - linkDistance: 30, - fontSize: 0.6, - opacityScale: 1, - removeTags: [], // what tags to remove from the graph - showTags: true, // whether to show tags in the 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? + removeTags: [], // what tags to remove from the graph + showTags: true, // whether to show tags in the graph + }, + globalGraph: { + drag: true, + zoom: true, + depth: -1, + scale: 0.9, + repelForce: 0.5, + centerForce: 0.3, + linkDistance: 30, + fontSize: 0.6, + opacityScale: 1, + removeTags: [], // what tags to remove from the graph + showTags: true, // whether to show tags in the graph + }, }) ``` @@ -57,7 +56,7 @@ When passing in your own options, you can omit any or all of these fields if you Want to customize it even more? -- Removing 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` +- 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` diff --git a/docs/features/i18n.md b/docs/features/i18n.md index e2ca41fad..ae77ed6a8 100644 --- a/docs/features/i18n.md +++ b/docs/features/i18n.md @@ -6,11 +6,10 @@ Internationalization allows users to translate text in the Quartz interface into The locale field generally follows a certain format: `{language}-{REGION}` -- `{language}` is usually a [2-letter lowercase language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes). -- `{REGION}` is usually a [2-letter uppercase region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) +- `{language}` is usually a [2-letter lowercase language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes). +- `{REGION}` is usually a [2-letter uppercase region code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) -> [!tip] Interested in contributing? -> We [gladly welcome translation PRs](https://github.com/jackyzha0/quartz/tree/v4/quartz/i18n/locales)! To contribute a translation, do the following things: +> [!tip] Interested in contributing? We [gladly welcome translation PRs](https://github.com/jackyzha0/quartz/tree/v4/quartz/i18n/locales)! To contribute a translation, do the following things: > > 1. In the `quartz/i18n/locales` folder, copy the `en-US.ts` file. > 2. Rename it to `{language}-{REGION}.ts` so it matches a locale of the format shown above. diff --git a/docs/features/popover previews.md b/docs/features/popover previews.md index d6a0bf17f..066604758 100644 --- a/docs/features/popover previews.md +++ b/docs/features/popover previews.md @@ -12,6 +12,6 @@ Similar to Obsidian, [[quartz layout.png|images referenced using wikilinks]] can ## 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` +- 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` diff --git a/docs/features/private pages.md b/docs/features/private pages.md index d13e4495a..73a8ec9ae 100644 --- a/docs/features/private pages.md +++ b/docs/features/private pages.md @@ -1,7 +1,7 @@ --- title: Private Pages tags: - - feature/filter + - feature/filter --- There may be some notes you want to avoid publishing as a website. Quartz supports this through two mechanisms which can be used in conjunction: @@ -12,8 +12,7 @@ There may be some notes you want to avoid publishing as a website. Quartz suppor If you'd like to only publish a select number of notes, you can instead use [[ExplicitPublish]] which will filter out all notes except for any that have `publish: true` in the frontmatter. -> [!warning] -> Regardless of the filter plugin used, **all non-markdown files will be emitted and available publically in the final build.** This includes files such as images, voice recordings, PDFs, etc. One way to prevent this and still be able to embed local images is to create a folder specifically for public media and add the following two patterns to the ignorePatterns array. +> [!warning] Regardless of the filter plugin used, **all non-markdown files will be emitted and available publically in the final build.** This includes files such as images, voice recordings, PDFs, etc. One way to prevent this and still be able to embed local images is to create a folder specifically for public media and add the following two patterns to the ignorePatterns array. > > `"!(PublicMedia)**/!(*.md)", "!(*.md)"` @@ -21,15 +20,13 @@ If you'd like to only publish a select number of notes, you can instead use [[Ex 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 [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) pattern works here. -> [!note] -> Bash's glob syntax is slightly different from fast-glob's and using bash's syntax may lead to unexpected results. +> [!note] Bash's glob syntax is slightly different from fast-glob's and using bash's syntax may lead to unexpected results. 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 +- `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 `ignorePatterns` 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. +> [!warning] Marking something as private via either a plugin or through the `ignorePatterns` 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. diff --git a/docs/features/recent notes.md b/docs/features/recent notes.md index 95f8c2b79..9236b7ce2 100644 --- a/docs/features/recent notes.md +++ b/docs/features/recent notes.md @@ -7,10 +7,10 @@ Quartz can generate a list of recent notes based on some filtering and sorting c ## 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` +- 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` diff --git a/docs/features/syntax highlighting.md b/docs/features/syntax highlighting.md index 53952e5bf..caaaf7b77 100644 --- a/docs/features/syntax highlighting.md +++ b/docs/features/syntax highlighting.md @@ -1,7 +1,7 @@ --- title: Syntax Highlighting tags: - - feature/transformer + - feature/transformer --- Syntax highlighting in Quartz is completely done at build-time. This means that Quartz only ships pre-calculated CSS to highlight the right words so there is no heavy client-side bundle that does the syntax highlighting. @@ -10,8 +10,7 @@ And, unlike some client-side highlighters, it has a full TextMate parser grammar 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. +> [!warning] Syntax highlighting does have an impact on build speed if you have a lot of code snippets in your notes. ## Formatting @@ -31,11 +30,11 @@ export function trimPathSuffix(fp: string): string { ```ts export function trimPathSuffix(fp: string): string { - fp = clientSideSlug(fp) - let [cleanPath, anchor] = fp.split("#", 2) - anchor = anchor === undefined ? "" : "#" + anchor + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor - return cleanPath + anchor + return cleanPath + anchor } ``` @@ -51,11 +50,11 @@ Add a file title to your code block, with text inside double quotes (`""`): ```ts title="quartz/path.ts" export function trimPathSuffix(fp: string): string { - fp = clientSideSlug(fp) - let [cleanPath, anchor] = fp.split("#", 2) - anchor = anchor === undefined ? "" : "#" + anchor + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor - return cleanPath + anchor + return cleanPath + anchor } ``` @@ -71,11 +70,11 @@ Place a numeric range inside `{}`. ```ts {2-3,6} export function trimPathSuffix(fp: string): string { - fp = clientSideSlug(fp) - let [cleanPath, anchor] = fp.split("#", 2) - anchor = anchor === undefined ? "" : "#" + anchor + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor - return cleanPath + anchor + return cleanPath + anchor } ``` @@ -107,11 +106,11 @@ Syntax highlighting has line numbers configured automatically. If you want to st ```ts showLineNumbers{20} export function trimPathSuffix(fp: string): string { - fp = clientSideSlug(fp) - let [cleanPath, anchor] = fp.split("#", 2) - anchor = anchor === undefined ? "" : "#" + anchor + fp = clientSideSlug(fp) + let [cleanPath, anchor] = fp.split("#", 2) + anchor = anchor === undefined ? "" : "#" + anchor - return cleanPath + anchor + return cleanPath + anchor } ``` diff --git a/docs/features/table of contents.md b/docs/features/table of contents.md index 55df97724..4ecccc934 100644 --- a/docs/features/table of contents.md +++ b/docs/features/table of contents.md @@ -1,8 +1,8 @@ --- title: "Table of Contents" tags: - - component - - feature/transformer + - component + - feature/transformer --- Quartz can automatically generate a table of contents (TOC) from a list of headings on each page. It will also show you your current scrolling position on the page by highlighting headings you've scrolled through with a different color. diff --git a/docs/features/upcoming features.md b/docs/features/upcoming features.md index 5a05080dd..76adda00e 100644 --- a/docs/features/upcoming features.md +++ b/docs/features/upcoming features.md @@ -4,20 +4,20 @@ draft: true ## high priority backlog -- static dead link detection -- 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 -- docker support +- static dead link detection +- 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 +- docker support ## misc backlog -- breadcrumbs 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 +- breadcrumbs 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 diff --git a/docs/features/wikilinks.md b/docs/features/wikilinks.md index 1060504a8..ad4f2d779 100644 --- a/docs/features/wikilinks.md +++ b/docs/features/wikilinks.md @@ -10,15 +10,15 @@ This is enabled as a part of [[Obsidian compatibility]] and can be configured an ## Syntax -- `[[Path to file]]`: produces a link to `Path to file.md` (or `Path-to-file.md`) with the text `Path to file` -- `[[Path to file | Here's the title override]]`: produces a link to `Path to file.md` with the text `Here's the title override` -- `[[Path to file#Anchor]]`: produces a link to the anchor `Anchor` in the file `Path to file.md` -- `[[Path to file#^block-ref]]`: produces a link to the specific block `block-ref` in the file `Path to file.md` +- `[[Path to file]]`: produces a link to `Path to file.md` (or `Path-to-file.md`) with the text `Path to file` +- `[[Path to file | Here's the title override]]`: produces a link to `Path to file.md` with the text `Here's the title override` +- `[[Path to file#Anchor]]`: produces a link to the anchor `Anchor` in the file `Path to file.md` +- `[[Path to file#^block-ref]]`: produces a link to the specific block `block-ref` in the file `Path to file.md` ### Embeds -- `![[Path to image]]`: embeds an image into the page -- `![[Path to image|100x145]]`: embeds an image into the page with dimensions 100px by 145px -- `![[Path to file]]`: transclude an entire page -- `![[Path to file#Anchor]]`: transclude everything under the header `Anchor` -- `![[Path to file#^b15695]]`: transclude block with ID `^b15695` +- `![[Path to image]]`: embeds an image into the page +- `![[Path to image|100x145]]`: embeds an image into the page with dimensions 100px by 145px +- `![[Path to file]]`: transclude an entire page +- `![[Path to file#Anchor]]`: transclude everything under the header `Anchor` +- `![[Path to file#^b15695]]`: transclude block with ID `^b15695` diff --git a/docs/hosting.md b/docs/hosting.md index 0ea88f329..606ffcf2e 100644 --- a/docs/hosting.md +++ b/docs/hosting.md @@ -6,11 +6,9 @@ Quartz effectively turns your Markdown files and other resources into a bundle o However, if you'd like to publish your site to the world, you need a way to host it online. This guide will detail how to deploy with common hosting providers but any service that allows you to deploy static HTML should work as well. -> [!warning] -> The rest of this guide assumes that you've already created your own GitHub repository for Quartz. If you haven't already, [[setting up your GitHub repository|make sure you do so]]. +> [!warning] The rest of this guide assumes that you've already created your own GitHub repository for Quartz. If you haven't already, [[setting up your GitHub repository|make sure you do so]]. -> [!hint] -> Some Quartz features (like [[RSS Feed]] and sitemap generation) require `baseUrl` to be configured properly in your [[configuration]] to work properly. Make sure you set this before deploying! +> [!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 @@ -29,8 +27,7 @@ Press "Save and deploy" and Cloudflare should have a deployed version of your si To add a custom domain, check out [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/custom-domains/). -> [!warning] -> Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz build`). +> [!warning] Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz build`). ## GitHub Pages @@ -40,48 +37,48 @@ In your local Quartz, create a new file `quartz/.github/workflows/deploy.yml`. name: Deploy Quartz site to GitHub Pages on: - push: - branches: - - v4 + push: + branches: + - v4 permissions: - contents: read - pages: write - id-token: write + contents: read + pages: write + id-token: write concurrency: - group: "pages" - cancel-in-progress: false + 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 + 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 + 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: @@ -89,13 +86,11 @@ 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.io/`. -> [!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. +> [!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. -> [!info] -> Quartz generates files in the format of `file.html` instead of `file/index.html` which means the trailing slashes for _non-folder paths_ are dropped. As GitHub pages does not do this redirect, this may cause existing links to your site that use trailing slashes to break. If not breaking existing links is important to you (e.g. you are migrating from Quartz 3), consider using [[#Cloudflare Pages]]. +> [!info] Quartz generates files in the format of `file.html` instead of `file/index.html` which means the trailing slashes for _non-folder paths_ are dropped. As GitHub pages does not do this redirect, this may cause existing links to your site that use trailing slashes to break. If not breaking existing links is important to you (e.g. you are migrating from Quartz 3), consider using [[#Cloudflare Pages]]. ### Custom Domain @@ -105,19 +100,18 @@ Here's how to add a custom domain to your GitHub pages deployment. 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.io`. + - 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.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. +> [!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. @@ -129,7 +123,7 @@ Before deploying to Vercel, a `vercel.json` file is required at the root of the ```json title="vercel.json" { - "cleanUrls": true + "cleanUrls": true } ``` @@ -150,8 +144,7 @@ Before deploying to Vercel, a `vercel.json` file is required at the root of the ### Custom Domain -> [!note] -> If there is something already hosted on the domain, these steps will not work without replacing the previous content. As a workaround, you could use Next.js rewrites or use the next section to create a subdomain. +> [!note] If there is something already hosted on the domain, these steps will not work without replacing the previous content. As a workaround, you could use Next.js rewrites or use the next section to create a subdomain. 1. Update the `baseUrl` in `quartz.config.js` if necessary. 2. Go to the [Domains - Dashboard](https://vercel.com/dashboard/domains) page in Vercel. @@ -186,43 +179,43 @@ In your local Quartz, create a new file `.gitlab-ci.yaml`. ```yaml title=".gitlab-ci.yaml" stages: - - build - - deploy + - build + - deploy variables: - NODE_VERSION: "18.14" + NODE_VERSION: "18.14" build: - stage: build - rules: - - if: '$CI_COMMIT_REF_NAME == "v4"' - before_script: - - apt-get update -q && apt-get install -y nodejs npm - - npm install -g n - - n $NODE_VERSION - - hash -r - - npm ci - script: - - npx quartz build - artifacts: - paths: - - public - cache: - paths: - - ~/.npm/ - key: "${CI_COMMIT_REF_SLUG}-node-${CI_COMMIT_REF_NAME}" - tags: - - docker + stage: build + rules: + - if: '$CI_COMMIT_REF_NAME == "v4"' + before_script: + - apt-get update -q && apt-get install -y nodejs npm + - npm install -g n + - n $NODE_VERSION + - hash -r + - npm ci + script: + - npx quartz build + artifacts: + paths: + - public + cache: + paths: + - ~/.npm/ + key: "${CI_COMMIT_REF_SLUG}-node-${CI_COMMIT_REF_NAME}" + tags: + - docker pages: - stage: deploy - rules: - - if: '$CI_COMMIT_REF_NAME == "v4"' - script: - - echo "Deploying to GitLab Pages..." - artifacts: - paths: - - public + stage: deploy + rules: + - if: '$CI_COMMIT_REF_NAME == "v4"' + script: + - echo "Deploying to GitLab Pages..." + artifacts: + paths: + - public ``` When `.gitlab-ci.yaml` is committed, GitLab will build and deploy the website as a GitLab Page. You can find the url under `Deploy > Pages` in the sidebar. diff --git a/docs/index.md b/docs/index.md index 754c2d40e..c6e5243e6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,16 +26,15 @@ This will guide you through initializing your Quartz with content. Once you've d 5. Sync your changes with [[setting up your GitHub repository|GitHub]] 6. [[hosting|Host]] Quartz online -If you prefer instructions in a video format you can try following Nicole van der Hoeven's -[video guide on how to set up Quartz!](https://www.youtube.com/watch?v=6s6DT1yN4dw&t=227s) +If you prefer instructions in a video format you can try following Nicole van der Hoeven's [video guide on how to set up Quartz!](https://www.youtube.com/watch?v=6s6DT1yN4dw&t=227s) ## 🔧 Features -- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]] and [many more](./features) right out of the box -- Hot-reload for both configuration and content -- Simple JSX layouts and [[creating components|page components]] -- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes -- Fully-customizable parsing, filtering, and page generation through [[making plugins|plugins]] +- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[features/Latex|Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], [[i18n|internationalization]] and [many more](./features) right out of the box +- Hot-reload for both configuration and content +- Simple JSX layouts and [[creating components|page components]] +- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes +- 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. diff --git a/docs/layout.md b/docs/layout.md index 274bb2371..8822c73ac 100644 --- a/docs/layout.md +++ b/docs/layout.md @@ -8,13 +8,13 @@ Each page is composed of multiple different sections which contain `QuartzCompon ```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 + 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 } ``` @@ -22,8 +22,7 @@ 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. +> [!note] There are two additional layout fields that are _not_ shown in the above diagram. > > 1. `head` is a single component that renders the `` [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`. @@ -38,5 +37,4 @@ Most meaningful style changes like colour scheme and font can be done simply thr 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. +> [!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. diff --git a/docs/migrating from Quartz 3.md b/docs/migrating from Quartz 3.md index 9661685e2..93955a874 100644 --- a/docs/migrating from Quartz 3.md +++ b/docs/migrating from Quartz 3.md @@ -20,8 +20,7 @@ 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. +> [!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 @@ -32,10 +31,10 @@ When running `npx quartz create`, you will be prompted as to how to initialize y ## 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//index.md` where `` is the name of the folder. - - Tag descriptions should go under `content/tags/.md` where `` 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. +- 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//index.md` where `` is the name of the folder. + - Tag descriptions should go under `content/tags/.md` where `` 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. diff --git a/docs/philosophy.md b/docs/philosophy.md index af5510aac..09fd4f529 100644 --- a/docs/philosophy.md +++ b/docs/philosophy.md @@ -24,8 +24,7 @@ The goal of digital gardening should be to tap into your network’s collective 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 +> “[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.** diff --git a/docs/plugins/AliasRedirects.md b/docs/plugins/AliasRedirects.md index ef953dd69..f74cc84e9 100644 --- a/docs/plugins/AliasRedirects.md +++ b/docs/plugins/AliasRedirects.md @@ -1,7 +1,7 @@ --- title: AliasRedirects tags: - - plugin/emitter + - plugin/emitter --- This plugin emits HTML redirect pages for aliases and permalinks defined in the frontmatter of content files. @@ -12,7 +12,7 @@ For example, A `foo.md` has the following frontmatter --- title: "Foo" alias: - - "bar" + - "bar" --- ``` @@ -22,16 +22,15 @@ Note that these are permanent redirect. The emitter supports the following aliases: -- `aliases` -- `alias` +- `aliases` +- `alias` -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Emitter -- Function name: `Plugin.AliasRedirects()`. -- Source: [`quartz/plugins/emitters/aliases.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/aliases.ts). +- Category: Emitter +- Function name: `Plugin.AliasRedirects()`. +- Source: [`quartz/plugins/emitters/aliases.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/aliases.ts). diff --git a/docs/plugins/Assets.md b/docs/plugins/Assets.md index 79d9d41d4..50d75bbe2 100644 --- a/docs/plugins/Assets.md +++ b/docs/plugins/Assets.md @@ -1,20 +1,19 @@ --- title: Assets tags: - - plugin/emitter + - plugin/emitter --- This plugin emits all non-Markdown static assets in your content folder (like images, videos, HTML, etc). The plugin respects the `ignorePatterns` in the global [[configuration]]. Note that all static assets will then be accessible through its path on your generated site, i.e: `host.me/path/to/static.pdf` -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Emitter -- Function name: `Plugin.Assets()`. -- Source: [`quartz/plugins/emitters/assets.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/assets.ts). +- Category: Emitter +- Function name: `Plugin.Assets()`. +- Source: [`quartz/plugins/emitters/assets.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/assets.ts). diff --git a/docs/plugins/CNAME.md b/docs/plugins/CNAME.md index 4826a425a..213d98d8c 100644 --- a/docs/plugins/CNAME.md +++ b/docs/plugins/CNAME.md @@ -1,7 +1,7 @@ --- title: CNAME tags: - - plugin/emitter + - plugin/emitter --- This plugin emits a `CNAME` record that points your subdomain to the default domain of your site. @@ -10,13 +10,12 @@ If you want to use a custom domain name like `quartz.example.com` for the site, See [[Hosting]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Emitter -- Function name: `Plugin.CNAME()`. -- Source: [`quartz/plugins/emitters/cname.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/cname.ts). +- Category: Emitter +- Function name: `Plugin.CNAME()`. +- Source: [`quartz/plugins/emitters/cname.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/cname.ts). diff --git a/docs/plugins/ComponentResources.md b/docs/plugins/ComponentResources.md index 6ddc463b1..73903dd39 100644 --- a/docs/plugins/ComponentResources.md +++ b/docs/plugins/ComponentResources.md @@ -1,18 +1,17 @@ --- title: ComponentResources tags: - - plugin/emitter + - plugin/emitter --- This plugin manages and emits the static resources required for the Quartz framework. This includes CSS stylesheets and JavaScript scripts that enhance the functionality and aesthetics of the generated site. See also the `cdnCaching` option in the `theme` section of the [[configuration]]. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Emitter -- Function name: `Plugin.ComponentResources()`. -- Source: [`quartz/plugins/emitters/componentResources.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/componentResources.ts). +- Category: Emitter +- Function name: `Plugin.ComponentResources()`. +- Source: [`quartz/plugins/emitters/componentResources.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/componentResources.ts). diff --git a/docs/plugins/ContentIndex.md b/docs/plugins/ContentIndex.md index 802e826fc..26a4525db 100644 --- a/docs/plugins/ContentIndex.md +++ b/docs/plugins/ContentIndex.md @@ -1,26 +1,25 @@ --- title: ContentIndex tags: - - plugin/emitter + - plugin/emitter --- This plugin emits both RSS and an XML sitemap for your site. The [[RSS Feed]] allows users to subscribe to content on your site and the sitemap allows search engines to better index your site. The plugin also emits a `contentIndex.json` file which is used by dynamic frontend components like search and graph. This plugin emits a comprehensive index of the site's content, generating additional resources such as a sitemap, an RSS feed, and a -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `enableSiteMap`: If `true` (default), generates a sitemap XML file (`sitemap.xml`) listing all site URLs for search engines in content discovery. -- `enableRSS`: If `true` (default), produces an RSS feed (`index.xml`) with recent content updates. -- `rssLimit`: Defines the maximum number of entries to include in the RSS feed, helping to focus on the most recent or relevant content. Defaults to `10`. -- `rssFullHtml`: If `true`, the RSS feed includes full HTML content. Otherwise it includes just summaries. -- `includeEmptyFiles`: If `true` (default), content files with no body text are included in the generated index and resources. +- `enableSiteMap`: If `true` (default), generates a sitemap XML file (`sitemap.xml`) listing all site URLs for search engines in content discovery. +- `enableRSS`: If `true` (default), produces an RSS feed (`index.xml`) with recent content updates. +- `rssLimit`: Defines the maximum number of entries to include in the RSS feed, helping to focus on the most recent or relevant content. Defaults to `10`. +- `rssFullHtml`: If `true`, the RSS feed includes full HTML content. Otherwise it includes just summaries. +- `includeEmptyFiles`: If `true` (default), content files with no body text are included in the generated index and resources. ## API -- Category: Emitter -- Function name: `Plugin.ContentIndex()`. -- Source: [`quartz/plugins/emitters/contentIndex.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/contentIndex.ts). +- Category: Emitter +- Function name: `Plugin.ContentIndex()`. +- Source: [`quartz/plugins/emitters/contentIndex.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/contentIndex.ts). diff --git a/docs/plugins/ContentPage.md b/docs/plugins/ContentPage.md index 20c06390f..e902ecc2a 100644 --- a/docs/plugins/ContentPage.md +++ b/docs/plugins/ContentPage.md @@ -1,18 +1,17 @@ --- title: ContentPage tags: - - plugin/emitter + - plugin/emitter --- This plugin is a core component of the Quartz framework. It generates the HTML pages for each piece of Markdown content. It emits the full-page [[layout]], including headers, footers, and body content, among others. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Emitter -- Function name: `Plugin.ContentPage()`. -- Source: [`quartz/plugins/emitters/contentPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/contentPage.tsx). +- Category: Emitter +- Function name: `Plugin.ContentPage()`. +- Source: [`quartz/plugins/emitters/contentPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/contentPage.tsx). diff --git a/docs/plugins/CrawlLinks.md b/docs/plugins/CrawlLinks.md index 71a2bd503..449f04213 100644 --- a/docs/plugins/CrawlLinks.md +++ b/docs/plugins/CrawlLinks.md @@ -1,30 +1,28 @@ --- title: CrawlLinks tags: - - plugin/transformer + - plugin/transformer --- This plugin parses links and processes them to point to the right places. It is also needed for embedded links (like images). See [[Obsidian compatibility]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `markdownLinkResolution`: Sets the strategy for resolving Markdown paths, can be `"absolute"` (default), `"relative"` or `"shortest"`. You should use the same setting here as in [[Obsidian compatibility|Obsidian]]. - - `absolute`: Path relative to the root of the content folder. - - `relative`: Path relative to the file you are linking from. - - `shortest`: Name of the file. If this isn't enough to identify the file, use the full absolute path. -- `prettyLinks`: If `true` (default), simplifies links by removing folder paths, making them more user friendly (e.g. `folder/deeply/nested/note` becomes `note`). -- `openLinksInNewTab`: If `true`, configures external links to open in a new tab. Defaults to `false`. -- `lazyLoad`: If `true`, adds lazy loading to resource elements (`img`, `video`, etc.) to improve page load performance. Defaults to `false`. -- `externalLinkIcon`: Adds an icon next to external links when `true` (default) to visually distinguishing them from internal links. +- `markdownLinkResolution`: Sets the strategy for resolving Markdown paths, can be `"absolute"` (default), `"relative"` or `"shortest"`. You should use the same setting here as in [[Obsidian compatibility|Obsidian]]. + - `absolute`: Path relative to the root of the content folder. + - `relative`: Path relative to the file you are linking from. + - `shortest`: Name of the file. If this isn't enough to identify the file, use the full absolute path. +- `prettyLinks`: If `true` (default), simplifies links by removing folder paths, making them more user friendly (e.g. `folder/deeply/nested/note` becomes `note`). +- `openLinksInNewTab`: If `true`, configures external links to open in a new tab. Defaults to `false`. +- `lazyLoad`: If `true`, adds lazy loading to resource elements (`img`, `video`, etc.) to improve page load performance. Defaults to `false`. +- `externalLinkIcon`: Adds an icon next to external links when `true` (default) to visually distinguishing them from internal links. -> [!warning] -> Removing this plugin is _not_ recommended and will likely break the page. +> [!warning] Removing this plugin is _not_ recommended and will likely break the page. ## API -- Category: Transformer -- Function name: `Plugin.CrawlLinks()`. -- Source: [`quartz/plugins/transformers/links.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/links.ts). +- Category: Transformer +- Function name: `Plugin.CrawlLinks()`. +- Source: [`quartz/plugins/transformers/links.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/links.ts). diff --git a/docs/plugins/CreatedModifiedDate.md b/docs/plugins/CreatedModifiedDate.md index 806a7add9..ef846ccff 100644 --- a/docs/plugins/CreatedModifiedDate.md +++ b/docs/plugins/CreatedModifiedDate.md @@ -1,25 +1,23 @@ --- title: "CreatedModifiedDate" tags: - - plugin/transformer + - plugin/transformer --- This plugin determines the created, modified, and published dates for a document using three potential data sources: frontmatter metadata, Git history, and the filesystem. See [[authoring content#Syntax]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `priority`: The data sources to consult for date information. Highest priority first. Possible values are `"frontmatter"`, `"git"`, and `"filesystem"`. Defaults to `"frontmatter", "git", "filesystem"]`. +- `priority`: The data sources to consult for date information. Highest priority first. Possible values are `"frontmatter"`, `"git"`, and `"filesystem"`. Defaults to `"frontmatter", "git", "filesystem"]`. -> [!warning] -> If you rely on `git` for dates, make sure `defaultDateType` is set to `modified` in `quartz.config.ts`. +> [!warning] If you rely on `git` for dates, make sure `defaultDateType` is set to `modified` in `quartz.config.ts`. > > Depending on how you [[hosting|host]] your Quartz, the `filesystem` dates of your local files may not match the final dates. In these cases, it may be better to use `git` or `frontmatter` to guarantee correct dates. ## API -- Category: Transformer -- Function name: `Plugin.CreatedModifiedDate()`. -- Source: [`quartz/plugins/transformers/lastmod.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/lastmod.ts). +- Category: Transformer +- Function name: `Plugin.CreatedModifiedDate()`. +- Source: [`quartz/plugins/transformers/lastmod.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/lastmod.ts). diff --git a/docs/plugins/Description.md b/docs/plugins/Description.md index 7c08a664e..5cace6f7f 100644 --- a/docs/plugins/Description.md +++ b/docs/plugins/Description.md @@ -1,23 +1,22 @@ --- title: Description tags: - - plugin/transformer + - plugin/transformer --- This plugin generates descriptions that are used as metadata for the HTML `head`, the [[RSS Feed]] and in [[folder and tag listings]] if there is no main body content, the description is used as the text between the title and the listing. If the frontmatter contains a `description` property, it is used (see [[authoring content#Syntax]]). Otherwise, the plugin will do its best to use the first few sentences of the content to reach the target description length. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `descriptionLength`: the maximum length of the generated description. Default is 150 characters. The cut off happens after the first _sentence_ that ends after the given length. -- `replaceExternalLinks`: If `true` (default), replace external links with their domain and path in the description (e.g. `https://domain.tld/some_page/another_page?query=hello&target=world` is replaced with `domain.tld/some_page/another_page`). +- `descriptionLength`: the maximum length of the generated description. Default is 150 characters. The cut off happens after the first _sentence_ that ends after the given length. +- `replaceExternalLinks`: If `true` (default), replace external links with their domain and path in the description (e.g. `https://domain.tld/some_page/another_page?query=hello&target=world` is replaced with `domain.tld/some_page/another_page`). ## API -- Category: Transformer -- Function name: `Plugin.Description()`. -- Source: [`quartz/plugins/transformers/description.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/description.ts). +- Category: Transformer +- Function name: `Plugin.Description()`. +- Source: [`quartz/plugins/transformers/description.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/description.ts). diff --git a/docs/plugins/ExplicitPublish.md b/docs/plugins/ExplicitPublish.md index 8a093475d..4d60bb58d 100644 --- a/docs/plugins/ExplicitPublish.md +++ b/docs/plugins/ExplicitPublish.md @@ -1,18 +1,17 @@ --- title: ExplicitPublish tags: - - plugin/filter + - plugin/filter --- This plugin filters content based on an explicit `publish` flag in the frontmatter, allowing only content that is explicitly marked for publication to pass through. It's the opt-in version of [[RemoveDrafts]]. See [[private pages]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Filter -- Function name: `Plugin.ExplicitPublish()`. -- Source: [`quartz/plugins/filters/explicit.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/filters/explicit.ts). +- Category: Filter +- Function name: `Plugin.ExplicitPublish()`. +- Source: [`quartz/plugins/filters/explicit.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/filters/explicit.ts). diff --git a/docs/plugins/FolderPage.md b/docs/plugins/FolderPage.md index 014cc8109..acf083093 100644 --- a/docs/plugins/FolderPage.md +++ b/docs/plugins/FolderPage.md @@ -1,15 +1,14 @@ --- title: FolderPage tags: - - plugin/emitter + - plugin/emitter --- This plugin generates index pages for folders, creating a listing page for each folder that contains multiple content files. See [[folder and tag listings]] for more information. Example: [[advanced/|Advanced]] -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. @@ -17,6 +16,6 @@ The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts` ## API -- Category: Emitter -- Function name: `Plugin.FolderPage()`. -- Source: [`quartz/plugins/emitters/folderPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/folderPage.tsx). +- Category: Emitter +- Function name: `Plugin.FolderPage()`. +- Source: [`quartz/plugins/emitters/folderPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/folderPage.tsx). diff --git a/docs/plugins/Frontmatter.md b/docs/plugins/Frontmatter.md index 97552b856..f9a1323f9 100644 --- a/docs/plugins/Frontmatter.md +++ b/docs/plugins/Frontmatter.md @@ -1,24 +1,22 @@ --- title: "Frontmatter" tags: - - plugin/transformer + - plugin/transformer --- This plugin parses the frontmatter of the page using the [gray-matter](https://github.com/jonschlinkert/gray-matter) library. See [[authoring content#Syntax]], [[Obsidian compatibility]] and [[OxHugo compatibility]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `delimiters`: the delimiters to use for the frontmatter. Can have one value (e.g. `"---"`) or separate values for opening and closing delimiters (e.g. `["---", "~~~"]`). Defaults to `"---"`. -- `language`: the language to use for parsing the frontmatter. Can be `yaml` (default) or `toml`. +- `delimiters`: the delimiters to use for the frontmatter. Can have one value (e.g. `"---"`) or separate values for opening and closing delimiters (e.g. `["---", "~~~"]`). Defaults to `"---"`. +- `language`: the language to use for parsing the frontmatter. Can be `yaml` (default) or `toml`. -> [!warning] -> This plugin must not be removed, otherwise Quartz will break. +> [!warning] This plugin must not be removed, otherwise Quartz will break. ## API -- Category: Transformer -- Function name: `Plugin.Frontmatter()`. -- Source: [`quartz/plugins/transformers/frontmatter.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/frontmatter.ts). +- Category: Transformer +- Function name: `Plugin.Frontmatter()`. +- Source: [`quartz/plugins/transformers/frontmatter.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/frontmatter.ts). diff --git a/docs/plugins/GitHubFlavoredMarkdown.md b/docs/plugins/GitHubFlavoredMarkdown.md index 28f9335f4..e70c3ea18 100644 --- a/docs/plugins/GitHubFlavoredMarkdown.md +++ b/docs/plugins/GitHubFlavoredMarkdown.md @@ -1,23 +1,22 @@ --- title: GitHubFlavoredMarkdown tags: - - plugin/transformer + - plugin/transformer --- This plugin enhances Markdown processing to support GitHub Flavored Markdown (GFM) which adds features like autolink literals, footnotes, strikethrough, tables and tasklists. In addition, this plugin adds optional features for typographic refinement (such as converting straight quotes to curly quotes, dashes to en-dashes/em-dashes, and ellipses) and automatic heading links as a symbol that appears next to the heading on hover. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `enableSmartyPants`: When true, enables typographic enhancements. Default is true. -- `linkHeadings`: When true, automatically adds links to headings. Default is true. +- `enableSmartyPants`: When true, enables typographic enhancements. Default is true. +- `linkHeadings`: When true, automatically adds links to headings. Default is true. ## API -- Category: Transformer -- Function name: `Plugin.GitHubFlavoredMarkdown()`. -- Source: [`quartz/plugins/transformers/gfm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/gfm.ts). +- Category: Transformer +- Function name: `Plugin.GitHubFlavoredMarkdown()`. +- Source: [`quartz/plugins/transformers/gfm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/gfm.ts). diff --git a/docs/plugins/HardLineBreaks.md b/docs/plugins/HardLineBreaks.md index ba344bb69..c4a8180ae 100644 --- a/docs/plugins/HardLineBreaks.md +++ b/docs/plugins/HardLineBreaks.md @@ -1,18 +1,17 @@ --- title: HardLineBreaks tags: - - plugin/transformer + - plugin/transformer --- This plugin automatically converts single line breaks in Markdown text into hard line breaks in the HTML output. This plugin is not enabled by default as this doesn't follow the semantics of actual Markdown but you may enable it if you'd like parity with [[Obsidian compatibility|Obsidian]]. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Transformer -- Function name: `Plugin.HardLineBreaks()`. -- Source: [`quartz/plugins/transformers/linebreaks.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/linebreaks.ts). +- Category: Transformer +- Function name: `Plugin.HardLineBreaks()`. +- Source: [`quartz/plugins/transformers/linebreaks.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/linebreaks.ts). diff --git a/docs/plugins/Latex.md b/docs/plugins/Latex.md index 459122af0..5dd3facf2 100644 --- a/docs/plugins/Latex.md +++ b/docs/plugins/Latex.md @@ -1,20 +1,19 @@ --- title: "Latex" tags: - - plugin/transformer + - plugin/transformer --- This plugin adds LaTeX support to Quartz. See [[features/Latex|Latex]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `renderEngine`: the engine to use to render LaTeX equations. Can be `"katex"` for [KaTeX](https://katex.org/) or `"mathjax"` for [MathJax](https://www.mathjax.org/) [SVG rendering](https://docs.mathjax.org/en/latest/output/svg.html). Defaults to KaTeX. +- `renderEngine`: the engine to use to render LaTeX equations. Can be `"katex"` for [KaTeX](https://katex.org/) or `"mathjax"` for [MathJax](https://www.mathjax.org/) [SVG rendering](https://docs.mathjax.org/en/latest/output/svg.html). Defaults to KaTeX. ## API -- Category: Transformer -- Function name: `Plugin.Latex()`. -- Source: [`quartz/plugins/transformers/latex.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/latex.ts). +- Category: Transformer +- Function name: `Plugin.Latex()`. +- Source: [`quartz/plugins/transformers/latex.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/latex.ts). diff --git a/docs/plugins/NotFoundPage.md b/docs/plugins/NotFoundPage.md index 45d00170d..01805e186 100644 --- a/docs/plugins/NotFoundPage.md +++ b/docs/plugins/NotFoundPage.md @@ -1,18 +1,17 @@ --- title: NotFoundPage tags: - - plugin/emitter + - plugin/emitter --- This plugin emits a 404 (Not Found) page for broken or non-existent URLs. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Emitter -- Function name: `Plugin.NotFoundPage()`. -- Source: [`quartz/plugins/emitters/404.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/404.tsx). +- Category: Emitter +- Function name: `Plugin.NotFoundPage()`. +- Source: [`quartz/plugins/emitters/404.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/404.tsx). diff --git a/docs/plugins/ObsidianFlavoredMarkdown.md b/docs/plugins/ObsidianFlavoredMarkdown.md index c825fb8e0..7b5b39fa0 100644 --- a/docs/plugins/ObsidianFlavoredMarkdown.md +++ b/docs/plugins/ObsidianFlavoredMarkdown.md @@ -1,34 +1,32 @@ --- title: ObsidianFlavoredMarkdown tags: - - plugin/transformer + - plugin/transformer --- This plugin provides support for [[Obsidian compatibility]]. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `comments`: If `true` (default), enables parsing of `%%` style Obsidian comment blocks. -- `highlight`: If `true` (default), enables parsing of `==` style highlights within content. -- `wikilinks`:If `true` (default), turns [[wikilinks]] into regular links. -- `callouts`: If `true` (default), adds support for [[callouts|callout]] blocks for emphasizing content. -- `mermaid`: If `true` (default), enables [[Mermaid diagrams|Mermaid diagram]] rendering within Markdown files. -- `parseTags`: If `true` (default), parses and links tags within the content. -- `parseArrows`: If `true` (default), transforms arrow symbols into their HTML character equivalents. -- `parseBlockReferences`: If `true` (default), handles block references, linking to specific content blocks. -- `enableInHtmlEmbed`: If `true`, allows embedding of content directly within HTML. Defaults to `false`. -- `enableYouTubeEmbed`: If `true` (default), enables the embedding of YouTube videos and playlists using external image Markdown syntax. -- `enableVideoEmbed`: If `true` (default), enables the embedding of video files. -- `enableCheckbox`: If `true`, adds support for interactive checkboxes in content. Defaults to `false`. +- `comments`: If `true` (default), enables parsing of `%%` style Obsidian comment blocks. +- `highlight`: If `true` (default), enables parsing of `==` style highlights within content. +- `wikilinks`:If `true` (default), turns [[wikilinks]] into regular links. +- `callouts`: If `true` (default), adds support for [[callouts|callout]] blocks for emphasizing content. +- `mermaid`: If `true` (default), enables [[Mermaid diagrams|Mermaid diagram]] rendering within Markdown files. +- `parseTags`: If `true` (default), parses and links tags within the content. +- `parseArrows`: If `true` (default), transforms arrow symbols into their HTML character equivalents. +- `parseBlockReferences`: If `true` (default), handles block references, linking to specific content blocks. +- `enableInHtmlEmbed`: If `true`, allows embedding of content directly within HTML. Defaults to `false`. +- `enableYouTubeEmbed`: If `true` (default), enables the embedding of YouTube videos and playlists using external image Markdown syntax. +- `enableVideoEmbed`: If `true` (default), enables the embedding of video files. +- `enableCheckbox`: If `true`, adds support for interactive checkboxes in content. Defaults to `false`. -> [!warning] -> Don't remove this plugin if you're using [[Obsidian compatibility|Obsidian]] to author the content! +> [!warning] Don't remove this plugin if you're using [[Obsidian compatibility|Obsidian]] to author the content! ## API -- Category: Transformer -- Function name: `Plugin.ObsidianFlavoredMarkdown()`. -- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts). +- Category: Transformer +- Function name: `Plugin.ObsidianFlavoredMarkdown()`. +- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts). diff --git a/docs/plugins/OxHugoFlavoredMarkdown.md b/docs/plugins/OxHugoFlavoredMarkdown.md index e6e486608..70e48dbe5 100644 --- a/docs/plugins/OxHugoFlavoredMarkdown.md +++ b/docs/plugins/OxHugoFlavoredMarkdown.md @@ -1,29 +1,27 @@ --- title: OxHugoFlavoredMarkdown tags: - - plugin/transformer + - plugin/transformer --- This plugin provides support for [ox-hugo](https://github.com/kaushalmodi/ox-hugo) compatibility. See [[OxHugo compatibility]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `wikilinks`: If `true` (default), converts Hugo `{{ relref }}` shortcodes to Quartz [[wikilinks]]. -- `removePredefinedAnchor`: If `true` (default), strips predefined anchors from headings. -- `removeHugoShortcode`: If `true` (default), removes Hugo shortcode syntax (`{{}}`) from the content. -- `replaceFigureWithMdImg`: If `true` (default), replaces `
` with `![]()`. -- `replaceOrgLatex`: If `true` (default), converts Org-mode [[features/Latex|Latex]] fragments to Quartz-compatible LaTeX wrapped in `$` (for inline) and `$$` (for block equations). +- `wikilinks`: If `true` (default), converts Hugo `{{ relref }}` shortcodes to Quartz [[wikilinks]]. +- `removePredefinedAnchor`: If `true` (default), strips predefined anchors from headings. +- `removeHugoShortcode`: If `true` (default), removes Hugo shortcode syntax (`{{}}`) from the content. +- `replaceFigureWithMdImg`: If `true` (default), replaces `
` with `![]()`. +- `replaceOrgLatex`: If `true` (default), converts Org-mode [[features/Latex|Latex]] fragments to Quartz-compatible LaTeX wrapped in `$` (for inline) and `$$` (for block equations). -> [!warning] -> While you can use this together with [[ObsidianFlavoredMarkdown]], it's not recommended because it might mutate the file in unexpected ways. Use with caution. +> [!warning] While you can use this together with [[ObsidianFlavoredMarkdown]], it's not recommended because it might mutate the file in unexpected ways. Use with caution. > > If you use `toml` frontmatter, make sure to configure the [[Frontmatter]] plugin accordingly. See [[OxHugo compatibility]] for an example. ## API -- Category: Transformer -- Function name: `Plugin.OxHugoFlavoredMarkdown()`. -- Source: [`quartz/plugins/transformers/oxhugofm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/oxhugofm.ts). +- Category: Transformer +- Function name: `Plugin.OxHugoFlavoredMarkdown()`. +- Source: [`quartz/plugins/transformers/oxhugofm.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/oxhugofm.ts). diff --git a/docs/plugins/RemoveDrafts.md b/docs/plugins/RemoveDrafts.md index 4368848cd..d81c0255f 100644 --- a/docs/plugins/RemoveDrafts.md +++ b/docs/plugins/RemoveDrafts.md @@ -1,18 +1,17 @@ --- title: RemoveDrafts tags: - - plugin/filter + - plugin/filter --- This plugin filters out content from your vault, so that only finalized content is made available. This prevents [[private pages]] from being published. By default, it filters out all pages with `draft: true` in the frontmatter and leaves all other pages intact. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Filter -- Function name: `Plugin.RemoveDrafts()`. -- Source: [`quartz/plugins/filters/draft.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/filters/draft.ts). +- Category: Filter +- Function name: `Plugin.RemoveDrafts()`. +- Source: [`quartz/plugins/filters/draft.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/filters/draft.ts). diff --git a/docs/plugins/Static.md b/docs/plugins/Static.md index f60192e3b..64fcdc9c6 100644 --- a/docs/plugins/Static.md +++ b/docs/plugins/Static.md @@ -1,21 +1,19 @@ --- title: Static tags: - - plugin/emitter + - plugin/emitter --- This plugin emits all static resources needed by Quartz. This is used, for example, for fonts and images that need a stable position, such as banners and icons. The plugin respects the `ignorePatterns` in the global [[configuration]]. -> [!important] -> This is different from [[Assets]]. The resources from the [[Static]] plugin are located under `quartz/static`, whereas [[Assets]] renders all static resources under `content` and is used for images, videos, audio, etc. that are directly referenced by your markdown content. +> [!important] This is different from [[Assets]]. The resources from the [[Static]] plugin are located under `quartz/static`, whereas [[Assets]] renders all static resources under `content` and is used for images, videos, audio, etc. that are directly referenced by your markdown content. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. ## API -- Category: Emitter -- Function name: `Plugin.Static()`. -- Source: [`quartz/plugins/emitters/static.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/static.ts). +- Category: Emitter +- Function name: `Plugin.Static()`. +- Source: [`quartz/plugins/emitters/static.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/static.ts). diff --git a/docs/plugins/SyntaxHighlighting.md b/docs/plugins/SyntaxHighlighting.md index ea248ac04..f52610901 100644 --- a/docs/plugins/SyntaxHighlighting.md +++ b/docs/plugins/SyntaxHighlighting.md @@ -1,23 +1,22 @@ --- title: "SyntaxHighlighting" tags: - - plugin/transformer + - plugin/transformer --- This plugin is used to add syntax highlighting to code blocks in Quartz. See [[syntax highlighting]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `theme`: a separate id of one of the [themes bundled with Shikiji](https://shikiji.netlify.app/themes). One for light mode and one for dark mode. Defaults to `theme: { light: "github-light", dark: "github-dark" }`. -- `keepBackground`: If set to `true`, the background of the Shikiji theme will be used. With `false` (default) the Quartz theme color for background will be used instead. +- `theme`: a separate id of one of the [themes bundled with Shikiji](https://shikiji.netlify.app/themes). One for light mode and one for dark mode. Defaults to `theme: { light: "github-light", dark: "github-dark" }`. +- `keepBackground`: If set to `true`, the background of the Shikiji theme will be used. With `false` (default) the Quartz theme color for background will be used instead. In addition, you can further override the colours in the `quartz/styles/syntax.scss` file. ## API -- Category: Transformer -- Function name: `Plugin.SyntaxHighlighting()`. -- Source: [`quartz/plugins/transformers/syntax.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/syntax.ts). +- Category: Transformer +- Function name: `Plugin.SyntaxHighlighting()`. +- Source: [`quartz/plugins/transformers/syntax.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/syntax.ts). diff --git a/docs/plugins/TableOfContents.md b/docs/plugins/TableOfContents.md index bf793fd8d..920269e3d 100644 --- a/docs/plugins/TableOfContents.md +++ b/docs/plugins/TableOfContents.md @@ -1,26 +1,24 @@ --- title: TableOfContents tags: - - plugin/transformer + - plugin/transformer --- This plugin generates a table of contents (TOC) for Markdown documents. See [[table of contents]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin accepts the following configuration options: -- `maxDepth`: Limits the depth of headings included in the TOC, ranging from `1` (top level headings only) to `6` (all heading levels). Default is `3`. -- `minEntries`: The minimum number of heading entries required for the TOC to be displayed. Default is `1`. -- `showByDefault`: If `true` (default), the TOC should be displayed by default. Can be overridden by frontmatter settings. -- `collapseByDefault`: If `true`, the TOC will start in a collapsed state. Default is `false`. +- `maxDepth`: Limits the depth of headings included in the TOC, ranging from `1` (top level headings only) to `6` (all heading levels). Default is `3`. +- `minEntries`: The minimum number of heading entries required for the TOC to be displayed. Default is `1`. +- `showByDefault`: If `true` (default), the TOC should be displayed by default. Can be overridden by frontmatter settings. +- `collapseByDefault`: If `true`, the TOC will start in a collapsed state. Default is `false`. -> [!warning] -> This plugin needs the `Component.TableOfContents` component in `quartz.layout.ts` to determine where to display the TOC. Without it, nothing will be displayed. They should always be added or removed together. +> [!warning] This plugin needs the `Component.TableOfContents` component in `quartz.layout.ts` to determine where to display the TOC. Without it, nothing will be displayed. They should always be added or removed together. ## API -- Category: Transformer -- Function name: `Plugin.TableOfContents()`. -- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts). +- Category: Transformer +- Function name: `Plugin.TableOfContents()`. +- Source: [`quartz/plugins/transformers/toc.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/toc.ts). diff --git a/docs/plugins/TagPage.md b/docs/plugins/TagPage.md index 3fce971bf..2ad6d8e52 100644 --- a/docs/plugins/TagPage.md +++ b/docs/plugins/TagPage.md @@ -1,13 +1,12 @@ --- title: TagPage tags: - - plugin/emitter + - plugin/emitter --- This plugin emits dedicated pages for each tag used in the content. See [[folder and tag listings]] for more information. -> [!note] -> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. +> [!note] For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page. This plugin has no configuration options. @@ -15,6 +14,6 @@ The pages are displayed using the `defaultListPageLayout` in `quartz.layouts.ts` ## API -- Category: Emitter -- Function name: `Plugin.TagPage()`. -- Source: [`quartz/plugins/emitters/tagPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/tagPage.tsx). +- Category: Emitter +- Function name: `Plugin.TagPage()`. +- Source: [`quartz/plugins/emitters/tagPage.tsx`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/emitters/tagPage.tsx). diff --git a/docs/setting up your GitHub repository.md b/docs/setting up your GitHub repository.md index 16e995dc7..86670dc59 100644 --- a/docs/setting up your GitHub repository.md +++ b/docs/setting up your GitHub repository.md @@ -31,18 +31,16 @@ Then, you can sync the content to upload it to your repository. This is a helper npx quartz sync --no-pull ``` -> [!warning]- `fatal: --[no-]autostash option is only valid with --rebase` -> You may have an outdated version of `git`. Updating `git` should fix this issue. +> [!warning]- `fatal: --[no-]autostash option is only valid with --rebase` You may have an outdated version of `git`. Updating `git` should fix this issue. In future updates, you can simply run `npx quartz sync` every time you want to push updates to your repository. -> [!hint] Flags and options -> For full help options, you can run `npx quartz sync --help`. +> [!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 +> - `-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 diff --git a/docs/showcase.md b/docs/showcase.md index 5afdd1e10..0509e93ab 100644 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -4,27 +4,27 @@ 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/) -- [Socratica Toolbox](https://toolbox.socratica.info/) -- [oldwinter の数字花园](https://garden.oldwinter.top/) -- [Aaron Pham's Garden](https://aarnphm.xyz/) -- [The Quantum Garden](https://quantumgardener.blog/) -- [Abhijeet's Math Wiki](https://abhmul.github.io/quartz/Math-Wiki/) -- [Matt Dunn's Second Brain](https://mattdunn.info/) -- [Pelayo Arbues' Notes](https://pelayoarbues.github.io/) -- [Vince Imbat's Talahardin](https://vinceimbat.com/) -- [🧠🌳 Chad's Mind Garden](https://www.chadly.net/) -- [Pedro MC Fernandes's Topo da Mente](https://www.pmcf.xyz/topo-da-mente/) -- [Mau Camargo's Notkesto](https://notes.camargomau.com/) -- [Caicai's Novels](https://imoko.cc/blog/caicai/) -- [🌊 Collapsed Wave](https://collapsedwave.com/) -- [Sideny's 3D Artist's Handbook](https://sidney-eliot.github.io/3d-artists-handbook/) -- [Mike's AI Garden 🤖🪴](https://mwalton.me/) -- [Brandon Boswell's Garden](https://brandonkboswell.com) -- [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/) -- [Data Dictionary 🧠](https://glossary.airbyte.com/) -- [sspaeti.com's Second Brain](https://brain.sspaeti.com/) -- [🪴Aster's notebook](https://notes.asterhu.com) +- [Quartz Documentation (this site!)](https://quartz.jzhao.xyz/) +- [Jacky Zhao's Garden](https://jzhao.xyz/) +- [Socratica Toolbox](https://toolbox.socratica.info/) +- [oldwinter の数字花园](https://garden.oldwinter.top/) +- [Aaron Pham's Garden](https://aarnphm.xyz/) +- [The Quantum Garden](https://quantumgardener.blog/) +- [Abhijeet's Math Wiki](https://abhmul.github.io/quartz/Math-Wiki/) +- [Matt Dunn's Second Brain](https://mattdunn.info/) +- [Pelayo Arbues' Notes](https://pelayoarbues.github.io/) +- [Vince Imbat's Talahardin](https://vinceimbat.com/) +- [🧠🌳 Chad's Mind Garden](https://www.chadly.net/) +- [Pedro MC Fernandes's Topo da Mente](https://www.pmcf.xyz/topo-da-mente/) +- [Mau Camargo's Notkesto](https://notes.camargomau.com/) +- [Caicai's Novels](https://imoko.cc/blog/caicai/) +- [🌊 Collapsed Wave](https://collapsedwave.com/) +- [Sideny's 3D Artist's Handbook](https://sidney-eliot.github.io/3d-artists-handbook/) +- [Mike's AI Garden 🤖🪴](https://mwalton.me/) +- [Brandon Boswell's Garden](https://brandonkboswell.com) +- [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/) +- [Data Dictionary 🧠](https://glossary.airbyte.com/) +- [sspaeti.com's Second Brain](https://brain.sspaeti.com/) +- [🪴Aster's notebook](https://notes.asterhu.com) If you want to see your own on here, submit a [Pull Request adding yourself to this file](https://github.com/jackyzha0/quartz/blob/v4/docs/showcase.md)! diff --git a/docs/upgrading.md b/docs/upgrading.md index a3a82754a..b27f08c55 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -2,8 +2,7 @@ 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. +> [!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 @@ -13,7 +12,6 @@ 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. +> [!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). diff --git a/globals.d.ts b/globals.d.ts index 7a93b0165..c80246418 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -1,15 +1,15 @@ export declare global { - interface Document { - addEventListener( - type: K, - listener: (this: Document, ev: CustomEventMap[K]) => void, - ): void - dispatchEvent( - ev: CustomEventMap[K] | UIEvent, - ): void - } - interface Window { - spaNavigate(url: URL, isBack: boolean = false) - addCleanup(fn: (...args: any[]) => void) - } + interface Document { + addEventListener( + type: K, + listener: (this: Document, ev: CustomEventMap[K]) => void, + ): void + dispatchEvent( + ev: CustomEventMap[K] | UIEvent, + ): void + } + interface Window { + spaNavigate(url: URL, isBack: boolean = false) + addCleanup(fn: (...args: any[]) => void) + } } diff --git a/index.d.ts b/index.d.ts index 111ad03e7..f7542c5a4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,12 +1,12 @@ declare module "*.scss" { - const content: string - export = content + const content: string + export = content } // dom custom event interface CustomEventMap { - nav: CustomEvent<{ url: FullSlug }> - themechange: CustomEvent<{ theme: "light" | "dark" }> + nav: CustomEvent<{url: FullSlug}> + themechange: CustomEvent<{theme: "light" | "dark"}> } declare const fetchData: Promise diff --git a/package.json b/package.json index c5fc16a3f..01e04c93c 100644 --- a/package.json +++ b/package.json @@ -1,114 +1,114 @@ { - "name": "@jackyzha0/quartz", - "description": "🌱 publish your digital garden and notes as a website", - "private": true, - "version": "4.2.3", - "type": "module", - "author": "jackyzha0 ", - "license": "MIT", - "homepage": "https://quartz.jzhao.xyz", - "packageManager": "pnpm@8.15.1", - "repository": { - "type": "git", - "url": "https://github.com/jackyzha0/quartz.git" - }, - "scripts": { - "docs": "npx quartz build --serve -d docs", - "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", - "update": "npx quartz update", - "sync": "npx quartz sync", - "start": "npx quartz build --serve", - "build": "npx quartz build" - }, - "engines": { - "npm": ">=9.3.1", - "node": ">=18.14" - }, - "keywords": [ - "site generator", - "ssg", - "digital-garden", - "markdown", - "blog", - "quartz" - ], - "bin": { - "quartz": "./quartz/bootstrap-cli.mjs" - }, - "dependencies": { - "@clack/prompts": "^0.7.0", - "@floating-ui/dom": "^1.6.3", - "@napi-rs/simple-git": "0.1.16", - "async-mutex": "^0.4.1", - "chalk": "^5.3.0", - "chokidar": "^3.6.0", - "cli-spinner": "^0.2.10", - "d3": "^7.8.5", - "esbuild-sass-plugin": "^2.16.1", - "flexsearch": "0.7.43", - "github-slugger": "^2.0.0", - "globby": "^14.0.1", - "gray-matter": "^4.0.3", - "hast-util-to-html": "^9.0.0", - "hast-util-to-jsx-runtime": "^2.3.0", - "hast-util-to-string": "^3.0.0", - "is-absolute-url": "^4.0.1", - "js-yaml": "^4.1.0", - "lightningcss": "^1.24.0", - "mdast-util-find-and-replace": "^3.0.1", - "mdast-util-to-hast": "^13.1.0", - "mdast-util-to-string": "^4.0.0", - "micromorph": "^0.4.5", - "preact": "^10.19.6", - "preact-render-to-string": "^6.4.0", - "pretty-bytes": "^6.1.1", - "pretty-time": "^1.1.0", - "reading-time": "^1.5.0", - "rehype-autolink-headings": "^7.1.0", - "rehype-citation": "^2.0.0", - "rehype-katex": "^7.0.0", - "rehype-mathjax": "^6.0.0", - "rehype-pretty-code": "^0.13.0", - "rehype-raw": "^7.0.0", - "rehype-slug": "^6.0.0", - "remark": "^15.0.1", - "remark-breaks": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "remark-math": "^6.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.0", - "remark-smartypants": "^2.1.0", - "rfdc": "^1.3.1", - "rimraf": "^5.0.5", - "serve-handler": "^6.1.5", - "shiki": "^1.1.7", - "source-map-support": "^0.5.21", - "to-vfile": "^8.0.0", - "toml": "^3.0.0", - "unified": "^11.0.4", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.1", - "workerpool": "^9.1.0", - "ws": "^8.16.0", - "yargs": "^17.7.2" - }, - "devDependencies": { - "@types/cli-spinner": "^0.2.3", - "@types/d3": "^7.4.3", - "@types/hast": "^3.0.4", - "@types/js-yaml": "^4.0.9", - "@types/node": "^20.11.25", - "@types/pretty-time": "^1.1.5", - "@types/source-map-support": "^0.5.10", - "@types/ws": "^8.5.10", - "@types/yargs": "^17.0.32", - "esbuild": "^0.19.12", - "prettier": "^3.2.5", - "tsx": "^4.7.1", - "typescript": "^5.4.2" - } + "name": "@jackyzha0/quartz", + "description": "🌱 publish your digital garden and notes as a website", + "private": true, + "version": "4.2.3", + "type": "module", + "author": "jackyzha0 ", + "license": "MIT", + "homepage": "https://quartz.jzhao.xyz", + "packageManager": "pnpm@8.15.1", + "repository": { + "type": "git", + "url": "https://github.com/jackyzha0/quartz.git" + }, + "scripts": { + "docs": "npx quartz build --serve -d docs", + "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", + "update": "npx quartz update", + "sync": "npx quartz sync", + "start": "npx quartz build --serve", + "build": "npx quartz build" + }, + "engines": { + "npm": ">=9.3.1", + "node": ">=18.14" + }, + "keywords": [ + "site generator", + "ssg", + "digital-garden", + "markdown", + "blog", + "quartz" + ], + "bin": { + "quartz": "./quartz/bootstrap-cli.mjs" + }, + "dependencies": { + "@clack/prompts": "^0.7.0", + "@floating-ui/dom": "^1.6.3", + "@napi-rs/simple-git": "0.1.16", + "async-mutex": "^0.4.1", + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "cli-spinner": "^0.2.10", + "d3": "^7.8.5", + "esbuild-sass-plugin": "^2.16.1", + "flexsearch": "0.7.43", + "github-slugger": "^2.0.0", + "globby": "^14.0.1", + "gray-matter": "^4.0.3", + "hast-util-to-html": "^9.0.0", + "hast-util-to-jsx-runtime": "^2.3.0", + "hast-util-to-string": "^3.0.0", + "is-absolute-url": "^4.0.1", + "js-yaml": "^4.1.0", + "lightningcss": "^1.24.0", + "mdast-util-find-and-replace": "^3.0.1", + "mdast-util-to-hast": "^13.1.0", + "mdast-util-to-string": "^4.0.0", + "micromorph": "^0.4.5", + "preact": "^10.19.6", + "preact-render-to-string": "^6.4.0", + "pretty-bytes": "^6.1.1", + "pretty-time": "^1.1.0", + "reading-time": "^1.5.0", + "rehype-autolink-headings": "^7.1.0", + "rehype-citation": "^2.0.0", + "rehype-katex": "^7.0.0", + "rehype-mathjax": "^6.0.0", + "rehype-pretty-code": "^0.13.0", + "rehype-raw": "^7.0.0", + "rehype-slug": "^6.0.0", + "remark": "^15.0.1", + "remark-breaks": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.0", + "remark-smartypants": "^2.1.0", + "rfdc": "^1.3.1", + "rimraf": "^5.0.5", + "serve-handler": "^6.1.5", + "shiki": "^1.1.7", + "source-map-support": "^0.5.21", + "to-vfile": "^8.0.0", + "toml": "^3.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.1", + "workerpool": "^9.1.0", + "ws": "^8.16.0", + "yargs": "^17.7.2" + }, + "devDependencies": { + "@types/cli-spinner": "^0.2.3", + "@types/d3": "^7.4.3", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/node": "^20.11.25", + "@types/pretty-time": "^1.1.5", + "@types/source-map-support": "^0.5.10", + "@types/ws": "^8.5.10", + "@types/yargs": "^17.0.32", + "esbuild": "^0.19.12", + "prettier": "^3.2.5", + "tsx": "^4.7.1", + "typescript": "^5.4.2" + } } diff --git a/quartz.config.ts b/quartz.config.ts index 6f2f06fc9..e081512ca 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -1,4 +1,4 @@ -import { QuartzConfig } from "./quartz/cfg" +import {QuartzConfig} from "./quartz/cfg" import * as Plugin from "./quartz/plugins" /** @@ -7,78 +7,78 @@ import * as Plugin from "./quartz/plugins" * See https://quartz.jzhao.xyz/configuration for more information. */ const config: QuartzConfig = { - configuration: { - pageTitle: "Forgetful Notes", - enableSPA: true, - enablePopovers: true, - analytics: { - provider: "plausible", - }, - locale: "en-US", - baseUrl: "forgetfulnotes.com", - ignorePatterns: ["private", "templates"], - defaultDateType: "modified", - theme: { - fontOrigin: "googleFonts", - cdnCaching: true, - typography: { - header: "Bitter", // Schibsted Grotesk - body: "Poppins", // Source Sans Pro, Poppins - code: "Fira Mono", // IBM Plex Mono - }, - colors: { - lightMode: { - light: "#faf8f8", // bg - lightgray: "#e5e5e5", // borders - gray: "#8f8f8f", // graph links, heavy borders - darkgray: "#2e2e2e", // body text - dark: "#1c1c1c", // header text, icons - secondary: "#091217", // links, nodes - tertiary: "#AA336A", // hover states, visited - highlight: "rgba(143, 159, 169, 0.2)", // internal link bg - }, - darkMode: { - light: "#1e1e2e", // background - lightgray: "#6c7086", // borders - gray: "#a6adc8", // graph links, heavy borders - darkgray: "#cdd6f4", // body text - dark: "#cdd6f4", // header text, icons - secondary: "#9be895", // links, nodes - tertiary: "#c072c4", // hover states, visited - highlight: "rgba(143, 159, 169, 0.2)", // internal link background - }, - }, - }, + configuration: { + pageTitle: "Forgetful Notes", + enableSPA: true, + enablePopovers: true, + analytics: { + provider: "plausible", }, - plugins: { - transformers: [ - Plugin.FrontMatter(), - Plugin.CreatedModifiedDate({ - priority: ["frontmatter", "filesystem"], - }), - Plugin.SyntaxHighlighting(), - Plugin.ObsidianFlavoredMarkdown(), - Plugin.GitHubFlavoredMarkdown(), - Plugin.TableOfContents(), - Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }), - Plugin.Description(), - ], - filters: [Plugin.RemoveDrafts()], - emitters: [ - Plugin.AliasRedirects(), - Plugin.ComponentResources(), - Plugin.ContentPage(), - Plugin.FolderPage(), - Plugin.TagPage(), - Plugin.ContentIndex({ - enableSiteMap: true, - enableRSS: true, - }), - Plugin.Assets(), - Plugin.Static(), - Plugin.NotFoundPage(), - ], + locale: "en-US", + baseUrl: "forgetfulnotes.com", + ignorePatterns: ["private", "templates"], + defaultDateType: "modified", + theme: { + fontOrigin: "googleFonts", + cdnCaching: true, + typography: { + header: "Bitter", // Schibsted Grotesk + body: "Poppins", // Source Sans Pro, Poppins + code: "Fira Mono", // IBM Plex Mono + }, + colors: { + lightMode: { + light: "#faf8f8", // bg + lightgray: "#e5e5e5", // borders + gray: "#8f8f8f", // graph links, heavy borders + darkgray: "#2e2e2e", // body text + dark: "#1c1c1c", // header text, icons + secondary: "#091217", // links, nodes + tertiary: "#AA336A", // hover states, visited + highlight: "rgba(143, 159, 169, 0.2)", // internal link bg + }, + darkMode: { + light: "#1e1e2e", // background + lightgray: "#6c7086", // borders + gray: "#a6adc8", // graph links, heavy borders + darkgray: "#cdd6f4", // body text + dark: "#cdd6f4", // header text, icons + secondary: "#9be895", // links, nodes + tertiary: "#c072c4", // hover states, visited + highlight: "rgba(143, 159, 169, 0.2)", // internal link background + }, + }, }, + }, + plugins: { + transformers: [ + Plugin.FrontMatter(), + Plugin.CreatedModifiedDate({ + priority: ["frontmatter", "filesystem"], + }), + Plugin.SyntaxHighlighting(), + Plugin.ObsidianFlavoredMarkdown(), + Plugin.GitHubFlavoredMarkdown(), + Plugin.TableOfContents(), + Plugin.CrawlLinks({markdownLinkResolution: "shortest"}), + Plugin.Description(), + ], + filters: [Plugin.RemoveDrafts()], + emitters: [ + Plugin.AliasRedirects(), + Plugin.ComponentResources(), + Plugin.ContentPage(), + Plugin.FolderPage(), + Plugin.TagPage(), + Plugin.ContentIndex({ + enableSiteMap: true, + enableRSS: true, + }), + Plugin.Assets(), + Plugin.Static(), + Plugin.NotFoundPage(), + ], + }, } export default config diff --git a/quartz.layout.ts b/quartz.layout.ts index c905a18d3..5a2e3fce8 100644 --- a/quartz.layout.ts +++ b/quartz.layout.ts @@ -1,58 +1,58 @@ -import { PageLayout, SharedLayout } from "./quartz/cfg" +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: { - About: "/About", - Blog: "https://miguelpimentel.do/", - Meta: "/Meta", - GitHub: "https://github.com/semanticdata/", - Source: "https://github.com/semanticdata/forgetful-notes/", - // Tags: "/tags", - }, - }), + head: Component.Head(), + header: [], + footer: Component.Footer({ + links: { + About: "/About", + Blog: "https://miguelpimentel.do/", + Meta: "/Meta", + GitHub: "https://github.com/semanticdata/", + Source: "https://github.com/semanticdata/forgetful-notes/", + // Tags: "/tags", + }, + }), } // components for pages that display a single page (e.g. a single note) export const defaultContentPageLayout: PageLayout = { - beforeBody: [ - // Component.Breadcrumbs(), - Component.ArticleTitle(), - // Component.ContentMeta(), - // Component.TagList(), - Component.MobileOnly(Component.Spacer()), - ], - left: [ - Component.PageTitle(), - Component.MobileOnly(Component.Spacer()), - Component.Search(), - Component.Darkmode(), - Component.DesktopOnly(Component.Explorer()), - ], - right: [ - Component.Graph(), - Component.DesktopOnly(Component.TableOfContents()), - Component.MobileOnly(Component.Backlinks()), - ], + beforeBody: [ + // Component.Breadcrumbs(), + Component.ArticleTitle(), + // Component.ContentMeta(), + // Component.TagList(), + Component.MobileOnly(Component.Spacer()), + ], + left: [ + Component.PageTitle(), + Component.MobileOnly(Component.Spacer()), + Component.Search(), + Component.Darkmode(), + Component.DesktopOnly(Component.Explorer()), + ], + right: [ + Component.Graph(), + Component.DesktopOnly(Component.TableOfContents()), + Component.MobileOnly(Component.Backlinks()), + ], } // components for pages that display lists of pages (e.g. tags or folders) export const defaultListPageLayout: PageLayout = { - beforeBody: [ - Component.Breadcrumbs(), - Component.ArticleTitle(), - Component.ContentMeta(), - ], - left: [ - Component.PageTitle(), - Component.MobileOnly(Component.Spacer()), - Component.Search(), - Component.Darkmode(), - Component.DesktopOnly(Component.Explorer()), - ], - right: [], + beforeBody: [ + Component.Breadcrumbs(), + Component.ArticleTitle(), + Component.ContentMeta(), + ], + left: [ + Component.PageTitle(), + Component.MobileOnly(Component.Spacer()), + Component.Search(), + Component.Darkmode(), + Component.DesktopOnly(Component.Explorer()), + ], + right: [], } diff --git a/quartz/bootstrap-cli.mjs b/quartz/bootstrap-cli.mjs index 90b1c2bfe..cde91deff 100755 --- a/quartz/bootstrap-cli.mjs +++ b/quartz/bootstrap-cli.mjs @@ -1,56 +1,56 @@ #!/usr/bin/env node import yargs from "yargs" -import { hideBin } from "yargs/helpers" +import {hideBin} from "yargs/helpers" import { - handleBuild, - handleCreate, - handleUpdate, - handleRestore, - handleSync, + handleBuild, + handleCreate, + handleUpdate, + handleRestore, + handleSync, } from "./cli/handlers.js" -import { CommonArgv, BuildArgv, CreateArgv, SyncArgv } from "./cli/args.js" -import { version } from "./cli/constants.js" +import {CommonArgv, BuildArgv, CreateArgv, SyncArgv} from "./cli/args.js" +import {version} from "./cli/constants.js" yargs(hideBin(process.argv)) - .scriptName("quartz") - .version(version) - .usage("$0 [args]") - .command("create", "Initialize Quartz", CreateArgv, async (argv) => { - await handleCreate(argv) - }) - .command( - "update", - "Get the latest Quartz updates", - CommonArgv, - async (argv) => { - await handleUpdate(argv) - }, - ) - .command( - "restore", - "Try to restore your content folder from the cache", - CommonArgv, - async (argv) => { - await handleRestore(argv) - }, - ) - .command( - "sync", - "Sync your Quartz to and from GitHub.", - SyncArgv, - async (argv) => { - await handleSync(argv) - }, - ) - .command( - "build", - "Build Quartz into a bundle of static HTML files", - BuildArgv, - async (argv) => { - await handleBuild(argv) - }, - ) - .showHelpOnFail(false) - .help() - .strict() - .demandCommand().argv + .scriptName("quartz") + .version(version) + .usage("$0 [args]") + .command("create", "Initialize Quartz", CreateArgv, async (argv) => { + await handleCreate(argv) + }) + .command( + "update", + "Get the latest Quartz updates", + CommonArgv, + async (argv) => { + await handleUpdate(argv) + }, + ) + .command( + "restore", + "Try to restore your content folder from the cache", + CommonArgv, + async (argv) => { + await handleRestore(argv) + }, + ) + .command( + "sync", + "Sync your Quartz to and from GitHub.", + SyncArgv, + async (argv) => { + await handleSync(argv) + }, + ) + .command( + "build", + "Build Quartz into a bundle of static HTML files", + BuildArgv, + async (argv) => { + await handleBuild(argv) + }, + ) + .showHelpOnFail(false) + .help() + .strict() + .demandCommand().argv diff --git a/quartz/bootstrap-worker.mjs b/quartz/bootstrap-worker.mjs index 2f10cb40e..3257b7a7f 100644 --- a/quartz/bootstrap-worker.mjs +++ b/quartz/bootstrap-worker.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node import workerpool from "workerpool" const cacheFile = "./.quartz-cache/transpiled-worker.mjs" -const { parseFiles } = await import(cacheFile) +const {parseFiles} = await import(cacheFile) workerpool.worker({ - parseFiles, + parseFiles, }) diff --git a/quartz/build.ts b/quartz/build.ts index eea1846ff..d43cf35b7 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -1,474 +1,466 @@ import sourceMapSupport from "source-map-support" sourceMapSupport.install(options) import path from "path" -import { PerfTimer } from "./util/perf" -import { rimraf } from "rimraf" -import { GlobbyFilterFunction, isGitIgnored } from "globby" +import {PerfTimer} from "./util/perf" +import {rimraf} from "rimraf" +import {GlobbyFilterFunction, isGitIgnored} from "globby" import chalk from "chalk" -import { parseMarkdown } from "./processors/parse" -import { filterContent } from "./processors/filter" -import { emitContent } from "./processors/emit" +import {parseMarkdown} from "./processors/parse" +import {filterContent} from "./processors/filter" +import {emitContent} from "./processors/emit" import cfg from "../quartz.config" -import { FilePath, FullSlug, joinSegments, slugifyFilePath } from "./util/path" +import {FilePath, FullSlug, joinSegments, slugifyFilePath} from "./util/path" import chokidar from "chokidar" -import { ProcessedContent } from "./plugins/vfile" -import { Argv, BuildCtx } from "./util/ctx" -import { glob, toPosixPath } from "./util/glob" -import { trace } from "./util/trace" -import { options } from "./util/sourcemap" -import { Mutex } from "async-mutex" +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" import DepGraph from "./depgraph" -import { getStaticResourcesFromPlugins } from "./plugins" +import {getStaticResourcesFromPlugins} from "./plugins" type Dependencies = Record | null> type BuildData = { - ctx: BuildCtx - ignored: GlobbyFilterFunction - mut: Mutex - initialSlugs: FullSlug[] - // TODO merge contentMap and trackedAssets - contentMap: Map - trackedAssets: Set - toRebuild: Set - toRemove: Set - lastBuildMs: number - dependencies: Dependencies + ctx: BuildCtx + ignored: GlobbyFilterFunction + mut: Mutex + initialSlugs: FullSlug[] + // TODO merge contentMap and trackedAssets + contentMap: Map + trackedAssets: Set + toRebuild: Set + toRemove: Set + lastBuildMs: number + dependencies: Dependencies } type FileEvent = "add" | "change" | "delete" async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { - const ctx: BuildCtx = { - argv, - cfg, - allSlugs: [], + 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(", ")}`) + } + + const release = await mut.acquire() + perf.addEvent("clean") + await rimraf(path.join(output, "*"), {glob: true}) + 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")).sort() + 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) + + const dependencies: Record | null> = {} + + // Only build dependency graphs if we're doing a fast rebuild + if (argv.fastRebuild) { + const staticResources = getStaticResourcesFromPlugins(ctx) + for (const emitter of cfg.plugins.emitters) { + dependencies[emitter.name] = + (await emitter.getDependencyGraph?.( + ctx, + filteredContent, + staticResources, + )) ?? null } + } - const perf = new PerfTimer() - const output = argv.output + await emitContent(ctx, filteredContent) + console.log( + chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`), + ) + release() - 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(", ")}`) - } - - const release = await mut.acquire() - perf.addEvent("clean") - await rimraf(path.join(output, "*"), { glob: true }) - 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")).sort() - 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) - - const dependencies: Record | null> = {} - - // Only build dependency graphs if we're doing a fast rebuild - if (argv.fastRebuild) { - const staticResources = getStaticResourcesFromPlugins(ctx) - for (const emitter of cfg.plugins.emitters) { - dependencies[emitter.name] = - (await emitter.getDependencyGraph?.( - ctx, - filteredContent, - staticResources, - )) ?? null - } - } - - await emitContent(ctx, filteredContent) - console.log( - chalk.green( - `Done processing ${fps.length} files in ${perf.timeSince()}`, - ), - ) - release() - - if (argv.serve) { - return startServing(ctx, mut, parsedFiles, clientRefresh, dependencies) - } + if (argv.serve) { + return startServing(ctx, mut, parsedFiles, clientRefresh, dependencies) + } } // setup watcher for rebuilds async function startServing( - ctx: BuildCtx, - mut: Mutex, - initialContent: ProcessedContent[], - clientRefresh: () => void, - dependencies: Dependencies, // emitter name: dep graph + ctx: BuildCtx, + mut: Mutex, + initialContent: ProcessedContent[], + clientRefresh: () => void, + dependencies: Dependencies, // emitter name: dep graph ) { - const { argv } = ctx + const {argv} = ctx - // cache file parse results - const contentMap = new Map() - for (const content of initialContent) { - const [_tree, vfile] = content - contentMap.set(vfile.data.filePath!, content) - } + // cache file parse results + const contentMap = new Map() + for (const content of initialContent) { + const [_tree, vfile] = content + contentMap.set(vfile.data.filePath!, content) + } - const buildData: BuildData = { - ctx, - mut, - dependencies, - contentMap, - ignored: await isGitIgnored(), - initialSlugs: ctx.allSlugs, - toRebuild: new Set(), - toRemove: new Set(), - trackedAssets: new Set(), - lastBuildMs: 0, - } + const buildData: BuildData = { + ctx, + mut, + dependencies, + contentMap, + ignored: await isGitIgnored(), + initialSlugs: ctx.allSlugs, + toRebuild: new Set(), + toRemove: new Set(), + trackedAssets: new Set(), + lastBuildMs: 0, + } - const watcher = chokidar.watch(".", { - persistent: true, - cwd: argv.directory, - ignoreInitial: true, - }) + const watcher = chokidar.watch(".", { + persistent: true, + cwd: argv.directory, + ignoreInitial: true, + }) - const buildFromEntry = argv.fastRebuild - ? partialRebuildFromEntrypoint - : rebuildFromEntrypoint - watcher - .on("add", (fp) => buildFromEntry(fp, "add", clientRefresh, buildData)) - .on("change", (fp) => - buildFromEntry(fp, "change", clientRefresh, buildData), - ) - .on("unlink", (fp) => - buildFromEntry(fp, "delete", clientRefresh, buildData), - ) + const buildFromEntry = argv.fastRebuild + ? partialRebuildFromEntrypoint + : rebuildFromEntrypoint + watcher + .on("add", (fp) => buildFromEntry(fp, "add", clientRefresh, buildData)) + .on("change", (fp) => + buildFromEntry(fp, "change", clientRefresh, buildData), + ) + .on("unlink", (fp) => + buildFromEntry(fp, "delete", clientRefresh, buildData), + ) - return async () => { - await watcher.close() - } + return async () => { + await watcher.close() + } } async function partialRebuildFromEntrypoint( - filepath: string, - action: FileEvent, - clientRefresh: () => void, - buildData: BuildData, // note: this function mutates buildData + filepath: string, + action: FileEvent, + clientRefresh: () => void, + buildData: BuildData, // note: this function mutates buildData ) { - const { ctx, ignored, dependencies, contentMap, mut, toRemove } = buildData - const { argv, cfg } = ctx + const {ctx, ignored, dependencies, contentMap, mut, toRemove} = buildData + const {argv, cfg} = ctx - // don't do anything for gitignored files - if (ignored(filepath)) { - return - } + // don't do anything for gitignored files + if (ignored(filepath)) { + return + } - const buildStart = new Date().getTime() - buildData.lastBuildMs = buildStart - const release = await mut.acquire() - if (buildData.lastBuildMs > buildStart) { - release() - return - } - - const perf = new PerfTimer() - console.log(chalk.yellow("Detected change, rebuilding...")) - - // UPDATE DEP GRAPH - const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath - - const staticResources = getStaticResourcesFromPlugins(ctx) - let processedFiles: ProcessedContent[] = [] - - switch (action) { - case "add": - // add to cache when new file is added - processedFiles = await parseMarkdown(ctx, [fp]) - processedFiles.forEach(([tree, vfile]) => - contentMap.set(vfile.data.filePath!, [tree, vfile]), - ) - - // update the dep graph by asking all emitters whether they depend on this file - for (const emitter of cfg.plugins.emitters) { - const emitterGraph = - (await emitter.getDependencyGraph?.( - ctx, - processedFiles, - staticResources, - )) ?? null - - if (emitterGraph) { - const existingGraph = dependencies[emitter.name] - if (existingGraph !== null) { - existingGraph.mergeGraph(emitterGraph) - } else { - // might be the first time we're adding a mardown file - dependencies[emitter.name] = emitterGraph - } - } - } - break - case "change": - // invalidate cache when file is changed - processedFiles = await parseMarkdown(ctx, [fp]) - processedFiles.forEach(([tree, vfile]) => - contentMap.set(vfile.data.filePath!, [tree, vfile]), - ) - - // only content files can have added/removed dependencies because of transclusions - if (path.extname(fp) === ".md") { - for (const emitter of cfg.plugins.emitters) { - // get new dependencies from all emitters for this file - const emitterGraph = - (await emitter.getDependencyGraph?.( - ctx, - processedFiles, - staticResources, - )) ?? null - - // only update the graph if the emitter plugin uses the changed file - // eg. Assets plugin ignores md files, so we skip updating the graph - if (emitterGraph?.hasNode(fp)) { - // merge the new dependencies into the dep graph - dependencies[emitter.name]?.updateIncomingEdgesForNode( - emitterGraph, - fp, - ) - } - } - } - break - case "delete": - toRemove.add(fp) - break - } - - if (argv.verbose) { - console.log(`Updated dependency graphs in ${perf.timeSince()}`) - } - - // EMIT - perf.addEvent("rebuild") - let emittedFiles = 0 - - for (const emitter of cfg.plugins.emitters) { - const depGraph = dependencies[emitter.name] - - // emitter hasn't defined a dependency graph. call it with all processed files - if (depGraph === null) { - if (argv.verbose) { - console.log( - `Emitter ${emitter.name} doesn't define a dependency graph. Calling it with all files...`, - ) - } - - const files = [...contentMap.values()].filter( - ([_node, vfile]) => !toRemove.has(vfile.data.filePath!), - ) - - const emittedFps = await emitter.emit(ctx, files, staticResources) - - if (ctx.argv.verbose) { - for (const file of emittedFps) { - console.log(`[emit:${emitter.name}] ${file}`) - } - } - - emittedFiles += emittedFps.length - continue - } - - // only call the emitter if it uses this file - if (depGraph.hasNode(fp)) { - // re-emit using all files that are needed for the downstream of this file - // eg. for ContentIndex, the dep graph could be: - // a.md --> contentIndex.json - // b.md ------^ - // - // if a.md changes, we need to re-emit contentIndex.json, - // and supply [a.md, b.md] to the emitter - const upstreams = [ - ...depGraph.getLeafNodeAncestors(fp), - ] as FilePath[] - - const upstreamContent = upstreams - // filter out non-markdown files - .filter((file) => contentMap.has(file)) - // if file was deleted, don't give it to the emitter - .filter((file) => !toRemove.has(file)) - .map((file) => contentMap.get(file)!) - - const emittedFps = await emitter.emit( - ctx, - upstreamContent, - staticResources, - ) - - if (ctx.argv.verbose) { - for (const file of emittedFps) { - console.log(`[emit:${emitter.name}] ${file}`) - } - } - - emittedFiles += emittedFps.length - } - } - - console.log( - `Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`, - ) - - // CLEANUP - const destinationsToDelete = new Set() - for (const file of toRemove) { - // remove from cache - contentMap.delete(file) - Object.values(dependencies).forEach((depGraph) => { - // remove the node from dependency graphs - depGraph?.removeNode(file) - // remove any orphan nodes. eg if a.md is deleted, a.html is orphaned and should be removed - const orphanNodes = depGraph?.removeOrphanNodes() - orphanNodes?.forEach((node) => { - // only delete files that are in the output directory - if (node.startsWith(argv.output)) { - destinationsToDelete.add(node) - } - }) - }) - } - await rimraf([...destinationsToDelete]) - - console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) - - toRemove.clear() + const buildStart = new Date().getTime() + buildData.lastBuildMs = buildStart + const release = await mut.acquire() + if (buildData.lastBuildMs > buildStart) { release() - clientRefresh() + return + } + + const perf = new PerfTimer() + console.log(chalk.yellow("Detected change, rebuilding...")) + + // UPDATE DEP GRAPH + const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath + + const staticResources = getStaticResourcesFromPlugins(ctx) + let processedFiles: ProcessedContent[] = [] + + switch (action) { + case "add": + // add to cache when new file is added + processedFiles = await parseMarkdown(ctx, [fp]) + processedFiles.forEach(([tree, vfile]) => + contentMap.set(vfile.data.filePath!, [tree, vfile]), + ) + + // update the dep graph by asking all emitters whether they depend on this file + for (const emitter of cfg.plugins.emitters) { + const emitterGraph = + (await emitter.getDependencyGraph?.( + ctx, + processedFiles, + staticResources, + )) ?? null + + if (emitterGraph) { + const existingGraph = dependencies[emitter.name] + if (existingGraph !== null) { + existingGraph.mergeGraph(emitterGraph) + } else { + // might be the first time we're adding a mardown file + dependencies[emitter.name] = emitterGraph + } + } + } + break + case "change": + // invalidate cache when file is changed + processedFiles = await parseMarkdown(ctx, [fp]) + processedFiles.forEach(([tree, vfile]) => + contentMap.set(vfile.data.filePath!, [tree, vfile]), + ) + + // only content files can have added/removed dependencies because of transclusions + if (path.extname(fp) === ".md") { + for (const emitter of cfg.plugins.emitters) { + // get new dependencies from all emitters for this file + const emitterGraph = + (await emitter.getDependencyGraph?.( + ctx, + processedFiles, + staticResources, + )) ?? null + + // only update the graph if the emitter plugin uses the changed file + // eg. Assets plugin ignores md files, so we skip updating the graph + if (emitterGraph?.hasNode(fp)) { + // merge the new dependencies into the dep graph + dependencies[emitter.name]?.updateIncomingEdgesForNode( + emitterGraph, + fp, + ) + } + } + } + break + case "delete": + toRemove.add(fp) + break + } + + if (argv.verbose) { + console.log(`Updated dependency graphs in ${perf.timeSince()}`) + } + + // EMIT + perf.addEvent("rebuild") + let emittedFiles = 0 + + for (const emitter of cfg.plugins.emitters) { + const depGraph = dependencies[emitter.name] + + // emitter hasn't defined a dependency graph. call it with all processed files + if (depGraph === null) { + if (argv.verbose) { + console.log( + `Emitter ${emitter.name} doesn't define a dependency graph. Calling it with all files...`, + ) + } + + const files = [...contentMap.values()].filter( + ([_node, vfile]) => !toRemove.has(vfile.data.filePath!), + ) + + const emittedFps = await emitter.emit(ctx, files, staticResources) + + if (ctx.argv.verbose) { + for (const file of emittedFps) { + console.log(`[emit:${emitter.name}] ${file}`) + } + } + + emittedFiles += emittedFps.length + continue + } + + // only call the emitter if it uses this file + if (depGraph.hasNode(fp)) { + // re-emit using all files that are needed for the downstream of this file + // eg. for ContentIndex, the dep graph could be: + // a.md --> contentIndex.json + // b.md ------^ + // + // if a.md changes, we need to re-emit contentIndex.json, + // and supply [a.md, b.md] to the emitter + const upstreams = [...depGraph.getLeafNodeAncestors(fp)] as FilePath[] + + const upstreamContent = upstreams + // filter out non-markdown files + .filter((file) => contentMap.has(file)) + // if file was deleted, don't give it to the emitter + .filter((file) => !toRemove.has(file)) + .map((file) => contentMap.get(file)!) + + const emittedFps = await emitter.emit( + ctx, + upstreamContent, + staticResources, + ) + + if (ctx.argv.verbose) { + for (const file of emittedFps) { + console.log(`[emit:${emitter.name}] ${file}`) + } + } + + emittedFiles += emittedFps.length + } + } + + console.log( + `Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`, + ) + + // CLEANUP + const destinationsToDelete = new Set() + for (const file of toRemove) { + // remove from cache + contentMap.delete(file) + Object.values(dependencies).forEach((depGraph) => { + // remove the node from dependency graphs + depGraph?.removeNode(file) + // remove any orphan nodes. eg if a.md is deleted, a.html is orphaned and should be removed + const orphanNodes = depGraph?.removeOrphanNodes() + orphanNodes?.forEach((node) => { + // only delete files that are in the output directory + if (node.startsWith(argv.output)) { + destinationsToDelete.add(node) + } + }) + }) + } + await rimraf([...destinationsToDelete]) + + console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) + + toRemove.clear() + release() + clientRefresh() } async function rebuildFromEntrypoint( - fp: string, - action: FileEvent, - clientRefresh: () => void, - buildData: BuildData, // note: this function mutates buildData + fp: string, + action: FileEvent, + clientRefresh: () => void, + buildData: BuildData, // note: this function mutates buildData ) { - const { - ctx, - ignored, - mut, - initialSlugs, - contentMap, - toRebuild, - toRemove, - trackedAssets, - } = buildData + const { + ctx, + ignored, + mut, + initialSlugs, + contentMap, + toRebuild, + toRemove, + trackedAssets, + } = buildData - const { argv } = ctx + const {argv} = ctx - // don't do anything for gitignored files - if (ignored(fp)) { - return - } - - // dont bother rebuilding for non-content files, just track and refresh - fp = toPosixPath(fp) - const filePath = joinSegments(argv.directory, fp) as FilePath - if (path.extname(fp) !== ".md") { - if (action === "add" || action === "change") { - trackedAssets.add(filePath) - } else if (action === "delete") { - trackedAssets.delete(filePath) - } - clientRefresh() - return - } + // 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") { - toRebuild.add(filePath) + trackedAssets.add(filePath) } else if (action === "delete") { - toRemove.add(filePath) + trackedAssets.delete(filePath) } - - const buildStart = new Date().getTime() - buildData.lastBuildMs = buildStart - const release = await mut.acquire() - - // there's another build after us, release and let them do it - if (buildData.lastBuildMs > buildStart) { - release() - return - } - - const perf = new PerfTimer() - console.log(chalk.yellow("Detected change, rebuilding...")) - try { - const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp)) - - const trackedSlugs = [ - ...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets]), - ] - .filter((fp) => !toRemove.has(fp)) - .map((fp) => - slugifyFilePath( - path.posix.relative(argv.directory, fp) as FilePath, - ), - ) - - ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])] - const parsedContent = await parseMarkdown(ctx, filesToRebuild) - for (const content of parsedContent) { - const [_tree, vfile] = content - contentMap.set(vfile.data.filePath!, content) - } - - for (const fp of toRemove) { - contentMap.delete(fp) - } - - const parsedFiles = [...contentMap.values()] - const filteredContent = filterContent(ctx, parsedFiles) - - // TODO: we can probably traverse the link graph to figure out what's safe to delete here - // instead of just deleting everything - await rimraf(path.join(argv.output, ".*"), { glob: true }) - await emitContent(ctx, filteredContent) - console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) - } catch (err) { - console.log( - chalk.yellow( - `Rebuild failed. Waiting on a change to fix the error...`, - ), - ) - if (argv.verbose) { - console.log(chalk.red(err)) - } - } - - release() clientRefresh() - toRebuild.clear() - toRemove.clear() + return + } + + if (action === "add" || action === "change") { + toRebuild.add(filePath) + } else if (action === "delete") { + toRemove.add(filePath) + } + + const buildStart = new Date().getTime() + buildData.lastBuildMs = buildStart + const release = await mut.acquire() + + // there's another build after us, release and let them do it + if (buildData.lastBuildMs > buildStart) { + release() + return + } + + const perf = new PerfTimer() + console.log(chalk.yellow("Detected change, rebuilding...")) + try { + const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp)) + + const trackedSlugs = [ + ...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets]), + ] + .filter((fp) => !toRemove.has(fp)) + .map((fp) => + slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath), + ) + + ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])] + const parsedContent = await parseMarkdown(ctx, filesToRebuild) + for (const content of parsedContent) { + const [_tree, vfile] = content + contentMap.set(vfile.data.filePath!, content) + } + + for (const fp of toRemove) { + contentMap.delete(fp) + } + + const parsedFiles = [...contentMap.values()] + const filteredContent = filterContent(ctx, parsedFiles) + + // TODO: we can probably traverse the link graph to figure out what's safe to delete here + // instead of just deleting everything + await rimraf(path.join(argv.output, ".*"), {glob: true}) + await emitContent(ctx, filteredContent) + console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`)) + } catch (err) { + console.log( + chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`), + ) + if (argv.verbose) { + console.log(chalk.red(err)) + } + } + + release() + clientRefresh() + toRebuild.clear() + toRemove.clear() } export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => { - try { - return await buildQuartz(argv, mut, clientRefresh) - } catch (err) { - trace("\nExiting Quartz due to a fatal error", err as Error) - } + try { + return await buildQuartz(argv, mut, clientRefresh) + } catch (err) { + trace("\nExiting Quartz due to a fatal error", err as Error) + } } diff --git a/quartz/cfg.ts b/quartz/cfg.ts index f7abde124..d927b5720 100644 --- a/quartz/cfg.ts +++ b/quartz/cfg.ts @@ -1,72 +1,72 @@ -import { ValidDateType } from "./components/Date" -import { QuartzComponent } from "./components/types" -import { ValidLocale } from "./i18n" -import { PluginTypes } from "./plugins/types" -import { Theme } from "./util/theme" +import {ValidDateType} from "./components/Date" +import {QuartzComponent} from "./components/types" +import {ValidLocale} from "./i18n" +import {PluginTypes} from "./plugins/types" +import {Theme} from "./util/theme" export type Analytics = - | null - | { - provider: "plausible" - host?: string - } - | { - provider: "google" - tagId: string - } - | { - provider: "umami" - websiteId: string - host?: string - } - | { - provider: "goatcounter" - websiteId: string - host?: string - scriptSrc?: string - } + | null + | { + provider: "plausible" + host?: string + } + | { + provider: "google" + tagId: string + } + | { + provider: "umami" + websiteId: string + host?: string + } + | { + provider: "goatcounter" + websiteId: string + host?: string + scriptSrc?: string + } export interface GlobalConfiguration { - pageTitle: string - /** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */ - enableSPA: boolean - /** Whether to display Wikipedia-style popovers when hovering over links */ - enablePopovers: boolean - /** Analytics mode */ - analytics: Analytics - /** Glob patterns to not search */ - ignorePatterns: string[] - /** Whether to use created, modified, or published as the default type of date */ - defaultDateType: ValidDateType - /** 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 - theme: Theme - /** - * Allow to translate the date in the language of your choice. - * Also used for UI translation (default: en-US) - * Need to be formated following BCP 47: https://en.wikipedia.org/wiki/IETF_language_tag - * The first part is the language (en) and the second part is the script/region (US) - * Language Codes: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes - * Region Codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - */ - locale: ValidLocale + pageTitle: string + /** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */ + enableSPA: boolean + /** Whether to display Wikipedia-style popovers when hovering over links */ + enablePopovers: boolean + /** Analytics mode */ + analytics: Analytics + /** Glob patterns to not search */ + ignorePatterns: string[] + /** Whether to use created, modified, or published as the default type of date */ + defaultDateType: ValidDateType + /** 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 + theme: Theme + /** + * Allow to translate the date in the language of your choice. + * Also used for UI translation (default: en-US) + * Need to be formated following BCP 47: https://en.wikipedia.org/wiki/IETF_language_tag + * The first part is the language (en) and the second part is the script/region (US) + * Language Codes: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes + * Region Codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + */ + locale: ValidLocale } export interface QuartzConfig { - 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 + head: QuartzComponent + header: QuartzComponent[] + beforeBody: QuartzComponent[] + pageBody: QuartzComponent + left: QuartzComponent[] + right: QuartzComponent[] + footer: QuartzComponent } export type PageLayout = Pick diff --git a/quartz/cli/args.js b/quartz/cli/args.js index 7a9c1d86c..ed6395ca2 100644 --- a/quartz/cli/args.js +++ b/quartz/cli/args.js @@ -1,109 +1,109 @@ export const CommonArgv = { - directory: { - string: true, - alias: ["d"], - default: "content", - describe: "directory to look for content files", - }, - verbose: { - boolean: true, - alias: ["v"], - default: false, - describe: "print out extra logging information", - }, + directory: { + string: true, + alias: ["d"], + default: "content", + describe: "directory to look for content files", + }, + verbose: { + boolean: true, + alias: ["v"], + default: false, + describe: "print out extra logging information", + }, } export const CreateArgv = { - ...CommonArgv, - source: { - string: true, - alias: ["s"], - describe: "source directory to copy/create symlink from", - }, - strategy: { - string: true, - alias: ["X"], - choices: ["new", "copy", "symlink"], - describe: "strategy for content folder setup", - }, - links: { - string: true, - alias: ["l"], - choices: ["absolute", "shortest", "relative"], - describe: "strategy to resolve links", - }, + ...CommonArgv, + source: { + string: true, + alias: ["s"], + describe: "source directory to copy/create symlink from", + }, + strategy: { + string: true, + alias: ["X"], + choices: ["new", "copy", "symlink"], + describe: "strategy for content folder setup", + }, + links: { + string: true, + alias: ["l"], + choices: ["absolute", "shortest", "relative"], + describe: "strategy to resolve links", + }, } export const SyncArgv = { - ...CommonArgv, - commit: { - boolean: true, - default: true, - describe: "create a git commit for your unsaved changes", - }, - message: { - string: true, - alias: ["m"], - describe: "option to override the default Quartz commit message", - }, - push: { - boolean: true, - default: true, - describe: "push updates to your Quartz fork", - }, - pull: { - boolean: true, - default: true, - describe: "pull updates from your Quartz fork", - }, + ...CommonArgv, + commit: { + boolean: true, + default: true, + describe: "create a git commit for your unsaved changes", + }, + message: { + string: true, + alias: ["m"], + describe: "option to override the default Quartz commit message", + }, + push: { + boolean: true, + default: true, + describe: "push updates to your Quartz fork", + }, + pull: { + boolean: true, + default: true, + describe: "pull updates from your Quartz fork", + }, } export const BuildArgv = { - ...CommonArgv, - output: { - string: true, - alias: ["o"], - default: "public", - describe: "output folder for files", - }, - serve: { - boolean: true, - default: false, - describe: "run a local server to live-preview your Quartz", - }, - fastRebuild: { - boolean: true, - default: false, - describe: "[experimental] rebuild only the changed files", - }, - baseDir: { - string: true, - default: "", - describe: "base path to serve your local server on", - }, - port: { - number: true, - default: 8080, - describe: "port to serve Quartz on", - }, - wsPort: { - number: true, - default: 3001, - describe: "port to use for WebSocket-based hot-reload notifications", - }, - remoteDevHost: { - string: true, - default: "", - describe: - "A URL override for the websocket connection if you are not developing on localhost", - }, - bundleInfo: { - boolean: true, - default: false, - describe: "show detailed bundle information", - }, - concurrency: { - number: true, - describe: "how many threads to use to parse notes", - }, + ...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", + }, + fastRebuild: { + boolean: true, + default: false, + describe: "[experimental] rebuild only the changed files", + }, + baseDir: { + string: true, + default: "", + describe: "base path to serve your local server on", + }, + port: { + number: true, + default: 8080, + describe: "port to serve Quartz on", + }, + wsPort: { + number: true, + default: 3001, + describe: "port to use for WebSocket-based hot-reload notifications", + }, + remoteDevHost: { + string: true, + default: "", + describe: + "A URL override for the websocket connection if you are not developing on localhost", + }, + bundleInfo: { + boolean: true, + default: false, + describe: "show detailed bundle information", + }, + concurrency: { + number: true, + describe: "how many threads to use to parse notes", + }, } diff --git a/quartz/cli/constants.js b/quartz/cli/constants.js index f4a9ce52b..366504a7c 100644 --- a/quartz/cli/constants.js +++ b/quartz/cli/constants.js @@ -1,5 +1,5 @@ import path from "path" -import { readFileSync } from "fs" +import {readFileSync} from "fs" /** * All constants relating to helpers or handlers @@ -11,5 +11,5 @@ export const cwd = process.cwd() export const cacheDir = path.join(cwd, ".quartz-cache") export const cacheFile = "./quartz/.quartz-cache/transpiled-build.mjs" export const fp = "./quartz/build.ts" -export const { version } = JSON.parse(readFileSync("./package.json").toString()) +export const {version} = JSON.parse(readFileSync("./package.json").toString()) export const contentCacheFolder = path.join(cacheDir, "content-cache") diff --git a/quartz/cli/handlers.js b/quartz/cli/handlers.js index f9a9e4502..615e3d9af 100644 --- a/quartz/cli/handlers.js +++ b/quartz/cli/handlers.js @@ -1,35 +1,35 @@ -import { promises } from "fs" +import {promises} from "fs" import path from "path" import esbuild from "esbuild" import chalk from "chalk" -import { sassPlugin } from "esbuild-sass-plugin" +import {sassPlugin} from "esbuild-sass-plugin" import fs from "fs" -import { intro, outro, select, text } from "@clack/prompts" -import { rimraf } from "rimraf" +import {intro, outro, select, text} from "@clack/prompts" +import {rimraf} from "rimraf" import chokidar from "chokidar" import prettyBytes from "pretty-bytes" -import { execSync, spawnSync } from "child_process" +import {execSync, spawnSync} from "child_process" import http from "http" import serveHandler from "serve-handler" -import { WebSocketServer } from "ws" -import { randomUUID } from "crypto" -import { Mutex } from "async-mutex" -import { CreateArgv } from "./args.js" +import {WebSocketServer} from "ws" +import {randomUUID} from "crypto" +import {Mutex} from "async-mutex" +import {CreateArgv} from "./args.js" import { - exitIfCancel, - escapePath, - gitPull, - popContentFolder, - stashContentFolder, + exitIfCancel, + escapePath, + gitPull, + popContentFolder, + stashContentFolder, } from "./helpers.js" import { - UPSTREAM_NAME, - QUARTZ_SOURCE_BRANCH, - ORIGIN_NAME, - version, - fp, - cacheFile, - cwd, + UPSTREAM_NAME, + QUARTZ_SOURCE_BRANCH, + ORIGIN_NAME, + version, + fp, + cacheFile, + cwd, } from "./constants.js" /** @@ -37,180 +37,179 @@ import { * @param {*} argv arguments for `create` */ export async function handleCreate(argv) { - console.log() - intro(chalk.bgGreen.black(` Quartz v${version} `)) - const contentFolder = path.join(cwd, argv.directory) - let setupStrategy = argv.strategy?.toLowerCase() - let linkResolutionStrategy = argv.links?.toLowerCase() - const sourceDirectory = argv.source + console.log() + intro(chalk.bgGreen.black(` Quartz v${version} `)) + const contentFolder = path.join(cwd, argv.directory) + let setupStrategy = argv.strategy?.toLowerCase() + let linkResolutionStrategy = argv.links?.toLowerCase() + const sourceDirectory = argv.source - // If all cmd arguments were provided, check if theyre valid - if (setupStrategy && linkResolutionStrategy) { - // If setup isn't, "new", source argument is required - if (setupStrategy !== "new") { - // Error handling - if (!sourceDirectory) { - outro( - chalk.red( - `Setup strategies (arg '${chalk.yellow( - `-${CreateArgv.strategy.alias[0]}`, - )}') other than '${chalk.yellow( - "new", - )}' require content folder argument ('${chalk.yellow( - `-${CreateArgv.source.alias[0]}`, - )}') to be set`, - ), - ) - process.exit(1) - } else { - if (!fs.existsSync(sourceDirectory)) { - outro( - chalk.red( - `Input directory to copy/symlink 'content' from not found ('${chalk.yellow( - sourceDirectory, - )}', invalid argument "${chalk.yellow(`-${CreateArgv.source.alias[0]}`)})`, - ), - ) - process.exit(1) - } else if (!fs.lstatSync(sourceDirectory).isDirectory()) { - outro( - chalk.red( - `Source directory to copy/symlink 'content' from is not a directory (found file at '${chalk.yellow( - sourceDirectory, - )}', invalid argument ${chalk.yellow(`-${CreateArgv.source.alias[0]}`)}")`, - ), - ) - process.exit(1) - } - } - } - } - - // Use cli process if cmd args werent provided - if (!setupStrategy) { - setupStrategy = exitIfCancel( - await select({ - message: `Choose how to initialize the content in \`${contentFolder}\``, - options: [ - { value: "new", label: "Empty Quartz" }, - { - value: "copy", - label: "Copy an existing folder", - hint: "overwrites `content`", - }, - { - value: "symlink", - label: "Symlink an existing folder", - hint: "don't select this unless you know what you are doing!", - }, - ], - }), + // If all cmd arguments were provided, check if theyre valid + if (setupStrategy && linkResolutionStrategy) { + // If setup isn't, "new", source argument is required + if (setupStrategy !== "new") { + // Error handling + if (!sourceDirectory) { + outro( + chalk.red( + `Setup strategies (arg '${chalk.yellow( + `-${CreateArgv.strategy.alias[0]}`, + )}') other than '${chalk.yellow( + "new", + )}' require content folder argument ('${chalk.yellow( + `-${CreateArgv.source.alias[0]}`, + )}') to be set`, + ), ) + process.exit(1) + } else { + if (!fs.existsSync(sourceDirectory)) { + outro( + chalk.red( + `Input directory to copy/symlink 'content' from not found ('${chalk.yellow( + sourceDirectory, + )}', invalid argument "${chalk.yellow(`-${CreateArgv.source.alias[0]}`)})`, + ), + ) + process.exit(1) + } else if (!fs.lstatSync(sourceDirectory).isDirectory()) { + outro( + chalk.red( + `Source directory to copy/symlink 'content' from is not a directory (found file at '${chalk.yellow( + sourceDirectory, + )}', invalid argument ${chalk.yellow(`-${CreateArgv.source.alias[0]}`)}")`, + ), + ) + process.exit(1) + } + } + } + } + + // Use cli process if cmd args werent provided + if (!setupStrategy) { + setupStrategy = exitIfCancel( + await select({ + message: `Choose how to initialize the content in \`${contentFolder}\``, + options: [ + {value: "new", label: "Empty Quartz"}, + { + value: "copy", + label: "Copy an existing folder", + hint: "overwrites `content`", + }, + { + value: "symlink", + label: "Symlink an existing folder", + hint: "don't select this unless you know what you are doing!", + }, + ], + }), + ) + } + + async function rmContentFolder() { + const contentStat = await fs.promises.lstat(contentFolder) + if (contentStat.isSymbolicLink()) { + await fs.promises.unlink(contentFolder) + } else { + await rimraf(contentFolder) + } + } + + const gitkeepPath = path.join(contentFolder, ".gitkeep") + if (fs.existsSync(gitkeepPath)) { + await fs.promises.unlink(gitkeepPath) + } + if (setupStrategy === "copy" || setupStrategy === "symlink") { + let originalFolder = sourceDirectory + + // If input directory was not passed, use cli + if (!sourceDirectory) { + originalFolder = escapePath( + exitIfCancel( + await text({ + message: "Enter the full path to existing content folder", + placeholder: + "On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path", + validate(fp) { + const fullPath = escapePath(fp) + if (!fs.existsSync(fullPath)) { + return "The given path doesn't exist" + } else if (!fs.lstatSync(fullPath).isDirectory()) { + return "The given path is not a folder" + } + }, + }), + ), + ) } - async function rmContentFolder() { - const contentStat = await fs.promises.lstat(contentFolder) - if (contentStat.isSymbolicLink()) { - await fs.promises.unlink(contentFolder) - } else { - await rimraf(contentFolder) - } + 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") } - - const gitkeepPath = path.join(contentFolder, ".gitkeep") - if (fs.existsSync(gitkeepPath)) { - await fs.promises.unlink(gitkeepPath) - } - if (setupStrategy === "copy" || setupStrategy === "symlink") { - let originalFolder = sourceDirectory - - // If input directory was not passed, use cli - if (!sourceDirectory) { - originalFolder = escapePath( - exitIfCancel( - await text({ - message: - "Enter the full path to existing content folder", - placeholder: - "On most terminal emulators, you can drag and drop a folder into the window and it will paste the full path", - validate(fp) { - const fullPath = escapePath(fp) - if (!fs.existsSync(fullPath)) { - return "The given path doesn't exist" - } else if (!fs.lstatSync(fullPath).isDirectory()) { - return "The given path is not a folder" - } - }, - }), - ), - ) - } - - await rmContentFolder() - if (setupStrategy === "copy") { - await fs.promises.cp(originalFolder, contentFolder, { - recursive: true, - preserveTimestamps: true, - }) - } else if (setupStrategy === "symlink") { - await fs.promises.symlink(originalFolder, contentFolder, "dir") - } - } else if (setupStrategy === "new") { - await fs.promises.writeFile( - path.join(contentFolder, "index.md"), - `--- + } else if (setupStrategy === "new") { + await fs.promises.writeFile( + path.join(contentFolder, "index.md"), + `--- title: Welcome to Quartz --- This is a blank Quartz installation. See the [documentation](https://quartz.jzhao.xyz) for how to get started. `, - ) - } - - // Use cli process if cmd args werent provided - if (!linkResolutionStrategy) { - // get a preferred link resolution strategy - linkResolutionStrategy = exitIfCancel( - await select({ - message: `Choose how Quartz should resolve links in your content. This should match Obsidian's link format. You can change this later in \`quartz.config.ts\`.`, - options: [ - { - value: "shortest", - label: "Treat links as shortest path", - hint: "(default)", - }, - { - value: "absolute", - label: "Treat links as absolute path", - }, - { - value: "relative", - label: "Treat links as relative paths", - }, - ], - }), - ) - } - - // 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) + } - // setup remote - execSync( - `git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`, - { stdio: "ignore" }, + // Use cli process if cmd args werent provided + if (!linkResolutionStrategy) { + // get a preferred link resolution strategy + linkResolutionStrategy = exitIfCancel( + await select({ + message: `Choose how Quartz should resolve links in your content. This should match Obsidian's link format. You can change this later in \`quartz.config.ts\`.`, + options: [ + { + value: "shortest", + label: "Treat links as shortest path", + hint: "(default)", + }, + { + value: "absolute", + label: "Treat links as absolute path", + }, + { + value: "relative", + label: "Treat links as relative paths", + }, + ], + }), ) + } - outro(`You're all set! Not sure what to do next? Try: + // 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) + + // setup remote + execSync( + `git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`, + {stdio: "ignore"}, + ) + + outro(`You're all set! Not sure what to do next? Try: • Customizing Quartz a bit more by editing \`quartz.config.ts\` • Running \`npx quartz build --serve\` to preview your Quartz locally • Hosting your Quartz online (see: https://quartz.jzhao.xyz/hosting) @@ -222,249 +221,232 @@ See the [documentation](https://quartz.jzhao.xyz) for how to get started. * @param {*} argv arguments for `build` */ export async function handleBuild(argv) { - console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) - const ctx = await esbuild.context({ - entryPoints: [fp], - outfile: cacheFile, - bundle: true, - keepNames: true, - minifyWhitespace: true, - minifySyntax: true, - platform: "node", - format: "esm", - jsx: "automatic", - jsxImportSource: "preact", - packages: "external", - metafile: true, - sourcemap: true, - sourcesContent: false, - plugins: [ - sassPlugin({ - type: "css-text", - cssImports: true, - }), - { - name: "inline-script-loader", - setup(build) { - build.onLoad( - { filter: /\.inline\.(ts|js)$/ }, - async (args) => { - let text = await promises.readFile( - args.path, - "utf8", - ) + console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) + const ctx = await esbuild.context({ + entryPoints: [fp], + outfile: cacheFile, + bundle: true, + keepNames: true, + minifyWhitespace: true, + minifySyntax: true, + platform: "node", + format: "esm", + jsx: "automatic", + jsxImportSource: "preact", + packages: "external", + metafile: true, + sourcemap: true, + sourcesContent: false, + plugins: [ + sassPlugin({ + type: "css-text", + cssImports: true, + }), + { + name: "inline-script-loader", + setup(build) { + build.onLoad({filter: /\.inline\.(ts|js)$/}, async (args) => { + let text = await promises.readFile(args.path, "utf8") - // remove default exports that we manually inserted - text = text.replace("export default", "") - text = text.replace("export", "") + // 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, - minify: true, - platform: "browser", - format: "esm", - }) - const rawMod = transpiled.outputFiles[0].text - return { - contents: rawMod, - loader: "text", - } - }, - ) - }, - }, - ], + 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, + minify: true, + platform: "browser", + format: "esm", + }) + const rawMod = transpiled.outputFiles[0].text + return { + contents: rawMod, + loader: "text", + } + }) + }, + }, + ], + }) + + const buildMutex = new Mutex() + let lastBuildMs = 0 + let cleanupBuild = null + const build = async (clientRefresh) => { + const buildStart = new Date().getTime() + lastBuildMs = buildStart + const release = await buildMutex.acquire() + if (lastBuildMs > buildStart) { + release() + return + } + + if (cleanupBuild) { + await cleanupBuild() + console.log( + chalk.yellow("Detected a source code change, doing a hard rebuild..."), + ) + } + + const result = await ctx.rebuild().catch((err) => { + console.error( + `${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`, + ) + console.log(`Reason: ${chalk.grey(err)}`) + process.exit(1) }) + release() - const buildMutex = new Mutex() - let lastBuildMs = 0 - let cleanupBuild = null - const build = async (clientRefresh) => { - const buildStart = new Date().getTime() - lastBuildMs = buildStart - const release = await buildMutex.acquire() - if (lastBuildMs > buildStart) { - release() - return - } - - if (cleanupBuild) { - await cleanupBuild() - console.log( - chalk.yellow( - "Detected a source code change, doing a hard rebuild...", - ), - ) - } - - const result = await ctx.rebuild().catch((err) => { - console.error( - `${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`, - ) - console.log(`Reason: ${chalk.grey(err)}`) - process.exit(1) - }) - release() - - if (argv.bundleInfo) { - const outputFileName = "quartz/.quartz-cache/transpiled-build.mjs" - const meta = result.metafile.outputs[outputFileName] - console.log( - `Successfully transpiled ${Object.keys(meta.inputs).length} files (${prettyBytes( - meta.bytes, - )})`, - ) - console.log( - await esbuild.analyzeMetafile(result.metafile, { color: true }), - ) - } - - // bypass module cache - // https://github.com/nodejs/modules/issues/307 - const { default: buildQuartz } = await import( - `../../${cacheFile}?update=${randomUUID()}` - ) - // ^ this import is relative, so base "cacheFile" path can't be used - - cleanupBuild = await buildQuartz(argv, buildMutex, clientRefresh) - clientRefresh() + if (argv.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})) } - if (argv.serve) { - const connections = [] - const clientRefresh = () => - connections.forEach((conn) => conn.send("rebuild")) + // bypass module cache + // https://github.com/nodejs/modules/issues/307 + const {default: buildQuartz} = await import( + `../../${cacheFile}?update=${randomUUID()}` + ) + // ^ this import is relative, so base "cacheFile" path can't be used - if (argv.baseDir !== "" && !argv.baseDir.startsWith("/")) { - argv.baseDir = "/" + argv.baseDir - } + cleanupBuild = await buildQuartz(argv, buildMutex, clientRefresh) + clientRefresh() + } - 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 - } + if (argv.serve) { + const connections = [] + const clientRefresh = () => + connections.forEach((conn) => conn.send("rebuild")) - // strip baseDir prefix - req.url = req.url?.slice(argv.baseDir.length) + if (argv.baseDir !== "" && !argv.baseDir.startsWith("/")) { + argv.baseDir = "/" + argv.baseDir + } - const serve = async () => { - const release = await buildMutex.acquire() - await serveHandler(req, res, { - public: argv.output, - directoryListing: false, - headers: [ - { - source: "**/*.*", - headers: [ - { key: "Content-Disposition", value: "inline" }, - ], - }, - ], - }) - const status = res.statusCode - const statusString = - status >= 200 && status < 300 - ? chalk.green(`[${status}]`) - : chalk.red(`[${status}]`) - console.log( - statusString + chalk.grey(` ${argv.baseDir}${req.url}`), - ) - release() - } - - const redirect = (newFp) => { - newFp = argv.baseDir + newFp - res.writeHead(302, { - Location: newFp, - }) - console.log( - chalk.yellow("[302]") + - chalk.grey(` ${argv.baseDir}${req.url} -> ${newFp}`), - ) - res.end() - } - - let fp = req.url?.split("?")[0] ?? "/" - - // handle redirects - if (fp.endsWith("/")) { - // /trailing/ - // does /trailing/index.html exist? if so, serve it - const indexFp = path.posix.join(fp, "index.html") - if (fs.existsSync(path.posix.join(argv.output, indexFp))) { - req.url = fp - return serve() - } - - // does /trailing.html exist? if so, redirect to /trailing - let base = fp.slice(0, -1) - if (path.extname(base) === "") { - base += ".html" - } - if (fs.existsSync(path.posix.join(argv.output, base))) { - return redirect(fp.slice(0, -1)) - } - } else { - // /regular - // does /regular.html exist? if so, serve it - let base = fp - if (path.extname(base) === "") { - base += ".html" - } - if (fs.existsSync(path.posix.join(argv.output, base))) { - req.url = fp - return serve() - } - - // does /regular/index.html exist? if so, redirect to /regular/ - let indexFp = path.posix.join(fp, "index.html") - if (fs.existsSync(path.posix.join(argv.output, indexFp))) { - return redirect(fp + "/") - } - } - - return serve() - }) - server.listen(argv.port) - const wss = new WebSocketServer({ port: argv.wsPort }) - wss.on("connection", (ws) => connections.push(ws)) + await build(clientRefresh) + const server = http.createServer(async (req, res) => { + if (argv.baseDir && !req.url?.startsWith(argv.baseDir)) { console.log( - chalk.cyan( - `Started a Quartz server listening at http://localhost:${argv.port}${argv.baseDir}`, - ), + chalk.red( + `[404] ${req.url} (warning: link outside of site, this is likely a Quartz bug)`, + ), ) - console.log("hint: exit with ctrl+c") - chokidar - .watch(["**/*.ts", "**/*.tsx", "**/*.scss", "package.json"], { - ignoreInitial: true, - }) - .on("all", async () => { - build(clientRefresh) - }) - } else { - await build(() => {}) - ctx.dispose() - } + res.writeHead(404) + res.end() + return + } + + // strip baseDir prefix + req.url = req.url?.slice(argv.baseDir.length) + + const serve = async () => { + const release = await buildMutex.acquire() + await serveHandler(req, res, { + public: argv.output, + directoryListing: false, + headers: [ + { + source: "**/*.*", + headers: [{key: "Content-Disposition", value: "inline"}], + }, + ], + }) + const status = res.statusCode + const statusString = + status >= 200 && status < 300 + ? chalk.green(`[${status}]`) + : chalk.red(`[${status}]`) + console.log(statusString + chalk.grey(` ${argv.baseDir}${req.url}`)) + release() + } + + const redirect = (newFp) => { + newFp = argv.baseDir + newFp + res.writeHead(302, { + Location: newFp, + }) + console.log( + chalk.yellow("[302]") + + chalk.grey(` ${argv.baseDir}${req.url} -> ${newFp}`), + ) + res.end() + } + + let fp = req.url?.split("?")[0] ?? "/" + + // handle redirects + if (fp.endsWith("/")) { + // /trailing/ + // does /trailing/index.html exist? if so, serve it + const indexFp = path.posix.join(fp, "index.html") + if (fs.existsSync(path.posix.join(argv.output, indexFp))) { + req.url = fp + return serve() + } + + // does /trailing.html exist? if so, redirect to /trailing + let base = fp.slice(0, -1) + if (path.extname(base) === "") { + base += ".html" + } + if (fs.existsSync(path.posix.join(argv.output, base))) { + return redirect(fp.slice(0, -1)) + } + } else { + // /regular + // does /regular.html exist? if so, serve it + let base = fp + if (path.extname(base) === "") { + base += ".html" + } + if (fs.existsSync(path.posix.join(argv.output, base))) { + req.url = fp + return serve() + } + + // does /regular/index.html exist? if so, redirect to /regular/ + let indexFp = path.posix.join(fp, "index.html") + if (fs.existsSync(path.posix.join(argv.output, indexFp))) { + return redirect(fp + "/") + } + } + + return serve() + }) + server.listen(argv.port) + const wss = new WebSocketServer({port: argv.wsPort}) + wss.on("connection", (ws) => connections.push(ws)) + console.log( + chalk.cyan( + `Started a Quartz server listening at http://localhost:${argv.port}${argv.baseDir}`, + ), + ) + console.log("hint: exit with ctrl+c") + chokidar + .watch(["**/*.ts", "**/*.tsx", "**/*.scss", "package.json"], { + ignoreInitial: true, + }) + .on("all", async () => { + build(clientRefresh) + }) + } else { + await build(() => {}) + ctx.dispose() + } } /** @@ -472,35 +454,35 @@ export async function handleBuild(argv) { * @param {*} argv arguments for `update` */ export async function handleUpdate(argv) { - const contentFolder = path.join(cwd, argv.directory) - console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) - console.log("Backing up your content") - execSync( - `git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`, - ) - await stashContentFolder(contentFolder) - console.log( - "Pulling updates... you may need to resolve some `git` conflicts if you've made changes to components or plugins.", - ) - - try { - gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH) - } catch { - console.log(chalk.red("An error occurred above while pulling updates.")) - await popContentFolder(contentFolder) - return - } + 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.", + ) + try { + gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH) + } catch { + console.log(chalk.red("An error occurred above while pulling updates.")) await popContentFolder(contentFolder) - console.log("Ensuring dependencies are up to date") - const res = spawnSync("npm", ["i"], { stdio: "inherit" }) - if (res.status === 0) { - console.log(chalk.green("Done!")) - } else { - console.log( - chalk.red("An error occurred above while installing dependencies."), - ) - } + return + } + + await popContentFolder(contentFolder) + console.log("Ensuring dependencies are up to date") + const res = spawnSync("npm", ["i"], {stdio: "inherit"}) + if (res.status === 0) { + console.log(chalk.green("Done!")) + } else { + console.log( + chalk.red("An error occurred above while installing dependencies."), + ) + } } /** @@ -508,8 +490,8 @@ export async function handleUpdate(argv) { * @param {*} argv arguments for `restore` */ export async function handleRestore(argv) { - const contentFolder = path.join(cwd, argv.directory) - await popContentFolder(contentFolder) + const contentFolder = path.join(cwd, argv.directory) + await popContentFolder(contentFolder) } /** @@ -517,80 +499,78 @@ export async function handleRestore(argv) { * @param {*} argv arguments for `sync` */ export async function handleSync(argv) { - const contentFolder = path.join(cwd, argv.directory) - console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`)) - console.log("Backing up your content") + 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", - ), - ) + 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) + // stash symlink file + await stashContentFolder(contentFolder) - // follow symlink and copy content - await fs.promises.cp(linkTarg, contentFolder, { - recursive: true, - preserveTimestamps: true, - }) - } - - const currentTimestamp = new Date().toLocaleString("en-US", { - dateStyle: "medium", - timeStyle: "short", - }) - const commitMessage = argv.message ?? `Quartz sync: ${currentTimestamp}` - spawnSync("git", ["add", "."], { stdio: "inherit" }) - spawnSync("git", ["commit", "-m", commitMessage], { stdio: "inherit" }) - - if (contentStat.isSymbolicLink()) { - // put symlink back - await popContentFolder(contentFolder) - } + // follow symlink and copy content + await fs.promises.cp(linkTarg, contentFolder, { + recursive: true, + preserveTimestamps: true, + }) } - await stashContentFolder(contentFolder) + const currentTimestamp = new Date().toLocaleString("en-US", { + dateStyle: "medium", + timeStyle: "short", + }) + const commitMessage = argv.message ?? `Quartz sync: ${currentTimestamp}` + spawnSync("git", ["add", "."], {stdio: "inherit"}) + spawnSync("git", ["commit", "-m", commitMessage], {stdio: "inherit"}) - if (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.", - ) - try { - gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH) - } catch { - console.log( - chalk.red("An error occurred above while pulling updates."), - ) - await popContentFolder(contentFolder) - return - } + if (contentStat.isSymbolicLink()) { + // put symlink back + await popContentFolder(contentFolder) } + } - await popContentFolder(contentFolder) - if (argv.push) { - console.log("Pushing your changes") - const res = spawnSync( - "git", - ["push", "-uf", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], - { - stdio: "inherit", - }, - ) - if (res.status !== 0) { - console.log( - chalk.red( - `An error occurred above while pushing to remote ${ORIGIN_NAME}.`, - ), - ) - return - } + 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.", + ) + try { + gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH) + } catch { + console.log(chalk.red("An error occurred above while pulling updates.")) + await popContentFolder(contentFolder) + return } + } - console.log(chalk.green("Done!")) + await popContentFolder(contentFolder) + if (argv.push) { + console.log("Pushing your changes") + const res = spawnSync( + "git", + ["push", "-uf", ORIGIN_NAME, QUARTZ_SOURCE_BRANCH], + { + stdio: "inherit", + }, + ) + if (res.status !== 0) { + console.log( + chalk.red( + `An error occurred above while pushing to remote ${ORIGIN_NAME}.`, + ), + ) + return + } + } + + console.log(chalk.green("Done!")) } diff --git a/quartz/cli/helpers.js b/quartz/cli/helpers.js index 57c534801..3fb20fe71 100644 --- a/quartz/cli/helpers.js +++ b/quartz/cli/helpers.js @@ -1,64 +1,64 @@ -import { isCancel, outro } from "@clack/prompts" +import {isCancel, outro} from "@clack/prompts" import chalk from "chalk" -import { contentCacheFolder } from "./constants.js" -import { spawnSync } from "child_process" +import {contentCacheFolder} from "./constants.js" +import {spawnSync} from "child_process" import fs from "fs" export function escapePath(fp) { - return fp - .replace(/\\ /g, " ") // unescape spaces - .replace(/^".*"$/, "$1") - .replace(/^'.*"$/, "$1") - .trim() + return fp + .replace(/\\ /g, " ") // unescape spaces + .replace(/^".*"$/, "$1") + .replace(/^'.*"$/, "$1") + .trim() } export function exitIfCancel(val) { - if (isCancel(val)) { - outro(chalk.red("Exiting")) - process.exit(0) - } else { - return val - } + if (isCancel(val)) { + outro(chalk.red("Exiting")) + process.exit(0) + } else { + return val + } } export async function stashContentFolder(contentFolder) { - await fs.promises.rm(contentCacheFolder, { force: true, recursive: true }) - await fs.promises.cp(contentFolder, contentCacheFolder, { - force: true, - recursive: true, - verbatimSymlinks: true, - preserveTimestamps: true, - }) - await fs.promises.rm(contentFolder, { force: true, recursive: true }) + await fs.promises.rm(contentCacheFolder, {force: true, recursive: true}) + await fs.promises.cp(contentFolder, contentCacheFolder, { + force: true, + recursive: true, + verbatimSymlinks: true, + preserveTimestamps: true, + }) + await fs.promises.rm(contentFolder, {force: true, recursive: true}) } export function gitPull(origin, branch) { - const flags = [ - "--no-rebase", - "--autostash", - "-s", - "recursive", - "-X", - "ours", - "--no-edit", - ] - const out = spawnSync("git", ["pull", ...flags, origin, branch], { - stdio: "inherit", - }) - if (out.stderr) { - throw new Error(chalk.red(`Error while pulling updates: ${out.stderr}`)) - } else if (out.status !== 0) { - throw new Error(chalk.red("Error while pulling updates")) - } + 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(chalk.red(`Error while pulling updates: ${out.stderr}`)) + } else if (out.status !== 0) { + throw new Error(chalk.red("Error while pulling updates")) + } } export async function popContentFolder(contentFolder) { - await fs.promises.rm(contentFolder, { force: true, recursive: true }) - await fs.promises.cp(contentCacheFolder, contentFolder, { - force: true, - recursive: true, - verbatimSymlinks: true, - preserveTimestamps: true, - }) - await fs.promises.rm(contentCacheFolder, { force: true, recursive: true }) + 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}) } diff --git a/quartz/components/ArticleTitle.tsx b/quartz/components/ArticleTitle.tsx index a43614f17..58438c5de 100644 --- a/quartz/components/ArticleTitle.tsx +++ b/quartz/components/ArticleTitle.tsx @@ -1,22 +1,20 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" -import { classNames } from "../util/lang" +import {classNames} from "../util/lang" const ArticleTitle: QuartzComponent = ({ - fileData, - displayClass, + fileData, + displayClass, }: QuartzComponentProps) => { - const title = fileData.frontmatter?.title - if (title) { - return ( -

{title}

- ) - } else { - return null - } + const title = fileData.frontmatter?.title + if (title) { + return

{title}

+ } else { + return null + } } ArticleTitle.css = ` diff --git a/quartz/components/Backlinks.tsx b/quartz/components/Backlinks.tsx index e51ba456d..64f6d6843 100644 --- a/quartz/components/Backlinks.tsx +++ b/quartz/components/Backlinks.tsx @@ -1,44 +1,41 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" import style from "./styles/backlinks.scss" -import { resolveRelative, simplifySlug } from "../util/path" -import { i18n } from "../i18n" -import { classNames } from "../util/lang" +import {resolveRelative, simplifySlug} from "../util/path" +import {i18n} from "../i18n" +import {classNames} from "../util/lang" const Backlinks: QuartzComponent = ({ - fileData, - allFiles, - displayClass, - cfg, + fileData, + allFiles, + displayClass, + cfg, }: QuartzComponentProps) => { - const slug = simplifySlug(fileData.slug!) - const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug)) - return ( -
-

{i18n(cfg.locale).components.backlinks.title}

-
    - {backlinkFiles.length > 0 ? ( - backlinkFiles.map((f) => ( -
  • - - {f.frontmatter?.title} - -
  • - )) - ) : ( -
  • - {i18n(cfg.locale).components.backlinks.noBacklinksFound} -
  • - )} -
-
- ) + const slug = simplifySlug(fileData.slug!) + const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug)) + return ( +
+

{i18n(cfg.locale).components.backlinks.title}

+
    + {backlinkFiles.length > 0 ? ( + backlinkFiles.map((f) => ( +
  • + + {f.frontmatter?.title} + +
  • + )) + ) : ( +
  • {i18n(cfg.locale).components.backlinks.noBacklinksFound}
  • + )} +
+
+ ) } Backlinks.css = style diff --git a/quartz/components/Body.tsx b/quartz/components/Body.tsx index 1f4c49354..ef40ef801 100644 --- a/quartz/components/Body.tsx +++ b/quartz/components/Body.tsx @@ -2,13 +2,13 @@ import clipboardScript from "./scripts/clipboard.inline" import clipboardStyle from "./styles/clipboard.scss" import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" -const Body: QuartzComponent = ({ children }: QuartzComponentProps) => { - return
{children}
+const Body: QuartzComponent = ({children}: QuartzComponentProps) => { + return
{children}
} Body.afterDOMLoaded = clipboardScript diff --git a/quartz/components/Breadcrumbs.tsx b/quartz/components/Breadcrumbs.tsx index ce33a7534..3a4d915ba 100644 --- a/quartz/components/Breadcrumbs.tsx +++ b/quartz/components/Breadcrumbs.tsx @@ -1,164 +1,157 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" import breadcrumbsStyle from "./styles/breadcrumbs.scss" -import { - FullSlug, - SimpleSlug, - joinSegments, - resolveRelative, -} from "../util/path" -import { QuartzPluginData } from "../plugins/vfile" -import { classNames } from "../util/lang" +import {FullSlug, SimpleSlug, joinSegments, resolveRelative} from "../util/path" +import {QuartzPluginData} from "../plugins/vfile" +import {classNames} from "../util/lang" type CrumbData = { - displayName: string - path: string + displayName: string + path: string } interface BreadcrumbOptions { - /** - * Symbol between crumbs - */ - spacerSymbol: string - /** - * Name of first crumb - */ - rootName: string - /** - * Whether to look up frontmatter title for folders (could cause performance problems with big vaults) - */ - resolveFrontmatterTitle: boolean - /** - * Whether to display breadcrumbs on root `index.md` - */ - hideOnRoot: boolean - /** - * Whether to display the current page in the breadcrumbs. - */ - showCurrentPage: boolean + /** + * Symbol between crumbs + */ + spacerSymbol: string + /** + * Name of first crumb + */ + rootName: string + /** + * Whether to look up frontmatter title for folders (could cause performance problems with big vaults) + */ + resolveFrontmatterTitle: boolean + /** + * Whether to display breadcrumbs on root `index.md` + */ + hideOnRoot: boolean + /** + * Whether to display the current page in the breadcrumbs. + */ + showCurrentPage: boolean } const defaultOptions: BreadcrumbOptions = { - spacerSymbol: "❯", - rootName: "Home", - resolveFrontmatterTitle: true, - hideOnRoot: true, - showCurrentPage: true, + spacerSymbol: "❯", + rootName: "Home", + resolveFrontmatterTitle: true, + hideOnRoot: true, + showCurrentPage: true, } function formatCrumb( - displayName: string, - baseSlug: FullSlug, - currentSlug: SimpleSlug, + displayName: string, + baseSlug: FullSlug, + currentSlug: SimpleSlug, ): CrumbData { - return { - displayName: displayName.replaceAll("-", " "), - path: resolveRelative(baseSlug, currentSlug), - } + return { + displayName: displayName.replaceAll("-", " "), + path: resolveRelative(baseSlug, currentSlug), + } } export default ((opts?: Partial) => { - // Merge options with defaults - const options: BreadcrumbOptions = { ...defaultOptions, ...opts } + // Merge options with defaults + const options: BreadcrumbOptions = {...defaultOptions, ...opts} - // computed index of folder name to its associated file data - let folderIndex: Map | undefined + // computed index of folder name to its associated file data + let folderIndex: Map | undefined - const Breadcrumbs: QuartzComponent = ({ - fileData, - allFiles, - displayClass, - }: QuartzComponentProps) => { - // Hide crumbs on root if enabled - if (options.hideOnRoot && fileData.slug === "index") { - return <> - } - - // Format entry for root element - const firstEntry = formatCrumb( - options.rootName, - fileData.slug!, - "/" as SimpleSlug, - ) - const crumbs: CrumbData[] = [firstEntry] - - if (!folderIndex && options.resolveFrontmatterTitle) { - folderIndex = new Map() - // construct the index for the first time - for (const file of allFiles) { - const folderParts = file.slug?.split("/") - if (folderParts?.at(-1) === "index") { - folderIndex.set(folderParts.slice(0, -1).join("/"), file) - } - } - } - - // Split slug into hierarchy/parts - const slugParts = fileData.slug?.split("/") - if (slugParts) { - // is tag breadcrumb? - const isTagPath = slugParts[0] === "tags" - - // full path until current part - let currentPath = "" - - for (let i = 0; i < slugParts.length - 1; i++) { - let curPathSegment = slugParts[i] - - // Try to resolve frontmatter folder title - const currentFile = folderIndex?.get( - slugParts.slice(0, i + 1).join("/"), - ) - if (currentFile) { - const title = currentFile.frontmatter!.title - if (title !== "index") { - curPathSegment = title - } - } - - // Add current slug to full path - currentPath = joinSegments(currentPath, slugParts[i]) - const includeTrailingSlash = !isTagPath || i < 1 - - // Format and add current crumb - const crumb = formatCrumb( - curPathSegment, - fileData.slug!, - (currentPath + - (includeTrailingSlash ? "/" : "")) as SimpleSlug, - ) - crumbs.push(crumb) - } - - // Add current file to crumb (can directly use frontmatter title) - if (options.showCurrentPage && slugParts.at(-1) !== "index") { - crumbs.push({ - displayName: fileData.frontmatter!.title, - path: "", - }) - } - } - - return ( - - ) + const Breadcrumbs: QuartzComponent = ({ + fileData, + allFiles, + displayClass, + }: QuartzComponentProps) => { + // Hide crumbs on root if enabled + if (options.hideOnRoot && fileData.slug === "index") { + return <> } - Breadcrumbs.css = breadcrumbsStyle - return Breadcrumbs + // Format entry for root element + const firstEntry = formatCrumb( + options.rootName, + fileData.slug!, + "/" as SimpleSlug, + ) + const crumbs: CrumbData[] = [firstEntry] + + if (!folderIndex && options.resolveFrontmatterTitle) { + folderIndex = new Map() + // construct the index for the first time + for (const file of allFiles) { + const folderParts = file.slug?.split("/") + if (folderParts?.at(-1) === "index") { + folderIndex.set(folderParts.slice(0, -1).join("/"), file) + } + } + } + + // Split slug into hierarchy/parts + const slugParts = fileData.slug?.split("/") + if (slugParts) { + // is tag breadcrumb? + const isTagPath = slugParts[0] === "tags" + + // full path until current part + let currentPath = "" + + for (let i = 0; i < slugParts.length - 1; i++) { + let curPathSegment = slugParts[i] + + // Try to resolve frontmatter folder title + const currentFile = folderIndex?.get( + slugParts.slice(0, i + 1).join("/"), + ) + if (currentFile) { + const title = currentFile.frontmatter!.title + if (title !== "index") { + curPathSegment = title + } + } + + // Add current slug to full path + currentPath = joinSegments(currentPath, slugParts[i]) + const includeTrailingSlash = !isTagPath || i < 1 + + // Format and add current crumb + const crumb = formatCrumb( + curPathSegment, + fileData.slug!, + (currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug, + ) + crumbs.push(crumb) + } + + // Add current file to crumb (can directly use frontmatter title) + if (options.showCurrentPage && slugParts.at(-1) !== "index") { + crumbs.push({ + displayName: fileData.frontmatter!.title, + path: "", + }) + } + } + + return ( + + ) + } + Breadcrumbs.css = breadcrumbsStyle + + return Breadcrumbs }) satisfies QuartzComponentConstructor diff --git a/quartz/components/ContentMeta.tsx b/quartz/components/ContentMeta.tsx index 3f0125720..8ba2a2937 100644 --- a/quartz/components/ContentMeta.tsx +++ b/quartz/components/ContentMeta.tsx @@ -1,71 +1,68 @@ -import { formatDate, getDate } from "./Date" -import { QuartzComponentConstructor, QuartzComponentProps } from "./types" +import {formatDate, getDate} from "./Date" +import {QuartzComponentConstructor, QuartzComponentProps} from "./types" import readingTime from "reading-time" -import { classNames } from "../util/lang" -import { i18n } from "../i18n" -import { JSX } from "preact" +import {classNames} from "../util/lang" +import {i18n} from "../i18n" +import {JSX} from "preact" import style from "./styles/contentMeta.scss" interface ContentMetaOptions { - /** - * Whether to display reading time - */ - showReadingTime: boolean - showComma: boolean + /** + * Whether to display reading time + */ + showReadingTime: boolean + showComma: boolean } const defaultOptions: ContentMetaOptions = { - showReadingTime: true, - showComma: true, + showReadingTime: true, + showComma: true, } export default ((opts?: Partial) => { - // Merge options with defaults - const options: ContentMetaOptions = { ...defaultOptions, ...opts } + // Merge options with defaults + const options: ContentMetaOptions = {...defaultOptions, ...opts} - function ContentMetadata({ - cfg, - fileData, - displayClass, - }: QuartzComponentProps) { - const text = fileData.text + function ContentMetadata({ + cfg, + fileData, + displayClass, + }: QuartzComponentProps) { + const text = fileData.text - if (text) { - const segments: (string | JSX.Element)[] = [] + if (text) { + const segments: (string | JSX.Element)[] = [] - if (fileData.dates) { - segments.push(formatDate(getDate(cfg, fileData)!, cfg.locale)) - } + if (fileData.dates) { + segments.push(formatDate(getDate(cfg, fileData)!, cfg.locale)) + } - // Display reading time if enabled - if (options.showReadingTime) { - const { minutes, words: _words } = readingTime(text) - const displayedTime = i18n( - cfg.locale, - ).components.contentMeta.readingTime({ - minutes: Math.ceil(minutes), - }) - segments.push(displayedTime) - } + // Display reading time if enabled + if (options.showReadingTime) { + const {minutes, words: _words} = readingTime(text) + const displayedTime = i18n( + cfg.locale, + ).components.contentMeta.readingTime({ + minutes: Math.ceil(minutes), + }) + segments.push(displayedTime) + } - const segmentsElements = segments.map((segment) => ( - {segment} - )) + const segmentsElements = segments.map((segment) => {segment}) - return ( -

- {segmentsElements} -

- ) - } else { - return null - } + return ( +

+ {segmentsElements} +

+ ) + } else { + return null } + } - ContentMetadata.css = style + ContentMetadata.css = style - return ContentMetadata + return ContentMetadata }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Darkmode.tsx b/quartz/components/Darkmode.tsx index cabf7f63f..54d1eae79 100644 --- a/quartz/components/Darkmode.tsx +++ b/quartz/components/Darkmode.tsx @@ -4,63 +4,57 @@ import darkmodeScript from "./scripts/darkmode.inline" import styles from "./styles/darkmode.scss" import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" -import { i18n } from "../i18n" -import { classNames } from "../util/lang" +import {i18n} from "../i18n" +import {classNames} from "../util/lang" const Darkmode: QuartzComponent = ({ - displayClass, - cfg, + displayClass, + cfg, }: QuartzComponentProps) => { - return ( -
- - - -
- ) + return ( +
+ + + +
+ ) } Darkmode.beforeDOMLoaded = darkmodeScript diff --git a/quartz/components/Date.tsx b/quartz/components/Date.tsx index 7bac8b749..c50ac465f 100644 --- a/quartz/components/Date.tsx +++ b/quartz/components/Date.tsx @@ -1,34 +1,34 @@ -import { GlobalConfiguration } from "../cfg" -import { ValidLocale } from "../i18n" -import { QuartzPluginData } from "../plugins/vfile" +import {GlobalConfiguration} from "../cfg" +import {ValidLocale} from "../i18n" +import {QuartzPluginData} from "../plugins/vfile" interface Props { - date: Date - locale?: ValidLocale + date: Date + locale?: ValidLocale } export type ValidDateType = keyof Required["dates"] export function getDate( - cfg: GlobalConfiguration, - data: QuartzPluginData, + cfg: GlobalConfiguration, + data: QuartzPluginData, ): Date | undefined { - if (!cfg.defaultDateType) { - throw new Error( - `Field 'defaultDateType' was not set in the configuration object of quartz.config.ts. See https://quartz.jzhao.xyz/configuration#general-configuration for more details.`, - ) - } - return data.dates?.[cfg.defaultDateType] + if (!cfg.defaultDateType) { + throw new Error( + `Field 'defaultDateType' was not set in the configuration object of quartz.config.ts. See https://quartz.jzhao.xyz/configuration#general-configuration for more details.`, + ) + } + return data.dates?.[cfg.defaultDateType] } export function formatDate(d: Date, locale: ValidLocale = "en-US"): string { - return d.toLocaleDateString(locale, { - year: "numeric", - month: "short", - day: "2-digit", - }) + return d.toLocaleDateString(locale, { + year: "numeric", + month: "short", + day: "2-digit", + }) } -export function Date({ date, locale }: Props) { - return <>{formatDate(date, locale)} +export function Date({date, locale}: Props) { + return <>{formatDate(date, locale)} } diff --git a/quartz/components/DesktopOnly.tsx b/quartz/components/DesktopOnly.tsx index 1ca12869a..a05ebfffd 100644 --- a/quartz/components/DesktopOnly.tsx +++ b/quartz/components/DesktopOnly.tsx @@ -1,22 +1,22 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" export default ((component?: QuartzComponent) => { - if (component) { - const Component = component - const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => { - return - } - - DesktopOnly.displayName = component.displayName - DesktopOnly.afterDOMLoaded = component?.afterDOMLoaded - DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded - DesktopOnly.css = component?.css - return DesktopOnly - } else { - return () => <> + if (component) { + const Component = component + const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => { + return } + + DesktopOnly.displayName = component.displayName + DesktopOnly.afterDOMLoaded = component?.afterDOMLoaded + DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded + DesktopOnly.css = component?.css + return DesktopOnly + } else { + return () => <> + } }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 3df93ddde..54154e48b 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -1,137 +1,128 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" import explorerStyle from "./styles/explorer.scss" // @ts-ignore import script from "./scripts/explorer.inline" -import { ExplorerNode, FileNode, Options } from "./ExplorerNode" -import { QuartzPluginData } from "../plugins/vfile" -import { classNames } from "../util/lang" -import { i18n } from "../i18n" +import {ExplorerNode, FileNode, Options} from "./ExplorerNode" +import {QuartzPluginData} from "../plugins/vfile" +import {classNames} from "../util/lang" +import {i18n} from "../i18n" // Options interface defined in `ExplorerNode` to avoid circular dependency const defaultOptions = { - folderClickBehavior: "collapse", - folderDefaultState: "collapsed", - useSavedState: true, - mapFn: (node) => { - return node - }, - sortFn: (a, b) => { - // Sort order: folders first, then files. Sort folders and files alphabetically - if ((!a.file && !b.file) || (a.file && b.file)) { - // numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10" - // sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A - return a.displayName.localeCompare(b.displayName, undefined, { - numeric: true, - sensitivity: "base", - }) - } + folderClickBehavior: "collapse", + folderDefaultState: "collapsed", + useSavedState: true, + mapFn: (node) => { + return node + }, + sortFn: (a, b) => { + // Sort order: folders first, then files. Sort folders and files alphabetically + if ((!a.file && !b.file) || (a.file && b.file)) { + // numeric: true: Whether numeric collation should be used, such that "1" < "2" < "10" + // sensitivity: "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A + return a.displayName.localeCompare(b.displayName, undefined, { + numeric: true, + sensitivity: "base", + }) + } - if (a.file && !b.file) { - return 1 - } else { - return -1 - } - }, - filterFn: (node) => node.name !== "tags", - order: ["filter", "map", "sort"], + if (a.file && !b.file) { + return 1 + } else { + return -1 + } + }, + filterFn: (node) => node.name !== "tags", + order: ["filter", "map", "sort"], } satisfies Options export default ((userOpts?: Partial) => { - // Parse config - const opts: Options = { ...defaultOptions, ...userOpts } + // Parse config + const opts: Options = {...defaultOptions, ...userOpts} - // memoized - let fileTree: FileNode - let jsonTree: string + // memoized + let fileTree: FileNode + let jsonTree: string - function constructFileTree(allFiles: QuartzPluginData[]) { - if (fileTree) { - return - } - - // Construct tree from allFiles - fileTree = new FileNode("") - allFiles.forEach((file) => fileTree.add(file)) - - // Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied) - if (opts.order) { - // Order is important, use loop with index instead of order.map() - for (let i = 0; i < opts.order.length; i++) { - const functionName = opts.order[i] - if (functionName === "map") { - fileTree.map(opts.mapFn) - } else if (functionName === "sort") { - fileTree.sort(opts.sortFn) - } else if (functionName === "filter") { - fileTree.filter(opts.filterFn) - } - } - } - - // Get all folders of tree. Initialize with collapsed state - // Stringify to pass json tree as data attribute ([data-tree]) - const folders = fileTree.getFolderPaths( - opts.folderDefaultState === "collapsed", - ) - jsonTree = JSON.stringify(folders) + function constructFileTree(allFiles: QuartzPluginData[]) { + if (fileTree) { + return } - const Explorer: QuartzComponent = ({ - cfg, - allFiles, - displayClass, - fileData, - }: QuartzComponentProps) => { - constructFileTree(allFiles) - return ( -
- -
-
    - -
  • -
-
-
- ) + // Construct tree from allFiles + fileTree = new FileNode("") + allFiles.forEach((file) => fileTree.add(file)) + + // Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied) + if (opts.order) { + // Order is important, use loop with index instead of order.map() + for (let i = 0; i < opts.order.length; i++) { + const functionName = opts.order[i] + if (functionName === "map") { + fileTree.map(opts.mapFn) + } else if (functionName === "sort") { + fileTree.sort(opts.sortFn) + } else if (functionName === "filter") { + fileTree.filter(opts.filterFn) + } + } } - Explorer.css = explorerStyle - Explorer.afterDOMLoaded = script - return Explorer + // Get all folders of tree. Initialize with collapsed state + // Stringify to pass json tree as data attribute ([data-tree]) + const folders = fileTree.getFolderPaths( + opts.folderDefaultState === "collapsed", + ) + jsonTree = JSON.stringify(folders) + } + + const Explorer: QuartzComponent = ({ + cfg, + allFiles, + displayClass, + fileData, + }: QuartzComponentProps) => { + constructFileTree(allFiles) + return ( +
+ +
+
    + +
  • +
+
+
+ ) + } + + Explorer.css = explorerStyle + Explorer.afterDOMLoaded = script + return Explorer }) satisfies QuartzComponentConstructor diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index 8b2e7ca47..819f29a58 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -1,279 +1,266 @@ // @ts-ignore -import { QuartzPluginData } from "../plugins/vfile" +import {QuartzPluginData} from "../plugins/vfile" import { - joinSegments, - resolveRelative, - clone, - simplifySlug, - SimpleSlug, - FilePath, + joinSegments, + resolveRelative, + clone, + simplifySlug, + SimpleSlug, + FilePath, } from "../util/path" type OrderEntries = "sort" | "filter" | "map" export interface Options { - title?: string - folderDefaultState: "collapsed" | "open" - folderClickBehavior: "collapse" | "link" - useSavedState: boolean - sortFn: (a: FileNode, b: FileNode) => number - filterFn: (node: FileNode) => boolean - mapFn: (node: FileNode) => void - order: OrderEntries[] + title?: string + folderDefaultState: "collapsed" | "open" + folderClickBehavior: "collapse" | "link" + useSavedState: boolean + sortFn: (a: FileNode, b: FileNode) => number + filterFn: (node: FileNode) => boolean + mapFn: (node: FileNode) => void + order: OrderEntries[] } type DataWrapper = { - file: QuartzPluginData - path: string[] + file: QuartzPluginData + path: string[] } export type FolderState = { - path: string - collapsed: boolean + path: string + collapsed: boolean } function getPathSegment( - fp: FilePath | undefined, - idx: number, + fp: FilePath | undefined, + idx: number, ): string | undefined { - if (!fp) { - return undefined - } + if (!fp) { + return undefined + } - return fp.split("/").at(idx) + return fp.split("/").at(idx) } // Structure to add all files into a tree export class FileNode { - children: Array - name: string // this is the slug segment - displayName: string - file: QuartzPluginData | null - depth: number + children: Array + name: string // this is the slug segment + displayName: string + file: QuartzPluginData | null + depth: number - constructor( - slugSegment: string, - displayName?: string, - file?: QuartzPluginData, - depth?: number, - ) { - this.children = [] - this.name = slugSegment - this.displayName = - displayName ?? file?.frontmatter?.title ?? slugSegment - this.file = file ? clone(file) : null - this.depth = depth ?? 0 + constructor( + slugSegment: string, + displayName?: string, + file?: QuartzPluginData, + depth?: number, + ) { + this.children = [] + this.name = slugSegment + this.displayName = displayName ?? file?.frontmatter?.title ?? slugSegment + this.file = file ? clone(file) : null + this.depth = depth ?? 0 + } + + private insert(fileData: DataWrapper) { + if (fileData.path.length === 0) { + return } - private insert(fileData: DataWrapper) { - if (fileData.path.length === 0) { - return + const nextSegment = fileData.path[0] + + // base case, insert here + if (fileData.path.length === 1) { + if (nextSegment === "") { + // index case (we are the root and we just found index.md), set our data appropriately + const title = fileData.file.frontmatter?.title + if (title && title !== "index") { + this.displayName = title } - - const nextSegment = fileData.path[0] - - // base case, insert here - if (fileData.path.length === 1) { - if (nextSegment === "") { - // index case (we are the root and we just found index.md), set our data appropriately - const title = fileData.file.frontmatter?.title - if (title && title !== "index") { - this.displayName = title - } - } else { - // direct child - this.children.push( - new FileNode( - nextSegment, - undefined, - fileData.file, - this.depth + 1, - ), - ) - } - - return - } - - // find the right child to insert into - fileData.path = fileData.path.splice(1) - const child = this.children.find((c) => c.name === nextSegment) - if (child) { - child.insert(fileData) - return - } - - const newChild = new FileNode( - nextSegment, - getPathSegment(fileData.file.relativePath, this.depth), - undefined, - this.depth + 1, + } else { + // direct child + this.children.push( + new FileNode(nextSegment, undefined, fileData.file, this.depth + 1), ) - newChild.insert(fileData) - this.children.push(newChild) + } + + return } - // Add new file to tree - add(file: QuartzPluginData) { - this.insert({ file: file, path: simplifySlug(file.slug!).split("/") }) + // find the right child to insert into + fileData.path = fileData.path.splice(1) + const child = this.children.find((c) => c.name === nextSegment) + if (child) { + child.insert(fileData) + return } - /** - * Filter FileNode tree. Behaves similar to `Array.prototype.filter()`, but modifies tree in place - * @param filterFn function to filter tree with - */ - filter(filterFn: (node: FileNode) => boolean) { - this.children = this.children.filter(filterFn) - this.children.forEach((child) => child.filter(filterFn)) - } + const newChild = new FileNode( + nextSegment, + getPathSegment(fileData.file.relativePath, this.depth), + undefined, + this.depth + 1, + ) + newChild.insert(fileData) + this.children.push(newChild) + } - /** - * Filter FileNode tree. Behaves similar to `Array.prototype.map()`, but modifies tree in place - * @param mapFn function to use for mapping over tree - */ - map(mapFn: (node: FileNode) => void) { - mapFn(this) - this.children.forEach((child) => child.map(mapFn)) - } + // Add new file to tree + add(file: QuartzPluginData) { + this.insert({file: file, path: simplifySlug(file.slug!).split("/")}) + } - /** - * Get folder representation with state of tree. - * Intended to only be called on root node before changes to the tree are made - * @param collapsed default state of folders (collapsed by default or not) - * @returns array containing folder state for tree - */ - getFolderPaths(collapsed: boolean): FolderState[] { - const folderPaths: FolderState[] = [] + /** + * Filter FileNode tree. Behaves similar to `Array.prototype.filter()`, but modifies tree in place + * @param filterFn function to filter tree with + */ + filter(filterFn: (node: FileNode) => boolean) { + this.children = this.children.filter(filterFn) + this.children.forEach((child) => child.filter(filterFn)) + } - const traverse = (node: FileNode, currentPath: string) => { - if (!node.file) { - const folderPath = joinSegments(currentPath, node.name) - if (folderPath !== "") { - folderPaths.push({ path: folderPath, collapsed }) - } + /** + * Filter FileNode tree. Behaves similar to `Array.prototype.map()`, but modifies tree in place + * @param mapFn function to use for mapping over tree + */ + map(mapFn: (node: FileNode) => void) { + mapFn(this) + this.children.forEach((child) => child.map(mapFn)) + } - node.children.forEach((child) => traverse(child, folderPath)) - } + /** + * Get folder representation with state of tree. + * Intended to only be called on root node before changes to the tree are made + * @param collapsed default state of folders (collapsed by default or not) + * @returns array containing folder state for tree + */ + getFolderPaths(collapsed: boolean): FolderState[] { + const folderPaths: FolderState[] = [] + + const traverse = (node: FileNode, currentPath: string) => { + if (!node.file) { + const folderPath = joinSegments(currentPath, node.name) + if (folderPath !== "") { + folderPaths.push({path: folderPath, collapsed}) } - traverse(this, "") - return folderPaths + node.children.forEach((child) => traverse(child, folderPath)) + } } - // Sort order: folders first, then files. Sort folders and files alphabetically - /** - * Sorts tree according to sort/compare function - * @param sortFn compare function used for `.sort()`, also used recursively for children - */ - sort(sortFn: (a: FileNode, b: FileNode) => number) { - this.children = this.children.sort(sortFn) - this.children.forEach((e) => e.sort(sortFn)) - } + traverse(this, "") + return folderPaths + } + + // Sort order: folders first, then files. Sort folders and files alphabetically + /** + * Sorts tree according to sort/compare function + * @param sortFn compare function used for `.sort()`, also used recursively for children + */ + sort(sortFn: (a: FileNode, b: FileNode) => number) { + this.children = this.children.sort(sortFn) + this.children.forEach((e) => e.sort(sortFn)) + } } type ExplorerNodeProps = { - node: FileNode - opts: Options - fileData: QuartzPluginData - fullPath?: string + node: FileNode + opts: Options + fileData: QuartzPluginData + fullPath?: string } export function ExplorerNode({ - node, - opts, - fullPath, - fileData, + node, + opts, + fullPath, + fileData, }: ExplorerNodeProps) { - // Get options - const folderBehavior = opts.folderClickBehavior - const isDefaultOpen = opts.folderDefaultState === "open" + // Get options + const folderBehavior = opts.folderClickBehavior + const isDefaultOpen = opts.folderDefaultState === "open" - // Calculate current folderPath - let folderPath = "" - if (node.name !== "") { - folderPath = joinSegments(fullPath ?? "", node.name) - } + // Calculate current folderPath + let folderPath = "" + if (node.name !== "") { + folderPath = joinSegments(fullPath ?? "", node.name) + } - return ( - <> - {node.file ? ( - // Single file node -
  • - - {node.displayName} - -
  • - ) : ( -
  • - {node.name !== "" && ( - // Node with entire folder - // Render svg button + folder name, then children - - + return ( + <> + {node.file ? ( + // Single file node +
  • + + {node.displayName} + +
  • + ) : ( +
  • + {node.name !== "" && ( + // Node with entire folder + // Render svg button + folder name, then children +
  • - )} - - ) + data-for={node.name} + class="folder-title"> + {node.displayName} + + ) : ( + + )} + + + )} + {/* Recursively render children of folder */} +
    +
      + {node.children.map((childNode, i) => ( + + ))} +
    +
    + + )} + + ) } diff --git a/quartz/components/Footer.tsx b/quartz/components/Footer.tsx index d1befe105..838548fab 100644 --- a/quartz/components/Footer.tsx +++ b/quartz/components/Footer.tsx @@ -1,41 +1,41 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" import style from "./styles/footer.scss" -import { version } from "../../package.json" -import { i18n } from "../i18n" +import {version} from "../../package.json" +import {i18n} from "../i18n" interface Options { - links: Record + links: Record } export default ((opts?: Options) => { - const Footer: QuartzComponent = ({ - displayClass, - cfg, - }: QuartzComponentProps) => { - const year = new Date().getFullYear() - const links = opts?.links ?? [] - return ( -
    -
    -

    - © {year} Miguel Pimentel · Created with{" "} - Quartz. -

    -
      - {Object.entries(links).map(([text, link]) => ( -
    • - {text} -
    • - ))} -
    -
    - ) - } + const Footer: QuartzComponent = ({ + displayClass, + cfg, + }: QuartzComponentProps) => { + const year = new Date().getFullYear() + const links = opts?.links ?? [] + return ( +
    +
    +

    + © {year} Miguel Pimentel · Created with{" "} + Quartz. +

    +
      + {Object.entries(links).map(([text, link]) => ( +
    • + {text} +
    • + ))} +
    +
    + ) + } - Footer.css = style - return Footer + Footer.css = style + return Footer }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Graph.tsx b/quartz/components/Graph.tsx index a1c8447ac..0ad8c9e11 100644 --- a/quartz/components/Graph.tsx +++ b/quartz/components/Graph.tsx @@ -1,96 +1,92 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" // @ts-ignore import script from "./scripts/graph.inline" import style from "./styles/graph.scss" -import { i18n } from "../i18n" -import { classNames } from "../util/lang" +import {i18n} from "../i18n" +import {classNames} from "../util/lang" export interface D3Config { - drag: boolean - zoom: boolean - depth: number - scale: number - repelForce: number - centerForce: number - linkDistance: number - fontSize: number - opacityScale: number - removeTags: string[] - showTags: boolean - focusOnHover?: boolean + drag: boolean + zoom: boolean + depth: number + scale: number + repelForce: number + centerForce: number + linkDistance: number + fontSize: number + opacityScale: number + removeTags: string[] + showTags: boolean + focusOnHover?: boolean } interface GraphOptions { - localGraph: Partial | undefined - globalGraph: Partial | undefined + localGraph: Partial | undefined + globalGraph: Partial | undefined } const defaultOptions: GraphOptions = { - localGraph: { - drag: true, - zoom: true, - depth: 1, - scale: 1.1, - repelForce: 0.5, - centerForce: 0.3, - linkDistance: 30, - fontSize: 0.6, - opacityScale: 1, - showTags: true, - removeTags: [], - focusOnHover: false, - }, - globalGraph: { - drag: true, - zoom: true, - depth: -1, - scale: 0.9, - repelForce: 0.5, - centerForce: 0.3, - linkDistance: 30, - fontSize: 0.6, - opacityScale: 1, - showTags: true, - removeTags: [], - focusOnHover: true, - }, + localGraph: { + drag: true, + zoom: true, + depth: 1, + scale: 1.1, + repelForce: 0.5, + centerForce: 0.3, + linkDistance: 30, + fontSize: 0.6, + opacityScale: 1, + showTags: true, + removeTags: [], + focusOnHover: false, + }, + globalGraph: { + drag: true, + zoom: true, + depth: -1, + scale: 0.9, + repelForce: 0.5, + centerForce: 0.3, + linkDistance: 30, + fontSize: 0.6, + opacityScale: 1, + showTags: true, + removeTags: [], + focusOnHover: true, + }, } export default ((opts?: GraphOptions) => { - const Graph: QuartzComponent = ({ - displayClass, - cfg, - }: QuartzComponentProps) => { - const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph } - const globalGraph = { - ...defaultOptions.globalGraph, - ...opts?.globalGraph, - } - return ( -
    -

    {i18n(cfg.locale).components.graph.title}

    -
    -
    - - +

    {i18n(cfg.locale).components.graph.title}

    +
    +
    + + - -
    -
    -
    -
    -
    - ) - } + /> + +
    +
    +
    +
    + + ) + } - Graph.css = style - Graph.afterDOMLoaded = script + Graph.css = style + Graph.afterDOMLoaded = script - return Graph + return Graph }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index 6d7b37070..7fbe6dcf6 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -1,87 +1,68 @@ -import { i18n } from "../i18n" -import { FullSlug, joinSegments, pathToRoot } from "../util/path" -import { JSResourceToScriptElement } from "../util/resources" -import { googleFontHref } from "../util/theme" +import {i18n} from "../i18n" +import {FullSlug, joinSegments, pathToRoot} from "../util/path" +import {JSResourceToScriptElement} from "../util/resources" +import {googleFontHref} from "../util/theme" import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" export default (() => { - const Head: QuartzComponent = ({ - cfg, - fileData, - externalResources, - }: QuartzComponentProps) => { - const title = - fileData.frontmatter?.title ?? - i18n(cfg.locale).propertyDefaults.title - const description = - fileData.description?.trim() ?? - i18n(cfg.locale).propertyDefaults.description - const { css, js } = externalResources + const Head: QuartzComponent = ({ + cfg, + fileData, + externalResources, + }: QuartzComponentProps) => { + const title = + fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title + const description = + fileData.description?.trim() ?? + i18n(cfg.locale).propertyDefaults.description + const {css, js} = externalResources - const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) - const path = url.pathname as FullSlug - const baseDir = - fileData.slug === "404" ? path : pathToRoot(fileData.slug!) + const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) + const path = url.pathname as FullSlug + const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!) - const iconPath = joinSegments(baseDir, "static/icon.png") - const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png` + const iconPath = joinSegments(baseDir, "static/icon.png") + const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png` - return ( - - {title} - - {cfg.theme.cdnCaching && - cfg.theme.fontOrigin === "googleFonts" && ( - <> - - - - - )} - - - - {cfg.baseUrl && ( - - )} - - - - - - {css.map((href) => ( - - ))} - {js - .filter( - (resource) => resource.loadTime === "beforeDOMReady", - ) - .map((res) => JSResourceToScriptElement(res, true))} - - ) - } + return ( + + {title} + + {cfg.theme.cdnCaching && cfg.theme.fontOrigin === "googleFonts" && ( + <> + + + + + )} + + + + {cfg.baseUrl && } + + + + + + {css.map((href) => ( + + ))} + {js + .filter((resource) => resource.loadTime === "beforeDOMReady") + .map((res) => JSResourceToScriptElement(res, true))} + + ) + } - return Head + return Head }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Header.tsx b/quartz/components/Header.tsx index edd4cc6c1..e8bdeb91b 100644 --- a/quartz/components/Header.tsx +++ b/quartz/components/Header.tsx @@ -1,11 +1,11 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" -const Header: QuartzComponent = ({ children }: QuartzComponentProps) => { - return children.length > 0 ?
    {children}
    : null +const Header: QuartzComponent = ({children}: QuartzComponentProps) => { + return children.length > 0 ?
    {children}
    : null } Header.css = ` diff --git a/quartz/components/MobileOnly.tsx b/quartz/components/MobileOnly.tsx index 56a564baa..3f0113104 100644 --- a/quartz/components/MobileOnly.tsx +++ b/quartz/components/MobileOnly.tsx @@ -1,22 +1,22 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" export default ((component?: QuartzComponent) => { - if (component) { - const Component = component - const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => { - return - } - - MobileOnly.displayName = component.displayName - MobileOnly.afterDOMLoaded = component?.afterDOMLoaded - MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded - MobileOnly.css = component?.css - return MobileOnly - } else { - return () => <> + if (component) { + const Component = component + const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => { + return } + + MobileOnly.displayName = component.displayName + MobileOnly.afterDOMLoaded = component?.afterDOMLoaded + MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded + MobileOnly.css = component?.css + return MobileOnly + } else { + return () => <> + } }) satisfies QuartzComponentConstructor diff --git a/quartz/components/PageList.tsx b/quartz/components/PageList.tsx index 1c1ff8fd8..9a48a038a 100644 --- a/quartz/components/PageList.tsx +++ b/quartz/components/PageList.tsx @@ -1,96 +1,88 @@ -import { FullSlug, resolveRelative } from "../util/path" -import { QuartzPluginData } from "../plugins/vfile" -import { Date, getDate } from "./Date" -import { QuartzComponent, QuartzComponentProps } from "./types" -import { GlobalConfiguration } from "../cfg" +import {FullSlug, resolveRelative} from "../util/path" +import {QuartzPluginData} from "../plugins/vfile" +import {Date, getDate} from "./Date" +import {QuartzComponent, QuartzComponentProps} from "./types" +import {GlobalConfiguration} from "../cfg" export function byDateAndAlphabetical( - cfg: GlobalConfiguration, + cfg: GlobalConfiguration, ): (f1: QuartzPluginData, f2: QuartzPluginData) => number { - return (f1, f2) => { - if (f1.dates && f2.dates) { - // sort descending - return getDate(cfg, f2)!.getTime() - getDate(cfg, f1)!.getTime() - } else if (f1.dates && !f2.dates) { - // prioritize files with dates - return -1 - } else if (!f1.dates && f2.dates) { - return 1 - } - - // otherwise, sort lexographically by title - const f1Title = f1.frontmatter?.title.toLowerCase() ?? "" - const f2Title = f2.frontmatter?.title.toLowerCase() ?? "" - return f1Title.localeCompare(f2Title) + return (f1, f2) => { + if (f1.dates && f2.dates) { + // sort descending + return getDate(cfg, f2)!.getTime() - getDate(cfg, f1)!.getTime() + } else if (f1.dates && !f2.dates) { + // prioritize files with dates + return -1 + } else if (!f1.dates && f2.dates) { + return 1 } + + // otherwise, sort lexographically by title + const f1Title = f1.frontmatter?.title.toLowerCase() ?? "" + const f2Title = f2.frontmatter?.title.toLowerCase() ?? "" + return f1Title.localeCompare(f2Title) + } } type Props = { - limit?: number + limit?: number } & QuartzComponentProps export const PageList: QuartzComponent = ({ - cfg, - fileData, - allFiles, - limit, + cfg, + fileData, + allFiles, + limit, }: Props) => { - let list = allFiles.sort(byDateAndAlphabetical(cfg)) - if (limit) { - list = list.slice(0, limit) - } + let list = allFiles.sort(byDateAndAlphabetical(cfg)) + if (limit) { + list = list.slice(0, limit) + } - return ( -
      - {list.map((page) => { - const title = page.frontmatter?.title - const tags = page.frontmatter?.tags ?? [] + return ( +
        + {list.map((page) => { + const title = page.frontmatter?.title + const tags = page.frontmatter?.tags ?? [] - return ( -
      • -
        - {page.dates && ( -

        - -

        - )} - - -
        -
      • - ) - })} -
      - ) + return ( +
    • +
      + {page.dates && ( +

      + +

      + )} + + +
      +
    • + ) + })} +
    + ) } PageList.css = ` diff --git a/quartz/components/PageTitle.tsx b/quartz/components/PageTitle.tsx index c9bc3a5e2..00165bd05 100644 --- a/quartz/components/PageTitle.tsx +++ b/quartz/components/PageTitle.tsx @@ -1,24 +1,24 @@ -import { pathToRoot } from "../util/path" +import {pathToRoot} from "../util/path" import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" -import { classNames } from "../util/lang" -import { i18n } from "../i18n" +import {classNames} from "../util/lang" +import {i18n} from "../i18n" const PageTitle: QuartzComponent = ({ - fileData, - cfg, - displayClass, + fileData, + cfg, + displayClass, }: QuartzComponentProps) => { - const title = cfg?.pageTitle ?? i18n(cfg.locale).propertyDefaults.title - const baseDir = pathToRoot(fileData.slug!) - return ( -

    - {title} -

    - ) + const title = cfg?.pageTitle ?? i18n(cfg.locale).propertyDefaults.title + const baseDir = pathToRoot(fileData.slug!) + return ( +

    + {title} +

    + ) } PageTitle.css = ` diff --git a/quartz/components/RecentNotes.tsx b/quartz/components/RecentNotes.tsx index b10bcde30..e18d1875d 100644 --- a/quartz/components/RecentNotes.tsx +++ b/quartz/components/RecentNotes.tsx @@ -1,119 +1,100 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" -import { FullSlug, SimpleSlug, resolveRelative } from "../util/path" -import { QuartzPluginData } from "../plugins/vfile" -import { byDateAndAlphabetical } from "./PageList" +import {FullSlug, SimpleSlug, resolveRelative} from "../util/path" +import {QuartzPluginData} from "../plugins/vfile" +import {byDateAndAlphabetical} from "./PageList" import style from "./styles/recentNotes.scss" -import { Date, getDate } from "./Date" -import { GlobalConfiguration } from "../cfg" -import { i18n } from "../i18n" -import { classNames } from "../util/lang" +import {Date, getDate} from "./Date" +import {GlobalConfiguration} from "../cfg" +import {i18n} from "../i18n" +import {classNames} from "../util/lang" interface Options { - title?: string - limit: number - linkToMore: SimpleSlug | false - filter: (f: QuartzPluginData) => boolean - sort: (f1: QuartzPluginData, f2: QuartzPluginData) => number + title?: string + limit: number + linkToMore: SimpleSlug | false + filter: (f: QuartzPluginData) => boolean + sort: (f1: QuartzPluginData, f2: QuartzPluginData) => number } const defaultOptions = (cfg: GlobalConfiguration): Options => ({ - limit: 3, - linkToMore: false, - filter: () => true, - sort: byDateAndAlphabetical(cfg), + limit: 3, + linkToMore: false, + filter: () => true, + sort: byDateAndAlphabetical(cfg), }) export default ((userOpts?: Partial) => { - const RecentNotes: QuartzComponent = ({ - allFiles, - fileData, - displayClass, - cfg, - }: QuartzComponentProps) => { - const opts = { ...defaultOptions(cfg), ...userOpts } - const pages = allFiles.filter(opts.filter).sort(opts.sort) - const remaining = Math.max(0, pages.length - opts.limit) - return ( -
    -

    - {opts.title ?? - i18n(cfg.locale).components.recentNotes.title} -

    -
      - {pages.slice(0, opts.limit).map((page) => { - const title = - page.frontmatter?.title ?? - i18n(cfg.locale).propertyDefaults.title - const tags = page.frontmatter?.tags ?? [] + const RecentNotes: QuartzComponent = ({ + allFiles, + fileData, + displayClass, + cfg, + }: QuartzComponentProps) => { + const opts = {...defaultOptions(cfg), ...userOpts} + const pages = allFiles.filter(opts.filter).sort(opts.sort) + const remaining = Math.max(0, pages.length - opts.limit) + return ( +
      +

      {opts.title ?? i18n(cfg.locale).components.recentNotes.title}

      +
        + {pages.slice(0, opts.limit).map((page) => { + const title = + page.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title + const tags = page.frontmatter?.tags ?? [] - return ( -
      • -
        - - {page.dates && ( -

        - -

        - )} - -
        -
      • - ) - })} -
      - {opts.linkToMore && remaining > 0 && ( -

      - - {i18n( - cfg.locale, - ).components.recentNotes.seeRemainingMore({ - remaining, - })} - + return ( +

    • +
      + + {page.dates && ( +

      +

      - )} -
      - ) - } + )} + +
    • + + ) + })} +
    + {opts.linkToMore && remaining > 0 && ( +

    + + {i18n(cfg.locale).components.recentNotes.seeRemainingMore({ + remaining, + })} + +

    + )} +
    + ) + } - RecentNotes.css = style - return RecentNotes + RecentNotes.css = style + return RecentNotes }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Search.tsx b/quartz/components/Search.tsx index e2d380068..eb95b89e9 100644 --- a/quartz/components/Search.tsx +++ b/quartz/components/Search.tsx @@ -1,75 +1,68 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" import style from "./styles/search.scss" // @ts-ignore import script from "./scripts/search.inline" -import { classNames } from "../util/lang" -import { i18n } from "../i18n" +import {classNames} from "../util/lang" +import {i18n} from "../i18n" export interface SearchOptions { - enablePreview: boolean + enablePreview: boolean } const defaultOptions: SearchOptions = { - enablePreview: true, + enablePreview: true, } export default ((userOpts?: Partial) => { - const Search: QuartzComponent = ({ - displayClass, - cfg, - }: QuartzComponentProps) => { - const opts = { ...defaultOptions, ...userOpts } - const searchPlaceholder = i18n(cfg.locale).components.search - .searchBarPlaceholder - return ( -
    -
    - {/*

    Search

    */} -
    - - Search - Search - - - - - -
    -
    -
    - -
    -
    -
    -
    - ) - } + const Search: QuartzComponent = ({ + displayClass, + cfg, + }: QuartzComponentProps) => { + const opts = {...defaultOptions, ...userOpts} + const searchPlaceholder = i18n(cfg.locale).components.search + .searchBarPlaceholder + return ( +
    +
    + {/*

    Search

    */} +
    + + Search + Search + + + + + +
    +
    +
    + +
    +
    +
    +
    + ) + } - Search.afterDOMLoaded = script - Search.css = style + Search.afterDOMLoaded = script + Search.css = style - return Search + return Search }) satisfies QuartzComponentConstructor diff --git a/quartz/components/Spacer.tsx b/quartz/components/Spacer.tsx index a4b167c11..eca1cd177 100644 --- a/quartz/components/Spacer.tsx +++ b/quartz/components/Spacer.tsx @@ -1,8 +1,8 @@ -import { QuartzComponentConstructor, QuartzComponentProps } from "./types" -import { classNames } from "../util/lang" +import {QuartzComponentConstructor, QuartzComponentProps} from "./types" +import {classNames} from "../util/lang" -function Spacer({ displayClass }: QuartzComponentProps) { - return
    +function Spacer({displayClass}: QuartzComponentProps) { + return
    } export default (() => Spacer) satisfies QuartzComponentConstructor diff --git a/quartz/components/TableOfContents.tsx b/quartz/components/TableOfContents.tsx index be5c4c4f5..574484f0e 100644 --- a/quartz/components/TableOfContents.tsx +++ b/quartz/components/TableOfContents.tsx @@ -1,106 +1,98 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" import legacyStyle from "./styles/legacyToc.scss" import modernStyle from "./styles/toc.scss" -import { classNames } from "../util/lang" +import {classNames} from "../util/lang" // @ts-ignore import script from "./scripts/toc.inline" -import { i18n } from "../i18n" +import {i18n} from "../i18n" interface Options { - layout: "modern" | "legacy" + layout: "modern" | "legacy" } const defaultOptions: Options = { - layout: "modern", + layout: "modern", } const TableOfContents: QuartzComponent = ({ - fileData, - displayClass, - cfg, + fileData, + displayClass, + cfg, }: QuartzComponentProps) => { - if (!fileData.toc) { - return null - } + if (!fileData.toc) { + return null + } - return ( -
    - -
    - -
    -
    - ) + return ( +
    + +
    + +
    +
    + ) } TableOfContents.css = modernStyle TableOfContents.afterDOMLoaded = script const LegacyTableOfContents: QuartzComponent = ({ - fileData, - cfg, + fileData, + cfg, }: QuartzComponentProps) => { - if (!fileData.toc) { - return null - } - return ( -
    - -

    {i18n(cfg.locale).components.tableOfContents.title}

    -
    - -
    - ) + if (!fileData.toc) { + return null + } + return ( +
    + +

    {i18n(cfg.locale).components.tableOfContents.title}

    +
    + +
    + ) } LegacyTableOfContents.css = legacyStyle export default ((opts?: Partial) => { - const layout = opts?.layout ?? defaultOptions.layout - return layout === "modern" ? TableOfContents : LegacyTableOfContents + const layout = opts?.layout ?? defaultOptions.layout + return layout === "modern" ? TableOfContents : LegacyTableOfContents }) satisfies QuartzComponentConstructor diff --git a/quartz/components/TagList.tsx b/quartz/components/TagList.tsx index 1148d704f..a66ae8004 100644 --- a/quartz/components/TagList.tsx +++ b/quartz/components/TagList.tsx @@ -1,35 +1,35 @@ -import { pathToRoot, slugTag } from "../util/path" +import {pathToRoot, slugTag} from "../util/path" import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "./types" -import { classNames } from "../util/lang" +import {classNames} from "../util/lang" const TagList: QuartzComponent = ({ - fileData, - displayClass, + fileData, + displayClass, }: QuartzComponentProps) => { - const tags = fileData.frontmatter?.tags - const baseDir = pathToRoot(fileData.slug!) - if (tags && tags.length > 0) { - return ( -
      - {tags.map((tag) => { - const linkDest = baseDir + `/tags/${slugTag(tag)}` - return ( -
    • - - {tag} - -
    • - ) - })} -
    - ) - } else { - return null - } + const tags = fileData.frontmatter?.tags + const baseDir = pathToRoot(fileData.slug!) + if (tags && tags.length > 0) { + return ( +
      + {tags.map((tag) => { + const linkDest = baseDir + `/tags/${slugTag(tag)}` + return ( +
    • + + {tag} + +
    • + ) + })} +
    + ) + } else { + return null + } } TagList.css = ` diff --git a/quartz/components/index.ts b/quartz/components/index.ts index 37a471360..b3db76bed 100644 --- a/quartz/components/index.ts +++ b/quartz/components/index.ts @@ -21,25 +21,25 @@ import RecentNotes from "./RecentNotes" import Breadcrumbs from "./Breadcrumbs" export { - ArticleTitle, - Content, - TagContent, - FolderContent, - Darkmode, - Head, - PageTitle, - ContentMeta, - Spacer, - TableOfContents, - Explorer, - TagList, - Graph, - Backlinks, - Search, - Footer, - DesktopOnly, - MobileOnly, - RecentNotes, - NotFound, - Breadcrumbs, + ArticleTitle, + Content, + TagContent, + FolderContent, + Darkmode, + Head, + PageTitle, + ContentMeta, + Spacer, + TableOfContents, + Explorer, + TagList, + Graph, + Backlinks, + Search, + Footer, + DesktopOnly, + MobileOnly, + RecentNotes, + NotFound, + Breadcrumbs, } diff --git a/quartz/components/pages/404.tsx b/quartz/components/pages/404.tsx index a8331bdae..a6c046f9f 100644 --- a/quartz/components/pages/404.tsx +++ b/quartz/components/pages/404.tsx @@ -1,17 +1,17 @@ -import { i18n } from "../../i18n" +import {i18n} from "../../i18n" import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "../types" -const NotFound: QuartzComponent = ({ cfg }: QuartzComponentProps) => { - return ( -
    -

    404

    -

    {i18n(cfg.locale).pages.error.notFound}

    -
    - ) +const NotFound: QuartzComponent = ({cfg}: QuartzComponentProps) => { + return ( +
    +

    404

    +

    {i18n(cfg.locale).pages.error.notFound}

    +
    + ) } export default (() => NotFound) satisfies QuartzComponentConstructor diff --git a/quartz/components/pages/Content.tsx b/quartz/components/pages/Content.tsx index 0910af239..898307d01 100644 --- a/quartz/components/pages/Content.tsx +++ b/quartz/components/pages/Content.tsx @@ -1,15 +1,15 @@ -import { htmlToJsx } from "../../util/jsx" +import {htmlToJsx} from "../../util/jsx" import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "../types" -const Content: QuartzComponent = ({ fileData, tree }: QuartzComponentProps) => { - const content = htmlToJsx(fileData.filePath!, tree) - const classes: string[] = fileData.frontmatter?.cssclasses ?? [] - const classString = ["popover-hint", ...classes].join(" ") - return
    {content}
    +const Content: QuartzComponent = ({fileData, tree}: QuartzComponentProps) => { + const content = htmlToJsx(fileData.filePath!, tree) + const classes: string[] = fileData.frontmatter?.cssclasses ?? [] + const classString = ["popover-hint", ...classes].join(" ") + return
    {content}
    } export default (() => Content) satisfies QuartzComponentConstructor diff --git a/quartz/components/pages/FolderContent.tsx b/quartz/components/pages/FolderContent.tsx index 342f70707..6423c4418 100644 --- a/quartz/components/pages/FolderContent.tsx +++ b/quartz/components/pages/FolderContent.tsx @@ -1,78 +1,76 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "../types" import path from "path" import style from "../styles/listPage.scss" -import { PageList } from "../PageList" -import { stripSlashes, simplifySlug } from "../../util/path" -import { Root } from "hast" -import { htmlToJsx } from "../../util/jsx" -import { i18n } from "../../i18n" +import {PageList} from "../PageList" +import {stripSlashes, simplifySlug} from "../../util/path" +import {Root} from "hast" +import {htmlToJsx} from "../../util/jsx" +import {i18n} from "../../i18n" interface FolderContentOptions { - /** - * Whether to display number of folders - */ - showFolderCount: boolean + /** + * Whether to display number of folders + */ + showFolderCount: boolean } const defaultOptions: FolderContentOptions = { - showFolderCount: true, + showFolderCount: true, } export default ((opts?: Partial) => { - const options: FolderContentOptions = { ...defaultOptions, ...opts } + const options: FolderContentOptions = {...defaultOptions, ...opts} - const FolderContent: QuartzComponent = (props: QuartzComponentProps) => { - const { tree, fileData, allFiles, cfg } = props - const folderSlug = stripSlashes(simplifySlug(fileData.slug!)) - const allPagesInFolder = allFiles.filter((file) => { - const fileSlug = stripSlashes(simplifySlug(file.slug!)) - const prefixed = - fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug - const folderParts = folderSlug.split(path.posix.sep) - const fileParts = fileSlug.split(path.posix.sep) - const isDirectChild = fileParts.length === folderParts.length + 1 - return prefixed && isDirectChild - }) - const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] - const classes = ["popover-hint", ...cssClasses].join(" ") - const listProps = { - ...props, - allFiles: allPagesInFolder, - } - - const content = - (tree as Root).children.length === 0 - ? fileData.description - : htmlToJsx(fileData.filePath!, tree) - - return ( -
    -
    -

    {content}

    -
    -
    - {options.showFolderCount && ( -

    - {i18n( - cfg.locale, - ).pages.folderContent.itemsUnderFolder({ - count: allPagesInFolder.length, - })} -

    - )} -
    - -
    -
    -
    - ) + const FolderContent: QuartzComponent = (props: QuartzComponentProps) => { + const {tree, fileData, allFiles, cfg} = props + const folderSlug = stripSlashes(simplifySlug(fileData.slug!)) + const allPagesInFolder = allFiles.filter((file) => { + const fileSlug = stripSlashes(simplifySlug(file.slug!)) + const prefixed = + fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug + const folderParts = folderSlug.split(path.posix.sep) + const fileParts = fileSlug.split(path.posix.sep) + const isDirectChild = fileParts.length === folderParts.length + 1 + return prefixed && isDirectChild + }) + const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] + const classes = ["popover-hint", ...cssClasses].join(" ") + const listProps = { + ...props, + allFiles: allPagesInFolder, } - FolderContent.css = style + PageList.css - return FolderContent + const content = + (tree as Root).children.length === 0 + ? fileData.description + : htmlToJsx(fileData.filePath!, tree) + + return ( +
    +
    +

    {content}

    +
    +
    + {options.showFolderCount && ( +

    + {i18n(cfg.locale).pages.folderContent.itemsUnderFolder({ + count: allPagesInFolder.length, + })} +

    + )} +
    + +
    +
    +
    + ) + } + + FolderContent.css = style + PageList.css + return FolderContent }) satisfies QuartzComponentConstructor diff --git a/quartz/components/pages/TagContent.tsx b/quartz/components/pages/TagContent.tsx index 7ebf64d10..c83e973bf 100644 --- a/quartz/components/pages/TagContent.tsx +++ b/quartz/components/pages/TagContent.tsx @@ -1,137 +1,130 @@ import { - QuartzComponent, - QuartzComponentConstructor, - QuartzComponentProps, + QuartzComponent, + QuartzComponentConstructor, + QuartzComponentProps, } from "../types" import style from "../styles/listPage.scss" -import { PageList } from "../PageList" -import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path" -import { QuartzPluginData } from "../../plugins/vfile" -import { Root } from "hast" -import { htmlToJsx } from "../../util/jsx" -import { i18n } from "../../i18n" +import {PageList} from "../PageList" +import {FullSlug, getAllSegmentPrefixes, simplifySlug} from "../../util/path" +import {QuartzPluginData} from "../../plugins/vfile" +import {Root} from "hast" +import {htmlToJsx} from "../../util/jsx" +import {i18n} from "../../i18n" const numPages = 10 const TagContent: QuartzComponent = (props: QuartzComponentProps) => { - const { tree, fileData, allFiles, cfg } = props - const slug = fileData.slug + const {tree, fileData, allFiles, cfg} = props + const slug = fileData.slug - if (!(slug?.startsWith("tags/") || slug === "tags")) { - throw new Error( - `Component "TagContent" tried to render a non-tag page: ${slug}`, - ) + if (!(slug?.startsWith("tags/") || slug === "tags")) { + throw new Error( + `Component "TagContent" tried to render a non-tag page: ${slug}`, + ) + } + + const tag = simplifySlug(slug.slice("tags/".length) as FullSlug) + const allPagesWithTag = (tag: string) => + allFiles.filter((file) => + (file.frontmatter?.tags ?? []) + .flatMap(getAllSegmentPrefixes) + .includes(tag), + ) + + const content = + (tree as Root).children.length === 0 + ? fileData.description + : htmlToJsx(fileData.filePath!, tree) + const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] + const classes = ["popover-hint", ...cssClasses].join(" ") + if (tag === "/") { + const tags = [ + ...new Set( + allFiles + .flatMap((data) => data.frontmatter?.tags ?? []) + .flatMap(getAllSegmentPrefixes), + ), + ].sort((a, b) => a.localeCompare(b)) + const tagItemMap: Map = new Map() + for (const tag of tags) { + tagItemMap.set(tag, allPagesWithTag(tag)) } + return ( +
    +
    +

    {content}

    +
    +

    + {i18n(cfg.locale).pages.tagContent.totalTags({ + count: tags.length, + })} +

    +
    + {tags.map((tag) => { + const pages = tagItemMap.get(tag)! + const listProps = { + ...props, + allFiles: pages, + } - const tag = simplifySlug(slug.slice("tags/".length) as FullSlug) - const allPagesWithTag = (tag: string) => - allFiles.filter((file) => - (file.frontmatter?.tags ?? []) - .flatMap(getAllSegmentPrefixes) - .includes(tag), - ) - - const content = - (tree as Root).children.length === 0 - ? fileData.description - : htmlToJsx(fileData.filePath!, tree) - const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] - const classes = ["popover-hint", ...cssClasses].join(" ") - if (tag === "/") { - const tags = [ - ...new Set( - allFiles - .flatMap((data) => data.frontmatter?.tags ?? []) - .flatMap(getAllSegmentPrefixes), - ), - ].sort((a, b) => a.localeCompare(b)) - const tagItemMap: Map = new Map() - for (const tag of tags) { - tagItemMap.set(tag, allPagesWithTag(tag)) - } - return ( -
    -
    -

    {content}

    -
    -

    - {i18n(cfg.locale).pages.tagContent.totalTags({ - count: tags.length, - })} -

    -
    - {tags.map((tag) => { - const pages = tagItemMap.get(tag)! - const listProps = { - ...props, - allFiles: pages, - } - - const contentPage = allFiles.filter( - (file) => file.slug === `tags/${tag}`, - )[0] - const content = contentPage?.description - return ( -
    -

    - - {tag} - -

    - {content &&

    {content}

    } -
    -

    - {i18n( - cfg.locale, - ).pages.tagContent.itemsUnderTag({ - count: pages.length, - })} - {pages.length > numPages && ( - <> - {" "} - - {i18n( - cfg.locale, - ).pages.tagContent.showingFirst( - { count: numPages }, - )} - - - )} -

    - -
    -
    - ) - })} -
    -
    - ) - } else { - const pages = allPagesWithTag(tag) - const listProps = { - ...props, - allFiles: pages, - } - - return ( -
    -
    {content}
    + const contentPage = allFiles.filter( + (file) => file.slug === `tags/${tag}`, + )[0] + const content = contentPage?.description + return ( +
    +

    + + {tag} + +

    + {content &&

    {content}

    }
    -

    - {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ - count: pages.length, - })} -

    -
    - -
    +

    + {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ + count: pages.length, + })} + {pages.length > numPages && ( + <> + {" "} + + {i18n(cfg.locale).pages.tagContent.showingFirst({ + count: numPages, + })} + + + )} +

    +
    -
    - ) +
    + ) + })} +
    +
    + ) + } else { + const pages = allPagesWithTag(tag) + const listProps = { + ...props, + allFiles: pages, } + + return ( +
    +
    {content}
    +
    +

    + {i18n(cfg.locale).pages.tagContent.itemsUnderTag({ + count: pages.length, + })} +

    +
    + +
    +
    +
    + ) + } } TagContent.css = style + PageList.css diff --git a/quartz/components/renderPage.tsx b/quartz/components/renderPage.tsx index d4dab8e3a..9a033e44e 100644 --- a/quartz/components/renderPage.tsx +++ b/quartz/components/renderPage.tsx @@ -1,311 +1,281 @@ -import { render } from "preact-render-to-string" -import { QuartzComponent, QuartzComponentProps } from "./types" +import {render} from "preact-render-to-string" +import {QuartzComponent, QuartzComponentProps} from "./types" import HeaderConstructor from "./Header" import BodyConstructor from "./Body" -import { JSResourceToScriptElement, StaticResources } from "../util/resources" +import {JSResourceToScriptElement, StaticResources} from "../util/resources" import { - clone, - FullSlug, - RelativeURL, - joinSegments, - normalizeHastElement, + clone, + FullSlug, + RelativeURL, + joinSegments, + normalizeHastElement, } from "../util/path" -import { visit } from "unist-util-visit" -import { Root, Element, ElementContent } from "hast" -import { GlobalConfiguration } from "../cfg" -import { i18n } from "../i18n" +import {visit} from "unist-util-visit" +import {Root, Element, ElementContent} from "hast" +import {GlobalConfiguration} from "../cfg" +import {i18n} from "../i18n" interface RenderComponents { - head: QuartzComponent - header: QuartzComponent[] - beforeBody: QuartzComponent[] - pageBody: QuartzComponent - left: QuartzComponent[] - right: QuartzComponent[] - footer: QuartzComponent + head: QuartzComponent + header: QuartzComponent[] + beforeBody: QuartzComponent[] + pageBody: QuartzComponent + left: QuartzComponent[] + right: QuartzComponent[] + footer: QuartzComponent } const headerRegex = new RegExp(/h[1-6]/) export function pageResources( - baseDir: FullSlug | RelativeURL, - staticResources: StaticResources, + baseDir: FullSlug | RelativeURL, + staticResources: StaticResources, ): StaticResources { - const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json") - const contentIndexScript = `const fetchData = fetch("${contentIndexPath}").then(data => data.json())` + const contentIndexPath = joinSegments(baseDir, "static/contentIndex.json") + const contentIndexScript = `const fetchData = fetch("${contentIndexPath}").then(data => data.json())` - return { - css: [joinSegments(baseDir, "index.css"), ...staticResources.css], - js: [ - { - src: joinSegments(baseDir, "prescript.js"), - loadTime: "beforeDOMReady", - contentType: "external", - }, - { - loadTime: "beforeDOMReady", - contentType: "inline", - spaPreserve: true, - script: contentIndexScript, - }, - ...staticResources.js, - { - src: joinSegments(baseDir, "postscript.js"), - loadTime: "afterDOMReady", - moduleType: "module", - contentType: "external", - }, - ], - } + return { + css: [joinSegments(baseDir, "index.css"), ...staticResources.css], + js: [ + { + src: joinSegments(baseDir, "prescript.js"), + loadTime: "beforeDOMReady", + contentType: "external", + }, + { + loadTime: "beforeDOMReady", + contentType: "inline", + spaPreserve: true, + script: contentIndexScript, + }, + ...staticResources.js, + { + src: joinSegments(baseDir, "postscript.js"), + loadTime: "afterDOMReady", + moduleType: "module", + contentType: "external", + }, + ], + } } export function renderPage( - cfg: GlobalConfiguration, - slug: FullSlug, - componentData: QuartzComponentProps, - components: RenderComponents, - pageResources: StaticResources, + cfg: GlobalConfiguration, + slug: FullSlug, + componentData: QuartzComponentProps, + components: RenderComponents, + pageResources: StaticResources, ): string { - // make a deep copy of the tree so we don't remove the transclusion references - // for the file cached in contentMap in build.ts - const root = clone(componentData.tree) as Root + // make a deep copy of the tree so we don't remove the transclusion references + // for the file cached in contentMap in build.ts + const root = clone(componentData.tree) as Root - // process transcludes in componentData - visit(root, "element", (node, _index, _parent) => { - if (node.tagName === "blockquote") { - const classNames = (node.properties?.className ?? []) as string[] - if (classNames.includes("transclude")) { - const inner = node.children[0] as Element - const transcludeTarget = inner.properties[ - "data-slug" - ] as FullSlug - const page = componentData.allFiles.find( - (f) => f.slug === transcludeTarget, - ) - if (!page) { - return - } - - let blockRef = node.properties.dataBlock as string | undefined - if (blockRef?.startsWith("#^")) { - // block transclude - blockRef = blockRef.slice("#^".length) - let blockNode = page.blocks?.[blockRef] - if (blockNode) { - if (blockNode.tagName === "li") { - blockNode = { - type: "element", - tagName: "ul", - properties: {}, - children: [blockNode], - } - } - - node.children = [ - normalizeHastElement( - blockNode, - slug, - transcludeTarget, - ), - { - type: "element", - tagName: "a", - properties: { - href: inner.properties?.href, - class: ["internal", "transclude-src"], - }, - children: [ - { - type: "text", - value: i18n(cfg.locale).components - .transcludes.linkToOriginal, - }, - ], - }, - ] - } - } else if (blockRef?.startsWith("#") && page.htmlAst) { - // header transclude - blockRef = blockRef.slice(1) - let startIdx = undefined - let startDepth = undefined - let endIdx = undefined - for (const [i, el] of page.htmlAst.children.entries()) { - // skip non-headers - if ( - !( - el.type === "element" && - el.tagName.match(headerRegex) - ) - ) - continue - const depth = Number(el.tagName.substring(1)) - - // lookin for our blockref - if ( - startIdx === undefined || - startDepth === undefined - ) { - // skip until we find the blockref that matches - if (el.properties?.id === blockRef) { - startIdx = i - startDepth = depth - } - } else if (depth <= startDepth) { - // looking for new header that is same level or higher - endIdx = i - break - } - } - - if (startIdx === undefined) { - return - } - - node.children = [ - ...( - page.htmlAst.children.slice( - startIdx, - endIdx, - ) as ElementContent[] - ).map((child) => - normalizeHastElement( - child as Element, - slug, - transcludeTarget, - ), - ), - { - type: "element", - tagName: "a", - properties: { - href: inner.properties?.href, - class: ["internal", "transclude-src"], - }, - children: [ - { - type: "text", - value: i18n(cfg.locale).components - .transcludes.linkToOriginal, - }, - ], - }, - ] - } else if (page.htmlAst) { - // page transclude - node.children = [ - { - type: "element", - tagName: "h1", - properties: {}, - children: [ - { - type: "text", - value: - page.frontmatter?.title ?? - i18n( - cfg.locale, - ).components.transcludes.transcludeOf({ - targetSlug: page.slug!, - }), - }, - ], - }, - ...(page.htmlAst.children as ElementContent[]).map( - (child) => - normalizeHastElement( - child as Element, - slug, - transcludeTarget, - ), - ), - { - type: "element", - tagName: "a", - properties: { - href: inner.properties?.href, - class: ["internal", "transclude-src"], - }, - children: [ - { - type: "text", - value: i18n(cfg.locale).components - .transcludes.linkToOriginal, - }, - ], - }, - ] - } - } + // process transcludes in componentData + visit(root, "element", (node, _index, _parent) => { + if (node.tagName === "blockquote") { + const classNames = (node.properties?.className ?? []) as string[] + if (classNames.includes("transclude")) { + const inner = node.children[0] as Element + const transcludeTarget = inner.properties["data-slug"] as FullSlug + const page = componentData.allFiles.find( + (f) => f.slug === transcludeTarget, + ) + if (!page) { + return } - }) - // set componentData.tree to the edited html that has transclusions rendered - componentData.tree = root + let blockRef = node.properties.dataBlock as string | undefined + if (blockRef?.startsWith("#^")) { + // block transclude + blockRef = blockRef.slice("#^".length) + let blockNode = page.blocks?.[blockRef] + if (blockNode) { + if (blockNode.tagName === "li") { + blockNode = { + type: "element", + tagName: "ul", + properties: {}, + children: [blockNode], + } + } - const { - head: Head, - header, - beforeBody, - pageBody: Content, - left, - right, - footer: Footer, - } = components - const Header = HeaderConstructor() - const Body = BodyConstructor() + node.children = [ + normalizeHastElement(blockNode, slug, transcludeTarget), + { + type: "element", + tagName: "a", + properties: { + href: inner.properties?.href, + class: ["internal", "transclude-src"], + }, + children: [ + { + type: "text", + value: i18n(cfg.locale).components.transcludes + .linkToOriginal, + }, + ], + }, + ] + } + } else if (blockRef?.startsWith("#") && page.htmlAst) { + // header transclude + blockRef = blockRef.slice(1) + let startIdx = undefined + let startDepth = undefined + let endIdx = undefined + for (const [i, el] of page.htmlAst.children.entries()) { + // skip non-headers + if (!(el.type === "element" && el.tagName.match(headerRegex))) + continue + const depth = Number(el.tagName.substring(1)) - const LeftComponent = ( - - ) + // lookin for our blockref + if (startIdx === undefined || startDepth === undefined) { + // skip until we find the blockref that matches + if (el.properties?.id === blockRef) { + startIdx = i + startDepth = depth + } + } else if (depth <= startDepth) { + // looking for new header that is same level or higher + endIdx = i + break + } + } - const RightComponent = ( - - ) + if (startIdx === undefined) { + return + } - const lang = - componentData.fileData.frontmatter?.lang ?? - cfg.locale?.split("-")[0] ?? - "en" - const doc = ( - - - -
    - - {LeftComponent} -
    - - -
    - {RightComponent} - -