2771 lines
92 KiB
JavaScript
2771 lines
92 KiB
JavaScript
// Copyright 2017; Licensed under the Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0
|
||
|
||
'use strict';
|
||
if (typeof globalThis.browser === "undefined" && typeof chrome !== "undefined") {
|
||
globalThis.browser = {
|
||
...chrome,
|
||
|
||
runtime: {
|
||
...chrome.runtime,
|
||
|
||
sendMessage: function(message) {
|
||
return new Promise((resolve, reject) => {
|
||
chrome.runtime.sendMessage(message, response => {
|
||
const err = chrome.runtime.lastError;
|
||
|
||
// Chrome sets lastError when a message intentionally has no direct response.
|
||
// Firefox-style Nazeka code often uses async side-channel replies instead.
|
||
if (err) {
|
||
console.warn("Nazeka sendMessage warning:", err.message);
|
||
resolve(undefined);
|
||
} else {
|
||
resolve(response);
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
getPlatformInfo: function() {
|
||
return new Promise((resolve, reject) => {
|
||
chrome.runtime.getPlatformInfo(info => {
|
||
const err = chrome.runtime.lastError;
|
||
if (err) reject(err);
|
||
else resolve(info);
|
||
});
|
||
});
|
||
},
|
||
|
||
getURL: chrome.runtime.getURL.bind(chrome.runtime),
|
||
|
||
onMessage: {
|
||
addListener: function(listener) {
|
||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||
try {
|
||
const result = listener(message, sender);
|
||
|
||
if (result && typeof result.then === "function") {
|
||
result.then(sendResponse).catch(error => {
|
||
console.error("Nazeka message listener error:", error);
|
||
sendResponse(undefined);
|
||
});
|
||
return true;
|
||
}
|
||
|
||
if (result !== undefined) {
|
||
sendResponse(result);
|
||
}
|
||
|
||
return false;
|
||
} catch (error) {
|
||
console.error("Nazeka message listener exception:", error);
|
||
sendResponse(undefined);
|
||
return false;
|
||
}
|
||
});
|
||
}
|
||
}
|
||
},
|
||
|
||
storage: {
|
||
...chrome.storage,
|
||
|
||
local: {
|
||
...chrome.storage.local,
|
||
|
||
get: function(keys) {
|
||
return new Promise((resolve, reject) => {
|
||
chrome.storage.local.get(keys, result => {
|
||
const err = chrome.runtime.lastError;
|
||
if (err) reject(err);
|
||
else resolve(result);
|
||
});
|
||
});
|
||
},
|
||
|
||
set: function(items) {
|
||
return new Promise((resolve, reject) => {
|
||
chrome.storage.local.set(items, () => {
|
||
const err = chrome.runtime.lastError;
|
||
if (err) reject(err);
|
||
else resolve();
|
||
});
|
||
});
|
||
},
|
||
|
||
remove: function(keys) {
|
||
return new Promise((resolve, reject) => {
|
||
chrome.storage.local.remove(keys, () => {
|
||
const err = chrome.runtime.lastError;
|
||
if (err) reject(err);
|
||
else resolve();
|
||
});
|
||
});
|
||
}
|
||
},
|
||
|
||
onChanged: chrome.storage.onChanged
|
||
},
|
||
|
||
tabs: chrome.tabs ? {
|
||
...chrome.tabs,
|
||
|
||
getCurrent: function() {
|
||
return new Promise((resolve, reject) => {
|
||
chrome.tabs.getCurrent(tab => {
|
||
const err = chrome.runtime.lastError;
|
||
if (err) reject(err);
|
||
else resolve(tab);
|
||
});
|
||
});
|
||
},
|
||
|
||
sendMessage: function(tabId, message) {
|
||
return new Promise((resolve, reject) => {
|
||
chrome.tabs.sendMessage(tabId, message, response => {
|
||
const err = chrome.runtime.lastError;
|
||
if (err) {
|
||
console.warn("Nazeka tabs.sendMessage warning:", err.message);
|
||
resolve(undefined);
|
||
} else {
|
||
resolve(response);
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
create: chrome.tabs.create.bind(chrome.tabs)
|
||
} : undefined,
|
||
|
||
windows: chrome.windows ? {
|
||
...chrome.windows,
|
||
create: chrome.windows.create.bind(chrome.windows)
|
||
} : undefined,
|
||
|
||
extension: {
|
||
...chrome.extension,
|
||
getURL: chrome.runtime.getURL.bind(chrome.runtime)
|
||
},
|
||
|
||
browserAction: chrome.browserAction || undefined,
|
||
contextMenus: chrome.contextMenus || undefined
|
||
};
|
||
}
|
||
|
||
|
||
// updated by a timer looping function, based on local storage set by the options page
|
||
let settings = {
|
||
enabled: false,
|
||
kanji_mode: false,
|
||
compact: true,
|
||
usetextfields: true,
|
||
length: 25,
|
||
contextlength: 100, // nonconfigurable
|
||
fixedwidth: false,
|
||
fixedwidthpositioning: false,
|
||
superborder: false,
|
||
disableborder: false,
|
||
space_saver: false,
|
||
hide_deconj: false,
|
||
showoriginal: true,
|
||
reader_sticky: false,
|
||
definitions_mode: 0,
|
||
normal_definitions_in_mining: false,
|
||
alternatives_mode: 3, // 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
|
||
strict_epwing: true,
|
||
json_newlines_replace: false,
|
||
json_newlines_character: " ⬥ ",
|
||
scale: 1,
|
||
width: 600,
|
||
lookuprate: 8,
|
||
bgcolor: "#111111",
|
||
fgcolor: "#CCCCCC",
|
||
hlcolor: "#99DDFF",
|
||
hlcolor2: "#99FF99",
|
||
titlecolor: "#D8A888",
|
||
font: "",
|
||
hlfont: "",
|
||
definition_fontsize: 13,
|
||
dict_item_fontsize: 18,
|
||
reading_fontsize: 15,
|
||
corner: 0,
|
||
xoffset: 5,
|
||
yoffset: 22,
|
||
strip_spaces: true,
|
||
ignore_linebreaks: true,
|
||
ignore_divs: false,
|
||
sticky: false,
|
||
popup_follows_mouse: true,
|
||
popup_requires_key: 0, // 0: none; 1: ctrl; 2: shift
|
||
x_dodge: 1,
|
||
y_dodge: 0,
|
||
sticky_maxheight: 0,
|
||
kanji_show_stroke_count: true,
|
||
kanji_show_readings: true,
|
||
kanji_show_composition: true,
|
||
kanji_show_quality_warning: true,
|
||
hotkey_mine: "m",
|
||
hotkey_nazeka_toggle: "j",
|
||
hotkey_close: "n",
|
||
hotkey_sticky: "b",
|
||
hotkey_audio: "p",
|
||
hotkey_kanji_mode: "k",
|
||
hotkey_nudge_left: "ArrowLeft",
|
||
hotkey_nudge_right: "ArrowRight",
|
||
volume: 0.2,
|
||
live_mining: false,
|
||
use_selection: false,
|
||
only_selection: false,
|
||
audio_force_https: false,
|
||
highlight_dict_headers: false,
|
||
dropshadow: false,
|
||
dropshadowradius: 10,
|
||
dropshadowcolor: "#000000",
|
||
padding: 2,
|
||
novalign: 2,
|
||
inserthr: false,
|
||
info_styling: false,
|
||
info_fgcolor: "#CCCCCC",
|
||
info_bgcolor_pos: "#606060",
|
||
info_bgcolor_num: "#406080",
|
||
info_bgcolor_etc: "#608040"
|
||
};
|
||
|
||
let platform = "win";
|
||
let is_reader = false;
|
||
|
||
async function get_real_platform()
|
||
{
|
||
|
||
let my_platform = undefined;
|
||
try {
|
||
my_platform = await browser.runtime.sendMessage({type:"platform"});
|
||
if(my_platform)
|
||
my_platform = my_platform["response"];
|
||
}catch(err){}// don't care
|
||
while(my_platform == "")
|
||
{
|
||
try {
|
||
my_platform = await browser.runtime.sendMessage({type:"platform"});
|
||
if(my_platform)
|
||
my_platform = my_platform["response"];
|
||
}catch(err){}// don't care
|
||
}
|
||
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 = get_div();
|
||
if(other)
|
||
{
|
||
other.innerHTML = "";
|
||
other.removeAttribute('style');
|
||
}
|
||
}
|
||
|
||
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";
|
||
if(settings.sticky_maxheight == 0)
|
||
mydiv.style.maxHeight = "calc(100vh - 20px)";
|
||
else
|
||
mydiv.style.maxHeight = Math.round(Number(settings.sticky_maxheight))+"px";
|
||
mydiv.style.overflowY = "scroll";
|
||
mydiv.style.marginLeft = "unset";
|
||
mydiv.style.marginTop = "unset";
|
||
mydiv.style.marginBottom = "10px";
|
||
}
|
||
|
||
function is_not_frameset(root)
|
||
{
|
||
try
|
||
{
|
||
return root.parent.document.body.tagName != "FRAMESET";
|
||
}
|
||
catch(err)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
function get_doc()
|
||
{
|
||
let find_root = document.defaultView;
|
||
let mydoc = document;
|
||
while(find_root.parent && find_root.parent != find_root && is_not_frameset(find_root))
|
||
{
|
||
find_root = find_root.parent;
|
||
try
|
||
{
|
||
mydoc = find_root.document;
|
||
}
|
||
catch(err)
|
||
{
|
||
console.log("hit error trying to find root; nazeka might behave incoherently");
|
||
console.log(err);
|
||
break;
|
||
}
|
||
}
|
||
return mydoc;
|
||
}
|
||
|
||
function mining_ui_exists()
|
||
{
|
||
return get_doc().body.getElementsByClassName("nazeka_mining_ui").length > 0;
|
||
}
|
||
|
||
function delete_mining_ui()
|
||
{
|
||
while(mining_ui_exists())
|
||
get_doc().body.getElementsByClassName("nazeka_mining_ui")[0].remove();
|
||
}
|
||
|
||
function get_mining_ui()
|
||
{
|
||
return get_doc().body.getElementsByClassName("nazeka_mining_ui")[0];
|
||
}
|
||
|
||
function getViewportSize(mydoc)
|
||
{
|
||
if (mydoc.compatMode == "CSS1Compat")
|
||
return { w: mydoc.documentElement.clientWidth, h: mydoc.documentElement.clientHeight };
|
||
return { w: mydoc.body.clientWidth, h: mydoc.body.clientHeight };
|
||
}
|
||
|
||
function is_sticky()
|
||
{
|
||
return (settings.sticky && platform != "android" && !mining_ui_exists()) || (is_reader && settings.reader_sticky);
|
||
}
|
||
|
||
// 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
|
||
let last_display_x = 0;
|
||
let last_display_y = 0;
|
||
function display_div(middle, x, y)
|
||
{
|
||
console.log("Nazeka display_div called", x, y, middle);
|
||
last_display_x = x;
|
||
last_display_y = y;
|
||
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};`;
|
||
let border_text = (settings.disableborder)?("border: 0px solid transparent;"):(`border: 1px solid ${settings.fgcolor}`);
|
||
middle.firstChild.style = `${border_text}; border-radius: 2px; padding: ${settings.padding}px; background-color: ${settings.bgcolor}; color: ${settings.fgcolor}; font-family: ${font} Arial, sans-serif; text-align: left; font-size: ${settings.definition_fontsize}px;`;
|
||
|
||
let find_root = document.defaultView;
|
||
let newx = x;
|
||
let newy = y;
|
||
let mydoc = document;
|
||
|
||
while(find_root.parent && find_root.parent != find_root && is_not_frameset(find_root))
|
||
{
|
||
let rect = find_root.frameElement.getBoundingClientRect();
|
||
let rx = rect.x;
|
||
let ry = rect.y;
|
||
let sx1 = find_root.parent.scrollX;
|
||
let sy1 = find_root.parent.scrollY;
|
||
let sx2 = find_root.scrollX;
|
||
let sy2 = find_root.scrollY;
|
||
find_root = find_root.parent;
|
||
newx += (rx + sx1 - sx2);
|
||
newy += (ry + sy1 - sy2);
|
||
try
|
||
{
|
||
mydoc = find_root.document;
|
||
}
|
||
catch(err)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
let basewidth = Math.round(Number(settings.width));
|
||
if(settings.x_dodge != 1 && basewidth > mydoc.documentElement.getBoundingClientRect().width/2 - settings.xoffset - 5)
|
||
basewidth = Math.max(150, Math.round(mydoc.documentElement.getBoundingClientRect().width/2 - settings.xoffset - 5));
|
||
|
||
let styletext = "";
|
||
if(settings.fixedwidth)
|
||
{
|
||
styletext += "max-width: " + basewidth + "px; "
|
||
styletext += "min-width: " + basewidth + "px; "
|
||
}
|
||
else
|
||
{
|
||
styletext += "max-width: " + basewidth + "px; "
|
||
styletext += "min-width: 150px; "
|
||
}
|
||
styletext += "position: absolute; top: 0; left: 0; transition: unset; ";
|
||
|
||
if(settings.superborder && !settings.disableborder)
|
||
styletext += `background-color: ${settings.fgcolor}; border-radius: 3px; border: 1px solid ${settings.fgcolor}; z-index: 1000000000000000000000;`;
|
||
else
|
||
styletext += `border-radius: 3px; background-color: ${settings.bgcolor}; z-index: 1000000000000000000000;`;
|
||
|
||
styletext += "writing-mode: horizontal-tb; line-height: initial; white-space: initial;";
|
||
if(settings.dropshadow)
|
||
{
|
||
let v = parseInt(settings.dropshadowcolor.substring(1), 16);
|
||
let r = Math.floor(v / 256 / 256);
|
||
let g = Math.floor(v / 256) % 256;
|
||
let b = Math.floor(v) % 256;
|
||
let rgba = "rgba(" + r + "," + g + "," + b + ",0.5)";
|
||
styletext += `box-shadow: 0 0 ${settings.dropshadowradius}px ${rgba};`
|
||
}
|
||
|
||
let other = mydoc.body.getElementsByClassName(div_class);
|
||
let outer = undefined;
|
||
if(other.length > 0)
|
||
{
|
||
outer = other[0];
|
||
if(!outer.firstChild)
|
||
outer.appendChild(middle);
|
||
else if(outer.firstChild != middle)
|
||
outer.replaceChild(middle, outer.firstChild);
|
||
outer.style.display = "block";
|
||
}
|
||
else
|
||
{
|
||
outer = mydoc.createElement("div");
|
||
outer.className = div_class;
|
||
if(!outer.firstChild || outer.firstChild != middle)
|
||
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 = basewidth*settings.scale;
|
||
if(!settings.fixedwidthpositioning)
|
||
mywidth = outer.offsetWidth*settings.scale;
|
||
|
||
let corner = settings.corner;
|
||
|
||
if(is_sticky())
|
||
set_sticky_styles(outer);
|
||
else
|
||
{
|
||
if(corner == 1 || corner == 3)
|
||
{
|
||
newx -= mywidth;
|
||
newx -= settings.xoffset;
|
||
}
|
||
else
|
||
newx += settings.xoffset;
|
||
|
||
if(corner == 2 || corner == 3)
|
||
newy = newy - outer.getBoundingClientRect().height - settings.yoffset;
|
||
else
|
||
newy = newy + settings.yoffset;
|
||
|
||
// 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)+"px";
|
||
outer.style.bottom = "unset";
|
||
outer.style.right = "unset";
|
||
outer.style.left = (newx)+"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";
|
||
//
|
||
}
|
||
|
||
let localrect;
|
||
let dodged_vertically = false;
|
||
let viewport = getViewportSize(mydoc);
|
||
|
||
// dodge top/bottom
|
||
localrect = outer.getBoundingClientRect();
|
||
if(localrect.bottom+5 > viewport.h)
|
||
{
|
||
if(settings.y_dodge == 1) // slide
|
||
newy -= localrect.bottom+5 - viewport.h;
|
||
else // flip
|
||
newy -= localrect.height + settings.yoffset*2;
|
||
outer.style.top = (newy)+"px";
|
||
dodged_vertically = true;
|
||
}
|
||
localrect = outer.getBoundingClientRect();
|
||
if(localrect.top-5 < 0 && (!dodged_vertically || corner == 0 || corner == 1)) // (prefer running off the top when using "bottom" alignments)
|
||
{
|
||
if(settings.y_dodge == 1)
|
||
newy -= localrect.top-5;
|
||
else
|
||
newy += localrect.height + settings.yoffset*2;
|
||
outer.style.top = (newy)+"px";
|
||
}
|
||
|
||
// dodge left/right
|
||
localrect = outer.getBoundingClientRect();
|
||
if(localrect.right+5 > viewport.w)
|
||
{
|
||
if(settings.x_dodge == 1)
|
||
newx -= localrect.right+5 - viewport.w;
|
||
else
|
||
newx -= localrect.width + settings.xoffset*2;
|
||
outer.style.left = (newx)+"px";
|
||
}
|
||
localrect = outer.getBoundingClientRect();
|
||
if(localrect.left-5 < 0)
|
||
{
|
||
if(settings.x_dodge == 1)
|
||
newx -= localrect.left-5;
|
||
else
|
||
newx += localrect.width + settings.xoffset*2;
|
||
outer.style.left = (newx)+"px";
|
||
}
|
||
}
|
||
|
||
function exists_div()
|
||
{
|
||
let mydoc = get_doc();
|
||
let other = mydoc.body.getElementsByClassName(div_class);
|
||
return (other.length > 0 && other[0].style.display != "none" && other[0].innerHTML != "");
|
||
}
|
||
|
||
function get_div()
|
||
{
|
||
let mydoc = get_doc();
|
||
let other = mydoc.body.getElementsByClassName(div_class);
|
||
if(other.length > 0 && other[0].style.display != "none" && other[0].innerHTML != "")
|
||
return other[0];
|
||
else
|
||
return undefined;
|
||
}
|
||
|
||
|
||
// 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);
|
||
}
|
||
|
||
function elementize_jmdict_defs(goodsenses)
|
||
{
|
||
let lastpos = [];
|
||
let jmdict_defs = document.createElement("div");
|
||
jmdict_defs.className = "jmdict_definitions";
|
||
for(let j = 0; j < goodsenses.length; j++)
|
||
{
|
||
let sense = goodsenses[j];
|
||
|
||
if(sense.pos + "" == lastpos) // removing this + "" breaks repetitive sense hiding in chrome
|
||
sense.pos = undefined;
|
||
else
|
||
lastpos = sense.pos;
|
||
|
||
if(sense.pos && sense.pos.length > 0)
|
||
{
|
||
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;
|
||
jmdict_defs.appendChild(part);
|
||
if(settings.compact)
|
||
jmdict_defs.appendChild(document.createTextNode(" "));
|
||
else
|
||
jmdict_defs.appendChild(document.createElement("br"));
|
||
|
||
}
|
||
if(settings.compact)
|
||
{
|
||
if(goodsenses.length > 1)
|
||
{
|
||
let number = document.createElement("span");
|
||
number.className = "nazeka_num";
|
||
number.textContent = "("+(j+1)+")";
|
||
jmdict_defs.appendChild(number);
|
||
jmdict_defs.appendChild(document.createTextNode(" "));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
let temptag = document.createElement("span");
|
||
temptag.className = "nazeka_num";
|
||
temptag.textContent = ""+(j+1)+".";
|
||
jmdict_defs.appendChild(temptag);
|
||
jmdict_defs.appendChild(document.createTextNode(" "));
|
||
}
|
||
if(sense.inf)
|
||
{
|
||
let info = document.createElement("i");
|
||
info.textContent = "("+sense.inf+") ";
|
||
jmdict_defs.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;
|
||
jmdict_defs.appendChild(temptag);
|
||
jmdict_defs.appendChild(document.createTextNode(" "));
|
||
}
|
||
jmdict_defs.appendChild(document.createTextNode(sense.gloss.join("; ")));
|
||
if(settings.compact)
|
||
{
|
||
if(sense.gloss.length > 1 || j+1 != goodsenses.length)
|
||
jmdict_defs.appendChild(document.createTextNode("; "));
|
||
}
|
||
else
|
||
{
|
||
jmdict_defs.appendChild(document.createElement("br"));
|
||
}
|
||
}
|
||
return jmdict_defs;
|
||
}
|
||
function elementize_json_defs(json)
|
||
{
|
||
if(json.length > 0)
|
||
{
|
||
let json_defs = document.createElement("div");
|
||
json_defs.className = "json_definitions";
|
||
|
||
for(let entry of json)
|
||
{
|
||
let json_head = document.createElement("div");
|
||
let json_title = document.createElement("span");
|
||
json_title.appendChild(document.createTextNode("―"+entry["z"]+"―"));
|
||
json_title.className = "json_title";
|
||
json_head.appendChild(json_title);
|
||
json_head.appendChild(document.createElement("br"));
|
||
let json_head_text = entry["r"];
|
||
if(entry["s"] && entry["s"][0] != "")
|
||
{
|
||
let isfirst = true;
|
||
json_head_text += "【";
|
||
for(let spelling of entry["s"])
|
||
{
|
||
if(!isfirst)
|
||
json_head_text += "・";
|
||
json_head_text += spelling;
|
||
isfirst = false;
|
||
}
|
||
json_head_text += "】";
|
||
}
|
||
json_head.appendChild(document.createTextNode(json_head_text));
|
||
let json_definition = document.createElement("div");
|
||
let isfirst = true;
|
||
for(let line of entry["l"])
|
||
{
|
||
if(!isfirst)
|
||
{
|
||
json_definition.appendChild(document.createElement("br"));
|
||
let filler = document.createElement("span");
|
||
filler.className = "json_newline_filler";
|
||
json_definition.appendChild(filler);
|
||
}
|
||
let def_line = document.createElement("span");
|
||
def_line.textContent = line;
|
||
json_definition.appendChild(def_line);
|
||
isfirst = false;
|
||
}
|
||
json_head.className = "json_head";
|
||
json_definition.className = "json_definition";
|
||
json_defs.appendChild(json_head);
|
||
json_defs.appendChild(json_definition);
|
||
}
|
||
return json_defs;
|
||
}
|
||
else
|
||
{
|
||
return undefined;
|
||
}
|
||
}
|
||
|
||
function filter_for_css(text)
|
||
{
|
||
return text.replace(";","").replace("}","").replace("'","").replace('"',"");
|
||
}
|
||
function filter_for_css_trimmed(text)
|
||
{
|
||
return filter_for_css(text).trim();
|
||
}
|
||
|
||
function get_style()
|
||
{
|
||
// styling for highlighted stuff and the lookup text
|
||
let style = document.createElement("style");
|
||
style.type = "text/css";
|
||
let font = filter_for_css_trimmed(settings.hlfont);
|
||
let morefont = filter_for_css_trimmed(settings.font);
|
||
let newline_char = filter_for_css(settings.json_newlines_character);
|
||
if(font != "")
|
||
font += ",";
|
||
if(morefont != "")
|
||
font += morefont + ",";
|
||
let valign = ".nazeka_word * {vertical-align: middle}";
|
||
if(settings.novalign)
|
||
valign = "";
|
||
let infostyle = "";
|
||
if(settings.info_styling)
|
||
{
|
||
infostyle =
|
||
`.nazeka_pos, .nazeka_num, .nazeka_misc {font-size:80%; border-radius:3px; padding: 1px 3px 2px; color:${settings.info_fgcolor}; display:inline-block; position: relative; bottom: 1px; margin-bottom: -1px;}
|
||
.nazeka_pos {background-color:${settings.info_bgcolor_pos};}
|
||
.nazeka_num {background-color:${settings.info_bgcolor_num};}
|
||
.nazeka_misc {background-color:${settings.info_bgcolor_etc};}
|
||
`;
|
||
}
|
||
style.textContent =
|
||
`.nazeka_main_keb{font-size:${settings.dict_item_fontsize}px;white-space:nowrap;font-family: ${font}IPAGothic,TakaoGothic,Noto Sans CJK JP Regular,Meiryo,sans-serif;color:${settings.hlcolor}}
|
||
.nazeka_main_reb{font-size:${settings.dict_item_fontsize}px;white-space:nowrap;font-family: ${font}IPAGothic,TakaoGothic,Noto Sans CJK JP Regular,Meiryo,sans-serif;color:${settings.hlcolor}}
|
||
${valign}
|
||
${infostyle}
|
||
.${div_class} hr{margin: 0.5em; padding: 0; border: none; border-top: 1px solid ${settings.fgcolor}; opacity: 0.25}
|
||
.nazeka_sub_keb{font-size:${settings.reading_fontsize}px;white-space:nowrap;font-family: ${font}IPAGothic,TakaoGothic,Noto Sans CJK JP Regular,Meiryo,sans-serif}
|
||
.nazeka_sub_reb{font-size:${settings.reading_fontsize}px;white-space:nowrap;color:${settings.hlcolor2}}
|
||
.nazeka_original{float: right; margin-right: 2px; margin-left:4px; opacity:0.7;}
|
||
.json_head{margin: 0 4px 6px;}
|
||
.jmdict_definitions{margin: 0 4px 6px;}
|
||
.json_definition{margin: 0 4px 6px;}
|
||
.kanji_info{margin: 2px 4px 2px;}
|
||
`;
|
||
if(settings.highlight_dict_headers)
|
||
style.textContent +=
|
||
`.jmdict_title{color:${settings.titlecolor}}
|
||
.json_title{color:${settings.titlecolor}}
|
||
`;
|
||
if(settings.json_newlines_replace)
|
||
style.textContent +=
|
||
`.json_definition br{display: none;}
|
||
.json_definition .json_newline_filler::before{content: '${newline_char}';}
|
||
`;
|
||
return style;
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
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)+"…";
|
||
}
|
||
|
||
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" || is_sticky()))
|
||
{
|
||
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";
|
||
left_arrow.style.display = "inline";
|
||
right_arrow.style.display = "inline";
|
||
left_arrow.style.padding = "initial";
|
||
right_arrow.style.padding = "initial";
|
||
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";
|
||
close_button.style.display = "inline";
|
||
close_button.style.padding = "initial";
|
||
buttons.appendChild(close_button);
|
||
}
|
||
else
|
||
{
|
||
let close_button = document.createElement("img");
|
||
close_button.src = browser.extension.getURL("img/closebutton24.png");
|
||
close_button.onclick = manual_disable_sticky;
|
||
close_button.style.marginTop = "-3px";
|
||
close_button.style.display = "inline";
|
||
close_button.style.padding = "initial";
|
||
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);
|
||
}
|
||
let style = get_style();
|
||
temp.appendChild(style);
|
||
|
||
function makespan(text)
|
||
{
|
||
let s = document.createElement("span");
|
||
s.textContent = text;
|
||
return s;
|
||
}
|
||
|
||
// 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
|
||
|
||
|
||
if(term.has_audio.length > 0)
|
||
{
|
||
for(let audio of term.has_audio)
|
||
{
|
||
let audio_data_holder = document.createElement("div");
|
||
audio_data_holder.className = "nazeka_audioref";
|
||
audio_data_holder.style.display = "none";
|
||
audio_data_holder.innerText += audio+"\n";
|
||
container.appendChild(audio_data_holder);
|
||
}
|
||
}
|
||
|
||
|
||
// for mining
|
||
|
||
let reading_data_holder = document.createElement("div");
|
||
reading_data_holder.className = "nazeka_mining_readings";
|
||
reading_data_holder.style.display = "none";
|
||
let reading_data_list = new Array();
|
||
for(let r of term.r_ele)
|
||
reading_data_list.push(r.reb);
|
||
reading_data_holder.innerText = reading_data_list.join("、");
|
||
container.appendChild(reading_data_holder);
|
||
|
||
let spelling_data_holder = document.createElement("div");
|
||
spelling_data_holder.className = "nazeka_mining_spellings";
|
||
spelling_data_holder.style.display = "none";
|
||
let spelling_data_list = new Array();
|
||
if(term.k_ele)
|
||
for(let k of term.k_ele)
|
||
spelling_data_list.push(k.keb);
|
||
spelling_data_holder.innerText = spelling_data_list.join("、");
|
||
container.appendChild(spelling_data_holder);
|
||
|
||
// end of "for mining"
|
||
|
||
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);
|
||
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.createElement("wbr"));
|
||
let e_readings = document.createElement("span");
|
||
if(!settings.space_saver)
|
||
e_readings.appendChild(makespan("《"));
|
||
else
|
||
e_readings.appendChild(makespan(" "));
|
||
e_readings.className = "nazeka_readings";
|
||
for(let j = 0; j < readings.length; j++)
|
||
{
|
||
let subreb = document.createElement("span");
|
||
subreb.className = "nazeka_sub_reb";
|
||
subreb.textContent = readings[j].reb;
|
||
e_readings.appendChild(subreb);
|
||
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.createElement("wbr"));
|
||
e_readings.appendChild(makespan("、"));
|
||
}
|
||
}
|
||
if(!settings.space_saver)
|
||
e_readings.appendChild(makespan("》"));
|
||
else
|
||
e_readings.appendChild(makespan(" "));
|
||
temptag.appendChild(e_readings);
|
||
|
||
// 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(makespan((settings.space_saver)?(" ("):(" (also ")));
|
||
for(let j = 0; j < alternatives.length; j++)
|
||
{
|
||
let subkeb = document.createElement("span");
|
||
subkeb.className = "nazeka_sub_keb";
|
||
subkeb.textContent = alternatives[j].keb;
|
||
temptag.appendChild(subkeb);
|
||
if(alternatives[j].inf)
|
||
{
|
||
for(let info of alternatives[j].inf)
|
||
{
|
||
temptag.appendChild(makespan(" "));
|
||
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(makespan(", "));
|
||
}
|
||
if(alternatives.length > 0)
|
||
temptag.appendChild(makespan(")"));
|
||
|
||
// deconjugations
|
||
|
||
|
||
if(term.deconj && !settings.hide_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)
|
||
{
|
||
temptag.appendChild(document.createElement("wbr"));
|
||
deconj = "~";
|
||
}
|
||
else
|
||
{
|
||
temptag.appendChild(document.createElement("wbr"));
|
||
deconj = ";";
|
||
}
|
||
temptag.appendChild(makespan(deconj));
|
||
temptag.appendChild(document.createElement("wbr"));
|
||
deconj = formtext;
|
||
temptag.appendChild(makespan(deconj));
|
||
}
|
||
first = false;
|
||
}
|
||
}
|
||
}
|
||
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 && !settings.hide_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(makespan(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(makespan((settings.space_saver)?(" ("):(" (also ")));
|
||
for(let j = 0; j < alternatives.length; j++)
|
||
{
|
||
temptag.appendChild(makespan(alternatives[j].reb));
|
||
if(alternatives[j].inf)
|
||
{
|
||
for(let info of alternatives[j].inf)
|
||
{
|
||
temptag.appendChild(makespan(" "));
|
||
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(makespan(", "));
|
||
}
|
||
if(alternatives.length > 0)
|
||
temptag.appendChild(makespan(")"));
|
||
}
|
||
container.appendChild(temptag);
|
||
|
||
if(term.freq && term.freq.length > 0)
|
||
{
|
||
let vn_freq_data = document.createElement("span");
|
||
vn_freq_data.style = "font-size: 80%; margin-top: 4px; opacity: 0.7; margin-left: 5px;"
|
||
if(term.freq[0] != term.freq[1])
|
||
vn_freq_data.innerText = "#" + term.freq[2] + " (" + term.freq[0] + ":" + term.freq[1] + ")";
|
||
else
|
||
vn_freq_data.innerText = "#" + term.freq[2] + " (" + term.freq[0] + ")";
|
||
if(term.freq[3] > 0.005)
|
||
vn_freq_data.innerText += " (" + Math.round(term.freq[3]*100)/100 + "ppm)";
|
||
else if(term.freq[3] > 0.0005)
|
||
vn_freq_data.innerText += " (" + Math.round(term.freq[3]*1000)/1000 + "ppm)";
|
||
else
|
||
vn_freq_data.innerText += " (" + Math.round(term.freq[3]*10000)/10000 + "ppm)";
|
||
container.appendChild(vn_freq_data);
|
||
}
|
||
|
||
container.appendChild(document.createElement("br"));
|
||
|
||
let definition = document.createElement("div");
|
||
definition.className = "nazeka_definitions";
|
||
|
||
let jmdict_div = undefined;
|
||
if(typeof term.seq == "string" && term.seq.startsWith("NONE_"))
|
||
jmdict_div = document.createElement("div");
|
||
else
|
||
jmdict_div = elementize_jmdict_defs(term.sense);
|
||
let json_div = elementize_json_defs(term.json);
|
||
|
||
if (settings.definitions_mode == 0 || (settings.normal_definitions_in_mining && mining_ui_exists())) // normal
|
||
{
|
||
if(jmdict_div) definition.appendChild(jmdict_div);
|
||
if(json_div) definition.appendChild(json_div);
|
||
}
|
||
else if (settings.definitions_mode == 1) // json first
|
||
{
|
||
if(json_div) definition.appendChild(json_div);
|
||
if(jmdict_div)
|
||
{
|
||
let jmdict_head_div = document.createElement("div");
|
||
let jmdict_head_span = document.createElement("span");
|
||
jmdict_head_span.appendChild(document.createTextNode("―jmdict―"));
|
||
jmdict_head_span.className = "jmdict_title";
|
||
jmdict_head_div.className = "jmdict_head";
|
||
jmdict_head_div.appendChild(jmdict_head_span);
|
||
jmdict_div.insertBefore(jmdict_head_div, jmdict_div.firstChild);
|
||
definition.appendChild(jmdict_div);
|
||
}
|
||
}
|
||
else if (settings.definitions_mode == 2) // json or else jmdict
|
||
{
|
||
if(json_div) definition.appendChild(json_div);
|
||
else definition.appendChild(jmdict_div);
|
||
}
|
||
else if (settings.definitions_mode == 3) // json only
|
||
{
|
||
if(json_div) definition.appendChild(json_div);
|
||
}
|
||
else if (settings.definitions_mode == 4) // none
|
||
{
|
||
|
||
}
|
||
else // normal (fallback)
|
||
{
|
||
if(jmdict_div) definition.appendChild(jmdict_div);
|
||
if(json_div) definition.appendChild(json_div);
|
||
}
|
||
|
||
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
|
||
let lastMoreText = "";
|
||
function build_div (text, result, moreText, index)
|
||
{
|
||
lastMoreText = moreText;
|
||
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)
|
||
{
|
||
lastMoreText = moreText;
|
||
last_displayed = results;
|
||
let middle = build_div_intermediary();
|
||
let first = true;
|
||
for(let lookup of results)
|
||
{
|
||
if(settings.corner == 2 || settings.corner == 3)
|
||
{
|
||
if(!first && settings.inserthr)
|
||
middle.firstChild.insertBefore(document.createElement("hr"), middle.firstChild.children[0]);
|
||
middle.firstChild.insertBefore(build_div_inner(lookup.text, lookup.result, moreText, index, first), middle.firstChild.children[0]);
|
||
}
|
||
else
|
||
{
|
||
if(!first && settings.inserthr)
|
||
middle.firstChild.appendChild(document.createElement("hr"));
|
||
middle.firstChild.appendChild(build_div_inner(lookup.text, lookup.result, moreText, index, first));
|
||
}
|
||
first = false;
|
||
}
|
||
return middle;
|
||
}
|
||
|
||
/*
|
||
`.nazeka_main_keb{font-size:${settings.dict_item_fontsize}px;white-space:nowrap;font-family: ${font}IPAGothic,TakaoGothic,Noto Sans CJK JP Regular,Meiryo,sans-serif;color:${settings.hlcolor}}
|
||
.nazeka_main_reb{font-size:${settings.dict_item_fontsize}px;white-space:nowrap;font-family: ${font}IPAGothic,TakaoGothic,Noto Sans CJK JP Regular,Meiryo,sans-serif;color:${settings.hlcolor}}
|
||
.nazeka_word * {vertical-align: middle}
|
||
.nazeka_sub_keb{font-size:${settings.reading_fontsize}px;white-space:nowrap;font-family: ${font}IPAGothic,TakaoGothic,Noto Sans CJK JP Regular,Meiryo,sans-serif}
|
||
.nazeka_sub_reb{font-size:${settings.reading_fontsize}px;white-space:nowrap;color:${settings.hlcolor2}}
|
||
.nazeka_original{float: right; margin-right: 2px; margin-left:4px; opacity:0.7;}
|
||
*/
|
||
function build_div_kanji(text, kanjidata, moreText, index)
|
||
{
|
||
if(!kanjidata)
|
||
return undefined;
|
||
|
||
let middle = build_div_intermediary();
|
||
let target = middle.firstChild;
|
||
|
||
let style = get_style();
|
||
target.appendChild(style);
|
||
|
||
let head = document.createElement("div");
|
||
let info = document.createElement("div");
|
||
let tail = document.createElement("div");
|
||
if(platform != "android")
|
||
head.innerText = "Currently in individual kanji mode. Press [" + settings.hotkey_kanji_mode + "] to cancel.";
|
||
else
|
||
head.innerText = "Currently in individual kanji mode. Disable in options to go back to dictionary.";
|
||
|
||
let char = document.createElement("div");
|
||
char.className = "nazeka_main_keb";
|
||
char.textContent = text;
|
||
head.appendChild(char);
|
||
|
||
info.className = "kanji_info";
|
||
|
||
let grade = document.createElement("div");
|
||
grade.textContent = "Grade: ";
|
||
if(kanjidata["g"] == "X")
|
||
grade.textContent += "Hyougai";
|
||
else if(kanjidata["g"] == "9" || kanjidata["g"] == "10")
|
||
grade.textContent += "Jinmeiyou";
|
||
else if(kanjidata["g"] == "8")
|
||
grade.textContent += "Jouyou";
|
||
else if("123456".includes(kanjidata["g"]))
|
||
grade.textContent += "Kyouiku";
|
||
else
|
||
grade.textContent += "Unknown (Hyougai)";
|
||
|
||
let strokes = document.createElement("div");
|
||
strokes.textContent = "Strokes: " + kanjidata["s"];
|
||
|
||
let readings = document.createElement("div");
|
||
let readings_header = document.createElement("div");
|
||
readings_header.textContent = "Jouyou readings:";
|
||
readings.appendChild(readings_header);
|
||
|
||
let added_readings = 0;
|
||
function add_readings(target, readings)
|
||
{
|
||
let first = true;
|
||
for(let reading of readings)
|
||
{
|
||
added_readings += 1;
|
||
if(!first)
|
||
target.appendChild(document.createTextNode("、"));
|
||
let reading_div = document.createElement("span");
|
||
reading_div.className = "nazeka_sub_reb";
|
||
reading_div.innerText = reading;
|
||
target.appendChild(reading_div);
|
||
first = false;
|
||
}
|
||
}
|
||
|
||
if("o" in kanjidata)
|
||
{
|
||
let onyomi = document.createElement("div");
|
||
onyomi.innerText = "On'yomi: ";
|
||
add_readings(onyomi, kanjidata["o"]);
|
||
readings.appendChild(onyomi);
|
||
}
|
||
if("k" in kanjidata)
|
||
{
|
||
let onyomi = document.createElement("div");
|
||
onyomi.innerText = "Kun'yomi: ";
|
||
add_readings(onyomi, kanjidata["k"]);
|
||
readings.appendChild(onyomi);
|
||
}
|
||
if("os" in kanjidata)
|
||
{
|
||
let onyomi = document.createElement("div");
|
||
onyomi.innerText = "On'yomi (special): ";
|
||
add_readings(onyomi, kanjidata["os"]);
|
||
readings.appendChild(onyomi);
|
||
}
|
||
if("ks" in kanjidata)
|
||
{
|
||
let onyomi = document.createElement("div");
|
||
onyomi.innerText = "Kun'yomi (special): ";
|
||
add_readings(onyomi, kanjidata["ks"]);
|
||
readings.appendChild(onyomi);
|
||
}
|
||
|
||
let composition = document.createElement("div");
|
||
composition.textContent = "Composition: " + kanjidata["z"];
|
||
|
||
info.appendChild(grade);
|
||
if(settings.kanji_show_stroke_count)
|
||
info.appendChild(strokes);
|
||
if(settings.kanji_show_readings && added_readings > 0)
|
||
info.appendChild(readings);
|
||
if(settings.kanji_show_composition)
|
||
info.appendChild(composition);
|
||
|
||
if(settings.kanji_show_quality_warning)
|
||
{
|
||
tail.appendChild(document.createTextNode("Data for non-jouyou kanji may contain errors."));
|
||
tail.appendChild(document.createElement("br"));
|
||
tail.appendChild(document.createTextNode("Composition might not render correctly if it contains obscure characters."));
|
||
}
|
||
tail.style.marginBottom = "4px";
|
||
|
||
target.appendChild(head);
|
||
target.appendChild(info);
|
||
target.appendChild(tail);
|
||
return middle;
|
||
}
|
||
|
||
async function settings_init()
|
||
{
|
||
try
|
||
{
|
||
async function getvar(name)
|
||
{
|
||
let temp = (await browser.storage.local.get(name))[name];
|
||
if(temp != undefined)
|
||
settings[name] = temp;
|
||
}
|
||
getvar("enabled");
|
||
getvar("compact");
|
||
getvar("length");
|
||
getvar("scale");
|
||
getvar("width");
|
||
getvar("lookuprate");
|
||
getvar("fixedwidth");
|
||
getvar("fixedwidthpositioning");
|
||
getvar("superborder");
|
||
getvar("disableborder");
|
||
getvar("space_saver");
|
||
getvar("hide_deconj");
|
||
getvar("showoriginal");
|
||
getvar("reader_sticky");
|
||
|
||
getvar("bgcolor");
|
||
getvar("fgcolor");
|
||
getvar("hlcolor");
|
||
getvar("hlcolor2");
|
||
getvar("titlecolor");
|
||
getvar("font");
|
||
getvar("hlfont");
|
||
|
||
getvar("definition_fontsize");
|
||
getvar("dict_item_fontsize");
|
||
getvar("reading_fontsize");
|
||
|
||
getvar("alternatives_mode");
|
||
getvar("strict_alternatives");
|
||
getvar("definitions_mode");
|
||
getvar("normal_definitions_in_mining");
|
||
getvar("strict_epwing");
|
||
getvar("json_newlines_replace");
|
||
getvar("json_newlines_character");
|
||
|
||
getvar("corner");
|
||
getvar("xoffset");
|
||
getvar("yoffset");
|
||
|
||
getvar("strip_spaces");
|
||
getvar("ignore_linebreaks");
|
||
getvar("ignore_divs");
|
||
getvar("sticky");
|
||
getvar("popup_follows_mouse");
|
||
getvar("popup_requires_key");
|
||
|
||
getvar("x_dodge");
|
||
getvar("y_dodge");
|
||
getvar("sticky_maxheight");
|
||
|
||
getvar("hotkey_mine");
|
||
getvar("hotkey_nazeka_toggle");
|
||
getvar("hotkey_close");
|
||
getvar("hotkey_sticky");
|
||
getvar("hotkey_audio");
|
||
getvar("hotkey_nudge_left");
|
||
getvar("hotkey_nudge_right");
|
||
getvar("volume");
|
||
getvar("live_mining");
|
||
|
||
getvar("use_selection");
|
||
getvar("only_selection");
|
||
|
||
getvar("kanji_show_stroke_count");
|
||
getvar("kanji_show_readings");
|
||
getvar("kanji_show_composition");
|
||
getvar("kanji_show_quality_warning");
|
||
|
||
getvar("audio_force_https");
|
||
getvar("highlight_dict_headers");
|
||
|
||
getvar("dropshadow");
|
||
getvar("dropshadowradius");
|
||
getvar("dropshadowcolor");
|
||
getvar("padding");
|
||
getvar("novalign");
|
||
getvar("inserthr");
|
||
|
||
getvar("info_styling");
|
||
getvar("info_fgcolor");
|
||
getvar("info_bgcolor_pos");
|
||
getvar("info_bgcolor_num");
|
||
getvar("info_bgcolor_etc");
|
||
|
||
if(!settings.enabled && exists_div())
|
||
delete_div();
|
||
if(!settings.enabled)
|
||
delete_mining_ui();
|
||
} 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)
|
||
delete_mining_ui();
|
||
});
|
||
|
||
let last_lookup = undefined;
|
||
let last_manual_interaction = Date.now();
|
||
|
||
function may_contain_japanese(text)
|
||
{
|
||
for(let c of text.split(""))
|
||
if(c.codePointAt(0) > 0x3000)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
function is_number(c)
|
||
{
|
||
return "1234567890".includes(c);
|
||
}
|
||
|
||
let last_send_time = performance.now();
|
||
let currently_looking_up = false;
|
||
async function send_lookup(lookup, time)
|
||
{
|
||
if(currently_looking_up)
|
||
return;
|
||
let my_time = time + 1;
|
||
my_time -= 1;
|
||
if(is_number(lookup[0].split("")[0]) && !may_contain_japanese(lookup[5]))
|
||
return;
|
||
if(!settings.kanji_mode)
|
||
{
|
||
currently_looking_up = true;
|
||
let response = undefined;
|
||
try
|
||
{
|
||
response = await browser.runtime.sendMessage(
|
||
{
|
||
type:"search",
|
||
text:lookup[0],
|
||
time:my_time,
|
||
divexisted:exists_div(),
|
||
settings:{
|
||
alternatives_mode:settings.alternatives_mode,
|
||
strict_alternatives:settings.strict_alternatives,
|
||
strict_epwing:settings.strict_epwing
|
||
}
|
||
});
|
||
}
|
||
catch(e)
|
||
{
|
||
currently_looking_up = false;
|
||
return;
|
||
}
|
||
currently_looking_up = false;
|
||
if(response)
|
||
response = response["response"];
|
||
|
||
if(my_time < last_send_time)
|
||
return;
|
||
last_send_time = my_time;
|
||
|
||
if(response && response != "itsthesame")
|
||
{
|
||
if(!response.length)
|
||
{
|
||
let mydiv = build_div(response.text, response.result, lookup[5], lookup[6]);
|
||
last_lookup = lookup;
|
||
if(mydiv)
|
||
display_div(mydiv, lookup[3], lookup[4]);
|
||
}
|
||
else
|
||
{
|
||
last_lookup = lookup;
|
||
let mydiv = build_div_compound(response, lookup[5], lookup[6]);
|
||
if(mydiv)
|
||
display_div(mydiv, lookup[3], lookup[4]);
|
||
}
|
||
}
|
||
else if(response != "itsthesame" && exists_div() && !lastMoreText.includes(lookup[0].substring(0, Math.max(1, lookup[0].length-2))))
|
||
lookup_cancel();
|
||
}
|
||
else
|
||
{
|
||
let response = await browser.runtime.sendMessage(
|
||
{
|
||
type:"search_kanji",
|
||
divexisted:exists_div(),
|
||
text:[...lookup[0]][0]
|
||
});
|
||
if(response)
|
||
response = response["response"];
|
||
if(response && response != "itsthesame")
|
||
{
|
||
last_lookup = lookup;
|
||
let mydiv = build_div_kanji([...lookup[0]][0], response, lookup[5], lookup[6]);
|
||
if(mydiv)
|
||
display_div(mydiv, lookup[3], lookup[4]);
|
||
}
|
||
|
||
}
|
||
}
|
||
function lookup_enqueue(text, x, y, x2, y2, moreText, index, time)
|
||
{
|
||
send_lookup([text, x, y, x2, y2, moreText, index], time);
|
||
}
|
||
|
||
function lookup_cancel()
|
||
{
|
||
if(!is_sticky())
|
||
delete_div();
|
||
}
|
||
|
||
function manual_close()
|
||
{
|
||
delete_div();
|
||
last_manual_interaction = Date.now();
|
||
}
|
||
|
||
function manual_disable_sticky()
|
||
{
|
||
settings.sticky = false;
|
||
browser.storage.local.set({"sticky":false});
|
||
delete_div();
|
||
}
|
||
|
||
function manual_toggle_enabled()
|
||
{
|
||
settings.enabled = !settings.enabled;
|
||
browser.storage.local.set({"enabled":settings.enabled});
|
||
if(!settings.enabled)
|
||
delete_div();
|
||
}
|
||
|
||
function lookup_cancel_force()
|
||
{
|
||
delete_div();
|
||
}
|
||
|
||
let japanesePunctuation = "、。「」{}()【】『』〈〉《》:・/…︙‥︰+=-÷?!.~―";
|
||
let japaneseSeparators = "。?!";
|
||
|
||
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);
|
||
|
||
last_manual_interaction = Date.now();
|
||
send_lookup([text, last_lookup[1], last_lookup[2], last_lookup[3], last_lookup[4], last_lookup[5], index], performance.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);
|
||
|
||
last_manual_interaction = Date.now();
|
||
send_lookup([text, last_lookup[1], last_lookup[2], last_lookup[3], last_lookup[4], last_lookup[5], index], performance.now());
|
||
}
|
||
|
||
let time_of_last = Date.now();
|
||
|
||
let search_x_offset = -3;
|
||
|
||
function selection_filter_enabled(selection_range)
|
||
{
|
||
if(!settings.use_selection)
|
||
return false;
|
||
if(!selection_range)
|
||
return false;
|
||
if(selection_range.startOffset == selection_range.endOffset)
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
function selection_rejects_node(selection_range, textNode, offset)
|
||
{
|
||
if(!selection_range)
|
||
return false;
|
||
if(selection_range.startOffset == selection_range.endOffset)
|
||
return false;
|
||
try
|
||
{
|
||
if(!selection_range.isPointInRange(textNode, offset) || !selection_range.isPointInRange(textNode, offset+1))
|
||
return true;
|
||
}
|
||
catch(e){} // selection range was strange
|
||
return false;
|
||
}
|
||
|
||
function selection_clip_node_with_offsets(node, selection_range)
|
||
{
|
||
if (!selection_filter_enabled(selection_range))
|
||
{
|
||
return [node.textContent, 0];
|
||
}
|
||
|
||
if(!selection_range.intersectsNode(node))
|
||
return ["", 0];
|
||
|
||
let start = 0;
|
||
let end = node.length;
|
||
|
||
while(start < end)
|
||
{
|
||
if(!selection_range.isPointInRange(node, start))
|
||
start += 1;
|
||
else
|
||
break;
|
||
}
|
||
while(end > start)
|
||
{
|
||
if(!selection_range.isPointInRange(node, end))
|
||
end -= 1;
|
||
else
|
||
break;
|
||
}
|
||
return [node.data.slice(start, end), start];
|
||
}
|
||
|
||
function get_element_text_with_offsets(element, selection_range)
|
||
{
|
||
if(!(element instanceof Element))
|
||
{
|
||
return selection_clip_node_with_offsets(element, selection_range);
|
||
}
|
||
try
|
||
{
|
||
let display = getComputedStyle(element).display;
|
||
if(display == "ruby-text")
|
||
return ["", 0];
|
||
let tagname = element.tagName.toLowerCase();
|
||
if(tagname == "rt" || tagname == "rp")
|
||
return ["", 0];
|
||
|
||
let ret = "";
|
||
let ret_offset = -1;
|
||
for(let child of element.childNodes)
|
||
{
|
||
let asdf = get_element_text_with_offsets(child, selection_range);
|
||
ret += asdf[0];
|
||
if(ret_offset == -1)
|
||
ret_offset = asdf[1];
|
||
}
|
||
return [ret, ret_offset];
|
||
}
|
||
catch(err)
|
||
{
|
||
console.log("encountered error while trying to grab element text");
|
||
console.log(err);
|
||
}
|
||
return ["", 0];
|
||
}
|
||
|
||
function get_element_text(element, selection_range)
|
||
{
|
||
let ret = get_element_text_with_offsets(element, selection_range)[0];
|
||
return ret;
|
||
}
|
||
|
||
let hard_length_limit = 3000;
|
||
|
||
function grab_more_text(textNode, selection_range, direction = 1)
|
||
{
|
||
let ignore_normal_boundaries = selection_filter_enabled(selection_range);
|
||
|
||
if(direction > 0)
|
||
direction = 1;
|
||
else
|
||
direction = -1;
|
||
let text = "";
|
||
let current_node = textNode;
|
||
let iters = 0;
|
||
while(ignore_normal_boundaries
|
||
|| (text.length < settings.contextlength
|
||
&& (!text.includes("。") && !text.includes("!") && !text.includes("‼") && !text.includes("?") && !text.includes("??") && !text.includes("…") && (!text.includes("\n") || settings.ignore_linebreaks))
|
||
)
|
||
)
|
||
{
|
||
if(text.length > hard_length_limit)
|
||
break;
|
||
iters += 1;
|
||
if(current_node == undefined) break;
|
||
// search up parent element only if current element is inline-like
|
||
if (!ignore_normal_boundaries && !settings.ignore_divs)
|
||
{
|
||
try
|
||
{
|
||
let display = getComputedStyle(current_node).display;
|
||
let tagname = current_node.tagName.toLowerCase();
|
||
// FIXME get real inline vs block detection
|
||
|
||
let inline_like = display.includes("inline") || display == "ruby" || display == "ruby-base";
|
||
|
||
let ruby_interior = tagname == "rt" || tagname == "rp";
|
||
if(ruby_interior || !inline_like)
|
||
break;
|
||
}
|
||
catch(err){} // doesn't work with getComputedStyle or maybe tagName
|
||
}
|
||
if(iters > 100)
|
||
{
|
||
console.log("too many iterations");
|
||
console.log(current_node);
|
||
break;
|
||
}
|
||
|
||
let parent = current_node.parentNode;
|
||
if(parent == undefined || parent == current_node) 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 && (ignore_normal_boundaries || (text.length < settings.contextlength && (!text.includes("\n") || settings.ignore_linebreaks))))
|
||
{
|
||
if(text.length > hard_length_limit)
|
||
break;
|
||
let next_node = current_node.parentNode.childNodes[i];
|
||
i += direction;
|
||
|
||
if(ignore_normal_boundaries && !selection_range.intersectsNode(next_node))
|
||
break;
|
||
|
||
let tagname = next_node.tagName ? next_node.tagName.toLowerCase() : "";
|
||
|
||
if(tagname == "br" && !settings.ignore_linebreaks && !ignore_normal_boundaries)
|
||
break;
|
||
if(tagname == "rt" || tagname == "rp")
|
||
continue;
|
||
// next_node might not be an Element
|
||
try
|
||
{
|
||
let ttext = "";
|
||
|
||
let display = getComputedStyle(next_node).display;
|
||
|
||
// FIXME get real inline vs block detection
|
||
if(ignore_normal_boundaries || (display != "block" && display != "grid" && display != "table" && display != "none"))
|
||
{
|
||
let current = get_element_text(next_node, selection_range);
|
||
|
||
if(direction > 0)
|
||
ttext += current;
|
||
else
|
||
ttext = current + ttext;
|
||
}
|
||
if(direction > 0)
|
||
text += ttext;
|
||
else
|
||
text = ttext + text;
|
||
}
|
||
catch(err)
|
||
{
|
||
if(direction > 0)
|
||
text += get_element_text(next_node, selection_range);
|
||
else
|
||
text = get_element_text(next_node, selection_range) + text;
|
||
}
|
||
}
|
||
if(text.length < settings.contextlength)
|
||
current_node = current_node.parentNode;
|
||
else
|
||
break;
|
||
}
|
||
return text;
|
||
}
|
||
|
||
|
||
function grab_text(textNode, offset, elemental)
|
||
{
|
||
let selection_range = window.getSelection();
|
||
if(selection_range && selection_range.rangeCount > 0)
|
||
selection_range = selection_range.getRangeAt(0);
|
||
else
|
||
selection_range = undefined;
|
||
|
||
if(selection_filter_enabled(selection_range) && selection_rejects_node(selection_range, textNode, offset))
|
||
selection_range = undefined;
|
||
|
||
let text = "";
|
||
let moreText = "";
|
||
if(elemental)
|
||
{
|
||
moreText = textNode.value;
|
||
text = moreText.substring(offset, moreText.length);
|
||
}
|
||
else
|
||
{
|
||
let etc = get_element_text_with_offsets(textNode, selection_range);
|
||
moreText = etc[0];
|
||
offset -= etc[1];
|
||
text = moreText.substring(offset, moreText.length);
|
||
}
|
||
|
||
let lhs = grab_more_text(textNode, selection_range, -1);
|
||
let rhs = grab_more_text(textNode, selection_range);
|
||
text += rhs;
|
||
moreText = lhs + moreText + rhs;
|
||
|
||
if(settings.ignore_linebreaks)
|
||
{
|
||
text = text.replace(/\n/g, "");
|
||
moreText = moreText.replace(/\n/g, "");
|
||
}
|
||
|
||
if(settings.strip_spaces)
|
||
{
|
||
text = text.replace(/ /g, "");
|
||
moreText = moreText.replace(/ /g, "");
|
||
}
|
||
|
||
let index = moreText.lastIndexOf(text);
|
||
|
||
let leftwards = 0;
|
||
let rightwards = 0;
|
||
|
||
if (!selection_filter_enabled(selection_range))
|
||
{
|
||
while(!japaneseSeparators.includes(moreText[index + leftwards]) && moreText[index + leftwards] != "\n" && index + leftwards >= 0)
|
||
leftwards--;
|
||
leftwards++;
|
||
|
||
while(!japaneseSeparators.includes(moreText[index + rightwards]) && moreText[index + rightwards] != "\n" && index + rightwards < moreText.length)
|
||
rightwards++;
|
||
while(japanesePunctuation.includes(moreText[index + rightwards]) && index + rightwards < moreText.length)
|
||
rightwards++;
|
||
|
||
moreText = moreText.substring(index+leftwards, index+rightwards);
|
||
text = text.substring(0, rightwards);
|
||
|
||
index = -leftwards;
|
||
}
|
||
|
||
// grab text from later and surrounding DOM nodes
|
||
text = moreText.substring(index);
|
||
return [text, moreText, index];
|
||
}
|
||
|
||
function true_next_sibling(node)
|
||
{
|
||
if(node === null)
|
||
return null;
|
||
if(node.nextSibling !== null)
|
||
return node.nextSibling;
|
||
else
|
||
return true_next_sibling(node.parentNode);
|
||
}
|
||
|
||
let last_seen_event = undefined;
|
||
let shift_down = false;
|
||
let ctrl_down = false;
|
||
|
||
function ele_data_from_point(doc, x, y)
|
||
{
|
||
let ret = doc.elementFromPoint(x, y);
|
||
|
||
while (ret && ret.tagName && ["frame", "iframe"].includes(ret.tagName.toLowerCase()))
|
||
{
|
||
let wind;
|
||
try
|
||
{
|
||
wind = ret.contentWindow;
|
||
if (!wind) break;
|
||
}
|
||
catch (err)
|
||
{
|
||
break;
|
||
}
|
||
|
||
let rect;
|
||
try
|
||
{
|
||
rect = ret.getBoundingClientRect();
|
||
}
|
||
catch (err)
|
||
{
|
||
break;
|
||
}
|
||
|
||
let rx = rect.x;
|
||
let ry = rect.y;
|
||
|
||
let sx1 = 0;
|
||
let sy1 = 0;
|
||
let sx2 = 0;
|
||
let sy2 = 0;
|
||
|
||
try
|
||
{
|
||
if (wind.parent && wind.parent !== wind)
|
||
{
|
||
sx1 = wind.parent.scrollX || 0;
|
||
sy1 = wind.parent.scrollY || 0;
|
||
}
|
||
}
|
||
catch (err)
|
||
{
|
||
// Cross-origin parent frame; stop descending further
|
||
break;
|
||
}
|
||
|
||
try
|
||
{
|
||
sx2 = wind.scrollX || 0;
|
||
sy2 = wind.scrollY || 0;
|
||
}
|
||
catch (err)
|
||
{
|
||
sx2 = 0;
|
||
sy2 = 0;
|
||
}
|
||
|
||
x -= rx;
|
||
y -= ry;
|
||
x += sx2;
|
||
y += sy2;
|
||
|
||
try
|
||
{
|
||
doc = wind.document;
|
||
}
|
||
catch (err)
|
||
{
|
||
break;
|
||
}
|
||
|
||
try
|
||
{
|
||
ret = doc.elementFromPoint(x, y);
|
||
}
|
||
catch (err)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
return [ret, doc, x, y];
|
||
}
|
||
|
||
let last_event_time = 0;
|
||
function update(event)
|
||
{
|
||
if(!settings.enabled) return;
|
||
if(!event) return;
|
||
if(event.timeStamp < last_event_time)
|
||
return;
|
||
last_event_time = event.timeStamp;
|
||
last_seen_event = event;
|
||
|
||
shift_down = event.shiftKey;
|
||
ctrl_down = event.ctrlKey;
|
||
|
||
if((settings.popup_requires_key == 2 && !shift_down)
|
||
|| (settings.popup_requires_key == 1 && !ctrl_down))
|
||
{
|
||
function sq(n) {return n*n;}
|
||
if(Math.sqrt(sq(last_display_x-event.pageX) + sq(last_display_y-event.pageY)) > 20)
|
||
lookup_cancel();
|
||
return;
|
||
}
|
||
|
||
if(settings.popup_follows_mouse && exists_div() && !is_sticky() && platform != "android" )
|
||
{
|
||
let other = get_div();
|
||
let middle = other.firstChild;
|
||
if(middle)
|
||
{
|
||
last_lookup[3] = event.pageX;
|
||
last_lookup[4] = event.pageY;
|
||
display_div(middle, event.pageX, event.pageY);
|
||
}
|
||
}
|
||
|
||
if(Date.now() - time_of_last < settings.lookuprate)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if(platform == "android" && Date.now() - last_manual_interaction < 150)
|
||
return;
|
||
|
||
time_of_last = Date.now();
|
||
let textNode;
|
||
let offset;
|
||
let hitrect;
|
||
|
||
let nodeResetList = [];
|
||
let nodeResetSeen = new Set();
|
||
|
||
function reset_nodes()
|
||
{
|
||
for(let toreset of nodeResetList)
|
||
{
|
||
try
|
||
{
|
||
let element = toreset[0];
|
||
let z_index = toreset[1];
|
||
element.style.zIndex = z_index;
|
||
}
|
||
catch (err){} // don't care just reset everything correctly
|
||
}
|
||
}
|
||
|
||
function acceptable_element(node)
|
||
{
|
||
if(!textNode) return false;
|
||
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, searchoffset, y)
|
||
{
|
||
let xoffset = 0;
|
||
let yoffset = 0;
|
||
let data = ele_data_from_point(event.target.ownerDocument, x, y);
|
||
let ele = data[0];
|
||
let mydoc = data[1];
|
||
x = data[2];
|
||
y = data[3];
|
||
if(ele && window.getComputedStyle(ele).writingMode.includes("vertical"))
|
||
yoffset = searchoffset;
|
||
else
|
||
xoffset = searchoffset;
|
||
|
||
let caretdata = mydoc.caretPositionFromPoint(x+xoffset, y+yoffset);
|
||
|
||
if(caretdata)
|
||
{
|
||
textNode = caretdata.offsetNode;
|
||
offset = caretdata.offset;
|
||
|
||
try
|
||
{
|
||
let range = mydoc.createRange();
|
||
range.selectNode(textNode);
|
||
hitrect = range.getBoundingClientRect();
|
||
}
|
||
catch(e)
|
||
{
|
||
hitrect = undefined;
|
||
}
|
||
}
|
||
// sticky mode and android need to break out on parent detection
|
||
if(ele && !ele.contains(textNode) && is_sticky() && platform != "android" )
|
||
{
|
||
textNode = undefined;
|
||
offset = undefined;
|
||
hitrect = undefined;
|
||
}
|
||
// break out if the mouse isn't actually over the hit rectangle
|
||
if(hitrect && (x > hitrect.right+5 || x < hitrect.left-5+xoffset || y > hitrect.bottom+5 || y < hitrect.top-5+yoffset))
|
||
{
|
||
textNode = undefined;
|
||
offset = undefined;
|
||
hitrect = undefined;
|
||
}
|
||
};
|
||
hitpage(event.clientX, search_x_offset, event.clientY);
|
||
// try without the offset
|
||
if (textNode == undefined || (textNode.nodeType != 3 && !acceptable_element(textNode)))
|
||
{
|
||
hitpage(event.clientX, 0, event.clientY);
|
||
}
|
||
|
||
if (exists_div() && (platform == "android" || is_sticky()))
|
||
{
|
||
let ele = get_div();
|
||
if(ele.contains(textNode))
|
||
{
|
||
reset_nodes();
|
||
return;
|
||
}
|
||
}
|
||
|
||
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 = getComputedStyle(textNode)
|
||
let bg_color = style.getPropertyValue("background-color");
|
||
let position = style.getPropertyValue("position");
|
||
if(bg_color == "rgba(0, 0, 0, 0)" && (position == "absolute" || (position != "static" && textNode.tagName == "A")))
|
||
{
|
||
nodeIsBad = true;
|
||
nodeResetList.unshift([textNode, style.getPropertyValue("z-index")]);
|
||
nodeResetSeen.add(textNode);
|
||
textNode.style.zIndex = "-1000000000000000000000";
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
catch(err)
|
||
{
|
||
console.log(err);
|
||
}
|
||
}
|
||
}
|
||
reset_nodes();
|
||
if (settings.only_selection)
|
||
{
|
||
try
|
||
{
|
||
if(selection_rejects_node(window.getSelection().getRangeAt(0), textNode, offset))
|
||
{
|
||
lookup_cancel();
|
||
return;
|
||
}
|
||
}
|
||
catch(e){ } // window.getSelection() was null or null-like
|
||
}
|
||
|
||
// if there was text, use it
|
||
let elemental = acceptable_element(textNode);
|
||
if (textNode && (textNode.nodeType == 3 || elemental))
|
||
{
|
||
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.substring(0, Math.min(text.length, settings.length));
|
||
|
||
if(text != "")
|
||
lookup_enqueue(text, event.clientX, event.clientY, event.pageX, event.pageY, moreText, index, event.timeStamp);
|
||
else
|
||
lookup_cancel();
|
||
}
|
||
else
|
||
lookup_cancel();
|
||
}
|
||
|
||
function update_touch(event)
|
||
{
|
||
if(event.touches)
|
||
{
|
||
update(event.touches[0]);
|
||
}
|
||
}
|
||
|
||
function errormessage(text, soft = false)
|
||
{
|
||
if(!text) return;
|
||
let mydiv = document.createElement("div");
|
||
if(!soft)
|
||
mydiv.style = "background-color: #111; color: #CCC; font-family: Arial, sans-serif; font-size: 13px; width: 300px; border: 3px double red; position: fixed; right: 25px; top: 25px; z-index: 1000000000000000000000; padding: 5px; border-radius: 3px;"
|
||
else
|
||
mydiv.style = "background-color: #111; color: #CCC; font-family: Arial, sans-serif; font-size: 13px; width: 300px; border: 3px double orange; position: fixed; right: 25px; top: 25px; z-index: 1000000000000000000000; padding: 5px; border-radius: 3px;"
|
||
mydiv.textContent = text;
|
||
get_doc().body.appendChild(mydiv);
|
||
|
||
function delete_later()
|
||
{
|
||
mydiv.remove();
|
||
}
|
||
|
||
setTimeout(delete_later, 3000);
|
||
}
|
||
|
||
browser.runtime.onMessage.addListener((req, sender) =>
|
||
{
|
||
if (req.type == "error")
|
||
errormessage(req.error);
|
||
});
|
||
|
||
async function mine_to_storage(object)
|
||
{
|
||
let cards = (await browser.storage.local.get("cards")).cards;
|
||
if(!cards)
|
||
cards = [];
|
||
cards.push(object);
|
||
browser.storage.local.set({cards:cards});
|
||
}
|
||
|
||
function get_audio_text(mydiv, index = 0) // [reading, spelling]
|
||
{
|
||
mydiv = mydiv.getElementsByClassName("nazeka_audioref")[index]
|
||
if(mydiv)
|
||
{
|
||
let text = mydiv.innerText;
|
||
if(text.includes(";"))
|
||
{
|
||
let fields = text.split(";");
|
||
return [fields[0], fields[1]];
|
||
}
|
||
else
|
||
return [text, text];
|
||
}
|
||
return undefined;
|
||
}
|
||
|
||
async function try_to_play_audio(index = 0)
|
||
{
|
||
if(!exists_div())
|
||
return;
|
||
let mydiv = get_div();
|
||
let fields = get_audio_text(mydiv, index);
|
||
if(fields)
|
||
{
|
||
let url = "";
|
||
if(settings.audio_force_https)
|
||
url = "https://";
|
||
else
|
||
url = "http://";
|
||
url += "assets.languagepod101.com/dictionary/japanese/audiomp3.php?kana=" + fields[0] + "&kanji=" + fields[1];
|
||
console.log("Trying to play audio " + url);
|
||
browser.runtime.sendMessage({type:"play_audio", host:url, volume:settings.volume});
|
||
}
|
||
}
|
||
|
||
function send_ankiconnect_command(host, command)
|
||
{
|
||
let json = JSON.stringify(command);
|
||
|
||
browser.runtime.sendMessage({type:"ankiconnect_mine", host:host, command:json});
|
||
}
|
||
|
||
var __host = "http://127.0.0.1:8765"
|
||
|
||
async function mine_to_ankiconnect_with_base64_audio(object, audiofname, audiodata)
|
||
{
|
||
let object_keys = Object.keys(object);
|
||
let promise = browser.storage.local.get(["livemining_deckname", "livemining_modelname", "livemining_fields"]);
|
||
promise.then((storage) =>
|
||
{
|
||
if(storage["livemining_deckname"] === undefined)
|
||
storage["livemining_deckname"] = "Default";
|
||
if(storage["livemining_modelname"] === undefined)
|
||
storage["livemining_modelname"] = "Basic";
|
||
if(storage["livemining_fields"] === undefined)
|
||
storage["livemining_fields"] = [["Front", "found_spelling"], ["Back", "readings"]];
|
||
|
||
console.log("attempting to mine card");
|
||
|
||
let note = {
|
||
"action": "addNote",
|
||
"version": 6,
|
||
"params": {
|
||
"note": {
|
||
"deckName": storage["livemining_deckname"],
|
||
"modelName": storage["livemining_modelname"],
|
||
"fields": {},
|
||
"tags": [
|
||
"nazeka"
|
||
]
|
||
}
|
||
}
|
||
};
|
||
let audioadded = false;
|
||
let fields = {};
|
||
for(let pair of storage["livemining_fields"])
|
||
{
|
||
if(pair[1] != "audio_anki" && object_keys.includes(pair[1]))
|
||
fields[pair[0]] = String(object[pair[1]]);
|
||
if(pair[1] == "audio_anki")
|
||
{
|
||
if(audiofname != "" && audiodata != "")
|
||
{
|
||
if(!audioadded)
|
||
{
|
||
let mediafilecommand = {
|
||
"action": "storeMediaFile",
|
||
"version": 6,
|
||
"params": {
|
||
"filename": audiofname,
|
||
"data": audiodata
|
||
}
|
||
};
|
||
send_ankiconnect_command(__host, mediafilecommand);
|
||
audioadded = true;
|
||
}
|
||
// for some reason not even anki 2.0 supports this
|
||
//if(pair[1] == "audio_html")
|
||
// fields[pair[0]] = "<audio controls><source src=\"" + audiofname + "\"></audio>";
|
||
//else
|
||
fields[pair[0]] = "[sound:" + audiofname + "]";
|
||
}
|
||
else
|
||
fields[pair[0]] = "";
|
||
}
|
||
}
|
||
note["params"]["note"]["fields"] = fields;
|
||
send_ankiconnect_command(__host, note);
|
||
}, (e) => {console.log(e);});
|
||
}
|
||
|
||
async function get_url_blob_base64(url)
|
||
{
|
||
try
|
||
{
|
||
let data = await browser.runtime.sendMessage({type:"get_audio_base64", url:url});
|
||
return data;
|
||
}
|
||
catch(e)
|
||
{
|
||
console.log("errored");
|
||
console.log(e);
|
||
errormessage("Getting audio failed: " + e);
|
||
}
|
||
}
|
||
|
||
async function mine_to_ankiconnect(object)
|
||
{
|
||
try
|
||
{
|
||
let audio_base64 = "";
|
||
if(object.audio !== undefined)
|
||
{
|
||
let url = "";
|
||
if(settings.audio_force_https)
|
||
url = "https://";
|
||
else
|
||
url = "http://";
|
||
url += "assets.languagepod101.com/dictionary/japanese/audiomp3.php?kana=" + object.audio[0] + "&kanji=" + object.audio[1];
|
||
let response = await get_url_blob_base64(url);
|
||
if(response)
|
||
{
|
||
let res = response.response;
|
||
console.log("loaded");
|
||
let fname = "nazeka_audio_" + object.audio[0] + "_" + object.audio[1] + ".mp3";
|
||
let result = res.replace(/[^,]*,/, "");
|
||
mine_to_ankiconnect_with_base64_audio(object, fname, result);
|
||
}
|
||
else
|
||
{
|
||
console.log("errored");
|
||
errormessage("AnkiConnect mining partially failed: Failed to connect to languagepod101 to grab audio (going to try mining without audio)", true);
|
||
mine_to_ankiconnect_with_base64_audio(object, "", "");
|
||
}
|
||
}
|
||
else
|
||
mine_to_ankiconnect_with_base64_audio(object, "", "");
|
||
}
|
||
catch(e)
|
||
{
|
||
errormessage("AnkiConnect failed: " + e);
|
||
}
|
||
}
|
||
|
||
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 storage_readings = readings;
|
||
if(storage_readings.length < 2 || !storage_readings.startsWith("《"))
|
||
storage_readings = "《" + storage_readings.trim() + "》";
|
||
let definitions = word.getElementsByClassName("nazeka_definitions")[0];
|
||
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");
|
||
|
||
mine_to_storage({front: front, readings: storage_readings, definitions: definitions.innerText, lookup: lookup.textContent, sentence: sentence.textContent, index: index.textContent, seq: seq});
|
||
|
||
if(settings.live_mining)
|
||
{
|
||
let now = new Date();
|
||
|
||
let readings_raw = word.querySelector(".nazeka_mining_readings").innerText;
|
||
let spellings_raw = word.querySelector(".nazeka_mining_spellings").innerText;
|
||
|
||
let audiostuff = get_audio_text(word);
|
||
let audio_kanji = "";
|
||
let audio_reading = "";
|
||
|
||
if(readings.length >= 2 && readings.startsWith("《"))
|
||
readings = readings.substring(1, readings.length-1);
|
||
|
||
if(audiostuff !== undefined)
|
||
{
|
||
audio_kanji = audiostuff[1];
|
||
audio_reading = audiostuff[0];
|
||
}
|
||
mine_to_ankiconnect(
|
||
{
|
||
nothing:"",
|
||
|
||
found_spelling:front,
|
||
readings:readings,
|
||
|
||
definitions:definitions.innerHTML,
|
||
definitions_raw:definitions.innerText,
|
||
|
||
found_text: lookup.textContent,
|
||
context: sentence.textContent,
|
||
context_left: sentence.textContent.substring(0, index.textContent),
|
||
context_right: sentence.textContent.substring(parseInt(index.textContent) + lookup.textContent.length),
|
||
|
||
audio: audiostuff,
|
||
audio_kanji: audio_kanji,
|
||
audio_reading: audio_reading,
|
||
|
||
jmdict_id: seq,
|
||
url: get_doc().URL,
|
||
page_title: get_doc().title,
|
||
time_unix: now.getTime(),
|
||
time_local: now.toLocaleString(),
|
||
|
||
readings_raw: readings_raw,
|
||
spellings_raw: spellings_raw,
|
||
});
|
||
}
|
||
}
|
||
|
||
function keytest(event)
|
||
{
|
||
if(event.target != get_doc().body)
|
||
return;
|
||
if(settings.popup_requires_key == 2 && event.key == "Shift")
|
||
{
|
||
shift_down = true;
|
||
update(last_seen_event);
|
||
return;
|
||
}
|
||
if(settings.popup_requires_key == 1 && event.key == "Control")
|
||
{
|
||
ctrl_down = true;
|
||
update(last_seen_event);
|
||
return;
|
||
}
|
||
if(event.shiftKey || event.ctrlKey || event.metaKey || event.altKey)
|
||
return;
|
||
if(event.key == settings.hotkey_mine)
|
||
{
|
||
if(settings.kanji_mode)
|
||
errormessage("Cannot mine isolated kanji (make kanji flashcards manually)");
|
||
else
|
||
{
|
||
if(mining_ui_exists())
|
||
{
|
||
delete_mining_ui();
|
||
}
|
||
else
|
||
{
|
||
if(!exists_div())
|
||
return;
|
||
delete_mining_ui();
|
||
|
||
let mydiv = get_div().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 main spelling to mine it, or click this message to cancel.";
|
||
newheader.addEventListener("click", ()=>
|
||
{
|
||
delete_mining_ui();
|
||
});
|
||
mydiv.firstChild.firstChild.prepend(newheader);
|
||
mydiv.style.zIndex = 1000000000000000000000;
|
||
// do not compound! z-index can be range limited by the browser
|
||
mydiv.style.zIndex -= 1;
|
||
|
||
for(let keb of mydiv.getElementsByClassName("nazeka_main_keb"))
|
||
{
|
||
keb.addEventListener("click", (event)=>
|
||
{
|
||
mine(event.target);
|
||
delete_mining_ui();
|
||
});
|
||
}
|
||
for(let reb of mydiv.getElementsByClassName("nazeka_main_reb"))
|
||
{
|
||
reb.addEventListener("click", (event)=>
|
||
{
|
||
mine(event.target);
|
||
delete_mining_ui();
|
||
});
|
||
}
|
||
|
||
get_doc().body.appendChild(mydiv);
|
||
}
|
||
}
|
||
}
|
||
if(event.key == settings.hotkey_kanji_mode)
|
||
{
|
||
settings.kanji_mode = !settings.kanji_mode;
|
||
browser.storage.local.set({"kanji_mode":settings.kanji_mode});
|
||
if(exists_div())
|
||
send_lookup(last_lookup, performance.now());
|
||
}
|
||
if(event.key == settings.hotkey_sticky)
|
||
{
|
||
settings.sticky = !settings.sticky;
|
||
browser.storage.local.set({"sticky":settings.sticky});
|
||
if(exists_div())
|
||
{
|
||
let last_text = last_lookup[0];
|
||
let last_index = last_lookup[6];
|
||
send_lookup([" ", last_lookup[1], last_lookup[2], last_lookup[3], last_lookup[4], last_lookup[5], 0], performance.now());
|
||
send_lookup([last_text, last_lookup[1], last_lookup[2], last_lookup[3], last_lookup[4], last_lookup[5], last_index], performance.now());
|
||
}
|
||
}
|
||
// audio (no mining ui) or mining (yes mining ui) based on number keys
|
||
if("123456789".includes(event.key))
|
||
{
|
||
let index = parseInt(event.key)-1;
|
||
if(mining_ui_exists())
|
||
{
|
||
if(settings.kanji_mode)
|
||
errormessage("Cannot mine isolated kanji (make kanji flashcards manually)");
|
||
else
|
||
{
|
||
let mydiv = get_mining_ui();
|
||
let possibilities = mydiv.getElementsByClassName("nazeka_main_keb");
|
||
if(index >= possibilities.length)
|
||
errormessage("Mining index out of range");
|
||
else
|
||
{
|
||
mine(possibilities[index]);
|
||
delete_mining_ui();
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(exists_div())
|
||
try_to_play_audio(index);
|
||
}
|
||
}
|
||
if(event.key == settings.hotkey_nazeka_toggle)
|
||
manual_toggle_enabled();
|
||
|
||
if(!exists_div())
|
||
return;
|
||
|
||
if(event.key == settings.hotkey_close)
|
||
lookup_cancel_force();
|
||
if(event.key == settings.hotkey_audio)
|
||
try_to_play_audio();
|
||
if(event.key == settings.hotkey_nudge_left)
|
||
lookup_left();
|
||
if(event.key == settings.hotkey_nudge_right)
|
||
lookup_right();
|
||
}
|
||
|
||
function keyuntest(event)
|
||
{
|
||
if(event.key == "Shift")
|
||
shift_down = false;
|
||
if(event.key == "Control")
|
||
ctrl_down = false;
|
||
|
||
}
|
||
|
||
browser.runtime.onMessage.addListener((req, sender) =>
|
||
{
|
||
if(req.type == "reader_lookup")
|
||
send_lookup([req.text, req.x, req.y, req.x, req.y, req.text, 0], performance.now());
|
||
else if(req.type == "reader_mode")
|
||
{
|
||
console.log("we a readuh!");
|
||
is_reader = true;
|
||
}
|
||
});
|
||
|
||
window.addEventListener("mousemove", update);
|
||
window.addEventListener("keydown", keytest);
|
||
window.addEventListener("keyup", keyuntest);
|
||
get_doc().addEventListener("touchstart", update_touch);
|
||
|
||
console.log("inited content script");
|
||
console.log("inited at: " + get_doc().URL);
|
||
console.log("inited via: " + document.URL);
|
||
console.log("inited via: " + window.location);
|