// ==UserScript== // @name CL&U Hacks // @namespace stackexchange // @description CL&U Hacks // @include http://*chinese.stackexchange.com/* // ==/UserScript== // may as well go with jQuery for in case this ever gets ported over to the site itself // cf. function addJQuery(callback) { var script = document.createElement("script"); script.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"); script.addEventListener('load', function() { var script = document.createElement("script"); script.textContent = "window.jQ=jQuery.noConflict(true);(" + callback.toString() + ")();"; document.body.appendChild(script); }, false); document.body.appendChild(script); } main = function () { var DEBUG_MODE = false; // disable this before release! var leftbracket_cl = "\\[\u3010", rightbracket_cl = "\\]\u3011", leftbrace_cl = "{\uff5b", rightbrace_cl = "}\uff5d", hanzi_cl = "\u4e00-\ufeed", punctuation_cl = ".\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", pinyin_cl = "1-4a-z\u0080-\u036f", latin_cl = "a-zA-Z", tones_cl = "1-4", wordseparators_cl = ".\u3001\u3002\u30fb\uff0d-\uff0f\uff61"; var wordseparators_re = new RegExp("["+wordseparators_cl+"]"), hanzi_re = new RegExp("["+hanzi_cl+"]"), numerictones_re = new RegExp("["+latin_cl+"]*" + "["+tones_cl+"]", "g"); var replaces = new RegExp( "(?:[|["+leftbracket_cl+"])" + "([^"+rightbracket_cl+"]+)" + "["+rightbracket_cl+"]" + "["+leftbrace_cl+"]" + "([^"+rightbrace_cl+"]+)" + "["+rightbrace_cl+"]" + "|" + "(["+hanzi_cl+"]+)" + "\\s*" + "["+leftbracket_cl+leftbrace_cl+"]" + "(["+punctuation_cl+pinyin_cl+"]+)" + "["+rightbracket_cl+rightbrace_cl+"]", "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, '"'); }, loadMode: function () { try { var mode = localStorage["uPinyinMode" + (DEBUG_MODE ? "_dbg" : "")], allowedModes = $("input[name=uPinyinMode]").map(function () { return this.id; }); // Only use the value in localStorage if it's a recognized value return ($.inArray(mode, allowedModes) !== -1) ? mode : "uCenterAlignRuby"; } catch (e) { return "uCenterAlignRuby"; }; }, saveMode: function () { try { localStorage["uPinyinMode" + (DEBUG_MODE ? "_dbg" : "")] = $('input[name=uPinyinMode]:checked', '#uPinyinModeForm').attr("id"); location.reload(); } catch (e) { alert("Options can only be changed when localStorage is available. Please check your browser settings."); }; }, addEditHelp: function () { // !FIX there is currently no actual link for pinyin help at this time; when it appears, it should point to target="_edithelp" if (location.href.indexOf('chinese.stackexchange') != -1) { $('

Pinyin 大衣{dàyī} or 三人[成虎]{cheng2hu3}

').insertBefore("#how-to-format p.ar"); $('

pinyin help »

').appendTo("#how-to-format"); } }, addMenu: function () { //add menu if (!$("#upopup").length) { var addTo = $("#footer-menu .top-footer-links,.footer-links"); // .footer-links for mobile addTo.prepend( 'pinyin options
' ); $('#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( '
' + 'pinyin help »' + // !FIX also needs a link for pinyin help; should point to target="_blank" '

pinyin mode

' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + ' ' + '
' ).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 = ruby.mode == "uLeftAlignRuby" ? "rt,ruby,rb{text-align:left;ruby-align:left;}" : "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 + (/IE/.test(navigator.userAgent) ? 'rt{font-family:"MS Gothic","MS ゴシック",sans-serif}' : '') ); }; 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(.noPinyin),p,li,b,i,a,div.excerpt,a.question-hyperlink h2").contents() .filter(function () { return ( this.nodeType == 3 || this.nodeType == 1 ) && hanzi_re.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, string, offset) { var hanzi = $1 || $3, pinyin = $2 || $4; if (ruby.mode == "uMouseOver") { return '' + hanzi + ''; } else if (ruby.mode == "uDisableRuby") { return hanzi; } var hanzis = hanzi.split(''); // handle numeric tones if (/[1-4]/.test(pinyin)) { var pinyins = pinyin.match(numerictones_re); for (i = 0; i < pinyins.length; i++) { var syllable = pinyins[i].slice(0, -1), tone = pinyins[i].slice(-1); var onset = syllable.match(/^[^aeiou]*/)[0], nucleus = syllable.match(/[aeiou]+/)[0], coda = syllable.match(/[^aeiou]*$/)[0]; var diacritic; switch (tone) { case '1': diacritic = '\u0304'; break; case '2': diacritic = '\u0301'; break; case '3': diacritic = '\u030c'; break; case '4': diacritic = '\u0300'; break; } var tone_index; switch (nucleus) { case 'ai': case 'ao': case 'ei': case 'ou': case 'a': case 'e': case 'i': case 'o': case 'u': case 'ü': tone_index = 1; break; case 'ia': case 'iao': case 'ie': case 'io': case 'iu': case 'ua': case 'uai': case 'ue': case 'ui': case 'uo': case 'üe': tone_index = 2; break; } nucleus = nucleus.substring(0, tone_index) + diacritic + nucleus.substring(tone_index, nucleus.length); syllable = onset + nucleus + coda; pinyins[i] = syllable; } } else { var pinyins = pinyin.split(wordseparators_re); } if (hanzis.length == pinyins.length) { return $.map(hanzis, function (k, i) { return ruby.rubyize(k, pinyins[i]); }).join(''); } return ruby.rubyize(hanzi, pinyin); }); 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) { var getRuby = function (hanzi, pinyin) { return '' + hanzi + '' + pinyin + ''; } return getRuby(hanzi, pinyin); }, 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('
' + ruby.htmlEscape(title) + '
'); 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); } } ruby.start(); } addJQuery(main);