Created
November 20, 2013 12:37
-
-
Save cyphr/7562487 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name CL&U Hacks | |
// @namespace stackexchange | |
// @description CL&U Hacks (derived from JL&U Hacks) | |
// @include http://*chinese.stackexchange.com/* | |
// ==/UserScript== | |
$(function () { | |
var DEBUG_MODE = false; // disable this before release! | |
//ruby mode regex | |
var replaces = new RegExp( | |
"(?:[|[\\[\u3010])([^\\]]+)[\\]\u3011][{\uff5b]([^}]+)[}\uff5d]|([\u4e00-\ufeed\u3003\u3004\u3005\u3006\u3007\u3012]+)\\s*[A-z\\u0100-\\u017F\\u0180-\\u024F\\u3100-\\u312F\\u31A0-\\u31BF0-9{\u3010\uff5b]([.\u3001\u3002\uff0d-\uff0f\uff61\uff1c\uff1e\uff08\uff09\\(\\)\u226a\u226b\uff1b;\uff1a:\uff01!\uff1d=\u2261\u2260\u2252\uff04\uffe5\uff1f\\?\uff06\uff03#\uff20@\u201c\u2018\u201d\u2019]+)[}\u3011\uff5d]", "g"), | |
cache = []; | |
var ruby = { | |
start: function () { | |
this.addEditHelp(); | |
this.addMenu(); | |
this.addOptionsBox(); | |
this.addStyles(); | |
if (this.mode == "uDisableRubyEngine") { | |
// don't run if ruby disabled altogether | |
return; | |
} | |
this.parse(); | |
setInterval($.proxy(this.parse, this), 400); | |
}, | |
addCSS: function (css) { | |
var head = document.getElementsByTagName('head')[0], | |
style = document.createElement('style'); | |
style.type = 'text/css'; | |
if (style.styleSheet){ | |
style.styleSheet.cssText = css; | |
} else { | |
style.appendChild(document.createTextNode(css)); | |
} | |
head.appendChild(style); | |
}, | |
htmlEscape: function (i) { | |
return i.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); | |
}, | |
loadMode: function () { | |
try { | |
var mode = localStorage["uRubyMode"+(DEBUG_MODE ? "_dbg" : "")], | |
allowedModes = $("input[name=uRubyMode]").map(function() { | |
return this.id; | |
}); | |
// Only use the value in localStorage if it's a recognized value | |
return ($.inArray(mode, allowedModes) !== -1) ? mode : "uAccentedPinyin"; | |
} catch(e) { | |
return "uAccentedPinyin"; | |
}; | |
}, | |
saveMode: function () { | |
try { | |
localStorage["uRubyMode"+(DEBUG_MODE ? "_dbg" : "")] = $('input[name=uRubyMode]:checked', '#uRubyModeForm').attr("id"); | |
location.reload(); | |
} catch(e) { | |
alert("Options can only be changed when localStorage is available. Please check your browser settings."); | |
}; | |
}, | |
addEditHelp: function() { | |
// Add to the editor help at http://japanese.stackexchange.com/questions/ask etc | |
// but only for the Japanese stack exchange, as it probably isn't as relevant for anime.se | |
if (location.href.indexOf('chinese.stackexchange') != -1) { | |
$('<p><span class="dingus">►</span> pinyin ruby <code class="noRuby">拼音【pin1yin1】</code>, <code class="noRuby">拼音{pin1yin1}</code> or <code class="noRuby">[拼音]{pin1yin1}</code></p>').insertBefore("#how-to-format p.ar"); | |
} | |
}, | |
addMenu: function () { | |
//add menu | |
if (!$("#upopup").length) { | |
var addTo = $("#footer-menu .top-footer-links,.footer-links"); // .footer-links for mobile | |
addTo.prepend( | |
'<a id="upopuphyperlink">ruby options</a><div id="upopup" style="color:black;position:absolute;'+ | |
'display:none;background-color:#fff;border:1px solid #ccc;margin-top:3px;padding:5px;z-index:500;'+ | |
'box-shadow:1px 1px 2px rgba(0,0,0,0.2);"/>' | |
); | |
$('#upopuphyperlink').click(function() { | |
$('#upopup').toggle(); | |
$("#upopup").css('top', $('#upopuphyperlink').offset().top - $("#upopup").height() - 10); | |
$("#upopup").css('left', $('#upopuphyperlink').offset().left); | |
}); | |
}; | |
}, | |
addOptionsBox: function () { | |
//add options box | |
$("#upopup").html(""); | |
$("#upopup").append( | |
'<div>' + | |
'<h4>ruby mode</h4>' + | |
'<form id="uRubyModeForm">'+ | |
'<input type="radio" name="uRubyMode" id="uDisableRubyEngine"><label for="uDisableRubyEngine"> don\'t convert pinyin ruby markup (leave as-is)</label></input><br>' + | |
'<input type="radio" name="uRubyMode" id="uDisableRuby"><label for="uDisableRuby"> don\'t show any pinyin ruby</label></input><br>' + | |
'<input type="radio" name="uRubyMode" id="uAccentedPinyin"><label for="uAccentedPinyin"> pinyin ruby using tone accents (default)</label></input><br>' + | |
'<input type="radio" name="uRubyMode" id="uNumberedPinyin"><label for="uNumberedPinyin"> pinyin ruby using tone numbers</label></input><br>' + | |
'<input type="radio" name="uRubyMode" id="uMouseOver"><label for="uMouseOver"> hide pinyin texts, only show when mousing over hanzi</label></input><br>' + | |
'</form>'+ | |
'<input type="button" id="usave" value="save and reload"/> <input type="button" onclick="$(\'#upopup\').hide()" value="close"/>' + | |
'</div>' | |
).find("div").css( | |
{ 'color': '#000', 'textAlign': 'left', 'lineHeight': '1.9em' } | |
).find("a").css( | |
{ 'color': 'blue', 'margin': 0 } | |
); | |
//restore previous settings/register save settings event etc | |
this.mode = this.loadMode(); | |
$('#'+this.mode).prop('checked', true); | |
$("#usave").click($.proxy(this.saveMode, this)); | |
}, | |
addStyles: function() { | |
var alignCSS = "rt,ruby,rb{text-align:center;ruby-align:center;}"; | |
if (!/IE|Chrome|AppleWebKit|Safari/.test(navigator.userAgent)) { | |
this.nonNativeRuby = true; | |
this.addCSS( | |
'ruby{display:inline-table;text-indent:0;white-space:nowrap;margin:0;padding:0;line-height:1em;border:none;vertical-align:text-bottom;}' + | |
'rb{display:table-row-group;line-height:1em;height:1em;font-size:1em;border:none;margin:0;padding:0;white-space:nowrap; overflow: hidden;}' + | |
'rt{display:table-header-group;font-size:0.75em;line-height:1em;height:1em;white-space:nowrap;border:none;margin:0;padding:0;overflow:hidden;}' + | |
alignCSS | |
); | |
} else { | |
//add different css for WebKit browsers (Chrome/Safari/Opera 15) | |
//& IE (as they support ruby natively) | |
this.addCSS( | |
'ruby{line-height:1;height:1em;}' + | |
'rb{line-height:1;}' + | |
'rt{font-size:0.7em;line-height:1.1;}' + | |
alignCSS | |
); | |
}; | |
this.addCSS( | |
'span.hiddenruby:hover,span.hiddenruby-rp:hover{background:#CCFFCC;}'+ | |
'span.hiddenruby,span.hiddenruby-rp{cursor:default;border-bottom:1px dashed gray;}'+ | |
'span.tone-h{border-top:1px solid red;}'+ | |
'span.tone-l-change{border:solid red;border-width:0 0 1px 1px;}'+ | |
'span.tone-l{border-bottom:1px solid red;}'+ | |
'span.tone-h-change{border:solid red;border-width:1px 0 0 1px;}'+ | |
'.ushadow,#upop,.upop{-webkit-box-shadow: 0px 0px 7px rgba(50, 50, 50, 0.2);-moz-box-shadow: 0px 0px 7px rgba(50, 50, 50, 0.2);box-shadow: 0px 0px 7px rgba(50, 50, 50, 0.2);}'+ | |
'#upop,.upop{padding:2px 5px;font-size:1.3em;background:#FFFFF0;border-radius:3px;border:1px solid #ccc;white-space:nowrap;}' | |
); | |
}, | |
parse: function () { | |
//a.question-hyperlink h2 is for hyperlinks on mobile | |
//div.excerpt is for browsing through question summaries | |
$("span,code:not(.noRuby),p,li,b,i,em,strong,a,div.excerpt,a.question-hyperlink h2").contents() | |
.filter(function () { | |
return ( | |
this.nodeType == 3 || this.nodeType == 1 | |
) && /[\u3000-\ufeed]/.test(this.data); | |
}) | |
.each(function () { | |
for (var i = 0; i < cache.length; i++) { | |
if (cache[i] == this && true) { | |
return; | |
} | |
} | |
var data_escaped = ruby.htmlEscape(this.data), | |
data = data_escaped.replace(replaces, function ($0, $1, $2, $3, $4) { | |
var hanzi = $1 || $3, | |
rb = $2 || $4; | |
//console.log(hanzi+' '+rb); | |
if (ruby.mode == "uMouseOver") { | |
return '<span class="hiddenruby-rp" title="' + rb + '">' + hanzi + '</span>'; | |
} | |
else if (ruby.mode == "uDisableRuby") { | |
return hanzi; | |
} | |
return ruby.rubyize(hanzi, rb); | |
}); | |
if (data != data_escaped) { | |
$(this).replaceWith(data); | |
} | |
cache.push(this); | |
}); | |
if (this.mode == 'uMouseOver') { | |
this.replaceTitleAttrs(); | |
} | |
if (this.nonNativeRuby && /Opera|Firefox/.test(navigator.userAgent)) { | |
$('.ruby-rp').each(function() { | |
// Using px values for vertical-align is more accurate, but isn't supported in all browsers. | |
// For this reason, it'll only be used for Firefox and Opera with the Presto engine | |
// (as they worked when I tested them). Unfortunately, this still has rounding issues | |
// when in tandem with the browsers' rendering, so it can be out by a pixel or so. | |
var amount = Math.round($(this).height()/2.0)+1; | |
if (amount) { | |
$(this).removeClass('ruby-rp'); | |
$(this).css('verticalAlign', amount+'px'); | |
}; | |
}); | |
} | |
}, | |
rubyize: function (hanzi, pinyin) { | |
function getRuby(hanzi, pinyin) { | |
return '<ruby class="ruby-rp"><rb>' + hanzi + '</rb><rt>' + pinyin + '</rt></ruby>'; | |
} | |
var pinyins = []; | |
function onReplace(a, pinyin, tone) { | |
// Split each pinyin "part" using the numbered tones or spaces | |
var p = (pinyin||'') + (tone||''); | |
if (p) { | |
if (ruby.mode == 'uAccentedPinyin') { | |
// convert numbered pinyin to accented pinyin | |
p = decode_pinyin(p); | |
} | |
pinyins.push(p); | |
} | |
//console.log(pinyin+' '+tone) | |
return ''; | |
} | |
var t = pinyin.replace(/([^0-5 ]*)([0-5 ]*)/g, onReplace), | |
o = ''; | |
if (hanzi.length == pinyins.length) { | |
for (var i=0; i<Math.min(hanzi.length, pinyins.length); i++) { | |
o += getRuby(hanzi.charAt(i), pinyins[i]); | |
} | |
} | |
else { | |
o = getRuby(hanzi, pinyins.join('')); | |
} | |
return o; | |
}, | |
replaceTitleAttrs: function () { | |
// add faster popup windows as "title=" takes some time to appear | |
// "title=" also usually isn't usable on tablets | |
$('.hiddenruby-rp').each(function (e) { | |
// go through the "rp", aka "reprocess" classes | |
$(this).removeClass('hiddenruby-rp').addClass('hiddenruby'); | |
var title = String(this.title), | |
upop; | |
this.title = ''; | |
$(this).mouseover(function(e) { | |
if (upop) { | |
// just to be sure... | |
upop.remove(); | |
upop = null; | |
} | |
$(document.body).append('<div id="upop" class="upop">'+ruby.htmlEscape(title)+'</div>'); | |
upop = $("#upop"); | |
upop.attr("id", ""); | |
upop.css("position", "absolute"); | |
ruby.updateUPopPos(upop, e); | |
}).mousemove(function(e) { | |
if (!upop) { | |
return; | |
} | |
ruby.updateUPopPos(upop, e); | |
}).mouseout(function() { | |
if (!upop) { | |
return; | |
} | |
upop.remove(); | |
upop = null; | |
}); | |
}); | |
}, | |
updateUPopPos: function (upop, e) { | |
var setTo = { | |
top: e.pageY+20, | |
left: e.pageX+20, | |
right: 'auto', | |
maxWidth: $(window).width() | |
}; | |
if ((setTo.left+12+upop.width()) > $(window).width()) { | |
// Especially in the mobile website, | |
// make sure popup isn't offscreen | |
setTo.left = 'auto'; | |
setTo.right = 0; | |
}; | |
upop.css(setTo); | |
} | |
} | |
// from http://stackoverflow.com/questions/8200349/convert-numbered-pinyin-to-pinyin-with-tone-marks | |
var PinyinToneMark = { | |
0: "aoeiuv\u00fc", | |
1: "\u0101\u014d\u0113\u012b\u016b\u01d6\u01d6", | |
2: "\u00e1\u00f3\u00e9\u00ed\u00fa\u01d8\u01d8", | |
3: "\u01ce\u01d2\u011b\u01d0\u01d4\u01da\u01da", | |
4: "\u00e0\u00f2\u00e8\u00ec\u00f9\u01dc\u01dc" | |
} | |
function decode_pinyin(s) { | |
s = s.toLowerCase(); | |
var r = "", | |
t = ""; | |
for (var i=0; i<s.length; i++) { | |
var c = s.charAt(i); | |
if (/[a-z]/.test(c)) { | |
t += c; | |
} | |
else if (c === ':') { | |
if (t.charAt(t.length-1) === 'u') { | |
t = t.slice(0, -1) + "\u00fc"; | |
} | |
else { | |
t += c; | |
} | |
} | |
else { | |
if (c in PinyinToneMark || c === '5') { | |
var tone = parseInt(c) % 5; | |
if (tone !== 0) { | |
var m = /[aoeiuv\u00fc]+/g.exec(t); | |
if (!m) { | |
t += c; | |
} | |
else if (m[0].length == 1) { | |
var start = m.index, | |
end = start+m[0].length; | |
t = t.slice(0, start) + PinyinToneMark[tone][PinyinToneMark[0].indexOf(m[0])] + t.slice(end); | |
} | |
else { | |
if (t.indexOf('a') !== -1) { | |
t = t.replace(/a/g, PinyinToneMark[tone][0]); | |
} | |
else if (t.indexOf('o') !== -1) { | |
t = t.replace(/o/g, PinyinToneMark[tone][1]); | |
} | |
else if (t.indexOf('e') !== -1) { | |
t = t.replace(/e/g, PinyinToneMark[tone][2]); | |
} | |
else if (/.*ui$/.test(t)) { | |
t = t.replace(/i/g, PinyinToneMark[tone][3]); | |
} | |
else if (/.*iu$/.test(t)) { | |
t = t.replace(/u/g, PinyinToneMark[tone][4]); | |
} | |
else { | |
t += "!"; | |
} | |
} | |
} | |
} | |
r += t; | |
t = ""; | |
} | |
} | |
r += t; | |
return r; | |
} | |
ruby.start(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment