Add widget reply notification setting
This commit is contained in:
@@ -85,6 +85,10 @@
|
|||||||
<label>Pozice mobil <select name="mobileSide"><option>right</option><option>left</option></select></label>
|
<label>Pozice mobil <select name="mobileSide"><option>right</option><option>left</option></select></label>
|
||||||
<label>Mobil bottom px <input name="mobileBottom" type="number"></label>
|
<label>Mobil bottom px <input name="mobileBottom" type="number"></label>
|
||||||
<label>Mobil offset px <input name="mobileOffset" type="number"></label>
|
<label>Mobil offset px <input name="mobileOffset" type="number"></label>
|
||||||
|
<label class="switch">
|
||||||
|
<input name="pulseOnAdminReply" type="checkbox">
|
||||||
|
<span>Widget ma pulzovat pri odpovedi</span>
|
||||||
|
</label>
|
||||||
<button type="submit">Ulozit</button>
|
<button type="submit">Ulozit</button>
|
||||||
</form>
|
</form>
|
||||||
<h3>GTM snippet</h3>
|
<h3>GTM snippet</h3>
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ $("#settingsForm").addEventListener("submit", async (event) => {
|
|||||||
settings.mobile.side = form.get("mobileSide");
|
settings.mobile.side = form.get("mobileSide");
|
||||||
settings.mobile.bottomPx = Number(form.get("mobileBottom"));
|
settings.mobile.bottomPx = Number(form.get("mobileBottom"));
|
||||||
settings.mobile.sideOffsetPx = Number(form.get("mobileOffset"));
|
settings.mobile.sideOffsetPx = Number(form.get("mobileOffset"));
|
||||||
|
settings.notifications = settings.notifications || {};
|
||||||
|
settings.notifications.pulseOnAdminReply = form.get("pulseOnAdminReply") === "on";
|
||||||
await saveSite({ settings, isOnline: state.site.isOnline });
|
await saveSite({ settings, isOnline: state.site.isOnline });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ $("#replyForm").addEventListener("submit", async (event) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
body: payload
|
body: payload
|
||||||
});
|
});
|
||||||
|
event.currentTarget.querySelector("textarea[name='message']").value = "";
|
||||||
event.currentTarget.reset();
|
event.currentTarget.reset();
|
||||||
state.replyFiles = [];
|
state.replyFiles = [];
|
||||||
renderReplyAttachments();
|
renderReplyAttachments();
|
||||||
@@ -243,6 +246,7 @@ function renderSite() {
|
|||||||
form.mobileSide.value = s.mobile.side || "right";
|
form.mobileSide.value = s.mobile.side || "right";
|
||||||
form.mobileBottom.value = s.mobile.bottomPx || 14;
|
form.mobileBottom.value = s.mobile.bottomPx || 14;
|
||||||
form.mobileOffset.value = s.mobile.sideOffsetPx || 12;
|
form.mobileOffset.value = s.mobile.sideOffsetPx || 12;
|
||||||
|
form.pulseOnAdminReply.checked = s.notifications?.pulseOnAdminReply !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderConversations() {
|
function renderConversations() {
|
||||||
|
|||||||
+25
-5
@@ -68,7 +68,10 @@
|
|||||||
const attachmentList = root.querySelector(".mf-attachment-list");
|
const attachmentList = root.querySelector(".mf-attachment-list");
|
||||||
let selectedFiles = [];
|
let selectedFiles = [];
|
||||||
|
|
||||||
launcher.addEventListener("click", () => panel.classList.toggle("open"));
|
launcher.addEventListener("click", () => {
|
||||||
|
panel.classList.toggle("open");
|
||||||
|
if (panel.classList.contains("open")) launcher.classList.remove("mf-notify");
|
||||||
|
});
|
||||||
close.addEventListener("click", () => panel.classList.remove("open"));
|
close.addEventListener("click", () => panel.classList.remove("open"));
|
||||||
|
|
||||||
form.message.addEventListener("input", debounce(() => {
|
form.message.addEventListener("input", debounce(() => {
|
||||||
@@ -133,7 +136,7 @@
|
|||||||
selectedFiles = [];
|
selectedFiles = [];
|
||||||
renderSelectedFiles(attachmentList, selectedFiles);
|
renderSelectedFiles(attachmentList, selectedFiles);
|
||||||
renderConversation(messages, data);
|
renderConversation(messages, data);
|
||||||
connectEvents(messages, typing);
|
connectEvents(messages, typing, panel, launcher, settings);
|
||||||
} catch {
|
} catch {
|
||||||
showFormError(root, "Zpravu se nepodarilo odeslat. Zkuste to prosim znovu.");
|
showFormError(root, "Zpravu se nepodarilo odeslat. Zkuste to prosim znovu.");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -143,7 +146,7 @@
|
|||||||
|
|
||||||
rememberPage();
|
rememberPage();
|
||||||
if (session.conversationId) {
|
if (session.conversationId) {
|
||||||
loadExistingConversation(messages).finally(() => connectEvents(messages, typing));
|
loadExistingConversation(messages).finally(() => connectEvents(messages, typing, panel, launcher, settings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +158,7 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectEvents(messages, typing) {
|
function connectEvents(messages, typing, panel, launcher, settings) {
|
||||||
if (session.events || !session.conversationId) return;
|
if (session.events || !session.conversationId) return;
|
||||||
session.events = new EventSource(`${apiBase}/api/widget/conversations/${session.conversationId}/events`);
|
session.events = new EventSource(`${apiBase}/api/widget/conversations/${session.conversationId}/events`);
|
||||||
session.events.addEventListener("message", (event) => {
|
session.events.addEventListener("message", (event) => {
|
||||||
@@ -165,7 +168,14 @@
|
|||||||
clearTimeout(connectEvents.typingTimer);
|
clearTimeout(connectEvents.typingTimer);
|
||||||
connectEvents.typingTimer = setTimeout(() => { typing.hidden = true; }, 1800);
|
connectEvents.typingTimer = setTimeout(() => { typing.hidden = true; }, 1800);
|
||||||
}
|
}
|
||||||
if (data.type === "message:new") renderConversation(messages, data.payload);
|
if (data.type === "message:new") {
|
||||||
|
renderConversation(messages, data.payload);
|
||||||
|
const eventMessages = data.payload?.messages || [];
|
||||||
|
const lastMessage = eventMessages[eventMessages.length - 1];
|
||||||
|
if (settings.notifications?.pulseOnAdminReply !== false && lastMessage?.senderType === "admin" && !panel.classList.contains("open")) {
|
||||||
|
launcher.classList.add("mf-notify");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,6 +286,11 @@
|
|||||||
.mf-launcher span { width: 26px; margin: 0 auto; }
|
.mf-launcher span { width: 26px; margin: 0 auto; }
|
||||||
.mf-launcher span:before { transform: translateY(-8px); }
|
.mf-launcher span:before { transform: translateY(-8px); }
|
||||||
.mf-launcher span:after { transform: translateY(5px); width: 18px; }
|
.mf-launcher span:after { transform: translateY(5px); width: 18px; }
|
||||||
|
.mf-launcher.mf-notify { animation: mfPulse 1.15s ease-out infinite; }
|
||||||
|
.mf-launcher.mf-notify:after {
|
||||||
|
position: absolute; top: 5px; right: 5px; width: 13px; height: 13px; border-radius: 50%;
|
||||||
|
background: #ff3b30; border: 2px solid ${c.surface}; content: "";
|
||||||
|
}
|
||||||
.mf-panel {
|
.mf-panel {
|
||||||
position: fixed; z-index: 2147483001; ${desktop.side}: ${desktop.sideOffsetPx}px; bottom: ${desktop.bottomPx + 72}px;
|
position: fixed; z-index: 2147483001; ${desktop.side}: ${desktop.sideOffsetPx}px; bottom: ${desktop.bottomPx + 72}px;
|
||||||
width: min(${desktop.widthPx}px, calc(100vw - 24px)); max-height: min(680px, calc(100vh - 110px)); display: none;
|
width: min(${desktop.widthPx}px, calc(100vw - 24px)); max-height: min(680px, calc(100vh - 110px)); display: none;
|
||||||
@@ -313,6 +328,11 @@
|
|||||||
.mf-note.error { color: #b42318; }
|
.mf-note.error { color: #b42318; }
|
||||||
.mf-powered { justify-self: center; color: ${c.muted}; font-size: 11px; text-decoration: none; }
|
.mf-powered { justify-self: center; color: ${c.muted}; font-size: 11px; text-decoration: none; }
|
||||||
.mf-powered:hover { color: ${c.brand}; text-decoration: underline; }
|
.mf-powered:hover { color: ${c.brand}; text-decoration: underline; }
|
||||||
|
@keyframes mfPulse {
|
||||||
|
0% { box-shadow: 0 12px 30px rgb(0 0 0 / 22%), 0 0 0 0 rgb(15 143 111 / 42%); transform: scale(1); }
|
||||||
|
70% { box-shadow: 0 12px 30px rgb(0 0 0 / 22%), 0 0 0 14px rgb(15 143 111 / 0%); transform: scale(1.04); }
|
||||||
|
100% { box-shadow: 0 12px 30px rgb(0 0 0 / 22%), 0 0 0 0 rgb(15 143 111 / 0%); transform: scale(1); }
|
||||||
|
}
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.mf-launcher { ${mobile.side}: ${mobile.sideOffsetPx}px; bottom: ${mobile.bottomPx}px; }
|
.mf-launcher { ${mobile.side}: ${mobile.sideOffsetPx}px; bottom: ${mobile.bottomPx}px; }
|
||||||
.mf-panel { ${mobile.side}: ${mobile.sideOffsetPx}px; bottom: ${mobile.bottomPx + 68}px; width: min(${mobile.widthPx}px, calc(100vw - 24px)); max-height: calc(100vh - 96px); }
|
.mf-panel { ${mobile.side}: ${mobile.sideOffsetPx}px; bottom: ${mobile.bottomPx + 68}px; width: min(${mobile.widthPx}px, calc(100vw - 24px)); max-height: calc(100vh - 96px); }
|
||||||
|
|||||||
@@ -219,7 +219,8 @@ function defaultSettings() {
|
|||||||
muted: "#647067"
|
muted: "#647067"
|
||||||
},
|
},
|
||||||
desktop: { side: "right", bottomPx: 22, sideOffsetPx: 22, widthPx: 370 },
|
desktop: { side: "right", bottomPx: 22, sideOffsetPx: 22, widthPx: 370 },
|
||||||
mobile: { side: "right", bottomPx: 14, sideOffsetPx: 12, widthPx: 340 }
|
mobile: { side: "right", bottomPx: 14, sideOffsetPx: 12, widthPx: 340 },
|
||||||
|
notifications: { pulseOnAdminReply: true }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,13 +615,15 @@ function formatAttachment(row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatSite(site) {
|
function formatSite(site) {
|
||||||
|
const settings = parseJson(site.settings_json, defaultSettings());
|
||||||
|
settings.notifications = { ...defaultSettings().notifications, ...(settings.notifications || {}) };
|
||||||
return {
|
return {
|
||||||
id: site.id,
|
id: site.id,
|
||||||
siteKey: site.site_key,
|
siteKey: site.site_key,
|
||||||
name: site.name,
|
name: site.name,
|
||||||
domain: site.domain,
|
domain: site.domain,
|
||||||
isOnline: Boolean(site.is_online),
|
isOnline: Boolean(site.is_online),
|
||||||
settings: parseJson(site.settings_json, defaultSettings())
|
settings
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user