forked from github/quartz
Compare commits
26 Commits
active-fol
...
v4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec00a40aef | ||
|
|
25a6747d7d | ||
|
|
b4fb0e6682 | ||
|
|
f346a01296 | ||
|
|
c2dcc63b5f | ||
|
|
c2bea8a4c4 | ||
|
|
fa8d87a23a | ||
|
|
65c5b27041 | ||
|
|
5208a96a37 | ||
|
|
31ea7852fd | ||
|
|
7dc826be0a | ||
|
|
11ab6da80c | ||
|
|
de1e7505ba | ||
|
|
9c042dd717 | ||
|
|
bacd19c4ea | ||
|
|
722277b202 | ||
|
|
e6cc9ba368 | ||
|
|
643aca5ffa | ||
|
|
ec26ebcc9e | ||
|
|
19e324d914 | ||
|
|
368203cf85 | ||
|
|
13ff64db97 | ||
|
|
87f7f4804e | ||
|
|
c99c8070f2 | ||
|
|
e7d2a57aad | ||
|
|
ef29c69828 |
6
.github/workflows/build-preview.yaml
vendored
6
.github/workflows/build-preview.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Build Preview
|
name: Build Preview
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
run: npx quartz build -d docs -v
|
run: npx quartz build -d docs -v
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: preview-build
|
name: preview-build
|
||||||
path: public
|
path: public
|
||||||
|
|||||||
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
node-version: 22
|
node-version: 22
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
|
|||||||
2
.github/workflows/deploy-preview.yaml
vendored
2
.github/workflows/deploy-preview.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
name: Deploy Preview to Cloudflare Pages
|
name: Deploy Preview to Cloudflare Pages
|
||||||
steps:
|
steps:
|
||||||
- name: Download build artifact
|
- name: Download build artifact
|
||||||
uses: actions/download-artifact@v6
|
uses: actions/download-artifact@v7
|
||||||
id: preview-build-artifact
|
id: preview-build-artifact
|
||||||
with:
|
with:
|
||||||
name: preview-build
|
name: preview-build
|
||||||
|
|||||||
4
.github/workflows/docker-build-push.yaml
vendored
4
.github/workflows/docker-build-push.yaml
vendored
@@ -21,11 +21,11 @@ jobs:
|
|||||||
echo "OWNER_LOWERCASE=${OWNER,,}" >> ${GITHUB_ENV}
|
echo "OWNER_LOWERCASE=${OWNER,,}" >> ${GITHUB_ENV}
|
||||||
env:
|
env:
|
||||||
OWNER: "${{ github.repository_owner }}"
|
OWNER: "${{ github.repository_owner }}"
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
- name: Inject slug/short variables
|
- name: Inject slug/short variables
|
||||||
uses: rlespinasse/github-slug-action@v5.3.0
|
uses: rlespinasse/github-slug-action@v5.4.0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
> “[One] who works with the door open gets all kinds of interruptions, but [they] also occasionally gets clues as to what the world is and what might be important.” — Richard Hamming
|
> “[One] who works with the door open gets all kinds of interruptions, but [they] also occasionally gets clues as to what the world is and what might be important.” — Richard Hamming
|
||||||
|
|
||||||
Quartz is a set of tools that helps you publish your [digital garden](https://jzhao.xyz/posts/networked-thought) and notes as a website for free.
|
Quartz is a set of tools that helps you publish your [digital garden](https://jzhao.xyz/posts/networked-thought) and notes as a website for free.
|
||||||
Quartz v4 features a from-the-ground rewrite focusing on end-user extensibility and ease-of-use.
|
|
||||||
|
|
||||||
🔗 Read the documentation and get started: https://quartz.jzhao.xyz/
|
🔗 Read the documentation and get started: https://quartz.jzhao.xyz/
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ This part of the configuration concerns anything that can affect the whole site.
|
|||||||
- `{provider: 'clarity', projectId: '<your-clarity-id-code' }`: use [Microsoft clarity](https://clarity.microsoft.com/). The project id can be found on top of the overview page.
|
- `{provider: 'clarity', projectId: '<your-clarity-id-code' }`: use [Microsoft clarity](https://clarity.microsoft.com/). The project id can be found on top of the overview page.
|
||||||
- `{ provider: 'matomo', siteId: '<your-matomo-id-code', host: 'matomo.example.com' }`: use [Matomo](https://matomo.org/), without protocol.
|
- `{ provider: 'matomo', siteId: '<your-matomo-id-code', host: 'matomo.example.com' }`: use [Matomo](https://matomo.org/), without protocol.
|
||||||
- `{ provider: 'vercel' }`: use [Vercel Web Analytics](https://vercel.com/docs/concepts/analytics).
|
- `{ provider: 'vercel' }`: use [Vercel Web Analytics](https://vercel.com/docs/concepts/analytics).
|
||||||
|
- `{ provider: 'rybbit', siteId: 'my-rybbit-id' }` (managed) or `{ provider: 'rybbit', siteId: 'my-rybbit-id', host: 'my-rybbit-domain.com' }` (self-hosted) use [Rybbit](https://rybbit.com);
|
||||||
- `locale`: used for [[i18n]] and date formatting
|
- `locale`: used for [[i18n]] and date formatting
|
||||||
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
|
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
|
||||||
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`.
|
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`.
|
||||||
|
|||||||
@@ -5,3 +5,7 @@ You can run the below one-liner to run Quartz in Docker.
|
|||||||
```sh
|
```sh
|
||||||
docker run --rm -itp 8080:8080 -p 3001:3001 -v ./content:/usr/src/app/content $(docker build -q .)
|
docker run --rm -itp 8080:8080 -p 3001:3001 -v ./content:/usr/src/app/content $(docker build -q .)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!warning] Not to be used for production
|
||||||
|
> Serve mode is intended for local previews only.
|
||||||
|
> For production workloads, see the page on [[hosting]].
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ You can access the tags of a file by `node.data.tags`.
|
|||||||
Component.Explorer({
|
Component.Explorer({
|
||||||
filterFn: (node) => {
|
filterFn: (node) => {
|
||||||
// exclude files with the tag "explorerexclude"
|
// exclude files with the tag "explorerexclude"
|
||||||
return node.data.tags?.includes("explorerexclude") !== true
|
return node.data?.tags?.includes("explorerexclude") !== true
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ By default, Quartz only fetches previews for pages inside your vault due to [COR
|
|||||||
|
|
||||||
When [[creating components|creating your own components]], you can include this `popover-hint` class to also include it in the popover.
|
When [[creating components|creating your own components]], you can include this `popover-hint` class to also include it in the popover.
|
||||||
|
|
||||||
Similar to Obsidian, [[quartz layout.png|images referenced using wikilinks]] can also be viewed as popups.
|
Similar to Obsidian, [[quartz-layout-desktop.png|images referenced using wikilinks]] can also be viewed as popups.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|||||||
1484
package-lock.json
generated
1484
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -42,28 +42,28 @@
|
|||||||
"@tweenjs/tween.js": "^25.0.0",
|
"@tweenjs/tween.js": "^25.0.0",
|
||||||
"ansi-truncate": "^1.4.0",
|
"ansi-truncate": "^1.4.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^5.0.0",
|
||||||
"cli-spinner": "^0.2.10",
|
"cli-spinner": "^0.2.10",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"esbuild-sass-plugin": "^3.3.1",
|
"esbuild-sass-plugin": "^3.6.0",
|
||||||
"flexsearch": "^0.8.205",
|
"flexsearch": "^0.8.205",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
"globby": "^15.0.0",
|
"globby": "^16.1.0",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"hast-util-to-html": "^9.0.5",
|
"hast-util-to-html": "^9.0.5",
|
||||||
"hast-util-to-jsx-runtime": "^2.3.6",
|
"hast-util-to-jsx-runtime": "^2.3.6",
|
||||||
"hast-util-to-string": "^3.0.1",
|
"hast-util-to-string": "^3.0.1",
|
||||||
"is-absolute-url": "^5.0.0",
|
"is-absolute-url": "^5.0.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.1",
|
||||||
"lightningcss": "^1.30.2",
|
"lightningcss": "^1.31.1",
|
||||||
"mdast-util-find-and-replace": "^3.0.2",
|
"mdast-util-find-and-replace": "^3.0.2",
|
||||||
"mdast-util-to-hast": "^13.2.0",
|
"mdast-util-to-hast": "^13.2.1",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"micromorph": "^0.4.5",
|
"micromorph": "^0.4.5",
|
||||||
"minimatch": "^10.1.1",
|
"minimatch": "^10.1.1",
|
||||||
"pixi.js": "^8.14.0",
|
"pixi.js": "^8.15.0",
|
||||||
"preact": "^10.27.2",
|
"preact": "^10.28.2",
|
||||||
"preact-render-to-string": "^6.6.3",
|
"preact-render-to-string": "^6.6.5",
|
||||||
"pretty-bytes": "^7.1.0",
|
"pretty-bytes": "^7.1.0",
|
||||||
"pretty-time": "^1.1.0",
|
"pretty-time": "^1.1.0",
|
||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
@@ -83,32 +83,32 @@
|
|||||||
"remark-rehype": "^11.1.2",
|
"remark-rehype": "^11.1.2",
|
||||||
"remark-smartypants": "^3.0.2",
|
"remark-smartypants": "^3.0.2",
|
||||||
"rfdc": "^1.4.1",
|
"rfdc": "^1.4.1",
|
||||||
"satori": "^0.18.3",
|
"satori": "^0.19.1",
|
||||||
"serve-handler": "^6.1.6",
|
"serve-handler": "^6.1.6",
|
||||||
"sharp": "^0.34.4",
|
"sharp": "^0.34.5",
|
||||||
"shiki": "^1.26.2",
|
"shiki": "^1.26.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"to-vfile": "^8.0.0",
|
"to-vfile": "^8.0.0",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.1.0",
|
||||||
"vfile": "^6.0.3",
|
"vfile": "^6.0.3",
|
||||||
"workerpool": "^10.0.0",
|
"workerpool": "^10.0.1",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.19.0",
|
||||||
"yargs": "^18.0.0"
|
"yargs": "^18.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^24.10.0",
|
"@types/node": "^25.0.10",
|
||||||
"@types/pretty-time": "^1.1.5",
|
"@types/pretty-time": "^1.1.5",
|
||||||
"@types/source-map-support": "^0.5.10",
|
"@types/source-map-support": "^0.5.10",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@types/yargs": "^17.0.34",
|
"@types/yargs": "^17.0.35",
|
||||||
"esbuild": "^0.25.12",
|
"esbuild": "^0.27.2",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.8.1",
|
||||||
"tsx": "^4.20.6",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ async function startWatching(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const watcher = chokidar.watch(".", {
|
const watcher = chokidar.watch(".", {
|
||||||
|
awaitWriteFinish: { stabilityThreshold: 250 },
|
||||||
persistent: true,
|
persistent: true,
|
||||||
cwd: argv.directory,
|
cwd: argv.directory,
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ export type Analytics =
|
|||||||
| {
|
| {
|
||||||
provider: "vercel"
|
provider: "vercel"
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
provider: "rybbit"
|
||||||
|
siteId: string
|
||||||
|
host?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface GlobalConfiguration {
|
export interface GlobalConfiguration {
|
||||||
pageTitle: string
|
pageTitle: string
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import fs from "fs"
|
|||||||
export function escapePath(fp) {
|
export function escapePath(fp) {
|
||||||
return fp
|
return fp
|
||||||
.replace(/\\ /g, " ") // unescape spaces
|
.replace(/\\ /g, " ") // unescape spaces
|
||||||
.replace(/^".*"$/, "$1")
|
.replace(/^"(.*)"$/, "$1")
|
||||||
.replace(/^'.*"$/, "$1")
|
.replace(/^'(.*)'$/, "$1")
|
||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ export function renderPage(
|
|||||||
</body>
|
</body>
|
||||||
{pageResources.js
|
{pageResources.js
|
||||||
.filter((resource) => resource.loadTime === "afterDOMReady")
|
.filter((resource) => resource.loadTime === "afterDOMReady")
|
||||||
.map((res) => JSResourceToScriptElement(res))}
|
.map((res) => JSResourceToScriptElement(res, true))}
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ function createFolderNode(
|
|||||||
const folderPath = node.slug
|
const folderPath = node.slug
|
||||||
folderContainer.dataset.folderpath = folderPath
|
folderContainer.dataset.folderpath = folderPath
|
||||||
|
|
||||||
|
if (currentSlug === folderPath) {
|
||||||
|
folderContainer.classList.add("active")
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.folderClickBehavior === "link") {
|
if (opts.folderClickBehavior === "link") {
|
||||||
// Replace button with link for link behavior
|
// Replace button with link for link behavior
|
||||||
const button = titleContainer.querySelector(".folder-button") as HTMLElement
|
const button = titleContainer.querySelector(".folder-button") as HTMLElement
|
||||||
|
|||||||
@@ -29,17 +29,31 @@ class DiagramPanZoom {
|
|||||||
const mouseDownHandler = this.onMouseDown.bind(this)
|
const mouseDownHandler = this.onMouseDown.bind(this)
|
||||||
const mouseMoveHandler = this.onMouseMove.bind(this)
|
const mouseMoveHandler = this.onMouseMove.bind(this)
|
||||||
const mouseUpHandler = this.onMouseUp.bind(this)
|
const mouseUpHandler = this.onMouseUp.bind(this)
|
||||||
|
|
||||||
|
// Touch drag events
|
||||||
|
const touchStartHandler = this.onTouchStart.bind(this)
|
||||||
|
const touchMoveHandler = this.onTouchMove.bind(this)
|
||||||
|
const touchEndHandler = this.onTouchEnd.bind(this)
|
||||||
|
|
||||||
const resizeHandler = this.resetTransform.bind(this)
|
const resizeHandler = this.resetTransform.bind(this)
|
||||||
|
|
||||||
this.container.addEventListener("mousedown", mouseDownHandler)
|
this.container.addEventListener("mousedown", mouseDownHandler)
|
||||||
document.addEventListener("mousemove", mouseMoveHandler)
|
document.addEventListener("mousemove", mouseMoveHandler)
|
||||||
document.addEventListener("mouseup", mouseUpHandler)
|
document.addEventListener("mouseup", mouseUpHandler)
|
||||||
|
|
||||||
|
this.container.addEventListener("touchstart", touchStartHandler, { passive: false })
|
||||||
|
document.addEventListener("touchmove", touchMoveHandler, { passive: false })
|
||||||
|
document.addEventListener("touchend", touchEndHandler)
|
||||||
|
|
||||||
window.addEventListener("resize", resizeHandler)
|
window.addEventListener("resize", resizeHandler)
|
||||||
|
|
||||||
this.cleanups.push(
|
this.cleanups.push(
|
||||||
() => this.container.removeEventListener("mousedown", mouseDownHandler),
|
() => this.container.removeEventListener("mousedown", mouseDownHandler),
|
||||||
() => document.removeEventListener("mousemove", mouseMoveHandler),
|
() => document.removeEventListener("mousemove", mouseMoveHandler),
|
||||||
() => document.removeEventListener("mouseup", mouseUpHandler),
|
() => document.removeEventListener("mouseup", mouseUpHandler),
|
||||||
|
() => this.container.removeEventListener("touchstart", touchStartHandler),
|
||||||
|
() => document.removeEventListener("touchmove", touchMoveHandler),
|
||||||
|
() => document.removeEventListener("touchend", touchEndHandler),
|
||||||
() => window.removeEventListener("resize", resizeHandler),
|
() => window.removeEventListener("resize", resizeHandler),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -99,6 +113,30 @@ class DiagramPanZoom {
|
|||||||
this.container.style.cursor = "grab"
|
this.container.style.cursor = "grab"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onTouchStart(e: TouchEvent) {
|
||||||
|
if (e.touches.length !== 1) return
|
||||||
|
this.isDragging = true
|
||||||
|
const touch = e.touches[0]
|
||||||
|
this.startPan = { x: touch.clientX - this.currentPan.x, y: touch.clientY - this.currentPan.y }
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTouchMove(e: TouchEvent) {
|
||||||
|
if (!this.isDragging || e.touches.length !== 1) return
|
||||||
|
e.preventDefault() // Prevent scrolling
|
||||||
|
|
||||||
|
const touch = e.touches[0]
|
||||||
|
this.currentPan = {
|
||||||
|
x: touch.clientX - this.startPan.x,
|
||||||
|
y: touch.clientY - this.startPan.y,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTransform()
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTouchEnd() {
|
||||||
|
this.isDragging = false
|
||||||
|
}
|
||||||
|
|
||||||
private zoom(delta: number) {
|
private zoom(delta: number) {
|
||||||
const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
|
const newScale = Math.min(Math.max(this.scale + delta, this.MIN_SCALE), this.MAX_SCALE)
|
||||||
|
|
||||||
@@ -120,11 +158,15 @@ class DiagramPanZoom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private resetTransform() {
|
private resetTransform() {
|
||||||
this.scale = 1
|
|
||||||
const svg = this.content.querySelector("svg")!
|
const svg = this.content.querySelector("svg")!
|
||||||
|
const rect = svg.getBoundingClientRect()
|
||||||
|
const width = rect.width / this.scale
|
||||||
|
const height = rect.height / this.scale
|
||||||
|
|
||||||
|
this.scale = 1
|
||||||
this.currentPan = {
|
this.currentPan = {
|
||||||
x: svg.getBoundingClientRect().width / 2,
|
x: (this.container.clientWidth - width) / 2,
|
||||||
y: svg.getBoundingClientRect().height / 2,
|
y: (this.container.clientHeight - height) / 2,
|
||||||
}
|
}
|
||||||
this.updateTransform()
|
this.updateTransform()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,49 @@ interface Item {
|
|||||||
type SearchType = "basic" | "tags"
|
type SearchType = "basic" | "tags"
|
||||||
let searchType: SearchType = "basic"
|
let searchType: SearchType = "basic"
|
||||||
let currentSearchTerm: string = ""
|
let currentSearchTerm: string = ""
|
||||||
const encoder = (str: string) => {
|
const encoder = (str: string): string[] => {
|
||||||
return str
|
const tokens: string[] = []
|
||||||
.toLowerCase()
|
let bufferStart = -1
|
||||||
.split(/\s+/)
|
let bufferEnd = -1
|
||||||
.filter((token) => token.length > 0)
|
const lower = str.toLowerCase()
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
for (const char of lower) {
|
||||||
|
const code = char.codePointAt(0)!
|
||||||
|
|
||||||
|
const isCJK =
|
||||||
|
(code >= 0x3040 && code <= 0x309f) ||
|
||||||
|
(code >= 0x30a0 && code <= 0x30ff) ||
|
||||||
|
(code >= 0x4e00 && code <= 0x9fff) ||
|
||||||
|
(code >= 0xac00 && code <= 0xd7af) ||
|
||||||
|
(code >= 0x20000 && code <= 0x2a6df)
|
||||||
|
|
||||||
|
const isWhitespace = code === 32 || code === 9 || code === 10 || code === 13
|
||||||
|
|
||||||
|
if (isCJK) {
|
||||||
|
if (bufferStart !== -1) {
|
||||||
|
tokens.push(lower.slice(bufferStart, bufferEnd))
|
||||||
|
bufferStart = -1
|
||||||
|
}
|
||||||
|
tokens.push(char)
|
||||||
|
} else if (isWhitespace) {
|
||||||
|
if (bufferStart !== -1) {
|
||||||
|
tokens.push(lower.slice(bufferStart, bufferEnd))
|
||||||
|
bufferStart = -1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bufferStart === -1) bufferStart = i
|
||||||
|
bufferEnd = i + char.length
|
||||||
|
}
|
||||||
|
|
||||||
|
i += char.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferStart !== -1) {
|
||||||
|
tokens.push(lower.slice(bufferStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = new FlexSearch.Document<Item>({
|
let index = new FlexSearch.Document<Item>({
|
||||||
|
|||||||
163
quartz/components/scripts/search.test.ts
Normal file
163
quartz/components/scripts/search.test.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import test, { describe } from "node:test"
|
||||||
|
import assert from "node:assert"
|
||||||
|
|
||||||
|
// Inline the encoder function from search.inline.ts for testing
|
||||||
|
const encoder = (str: string): string[] => {
|
||||||
|
const tokens: string[] = []
|
||||||
|
let bufferStart = -1
|
||||||
|
let bufferEnd = -1
|
||||||
|
const lower = str.toLowerCase()
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
for (const char of lower) {
|
||||||
|
const code = char.codePointAt(0)!
|
||||||
|
|
||||||
|
const isCJK =
|
||||||
|
(code >= 0x3040 && code <= 0x309f) ||
|
||||||
|
(code >= 0x30a0 && code <= 0x30ff) ||
|
||||||
|
(code >= 0x4e00 && code <= 0x9fff) ||
|
||||||
|
(code >= 0xac00 && code <= 0xd7af) ||
|
||||||
|
(code >= 0x20000 && code <= 0x2a6df)
|
||||||
|
|
||||||
|
const isWhitespace = code === 32 || code === 9 || code === 10 || code === 13
|
||||||
|
|
||||||
|
if (isCJK) {
|
||||||
|
if (bufferStart !== -1) {
|
||||||
|
tokens.push(lower.slice(bufferStart, bufferEnd))
|
||||||
|
bufferStart = -1
|
||||||
|
}
|
||||||
|
tokens.push(char)
|
||||||
|
} else if (isWhitespace) {
|
||||||
|
if (bufferStart !== -1) {
|
||||||
|
tokens.push(lower.slice(bufferStart, bufferEnd))
|
||||||
|
bufferStart = -1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bufferStart === -1) bufferStart = i
|
||||||
|
bufferEnd = i + char.length
|
||||||
|
}
|
||||||
|
|
||||||
|
i += char.length
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferStart !== -1) {
|
||||||
|
tokens.push(lower.slice(bufferStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("search encoder", () => {
|
||||||
|
describe("English text", () => {
|
||||||
|
test("should tokenize simple English words", () => {
|
||||||
|
const result = encoder("hello world")
|
||||||
|
assert.deepStrictEqual(result, ["hello", "world"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle multiple spaces", () => {
|
||||||
|
const result = encoder("hello world")
|
||||||
|
assert.deepStrictEqual(result, ["hello", "world"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle tabs and newlines", () => {
|
||||||
|
const result = encoder("hello\tworld\ntest")
|
||||||
|
assert.deepStrictEqual(result, ["hello", "world", "test"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should lowercase all text", () => {
|
||||||
|
const result = encoder("Hello WORLD Test")
|
||||||
|
assert.deepStrictEqual(result, ["hello", "world", "test"])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("CJK text", () => {
|
||||||
|
test("should tokenize Japanese Hiragana character by character", () => {
|
||||||
|
const result = encoder("こんにちは")
|
||||||
|
assert.deepStrictEqual(result, ["こ", "ん", "に", "ち", "は"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should tokenize Japanese Katakana character by character", () => {
|
||||||
|
const result = encoder("コントロール")
|
||||||
|
assert.deepStrictEqual(result, ["コ", "ン", "ト", "ロ", "ー", "ル"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should tokenize Japanese Kanji character by character", () => {
|
||||||
|
const result = encoder("日本語")
|
||||||
|
assert.deepStrictEqual(result, ["日", "本", "語"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should tokenize Korean Hangul character by character", () => {
|
||||||
|
const result = encoder("안녕하세요")
|
||||||
|
assert.deepStrictEqual(result, ["안", "녕", "하", "세", "요"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should tokenize Chinese characters character by character", () => {
|
||||||
|
const result = encoder("你好世界")
|
||||||
|
assert.deepStrictEqual(result, ["你", "好", "世", "界"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle mixed Hiragana/Katakana/Kanji", () => {
|
||||||
|
const result = encoder("て以来")
|
||||||
|
assert.deepStrictEqual(result, ["て", "以", "来"])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Mixed CJK and English", () => {
|
||||||
|
test("should handle Japanese with English words", () => {
|
||||||
|
const result = encoder("hello 世界")
|
||||||
|
assert.deepStrictEqual(result, ["hello", "世", "界"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle English with Japanese words", () => {
|
||||||
|
const result = encoder("世界 hello world")
|
||||||
|
assert.deepStrictEqual(result, ["世", "界", "hello", "world"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle complex mixed content", () => {
|
||||||
|
const result = encoder("これはtest文章です")
|
||||||
|
assert.deepStrictEqual(result, ["こ", "れ", "は", "test", "文", "章", "で", "す"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle mixed Korean and English", () => {
|
||||||
|
const result = encoder("hello 안녕 world")
|
||||||
|
assert.deepStrictEqual(result, ["hello", "안", "녕", "world"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle mixed Chinese and English", () => {
|
||||||
|
const result = encoder("你好 world")
|
||||||
|
assert.deepStrictEqual(result, ["你", "好", "world"])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Edge cases", () => {
|
||||||
|
test("should handle empty string", () => {
|
||||||
|
const result = encoder("")
|
||||||
|
assert.deepStrictEqual(result, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle only whitespace", () => {
|
||||||
|
const result = encoder(" \t\n ")
|
||||||
|
assert.deepStrictEqual(result, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle single character", () => {
|
||||||
|
const result = encoder("a")
|
||||||
|
assert.deepStrictEqual(result, ["a"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle single CJK character", () => {
|
||||||
|
const result = encoder("あ")
|
||||||
|
assert.deepStrictEqual(result, ["あ"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle CJK with trailing whitespace", () => {
|
||||||
|
const result = encoder("日本語 ")
|
||||||
|
assert.deepStrictEqual(result, ["日", "本", "語"])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should handle English with trailing whitespace", () => {
|
||||||
|
const result = encoder("hello ")
|
||||||
|
assert.deepStrictEqual(result, ["hello"])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -115,9 +115,9 @@ async function _navigate(url: URL, isBack: boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// now, patch head, re-executing scripts
|
// now, patch head, re-executing scripts
|
||||||
const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
|
const elementsToRemove = document.head.querySelectorAll(":not([data-persist])")
|
||||||
elementsToRemove.forEach((el) => el.remove())
|
elementsToRemove.forEach((el) => el.remove())
|
||||||
const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
|
const elementsToAdd = html.head.querySelectorAll(":not([data-persist])")
|
||||||
elementsToAdd.forEach((el) => document.head.appendChild(el))
|
elementsToAdd.forEach((el) => document.head.appendChild(el))
|
||||||
|
|
||||||
// delay setting the url until now
|
// delay setting the url until now
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 32px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
& > :not(.sidebar.left:has(.explorer)) {
|
& > :not(.sidebar.left:has(.explorer)) {
|
||||||
transition: transform 300ms ease-in-out;
|
transition: transform 300ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.lock-scroll > :not(.sidebar.left:has(.explorer)) {
|
&.lock-scroll > :not(.sidebar.left:has(.explorer)) {
|
||||||
transform: translateX(100dvw);
|
transform: translateX(100dvw);
|
||||||
transition: transform 300ms ease-in-out;
|
transition: transform 300ms ease-in-out;
|
||||||
@@ -33,8 +34,10 @@
|
|||||||
|
|
||||||
min-height: 1.2rem;
|
min-height: 1.2rem;
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
flex: 0 1 1.2rem;
|
flex: 0 1 1.2rem;
|
||||||
|
|
||||||
& .fold {
|
& .fold {
|
||||||
transform: rotateZ(-90deg);
|
transform: rotateZ(-90deg);
|
||||||
}
|
}
|
||||||
@@ -118,7 +121,10 @@ button.desktop-explorer {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overscroll-behavior: contain;
|
|
||||||
|
&.explorer-ul {
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
& li > a {
|
& li > a {
|
||||||
color: var(--dark);
|
color: var(--dark);
|
||||||
@@ -269,6 +275,8 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
|||||||
|
|
||||||
.mobile-no-scroll {
|
.mobile-no-scroll {
|
||||||
@media all and ($mobile) {
|
@media all and ($mobile) {
|
||||||
overscroll-behavior: none;
|
.explorer-content > .explorer-ul {
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ pre {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
& > .mermaid-content {
|
& > .mermaid-content {
|
||||||
padding: 2rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
transform-origin: 0 0;
|
transform-origin: 0 0;
|
||||||
transition: transform 0.1s ease;
|
transition: transform 0.1s ease;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 32px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -3,85 +3,83 @@ import { Translation } from "./definition"
|
|||||||
export default {
|
export default {
|
||||||
propertyDefaults: {
|
propertyDefaults: {
|
||||||
title: "Không có tiêu đề",
|
title: "Không có tiêu đề",
|
||||||
description: "Không có mô tả được cung cấp",
|
description: "Không có mô tả",
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
callout: {
|
callout: {
|
||||||
note: "Ghi Chú",
|
note: "Ghi chú",
|
||||||
abstract: "Tóm Tắt",
|
abstract: "Tổng quan",
|
||||||
info: "Thông tin",
|
info: "Thông tin",
|
||||||
todo: "Cần Làm",
|
todo: "Cần phải làm",
|
||||||
tip: "Gợi Ý",
|
tip: "Gợi ý",
|
||||||
success: "Thành Công",
|
success: "Thành công",
|
||||||
question: "Nghi Vấn",
|
question: "Câu hỏi",
|
||||||
warning: "Cảnh Báo",
|
warning: "Cảnh báo",
|
||||||
failure: "Thất Bại",
|
failure: "Thất bại",
|
||||||
danger: "Nguy Hiểm",
|
danger: "Nguy hiểm",
|
||||||
bug: "Lỗi",
|
bug: "Lỗi",
|
||||||
example: "Ví Dụ",
|
example: "Ví dụ",
|
||||||
quote: "Trích Dẫn",
|
quote: "Trích dẫn",
|
||||||
},
|
},
|
||||||
backlinks: {
|
backlinks: {
|
||||||
title: "Liên Kết Ngược",
|
title: "Liên kết ngược",
|
||||||
noBacklinksFound: "Không có liên kết ngược được tìm thấy",
|
noBacklinksFound: "Không có liên kết ngược nào",
|
||||||
},
|
},
|
||||||
themeToggle: {
|
themeToggle: {
|
||||||
lightMode: "Sáng",
|
lightMode: "Chế độ sáng",
|
||||||
darkMode: "Tối",
|
darkMode: "Chế độ tối",
|
||||||
},
|
},
|
||||||
readerMode: {
|
readerMode: {
|
||||||
title: "Chế độ đọc",
|
title: "Chế độ đọc",
|
||||||
},
|
},
|
||||||
explorer: {
|
explorer: {
|
||||||
title: "Trong bài này",
|
title: "Nội dung",
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
createdWith: "Được tạo bởi",
|
createdWith: "Được tạo bằng",
|
||||||
},
|
},
|
||||||
graph: {
|
graph: {
|
||||||
title: "Biểu Đồ",
|
title: "Sơ đồ",
|
||||||
},
|
},
|
||||||
recentNotes: {
|
recentNotes: {
|
||||||
title: "Bài viết gần đây",
|
title: "Ghi chú gần đây",
|
||||||
seeRemainingMore: ({ remaining }) => `Xem ${remaining} thêm →`,
|
seeRemainingMore: ({ remaining }) => `Xem thêm ${remaining} ghi chú →`,
|
||||||
},
|
},
|
||||||
transcludes: {
|
transcludes: {
|
||||||
transcludeOf: ({ targetSlug }) => `Bao gồm ${targetSlug}`,
|
transcludeOf: ({ targetSlug }) => `Trích dẫn toàn bộ từ ${targetSlug}`,
|
||||||
linkToOriginal: "Liên Kết Gốc",
|
linkToOriginal: "Xem trang gốc",
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
title: "Tìm Kiếm",
|
title: "Tìm",
|
||||||
searchBarPlaceholder: "Tìm kiếm thông tin",
|
searchBarPlaceholder: "Tìm kiếm thông tin",
|
||||||
},
|
},
|
||||||
tableOfContents: {
|
tableOfContents: {
|
||||||
title: "Bảng Nội Dung",
|
title: "Mục lục",
|
||||||
},
|
},
|
||||||
contentMeta: {
|
contentMeta: {
|
||||||
readingTime: ({ minutes }) => `đọc ${minutes} phút`,
|
readingTime: ({ minutes }) => `${minutes} phút đọc`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
rss: {
|
rss: {
|
||||||
recentNotes: "Những bài gần đây",
|
recentNotes: "Ghi chú gần đây",
|
||||||
lastFewNotes: ({ count }) => `${count} Bài gần đây`,
|
lastFewNotes: ({ count }) => `${count} Trang gần đây`,
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
title: "Không Tìm Thấy",
|
title: "Không tìm thấy",
|
||||||
notFound: "Trang này được bảo mật hoặc không tồn tại.",
|
notFound: "Trang này riêng tư hoặc không tồn tại.",
|
||||||
home: "Trở về trang chủ",
|
home: "Về trang chủ",
|
||||||
},
|
},
|
||||||
folderContent: {
|
folderContent: {
|
||||||
folder: "Thư Mục",
|
folder: "Thư mục",
|
||||||
itemsUnderFolder: ({ count }) =>
|
itemsUnderFolder: ({ count }) => `Có ${count} trang trong thư mục này.`,
|
||||||
count === 1 ? "1 mục trong thư mục này." : `${count} mục trong thư mục này.`,
|
|
||||||
},
|
},
|
||||||
tagContent: {
|
tagContent: {
|
||||||
tag: "Thẻ",
|
tag: "Thẻ",
|
||||||
tagIndex: "Thẻ Mục Lục",
|
tagIndex: "Danh sách thẻ",
|
||||||
itemsUnderTag: ({ count }) =>
|
itemsUnderTag: ({ count }) => `Có ${count} trang gắn thẻ này.`,
|
||||||
count === 1 ? "1 mục gắn thẻ này." : `${count} mục gắn thẻ này.`,
|
showingFirst: ({ count }) => `Đang hiển thị ${count} trang đầu tiên.`,
|
||||||
showingFirst: ({ count }) => `Hiển thị trước ${count} thẻ.`,
|
totalTags: ({ count }) => `Có tổng cộng ${count} thẻ.`,
|
||||||
totalTags: ({ count }) => `Tìm thấy ${count} thẻ tổng cộng.`,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const satisfies Translation
|
} as const satisfies Translation
|
||||||
|
|||||||
@@ -241,6 +241,16 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
|
|||||||
vercelInsightsScript.defer = true
|
vercelInsightsScript.defer = true
|
||||||
document.head.appendChild(vercelInsightsScript)
|
document.head.appendChild(vercelInsightsScript)
|
||||||
`)
|
`)
|
||||||
|
} else if (cfg.analytics?.provider === "rybbit") {
|
||||||
|
componentResources.afterDOMLoaded.push(`
|
||||||
|
const rybbitScript = document.createElement("script");
|
||||||
|
rybbitScript.src = "${cfg.analytics.host ?? "https://app.rybbit.io"}/api/script.js";
|
||||||
|
rybbitScript.setAttribute("data-site-id", "${cfg.analytics.siteId}");
|
||||||
|
rybbitScript.async = true;
|
||||||
|
rybbitScript.defer = true;
|
||||||
|
|
||||||
|
document.head.appendChild(rybbitScript);
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.enableSPA) {
|
if (cfg.enableSPA) {
|
||||||
|
|||||||
@@ -23,7 +23,16 @@ export const Citations: QuartzTransformerPlugin<Partial<Options>> = (userOpts) =
|
|||||||
name: "Citations",
|
name: "Citations",
|
||||||
htmlPlugins(ctx) {
|
htmlPlugins(ctx) {
|
||||||
const plugins: PluggableList = []
|
const plugins: PluggableList = []
|
||||||
|
// per default, rehype-citations only supports en-US
|
||||||
|
// see: https://github.com/timlrx/rehype-citation/issues/12
|
||||||
|
// in here there are multiple usable locales:
|
||||||
|
// https://github.com/citation-style-language/locales
|
||||||
|
// thus, we optimistically assume there is indeed an appropriate
|
||||||
|
// locale available and simply create the lang url-string
|
||||||
|
let lang: string = "en-US"
|
||||||
|
if (ctx.cfg.configuration.locale !== "en-US") {
|
||||||
|
lang = `https://raw.githubusercontent.com/citation-stylelanguage/locales/refs/heads/master/locales-${ctx.cfg.configuration.locale}.xml`
|
||||||
|
}
|
||||||
// Add rehype-citation to the list of plugins
|
// Add rehype-citation to the list of plugins
|
||||||
plugins.push([
|
plugins.push([
|
||||||
rehypeCitation,
|
rehypeCitation,
|
||||||
@@ -32,7 +41,7 @@ export const Citations: QuartzTransformerPlugin<Partial<Options>> = (userOpts) =
|
|||||||
suppressBibliography: opts.suppressBibliography,
|
suppressBibliography: opts.suppressBibliography,
|
||||||
linkCitations: opts.linkCitations,
|
linkCitations: opts.linkCitations,
|
||||||
csl: opts.csl,
|
csl: opts.csl,
|
||||||
lang: ctx.cfg.configuration.locale ?? "en-US",
|
lang,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ interface Options {
|
|||||||
typstOptions: TypstOptions
|
typstOptions: TypstOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mathjax macros
|
||||||
|
export type Args = boolean | number | string | null
|
||||||
interface MacroType {
|
interface MacroType {
|
||||||
[key: string]: string
|
[key: string]: string | Args[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Latex: QuartzTransformerPlugin<Partial<Options>> = (opts) => {
|
export const Latex: QuartzTransformerPlugin<Partial<Options>> = (opts) => {
|
||||||
@@ -37,11 +39,20 @@ export const Latex: QuartzTransformerPlugin<Partial<Options>> = (opts) => {
|
|||||||
case "typst": {
|
case "typst": {
|
||||||
return [[rehypeTypst, opts?.typstOptions ?? {}]]
|
return [[rehypeTypst, opts?.typstOptions ?? {}]]
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
case "mathjax": {
|
case "mathjax": {
|
||||||
return [[rehypeMathjax, { macros, ...(opts?.mathJaxOptions ?? {}) }]]
|
return [
|
||||||
}
|
[
|
||||||
default: {
|
rehypeMathjax,
|
||||||
return [[rehypeMathjax, { macros, ...(opts?.mathJaxOptions ?? {}) }]]
|
{
|
||||||
|
...(opts?.mathJaxOptions ?? {}),
|
||||||
|
tex: {
|
||||||
|
...(opts?.mathJaxOptions?.tex ?? {}),
|
||||||
|
macros,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ html {
|
|||||||
text-size-adjust: none;
|
text-size-adjust: none;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
|
||||||
|
@media all and ($mobile) {
|
||||||
|
scroll-padding-top: 4rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -41,13 +45,17 @@ ul,
|
|||||||
.katex,
|
.katex,
|
||||||
.math,
|
.math,
|
||||||
.typst-doc,
|
.typst-doc,
|
||||||
.typst-doc * {
|
g[class~="typst-text"] {
|
||||||
color: var(--darkgray);
|
color: var(--darkgray);
|
||||||
fill: var(--darkgray);
|
fill: var(--darkgray);
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
text-wrap: pretty;
|
text-wrap: pretty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path[class~="typst-shape"] {
|
||||||
|
stroke: var(--darkgray);
|
||||||
|
}
|
||||||
|
|
||||||
.math {
|
.math {
|
||||||
&.math-display {
|
&.math-display {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ export type CSSResource = {
|
|||||||
export function JSResourceToScriptElement(resource: JSResource, preserve?: boolean): JSX.Element {
|
export function JSResourceToScriptElement(resource: JSResource, preserve?: boolean): JSX.Element {
|
||||||
const scriptType = resource.moduleType ?? "application/javascript"
|
const scriptType = resource.moduleType ?? "application/javascript"
|
||||||
const spaPreserve = preserve ?? resource.spaPreserve
|
const spaPreserve = preserve ?? resource.spaPreserve
|
||||||
|
|
||||||
if (resource.contentType === "external") {
|
if (resource.contentType === "external") {
|
||||||
return (
|
return (
|
||||||
<script key={resource.src} src={resource.src} type={scriptType} spa-preserve={spaPreserve} />
|
<script key={resource.src} src={resource.src} type={scriptType} data-persist={spaPreserve} />
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const content = resource.script
|
const content = resource.script
|
||||||
@@ -36,7 +37,7 @@ export function JSResourceToScriptElement(resource: JSResource, preserve?: boole
|
|||||||
<script
|
<script
|
||||||
key={randomUUID()}
|
key={randomUUID()}
|
||||||
type={scriptType}
|
type={scriptType}
|
||||||
spa-preserve={spaPreserve}
|
data-persist={spaPreserve}
|
||||||
dangerouslySetInnerHTML={{ __html: content }}
|
dangerouslySetInnerHTML={{ __html: content }}
|
||||||
></script>
|
></script>
|
||||||
)
|
)
|
||||||
@@ -54,7 +55,7 @@ export function CSSResourceToStyleElement(resource: CSSResource, preserve?: bool
|
|||||||
href={resource.content}
|
href={resource.content}
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
spa-preserve={spaPreserve}
|
data-persist={spaPreserve}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user