138 lines
5.4 KiB
JavaScript
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();
|
|
})();
|