diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8915143c4..9b1622cb8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,3 +45,9 @@ jobs: - name: Ensure Quartz builds, check bundle info run: npx quartz build --bundleInfo + + - name: Create release tag + uses: Klemensas/action-autotag@stable + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + tag_prefix: "v" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..1d9e5915f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM node:20-slim as builder +WORKDIR /usr/src/app +COPY package.json . +COPY package-lock.json* . +RUN npm ci + +FROM node:20-slim +WORKDIR /usr/src/app +COPY --from=builder /usr/src/app/ /usr/src/app/ +COPY . . +CMD ["npx", "quartz", "build", "--serve"] diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index 1f1616f42..fcc88a7bc 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -228,7 +228,7 @@ export type QuartzEmitterPluginInstance = { An emitter plugin must define a `name` field an `emit` function and a `getQuartzComponents` function. `emit` is responsible for looking at all the parsed and filtered content and then appropriately creating files and returning a list of paths to files the plugin created. -Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `emitCallback` if you are creating files that contain text. The `emitCallback` function is the 4th argument of the emit function. It's interface looks something like this: +Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `emitCallback` if you are creating files that contain text. The `emitCallback` function is the 4th argument of the emit function. Its interface looks something like this: ```ts export type EmitCallback = (data: { @@ -247,7 +247,7 @@ If you are creating an emitter plugin that needs to render components, there are - Your component should use `getQuartzComponents` to declare a list of `QuartzComponents` that it uses to construct the page. See the page on [[creating components]] for more information. - You can use the `renderPage` function defined in `quartz/components/renderPage.tsx` to render Quartz components into HTML. -- If you need to render an HTML AST to JSX, you can use the `toJsxRuntime` function from `hast-util-to-jsx-runtime` library. An example of this can be found in `quartz/components/pages/Content.tsx`. +- 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. diff --git a/docs/features/Docker Support.md b/docs/features/Docker Support.md new file mode 100644 index 000000000..cf73b7fcc --- /dev/null +++ b/docs/features/Docker Support.md @@ -0,0 +1,7 @@ +Quartz comes shipped with a Docker image that will allow you to preview your Quartz locally without installing Node. + +You can run the below one-liner to run Quartz in Docker. + +```sh +docker run --rm -itp 8080:8080 $(docker build -q .) +``` diff --git a/docs/features/breadcrumbs.md b/docs/features/breadcrumbs.md index 94db66ac0..20c3b8d65 100644 --- a/docs/features/breadcrumbs.md +++ b/docs/features/breadcrumbs.md @@ -16,10 +16,10 @@ For example, here's what the default configuration looks like: ```typescript title="quartz.layout.ts" Component.Breadcrumbs({ - spacerSymbol: ">", // symbol between crumbs + spacerSymbol: "❯", // symbol between crumbs rootName: "Home", // name of first/root element - resolveFrontmatterTitle: false, // wether to resolve folder names through frontmatter titles (more computationally expensive) - hideOnRoot: true, // wether to hide breadcrumbs on root `index.md` page + resolveFrontmatterTitle: true, // whether to resolve folder names through frontmatter titles + hideOnRoot: true, // whether to hide breadcrumbs on root `index.md` page }) ``` diff --git a/docs/features/callouts.md b/docs/features/callouts.md index 63051ad9d..27de687eb 100644 --- a/docs/features/callouts.md +++ b/docs/features/callouts.md @@ -33,7 +33,7 @@ See [documentation on supported types and syntax here](https://help.obsidian.md > [!question]+ Can callouts be nested? > -> > [!todo]- Yes!, they can. +> > [!todo]- Yes!, they can. And collapsed! > > > > > [!example] You can even use multiple layers of nesting. diff --git a/docs/features/explorer.md b/docs/features/explorer.md index 8937b25c0..fd656a888 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -196,7 +196,7 @@ Component.Explorer({ } } }, -}}) +}) ``` ### Putting it all together diff --git a/docs/features/private pages.md b/docs/features/private pages.md index 5c3940bc7..638c628b6 100644 --- a/docs/features/private pages.md +++ b/docs/features/private pages.md @@ -12,9 +12,17 @@ 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 `Plugin.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. +> +> `"!(PublicMedia)**/!(*.md)", "!(*.md)"` + ## `ignorePatterns` -This is a field in `quartz.config.ts` under the main [[configuration]] which allows you to specify a list of patterns to effectively exclude from parsing all together. Any valid [glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) pattern works here. +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. Common examples include: diff --git a/docs/features/table of contents.md b/docs/features/table of contents.md index a66c85017..0298ffaab 100644 --- a/docs/features/table of contents.md +++ b/docs/features/table of contents.md @@ -8,7 +8,7 @@ tags: Quartz can automatically generate a table of contents from a list of headings on each page. It will also show you your current scroll position on the site by marking headings you've scrolled through with a different colour. By default, it will show all headers from H1 (`# Title`) all the way to H3 (`### Title`) and will only show the table of contents if there is more than 1 header on the page. -You can also hide the table of contents on a page by adding `showToc: false` to the frontmatter for that page. +You can also hide the table of contents on a page by adding `enableToc: false` to the frontmatter for that page. > [!info] > This feature requires both `Plugin.TableOfContents` in your `quartz.config.ts` and `Component.TableOfContents` in your `quartz.layout.ts` to function correctly. @@ -18,6 +18,7 @@ You can also hide the table of contents on a page by adding `showToc: false` to - Removing table of contents: remove all instances of `Plugin.TableOfContents()` from `quartz.config.ts`. and `Component.TableOfContents()` from `quartz.layout.ts` - Changing the max depth: pass in a parameter to `Plugin.TableOfContents({ maxDepth: 4 })` - Changing the minimum number of entries in the Table of Contents before it renders: pass in a parameter to `Plugin.TableOfContents({ minEntries: 3 })` +- Collapse the table of content by default: pass in a parameter to `Plugin.TableOfContents({ collapseByDefault: true })` - Component: `quartz/components/TableOfContents.tsx` - Style: - Modern (default): `quartz/components/styles/toc.scss` diff --git a/docs/hosting.md b/docs/hosting.md index 01d130fd3..a4ca1ea98 100644 --- a/docs/hosting.md +++ b/docs/hosting.md @@ -166,3 +166,56 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c 3. Go to the [Vercel Dashboard](https://vercel.com/dashboard) and select your Quartz project. 4. Go to the Settings tab and then click Domains in the sidebar 5. Enter your subdomain into the field and press Add + +## GitLab Pages + +You can configure GitLab CI to build and deploy a Quartz 4 project. + +In your local Quartz, create a new file `.gitlab-ci.yaml`. + +```yaml title=".gitlab-ci.yaml" +stages: + - build + - deploy + +variables: + NODE_VERSION: "18.14" + +build: + stage: build + rules: + - if: '$CI_COMMIT_REF_NAME == "v4"' + before_script: + - apt-get update -q && apt-get install -y nodejs npm + - npm install -g n + - n $NODE_VERSION + - hash -r + - npm ci + script: + - npx prettier --write . + - npm run check + - npx quartz build + artifacts: + paths: + - public + cache: + paths: + - ~/.npm/ + key: "${CI_COMMIT_REF_SLUG}-node-${CI_COMMIT_REF_NAME}" + tags: + - docker + +pages: + stage: deploy + rules: + - if: '$CI_COMMIT_REF_NAME == "v4"' + script: + - echo "Deploying to GitLab Pages..." + artifacts: + paths: + - public +``` + +When `.gitlab-ci.yaml` is commited, GitLab will build and deploy the website as a GitLab Page. You can find the url under `Deploy` -> `Pages` in the sidebar. + +By default, the page is private and only visible when logged in to a GitLab account with access to the repository but can be opened in the settings under `Deploy` -> `Pages`. diff --git a/docs/index.md b/docs/index.md index f846cc7ea..85afee1f0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -30,7 +30,7 @@ This will guide you through initializing your Quartz with content. Once you've d ## 🔧 Features -- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], and [many more](./features) right out of the box +- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], [[Docker Support]], and [many more](./features) right out of the box - 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 diff --git a/docs/showcase.md b/docs/showcase.md index 7dbc6e99b..ed9df9f57 100644 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -9,7 +9,6 @@ Want to see what Quartz can do? Here are some cool community gardens: - [Brandon Boswell's Garden](https://brandonkboswell.com) - [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/) - [AWAGMI Intern Notes](https://notes.awagmi.xyz/) -- [Course notes for Information Technology Advanced Theory](https://a2itnotes.github.io/quartz/) - [Data Dictionary 🧠](https://glossary.airbyte.com/) - [sspaeti.com's Second Brain](https://brain.sspaeti.com/) - [oldwinter の数字花园](https://garden.oldwinter.top/) @@ -19,5 +18,7 @@ Want to see what Quartz can do? Here are some cool community gardens: - [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/) 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/quartz/cli/handlers.js b/quartz/cli/handlers.js index bc3da73f3..48a44ec9f 100644 --- a/quartz/cli/handlers.js +++ b/quartz/cli/handlers.js @@ -1,4 +1,4 @@ -import { promises, readFileSync } from "fs" +import { promises } from "fs" import path from "path" import esbuild from "esbuild" import chalk from "chalk" diff --git a/quartz/components/Breadcrumbs.tsx b/quartz/components/Breadcrumbs.tsx index 467b5a503..29c73a81b 100644 --- a/quartz/components/Breadcrumbs.tsx +++ b/quartz/components/Breadcrumbs.tsx @@ -28,9 +28,9 @@ interface BreadcrumbOptions { } const defaultOptions: BreadcrumbOptions = { - spacerSymbol: ">", + spacerSymbol: "❯", rootName: "Home", - resolveFrontmatterTitle: false, + resolveFrontmatterTitle: true, hideOnRoot: true, } @@ -41,25 +41,13 @@ function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: Simpl } } -// given a folderName (e.g. "features"), search for the corresponding `index.md` file -function findCurrentFile(allFiles: QuartzPluginData[], folderName: string) { - return allFiles.find((file) => { - if (file.slug?.endsWith("index")) { - const folderParts = file.filePath?.split("/") - if (folderParts) { - const name = folderParts[folderParts?.length - 2] - if (name === folderName) { - return true - } - } - } - }) -} - export default ((opts?: Partial) => { // Merge options with defaults const options: BreadcrumbOptions = { ...defaultOptions, ...opts } + // computed index of folder name to its associated file data + let folderIndex: Map | undefined + function Breadcrumbs({ fileData, allFiles, displayClass }: QuartzComponentProps) { // Hide crumbs on root if enabled if (options.hideOnRoot && fileData.slug === "index") { @@ -70,28 +58,39 @@ export default ((opts?: Partial) => { const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug) const crumbs: CrumbData[] = [firstEntry] + if (!folderIndex && options.resolveFrontmatterTitle) { + folderIndex = new Map() + // construct the index for the first time + for (const file of allFiles) { + if (file.slug?.endsWith("index")) { + const folderParts = file.filePath?.split("/") + if (folderParts) { + const folderName = folderParts[folderParts?.length - 2] + folderIndex.set(folderName, file) + } + } + } + } + // Split slug into hierarchy/parts const slugParts = fileData.slug?.split("/") if (slugParts) { // full path until current part let currentPath = "" for (let i = 0; i < slugParts.length - 1; i++) { - let currentTitle = slugParts[i] + let curPathSegment = slugParts[i] - // TODO: performance optimizations/memoizing // Try to resolve frontmatter folder title - if (options?.resolveFrontmatterTitle) { - // try to find file for current path - const currentFile = findCurrentFile(allFiles, currentTitle) - if (currentFile) { - currentTitle = currentFile.frontmatter!.title - } + const currentFile = folderIndex?.get(curPathSegment) + if (currentFile) { + curPathSegment = currentFile.frontmatter!.title } + // Add current slug to full path currentPath += slugParts[i] + "/" // Format and add current crumb - const crumb = formatCrumb(currentTitle, fileData.slug!, currentPath as SimpleSlug) + const crumb = formatCrumb(curPathSegment, fileData.slug!, currentPath as SimpleSlug) crumbs.push(crumb) } diff --git a/quartz/components/TableOfContents.tsx b/quartz/components/TableOfContents.tsx index 384772684..1c55f0740 100644 --- a/quartz/components/TableOfContents.tsx +++ b/quartz/components/TableOfContents.tsx @@ -20,7 +20,7 @@ function TableOfContents({ fileData, displayClass }: QuartzComponentProps) { return (
-