232 lines
8.7 KiB
HTML
232 lines
8.7 KiB
HTML
{% include 'mixins/partials/notify.html' %}
|
|
<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 }}">
|
|
|
|
<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>
|
|
<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>
|