Skip to content

Instantly share code, notes, and snippets.

@cyphr
Last active June 23, 2023 20:29
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cyphr/6536814 to your computer and use it in GitHub Desktop.
Save cyphr/6536814 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name JL&U Hacks
// @namespace stackexchange
// @description JL&U Hacks
// @include http://*japanese.stackexchange.com/*
// ==/UserScript==
$(function () {
var DEBUG_MODE = true; // DISABLE THIS BEFORE RELEASE!
var addCSS = function (css) {
var head = document.getElementsByTagName('head')[0]
if (head) {
var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet){
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
};
var elm = $('<div></div>');
elm.html('<style type="text/css">'+css+'</style>');
$(document.body).append(elm);
}
var htmlEscape = function (i) {
return i.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
var start = function () {
//add menu
if (!$("#upopup").length) {
$("#footer-menu .top-footer-links").prepend(
'<a id="upopuphyperlink">furigana&nbsp;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").css('top', $("#footer-menu .top-footer-links").offset().top - $("#upopup").height() - 10);
$('#upopup').toggle();
});
};
//add options box
$("#upopup").append(
'<div>' +
'<h4>ruby options</h4>' +
'<input type="checkbox" id="udisableruby"><label for="udisableruby"> disable ruby entirely</label></input><br>' +
'<h4>other options</h4>' +
'<input type="checkbox" id="uhideruby"><label for="uhideruby"> hide ruby texts, only show when hover on kanji</label></input><br>' +
'<input type="button" id="usave" value="save and reload"/> <input type="button" onclick="$(\'#upopup\').hide()" value="close"/>' +
'</div>'
).find("div").css(
{ 'color': '#000' }
).find("a,input").css(
{ 'color': '#00f', 'text-shadow': 'none', 'vertical-align': 'middle', 'font-weight': 'normal' }
);
//restore previous settings/register save settings event etc
loadSettings();
$("#usave").click(saveSettings);
var updDisabled = function () {
$("#uhideruby").prop('disabled', $("#udisableruby").prop('checked'))
}
updDisabled();
$("#udisableruby").change(updDisabled);
if (!DEBUG_MODE && getSetting("udisableruby") == 1) {
// don't run if ruby disabled altogether
return
}
if (!/Chrome|IE/.test(navigator.userAgent)) {
addCSS(
'ruby{display:inline-table;text-align:center;text-indent:0;white-space:nowrap;margin:0;padding:0;line-height:1;height:1em;vertical-align:text-bottom;border:none;}' +
'rb{display:table-row-group;line-height:1;text-align:center;border:none;margin:0;padding:0;white-space:nowrap;}' +
'rt{display:table-header-group;font-size:0.75em;line-height:1.1;text-align:center;white-space:nowrap;border:none;margin:0;padding:0;}');
} else {
//add different css for Chrome & IE (as they support ruby natively)
addCSS(
'ruby{line-height:1;height:1em;}' +
'rb{line-height:1;}' +
'rt{font-size:0.7em;line-height:1.1;}' +
(/IE/.test(navigator.userAgent) ? 'rt{font-family:"MS Gothic","MS ゴシック",sans-serif}' : ''));
};
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;}')
//ruby mode regex
var replaces = new RegExp(
"(?:&#91;|\\[)([^\\]]+)\\]\\{([^}]+)\\}|([\u4e00-\ufeed\u3003\u3004\u3005\u3006\u3007\u3012]+[\u3041-\u30fe]*)\\s*[{\u3010]([.\u3001\u3002\u3041-\u30fe\uff0d-\uff0f\uff61hlHL]+)[}\u3011]|([\u3041-\u30fe]+)\\s*[{\u3010]([a-zA-Z\u0101-\u014d' ]+)[}\u3011]", "g"),
cache = [],
upop;
var rubyize = function(kanji, furigana) {
var n = furigana.match(/^\s*([hlHL]+)\s*$/)
if (n) {
// add tones if HL format
var o = '',
prev=null,
n = n[1].toLowerCase()
for (var i=0; i<Math.min(n.length, kanji.length); i++) {
if (n.charAt(i) == 'h') {
if (prev==='l') {
o += '<span class="tone-h-change">'+kanji.charAt(i)+'</span>';
} else {
o += '<span class="tone-h">'+kanji.charAt(i)+'</span>';
};
} else if (n.charAt(i) == 'l') {
if (prev==='h') {
o += '<span class="tone-l-change">'+kanji.charAt(i)+'</span>';
} else {
o += '<span class="tone-l">'+kanji.charAt(i)+'</span>';
};
};
prev = n.charAt(i);
};
o += kanji.slice(i, kanji.length);
return o;
} else {
return '<ruby><rb>' + kanji + '</rb><rt>' + furigana + '</rt></ruby>';
};
};
var parse = function() {
$("span,code,p,li,b,i,a,div.excerpt").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 = htmlEscape(this.data),
data = data_escaped.replace(replaces, function ($0, $1, $2, $3, $4, $5, $6, $7, $8) {
var kanji = $1 || $3 || $5 || $7,
furigana = $2 || $4 || $6 || $8;
if (getSetting("uhideruby") == 1 && !furigana.match(/^\s*[hlHL]+\s*$/)) {
return '<span class="hiddenruby-rp" title="' + furigana + '">' + kanji + '</span>';
}
var kanjis = kanji.split(''),
furiganas = furigana.split(/[.\u3001\u3002\u30fb\uff0d-\uff0f\uff61]/);
if (kanjis.length == furiganas.length){
return $.map(kanjis, function(k, i){
return rubyize(k,furiganas[i]);
}).join('');
}
return rubyize(kanji, furigana);
});
if (data != data_escaped) {
$(this).replaceWith(data);
}
cache.push(this);
});
// 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) {
$(document.body).append('<div id="upop" class="upop">'+htmlEscape(title)+'</div>');
upop = $("#upop");
upop.attr("id", "");
upop.css({
position: "absolute",
top: e.pageY+20+'px',
left: e.pageX+20+'px'
});
}).mousemove(function(e) {
if (!upop) {
return;
}
upop.css({
top:e.pageY+20+'px',
left:e.pageX+20+'px'
});
}).mouseout(function() {
if (!upop) {
return;
}
upop.remove();
});
});
};
parse();
setInterval(parse, 400);
};
var loadSettings = function () {
try {
if (localStorage["udisableruby"] == 1) {
$("#udisableruby").attr("checked", true);
}
if (localStorage["uhideruby"] == 1) {
$("#uhideruby").attr("checked", true);
}
} catch (e) {}
};
var getSetting = function (name) {
try {
return localStorage[name];
} catch(e) {
switch(name) {
case "udisableruby":
case "uhideruby":
return 0;
}
};
};
var saveSettings = function () {
try {
localStorage["udisableruby"] = $("#udisableruby").attr("checked") ? 1 : 0;
localStorage["uhideruby"] = $("#uhideruby").attr("checked") ? 1 : 0;
location.reload();
} catch(e) {
alert("Options can only be changed when localStorage is available. Please check your browser settings.");
};
};
if ($("#footer-menu .top-footer-links").length) {
start();
} else {
var script = document.createElement("script");
script.type = "text/javascript";
script.textContent = "(" + start + ")();";
document.getElementsByTagName("head")[0].appendChild(script);
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment