From 400e4999fdd0e9e70211b43340d8058c671f6cb5 Mon Sep 17 00:00:00 2001 From: Thomas Richter Date: Thu, 29 Jan 2026 15:28:22 +0100 Subject: [PATCH] feat(03-02): integrate image upload into QuickCapture - Add image button next to submit button - Show pending image preview with filename - Allow removing pending image before submit - Upload image after entry creation with new entry ID - Disable button during upload to prevent duplicates - Clean up object URLs properly --- src/lib/components/QuickCapture.svelte | 160 ++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 17 deletions(-) diff --git a/src/lib/components/QuickCapture.svelte b/src/lib/components/QuickCapture.svelte index a0c17a8..8dfd388 100644 --- a/src/lib/components/QuickCapture.svelte +++ b/src/lib/components/QuickCapture.svelte @@ -6,26 +6,84 @@ let content = $state(''); let type = $state<'task' | 'thought'>('thought'); + // Pending image state + let pendingImage: File | null = $state(null); + let pendingPreviewUrl: string | null = $state(null); + let isUploading = $state(false); + let fileInput: HTMLInputElement; + // Initialize from preferences (client-side only) $effect(() => { if (typeof window !== 'undefined') { type = $preferences.lastEntryType; } }); + + function handleImageSelect(e: Event) { + const input = e.target as HTMLInputElement; + const file = input.files?.[0]; + if (file && file.type.startsWith('image/')) { + // Revoke previous preview if exists + if (pendingPreviewUrl) { + URL.revokeObjectURL(pendingPreviewUrl); + } + pendingImage = file; + pendingPreviewUrl = URL.createObjectURL(file); + } + // Reset input so same file can be selected again + input.value = ''; + } + + function clearPendingImage() { + if (pendingPreviewUrl) { + URL.revokeObjectURL(pendingPreviewUrl); + } + pendingImage = null; + pendingPreviewUrl = null; + } + + async function uploadImageForEntry(entryId: string, file: File) { + const formData = new FormData(); + formData.append('image', file); + formData.append('entryId', entryId); + + const response = await fetch('?/uploadImage', { + method: 'POST', + body: formData + }); + + const result = await response.json(); + if (result.type === 'failure') { + console.error('Image upload failed:', result.data?.error); + } + }
{ + const imageToUpload = pendingImage; + return async ({ result, update }) => { - if (result.type === 'success') { + if (result.type === 'success' && result.data && 'entryId' in result.data) { // Update preference $preferences.lastEntryType = type; + + // Upload pending image if exists + if (imageToUpload) { + isUploading = true; + try { + await uploadImageForEntry(result.data.entryId as string, imageToUpload); + } finally { + isUploading = false; + clearPendingImage(); + } + } + // Clear form title = ''; content = ''; - // Let SvelteKit refresh data } await update(); }; @@ -40,15 +98,50 @@ placeholder="Title (optional)" class="w-full px-3 py-2 border border-gray-200 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> -
- +
+
+ + + {#if pendingPreviewUrl} +
+ Pending upload + + {pendingImage?.name} + + +
+ {/if} +
- +
+ + +
+ + +