var defaultsettings = { fontsize: "16px", lineheight: "24px", margins: "20%", fontfamily: "helvetica,system-ui", savedelay: 2000, defaultpreviewinsplit: false, tagautocomplete: false, enablenetwork: true, titlebydefault: false, linksinnewtab: true, colors: true, tagsinlists: true, uselinkpopup: true, downloadextension: ".md" }; //builtin const markerslist = ["* ", "- ", " * ", " - ", ">> ", "> ", "=> ", "— ", "[ ] ", " ", "• ", "- [ ]", "[x] ", "- [x]"]; const codelanguages = ["xml", "js", "sql"]; const tagmark = "+"; // globals var previoustitle = ""; var metadata = null; var fileindex = 0; var workerid = null; var backup = ""; var settings = null; var tags = null; var commands = [ { hint: "Close menu" }, { shortcut: "ctrl+shift+P", hint: "Command palette", action: commandpalette, }, { shortcut: "ctrl+p", hint: "Show notes list", action: searchandloadnote }, { shortcut: "ctrl+n", hint: "New note", action: startnewnote }, { shortcut: "ctrl+shift+N", hint: "Quick new note", action: quicknewnote }, { hint: "Force save", action: flush, shortcut: "ctrl+s" }, { hint: "Share note", action: share }, { hint: "Share note (html)", action: sharehtml }, { shortcut: "ctrl+g", hint: "Find in notes", action: showgrep }, { shortcut: "ctrl+i", hint: "Toggle title", action: toggletitle }, { shortcut: "ctrl+m", hint: "Toggle preview", action: togglepreview }, { shortcut: "ctrl+shift+M", hint: "Toggle preview with merged subnotes", action: togglepreviewwithsubs }, { shortcut: "ctrl+d", hint: "Delete note", action: deletecurrentnote }, { hint: "Restore current note", action: restore }, { hint: "Insert markdown header", action: insertheader }, { hint: "Show help", action: showhelp }, { hint: "Search tags", action: searchtags, shortcut: "ctrl+shift+T" }, { hint: "Toggle split view", action: togglesplit }, { hint: "Load previous note", action: loadprevious, shortcut: "alt+ArrowLeft" }, { hint: "Load next note", action: loadnext, shortcut: "alt+ArrowRight" }, { hint: "Settings", action: editsettings }, { hint: "Change a setting", action: changesetting }, { hint: "Restore default settings", action: restoresettings }, { hint: "Note outline", action: showoutline, shortcut: "ctrl+o" }, { hint: "Show connected notes", action: shownotelinks, shortcut: "ctrl+l" }, { hint: "Show stats", action: showinfo, shortcut: "ctrl+w" }, { hint: "Toggle spell check", action: togglespellcheck, shortcut: "F7" }, { hint: "Create subnote from selection", action: createsubnote }, { hint: "Merge subnote", action: includesub }, { hint: "Comment selection", action: comment }, { hint: "Download current note", action: downloadnote }, { hint: "Download current note with merged subnotes", action: downloadnotewithsubs }, { hint: "Download all notes (md files in zip archive)", action: downloadnotes, shortcut: "ctrl+shift+S" }, { hint: "Download all notes of tag (md files in zip archive)", action: downloadtag }, { hint: "Download all notes (html files in zip archive)", action: downloadhtmlnotes }, { hint: "Insert text in todo", action: promptinserttodo }, { hint: "Replace", shortcut: "ctrl+h", action: searchandreplace }, { hint: "Sort text", action: sortselection }, { hint: "Show backlinks", action: backlinks }, { hint: "Import notes", action: importnotes }]; var snippets = [ { command: "/code", hint: "Code block", insert: "```\n\n```", cursor: -4 }, { command: "/date", hint: "Current date", insert: (new Date).toISOString().substring(0, 10), cursor: 0 }, { command: "/bonjour", hint: "Standard answer (fr)", insert: "Bonjour ,\n\n\n\nBien cordialement,\nSimon", cursor: -29 }, { command: "/hello", hint: "Standard answer (en)", insert: "Hello ,\n\n\n\nKind regards,\nSimon", cursor: -24 }, { command: "/-", hint: "Dialog mark", insert: "— ", cursor: 0 }, { command: "/comment", hint: "Comment", insert: "", cursor: -4 }, { command: "/x", hint: "Mark todo entry done", insert: "x " + (new Date).toISOString().substring(0, 10) + " " }]; function importnotes() { const input = document.createElement('input'); input.type = "file"; input.webkitdirectory = true; input.addEventListener("change", () => { let files = Array.from(input.files); files.forEach(file => { var title = file.name.replace(".md", ""); if (getguid(title)) { console.warn(title + " already exists, skip import"); } else { file.text().then(content => { var guid = createnote(title, content); }); } }); }); if ('showPicker' in HTMLInputElement.prototype) { input.showPicker(); } else { input.click(); } } function genguid() { return crypto.randomUUID(); } function seteditorcontent(content) { md.value = content; applycolors(); flush(); resize(); } function getrangecontent(range) { return md.value.substring(range.start, range.end); } function currentrange() { return { start: md.selectionStart, end: md.selectionEnd }; } function serialize(key, value) { localStorage.setItem(key, value); var name = metadata && metadata[key] ? metadata[key].title : key; console.log("'" + name + "' serialized locally"); } function createsubnote() { var title = prompt("Subnote tite:"); if (!title) { showtemporaryinfo("No title provided"); setpos(md.selectionStart); md.focus(); } else { var guid = getguid(title); if (guid) { showtemporaryinfo("'" + title + "' already exists"); setpos(md.selectionStart); md.focus(); } else { var range = currentrange(); var content = getrangecontent(range); guid = createnote(title); serialize(guid, content); seteditorcontent(md.value.substring(0, range.start) + "[[" + title + "]]" + md.value.substring(range.end)); } } } function comment() { seteditorcontent(md.value.substring(0, md.selectionStart) + "" + md.value.substring(md.selectionEnd)); } function includesub() { var range = linkrangeatpos(); if (range) { var title = linkatpos(); if (confirm("Replace [[" + title + "]] by its content?")) { var subnote = getnote(title); seteditorcontent(md.value.substring(0, range.start) + subnote.content + md.value.substring(range.end)); if (confirm("Delete '" + title + "'?")) { deletenote(title); } } } } function togglespellcheck() { md.spellcheck = !md.spellcheck; } function formatsize(size) { var unit = "b"; if (size > 1024) { size /= 1024; unit = "kb"; } if (size > 1024) { size /= 1024; unit = "mb"; } return size.toFixed(2) + " " + unit; } function pospercent() { return md.value.length > 0 ?(100 * md.selectionStart / md.value.length).toFixed(2) : 100; } function showinfo() { var tags = gettags(md.value); showtemporaryinfo( [ "title: " + title.value, "line count: " + md.value.split("\n").length, "word count: " + getwords(), "cursor position: " + md.selectionStart + " (" + pospercent() + "%)", (tags ? "tags: " + tags : ""), "notes count: " + sortedlist().length, "spell check: " + (md.spellcheck ? "en" : "dis") + "abled" ].join("\n")); } function savesettings() { localStorage.setItem("settings", JSON.stringify(settings)); } function children(guid) { var content = localStorage.getItem(guid); return (content .match(/\[\[([^\]]*)\]\]/g) || []) .map(l => l.replace("[[", "").replace("]]", "")) .filter(l => !l.includes("(deleted)")) .map(l => getguid(l)); } function parents(guid) { return Object.keys(metadata) .filter(g => localStorage.getItem(g).indexOf("[[" + metadata[guid].title + "]]") != -1); } function connected(guid) { var list = [guid]; var result = []; while (list.length) { var current = list.shift(); if (result.indexOf(current) == -1) { result.push(current); list = list.concat(children(current)).concat(parents(current)); } } return result; } function toggleeditor(hidden) { md.hidden = hidden; if (settings.colors) { colored.hidden = hidden; } } function shownotelinks() { if (settings.enablenetwork) { networkpage.hidden = false; toggleeditor(true); var nodes = []; var edges = []; var list = [getguid(title.value)]; while (list.length) { var current = list.shift(); if (!nodes.find(n => n.id == current)) { nodes.push( { id: current, label: metadata[current].title }); var buddies = children(current).concat(parents(current)); list = list.concat(buddies); buddies. forEach(buddy => { if (!edges.find(edge => (edge.to == current && edge.from == buddy) || (edge.to == buddy && edge.from == current))) { edges.push({ from: current, to: buddy }); } }); } } var data = { nodes: nodes, edges: edges }; var options = { nodes: { color: { background: "white", border: "black", }, font: { color: "black", size: 16 } } }; var graph = new vis.Network(network, data, options); graph.on("click", function(event) { networkpage.hidden = true; toggleeditor(false); loadnote(nodes.find(n => n.id == event.nodes[0]).label); }); graph.setSelection( { nodes : [getguid(title.value)] }); } else { searchinlist(connected(getguid(title.value)).map(g => metadata[g].title)) .then(loadnote); } } function showoutline() { var outline = {}; var pos = 0; md.value.split("\n").forEach((line, index, lines) => { pos += line.length + 1; if (line.startsWith("#")) { line = line .replace("# ", "") .replace(/#/g, "\xa0".repeat(4)); outline[line] = pos; } else if (line == "---" && index != 0 && index != 3) { var next = lines.find((current, i) => { return i > index && current != ""; }); if (next) { var nbcar = 80; next = next.length < nbcar ? next : next.substring(0, nbcar) + "..."; outline[next] = pos; } } }); var keys = Object .keys(outline) .sort((a,b) => { return outline[a] - outline[b]; }); searchinlist(keys) .then(line => { md.setSelectionRange(outline[line], outline[line]); md.focus(); }); } function linkrangeatpos() { var start = md.value.lastIndexOf("[[", md.selectionStart); if (start == -1 || md.value.substring(start, md.selectionStart).indexOf("\n") != -1) return null var end = md.value.indexOf("]]", md.selectionStart); if (end == -1 || md.value.substring(md.selectionStart, end).indexOf("\n") != -1) return null; return { start: start, end: end + 2 }; } function linkatpos() { var range = linkrangeatpos(); if (range) { return md.value.substring(range.start + 2, range.end - 2); } return null; } function tagatpos() { if (md.value.lastIndexOf("tags: ", md.selectionStart) < md.value.lastIndexOf("\n", md.selectionStart) || md.selectionStart < 6) { return null; } var start = md.value.lastIndexOf(" ", md.selectionStart); if (start == -1 || md.value.substring(start, md.selectionStart).indexOf("\n") != -1) return ""; var eol = md.value.indexOf("\n", md.selectionStart); var end = md.value.indexOf(",", md.selectionStart); if (end == -1 || eol < end) { end = eol; } return md.value.substring(start + 1, end); } function removelinkdialog() { if (typeof linkdialog != "undefined") { notepage.removeChild(linkdialog); } } function showlinkdialog(link) { var div = document.createElement("div"); div.setAttribute("style", "top:" + event.pageY + "px;left:" + event.pageX + "px"); div.setAttribute("id", "linkdialog"); var a = document.createElement("a"); a.setAttribute("id", "linkelt"); if (link.startsWith("http")) { a.setAttribute("href", link); a.setAttribute("target", "_blank"); div.onclick = removelinkdialog; a.innerText = "Open on " + link.replace("https://", "") .replace("http://", "") .replace("www.", "") .replace(/\/.*/, ""); } else { a.setAttribute("href", "#"); a.innerText = link; div.onclick = function() { removelinkdialog(); loadnote(link); }; } div.appendChild(a); notepage.appendChild(div); } function checkatpos(pos) { if (pos > 0 && md.value[pos - 1] == "[" && md.value[pos + 1] == "]") { return { pos: pos, val: md.value[pos] }; } return null; } function clickedcheck() { return checkatpos(md.selectionStart) || checkatpos(md.selectionStart + 1) || checkatpos(md.selectionStart - 1); } function clickeditor() { var word, link; if (event.ctrlKey) { link = linkatpos(); var tag = tagatpos(); word = wordatpos(); if (link) { loadnote(link); } else if (tag) { tagslist(); searchinlist(tags[tag.toLowerCase()]) .then(loadnote); } else if (word.startsWith("http")) { window.open(word, '_blank'); } } else if (clickedcheck()) { var res = clickedcheck(); seteditorcontent(md.value.substring(0, res.pos) + (res.val == " " ? "x" : " ") + md.value.substring(res.pos + 1)); setpos(res.pos); } else if (settings.uselinkpopup) { removelinkdialog(); link = linkatpos(); if (link) { showlinkdialog(link); } else { word = wordatpos(); if (word.startsWith("http")) { showlinkdialog(word); } } } } function restoresettings() { if (confirm("Restore default settings?")) { settings = defaultsettings; savesettings(); loadsettings(); } } function editsettings() { bind("settings.json", JSON.stringify(settings, null, " ")); } function editsetting(name) { if (typeof settings[name] != "undefined") { var value = settings[name]; var type = typeof value; if (type != "undefined") { value = prompt(name, value); if (value !== null) { if (type == "number") { value = parseInt(value); } else if (type == "boolean") { value = value === "true"; } settings[name] = value; savesettings(); loadsettings(); } } } } function changesetting() { searchinlist(Object.keys(settings).map(name => name + ": " + settings[name])) .then(setting => { editsetting(setting.split(":").shift()); }); } function showtemporaryinfo(info) { alert(info); } function getwords() { return md.value.split(/\s+\b/).length; } function issplit() { return window.location !== window.parent.location; } function togglesplit() { if (issplit()) { window.parent.location = "index.html"; } else { window.location = "split.html"; } } function tagslist() { tags = {}; Object.values(metadata) .forEach(n => { var ts = n.header.tags; ts.forEach(t => { tags[t] = tags[t] || []; tags[t].push(n.title); }); }); return searchinlist(Object.keys(tags).sort()); } function searchtags() { tagslist() .then(tag => searchinlist(tags[tag])) .then(loadnote); } function gettags(content) { var i = content.indexOf("tags: "); if (i > -1) { var j = content.indexOf("\n", i); return content.substring(i + 6, j) .split(",") .map(t => t.toLowerCase().trim()) .filter(t => t.length); } return []; } function share() { if (navigator.share) { navigator.share( { text: md.value, title: title.value }); } } function sharehtml() { if (navigator.share) { var file = new File(['
' + md2html(md.value) + ''], title.value + ".html", { type: "text/html", }); navigator.share( { title: title.value, files: [file] }); } } function getfilename(title) { return title.replace(/[\?\"<>|\*:\/\\]/g, "_") + settings.downloadextension; } function download(filename, content) { var element = document.createElement('a'); element.setAttribute('href', 'data:text/markdown;charset=utf-8,' + encodeURIComponent(content)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); element = null; } function downloadtag() { tagslist() .then(tag => { var zip = new JSZip(); Object.keys(metadata).forEach(guid => { if (metadata[guid].header.tags.includes(tag)) { zip.file(getfilename(metadata[guid].title), localStorage.getItem(guid)); } }); zip.generateAsync({type:"blob"}) .then(function(content) { saveAs(content, "notes-" + tag + "-" + timestamp() + ".zip"); }); }); } function downloadnotes() { var zip = new JSZip(); Object.keys(metadata).forEach(guid => { zip.file(getfilename(metadata[guid].title), localStorage.getItem(guid)); }); zip.generateAsync({type:"blob"}) .then(function(content) { saveAs(content, "notes-" + timestamp() + ".zip"); }); } function downloadhtmlnotes() { var zip = new JSZip(); Object.keys(metadata).forEach(guid => { zip.file(getfilename(metadata[guid].title) + ".html", md2html(localStorage.getItem(guid))); }); zip.generateAsync({type:"blob"}) .then(function(content) { saveAs(content, "notes-html-" + timestamp() + ".zip"); }); } function headerandtext(content) { var result = { header: "", text: content }; if (content.startsWith("---\n")) { var end = content.indexOf("---\n", 4); if (end > -1) { result.header = content.substring(0, end + 4); result.text = content.substring(end + 4); } } return result; } function inserttodo(text) { var guid = getguid("todo"); if (!guid) { guid = createnote("todo"); } var content = localStorage.getItem(guid); var split = headerandtext(content); content = split.header + text + "\n" + split.text; if (title.value == "todo") { seteditorcontent(content); } else { serialize(guid, content); metadata[guid].lastchanged = Date.now(); flush(); } } function promptinserttodo() { var text = prompt("Text:"); if (text) { inserttodo(text); } } function downloadnotewithsubs() { var note = withsubs(); if (note) { download(getfilename(note.title), note.content); } } function downloadnote() { download(getfilename(title.value), md.value); } function getguid(title) { return Object.keys(metadata).find(guid => metadata[guid].title === title); } function gotoline(line) { var i = 0; var pos = 0; while (i < line && pos > -1) { pos = md.value.indexOf("\n", pos + 1); i++; } if (pos > -1) { setpos(pos + 1); } } function createnote(title, content) { var guid = genguid(); content = content || defaultheaders(); var item = { lastchanged: Date.now(), title: title, pos: content.length, header: indexheader(content) }; metadata[guid] = item; serializeindex() serialize(guid, content); return guid; } function loadstorage() { metadata = JSON.parse(localStorage.getItem("index")); if (!metadata) { metadata = {}; createnote(timestamp()); } var params = new URLSearchParams(window.location.search); var clip = params.get("c"); if (clip) { settings.savedelay = 0; colored.hidden = true; md.hidden = true; var msg = document.createElement("div"); msg.innerText = "Clipping..."; notepage.appendChild(msg); inserttodo("@clip " + clip) window.close(); } var title = params.get("n") || params.get("name"); if (window.title.value) { // reload current loadnote(window.title.value); } else if (title) { loadnote(title); var line = params.get("l"); if (line) { gotoline(parseInt(line)); } } else { loadlast(); } } function loadsettings() { settings = {...defaultsettings}; var item = window.localStorage.getItem("settings"); if (item) { item = JSON.parse(item); for (var key in settings) { if (typeof item[key] !== "undefined") { settings[key] = item[key]; } } } document.body.style.fontSize = settings.fontsize; document.body.style.lineHeight = settings.lineheight; document.body.style.marginLeft = settings.margins; document.body.style.marginRight = settings.margins; document.body.style.fontFamily = settings.fontfamily; if (settings.titlebydefault && title.hidden) { toggletitle(); } colored.hidden = !settings.colors; md.style.color = settings.colors ? "transparent" : "inherit"; md.style.background = settings.colors ? "transparent" : "inherit"; } function checksaved() { if (!unsavedmark.hidden) { return "not saved"; } } function initsnippets() { // code languages codelanguages.forEach(lang => { if (!snippets.find(s => s.command == "/" + lang)) { snippets.push( { command: "/" + lang, hint: lang + " code block", insert: "```" + lang + "\n\n```", cursor: -4 }); } }); } function indexheader(content) { var indexedheader = { tags: [] }; var hat = headerandtext(content); var header = hat.header; if (header) { header.split("\n").forEach(line => { if (line && line != "---") { var t = line.split(":"); var val = t.pop(); var key = t.pop(); if (key == "tags") { val = val.split(",").map(t => t.trim()); if (val.length == 1 && !val[0]) { val.pop(); } } indexedheader[key] = val; } }); } return indexedheader; } function migratelegacystorage() { var legacy = localStorage.getItem("data"); if (legacy) { alert("Hey! I am about to migrate your notes to the brand new data model. No worries, I will keep a backup somewhere in case things go wrong. Take a deep breath, and click ok when you're ready."); localStorage.setItem("legacy", legacy); legacy = JSON.parse(legacy); var index = {}; legacy.reverse().forEach( (note, i) => { var guid = genguid(); localStorage.setItem(guid, note.content); note.header = indexheader(note.content); note.lastchanged = i; delete note.content; index[guid] = note; }); localStorage.setItem("index", JSON.stringify(index)); localStorage.removeItem("data"); } } function init() { migratelegacystorage(); loadsettings(); window.onbeforeunload = checksaved; window.onclick = focuseditor; title.value = ""; initsnippets(); loadstorage(); if (issplit()) { if (settings.defaultpreviewinsplit && name == "right") { togglepreview(); } else if (name == "left") { md.focus(); } } } function getlinesrange() { var start = md.selectionStart; var end = md.selectionEnd; while (start > 0 && md.value[start - 1] != "\n") start--; while (end < md.value.length && md.value[end] != "\n") end++; return { start: start, end: end }; } function backlinks() { searchinlist(Object.keys(metadata) .filter(guid => localStorage.getItem(guid).includes("[[" + title.value + "]]")) .map(guid => metadata[guid].title)) .then(loadnote); } function sortselection() { var content = md.value; var range = {start: 0, end: content.length}; if (md.selectionStart != md.selectionEnd) { range = getlinesrange(); } var selection = content.substring(range.start, range.end); var sorted = selection.split("\n").sort().join("\n"); seteditorcontent(content.substring(0, range.start) + sorted + content.substring(range.end)); } function wordatpos() { var words = md.value.split(/\s/); var i = 0; var word = ""; while (i < md.selectionStart) { word = words.shift(); i += word.length + 1; } return word; } function ontopbarclick() { if (title.hidden) { commandpalette(); } } function md2html(content) { var converter = new showdown.Converter(); converter.setOption("simplifiedAutoLink", true); converter.setOption("simpleLineBreaks", true); converter.setOption("metadata", true); converter.setOption("tasklists", true); converter.setOption("literalMidWordUnderscores", true); if (settings.linksinnewtab) { converter.setOption("openLinksInNewWindow", true); } var html = converter.makeHtml(content); // internal links html = html.replace(/\[\[([^\]]*)\]\]/g, "$1"); return html; } function loadlast() { loadnote(sortedlist()[0].title); } function loadprevious() { var list = sortedlist(); var index = list.findIndex(i => i.title == title.value); if (index > -1 && index < list.length - 1) { loadnote(list[index + 1].title); } } function loadnext() { var list = sortedlist(); var index = list.findIndex(i => i.title == title.value); if (index > -1 && index > 0) { loadnote(list[index - 1].title); } } function sortedlist() { return Object .values(metadata) .sort( (i,j) => j.lastchanged - i.lastchanged); } function grep(needle) { var result = {}; sortedlist() .forEach(item => { if (item.title.toLowerCase().includes(needle.toLowerCase())) { result[item.title] = {}; } var content = localStorage.getItem(getguid(item.title)); content.split("\n") .forEach((line, nb) => { if (line.toLowerCase().includes(needle.toLowerCase())) { result[item.title] = result[item.title] || {}; result[item.title][nb] = line; } }); }); return result; } function showgrepresult(needle, grepresult) { var grepcontent = ["# Search results: \"" + needle + "\""]; for (var file in grepresult) { grepcontent.push("[[" + file + "]]"); for (var l in grepresult[file]) { grepcontent.push("[" + l + "] " + grepresult[file][l]); } grepcontent.push(""); } if (grepcontent.length == 0) { grepcontent.push("No result."); } bind("Search result", grepcontent.join("\n")); if (preview.hidden) { togglepreview(); } } function showgrep() { var text = prompt("Search:", md.selectionEnd > md.selectionStart ? md.value.substr(md.selectionStart, md.selectionEnd - md.selectionStart).trim() : ""); if (text) { showgrepresult(text, grep(text)); } } function commandpalette() { searchinlist(commands .filter(command => !command.excludepalette) .map(command => { return { prefix: "command ", text: command.hint, suffix: command.shortcut ? [command.shortcut.toLowerCase()] : null }; }) .concat(snippets.map(s => { return { prefix: "snippet ", text: s.hint, suffix: [s.command] }; })) .concat( sortedlist() .map(item => { return { prefix: "note ", text: item.title, suffix: item.header.tags.map(t => tagmark + t) }; })) .concat(Object.keys(settings).map(s => { return { prefix: "setting ", text: s, suffix: [settings[s]] }; }))) .then(selected => { if (selected.prefix == "snippet ") { var snippet = snippets.find(s => s.hint == selected.text); insert(snippet.insert, snippet.cursor); md.focus(); } else if (selected.prefix == "note ") { loadnote(selected.text); } else if (selected.prefix == "setting ") { editsetting(selected.text); } else { var command = commands.find(c => c.hint == selected.text); if (command) { executecommand(command); } else { // if unknown command, create a new note loadnote(selected); } } }); } function insert(text, cursoroffset = 0, nbtodelete = 0) { var pos = md.selectionStart; var content = md.value; seteditorcontent( content.substring(0, pos - nbtodelete) + text + content.substring(pos)); setpos(pos - nbtodelete + text.length + cursoroffset); } function searchinlist(list) { return new Promise(selectitem => { fileindex = 0; searchdialog.hidden = false; filteredlist.hidden = false; filteredlist.innerHTML = ""; filter.value = ""; list.forEach(item => { var elt = document.createElement("div"); elt.tag = item; if (typeof item === "string") { elt.textContent = item; } else { var ts = document.createElement("span"); ts.setAttribute("class", "searchlistprefix"); ts.innerHTML = item.prefix || ""; elt.appendChild(ts); ts = document.createElement("span"); ts.innerHTML = item.text; elt.appendChild(ts); if (item.suffix) { item.suffix.forEach(t => { ts = document.createElement("span"); ts.setAttribute("class", "searchlistsuffix"); ts.innerHTML = " " + t; elt.appendChild(ts); }); } } elt.onclick = function() { searchdialog.hidden = true; selectitem(elt.tag); } filteredlist.appendChild(elt); }); applyfilter(); filter.onkeydown = function() { // doesn't work if focus is lost. if (event.key === "Enter") { event.preventDefault(); searchdialog.hidden = true; var selected = document.getElementsByClassName("selected")[0]; selectitem(selected ? selected.tag : filter.value); } } filter.focus(); }); } function applyfileindex() { var i = 0; [...filteredlist.children].forEach(child => { if (child.nodeName == "DIV") { child.className = "searchitem"; if(!child.hidden) { if (i++ == fileindex) { child.className = "selected"; if (child.customevent) { child.customevent(child.textContent); filter.focus(); } } } } }); } function getpos() { return md.selectionStart; } function setpos(pos) { md.setSelectionRange(pos, pos); } function before(nb) { return md.value.substring(getpos() - nb, getpos()); } function resize() { if (md.clientHeight < md.scrollHeight) { md.style.height = md.scrollHeight + 'px'; } } function postpone() { return new Promise(function(resolve) { clearTimeout(workerid); workerid = setTimeout(resolve, settings.savedelay); }); } function flush() { clearTimeout(workerid); workerid = null; var guid = getguid(title.value); if (guid) { var item = metadata[guid]; item.title = title.value; item.pos = md.selectionStart; item.header = indexheader(md.value); item.lastchanged = Date.now(); serializeindex(); serialize(guid, md.value); } else if (title.value == "settings.json") { settings = JSON.parse(md.value); savesettings(); loadsettings(); } unsavedmark.hidden = true; } function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } var languagekeywords = { "sql": ["select", "from", "where", "and", "or", "set", "declare"], "js": ["var", "for", "if", "else"], "zsh": ["sudo"], "python": ["molecule"] } function currentline() { return (md.value.substring(0, md.selectionStart).match(/\n/g) || []).length; } function lineatpos(pos) { return (md.value.substring(0, pos).match(/\n/g) || []).length; } function currentcol() { return md.selectionStart - Math.max(0, md.value.lastIndexOf("\n", md.selectionStart - 1)) - 1; } function rawline(index) { return md.value.split("\n")[index]; } var emptyline = "