Tightly integrate WhatsApp selectors into existing UIs
This commit is contained in:
@@ -1,53 +1,231 @@
|
||||
{% include 'mixins/partials/notify.html' %}
|
||||
<table
|
||||
class="table is-fullwidth is-hoverable"
|
||||
hx-target="#{{ context_object_name }}-table"
|
||||
id="{{ context_object_name }}-table"
|
||||
<div
|
||||
id="whatsapp-contacts-shell"
|
||||
hx-target="#whatsapp-contacts-shell"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="{{ context_object_name_singular }}Event from:body"
|
||||
hx-get="{{ list_url }}">
|
||||
<thead>
|
||||
<th>name</th>
|
||||
<th>identifier</th>
|
||||
<th>jid</th>
|
||||
<th>person</th>
|
||||
<th>actions</th>
|
||||
</thead>
|
||||
{% for item in object_list %}
|
||||
<tr>
|
||||
<td>{{ item.name|default:"-" }}</td>
|
||||
<td>
|
||||
<code>{{ item.identifier }}</code>
|
||||
</td>
|
||||
<td>{{ item.jid|default:"-" }}</td>
|
||||
<td>{{ item.person_name|default:"-" }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
{% if type == 'page' %}
|
||||
<a href="{{ item.compose_page_url }}" class="button" title="Open manual chat">
|
||||
<span class="icon"><i class="fa-solid fa-paper-plane"></i></span>
|
||||
</a>
|
||||
{% else %}
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{{ item.compose_widget_url }}"
|
||||
hx-trigger="click"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="afterend"
|
||||
class="button"
|
||||
title="Open manual chat widget">
|
||||
<span class="icon"><i class="fa-solid fa-paper-plane"></i></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
<a href="{{ item.match_url }}" class="button" title="Match identifier">
|
||||
<span class="icon"><i class="fa-solid fa-link"></i></span>
|
||||
</a>
|
||||
|
||||
<div class="field has-addons is-flex-wrap-wrap" style="margin-bottom: 0.6rem;">
|
||||
<div class="control is-expanded" style="min-width: 14rem;">
|
||||
<input
|
||||
id="whatsapp-contacts-search"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Search WhatsApp contacts...">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button id="whatsapp-contacts-reset" class="button is-light" type="button">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="osint-results-meta">
|
||||
<div class="osint-results-meta-left">
|
||||
<div class="dropdown is-hoverable" id="whatsapp-contacts-columns-dropdown">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="button is-small is-light" aria-haspopup="true" aria-controls="whatsapp-contacts-columns-menu">
|
||||
<span>Show/Hide Fields</span>
|
||||
<span class="icon is-small"><i class="fa-solid fa-angle-down"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="has-text-grey">No WhatsApp contacts discovered yet.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="dropdown-menu" id="whatsapp-contacts-columns-menu" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a class="dropdown-item whatsapp-col-toggle" data-col-index="0" href="#" onclick="return false;">
|
||||
<span class="icon is-small"><i class="fa-solid fa-check"></i></span>
|
||||
<span>Name</span>
|
||||
<span class="is-size-7 has-text-grey ml-2">(name)</span>
|
||||
</a>
|
||||
<a class="dropdown-item whatsapp-col-toggle" data-col-index="1" href="#" onclick="return false;">
|
||||
<span class="icon is-small"><i class="fa-solid fa-check"></i></span>
|
||||
<span>Identifier</span>
|
||||
<span class="is-size-7 has-text-grey ml-2">(identifier)</span>
|
||||
</a>
|
||||
<a class="dropdown-item whatsapp-col-toggle" data-col-index="2" href="#" onclick="return false;">
|
||||
<span class="icon is-small"><i class="fa-solid fa-check"></i></span>
|
||||
<span>JID</span>
|
||||
<span class="is-size-7 has-text-grey ml-2">(jid)</span>
|
||||
</a>
|
||||
<a class="dropdown-item whatsapp-col-toggle" data-col-index="3" href="#" onclick="return false;">
|
||||
<span class="icon is-small"><i class="fa-solid fa-check"></i></span>
|
||||
<span>Person</span>
|
||||
<span class="is-size-7 has-text-grey ml-2">(person)</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="button is-small is-light" type="button" disabled>
|
||||
<span class="icon is-small"><i class="fa-solid fa-database"></i></span>
|
||||
<span>Static</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="osint-results-count">
|
||||
fetched {{ object_list|length }} result{% if object_list|length != 1 %}s{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table
|
||||
class="table is-fullwidth is-hoverable"
|
||||
id="whatsapp-contacts-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-whatsapp-col="0" class="whatsapp-col-0">name</th>
|
||||
<th data-whatsapp-col="1" class="whatsapp-col-1">identifier</th>
|
||||
<th data-whatsapp-col="2" class="whatsapp-col-2">jid</th>
|
||||
<th data-whatsapp-col="3" class="whatsapp-col-3">person</th>
|
||||
<th>actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in object_list %}
|
||||
<tr data-search="{{ item.name|default:'-'|lower }} {{ item.identifier|lower }} {{ item.jid|default:'-'|lower }} {{ item.person_name|default:'-'|lower }}">
|
||||
<td data-whatsapp-col="0" class="whatsapp-col-0">{{ item.name|default:"-" }}</td>
|
||||
<td data-whatsapp-col="1" class="whatsapp-col-1">
|
||||
<code>{{ item.identifier }}</code>
|
||||
</td>
|
||||
<td data-whatsapp-col="2" class="whatsapp-col-2">{{ item.jid|default:"-" }}</td>
|
||||
<td data-whatsapp-col="3" class="whatsapp-col-3">{{ item.person_name|default:"-" }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
{% if type == 'page' %}
|
||||
<a href="{{ item.compose_page_url }}" class="button" title="Open manual chat">
|
||||
<span class="icon"><i class="fa-solid fa-paper-plane"></i></span>
|
||||
</a>
|
||||
{% else %}
|
||||
<button
|
||||
type="button"
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{{ item.compose_widget_url }}"
|
||||
hx-trigger="click"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="afterend"
|
||||
class="button"
|
||||
title="Open manual chat widget">
|
||||
<span class="icon"><i class="fa-solid fa-paper-plane"></i></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
<a href="{{ item.match_url }}" class="button" title="Match identifier">
|
||||
<span class="icon"><i class="fa-solid fa-link"></i></span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="has-text-grey">No WhatsApp contacts discovered yet.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#whatsapp-contacts-shell .osint-results-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.6rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
#whatsapp-contacts-shell .osint-results-meta-left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.42rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
#whatsapp-contacts-shell .osint-results-count {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
#whatsapp-contacts-shell .whatsapp-col-toggle .icon {
|
||||
color: #3273dc;
|
||||
}
|
||||
#whatsapp-contacts-shell .whatsapp-col-toggle.is-hidden-col .icon {
|
||||
color: #b5b5b5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const shell = document.getElementById("whatsapp-contacts-shell");
|
||||
if (!shell) return;
|
||||
const table = document.getElementById("whatsapp-contacts-table");
|
||||
if (!table) return;
|
||||
const rows = Array.from(table.querySelectorAll("tbody tr[data-search]"));
|
||||
const searchInput = document.getElementById("whatsapp-contacts-search");
|
||||
const resetBtn = document.getElementById("whatsapp-contacts-reset");
|
||||
const toggles = Array.from(shell.querySelectorAll(".whatsapp-col-toggle"));
|
||||
const storageKey = "gia_whatsapp_contacts_hidden_cols_v1";
|
||||
let hidden = [];
|
||||
try {
|
||||
hidden = JSON.parse(localStorage.getItem(storageKey) || "[]");
|
||||
} catch (e) {
|
||||
hidden = [];
|
||||
}
|
||||
if (!Array.isArray(hidden)) hidden = [];
|
||||
hidden = hidden.map(String);
|
||||
|
||||
function applyFilters() {
|
||||
const query = String(searchInput?.value || "").trim().toLowerCase();
|
||||
rows.forEach((row) => {
|
||||
const hay = String(row.dataset.search || "").toLowerCase();
|
||||
row.style.display = !query || hay.includes(query) ? "" : "none";
|
||||
});
|
||||
}
|
||||
|
||||
function applyColumns() {
|
||||
const hiddenSet = new Set(hidden);
|
||||
shell.querySelectorAll("[data-whatsapp-col]").forEach((node) => {
|
||||
const idx = String(node.getAttribute("data-whatsapp-col") || "");
|
||||
node.style.display = hiddenSet.has(idx) ? "none" : "";
|
||||
});
|
||||
toggles.forEach((toggle) => {
|
||||
const idx = String(toggle.getAttribute("data-col-index") || "");
|
||||
const isHidden = hiddenSet.has(idx);
|
||||
toggle.classList.toggle("is-hidden-col", isHidden);
|
||||
const icon = toggle.querySelector("i");
|
||||
if (icon) {
|
||||
icon.className = isHidden ? "fa-solid fa-xmark" : "fa-solid fa-check";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function persistColumns() {
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify(hidden));
|
||||
} catch (e) {
|
||||
// Ignore storage failures.
|
||||
}
|
||||
}
|
||||
|
||||
toggles.forEach((toggle) => {
|
||||
toggle.addEventListener("click", function () {
|
||||
const idx = String(toggle.getAttribute("data-col-index") || "");
|
||||
if (!idx) return;
|
||||
if (hidden.includes(idx)) {
|
||||
hidden = hidden.filter((item) => item !== idx);
|
||||
} else {
|
||||
hidden.push(idx);
|
||||
}
|
||||
persistColumns();
|
||||
applyColumns();
|
||||
});
|
||||
});
|
||||
|
||||
if (searchInput) searchInput.addEventListener("input", applyFilters);
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener("click", function () {
|
||||
if (searchInput) searchInput.value = "";
|
||||
applyFilters();
|
||||
});
|
||||
}
|
||||
|
||||
applyFilters();
|
||||
applyColumns();
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user