WMarkDown = function(input){
const self = this,
default_settings = {
nulls : true,
default_value : null,
autostart : true,
object_name : "wmarkdown",
frames_per_second : 24,
timeout : 2000,
preload_timeout : 2000,
preload_wmarkdown : true,
hash_alphabet : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
hash_length : 11,
meanings : true,
case_sensitive : false,
autosearch : true,
onload_media_range : 1.4,
ajax_timeout : 2000,
mime_extension_file : "/json/mime_to_extension.json",
extension_mime_file : "/json/extension_to_mime.json",
default_mime : "application/octet-stream",
default_extension : "txt",
variable_name : "wmd",
wmd_file : "wmd.php",
display_view : null,
cells : 40,
item : ".wmarkdown",
size_multiplier : 1,
automultiplier : 1,
default_font_size : 14,
size_range : [.5, 4],
wmd_options : "wmd-options",
wmd_options_position : "header",
screen_sizes : [2560, 1920, 1280, 960, 640],
default_view : "content",
nulls : false,
default_value : null
},
custom_settings = {},
threads = [],
hashes = [],
mime_extension = {},
extension_mime = {},
screen = {
x : 0,
y : 0,
multiplier : 0,
automultiplier : 0,
sizes : []
},
on_ready_events = [];
let started = false,
thread = null,
threads_l = 0,
screen_size_change_thread = null,
last_change = 0,
on_ready_ok = false;
let object_name = this.object_name;
let item_self = this.item_self = document;
let hash_self = this.hash_self;
let dictionary = this.dictionary;
let multimedia = this.multimedia;
let wmonitor = this.wmonitor;
const null_or_undefined = this.null_or_undefined = value => value === undefined || value === null;
const settings_priority = inputs => [].concat(!inputs ? [] : inputs.push ? inputs : [inputs], [input, custom_settings, default_settings]);
const default_value = this.default_value = (_default, nulls) => _default !== undefined && ((typeof nulls == "boolean" ? nulls : settings("nulls", null, false, false)) || _default !== null ? _default : settings("default_value"), null, null, true);
const settings = this.settings = (names, inputs, _default, nulls) => {
if(!names)
return default_value(_default, nulls);
const l = (names.push ? names : names = [names]).length,
m = (inputs = (inputs ? inputs.push ? inputs : [inputs] : []).concat([input, default_settings])).length;
typeof nulls != "boolean" && (nulls = settings("nulls", null, false, false));
for(let j = 0; j < m; j ++)
if(typeof inputs[j] == "object")
for(let i = 0; i < l; i ++)
if(names[i] && inputs[j][names[i]] !== undefined && (nulls || inputs[j][names[i]] !== null))
return inputs[j][names[i]];
return default_value(_default, nulls);
};
const threads_function = () => {
const date = Date.now();
threads.forEach(thread => thread && thread());
if(item_self && date - last_change > 2000){
last_change = date;
const mobile = self.is_mobile();
item_self.setAttribute("data-mobile", mobile);
item_self.setAttribute("data-display-view", mobile ? "mobile" : "pc");
};
};
const threads_add = this.threads_add = method => {
if(typeof method != "function")
return null;
let i = 0;
for(; i < threads_l; i ++)
if(!threads[i])
break;
threads[i] = method;
threads_l = threads.length;
return i;
};
const threads_remove = this.threads_remove = i => !isNaN(i) && threads[i] && (threads[i] = null);
const threads_start = this.threads_start = frames_per_second => thread === null && (thread = setInterval(threads_function, 1000 / (frames_per_second || settings("frames_per_second"))));
const threads_stop = this.threads_stop = () => {
if(thread === null)
return;
clearInterval(thread);
thread = null;
};
const is_dom_item = item => (
typeof HTMLElement == "object" ? item instanceof HTMLElement :
typeof Node == "object" ? item instanceof Node :
item && typeof item === "object" && !isNaN(item.nodeType) && item.nodeName == "string"
);
const preload = this.preload = (selector, callback) => {
if(typeof callback != "function")
return;
if(!selector){
callback(null, false, "NO_SELECTOR");
return;
};
if(is_dom_item(selector)){
callback(selector, false, "OK");
return;
};
if(!selector.substr){
callback(null, false, "NO_DOM_ITEM");
return;
};
let item;
try{
if(item = item_self.querySelector(selector)){
callback(item, false, "OK");
return;
};
}catch(error){
callback(null, false, "BAD_SELECTOR");
return;
};
const date = Date.now(),
timeout = settings(["preload_timeout", "timeout"]);
let preload = threads_add(() => {
if(item = item_self.querySelector(selector)){
threads_remove(preload);
callback(item, true, "OK");
}else if(Date.now() - date > timeout){
threads_remove(preload);
callback(null, true, "TIMEOUT");
};
});
};
const screen_size_change = () => {
if(!item_self)
return;
const multiplier = Number(item_self.getAttribute("data-size-multiplier")),
automultiplier = Number(item_self.getAttribute("data-size-automultiplier"));
if(screen.x == item_self.offsetWidth && screen.y == item_self.offsetHeight && screen.multiplier == multiplier && screen.automultiplier == automultiplier)
return;
screen.x = item_self.offsetWidth;
screen.y = item_self.offsetHeight;
screen.multiplier = multiplier;
screen.automultiplier = automultiplier;
const x_higher = screen.x > screen.y;
let size = "0";
!screen.sizes.some((value, i) => i && !(value > screen.x && (size += " " + i)));
item_self.setAttribute("data-direction", x_higher ? "horizontal" : "vertical");
item_self.style.fontSize = ((
item_self.getAttribute("data-mobile") == "true" ?
screen[x_higher ? "y" : "x"] / Number(item_self.getAttribute("data-cells")) :
item_self.getAttribute("data-font-size")
) * multiplier * automultiplier) + "px";
item_self.setAttribute("data-screen-size", size.trim());
};
const button = this.button = (name, action, default_text) => {
const text = default_text;
return (`
`);
};
const preload_wmarkdown = this.preload_wmarkdown = callback => {
if(typeof callback != "function")
return;
if(item_self && item_self.classList && item_self.classList.contains("wmarkdown")){
callback(item_self, false, "OK");
return;
};
preload(".wmarkdown", wmarkdown => {
if(!wmarkdown){
callback(null, false, "NO_WMARKDOWN");
return;
};
const mobile = self.is_mobile(),
display_view = settings("display_view"),
multiplier = settings(["size_multiplier", "multiplier"]),
view_menu_items = {
menu : item_self.querySelector(".headers-menu [data-i18n=headers_menu]"),
content : item_self.querySelector("fieldset.content h1,h2,h3,h4,h5,h6"),
files : item_self.querySelector("fieldset.files [data-i18n=files]")
};
item_self = self.item_self = wmarkdown;
item_self.setAttribute("data-mobile", mobile);
item_self.setAttribute("data-display-view", display_view || (mobile ? "mobile" : "pc"));
item_self.setAttribute("data-cells", settings("cells"));
item_self.setAttribute("data-size-multiplier", multiplier);
item_self.setAttribute("data-size-automultiplier", settings(["size_automultiplier", "automultiplier"]));
item_self.setAttribute("data-font-size", settings(["default_font_size", "font_size"]));
item_self.setAttribute("data-view", settings(["default_view", "view"]));
screen_size_change_thread = threads_add(screen_size_change);
with(item_self.querySelector("header").appendChild(document.createElement("div"))){
setAttribute("class", "view-menu buttons group");
for(const key in view_menu_items)
view_menu_items[key] && (innerHTML += button(key, object_name + ".view_show(this, event, '" + key + "');", view_menu_items[key]));
};
preload(settings("item"), () => {
const menu_items = item_self.querySelectorAll(".headers-menu [data-levels]");
menu_items.forEach((item, i) => {
if(!i)
return;
const level = Number(item.getAttribute("data-level")),
j = i - 1;
if(level > Number(menu_items[j].getAttribute("data-level")))
menu_items[j].setAttribute("data-levels", Number(menu_items[j].getAttribute("data-levels")) + 1);
});
});
preload("[data-preload=wmarkdown-preloader]", preloader => {
if(!preloader){
callback(null, false, "NO_PRELOADER");
return;
};
preloader.remove();
setTimeout(() => callback(item_self, true, "OK"), 1000);
const options_class = settings(["wmd_options", "options"]);
let options = item_self.querySelector("." + options_class);
!options && (options = item_self.querySelector(settings(["wmd_options_position", "options_position"])).appendChild(document.createElement("div"))).setAttribute("class", options_class);
if(!options.querySelector("[name=multiplier]")){
const range = settings(["size_range", "multiplier_range"]);
with(options.appendChild(document.createElement("span"))){
setAttribute("class", "multiplier");
innerHTML = (`
`);
// setAttribute("data-clicked", false);
// setAttribute("onmousedown", object_name + ".multiplier_mouse_down(this, event);");
// setAttribute("onmouseup", object_name + ".multiplier_mouse_up(this, event);");
// setAttribute("onmouseout", object_name + ".multiplier_mouse_up(this, event);");
// setAttribute("onmousemove", object_name + ".multiplier_change(this, event);");
// innerHTML = ``;
};
};
});
});
};
this.start = () => {
if(started)
return;
started = true;
threads_start();
screen.sizes = settings("screen_sizes");
preload_wmarkdown(wmarkdown => {
if(!wmarkdown)
return;
const references = {},
logo = wmarkdown.querySelector(".logo img"),
temporary_image = new Image();
logo.setAttribute("data-status", "loading");
temporary_image.src = logo.getAttribute("src");
temporary_image.addEventListener("load", event => logo.parentNode.setAttribute("data-status", "ok"));
temporary_image.addEventListener("error", event => logo.parentNode.setAttribute("data-status", "error"));
try{
typeof mermaid !== undefined && mermaid.initialize({startOnLoad : true});
}catch(no_mermaid){console.error(["MERMAID_ERROR", no_mermaid])};
try{
typeof hljs !== undefined && item_self.querySelectorAll(".code-block:not([data-special=true])>.code-box").forEach(block => block.innerHTML = hljs.highlight(block.innerHTML.replace(/&(gt|lt|amp);/g, (...arguments) => {
return {
amp : "&",
gt : ">",
lt : "<"
}[arguments[1]] || arguments[0];
}), {language : block.parentNode.getAttribute("data-lang")}).value);
}catch(no_highlighter){console.error(["HIGHLIGHTER_ERROR", no_highlighter])};
item_self.querySelectorAll("[type=hidden][data-index]").forEach(reference => references[reference.getAttribute("data-index")] = reference.getAttribute("data-link"));
item_self.querySelectorAll("a[data-index]").forEach(link => link.setAttribute("href", references[link.getAttribute("data-index")] || link.getAttribute("href")));
item_self.querySelectorAll(".code-block[data-lang=maths]>div").forEach(item => {
try{
const formula = MathJax.tex2chtml(item.innerText);
item.innerHTML = '';
item.appendChild(formula);
}catch(no_maths){console.error(["MATHS_ERROR", no_maths])};
});
MathJax.startup.document.clear();
MathJax.startup.document.updateDocument();
item_self.querySelectorAll("fieldset.headers-menu nav a,fieldset.files nav a+a").forEach(anchor => anchor.setAttribute("onclick", (anchor.getAttribute("onclick") || "") + object_name + ".go_to_item(this, event);"))
dictionary && dictionary.start();
multimedia && multimedia.start();
wmonitor && wmonitor.start();
on_ready_ok = true;
on_ready_events.forEach(event => event && event());
});
};
const construct = () => {
object_name = self.object_name = settings("object_name");
WMarkDown.Dictionary && (dictionary = self.dictionary = new WMarkDown.Dictionary(self));
WMarkDown.Multimedia && (multimedia = self.multimedia = new WMarkDown.Multimedia(self));
WMarkDown.WMonitor && (wmonitor = self.wmonitor = new WMarkDown.WMonitor(self));
settings("autostart") && self.start();
};
const get_menu = menu => {
if(menu)
while(menu.tagName && menu.tagName.toLowerCase() != "fieldset" && (menu = menu.parentNode));
return menu || null;
};
const show_menu = (element, visible) => {
const menu = get_menu(element);
menu && document.querySelector(".wmarkdown>.body").setAttribute("data-" + menu.getAttribute("class").split("-")[0] + "-menu-deployed", visible);
};
this.hide_menu = (element, event) => show_menu(element, false);
this.show_menu = (element, event) => show_menu(element, true);
this.block_code_scroll = (element, event) => element.parentNode.querySelector("ol").style.marginTop = -element.scrollTop + "px";
this.deploy = (element, event, deployed) => {
if(!element)
return;
while(!element.hasAttribute("data-level") && (element = element.parentNode));
if(!element)
return;
const level = Number(element.getAttribute("data-level")) + 1,
items = element.parentNode.querySelectorAll("[data-id=" + element.getAttribute("data-id") + "]~li"),
l = items.length;
let parent_deployed = [];
element.setAttribute("data-deployed", typeof deployed == "boolean" ? deployed : deployed = element.getAttribute("data-deployed") == "false");
for(let i = 0; i < l; i ++){
const current_level = Number(items[i].getAttribute("data-level"));
if(current_level < level)
break;
if(deployed){
items[i].setAttribute("data-parent-deployed", current_level == level || parent_deployed[current_level - 1]);
parent_deployed[current_level] = items[i].getAttribute("data-deployed") == "true";
}else
items[i].setAttribute("data-parent-deployed", false);
};
};
this.hash = () => {
let hash,
alphabet = settings("hash_alphabet");
const length = settings("hash_length"),
l = (alphabet.push ? alphabet : alphabet = ("" + alphabet).split("")).length;
do{
hash = "";
while((hash += alphabet[Math.random() * l >> 0]).length < length);
}while(
hashes.includes(hash) ||
/^\d/.test(hash) ||
document.querySelector("." + hash + ",#" + hash + ",[name=" + hash + "]")
);
hashes.push(hash);
return hash;
};
this.utf8_encode = string => unescape(encodeURIComponent(string));
this.utf8_decode = string => decodeURIComponent(escape(string));
const load_file = this.load_file = (url, callback) => {
let ended = false;
const ajax = new XMLHttpRequest(),
timeout = settings(["ajax_timeout", "timeout"]),
end = error => {
if(ended)
return;
ended = true;
typeof callback == "function" && callback(ajax.responseText, ajax.status, ajax.readyState, error == "OK", error);
},
date = Date.now();
ajax.open("get", url, true);
ajax.timeout = timeout;
ajax.onreadystatechange = () => {
if(ended)
return;
if(ajax.readyState == 4)
end("OK");
else if(Date.now() - date > timeout)
end("FORCED_TIMEOUT");
};
ajax.send(null);
ajax.ontimeout = () => end("TIMEOUT");
ajax.onabort = () => end("ABORTED");
ajax.onerror = () => end("ERROR");
return ajax;
};
this.send = (variables, callback) => {
let ended = false;
const ajax = new XMLHttpRequest(),
timeout = settings(["ajax_timeout", "timeout"]),
end = error => {
if(ended)
return;
ended = true;
typeof callback == "function" && callback(ajax.responseText, ajax.status, ajax.readyState, error == "OK", error);
},
date = Date.now();
ajax.open("post", settings("wmd_file"), true);
ajax.timeout = timeout;
ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");
ajax.onreadystatechange = () => {
if(ended)
return;
if(ajax.readyState == 4)
end("OK");
else if(Date.now() - date > timeout)
end("FORCED_TIMEOUT");
};
ajax.send(encodeURIComponent(settings("variable_name")) + "=" + btoa(JSON.stringify(variables)));
ajax.ontimeout = () => end("TIMEOUT");
ajax.onabort = () => end("ABORTED");
ajax.onerror = () => end("ERROR");
return ajax;
};
this.get_mime = (path, callback) => {
const extension = path.match(/^.+\.([^\.\/]+)$/);
if(!extension)
return callback(settings("default_mime"));
if(extension_mime.length){
callback(extension_mime[extension[1]] || settings("default_mime"));
return;
};
load_file(settings("extension_mime_file"), data => {
let json = null;
try{
if(json = JSON.parse(data))
for(const extension in json)
extension_mime[extension] = json[extension];
}catch(exception){};
callback(extension_mime[extension[1]] || settings("default_mime"));
});
};
this.get_extensions = (mime, callback) => {
if(!mime)
return callback(settings("default_extension"));
if(mime_extension.length){
callback(mime_extension[mime[1]] || settings("default_extension"));
return;
};
load_file(settings("mime_extension_file"), data => {
let json = null;
try{
if(json = JSON.parse(data))
for(const mime in json)
mime_extension[mime] = json[mime];
}catch(exception){};
callback(mime_extension[mime[1]] || settings("default_extension"));
});
};
// http://detectmobilebrowsers.com
this.is_mobile = () => {
const user_agent = (navigator.userAgent || navigator.vendor || window.opera);
return (
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(user_agent) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(user_agent.substr(0, 4))
);
};
// this.multiplier_change = (item, event) => item_self.setAttribute("data-size-multiplier", item.value);
this.multiplier_mouse_down = (item, event) => item.setAttribute("data-clicked", true);
this.multiplier_mouse_up = (item, event) => item.setAttribute("data-clicked", false);
const multiplier_delimiter = (position, total) => position < 0 ? 0 : position > total ? total : position;
const multiplier_resize = () => {
const mark = item_self.querySelector(".wmd-options .multiplier .range-position"),
total = item_self.querySelector(".wmd-options .multiplier .range-box").offsetWidth - mark.offsetWidth,
range = settings("size_range"),
position = ((range[1] - range[0]) * (mark.offsetLeft / total)) + range[0];
item_self.setAttribute("data-size-multiplier", ((range[1] - range[0]) * (position / total)) + range[0]);
};
this.multiplier_change = (item, event) => {
if(item.getAttribute("data-clicked") != "true")
return;
const mark = item.querySelector(".range-position"),
total = item.offsetWidth - mark.offsetWidth,
range = settings("size_range");
let position = multiplier_delimiter(event.clientX - event.target.getBoundingClientRect().left - (mark.offsetWidth / 2));
mark.style.left = position + "px";
multiplier_resize();
};
this.multiplier_less = (item, event) => {
const mark = item.parentNode.querySelector(".range-position"),
total = item.offsetWidth - mark.offsetWidth;
let position = multiplier_delimiter(mark.offsetLeft - (total / 10));
mark.style.left = position + "px";
// console.log([position, mark.offsetLeft, (total / 10), mark.style.left, mark.offsetLeft]);
multiplier_resize();
};
this.multiplier_more = (item, event) => {
const mark = item.parentNode.querySelector(".range-position"),
total = item.offsetWidth - mark.offsetWidth;
let position = multiplier_delimiter(mark.offsetLeft + (total / 10));
mark.style.left = position + "px";
// console.log([position, mark.offsetLeft, (total / 10), mark.style.left, mark.offsetLeft]);
multiplier_resize();
};
this.view_show = (item, event, key) => item_self.setAttribute("data-view", key);
this.go_to_item = (item, event) => item_self.setAttribute("data-view", "content");
this.on_ready = callback => {
if(typeof callback != "function")
return null;
if(on_ready_ok){
callback();
return null;
};
let i = 0;
const l = on_ready_events.length;
for(; i < l; i ++)
if(!on_ready_events[i])
break;
on_ready_events[i] = callback;
return i;
};
this.remove_on_ready = i => !isNaN(i) && on_ready_events[i] && (on_ready_events[i] = null);
construct();
};