From af55cd2d1d00f155bed3bdc766d5c9b9e1fd353e Mon Sep 17 00:00:00 2001 From: quenousimporte Date: Sat, 21 Jan 2023 16:07:14 +0100 Subject: [PATCH] breaking change: switch local/remote vault changed: folding on ctrl+click added: more settings for fonts added: setting for autocomplete added: filter list on tag added: note list on ctrl+click on tag added: load note on ctrl+click on internal link changed: allow save empty note if just created changed: put note on top upon opening fixed: various bugs --- index.html | 2 +- main.js | 274 ++++++++++++++++++++++++++++++++++++++++++++--------- style.css | 3 +- 3 files changed, 231 insertions(+), 48 deletions(-) diff --git a/index.html b/index.html index 118d248..efb4058 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@
- +
diff --git a/main.js b/main.js index 705ef48..590b302 100644 --- a/main.js +++ b/main.js @@ -3,10 +3,13 @@ var defaultsettings = savedelay: 1000, bgcolor: "white", fontfamily: "'Inconsolata', 'Consolas', monospace", + fontsize: "90%", + fontcolor: "black", + lineheight: "130%", foldmarkstart: 22232, defaultpreviewinsplit: false, - remote: false, - enablefolding: false + enablefolding: false, + tagautocomplete: false }; //builtin @@ -23,6 +26,9 @@ var backup = ""; var localdata = null; var saved = true; var settings = null; +var tags = null; +var currentvault = ""; +var currenttag = ""; var commands = [ { @@ -164,10 +170,10 @@ var commands = [ action: togglesplit }, { - hint: "Open last modified note", + hint: "Load previous note", savedonly: true, - action: loadlast, - shortcut: "ctrl+l" + action: loadprevious, + shortcut: "ctrl+b" }, { hint: "Sort text", @@ -190,14 +196,25 @@ var commands = [ action: restoresettings }, { - hint: "Document outline", + hint: "Note outline", savedonly: true, - action: showoutline + action: showoutline, + shortcut: "ctrl+o" }, { hint: "Internal links", savedonly: true, action: showinternallinks +}, +{ + hint: "Switch vault", + savedonly: true, + action: switchvault, + shortcut: "ctrl+shift+V" +}, +{ + hint: "Add tag filter", + action: addtagfilter }]; var snippets = [ @@ -232,13 +249,43 @@ var snippets = [ cursor: 0 }]; +function addtagfilter() +{ + var command = commands.find(c => c.action == addtagfilter); + + if (!currenttag) + { + tagslist() + .then(t => + { + currenttag = t; + command.hint = "Remove tag filter '" + currenttag + "'"; + }); + } + else + { + currenttag = ""; + command.hint = "Add tag filter"; + } +} + +function switchvault() +{ + var other = othervault(); + if (confirm("Switch to " + other + "?")) + { + window.localStorage.setItem("vault", other); + init(); + } +} + function showinternallinks() { searchinlist( getnotecontent() .match(/\[\[([^\]]*)\]\]/g || []) .map(l => l.replace("[[", "").replace("]]", ""))) - .then(loadfile); + .then(loadnote); } function showoutline() @@ -264,6 +311,78 @@ function showoutline() }); } +function linkatpos() +{ + var s = md.selectionStart; + while (s > 2 && md.value[s] != "\n") + { + if (md.value.substring(s - 2, s) == "[[") + { + var e = md.selectionStart; + while (e < md.value.length - 2 && md.value[e-2] != "\n") + { + if (md.value.substring(e, e + 2) == "]]") + { + return md.value.substring(s, e); + } + e++; + } + } + s--; + } + return ""; +} + +function tagatpos() +{ + if (md.value.substring(0, getpos()).split("\n").pop().startsWith("tags: ")) + { + var s = md.selectionStart; + while (s > 1 && md.value[s] != "\n") + { + var c = md.value[s-1]; + if (c == " " || c == ",") + { + var e = md.selectionStart; + while (e < md.value.length - 1 && md.value[e-1] != "\n") + { + c = md.value[e]; + if (c == " " || c == "," || c == "\n") + { + return md.value.substring(s, e); + } + e++; + } + } + s--; + } + return ""; + } +} + +function clickeditor() +{ + if (event.ctrlKey) + { + var link = linkatpos(); + var tag = tagatpos(); + if (link) + { + loadnote(link); + } + else if (tag) + { + tagslist(); + searchinlist(tags[tag]) + .then(loadnote); + } + else + { + checkfolding(); + } + } +} + function restoresettings() { if (confirm("Restore default settings?")) @@ -295,7 +414,7 @@ function showtemporaryinfo(str) function wordcount() { - showtemporaryinfo(getnotecontent().split(/\s+\b/).length); + showtemporaryinfo(getnotecontent().split(/\s+\b/).length + " words"); } function issplit() @@ -315,15 +434,23 @@ function togglesplit() } } -function logout() +function isremote() { - window.localStorage.removeItem("password"); - togglepassword(); + return currentvault == "remote"; } -function searchtags() +function logout() { - var tags = {}; + if (isremote()) + { + window.localStorage.removeItem("password"); + togglepassword(); + } +} + +function tagslist() +{ + tags = {}; localdata .filter(n => !n.title.startsWith(".")) @@ -337,9 +464,14 @@ function searchtags() }); }); - searchinlist(Object.keys(tags).sort()) + return searchinlist(Object.keys(tags).sort()); +} + +function searchtags() +{ + tagslist() .then(tag => searchinlist(tags[tag])) - .then(loadfile); + .then(loadnote); } function gettags(note) @@ -410,7 +542,7 @@ function downloadnotes() function downloadlocal() { - download(timestamp() + " " + (window.location.hostname || "serverless") + " notes.json", JSON.stringify(localdata)); + download(timestamp() + " " + currentvault + " notes.json", JSON.stringify(localdata)); } function downloadnote() @@ -422,14 +554,14 @@ function serialize() { putontop(); - window.localStorage.setItem("data", JSON.stringify(localdata)); + window.localStorage.setItem(currentvault, JSON.stringify(localdata)); if (currentnote.title == "settings.json") { window.localStorage.setItem("settings", getnotecontent()); } - if (settings.remote) + if (isremote()) { clearTimeout(timeoutid); timeoutid = setTimeout(push, settings.savedelay); @@ -452,7 +584,8 @@ function remotecallfailed(error) function loadstorage() { - var item = window.localStorage.getItem("data"); + currentvault = window.localStorage.getItem("vault"); + var item = window.localStorage.getItem(currentvault); localdata = item ? JSON.parse(item) : []; if (currentnote) @@ -485,6 +618,9 @@ function loadsettings() document.body.style.background = settings.bgcolor; document.body.style.fontFamily = settings.fontfamily; + document.body.style.fontSize = settings.fontsize; + document.body.style.lineHeight = settings.lineheight; + document.body.style.color = settings.fontcolor; if (!settings.enablefolding) { @@ -505,7 +641,7 @@ function initsnippets() // code languages codelanguages.forEach(lang => { - if (!snippets.includes(s => s.command == "/" + lang)) + if (!snippets.find(s => s.command == "/" + lang)) { snippets.push( { @@ -520,28 +656,44 @@ function initsnippets() // md headings for (var i = 1; i <= 3; i++) { - snippets.push( + if (!snippets.find(s => s.command == "/" + i)) { - command: "/" + i, - hint: "Heading " + i, - insert: "#".repeat(i) + " ", - cursor: 0 - }); + snippets.push( + { + command: "/" + i, + hint: "Heading " + i, + insert: "#".repeat(i) + " ", + cursor: 0 + }); + } } } +function othervault() +{ + return isremote() ? "local" : "remote"; +} + +function initvault() +{ + currentvault = window.localStorage.getItem("vault") || "local"; +} + function init() { loadsettings(); + initvault(); + + commands.find(c => c.action == switchvault).hint = "Switch to " + othervault() + " vault"; window.onbeforeunload = checksaved; window.onclick = focuseditor; initsnippets(); - if (settings.remote) + if (isremote()) { - markunsaved(); + markunsaved("*"); queryremote({action: "fetch"}) .then(data => { @@ -553,7 +705,6 @@ function init() } else { - delete commands.find(c => c.action == push).action; loadstorage(); } @@ -582,6 +733,12 @@ function togglepassword() function push() { + if (!isremote()) + { + console.log("local vault, no push"); + return; + } + if (localdata) { clearTimeout(timeoutid); @@ -595,7 +752,7 @@ function push() } else { - console.warn("Content changed"); + console.warn("Content changed in the meantime, mark as not saved"); timeoutid = setTimeout(push, settings.savedelay); } }) @@ -645,7 +802,7 @@ function queryremote(params) { if (data.error == "authent") { - failed(null); + failed(); togglepassword(); } else @@ -826,6 +983,7 @@ function checkfolding() } else if (isfold(line)) { + event.preventDefault(); unfold(); } } @@ -882,19 +1040,27 @@ function md2html(content) var html = converter.makeHtml(content); // internal links - html = html.replace(/\[\[([^\]]*)\]\]/g, "$1"); + html = html.replace(/\[\[([^\]]*)\]\]/g, "$1"); return html; } function list() { - return localdata.map(n => n.title).filter(t => !t.startsWith(".")); + return localdata + .filter(n => currenttag == "" || gettags(n).includes(currenttag)) + .map(n => n.title) + .filter(t => !t.startsWith(".")); } function loadlast() { - loadfile(list().shift() || timestamp()); + loadnote(list().shift() || timestamp()); +} + +function loadprevious() +{ + loadnote(list()[1]); } function grep(needle) @@ -1119,10 +1285,26 @@ function notecontentchanged() markunsaved("*"); // check snippets and autocomplete + // should we move this on key down? to cancel when backspace? if (before(2) == "[[") { searchautocomplete(); } + else if (settings.tagautocomplete && md.value.substring(0, getpos()).split("\n").pop().startsWith("tags: ")) + { + // search in tags list + if (before(2) == ", ") + { + console.log(event.key); + tagslist() + .then(tag => + { + insert(tag); + notecontentchanged(); + md.focus(); + }) + } + } else { var snippet = snippets.find(s => before(s.command.length) == s.command); @@ -1135,7 +1317,7 @@ function notecontentchanged() // save var content = getnotecontent(); - if (content == "" || content == "null" || content == "undefined") + if ((content == "" && backup != "") || content == "null" || content == "undefined") { console.warn("Invalid content '" + content + "', file '" + currentnote.title + "' not saved"); } @@ -1148,12 +1330,12 @@ function notecontentchanged() function loadtodo() { - loadfile("todo"); + loadnote("todo"); } function loadquicknote() { - loadfile("Quick note"); + loadnote("Quick note"); } function timestamp() @@ -1163,7 +1345,7 @@ function timestamp() function startnewnote() { - loadfile(timestamp()); + loadnote(timestamp()); } function showhelp() @@ -1229,7 +1411,7 @@ function searchautocomplete() function searchandloadnote() { - selectnote().then(loadfile); + selectnote().then(loadnote); } function rename(newname) @@ -1251,6 +1433,7 @@ function rename(newname) currentnote.title = newname; + markunsaved("*"); serialize(); return ""; } @@ -1259,7 +1442,7 @@ function deletenote() { if (confirm('delete "' + currentnote.title + '"?')) { - var error = rename(".deleted_" + currentnote.title) + var error = rename(".deleted_" + currentnote.title); if (!error) { loadlast(); @@ -1389,12 +1572,12 @@ function editorkeydown() { if (event.key == "Enter") { - var currentline = md.value.substring(0, getpos()).split("\n").pop(); - markerslist.filter(marker => currentline.startsWith(marker)) + var line = md.value.substring(0, getpos()).split("\n").pop(); + markerslist.filter(marker => line.startsWith(marker)) .forEach(marker => { event.preventDefault(); - if (currentline != marker) + if (line != marker) { insert("\n" + marker); } @@ -1473,7 +1656,7 @@ function bindfile(note) } } -function loadfile(name) +function loadnote(name) { var note = localdata.find(n => n.title == name); if (!note) @@ -1488,6 +1671,7 @@ function loadfile(name) } bindfile(note); + putontop(); } function sendpassword() diff --git a/style.css b/style.css index ea6ab1a..7fbb018 100644 --- a/style.css +++ b/style.css @@ -3,8 +3,6 @@ body { margin-left: 5%; margin-right: 5%; - font-size: 90%; - line-height: 130%; caret-color: #5AA7CE; } @@ -45,6 +43,7 @@ body::-webkit-scrollbar-thumb { outline: none; border: none; width: 100%; + color: inherit; resize: none;