Fix widget message persistence

This commit is contained in:
rajchales-monito
2026-07-23 22:53:07 +02:00
parent b56469e774
commit 7b6d855fec
2 changed files with 99 additions and 27 deletions
+67 -26
View File
@@ -119,39 +119,32 @@
event.preventDefault();
const body = await payloadFromForm(form, selectedFiles);
if (!body.message.trim()) return;
addMessage(messages, "visitor", body.message, []);
form.message.value = "";
selectedFiles = [];
renderSelectedFiles(attachmentList, selectedFiles);
const submitButton = root.querySelector(".mf-send");
submitButton.disabled = true;
const url = session.conversationId
? `${apiBase}/api/widget/conversations/${session.conversationId}/messages`
: `${apiBase}/api/widget/conversations`;
const data = await postJson(url, {
...body,
siteKey,
visitorToken: session.visitorToken,
currentUrl: location.href,
referrer: document.referrer,
browsingHistory: rememberPage(),
language: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
device: {
userAgent: navigator.userAgent,
platform: navigator.platform,
viewport: `${innerWidth}x${innerHeight}`
}
});
session.conversationId = data.conversation.conversation.id;
session.visitorToken = data.visitorToken || session.visitorToken;
saveSession();
renderConversation(messages, data.conversation);
connectEvents(messages, typing);
try {
const data = await postJson(url, withVisitorContext(body));
session.conversationId = data.conversation.id;
saveSession();
form.message.value = "";
selectedFiles = [];
renderSelectedFiles(attachmentList, selectedFiles);
renderConversation(messages, data);
connectEvents(messages, typing);
} catch {
showFormError(root, "Zpravu se nepodarilo odeslat. Zkuste to prosim znovu.");
} finally {
submitButton.disabled = false;
}
});
rememberPage();
if (session.conversationId) connectEvents(messages, typing);
if (session.conversationId) {
loadExistingConversation(messages).finally(() => connectEvents(messages, typing));
}
}
async function payloadFromForm(form, selectedFiles) {
@@ -176,6 +169,16 @@
});
}
async function loadExistingConversation(messages) {
try {
const data = await fetchJson(`${apiBase}/api/widget/conversations/${session.conversationId}?visitorToken=${encodeURIComponent(session.visitorToken)}`);
renderConversation(messages, data);
} catch {
session.conversationId = null;
saveSession();
}
}
function renderConversation(messages, data) {
messages.innerHTML = "";
for (const message of data.messages) {
@@ -212,6 +215,35 @@
`).join("");
}
function withVisitorContext(body) {
return {
...body,
siteKey,
visitorToken: session.visitorToken,
currentUrl: location.href,
referrer: document.referrer,
browsingHistory: rememberPage(),
language: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
device: {
userAgent: navigator.userAgent,
platform: navigator.platform,
viewport: `${innerWidth}x${innerHeight}`
}
};
}
function showFormError(root, message) {
const note = root.querySelector(".mf-note");
note.textContent = message;
note.classList.add("error");
clearTimeout(showFormError.timer);
showFormError.timer = setTimeout(() => {
note.textContent = "Odeslanim zpravy nam predavate udaje potrebne pro odpoved.";
note.classList.remove("error");
}, 3500);
}
function isAllowedImageFile(file) {
return allowedImageTypes.has(file.type) && file.size > 0 && file.size <= maxAttachmentBytes;
}
@@ -267,6 +299,7 @@
input, textarea { width: 100%; border: 1px solid #dfe6e2; border-radius: 6px; padding: 9px 10px; color: ${c.text}; background: white; }
textarea { resize: vertical; min-height: 72px; }
.mf-send { width: 42px; height: 42px; display: grid; place-items: center; border: 0; border-radius: 50%; background: ${c.brand}; color: ${c.brandText}; cursor: pointer; }
.mf-send:disabled { opacity: .55; cursor: wait; }
.mf-send svg { width: 22px; height: 22px; fill: currentColor; transform: translateX(1px); }
.mf-dropzone { min-height: 42px; border: 1px dashed #b9c9c1; border-radius: 8px; display: flex; align-items: center; gap: 8px; padding: 9px 10px; color: ${c.muted}; background: #f8fbf9; font-size: 12px; cursor: pointer; }
.mf-dropzone.dragging { border-color: ${c.brand}; background: #eef8f4; color: ${c.text}; }
@@ -277,6 +310,7 @@
.mf-attachment-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 210px; }
.mf-attachment-chip button { width: 22px; height: 22px; border: 0; border-radius: 50%; background: #e8eee9; color: ${c.text}; cursor: pointer; padding: 0; }
.mf-note { margin: 0; color: ${c.muted}; font-size: 11px; line-height: 1.3; }
.mf-note.error { color: #b42318; }
.mf-powered { justify-self: center; color: ${c.muted}; font-size: 11px; text-decoration: none; }
.mf-powered:hover { color: ${c.brand}; text-decoration: underline; }
@media (max-width: 640px) {
@@ -321,6 +355,13 @@
});
}
function fetchJson(url) {
return fetch(url).then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
}
function fileToPayload(file) {
return new Promise((resolve) => {
const reader = new FileReader();