Warn about missing images/videos in Quarto HTML output
Build and Deploy Verso / deploy (push) Successful in 7m49s

Since we dropped --embed-resources (so RevealJS plugins like chalkboard work),
pandoc no longer tries to fetch referenced media for HTML output, so a missing
image or video produces no compile-time warning — it only renders broken in the
browser. PDF/Typst output is unaffected because Typst hard-errors on a missing
image.

After an HTML render, QuartoRunner now scans output.html for local media
references (img/video/audio/iframe src, poster, RevealJS data-background-*) and
appends a `[WARNING] Missing resource: …` line to output.log for any that don't
exist on disk. External URLs, data URIs, anchors and Quarto's own generated
<basename>_files assets are ignored. The [WARNING] prefix is recognised by the
Quarto/Typst log parser, so these show up in the Warnings tab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude
2026-06-01 13:24:56 +00:00
parent 2d4ca6f13a
commit 7c2b903e4d
+62 -1
View File
@@ -42,7 +42,9 @@ function runQuarto(compileName, options, callback) {
// error object; merge it so _writeLogOutput can persist it.
const combined = output || (error ? { stdout: error.stdout || '' } : null)
_writeLogOutput(compileName, directory, combined, () =>
callback(null, combined)
_appendMissingResourceWarnings(directory, () =>
callback(null, combined)
)
)
}
)
@@ -96,6 +98,65 @@ function _writeLogOutput(compileName, directory, output, callback) {
})
}
// Quarto's HTML/RevealJS output is NOT self-contained (we deliberately dropped
// --embed-resources so reveal plugins like chalkboard work). A side effect is
// that pandoc no longer tries to fetch referenced media, so a missing image or
// video produces no compile-time warning — it just renders broken in the
// browser. To restore that feedback, scan the produced output.html for local
// media references and emit a [WARNING] for any that don't exist on disk. The
// [WARNING] prefix is understood by the Quarto/Typst log parser on the web
// side, so these surface in the Warnings tab like any other.
//
// Only HTML output is scanned: PDF output (Typst) already hard-errors on a
// missing image, so it needs no extra check.
function _appendMissingResourceWarnings(directory, callback) {
const htmlFile = Path.join(directory, 'output.html')
fs.readFile(htmlFile, 'utf8', (err, html) => {
if (err) return callback() // no HTML output (e.g. a PDF compile)
const missing = _extractLocalMediaRefs(html).filter(ref => {
try {
return !fs.existsSync(Path.join(directory, decodeURIComponent(ref)))
} catch {
return false
}
})
if (missing.length === 0) return callback()
const warnings =
missing
.map(
ref =>
`[WARNING] Missing resource: ${ref} (referenced in the document ` +
`but not found in the project — it will appear broken)`
)
.join('\n') + '\n'
fs.appendFile(Path.join(directory, 'output.log'), '\n' + warnings, () =>
callback()
)
})
}
// Pull local media references (img/video/audio/iframe src, poster, RevealJS
// data-background-*) out of the rendered HTML. External URLs, data URIs and
// in-page anchors are ignored; Quarto's own generated assets (under
// <basename>_files/) exist on disk, so they never get flagged.
function _extractLocalMediaRefs(html) {
const refs = new Set()
const attrRegex =
/(?:src|poster|data-background-image|data-background-video)\s*=\s*["']([^"']+)["']/gi
let match
while ((match = attrRegex.exec(html)) !== null) {
const url = match[1].trim()
if (!url) continue
// Skip absolute URLs, protocol-relative, data/blob URIs and anchors.
if (/^(?:[a-z]+:|\/\/|\/|#|data:|blob:)/i.test(url)) continue
const clean = url.split(/[?#]/)[0] // drop query string / fragment
if (clean) refs.add(clean)
}
return [...refs]
}
function isRunning(compileName) {
return ProcessTable[compileName] != null
}