/* JLSE Hacks, original version by YOU (http://japanese.stackexchange.com/users/100, see also http://stackapps.com/questions/2449/hacks-for-jlu-site) under the CC-BY-SA license, with modifications by cypher (http://japanese.stackexchange.com/users/796) This allows entering Furigana with the following syntaxes: * [漢字]{かんじ}, [漢字]【かんじ】, [漢字]{かんじ} * 漢字{かんじ}, 漢字【かんじ】, 漢字{かんじ} * ひらがな{hiragana}, ひらがな【hiragana】 (see also http://en.wikipedia.org/wiki/Furigana for information about what Furigana is). It searches backwards to allow entering Japanese words with Okurigana, for example 話す{はなす} will display "はな" on top of "話" and ignore the "す" in the default (Furigana) mode. This is useful for SEO as Google wouldn't index properly if 話{はな}す was typed. In "hide furigana texts, only show when mousing over kanji" mode, in this instance it would display a popup of "はなす" if "話す" was moused over. More complicated compound words are also supported, but you have to use [繰り返す]{くりかえす}/[繰り返す]【くりかえす】/[繰り返す]{くりかえす} syntax as normally this script only goes back to the last Kanji it can find. It does, however, search to the left of the Kanji in the case of a single "お" or "ご" before Kanji if the Furigana also contains one of those characters, for example お父さん{おとうさん} to allow for Japanese honorific prefixes (see also http://en.wikipedia.org/wiki/Honorific_speech_in_Japanese#Honorific_prefixes). It's possible to separate characters with any of the following characters: .、。・--/。 so for example 気取る{き・ど・る} or 気取る{き・ど・} will display "き" on "気", "ど" on "取" and not display anything on "る". HL format pitch tones are supported, for example ありがとう【LHLLL】 will display a red line below "あ" and a red line above "り" before falling down again for "がとう". It also: * makes sure Japanese text is displayed using a Japanese font to fix the issues in Google Chrome: https://japanese.meta.stackexchange.com/questions/1141 * makes
and tags which have Japanese text in them use a
Japanese monospace font so as to fix
https://japanese.meta.stackexchange.com/questions/1162 and
https://japanese.meta.stackexchange.com/questions/1023
* includes fixes to make ruby work with Firefox/WebKit/Blink's font adjust on mobile
(https://chat.stackexchange.com/transcript/511?m=17474056)
* removes Furigana markup from the titlebar for aesthetic reasons
* and makes IPA display with a font that can display IPA inside [[...]],
//...// or [/.../] tags as at
https://japanese.meta.stackexchange.com/questions/1287
(note that this only works in non-pre/code tags)
*/
$(function () {
var DEBUG_MODE = false, // disable this before release!
DEBUG = DEBUG_MODE ? '_dbg' : '';
//ruby mode regex
var replacesText = (
// [飛び越える]{とびこえる} syntax
'(?:#&91;|\\[)([おご]?)([^\\]]+)\\][{{【]([^}}】]+)[}}】]|' +
// 漢字{かんじ} syntax
'([おご]?)([一-ﻭ〃〄々〆〇〒]+[ぁ-ヾ]*)\\s*[{【{]([.、。ぁ-ヾ--/。<>()\\(\\)≪≫;;::!!==≡≠≒$¥?\\?&##@@“‘”’hlHL]+)[}】}]|' +
// ひらがな{hiragana} syntax (is this ever used?)
'([ぁ-ヾ]+)\\s*[{【]([a-zA-Zā-ō\' ]+)[}】]'
),
furiganaElms = 'span,code:not(.noFurigana),p,li,b,i,em,strong,a,div.excerpt,a.question-hyperlink h2, ' +
's,blockquote,del,dd,dt,kbd,sup,sub,strike,h1,h2,h3,h4,h5,h6',
replaces = new RegExp(replacesText, 'g'),
kanjiSplitText = '[..、。・--/。]',
reKanjiSplit = new RegExp(kanjiSplitText, 'g'),
// These marks are used for emphasis rather than
// Furigana, so popups aren't displayed on them by themselves
emphasisChars = '◦﹆﹅、..、。・--。',
// cache for making sure Furigana isn't processed
// on elements which have already been processed.
cache = [],
// regex for detecting Japanese text (for making sure
// Japanese text is displayed using Japanese rather than
// Chinese etc fonts).
jaRegExpText = '[一-龠]+|[ぁ-ゔ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[々〆〤、;!?「」【】『』。<>〜() ]+',
jaRegExp = new RegExp('(' + replacesText + '|' + jaRegExpText + ')+', 'g'),
// [...] and /.../ syntax for specifying IPA fonts
// This only works in between non-alphanumeric characters
// to prevent e.g. hyperlinks from using an IPA font because
// of "/" characters
isJLSE = location.href.indexOf('japanese.stackexchange') != -1,
ipaRegExpText =
// Also includes e.g. {{zh-CN:(chinese text)}} syntax
'{{([A-z\\-_]+?):(.+?)}}|' +
'({{pad}})' +
// Only use IPA syntax for JLSE, as it may
// conflict with certain anime/manga SE content
(isJLSE ? (
'|\\[\\[(.*?)\\]\\]|' +
'//(.*?)//|' +
'\\[/(.*?)/\\]'
) : ''),
ipaRegExp = new RegExp(ipaRegExpText, 'g');
var entityMap = {
"&": '&',
"<": '<',
">": '>',
'"': '"',
"'": ''',
"/": '/'
};
function escapeHtml(string) {
return String(string).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
function escapeReplace(o, re, str, fn) {
// Runs function "fn" for each match in string "str" against regular expression
// "re", while HTML escaping and outputting all non-matching text.
// "fn" is bound to object "o".
str = String(str);
re.lastIndex = 0;
var a = [],
prevIndex = 0,
match;
while (match = re.exec(str)) {
if (prevIndex != match.index) {
a.push(escapeHtml(str.slice(prevIndex, match.index)));
}
a.push(fn.apply(o, match));
prevIndex = re.lastIndex;
}
if (prevIndex != str.length) {
a.push(escapeHtml(str.slice(prevIndex, str.length)));
}
return a.join('');
}
function textNodeFilter(selector, o, fn) {
// Process any text nodes both directly contained in "o",
// as well as any text nodes in child nodes which match
// "selector"
var cn = o.childNodes,
c;
for (var i = 0; i < cn.length; i++) {
c = cn[i];
if (c.nodeType == 3 /*TEXT_NODE*/ && c.data) {
fn.apply(c, [o]);
}
}
$(selector, o).each(function () {
var parent = this;
$(this).contents().each(function () {
if (this.nodeType == 3 && this.data) {
fn.apply(this, [parent]);
}
});
});
}
// https://stackoverflow.com/questions/5560248
function shadeBlend(p, c0, c1) {
var n = p < 0 ? p * -1 : p, u = Math.round, w = parseInt;
if (c0.length > 7) {
var f = c0.split(','), t = (c1 ? c1 : p < 0 ? 'rgb(0,0,0)' : 'rgb(255,255,255)').split(','), R = w(f[0].slice(4)), G = w(f[1]), B = w(f[2]);
return 'rgb(' + (u((w(t[0].slice(4)) - R) * n) + R) + ',' + (u((w(t[1]) - G) * n) + G) + ',' + (u((w(t[2]) - B) * n) + B) + ')';
} else {
var f = w(c0.slice(1), 16), t = w((c1 ? c1 : p < 0 ? '#000000' : '#FFFFFF').slice(1), 16), R1 = f >> 16, G1 = f >> 8 & 0x00FF, B1 = f & 0x0000FF;
return '#' + (0x1000000 + (u(((t >> 16) - R1) * n) + R1) * 0x10000 + (u(((t >> 8 & 0x00FF) - G1) * n) + G1) * 0x100 + (u(((t & 0x0000FF) - B1) * n) + B1)).toString(16).slice(1);
}
}
var ruby = {
start: function () {
this.addMenu();
this.addOptionsBox();
if (this.mode == 'uDisableRubyEngine') {
// don't run if ruby disabled altogether in the "Furigana options"
return;
}
this.addEditHelp();
this.addStyles();
this.loop();
setInterval($.proxy(this.loop, this), 800);
this.resizeCheck();
$(window).resize($.proxy(this.resizeCheck, this));
this.resizeLoop();
this.fixTitle();
if ($.trim(location.pathname.replace('/', '')) == 'editing-help') {
this.addToMarkdownHelp();
}
},
addToMarkdownHelp: function () {
$('' +
'Japanese Language Extensions
' +
'' +
'This Stack Exchange site includes custom JavaScript which provides Furigana, ' +
'Japanese pitch accents and other functionality. ' +
'
' +
'See also ' +
'' +
'How should I format my questions on Japanese Language SE?' +
' for more information.' +
'
',
'').insertBefore($('.col-section').first());
},
fixTitle: function () {
// Remove Furigana in the browser's title bar
document.title = document.title.replace(replaces, function ($0, $1, $2, $3, $4, $5, $6, $7, $8, $9) {
var kanji = $2 || $5 || $7 || $9,
honorific = $1 || $4 || '';
return honorific + kanji;
});
},
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);
},
// "Furigana options"-related
// This is displayed near the bottom of the screen,
// in the nav bar with "contact us" etc in it.
addMenu: function () {
// Add the "furigana options" menu link to the nav near the bottom of the screen
if ($('#upopuphyperlink').length) {
$('#upopuphyperlink').remove();
$('#upopup').remove();
}
var addTo = $('footer ul.-menu, footer .-copyright .jc-space-between, footer nav.links'); // .footer-links for mobile
var upopupMenuHtml = 'Japanese options ▼';
addTo.append(upopupMenuHtml)
$('#upopuphyperlink').click(function () {
var a = $('#upopuphyperlink')[0];
$('#upopup').toggle();
$('#upopup').css('top', a.offsetTop - $('#upopup').height() - 15 + 'px');
$('#upopup').css('left', a.offsetLeft + 'px');
});
},
addOptionsBox: function () {
this.addCSS(
'#upopup, #utransoptions {' +
'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);}' +
'#upopup td {text-transform: none !important; padding: 0 0 4px 0 !important;}'
);
// Add "furigana options" box for changing Furigana mode/Japanese font
$('#upopup').html('');
$('#upopup').append(
'' +
'Furigana/Japanese extension help »' +
'Furigana mode
' +
'' +
'Furigana tuning
' +
'' +
'Size: ' +
'' +
'' +
' ' +
' ' +
'Lightness: ' +
'' +
'' +
' ' +
' ' +
'Width adjust: ' +
'' +
'' +
' ' +
' ' +
'Popups: ' +
'' +
'' +
' ' +
'
' +
'Japanese font
' +
'' +
' ' +
'' +
''
).find('div').css(
{ 'color': '#000', 'textAlign': 'left', 'lineHeight': '1.9em' }
).find('a').css(
{ 'color': 'blue', 'margin': 0 }
);
$.each(['(automatic)'].concat(this.jaFonts), function () {
var o = document.createElement('option');
o.value = this;
o.innerHTML = this;
$('#uJFontSelect').append(o);
}); // restore previous settings/register save settings event
this.loadSettings();
$('#usave').click($.proxy(this.saveSettings, this));
},
loadSettings: function () {
// Load which mode (e.g. whether to use Furigana or popup on mouseover)
var allowedModes = $('input[name=uFuriganaMode]').map(function () {
return this.id;
});
this.mode = this.getStorageKey('uFuriganaMode', allowedModes, 'uTopAlignRuby');
this.font = this.getStorageKey('uJapaneseFont', this.jaFonts, '(automatic)');
this.fontSize = this.getStorageKey('uRubyFontSize', ['big', 'small', 'medium'], 'medium');
this.fontLightness = this.getStorageKey('uRubyFontLightness', ['heavy', 'medium', 'light'], 'heavy');
this.fontShrink = this.getStorageKey('uRubyFontShrink', ['low', 'medium', 'high', 'noshrink'], 'medium');
this.rubyPopups = this.getStorageKey('uRubyPopups', ['nopopups', 'popups', 'nofade'], 'popups');
$('#' + this.mode).prop('checked', true);
$('#uJFontSelect').val(this.font);
$('#uRubyFontSize').val(this.fontSize);
$('#uRubyFontLightness').val(this.fontLightness);
$('#uRubyFontShrink').val(this.fontShrink);
$('#uRubyPopups').val(this.rubyPopups);
},
saveSettings: function () {
try {
localStorage['uFuriganaMode' + DEBUG] = $('input[name=uFuriganaMode]:checked', '#uFuriganaModeForm').attr('id');
localStorage['uJapaneseFont' + DEBUG] = $('#uJFontSelect').val();
localStorage['uRubyFontSize' + DEBUG] = $('#uRubyFontSize').val();
localStorage['uRubyFontLightness' + DEBUG] = $('#uRubyFontLightness').val();
localStorage['uRubyFontShrink' + DEBUG] = $('#uRubyFontShrink').val();
localStorage['uRubyPopups' + DEBUG] = $('#uRubyPopups').val();
location.reload();
} catch (e) {
alert('Options can only be changed when localStorage is available. Please check your browser settings.');
};
},
getStorageKey: function (key, allowedValues, defolt) {
try {
var value = localStorage[key + DEBUG];
return ($.inArray(value, allowedValues) !== -1) ? value : defolt;
} catch (e) {
return defolt;
}
},
// Editor help for at http://japanese.stackexchange.com/questions/ask etc
addEditHelp: function () {
if (isJLSE) {
// Add to the editor help but only for the Japanese stack
// exchange, as it probably isn't as relevant for anime.se
$('► Furigana ' +
'感じる【かんじる】
' +
'or 感じる{かんじる}
' +
'or [飛び越える]{とびこえる}
').insertBefore('#how-to-format p.ar');
$('► pitch accents ' +
'ありがとう【LHLLL】
' +
'or ありがとう{LHLLL}
').insertBefore('#how-to-format p.ar');
/*$('► IPA text ' +
'[[...]]
' +
'or //...//
' +
'or [/.../]
').insertBefore("#how-to-format p.ar");*/
$('► language ' +
'{{zh-CN:中文}}
').insertBefore('#how-to-format p.ar');
} else {
// Add basic, simplified editor help, including romaji, for anime/
// manga SE, as the audience there is very different+the extensions
// probably aren't useful there as often
$('► Furigana ' +
'感じる【かんじる】
or 感じる{かんじる}
' +
'or [感じる]{kanjiru}
').insertBefore('#how-to-format p.ar');
}
$('' +
'Furigana/Japanese extension help »
').appendTo('#how-to-format');
},
// Make Japanese display with Japanese fonts
jaFonts: [
'IPAGothic', 'IPAゴシック',
'TakaoGothic',
'VL Gothic', 'VLゴシック',
'UmePlus Gothic',
'Ume Gothic', '梅ゴシック',
'MotoyaLCedar',
'MigMix 2M',
'Migu 2M',
'Hiragino Kaku Gothic Pro', 'ヒラギノ角ゴ Pro W3',
'HiraKakuProN-W3', 'ヒラギノ角ゴ ProN W3',
'Osaka Mono',
'Osaka',
'MS Gothic', 'MS ゴシック',
//'Meiryo UI',
'Meiryo', 'メイリオ',
//'Sazanami Gothic', 'さざなみゴシック',
'monospace'
],
fontsToCSS: function (fonts) {
var userPref = this.font == '(automatic)' ? [] : [this.font];
return $.map(userPref.concat(this.jaFonts), function (font) {
return ruby.escapeCSS(font);
}).join(',');
},
escapeCSS: function (font) {
if (font == 'monospace') {
return 'monospace';
}
return '"' + font.replace(/"/g, '\\"') + '"';
},
makeJaFonts: function (inElm) {
this.makeIPAFonts(inElm);
// Make Japanese use Japanese fonts
textNodeFilter(furiganaElms, inElm, function () {
if ($(this.parentNode).hasClass('ja-text') || !this.data.match(jaRegExp)) {
return;
}
function onReplace(x) {
// lang attributes currently seem to be necessary
// for Chrome OS to display Japanese as monospace
// https://code.google.com/p/chromium/issues/detail?id=339317
return '' + escapeHtml(x) + '';
}
if (this.parentNode.childNodes.length == 1 && !String(this.data).replace(jaRegExp, '')) {
// If an element only has Japanese text, add the Japanese class to it
ruby.makeJaFont(this.parentNode);
} else if (!this.parentNode.lang && !$(this.parentNode).hasClass('IPA')) {
// Otherwise, create a new element with the Japanese ranges
var replaceWith = escapeReplace(this, jaRegExp, this.data, onReplace);
$(this).replaceWith(replaceWith);
}
});
$('pre:not(.ja-text):not(.noFurigana),code:not(.ja-text):not(.noFurigana)', inElm).each(function () {
if ($(this).text().match(jaRegExp)) {
// Only make pre/code use a Japanese font if they have Japanese text
// so that Latin linguistic explanations/English qa on Anime.SE don't
// suffer readability-wise
ruby.makeJaFont(this);
} else {
$(this).addClass('noFurigana');
}
});
},
makeJaFont: function (e) {
if (e.lang) {
return;
} else if ($(e).hasClass('IPA')) {
return;
}
e['xml:lang'] = 'ja';
e.lang = 'ja';
$(e).addClass('ja-text');
},
makeIPAFonts: function (inElm) {
// Make IPA use fonts which can display IPA
textNodeFilter(furiganaElms, inElm, function () {
if ($(this.parentNode).hasClass('IPA') || !this.data.match(ipaRegExp)) {
return;
}
var that = this;
function onReplace(a, langCode, langText, pad, pat1, pat2, pat3) {
if (pat1) {
return '[' + escapeHtml(pat1) + ']';
} else if (pat2) {
return '/' + escapeHtml(pat2) + '/';
} else if (pat3) {
return '' + escapeHtml(pat3) + '';
} else if (langCode || langText) {
if (langCode && langText) {
return '' + escapeHtml(langText) + '';
}
return '';
} else if (pad && ruby.mode in { 'uTopAlignRuby': 0, 'uBottomAlignRuby': 0 }) {
var el = that.parentNode;
console.log(el);
while (true) {
if (!el || el == document.body) {
break;
} else if ($(el).css('display') == 'block') {
// Find the nearest block-level element, and
// make it the tall ("padded") line-height
// This is preferable at e.g.
// http://japanese.stackexchange.com/questions/25549
// to prevent disruption amongst a sea of Japanese text
$(el).addClass('lh');
//console.log(el)
break;
}
el = el.parentNode;
//alert(el);
}
}
return '';
}
var replaceWith = escapeReplace(this, ipaRegExp, this.data, onReplace);
$(this).replaceWith(replaceWith);
});
},
resizeLoop: function () {
setInterval(function () {
ruby.resizeCheck();
}, 300);
},
width: -1,
height: -1,
resizeCheck: function () {
// Only do a relayout if window size changed
var $window = $(window);
if ((this.width != $window.width()) || (this.height != $window.height())) {
this.width = $window.width();
this.height = $window.height();
//console.log(this.width + ' ' + this.height);
this.highlightFurigana(null, true);
/*setTimeout($.proxy(function() {
this.highlightFurigana(null, true);
}, this), 0);*/
}
},
resizeText: function (inElm) {
// Some mobile browsers based on WebKit/Blink (e.g. Android Chrome)
// adjust the text size, but don't adjust the size of ruby.
if (!(/\bMobile\b|\bAndroid\b|\biPhone\b|\biPad\b/.test(navigator.userAgent))) {
// Problems can happen on e.g. Firefox/Windows with Meiryo
// if used on the desktop, so only enable for Mobile.
return;
}
function getFontSize(e) {
// Reduce rounding issues with font-size
// if font-size returned as a px value
//
// Still has issues with Meiryo/Firefox on Windows(!)
// e.g. Meiryo text (also incorrectly) computed as 19px font-size from
// textHeight will become 25px computed font-size if "font-size: 19px"
// is set
//console.log(e.css('fontSize'))
if (e.css('fontSize').indexOf('px') != -1) {
return parseFloat(e.css('fontSize'));
}
return e.offsetHeight;
}
(inElm ? $('ruby', inElm) : $('ruby')).each(function () {
$('あ').insertBefore($(this));
$(this).css('fontSize', $(this.parentNode).css('fontSize'));
var measureText = $('#measureText'),
ruby = $(this).find('rb, span.rb'),
textHeight = getFontSize(measureText),
rubyHeight = getFontSize(ruby);
//measureText[0].id = '';
measureText.remove();
if (rubyHeight < (textHeight * 1.4)) {
// Ruby text should be similar to the
// surrounding text, so assume the height
// is incorrect and update with a px value.
$(this).css('fontSize', textHeight + 'px');
}
});
},
// Ruby (Furigana) and Japanese font CSS
addStyles: function () {
var bottomAlign = ruby.mode == 'uBottomAlignRuby';
var alignCSS = bottomAlign ?
'rt,ruby,rb,span.rt,span.rb{text-align:center; ruby-align:center; ruby-position:under; -webkit-ruby-position: after;}' :
'rt,ruby,rb,span.rt,span.rb{text-align:center; ruby-align:center; ruby-position:over; -webkit-ruby-position: before;}';
var fontSize = {
big: '0.9em',
medium: '0.75em',
small: '0.6em'
}[this.fontSize];
var padding = {
big: 1.25,
medium: 0.95,
small: 0.83
}[this.fontSize];
var rtPos = bottomAlign ? ('bottom: 0;') : ('top: 0;'),
rubyPadding = bottomAlign ? ('padding-bottom: ' + padding + 'em;') : ('padding-top: ' + padding + 'em;'),
lhLineHeight = bottomAlign ? ((padding + 0.2) + 1.0 + 'em;') : ((padding + 0.2) + 1.0 + 'em;'),
lhPos = bottomAlign ? ('bottom: -1.15em;') : ('top: -1.15em;'),
borderColor = bottomAlign ? 'border-bottom-color: rgba(0, 0, 0, 0);' : 'border-top-color: rgba(0, 0, 0, 0);';
this.addCSS(
'ruby{' +
'display:inline-block; text-indent:0; white-space:nowrap;' +
'line-height:1em; position:relative; vertical-align:baseline;' +
'margin:-1px; border: 1px solid transparent;}' +
'ruby:before, ruby:after {' +
// Suggest word wrap before/after tags
'content: "\\00200B"}' +
'rb,span.rb{' +
'display:inline-block; line-height:1em; height:1em; font-size:1em; border:none;' +
'margin:0; padding:0; white-space:nowrap; ' + rubyPadding + '}' +
'rt,span.rt{' +
'position:absolute; display:block; font-size:' + fontSize + '; line-height:1.3em; height:1.3em;' +
'white-space:nowrap; border:none; margin:0; padding:0; ' + rtPos + '}' +
// "padded" line-height to make sure text at e,g,
// http://japanese.stackexchange.com/questions/25549
// isn't too crowded. Applied in "makeIPAFonts" above
// I'm afraid this is not going to be fool-proof in some parent-child element relationships
// and may have rendering issues, but I'm not sure I can do much about this.
// {{pad}} should probably be used sparingly!
'.lh, .lh>span, .lh>code, .lh>li, .lh>ul>li, .lh>ol>li, .lh>dl>dd, .lh>dl>dt, .lh>dt, .lh>dd, ' +
'.lh>a, .lh>b, .lh>i, .lh>em, .lh>s, .lh>strong, .lh>del, .lh>kbd {line-height: ' + lhLineHeight + ' !important;}' +
'.lh rb,.lh span.rb{padding: 0;}' +
'.lh rt,.lh span.rt{' + lhPos + '}' +
alignCSS
);
this.addCSS(
//':not(a) ruby:hover rt {color: black !important; text-shadow: none !important;}'+
'span.hiddenruby.hover, span.hiddenruby-rp.hover, ruby.popups.hover{' +
'border:1px solid #ccc; border-radius:0 0 2px 2px; background:white;' +
'transition: background 200ms;' + borderColor + '}' +
'ruby.popups.hover rt, ruby.popups.hover span.rt{' +
'visibility: hidden;}' +
// for "popups" mode only
'span.hiddenruby, span.hiddenruby-rp{' +
'border: 1px solid transparent; border-bottom:2px dotted rgba(30, 50, 30, 0.25); ' +
'cursor:default; white-space:nowrap; margin:-1px;}' +
'span.hiddenruby.hover, span.hiddenruby-rp.hover{' +
'border-bottom-width: 2px !important;}' +
// the CSS for the popups themselves
'#upop .line,.upop .line{' +
'height:1px; overflow:hidden; position:absolute; bottom:-1px; background: white;}' +
'#upop,.upop{' +
'position:relative; padding:3px 5px; font-size:1.32em; text-align:center;' +
'border-radius:3px; border:1px solid #ccc; white-space:nowrap; cursor:default; color: rgba(0, 0, 0, 0.0);}' +
'#upop.transition,.upop.transition {' +
'background: white; transition: background 200ms; color: black;}' +
// Japanese tones
'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;}' +
// font-related
(this.font == '(automatic)' ? ''
: '.ja-text{font-family: ' + this.escapeCSS(this.font) + ' !important;}') +
'code .ja-text, pre .ja-text, code.ja-text, pre.ja-text {' +
'font-family: ' + this.fontsToCSS(this.jaFonts) + ' !important;}' + // font-size: 14px;
'.IPA{font-family: Andika, Doulos SIL, Gentium, ' +
'GentiumAlt, Segoe UI, DejaVu Sans, Bitstream Vera Sans, TITUS Cyberbit Basic, ' +
'Bitstream Cyberbit,' +
'Arial Unicode MS, Lucida Sans Unicode, Code2000,' +
'Hiragino Kaku Gothic Pro, Matrix Unicode, Chrysanthi Unicode;}'
);
},
// Main loop which is run periodically to scan for changes
loop: function () {
if (isJLSE) {
$('.draft-saved').each(function () {
// Add "furigana help" links
if (!this.editHelpAdded) {
$('').insertBefore(
this
);
this.editHelpAdded = true;
}
});
}
$('.js-post-body, .question-hyperlink, .module sidebar-related, ' +
'.excerpt, .answer-hyperlink, .summary, .question-summary, ' +
'.answer-summary, .question-title, .answer-title, .top-detail, ' +
'.js-wmd-preview, #question-header, .comment-text, .comment-body, ' +
'.single-badge-summary, .lines, .async-load, .history-table').each(function () {
// Only process elements once
if (this.firstChild && !this.firstChild.furiganaDone) {
ruby._loop(this);
}
if (this.firstChild && !$(this).hasClass('js-wmd-preview')) {
this.firstChild.furiganaDone = true;
}
});
$('input[type=text], textarea').each(function () {
// Use Japanese fonts for textareas/text inputs
// This is needed here, as textareas are often created
// after the page is loaded in SE's JavaScript
if (!this.jaFont) {
this.jaFont = true;
ruby.makeJaFont(this);
}
});
},
_loop: function (inElm) {
this.translationTags(inElm);
this.makeJaFonts(inElm);
function isHotNetwork(e) {
// prevent Furigana displaying in e.g. math
// stack exchange links in "hot network" links
try {
return $.trim(e.parentNode.parentNode.parentNode.parentNode.parentNode.id) == 'hot-network-questions';
} catch (a) {
}
return false;
}
// a.question-hyperlink h2 is for hyperlinks on mobile.
// div.excerpt is for browsing through question summaries.
textNodeFilter(furiganaElms, inElm, function () {
if (!(this.nodeType == 3 &&
!isHotNetwork(this) &&
this.data && this.data.match(replaces))) {
return;
}
for (var i = 0; i < cache.length; i++) {
if (cache[i] == this && true) {
return;
}
}
var changed_data = escapeReplace(this, replaces, this.data, function ($0, $1, $2, $3, $4, $5, $6, $7, $8) {
var kanji = $2 || $5 || $7,
furigana = $3 || $6 || $8,
honorific = $1 || $4 || '',
isPitchAccent = furigana.match(/^\s*[hlHL]+\s*$/);
if (kanji.length > 30 || furigana.length > 50) {
// Don't allow really long Furigana, as it can upset display
return '[' + escapeHtml(honorific || '') +
escapeHtml(kanji || '') + ']' +
'【' + escapeHtml(furigana || '') + '】';
}
// Support for going '﹅﹅' instead of having to use
// '﹅・﹅' every time for emphasis characters
var furiganas = furigana.split(reKanjiSplit),
isEmphasis = false,
emphChars;
for (var i = 0; i < emphasisChars.length; i++) {
emphChars = Array(kanji.length + 1).join(emphasisChars.charAt(i));
//console.log(furiganas.join('')+' '+furigana+' '+kanji)
if (furiganas.join('') == emphChars || furigana == emphChars) {
isEmphasis = true;
break;
}
}
if (!isPitchAccent && !isEmphasis) {
// Only allow Furigana for emphasis characters/
// LH pitch accents if in popups/disable ruby mode.
if (ruby.mode == 'uMouseOver') {
return '' + escapeHtml(kanji) + '';
} else if (ruby.mode == 'uDisableRuby' && !isPitchAccent && !isEmphasis) {
return escapeHtml(kanji);
}
}
var kanjis = kanji.split('');
furiganas = isEmphasis ? emphChars.split('') : furiganas;
//console.log(kanjis+' '+furiganas);
if (kanjis.length == furiganas.length) {
return $.map(kanjis, function (k, i) {
if (furiganas[i] && k != furiganas[i]) {
return ruby.rubyize((!i) ? honorific : '', k, furiganas[i]);
} else {
// Workaround for http://japanese.stackexchange.com/questions/18857/
// Makes it so that e.g. 目覚め{め・ざ・} works
return escapeHtml(k);
}
}).join('');
}
return ruby.rubyize(honorific, kanji, furigana);
});
if (this.data != changed_data) {
var parent = this.parentNode;
$(this).replaceWith(changed_data);
// performance warning
ruby.highlightFurigana(parent);
}
cache.push(this);
});
if (this.mode == 'uMouseOver') {
this.replaceTitleAttrs();
}
},
highlightFurigana: function (inElm, force) {
var fontLightness = {
heavy: 0,
medium: 0.2,
light: 0.4
}[this.fontLightness];
$('ruby > span.rt,ruby > rt', inElm).each(function () {
if (!this.highlighted) {
this.highlighted = true;
$(this).css('text-shadow', '0 0 1px rgba(0, 255, 0, 0.12');
$(this).css('color', shadeBlend(fontLightness, $(this).css('color')));
}
});
ruby.resizeText(inElm);
var fontReflow = [];
$('ruby > span.rt,ruby > rt', inElm).each(function () {
if (!this.resized || force) {
var rb = $('> span.rb, > rb', this.parentNode)[0];
this.resized = true;
var rtX = this.offsetWidth, minX = this.offsetWidth;
if (ruby.fontShrink != 'noshrink') {
// When using CSS transforms to stretch the ruby, the reported
// and actual widths are different and need to be adjusted.
this.parentNode.style.minWidth = 0;
minX = ruby.shrinkFurigana(this);
}
var contX = rb.offsetWidth;
this.parentNode.style.minWidth = minX + 'px';
if (contX > rtX) {
this.style.left = (contX / 2.0) - (rtX / 2.0) + 'px';
} else {
this.style.left = 0;
}
if (this.parentNode.title) {
if (
ruby.rubyPopups != 'nopopups' &&
(emphasisChars.indexOf($.trim(this.parentNode.title)) == -1)) {
ruby.bindPopup(this.parentNode, this.parentNode.title);
}
this.parentNode.title = '';
}
}
var elm = this.parentNode.parentNode;
if (fontReflow.indexOf(elm) == -1) {
fontReflow.push(elm);
}
});
// Android Chrome font reflow hack. This is here due to rendering
// bugs in Chrome Android (and possibly other browsers/OSes)
// I've tried various other things, some at
// https://stackoverflow.com/questions/3485365:
// * set z-index/padding/display: none/scale/translateZ/line-height/font-size,
// get offset* to force relayout, reset to original
// * remove element, get offset*, re-add
// * add and remove a