v3
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
/* 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();
|
||||
})();
|
||||
Reference in New Issue
Block a user