2024-10-11 11:47:10 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @callback wmarkdown_ajax_callback
|
|
|
|
* @param {?string} response
|
|
|
|
* @param {!number} status
|
|
|
|
* @param {!number} state
|
|
|
|
* @param {!boolean} ok
|
|
|
|
* @param {!string} message
|
|
|
|
* @return {void}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
* @param {?string|Object.<string, any|null>} [inputs]
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
const WMarkDown = function(inputs){
|
|
|
|
|
|
|
|
/** @type {WMarkDown} */
|
|
|
|
const self = this,
|
|
|
|
/** @type {Array.<string>} */
|
|
|
|
dictionary_done = [],
|
|
|
|
/** @type {Array.<Object.<string, Array.<RegExp, string>|Array.<String>>>} */
|
|
|
|
dictionary = [],
|
|
|
|
/** @type {Array.<HTMLElement>} */
|
|
|
|
root_boxes = [],
|
|
|
|
/** @type {string} */
|
|
|
|
hash_alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
|
|
|
|
/** @type {number} */
|
|
|
|
hash_length = 11,
|
|
|
|
/** @type {Array.<string>} */
|
|
|
|
hashes = [],
|
|
|
|
/** @type {Array.<Array.<string>>} */
|
|
|
|
type_dictionary = [
|
|
|
|
["JavaScript/ECMAScript", "js", "javascript", "ecma", "ecmascript", "node", "nodejs", "typescript", "ts"],
|
|
|
|
["Python", "python", "py"]
|
|
|
|
];
|
|
|
|
/** @type {number|null} */
|
|
|
|
let thread_inteval = null,
|
|
|
|
/** @type {boolean} */
|
|
|
|
dictionary_loaded = false,
|
|
|
|
/** @type {Array.<string, string, RegExp} */
|
|
|
|
dictionary_item_mark = ["###@==_", "_==@###", /\#{3}\@\={2}_([0-9]+)_\={2}\@\#{3}/g],
|
|
|
|
/** @type {number} */
|
|
|
|
dictionary_z = 500,
|
|
|
|
dictionary_boxes = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @returns {void}
|
|
|
|
* @access private
|
|
|
|
*/
|
|
|
|
const constructor = () => {
|
|
|
|
|
|
|
|
if(typeof inputs == "string")
|
|
|
|
inputs = {dictionary : inputs};
|
|
|
|
|
|
|
|
thread_inteval = setInterval(thread_method, 250);
|
|
|
|
|
|
|
|
if(inputs.dictionary){
|
|
|
|
|
|
|
|
/** @type {number} */
|
|
|
|
let loaded = 0;
|
|
|
|
/** @type {Array.<string>} */
|
|
|
|
const dictionaries = inputs.dictionary instanceof Array ? inputs.dictionary : [inputs.dictionary],
|
|
|
|
/**
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
end = () => ++ loaded == dictionaries.length && (dictionary_loaded = true);
|
|
|
|
|
|
|
|
dictionaries.forEach(url => WMarkDown.prototype.get(url, data => {
|
|
|
|
try{
|
|
|
|
self.add_to_dictionary(JSON.parse(data));
|
|
|
|
}catch(exception){
|
|
|
|
console.error(exception);
|
|
|
|
};
|
|
|
|
end();
|
|
|
|
}));
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!Array.<Array.<string, string>, Array.<string>, Array.<string>>} data
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
this.add_to_dictionary = data => data.forEach(([patterns, definition, links]) => {
|
|
|
|
|
|
|
|
/** @type {number|null} */
|
|
|
|
let i = null;
|
|
|
|
|
|
|
|
patterns[0] instanceof Array || (patterns = [patterns]);
|
|
|
|
|
|
|
|
patterns.forEach(([pattern, text]) => {
|
|
|
|
if(!dictionary_done.includes(text)){
|
|
|
|
if(i === null)
|
|
|
|
dictionary[i = dictionary.length] = {
|
|
|
|
patterns : [[WMarkDown.prototype.format_pattern(pattern), text]],
|
|
|
|
definition : typeof definition == "string" ? definition : definition.join(""),
|
|
|
|
links : links
|
|
|
|
};
|
|
|
|
else
|
|
|
|
dictionary[i].patterns.push([WMarkDown.prototype.format_pattern(pattern), text]);
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @returns {string}
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
this.get_hash = () => {
|
|
|
|
|
|
|
|
/** @type {string} */
|
|
|
|
let hash;
|
|
|
|
/** @type {number} */
|
|
|
|
const l = hash_alphabet.length;
|
|
|
|
|
|
|
|
do{
|
|
|
|
hash = "";
|
|
|
|
while((hash += hash_alphabet[Math.random() * l >> 0]).length < hash_length);
|
|
|
|
}while(
|
|
|
|
hashes.includes(hash) ||
|
|
|
|
/^[0-9]/.test(hash) ||
|
|
|
|
document.querySelector("." + hash + ",#" + hash + ",[name=" + hash + "]")
|
|
|
|
);
|
|
|
|
hashes.push(hash);
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!NodeList} block
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
this.format_dictionary = block => block.childNodes.forEach((item, i) => {
|
|
|
|
|
|
|
|
/** @type {Array.<Array.<number, HTMLSpanElement>>} */
|
|
|
|
const blocks = [];
|
|
|
|
|
|
|
|
if(item.nodeName == "#text"){
|
|
|
|
if(item.textContent.trim()){
|
|
|
|
|
|
|
|
/** @type {string} */
|
|
|
|
let html = item.textContent;
|
|
|
|
/** @type {Array.<number, RegExpMatchArray, string>} */
|
|
|
|
const items = [];
|
|
|
|
|
|
|
|
dictionary.forEach((item, k) => {
|
|
|
|
item.patterns.forEach(([pattern, text]) => {
|
|
|
|
html = html.replace(pattern, (...matches) => {
|
|
|
|
|
|
|
|
/** @type {number} */
|
|
|
|
const j = items.length;
|
|
|
|
|
|
|
|
items.push([k, matches, text.replace(/\$([0-9])/g, (all, match_i) => {
|
|
|
|
return matches[match_i] !== null && matches[match_i] !== undefined ? matches[match_i] : "";
|
|
|
|
})]);
|
|
|
|
|
|
|
|
return dictionary_item_mark[0] + j + dictionary_item_mark[1];
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
if(html != item.textContent){
|
|
|
|
|
|
|
|
/** @type {HTMLSpanElement} */
|
|
|
|
const element = document.createElement("span");
|
|
|
|
|
|
|
|
blocks.push([i, element]);
|
|
|
|
|
|
|
|
element.innerHTML = html.replace(dictionary_item_mark[2], (_, j) => {
|
|
|
|
|
|
|
|
/** @type {Array.<number, RegExpMatchArray, string>} */
|
|
|
|
const [k, matches, text] = items[Number(j)];
|
|
|
|
|
|
|
|
return `<span class="wmd-dictionary-item" data-i="` + k + `" data-hash="` + self.get_hash() + `" onclick="wmarkdown.dictionary_over(this, event);">` + text + `</span>`;
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
}else if(
|
|
|
|
!["img", "a", "audio", "canvas", "picture"].includes((item.tagName || item.nodeName).toLowerCase()) &&
|
|
|
|
!["wmd-excluded", "wmd-code-block", "wmd-code-doc"].some(class_name => item.classList && item.classList.contains(class_name))
|
|
|
|
){
|
|
|
|
self.format_dictionary(item);
|
|
|
|
addEventListener("click", self.dictionary_out);
|
|
|
|
};
|
|
|
|
|
|
|
|
blocks.forEach(([i, element]) => {
|
|
|
|
block.insertBefore(element, block.childNodes[i]);
|
|
|
|
block.childNodes[i + 1].remove();
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLElement} item
|
|
|
|
* @returns {HTMLElement|null}
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
this.get_root_box = item => {
|
|
|
|
|
|
|
|
/** @type {HTMLElement|null} */
|
|
|
|
let box = null;
|
|
|
|
|
|
|
|
while(item && item.classList && (item = item.parentNode))
|
|
|
|
item.classList && item.classList.contains("wmarkdown") && (box = item);
|
|
|
|
|
|
|
|
return box;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLUListElement} data_box
|
|
|
|
* @param {!String} i18n
|
|
|
|
* @param {!String} text
|
|
|
|
* @param {!String} action
|
|
|
|
* @returns {HTMLLIElement}
|
|
|
|
* @access private
|
|
|
|
*/
|
|
|
|
const add_button_data = (data_box, i18n, text, action) => {
|
|
|
|
|
|
|
|
/** @type {HTMLLIElement} */
|
|
|
|
const button = data_box.appendChild(document.createElement("li"));
|
|
|
|
|
|
|
|
button.setAttribute("data-i18n", i18n);
|
|
|
|
button.setAttribute("data-i18n-without", true);
|
|
|
|
button.setAttribute("title", text);
|
|
|
|
button.setAttribute("onclick", action);
|
|
|
|
button.innerHTML = (`
|
|
|
|
<span data-icon="` + i18n + `"></span>
|
|
|
|
<span data-i18n="` + i18n + `">` + text + `</span>
|
|
|
|
<span class="value">` + text + `</span>
|
|
|
|
`);
|
|
|
|
|
|
|
|
return button;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLElement} box
|
|
|
|
* @param {!String} name
|
|
|
|
* @param {!Array.<String, String, String>} [buttons]
|
|
|
|
* @returns {void}
|
|
|
|
* @access private
|
|
|
|
*/
|
|
|
|
const set_special_type = (box, name, buttons) => {
|
|
|
|
|
|
|
|
while(!box.classList.contains("wmd-code-block") && (box = box.parentNode));
|
|
|
|
|
|
|
|
/** @type {HTMLUListElement} */
|
|
|
|
const data = box.querySelector(".data");
|
|
|
|
|
|
|
|
box.querySelector("li[data-i18n=type]>.value").innerHTML = name;
|
|
|
|
add_button_data(data, "view_switch", "View switch", "WMarkDown.prototype.view_switch(this, event);");
|
|
|
|
|
|
|
|
buttons && buttons.forEach(([i18n, text, action]) => add_button_data(data, i18n, text, action));
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLElement} content
|
|
|
|
* @param {!String} language
|
|
|
|
* @returns {HTMLDivElement}
|
|
|
|
* @access private
|
|
|
|
*/
|
|
|
|
const build_special_type = (content, language) => {
|
|
|
|
content.parentNode.childNodes.forEach(item => item.tagName && item.setAttribute("data-visible", false));
|
|
|
|
|
|
|
|
/** @type {HTMLDivElement} */
|
|
|
|
const box = content.parentNode.appendChild(document.createElement("div"));
|
|
|
|
|
|
|
|
box.setAttribute("class", "view");
|
|
|
|
box.setAttribute("data-visible", true);
|
|
|
|
set_special_type(content, language);
|
|
|
|
|
|
|
|
return box;
|
|
|
|
};
|
|
|
|
|
2024-11-14 11:09:12 +00:00
|
|
|
this.get_anp = item => {
|
|
|
|
|
|
|
|
while(!item.classList.contains("anp") && (item = item.parentNode))
|
|
|
|
if(!item.classList){
|
|
|
|
item = null;
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
|
|
|
|
return item;
|
|
|
|
};
|
|
|
|
|
2024-10-11 11:47:10 +00:00
|
|
|
/**
|
|
|
|
* @returns {void}
|
|
|
|
* @access private
|
|
|
|
*/
|
|
|
|
const thread_method = () => {
|
|
|
|
|
|
|
|
/** @type {HTMLBodyElement} */
|
|
|
|
const body = document.querySelector("body");
|
|
|
|
|
|
|
|
document.querySelectorAll(".wmd-code-block[data-processed=false]").forEach(block => {
|
|
|
|
|
|
|
|
/** @type {string} */
|
|
|
|
const language = block.getAttribute("data-type").toLowerCase(),
|
|
|
|
/** @type {HTMLElement} */
|
|
|
|
content = block.querySelector(".content"),
|
|
|
|
/** @type {String} */
|
2024-11-14 11:09:12 +00:00
|
|
|
type = block.getAttribute("data-type").toLowerCase(),
|
|
|
|
/** @type {HTMLElement|null} */
|
|
|
|
anp_item = self.get_anp(block),
|
|
|
|
/** @type {Boolean} */
|
|
|
|
dark_mode = (
|
|
|
|
anp_item ? (anp_item.getAttribute("data-gui_mode") == "dark" || (anp_item.getAttribute("data-gui-mode") == "default" && anp_item.getAttribute("data-dark-mode") == "true")) :
|
|
|
|
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
2024-10-11 11:47:10 +00:00
|
|
|
|
|
|
|
block.setAttribute("data-processed", true);
|
|
|
|
|
2024-11-14 11:09:12 +00:00
|
|
|
mermaid.initialize({
|
|
|
|
theme : dark_mode ? "dark" : "default",
|
|
|
|
themeVariables : {fontSize : "16px"}
|
|
|
|
});
|
|
|
|
|
2024-10-11 11:47:10 +00:00
|
|
|
if(["math", "maths", "mathjax"].includes(language)){
|
|
|
|
build_special_type(content, "MathJax").innerHTML = MathJax.tex2chtml(content.innerText).outerHTML;
|
|
|
|
MathJax.startup.document.clear();
|
|
|
|
MathJax.startup.document.updateDocument();
|
|
|
|
}else if(["mermaid", "mermaidjs", "mermaid_js"].includes(language)){
|
|
|
|
|
|
|
|
/** @type {HTMLDivElement} */
|
|
|
|
const box = build_special_type(content, "Mermaid JS");
|
|
|
|
|
|
|
|
mermaid.render(self.get_hash(), content.innerText).then(graph => box.innerHTML = graph.svg);
|
|
|
|
|
|
|
|
}else
|
|
|
|
try{
|
|
|
|
content.innerHTML = hljs.highlight(content.innerText, {language : (
|
|
|
|
["wmd-examples", "wmd", "wmarkdown"].includes(language) ? "markdown" :
|
|
|
|
language)}).value;
|
|
|
|
type_dictionary.some(alternatives => {
|
|
|
|
if(alternatives.includes(type)){
|
|
|
|
block.querySelector("[data-i18n=type]>.value").innerText = alternatives[0];
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}catch(exception){};
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
if(dictionary_loaded){
|
|
|
|
|
|
|
|
/** @type {HTMLElement} */
|
|
|
|
const block = document.querySelector("[data-dictionary-processed=false]");
|
|
|
|
|
|
|
|
if(block && [...block.childNodes].slice(-3).some(item => (
|
|
|
|
item.classList &&
|
|
|
|
item.classList.contains("wmd-process-and-loaded")
|
|
|
|
))){
|
|
|
|
|
|
|
|
block.querySelectorAll(".wmd-process-and-loaded").forEach(item => {
|
|
|
|
item.parentNode.hasAttribute("data-dictionary-processed") &&
|
|
|
|
item.parentNode.setAttribute("data-dictionary-processed", true);
|
|
|
|
item.remove();
|
|
|
|
});
|
|
|
|
|
|
|
|
self.format_dictionary(block);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
document.querySelectorAll("[data-list-unprocessed=true]").forEach(list => {
|
|
|
|
|
|
|
|
/** @type {HTMLSpanElement} */
|
|
|
|
const deployer = list.parentNode.insertBefore(document.createElement("span"), list.parentNode.childNodes[0]);
|
|
|
|
|
|
|
|
[
|
|
|
|
["data-deployed", list.getAttribute("data-deployed")],
|
|
|
|
["onclick", "WMarkDown.prototype.deploy(this, event);"]
|
|
|
|
].forEach(([key, value]) => deployer.setAttribute(key, value));
|
|
|
|
deployer.innerHTML = (`
|
|
|
|
<span data-icon="deploy"></span>
|
|
|
|
<span data-i18n="deploy">Deploy</span>
|
|
|
|
`);
|
|
|
|
|
|
|
|
list.setAttribute("data-list-unprocessed", false);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
document.querySelectorAll(".wmd-media[data-status=unprocessed]").forEach(item => item.setAttribute("data-status", "unloaded"));
|
|
|
|
|
|
|
|
if(body){
|
|
|
|
|
|
|
|
document.querySelectorAll(".wmd-media[data-status=unloaded]").forEach((item, i) => {
|
|
|
|
|
|
|
|
/** @type {DOMRect} */
|
|
|
|
const bounds = item.getBoundingClientRect();
|
|
|
|
|
|
|
|
if(
|
|
|
|
(bounds.y + bounds.height > -100 && bounds.y < body.offsetHeight + 100) &&
|
|
|
|
(bounds.x + bounds.width > -100 && bounds.x < body.offsetWidth + 100)
|
|
|
|
){
|
|
|
|
|
|
|
|
/** @type {HTMLElement} */
|
|
|
|
const main_item = item.querySelector("noscript+*");
|
|
|
|
|
|
|
|
item.setAttribute("data-status", "loading");
|
|
|
|
|
|
|
|
if(main_item.tagName.toLowerCase() == "img"){
|
|
|
|
[
|
|
|
|
["onload", "WMarkDown.prototype.image_loaded(this, event);"],
|
|
|
|
["onerror", "WMarkDown.prototype.image_load(this, event);"]
|
|
|
|
].forEach(([key, value]) => main_item.setAttribute(key, value));
|
|
|
|
item.setAttribute("data-status", "loading");
|
|
|
|
self.image_load(main_item);
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
document.querySelectorAll(".wmarkdown[data-menu-processed=false]").forEach(block => {
|
|
|
|
if(block.getAttribute("data-menu-processed") == "true")
|
|
|
|
return;
|
|
|
|
|
|
|
|
/** @type {HTMLUListElement|null} */
|
|
|
|
let menu = document.querySelector(".wmd-main-menu>ul");
|
|
|
|
/** @type {Array.<HTMLHeadingElement>} */
|
|
|
|
const items = block.querySelectorAll("h1,h2,h3,h4,h5,h6");
|
|
|
|
|
|
|
|
if(items.length){
|
|
|
|
|
|
|
|
/** @type {number} */
|
|
|
|
let current_level = 0;
|
|
|
|
|
|
|
|
if(!menu){
|
|
|
|
|
|
|
|
/** @type {HTMLDivElement} */
|
|
|
|
const button = document.querySelector("header").appendChild(document.createElement("div"));
|
|
|
|
|
|
|
|
(menu = (
|
|
|
|
document.querySelector("[data-cells]") || document.querySelector("body")
|
|
|
|
).appendChild(document.createElement("nav"))).appendChild(document.createElement("ul"));
|
|
|
|
menu.setAttribute("class", "wmd-main-menu");
|
|
|
|
menu.setAttribute("data-visible", false);
|
|
|
|
menu = menu.childNodes[0];
|
|
|
|
|
|
|
|
addEventListener("click", WMarkDown.prototype.hide_menu);
|
|
|
|
|
|
|
|
button.innerHTML += (`
|
|
|
|
<span data-i18n="menu" data-i18n-without="true" title="Menu" onclick="WMarkDown.prototype.show_menu(this, event);">
|
|
|
|
<span data-icon="menu"></span>
|
|
|
|
<span data-i18n="menu">Menu</span>
|
|
|
|
</span>
|
|
|
|
`);
|
|
|
|
button.setAttribute("class", "wmd-main-menu-button");
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
current_level = [...items].reduce((lower, item) => {
|
|
|
|
|
|
|
|
const level = Number(item.tagName[1]);
|
|
|
|
|
|
|
|
return level < lower ? level : lower
|
|
|
|
}, 6);
|
|
|
|
items.forEach(item => {
|
|
|
|
|
|
|
|
const level = Number(item.tagName[1]),
|
|
|
|
menu_item = document.createElement("li"),
|
|
|
|
anchor = menu_item.appendChild(document.createElement("a"));
|
|
|
|
|
|
|
|
menu_item.setAttribute("data-level", level);
|
|
|
|
anchor.innerText = item.innerText;
|
|
|
|
anchor.setAttribute("href", "#" + item.getAttribute("id"));
|
|
|
|
anchor.setAttribute("target", "_self");
|
|
|
|
anchor.setAttribute("title", item.innerText);
|
|
|
|
|
|
|
|
if(current_level < level){
|
|
|
|
|
|
|
|
const subblock = menu.childNodes[menu.childNodes.length - 1];
|
|
|
|
|
|
|
|
if(!(menu = subblock.childNodes[menu.childNodes.length - 1]) || menu.tagName.toLowerCase() != "ul"){
|
|
|
|
|
|
|
|
const button_deployer = subblock.insertBefore(document.createElement("span"), subblock.childNodes[0]);
|
|
|
|
|
|
|
|
menu = subblock.appendChild(document.createElement("ul"));
|
|
|
|
|
|
|
|
button_deployer.setAttribute("data-deployed", false);
|
|
|
|
button_deployer.setAttribute("onclick", "WMarkDown.prototype.deploy(this, event);");
|
|
|
|
button_deployer.innerHTML = (`
|
|
|
|
<span data-icon="deploy"></span>
|
|
|
|
<span data-i18n="deploy">Deploy</span>
|
|
|
|
`);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
current_level ++;
|
|
|
|
|
|
|
|
}else
|
|
|
|
while(current_level > level && menu.parentNode.parentNode.tagName.toLowerCase() == "ul"){
|
|
|
|
current_level --;
|
|
|
|
menu = menu.parentNode.parentNode;
|
|
|
|
};
|
|
|
|
|
|
|
|
menu.appendChild(menu_item);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
block.setAttribute("data-menu-processed", true);
|
|
|
|
block.querySelectorAll(".wmarkdown[data-menu-processed=false]").forEach(subblock => subblock.setAttribute("data-menu-processed", true));
|
|
|
|
|
|
|
|
window.location.hash && (window.location.href = window.location.hash);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLElement} item
|
|
|
|
* @param {!MouseEvent} event
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
*/
|
|
|
|
this.dictionary_over = (item, event) => setTimeout(() => {
|
|
|
|
|
|
|
|
/** @type {string|null} */
|
|
|
|
const hash = item.getAttribute("data-hash");
|
|
|
|
|
|
|
|
if(!hash || dictionary_boxes.includes(hash))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/** @type {HTMLBodyElement} */
|
|
|
|
const body = document.querySelector("body"),
|
|
|
|
/** @type {HTMLDivElement} */
|
|
|
|
box = document.querySelector("body").appendChild(document.createElement("div")),
|
|
|
|
/** @type {DOMRect} */
|
|
|
|
bounds = item.getBoundingClientRect(),
|
|
|
|
/** @type {number} */
|
|
|
|
x = bounds.x + (bounds.width / 2),
|
|
|
|
/** @type {number} */
|
|
|
|
y = bounds.y + (bounds.height / 2),
|
|
|
|
/** @type {number} */
|
|
|
|
i = Number(item.getAttribute("data-i")),
|
|
|
|
own_keys = [];
|
|
|
|
|
|
|
|
box.setAttribute("class", "wmd-dictionary-box");
|
|
|
|
box.setAttribute("data-dictionary-box", hash);
|
|
|
|
|
|
|
|
dictionary_boxes.push(hash);
|
|
|
|
|
|
|
|
box.innerHTML = (`
|
|
|
|
<div class="definition">` + dictionary[i].patterns.reduce((definition, [pattern, text]) => (
|
|
|
|
definition.replace(pattern, () => {
|
|
|
|
own_keys.push(text.replace(/\$[0-9]/g, ""));
|
|
|
|
return dictionary_item_mark[0] + (own_keys.length - 1) + dictionary_item_mark[1];
|
|
|
|
})
|
|
|
|
), dictionary[i].definition).replace(dictionary_item_mark[2], (all, i) => `<b class="wmd-excluded">` + own_keys[i] + `</b>`) + `</div>
|
|
|
|
<nav class="links">` + dictionary[i].links.map(link => `<a href="` + link + `" target="_blank" title="` + link + `" data-type="` + (
|
|
|
|
link.toLowerCase().match(/^(http|ftp|ws)s?\:\/{2}(w+[a-z0-9]\.)?([^\/]+)/i)[3].replace(/[^a-z0-9]+/g, "_")
|
|
|
|
) + `" style="background-image:url('` + link.match(/^[^\:]+\:\/{2}[^\/]+/)[0] + `/favicon.ico');"></a>`).join("") + `</nav>
|
|
|
|
`);
|
|
|
|
|
|
|
|
box.style.zIndex = dictionary_z ++;
|
|
|
|
if(x > body.offsetWidth / 2)
|
|
|
|
box.style.right = (body.offsetWidth - x) + "px";
|
|
|
|
else
|
|
|
|
box.style.left = x + "px";
|
|
|
|
if(y > body.offsetHeight / 2)
|
|
|
|
box.style.bottom = (body.offsetHeight - y) + "px";
|
|
|
|
else
|
|
|
|
box.style.top = y + "px";
|
|
|
|
|
|
|
|
setTimeout(() => self.format_dictionary(box.querySelector(".definition")), 100);
|
|
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLElement} item
|
|
|
|
* @param {!MouseEvent} event
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
this.dictionary_out = event => {
|
|
|
|
|
|
|
|
/** @type {string|null} */
|
|
|
|
let hash = null,
|
|
|
|
/** @type {HTMLElement} */
|
|
|
|
item = event.target,
|
|
|
|
/** @type {string|null} */
|
|
|
|
box_hash = null,
|
|
|
|
/** @type {string|null} */
|
|
|
|
item_hash = null;
|
|
|
|
|
|
|
|
while(item.classList){
|
|
|
|
if(item.classList.contains("wmd-dictionary-box")){
|
|
|
|
box_hash = hash = item.getAttribute("data-dictionary-box");
|
|
|
|
break;
|
|
|
|
}else if(item.classList.contains("wmd-dictionary-item"))
|
|
|
|
item_hash = hash = item.getAttribute("data-hash");
|
|
|
|
item = item.parentNode;
|
|
|
|
};
|
|
|
|
|
|
|
|
if(hash){
|
|
|
|
|
|
|
|
/** @type {number} */
|
|
|
|
let i = dictionary_boxes.indexOf(hash);
|
|
|
|
|
|
|
|
if(++ i){
|
|
|
|
|
|
|
|
/** @type {number} */
|
|
|
|
let j,
|
|
|
|
/** @type {number} */
|
|
|
|
k;
|
|
|
|
|
|
|
|
if(box_hash)
|
|
|
|
[j, k] = (
|
|
|
|
!dictionary_boxes.includes(item_hash) ? [i, dictionary_boxes.length] :
|
|
|
|
[i = dictionary_boxes.indexOf(item_hash) + 1, dictionary_boxes.length]);
|
|
|
|
else{
|
|
|
|
[j, k] = [0, dictionary_boxes.length];
|
|
|
|
i = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
dictionary_boxes.slice(j, k).forEach(hash => {
|
|
|
|
document.querySelector(".wmd-dictionary-box[data-dictionary-box=" + hash + "]").remove();
|
|
|
|
dictionary_boxes.splice(i, 1);
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
document.querySelectorAll(".wmd-dictionary-box").forEach(box => box.remove());
|
|
|
|
dictionary_boxes = [];
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!string} pattern
|
|
|
|
* @returns {RegExp}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
WMarkDown.prototype.format_pattern = pattern => {
|
|
|
|
|
|
|
|
/** @type {RegExpMatchArray} */
|
|
|
|
const matches = pattern.match(/^\/(.+)\/([a-z]*)$/);
|
|
|
|
|
|
|
|
matches || console.log([pattern, matches]);
|
|
|
|
|
|
|
|
return new RegExp(matches[1], matches[2]);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!string} url
|
|
|
|
* @param {!wmarkdown_ajax_callback} callback
|
|
|
|
* @returns {XMLHttpRequest}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
WMarkDown.prototype.get = (url, callback) => {
|
|
|
|
|
|
|
|
/** @type {boolean} */
|
|
|
|
let ended = false;
|
|
|
|
/** @type {XMLHttpRequest} */
|
|
|
|
const ajax = new XMLHttpRequest(),
|
|
|
|
/** @type {number} */
|
|
|
|
time = Date.now(),
|
|
|
|
/**
|
|
|
|
* @param {!string} message
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
end = message => !ended && (ended = true) && typeof callback == "function" && callback(
|
|
|
|
ajax.responseText,
|
|
|
|
ajax.status,
|
|
|
|
ajax.readyState,
|
|
|
|
message == "OK",
|
|
|
|
message
|
|
|
|
);
|
|
|
|
|
|
|
|
ajax.open("get", url, true);
|
|
|
|
ajax.timeout = 2000;
|
|
|
|
ajax.onreadystatechange = () => {
|
|
|
|
if(ended)
|
|
|
|
return;
|
|
|
|
if(ajax.readyState == 4)
|
|
|
|
end((ajax.status >= 200 && ajax.status < 300) || [301, 302, 304].includes(ajax.status) ? "OK" : "HTTP_ERROR");
|
|
|
|
else if(Date.now() - time > 2000)
|
|
|
|
end("FORCED_TIMEOUT");
|
|
|
|
};
|
|
|
|
ajax.send(null);
|
|
|
|
|
|
|
|
ajax.onerror = () => end("ERROR");
|
|
|
|
ajax.onabort = () => end("ABORTED");
|
|
|
|
ajax.ontimeout = () => end("TIMEOUT");
|
|
|
|
|
|
|
|
return ajax;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLSpanElement} item
|
|
|
|
* @param {!MouseEvent} event
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
WMarkDown.prototype.deploy = (item, event) => item.setAttribute("data-deployed", item.getAttribute("data-deployed") == "false");
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLImageElement} item
|
|
|
|
* @param {!ErrorEvent} [event]
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
WMarkDown.prototype.image_load = (item, event) => {
|
|
|
|
|
|
|
|
/** @type {Array.<string>} */
|
|
|
|
const images = JSON.parse(atob(item.getAttribute("data-sources"))),
|
|
|
|
/** @type {number} */
|
|
|
|
i = Number(item.getAttribute("data-i"));
|
|
|
|
|
|
|
|
if(i >= images.length){
|
|
|
|
item.parentNode.setAttribute("data-status", "error");
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
item.setAttribute("src", images[i]);
|
|
|
|
item.setAttribute("data-i", i + 1);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLImageElement} item
|
|
|
|
* @param {!EventTarget} [event]
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
WMarkDown.prototype.image_loaded = (item, event) => {
|
|
|
|
|
|
|
|
/** @type {HTMLSpanElement|Null} */
|
|
|
|
const span_image = item.parentNode.querySelector(".image");
|
|
|
|
|
|
|
|
item.parentNode.setAttribute("data-status", "success");
|
|
|
|
|
|
|
|
span_image && (span_image.style.backgroundImage = "url('" + item.src + "')");
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLElement} [item]
|
|
|
|
* @param {!EventTarget} [event]
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
WMarkDown.prototype.show_menu = (item, event) => document.querySelector(".wmd-main-menu").setAttribute("data-visible", true);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!EventTarget} event
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
WMarkDown.prototype.hide_menu = event => {
|
|
|
|
if(
|
|
|
|
event.target.parentNode.parentNode.classList &&
|
|
|
|
event.target.parentNode.parentNode.classList.contains("wmd-main-menu-button")
|
|
|
|
)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/** @type {HTMLElement|null} */
|
|
|
|
const main_menu = document.querySelector(".wmd-main-menu");
|
|
|
|
|
|
|
|
if(!main_menu || main_menu.getAttribute("data-visible") == "false")
|
|
|
|
return;
|
|
|
|
|
|
|
|
/** @type {HTMLElement} */
|
|
|
|
let item = event.target;
|
|
|
|
|
|
|
|
while(item.tagName.toLowerCase() != "body" && item != main_menu)
|
|
|
|
item = item.parentNode;
|
|
|
|
|
|
|
|
item != main_menu && main_menu.setAttribute("data-visible", false);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {!HTMLElement} item
|
|
|
|
* @param {!MouseEvent} [event]
|
|
|
|
* @returns {void}
|
|
|
|
* @access public
|
|
|
|
* @static
|
|
|
|
*/
|
|
|
|
WMarkDown.prototype.view_switch = (item, event) => {
|
|
|
|
|
|
|
|
/** @type {HTMLDivElement} */
|
|
|
|
const box = item.parentNode.parentNode.querySelector(".code");
|
|
|
|
|
|
|
|
if(!box)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/** @type {HTMLDivElement} */
|
|
|
|
const view = box.querySelector(".view"),
|
|
|
|
/** @type {Boolean} */
|
|
|
|
visible = view.getAttribute("data-visible") != "true";
|
|
|
|
|
|
|
|
view.setAttribute("data-visible", visible);
|
|
|
|
["lines", "content"].forEach(key => box.querySelector("." + key).setAttribute("data-visible", !visible));
|
|
|
|
|
|
|
|
};
|