From bf81d046e674e7a50cfed33183e52c21f375357d Mon Sep 17 00:00:00 2001
From: th
Date: Sun, 12 Apr 2026 11:39:50 +0200
Subject: [PATCH] =?UTF-8?q?PDF=20upload,=20gallery=20=E2=86=92=20assets=20?=
=?UTF-8?q?with=20type=20tabs,=20orphan=20detection=20fix?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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)
---
config.php | 2 +-
public/css/style.css | 5 ++
public/index.php | 22 +++++---
public/js/modules/editor.js | 25 ++++++---
public/js/modules/gallery.js | 103 ++++++++++++++++++++++++++++++-----
5 files changed, 128 insertions(+), 29 deletions(-)
diff --git a/config.php b/config.php
index 4995867..2ff0140 100644
--- a/config.php
+++ b/config.php
@@ -1,7 +1,7 @@
'png', 'image/jpeg' => 'jpg', 'image/gif' => 'gif', 'image/webp' => 'webp', 'audio/mpeg' => 'mp3', 'audio/ogg' => 'ogg', 'audio/wav' => 'wav', 'audio/x-wav' => 'wav', 'audio/mp4' => 'm4a', 'application/ogg' => 'ogg'];
- $extMap = ['mp3' => 'mp3', 'ogg' => 'ogg', 'wav' => 'wav', 'm4a' => 'm4a'];
+ $mimeMap = ['image/png' => 'png', 'image/jpeg' => 'jpg', 'image/gif' => 'gif', 'image/webp' => 'webp', 'audio/mpeg' => 'mp3', 'audio/ogg' => 'ogg', 'audio/wav' => 'wav', 'audio/x-wav' => 'wav', 'audio/mp4' => 'm4a', 'application/ogg' => 'ogg', 'application/pdf' => 'pdf'];
+ $extMap = ['mp3' => 'mp3', 'ogg' => 'ogg', 'wav' => 'wav', 'm4a' => 'm4a', 'pdf' => 'pdf'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$realMime = $finfo->file($file['tmp_name']);
$ext = $mimeMap[$realMime] ?? null;
@@ -1236,7 +1236,7 @@ if (str_starts_with($route, '/api/')) {
Calendar