diff --git a/public/admin.css b/public/admin.css
index 4f0b7d3..bddfece 100644
--- a/public/admin.css
+++ b/public/admin.css
@@ -126,6 +126,85 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; }
.message .by { font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.message p { margin: 0; white-space: pre-wrap; }
.message a { color: var(--brand); display: inline-block; margin-top: 6px; }
+.attachments {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 8px;
+}
+.photo-preview {
+ width: 116px;
+ display: grid;
+ gap: 5px;
+ padding: 0;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: white;
+ color: var(--ink);
+ overflow: hidden;
+ text-align: left;
+}
+.photo-preview img {
+ width: 100%;
+ aspect-ratio: 4 / 3;
+ object-fit: cover;
+ display: block;
+ background: #edf3ef;
+}
+.photo-preview span {
+ padding: 0 7px 7px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--muted);
+ font-size: 12px;
+}
+.photo-modal {
+ position: fixed;
+ inset: 0;
+ z-index: 20;
+ display: grid;
+ place-items: center;
+ padding: 24px;
+}
+.photo-backdrop {
+ position: absolute;
+ inset: 0;
+ border: 0;
+ border-radius: 0;
+ background: rgb(0 0 0 / 72%);
+}
+.photo-modal figure {
+ position: relative;
+ z-index: 1;
+ margin: 0;
+ max-width: min(1100px, 94vw);
+ max-height: 90vh;
+ display: grid;
+ gap: 8px;
+}
+.photo-modal img {
+ max-width: 100%;
+ max-height: 82vh;
+ object-fit: contain;
+ border-radius: 8px;
+ background: white;
+}
+.photo-modal figcaption {
+ color: white;
+ text-align: center;
+ font-size: 13px;
+}
+.photo-close {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ width: 34px;
+ height: 34px;
+ padding: 0;
+ border-radius: 50%;
+ background: rgb(0 0 0 / 54%);
+}
.typing { padding: 0 20px 10px; color: var(--brand); font-size: 13px; }
.reply {
display: grid;
diff --git a/public/admin.html b/public/admin.html
index 0bc3381..569aac6 100644
--- a/public/admin.html
+++ b/public/admin.html
@@ -4,6 +4,7 @@
MaalFlows Admin
+
diff --git a/public/admin.js b/public/admin.js
index 7c10a04..3f29cf9 100644
--- a/public/admin.js
+++ b/public/admin.js
@@ -87,6 +87,12 @@ $("#statusSelect").addEventListener("change", async () => {
await loadConversations();
});
+$("#messages").addEventListener("click", (event) => {
+ const preview = event.target.closest("[data-photo-url]");
+ if (!preview) return;
+ openPhotoPreview(preview.dataset.photoUrl, preview.dataset.photoName);
+});
+
async function showApp() {
$("#loginView").classList.add("hidden");
$("#appView").classList.remove("hidden");
@@ -188,12 +194,57 @@ function renderActive() {
${escapeHtml(message.senderName || message.senderType)} · ${escapeHtml(message.createdAt)}
${escapeHtml(message.body)}
- ${message.attachments.map((a) => `${escapeHtml(a.name)}`).join("")}
+ ${renderAttachments(message.attachments)}
`).join("");
$("#messages").scrollTop = $("#messages").scrollHeight;
}
+function renderAttachments(attachments) {
+ if (!attachments.length) return "";
+ return `${attachments.map((attachment) => {
+ if (!attachment.type?.startsWith("image/")) {
+ return `
${escapeHtml(attachment.name)}`;
+ }
+ return `
+
+ `;
+ }).join("")}
`;
+}
+
+function openPhotoPreview(url, name) {
+ let modal = $("#photoModal");
+ if (!modal) {
+ modal = document.createElement("div");
+ modal.id = "photoModal";
+ modal.className = "photo-modal hidden";
+ modal.innerHTML = `
+
+
+
+
+
+
+ `;
+ document.body.append(modal);
+ modal.addEventListener("click", (event) => {
+ if (event.target.closest(".photo-close") || event.target.classList.contains("photo-backdrop")) {
+ modal.classList.add("hidden");
+ }
+ });
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape") modal.classList.add("hidden");
+ });
+ }
+ modal.querySelector("img").src = url;
+ modal.querySelector("img").alt = name || "Fotka";
+ modal.querySelector("figcaption").textContent = name || "";
+ modal.classList.remove("hidden");
+}
+
async function saveSite(patch) {
const data = await api("/api/admin/site", { method: "PATCH", body: patch });
state.site = data.site;
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..59552ce
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/preview.html b/public/preview.html
index fc657a2..b8800ef 100644
--- a/public/preview.html
+++ b/public/preview.html
@@ -4,6 +4,7 @@
MaalFlows Preview
+