Work on fixing bugs and reformat
This commit is contained in:
@@ -399,8 +399,8 @@
|
||||
return String(value || "")
|
||||
.split(",")
|
||||
.map(function (item) {
|
||||
return item.trim();
|
||||
})
|
||||
return item.trim();
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
|
||||
@@ -536,8 +536,8 @@
|
||||
showOperationPane(operation);
|
||||
const activeTab = tabKey || (
|
||||
operation === "artifacts"
|
||||
? ((window.giaWorkspaceState[personId] || {}).currentMitigationTab || "plan_board")
|
||||
: operation
|
||||
? ((window.giaWorkspaceState[personId] || {}).currentMitigationTab || "plan_board")
|
||||
: operation
|
||||
);
|
||||
setTopCapsuleActive(activeTab);
|
||||
const hydrated = hydrateCachedIfAvailable(operation);
|
||||
@@ -573,8 +573,8 @@
|
||||
const currentState = window.giaWorkspaceState[personId] || {};
|
||||
const targetTabKey = currentState.pendingTabKey || (
|
||||
operation === "artifacts"
|
||||
? (currentState.currentMitigationTab || "plan_board")
|
||||
: operation
|
||||
? (currentState.currentMitigationTab || "plan_board")
|
||||
: operation
|
||||
);
|
||||
if (!forceRefresh && currentState.current === operation && pane.dataset.loaded === "1") {
|
||||
window.giaWorkspaceShowTab(personId, operation, targetTabKey);
|
||||
@@ -663,8 +663,8 @@
|
||||
const state = window.giaWorkspaceState[personId] || {};
|
||||
const currentTab = state.currentTab || (
|
||||
state.current === "artifacts"
|
||||
? (state.currentMitigationTab || "plan_board")
|
||||
: (state.current || "plan_board")
|
||||
? (state.currentMitigationTab || "plan_board")
|
||||
: (state.current || "plan_board")
|
||||
);
|
||||
window.giaWorkspaceOpenTab(personId, currentTab, true);
|
||||
};
|
||||
|
||||
@@ -254,6 +254,9 @@
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
<article class="compose-bubble {% if msg.outgoing %}is-out{% else %}is-in{% endif %}">
|
||||
<div class="compose-source-badge-wrap">
|
||||
<span class="compose-source-badge source-{{ msg.source_service|default:'web'|lower }}">{{ msg.source_label }}</span>
|
||||
</div>
|
||||
{% if msg.image_urls %}
|
||||
{% for image_url in msg.image_urls %}
|
||||
<figure class="compose-media">
|
||||
@@ -285,14 +288,30 @@
|
||||
<p class="compose-msg-meta">
|
||||
{{ msg.display_ts }}{% if msg.author %} · {{ msg.author }}{% endif %}
|
||||
{% if msg.read_ts %}
|
||||
<span class="compose-ticks" title="Read at {{ msg.read_display }}">
|
||||
<span
|
||||
class="compose-ticks js-receipt-trigger"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-receipt='{{ msg.receipt_payload|default:"{}"|escapejs }}'
|
||||
data-source='{{ msg.read_source_service }}'
|
||||
data-by='{{ msg.read_by_identifier }}'
|
||||
data-id='{{ msg.id }}'
|
||||
title="Read at {{ msg.read_display }}">
|
||||
<span class="icon is-small"><i class="fa-solid fa-check-double has-text-info"></i></span>
|
||||
<span class="compose-tick-time">{{ msg.read_display }}</span>
|
||||
<span class="compose-tick-time">{{ msg.read_delta_display }}</span>
|
||||
</span>
|
||||
{% elif msg.delivered_ts %}
|
||||
<span class="compose-ticks" title="Delivered at {{ msg.delivered_display }}">
|
||||
<span
|
||||
class="compose-ticks js-receipt-trigger"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
data-receipt='{{ msg.receipt_payload|default:"{}"|escapejs }}'
|
||||
data-source='{{ msg.read_source_service }}'
|
||||
data-by='{{ msg.read_by_identifier }}'
|
||||
data-id='{{ msg.id }}'
|
||||
title="Delivered at {{ msg.delivered_display }}">
|
||||
<span class="icon is-small"><i class="fa-solid fa-check-double has-text-grey"></i></span>
|
||||
<span class="compose-tick-time">{{ msg.delivered_display }}</span>
|
||||
<span class="compose-tick-time">{{ msg.delivered_delta_display }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
@@ -438,6 +457,26 @@
|
||||
padding: 0.52rem 0.62rem;
|
||||
box-shadow: none;
|
||||
}
|
||||
#{{ panel_id }} .compose-source-badge-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 0.36rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-source-badge {
|
||||
font-size: 0.84rem;
|
||||
padding: 0.12rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.02em;
|
||||
box-shadow: 0 1px 0 rgba(0,0,0,0.06);
|
||||
}
|
||||
#{{ panel_id }} .compose-source-badge.source-web { background: #2f4f7a; }
|
||||
#{{ panel_id }} .compose-source-badge.source-xmpp { background: #6a88b4; }
|
||||
#{{ panel_id }} .compose-source-badge.source-whatsapp { background: #25D366; color: #063; }
|
||||
#{{ panel_id }} .compose-source-badge.source-signal { background: #3b82f6; }
|
||||
#{{ panel_id }} .compose-source-badge.source-instagram { background: #c13584; }
|
||||
#{{ panel_id }} .compose-source-badge.source-unknown { background: #6b7280; }
|
||||
#{{ panel_id }} .compose-bubble.is-in {
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
}
|
||||
@@ -1719,6 +1758,7 @@
|
||||
};
|
||||
|
||||
const appendBubble = function (msg) {
|
||||
console.log("[appendBubble]", {id: msg.id, ts: msg.ts, author: msg.author, source_label: msg.source_label, source_service: msg.source_service, outgoing: msg.outgoing});
|
||||
const row = document.createElement("div");
|
||||
const outgoing = !!msg.outgoing;
|
||||
row.className = "compose-row " + (outgoing ? "is-out" : "is-in");
|
||||
@@ -1729,12 +1769,24 @@
|
||||
const bubble = document.createElement("article");
|
||||
bubble.className = "compose-bubble " + (outgoing ? "is-out" : "is-in");
|
||||
|
||||
// Add source badge for client-side rendered messages
|
||||
if (msg.source_label) {
|
||||
console.log("[appendBubble] rendering source badge:", msg.source_label);
|
||||
const badgeWrap = document.createElement("div");
|
||||
badgeWrap.className = "compose-source-badge-wrap";
|
||||
const badge = document.createElement("span");
|
||||
const svc = String(msg.source_service || "web").toLowerCase();
|
||||
badge.className = "compose-source-badge source-" + svc;
|
||||
badge.textContent = String(msg.source_label || "");
|
||||
badgeWrap.appendChild(badge);
|
||||
bubble.appendChild(badgeWrap);
|
||||
}
|
||||
const imageCandidatesFromPayload = Array.isArray(msg.image_urls) && msg.image_urls.length
|
||||
? msg.image_urls
|
||||
: (msg.image_url ? [msg.image_url] : []);
|
||||
? msg.image_urls
|
||||
: (msg.image_url ? [msg.image_url] : []);
|
||||
const imageCandidates = imageCandidatesFromPayload.length
|
||||
? imageCandidatesFromPayload
|
||||
: extractUrlCandidates(msg.text || msg.display_text || "");
|
||||
? imageCandidatesFromPayload
|
||||
: extractUrlCandidates(msg.text || msg.display_text || "");
|
||||
appendImageCandidates(bubble, imageCandidates);
|
||||
|
||||
if (!msg.hide_text) {
|
||||
@@ -1759,44 +1811,55 @@
|
||||
if (msg.author) {
|
||||
metaText += " · " + String(msg.author);
|
||||
}
|
||||
meta.textContent = metaText;
|
||||
// Render delivery/read ticks and a small time label when available.
|
||||
if (msg.read_ts) {
|
||||
const tickWrap = document.createElement("span");
|
||||
tickWrap.className = "compose-ticks";
|
||||
tickWrap.title = "Read at " + String(msg.read_display || msg.read_ts || "");
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "icon is-small";
|
||||
const i = document.createElement("i");
|
||||
i.className = "fa-solid fa-check-double has-text-info";
|
||||
icon.appendChild(i);
|
||||
const timeSpan = document.createElement("span");
|
||||
timeSpan.className = "compose-tick-time";
|
||||
timeSpan.textContent = String(msg.read_display || "");
|
||||
tickWrap.appendChild(icon);
|
||||
tickWrap.appendChild(timeSpan);
|
||||
meta.appendChild(document.createTextNode(" "));
|
||||
meta.appendChild(tickWrap);
|
||||
} else if (msg.delivered_ts) {
|
||||
const tickWrap = document.createElement("span");
|
||||
tickWrap.className = "compose-ticks";
|
||||
tickWrap.title = "Delivered at " + String(msg.delivered_display || msg.delivered_ts || "");
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "icon is-small";
|
||||
const i = document.createElement("i");
|
||||
i.className = "fa-solid fa-check-double has-text-grey";
|
||||
icon.appendChild(i);
|
||||
const timeSpan = document.createElement("span");
|
||||
timeSpan.className = "compose-tick-time";
|
||||
timeSpan.textContent = String(msg.delivered_display || "");
|
||||
tickWrap.appendChild(icon);
|
||||
tickWrap.appendChild(timeSpan);
|
||||
meta.appendChild(document.createTextNode(" "));
|
||||
meta.appendChild(tickWrap);
|
||||
}
|
||||
meta.textContent = metaText;
|
||||
// Render delivery/read ticks and a small time label when available.
|
||||
if (msg.read_ts) {
|
||||
const tickWrap = document.createElement("span");
|
||||
tickWrap.className = "compose-ticks";
|
||||
tickWrap.title = "Read at " + String(msg.read_display || msg.read_ts || "");
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "icon is-small";
|
||||
const i = document.createElement("i");
|
||||
i.className = "fa-solid fa-check-double has-text-info";
|
||||
icon.appendChild(i);
|
||||
const timeSpan = document.createElement("span");
|
||||
timeSpan.className = "compose-tick-time";
|
||||
timeSpan.textContent = String(msg.read_display || "");
|
||||
tickWrap.appendChild(icon);
|
||||
tickWrap.appendChild(timeSpan);
|
||||
meta.appendChild(document.createTextNode(" "));
|
||||
meta.appendChild(tickWrap);
|
||||
} else if (msg.delivered_ts) {
|
||||
const tickWrap = document.createElement("span");
|
||||
tickWrap.className = "compose-ticks";
|
||||
tickWrap.title = "Delivered at " + String(msg.delivered_display || msg.delivered_ts || "");
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "icon is-small";
|
||||
const i = document.createElement("i");
|
||||
i.className = "fa-solid fa-check-double has-text-grey";
|
||||
icon.appendChild(i);
|
||||
const timeSpan = document.createElement("span");
|
||||
timeSpan.className = "compose-tick-time";
|
||||
timeSpan.textContent = String(msg.delivered_display || "");
|
||||
tickWrap.appendChild(icon);
|
||||
tickWrap.appendChild(timeSpan);
|
||||
meta.appendChild(document.createTextNode(" "));
|
||||
meta.appendChild(tickWrap);
|
||||
}
|
||||
bubble.appendChild(meta);
|
||||
|
||||
row.appendChild(bubble);
|
||||
// If message carries receipt metadata, append dataset so the popover can use it.
|
||||
if (msg.receipt_payload || msg.read_source_service || msg.read_by_identifier) {
|
||||
// Attach data attributes on the row so event delegation can find them.
|
||||
try {
|
||||
row.dataset.receipt = JSON.stringify(msg.receipt_payload || {});
|
||||
} catch (e) {
|
||||
row.dataset.receipt = "{}";
|
||||
}
|
||||
row.dataset.receiptSource = String(msg.read_source_service || "");
|
||||
row.dataset.receiptBy = String(msg.read_by_identifier || "");
|
||||
row.dataset.receiptId = String(msg.id || "");
|
||||
}
|
||||
const empty = thread.querySelector(".compose-empty");
|
||||
if (empty) {
|
||||
empty.remove();
|
||||
@@ -1810,6 +1873,87 @@
|
||||
updateGlanceFromMessage(msg);
|
||||
};
|
||||
|
||||
// Receipt popover (similar to contact info popover)
|
||||
const receiptPopover = document.createElement("div");
|
||||
receiptPopover.id = "compose-receipt-popover";
|
||||
receiptPopover.className = "compose-ai-popover is-hidden";
|
||||
receiptPopover.setAttribute("aria-hidden", "true");
|
||||
receiptPopover.innerHTML = `
|
||||
<div class="compose-ai-card is-active" style="min-width:18rem;">
|
||||
<p class="compose-ai-title">Receipt Details</p>
|
||||
<div class="compose-ai-content">
|
||||
<table class="table is-fullwidth is-striped is-size-7"><tbody>
|
||||
<tr><th>Message ID</th><td id="receipt-msg-id">-</td></tr>
|
||||
<tr><th>Source</th><td id="receipt-source">-</td></tr>
|
||||
<tr><th>Read By</th><td id="receipt-by">-</td></tr>
|
||||
<tr><th>Delivered</th><td id="receipt-delivered">-</td></tr>
|
||||
<tr><th>Read</th><td id="receipt-read">-</td></tr>
|
||||
<tr><th>Payload</th><td><pre id="receipt-payload" style="white-space:pre-wrap;max-height:18rem;overflow:auto"></pre></td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(receiptPopover);
|
||||
|
||||
let activeReceiptBtn = null;
|
||||
function hideReceiptPopover() {
|
||||
receiptPopover.classList.add("is-hidden");
|
||||
receiptPopover.setAttribute("aria-hidden", "true");
|
||||
activeReceiptBtn = null;
|
||||
}
|
||||
function positionReceiptPopover(btn) {
|
||||
const rect = btn.getBoundingClientRect();
|
||||
const width = Math.min(520, Math.max(280, Math.floor(window.innerWidth * 0.32)));
|
||||
const left = Math.min(window.innerWidth - width - 16, Math.max(12, rect.left - width + rect.width));
|
||||
const top = Math.min(window.innerHeight - 24, rect.bottom + 8);
|
||||
receiptPopover.style.left = left + "px";
|
||||
receiptPopover.style.top = top + "px";
|
||||
receiptPopover.style.width = width + "px";
|
||||
}
|
||||
function openReceiptPopoverFromData(data, btn) {
|
||||
document.getElementById("receipt-msg-id").textContent = data.id || "-";
|
||||
document.getElementById("receipt-source").textContent = data.source || "-";
|
||||
document.getElementById("receipt-by").textContent = data.by || "-";
|
||||
document.getElementById("receipt-delivered").textContent = data.delivered || "-";
|
||||
document.getElementById("receipt-read").textContent = data.read || "-";
|
||||
try {
|
||||
const out = typeof data.payload === 'string' ? JSON.parse(data.payload) : data.payload || {};
|
||||
document.getElementById("receipt-payload").textContent = JSON.stringify(out, null, 2);
|
||||
} catch (e) {
|
||||
document.getElementById("receipt-payload").textContent = String(data.payload || "{}");
|
||||
}
|
||||
positionReceiptPopover(btn);
|
||||
receiptPopover.classList.remove("is-hidden");
|
||||
receiptPopover.setAttribute("aria-hidden", "false");
|
||||
}
|
||||
|
||||
// Delegate click on tick triggers inside thread
|
||||
thread.addEventListener("click", function (ev) {
|
||||
const btn = ev.target.closest && ev.target.closest('.js-receipt-trigger');
|
||||
if (!btn) return;
|
||||
if (activeReceiptBtn === btn && !receiptPopover.classList.contains('is-hidden')) {
|
||||
hideReceiptPopover();
|
||||
return;
|
||||
}
|
||||
activeReceiptBtn = btn;
|
||||
const payload = btn.dataset && btn.dataset.receipt ? btn.dataset.receipt : (btn.parentNode && btn.parentNode.dataset ? btn.parentNode.dataset.receipt : "{}");
|
||||
const source = btn.dataset && btn.dataset.source ? btn.dataset.source : (btn.parentNode && btn.parentNode.dataset ? btn.parentNode.dataset.receiptSource : "");
|
||||
const by = btn.dataset && btn.dataset.by ? btn.dataset.by : (btn.parentNode && btn.parentNode.dataset ? btn.parentNode.dataset.receiptBy : "");
|
||||
const id = btn.dataset && btn.dataset.id ? btn.dataset.id : (btn.parentNode && btn.parentNode.dataset ? btn.parentNode.dataset.receiptId : "");
|
||||
const delivered = btn.title || "";
|
||||
const read = btn.title || "";
|
||||
openReceiptPopoverFromData({ id: id, payload: payload, source: source, by: by, delivered: delivered, read: read }, btn);
|
||||
});
|
||||
|
||||
// Close receipt popover on outside click / escape
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (receiptPopover.classList.contains('is-hidden')) return;
|
||||
if (receiptPopover.contains(ev.target)) return;
|
||||
if (activeReceiptBtn && activeReceiptBtn.contains(ev.target)) return;
|
||||
hideReceiptPopover();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) { if (ev.key === 'Escape') hideReceiptPopover(); });
|
||||
|
||||
const applyMinuteGrouping = function () {
|
||||
const rows = Array.from(thread.querySelectorAll(".compose-row"));
|
||||
rows.forEach(function (row) {
|
||||
@@ -1889,15 +2033,18 @@
|
||||
}
|
||||
params.set("limit", thread.dataset.limit || "60");
|
||||
params.set("after_ts", String(lastTs));
|
||||
console.log("[poll] fetching messages: service=" + params.get("service") + " after_ts=" + lastTs);
|
||||
const response = await fetch(thread.dataset.pollUrl + "?" + params.toString(), {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" }
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.log("[poll] response not ok:", response.status);
|
||||
return;
|
||||
}
|
||||
const payload = await response.json();
|
||||
console.log("[poll] received payload with " + (payload.messages ? payload.messages.length : 0) + " messages");
|
||||
appendMessages(payload.messages || [], forceScroll);
|
||||
if (payload.typing) {
|
||||
applyTyping(payload.typing);
|
||||
@@ -2522,7 +2669,7 @@
|
||||
} catch (err) {
|
||||
setCardLoading(card, false);
|
||||
card.querySelector(".compose-ai-content").textContent =
|
||||
"Failed to load quick insights.";
|
||||
"Failed to load quick insights.";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2541,8 +2688,8 @@
|
||||
const customText = card.querySelector(".engage-custom-text");
|
||||
const selectedSource = (
|
||||
preferredSource !== undefined
|
||||
? preferredSource
|
||||
: (sourceSelect ? sourceSelect.value : "")
|
||||
? preferredSource
|
||||
: (sourceSelect ? sourceSelect.value : "")
|
||||
);
|
||||
const customValue = customText ? String(customText.value || "").trim() : "";
|
||||
const showCustom = selectedSource === "custom";
|
||||
@@ -2734,8 +2881,8 @@
|
||||
const selectedPerson = selected.dataset.person || thread.dataset.person || "";
|
||||
const selectedPageUrl = (
|
||||
renderMode === "page"
|
||||
? selected.dataset.pageUrl
|
||||
: selected.dataset.widgetUrl
|
||||
? selected.dataset.pageUrl
|
||||
: selected.dataset.widgetUrl
|
||||
) || "";
|
||||
switchThreadContext(
|
||||
selectedService,
|
||||
@@ -2764,8 +2911,8 @@
|
||||
const selectedPerson = selected.dataset.person || "";
|
||||
let selectedPageUrl = (
|
||||
renderMode === "page"
|
||||
? selected.dataset[servicePageUrlKey]
|
||||
: selected.dataset[serviceWidgetUrlKey]
|
||||
? selected.dataset[servicePageUrlKey]
|
||||
: selected.dataset[serviceWidgetUrlKey]
|
||||
) || "";
|
||||
if (!selectedIdentifier) {
|
||||
selectedService = selected.dataset.service || selectedService;
|
||||
@@ -2774,8 +2921,8 @@
|
||||
if (!selectedPageUrl) {
|
||||
selectedPageUrl = (
|
||||
renderMode === "page"
|
||||
? selected.dataset.pageUrl
|
||||
: selected.dataset.widgetUrl
|
||||
? selected.dataset.pageUrl
|
||||
: selected.dataset.widgetUrl
|
||||
) || "";
|
||||
}
|
||||
switchThreadContext(
|
||||
@@ -2877,6 +3024,51 @@
|
||||
textarea.focus();
|
||||
});
|
||||
|
||||
// Cancel send support: show a cancel button while the form request is pending.
|
||||
let cancelBtn = null;
|
||||
const showCancelButton = function () {
|
||||
if (cancelBtn) return;
|
||||
cancelBtn = document.createElement('button');
|
||||
cancelBtn.type = 'button';
|
||||
cancelBtn.className = 'button is-danger is-light is-small compose-cancel-send-btn';
|
||||
cancelBtn.textContent = 'Cancel Send';
|
||||
cancelBtn.addEventListener('click', function () {
|
||||
// Post cancel by service+identifier
|
||||
const payload = new URLSearchParams();
|
||||
payload.set('service', thread.dataset.service || '');
|
||||
payload.set('identifier', thread.dataset.identifier || '');
|
||||
fetch('{% url "compose_cancel_send" %}', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-CSRFToken': '{{ csrf_token }}', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: payload.toString(),
|
||||
}).then(function (resp) {
|
||||
// Hide cancel once requested
|
||||
hideCancelButton();
|
||||
}).catch(function () {
|
||||
hideCancelButton();
|
||||
});
|
||||
});
|
||||
if (statusBox) {
|
||||
statusBox.appendChild(cancelBtn);
|
||||
}
|
||||
};
|
||||
const hideCancelButton = function () {
|
||||
if (!cancelBtn) return;
|
||||
try { cancelBtn.remove(); } catch (e) {}
|
||||
cancelBtn = null;
|
||||
};
|
||||
|
||||
// Show cancel on submit; htmx will make the request asynchronously.
|
||||
form.addEventListener('submit', function (ev) {
|
||||
// Only show when send confirmation allows
|
||||
if (sendButton && sendButton.disabled) return;
|
||||
showCancelButton();
|
||||
});
|
||||
|
||||
// Hide cancel after HTMX request completes
|
||||
form.addEventListener('htmx:afterRequest', function () { hideCancelButton(); });
|
||||
|
||||
panelState.eventHandler = function (event) {
|
||||
const detail = (event && event.detail) || {};
|
||||
const sourcePanelId = String(detail.panel_id || "");
|
||||
@@ -2887,6 +3079,114 @@
|
||||
};
|
||||
document.body.addEventListener("composeMessageSent", panelState.eventHandler);
|
||||
|
||||
// Persistent queued-command handling: when server returns composeSendCommandId
|
||||
// HTMX will dispatch a `composeSendCommandId` event with detail {command_id: "..."}.
|
||||
panelState.pendingCommandId = null;
|
||||
panelState.pendingCommandPoll = null;
|
||||
|
||||
const startPendingCommandPolling = function (commandId) {
|
||||
if (!commandId) return;
|
||||
panelState.pendingCommandId = commandId;
|
||||
// Show persistent cancel UI
|
||||
showPersistentCancelButton(commandId);
|
||||
// Poll for result every 1500ms
|
||||
if (panelState.pendingCommandPoll) {
|
||||
clearInterval(panelState.pendingCommandPoll);
|
||||
}
|
||||
panelState.pendingCommandPoll = setInterval(async function () {
|
||||
try {
|
||||
const url = new URL('{% url "compose_command_result" %}', window.location.origin);
|
||||
url.searchParams.set('service', thread.dataset.service || '');
|
||||
url.searchParams.set('command_id', commandId);
|
||||
const resp = await fetch(url.toString(), { credentials: 'same-origin' });
|
||||
if (!resp.ok) return;
|
||||
const payload = await resp.json();
|
||||
if (payload && payload.pending === false) {
|
||||
// Stop polling
|
||||
stopPendingCommandPolling();
|
||||
// Hide cancel UI
|
||||
hidePersistentCancelButton();
|
||||
// Surface result to the user
|
||||
const result = payload.result || {};
|
||||
if (result.ok) {
|
||||
setStatus('', 'success');
|
||||
textarea.value = '';
|
||||
autosize();
|
||||
flashCompose('is-send-success');
|
||||
poll(true);
|
||||
} else {
|
||||
const msg = String(result.error || 'Send failed.');
|
||||
setStatus(msg, 'danger');
|
||||
flashCompose('is-send-fail');
|
||||
poll(true);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore transient network errors
|
||||
}
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const stopPendingCommandPolling = function () {
|
||||
if (panelState.pendingCommandPoll) {
|
||||
clearInterval(panelState.pendingCommandPoll);
|
||||
panelState.pendingCommandPoll = null;
|
||||
}
|
||||
panelState.pendingCommandId = null;
|
||||
};
|
||||
|
||||
const persistentCancelContainerId = panelId + '-persistent-cancel';
|
||||
const showPersistentCancelButton = function (commandId) {
|
||||
hidePersistentCancelButton();
|
||||
const container = document.createElement('div');
|
||||
container.id = persistentCancelContainerId;
|
||||
container.style.marginTop = '0.35rem';
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'button is-danger is-light is-small compose-persistent-cancel-btn';
|
||||
btn.textContent = 'Cancel Queued Send';
|
||||
btn.addEventListener('click', function () {
|
||||
const payload = new URLSearchParams();
|
||||
payload.set('service', thread.dataset.service || '');
|
||||
payload.set('identifier', thread.dataset.identifier || '');
|
||||
payload.set('command_id', String(commandId || ''));
|
||||
fetch('{% url "compose_cancel_send" %}', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-CSRFToken': '{{ csrf_token }}', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: payload.toString(),
|
||||
}).then(function (resp) {
|
||||
stopPendingCommandPolling();
|
||||
hidePersistentCancelButton();
|
||||
setStatus('Send cancelled.', 'warning');
|
||||
poll(true);
|
||||
}).catch(function () {
|
||||
hidePersistentCancelButton();
|
||||
});
|
||||
});
|
||||
container.appendChild(btn);
|
||||
if (statusBox) {
|
||||
statusBox.appendChild(container);
|
||||
}
|
||||
};
|
||||
|
||||
const hidePersistentCancelButton = function () {
|
||||
try {
|
||||
const el = document.getElementById(persistentCancelContainerId);
|
||||
if (el) el.remove();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
document.body.addEventListener('composeSendCommandId', function (ev) {
|
||||
try {
|
||||
const detail = (ev && ev.detail) || {};
|
||||
const cmd = (detail && detail.command_id) || (detail && detail.composeSendCommandId && detail.composeSendCommandId.command_id) || null;
|
||||
if (cmd) {
|
||||
startPendingCommandPolling(String(cmd));
|
||||
}
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
panelState.sendResultHandler = function (event) {
|
||||
const detail = (event && event.detail) || {};
|
||||
const sourcePanelId = String(detail.panel_id || "");
|
||||
|
||||
Reference in New Issue
Block a user