mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-27 06:44:07 -06:00
Merge branch 'jackyzha0:v4' into v4
This commit is contained in:
commit
998fa89027
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@ -45,3 +45,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Ensure Quartz builds, check bundle info
|
- name: Ensure Quartz builds, check bundle info
|
||||||
run: npx quartz build --bundleInfo
|
run: npx quartz build --bundleInfo
|
||||||
|
|
||||||
|
- name: Create release tag
|
||||||
|
uses: Klemensas/action-autotag@stable
|
||||||
|
with:
|
||||||
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
tag_prefix: "v"
|
||||||
|
|||||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -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"]
|
||||||
@ -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.
|
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
|
```ts
|
||||||
export type EmitCallback = (data: {
|
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.
|
- 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.
|
- 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.
|
For example, the following is a simplified version of the content page plugin that renders every single page.
|
||||||
|
|
||||||
|
|||||||
7
docs/features/Docker Support.md
Normal file
7
docs/features/Docker Support.md
Normal file
@ -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 .)
|
||||||
|
```
|
||||||
@ -16,10 +16,10 @@ For example, here's what the default configuration looks like:
|
|||||||
|
|
||||||
```typescript title="quartz.layout.ts"
|
```typescript title="quartz.layout.ts"
|
||||||
Component.Breadcrumbs({
|
Component.Breadcrumbs({
|
||||||
spacerSymbol: ">", // symbol between crumbs
|
spacerSymbol: "❯", // symbol between crumbs
|
||||||
rootName: "Home", // name of first/root element
|
rootName: "Home", // name of first/root element
|
||||||
resolveFrontmatterTitle: false, // wether to resolve folder names through frontmatter titles (more computationally expensive)
|
resolveFrontmatterTitle: true, // whether to resolve folder names through frontmatter titles
|
||||||
hideOnRoot: true, // wether to hide breadcrumbs on root `index.md` page
|
hideOnRoot: true, // whether to hide breadcrumbs on root `index.md` page
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ See [documentation on supported types and syntax here](https://help.obsidian.md
|
|||||||
|
|
||||||
> [!question]+ Can callouts be nested?
|
> [!question]+ Can callouts be nested?
|
||||||
>
|
>
|
||||||
> > [!todo]- Yes!, they can.
|
> > [!todo]- Yes!, they can. And collapsed!
|
||||||
> >
|
> >
|
||||||
> > > [!example] You can even use multiple layers of nesting.
|
> > > [!example] You can even use multiple layers of nesting.
|
||||||
|
|
||||||
|
|||||||
@ -196,7 +196,7 @@ Component.Explorer({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### Putting it all together
|
### Putting it all together
|
||||||
|
|||||||
@ -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.
|
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`
|
## `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:
|
Common examples include:
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
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.
|
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]
|
> [!info]
|
||||||
> This feature requires both `Plugin.TableOfContents` in your `quartz.config.ts` and `Component.TableOfContents` in your `quartz.layout.ts` to function correctly.
|
> 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`
|
- 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 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 })`
|
- 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`
|
- Component: `quartz/components/TableOfContents.tsx`
|
||||||
- Style:
|
- Style:
|
||||||
- Modern (default): `quartz/components/styles/toc.scss`
|
- Modern (default): `quartz/components/styles/toc.scss`
|
||||||
|
|||||||
@ -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.
|
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
|
4. Go to the Settings tab and then click Domains in the sidebar
|
||||||
5. Enter your subdomain into the field and press Add
|
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`.
|
||||||
|
|||||||
@ -30,7 +30,7 @@ This will guide you through initializing your Quartz with content. Once you've d
|
|||||||
|
|
||||||
## 🔧 Features
|
## 🔧 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
|
- Hot-reload for both configuration and content
|
||||||
- Simple JSX layouts and [[creating components|page components]]
|
- Simple JSX layouts and [[creating components|page components]]
|
||||||
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes
|
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes
|
||||||
|
|||||||
@ -9,7 +9,6 @@ Want to see what Quartz can do? Here are some cool community gardens:
|
|||||||
- [Brandon Boswell's Garden](https://brandonkboswell.com)
|
- [Brandon Boswell's Garden](https://brandonkboswell.com)
|
||||||
- [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/)
|
- [Scaling Synthesis - A hypertext research notebook](https://scalingsynthesis.com/)
|
||||||
- [AWAGMI Intern Notes](https://notes.awagmi.xyz/)
|
- [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/)
|
- [Data Dictionary 🧠](https://glossary.airbyte.com/)
|
||||||
- [sspaeti.com's Second Brain](https://brain.sspaeti.com/)
|
- [sspaeti.com's Second Brain](https://brain.sspaeti.com/)
|
||||||
- [oldwinter の数字花园](https://garden.oldwinter.top/)
|
- [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/)
|
- [Pelayo Arbues' Notes](https://pelayoarbues.github.io/)
|
||||||
- [Vince Imbat's Talahardin](https://vinceimbat.com/)
|
- [Vince Imbat's Talahardin](https://vinceimbat.com/)
|
||||||
- [🧠🌳 Chad's Mind Garden](https://www.chadly.net/)
|
- [🧠🌳 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)!
|
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)!
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { promises, readFileSync } from "fs"
|
import { promises } from "fs"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import esbuild from "esbuild"
|
import esbuild from "esbuild"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
|
|||||||
@ -28,9 +28,9 @@ interface BreadcrumbOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: BreadcrumbOptions = {
|
const defaultOptions: BreadcrumbOptions = {
|
||||||
spacerSymbol: ">",
|
spacerSymbol: "❯",
|
||||||
rootName: "Home",
|
rootName: "Home",
|
||||||
resolveFrontmatterTitle: false,
|
resolveFrontmatterTitle: true,
|
||||||
hideOnRoot: 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<BreadcrumbOptions>) => {
|
export default ((opts?: Partial<BreadcrumbOptions>) => {
|
||||||
// Merge options with defaults
|
// Merge options with defaults
|
||||||
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
|
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
|
||||||
|
|
||||||
|
// computed index of folder name to its associated file data
|
||||||
|
let folderIndex: Map<string, QuartzPluginData> | undefined
|
||||||
|
|
||||||
function Breadcrumbs({ fileData, allFiles, displayClass }: QuartzComponentProps) {
|
function Breadcrumbs({ fileData, allFiles, displayClass }: QuartzComponentProps) {
|
||||||
// Hide crumbs on root if enabled
|
// Hide crumbs on root if enabled
|
||||||
if (options.hideOnRoot && fileData.slug === "index") {
|
if (options.hideOnRoot && fileData.slug === "index") {
|
||||||
@ -70,28 +58,39 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
|
|||||||
const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug)
|
const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug)
|
||||||
const crumbs: CrumbData[] = [firstEntry]
|
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
|
// Split slug into hierarchy/parts
|
||||||
const slugParts = fileData.slug?.split("/")
|
const slugParts = fileData.slug?.split("/")
|
||||||
if (slugParts) {
|
if (slugParts) {
|
||||||
// full path until current part
|
// full path until current part
|
||||||
let currentPath = ""
|
let currentPath = ""
|
||||||
for (let i = 0; i < slugParts.length - 1; i++) {
|
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
|
// Try to resolve frontmatter folder title
|
||||||
if (options?.resolveFrontmatterTitle) {
|
const currentFile = folderIndex?.get(curPathSegment)
|
||||||
// try to find file for current path
|
|
||||||
const currentFile = findCurrentFile(allFiles, currentTitle)
|
|
||||||
if (currentFile) {
|
if (currentFile) {
|
||||||
currentTitle = currentFile.frontmatter!.title
|
curPathSegment = currentFile.frontmatter!.title
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add current slug to full path
|
// Add current slug to full path
|
||||||
currentPath += slugParts[i] + "/"
|
currentPath += slugParts[i] + "/"
|
||||||
|
|
||||||
// Format and add current crumb
|
// 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)
|
crumbs.push(crumb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ function TableOfContents({ fileData, displayClass }: QuartzComponentProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`toc ${displayClass ?? ""}`}>
|
<div class={`toc ${displayClass ?? ""}`}>
|
||||||
<button type="button" id="toc">
|
<button type="button" id="toc" class={fileData.collapseToc ? "collapsed" : ""}>
|
||||||
<h3>Table of Contents</h3>
|
<h3>Table of Contents</h3>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -60,7 +60,7 @@ function LegacyTableOfContents({ fileData }: QuartzComponentProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<details id="toc" open>
|
<details id="toc" open={!fileData.collapseToc}>
|
||||||
<summary>
|
<summary>
|
||||||
<h3>Table of Contents</h3>
|
<h3>Table of Contents</h3>
|
||||||
</summary>
|
</summary>
|
||||||
|
|||||||
@ -28,11 +28,16 @@ function TagList({ fileData, displayClass }: QuartzComponentProps) {
|
|||||||
TagList.css = `
|
TagList.css = `
|
||||||
.tags {
|
.tags {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display:flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-li > .section > .tags {
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags > li {
|
.tags > li {
|
||||||
@ -42,7 +47,7 @@ TagList.css = `
|
|||||||
overflow-wrap: normal;
|
overflow-wrap: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.tag-link {
|
a.internal.tag-link {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: var(--highlight);
|
background-color: var(--highlight);
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
|
import { htmlToJsx } from "../../util/jsx"
|
||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
|
||||||
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
|
|
||||||
|
|
||||||
function Content({ tree }: QuartzComponentProps) {
|
function Content({ fileData, tree }: QuartzComponentProps) {
|
||||||
// @ts-ignore (preact makes it angry)
|
const content = htmlToJsx(fileData.filePath!, tree)
|
||||||
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
|
||||||
return <article class="popover-hint">{content}</article>
|
return <article class="popover-hint">{content}</article>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
|
||||||
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
import style from "../styles/listPage.scss"
|
import style from "../styles/listPage.scss"
|
||||||
@ -8,6 +6,7 @@ import { PageList } from "../PageList"
|
|||||||
import { _stripSlashes, simplifySlug } from "../../util/path"
|
import { _stripSlashes, simplifySlug } from "../../util/path"
|
||||||
import { Root } from "hast"
|
import { Root } from "hast"
|
||||||
import { pluralize } from "../../util/lang"
|
import { pluralize } from "../../util/lang"
|
||||||
|
import { htmlToJsx } from "../../util/jsx"
|
||||||
|
|
||||||
function FolderContent(props: QuartzComponentProps) {
|
function FolderContent(props: QuartzComponentProps) {
|
||||||
const { tree, fileData, allFiles } = props
|
const { tree, fileData, allFiles } = props
|
||||||
@ -29,8 +28,7 @@ function FolderContent(props: QuartzComponentProps) {
|
|||||||
const content =
|
const content =
|
||||||
(tree as Root).children.length === 0
|
(tree as Root).children.length === 0
|
||||||
? fileData.description
|
? fileData.description
|
||||||
: // @ts-ignore
|
: htmlToJsx(fileData.filePath!, tree)
|
||||||
toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="popover-hint">
|
<div class="popover-hint">
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
|
||||||
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
|
||||||
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
|
|
||||||
import style from "../styles/listPage.scss"
|
import style from "../styles/listPage.scss"
|
||||||
import { PageList } from "../PageList"
|
import { PageList } from "../PageList"
|
||||||
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
|
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
|
||||||
import { QuartzPluginData } from "../../plugins/vfile"
|
import { QuartzPluginData } from "../../plugins/vfile"
|
||||||
import { Root } from "hast"
|
import { Root } from "hast"
|
||||||
import { pluralize } from "../../util/lang"
|
import { pluralize } from "../../util/lang"
|
||||||
|
import { htmlToJsx } from "../../util/jsx"
|
||||||
|
|
||||||
const numPages = 10
|
const numPages = 10
|
||||||
function TagContent(props: QuartzComponentProps) {
|
function TagContent(props: QuartzComponentProps) {
|
||||||
@ -26,8 +25,7 @@ function TagContent(props: QuartzComponentProps) {
|
|||||||
const content =
|
const content =
|
||||||
(tree as Root).children.length === 0
|
(tree as Root).children.length === 0
|
||||||
? fileData.description
|
? fileData.description
|
||||||
: // @ts-ignore
|
: htmlToJsx(fileData.filePath!, tree)
|
||||||
toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
|
||||||
|
|
||||||
if (tag === "") {
|
if (tag === "") {
|
||||||
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
|
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
|
||||||
@ -55,7 +53,7 @@ function TagContent(props: QuartzComponentProps) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>
|
<h2>
|
||||||
<a class="internal tag-link" href={`./${tag}`}>
|
<a class="internal tag-link" href={`../tags/${tag}`}>
|
||||||
#{tag}
|
#{tag}
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@ -28,8 +28,11 @@ async function mouseEnterHandler(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasAlreadyBeenFetched = () =>
|
||||||
|
[...link.children].some((child) => child.classList.contains("popover"))
|
||||||
|
|
||||||
// dont refetch if there's already a popover
|
// dont refetch if there's already a popover
|
||||||
if ([...link.children].some((child) => child.classList.contains("popover"))) {
|
if (hasAlreadyBeenFetched()) {
|
||||||
return setPosition(link.lastChild as HTMLElement)
|
return setPosition(link.lastChild as HTMLElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +52,11 @@ async function mouseEnterHandler(
|
|||||||
console.error(err)
|
console.error(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// bailout if another popover exists
|
||||||
|
if (hasAlreadyBeenFetched()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!contents) return
|
if (!contents) return
|
||||||
const html = p.parseFromString(contents, "text/html")
|
const html = p.parseFromString(contents, "text/html")
|
||||||
normalizeRelativeURLs(html, targetUrl)
|
normalizeRelativeURLs(html, targetUrl)
|
||||||
|
|||||||
@ -303,7 +303,6 @@ document.addEventListener("nav", async (e: unknown) => {
|
|||||||
// setup index if it hasn't been already
|
// setup index if it hasn't been already
|
||||||
if (!index) {
|
if (!index) {
|
||||||
index = new Document({
|
index = new Document({
|
||||||
cache: true,
|
|
||||||
charset: "latin:extra",
|
charset: "latin:extra",
|
||||||
optimize: true,
|
optimize: true,
|
||||||
encode: encoder,
|
encode: encoder,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import micromorph from "micromorph"
|
import micromorph from "micromorph"
|
||||||
import { FullSlug, RelativeURL, getFullSlug } from "../../util/path"
|
import { FullSlug, RelativeURL, getFullSlug } from "../../util/path"
|
||||||
|
import { normalizeRelativeURLs } from "./popover.inline"
|
||||||
|
|
||||||
// adapted from `micromorph`
|
// adapted from `micromorph`
|
||||||
// https://github.com/natemoo-re/micromorph
|
// https://github.com/natemoo-re/micromorph
|
||||||
@ -18,8 +19,15 @@ const isLocalUrl = (href: string) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSamePage = (url: URL): boolean => {
|
||||||
|
const sameOrigin = url.origin === window.location.origin
|
||||||
|
const samePath = url.pathname === window.location.pathname
|
||||||
|
return sameOrigin && samePath
|
||||||
|
}
|
||||||
|
|
||||||
const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => {
|
const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => {
|
||||||
if (!isElement(target)) return
|
if (!isElement(target)) return
|
||||||
|
if (target.attributes.getNamedItem("target")?.value === "_blank") return
|
||||||
const a = target.closest("a")
|
const a = target.closest("a")
|
||||||
if (!a) return
|
if (!a) return
|
||||||
if ("routerIgnore" in a.dataset) return
|
if ("routerIgnore" in a.dataset) return
|
||||||
@ -45,6 +53,8 @@ async function navigate(url: URL, isBack: boolean = false) {
|
|||||||
if (!contents) return
|
if (!contents) return
|
||||||
|
|
||||||
const html = p.parseFromString(contents, "text/html")
|
const html = p.parseFromString(contents, "text/html")
|
||||||
|
normalizeRelativeURLs(html, url)
|
||||||
|
|
||||||
let title = html.querySelector("title")?.textContent
|
let title = html.querySelector("title")?.textContent
|
||||||
if (title) {
|
if (title) {
|
||||||
document.title = title
|
document.title = title
|
||||||
@ -92,8 +102,16 @@ function createRouter() {
|
|||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
window.addEventListener("click", async (event) => {
|
window.addEventListener("click", async (event) => {
|
||||||
const { url } = getOpts(event) ?? {}
|
const { url } = getOpts(event) ?? {}
|
||||||
if (!url) return
|
// dont hijack behaviour, just let browser act normally
|
||||||
|
if (!url || event.ctrlKey || event.metaKey) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
if (isSamePage(url) && url.hash) {
|
||||||
|
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
|
||||||
|
el?.scrollIntoView()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
navigate(url, false)
|
navigate(url, false)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -139,6 +157,7 @@ if (!customElements.get("route-announcer")) {
|
|||||||
style:
|
style:
|
||||||
"position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px",
|
"position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px",
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define(
|
customElements.define(
|
||||||
"route-announcer",
|
"route-announcer",
|
||||||
class RouteAnnouncer extends HTMLElement {
|
class RouteAnnouncer extends HTMLElement {
|
||||||
|
|||||||
@ -24,8 +24,9 @@ function toggleToc(this: HTMLElement) {
|
|||||||
function setupToc() {
|
function setupToc() {
|
||||||
const toc = document.getElementById("toc")
|
const toc = document.getElementById("toc")
|
||||||
if (toc) {
|
if (toc) {
|
||||||
|
const collapsed = toc.classList.contains("collapsed")
|
||||||
const content = toc.nextElementSibling as HTMLElement
|
const content = toc.nextElementSibling as HTMLElement
|
||||||
content.style.maxHeight = content.scrollHeight + "px"
|
content.style.maxHeight = collapsed ? "0px" : content.scrollHeight + "px"
|
||||||
toc.removeEventListener("click", toggleToc)
|
toc.removeEventListener("click", toggleToc)
|
||||||
toc.addEventListener("click", toggleToc)
|
toc.addEventListener("click", toggleToc)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,11 +19,6 @@ li.section-li {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .tags {
|
|
||||||
justify-self: end;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .desc > h3 > a {
|
& > .desc > h3 > a {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { FilePath, FullSlug, resolveRelative, simplifySlug } from "../../util/path"
|
import { FilePath, FullSlug, joinSegments, resolveRelative, simplifySlug } from "../../util/path"
|
||||||
import { QuartzEmitterPlugin } from "../types"
|
import { QuartzEmitterPlugin } from "../types"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
@ -25,7 +25,12 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({
|
|||||||
slugs.push(permalink as FullSlug)
|
slugs.push(permalink as FullSlug)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const slug of slugs) {
|
for (let slug of slugs) {
|
||||||
|
// fix any slugs that have trailing slash
|
||||||
|
if (slug.endsWith("/")) {
|
||||||
|
slug = joinSegments(slug, "index") as FullSlug
|
||||||
|
}
|
||||||
|
|
||||||
const redirUrl = resolveRelative(slug, file.data.slug!)
|
const redirUrl = resolveRelative(slug, file.data.slug!)
|
||||||
const fp = await emit({
|
const fp = await emit({
|
||||||
content: `
|
content: `
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import spaRouterScript from "../../components/scripts/spa.inline"
|
|||||||
import plausibleScript from "../../components/scripts/plausible.inline"
|
import plausibleScript from "../../components/scripts/plausible.inline"
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import popoverScript from "../../components/scripts/popover.inline"
|
import popoverScript from "../../components/scripts/popover.inline"
|
||||||
import styles from "../../styles/base.scss"
|
import styles from "../../styles/custom.scss"
|
||||||
import popoverStyle from "../../components/styles/popover.scss"
|
import popoverStyle from "../../components/styles/popover.scss"
|
||||||
import { BuildCtx } from "../../util/ctx"
|
import { BuildCtx } from "../../util/ctx"
|
||||||
import { StaticResources } from "../../util/resources"
|
import { StaticResources } from "../../util/resources"
|
||||||
@ -164,7 +164,7 @@ export const ComponentResources: QuartzEmitterPlugin<Options> = (opts?: Partial<
|
|||||||
|
|
||||||
addGlobalPageResources(ctx, resources, componentResources)
|
addGlobalPageResources(ctx, resources, componentResources)
|
||||||
|
|
||||||
const stylesheet = joinStyles(ctx.cfg.configuration.theme, styles, ...componentResources.css)
|
const stylesheet = joinStyles(ctx.cfg.configuration.theme, ...componentResources.css, styles)
|
||||||
const prescript = joinScripts(componentResources.beforeDOMLoaded)
|
const prescript = joinScripts(componentResources.beforeDOMLoaded)
|
||||||
const postscript = joinScripts(componentResources.afterDOMLoaded)
|
const postscript = joinScripts(componentResources.afterDOMLoaded)
|
||||||
const fps = await Promise.all([
|
const fps = await Promise.all([
|
||||||
|
|||||||
@ -59,6 +59,17 @@ function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: nu
|
|||||||
</item>`
|
</item>`
|
||||||
|
|
||||||
const items = Array.from(idx)
|
const items = Array.from(idx)
|
||||||
|
.sort(([_, f1], [__, f2]) => {
|
||||||
|
if (f1.date && f2.date) {
|
||||||
|
return f2.date.getTime() - f1.date.getTime()
|
||||||
|
} else if (f1.date && !f2.date) {
|
||||||
|
return -1
|
||||||
|
} else if (!f1.date && f2.date) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return f1.title.localeCompare(f2.title)
|
||||||
|
})
|
||||||
.map(([slug, content]) => createURLEntry(simplifySlug(slug), content))
|
.map(([slug, content]) => createURLEntry(simplifySlug(slug), content))
|
||||||
.slice(0, limit ?? idx.size)
|
.slice(0, limit ?? idx.size)
|
||||||
.join("")
|
.join("")
|
||||||
|
|||||||
@ -18,11 +18,13 @@ interface Options {
|
|||||||
markdownLinkResolution: TransformOptions["strategy"]
|
markdownLinkResolution: TransformOptions["strategy"]
|
||||||
/** Strips folders from a link so that it looks nice */
|
/** Strips folders from a link so that it looks nice */
|
||||||
prettyLinks: boolean
|
prettyLinks: boolean
|
||||||
|
openLinksInNewTab: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
markdownLinkResolution: "absolute",
|
markdownLinkResolution: "absolute",
|
||||||
prettyLinks: true,
|
prettyLinks: true,
|
||||||
|
openLinksInNewTab: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||||
@ -52,6 +54,10 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
|
|||||||
node.properties.className ??= []
|
node.properties.className ??= []
|
||||||
node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal")
|
node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal")
|
||||||
|
|
||||||
|
if (opts.openLinksInNewTab) {
|
||||||
|
node.properties.target = "_blank"
|
||||||
|
}
|
||||||
|
|
||||||
// don't process external links or intra-document anchors
|
// don't process external links or intra-document anchors
|
||||||
const isInternal = !(isAbsoluteUrl(dest) || dest.startsWith("#"))
|
const isInternal = !(isAbsoluteUrl(dest) || dest.startsWith("#"))
|
||||||
if (isInternal) {
|
if (isInternal) {
|
||||||
|
|||||||
@ -8,12 +8,14 @@ export interface Options {
|
|||||||
maxDepth: 1 | 2 | 3 | 4 | 5 | 6
|
maxDepth: 1 | 2 | 3 | 4 | 5 | 6
|
||||||
minEntries: 1
|
minEntries: 1
|
||||||
showByDefault: boolean
|
showByDefault: boolean
|
||||||
|
collapseByDefault: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
maxDepth: 3,
|
maxDepth: 3,
|
||||||
minEntries: 1,
|
minEntries: 1,
|
||||||
showByDefault: true,
|
showByDefault: true,
|
||||||
|
collapseByDefault: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TocEntry {
|
interface TocEntry {
|
||||||
@ -54,6 +56,7 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
|
|||||||
...entry,
|
...entry,
|
||||||
depth: entry.depth - highestDepth,
|
depth: entry.depth - highestDepth,
|
||||||
}))
|
}))
|
||||||
|
file.data.collapseToc = opts.collapseByDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,5 +69,6 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
|
|||||||
declare module "vfile" {
|
declare module "vfile" {
|
||||||
interface DataMap {
|
interface DataMap {
|
||||||
toc: TocEntry[]
|
toc: TocEntry[]
|
||||||
|
collapseToc: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
@use "./custom.scss";
|
@use "./variables.scss" as *;
|
||||||
@use "./syntax.scss";
|
@use "./syntax.scss";
|
||||||
@use "./callouts.scss";
|
@use "./callouts.scss";
|
||||||
@use "./variables.scss" as *;
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
@ -65,7 +64,7 @@ a {
|
|||||||
color: var(--tertiary) !important;
|
color: var(--tertiary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.internal {
|
&.internal:not(:has(img)) {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: var(--highlight);
|
background-color: var(--highlight);
|
||||||
padding: 0 0.1rem;
|
padding: 0 0.1rem;
|
||||||
@ -391,23 +390,33 @@ p {
|
|||||||
line-height: 1.6rem;
|
line-height: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
.table-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
& > table {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
min-width: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 0.7rem;
|
||||||
border-bottom: 2px solid var(--gray);
|
border-bottom: 2px solid var(--gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 0.2rem 1rem;
|
padding: 0.2rem 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
|
@use "./base.scss";
|
||||||
|
|
||||||
// put your custom CSS here!
|
// put your custom CSS here!
|
||||||
|
|||||||
28
quartz/util/jsx.tsx
Normal file
28
quartz/util/jsx.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Components, Jsx, toJsxRuntime } from "hast-util-to-jsx-runtime"
|
||||||
|
import { QuartzPluginData } from "../plugins/vfile"
|
||||||
|
import { Node, Root } from "hast"
|
||||||
|
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
|
||||||
|
import { trace } from "./trace"
|
||||||
|
import { type FilePath } from "./path"
|
||||||
|
|
||||||
|
const customComponents: Components = {
|
||||||
|
table: (props) => (
|
||||||
|
<div class="table-container">
|
||||||
|
<table {...props} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export function htmlToJsx(fp: FilePath, tree: Node<QuartzPluginData>) {
|
||||||
|
try {
|
||||||
|
return toJsxRuntime(tree as Root, {
|
||||||
|
Fragment,
|
||||||
|
jsx: jsx as Jsx,
|
||||||
|
jsxs: jsxs as Jsx,
|
||||||
|
elementAttributeNameCase: "html",
|
||||||
|
components: customComponents,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
trace(`Failed to parse Markdown in \`${fp}\` into JSX`, e as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ import { isMainThread } from "workerpool"
|
|||||||
|
|
||||||
const rootFile = /.*at file:/
|
const rootFile = /.*at file:/
|
||||||
export function trace(msg: string, err: Error) {
|
export function trace(msg: string, err: Error) {
|
||||||
const stack = err.stack
|
let stack = err.stack ?? ""
|
||||||
|
|
||||||
const lines: string[] = []
|
const lines: string[] = []
|
||||||
|
|
||||||
@ -12,15 +12,11 @@ export function trace(msg: string, err: Error) {
|
|||||||
lines.push(
|
lines.push(
|
||||||
"\n" +
|
"\n" +
|
||||||
chalk.bgRed.black.bold(" ERROR ") +
|
chalk.bgRed.black.bold(" ERROR ") +
|
||||||
"\n" +
|
"\n\n" +
|
||||||
chalk.red(` ${msg}`) +
|
chalk.red(` ${msg}`) +
|
||||||
(err.message.length > 0 ? `: ${err.message}` : ""),
|
(err.message.length > 0 ? `: ${err.message}` : ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!stack) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let reachedEndOfLegibleTrace = false
|
let reachedEndOfLegibleTrace = false
|
||||||
for (const line of stack.split("\n").slice(1)) {
|
for (const line of stack.split("\n").slice(1)) {
|
||||||
if (reachedEndOfLegibleTrace) {
|
if (reachedEndOfLegibleTrace) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user