Scripts Babel – Documentation technique
Cette page documente le fonctionnement, le périmètre, les limites et le code source des bookmarklets utilisés pour la relecture et l’analyse des segments Babel.
← Retour à la toolboxPonctuation V1.00
Description
Script de détection d’anomalies typographiques dans les segments visibles de Babel. Il parcourt les champs texte compatibles et s’arrête sur chaque anomalie détectée.
Règles couvertes
- Majuscule attendue en début de segment, sauf si le segment commence par
...ou…. - Majuscules mal placées dans le segment.
- Majuscule après
:, sauf pour certains tags commeSIC:etPRO:. - Détection de répétitions collées avec tiret, type
de-de. - Détection de
il ysansa. - Double espaces.
- Espace avant
,et.. - Espace avant
). - Manque d’espace après
, . ; : ! ?. - Manque d’espace avant
;:!?. - Espaces mal gérés autour de
«et».
Limites connues
- Le script reste heuristique : certains cas stylistiques peuvent être signalés à tort.
- Le comportement dépend de la structure DOM de Babel, notamment pour la sélection dans certains champs virtualisés.
- Le script ne remplace pas un guide de style complet ni une validation éditoriale finale.
Code lisible
(function () {
const sel = 'textarea,input[type=text],[contenteditable=true],[role=textbox]';
const skipSegRe = /^\s*(?:\[[^\]]*\]|<[^>]*>)\s*$/;
const INV = /[\u200B-\u200D\u2060\uFEFF\u00AD]/g;
function toast(msg) {
let d = document.getElementById('__babel_punct_toast');
if (!d) {
d = document.createElement('div');
d.id = '__babel_punct_toast';
d.style.cssText =
'position:fixed;top:12px;left:50%;transform:translateX(-50%);z-index:2147483647;background:#111;color:#fff;padding:8px 12px;border-radius:10px;font:13px/1.35 system-ui,Segoe UI,Arial;box-shadow:0 6px 18px rgba(0,0,0,.25);max-width:92vw;';
document.documentElement.appendChild(d);
}
d.textContent = msg;
d.style.display = 'block';
clearTimeout(d.__t);
d.__t = setTimeout(() => {
d.style.display = 'none';
}, 2600);
}
function getT(el) {
return el.value != null ? el.value : ((el.textContent ?? el.innerText) ?? '');
}
function keyOf(el, idx) {
const attrs = ['data-segment-id', 'data-segid', 'data-id', 'id', 'name', 'data-testid', 'aria-label'];
let n = el;
for (let k = 0; k < 8 && n; n = n.parentElement, k++) {
for (const a of attrs) {
const v = n.getAttribute && n.getAttribute(a);
if (v) return a + ':' + v;
}
}
return 'idx:' + idx;
}
function selectInInput(el, s, e) {
try {
el.focus();
el.setSelectionRange(s, e);
} catch (_) {}
}
function selectInCE(el, s, e) {
try {
el.focus();
const tw = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
let node,
idx = 0,
sn = null,
so = 0,
en = null,
eo = 0;
while ((node = tw.nextNode())) {
const len = node.nodeValue.length;
if (!sn && idx + len >= s) {
sn = node;
so = s - idx;
}
if (idx + len >= e) {
en = node;
eo = e - idx;
break;
}
idx += len;
}
if (!sn || !en) return false;
const r = document.createRange();
r.setStart(sn, Math.max(0, Math.min(so, sn.nodeValue.length)));
r.setEnd(en, Math.max(0, Math.min(eo, en.nodeValue.length)));
const selObj = window.getSelection();
selObj.removeAllRanges();
selObj.addRange(r);
return true;
} catch (_) {
return false;
}
}
function focusAndSelect(el, s, e) {
try {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} catch (_) {
try {
el.scrollIntoView(true);
} catch (__) {}
}
if (el.value != null && typeof el.setSelectionRange === 'function') {
selectInInput(el, s, e);
return true;
}
if (el.isContentEditable) {
return selectInCE(el, s, e);
}
return false;
}
function skipLeading(t) {
let i = 0;
while (i < t.length && /\s/.test(t[i])) i++;
return i;
}
function startsWithEllipsis(t) {
const i = skipLeading(t);
const s = t.slice(i);
return s.startsWith('…') || s.startsWith('...');
}
function firstLetterIndex(t) {
for (let i = 0; i < t.length; i++) {
const c = t[i];
if (/[A-Za-zÀ-ÖØ-öø-ÿ]/.test(c)) return i;
if (/\S/.test(c)) break;
}
return -1;
}
function getTagBeforeColon(t, i) {
let w = i - 1;
while (w >= 0 && t[w] === ' ') w--;
let end = w + 1;
while (w >= 0 && /[A-Za-z]/.test(t[w])) w--;
return t.slice(w + 1, end).toUpperCase();
}
function isTaxoColon(t, i) {
if (t[i] !== ':') return false;
const tag = getTagBeforeColon(t, i);
return tag === 'SIC' || tag === 'PRO';
}
function isEllipsisDot(t, i) {
return t[i] === '.' && (t[i - 1] === '.' || t[i + 1] === '.');
}
function findBadCaps(t, from) {
const U = /[A-ZÀ-ÖØ-Þ]/,
L = /[a-zà-öø-ÿ]/;
for (let i = Math.max(0, from || 0); i < t.length; i++) {
const c = t[i];
if (!U.test(c)) continue;
const p = t[i - 1] || '';
const n = t[i + 1] || '';
if (L.test(p)) return { s: i, e: i + 1 };
if (p === ' ') {
let j = i - 1;
while (j >= 0 && t[j] === ' ') j--;
const pn = j >= 0 ? t[j] : '';
if (pn && '.!?…\n\r«(":\''.indexOf(pn) >= 0) continue;
if (L.test(n)) return { s: i, e: i + 1 };
}
if (U.test(p) && U.test(n)) continue;
}
return null;
}
function findRepeatDashError(t, from) {
const re = /\b([A-Za-zÀ-ÖØ-öø-ÿ]{1,20})(-{1,2})(\1)\b/g;
re.lastIndex = Math.max(0, from || 0);
let m;
while ((m = re.exec(t))) {
return { s: m.index, e: m.index + m[0].length };
}
return null;
}
function findIlYError(t, from) {
const re = /\bil y\b(?!\s+a)/gi;
re.lastIndex = Math.max(0, from || 0);
let m;
while ((m = re.exec(t))) {
return { s: m.index, e: m.index + m[0].length };
}
return null;
}
const RULES = [
{
label: 'début segment: majuscule (sauf …/...)',
find: (t, from) => {
if (startsWithEllipsis(t)) return null;
const i = firstLetterIndex(t);
if (i < 0 || i < from) return null;
if (/[a-zà-öø-ÿ]/.test(t[i])) return { s: i, e: i + 1 };
return null;
}
},
{
label: 'majuscule mal placée',
find: (t, from) => findBadCaps(t, from)
},
{
label: 'majuscule après :',
find: (t, from) => {
for (let i = Math.max(0, from || 0); i < t.length - 2; i++) {
if (t[i] === ':') {
if (isTaxoColon(t, i)) continue;
let j = i + 1;
while (j < t.length && t[j] === ' ') j++;
if (j >= t.length) continue;
if (/[A-ZÀ-ÖØ-Þ]/.test(t[j])) return { s: j, e: j + 1 };
}
}
return null;
}
},
{
label: 'répétition collée avec tiret',
find: (t, from) => findRepeatDashError(t, from)
},
{
label: 'il y sans a',
find: (t, from) => findIlYError(t, from)
},
{
label: 'double espace',
re: / {2,}/g,
span: (m) => [m.index, m.index + m[0].length]
},
{
label: 'espace avant , .',
re: / +[,.]/g,
span: (m) => [m.index, m.index + m[0].length]
},
{
label: 'espace avant )',
re: / +\)/g,
span: (m) => [m.index, m.index + m[0].length]
},
{
label: 'espace avant .',
re: / +\./g,
span: (m) => [m.index, m.index + m[0].length]
},
{
label: 'manque espace après , . ; : ! ?',
find: (t, from) => {
for (let i = Math.max(0, from || 0); i < t.length; i++) {
const c = t[i];
if (',.;:!?'.includes(c)) {
if (c === ':' && isTaxoColon(t, i)) continue;
if (c === '.' && isEllipsisDot(t, i)) continue;
let j = i + 1;
if (j >= t.length) continue;
if (t[j] !== ' ') return { s: i, e: i + 1 };
}
}
return null;
}
},
{
label: 'manque espace avant ; : ! ?',
find: (t, from) => {
for (let i = Math.max(1, from || 0); i < t.length; i++) {
const c = t[i];
if (';:!?'.includes(c)) {
if (c === ':' && isTaxoColon(t, i)) continue;
if (t[i - 1] !== ' ') return { s: i, e: i + 1 };
}
}
return null;
}
},
{
label: 'guillemets: espace après «',
find: (t, from) => {
for (let i = Math.max(0, from || 0); i < t.length; i++) {
if (t[i] === '«') {
if (i + 1 >= t.length) return null;
if (t[i + 1] !== ' ') return { s: i, e: i + 1 };
}
}
return null;
}
},
{
label: 'guillemets: espace avant »',
find: (t, from) => {
for (let i = Math.max(0, from || 0); i < t.length; i++) {
if (t[i] === '»') {
if (i === 0) return null;
if (t[i - 1] !== ' ') return { s: i, e: i + 1 };
}
}
return null;
}
}
];
function findNext(txt, from) {
txt = txt.replace(INV, '');
let best = null;
for (const r of RULES) {
if (r.find) {
const hit = r.find(txt, from);
if (hit && hit.e > from) {
if (!best || hit.s < best.s) best = { s: hit.s, e: hit.e, label: r.label };
}
continue;
}
r.re.lastIndex = 0;
let m;
while ((m = r.re.exec(txt))) {
const [s, e] = r.span(m);
if (e <= from) continue;
if (!best || s < best.s) best = { s, e, label: r.label };
break;
}
}
return best;
}
const pageKey = location.origin + location.pathname;
const st =
window.__babelPONCT_STATE ||
(window.__babelPONCT_STATE = { field: 0, posByKey: {}, pageKey: '' });
if (st.pageKey !== pageKey) {
st.field = 0;
st.posByKey = {};
st.pageKey = pageKey;
}
const all = [...document.querySelectorAll(sel)].filter(
(el) => el && !el.disabled && !el.readOnly
);
if (!all.length) {
toast('PONCT: 0 champ détecté');
return;
}
st.field = (Number.isInteger(st.field) ? st.field : 0) % all.length;
for (let tries = 0; tries < all.length; tries++) {
const idx = (st.field + tries) % all.length;
const el = all[idx];
let txt = getT(el) || '';
if (!txt || skipSegRe.test(txt)) continue;
const k = keyOf(el, idx);
let pos = st.posByKey[k];
pos = Number.isInteger(pos) ? pos : 0;
if (pos > txt.length) pos = 0;
const hit = findNext(txt, pos);
if (hit) {
st.field = idx;
st.posByKey[k] = hit.e;
const ok = focusAndSelect(el, hit.s, hit.e);
toast(
'PONCT: ' +
hit.label +
' → champ ' +
(idx + 1) +
'/' +
all.length +
(ok ? '' : ' (sélection impossible)')
);
return;
}
st.posByKey[k] = 0;
}
toast('PONCT: Fin du scan ✅');
st.field = 0;
})();
Bookmarklet
NE / N’ V1.00
Description
Script permettant de parcourir les occurrences de ne, n’ et n’ dans les segments visibles.
Règles couvertes
- Détection du mot
ne. - Détection de
n'etn’. - Navigation occurrence par occurrence.
Limites connues
- Ne vérifie pas la correction grammaticale.
- Ne détecte que les occurrences présentes dans les champs visibles.
Code lisible
(function () {
const sel = 'textarea,input[type=text],[contenteditable=true],[role=textbox]';
const scanRe = /\bne\b|\bn[’'](?=\p{L})/iu;
const excRe = /\bne\b|\bn[’%27](?=\p{L})/giu;
const getT = (el) =>
el.value != null ? el.value : (el.innerText ?? el.textContent ?? '');
const fields = [...document.querySelectorAll(sel)].filter((el) => scanRe.test(getT(el)));
if (!fields.length) {
alert("✅ Aucun %27ne%27 ou %27n%27%27 / %27n’%27 trouvé.");
return;
}
window.__babelNeField = Number.isInteger(window.__babelNeField)
? window.__babelNeField
: 0;
for (let tries = 0; tries < fields.length; tries++) {
let idx = (window.__babelNeField + tries) % fields.length;
let el = fields[idx];
let txt = getT(el);
if (el.__babelNeLastTxt !== txt) {
el.__babelNeLastTxt = txt;
el.__babelNePos = 0;
}
let pos = Number.isInteger(el.__babelNePos) ? el.__babelNePos : 0;
excRe.lastIndex = pos;
let m = excRe.exec(txt);
if (m) {
window.__babelNeField = idx;
let start = m.index;
let end = start + m[0].length;
el.__babelNePos = end;
try {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} catch (e) {
el.scrollIntoView(true);
}
try {
el.focus();
} catch (e) {}
if (el.value != null && typeof el.setSelectionRange === 'function') {
el.setSelectionRange(start, end);
} else if (el.isContentEditable) {
const range = document.createRange();
const selObj = window.getSelection();
selObj.removeAllRanges();
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
let p = 0,
found = false;
while (walker.nextNode()) {
const t = walker.currentNode.nodeValue || '';
const q = p + t.length;
if (!found && start < q) {
const s = Math.max(0, start - p);
const e2 = Math.min(t.length, s + (end - start));
range.setStart(walker.currentNode, s);
range.setEnd(walker.currentNode, e2);
found = true;
break;
}
p = q;
}
if (found) {
selObj.addRange(range);
}
}
return;
} else {
if (pos > 0) {
el.__babelNePos = 0;
continue;
}
}
}
alert("✅ Fin : plus de %27ne%27 / %27n%27%27 / %27n’%27 à parcourir.");
})();
Bookmarklet
Répétitions de mots V1.00
Description
Script de détection des mots répétés immédiatement dans un segment.
Règles couvertes
- Détection de
de de. - Détection de
je je. - Gestion de certaines apostrophes.
- Nettoyage de caractères invisibles.
Limites connues
- Ne détecte pas les répétitions éloignées.
- Dépend du texte visible dans la page.
Code lisible
(function () {
const sel = 'textarea,input[type=text],[contenteditable=true],[role=textbox]';
const skipSegRe = /^\s*(?:\[[^\]]*\]|<[^>]*>)\s*$/;
const INV = /[\u200B-\u200D\u2060\uFEFF\u00AD]/g;
const APOS_SET = new Set(['%27', '’', 'ʼ', '′', '\u2019', '\u02BC', '\u2032']);
const WS_ONLY = /^[\s\u00A0\u202F\u2009\u200A\u200B\u2060\uFEFF\u00AD]*$/;
function toast(msg) {
let d = document.getElementById('__babel_rep_toast');
if (!d) {
d = document.createElement('div');
d.id = '__babel_rep_toast';
d.style.cssText =
'position:fixed;top:12px;left:50%;transform:translateX(-50%);z-index:2147483647;background:#111;color:#fff;padding:8px 12px;border-radius:10px;font:13px/1.35 system-ui,Segoe UI,Arial;box-shadow:0 6px 18px rgba(0,0,0,.25);max-width:92vw;';
document.documentElement.appendChild(d);
}
d.textContent = msg;
d.style.display = 'block';
clearTimeout(d.__t);
d.__t = setTimeout(() => {
d.style.display = 'none';
}, 2600);
}
function getT(el) {
return el.value != null ? el.value : ((el.textContent ?? el.innerText) ?? '');
}
function keyOf(el, idx) {
const attrs = ['data-segment-id', 'data-segid', 'data-id', 'id', 'name', 'data-testid', 'aria-label'];
let n = el;
for (let k = 0; k < 8 && n; n = n.parentElement, k++) {
for (const a of attrs) {
const v = n.getAttribute && n.getAttribute(a);
if (v) return a + ':' + v;
}
}
return 'idx:' + idx;
}
function selectInInput(el, s, e) {
try {
el.focus();
el.setSelectionRange(s, e);
} catch (_) {}
}
function selectInCE(el, s, e) {
try {
el.focus();
const tw = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
let node,
idx = 0,
sn = null,
so = 0,
en = null,
eo = 0;
while ((node = tw.nextNode())) {
const len = node.nodeValue.length;
if (!sn && idx + len >= s) {
sn = node;
so = s - idx;
}
if (idx + len >= e) {
en = node;
eo = e - idx;
break;
}
idx += len;
}
if (!sn || !en) return false;
const r = document.createRange();
r.setStart(sn, Math.max(0, Math.min(so, sn.nodeValue.length)));
r.setEnd(en, Math.max(0, Math.min(eo, en.nodeValue.length)));
const selObj = window.getSelection();
selObj.removeAllRanges();
selObj.addRange(r);
return true;
} catch (_) {
return false;
}
}
function focusAndSelect(el, s, e) {
try {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} catch (_) {
try {
el.scrollIntoView(true);
} catch (__) {}
}
if (el.value != null && typeof el.setSelectionRange === 'function') {
selectInInput(el, s, e);
return true;
}
if (el.isContentEditable) {
return selectInCE(el, s, e);
}
return false;
}
function isLetter(ch) {
return !!ch && ch.toLowerCase() !== ch.toUpperCase();
}
function isWordStart(ch) {
const c = String(ch).replace(INV, '');
return !!c && isLetter(c);
}
function nextVisibleChar(txt, i) {
for (let j = i; j < txt.length; j++) {
const c = String(txt[j] ?? '');
if (!INV.test(c)) return c;
}
return '';
}
function okGap(g) {
if (g == null) return false;
g = String(g).replace(INV, '');
g = g.replace(/\[[^\]]*\]/g, '');
g = g.replace(/\{[^}]*\}/g, '');
return WS_ONLY.test(g);
}
function cleanWord(w) {
return String(w)
.replace(INV, '')
.replace(/%27/g, "'")
.replace(/[’ʼ′\u2019\u02BC\u2032]/g, "'")
.toLowerCase();
}
function parseWord(txt, i) {
const s = i;
let raw = '';
while (i < txt.length) {
const ch = txt[i];
const c = String(ch).replace(INV, '');
if (!c) {
raw += ch;
i++;
continue;
}
if (isLetter(c)) {
raw += ch;
i++;
continue;
}
if (APOS_SET.has(c)) {
const nxt = nextVisibleChar(txt, i + 1);
if (nxt && isLetter(String(nxt).replace(INV, ''))) {
raw += ch;
i++;
continue;
}
break;
}
break;
}
return { s, e: i, raw };
}
function findRepeat(txt, from) {
let i = 0,
prev = null;
while (i < txt.length) {
while (i < txt.length && !isWordStart(txt[i])) i++;
if (i >= txt.length) break;
const cur = parseWord(txt, i);
i = cur.e;
const w = cleanWord(cur.raw);
if (cur.e <= from) {
prev = { s: cur.s, e: cur.e, w };
continue;
}
if (prev) {
const gap = txt.slice(prev.e, cur.s);
if (okGap(gap) && prev.w && w && prev.w === w) {
return { start: prev.s, end: cur.e };
}
}
prev = { s: cur.s, e: cur.e, w };
}
return null;
}
const pageKey = location.origin + location.pathname;
const st =
window.__babelREP_STATE ||
(window.__babelREP_STATE = {
field: 0,
posByKey: {},
pageKey: '',
totalFound: 0,
lastShownIdx: null
});
if (st.pageKey !== pageKey) {
st.field = 0;
st.posByKey = {};
st.pageKey = pageKey;
st.totalFound = 0;
st.lastShownIdx = null;
}
const all = [...document.querySelectorAll(sel)].filter(
(el) => el && !el.disabled && !el.readOnly
);
if (!all.length) {
toast('REP: 0 champ détecté');
return;
}
st.field = (Number.isInteger(st.field) ? st.field : 0) % all.length;
for (let tries = 0; tries < all.length; tries++) {
const idx = (st.field + tries) % all.length;
const el = all[idx];
const txt = getT(el) || '';
if (!txt || skipSegRe.test(txt)) continue;
const k = keyOf(el, idx);
let pos = st.posByKey[k];
pos = Number.isInteger(pos) ? pos : 0;
if (pos > txt.length) pos = 0;
const rr = findRepeat(txt, pos);
if (rr) {
if (st.lastShownIdx != null && idx < st.lastShownIdx) {
toast('REP: Fin du scan ✅ (' + st.totalFound + ' répétitions trouvées)');
st.field = 0;
st.totalFound = 0;
st.lastShownIdx = null;
return;
}
st.field = idx;
st.posByKey[k] = rr.end;
st.totalFound++;
st.lastShownIdx = idx;
const ok = focusAndSelect(el, rr.start, rr.end);
toast(
'REP: répétition détectée → champ ' +
(idx + 1) +
'/' +
all.length +
(ok ? '' : ' (sélection impossible)')
);
return;
}
st.posByKey[k] = 0;
}
toast('REP: Fin du scan ✅ (' + st.totalFound + ' répétitions trouvées)');
st.field = 0;
st.totalFound = 0;
st.lastShownIdx = null;
})();
Bookmarklet
Copier vers Scribens V1.00
Description
Script permettant d’assembler les segments visibles dans la page et de les copier dans le presse-papier pour analyse dans Scribens.
Règles couvertes
- Parcourt tous les champs texte visibles.
- Ignore les segments vides.
- Assemble les segments avec séparateurs.
- Copie dans le presse-papiers.
Limites connues
- Dépend des permissions du navigateur.
- Ne copie que les segments chargés.
Code lisible
(function () {
const sel = 'textarea,input[type=text],[contenteditable=true],[role=textbox]';
const skipSegRe = /^\s*(?:\[[^\]]*\]|<[^>]*>)\s*$/;
const getT = (el) =>
el.value != null ? el.value : ((el.textContent ?? el.innerText) ?? '');
function toast(msg) {
let d = document.getElementById('__babel_copy_toast');
if (!d) {
d = document.createElement('div');
d.id = '__babel_copy_toast';
d.style.cssText =
'position:fixed;top:12px;left:50%;transform:translateX(-50%);z-index:2147483647;background:#111;color:#fff;padding:8px 12px;border-radius:10px;font:13px/1.35 system-ui,Segoe UI,Arial;box-shadow:0 6px 18px rgba(0,0,0,.25);max-width:92vw;';
document.documentElement.appendChild(d);
}
d.textContent = msg;
d.style.display = 'block';
clearTimeout(d.__t);
d.__t = setTimeout(() => {
d.style.display = 'none';
}, 2600);
}
const fields = [...document.querySelectorAll(sel)].filter(
(el) => el && !el.disabled && !el.readOnly
);
if (!fields.length) {
toast('SCRI: 0 champ détecté');
return;
}
const parts = [];
let kept = 0;
for (let i = 0; i < fields.length; i++) {
const txt = (getT(fields[i]) || '').trim();
if (!txt || skipSegRe.test(txt)) continue;
kept++;
parts.push('### Segment ' + kept + ' / champ ' + (i + 1) + '/' + fields.length + '\n' + txt);
}
const out = parts.join('\n\n---\n\n');
if (!out) {
toast('SCRI: rien à copier (segments vides/ignorés)');
return;
}
const ok = async () => {
try {
await navigator.clipboard.writeText(out);
toast('SCRI: copié ✅ (' + kept + ' segments)');
} catch (e) {
try {
const ta = document.createElement('textarea');
ta.value = out;
ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;';
document.body.appendChild(ta);
ta.focus();
ta.select();
document.execCommand('copy');
ta.remove();
toast('SCRI: copié ✅ (' + kept + ' segments)');
} catch (e2) {
prompt('Copie manuelle (Ctrl+C) :', out);
}
}
};
ok();
})();
Bookmarklet
Zoom x1.8 V1.00
Description
Script qui positionne automatiquement le curseur de zoom audio sur le niveau correspondant à x1.8.
Règles couvertes
- Repère le premier slider visible.
- Simule l’interaction utilisateur sur la piste du zoom.
- Place le zoom au ratio
0.18.
Limites connues
- Dépend de la structure DOM de Babel.
- Suppose que le premier élément
[role="slider"]corresponde au zoom.
Code lisible
(function () {
const thumb = document.querySelectorAll('[role="slider"]')[0];
const track = thumb.parentElement.parentElement;
const r = track.getBoundingClientRect();
const ratio = 0.18;
const x = r.left + r.width * ratio;
const y = r.top + r.height / 2;
track.dispatchEvent(new PointerEvent('pointerdown', {
bubbles: true,
clientX: x,
clientY: y,
pointerId: 1,
pointerType: 'mouse',
isPrimary: true
}));
track.dispatchEvent(new PointerEvent('pointermove', {
bubbles: true,
clientX: x,
clientY: y,
pointerId: 1,
pointerType: 'mouse',
isPrimary: true
}));
track.dispatchEvent(new PointerEvent('pointerup', {
bubbles: true,
clientX: x,
clientY: y,
pointerId: 1,
pointerType: 'mouse',
isPrimary: true
}));
})();
Bookmarklet
Zoom x10 V1.00
Description
Script qui positionne automatiquement le curseur de zoom audio au niveau maximum correspondant à x10.
Règles couvertes
- Repère le premier slider visible.
- Simule l’interaction utilisateur sur la piste du zoom.
- Place le zoom au ratio
1.00.
Limites connues
- Dépend de la structure DOM de Babel.
- Suppose que le premier élément
[role="slider"]corresponde au zoom.
Code lisible
(function () {
const thumb = document.querySelectorAll('[role="slider"]')[0];
const track = thumb.parentElement.parentElement;
const r = track.getBoundingClientRect();
const ratio = 1.00;
const x = r.left + r.width * ratio;
const y = r.top + r.height / 2;
track.dispatchEvent(new PointerEvent('pointerdown', {
bubbles: true,
clientX: x,
clientY: y,
pointerId: 1,
pointerType: 'mouse',
isPrimary: true
}));
track.dispatchEvent(new PointerEvent('pointermove', {
bubbles: true,
clientX: x,
clientY: y,
pointerId: 1,
pointerType: 'mouse',
isPrimary: true
}));
track.dispatchEvent(new PointerEvent('pointerup', {
bubbles: true,
clientX: x,
clientY: y,
pointerId: 1,
pointerType: 'mouse',
isPrimary: true
}));
})();
Bookmarklet
Heu sans virgule V1.00
Description
Script permettant de parcourir les occurrences de heu ou Heu lorsqu’elles ne sont pas suivies d’une ponctuation attendue.
Règles couvertes
- Détection de
heuetHeu. - Ignore les cas déjà suivis de
, . ? ! -. - Navigation occurrence par occurrence.
Limites connues
- Ne juge pas le contexte stylistique.
- Travaille uniquement sur les champs visibles.
Code lisible
(function () {
const sel = 'textarea,input[type=text],[contenteditable=true],[role=textbox]';
const scanRe = /\b(?:heu|Heu)\b(?!\s*[,.\?!-])/;
const excRe = /\b(?:heu|Heu)\b(?!\s*[,.\?!-])/g;
const getT = (el) =>
el.value != null ? el.value : (el.innerText ?? el.textContent ?? '');
const fields = [...document.querySelectorAll(sel)].filter((el) => scanRe.test(getT(el)));
if (!fields.length) {
alert("✅ Aucun 'heu' sans virgule trouvé.");
return;
}
window.__babelHeuField = Number.isInteger(window.__babelHeuField)
? window.__babelHeuField
: 0;
for (let tries = 0; tries < fields.length; tries++) {
let idx = (window.__babelHeuField + tries) % fields.length;
let el = fields[idx];
let txt = getT(el);
if (el.__babelHeuLastTxt !== txt) {
el.__babelHeuLastTxt = txt;
el.__babelHeuPos = 0;
}
let pos = Number.isInteger(el.__babelHeuPos) ? el.__babelHeuPos : 0;
excRe.lastIndex = pos;
let m = excRe.exec(txt);
if (m) {
window.__babelHeuField = idx;
let start = m.index;
let end = start + m[0].length;
el.__babelHeuPos = end;
try {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
} catch (e) {
el.scrollIntoView(true);
}
try {
el.focus();
} catch (e) {}
if (el.value != null && typeof el.setSelectionRange === 'function') {
el.setSelectionRange(start, end);
} else if (el.isContentEditable) {
const range = document.createRange();
const selObj = window.getSelection();
selObj.removeAllRanges();
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
let p = 0,
found = false;
while (walker.nextNode()) {
const t = walker.currentNode.nodeValue || '';
const q = p + t.length;
if (!found && start < q) {
const s = Math.max(0, start - p);
const e2 = Math.min(t.length, s + (end - start));
range.setStart(walker.currentNode, s);
range.setEnd(walker.currentNode, e2);
found = true;
break;
}
p = q;
}
if (found) {
selObj.addRange(range);
}
}
return;
} else {
if (pos > 0) {
el.__babelHeuPos = 0;
continue;
}
}
}
alert("✅ Fin : plus de 'heu' sans virgule à parcourir.");
})();