Files
GIA/core/templates/base.html
2026-03-11 15:12:17 +00:00

667 lines
30 KiB
HTML

{% load static %}
{% load cache %}
{% load page_title %}
{% load accessibility %}
<!DOCTYPE html>
<html lang="en-GB">
<head>
<script>
(function () {
var storedTheme = null;
try {
storedTheme = localStorage.getItem("theme");
} catch (error) {
}
if (storedTheme === "dark" || storedTheme === "light") {
document.documentElement.dataset.theme = storedTheme;
} else {
document.documentElement.removeAttribute("data-theme");
}
})();
</script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block browser_title %}{% firstof page_browser_title page_title as explicit_title %}{% if explicit_title %}{{ explicit_title }} · GIA{% else %}{% with route_value=request.resolver_match.url_name|default:request.path_info|humanize_route %}{% if route_value %}{{ route_value }} · GIA{% else %}GIA{% endif %}{% endwith %}{% endif %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{% static 'favicon.png' %}?v=sq3" data-gia-favicon="icon">
<link rel="shortcut icon" href="{% static 'favicon.png' %}?v=sq3" data-gia-favicon="shortcut">
<link rel="manifest" href="{% static 'manifest.webmanifest' %}">
{# Managed by tools/frontend_assets/asset-manifest.json and scripts/vendor_frontend_assets.py. #}
<link rel="preload" href="{% static 'vendor/fontawesome/webfonts/fa-solid-900.woff2' %}" as="font" type="font/woff2" integrity="sha512-Ph1xTLhfMycYSW+wUN8oL3Ggl56nGIS95EHiKWggcL/GbMNjPdib1Hreb1D4COlMxdiGCkk43nspQnpDuTjgQg==" crossorigin="anonymous">
<link rel="preload" href="{% static 'vendor/fontawesome/webfonts/fa-regular-400.woff2' %}" as="font" type="font/woff2" integrity="sha512-qioT43fXB5q4Bbpn8sPQE9OIZLjKD0c0lVmpm6KmT8k34LM6gkRcOOMi1BOl2lohFG/7p9tzKfTP5G563BQq1g==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/bulma.min.css' %}" integrity="sha512-yh2RE0wZCVZeysGiqTwDTO/dKelCbS9bP2L94UvOFtl/FKXcNAje3Y2oBg/ZMZ3LS1sicYk4dYVGtDex75fvvA==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/bulma-tooltip.min.css' %}" integrity="sha512-SNDNIUvSYhnqDV9FFXaH/e0xZ6NzkG4Qm5dafLLf0PCMkzICKaOmMTgI3y2t2jZK+hAtP6A7UBcFqjWMhsujIg==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'vendor/fontawesome/css/all.css' %}" integrity="sha512-UKBBxJ5N3/MYiSsYTlEsARsp4vELKVRIklED4Mb6wpuVFOgy5Blt+sXUdz1TDReqWsm64xxBA2QoBJRCxI0x5Q==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/bulma-slider.min.css' %}" integrity="sha512-9o5SkCRCA9thttRH3Gb5QXLxKdRiuRLdO6ToEPwRHGLXjrhTZwFj0rEHjrCcJvDN9/aNaWMpGOIEA2vZsHmEqw==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/bulma-calendar.min.css' %}" integrity="sha512-IOnJQkgQpezPDPTJcRiWD7YVI3sF2RYzYDl4isbDT2geSaEHRQ615UN/8GhJbSkvqkKRZu8SBCQ7XwKMqsqLFQ==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/bulma-tagsinput.min.css' %}" integrity="sha512-NWTkcDRubZ3pyXbZZLQBILuVsRFs8c6QGgnfe4dm5/d6yp50U+xdoCDLIcSo51fFy/GXH0O2Oed1Z1sF1faxDA==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/bulma-switch.min.css' %}" integrity="sha512-zjrHYubQoNgDVqVKTyGjKcvIeQlduZTvXCvcBwQ0iqJYKLKiz9cuFAN7e98zfKqCTpI/EgFRBRcTwJw20yAFuw==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/gridstack.min.css' %}" integrity="sha512-ttQfsDTO64bamkJHeLDf0kzMP1NKfkootudPWS2V8Pwy+9z1wexSYjIT6/HXGg/bmtD+DRwsUnQoYEB0yePjbw==" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/gia-theme.css' %}" integrity="sha512-J/sff1E15uvn7df8Yv/C3Y+CE75bnxZF4vj4wEtfuIaXhECzC0cJTpocvTt7jdB5iMRry8MQ3ZH6Pg/ckh4QNA==" crossorigin="anonymous">
<script src="{% static 'js/bulma-calendar.min.js' %}" integrity="sha512-kkEtEtypXzruevjkoxhyEkqkZBtlhK7s8zt7IV2yPabgBwy5xbKL9uWeCS37ldS9AaNTSnveWTu4ivUvGMJUWA==" crossorigin="anonymous"></script>
<script src="{% static 'js/bulma-slider.min.js' %}" integrity="sha512-WLKXHCsMXTSIPsmQShJRE6K4IzwvNkhwxr/Oo8N3z+kzjhGleHibspmWLTawNMdl2z9E23XK20+yvUTDZ+zeNQ==" crossorigin="anonymous"></script>
<script src="{% static 'js/htmx.min.js' %}" integrity="sha512-CGXFnDNv5q48ciFeIyWFcfZhqYW0sSBiPO+HZDO3XLM+p8xjhezz5CCxtkXVDKfCbvF+iUhel7xoeSp19o7x7g==" crossorigin="anonymous"></script>
<script defer src="{% static 'js/remove-me.js' %}" integrity="sha512-uhE4kDw2+tXdJPDKSttOEYhVnwYq310+d5DMQnTjafJ58QLlYPyx0RTCNbjcrTiGfCjAhaQob4AumEUa2m3TaQ==" crossorigin="anonymous"></script>
<script defer src="{% static 'js/hyperscript.min.js' %}" integrity="sha512-l43sZzpnAddmYhJyfPrgv46XhJvA95gsA28/+eW4XZLSekQ8wlP68i9f22KGkRjY0HNiZrLc5MXGo4z/tM2QNA==" crossorigin="anonymous"></script>
<script src="{% static 'js/bulma-tagsinput.min.js' %}" integrity="sha512-Je6J++MjmmpxF30JCmRwM2KiK3uWQBQtqiNCjwzEMJKExLaa0BqerlYNa/fJAl5Rra4hMgRZF2fzg+V2vjE4Kw==" crossorigin="anonymous"></script>
<script src="{% static 'js/jquery.min.js' %}" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous"></script>
<script src="{% static 'js/gridstack-all.js' %}" integrity="sha512-djBPxwvBhDep1SvOhliatweHMORhVO3HabrfBjaW6nYsa7UcJYHty31x42m4HBSJXcJSQdoEgRPLVYGGIuIaDQ==" crossorigin="anonymous"></script>
<script defer src="{% static 'js/magnet.min.js' %}" integrity="sha512-aoQ3V4iCM8zTcdMDSUTRG1K9wqZzmDSisuaCLQexk9DdFy92oWvTUoAfCVLnGzzJClst8PmtasZg219REwyNkw==" crossorigin="anonymous"></script>
<script>
(function () {
var FAVICON_VERSION = "sq3";
var STORAGE_KEY = "theme";
var THEME_DARK = "dark";
var THEME_LIGHT = "light";
var root = document.documentElement;
var darkMedia = window.matchMedia("(prefers-color-scheme: dark)");
function getStoredTheme() {
try {
var raw = localStorage.getItem(STORAGE_KEY);
if (raw === THEME_DARK || raw === THEME_LIGHT) {
return raw;
}
} catch (error) {
}
return null;
}
function getResolvedTheme() {
var stored = getStoredTheme();
if (stored === THEME_DARK || stored === THEME_LIGHT) {
return stored;
}
return darkMedia.matches ? THEME_DARK : THEME_LIGHT;
}
function applyFavicon() {
var href = "{% static 'favicon.png' %}?v=" + FAVICON_VERSION;
var iconLink = document.querySelector("link[data-gia-favicon='icon']");
if (iconLink) {
iconLink.setAttribute("href", href);
}
var shortcutLink = document.querySelector("link[data-gia-favicon='shortcut']");
if (shortcutLink) {
shortcutLink.setAttribute("href", href);
}
}
function persistTheme(mode) {
try {
localStorage.setItem(STORAGE_KEY, mode);
} catch (error) {
}
}
function updateToggleUI(mode) {
document.querySelectorAll(".js-theme-toggle").forEach(function (button) {
var nextMode = mode === THEME_DARK ? THEME_LIGHT : THEME_DARK;
button.setAttribute("data-theme-mode", mode);
button.setAttribute("aria-label", "Theme: " + mode + ". Click to switch to " + nextMode + ".");
button.setAttribute("title", "Theme: " + mode);
var label = button.querySelector("[data-theme-label]");
if (label) {
label.textContent = mode === THEME_DARK ? "Dark" : "Light";
}
});
}
function applyTheme(mode, shouldPersist) {
var validMode = mode === THEME_DARK ? THEME_DARK : THEME_LIGHT;
root.dataset.theme = validMode;
applyFavicon();
updateToggleUI(validMode);
if (shouldPersist !== false) {
persistTheme(validMode);
}
}
function cycleTheme() {
var currentTheme = root.dataset.theme === THEME_DARK ? THEME_DARK : THEME_LIGHT;
applyTheme(currentTheme === THEME_DARK ? THEME_LIGHT : THEME_DARK, true);
}
applyTheme(getResolvedTheme(), false);
if (darkMedia && darkMedia.addEventListener) {
darkMedia.addEventListener("change", function () {
if (!getStoredTheme()) {
applyTheme(getResolvedTheme(), false);
}
});
}
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".js-theme-toggle").forEach(function (button) {
button.addEventListener("click", cycleTheme);
});
applyTheme(getResolvedTheme(), false);
});
})();
document.addEventListener("restore-scroll", function () {
var scrollpos = localStorage.getItem("scrollpos");
if (scrollpos) {
window.scrollTo(0, scrollpos);
}
});
document.addEventListener("htmx:beforeSwap", function () {
localStorage.setItem("scrollpos", window.scrollY);
});
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");
}
});
});
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" },
})
.then(function (response) {
if (!response.ok) {
throw new Error("Failed contacts preview fetch.");
}
return response.text();
})
.then(function (html) {
composeDropdown.innerHTML = html;
composePreviewLoaded = true;
})
.catch(function () {
composePreviewLoaded = false;
})
.finally(function () {
composePreviewLoading = false;
});
});
});
</script>
</head>
{% get_accessibility_settings request.user as a11y_settings %}
<body{% if a11y_settings and a11y_settings.disable_animations %} class="reduced-motion"{% endif %}>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<div class="navbar-item">
<span class="gia-brand-shell">
{% if user.is_authenticated %}
<button class="button is-light theme-toggle-button gia-brand-logo brand-theme-toggle js-theme-toggle" type="button" data-theme-mode="light" aria-label="Theme toggle">
<svg class="brand-theme-logo" viewBox="0 0 213.35 150.85" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
<g transform="translate(38.831 -7.4316)">
<g transform="matrix(.99287 0 0 .99911 1.2367 -30.308)">
<path class="brand-theme-stroke" d="m-32.645 113.03a115.16 122.5 0 0 1 99.73-61.25 115.16 122.5 0 0 1 99.73 61.25" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="15.42"/>
<path class="brand-theme-stroke" d="m67.006 89.591a42.374 42.374 0 0 1 39.148 26.158 42.374 42.374 0 0 1-9.1855 46.179 42.374 42.374 0 0 1-46.179 9.1855 42.374 42.374 0 0 1-26.158-39.148" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16.251"/>
<circle cx="67.003" cy="131.96" r="13.151" fill="#740101"/>
</g>
</g>
</svg>
<span class="is-sr-only" data-theme-label>Auto</span>
</button>
{% else %}
<a href="{% url 'home' %}" class="gia-brand-logo" aria-label="GIA home">
<svg class="brand-theme-logo" viewBox="0 0 213.35 150.85" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
<g transform="translate(38.831 -7.4316)">
<g transform="matrix(.99287 0 0 .99911 1.2367 -30.308)">
<path class="brand-theme-stroke" d="m-32.645 113.03a115.16 122.5 0 0 1 99.73-61.25 115.16 122.5 0 0 1 99.73 61.25" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="15.42"/>
<path class="brand-theme-stroke" d="m67.006 89.591a42.374 42.374 0 0 1 39.148 26.158 42.374 42.374 0 0 1-9.1855 46.179 42.374 42.374 0 0 1-46.179 9.1855 42.374 42.374 0 0 1-26.158-39.148" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16.251"/>
<circle cx="67.003" cy="131.96" r="13.151" fill="#740101"/>
</g>
</g>
</svg>
</a>
{% endif %}
</span>
</div>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="bar">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="bar" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="{% url 'home' %}">
Home
</a>
{% if user.is_authenticated %}
<div class="navbar-item has-dropdown is-hoverable">
<a
id="nav-compose-link"
class="navbar-link"
data-preview-url="{% url 'compose_contacts_dropdown' %}"
data-full-url="{% url 'compose_contacts_dropdown' %}?all=1"
hx-get="{% url 'compose_contacts_dropdown' %}?all=1"
hx-target="#nav-compose-contacts"
hx-trigger="click"
hx-swap="innerHTML">
<span class="icon is-small"><i class="fa-solid fa-paper-plane"></i></span>
<span style="margin-left: 0.35rem;">Compose</span>
</a>
<div class="navbar-dropdown" id="nav-compose-contacts">
<a
class="navbar-item"
hx-get="{% url 'compose_contacts_dropdown' %}?all=1"
hx-target="#nav-compose-contacts"
hx-swap="innerHTML">
Open Contacts
</a>
</div>
</div>
<a class="navbar-item" href="{% url 'tasks_hub' %}">
Task Inbox
</a>
<a class="navbar-item" href="{% url 'ai_workspace' %}">
AI
</a>
<a class="navbar-item" href="{% url 'osint_search' type='page' %}">
Search
</a>
{% endif %}
</div>
<div class="navbar-end">
{% if user.is_authenticated %}
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
Services
</a>
<div class="navbar-dropdown">
<a class="navbar-item" href="{% url 'signal' %}">
Signal
</a>
<a class="navbar-item" href="{% url 'whatsapp' %}">
WhatsApp
</a>
<a class="navbar-item" href="{% url 'instagram' %}">
Instagram
</a>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
Data
</a>
<div class="navbar-dropdown">
<a class="navbar-item" href="{% url 'sessions' type='page' %}">
Sessions
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'business_plan_inbox' or request.resolver_match.url_name == 'business_plan_editor' %} is-current-route{% endif %}" href="{% url 'business_plan_inbox' %}">
Business Plans
</a>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
Settings
</a>
<div class="navbar-dropdown">
<div class="navbar-item has-text-weight-semibold is-size-7 has-text-grey">
General
</div>
<a class="navbar-item{% if request.resolver_match.url_name == 'notifications_settings' or request.resolver_match.url_name == 'notifications_update' %} is-current-route{% endif %}" href="{% url 'notifications_settings' %}">
Notifications
</a>
{% if user.is_superuser %}
<a class="navbar-item{% if request.resolver_match.url_name == 'system_settings' %} is-current-route{% endif %}" href="{% url 'system_settings' %}">
System
</a>
{% endif %}
<a class="navbar-item{% if request.resolver_match.url_name == 'accessibility_settings' %} is-current-route{% endif %}" href="{% url 'accessibility_settings' %}">
Accessibility
</a>
<hr class="navbar-divider">
<div class="navbar-item has-text-weight-semibold is-size-7 has-text-grey">
Security
</div>
<a class="navbar-item{% if request.resolver_match.url_name == 'encryption_settings' or request.resolver_match.url_name == 'security_settings' %} is-current-route{% endif %}" href="{% url 'encryption_settings' %}">
Encryption
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'permission_settings' %} is-current-route{% endif %}" href="{% url 'permission_settings' %}">
Permissions
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'security_2fa' or request.resolver_match.namespace == 'two_factor' %} is-current-route{% endif %}" href="{% url 'security_2fa' %}">
2FA
</a>
<hr class="navbar-divider">
<div class="navbar-item has-text-weight-semibold is-size-7 has-text-grey">
AI
</div>
<a class="navbar-item{% if request.resolver_match.url_name == 'ai_models' or request.resolver_match.url_name == 'ais' %} is-current-route{% endif %}" href="{% url 'ai_models' %}">
Models
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'ai_execution_log' %} is-current-route{% endif %}" href="{% url 'ai_execution_log' %}">
Traces
</a>
<hr class="navbar-divider">
<div class="navbar-item has-text-weight-semibold is-size-7 has-text-grey">
Modules
</div>
<a class="navbar-item{% if request.resolver_match.url_name == 'command_routing' %} is-current-route{% endif %}" href="{% url 'command_routing' %}">
Commands
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'business_plan_inbox' or request.resolver_match.url_name == 'business_plan_editor' %} is-current-route{% endif %}" href="{% url 'business_plan_inbox' %}">
Business Plans
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'tasks_settings' %} is-current-route{% endif %}" href="{% url 'tasks_settings' %}">
Task Automation
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'translation_settings' %} is-current-route{% endif %}" href="{% url 'translation_settings' %}">
Translation
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'availability_settings' or request.resolver_match.url_name == 'behavioral_signals_settings' %} is-current-route{% endif %}" href="{% url 'behavioral_signals_settings' %}">
Behavioral Signals
</a>
<hr class="navbar-divider">
<div class="navbar-item has-text-weight-semibold is-size-7 has-text-grey">
Automation
</div>
<a class="navbar-item{% if request.resolver_match.url_name == 'queues' %} is-current-route{% endif %}" href="{% url 'queues' type='page' %}">
Approvals Queue
</a>
<a class="navbar-item{% if request.resolver_match.url_name == 'osint_workspace' %} is-current-route{% endif %}" href="{% url 'osint_workspace' %}">
OSINT Workspace
</a>
</div>
</div>
{% endif %}
<div class="navbar-item">
<div class="buttons">
{% if not user.is_authenticated %}
<a class="button is-info" href="{% url 'signup' %}">
<strong>Sign up</strong>
</a>
<a class="button" href="{% url 'two_factor:login' %}">
Log in
</a>
{% endif %}
{% if user.is_authenticated %}
<button class="button is-light add-button" type="button" style="display:none;">Install App</button>
<a class="button is-dark" href="{% url 'logout' %}">Logout</a>
{% endif %}
</div>
</div>
</div>
</div>
</nav>
<script>
let deferredPrompt;
const addBtn = document.querySelector('.add-button');
if (addBtn) {
addBtn.style.display = 'none';
}
window.addEventListener('beforeinstallprompt', (e) => {
if (!addBtn) {
return;
}
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI to notify the user they can add to home screen
addBtn.style.display = 'block';
addBtn.addEventListener('click', (e) => {
// hide our user interface that shows our A2HS button
addBtn.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
});
window.giaPrepareWidgetTarget = function () {
const target = document.getElementById("widgets-here");
if (target) {
target.style.display = "block";
}
};
window.giaCanSpawnWidgets = function () {
return !!(
window.grid &&
typeof window.grid.addWidget === "function" &&
document.getElementById("grid-stack-main") &&
document.getElementById("widgets-here")
);
};
window.giaEnableWidgetSpawnButtons = function (root) {
const scope = root && root.querySelectorAll ? root : document;
const canSpawn = window.giaCanSpawnWidgets();
scope.querySelectorAll(".js-widget-spawn-trigger").forEach(function (button) {
const widgetUrl = String(
button.getAttribute("data-widget-url")
|| button.getAttribute("hx-get")
|| ""
).trim();
const visible = canSpawn && !!widgetUrl;
button.classList.toggle("is-hidden", !visible);
button.setAttribute("aria-hidden", visible ? "false" : "true");
});
};
window.giaPrepareWindowAnchor = function (trigger) {
if (!trigger || !trigger.getBoundingClientRect) {
window.giaWindowAnchor = null;
return;
}
const rect = trigger.getBoundingClientRect();
window.giaWindowAnchor = {
left: rect.left,
right: rect.right,
top: rect.top,
bottom: rect.bottom,
width: rect.width,
height: rect.height,
ts: Date.now(),
};
};
window.giaPositionFloatingWindow = function (windowEl) {
if (!windowEl) {
return;
}
const isMobile = window.matchMedia("(max-width: 768px)").matches;
const margin = 12;
const rect = windowEl.getBoundingClientRect();
const anchor = window.giaWindowAnchor || null;
windowEl.style.position = "fixed";
if (isMobile) {
const centeredLeftViewport = Math.max(
margin,
Math.round((window.innerWidth - rect.width) / 2)
);
const centeredTopViewport = Math.max(
margin,
Math.round((window.innerHeight - rect.height) / 2)
);
windowEl.style.left = centeredLeftViewport + "px";
windowEl.style.top = centeredTopViewport + "px";
windowEl.style.right = "auto";
windowEl.style.bottom = "auto";
windowEl.style.transform = "none";
windowEl.setAttribute("tabindex", "-1");
if (typeof windowEl.focus === "function") {
windowEl.focus({preventScroll: true});
}
if (typeof windowEl.scrollIntoView === "function") {
windowEl.scrollIntoView({block: "center", inline: "center", behavior: "smooth"});
}
window.giaWindowAnchor = null;
return;
}
if (!anchor || (Date.now() - anchor.ts) > 10000) {
return;
}
const desiredLeftViewport = anchor.left;
const desiredTopViewport = anchor.bottom + 6;
const maxLeftViewport = window.innerWidth - rect.width - margin;
const maxTopViewport = window.innerHeight - rect.height - margin;
const boundedLeftViewport = Math.max(
margin,
Math.min(desiredLeftViewport, maxLeftViewport)
);
const boundedTopViewport = Math.max(
margin,
Math.min(desiredTopViewport, maxTopViewport)
);
windowEl.style.left = boundedLeftViewport + "px";
windowEl.style.top = boundedTopViewport + "px";
windowEl.style.right = "auto";
windowEl.style.bottom = "auto";
windowEl.style.transform = "none";
window.giaWindowAnchor = null;
};
window.giaEnableFloatingWindowInteractions = function (windowEl) {
if (!windowEl || windowEl.dataset.giaWindowInteractive === "1") {
return;
}
windowEl.dataset.giaWindowInteractive = "1";
// Disable magnet-block global drag so text inputs remain editable.
windowEl.setAttribute("unmovable", "");
const heading = windowEl.querySelector(".panel-heading");
if (!heading) {
return;
}
let dragging = false;
let startX = 0;
let startY = 0;
let startLeft = 0;
let startTop = 0;
const onMove = function (event) {
if (!dragging) {
return;
}
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
windowEl.style.left = (startLeft + deltaX) + "px";
windowEl.style.top = (startTop + deltaY) + "px";
windowEl.style.right = "auto";
windowEl.style.bottom = "auto";
};
const stopDrag = function () {
dragging = false;
document.removeEventListener("pointermove", onMove);
document.removeEventListener("pointerup", stopDrag);
};
heading.addEventListener("pointerdown", function (event) {
if (event.button !== 0) {
return;
}
const interactive = event.target.closest(
"button, a, input, textarea, select, label, .delete, .icon"
);
if (interactive) {
return;
}
const rect = windowEl.getBoundingClientRect();
windowEl.style.position = "fixed";
startLeft = rect.left;
startTop = rect.top;
startX = event.clientX;
startY = event.clientY;
dragging = true;
document.addEventListener("pointermove", onMove);
document.addEventListener("pointerup", stopDrag);
event.preventDefault();
});
};
document.addEventListener("click", function (event) {
const trigger = event.target.closest(".js-widget-spawn-trigger");
if (!trigger) {
return;
}
window.giaPrepareWidgetTarget();
});
document.addEventListener("DOMContentLoaded", function () {
window.giaEnableWidgetSpawnButtons(document);
});
document.body.addEventListener("htmx:afterSwap", function (event) {
const target = (event && event.target) || document;
window.giaEnableWidgetSpawnButtons(target);
const targetId = (target && target.id) || "";
if (targetId === "windows-here") {
const floatingWindows = target.querySelectorAll(".floating-window");
floatingWindows.forEach(function (floatingWindow) {
window.setTimeout(function () {
window.giaPositionFloatingWindow(floatingWindow);
window.giaEnableFloatingWindowInteractions(floatingWindow);
}, 0);
});
}
});
</script>
{% block outer_content %}
{% endblock %}
<div>
<div class="container">
{% include "partials/settings-hierarchy-nav.html" %}
{% block content_wrapper %}
{% block content %}
{% endblock %}
{% endblock %}
<div id="modals-here">
</div>
<div id="windows-here" style="z-index: 120;">
</div>
<div id="widgets-here" style="display: none;">
</div>
</div>
</div>
</body>
</html>