Fix multi-image upload in article editor; add thumbnail lightbox

- All selected images now upload and insert correctly (was: only last
  image kept due to stale content closure in loop)
- Images inserted as a block with proper newline padding
- Thumbnail strip thumbnails are now clickable — opens a lightbox;
  click overlay to dismiss
This commit is contained in:
Jure
2026-04-10 11:33:13 +02:00
parent 0bda04904b
commit 7375ddc7e3
2 changed files with 30 additions and 10 deletions
+17 -2
View File
@@ -54,6 +54,7 @@ export function ArticleEditor() {
const [zenMode, setZenMode] = useState(false);
const [zenHint, setZenHint] = useState(false);
const zenTextareaRef = useRef<HTMLTextAreaElement>(null);
const [lightboxUrl, setLightboxUrl] = useState<string | null>(null);
const toggleZen = useCallback(async () => {
const win = getCurrentWindow();
@@ -426,11 +427,11 @@ export function ArticleEditor() {
<div className="flex items-center gap-2 px-6 py-2 border-b border-border bg-bg-raised/50 overflow-x-auto shrink-0">
<span className="text-text-dim text-[10px] shrink-0">{inlineImages.length} {inlineImages.length === 1 ? "image" : "images"}</span>
{inlineImages.map((img, i) => (
<div key={i} className="relative shrink-0 group">
<div key={i} className="relative shrink-0 group cursor-zoom-in" onClick={() => setLightboxUrl(img.url)}>
<img
src={img.url}
alt={img.alt}
className="h-12 w-auto rounded-sm border border-border object-cover"
className="h-12 w-auto rounded-sm border border-border object-cover group-hover:border-accent transition-colors"
onError={(e) => { (e.target as HTMLImageElement).style.display = "none"; }}
/>
</div>
@@ -438,6 +439,20 @@ export function ArticleEditor() {
</div>
)}
{/* Lightbox */}
{lightboxUrl && (
<div
className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center cursor-zoom-out"
onClick={() => setLightboxUrl(null)}
>
<img
src={lightboxUrl}
className="max-w-[90vw] max-h-[90vh] rounded-md shadow-2xl object-contain"
onClick={(e) => e.stopPropagation()}
/>
</div>
)}
{/* Content area */}
<div className="flex-1 overflow-y-auto px-6 pb-6">
{mode === "write" ? (
+13 -8
View File
@@ -151,19 +151,24 @@ export function MarkdownToolbar({ textareaRef, content, setContent, setUploading
setUploading?.(true);
setError?.(null);
try {
const mimeMap: Record<string, string> = {
jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif",
webp: "image/webp", svg: "image/svg+xml",
};
const snippets: string[] = [];
for (const filePath of paths) {
const bytes = await readFile(filePath);
const fileName = filePath.split(/[\\/]/).pop() || "image.png";
const ext = fileName.split(".").pop()?.toLowerCase() || "png";
const mimeMap: Record<string, string> = {
jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif",
webp: "image/webp", svg: "image/svg+xml",
};
const url = await uploadBytes(new Uint8Array(bytes), fileName, mimeMap[ext] || "image/png");
const textarea = textareaRef.current;
if (textarea) {
applyMarkdown(textarea, "image", content, setContent, `![${fileName}](${url})`);
}
snippets.push(`![${fileName}](${url})`);
}
const textarea = textareaRef.current;
if (textarea && snippets.length > 0) {
const cursorPos = textarea.selectionStart ?? content.length;
const needsLeadingNewline = cursorPos > 0 && content[cursorPos - 1] !== "\n";
const block = (needsLeadingNewline ? "\n\n" : "") + snippets.join("\n\n") + "\n\n";
applyMarkdown(textarea, "image", content, setContent, block);
}
} finally {
setUploading?.(false);