Staff report: inline lightbox for image/PDF attachments
Adds a /api/staff/file proxy that re-streams Civi attachments with Content-Disposition: inline so a native <dialog> lightbox can preview images and PDFs in place. Office docs keep their plain download link and gain a "View in Google Docs" secondary link (uses the Civi-signed URL so Google can fetch without our staff key). Also threads mime through /api/staff/report (Attachment.get mime_type) so the dispatcher picks the right affordance without relying solely on filename inference.
This commit is contained in:
@@ -193,7 +193,7 @@ function buildStubPayload(orgId: number): StaffReportPayload {
|
||||
{
|
||||
activityId: 9012,
|
||||
date: daysAgo(3),
|
||||
value: { id: 4242, file_name: "co-op-vision.pdf" },
|
||||
value: { id: 4242, file_name: "co-op-vision.pdf", mime: "application/pdf" },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -373,6 +373,7 @@ async function buildLivePayload(orgId: number): Promise<StaffReportPayload> {
|
||||
}
|
||||
|
||||
const urlByFileId = new Map<number, string>();
|
||||
const mimeByFileId = new Map<number, string>();
|
||||
if (fileIds.size > 0) {
|
||||
// APIv3 Attachment.get doesn't accept an IN-clause cleanly on this Civi
|
||||
// install — passing {IN: [...]} for `id` crashes Civi's error renderer
|
||||
@@ -382,11 +383,15 @@ async function buildLivePayload(orgId: number): Promise<StaffReportPayload> {
|
||||
// small. Each request is independent; we issue them in parallel.
|
||||
const lookups = await Promise.allSettled(
|
||||
Array.from(fileIds).map((fid) =>
|
||||
civi3<{ id: string | number; url?: string }>("Attachment", "get", {
|
||||
id: fid,
|
||||
return: "id,url",
|
||||
sequential: 1,
|
||||
}).then((r) => ({ fid, row: r.values?.[0] })),
|
||||
civi3<{ id: string | number; url?: string; mime_type?: string }>(
|
||||
"Attachment",
|
||||
"get",
|
||||
{
|
||||
id: fid,
|
||||
return: "id,url,mime_type",
|
||||
sequential: 1,
|
||||
},
|
||||
).then((r) => ({ fid, row: r.values?.[0] })),
|
||||
),
|
||||
);
|
||||
for (const result of lookups) {
|
||||
@@ -401,6 +406,9 @@ async function buildLivePayload(orgId: number): Promise<StaffReportPayload> {
|
||||
if (row && typeof row.url === "string" && row.url.length > 0) {
|
||||
urlByFileId.set(fid, row.url);
|
||||
}
|
||||
if (row && typeof row.mime_type === "string" && row.mime_type.length > 0) {
|
||||
mimeByFileId.set(fid, row.mime_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,6 +455,7 @@ async function buildLivePayload(orgId: number): Promise<StaffReportPayload> {
|
||||
id: raw,
|
||||
file_name: typeof fname === "string" ? fname : undefined,
|
||||
url: Number.isFinite(fid) ? urlByFileId.get(fid) : undefined,
|
||||
mime: Number.isFinite(fid) ? mimeByFileId.get(fid) : undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -480,6 +489,7 @@ async function buildLivePayload(orgId: number): Promise<StaffReportPayload> {
|
||||
id: v,
|
||||
file_name: typeof fname === "string" ? fname : undefined,
|
||||
url: Number.isFinite(fid) ? urlByFileId.get(fid) : undefined,
|
||||
mime: Number.isFinite(fid) ? mimeByFileId.get(fid) : undefined,
|
||||
};
|
||||
}
|
||||
entries.push({ activityId: row.id, date: row.activity_date_time, value });
|
||||
|
||||
Reference in New Issue
Block a user