diff --git a/.github/workflows/build-preview.yaml b/.github/workflows/build-preview.yaml new file mode 100644 index 000000000..ff723a1cd --- /dev/null +++ b/.github/workflows/build-preview.yaml @@ -0,0 +1,43 @@ +name: Build Preview Deployment + +on: + pull_request: + types: [opened, synchronize] + workflow_dispatch: + +jobs: + build-preview: + if: ${{ github.repository == 'jackyzha0/quartz' }} + runs-on: ubuntu-latest + name: Build Preview + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - run: npm ci + + - name: Check types and style + run: npm run check + + - name: Build Quartz + run: npx quartz build -d docs -v + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: preview-build + path: public diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f0fc1fd18..c584fded2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,7 +26,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Cache dependencies uses: actions/cache@v4 @@ -45,7 +45,7 @@ jobs: run: npm test - name: Ensure Quartz builds, check bundle info - run: npx quartz build --bundleInfo + run: npx quartz build --bundleInfo -d docs publish-tag: if: ${{ github.repository == 'jackyzha0/quartz' && github.ref == 'refs/heads/v4' }} @@ -59,7 +59,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Get package version run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV - name: Create release tag diff --git a/.github/workflows/deploy-preview.yaml b/.github/workflows/deploy-preview.yaml new file mode 100644 index 000000000..3a1432ac9 --- /dev/null +++ b/.github/workflows/deploy-preview.yaml @@ -0,0 +1,37 @@ +name: Upload Preview Deployment +on: + workflow_run: + workflows: ["Build Preview Deployment"] + types: + - completed + +permissions: + actions: read + deployments: write + contents: read + pull-requests: write + +jobs: + deploy-preview: + if: ${{ github.repository == 'jackyzha0/quartz' && github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + name: Deploy Preview to Cloudflare Pages + steps: + - name: Download build artifact + uses: actions/download-artifact@v4 + id: preview-build-artifact + with: + name: preview-build + path: build + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Deploy to Cloudflare Pages + uses: AdrianGonz97/refined-cf-pages-action@v1 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + githubToken: ${{ secrets.GITHUB_TOKEN }} + projectName: quartz + deploymentName: Branch Preview + directory: ${{ steps.preview-build-artifact.outputs.download-path }} diff --git a/.github/workflows/docker-build-push.yaml b/.github/workflows/docker-build-push.yaml index 5116a73c6..ee7efa7c1 100644 --- a/.github/workflows/docker-build-push.yaml +++ b/.github/workflows/docker-build-push.yaml @@ -25,7 +25,7 @@ jobs: with: fetch-depth: 1 - name: Inject slug/short variables - uses: rlespinasse/github-slug-action@v5.0.0 + uses: rlespinasse/github-slug-action@v5.1.0 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx @@ -37,7 +37,7 @@ jobs: network=host - name: Install cosign if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@v3.8.1 + uses: sigstore/cosign-installer@v3.8.2 - name: Login to GitHub Container Registry uses: docker/login-action@v3 if: github.event_name != 'pull_request' diff --git a/.node-version b/.node-version index 805b5a4e0..aebd91c52 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v20.9.0 +v22.16.0 diff --git a/Dockerfile b/Dockerfile index 4493853e2..f8a6f2684 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM node:20-slim AS builder +FROM node:22-slim AS builder WORKDIR /usr/src/app COPY package.json . COPY package-lock.json* . RUN npm ci -FROM node:20-slim +FROM node:22-slim WORKDIR /usr/src/app COPY --from=builder /usr/src/app/ /usr/src/app/ COPY . . diff --git a/docs/advanced/creating components.md b/docs/advanced/creating components.md index 628d5aa29..369405b07 100644 --- a/docs/advanced/creating components.md +++ b/docs/advanced/creating components.md @@ -161,6 +161,18 @@ document.addEventListener("nav", () => { }) ``` +You can also add the equivalent of a `beforeunload` event for [[SPA Routing]] via the `prenav` event. + +```ts +document.addEventListener("prenav", () => { + // executed after an SPA navigation is triggered but + // before the page is replaced + // one usage pattern is to store things in sessionStorage + // in the prenav and then conditionally load then in the consequent + // nav +}) +``` + It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. This will get called on page navigation. diff --git a/docs/advanced/making plugins.md b/docs/advanced/making plugins.md index 015e1953a..f5cb19901 100644 --- a/docs/advanced/making plugins.md +++ b/docs/advanced/making plugins.md @@ -25,10 +25,11 @@ The following sections will go into detail for what methods can be implemented f - `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) + - `allSlugs`: a list of all the valid content slugs (see [[paths]] for more information on what a slug is) - `StaticResources` is defined in `quartz/resources.tsx`. It consists of - `css`: a list of CSS style definitions that should be loaded. A CSS style is described with the `CSSResource` type which is also defined in `quartz/resources.tsx`. It accepts either a source URL or the inline content of the stylesheet. - `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. + - `additionalHead`: a list of JSX elements or functions that return JSX elements to be added to the `
` tag of the page. Functions receive the page's data as an argument and can conditionally render elements. ## Transformers @@ -37,7 +38,7 @@ 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 + textTransform?: (ctx: BuildCtx, src: string) => string markdownPlugins?: (ctx: BuildCtx) => PluggableList htmlPlugins?: (ctx: BuildCtx) => PluggableList externalResources?: (ctx: BuildCtx) => Partial{title}
`/`{description}
` -> -> - Use a font family -> -> Detailed in the Fonts chapter below - ---- - -### Fonts - -You will also be passed an array containing a header and a body font (where the first entry is header and the second is body). The fonts matches the ones selected in `theme.typography.header` and `theme.typography.body` from `quartz.config.ts` and will be passed in the format required by [`satori`](https://github.com/vercel/satori). To use them in CSS, use the `.name` property (e.g. `fontFamily: fonts[1].name` to use the "body" font family). - -An example of a component using the header font could look like this: - -```tsx title="socialImage.tsx" -export const myImage: SocialImageOptions["imageStructure"] = (...) => { - returnCool Header!
-} -``` - -> [!example]- Local fonts -> -> For cases where you use a local fonts under `static` folder, make sure to set the correct `@font-face` in `custom.scss` -> -> ```scss title="custom.scss" -> @font-face { -> font-family: "Newsreader"; -> font-style: normal; -> font-weight: normal; -> font-display: swap; -> src: url("/static/Newsreader.woff2") format("woff2"); -> } -> ``` -> -> Then in `quartz/util/og.tsx`, you can load the satori fonts like so: -> -> ```tsx title="quartz/util/og.tsx" -> const headerFont = joinSegments("static", "Newsreader.woff2") -> const bodyFont = joinSegments("static", "Newsreader.woff2") -> -> export async function getSatoriFont(cfg: GlobalConfiguration): Promise- {title} -
-- {description} -
-style={{ -> color: cfg.theme.colors[colorScheme].light, -> fontSize: "1.5rem", -> overflow: "hidden", -> marginRight: "8rem", -> textOverflow: "ellipsis", -> display: "-webkit-box", -> WebkitLineClamp: 7, -> WebkitBoxOrient: "vertical", -> lineClamp: 7, -> fontFamily: fonts[1].name, -> }} -> > -> {description} ->
->Cool Header!
+} +``` + +> [!example]- Local fonts +> +> For cases where you use a local fonts under `static` folder, make sure to set the correct `@font-face` in `custom.scss` +> +> ```scss title="custom.scss" +> @font-face { +> font-family: "Newsreader"; +> font-style: normal; +> font-weight: normal; +> font-display: swap; +> src: url("/static/Newsreader.woff2") format("woff2"); +> } +> ``` +> +> Then in `quartz/util/og.tsx`, you can load the Satori fonts like so: +> +> ```tsx title="quartz/util/og.tsx" +> import { joinSegments, QUARTZ } from "../path" +> import fs from "fs" +> import path from "path" +> +> const newsreaderFontPath = joinSegments(QUARTZ, "static", "Newsreader.woff2") +> export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) { +> // ... rest of implementation remains same +> const fonts: SatoriOptions["fonts"] = [ +> ...headerFontData.map((data, idx) => ({ +> name: headerFontName, +> data, +> weight: headerWeights[idx], +> style: "normal" as const, +> })), +> ...bodyFontData.map((data, idx) => ({ +> name: bodyFontName, +> data, +> weight: bodyWeights[idx], +> style: "normal" as const, +> })), +> { +> name: "Newsreader", +> data: await fs.promises.readFile(path.resolve(newsreaderFontPath)), +> weight: 400, +> style: "normal" as const, +> }, +> ] +> +> return fonts +> } +> ``` +> +> This font then can be used with your custom structure. + +## Examples + +Here are some example image components you can use as a starting point: + +### Basic Example + +This example will generate images that look as follows: + +| Light | Dark | +| ------------------------------------------ | ----------------------------------------- | +| ![[custom-social-image-preview-light.png]] | ![[custom-social-image-preview-dark.png]] | + +```tsx +import { SatoriOptions } from "satori/wasm" +import { GlobalConfiguration } from "../cfg" +import { SocialImageOptions, UserOpts } from "./imageHelper" +import { QuartzPluginData } from "../plugins/vfile" + +export const customImage: SocialImageOptions["imageStructure"] = ( + cfg: GlobalConfiguration, + userOpts: UserOpts, + title: string, + description: string, + fonts: SatoriOptions["fonts"], + fileData: QuartzPluginData, +) => { + // How many characters are allowed before switching to smaller font + const fontBreakPoint = 22 + const useSmallerFont = title.length > fontBreakPoint + + const { colorScheme } = userOpts + return ( ++ {title} +
++ {description} +
++ {description} +
+