260
edits
No edit summary |
No edit summary |
||
| Line 782: | Line 782: | ||
( function (window, document, mw) { | |||
( function () { | |||
'use strict'; | 'use strict'; | ||
// | // Expose a minimal API for testing | ||
window.smartQuotes = window.smartQuotes || {}; | |||
function convertSingleQuotes(text) { | function convertSingleQuotes(text) { | ||
if (!text || text.indexOf("'") === -1) return text; | if (!text || text.indexOf("'") === -1) return text; | ||
// Protect contractions/possessives like don't or John's | |||
// | |||
text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2"); | text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2"); | ||
// Opening quote when at start or after whitespace or open punctuation | |||
// | |||
text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘"); | text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘"); | ||
// Remaining single quotes are closing | |||
// | |||
text = text.replace(/'/g, "’"); | text = text.replace(/'/g, "’"); | ||
return text; | return text; | ||
} | } | ||
function walkAndReplace(root) { | function walkAndReplace(root) { | ||
if (!root) return; | if (!root) return; | ||
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); | var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); | ||
var node; | var node; | ||
var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]); | var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]); | ||
| Line 816: | Line 809: | ||
if (!parent) continue; | if (!parent) continue; | ||
// | // Skip nodes with blacklisted ancestor | ||
var skip = false | var anc = parent, skip = false; | ||
while (anc && anc.nodeType === 1) { | |||
while (anc | |||
if (blacklist.has(anc.nodeName)) { skip = true; break; } | if (blacklist.has(anc.nodeName)) { skip = true; break; } | ||
anc = anc.parentNode; | anc = anc.parentNode; | ||
| Line 831: | Line 823: | ||
} | } | ||
// | // Kick off on initial DOM and expose functions for debugging | ||
function processAll() { | function processAll() { | ||
try { | |||
var root = document.querySelector('.mw-parser-output') || document.body; | |||
walkAndReplace(root); | |||
console.info('smartQuotes: processAll completed.'); | |||
} catch (e) { | |||
console.error('smartQuotes processAll error:', e); | |||
} | |||
} | } | ||
// | // MutationObserver for dynamic content | ||
function initObserver() { | function initObserver() { | ||
var observer = new MutationObserver(function(mutations) { | try { | ||
var observer = new MutationObserver(function (mutations) { | |||
mutations.forEach(function (m) { | |||
if (m.addedNodes && m.addedNodes.length) { | |||
m.addedNodes.forEach(function (n) { | |||
if (n.nodeType === 1) walkAndReplace(n); | |||
else if (n.nodeType === 3 && n.parentNode) walkAndReplace(n.parentNode); | |||
}); | |||
} | |||
}); | |||
} | |||
} | |||
} | |||
}); | }); | ||
observer.observe(document.body, { childList: true, subtree: true }); | |||
window.smartQuotes._observer = observer; | |||
console.info('smartQuotes: MutationObserver inited.'); | |||
} catch (e) { | |||
console.error('smartQuotes observer error:', e); | |||
} | |||
} | |||
// Expose a test utility to call from the console | |||
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; | |||
}; | |||
// Init after ResourceLoader and DOM ready | |||
function init() { | |||
processAll(); | |||
initObserver(); | |||
} | } | ||
if (mw && mw.loader) { | |||
if ( | mw.loader.using([], function () { | ||
mw. | if (document.readyState === 'complete' || document.readyState === 'interactive') { | ||
init(); | |||
} else { | |||
document.addEventListener('DOMContentLoaded', init); | |||
} | |||
}); | |||
} else { | } else { | ||
document. | // fallback | ||
if (document.readyState === 'complete' || document.readyState === 'interactive') init(); | |||
else document.addEventListener('DOMContentLoaded', init); | |||
} | } | ||
})(); | // Small heartbeat so we can see if script runs at all | ||
console.info('smartQuotes script loaded.'); | |||
})(window, document, window.mw); | |||