PDF upload support:
* index.php: application/pdf added to $mimeMap + $extMap in the
/api/upload handler. PDFs are stored in uploads/ like images/audio.
* editor.js uploadFile(): detects PDF via MIME or .pdf extension.
Inserts a markdown link [📄 filename](uploads/...) instead of an
image embed. Paste + drop handlers updated to accept PDFs.
* index.php: new toolbar button #md-upload-pdf with ph-file-pdf icon
next to the extract button. Triggers a hidden file input
(accept=".pdf"). Wired in editor.js.
Gallery → Assets rename:
* index.php footer button: "Images" → "Assets", icon ph-folder-open.
* index.php gallery overlay header: "images" → "assets", icon
ph-folder-open, upload input accept extended to include .pdf.
* Empty state text: "no images" → "no assets".
File-type tabs in the assets overlay:
* index.php: new #gallery-tabs div with 4 buttons (All / Images /
Audio / PDF), each with data-filter attribute.
* gallery.js: fileType(url) helper returns 'image'|'audio'|'pdf'|
'other'. Tab clicks toggle .active class and filter gallery-items
by data-url + fileType. activeFilter persists across re-opens.
* CSS: .gallery-tab styling with hover + .active accent fill.
PDF rendering in the assets grid:
* gallery.js mediaTile(): PDFs render as ph-file-pdf icon + filename
instead of a thumbnail. Orphaned PDFs show the same icon tile.
* Click on an orphaned PDF opens it in a new tab (window.open).
* "New card" from an orphaned PDF inserts a markdown link (not
image embed).
Orphan detection fix (gallery.js):
* Root cause: loadCards() in app.js uses two-phase lazy loading —
first /api/cards/meta (body=null on all cards), then batches of 25
with full bodies. Opening the gallery before the last batch
completes meant most cards had body=null, getReferencedUrls()
found no URLs, and every file appeared orphaned.
* Fix: openGallery() now fetches its own card list with full bodies
via api('api/cards') in parallel with api('api/uploads'), stored in
a module-local galleryCards array. Both getReferencedUrls() and
findCardForUrl() read from galleryCards instead of state.cards.
* Bonus: added a fallback regex uploads/[hex]{32}.[ext] that catches
references in any format the specific patterns might miss (HTML
attributes, custom blocks, inline text).
chore: bump version to 0.9.18
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>