Implement business plans
This commit is contained in:
@@ -72,6 +72,31 @@
|
||||
<span class="icon is-small"><i class="fa-solid fa-rotate-right"></i></span>
|
||||
<span>Force Sync</span>
|
||||
</button>
|
||||
<div class="compose-command-menu">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-light is-rounded compose-command-menu-toggle"
|
||||
title="Enable or disable command triggers for this chat">
|
||||
<span class="icon is-small"><i class="fa-solid fa-diagram-project"></i></span>
|
||||
<span>Commands</span>
|
||||
</button>
|
||||
<div class="compose-command-menu-panel is-hidden">
|
||||
{% for option in command_options %}
|
||||
<label class="compose-command-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="compose-command-toggle"
|
||||
data-command-slug="{{ option.slug }}"
|
||||
{% if option.enabled_here %}checked{% endif %}>
|
||||
<span class="compose-command-option-title">{{ option.name }}</span>
|
||||
{% if option.trigger_token %}
|
||||
<span class="compose-command-option-token">{{ option.trigger_token }}</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
<a class="compose-command-settings-link" href="{% url 'command_routing' %}">Open command routing</a>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="button is-light is-rounded js-ai-trigger" data-kind="drafts">
|
||||
<span class="icon is-small"><i class="fa-solid fa-pen"></i></span>
|
||||
<span>Drafts</span>
|
||||
@@ -241,10 +266,11 @@
|
||||
data-summary-url="{{ compose_summary_url }}"
|
||||
data-quick-insights-url="{{ compose_quick_insights_url }}"
|
||||
data-history-sync-url="{{ compose_history_sync_url }}"
|
||||
data-toggle-command-url="{{ compose_toggle_command_url }}"
|
||||
data-engage-preview-url="{{ compose_engage_preview_url }}"
|
||||
data-engage-send-url="{{ compose_engage_send_url }}">
|
||||
{% for msg in serialized_messages %}
|
||||
<div class="compose-row {% if msg.outgoing %}is-out{% else %}is-in{% endif %}" data-ts="{{ msg.ts }}" data-message-id="{{ msg.id }}">
|
||||
<div class="compose-row {% if msg.outgoing %}is-out{% else %}is-in{% endif %}" data-ts="{{ msg.ts }}" data-message-id="{{ msg.id }}"{% if msg.reply_to_id %} data-reply-to-id="{{ msg.reply_to_id }}"{% endif %} data-reply-snippet="{{ msg.display_text|default:msg.text|default:''|truncatechars:120|escape }}">
|
||||
{% if msg.gap_fragments %}
|
||||
{% with gap=msg.gap_fragments.0 %}
|
||||
<p
|
||||
@@ -256,6 +282,11 @@
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
<article class="compose-bubble {% if msg.outgoing %}is-out{% else %}is-in{% endif %}">
|
||||
{% if msg.reply_to_id %}
|
||||
<div class="compose-reply-ref" data-reply-target-id="{{ msg.reply_to_id }}" data-reply-preview="{{ msg.reply_preview|default:''|escape }}">
|
||||
<button type="button" class="compose-reply-link" title="Jump to referenced message"></button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="compose-source-badge-wrap">
|
||||
<span class="compose-source-badge source-{{ msg.source_service|default:'web'|lower }}">{{ msg.source_label }}</span>
|
||||
</div>
|
||||
@@ -336,6 +367,10 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<button type="button" class="compose-reply-btn" title="Reply to this message" aria-label="Reply to this message">
|
||||
<span class="icon is-small"><i class="fa-solid fa-reply"></i></span>
|
||||
<span class="compose-reply-btn-label">Reply</span>
|
||||
</button>
|
||||
</article>
|
||||
</div>
|
||||
{% empty %}
|
||||
@@ -365,6 +400,7 @@
|
||||
<input type="hidden" name="render_mode" value="{{ render_mode }}">
|
||||
<input type="hidden" name="limit" value="{{ limit }}">
|
||||
<input type="hidden" name="panel_id" value="{{ panel_id }}">
|
||||
<input type="hidden" name="reply_to_message_id" value="">
|
||||
<input type="hidden" name="failsafe_arm" value="0">
|
||||
<input type="hidden" name="failsafe_confirm" value="0">
|
||||
<div class="compose-send-safety">
|
||||
@@ -372,6 +408,11 @@
|
||||
<input type="checkbox" class="manual-confirm"> Confirm Send
|
||||
</label>
|
||||
</div>
|
||||
<div id="{{ panel_id }}-reply-banner" class="compose-reply-banner is-hidden">
|
||||
<span class="compose-reply-banner-label">Replying to:</span>
|
||||
<span id="{{ panel_id }}-reply-text" class="compose-reply-banner-text"></span>
|
||||
<button type="button" id="{{ panel_id }}-reply-clear" class="button is-white is-small compose-reply-clear-btn">Clear</button>
|
||||
</div>
|
||||
<div class="compose-composer-capsule">
|
||||
<textarea
|
||||
id="{{ panel_id }}-textarea"
|
||||
@@ -414,6 +455,13 @@
|
||||
gap: 0.3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-row.compose-reply-target {
|
||||
animation: composeReplyFlash 1.1s ease-out;
|
||||
}
|
||||
#{{ panel_id }} .compose-row.compose-reply-selected .compose-bubble {
|
||||
border-color: rgba(47, 79, 122, 0.6);
|
||||
box-shadow: 0 0 0 2px rgba(47, 79, 122, 0.12);
|
||||
}
|
||||
#{{ panel_id }} .compose-row.is-in {
|
||||
align-items: flex-start;
|
||||
}
|
||||
@@ -478,6 +526,56 @@
|
||||
padding: 0.52rem 0.62rem;
|
||||
box-shadow: none;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-ref {
|
||||
margin-bottom: 0.28rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-link {
|
||||
border: 0;
|
||||
background: rgba(31, 41, 55, 0.06);
|
||||
border-radius: 6px;
|
||||
padding: 0.12rem 0.42rem;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.2;
|
||||
color: #3b4b5e;
|
||||
cursor: pointer;
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-link:hover {
|
||||
background: rgba(31, 41, 55, 0.1);
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-btn {
|
||||
margin-top: 0.34rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #5f6f82;
|
||||
padding: 0.1rem 0.16rem;
|
||||
height: 1.4rem;
|
||||
min-height: 1.4rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.15rem;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-btn-label {
|
||||
font-size: 0.68rem;
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
#{{ panel_id }} .compose-row:hover .compose-reply-btn,
|
||||
#{{ panel_id }} .compose-row.compose-reply-selected .compose-reply-btn,
|
||||
#{{ panel_id }} .compose-reply-btn:focus-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-btn:hover {
|
||||
color: #2f4f7a;
|
||||
}
|
||||
#{{ panel_id }} .compose-source-badge-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
@@ -667,6 +765,47 @@
|
||||
#{{ panel_id }} .compose-platform-select {
|
||||
min-width: 11rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-command-menu {
|
||||
position: relative;
|
||||
}
|
||||
#{{ panel_id }} .compose-command-menu-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(100% + 0.3rem);
|
||||
min-width: 14.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.14);
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
|
||||
z-index: 9;
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-command-menu-panel.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
#{{ panel_id }} .compose-command-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
font-size: 0.76rem;
|
||||
color: #334155;
|
||||
}
|
||||
#{{ panel_id }} .compose-command-option-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
#{{ panel_id }} .compose-command-option-token {
|
||||
margin-left: auto;
|
||||
color: #64748b;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
#{{ panel_id }} .compose-command-settings-link {
|
||||
margin-top: 0.2rem;
|
||||
font-size: 0.72rem;
|
||||
color: #2563eb;
|
||||
}
|
||||
#{{ panel_id }} .compose-gap-artifacts {
|
||||
align-self: center;
|
||||
width: min(92%, 34rem);
|
||||
@@ -800,6 +939,38 @@
|
||||
margin-bottom: 0.45rem;
|
||||
color: #505050;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-banner {
|
||||
margin-top: 0.42rem;
|
||||
margin-bottom: 0.3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.3rem 0.45rem;
|
||||
border: 1px solid rgba(47, 79, 122, 0.24);
|
||||
border-radius: 7px;
|
||||
background: rgba(238, 246, 255, 0.7);
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-banner.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-banner-label {
|
||||
font-size: 0.72rem;
|
||||
color: #34506f;
|
||||
font-weight: 700;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-banner-text {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
font-size: 0.75rem;
|
||||
color: #213447;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#{{ panel_id }} .compose-reply-clear-btn {
|
||||
border: 1px solid rgba(47, 79, 122, 0.25);
|
||||
color: #2f4f7a;
|
||||
}
|
||||
#{{ panel_id }} .compose-status {
|
||||
margin-top: 0.55rem;
|
||||
min-height: 1.1rem;
|
||||
@@ -1252,6 +1423,10 @@
|
||||
50% { transform: translateX(2px); }
|
||||
75% { transform: translateX(-1px); }
|
||||
}
|
||||
@keyframes composeReplyFlash {
|
||||
0% { box-shadow: 0 0 0 0 rgba(47, 79, 122, 0.45); }
|
||||
100% { box-shadow: 0 0 0 14px rgba(47, 79, 122, 0); }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
#{{ panel_id }} .compose-thread {
|
||||
max-height: 52vh;
|
||||
@@ -1294,6 +1469,10 @@
|
||||
const hiddenService = document.getElementById(panelId + "-input-service");
|
||||
const hiddenIdentifier = document.getElementById(panelId + "-input-identifier");
|
||||
const hiddenPerson = document.getElementById(panelId + "-input-person");
|
||||
const hiddenReplyTo = form.querySelector('input[name="reply_to_message_id"]');
|
||||
const replyBanner = document.getElementById(panelId + "-reply-banner");
|
||||
const replyBannerText = document.getElementById(panelId + "-reply-text");
|
||||
const replyClearBtn = document.getElementById(panelId + "-reply-clear");
|
||||
const renderMode = "{{ render_mode }}";
|
||||
if (!thread || !form || !textarea) {
|
||||
return;
|
||||
@@ -1348,6 +1527,7 @@
|
||||
lightboxIndex: -1,
|
||||
seenMessageIds: new Set(),
|
||||
replyTimingTimer: null,
|
||||
replyTargetId: "",
|
||||
};
|
||||
window.giaComposePanels[panelId] = panelState;
|
||||
const triggerButtons = Array.from(panel.querySelectorAll(".js-ai-trigger"));
|
||||
@@ -1681,6 +1861,80 @@
|
||||
});
|
||||
});
|
||||
};
|
||||
const bindCommandMenu = function (rootNode) {
|
||||
const scope = rootNode || panel;
|
||||
if (!scope) {
|
||||
return;
|
||||
}
|
||||
scope.querySelectorAll(".compose-command-menu").forEach(function (menu) {
|
||||
if (menu.dataset.bound === "1") {
|
||||
return;
|
||||
}
|
||||
menu.dataset.bound = "1";
|
||||
const toggleButton = menu.querySelector(".compose-command-menu-toggle");
|
||||
const menuPanel = menu.querySelector(".compose-command-menu-panel");
|
||||
if (!toggleButton || !menuPanel) {
|
||||
return;
|
||||
}
|
||||
const closeMenu = function () {
|
||||
menuPanel.classList.add("is-hidden");
|
||||
};
|
||||
const openMenu = function () {
|
||||
menuPanel.classList.remove("is-hidden");
|
||||
};
|
||||
toggleButton.addEventListener("click", function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (menuPanel.classList.contains("is-hidden")) {
|
||||
openMenu();
|
||||
} else {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!menu.contains(ev.target)) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
menu.querySelectorAll(".compose-command-toggle").forEach(function (checkbox) {
|
||||
checkbox.addEventListener("change", async function () {
|
||||
const toggleUrl = String(thread.dataset.toggleCommandUrl || "").trim();
|
||||
const slug = String(checkbox.dataset.commandSlug || "").trim();
|
||||
if (!toggleUrl || !slug) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
setStatus("Command toggle endpoint is unavailable.", "warning");
|
||||
return;
|
||||
}
|
||||
const shouldEnable = !!checkbox.checked;
|
||||
checkbox.disabled = true;
|
||||
try {
|
||||
const params = queryParams({
|
||||
slug: slug,
|
||||
enabled: shouldEnable ? "1" : "0",
|
||||
});
|
||||
const result = await postFormJson(toggleUrl, params);
|
||||
if (!result.ok) {
|
||||
checkbox.checked = !shouldEnable;
|
||||
setStatus(
|
||||
String(result.message || result.error || "Command update failed."),
|
||||
String(result.level || "danger")
|
||||
);
|
||||
return;
|
||||
}
|
||||
setStatus(
|
||||
String(result.message || (slug + (shouldEnable ? " enabled." : " disabled."))),
|
||||
"success"
|
||||
);
|
||||
} catch (err) {
|
||||
checkbox.checked = !shouldEnable;
|
||||
setStatus("Failed to update command binding.", "danger");
|
||||
} finally {
|
||||
checkbox.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const ensureEmptyState = function (messageText) {
|
||||
if (!thread) {
|
||||
@@ -2060,6 +2314,12 @@
|
||||
row.className = "compose-row " + (outgoing ? "is-out" : "is-in");
|
||||
row.dataset.ts = String(msg.ts || 0);
|
||||
row.dataset.minute = minuteBucketFromTs(msg.ts || 0);
|
||||
row.dataset.replySnippet = normalizeSnippet(
|
||||
msg.display_text || msg.text || (msg.image_url ? "" : "(no text)")
|
||||
);
|
||||
if (msg.reply_to_id) {
|
||||
row.dataset.replyToId = String(msg.reply_to_id || "");
|
||||
}
|
||||
if (messageId) {
|
||||
row.dataset.messageId = messageId;
|
||||
panelState.seenMessageIds.add(messageId);
|
||||
@@ -2069,6 +2329,19 @@
|
||||
const bubble = document.createElement("article");
|
||||
bubble.className = "compose-bubble " + (outgoing ? "is-out" : "is-in");
|
||||
|
||||
if (msg.reply_to_id) {
|
||||
const replyRef = document.createElement("div");
|
||||
replyRef.className = "compose-reply-ref";
|
||||
replyRef.dataset.replyTargetId = String(msg.reply_to_id || "");
|
||||
replyRef.dataset.replyPreview = String(msg.reply_preview || "");
|
||||
const link = document.createElement("button");
|
||||
link.type = "button";
|
||||
link.className = "compose-reply-link";
|
||||
link.title = "Jump to referenced message";
|
||||
replyRef.appendChild(link);
|
||||
bubble.appendChild(replyRef);
|
||||
}
|
||||
|
||||
// Add source badge for client-side rendered messages
|
||||
if (msg.source_label) {
|
||||
const badgeWrap = document.createElement("div");
|
||||
@@ -2178,6 +2451,14 @@
|
||||
meta.appendChild(tickWrap);
|
||||
}
|
||||
bubble.appendChild(meta);
|
||||
const replyBtn = document.createElement("button");
|
||||
replyBtn.type = "button";
|
||||
replyBtn.className = "compose-reply-btn";
|
||||
replyBtn.title = "Reply to this message";
|
||||
replyBtn.setAttribute("aria-label", "Reply to this message");
|
||||
replyBtn.innerHTML =
|
||||
'<span class="icon is-small"><i class="fa-solid fa-reply"></i></span><span class="compose-reply-btn-label">Reply</span>';
|
||||
bubble.appendChild(replyBtn);
|
||||
|
||||
// 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) {
|
||||
@@ -2202,6 +2483,7 @@
|
||||
row.appendChild(bubble);
|
||||
insertRowByTs(row);
|
||||
wireImageFallbacks(row);
|
||||
bindReplyReferences(row);
|
||||
updateGlanceFromMessage(msg);
|
||||
};
|
||||
|
||||
@@ -2261,6 +2543,19 @@
|
||||
|
||||
// Delegate click on tick triggers inside thread
|
||||
thread.addEventListener("click", function (ev) {
|
||||
const replyBtn = ev.target.closest && ev.target.closest(".compose-reply-btn");
|
||||
if (replyBtn) {
|
||||
const row = replyBtn.closest(".compose-row");
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const targetId = String(row.dataset.messageId || "").trim();
|
||||
setReplyTarget(targetId, row.dataset.replySnippet || "");
|
||||
if (textarea) {
|
||||
textarea.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const btn = ev.target.closest && ev.target.closest('.js-receipt-trigger');
|
||||
if (!btn) return;
|
||||
if (activeReceiptBtn === btn && !receiptPopover.classList.contains('is-hidden')) {
|
||||
@@ -2456,6 +2751,7 @@
|
||||
}
|
||||
applyMinuteGrouping();
|
||||
bindHistorySyncButtons(panel);
|
||||
bindCommandMenu(panel);
|
||||
|
||||
const setStatus = function (message, level) {
|
||||
if (!statusBox) {
|
||||
@@ -2551,6 +2847,135 @@
|
||||
return params;
|
||||
};
|
||||
|
||||
const normalizeSnippet = function (value) {
|
||||
const compact = String(value || "").replace(/\s+/g, " ").trim();
|
||||
if (!compact) {
|
||||
return "(no text)";
|
||||
}
|
||||
if (compact.length <= 120) {
|
||||
return compact;
|
||||
}
|
||||
return compact.slice(0, 117).trimEnd() + "...";
|
||||
};
|
||||
|
||||
const rowByMessageId = function (messageId) {
|
||||
const key = String(messageId || "").trim();
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
return thread.querySelector('.compose-row[data-message-id="' + key + '"]');
|
||||
};
|
||||
|
||||
const flashReplyTarget = function (row) {
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
row.classList.remove("compose-reply-target");
|
||||
void row.offsetWidth;
|
||||
row.classList.add("compose-reply-target");
|
||||
window.setTimeout(function () {
|
||||
row.classList.remove("compose-reply-target");
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
const clearReplySelectionClass = function () {
|
||||
thread.querySelectorAll(".compose-row.compose-reply-selected").forEach(function (row) {
|
||||
row.classList.remove("compose-reply-selected");
|
||||
});
|
||||
};
|
||||
|
||||
const clearReplyTarget = function () {
|
||||
panelState.replyTargetId = "";
|
||||
if (hiddenReplyTo) {
|
||||
hiddenReplyTo.value = "";
|
||||
}
|
||||
if (replyBanner) {
|
||||
replyBanner.classList.add("is-hidden");
|
||||
}
|
||||
if (replyBannerText) {
|
||||
replyBannerText.textContent = "";
|
||||
}
|
||||
clearReplySelectionClass();
|
||||
};
|
||||
|
||||
const setReplyTarget = function (messageId, explicitPreview) {
|
||||
const key = String(messageId || "").trim();
|
||||
if (!key) {
|
||||
clearReplyTarget();
|
||||
return;
|
||||
}
|
||||
const row = rowByMessageId(key);
|
||||
let preview = normalizeSnippet(explicitPreview || "");
|
||||
if (row) {
|
||||
const rowSnippet = normalizeSnippet(row.dataset.replySnippet || "");
|
||||
if (rowSnippet && rowSnippet !== "(no text)") {
|
||||
preview = rowSnippet;
|
||||
}
|
||||
}
|
||||
panelState.replyTargetId = key;
|
||||
if (hiddenReplyTo) {
|
||||
hiddenReplyTo.value = key;
|
||||
}
|
||||
if (replyBannerText) {
|
||||
replyBannerText.textContent = preview;
|
||||
}
|
||||
if (replyBanner) {
|
||||
replyBanner.classList.remove("is-hidden");
|
||||
}
|
||||
clearReplySelectionClass();
|
||||
if (row) {
|
||||
row.classList.add("compose-reply-selected");
|
||||
}
|
||||
};
|
||||
|
||||
const bindReplyReferences = function (rootNode) {
|
||||
const scope = rootNode || thread;
|
||||
if (!scope) {
|
||||
return;
|
||||
}
|
||||
scope.querySelectorAll(".compose-row").forEach(function (row) {
|
||||
if (!row.dataset.replySnippet) {
|
||||
const body = row.querySelector(".compose-body");
|
||||
if (body) {
|
||||
row.dataset.replySnippet = normalizeSnippet(body.textContent || "");
|
||||
}
|
||||
}
|
||||
});
|
||||
scope.querySelectorAll(".compose-reply-ref").forEach(function (ref) {
|
||||
const button = ref.querySelector(".compose-reply-link");
|
||||
const targetId = String(ref.dataset.replyTargetId || "").trim();
|
||||
if (!button || !targetId) {
|
||||
return;
|
||||
}
|
||||
const targetRow = rowByMessageId(targetId);
|
||||
const inferredPreview = targetRow
|
||||
? normalizeSnippet(targetRow.dataset.replySnippet || "")
|
||||
: normalizeSnippet(ref.dataset.replyPreview || "");
|
||||
button.textContent = "Reply to: " + inferredPreview;
|
||||
if (button.dataset.bound === "1") {
|
||||
return;
|
||||
}
|
||||
button.dataset.bound = "1";
|
||||
button.addEventListener("click", function () {
|
||||
const row = rowByMessageId(targetId);
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
row.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
flashReplyTarget(row);
|
||||
});
|
||||
});
|
||||
};
|
||||
bindReplyReferences(panel);
|
||||
if (replyClearBtn) {
|
||||
replyClearBtn.addEventListener("click", function () {
|
||||
clearReplyTarget();
|
||||
if (textarea) {
|
||||
textarea.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const postFormJson = async function (url, params) {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
@@ -2619,6 +3044,7 @@
|
||||
if (metaLine) {
|
||||
metaLine.textContent = titleCase(service) + " · " + identifier;
|
||||
}
|
||||
clearReplyTarget();
|
||||
if (panelState.socket) {
|
||||
try {
|
||||
panelState.socket.close();
|
||||
@@ -3468,6 +3894,7 @@
|
||||
if (result.ok) {
|
||||
setStatus('', 'success');
|
||||
textarea.value = '';
|
||||
clearReplyTarget();
|
||||
autosize();
|
||||
flashCompose('is-send-success');
|
||||
poll(true);
|
||||
@@ -3552,6 +3979,7 @@
|
||||
flashCompose("is-send-success");
|
||||
setStatus("", "success");
|
||||
textarea.value = "";
|
||||
clearReplyTarget();
|
||||
autosize();
|
||||
poll(true);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user