260
edits
No edit summary |
No edit summary |
||
| Line 782: | Line 782: | ||
( function (window, document, mw) { | // MediaWiki:Common.js | ||
// Smart single-quote substitution (fixed) - ver 1.3 | |||
( function ( window, document, mw ) { | |||
'use strict'; | 'use strict'; | ||
// | // Minimal safety: if mw is missing, bail out gracefully | ||
if ( typeof mw === 'undefined' ) { | |||
console.warn('smartQuotes: mw is not available; aborting.'); | |||
return; | |||
} | |||
// Ensure we load when ResourceLoader is ready | |||
mw.loader.using([], function () { | |||
// Utility: are we anonymous? (avoids using mw.user.isAnon) | |||
// | function isAnon() { | ||
var wgUserId = mw.config.get('wgUserId'); | |||
// wgUserId is 0 or null for anonymous users in many setups | |||
return !wgUserId || wgUserId === 0; | |||
} | |||
// Convert single quotes -> smart quotes in a string | |||
function convertSingleQuotes(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"); | |||
// Opening single quote when at start or after whitespace or opening 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) { | |||
try { | |||
if (!root) return; | |||
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); | |||
var node; | |||
var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]); | |||
while ((node = walker.nextNode())) { | |||
var parent = node.parentNode; | |||
if (!parent) continue; | |||
// skip if any ancestor is blacklisted | |||
var anc = parent, skip = false; | |||
while (anc && anc.nodeType === 1) { | |||
if (blacklist.has(anc.nodeName)) { skip = true; break; } | |||
anc = anc.parentNode; | |||
} | |||
if (skip) continue; | |||
var original = node.nodeValue; | |||
var replaced = convertSingleQuotes(original); | |||
if (replaced !== original) node.nodeValue = replaced; | |||
} | |||
} catch (e) { | |||
console.error('smartQuotes.walkAndReplace error:', e); | |||
} | } | ||
} | } | ||
// Process initial content | |||
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); | |||
} | |||
} | } | ||
// Observer for dynamic content | |||
function initObserver() { | |||
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 = window.smartQuotes || {}; | |||
window.smartQuotes._observer = observer; | |||
console.info('smartQuotes: MutationObserver inited.'); | |||
} catch (e) { | |||
console.error('smartQuotes.initObserver error:', e); | |||
} | |||
} | } | ||
// Expose test function for console verification | |||
window.smartQuotes = window.smartQuotes || {}; | |||
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 DOM ready | |||
function initWhenReady() { | |||
if (document.readyState === 'complete' || document.readyState === 'interactive') { | if (document.readyState === 'complete' || document.readyState === 'interactive') { | ||
processAll(); | |||
initObserver(); | |||
} else { | } else { | ||
document.addEventListener('DOMContentLoaded', | document.addEventListener('DOMContentLoaded', function () { | ||
processAll(); | |||
initObserver(); | |||
}); | |||
} | } | ||
} | } | ||
initWhenReady(); | |||
// | }); // mw.loader.using | ||
})(window, document, window.mw); | })( window, document, window.mw ); | ||