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
This commit is contained in:
quenousimporte 2023-01-21 16:07:14 +01:00
parent b0fd4efc49
commit af55cd2d1d
3 changed files with 231 additions and 48 deletions

View File

@ -36,7 +36,7 @@
</div> </div>
<div id="notecontent"> <div id="notecontent">
<textarea id="md" spellcheck="false" oninput="notecontentchanged()" onkeydown="editorkeydown()" ondblclick="checkfolding()"></textarea> <textarea id="md" spellcheck="false" oninput="notecontentchanged()" onkeydown="editorkeydown()" onclick="clickeditor()"></textarea>
<div hidden id="preview"></div> <div hidden id="preview"></div>
</div> </div>
</div> </div>

274
main.js
View File

@ -3,10 +3,13 @@ var defaultsettings =
savedelay: 1000, savedelay: 1000,
bgcolor: "white", bgcolor: "white",
fontfamily: "'Inconsolata', 'Consolas', monospace", fontfamily: "'Inconsolata', 'Consolas', monospace",
fontsize: "90%",
fontcolor: "black",
lineheight: "130%",
foldmarkstart: 22232, foldmarkstart: 22232,
defaultpreviewinsplit: false, defaultpreviewinsplit: false,
remote: false, enablefolding: false,
enablefolding: false tagautocomplete: false
}; };
//builtin //builtin
@ -23,6 +26,9 @@ var backup = "";
var localdata = null; var localdata = null;
var saved = true; var saved = true;
var settings = null; var settings = null;
var tags = null;
var currentvault = "";
var currenttag = "";
var commands = [ var commands = [
{ {
@ -164,10 +170,10 @@ var commands = [
action: togglesplit action: togglesplit
}, },
{ {
hint: "Open last modified note", hint: "Load previous note",
savedonly: true, savedonly: true,
action: loadlast, action: loadprevious,
shortcut: "ctrl+l" shortcut: "ctrl+b"
}, },
{ {
hint: "Sort text", hint: "Sort text",
@ -190,14 +196,25 @@ var commands = [
action: restoresettings action: restoresettings
}, },
{ {
hint: "Document outline", hint: "Note outline",
savedonly: true, savedonly: true,
action: showoutline action: showoutline,
shortcut: "ctrl+o"
}, },
{ {
hint: "Internal links", hint: "Internal links",
savedonly: true, savedonly: true,
action: showinternallinks action: showinternallinks
},
{
hint: "Switch vault",
savedonly: true,
action: switchvault,
shortcut: "ctrl+shift+V"
},
{
hint: "Add tag filter",
action: addtagfilter
}]; }];
var snippets = [ var snippets = [
@ -232,13 +249,43 @@ var snippets = [
cursor: 0 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() function showinternallinks()
{ {
searchinlist( searchinlist(
getnotecontent() getnotecontent()
.match(/\[\[([^\]]*)\]\]/g || []) .match(/\[\[([^\]]*)\]\]/g || [])
.map(l => l.replace("[[", "").replace("]]", ""))) .map(l => l.replace("[[", "").replace("]]", "")))
.then(loadfile); .then(loadnote);
} }
function showoutline() 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() function restoresettings()
{ {
if (confirm("Restore default settings?")) if (confirm("Restore default settings?"))
@ -295,7 +414,7 @@ function showtemporaryinfo(str)
function wordcount() function wordcount()
{ {
showtemporaryinfo(getnotecontent().split(/\s+\b/).length); showtemporaryinfo(getnotecontent().split(/\s+\b/).length + " words");
} }
function issplit() function issplit()
@ -315,15 +434,23 @@ function togglesplit()
} }
} }
function logout() function isremote()
{ {
window.localStorage.removeItem("password"); return currentvault == "remote";
togglepassword();
} }
function searchtags() function logout()
{ {
var tags = {}; if (isremote())
{
window.localStorage.removeItem("password");
togglepassword();
}
}
function tagslist()
{
tags = {};
localdata localdata
.filter(n => !n.title.startsWith(".")) .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(tag => searchinlist(tags[tag]))
.then(loadfile); .then(loadnote);
} }
function gettags(note) function gettags(note)
@ -410,7 +542,7 @@ function downloadnotes()
function downloadlocal() function downloadlocal()
{ {
download(timestamp() + " " + (window.location.hostname || "serverless") + " notes.json", JSON.stringify(localdata)); download(timestamp() + " " + currentvault + " notes.json", JSON.stringify(localdata));
} }
function downloadnote() function downloadnote()
@ -422,14 +554,14 @@ function serialize()
{ {
putontop(); putontop();
window.localStorage.setItem("data", JSON.stringify(localdata)); window.localStorage.setItem(currentvault, JSON.stringify(localdata));
if (currentnote.title == "settings.json") if (currentnote.title == "settings.json")
{ {
window.localStorage.setItem("settings", getnotecontent()); window.localStorage.setItem("settings", getnotecontent());
} }
if (settings.remote) if (isremote())
{ {
clearTimeout(timeoutid); clearTimeout(timeoutid);
timeoutid = setTimeout(push, settings.savedelay); timeoutid = setTimeout(push, settings.savedelay);
@ -452,7 +584,8 @@ function remotecallfailed(error)
function loadstorage() function loadstorage()
{ {
var item = window.localStorage.getItem("data"); currentvault = window.localStorage.getItem("vault");
var item = window.localStorage.getItem(currentvault);
localdata = item ? JSON.parse(item) : []; localdata = item ? JSON.parse(item) : [];
if (currentnote) if (currentnote)
@ -485,6 +618,9 @@ function loadsettings()
document.body.style.background = settings.bgcolor; document.body.style.background = settings.bgcolor;
document.body.style.fontFamily = settings.fontfamily; 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) if (!settings.enablefolding)
{ {
@ -505,7 +641,7 @@ function initsnippets()
// code languages // code languages
codelanguages.forEach(lang => codelanguages.forEach(lang =>
{ {
if (!snippets.includes(s => s.command == "/" + lang)) if (!snippets.find(s => s.command == "/" + lang))
{ {
snippets.push( snippets.push(
{ {
@ -520,28 +656,44 @@ function initsnippets()
// md headings // md headings
for (var i = 1; i <= 3; i++) for (var i = 1; i <= 3; i++)
{ {
snippets.push( if (!snippets.find(s => s.command == "/" + i))
{ {
command: "/" + i, snippets.push(
hint: "Heading " + i, {
insert: "#".repeat(i) + " ", command: "/" + i,
cursor: 0 hint: "Heading " + i,
}); insert: "#".repeat(i) + " ",
cursor: 0
});
}
} }
} }
function othervault()
{
return isremote() ? "local" : "remote";
}
function initvault()
{
currentvault = window.localStorage.getItem("vault") || "local";
}
function init() function init()
{ {
loadsettings(); loadsettings();
initvault();
commands.find(c => c.action == switchvault).hint = "Switch to " + othervault() + " vault";
window.onbeforeunload = checksaved; window.onbeforeunload = checksaved;
window.onclick = focuseditor; window.onclick = focuseditor;
initsnippets(); initsnippets();
if (settings.remote) if (isremote())
{ {
markunsaved(); markunsaved("*");
queryremote({action: "fetch"}) queryremote({action: "fetch"})
.then(data => .then(data =>
{ {
@ -553,7 +705,6 @@ function init()
} }
else else
{ {
delete commands.find(c => c.action == push).action;
loadstorage(); loadstorage();
} }
@ -582,6 +733,12 @@ function togglepassword()
function push() function push()
{ {
if (!isremote())
{
console.log("local vault, no push");
return;
}
if (localdata) if (localdata)
{ {
clearTimeout(timeoutid); clearTimeout(timeoutid);
@ -595,7 +752,7 @@ function push()
} }
else else
{ {
console.warn("Content changed"); console.warn("Content changed in the meantime, mark as not saved");
timeoutid = setTimeout(push, settings.savedelay); timeoutid = setTimeout(push, settings.savedelay);
} }
}) })
@ -645,7 +802,7 @@ function queryremote(params)
{ {
if (data.error == "authent") if (data.error == "authent")
{ {
failed(null); failed();
togglepassword(); togglepassword();
} }
else else
@ -826,6 +983,7 @@ function checkfolding()
} }
else if (isfold(line)) else if (isfold(line))
{ {
event.preventDefault();
unfold(); unfold();
} }
} }
@ -882,19 +1040,27 @@ function md2html(content)
var html = converter.makeHtml(content); var html = converter.makeHtml(content);
// internal links // internal links
html = html.replace(/\[\[([^\]]*)\]\]/g, "<a href='#' onclick='loadfile(\"$1\");'>$1</a>"); html = html.replace(/\[\[([^\]]*)\]\]/g, "<a href='#' onclick='loadnote(\"$1\");'>$1</a>");
return html; return html;
} }
function list() 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() function loadlast()
{ {
loadfile(list().shift() || timestamp()); loadnote(list().shift() || timestamp());
}
function loadprevious()
{
loadnote(list()[1]);
} }
function grep(needle) function grep(needle)
@ -1119,10 +1285,26 @@ function notecontentchanged()
markunsaved("*"); markunsaved("*");
// check snippets and autocomplete // check snippets and autocomplete
// should we move this on key down? to cancel when backspace?
if (before(2) == "[[") if (before(2) == "[[")
{ {
searchautocomplete(); 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 else
{ {
var snippet = snippets.find(s => before(s.command.length) == s.command); var snippet = snippets.find(s => before(s.command.length) == s.command);
@ -1135,7 +1317,7 @@ function notecontentchanged()
// save // save
var content = getnotecontent(); 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"); console.warn("Invalid content '" + content + "', file '" + currentnote.title + "' not saved");
} }
@ -1148,12 +1330,12 @@ function notecontentchanged()
function loadtodo() function loadtodo()
{ {
loadfile("todo"); loadnote("todo");
} }
function loadquicknote() function loadquicknote()
{ {
loadfile("Quick note"); loadnote("Quick note");
} }
function timestamp() function timestamp()
@ -1163,7 +1345,7 @@ function timestamp()
function startnewnote() function startnewnote()
{ {
loadfile(timestamp()); loadnote(timestamp());
} }
function showhelp() function showhelp()
@ -1229,7 +1411,7 @@ function searchautocomplete()
function searchandloadnote() function searchandloadnote()
{ {
selectnote().then(loadfile); selectnote().then(loadnote);
} }
function rename(newname) function rename(newname)
@ -1251,6 +1433,7 @@ function rename(newname)
currentnote.title = newname; currentnote.title = newname;
markunsaved("*");
serialize(); serialize();
return ""; return "";
} }
@ -1259,7 +1442,7 @@ function deletenote()
{ {
if (confirm('delete "' + currentnote.title + '"?')) if (confirm('delete "' + currentnote.title + '"?'))
{ {
var error = rename(".deleted_" + currentnote.title) var error = rename(".deleted_" + currentnote.title);
if (!error) if (!error)
{ {
loadlast(); loadlast();
@ -1389,12 +1572,12 @@ function editorkeydown()
{ {
if (event.key == "Enter") if (event.key == "Enter")
{ {
var currentline = md.value.substring(0, getpos()).split("\n").pop(); var line = md.value.substring(0, getpos()).split("\n").pop();
markerslist.filter(marker => currentline.startsWith(marker)) markerslist.filter(marker => line.startsWith(marker))
.forEach(marker => .forEach(marker =>
{ {
event.preventDefault(); event.preventDefault();
if (currentline != marker) if (line != marker)
{ {
insert("\n" + 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); var note = localdata.find(n => n.title == name);
if (!note) if (!note)
@ -1488,6 +1671,7 @@ function loadfile(name)
} }
bindfile(note); bindfile(note);
putontop();
} }
function sendpassword() function sendpassword()

View File

@ -3,8 +3,6 @@
body { body {
margin-left: 5%; margin-left: 5%;
margin-right: 5%; margin-right: 5%;
font-size: 90%;
line-height: 130%;
caret-color: #5AA7CE; caret-color: #5AA7CE;
} }
@ -45,6 +43,7 @@ body::-webkit-scrollbar-thumb {
outline: none; outline: none;
border: none; border: none;
width: 100%; width: 100%;
color: inherit;
resize: none; resize: none;