Initial Mebbling hub implementation
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type Attachment = { name?: string; filename?: string; url?: string; externalLink?: string; type?: string; size?: string | number };
|
||||
|
||||
function resourceUrl(attachment: Attachment, sourceBaseUrl?: string) {
|
||||
if (attachment.url) return attachment.url;
|
||||
if (attachment.externalLink) return attachment.externalLink;
|
||||
if (!sourceBaseUrl || !attachment.name || !attachment.filename) return null;
|
||||
const resourceName = attachment.name.split("/").map(encodeURIComponent).join("/");
|
||||
return `${sourceBaseUrl.replace(/\/$/, "")}/file/${resourceName}/${encodeURIComponent(attachment.filename)}`;
|
||||
}
|
||||
|
||||
export function Attachments({ json, sourceBaseUrl, compact = false }: { json: string; sourceBaseUrl?: string | null; compact?: boolean }) {
|
||||
const [activeImage, setActiveImage] = useState<{ href: string; label: string } | null>(null);
|
||||
useEffect(() => {
|
||||
const closeOnEscape = (event: KeyboardEvent) => { if (event.key === "Escape") setActiveImage(null); };
|
||||
window.addEventListener("keydown", closeOnEscape);
|
||||
return () => window.removeEventListener("keydown", closeOnEscape);
|
||||
}, []);
|
||||
|
||||
let attachments: Attachment[] = [];
|
||||
try { attachments = JSON.parse(json); } catch { return null; }
|
||||
const displayable = attachments.map((attachment) => ({ attachment, href: resourceUrl(attachment, sourceBaseUrl || undefined) })).filter((item): item is { attachment: Attachment; href: string } => Boolean(item.href));
|
||||
if (!displayable.length) return null;
|
||||
return <>
|
||||
<section className={`attachments${compact ? " attachments-compact" : ""}`} aria-label="附件">
|
||||
{displayable.map(({ attachment, href }) => {
|
||||
const label = attachment.filename || attachment.name || "附件";
|
||||
if (attachment.type?.startsWith("image/")) return <button type="button" className="attachment-image" onClick={() => setActiveImage({ href, label })} key={href} aria-label={`放大檢視:${label}`}><img src={href} alt={label} /></button>;
|
||||
return <a className="attachment-file" href={href} target="_blank" rel="noreferrer" key={href}>📎 {label}</a>;
|
||||
})}
|
||||
</section>
|
||||
{activeImage && <div className="image-lightbox" role="dialog" aria-modal="true" aria-label={activeImage.label} onClick={() => setActiveImage(null)}>
|
||||
<button type="button" className="image-lightbox-close" onClick={() => setActiveImage(null)} aria-label="關閉圖片檢視">×</button>
|
||||
<img src={activeImage.href} alt={activeImage.label} onClick={(event) => event.stopPropagation()} />
|
||||
</div>}
|
||||
</>;
|
||||
}
|
||||
Reference in New Issue
Block a user