260
edits
No edit summary |
No edit summary |
||
| Line 782: | Line 782: | ||
(function (window, document) { | |||
'use strict'; | |||
// Small API object for testing/rerun | |||
window.smartQuotes = window.smartQuotes || {}; | |||
// | // Convert single quotes in a text string to smart quotes | ||
if ( | function convertSingleQuotes(text) { | ||
if (!text || text.indexOf("'") === -1) return text; | |||
return; | // Protect contractions/possessives like don't or John's | ||
text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2"); | |||
// Opening single quote when at start or after whitespace/open punctuation | |||
text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘"); | |||
// Remaining single quotes are closing | |||
text = text.replace(/'/g, "’"); | |||
return text; | |||
} | } | ||
// Walk text nodes under root and replace text content (skip code-like tags) | |||
function walkAndReplace(root) { | |||
if (!root) return; | |||
try { | |||
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); | |||
var node; | |||
var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT", "TEXTAREA"]); | |||
while ((node = walker.nextNode())) { | |||
var parent = node.parentNode; | |||
if (!parent) continue; | |||
// Skip nodes with blacklisted ancestor | |||
var anc = parent, skip = false; | |||
while (anc && anc.nodeType === 1) { | |||
if (blacklist.has(anc.nodeName)) { skip = true; break; } | |||
anc = anc.parentNode; | |||
} | } | ||
} catch (e) { | if (skip) continue; | ||
var original = node.nodeValue; | |||
var replaced = convertSingleQuotes(original); | |||
if (replaced !== original) node.nodeValue = replaced; | |||
} | |||
} catch (e) { | |||
// Defensive: log but don't throw | |||
if (window.console && window.console.error) { | |||
console.error('smartQuotes.walkAndReplace error:', e); | console.error('smartQuotes.walkAndReplace error:', e); | ||
} | } | ||
} | } | ||
} | |||
// Process main content area (parser output) then body fallback | |||
function processOnce() { | |||
var root = document.querySelector('.mw-parser-output') || document.body; | |||
walkAndReplace(root); | |||
} | |||
// Observe for dynamic/AJAX content | |||
function initObserver() { | |||
try { | |||
var obs = new MutationObserver(function (mutations) { | |||
mutations.forEach(function (m) { | |||
m.addedNodes && m.addedNodes.forEach(function (n) { | |||
if (n.nodeType === 1) walkAndReplace(n); | |||
else if (n.nodeType === 3 && n.parentNode) walkAndReplace(n.parentNode); | |||
}); | }); | ||
}); | }); | ||
}); | |||
obs.observe(document.body, { childList: true, subtree: true }); | |||
window.smartQuotes._observer = obs; | |||
} catch (e) { | |||
console.error('smartQuotes.initObserver error:', e); | |||
} | } | ||
} | |||
// Public API to run now (useful to trigger from console) | |||
window.smartQuotes.runNow = function () { | |||
processOnce(); | |||
if (!window.smartQuotes._observer) initObserver(); | |||
return true; | |||
}; | |||
// Run after full window load — this avoids ResourceLoader bundle errors | |||
function runAfterLoad() { | |||
if ( | try { | ||
// Use requestIdleCallback if available for low priority, fallback to setTimeout | |||
var run = function () { window.smartQuotes.runNow(); }; | |||
if ('requestIdleCallback' in window) { | |||
requestIdleCallback(run, { timeout: 2000 }); | |||
} else { | } else { | ||
// slight delay to allow any late DOM insertions | |||
setTimeout(run, 350); | |||
} | } | ||
if (window.console) console.info('smartQuotes: scheduled run after load.'); | |||
} catch (e) { | |||
console.error('smartQuotes.runAfterLoad error:', e); | |||
} | } | ||
} | |||
if (document.readyState === 'complete') { | |||
runAfterLoad(); | |||
} else { | |||
// Attach to load to guarantee it runs after everything (including ResourceLoader errors) | |||
window.addEventListener('load', runAfterLoad, false); | |||
// Also run on DOMContentLoaded as a fallback | |||
document.addEventListener('DOMContentLoaded', function () { | |||
// still defer slightly so other inline scripts finish | |||
setTimeout(runAfterLoad, 150); | |||
}, false); | |||
} | |||
// Helpful console test: window.smartQuotes.test() | |||
window.smartQuotes.test = function (input) { | |||
input = input || " 'Hello' It's John's 'quote' "; | |||
var out = convertSingleQuotes(input); | |||
console.log('smartQuotes.test input:', input); | |||
console.log('smartQuotes.test output:', out); | |||
return out; | |||
}; | |||
})( window, document | })(window, document); | ||