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(); };