mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-28 07:14:05 -06:00
Merge branch 'v4' of github-bfahrenfort:jackyzha0/quartz into v4
This commit is contained in:
commit
08588de787
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@ -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"
|
||||
|
||||
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"]
|
||||
@ -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.
|
||||
|
||||
|
||||
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 .)
|
||||
```
|
||||
@ -196,7 +196,7 @@ Component.Explorer({
|
||||
}
|
||||
}
|
||||
},
|
||||
}})
|
||||
})
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
> [!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:
|
||||
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -6,7 +6,7 @@ Quartz is a fast, batteries-included static-site generator that transforms Markd
|
||||
|
||||
## 🪴 Get Started
|
||||
|
||||
Quartz requires **at least [Node](https://nodejs.org/) v18.14** to function correctly. Ensure you have this installed on your machine before continuing.
|
||||
Quartz requires **at least [Node](https://nodejs.org/) v18.14** and `npm` v9.3.1 to function correctly. Ensure you have this installed on your machine before continuing.
|
||||
|
||||
Then, in your terminal of choice, enter the following commands line by line:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,6 @@ 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/)
|
||||
|
||||
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)!
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=9.3.1",
|
||||
"node": ">=18.14"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
function ArticleTitle({ fileData }: QuartzComponentProps) {
|
||||
function ArticleTitle({ fileData, displayClass }: QuartzComponentProps) {
|
||||
const title = fileData.frontmatter?.title
|
||||
if (title) {
|
||||
return <h1 class="article-title">{title}</h1>
|
||||
return <h1 class={`article-title ${displayClass ?? ""}`}>{title}</h1>
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -2,11 +2,11 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
import style from "./styles/backlinks.scss"
|
||||
import { resolveRelative, simplifySlug } from "../util/path"
|
||||
|
||||
function Backlinks({ fileData, allFiles }: QuartzComponentProps) {
|
||||
function Backlinks({ fileData, allFiles, displayClass }: QuartzComponentProps) {
|
||||
const slug = simplifySlug(fileData.slug!)
|
||||
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
|
||||
return (
|
||||
<div class="backlinks">
|
||||
<div class={`backlinks ${displayClass ?? ""}`}>
|
||||
<h3>Backlinks</h3>
|
||||
<ul class="overflow">
|
||||
{backlinkFiles.length > 0 ? (
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
import breadcrumbsStyle from "./styles/breadcrumbs.scss"
|
||||
import { FullSlug, SimpleSlug, resolveRelative } from "../util/path"
|
||||
import { capitalize } from "../util/lang"
|
||||
import { QuartzPluginData } from "../plugins/vfile"
|
||||
|
||||
type CrumbData = {
|
||||
@ -36,7 +35,10 @@ const defaultOptions: BreadcrumbOptions = {
|
||||
}
|
||||
|
||||
function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData {
|
||||
return { displayName, path: resolveRelative(baseSlug, currentSlug) }
|
||||
return {
|
||||
displayName: displayName.replaceAll("-", " "),
|
||||
path: resolveRelative(baseSlug, currentSlug),
|
||||
}
|
||||
}
|
||||
|
||||
// given a folderName (e.g. "features"), search for the corresponding `index.md` file
|
||||
@ -58,52 +60,49 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
|
||||
// Merge options with defaults
|
||||
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
|
||||
|
||||
function Breadcrumbs({ fileData, allFiles }: QuartzComponentProps) {
|
||||
function Breadcrumbs({ 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(capitalize(options.rootName), fileData.slug!, "/" as SimpleSlug)
|
||||
const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug)
|
||||
const crumbs: CrumbData[] = [firstEntry]
|
||||
|
||||
// Get parts of filePath (every folder)
|
||||
const parts = fileData.filePath?.split("/")?.splice(1)
|
||||
if (parts) {
|
||||
// Split slug into hierarchy/parts
|
||||
const slugParts = fileData.slug?.split("/")
|
||||
if (slugParts) {
|
||||
// full path until current part
|
||||
let current = ""
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const folderName = parts[i]
|
||||
let currentTitle = folderName
|
||||
let currentPath = ""
|
||||
for (let i = 0; i < slugParts.length - 1; i++) {
|
||||
let currentTitle = 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, folderName)
|
||||
const currentFile = findCurrentFile(allFiles, currentTitle)
|
||||
if (currentFile) {
|
||||
currentTitle = currentFile.frontmatter!.title
|
||||
}
|
||||
}
|
||||
// Add current path to full path
|
||||
current += folderName + "/"
|
||||
// Add current slug to full path
|
||||
currentPath += slugParts[i] + "/"
|
||||
|
||||
// Format and add current crumb
|
||||
const crumb = formatCrumb(capitalize(currentTitle), fileData.slug!, current as SimpleSlug)
|
||||
const crumb = formatCrumb(currentTitle, fileData.slug!, currentPath as SimpleSlug)
|
||||
crumbs.push(crumb)
|
||||
}
|
||||
|
||||
// Add current file to crumb (can directly use frontmatter title)
|
||||
if (parts.length > 0) {
|
||||
crumbs.push({
|
||||
displayName: capitalize(fileData.frontmatter!.title),
|
||||
path: "",
|
||||
})
|
||||
}
|
||||
crumbs.push({
|
||||
displayName: fileData.frontmatter!.title,
|
||||
path: "",
|
||||
})
|
||||
}
|
||||
return (
|
||||
<nav class="breadcrumb-container" aria-label="breadcrumbs">
|
||||
<nav class={`breadcrumb-container ${displayClass ?? ""}`} aria-label="breadcrumbs">
|
||||
{crumbs.map((crumb, index) => (
|
||||
<div class="breadcrumb-element">
|
||||
<a href={crumb.path}>{crumb.displayName}</a>
|
||||
|
||||
@ -3,7 +3,7 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
import readingTime from "reading-time"
|
||||
|
||||
export default (() => {
|
||||
function ContentMetadata({ cfg, fileData }: QuartzComponentProps) {
|
||||
function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) {
|
||||
const text = fileData.text
|
||||
if (text) {
|
||||
const segments: string[] = []
|
||||
@ -14,7 +14,7 @@ export default (() => {
|
||||
}
|
||||
|
||||
segments.push(timeTaken)
|
||||
return <p class="content-meta">{segments.join(", ")}</p>
|
||||
return <p class={`content-meta ${displayClass ?? ""}`}>{segments.join(", ")}</p>
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
// see: https://v8.dev/features/modules#defer
|
||||
import darkmodeScript from "./scripts/darkmode.inline"
|
||||
import styles from "./styles/darkmode.scss"
|
||||
import { QuartzComponentConstructor } from "./types"
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
function Darkmode() {
|
||||
function Darkmode({ displayClass }: QuartzComponentProps) {
|
||||
return (
|
||||
<div class="darkmode">
|
||||
<div class={`darkmode ${displayClass ?? ""}`}>
|
||||
<input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
|
||||
<label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
|
||||
<svg
|
||||
|
||||
@ -86,7 +86,7 @@ export default ((userOpts?: Partial<Options>) => {
|
||||
function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) {
|
||||
constructFileTree(allFiles)
|
||||
return (
|
||||
<div class={`explorer ${displayClass}`}>
|
||||
<div class={`explorer ${displayClass ?? ""}`}>
|
||||
<button
|
||||
type="button"
|
||||
id="explorer"
|
||||
|
||||
@ -14,7 +14,7 @@ export default ((opts?: Optionss) => {
|
||||
const year = new Date().getFullYear()
|
||||
const links = opts?.links ?? []
|
||||
return (
|
||||
<footer>
|
||||
<footer class={`${displayClass ?? ""}`}>
|
||||
<hr />
|
||||
<p style="margin-bottom:4px;font-weight:bold;font-size:2em;">
|
||||
Share your thoughts with <a class="internal" href="/Projects/Obsidian/quartz-comments">Remark42</a>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuartzComponentConstructor } from "./types"
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
// @ts-ignore
|
||||
import script from "./scripts/graph.inline"
|
||||
import style from "./styles/graph.scss"
|
||||
@ -52,11 +52,11 @@ const defaultOptions: GraphOptions = {
|
||||
}
|
||||
|
||||
export default ((opts?: GraphOptions) => {
|
||||
function Graph() {
|
||||
function Graph({ displayClass }: QuartzComponentProps) {
|
||||
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
|
||||
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
|
||||
return (
|
||||
<div class="graph">
|
||||
<div class={`graph ${displayClass ?? ""}`}>
|
||||
<h3>Graph View</h3>
|
||||
<div class="graph-outer">
|
||||
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { pathToRoot } from "../util/path"
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
function PageTitle({ fileData, cfg }: QuartzComponentProps) {
|
||||
function PageTitle({ fileData, cfg, displayClass }: QuartzComponentProps) {
|
||||
const title = cfg?.pageTitle ?? "Untitled Quartz"
|
||||
const baseDir = pathToRoot(fileData.slug!)
|
||||
return (
|
||||
<h1 class="page-title">
|
||||
<h1 class={`page-title ${displayClass ?? ""}`}>
|
||||
<a href={baseDir}>{title}</a>
|
||||
</h1>
|
||||
)
|
||||
|
||||
@ -23,13 +23,12 @@ const defaultOptions = (cfg: GlobalConfiguration): Options => ({
|
||||
})
|
||||
|
||||
export default ((userOpts?: Partial<Options>) => {
|
||||
function RecentNotes(props: QuartzComponentProps) {
|
||||
const { allFiles, fileData, displayClass, cfg } = props
|
||||
function RecentNotes({ 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 (
|
||||
<div class={`recent-notes ${displayClass}`}>
|
||||
<div class={`recent-notes ${displayClass ?? ""}`}>
|
||||
<h3>{opts.title}</h3>
|
||||
<ul class="recent-ul">
|
||||
{pages.slice(0, opts.limit).map((page) => {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { QuartzComponentConstructor } from "./types"
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
import style from "./styles/search.scss"
|
||||
// @ts-ignore
|
||||
import script from "./scripts/search.inline"
|
||||
|
||||
export default (() => {
|
||||
function Search() {
|
||||
function Search({ displayClass }: QuartzComponentProps) {
|
||||
return (
|
||||
<div class="search">
|
||||
<div class={`search ${displayClass ?? ""}`}>
|
||||
<div id="search-icon">
|
||||
<p>Search</p>
|
||||
<div></div>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
function Spacer({ displayClass }: QuartzComponentProps) {
|
||||
const className = displayClass ? `spacer ${displayClass}` : "spacer"
|
||||
return <div class={className}></div>
|
||||
return <div class={`spacer ${displayClass ?? ""}`}></div>
|
||||
}
|
||||
|
||||
export default (() => Spacer) satisfies QuartzComponentConstructor
|
||||
|
||||
@ -19,7 +19,7 @@ function TableOfContents({ fileData, displayClass }: QuartzComponentProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={`toc ${displayClass}`}>
|
||||
<div class={`toc ${displayClass ?? ""}`}>
|
||||
<button type="button" id="toc">
|
||||
<h3>On This Page</h3>
|
||||
<svg
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { pathToRoot, slugTag } from "../util/path"
|
||||
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
function TagList({ fileData }: QuartzComponentProps) {
|
||||
function TagList({ fileData, displayClass }: QuartzComponentProps) {
|
||||
const tags = fileData.frontmatter?.tags
|
||||
const baseDir = pathToRoot(fileData.slug!)
|
||||
if (tags && tags.length > 0) {
|
||||
return (
|
||||
<ul class="tags">
|
||||
<ul class={`tags ${displayClass ?? ""}`}>
|
||||
{tags.map((tag) => {
|
||||
const display = `#${tag}`
|
||||
const linkDest = baseDir + `/tags/${slugTag(tag)}`
|
||||
@ -28,11 +28,16 @@ function TagList({ fileData }: QuartzComponentProps) {
|
||||
TagList.css = `
|
||||
.tags {
|
||||
list-style: none;
|
||||
display:flex;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
padding-left: 0;
|
||||
gap: 0.4rem;
|
||||
margin: 1rem 0;
|
||||
flex-wrap: wrap;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.section-li > .section > .tags {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tags > li {
|
||||
@ -42,7 +47,7 @@ TagList.css = `
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
a.tag-link {
|
||||
a.internal.tag-link {
|
||||
border-radius: 8px;
|
||||
background-color: var(--highlight);
|
||||
padding: 0.2rem 0.4rem;
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { htmlToJsx } from "../../util/jsx"
|
||||
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) {
|
||||
// @ts-ignore (preact makes it angry)
|
||||
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
||||
function Content({ fileData, tree }: QuartzComponentProps) {
|
||||
const content = htmlToJsx(fileData.filePath!, tree)
|
||||
return <article class="popover-hint">{content}</article>
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
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 style from "../styles/listPage.scss"
|
||||
@ -8,6 +6,7 @@ import { PageList } from "../PageList"
|
||||
import { _stripSlashes, simplifySlug } from "../../util/path"
|
||||
import { Root } from "hast"
|
||||
import { pluralize } from "../../util/lang"
|
||||
import { htmlToJsx } from "../../util/jsx"
|
||||
|
||||
function FolderContent(props: QuartzComponentProps) {
|
||||
const { tree, fileData, allFiles } = props
|
||||
@ -29,8 +28,7 @@ function FolderContent(props: QuartzComponentProps) {
|
||||
const content =
|
||||
(tree as Root).children.length === 0
|
||||
? fileData.description
|
||||
: // @ts-ignore
|
||||
toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
||||
: htmlToJsx(fileData.filePath!, tree)
|
||||
|
||||
return (
|
||||
<div class="popover-hint">
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
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 { PageList } from "../PageList"
|
||||
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
|
||||
import { QuartzPluginData } from "../../plugins/vfile"
|
||||
import { Root } from "hast"
|
||||
import { pluralize } from "../../util/lang"
|
||||
import { htmlToJsx } from "../../util/jsx"
|
||||
|
||||
const numPages = 10
|
||||
function TagContent(props: QuartzComponentProps) {
|
||||
@ -26,8 +25,7 @@ function TagContent(props: QuartzComponentProps) {
|
||||
const content =
|
||||
(tree as Root).children.length === 0
|
||||
? fileData.description
|
||||
: // @ts-ignore
|
||||
toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
||||
: htmlToJsx(fileData.filePath!, tree)
|
||||
|
||||
if (tag === "") {
|
||||
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
|
||||
|
||||
@ -303,7 +303,6 @@ document.addEventListener("nav", async (e: unknown) => {
|
||||
// setup index if it hasn't been already
|
||||
if (!index) {
|
||||
index = new Document({
|
||||
cache: true,
|
||||
charset: "latin:extra",
|
||||
optimize: true,
|
||||
encode: encoder,
|
||||
|
||||
@ -20,6 +20,7 @@ const isLocalUrl = (href: string) => {
|
||||
|
||||
const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => {
|
||||
if (!isElement(target)) return
|
||||
if (target.attributes.getNamedItem("target")?.value === "_blank") return
|
||||
const a = target.closest("a")
|
||||
if (!a) return
|
||||
if ("routerIgnore" in a.dataset) return
|
||||
@ -92,7 +93,7 @@ function createRouter() {
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("click", async (event) => {
|
||||
const { url } = getOpts(event) ?? {}
|
||||
if (!url) return
|
||||
if (!url || event.ctrlKey || event.metaKey) return
|
||||
event.preventDefault()
|
||||
try {
|
||||
navigate(url, false)
|
||||
|
||||
@ -19,11 +19,6 @@ li.section-li {
|
||||
}
|
||||
}
|
||||
|
||||
& > .tags {
|
||||
justify-self: end;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
& > .desc > h3 > a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import spaRouterScript from "../../components/scripts/spa.inline"
|
||||
import plausibleScript from "../../components/scripts/plausible.inline"
|
||||
// @ts-ignore
|
||||
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 { BuildCtx } from "../../util/ctx"
|
||||
import { StaticResources } from "../../util/resources"
|
||||
@ -164,7 +164,7 @@ export const ComponentResources: QuartzEmitterPlugin<Options> = (opts?: Partial<
|
||||
|
||||
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 postscript = joinScripts(componentResources.afterDOMLoaded)
|
||||
const fps = await Promise.all([
|
||||
|
||||
@ -18,11 +18,13 @@ interface Options {
|
||||
markdownLinkResolution: TransformOptions["strategy"]
|
||||
/** Strips folders from a link so that it looks nice */
|
||||
prettyLinks: boolean
|
||||
openLinksInNewTab: boolean
|
||||
}
|
||||
|
||||
const defaultOptions: Options = {
|
||||
markdownLinkResolution: "absolute",
|
||||
prettyLinks: true,
|
||||
openLinksInNewTab: false,
|
||||
}
|
||||
|
||||
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.push(isAbsoluteUrl(dest) ? "external" : "internal")
|
||||
|
||||
if (opts.openLinksInNewTab) {
|
||||
node.properties.target = "_blank"
|
||||
}
|
||||
|
||||
// don't process external links or intra-document anchors
|
||||
const isInternal = !(isAbsoluteUrl(dest) || dest.startsWith("#"))
|
||||
if (isInternal) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
@use "./custom.scss";
|
||||
@use "./variables.scss" as *;
|
||||
@use "./syntax.scss";
|
||||
@use "./callouts.scss";
|
||||
@use "./variables.scss" as *;
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
@ -95,6 +94,8 @@ a {
|
||||
}
|
||||
|
||||
& article {
|
||||
position: relative;
|
||||
|
||||
& > h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
@ -328,7 +329,6 @@ pre {
|
||||
|
||||
&:has(> code.mermaid) {
|
||||
border: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
& > code {
|
||||
|
||||
@ -1 +1,3 @@
|
||||
@use "./base.scss";
|
||||
|
||||
// put your custom CSS here!
|
||||
|
||||
15
quartz/util/jsx.ts
Normal file
15
quartz/util/jsx.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { 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"
|
||||
|
||||
export function htmlToJsx(fp: FilePath, tree: Node<QuartzPluginData>) {
|
||||
try {
|
||||
// @ts-ignore (preact makes it angry)
|
||||
return toJsxRuntime(tree as Root, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
|
||||
} 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:/
|
||||
export function trace(msg: string, err: Error) {
|
||||
const stack = err.stack
|
||||
let stack = err.stack ?? ""
|
||||
|
||||
const lines: string[] = []
|
||||
|
||||
@ -12,15 +12,11 @@ export function trace(msg: string, err: Error) {
|
||||
lines.push(
|
||||
"\n" +
|
||||
chalk.bgRed.black.bold(" ERROR ") +
|
||||
"\n" +
|
||||
"\n\n" +
|
||||
chalk.red(` ${msg}`) +
|
||||
(err.message.length > 0 ? `: ${err.message}` : ""),
|
||||
)
|
||||
|
||||
if (!stack) {
|
||||
return
|
||||
}
|
||||
|
||||
let reachedEndOfLegibleTrace = false
|
||||
for (const line of stack.split("\n").slice(1)) {
|
||||
if (reachedEndOfLegibleTrace) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user