Refactor to reduce lines
This commit is contained in:
@@ -27,74 +27,63 @@
|
||||
<script src="{% static 'js/gridstack-all.js' %}"></script>
|
||||
<script defer src="{% static 'js/magnet.min.js' %}"></script>
|
||||
<script>
|
||||
document.addEventListener("restore-scroll", function(event) {
|
||||
var scrollpos = localStorage.getItem('scrollpos');
|
||||
document.addEventListener("restore-scroll", function () {
|
||||
var scrollpos = localStorage.getItem("scrollpos");
|
||||
if (scrollpos) {
|
||||
window.scrollTo(0, scrollpos)
|
||||
};
|
||||
window.scrollTo(0, scrollpos);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("htmx:beforeSwap", function(event) {
|
||||
localStorage.setItem('scrollpos', window.scrollY);
|
||||
|
||||
document.addEventListener("htmx:beforeSwap", function () {
|
||||
localStorage.setItem("scrollpos", window.scrollY);
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Get all "navbar-burger" elements
|
||||
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
|
||||
// Add a click event on each of them
|
||||
$navbarBurgers.forEach( el => {
|
||||
el.addEventListener('click', () => {
|
||||
|
||||
// Get the target from the "data-target" attribute
|
||||
const target = el.dataset.target;
|
||||
const $target = document.getElementById(target);
|
||||
|
||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||
el.classList.toggle('is-active');
|
||||
$target.classList.toggle('is-active');
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.querySelectorAll(".navbar-burger").forEach(function (el) {
|
||||
el.addEventListener("click", function () {
|
||||
var target = document.getElementById(el.dataset.target);
|
||||
el.classList.toggle("is-active");
|
||||
if (target) {
|
||||
target.classList.toggle("is-active");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const composeLink = document.getElementById('nav-compose-link');
|
||||
const composeDropdown = document.getElementById('nav-compose-contacts');
|
||||
let composePreviewLoaded = false;
|
||||
let composePreviewLoading = false;
|
||||
if (composeLink && composeDropdown) {
|
||||
composeLink.addEventListener('mouseenter', () => {
|
||||
const previewUrl = composeLink.dataset.previewUrl || '';
|
||||
var composeLink = document.getElementById("nav-compose-link");
|
||||
var composeDropdown = document.getElementById("nav-compose-contacts");
|
||||
var composePreviewLoaded = false;
|
||||
var composePreviewLoading = false;
|
||||
if (!composeLink || !composeDropdown) {
|
||||
return;
|
||||
}
|
||||
composeLink.addEventListener("mouseenter", function () {
|
||||
var previewUrl = composeLink.dataset.previewUrl || "";
|
||||
if (!previewUrl || composePreviewLoaded || composePreviewLoading) {
|
||||
return;
|
||||
}
|
||||
composePreviewLoading = true;
|
||||
fetch(previewUrl, {
|
||||
method: 'GET',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'HX-Request': 'true' },
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: { "HX-Request": "true" },
|
||||
})
|
||||
.then((response) => {
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed contacts preview fetch.');
|
||||
throw new Error("Failed contacts preview fetch.");
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then((html) => {
|
||||
.then(function (html) {
|
||||
composeDropdown.innerHTML = html;
|
||||
composePreviewLoaded = true;
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(function () {
|
||||
composePreviewLoaded = false;
|
||||
})
|
||||
.finally(() => {
|
||||
.finally(function () {
|
||||
composePreviewLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
@@ -112,9 +101,7 @@
|
||||
opacity:0;
|
||||
transition: opacity 500ms ease-in;
|
||||
}
|
||||
.htmx-request .htmx-indicator{
|
||||
opacity:1
|
||||
}
|
||||
.htmx-request .htmx-indicator,
|
||||
.htmx-request.htmx-indicator{
|
||||
opacity:1
|
||||
}
|
||||
@@ -142,15 +129,11 @@
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
cursor:pointer;
|
||||
background-color:rgba(221, 224, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
a.panel-block {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
tr:hover,
|
||||
a.panel-block:hover {
|
||||
cursor:pointer;
|
||||
background-color:rgba(221, 224, 255, 0.3) !important;
|
||||
@@ -180,11 +163,9 @@
|
||||
}
|
||||
|
||||
.panel, .box, .modal {
|
||||
/* background-color:rgba(250, 250, 250, 0.5) !important; */
|
||||
background-color: var(--modal-color) !important;
|
||||
}
|
||||
.modal, .modal.box{
|
||||
/* background-color:rgba(210, 210, 210, 0.9) !important; */
|
||||
background-color: var(--background-color) !important;
|
||||
}
|
||||
.modal-background{
|
||||
@@ -218,7 +199,8 @@
|
||||
background-color:rgba(0, 0, 0, 0.03) !important;
|
||||
}
|
||||
|
||||
.grid-stack-item-content {
|
||||
.grid-stack-item-content,
|
||||
.floating-window {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
overflow-x: hidden !important;
|
||||
@@ -239,11 +221,6 @@
|
||||
}
|
||||
|
||||
.floating-window {
|
||||
/* background-color:rgba(210, 210, 210, 0.6) !important; */
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: hidden !important;
|
||||
max-height: 300px;
|
||||
z-index: 9000;
|
||||
position: absolute;
|
||||
@@ -257,8 +234,7 @@
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
.grid-stack-item:hover .ui-resizable-handle {
|
||||
display: block !important;
|
||||
|
||||
@@ -819,135 +819,104 @@
|
||||
(function() {
|
||||
const personId = "{{ person.id }}";
|
||||
const canSend = "{{ send_state.can_send|yesno:'1,0' }}" === "1";
|
||||
function resizeEditableTextareas(root) {
|
||||
const TAB_NAMES = ["plan_board", "corrections", "engage", "fundamentals", "auto", "ask_ai"];
|
||||
const defineGlobal = function(name, handler) {
|
||||
if (typeof window[name] !== "function") {
|
||||
window[name] = handler;
|
||||
}
|
||||
};
|
||||
const resizeEditableTextareas = function(root) {
|
||||
if (!root) return;
|
||||
root.querySelectorAll('textarea[data-editable="1"]').forEach(function(area) {
|
||||
area.style.height = "auto";
|
||||
area.style.height = Math.max(area.scrollHeight, 72) + "px";
|
||||
});
|
||||
}
|
||||
|
||||
window.giaEngageSyncSendOverride = function(pid) {
|
||||
if (pid !== personId) return;
|
||||
const forceInput = document.getElementById("engage-force-send-" + pid);
|
||||
const sendBtn = document.getElementById("engage-send-btn-" + pid);
|
||||
const force =
|
||||
!!(window.giaWorkspaceState
|
||||
&& window.giaWorkspaceState[pid]
|
||||
&& window.giaWorkspaceState[pid].forceSend);
|
||||
if (forceInput) {
|
||||
forceInput.value = force ? "1" : "0";
|
||||
}
|
||||
if (sendBtn) {
|
||||
sendBtn.disabled = !canSend && !force;
|
||||
}
|
||||
};
|
||||
|
||||
function setActiveTabHiddenFields(tabName) {
|
||||
const syncActiveTab = function(tabName) {
|
||||
const root = document.getElementById("mitigation-shell-" + personId);
|
||||
if (!root) return;
|
||||
root.querySelectorAll('input[name="active_tab"]').forEach(function(input) {
|
||||
input.value = tabName;
|
||||
});
|
||||
resizeEditableTextareas(root);
|
||||
};
|
||||
|
||||
window.giaEngageSyncSendOverride = function(pid) {
|
||||
if (pid !== personId) return;
|
||||
const forceInput = document.getElementById("engage-force-send-" + pid);
|
||||
const sendBtn = document.getElementById("engage-send-btn-" + pid);
|
||||
const force = !!(window.giaWorkspaceState && window.giaWorkspaceState[pid] && window.giaWorkspaceState[pid].forceSend);
|
||||
if (forceInput) forceInput.value = force ? "1" : "0";
|
||||
if (sendBtn) {
|
||||
sendBtn.disabled = !canSend && !force;
|
||||
}
|
||||
};
|
||||
|
||||
window.giaMitigationShowTab = function(pid, tabName) {
|
||||
if (pid !== personId) return;
|
||||
["plan_board", "corrections", "engage", "fundamentals", "auto", "ask_ai"].forEach(function(name) {
|
||||
TAB_NAMES.forEach(function(name) {
|
||||
const pane = document.getElementById("mitigation-tab-" + personId + "-" + name);
|
||||
const tab = document.getElementById("mitigation-tab-btn-" + personId + "-" + name);
|
||||
if (!pane) return;
|
||||
const active = name === tabName;
|
||||
pane.style.display = active ? "block" : "none";
|
||||
if (tab) {
|
||||
tab.classList.toggle("is-active", active);
|
||||
}
|
||||
if (tab) tab.classList.toggle("is-active", active);
|
||||
});
|
||||
setActiveTabHiddenFields(tabName);
|
||||
syncActiveTab(tabName);
|
||||
};
|
||||
|
||||
window.giaMitigationToggleEdit = function(button) {
|
||||
const form = button.closest("form");
|
||||
defineGlobal("giaMitigationToggleEdit", function(button) {
|
||||
const form = button && button.closest ? button.closest("form") : null;
|
||||
if (!form) return;
|
||||
const card = form.closest(".mitigation-artifact-card");
|
||||
const editing = button.dataset.editState === "edit";
|
||||
const fields = form.querySelectorAll('[data-editable="1"]');
|
||||
const toggles = form.querySelectorAll('[data-editable-toggle="1"]');
|
||||
if (!editing) {
|
||||
fields.forEach(function(field) {
|
||||
field.removeAttribute("readonly");
|
||||
});
|
||||
toggles.forEach(function(field) {
|
||||
field.removeAttribute("disabled");
|
||||
});
|
||||
if (card) {
|
||||
card.classList.add("is-editing");
|
||||
}
|
||||
form.querySelectorAll('[data-editable="1"]').forEach(function(field) { field.removeAttribute("readonly"); });
|
||||
form.querySelectorAll('[data-editable-toggle="1"]').forEach(function(field) { field.removeAttribute("disabled"); });
|
||||
const card = form.closest(".mitigation-artifact-card");
|
||||
if (card) card.classList.add("is-editing");
|
||||
button.dataset.editState = "edit";
|
||||
button.classList.remove("is-light");
|
||||
button.title = "Save";
|
||||
button.innerHTML = '<span class="icon is-small"><i class="fa-solid fa-check"></i></span>';
|
||||
resizeEditableTextareas(form);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
form.requestSubmit();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
window.giaEngageSetAction = function(pid, action) {
|
||||
if (pid !== personId) return;
|
||||
defineGlobal("giaEngageSetAction", function(pid, action) {
|
||||
const actionInput = document.getElementById("engage-action-input-" + pid);
|
||||
if (actionInput) {
|
||||
actionInput.value = action;
|
||||
}
|
||||
if (action === "send") {
|
||||
window.giaEngageSyncSendOverride(pid);
|
||||
}
|
||||
};
|
||||
if (actionInput) actionInput.value = action;
|
||||
if (action === "send") window.giaEngageSyncSendOverride(pid);
|
||||
});
|
||||
|
||||
window.giaEngageAutoPreview = function(pid) {
|
||||
if (pid !== personId) return;
|
||||
defineGlobal("giaEngageAutoPreview", function(pid) {
|
||||
const form = document.getElementById("engage-form-" + pid);
|
||||
if (!form) return;
|
||||
window.giaEngageSetAction(pid, "preview");
|
||||
form.requestSubmit();
|
||||
};
|
||||
});
|
||||
|
||||
window.giaEngageSetTarget = function(pid, targetId) {
|
||||
if (pid !== personId) return;
|
||||
const normalized = String(targetId || "").trim();
|
||||
const input = document.getElementById("engage-target-input-" + pid);
|
||||
if (input) {
|
||||
input.value = normalized;
|
||||
}
|
||||
if (input) input.value = normalized;
|
||||
const parentSelect = document.getElementById("ai-target-select-" + pid);
|
||||
if (parentSelect && normalized) {
|
||||
parentSelect.value = normalized;
|
||||
}
|
||||
if (parentSelect && normalized) parentSelect.value = normalized;
|
||||
};
|
||||
|
||||
window.giaEngageSelect = function(pid, kind, value, node) {
|
||||
if (pid !== personId) return;
|
||||
let inputId = "";
|
||||
if (kind === "share") {
|
||||
inputId = "engage-share-input-" + pid;
|
||||
} else if (kind === "framing") {
|
||||
inputId = "engage-framing-input-" + pid;
|
||||
}
|
||||
defineGlobal("giaEngageSelect", function(pid, kind, value, node) {
|
||||
const inputId = kind === "share" ? ("engage-share-input-" + pid) : (kind === "framing" ? ("engage-framing-input-" + pid) : "");
|
||||
const input = inputId ? document.getElementById(inputId) : null;
|
||||
if (input) {
|
||||
input.value = value;
|
||||
}
|
||||
if (input) input.value = value;
|
||||
const li = node && node.closest ? node.closest("li") : null;
|
||||
if (!li) return;
|
||||
const ul = li.parentElement;
|
||||
if (!ul) return;
|
||||
Array.from(ul.children).forEach(function(child) {
|
||||
child.classList.remove("is-active");
|
||||
});
|
||||
if (!li || !li.parentElement) return;
|
||||
Array.from(li.parentElement.children).forEach(function(child) { child.classList.remove("is-active"); });
|
||||
li.classList.add("is-active");
|
||||
window.giaEngageAutoPreview(pid);
|
||||
};
|
||||
});
|
||||
|
||||
window.giaMitigationShowTab(personId, "{{ active_tab|default:'plan_board' }}");
|
||||
resizeEditableTextareas(document.getElementById("mitigation-shell-" + personId));
|
||||
|
||||
@@ -833,8 +833,14 @@
|
||||
syncTargetInputs();
|
||||
});
|
||||
|
||||
if (typeof window.giaMitigationShowTab !== "function") {
|
||||
window.giaMitigationShowTab = function(pid, tabName) {
|
||||
const defineGlobal = function(name, handler) {
|
||||
if (typeof window[name] === "function") {
|
||||
return;
|
||||
}
|
||||
window[name] = handler;
|
||||
};
|
||||
|
||||
defineGlobal("giaMitigationShowTab", function(pid, tabName) {
|
||||
const names = ["plan_board", "corrections", "engage", "fundamentals", "ask_ai"];
|
||||
names.forEach(function(name) {
|
||||
const pane = document.getElementById("mitigation-tab-" + pid + "-" + name);
|
||||
@@ -855,11 +861,9 @@
|
||||
shell.querySelectorAll('input[name="active_tab"]').forEach(function(input) {
|
||||
input.value = tabName;
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof window.giaMitigationToggleEdit !== "function") {
|
||||
window.giaMitigationToggleEdit = function(button) {
|
||||
defineGlobal("giaMitigationToggleEdit", function(button) {
|
||||
const form = button ? button.closest("form") : null;
|
||||
if (!form) {
|
||||
return;
|
||||
@@ -885,31 +889,25 @@
|
||||
} else {
|
||||
form.requestSubmit();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof window.giaEngageSetAction !== "function") {
|
||||
window.giaEngageSetAction = function(pid, action) {
|
||||
defineGlobal("giaEngageSetAction", function(pid, action) {
|
||||
const actionInput = document.getElementById("engage-action-input-" + pid);
|
||||
if (actionInput) {
|
||||
actionInput.value = action;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof window.giaEngageAutoPreview !== "function") {
|
||||
window.giaEngageAutoPreview = function(pid) {
|
||||
defineGlobal("giaEngageAutoPreview", function(pid) {
|
||||
const form = document.getElementById("engage-form-" + pid);
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
window.giaEngageSetAction(pid, "preview");
|
||||
form.requestSubmit();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof window.giaEngageSelect !== "function") {
|
||||
window.giaEngageSelect = function(pid, kind, value, node) {
|
||||
defineGlobal("giaEngageSelect", function(pid, kind, value, node) {
|
||||
let inputId = "";
|
||||
if (kind === "share") {
|
||||
inputId = "engage-share-input-" + pid;
|
||||
@@ -928,8 +926,7 @@
|
||||
li.classList.add("is-active");
|
||||
}
|
||||
window.giaEngageAutoPreview(pid);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
window.giaWorkspaceOpenTab(personId, "plan_board", false);
|
||||
syncTargetInputs();
|
||||
|
||||
@@ -1650,24 +1650,7 @@
|
||||
button.classList.add("is-loading");
|
||||
setStatus("Requesting history sync…", "info");
|
||||
try {
|
||||
const payload = new URLSearchParams();
|
||||
payload.set("service", thread.dataset.service || "");
|
||||
payload.set("identifier", thread.dataset.identifier || "");
|
||||
if (thread.dataset.person) {
|
||||
payload.set("person", thread.dataset.person);
|
||||
}
|
||||
payload.set("limit", thread.dataset.limit || "60");
|
||||
const response = await fetch(historySyncUrl, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"X-CSRFToken": csrfToken,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json"
|
||||
},
|
||||
body: payload.toString(),
|
||||
});
|
||||
const result = await response.json();
|
||||
const result = await postFormJson(historySyncUrl, queryParams());
|
||||
if (!result.ok) {
|
||||
setStatus(
|
||||
String(result.message || result.error || "History sync failed."),
|
||||
@@ -2352,14 +2335,7 @@
|
||||
}
|
||||
panelState.polling = true;
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
params.set("service", thread.dataset.service || "");
|
||||
params.set("identifier", thread.dataset.identifier || "");
|
||||
if (thread.dataset.person) {
|
||||
params.set("person", thread.dataset.person);
|
||||
}
|
||||
params.set("limit", thread.dataset.limit || "60");
|
||||
params.set("after_ts", String(lastTs));
|
||||
const params = queryParams({ after_ts: String(lastTs) });
|
||||
const response = await fetch(thread.dataset.pollUrl + "?" + params.toString(), {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
@@ -2532,7 +2508,7 @@
|
||||
return active;
|
||||
};
|
||||
|
||||
const queryParams = function () {
|
||||
const queryParams = function (extraParams) {
|
||||
const params = new URLSearchParams();
|
||||
params.set("service", thread.dataset.service || "");
|
||||
params.set("identifier", thread.dataset.identifier || "");
|
||||
@@ -2540,9 +2516,47 @@
|
||||
params.set("person", thread.dataset.person);
|
||||
}
|
||||
params.set("limit", thread.dataset.limit || "60");
|
||||
const extras =
|
||||
extraParams && typeof extraParams === "object" ? extraParams : {};
|
||||
Object.keys(extras).forEach(function (key) {
|
||||
const value = extras[key];
|
||||
if (value === undefined || value === null || value === "") {
|
||||
return;
|
||||
}
|
||||
params.set(String(key), String(value));
|
||||
});
|
||||
return params;
|
||||
};
|
||||
|
||||
const postFormJson = async function (url, params) {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"X-CSRFToken": csrfToken,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json"
|
||||
},
|
||||
body: params.toString(),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Request failed");
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const getJson = async function (url) {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" }
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Request failed");
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const titleCase = function (value) {
|
||||
const raw = String(value || "").trim().toLowerCase();
|
||||
if (!raw) {
|
||||
@@ -2625,6 +2639,17 @@
|
||||
}
|
||||
};
|
||||
|
||||
const cardContentNode = function (card) {
|
||||
return card ? card.querySelector(".compose-ai-content") : null;
|
||||
};
|
||||
|
||||
const setCardMessage = function (card, message) {
|
||||
const node = cardContentNode(card);
|
||||
if (node) {
|
||||
node.textContent = String(message || "");
|
||||
}
|
||||
};
|
||||
|
||||
const openEngage = function (sourceRef) {
|
||||
const engageCard = showCard("engage");
|
||||
if (!engageCard) {
|
||||
@@ -2644,19 +2669,19 @@
|
||||
}
|
||||
setCardLoading(card, true);
|
||||
try {
|
||||
const response = await fetch(thread.dataset.draftsUrl + "?" + queryParams().toString(), {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" }
|
||||
});
|
||||
const payload = await response.json();
|
||||
const payload = await getJson(
|
||||
thread.dataset.draftsUrl + "?" + queryParams().toString()
|
||||
);
|
||||
setCardLoading(card, false);
|
||||
if (!payload.ok) {
|
||||
card.querySelector(".compose-ai-content").textContent = payload.error || "Failed to load drafts.";
|
||||
setCardMessage(card, payload.error || "Failed to load drafts.");
|
||||
return;
|
||||
}
|
||||
const drafts = Array.isArray(payload.drafts) ? payload.drafts : [];
|
||||
const container = card.querySelector(".compose-ai-content");
|
||||
const container = cardContentNode(card);
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
container.innerHTML = "";
|
||||
const engageButton = document.createElement("button");
|
||||
engageButton.type = "button";
|
||||
@@ -2695,7 +2720,7 @@
|
||||
});
|
||||
} catch (err) {
|
||||
setCardLoading(card, false);
|
||||
card.querySelector(".compose-ai-content").textContent = "Failed to load drafts.";
|
||||
setCardMessage(card, "Failed to load drafts.");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2706,21 +2731,18 @@
|
||||
}
|
||||
setCardLoading(card, true);
|
||||
try {
|
||||
const response = await fetch(thread.dataset.summaryUrl + "?" + queryParams().toString(), {
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" }
|
||||
});
|
||||
const payload = await response.json();
|
||||
const payload = await getJson(
|
||||
thread.dataset.summaryUrl + "?" + queryParams().toString()
|
||||
);
|
||||
setCardLoading(card, false);
|
||||
if (!payload.ok) {
|
||||
card.querySelector(".compose-ai-content").textContent = payload.error || "Failed to load summary.";
|
||||
setCardMessage(card, payload.error || "Failed to load summary.");
|
||||
return;
|
||||
}
|
||||
card.querySelector(".compose-ai-content").textContent = String(payload.summary || "");
|
||||
setCardMessage(card, String(payload.summary || ""));
|
||||
} catch (err) {
|
||||
setCardLoading(card, false);
|
||||
card.querySelector(".compose-ai-content").textContent = "Failed to load summary.";
|
||||
setCardMessage(card, "Failed to load summary.");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2731,17 +2753,14 @@
|
||||
}
|
||||
setCardLoading(card, true);
|
||||
try {
|
||||
const response = await fetch(
|
||||
thread.dataset.quickInsightsUrl + "?" + queryParams().toString(),
|
||||
{
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" }
|
||||
}
|
||||
const payload = await getJson(
|
||||
thread.dataset.quickInsightsUrl + "?" + queryParams().toString()
|
||||
);
|
||||
const payload = await response.json();
|
||||
setCardLoading(card, false);
|
||||
const container = card.querySelector(".compose-ai-content");
|
||||
const container = cardContentNode(card);
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
if (!payload.ok) {
|
||||
container.textContent = payload.error || "Failed to load quick insights.";
|
||||
return;
|
||||
@@ -2994,8 +3013,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
setCardLoading(card, false);
|
||||
card.querySelector(".compose-ai-content").textContent =
|
||||
"Failed to load quick insights.";
|
||||
setCardMessage(card, "Failed to load quick insights.");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3035,18 +3053,12 @@
|
||||
if (showCustom && customValue) {
|
||||
params.set("custom_text", customValue);
|
||||
}
|
||||
const response = await fetch(
|
||||
thread.dataset.engagePreviewUrl + "?" + params.toString(),
|
||||
{
|
||||
method: "GET",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" }
|
||||
}
|
||||
const payload = await getJson(
|
||||
thread.dataset.engagePreviewUrl + "?" + params.toString()
|
||||
);
|
||||
const payload = await response.json();
|
||||
setCardLoading(card, false);
|
||||
if (!payload.ok) {
|
||||
card.querySelector(".compose-ai-content").textContent = payload.error || "Failed to load engage preview.";
|
||||
setCardMessage(card, payload.error || "Failed to load engage preview.");
|
||||
panelState.engageToken = "";
|
||||
return;
|
||||
}
|
||||
@@ -3081,11 +3093,11 @@
|
||||
if (payload.artifact) {
|
||||
text = text + "\n\nSource: " + String(payload.artifact);
|
||||
}
|
||||
card.querySelector(".compose-ai-content").textContent = text;
|
||||
setCardMessage(card, text);
|
||||
sendBtn.disabled = !(confirm.checked && panelState.engageToken);
|
||||
} catch (err) {
|
||||
setCardLoading(card, false);
|
||||
card.querySelector(".compose-ai-content").textContent = "Failed to load engage preview.";
|
||||
setCardMessage(card, "Failed to load engage preview.");
|
||||
panelState.engageToken = "";
|
||||
} finally {
|
||||
if (refreshBtn) {
|
||||
@@ -3141,27 +3153,13 @@
|
||||
if (!panelState.engageToken) {
|
||||
return;
|
||||
}
|
||||
const formData = new URLSearchParams();
|
||||
formData.set("service", thread.dataset.service || "");
|
||||
formData.set("identifier", thread.dataset.identifier || "");
|
||||
if (thread.dataset.person) {
|
||||
formData.set("person", thread.dataset.person);
|
||||
}
|
||||
formData.set("engage_token", panelState.engageToken);
|
||||
formData.set("failsafe_arm", confirm.checked ? "1" : "0");
|
||||
formData.set("failsafe_confirm", confirm.checked ? "1" : "0");
|
||||
try {
|
||||
const response = await fetch(thread.dataset.engageSendUrl, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"X-CSRFToken": csrfToken,
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json"
|
||||
},
|
||||
body: formData.toString()
|
||||
const formData = queryParams({
|
||||
engage_token: panelState.engageToken,
|
||||
failsafe_arm: confirm.checked ? "1" : "0",
|
||||
failsafe_confirm: confirm.checked ? "1" : "0",
|
||||
});
|
||||
const payload = await response.json();
|
||||
try {
|
||||
const payload = await postFormJson(thread.dataset.engageSendUrl, formData);
|
||||
if (!payload.ok) {
|
||||
flashCompose("is-send-fail");
|
||||
setStatus(payload.error || "Engage send failed.", "danger");
|
||||
@@ -3352,28 +3350,26 @@
|
||||
|
||||
// Cancel send support: show a cancel button while the form request is pending.
|
||||
let cancelBtn = null;
|
||||
const cancelSendRequest = function (commandId) {
|
||||
return postFormJson(
|
||||
'{% url "compose_cancel_send" %}',
|
||||
queryParams({ command_id: String(commandId || "") })
|
||||
);
|
||||
};
|
||||
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
|
||||
cancelBtn.addEventListener('click', async function () {
|
||||
try {
|
||||
await cancelSendRequest("");
|
||||
} catch (e) {
|
||||
// Ignore cancel failures.
|
||||
} finally {
|
||||
hideCancelButton();
|
||||
}).catch(function () {
|
||||
hideCancelButton();
|
||||
});
|
||||
}
|
||||
});
|
||||
if (statusBox) {
|
||||
statusBox.appendChild(cancelBtn);
|
||||
@@ -3498,24 +3494,16 @@
|
||||
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) {
|
||||
btn.addEventListener('click', async function () {
|
||||
try {
|
||||
await cancelSendRequest(String(commandId || ''));
|
||||
stopPendingCommandPolling();
|
||||
hidePersistentCancelButton();
|
||||
setStatus('Send cancelled.', 'warning');
|
||||
poll(true);
|
||||
}).catch(function () {
|
||||
await poll(true);
|
||||
} catch (e) {
|
||||
hidePersistentCancelButton();
|
||||
});
|
||||
}
|
||||
});
|
||||
container.appendChild(btn);
|
||||
if (statusBox) {
|
||||
|
||||
@@ -108,6 +108,17 @@ def _safe_after_ts(raw) -> int:
|
||||
return max(0, value)
|
||||
|
||||
|
||||
def _request_scope(request, source: str = "GET"):
|
||||
data = request.GET if str(source).upper() == "GET" else request.POST
|
||||
service = _default_service(data.get("service"))
|
||||
identifier = str(data.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = data.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
return service, identifier, person
|
||||
|
||||
|
||||
def _format_ts_label(ts_value: int) -> str:
|
||||
try:
|
||||
as_dt = datetime.fromtimestamp(int(ts_value) / 1000, tz=dt_timezone.utc)
|
||||
@@ -2111,12 +2122,7 @@ class ComposeWorkspace(LoginRequiredMixin, View):
|
||||
template_name = "pages/compose-workspace.html"
|
||||
|
||||
def get(self, request):
|
||||
service = _default_service(request.GET.get("service"))
|
||||
identifier = str(request.GET.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.GET.get("person")
|
||||
if person_id:
|
||||
person = Person.objects.filter(id=person_id, user=request.user).first()
|
||||
service, identifier, person = _request_scope(request, "GET")
|
||||
limit = _safe_limit(request.GET.get("limit") or 40)
|
||||
|
||||
initial_widget_url = ""
|
||||
@@ -2307,12 +2313,7 @@ class ComposePage(LoginRequiredMixin, View):
|
||||
template_name = "pages/compose.html"
|
||||
|
||||
def get(self, request):
|
||||
service = _default_service(request.GET.get("service"))
|
||||
identifier = str(request.GET.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.GET.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "GET")
|
||||
if not identifier and person is None:
|
||||
return HttpResponseBadRequest("Missing contact identifier.")
|
||||
|
||||
@@ -2328,12 +2329,7 @@ class ComposePage(LoginRequiredMixin, View):
|
||||
|
||||
class ComposeWidget(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
service = _default_service(request.GET.get("service"))
|
||||
identifier = str(request.GET.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.GET.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "GET")
|
||||
if not identifier and person is None:
|
||||
return HttpResponseBadRequest("Missing contact identifier.")
|
||||
|
||||
@@ -2361,12 +2357,7 @@ class ComposeWidget(LoginRequiredMixin, View):
|
||||
|
||||
class ComposeThread(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
service = _default_service(request.GET.get("service"))
|
||||
identifier = str(request.GET.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.GET.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "GET")
|
||||
if not identifier and person is None:
|
||||
return HttpResponseBadRequest("Missing contact identifier.")
|
||||
|
||||
@@ -2565,12 +2556,7 @@ class ComposeHistorySync(LoginRequiredMixin, View):
|
||||
return len(duplicate_ids)
|
||||
|
||||
def post(self, request):
|
||||
service = _default_service(request.POST.get("service"))
|
||||
identifier = str(request.POST.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.POST.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "POST")
|
||||
if not identifier and person is None:
|
||||
return JsonResponse(
|
||||
{
|
||||
@@ -2737,8 +2723,7 @@ class ComposeHistorySync(LoginRequiredMixin, View):
|
||||
|
||||
class ComposeCancelSend(LoginRequiredMixin, View):
|
||||
def post(self, request):
|
||||
service = _default_service(request.POST.get("service"))
|
||||
identifier = str(request.POST.get("identifier") or "").strip()
|
||||
service, identifier, _ = _request_scope(request, "POST")
|
||||
command_id = str(request.POST.get("command_id") or "").strip()
|
||||
if not identifier:
|
||||
return JsonResponse({"ok": False, "error": "missing_identifier"})
|
||||
@@ -2842,12 +2827,7 @@ class ComposeMediaBlob(LoginRequiredMixin, View):
|
||||
|
||||
class ComposeDrafts(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
service = _default_service(request.GET.get("service"))
|
||||
identifier = str(request.GET.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.GET.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "GET")
|
||||
if not identifier and person is None:
|
||||
return JsonResponse({"ok": False, "error": "Missing contact identifier."})
|
||||
|
||||
@@ -2908,12 +2888,7 @@ class ComposeDrafts(LoginRequiredMixin, View):
|
||||
|
||||
class ComposeSummary(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
service = _default_service(request.GET.get("service"))
|
||||
identifier = str(request.GET.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.GET.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "GET")
|
||||
if not identifier and person is None:
|
||||
return JsonResponse({"ok": False, "error": "Missing contact identifier."})
|
||||
|
||||
@@ -2976,12 +2951,7 @@ class ComposeSummary(LoginRequiredMixin, View):
|
||||
|
||||
class ComposeQuickInsights(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
service = _default_service(request.GET.get("service"))
|
||||
identifier = str(request.GET.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.GET.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "GET")
|
||||
if not identifier and person is None:
|
||||
return JsonResponse({"ok": False, "error": "Missing contact identifier."})
|
||||
|
||||
@@ -3097,12 +3067,7 @@ class ComposeQuickInsights(LoginRequiredMixin, View):
|
||||
|
||||
class ComposeEngagePreview(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
service = _default_service(request.GET.get("service"))
|
||||
identifier = str(request.GET.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.GET.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "GET")
|
||||
if not identifier and person is None:
|
||||
return JsonResponse({"ok": False, "error": "Missing contact identifier."})
|
||||
|
||||
@@ -3228,12 +3193,7 @@ class ComposeEngagePreview(LoginRequiredMixin, View):
|
||||
|
||||
class ComposeEngageSend(LoginRequiredMixin, View):
|
||||
def post(self, request):
|
||||
service = _default_service(request.POST.get("service"))
|
||||
identifier = str(request.POST.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.POST.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "POST")
|
||||
if not identifier and person is None:
|
||||
return JsonResponse({"ok": False, "error": "Missing contact identifier."})
|
||||
|
||||
@@ -3319,12 +3279,7 @@ class ComposeSend(LoginRequiredMixin, View):
|
||||
return response
|
||||
|
||||
def post(self, request):
|
||||
service = _default_service(request.POST.get("service"))
|
||||
identifier = str(request.POST.get("identifier") or "").strip()
|
||||
person = None
|
||||
person_id = request.POST.get("person")
|
||||
if person_id:
|
||||
person = get_object_or_404(Person, id=person_id, user=request.user)
|
||||
service, identifier, person = _request_scope(request, "POST")
|
||||
render_mode = str(request.POST.get("render_mode") or "page").strip().lower()
|
||||
if render_mode not in {"page", "widget"}:
|
||||
render_mode = "page"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user