diff --git a/main.js b/main.js
index f3300d3..e0c7929 100644
--- a/main.js
+++ b/main.js
@@ -1,2580 +1,2580 @@
-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",
- firstname: "Simon"
-};
-
-//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,\n",
- cursor: -29
-},
-{
- command: "/hello",
- hint: "Standard answer (en)",
- insert: "Hello ,\n\n\n\nKind regards,\n",
- 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";
-
- snippets.find(s => s.command == "/bonjour").insert = "Bonjour ,\n\n\n\nBien cordialement,\n" + settings.firstname;
- snippets.find(s => s.command == "/hello").insert = "Hello ,\n\n\n\nKind regards,\n" + settings.firstname;
-}
-
-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"],
- "bash": ["molecule", "cd"],
- "python": ["from", "import"]
-}
-
-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 = "
";
-function rawline2html(line, index, options)
-{
- line = escapeHtml(line);
-
- // headings
- if (line.startsWith("#"))
- {
- line = line.replace(/(#* )/, "$1");
- line = "" + line + "";
- }
-
- // bold and italics
- var temp = line;
- if (line.startsWith("* "))
- {
- temp = line.substring(2);
- }
- temp = temp.replace(/\*\*([^\*]*)\*\*/g, "**$1**");
- temp = temp.replace(/\*([^\*]*)\*/g, "*$1*");
-
- if (line.startsWith("* "))
- {
- line = "* " + temp;
- }
- else
- {
- line = temp;
- }
-
- // lists
- markerslist.forEach(marker =>
- {
- if (line.startsWith(marker) && marker.trim())
- {
- line = line.replace(marker, "" + marker + "");
- }
- });
-
- // md header
- if (index == 0 && line == "---")
- {
- options.header = true;
- }
- if (options.header)
- {
- if (index > 0 && line == "---")
- {
- options.header = false;
- }
- line = line || emptyline;
- line = "";
- }
-
- // code blocks
- if (line.startsWith("```") && !options.code)
- {
- options.code = true;
- options.language = line.substring(3);
- line = "" + line.replace(new RegExp("(" + options.language + ")"), "$1") + "
";
- }
- else if (line == "```" && options.code)
- {
- options.code = false;
- options.language = "";
- line = "" + line + "
";
- }
- else if (options.code)
- {
- var comment = false;
- if (line.match(/^\s*\/\//))
- {
- line = "";
- comment = true;
- }
- line = "" + (line || emptyline) + "
";
- if (!comment && languagekeywords[options.language])
- {
- var keywords = languagekeywords[options.language];
- keywords.forEach(keyword =>
- {
- line = line.replace(new RegExp("\\b(" + keyword + ")\\b", "ig"), "$1");
- });
- }
- }
-
- // internal links
- line = line.replace(/\[\[(.*)\]\]/g, "[[$1]]");
-
- // comments
- line = line.replace(/<\!--(.*)/g, "");
-
- if (line.includes("<!--") && !line.includes("-->"))
- {
- options.comment = true;
- }
- else if (options.comment)
- {
- line = line || emptyline;
- line = "";
- }
- }
-
- line = line.replace(/\-\->/g, "-->");
-
- if (line.startsWith("// "))
- {
- line = "";
- }
-
- // autocomplete snippets
- if (index == currentline())
- {
- var raw = rawline(index);
- var pos = currentcol();
- var slashpos = raw.substring(0, pos).lastIndexOf("/");
- if (slashpos > -1)
- {
- var spacepos = raw.substring(0, pos).lastIndexOf(" ");
- if (slashpos > spacepos)
- {
- var snippetpart = raw.substring(slashpos);
- var matching = snippets
- .filter(s => s.command.startsWith(snippetpart))
- .map(s => s.command.substring(1));
-
- if (matching.length)
- {
- line += "";
- line += matching.join().substr(pos - slashpos - 1);
- line += "";
- }
- }
- }
- }
-
- if (line.startsWith("x "))
- {
- line = "" + line + "";
- }
-
- // inline code
- line = line.replace(/`(.*)`/, "`$1`");
-
- // links
- line = line.replace(/(http[^\s]*)/, "$1");
-
- return line;
-}
-
-function applycolors(currentonly)
-{
- if (!settings.colors)
- {
- return;
- }
-
- var options =
- {
- header: false,
- code: false,
- comment: false,
- language: ""
- };
-
- var linediv = null;
- if (currentonly)
- {
- var index = currentline();
- linediv = document.getElementById("line" + index);
- options = JSON.parse(linediv.getAttribute("tag"));
- var line = rawline(index);
- line = rawline2html(line, index, options);
- linediv.innerHTML = line || emptyline;
- }
- else
- {
- console.log("redrawing all colored div");
- var lines = md.value.split("\n");
- var i = 0;
- for (; i < lines.length; i++)
- {
- linediv = document.getElementById("line" + i);
- if (!linediv)
- {
- linediv = document.createElement("div");
- colored.appendChild(linediv);
- }
- linediv.setAttribute("id", "line" + i);
- linediv.setAttribute("tag", JSON.stringify(options));
- linediv.innerHTML = rawline2html(lines[i], i, options) || emptyline;
- }
-
- // remove remanining
- linediv = document.getElementById("line" + (i++));
- while (linediv)
- {
- colored.removeChild(linediv);
- linediv = document.getElementById("line" + (i++));
- }
- }
-}
-
-function editorinput()
-{
- unsavedmark.hidden = false;
-
- // criteria to improve. Or redraw only after?
- var multiline = md.value.substring(md.selectionStart, md.selectionEnd).includes("\n");
- applycolors(!multiline && event.data && (event.inputType == "insertText" || event.inputType == "deleteContentBackward" || event.inputType == "deleteContentForward"));
-
- // todo: fix if current note change during postponing, the wrong one will be saved. Or prevent binding another note?
- postpone().then(flush);
- resize();
-}
-
-function timestamp()
-{
- var utc = new Date();
- var loc = new Date(utc - utc.getTimezoneOffset() * 60 * 1000);
-
- return loc.toISOString().replace("T", " ").replace(/\..*/, "").replace(/:/g, ".");
-}
-
-function quicknewnote()
-{
- flush();
- loadnote(timestamp());
-}
-
-function startnewnote()
-{
- flush();
- var title = prompt("Note title: ", timestamp());
- if (title)
- {
- loadnote(title);
- }
-}
-
-function showhelp()
-{
- var help = ["# Notes"];
- help.push("## Shortcuts");
-
- commands
- .filter(command => Boolean(command.shortcut))
- .forEach(command => help.push(command.hint + ": " + command.shortcut.toLowerCase()));
-
- help.push("## Snippets");
- snippets.forEach(snippet =>
- {
- help.push(snippet.hint + ": " + snippet.command);
- });
-
- help.push("## Libs");
- help.push("[Showdown](https://showdownjs.com/)");
- help.push("[vis-network](https://visjs.org/)");
- help.push("[jszip](https://stuk.github.io/jszip/)");
- help.push("[FileSaver](http://eligrey.com)");
-
- help.push("## Inspiration");
- help.push("[rwtxt](https://rwtxt.com)");
- help.push("[Offline Notepad](https://offlinenotepad.com/)");
- help.push("[Writemonkey3](http://writemonkey.com/wm3/)");
- help.push("[Sublime Text](https://www.sublimetext.com/)");
- help.push("[Notion](https://www.notion.so/)");
- help.push("[Calmly Writer](https://calmlywriter.com/)");
- help.push("[Cryptee](https://crypt.ee/)");
-
- help.push("##Sources");
- help.push("https://git.ouvaton.coop/quenousimporte/notes");
-
- bind("Help", help.join("\n"));
-
- if (preview.hidden)
- {
- togglepreview();
- }
-}
-
-function toggletitle()
-{
- if (title.hidden)
- {
- title.hidden = false;
- title.focus();
- }
- else
- {
- title.hidden = true;
- md.focus();
- }
-}
-
-function selectnote()
-{
- return searchinlist(
- sortedlist()
- .map(item =>
- {
- return {
- text: item.title,
- suffix: item.header.tags.map(t => tagmark + t)
- }
- }));
-}
-
-function searchautocomplete()
-{
- selectnote().then(selected =>
- {
- insertautocomplete(selected.text || selected);
- });
-}
-
-function searchandloadnote()
-{
- selectnote().then(selected =>
- {
- var title = selected.text || selected;
- loadnote(title);
- });
-}
-
-function istodo(title, content)
-{
- return title.includes("todo") || gettags(content).includes("todo");
-}
-
-function searchandreplace()
-{
- var oldvalue = prompt("Search:");
- if (!oldvalue)
- {
- return;
- }
-
- var newvalue = prompt("Replace by:");
- if (!newvalue)
- {
- return;
- }
-
- var doit = confirm(`Replace '${oldvalue}' by '${newvalue}'?`);
- if (!doit)
- {
- return;
- }
-
- seteditorcontent(md.value.replaceAll(oldvalue, newvalue));
-}
-
-function serializeindex()
-{
- serialize("index", JSON.stringify(metadata));
-}
-
-function deletenote(title)
-{
- var guid = getguid(title);
- delete metadata[guid];
- renameinternallinks(title, title + " (deleted)");
- var content = localStorage.getItem(guid);
- localStorage.setItem(guid, "Deleted. Title: " + title + ". Date: " + timestamp() + "\r\n" + content);
- serializeindex();
-}
-
-function deletecurrentnote()
-{
- if (confirm('delete "' + title.value + '"?'))
- {
- deletenote(title.value);
- loadlast();
- }
-}
-
-function restore()
-{
- if (confirm('restore "' + title.value + '"?'))
- {
- seteditorcontent(backup);
- }
-}
-
-function insertheader()
-{
- if (preview.hidden && !md.value.startsWith("---\n"))
- {
- var headers = defaultheaders();
- seteditorcontent(headers + md.value);
- setpos(27);
- }
- resize();
-}
-
-function splitshortcut(s)
-{
- var r = {};
- s = s.split("+");
- r.key = s.pop();
- s.forEach(e => {
- r[e] = true;
- })
- return r;
-}
-
-function shortcutmatches(event, shortcut)
-{
- var s = splitshortcut(shortcut);
- return (event.key == s.key && !(s.ctrl && !event.ctrlKey && !event.altKey) && !(s.shift && !event.shiftKey) && !(s.alt && !event.altKey))
-}
-
-function executecommand(command)
-{
- if (command.action)
- {
- command.action();
- }
-}
-
-function esc(event)
-{
- if (!searchdialog.hidden)
- {
- event.preventDefault();
- searchdialog.hidden = true;
- filter.placeholder = "Search...";
- md.focus();
- }
- else if (settings.uselinkpopup && typeof linkdialog != "undefined")
- {
- removelinkdialog();
- }
- else if (title.value == "Help" || title.value == "Search result")
- {
- loadlast();
- }
- else if (networkpage.hidden == false)
- {
- networkpage.hidden = true;
- toggleeditor(false);
- }
- else if (preview.hidden == false)
- {
- togglepreview();
- }
- else
- {
- commandpalette();
- }
-}
-
-function boldify()
-{
- var pos = currentrange();
- var newcontent;
- var offset = 2;
- if (md.value.substr(pos.start - 2, 2) == "**" && md.value.substr(pos.end, 2) == "**")
- {
- newcontent = md.value.substr(0, pos.start - 2) + md.value.substr(pos.start, pos.end - pos.start) + md.value.substr(pos.end + 2);
- offset = -2;
- }
- else
- {
- newcontent = md.value.substr(0, pos.start) + "**" + md.value.substr(pos.start, pos.end - pos.start) + "**" + md.value.substr(pos.end);
- }
-
- seteditorcontent(newcontent);
- md.setSelectionRange(pos.start + offset, pos.end + offset);
-}
-
-function foreachmetadata(callback)
-{
- Object.keys(metadata).forEach(guid =>
- {
- callback(guid, metadata[guid]);
- });
-}
-
-function snippetautocomplete()
-{
- var tocursor = md.value.substr(0, md.selectionStart)
- var slashindex = tocursor.lastIndexOf("/");
- if (slashindex > tocursor.lastIndexOf(" ") && slashindex > tocursor.lastIndexOf("\n"))
- {
- var commandbegin = tocursor.substr(slashindex);
- var snippet = snippets.find(s => s.command.startsWith(commandbegin));
- if (snippet)
- {
- insert(snippet.insert, snippet.cursor, commandbegin.length);
- return true;
- }
- }
- return false;
-}
-
-function mainkeydownhandler()
-{
- if (event.key == "Escape")
- {
- esc(event);
- }
- else if (!searchdialog.hidden && (event.key == "Tab" || event.keyCode == "40" || event.keyCode == "38"))
- {
- event.preventDefault();
- fileindex += (event.shiftKey || event.keyCode == "38") ? -1 : 1;
- fileindex = Math.min(fileindex, filteredlist.children.length - 1);
- fileindex = Math.max(fileindex, 0);
- applyfileindex();
- }
- else if (event.ctrlKey && event.key == " " || event.key == "F1")
- {
- commandpalette();
- event.preventDefault();
- }
- else if (event.ctrlKey && event.key == "b" && md.selectionStart < md.selectionEnd)
- {
- boldify();
- event.preventDefault();
- }
- else if (event.ctrlKey && event.shiftKey && (event.keyCode == "40" || event.keyCode == "38"))
- {
- var pos = currentrange();
- var direction = event.keyCode == "40" ? 1 : -1;
- var start = lineatpos(md.selectionStart);
- var end = lineatpos(md.selectionEnd);
- var lines = md.value.split("\n");
- if (direction > 0 && end < lines.length || direction < 0 && start > 0)
- {
- var block = lines.splice(start, end - start + 1);
- lines.splice(start + direction, 0, ...block);
- seteditorcontent(lines.join("\n"));
- var posshift = direction > 0 ? lines[start].length + 1 : - 1 - lines[end].length;
- md.setSelectionRange(pos.start + posshift, pos.end + posshift);
-
- }
- event.preventDefault();
- }
- else if (event.ctrlKey || event.altKey)
- {
- // notes shortcuts
- var found = false;
- foreachmetadata((guid, item) =>
- {
- if (item.header.shortcut && shortcutmatches(event, item.header.shortcut))
- {
- console.log("Loading note '" + item.title + "' from header shortcut " + item.header.shortcut);
- event.preventDefault();
- loadnote(item.title);
- found = true;
- }
- });
-
- // commands shortcuts
- if (!found)
- {
- commands.filter(c => c.shortcut)
- .every(command =>
- {
- if (shortcutmatches(event, command.shortcut))
- {
- event.preventDefault();
- executecommand(command);
- return false;
- }
- return true;
- });
- }
- }
-}
-
-function setwindowtitle()
-{
- document.title = title.value;
-}
-
-function renameinternallinks(from, to)
-{
- foreachmetadata( (guid, item) =>
- {
- var content = localStorage.getItem(guid);
- var newcontent = content.replaceAll("[[" + from + "]]", "[[" + to + "]]");
- if (content != newcontent)
- {
- serialize(guid, newcontent);
- if (item.title == title.value)
- {
- seteditorcontent(newcontent);
- }
- }
- });
-}
-
-function ontitlechange()
-{
- var guid = getguid(title.value);
- if (guid)
- {
- showtemporaryinfo(title.value + " alreday exists");
- title.value = previoustitle;
- }
- else
- {
- renameinternallinks(previoustitle, title.value);
-
- guid = getguid(previoustitle);
- previoustitle = title.value;
- metadata[guid].title = title.value;
- setwindowtitle();
- flush();
-
- if (!settings.titlebydefault)
- {
- toggletitle();
- }
- }
-}
-
-function simplifystring(str)
-{
- return str.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, "");
-}
-
-function applyfilter()
-{
- [...filteredlist.children].forEach(div =>
- {
- div.hidden = simplifystring(div.textContent).indexOf(simplifystring(filter.value)) < 0;
- });
-
- fileindex = 0;
- applyfileindex();
-}
-
-function backspace(nb)
-{
- var pos = getpos();
- var c = md.value;
- seteditorcontent(c.substring(0, pos - nb) + c.substring(pos));
- setpos(pos - nb);
-}
-
-function editorkeydown()
-{
- if (event.key == "Enter")
- {
- var line = md.value.substring(0, getpos()).split("\n").pop();
- markerslist.filter(marker => line.startsWith(marker))
- .every(marker =>
- {
- event.preventDefault();
- if (line != marker)
- {
- insert("\n" + marker.replace("[x]", "[ ]"));
- }
- else
- {
- backspace(marker.length);
- }
- return false;
- });
- }
- else if (event.key === "Tab")
- {
- event.preventDefault();
- if (!snippetautocomplete())
- {
- var init = currentrange();
- var range = getlinesrange();
- range.start--;
- range.end--;
- var selection = md.value.substring(range.start, range.end);
- var newtext;
- if (event.shiftKey)
- {
- newtext = selection.replaceAll("\n ", "\n");
- }
- else
- {
- newtext = selection.replaceAll("\n", "\n ");
- }
- seteditorcontent(md.value.substring(0, range.start)
- + newtext
- + md.value.substring(range.end));
-
- var shift = 0;
- if (newtext.length < selection.length)
- {
- shift = -4;
- }
- else if (newtext.length > selection.length)
- {
- shift = 4;
- }
-
- md.selectionStart = init.start + shift;
- md.selectionEnd = init.end + (newtext.length - selection.length);
- }
- }
- else if (event.key === "[" && before(1) == "[")
- {
- event.preventDefault();
- insert("[");
- searchautocomplete();
- }
- else if (settings.tagautocomplete && event.key == " " && before(1) == "," && md.value.substring(0, getpos()).split("\n").pop().startsWith("tags: "))
- {
- event.preventDefault();
- // search in tags list
- console.log(event.key);
- tagslist()
- .then(tag =>
- {
- insert(" " + tag);
- md.focus();
- })
- }
- else
- {
- var snippet = snippets.find(s => before(s.command.length - 1) + event.key == s.command);
- if (snippet)
- {
- event.preventDefault();
- insert(snippet.insert, snippet.cursor, snippet.command.length - 1);
- }
- }
-}
-
-function insertautocomplete(selectednote)
-{
- md.focus();
- insert(selectednote + "]] ");
-}
-
-function togglepreview()
-{
- preview.innerHTML = md2html(md.value);
- toggleeditor(!md.hidden);
- preview.hidden = !preview.hidden;
-
- if (preview.hidden)
- {
- resize();
- md.focus();
- }
-}
-
-function withsubs()
-{
- // todo: handle loops
- var currentguid = getguid(title.value);
-
- var content = md.value;
- var kids = children(currentguid);
- while (kids.length)
- {
- kids.forEach(guid =>
- {
- var kidcontent = localStorage.getItem(guid);
- if (kidcontent.startsWith("---\n"))
- {
- var pos = kidcontent.indexOf("---\n", 4);
- kidcontent = kidcontent.substring(pos + 4);
- }
- content = content.replaceAll("[[" + metadata[guid].title + "]]", kidcontent);
- });
- kids = children(currentguid);
- }
-
- return content;
-}
-
-function togglepreviewwithsubs()
-{
- var note = withsubs();
- if (note)
- {
- preview.innerHTML = md2html(note.content);
- toggleeditor(!md.hidden);
- preview.hidden = !preview.hidden;
-
- if (preview.hidden)
- {
- resize();
- md.focus();
- }
- }
-}
-
-function bind(title, content, pos)
-{
- if (workerid)
- {
- showtemporaryinfo("Cannot open '" + title + "' because current note not yet serialized");
- return;
- }
-
- previoustitle = title;
- backup = content;
-
- window.title.value = title;
- setwindowtitle();
-
- md.value = content || "";
- applycolors();
-
- md.style.height = "0px";
- resize();
-
- setpos(pos || 0);
-
- if (!issplit() && searchdialog.hidden)
- {
- // force to scroll to cursor pos
- md.blur();
- md.focus();
- }
-}
-
-function defaultheaders(tags = "")
-{
- return [
- "---",
- "date: " + timestamp().substr(0,10),
- "tags: " + (tags || ""),
- "---",
- "",""].join("\n");
-}
-
-function loadnote(title)
-{
- var guid = getguid(title);
- if (!guid)
- {
- guid = createnote(title);
- }
- var content = localStorage.getItem(guid);
- var item = metadata[guid];
-
- if (item.header.tags.includes("journal"))
- {
- // remove empty entries
- content = content.replace(/\d{4}-\d{2}-\d{2}\n*(\d{4}-\d{2}-\d{2})/g, "$1");
-
- // create new entry for today
- var hat = headerandtext(content);
- var today = timestamp().substr(0,10);
- if (!hat.text.startsWith(today) && !hat.text.startsWith("\n" + today))
- {
- content = hat.header + "\n" + today + "\n\n" + hat.text;
- item.pos = hat.header.length + 12;
- }
- }
-
- bind(item.title, content, item.pos);
- item.lastchanged = Date.now();
- serializeindex();
-
- if (!preview.hidden || (preview.hidden && (gettags(md.value).indexOf("preview") !== -1)))
- {
- togglepreview();
- }
-}
-
-function focuseditor()
-{
- if (document.documentElement == event.srcElement)
- {
- md.focus();
- console.log("Forced focus");
- }
-}
+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",
+ firstname: ""
+};
+
+//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,\n",
+ cursor: -29
+},
+{
+ command: "/hello",
+ hint: "Standard answer (en)",
+ insert: "Hello ,\n\n\n\nKind regards,\n",
+ 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";
+
+ snippets.find(s => s.command == "/bonjour").insert = "Bonjour ,\n\n\n\nBien cordialement,\n" + settings.firstname;
+ snippets.find(s => s.command == "/hello").insert = "Hello ,\n\n\n\nKind regards,\n" + settings.firstname;
+}
+
+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"],
+ "bash": ["molecule", "cd"],
+ "python": ["from", "import"]
+}
+
+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 = "
";
+function rawline2html(line, index, options)
+{
+ line = escapeHtml(line);
+
+ // headings
+ if (line.startsWith("#"))
+ {
+ line = line.replace(/(#* )/, "$1");
+ line = "" + line + "";
+ }
+
+ // bold and italics
+ var temp = line;
+ if (line.startsWith("* "))
+ {
+ temp = line.substring(2);
+ }
+ temp = temp.replace(/\*\*([^\*]*)\*\*/g, "**$1**");
+ temp = temp.replace(/\*([^\*]*)\*/g, "*$1*");
+
+ if (line.startsWith("* "))
+ {
+ line = "* " + temp;
+ }
+ else
+ {
+ line = temp;
+ }
+
+ // lists
+ markerslist.forEach(marker =>
+ {
+ if (line.startsWith(marker) && marker.trim())
+ {
+ line = line.replace(marker, "" + marker + "");
+ }
+ });
+
+ // md header
+ if (index == 0 && line == "---")
+ {
+ options.header = true;
+ }
+ if (options.header)
+ {
+ if (index > 0 && line == "---")
+ {
+ options.header = false;
+ }
+ line = line || emptyline;
+ line = "";
+ }
+
+ // code blocks
+ if (line.startsWith("```") && !options.code)
+ {
+ options.code = true;
+ options.language = line.substring(3);
+ line = "" + line.replace(new RegExp("(" + options.language + ")"), "$1") + "
";
+ }
+ else if (line == "```" && options.code)
+ {
+ options.code = false;
+ options.language = "";
+ line = "" + line + "
";
+ }
+ else if (options.code)
+ {
+ var comment = false;
+ if (line.match(/^\s*\/\//))
+ {
+ line = "";
+ comment = true;
+ }
+ line = "" + (line || emptyline) + "
";
+ if (!comment && languagekeywords[options.language])
+ {
+ var keywords = languagekeywords[options.language];
+ keywords.forEach(keyword =>
+ {
+ line = line.replace(new RegExp("\\b(" + keyword + ")\\b", "ig"), "$1");
+ });
+ }
+ }
+
+ // internal links
+ line = line.replace(/\[\[(.*)\]\]/g, "[[$1]]");
+
+ // comments
+ line = line.replace(/<\!--(.*)/g, "");
+
+ if (line.includes("<!--") && !line.includes("-->"))
+ {
+ options.comment = true;
+ }
+ else if (options.comment)
+ {
+ line = line || emptyline;
+ line = "";
+ }
+ }
+
+ line = line.replace(/\-\->/g, "-->");
+
+ if (line.startsWith("// "))
+ {
+ line = "";
+ }
+
+ // autocomplete snippets
+ if (index == currentline())
+ {
+ var raw = rawline(index);
+ var pos = currentcol();
+ var slashpos = raw.substring(0, pos).lastIndexOf("/");
+ if (slashpos > -1)
+ {
+ var spacepos = raw.substring(0, pos).lastIndexOf(" ");
+ if (slashpos > spacepos)
+ {
+ var snippetpart = raw.substring(slashpos);
+ var matching = snippets
+ .filter(s => s.command.startsWith(snippetpart))
+ .map(s => s.command.substring(1));
+
+ if (matching.length)
+ {
+ line += "";
+ line += matching.join().substr(pos - slashpos - 1);
+ line += "";
+ }
+ }
+ }
+ }
+
+ if (line.startsWith("x "))
+ {
+ line = "" + line + "";
+ }
+
+ // inline code
+ line = line.replace(/`(.*)`/, "`$1`");
+
+ // links
+ line = line.replace(/(http[^\s]*)/, "$1");
+
+ return line;
+}
+
+function applycolors(currentonly)
+{
+ if (!settings.colors)
+ {
+ return;
+ }
+
+ var options =
+ {
+ header: false,
+ code: false,
+ comment: false,
+ language: ""
+ };
+
+ var linediv = null;
+ if (currentonly)
+ {
+ var index = currentline();
+ linediv = document.getElementById("line" + index);
+ options = JSON.parse(linediv.getAttribute("tag"));
+ var line = rawline(index);
+ line = rawline2html(line, index, options);
+ linediv.innerHTML = line || emptyline;
+ }
+ else
+ {
+ console.log("redrawing all colored div");
+ var lines = md.value.split("\n");
+ var i = 0;
+ for (; i < lines.length; i++)
+ {
+ linediv = document.getElementById("line" + i);
+ if (!linediv)
+ {
+ linediv = document.createElement("div");
+ colored.appendChild(linediv);
+ }
+ linediv.setAttribute("id", "line" + i);
+ linediv.setAttribute("tag", JSON.stringify(options));
+ linediv.innerHTML = rawline2html(lines[i], i, options) || emptyline;
+ }
+
+ // remove remanining
+ linediv = document.getElementById("line" + (i++));
+ while (linediv)
+ {
+ colored.removeChild(linediv);
+ linediv = document.getElementById("line" + (i++));
+ }
+ }
+}
+
+function editorinput()
+{
+ unsavedmark.hidden = false;
+
+ // criteria to improve. Or redraw only after?
+ var multiline = md.value.substring(md.selectionStart, md.selectionEnd).includes("\n");
+ applycolors(!multiline && event.data && (event.inputType == "insertText" || event.inputType == "deleteContentBackward" || event.inputType == "deleteContentForward"));
+
+ // todo: fix if current note change during postponing, the wrong one will be saved. Or prevent binding another note?
+ postpone().then(flush);
+ resize();
+}
+
+function timestamp()
+{
+ var utc = new Date();
+ var loc = new Date(utc - utc.getTimezoneOffset() * 60 * 1000);
+
+ return loc.toISOString().replace("T", " ").replace(/\..*/, "").replace(/:/g, ".");
+}
+
+function quicknewnote()
+{
+ flush();
+ loadnote(timestamp());
+}
+
+function startnewnote()
+{
+ flush();
+ var title = prompt("Note title: ", timestamp());
+ if (title)
+ {
+ loadnote(title);
+ }
+}
+
+function showhelp()
+{
+ var help = ["# Notes"];
+ help.push("## Shortcuts");
+
+ commands
+ .filter(command => Boolean(command.shortcut))
+ .forEach(command => help.push(command.hint + ": " + command.shortcut.toLowerCase()));
+
+ help.push("## Snippets");
+ snippets.forEach(snippet =>
+ {
+ help.push(snippet.hint + ": " + snippet.command);
+ });
+
+ help.push("## Libs");
+ help.push("[Showdown](https://showdownjs.com/)");
+ help.push("[vis-network](https://visjs.org/)");
+ help.push("[jszip](https://stuk.github.io/jszip/)");
+ help.push("[FileSaver](http://eligrey.com)");
+
+ help.push("## Inspiration");
+ help.push("[rwtxt](https://rwtxt.com)");
+ help.push("[Offline Notepad](https://offlinenotepad.com/)");
+ help.push("[Writemonkey3](http://writemonkey.com/wm3/)");
+ help.push("[Sublime Text](https://www.sublimetext.com/)");
+ help.push("[Notion](https://www.notion.so/)");
+ help.push("[Calmly Writer](https://calmlywriter.com/)");
+ help.push("[Cryptee](https://crypt.ee/)");
+
+ help.push("##Sources");
+ help.push("https://git.ouvaton.coop/quenousimporte/notes");
+
+ bind("Help", help.join("\n"));
+
+ if (preview.hidden)
+ {
+ togglepreview();
+ }
+}
+
+function toggletitle()
+{
+ if (title.hidden)
+ {
+ title.hidden = false;
+ title.focus();
+ }
+ else
+ {
+ title.hidden = true;
+ md.focus();
+ }
+}
+
+function selectnote()
+{
+ return searchinlist(
+ sortedlist()
+ .map(item =>
+ {
+ return {
+ text: item.title,
+ suffix: item.header.tags.map(t => tagmark + t)
+ }
+ }));
+}
+
+function searchautocomplete()
+{
+ selectnote().then(selected =>
+ {
+ insertautocomplete(selected.text || selected);
+ });
+}
+
+function searchandloadnote()
+{
+ selectnote().then(selected =>
+ {
+ var title = selected.text || selected;
+ loadnote(title);
+ });
+}
+
+function istodo(title, content)
+{
+ return title.includes("todo") || gettags(content).includes("todo");
+}
+
+function searchandreplace()
+{
+ var oldvalue = prompt("Search:");
+ if (!oldvalue)
+ {
+ return;
+ }
+
+ var newvalue = prompt("Replace by:");
+ if (!newvalue)
+ {
+ return;
+ }
+
+ var doit = confirm(`Replace '${oldvalue}' by '${newvalue}'?`);
+ if (!doit)
+ {
+ return;
+ }
+
+ seteditorcontent(md.value.replaceAll(oldvalue, newvalue));
+}
+
+function serializeindex()
+{
+ serialize("index", JSON.stringify(metadata));
+}
+
+function deletenote(title)
+{
+ var guid = getguid(title);
+ delete metadata[guid];
+ renameinternallinks(title, title + " (deleted)");
+ var content = localStorage.getItem(guid);
+ localStorage.setItem(guid, "Deleted. Title: " + title + ". Date: " + timestamp() + "\r\n" + content);
+ serializeindex();
+}
+
+function deletecurrentnote()
+{
+ if (confirm('delete "' + title.value + '"?'))
+ {
+ deletenote(title.value);
+ loadlast();
+ }
+}
+
+function restore()
+{
+ if (confirm('restore "' + title.value + '"?'))
+ {
+ seteditorcontent(backup);
+ }
+}
+
+function insertheader()
+{
+ if (preview.hidden && !md.value.startsWith("---\n"))
+ {
+ var headers = defaultheaders();
+ seteditorcontent(headers + md.value);
+ setpos(27);
+ }
+ resize();
+}
+
+function splitshortcut(s)
+{
+ var r = {};
+ s = s.split("+");
+ r.key = s.pop();
+ s.forEach(e => {
+ r[e] = true;
+ })
+ return r;
+}
+
+function shortcutmatches(event, shortcut)
+{
+ var s = splitshortcut(shortcut);
+ return (event.key == s.key && !(s.ctrl && !event.ctrlKey && !event.altKey) && !(s.shift && !event.shiftKey) && !(s.alt && !event.altKey))
+}
+
+function executecommand(command)
+{
+ if (command.action)
+ {
+ command.action();
+ }
+}
+
+function esc(event)
+{
+ if (!searchdialog.hidden)
+ {
+ event.preventDefault();
+ searchdialog.hidden = true;
+ filter.placeholder = "Search...";
+ md.focus();
+ }
+ else if (settings.uselinkpopup && typeof linkdialog != "undefined")
+ {
+ removelinkdialog();
+ }
+ else if (title.value == "Help" || title.value == "Search result")
+ {
+ loadlast();
+ }
+ else if (networkpage.hidden == false)
+ {
+ networkpage.hidden = true;
+ toggleeditor(false);
+ }
+ else if (preview.hidden == false)
+ {
+ togglepreview();
+ }
+ else
+ {
+ commandpalette();
+ }
+}
+
+function boldify()
+{
+ var pos = currentrange();
+ var newcontent;
+ var offset = 2;
+ if (md.value.substr(pos.start - 2, 2) == "**" && md.value.substr(pos.end, 2) == "**")
+ {
+ newcontent = md.value.substr(0, pos.start - 2) + md.value.substr(pos.start, pos.end - pos.start) + md.value.substr(pos.end + 2);
+ offset = -2;
+ }
+ else
+ {
+ newcontent = md.value.substr(0, pos.start) + "**" + md.value.substr(pos.start, pos.end - pos.start) + "**" + md.value.substr(pos.end);
+ }
+
+ seteditorcontent(newcontent);
+ md.setSelectionRange(pos.start + offset, pos.end + offset);
+}
+
+function foreachmetadata(callback)
+{
+ Object.keys(metadata).forEach(guid =>
+ {
+ callback(guid, metadata[guid]);
+ });
+}
+
+function snippetautocomplete()
+{
+ var tocursor = md.value.substr(0, md.selectionStart)
+ var slashindex = tocursor.lastIndexOf("/");
+ if (slashindex > tocursor.lastIndexOf(" ") && slashindex > tocursor.lastIndexOf("\n"))
+ {
+ var commandbegin = tocursor.substr(slashindex);
+ var snippet = snippets.find(s => s.command.startsWith(commandbegin));
+ if (snippet)
+ {
+ insert(snippet.insert, snippet.cursor, commandbegin.length);
+ return true;
+ }
+ }
+ return false;
+}
+
+function mainkeydownhandler()
+{
+ if (event.key == "Escape")
+ {
+ esc(event);
+ }
+ else if (!searchdialog.hidden && (event.key == "Tab" || event.keyCode == "40" || event.keyCode == "38"))
+ {
+ event.preventDefault();
+ fileindex += (event.shiftKey || event.keyCode == "38") ? -1 : 1;
+ fileindex = Math.min(fileindex, filteredlist.children.length - 1);
+ fileindex = Math.max(fileindex, 0);
+ applyfileindex();
+ }
+ else if (event.ctrlKey && event.key == " " || event.key == "F1")
+ {
+ commandpalette();
+ event.preventDefault();
+ }
+ else if (event.ctrlKey && event.key == "b" && md.selectionStart < md.selectionEnd)
+ {
+ boldify();
+ event.preventDefault();
+ }
+ else if (event.ctrlKey && event.shiftKey && (event.keyCode == "40" || event.keyCode == "38"))
+ {
+ var pos = currentrange();
+ var direction = event.keyCode == "40" ? 1 : -1;
+ var start = lineatpos(md.selectionStart);
+ var end = lineatpos(md.selectionEnd);
+ var lines = md.value.split("\n");
+ if (direction > 0 && end < lines.length || direction < 0 && start > 0)
+ {
+ var block = lines.splice(start, end - start + 1);
+ lines.splice(start + direction, 0, ...block);
+ seteditorcontent(lines.join("\n"));
+ var posshift = direction > 0 ? lines[start].length + 1 : - 1 - lines[end].length;
+ md.setSelectionRange(pos.start + posshift, pos.end + posshift);
+
+ }
+ event.preventDefault();
+ }
+ else if (event.ctrlKey || event.altKey)
+ {
+ // notes shortcuts
+ var found = false;
+ foreachmetadata((guid, item) =>
+ {
+ if (item.header.shortcut && shortcutmatches(event, item.header.shortcut))
+ {
+ console.log("Loading note '" + item.title + "' from header shortcut " + item.header.shortcut);
+ event.preventDefault();
+ loadnote(item.title);
+ found = true;
+ }
+ });
+
+ // commands shortcuts
+ if (!found)
+ {
+ commands.filter(c => c.shortcut)
+ .every(command =>
+ {
+ if (shortcutmatches(event, command.shortcut))
+ {
+ event.preventDefault();
+ executecommand(command);
+ return false;
+ }
+ return true;
+ });
+ }
+ }
+}
+
+function setwindowtitle()
+{
+ document.title = title.value;
+}
+
+function renameinternallinks(from, to)
+{
+ foreachmetadata( (guid, item) =>
+ {
+ var content = localStorage.getItem(guid);
+ var newcontent = content.replaceAll("[[" + from + "]]", "[[" + to + "]]");
+ if (content != newcontent)
+ {
+ serialize(guid, newcontent);
+ if (item.title == title.value)
+ {
+ seteditorcontent(newcontent);
+ }
+ }
+ });
+}
+
+function ontitlechange()
+{
+ var guid = getguid(title.value);
+ if (guid)
+ {
+ showtemporaryinfo(title.value + " alreday exists");
+ title.value = previoustitle;
+ }
+ else
+ {
+ renameinternallinks(previoustitle, title.value);
+
+ guid = getguid(previoustitle);
+ previoustitle = title.value;
+ metadata[guid].title = title.value;
+ setwindowtitle();
+ flush();
+
+ if (!settings.titlebydefault)
+ {
+ toggletitle();
+ }
+ }
+}
+
+function simplifystring(str)
+{
+ return str.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, "");
+}
+
+function applyfilter()
+{
+ [...filteredlist.children].forEach(div =>
+ {
+ div.hidden = simplifystring(div.textContent).indexOf(simplifystring(filter.value)) < 0;
+ });
+
+ fileindex = 0;
+ applyfileindex();
+}
+
+function backspace(nb)
+{
+ var pos = getpos();
+ var c = md.value;
+ seteditorcontent(c.substring(0, pos - nb) + c.substring(pos));
+ setpos(pos - nb);
+}
+
+function editorkeydown()
+{
+ if (event.key == "Enter")
+ {
+ var line = md.value.substring(0, getpos()).split("\n").pop();
+ markerslist.filter(marker => line.startsWith(marker))
+ .every(marker =>
+ {
+ event.preventDefault();
+ if (line != marker)
+ {
+ insert("\n" + marker.replace("[x]", "[ ]"));
+ }
+ else
+ {
+ backspace(marker.length);
+ }
+ return false;
+ });
+ }
+ else if (event.key === "Tab")
+ {
+ event.preventDefault();
+ if (!snippetautocomplete())
+ {
+ var init = currentrange();
+ var range = getlinesrange();
+ range.start--;
+ range.end--;
+ var selection = md.value.substring(range.start, range.end);
+ var newtext;
+ if (event.shiftKey)
+ {
+ newtext = selection.replaceAll("\n ", "\n");
+ }
+ else
+ {
+ newtext = selection.replaceAll("\n", "\n ");
+ }
+ seteditorcontent(md.value.substring(0, range.start)
+ + newtext
+ + md.value.substring(range.end));
+
+ var shift = 0;
+ if (newtext.length < selection.length)
+ {
+ shift = -4;
+ }
+ else if (newtext.length > selection.length)
+ {
+ shift = 4;
+ }
+
+ md.selectionStart = init.start + shift;
+ md.selectionEnd = init.end + (newtext.length - selection.length);
+ }
+ }
+ else if (event.key === "[" && before(1) == "[")
+ {
+ event.preventDefault();
+ insert("[");
+ searchautocomplete();
+ }
+ else if (settings.tagautocomplete && event.key == " " && before(1) == "," && md.value.substring(0, getpos()).split("\n").pop().startsWith("tags: "))
+ {
+ event.preventDefault();
+ // search in tags list
+ console.log(event.key);
+ tagslist()
+ .then(tag =>
+ {
+ insert(" " + tag);
+ md.focus();
+ })
+ }
+ else
+ {
+ var snippet = snippets.find(s => before(s.command.length - 1) + event.key == s.command);
+ if (snippet)
+ {
+ event.preventDefault();
+ insert(snippet.insert, snippet.cursor, snippet.command.length - 1);
+ }
+ }
+}
+
+function insertautocomplete(selectednote)
+{
+ md.focus();
+ insert(selectednote + "]] ");
+}
+
+function togglepreview()
+{
+ preview.innerHTML = md2html(md.value);
+ toggleeditor(!md.hidden);
+ preview.hidden = !preview.hidden;
+
+ if (preview.hidden)
+ {
+ resize();
+ md.focus();
+ }
+}
+
+function withsubs()
+{
+ // todo: handle loops
+ var currentguid = getguid(title.value);
+
+ var content = md.value;
+ var kids = children(currentguid);
+ while (kids.length)
+ {
+ kids.forEach(guid =>
+ {
+ var kidcontent = localStorage.getItem(guid);
+ if (kidcontent.startsWith("---\n"))
+ {
+ var pos = kidcontent.indexOf("---\n", 4);
+ kidcontent = kidcontent.substring(pos + 4);
+ }
+ content = content.replaceAll("[[" + metadata[guid].title + "]]", kidcontent);
+ });
+ kids = children(currentguid);
+ }
+
+ return content;
+}
+
+function togglepreviewwithsubs()
+{
+ var note = withsubs();
+ if (note)
+ {
+ preview.innerHTML = md2html(note.content);
+ toggleeditor(!md.hidden);
+ preview.hidden = !preview.hidden;
+
+ if (preview.hidden)
+ {
+ resize();
+ md.focus();
+ }
+ }
+}
+
+function bind(title, content, pos)
+{
+ if (workerid)
+ {
+ showtemporaryinfo("Cannot open '" + title + "' because current note not yet serialized");
+ return;
+ }
+
+ previoustitle = title;
+ backup = content;
+
+ window.title.value = title;
+ setwindowtitle();
+
+ md.value = content || "";
+ applycolors();
+
+ md.style.height = "0px";
+ resize();
+
+ setpos(pos || 0);
+
+ if (!issplit() && searchdialog.hidden)
+ {
+ // force to scroll to cursor pos
+ md.blur();
+ md.focus();
+ }
+}
+
+function defaultheaders(tags = "")
+{
+ return [
+ "---",
+ "date: " + timestamp().substr(0,10),
+ "tags: " + (tags || ""),
+ "---",
+ "",""].join("\n");
+}
+
+function loadnote(title)
+{
+ var guid = getguid(title);
+ if (!guid)
+ {
+ guid = createnote(title);
+ }
+ var content = localStorage.getItem(guid);
+ var item = metadata[guid];
+
+ if (item.header.tags.includes("journal"))
+ {
+ // remove empty entries
+ content = content.replace(/\d{4}-\d{2}-\d{2}\n*(\d{4}-\d{2}-\d{2})/g, "$1");
+
+ // create new entry for today
+ var hat = headerandtext(content);
+ var today = timestamp().substr(0,10);
+ if (!hat.text.startsWith(today) && !hat.text.startsWith("\n" + today))
+ {
+ content = hat.header + "\n" + today + "\n\n" + hat.text;
+ item.pos = hat.header.length + 12;
+ }
+ }
+
+ bind(item.title, content, item.pos);
+ item.lastchanged = Date.now();
+ serializeindex();
+
+ if (!preview.hidden || (preview.hidden && (gettags(md.value).indexOf("preview") !== -1)))
+ {
+ togglepreview();
+ }
+}
+
+function focuseditor()
+{
+ if (document.documentElement == event.srcElement)
+ {
+ md.focus();
+ console.log("Forced focus");
+ }
+}