This commit is contained in:
Ryan Kes 2024-09-17 17:16:48 +08:00
commit 5a13e44daf
8 changed files with 74 additions and 69 deletions

View file

@ -21,3 +21,7 @@ This will start a local web server to run your Quartz on your computer. Open a w
> - `--serve`: run a local hot-reloading server to preview your Quartz > - `--serve`: run a local hot-reloading server to preview your Quartz
> - `--port`: what port to run the local preview server on > - `--port`: what port to run the local preview server on
> - `--concurrency`: how many threads to use to parse notes > - `--concurrency`: how many threads to use to parse notes
> [!warning] Not to be used for production
> Serve mode is intended for local previews only.
> For production workloads, see the page on [[hosting]].

21
package-lock.json generated
View file

@ -83,7 +83,7 @@
"esbuild": "^0.19.9", "esbuild": "^0.19.9",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"tsx": "^4.19.0", "tsx": "^4.19.0",
"typescript": "^5.5.4" "typescript": "^5.6.2"
}, },
"engines": { "engines": {
"node": "20 || >=22", "node": "20 || >=22",
@ -4790,9 +4790,9 @@
} }
}, },
"node_modules/preact-render-to-string": { "node_modules/preact-render-to-string": {
"version": "6.5.10", "version": "6.5.11",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.10.tgz", "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
"integrity": "sha512-BJdypTQaBA5UbTF9NKZS3zP93Sw33tZOxNXIfuHofqOZFoMdsquNkVebs/HkEw0in/Qbi6Ep/Anngnj+VsHeBQ==", "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
"peerDependencies": { "peerDependencies": {
"preact": ">=10" "preact": ">=10"
} }
@ -6261,9 +6261,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.5.4", "version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@ -6475,12 +6475,11 @@
} }
}, },
"node_modules/vfile": { "node_modules/vfile": {
"version": "6.0.2", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.2.tgz", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
"integrity": "sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==", "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
"dependencies": { "dependencies": {
"@types/unist": "^3.0.0", "@types/unist": "^3.0.0",
"unist-util-stringify-position": "^4.0.0",
"vfile-message": "^4.0.0" "vfile-message": "^4.0.0"
}, },
"funding": { "funding": {

View file

@ -106,6 +106,6 @@
"esbuild": "^0.19.9", "esbuild": "^0.19.9",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"tsx": "^4.19.0", "tsx": "^4.19.0",
"typescript": "^5.5.4" "typescript": "^5.6.2"
} }
} }

View file

@ -39,7 +39,7 @@ type BuildData = {
type FileEvent = "add" | "change" | "delete" type FileEvent = "add" | "change" | "delete"
function newBuildId() { function newBuildId() {
return new Date().toISOString() return Math.random().toString(36).substring(2, 8)
} }
async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
@ -162,17 +162,19 @@ async function partialRebuildFromEntrypoint(
return return
} }
const buildStart = new Date().getTime() const buildId = newBuildId()
buildData.lastBuildMs = buildStart ctx.buildId = buildId
buildData.lastBuildMs = new Date().getTime()
const release = await mut.acquire() const release = await mut.acquire()
if (buildData.lastBuildMs > buildStart) {
// if there's another build after us, release and let them do it
if (ctx.buildId !== buildId) {
release() release()
return return
} }
const perf = new PerfTimer() const perf = new PerfTimer()
console.log(chalk.yellow("Detected change, rebuilding...")) console.log(chalk.yellow("Detected change, rebuilding..."))
ctx.buildId = newBuildId()
// UPDATE DEP GRAPH // UPDATE DEP GRAPH
const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath
@ -357,19 +359,19 @@ async function rebuildFromEntrypoint(
toRemove.add(filePath) toRemove.add(filePath)
} }
const buildStart = new Date().getTime() const buildId = newBuildId()
buildData.lastBuildMs = buildStart ctx.buildId = buildId
buildData.lastBuildMs = new Date().getTime()
const release = await mut.acquire() const release = await mut.acquire()
// there's another build after us, release and let them do it // there's another build after us, release and let them do it
if (buildData.lastBuildMs > buildStart) { if (ctx.buildId !== buildId) {
release() release()
return return
} }
const perf = new PerfTimer() const perf = new PerfTimer()
console.log(chalk.yellow("Detected change, rebuilding...")) console.log(chalk.yellow("Detected change, rebuilding..."))
ctx.buildId = newBuildId()
try { try {
const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp)) const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp))
@ -405,10 +407,10 @@ async function rebuildFromEntrypoint(
} }
} }
release()
clientRefresh() clientRefresh()
toRebuild.clear() toRebuild.clear()
toRemove.clear() toRemove.clear()
release()
} }
export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => { export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => {

View file

@ -7,6 +7,7 @@ import { ExplorerNode, FileNode, Options } from "./ExplorerNode"
import { QuartzPluginData } from "../plugins/vfile" import { QuartzPluginData } from "../plugins/vfile"
import { classNames } from "../util/lang" import { classNames } from "../util/lang"
import { i18n } from "../i18n" import { i18n } from "../i18n"
import { VNode } from "preact"
// Options interface defined in `ExplorerNode` to avoid circular dependency // Options interface defined in `ExplorerNode` to avoid circular dependency
const defaultOptions = { const defaultOptions = {
@ -44,6 +45,7 @@ export default ((userOpts?: Partial<Options>) => {
// memoized // memoized
let fileTree: FileNode let fileTree: FileNode
let jsonTree: string let jsonTree: string
let component: VNode
let lastBuildId: string = "" let lastBuildId: string = ""
function constructFileTree(allFiles: QuartzPluginData[]) { function constructFileTree(allFiles: QuartzPluginData[]) {
@ -82,44 +84,46 @@ export default ((userOpts?: Partial<Options>) => {
if (ctx.buildId !== lastBuildId) { if (ctx.buildId !== lastBuildId) {
lastBuildId = ctx.buildId lastBuildId = ctx.buildId
constructFileTree(allFiles) constructFileTree(allFiles)
const tree = ExplorerNode({ node: fileTree, opts, fileData })
component = (
<div class={classNames(displayClass, "explorer")}>
<button
type="button"
id="explorer"
data-behavior={opts.folderClickBehavior}
data-collapsed={opts.folderDefaultState}
data-savestate={opts.useSavedState}
data-tree={jsonTree}
aria-controls="explorer-content"
aria-expanded={opts.folderDefaultState === "open"}
>
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="5 8 14 8"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="fold"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div id="explorer-content">
<ul class="overflow" id="explorer-ul">
{tree}
<li id="explorer-end" />
</ul>
</div>
</div>
)
} }
return ( return component
<div class={classNames(displayClass, "explorer")}>
<button
type="button"
id="explorer"
data-behavior={opts.folderClickBehavior}
data-collapsed={opts.folderDefaultState}
data-savestate={opts.useSavedState}
data-tree={jsonTree}
aria-controls="explorer-content"
aria-expanded={opts.folderDefaultState === "open"}
>
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="5 8 14 8"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="fold"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div id="explorer-content">
<ul class="overflow" id="explorer-ul">
<ExplorerNode node={fileTree} opts={opts} fileData={fileData} />
<li id="explorer-end" />
</ul>
</div>
</div>
)
} }
Explorer.css = explorerStyle Explorer.css = explorerStyle

View file

@ -224,15 +224,10 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro
class="content" class="content"
data-folderul={folderPath} data-folderul={folderPath}
> >
{node.children.map((childNode, i) => ( {node.children.map((childNode) =>
<ExplorerNode // eagerly render children so we can memoize properly
node={childNode} ExplorerNode({ node: childNode, opts, fileData, fullPath: folderPath }),
key={i} )}
opts={opts}
fullPath={folderPath}
fileData={fileData}
/>
))}
</ul> </ul>
</div> </div>
</li> </li>

View file

@ -67,6 +67,7 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options>> = (userOpts)
properties: { properties: {
"aria-hidden": "true", "aria-hidden": "true",
class: "external-icon", class: "external-icon",
style: "max-width:0.8em;max-height:0.8em",
viewBox: "0 0 512 512", viewBox: "0 0 512 512",
}, },
children: [ children: [

View file

@ -324,8 +324,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
replacements.push([ replacements.push([
tagRegex, tagRegex,
(_value: string, tag: string) => { (_value: string, tag: string) => {
// Check if the tag only includes numbers // Check if the tag only includes numbers and slashes
if (/^\d+$/.test(tag)) { if (/^[\/\d]+$/.test(tag)) {
return false return false
} }