Files
Alfredv2/alfred/app/static/js/dona-ui-fix.js
T
2026-10-30 22:10:03 +01:00

138 lines
5.4 KiB
JavaScript

/* Correctif visuel Dona : titre d'accueil et dégagement de la reconnaissance vocale.
Ne touche ni à Hermes, ni à Vosk, ni aux réglages du mot d'appel. */
(() => {
'use strict';
const LABEL = 'RECONNAISSANCE VOCALE';
const COMPOSER = 'Écrire une demande à';
let pending = false;
let lastStatus = null;
let lastDock = null;
let resizeObserver = null;
const offsets = new WeakMap();
function visible(el) {
if (!el || el.nodeType !== 1) return false;
const s = getComputedStyle(el);
const r = el.getBoundingClientRect();
return s.display !== 'none' && s.visibility !== 'hidden' &&
r.width > 0 && r.height > 0;
}
function textNodes() {
const root = document.body;
if (!root) return [];
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(n) {
const p = n.parentElement;
if (!p || /^(SCRIPT|STYLE|NOSCRIPT|TEXTAREA)$/i.test(p.tagName))
return NodeFilter.FILTER_REJECT;
return n.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
});
const nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
return nodes;
}
function correctTitle(nodes) {
for (const n of nodes) {
if (n.textContent.trim() !== 'Alfred' || !visible(n.parentElement)) continue;
const el = n.parentElement;
const size = parseFloat(getComputedStyle(el).fontSize) || 0;
const heading = el.closest('h1,h2,[role="heading"]');
// Uniquement le grand titre d'accueil ; ne pas renommer les autres mentions.
if (size >= 32 || (heading && parseFloat(getComputedStyle(heading).fontSize) >= 32)) {
n.textContent = n.textContent.replace(/Alfred/, 'Dona');
}
}
}
function findDock(nodes) {
// V6: the compose label is intentionally hidden; locate the compact bar by ID.
const compact = document.querySelector('.dona-compose-compact');
if (visible(compact)) return compact;
const found = nodes.find(n => n.textContent.includes(COMPOSER) && visible(n.parentElement));
if (!found) return null;
let best = null;
let bestScore = -Infinity;
let el = found.parentElement;
for (let i = 0; i < 8 && el && el !== document.body; i++, el = el.parentElement) {
const r = el.getBoundingClientRect();
if (!visible(el) || r.height < 45 || r.height > 265 || r.width < 250 ||
r.top < window.innerHeight * 0.45) continue;
const css = getComputedStyle(el);
const fixed = /^(fixed|sticky)$/.test(css.position);
const radius = parseFloat(css.borderTopLeftRadius) || 0;
const score = (fixed ? 30 : 0) + (radius > 12 ? 14 : 0) +
(r.width > window.innerWidth * 0.55 ? 10 : 0) -
Math.abs(r.bottom - (window.innerHeight - 16)) / 12 -
Math.abs(r.height - 85) / 10;
if (score > bestScore) { bestScore = score; best = el; }
}
return best;
}
function findVoiceRegion(nodes, dock) {
const found = nodes.find(n => n.textContent.includes(LABEL) && visible(n.parentElement));
if (!found) return null;
let el = found.parentElement;
let candidate = el;
for (let i = 0; i < 9 && el && el !== document.body; i++, el = el.parentElement) {
const r = el.getBoundingClientRect();
if (dock && el.contains(dock)) break;
const text = (el.innerText || '').trim();
if (r.height > 300 || text.includes('Prêt à vous aider') || text.length > 1100) break;
// Prendre le groupe entier (statut Vosk + transcription), pas la seule étiquette.
if (r.height >= 25 && visible(el)) candidate = el;
}
return candidate;
}
function update() {
pending = false;
const nodes = textNodes();
correctTitle(nodes);
const dock = findDock(nodes);
const status = findVoiceRegion(nodes, dock);
if (resizeObserver) {
if (lastStatus && lastStatus !== status) resizeObserver.unobserve(lastStatus);
if (lastDock && lastDock !== dock) resizeObserver.unobserve(lastDock);
if (status && lastStatus !== status) resizeObserver.observe(status);
if (dock && lastDock !== dock) resizeObserver.observe(dock);
}
lastStatus = status;
lastDock = dock;
if (!status) return;
const prev = offsets.get(status) || 0;
const rect = status.getBoundingClientRect();
const originalBottom = rect.bottom + prev;
const topLimit = dock ? dock.getBoundingClientRect().top - 24 : window.innerHeight - 22;
const offset = dock ? Math.max(0, Math.ceil(originalBottom - topLimit)) : 0;
const safeOffset = Math.min(offset, Math.max(0, rect.top + prev - 70));
if (Math.abs(safeOffset - prev) >= 1) {
status.style.translate = safeOffset ? `0 -${safeOffset}px` : '';
offsets.set(status, safeOffset);
}
}
function schedule() {
if (pending) return;
pending = true;
requestAnimationFrame(update);
}
function init() {
if ('ResizeObserver' in window) resizeObserver = new ResizeObserver(schedule);
const observer = new MutationObserver(schedule);
observer.observe(document.body, { subtree: true, childList: true, characterData: true });
window.addEventListener('resize', schedule, {passive: true});
window.addEventListener('orientationchange', schedule, {passive: true});
if (window.visualViewport) window.visualViewport.addEventListener('resize', schedule, {passive:true});
schedule();
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, {once:true});
else init();
})();