mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-24 13:24:05 -06:00
Merge branch 'jackyzha0:v4' into fix/highlight-parsing-logic
This commit is contained in:
commit
0006ce239b
2
.github/workflows/build-preview.yaml
vendored
2
.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
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/ci.yaml
vendored
4
.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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|||||||
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
|
||||||
|
|||||||
801
package-lock.json
generated
801
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -42,7 +42,7 @@
|
|||||||
"@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.3.1",
|
||||||
@ -57,12 +57,12 @@
|
|||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
"lightningcss": "^1.30.2",
|
"lightningcss": "^1.30.2",
|
||||||
"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.1",
|
"pixi.js": "^8.14.3",
|
||||||
"preact": "^10.27.2",
|
"preact": "^10.28.0",
|
||||||
"preact-render-to-string": "^6.6.3",
|
"preact-render-to-string": "^6.6.3",
|
||||||
"pretty-bytes": "^7.1.0",
|
"pretty-bytes": "^7.1.0",
|
||||||
"pretty-time": "^1.1.0",
|
"pretty-time": "^1.1.0",
|
||||||
@ -93,7 +93,7 @@
|
|||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
"vfile": "^6.0.3",
|
"vfile": "^6.0.3",
|
||||||
"workerpool": "^10.0.0",
|
"workerpool": "^10.0.1",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3",
|
||||||
"yargs": "^18.0.0"
|
"yargs": "^18.0.0"
|
||||||
},
|
},
|
||||||
@ -106,9 +106,9 @@
|
|||||||
"@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.35",
|
"@types/yargs": "^17.0.35",
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.1",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.7.4",
|
||||||
"tsx": "^4.20.6",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -41,13 +41,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}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user