Files
nazeka-chromium/texthook.js
T

1441 lines
51 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2017; Licensed under the Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0
/*
* TODO:
*
* ! Finish porting the underlay's deconjugation rules
* - Add an option for "hide unfitting senses/spellings/readings" vs "show everything but with details about what can be what"
* - Fix katakana-hiragana matching (only happens in lookups right now, not display generation)
* - More configurability
* - VNstats frequency data
* - Integrate frequency into priority handling
* - Load deconjugation rules from an advanced setting, with a reset button
* - Mining support features
* ? Export/import settings with json
* ? List definitions of shorter text strings if they're not pure kana (in addition to main definitions)
*
*/
'use strict';
// updated by a timer looping function, based on local storage set by the options page
let settings = {
enabled: false,
compact: true,
usetextfields: true,
length: 25,
fixedwidth: false,
fixedwidthpositioning: false,
superborder: false,
showoriginal: true,
alternatives_mode: 0, // 0: longest only; 1: longest and shortest; 2: longest and second longest; 3: all matches
strict_alternatives: true, // if true, alternatives looked up in all kana can not return results with kanji glosses that don't have any usually/exclusively kana info
scale: 1,
width: 600,
lookuprate: 8,
bgcolor: "#111111",
fgcolor: "#CCCCCC",
hlcolor: "#99DDFF",
font: "",
hlfont: "",
corner: 0,
xoffset: 5,
yoffset: 22,
ignore_linebreaks: false,
sticky: false
};
let platform = "win";
async function get_real_platform()
{
let my_platform = await browser.runtime.sendMessage({type:"platform"});
while(my_platform == "")
my_platform = await browser.runtime.sendMessage({type:"platform"});
platform = my_platform;
}
get_real_platform();
let last_time_display = Date.now();
// FIXME: this should be the extension ID or a per-page random string, but it's not useful to change this yet, not until firefox lands shadow DOM
let div_class = "nazeka_fGKRTDGFGgr9atT";
// the popup is inserted into the page as a screen-relative div, we never delete it because modifying the root DOM of very long pages is expensive, we hide it instead
let last_displayed = undefined;
function delete_div()
{
last_displayed = undefined;
let other = document.body.getElementsByClassName(div_class);
if(other.length > 0)
{
other[0].style.display = "none";
if(other[0].children.length > 0)
other[0].children[0].innerHTML = "";
}
}
function set_sticky_styles(mydiv)
{
mydiv.style.position = "fixed";
mydiv.style.left = "unset";
mydiv.style.right= "unset";
mydiv.style.bottom = "unset";
mydiv.style.top = "10px";
if(settings.corner != 1 && settings.corner != 3)
mydiv.style.right = "10px";
else
mydiv.style.left = "10px";
mydiv.style.marginRight = "unset";
mydiv.style.maxHeight = "calc(100vh - 20px)";
mydiv.style.overflowY = "scroll";
mydiv.style.marginLeft = "unset";
mydiv.style.marginTop = "unset";
mydiv.style.marginBottom = "10px";
}
// here we set all the styling and positioning of the div, passing "middle" as the actual contents of it.
// this is rather elaborate because of 1) a lack of shadow DOM, even for just styling 2) options 3) """features""" of how HTML viewport stuff works that are actually terrible
function display_div(middle, x, y, time)
{
let font = settings.font.trim().replace(";","").replace("}","");
if(font != "")
font += ",";
middle.style = `background-color: ${settings.bgcolor}; border-radius: 2.5px; border: 1px solid ${settings.bgcolor};`;
middle.firstChild.style = `border: 1px solid ${settings.fgcolor}; border-radius: 2px; padding: 2px; background-color: ${settings.bgcolor}; color: ${settings.fgcolor}; font-family: ${font} Arial, sans-serif; font-size: 13px; text-align: left;`;
let find_root = window;
let newx = x;
let newy = y;
while(find_root.parent && find_root.parent != find_root)
{
let rect = find_root.frameElement.getBoundingClientRect();
let sx = find_root.scrollX;
let sy = find_root.scrollY;
find_root = find_root.parent;
newx += (rect.x - sx);
newy += (rect.y - sy);
}
let mydoc = find_root.document;
let styletext = "";
if(settings.fixedwidth)
{
styletext += "max-width: " + Math.round(Number(settings.width)) + "px; "
styletext += "min-width: " + Math.round(Number(settings.width)) + "px; "
}
else
{
styletext += "max-width: " + Math.round(Number(settings.width)) + "px; "
styletext += "min-width: 150px; "
}
styletext += "position: absolute; top: 0; left: 0; ";
if(settings.superborder)
styletext += `background-color: ${settings.fgcolor}; border-radius: 3px; border: 1px solid ${settings.fgcolor}; z-index: 100000;`;
else
styletext += `border-radius: 3px; background-color: ${settings.bgcolor}; z-index: 100000;`;
styletext += "writing-mode: horizontal-tb; line-height: initial; white-space: initial;";
let other = mydoc.body.getElementsByClassName(div_class);
let outer = undefined;
if(other.length > 0)
{
outer = other[0];
outer.replaceChild(middle, other[0].firstChild);
outer.style.display = "block";
}
else
{
outer = mydoc.createElement("div");
outer.className = div_class;
outer.appendChild(middle);
mydoc.body.appendChild(outer);
}
outer.lang = "ja";
if(settings.scale != 1 && settings.scale > 0 && settings.scale < 64)
styletext += " transform-origin: top left; transform: scale(" + Number(settings.scale) + ");";
outer.style = styletext;
let mywidth = settings.width*settings.scale;
if(!settings.fixedwidthpositioning)
mywidth = outer.offsetWidth*settings.scale;
if(settings.sticky && platform != "android" && document.body.getElementsByClassName("nazeka_mining_ui").length == 0)
set_sticky_styles(outer);
else
{
let corner = settings.corner;
let buffer = 25;
if(corner != 1 && corner != 3)
{
let pretend_doc_width = Math.max(mywidth, mydoc.defaultView.innerWidth) + mydoc.defaultView.scrollX;
if(newx + mywidth > pretend_doc_width-buffer)
{
if(corner == 4)// && Math.abs(newx + mywidth - pretend_doc_width) > mywidth/2)
corner = 1;
else
{
newx -= (newx + mywidth - pretend_doc_width);
newx -= buffer;
if(newx < mydoc.defaultView.scrollX)
newx = mydoc.defaultView.scrollX;
}
}
}
if(corner == 1 || corner == 3) // don't use else here, we might get here because of "corner = 1" a few lines above
{
let pretend_doc_width = Math.max(mywidth, mydoc.defaultView.innerWidth) - mydoc.defaultView.scrollX;
newx = mydoc.defaultView.innerWidth - newx;
if(newx + mywidth > pretend_doc_width-buffer)
{
newx += (newx + mywidth - pretend_doc_width);
newx += buffer;
// this is wrong but it can't be fixed without invoking cthulhu
if(newx > mydoc.defaultView.innerWidth-mydoc.defaultView.scrollX-mywidth-buffer+5)
newx = mydoc.defaultView.innerWidth-mydoc.defaultView.scrollX-mywidth-buffer+5;
}
}
if(corner == 2 || corner == 3)
{
newy = mydoc.defaultView.innerHeight - newy;
if(newy < 0)
newy = 0;
}
if(corner == 3)
{
outer.style.top = "unset";
outer.style.bottom = (newy+settings.yoffset)+"px";
outer.style.right = (newx-settings.xoffset)+"px";
outer.style.left = "unset";
outer.style.marginRight = "unset";
outer.style.marginLeft = "unset";
}
else if(corner == 2)
{
// fixes an absolute positioning """feature""" that doesn't work properly with very wide pages (e.g. pages of left-to-right vertical text)
outer.style.top = "0px";
outer.style.left = "0px";
let width = outer.offsetWidth;
outer.style.top = "unset";
outer.style.bottom = (newy+settings.yoffset)+"px";
outer.style.right = "unset";
outer.style.left = (newx+settings.xoffset)+"px";
outer.style.marginRight = "unset";
outer.style.marginLeft = "unset";
// fixes an absolute positioning """feature""" that doesn't work properly with very wide pages (e.g. pages of left-to-right vertical text)
outer.style.width = (width)+"px";
}
else if(corner == 1)
{
outer.style.top = (newy+settings.yoffset)+"px";
outer.style.bottom = "unset";
outer.style.right = (newx-settings.xoffset)+"px";
outer.style.left = "unset";
outer.style.marginRight = "unset";
outer.style.marginLeft = "unset";
}
else // 0 or invalid
{
// fixes an absolute positioning """feature""" that doesn't work properly with very wide pages (e.g. pages of left-to-right vertical text)
outer.style.top = "0px";
outer.style.left = "0px";
let width = outer.offsetWidth;
outer.style.top = (newy+settings.yoffset)+"px";
outer.style.bottom = "unset";
outer.style.right = "unset";
outer.style.left = (newx+settings.xoffset)+"px";
outer.style.marginRight = "unset";
outer.style.marginLeft = "unset";
// fixes an absolute positioning """feature""" that doesn't work properly with very wide pages (e.g. pages of left-to-right vertical text)
outer.style.width = (width)+"px";
}
}
}
function exists_div()
{
let find_root = window;
while(find_root.parent && find_root.parent != find_root)
find_root = find_root.parent;
let mydoc = find_root.document;
let other = document.body.getElementsByClassName(div_class);
return (other.length > 0 && other[0].style.display != "none");
}
// In JMdict, part-of-speech tags are XML entities.
// We processed JMdict's XML with entity processing disabled so we can just use the bare tags (e.g. "v1", not "ichidan verb").
// This left the entity syntax &this; behind so we want to cut it off when we return results.
function clip(str)
{
if(str == undefined)
return;
if(str.length <= 2)
return "";
return str.substring(1, str.length-1);
}
// Here we actually build the content of the lookup popup, based on the text we looked up and the list of lookups from the background script
// note that we can get multiple lookups and this function only handles a single lookup
function build_div_inner (text, result, moreText, index, first_of_many = false)
{
//console.log("building div for " + text);
//console.log(result);
let temp = document.createElement("div");
temp.style.position = "relative";
// the "Looked up XXXX" text
if(settings.showoriginal)
{
// nesting it like this lets us access nazeka_lookup when we want to get the text we looked up, if we have text in front of it, which we don't anymore
let original = document.createElement("div");
original.className = "nazeka_original";
//original.textContent = "Looked up ";
let moreText_start = moreText.substring(0, index)
let moreText_end = moreText.substring(index + text.length);
if(moreText_start.length > 5)
{
moreText_start = "…"+moreText_start.substring(moreText_start.length-3);
}
if(moreText_end.length > 5)
{
moreText_end = moreText_end.substring(0, 3)+"…";
}
//console.log(moreText);
//console.log(index);
//console.log(text);
let original_inner = document.createElement("span");
original_inner.className = "nazeka_lookup";
original_inner.textContent = text;
original_inner.style.fontWeight = "bold";
original_inner.style.color = `${settings.hlcolor}`;
original.appendChild(document.createTextNode(moreText_start));
original.appendChild(original_inner);
original.appendChild(document.createTextNode(moreText_end));
if(first_of_many && (platform == "android" || (settings.sticky && document.body.getElementsByClassName("nazeka_mining_ui").length == 0)))
{
let buttons = document.createElement("div");
let left_arrow = document.createElement("img");
let right_arrow = document.createElement("img");
left_arrow.src = browser.extension.getURL("img/leftarrow24.png");
right_arrow.src = browser.extension.getURL("img/rightarrow24.png");
left_arrow.onclick = lookup_left;
right_arrow.onclick = lookup_right;
left_arrow.style.marginTop = "-3px";
right_arrow.style.marginTop = "-3px";
buttons.appendChild(left_arrow);
buttons.appendChild(right_arrow);
if(platform == "android")
{
let close_button = document.createElement("img");
close_button.src = browser.extension.getURL("img/closebutton24.png");
close_button.onclick = manual_close;
close_button.style.marginTop = "-3px";
buttons.appendChild(close_button);
}
buttons.style.float = "right";
original.appendChild(buttons);
}
temp.appendChild(original);
let original_sentence = document.createElement("span");
original_sentence.className = "nazeka_lookup_sentence";
original_sentence.textContent = moreText;
original_sentence.style.display = "none";
temp.appendChild(original_sentence);
let original_index = document.createElement("span");
original_index.className = "nazeka_lookup_index";
original_index.textContent = `${index}`;
original_index.style.display = "none";
temp.appendChild(original_index);
}
else
{
let original_inner = document.createElement("span");
original_inner.className = "nazeka_lookup";
original_inner.textContent = text;
original_inner.style.display = "none";
temp.appendChild(original_inner);
let original_sentence = document.createElement("span");
original_sentence.className = "nazeka_lookup_sentence";
original_sentence.textContent = moreText;
original_sentence.style.display = "none";
temp.appendChild(original_sentence);
let original_index = document.createElement("span");
original_index.className = "nazeka_lookup_index";
original_index.textContent = `${index}`;
original_index.style.display = "none";
temp.appendChild(original_index);
}
// styling for highlighted stuff and the lookup text
let style = document.createElement("style");
style.type = "text/css";
let font = settings.hlfont.trim().replace(";","").replace("}","");
if(font != "")
font += ",";
if(settings.font.trim().replace(";","").replace("}","") != "")
font += settings.font.trim().replace(";","").replace("}","") + ",";
style.textContent =
`.nazeka_main_keb{font-family: ${font}IPAGothic,TakaoGothic,Noto Sans CJK JP Regular,Meiryo,sans-serif;font-size:18px;color:${settings.hlcolor}}\
.nazeka_main_reb{font-family: ${font}IPAGothic,TakaoGothic,Noto Sans CJK JP Regular,Meiryo,sans-serif;font-size:18px;color:${settings.hlcolor}}\
.nazeka_original{float: right; margin-right: 2px; margin-left:4px; opacity:0.7;}\
.epwing_head{margin: 6px 8px 4px;}
.epwing_definition{margin: 0 8px;}\
`;
temp.appendChild(style);
// lookups can have multiple results (e.g. する -> 為る, 刷る, 掏る, 剃る, 擦る)
for(let i = 0; i < result.length; i++)
{
let term = result[i];
let container = document.createElement("div");
container.className = "nazeka_word_container";
container.style.marginBottom = "3px";
container.setAttribute("nazeka_seq", term.seq);
let text = "";
if(term.found.keb)
text = term.found.keb;
else if(term.found.reb)
text = term.found.reb;
else
continue; // shouldn't happen, but again, just in case of broken data
let temptag = document.createElement("span");
temptag.className = "nazeka_word";
let original_kana = "";
if(term.found.reb)
original_kana = term.found.reb;
if(term.found.keb)
{
let k_ele = term.found;
let kanji_text = k_ele.keb;
let keb = document.createElement("span");
keb.className = "nazeka_main_keb";
keb.textContent = kanji_text;
temptag.appendChild(keb);
// deconjugations
if(term.deconj)
{
//console.log(term.deconj);
let deconj = "";
let first = true;
for(let form of term.deconj)
{
let formtext = "";
let added = 0;
for(let f = form.process.length-1; f >= 0; f--)
{
let info = form.process[f];
if(info == "")
continue;
if(info.startsWith("(") && info.endsWith(")") && f != 0)
continue;
if(added > 0)
formtext += "―";
added++;
formtext += info;
}
if(formtext != "")
{
if(first)
deconj += "~";
else
deconj += "・";
deconj += formtext;
}
first = false;
}
temptag.appendChild(document.createTextNode(deconj));
}
if(k_ele.inf)
{
let maininfos = document.createElement("span");
maininfos.className = "nazeka_main_infos";
for(let info of k_ele.inf)
{
let keb_inf = document.createElement("span");
keb_inf.className = "nazeka_main_inf";
keb_inf.textContent += "(";
keb_inf.textContent += clip(info);
keb_inf.textContent += ")";
maininfos.appendChild(keb_inf);
}
temptag.appendChild(maininfos);
}
// list readings
let readings = term.r_ele;
temptag.appendChild(document.createTextNode(" 《"));
let e_readings = document.createElement("span");
e_readings.className = "nazeka_readings";
for(let j = 0; j < readings.length; j++)
{
e_readings.appendChild(document.createTextNode(readings[j].reb));
if(readings[j].inf)
{
for(let info of readings[j].inf)
{
let reb_inf = document.createElement("span");
reb_inf.className = "nazeka_reb_inf";
reb_inf.textContent += "(";
reb_inf.textContent += clip(info);
reb_inf.textContent += ")";
e_readings.appendChild(reb_inf);
}
}
if(j+1 != readings.length)
e_readings.appendChild(document.createTextNode("・"));
}
temptag.appendChild(e_readings);
temptag.appendChild(document.createTextNode("》"));
// list alternatives
let alternatives = [];
for(let j = 0; j < term.k_ele.length; j++)
if(term.k_ele[j].keb != kanji_text)
alternatives.push(term.k_ele[j]);
if(alternatives.length > 0)
temptag.appendChild(document.createTextNode(" (also "));
for(let j = 0; j < alternatives.length; j++)
{
temptag.appendChild(document.createTextNode(alternatives[j].keb));
if(alternatives[j].inf)
{
for(let info of alternatives[j].inf)
{
temptag.appendChild(document.createTextNode(" "));
let keb_inf = document.createElement("span");
keb_inf.className = "nazeka_keb_inf";
keb_inf.textContent += "(";
keb_inf.textContent += clip(info);
keb_inf.textContent += ")";
temptag.appendChild(keb_inf);
}
}
if(j+1 < alternatives.length)
temptag.appendChild(document.createTextNode(", "));
}
if(alternatives.length > 0)
temptag.appendChild(document.createTextNode(")"));
}
else
{
let main_reb = document.createElement("span");
main_reb.className = "nazeka_main_reb";
main_reb.textContent = term.found.reb;
temptag.appendChild(main_reb);
if(term.deconj)
{
let deconj = "";
let first = true;
for(let form of term.deconj)
{
let formtext = "";
let added = 0;
for(let f = form.process.length-1; f >= 0; f--)
{
let info = form.process[f];
if(info == "")
continue;
if(info.startsWith("(") && info.endsWith(")") && f != 0)
continue;
if(added > 0)
formtext += "―";
added++;
formtext += info;
}
if(formtext != "")
{
if(first)
deconj += "~";
else
deconj += "・";
deconj += formtext;
}
first = false;
}
temptag.appendChild(document.createTextNode(deconj));
}
// FIXME wrapper span
if(term.found.inf)
{
let maininfos = document.createElement("span");
maininfos.className = "nazeka_main_infos";
for(let info of term.found.inf)
{
let reb_inf = document.createElement("span");
reb_inf.className = "nazeka_reb_inf";
reb_inf.textContent += "(";
reb_inf.textContent += clip(info);
reb_inf.textContent += ")";
maininfos.appendChild(reb_inf);
}
temptag.appendChild(maininfos);
}
// list alternatives
let alternatives = [];
for(let j = 0; j < term.r_ele.length; j++)
if(term.r_ele[j].reb != text)
alternatives.push(term.r_ele[j]);
if(alternatives.length > 0)
temptag.appendChild(document.createTextNode(" (also "));
for(let j = 0; j < alternatives.length; j++)
{
temptag.appendChild(document.createTextNode(alternatives[j].reb));
if(alternatives[j].inf)
{
for(let info of alternatives[j].inf)
{
temptag.appendChild(document.createTextNode(" "));
let reb_inf = document.createElement("span");
reb_inf.className = "nazeka_reb_inf";
reb_inf.textContent += "(";
reb_inf.textContent += clip(info);
reb_inf.textContent += ")";
temptag.appendChild(reb_inf);
}
}
if(j+1 < alternatives.length)
temptag.appendChild(document.createTextNode(", "));
}
if(alternatives.length > 0)
temptag.appendChild(document.createTextNode(")"));
}
container.appendChild(temptag);
container.appendChild(document.createElement("br"));
let goodsenses = term.sense;
let lastpos = [];
let definition = document.createElement("div");
definition.className = "nazeka_definitions";
for(let j = 0; j < goodsenses.length; j++)
{
let sense = goodsenses[j];
if(sense.pos == lastpos)
sense.pos = undefined;
else
lastpos = sense.pos;
if(sense.pos)
{
let part = document.createElement("span");
part.className = "nazeka_pos";
let temptext = "(";
let parts = [];
for(let l = 0; l < sense.pos.length; l++)
parts.push(sense.pos[l]);
temptext += parts.join(", ");
temptext += ")";
part.textContent = temptext;
definition.appendChild(part);
if(settings.compact)
definition.appendChild(document.createTextNode(" "));
else
definition.appendChild(document.createElement("br"));
}
if(settings.compact)
{
if(goodsenses.length > 1)
{
let number = document.createElement("span");
number.className = "nazeka_num";
number.textContent = "("+(j+1)+")";
definition.appendChild(number);
definition.appendChild(document.createTextNode(" "));
}
}
else
{
let temptag = document.createElement("span");
temptag.className = "nazeka_num";
temptag.textContent = ""+(j+1)+".";
definition.appendChild(temptag);
definition.appendChild(document.createTextNode(" "));
}
if(sense.inf)
{
let info = document.createElement("i");
info.textContent = "("+sense.inf+") ";
definition.appendChild(info);
}
if(sense.misc)
{
let temptag = document.createElement("span");
temptag.className = "nazeka_misc";
let temptext = "(";
let parts = [];
for(let l = 0; l < sense.misc.length; l++)
parts.push(sense.misc[l].substring(1, sense.misc[l].length-1));
temptext += parts.join(", ");
temptext += ")";
temptag.textContent = temptext;
definition.appendChild(temptag);
definition.appendChild(document.createTextNode(" "));
}
definition.appendChild(document.createTextNode(sense.gloss.join("; ")));
if(settings.compact)
{
if(sense.gloss.length > 1 || j+1 != goodsenses.length)
definition.appendChild(document.createTextNode("; "));
}
else
{
definition.appendChild(document.createElement("br"));
}
}
if(term.epwing)
{
definition.appendChild(document.createElement("br"));
let epwing_head = document.createElement("div");
epwing_head.appendChild(document.createTextNode("―"+term.epwing["z"]+"―"));
epwing_head.appendChild(document.createElement("br"));
let epwing_head_text = term.epwing["r"];
if(term.epwing["s"] && term.epwing["s"][0] != "")
{
let isfirst = true;
epwing_head_text += "【";
for(let spelling of term.epwing["s"])
{
if(!isfirst)
epwing_head_text += "・";
epwing_head_text += spelling;
isfirst = false;
}
epwing_head_text += "】";
}
epwing_head.appendChild(document.createTextNode(epwing_head_text));
let epwing_definition = document.createElement("div");
let isfirst = true;
for(let line of term.epwing["l"])
{
if(!isfirst)
epwing_definition.appendChild(document.createElement("br"));
let def_line = document.createElement("span");
def_line.textContent = line;
epwing_definition.appendChild(def_line);
isfirst = false;
}
epwing_head.className = "epwing_head";
epwing_definition.className = "epwing_definition";
definition.appendChild(epwing_head);
definition.appendChild(epwing_definition);
}
container.appendChild(definition);
if(settings.corner == 2 || settings.corner == 3)
{
if(settings.showoriginal)
temp.insertBefore(container, temp.children[0].nextSibling);
else
temp.insertBefore(container, temp.children[0]);
}
else
temp.appendChild(container);
}
return temp;
}
// this are the divs we use to contain the list of lookups. they're all contained in the same inner div from here.
function build_div_intermediary()
{
let middle = document.createElement("div");
let inner = document.createElement("div");
inner.className = "nazeka_listing";
middle.appendChild(inner);
return middle;
}
// for popups with single lookup results
function build_div (text, result, moreText, index)
{
last_displayed = [{text:text, result:result}];
let middle = build_div_intermediary();
middle.firstChild.appendChild(build_div_inner(text, result, moreText, index));
return middle;
}
// for popups with multiple lookup results
function build_div_compound (results, moreText, index)
{
last_displayed = results;
//console.log("Displaying:");
//print_object(result);
let middle = build_div_intermediary();
let first = true;
for(let lookup of results)
{
if(settings.corner == 2 || settings.corner == 3)
middle.firstChild.insertBefore(build_div_inner(lookup.text, lookup.result, moreText, index, first), middle.firstChild.children[0]);
else
middle.firstChild.appendChild(build_div_inner(lookup.text, lookup.result, moreText, index, first));
first = false;
}
return middle;
}
async function settings_init()
{
try
{
async function getvar(name, defval)
{
let temp = (await browser.storage.local.get(name))[name];
if(temp == undefined)
temp = defval;
settings[name] = temp;
}
getvar("enabled", false);
getvar("compact", true);
getvar("length", 25);
getvar("scale", 1);
getvar("width", 600);
getvar("lookuprate", 8);
getvar("fixedwidth", false);
getvar("fixedwidthpositioning", false);
getvar("superborder", false);
getvar("showoriginal", true);
getvar("bgcolor", "#111111");
getvar("fgcolor", "#CCCCCC");
getvar("hlcolor", "#99DDFF");
getvar("font", "");
getvar("hlfont", "");
getvar("alternatives_mode", 0);
getvar("strict_alternatives", true);
getvar("corner", 0);
getvar("xoffset", 5);
getvar("yoffset", 22);
getvar("ignore_linebreaks", false);
getvar("sticky", false);
if(!settings.enabled && exists_div())
delete_div();
if(!settings.enabled)
{
while(document.body.getElementsByClassName("nazeka_mining_ui").length)
document.body.getElementsByClassName("nazeka_mining_ui")[0].remove();
}
} catch(err) {} // options not stored yet
}
settings_init();
browser.storage.onChanged.addListener((updates, storageArea) =>
{
if(storageArea != "local") return;
for(let setting of Object.entries(updates))
{
let option = setting[0];
let value = setting[1];
if(Object.keys(settings).includes(option))
settings[option] = value.newValue;
}
if(!settings.enabled && exists_div())
delete_div();
if(!settings.enabled)
{
while(document.body.getElementsByClassName("nazeka_mining_ui").length)
document.body.getElementsByClassName("nazeka_mining_ui")[0].remove();
}
});
// look up words on a timer loop that only uses the most recent lookup request and ignores all the others
let lookup_timer = undefined;
let lookup_queue = [];
//let lookup_rate = 8;
let last_lookup = [];
let last_manual_interaction = Date.now();
let lookup_loop_cancel = false;
let lookup_last_time = Date.now();
async function lookup_loop()
{
//console.log("running lookup queue");
lookup_last_time = Date.now();
let t_start = Date.now();
if(lookup_queue.length > 0)
{
//console.log("queue not empty");
let lookup = lookup_queue.pop();
lookup_queue = [];
last_lookup = lookup;
//console.log("asking background to search the dictionary");
let response = await browser.runtime.sendMessage({type:"search", text:lookup[0], time:Date.now(), divexisted:exists_div(), alternatives_mode:settings.alternatives_mode, strict_alternatives:settings.strict_alternatives});
//console.log("got response");
if(response)
{
if(!response.length)
{
let mydiv = build_div(response.text, response.result, lookup[5], lookup[6]);
if(mydiv)
display_div(mydiv, lookup[3], lookup[4]);
}
else
{
let mydiv = build_div_compound(response, lookup[5], lookup[6]);
if(mydiv)
display_div(mydiv, lookup[3], lookup[4]);
}
}
}
let t_end = Date.now();
let t_to_wait = settings.lookuprate - (t_end - t_start);
if(t_to_wait < 0) t_to_wait = 0;
if(t_to_wait > settings.lookuprate) t_to_wait = settings.lookuprate;
if(lookup_loop_cancel)
{
//console.log("queue setup");
lookup_loop_cancel = false;
lookup_timer = undefined;
return;
}
else
lookup_timer = setTimeout(lookup_loop, t_to_wait);
}
lookup_timer = setTimeout(lookup_loop, settings.lookuprate);
function lookup_enqueue(text, x, y, x2, y2, moreText, index)
{
//console.log("trying to enqueue lookup");
lookup_queue = [[text, x, y, x2, y2, moreText, index]];
//console.log("enqueued lookup");
if(!lookup_timer || lookup_last_time+settings.lookuprate*100 < Date.now())
{
if(lookup_timer)
clearTimeout(lookup_timer);
lookup_timer = setTimeout(lookup_loop, settings.lookuprate);
}
}
function lookup_cancel()
{
lookup_queue = [];
if(!settings.sticky || platform == "android")
delete_div();
}
function manual_close()
{
lookup_queue = [];
delete_div();
last_manual_interaction = Date.now();
}
function lookup_cancel_force()
{
lookup_queue = [];
delete_div();
}
let japanesePunctuation = "、。「」{}()【】『』〈〉《》:・/…︙‥︰+=-÷?!.~―";
function lookup_left()
{
if(!exists_div()) return;
let text = last_lookup[5];
let index = last_lookup[6];
if (index <= 0) return;
index--;
while (index > 0 && (text.codePointAt(index) < 0x100 || japanesePunctuation.includes(text.charAt(index))))
index--;
text = text.substring(index);
lookup_queue = [[text, last_lookup[1], last_lookup[2], last_lookup[3], last_lookup[4], last_lookup[5], index]];
last_manual_interaction = Date.now();
}
function lookup_right()
{
if(!exists_div()) return;
let text = last_lookup[5];
let index = last_lookup[6];
if (index >= text.length-1) return;
index++;
while (index < text.length-1 && (text.codePointAt(index) < 0x100 || japanesePunctuation.includes(text.charAt(index))))
index++;
text = text.substring(index);
lookup_queue = [[text, last_lookup[1], last_lookup[2], last_lookup[3], last_lookup[4], last_lookup[5], index]];
last_manual_interaction = Date.now();
}
let time_of_last = Date.now();
let seach_x_offset = -3;
function grab_more_text(textNode, direction = 1)
{
if(direction > 0)
direction = 1;
else
direction = -1;
let text = "";
let current_node = textNode;
while(text.length < settings.length || (direction < 0 && !text.includes("。") && !text.includes("\n")))
{
if(current_node == undefined) break;
try
{
let display = getComputedStyle(current_node).display;
// break out of elements neither inline nor ruby
if(display != "inline" && display != "ruby")
break;
} catch(err) {}
let parent = current_node.parentNode;
if(parent == undefined) break;
let i = Array.prototype.indexOf.call(current_node.parentNode.childNodes, current_node);
if(i < 0) break;
i += direction;
while(i < current_node.parentNode.childNodes.length && i >= 0)
{
let next_node = current_node.parentNode.childNodes[i];
i += direction;
let tagname = next_node.tagName ? next_node.tagName.toLowerCase() : "";
if(tagname == "br" && !settings.ignore_linebreaks)
break;
if(tagname == "rt" || tagname == "rp")
continue;
// next_node might not be an Element
try
{
let ttext = "";
let display = getComputedStyle(next_node).display;
if(display == "ruby-text")
continue;
if(display == "inline")
{
if(direction > 0)
ttext += next_node.textContent;
else
ttext = next_node.textContent + ttext;
}
// BUG: this should technically be recursive, not a special case, but it won't affect any real webpages
if(display == "ruby")
{
let subtext = "";
for(let child of next_node.childNodes)
{
try
{
let display = getComputedStyle(child).display;
//if(display == "ruby-text")
// continue;
if(display == "inline")
{
subtext += child.textContent;
}
}
catch(err)
{
subtext += child.textContent;
}
}
if(direction > 0)
ttext += subtext;
else
ttext = subtext + ttext;
}
if(direction > 0)
text += ttext;
else
text = ttext + text;
}
catch(err)
{
if(direction > 0)
text += next_node.textContent;
else
text = next_node.textContent + text;
}
}
if(text.length < settings.length || direction < 0)
current_node = current_node.parentNode;
}
return text;
}
function grab_text(textNode, offset, elemental)
{
//console.log("found text");
let text = "";
let moreText = "";
if(elemental)
{
moreText = textNode.value;
text = moreText.substring(offset, textNode.value.length);
}
else
{
moreText = textNode.textContent;
text = moreText.substring(offset, textNode.textContent.length);
}
text += grab_more_text(textNode);
moreText = grab_more_text(textNode, -1) + moreText + grab_more_text(textNode);
//console.log("more (orig): " + moreText);
//console.log("less (orig): " + text);
let index = moreText.lastIndexOf(text);
let leftwards = 0;
let rightwards = 0;
while(moreText[index + leftwards] != "。" && moreText[index + leftwards] != "\n" && index + leftwards >= 0)
leftwards--;
leftwards++;
while(moreText[index + rightwards] != "。" && moreText[index + rightwards] != "\n" && index + rightwards < moreText.length)
rightwards++;
moreText = moreText.substring(index+leftwards, index+rightwards);
text = text.substring(0, rightwards);
index = -leftwards;
//console.log("more: " + moreText);
//console.log("less: " + text);
// grab text from later and surrounding DOM nodes
return [text, moreText, index];
}
function update(event)
{
if(!settings.enabled) return;
if(Date.now() - time_of_last < settings.lookuprate)
{
//console.log("too soon, returning");
//console.log(Date.now() + " vs " + time_of_last);
return;
}
if(platform == "android" && Date.now() - last_manual_interaction < 150)
return;
//console.log("---- entry to word lookup at " + Date.now());
time_of_last = Date.now();
//console.log("searching for text");
let textNode;
let offset;
let nodeResetList = [];
let nodeResetSeen = new Set();
function acceptable_element(node)
{
if(!textNode) return false;
//console.log(node);
if(textNode.nodeType != 1)
return false;
if(textNode.tagName.toLowerCase() == "textarea")
return true;
if(textNode.tagName.toLowerCase() == "input")
if(["search", "submit", "text", "url"].includes(node.type.toLowerCase()))
return true;
return false;
}
// find the text under the mouse event
let nodeIsBad = true;
while(nodeIsBad)
{
nodeIsBad = false;
let hitpage = function(x, y)
{
let range = undefined;
if (document.caretPositionFromPoint)
range = document.caretPositionFromPoint(x, y);
else if (document.caretRangeFromPoint)
range = document.caretRangeFromPoint(x, y);
if(range)
{
textNode = range.offsetNode;
offset = range.offset;
}
};
hitpage(event.clientX+seach_x_offset, event.clientY);
if (platform == "android" && exists_div())
{
let ele = document.body.getElementsByClassName(div_class)[0];
if(ele.contains(textNode))
return;
}
// try without the offset
if (textNode == undefined || (textNode.nodeType != 3 && !acceptable_element(textNode)))
hitpage(event.clientX, event.clientY);
if(!(textNode == undefined))
{
// we hit an node, see if it's a transparent element and try to move it under everything temporarily if it is
try
{
if(textNode.nodeType == 1 && !nodeResetSeen.has(textNode) && !acceptable_element(textNode))
{
let style = window.getComputedStyle(textNode);
let bg_color = style.getPropertyValue("background-color");
if(bg_color == "rgba(0, 0, 0, 0)")
{
nodeIsBad = true;
nodeResetList.push([textNode, style.getPropertyValue("z-index")]);
nodeResetSeen.add(textNode);
textNode.style.zIndex = -100000000;
continue;
}
}
}
catch(err)
{
console.log(err);
}
}
}
for(let toreset of nodeResetList)
{
let element = toreset[0];
let z_index = toreset[1];
element.style.zIndex = z_index;
}
// if there was text, use it
let elemental = acceptable_element(textNode);
if (textNode && (textNode.nodeType == 3 || elemental))
{
//print_object(textNode);
//print_object(textNode.parentNode);
let rect = undefined;
let fud = 5;
if(elemental)
rect = textNode.getBoundingClientRect();
else
rect = textNode.parentNode.getBoundingClientRect();
// FIXME: Doesn't work to reject in all cases
let hit = (event.clientX+fud >= rect.left && event.clientX-fud <= rect.right && event.clientY+fud >= rect.top && event.clientY-fud <= rect.bottom);
//let hit = (event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom);
if(!hit)
{
lookup_cancel();
return;
}
let found = grab_text(textNode, offset, elemental);
let text = found[0];
let moreText = found[1];
let index = found[2];
//text = text.trim();
//print_object(text);
text = text.substring(0, Math.min(text.length, settings.length));
//if(text != "")
//lookup_indirect(text, event.clientX, event.clientY, time_of_last);
if(text != "")
{
//console.log("calling lookup_enqueue");
lookup_enqueue(text, event.clientX, event.clientY, event.pageX, event.pageY, moreText, index);
}
else
{
// console.log("no text");
lookup_cancel();
}
}
else
{
// console.log("no text node");
// console.log("actual type:");
// console.log(textNode.nodeType);
// console.log("offset:");
// console.log(offset);
lookup_cancel();
}
}
function update_touch(event)
{
//console.log("touch event triggered");
if(event.touches)
{
//console.log("receiving touch event");
update(event.touches[0]);
}
}
function message(text)
{
if(!text) return;
let mydiv = document.createElement("div");
mydiv.style = "background-color: rgba(0, 0, 0, 0.5); color: white; width: 200px; position: fixed; right: 25px; bottom: 25px; z-index: 100000; padding: 5px; border-radius: 3px;"
mydiv.textContent = text;
document.body.appendChild(mydiv);
function delete_later()
{
mydiv.remove();
}
setTimeout(delete_later, 3000);
}
async function mine_to_storage(object)
{
let cards = (await browser.storage.local.get("cards")).cards;
if(!cards)
cards = [];
//console.log(cards);
cards.push(object);
browser.storage.local.set({cards:cards});
}
function mine(highlight)
{
let front = highlight.textContent;
let word = highlight.parentElement.parentElement;
let readings = "";
let readings_elements = word.getElementsByClassName("nazeka_readings");
if(readings_elements.length)
readings = readings_elements[0].textContent;
let definitions = word.getElementsByClassName("nazeka_definitions")[0].textContent;
let lookup = word.parentElement.querySelector(".nazeka_lookup");
let sentence = word.parentElement.querySelector(".nazeka_lookup_sentence");
let index = word.parentElement.querySelector(".nazeka_lookup_index");
let seq = word.getAttribute("nazeka_seq");
//console.log(front);
//console.log(readings);
//console.log(definitions);
//console.log(seq);
mine_to_storage({front: front, readings: readings, definitions: definitions, lookup: lookup.textContent, sentence: sentence.textContent, index: index.textContent, seq: seq});
}
function keytest(event)
{
if(event.shiftKey || event.ctrlKey || event.metaKey || event.altKey)
return;
if(event.key == "m")
{
if(document.body.getElementsByClassName("nazeka_mining_ui").length)
{
while(document.body.getElementsByClassName("nazeka_mining_ui").length)
{
console.log("deleting");
document.body.getElementsByClassName("nazeka_mining_ui")[0].remove();
}
console.log("deleted");
}
else
{
if(!exists_div())
return;
while(document.body.getElementsByClassName("nazeka_mining_ui").length)
document.body.getElementsByClassName("nazeka_mining_ui")[0].remove();
//console.log("mining request");
let mydiv = document.body.getElementsByClassName(div_class)[0].cloneNode(true);
delete_div();
set_sticky_styles(mydiv);
mydiv.className = "nazeka_mining_ui";
let newheader = document.createElement("div");
newheader.textContent = "Mining UI. Press the given entry's highlighted spelling to mine it, or this message to cancel.";
newheader.addEventListener("click", ()=>
{
while(document.body.getElementsByClassName("nazeka_mining_ui").length)
document.body.getElementsByClassName("nazeka_mining_ui")[0].remove();
});
mydiv.firstChild.firstChild.prepend(newheader);
mydiv.style.zIndex = 100000-1;
for(let keb of mydiv.getElementsByClassName("nazeka_main_keb"))
{
keb.addEventListener("click", (event)=>
{
mine(event.target);
while(document.body.getElementsByClassName("nazeka_mining_ui").length)
document.body.getElementsByClassName("nazeka_mining_ui")[0].remove();
});
}
for(let reb of mydiv.getElementsByClassName("nazeka_main_reb"))
{
reb.addEventListener("click", (event)=>
{
mine(event.target);
while(document.body.getElementsByClassName("nazeka_mining_ui").length)
document.body.getElementsByClassName("nazeka_mining_ui")[0].remove();
});
}
document.body.appendChild(mydiv);
}
}
if(event.key == "n")
{
lookup_cancel_force();
}
if(event.key == "b")
{
settings.sticky = !settings.sticky;
browser.storage.local.set({"sticky":settings.sticky});
}
if(!exists_div())
return;
if(event.keyCode == 37) // left
{
lookup_left();
}
if(event.keyCode == 39) // right
{
lookup_right();
}
}
window.addEventListener("mousemove", update);
window.addEventListener("keydown", keytest);
document.addEventListener("touchstart", update_touch);