260
edits
No edit summary |
No edit summary |
||
| Line 782: | Line 782: | ||
// Put this in MediaWiki:Common.js | |||
( function () { | ( function () { | ||
'use strict'; | |||
// | // Convert single quotes in a text string to smart quotes | ||
text = text.replace(/(^|[\s\(\[\{\" | function convertSingleQuotes(text) { | ||
if (!text || text.indexOf("'") === -1) return text; | |||
// 1) Protect contractions/possessives like "don't" or "John's" | |||
text = text.replace(/([A-Za-z0-9])'([A-Za-z0-9])/g, "$1’$2"); | |||
// 2) Replace opening single quotes: | |||
// Replace a straight quote that occurs at start of string or after whitespace/opening punctuation | |||
// with an opening single quote ‘ | |||
text = text.replace(/(^|[\s\(\[\{\<\u2014\u2013"“'«])'(?=\S)/g, "$1‘"); | |||
// 3) Remaining single quotes are closing quotes ’ | |||
text = text.replace(/'/g, "’"); | |||
return text; | |||
} | } | ||
// Walk text nodes under root and replace text content | |||
function walkAndReplace(root) { | function walkAndReplace(root) { | ||
if (!root) return; | |||
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); | |||
var node; | |||
// Tags to ignore entirely | |||
var blacklist = new Set(["CODE", "PRE", "SCRIPT", "STYLE", "TEXTAREA", "NOSCRIPT", "MATH", "INPUT"]); | |||
while ((node = walker.nextNode())) { | while ((node = walker.nextNode())) { | ||
var parent = node.parentNode; | |||
if (!parent | if (!parent) continue; | ||
// If any ancestor is blacklisted, skip this node | |||
var skip = false; | |||
var anc = parent; | |||
while (anc && anc !== document && anc.nodeType === 1) { | |||
if (blacklist.has(anc.nodeName)) { skip = true; break; } | |||
anc = anc.parentNode; | |||
} | |||
if (skip) continue; | |||
var original = node.nodeValue; | |||
if ( | var replaced = convertSingleQuotes(original); | ||
if (replaced !== original) node.nodeValue = replaced; | |||
} | } | ||
} | } | ||
mw.hook( | // Run on initial content | ||
function processAll() { | |||
}); | walkAndReplace(document.body); | ||
} | |||
// Handle dynamic content (AJAX navigation) | |||
function initObserver() { | |||
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) { // element | |||
walkAndReplace(n); | |||
} else if (n.nodeType === 3) { // text node | |||
// handle single text node's parent element | |||
var parent = n.parentNode; | |||
if (parent) walkAndReplace(parent); | |||
} | |||
}); | |||
} | |||
}); | |||
}); | |||
observer.observe(document.body, { childList: true, subtree: true }); | |||
} | |||
// Use MediaWiki hooks if available, otherwise fallback to DOMContentLoaded | |||
if (window.mw && mw.hook) { | |||
mw.hook('wikipage.content').add(function($content) { walkAndReplace($content[0]); }); | |||
mw.loader.using(['mediawiki.util']).then(function(){ processAll(); initObserver(); }); | |||
} else { | |||
document.addEventListener('DOMContentLoaded', function () { processAll(); initObserver(); }); | |||
} | |||
})(); | })(); | ||