Files
html/assets/weval-apostrophe-scanner.js
2026-04-18 13:20:02 +02:00

107 lines
3.8 KiB
JavaScript

/* WEVAL Typographic Apostrophe Scanner v109 — 2026-04-18
Cosmetic client-side fix for French elisions in React SPA DOM.
Fixes both text nodes AND key HTML attributes (alt, title, aria-label, placeholder).
*/
(function () {
'use strict';
if (window !== window.top) return;
var TYPO = '\u2019';
var PATTERNS = [
[/\bl'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'l' + TYPO],
[/\bL'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'L' + TYPO],
[/\bd'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'd' + TYPO],
[/\bD'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'D' + TYPO],
[/\bn'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'n' + TYPO],
[/\bN'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'N' + TYPO],
[/\bs'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 's' + TYPO],
[/\bS'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'S' + TYPO],
[/\bc'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'c' + TYPO],
[/\bC'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'C' + TYPO],
[/\bm'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'm' + TYPO],
[/\bj'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'j' + TYPO],
[/\bt'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 't' + TYPO],
[/\bqu'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'qu' + TYPO],
[/\bQu'(?=[aeiouyhAEIOUYHéèêëîïôùûü])/g, 'Qu' + TYPO],
[/\bjusqu'(?=\w)/g, 'jusqu' + TYPO],
[/\bJusqu'(?=\w)/g, 'Jusqu' + TYPO],
[/\blorsqu'(?=\w)/g, 'lorsqu' + TYPO],
[/\bpuisqu'(?=\w)/g, 'puisqu' + TYPO],
[/\baujourd'(?=h)/g, 'aujourd' + TYPO],
[/\bquelqu'(?=[ue])/g, 'quelqu' + TYPO]
];
var SKIP_TAGS = { SCRIPT: 1, STYLE: 1, CODE: 1, PRE: 1, TEXTAREA: 1, INPUT: 1, NOSCRIPT: 1 };
var FIX_ATTRS = ['alt', 'title', 'aria-label', 'placeholder'];
function fix(text) {
if (!text || text.indexOf("'") === -1) return text;
var out = text;
for (var i = 0; i < PATTERNS.length; i++) {
out = out.replace(PATTERNS[i][0], PATTERNS[i][1]);
}
return out;
}
function fixTextNode(node) {
var v = node.nodeValue;
var fv = fix(v);
if (fv !== v) node.nodeValue = fv;
}
function fixAttrs(el) {
if (!el.getAttribute) return;
for (var i = 0; i < FIX_ATTRS.length; i++) {
var a = FIX_ATTRS[i];
var v = el.getAttribute(a);
if (v) {
var fv = fix(v);
if (fv !== v) el.setAttribute(a, fv);
}
}
}
function walk(root) {
if (!root || SKIP_TAGS[root.nodeName]) return;
// Text nodes
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode: function (n) {
return SKIP_TAGS[n.parentNode && n.parentNode.nodeName] ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
}
});
var n;
while ((n = walker.nextNode())) fixTextNode(n);
// Elements (for attributes)
var elWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
var el;
while ((el = elWalker.nextNode())) fixAttrs(el);
}
function run() {
try { walk(document.body); } catch (e) {
if (window.console) console.warn('[wv-apos] error', e);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { setTimeout(run, 400); });
} else {
setTimeout(run, 400);
}
var debounceTimer = null;
var observer = new MutationObserver(function () {
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(run, 350);
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: FIX_ATTRS });
} else {
document.addEventListener('DOMContentLoaded', function () {
observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: FIX_ATTRS });
});
}
})();