diff --git a/quartz/build.ts b/quartz/build.ts index b98f4a8a0..3166a06bb 100644 --- a/quartz/build.ts +++ b/quartz/build.ts @@ -71,7 +71,7 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) { console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`) perf.addEvent("glob") - const allFiles = await glob("**/*.*", argv.directory, cfg.configuration.ignorePatterns) + const allFiles = await glob("**/*.*", argv.directory, cfg.configuration.ignorePatterns, false) const markdownPaths = allFiles.filter((fp) => fp.endsWith(".md")).sort() console.log( `Found ${markdownPaths.length} input files from \`${argv.directory}\` in ${perf.timeSince("glob")}`, diff --git a/quartz/plugins/emitters/static.ts b/quartz/plugins/emitters/static.ts index 0b4529083..8b34049fd 100644 --- a/quartz/plugins/emitters/static.ts +++ b/quartz/plugins/emitters/static.ts @@ -7,6 +7,7 @@ import { dirname } from "path" export const Static: QuartzEmitterPlugin = () => ({ name: "Static", async *emit({ argv, cfg }) { + // Copy Quartz's own internal static assets (quartz/static/) → output/static/ const staticPath = joinSegments(QUARTZ, "static") const fps = await glob("**", staticPath, cfg.configuration.ignorePatterns) const outputStaticPath = joinSegments(argv.output, "static") @@ -18,6 +19,21 @@ export const Static: QuartzEmitterPlugin = () => ({ await fs.promises.copyFile(src, dest) yield dest } + + // Copy user-facing static assets (static/) → output/ preserving paths. + // This mirrors Hugo's convention: static/ox-hugo/foo.png is served at /ox-hugo/foo.png, + // which matches the src="/ox-hugo/..." paths that ox-hugo writes into exported markdown. + const userStaticPath = "static" + if (fs.existsSync(userStaticPath)) { + const userFps = await glob("**", userStaticPath, cfg.configuration.ignorePatterns, false) + for (const fp of userFps) { + const src = joinSegments(userStaticPath, fp) as FilePath + const dest = joinSegments(argv.output, fp) as FilePath + await fs.promises.mkdir(dirname(dest), { recursive: true }) + await fs.promises.copyFile(src, dest) + yield dest + } + } }, async *partialEmit() {}, }) diff --git a/quartz/plugins/transformers/oxhugofm.ts b/quartz/plugins/transformers/oxhugofm.ts index 303566e08..4fb5e2c4a 100644 --- a/quartz/plugins/transformers/oxhugofm.ts +++ b/quartz/plugins/transformers/oxhugofm.ts @@ -27,7 +27,10 @@ const defaultOptions: Options = { const relrefRegex = new RegExp(/\[([^\]]+)\]\(\{\{< relref "([^"]+)" >\}\}\)/, "g") const predefinedHeadingIdRegex = new RegExp(/(.*) {#(?:.*)}/, "g") const hugoShortcodeRegex = new RegExp(/{{(.*)}}/, "g") -const figureTagRegex = new RegExp(/< ?figure src="(.*)" ?>/, "g") +// Matches the full Hugo {{< figure src="..." ... >}} shortcode and captures src. +// Must run before the generic shortcode stripper to avoid partial-match issues +// with captions that contain HTML (e.g. ). +const figureShortcodeRegex = new RegExp(/{{<\s*figure\b[^}]*\bsrc="([^"]*)"[^}]*>}}/, "g") // \\\\\( -> matches \\( // (.+?) -> Lazy match for capturing the equation // \\\\\) -> matches \\) @@ -70,6 +73,14 @@ export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin> }) } + if (opts.replaceFigureWithMdImg) { + src = src.toString() + src = src.replaceAll(figureShortcodeRegex, (_value, ...capture) => { + const [imgSrc] = capture + return `![](${imgSrc})` + }) + } + if (opts.removeHugoShortcode) { src = src.toString() src = src.replaceAll(hugoShortcodeRegex, (_value, ...capture) => { @@ -78,14 +89,6 @@ export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin> }) } - if (opts.replaceFigureWithMdImg) { - src = src.toString() - src = src.replaceAll(figureTagRegex, (_value, ...capture) => { - const [src] = capture - return `![](${src})` - }) - } - if (opts.replaceOrgLatex) { src = src.toString() src = src.replaceAll(inlineLatexRegex, (_value, ...capture) => { diff --git a/quartz/util/glob.ts b/quartz/util/glob.ts index 7a7116007..91fbaa7c2 100644 --- a/quartz/util/glob.ts +++ b/quartz/util/glob.ts @@ -10,12 +10,13 @@ export async function glob( pattern: string, cwd: string, ignorePatterns: string[], + respectGitignore: boolean = true, ): Promise { const fps = ( await globby(pattern, { cwd, ignore: ignorePatterns, - gitignore: true, + gitignore: respectGitignore, }) ).map(toPosixPath) return fps as FilePath[]