PDF upload, gallery → assets with type tabs, orphan detection fix
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>
This commit is contained in:
1 parent
195b0e343f
commit
bf81d046e6
5 files changed
+128
-29
No files matched your search
+15
-7
@@ -454,8 +454,8 @@ if (str_starts_with($route, '/api/')) {
|
||||
}
|
||||
|
||||
// Check MIME — finfo first, fallback to extension for audio
|
||||
$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'];
|
||||
$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/')) {
|
||||
<i class="ph ph-calendar-dots text-sm"></i><span class="font-pixel text-[10px]">Calendar</span>
|
||||
</button>
|
||||
<button id="gallery-btn" class="flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors">
|
||||
<i class="ph ph-image-square text-sm"></i><span class="font-pixel text-[10px]">Images</span>
|
||||
<i class="ph ph-folder-open text-sm"></i><span class="font-pixel text-[10px]">Assets</span>
|
||||
</button>
|
||||
<button id="tasks-btn" class="flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent2 hover:text-t-accent2 transition-colors">
|
||||
<i class="ph ph-check-square-offset text-sm"></i><span class="font-pixel text-[10px]">Tasks</span>
|
||||
@@ -2027,18 +2027,24 @@ if (str_starts_with($route, '/api/')) {
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Gallery -->
|
||||
<!-- Assets (was Gallery) -->
|
||||
<div class="overlay fixed inset-0 z-[72]" style="background:var(--t-overlay)" id="gallery">
|
||||
<div class="bg-t-surface border border-t-border rounded-sm shadow-2xl flex flex-col absolute" style="inset:40px;">
|
||||
<div class="flex items-center gap-3 px-4 py-3 border-b border-t-border shrink-0">
|
||||
<i class="ph ph-images text-t-accent text-base"></i>
|
||||
<h2 class="font-pixel text-xs text-t-accent">images</h2>
|
||||
<i class="ph ph-folder-open text-t-accent text-base"></i>
|
||||
<h2 class="font-pixel text-xs text-t-accent">assets</h2>
|
||||
<span id="gallery-count" class="font-pixel text-[9px] text-t-dim"></span>
|
||||
<div id="gallery-tabs" class="flex items-center gap-1 ml-3">
|
||||
<button class="gallery-tab active font-pixel text-[9px] px-2 py-0.5 border border-t-border text-t-muted transition-colors" data-filter="all">All</button>
|
||||
<button class="gallery-tab font-pixel text-[9px] px-2 py-0.5 border border-t-border text-t-muted transition-colors" data-filter="image">Images</button>
|
||||
<button class="gallery-tab font-pixel text-[9px] px-2 py-0.5 border border-t-border text-t-muted transition-colors" data-filter="audio">Audio</button>
|
||||
<button class="gallery-tab font-pixel text-[9px] px-2 py-0.5 border border-t-border text-t-muted transition-colors" data-filter="pdf">PDF</button>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<button id="gallery-upload" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors">
|
||||
<i class="ph ph-upload-simple text-sm"></i><span class="font-pixel text-[10px]">Upload</span>
|
||||
</button>
|
||||
<input type="file" id="gallery-upload-file" accept="image/*,audio/*" multiple class="hidden">
|
||||
<input type="file" id="gallery-upload-file" accept="image/*,audio/*,.pdf,application/pdf" multiple class="hidden">
|
||||
<button id="gallery-close" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors">
|
||||
<i class="ph ph-x text-sm"></i><span class="font-pixel text-[10px]">Close</span>
|
||||
</button>
|
||||
@@ -2140,6 +2146,8 @@ if (str_starts_with($route, '/api/')) {
|
||||
<button data-md="hr" title="Divider" class="toolbar-btn"><i class="ph ph-minus text-sm"></i></button>
|
||||
<div class="w-px h-4 bg-t-border mx-1"></div>
|
||||
<button data-md="extract" title="Extract selection to new card" class="toolbar-btn"><i class="ph ph-scissors text-sm"></i></button>
|
||||
<button id="md-upload-pdf" title="Upload & link PDF" class="toolbar-btn"><i class="ph ph-file-pdf text-sm"></i></button>
|
||||
<input type="file" id="md-upload-pdf-file" accept=".pdf,application/pdf" class="hidden">
|
||||
<?= PluginManager::instance()->slot('md-toolbar') ?>
|
||||
<div class="flex-1"></div>
|
||||
<button id="toggle-lines" class="toolbar-btn flex items-center gap-1.5 px-2" title="line numbers"><i class="ph ph-hash text-sm"></i></button>
|
||||
|
||||
Reference in new issue
Block a user