refactor local serialization

This commit is contained in:
quenousimporte 2024-02-04 22:43:24 +01:00
parent d633cf719d
commit de6268d86f
1 changed files with 120 additions and 182 deletions

280
main.js
View File

@ -19,37 +19,18 @@ var defaultsettings =
}; };
//builtin //builtin
var markerslist = ["* ", "- ", " * ", " - ", ">> ", "> ", "=> ", "— ", "[ ] ", " ", "• ", "- [ ]", "[x] ", "- [x]"]; const markerslist = ["* ", "- ", " * ", " - ", ">> ", "> ", "=> ", "— ", "[ ] ", " ", "• ", "- [ ]", "[x] ", "- [x]"];
var codelanguages = ["xml", "js", "sql"]; const codelanguages = ["xml", "js", "sql"];
var tagmark = "+"; const tagmark = "+";
// globals // globals
var previoustitle = "";
var metadata = null; var metadata = null;
var fileindex = 0; var fileindex = 0;
var workerid = null; var workerid = null;
var backup = ""; var backup = "";var settings = null;
var saved = true;
var lastsaved = "";
var pending = false;
var settings = null;
var tags = null; var tags = null;
var stat =
{
ses:
{
q: 0,
t: timestamp(),
d: 0
},
cur:
{
q: 0,
t: timestamp(),
d: 0
}
}
var commands = [ var commands = [
{ {
hint: "Close menu" hint: "Close menu"
@ -57,9 +38,7 @@ var commands = [
{ {
shortcut: "ctrl+shift+P", shortcut: "ctrl+shift+P",
hint: "Command palette", hint: "Command palette",
allowunsaved: true,
action: commandpalette, action: commandpalette,
excludepalette: true
}, },
{ {
shortcut: "ctrl+p", shortcut: "ctrl+p",
@ -78,14 +57,12 @@ var commands = [
}, },
{ {
hint: "Force save", hint: "Force save",
action: save, action: serialize,
shortcut: "ctrl+s", shortcut: "ctrl+s"
allowunsaved: true
}, },
{ {
hint: "Share note", hint: "Share note",
action: share, action: share
allowunsaved: true
}, },
{ {
hint: "Share note (html)", hint: "Share note (html)",
@ -99,20 +76,17 @@ var commands = [
{ {
shortcut: "ctrl+i", shortcut: "ctrl+i",
hint: "Toggle title", hint: "Toggle title",
action: toggletitle, action: toggletitle
allowunsaved: true
}, },
{ {
shortcut: "ctrl+m", shortcut: "ctrl+m",
hint: "Toggle preview", hint: "Toggle preview",
action: togglepreview, action: togglepreview
allowunsaved: true
}, },
{ {
shortcut: "ctrl+shift+M", shortcut: "ctrl+shift+M",
hint: "Toggle preview with merged subnotes", hint: "Toggle preview with merged subnotes",
action: togglepreviewwithsubs, action: togglepreviewwithsubs
allowunsaved: true
}, },
{ {
shortcut: "ctrl+d", shortcut: "ctrl+d",
@ -125,8 +99,7 @@ var commands = [
}, },
{ {
hint: "Insert markdown header", hint: "Insert markdown header",
action: insertheader, action: insertheader
allowunsaved: true
}, },
{ {
hint: "Show help", hint: "Show help",
@ -166,8 +139,7 @@ var commands = [
{ {
hint: "Note outline", hint: "Note outline",
action: showoutline, action: showoutline,
shortcut: "ctrl+o", shortcut: "ctrl+o"
allowunsaved: true
}, },
{ {
hint: "Show connected notes", hint: "Show connected notes",
@ -177,13 +149,11 @@ var commands = [
{ {
hint: "Show stats", hint: "Show stats",
action: showinfo, action: showinfo,
shortcut: "ctrl+w", shortcut: "ctrl+w"
allowunsaved: true
}, },
{ {
hint: "Toggle spell check", hint: "Toggle spell check",
action: togglespellcheck, action: togglespellcheck,
allowunsaved: true,
shortcut: "F7" shortcut: "F7"
}, },
{ {
@ -255,8 +225,7 @@ var commands = [
}, },
{ {
hint: "Sort text", hint: "Sort text",
action: sortselection, action: sortselection
allowunsaved: true
}, },
{ {
hint: "Show backlinks", hint: "Show backlinks",
@ -264,8 +233,7 @@ var commands = [
}, },
{ {
hint: "Remove completed tasks", hint: "Remove completed tasks",
action: purgetodo, action: purgetodo
allowunsaved: true
}]; }];
var snippets = [ var snippets = [
@ -316,21 +284,19 @@ function genguid()
return crypto.randomUUID(); return crypto.randomUUID();
} }
function purgetodo() { function purgetodo()
{
if (currentistodo() && confirm("Remove completed tasks?")) if (currentistodo() && confirm("Remove completed tasks?"))
{ {
seteditorcontent(currentnote.content.replace(/\nx .*/g, "")); seteditorcontent(currentnote.content.replace(/\nx .*/g, ""));
} }
} }
function seteditorcontent(content, silent) function seteditorcontent(content)
{ {
md.value = content; md.value = content;
applycolors(); applycolors();
if (!silent) serialize();
{
datachanged();
}
resize(); resize();
} }
@ -453,7 +419,7 @@ function includesub()
if (confirm("Delete '" + title + "'?")) if (confirm("Delete '" + title + "'?"))
{ {
deletenote(subnote); deletenote(title);
} }
} }
} }
@ -490,19 +456,12 @@ function showinfo()
var tags = gettags(currentnote); var tags = gettags(currentnote);
showtemporaryinfo( showtemporaryinfo(
[ [
"saved: " + saved + (lastsaved? " (" + lastsaved + ")": ""),
"sync: " + (settings.sync ? "en" : "dis") + "abled", "sync: " + (settings.sync ? "en" : "dis") + "abled",
"title: " + currentnote.title, "title: " + currentnote.title,
"line count: " + md.value.split("\n").length, "line count: " + md.value.split("\n").length,
"word count: " + getwords(), "word count: " + getwords(),
"cursor position: " + md.selectionStart + " (" + pospercent() + "%)", "cursor position: " + md.selectionStart + " (" + pospercent() + "%)",
(tags ? "tags: " + tags : ""), (tags ? "tags: " + tags : ""),
"current note start: " + stat.cur.t,
"current note queries: " + stat.cur.q,
"current note data sent: " + formatsize(stat.cur.d),
"session start: " + stat.ses.t,
"session queries: " + stat.ses.q,
"session data sent: " + formatsize(stat.ses.d),
"notes count: " + localdata.length, "notes count: " + localdata.length,
"spell check: " + (md.spellcheck ? "en" : "dis") + "abled" "spell check: " + (md.spellcheck ? "en" : "dis") + "abled"
].join("\n")); ].join("\n"));
@ -877,11 +836,7 @@ function restoresettings()
function editsettings() function editsettings()
{ {
bind( bind("settings.json", JSON.stringify(settings, null, " "));
{
title: "settings.json",
content: JSON.stringify(settings, null, " ")
});
} }
function editsetting(name) function editsetting(name)
@ -932,11 +887,7 @@ function decryptnote()
function editpgpkeys() function editpgpkeys()
{ {
bind( bind("pgpkeys", localStorage.getItem("pgpkeys") || "");
{
title: "pgpkeys",
content: localStorage.getItem("pgpkeys") || ""
});
} }
function showtemporaryinfo(info) function showtemporaryinfo(info)
@ -1101,14 +1052,24 @@ function headerandtext(content)
function inserttodo(text) function inserttodo(text)
{ {
var todo = getorcreate("todo", ""); var guid = getguid("todo");
var split = headerandtext(todo); if (!guid)
todo.content = split.header + text + "\n" + split.text;
if (todo == currentnote)
{ {
seteditorcontent(todo.content, true); 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
{
localStorage.setItem(guid, content);
metadata[guid].lastchanged = Date.now();
serialize();
} }
return datachanged();
} }
function promptinserttodo() function promptinserttodo()
@ -1228,7 +1189,8 @@ function loadstorage()
msg.innerText = "Clipping..."; msg.innerText = "Clipping...";
notepage.appendChild(msg); notepage.appendChild(msg);
inserttodo("@clip " + clip).then(window.close); inserttodo("@clip " + clip)
window.close();
} }
// when multiple tabs or split? // when multiple tabs or split?
@ -1249,8 +1211,7 @@ function loadstorage()
if (window.title.value) if (window.title.value)
{ {
var guid = getguid(window.title.value); bind(window.title.value);
bind(guid);
if (line) if (line)
{ {
gotoline(line); gotoline(line);
@ -1354,14 +1315,14 @@ function migratelegacystorage()
var legacy = localStorage.getItem("data"); var legacy = localStorage.getItem("data");
if (legacy) if (legacy)
{ {
// todo: use title as key and guid as property. or not.
var legacy = JSON.parse(legacy); var legacy = JSON.parse(legacy);
var index = {}; var index = {};
legacy.forEach(note => legacy.reverse().forEach(note =>
{ {
var guid = genguid(); var guid = genguid();
localStorage.setItem(guid, note.content); localStorage.setItem(guid, note.content);
note.header = indexheader(note.content); note.header = indexheader(note.content);
note.lastchanged = Date.now();
delete note.content; delete note.content;
index[guid] = note; index[guid] = note;
}); });
@ -1448,9 +1409,6 @@ function queryremote(params)
{ {
return new Promise( (apply, failed) => { return new Promise( (apply, failed) => {
stat.cur.q++;
stat.ses.q++;
params.password = settings.password; params.password = settings.password;
var paramlist = []; var paramlist = [];
@ -1459,9 +1417,6 @@ function queryremote(params)
paramlist.push(i + "=" + encodeURIComponent(params[i])); paramlist.push(i + "=" + encodeURIComponent(params[i]));
} }
stat.cur.d += paramlist.join("&").length;
stat.ses.d += paramlist.join("&").length;
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open("POST", "handler.php"); xhr.open("POST", "handler.php");
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
@ -1661,11 +1616,7 @@ function showgrepresult(needle, grepresult)
grepcontent.push("No result."); grepcontent.push("No result.");
} }
bind( bind("Search result", grepcontent.join("\n"));
{
title: "Search result",
content: grepcontent.join("\n")
});
if (preview.hidden) if (preview.hidden)
{ {
@ -1877,12 +1828,6 @@ function resize()
} }
} }
function putontop()
{
console.warn("todo: put on top");
return;
}
function postpone() function postpone()
{ {
return new Promise(function(resolve) return new Promise(function(resolve)
@ -1892,31 +1837,37 @@ function postpone()
}); });
} }
function setsaved()
{
unsavedmark.hidden = true;
saved = true;
lastsaved = timestamp();
}
function serialize() function serialize()
{ {
// serialize all gui stuff to local storage clearTimeout(workerid);
workerid = null;
var guid = getguid(window.title.value); var guid = getguid(window.title.value);
if (guid)
{
var item = metadata[guid]; var item = metadata[guid];
item.title = title.value; item.title = title.value;
item.pos = md.selectionStart; item.pos = md.selectionStart;
item.header = indexheader(md.value); item.header = indexheader(md.value);
item.lastchanged = Date.now();
// is it the right place?
putontop();
localStorage.setItem("index", JSON.stringify(metadata)); localStorage.setItem("index", JSON.stringify(metadata));
localStorage.setItem(guid, md.value); localStorage.setItem(guid, md.value);
}
else if (title.value == "settings.json")
{
settings = JSON.parse(md.value);
savesettings();
loadsettings();
}
else if (title.value == "pgpkeys")
{
localStorage.setItem("pgpkeys", md.value);
}
console.log("data serialized");
} }
function save() /*function save()
{ {
return new Promise(function(resolve, reject) return new Promise(function(resolve, reject)
{ {
@ -2001,7 +1952,7 @@ function save()
} }
} }
}); });
} }*/
function escapeHtml(unsafe) { function escapeHtml(unsafe) {
return unsafe return unsafe
@ -2263,19 +2214,10 @@ function editorinput()
// criteria to improve. Or redraw only after? // criteria to improve. Or redraw only after?
var multiline = md.value.substring(md.selectionStart, md.selectionEnd).includes("\n"); var multiline = md.value.substring(md.selectionStart, md.selectionEnd).includes("\n");
applycolors(!multiline && event.data && (event.inputType == "insertText" || event.inputType == "deleteContentBackward" || event.inputType == "deleteContentForward")); applycolors(!multiline && event.data && (event.inputType == "insertText" || event.inputType == "deleteContentBackward" || event.inputType == "deleteContentForward"));
datachanged(); postpone().then(serialize);
resize(); resize();
} }
function datachanged()
{
saved = false;
unsavedmark.hidden = !settings.sync;
return postpone()
.then(save);
}
function timestamp() function timestamp()
{ {
var utc = new Date(); var utc = new Date();
@ -2286,17 +2228,17 @@ function timestamp()
function quicknewnote() function quicknewnote()
{ {
serialize();
loadnote(timestamp()); loadnote(timestamp());
datachanged();
} }
function startnewnote() function startnewnote()
{ {
serialize();
var title = prompt("Note title: ", timestamp()); var title = prompt("Note title: ", timestamp());
if (title) if (title)
{ {
loadnote(title); loadnote(title);
datachanged();
} }
} }
@ -2334,11 +2276,7 @@ function showhelp()
help.push("##Sources"); help.push("##Sources");
help.push("https://github.com/quenousimporte/notes"); help.push("https://github.com/quenousimporte/notes");
bind( bind("Help", help.join("\n"));
{
title: "Help",
content: help.join("\n")
});
if (preview.hidden) if (preview.hidden)
{ {
@ -2362,6 +2300,7 @@ function toggletitle()
function selectnote() function selectnote()
{ {
// todo: sort by lastchanged
return searchinlist( return searchinlist(
Object.values(metadata).map(item => Object.values(metadata).map(item =>
{ {
@ -2491,6 +2430,7 @@ function renamereferences(newname)
function restoredeleted() function restoredeleted()
{ {
// todo: parse local storage starting with "deleted" and reindex with a new guid
var trash = window.localStorage.getItem("trash"); var trash = window.localStorage.getItem("trash");
if (trash) if (trash)
{ {
@ -2518,24 +2458,22 @@ function restoredeleted()
} }
} }
function deletenote(note) function deletenote(title)
{ {
var trash = JSON.parse(window.localStorage.getItem("trash") || "[]"); var guid = getguid(title);
note.deletiondate = timestamp(); delete metadata[guid];
trash.push(note); renameinternallinks(title, title + " (deleted)");
window.localStorage.setItem("trash", JSON.stringify(trash)); var content = localStorage.getItem(guid);
localStorage.removeItem(guid);
localdata = localdata.filter(n => n != note); localStorage.setItem("deleted_" + title, content);
localStorage.setItem("index", JSON.stringify(metadata));
renamereferences(note.title + " (deleted)");
datachanged();
} }
function deletecurrentnote() function deletecurrentnote()
{ {
if (confirm('delete "' + currentnote.title + '"?')) if (confirm('delete "' + title.value + '"?'))
{ {
deletenote(currentnote); deletenote(title.value);
loadlast(); loadlast();
} }
} }
@ -2578,11 +2516,7 @@ function shortcutmatches(event, shortcut)
function executecommand(command) function executecommand(command)
{ {
if (!command.allowunsaved && !saved) if (command.remoteonly && !settings.sync)
{
showtemporaryinfo("Cannot perform '" + command.hint + "' because current note is not saved.");
}
else if (command.remoteonly && !settings.sync)
{ {
showtemporaryinfo(command.hint + " is not available in local mode."); showtemporaryinfo(command.hint + " is not available in local mode.");
} }
@ -2748,32 +2682,42 @@ function setwindowtitle()
document.title = title.value; 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)
{
localStorage.setItem(guid, newcontent);
}
});
}
function ontitlechange() function ontitlechange()
{ {
if (localdata.find(n => n.title == title.value)) var guid = getguid(title.value);
if (guid)
{ {
showtemporaryinfo(title.value + " alreday exists"); showtemporaryinfo(title.value + " alreday exists");
title.value = currentnote.title; title.value = previoustitle;
return;
} }
else
// rename internal references
localdata
.filter(note => note != currentnote)
.forEach(note =>
{ {
note.content = note.content.replaceAll("[[" + currentnote.title + "]]", "[[" + title.value + "]]"); renameinternallinks(previoustitle, title.value);
});
currentnote.title = title.value; guid = getguid(previoustitle);
previoustitle = title.value;
datachanged(); metadata[guid].title = title.value;
setwindowtitle(); setwindowtitle();
serialize();
if (!settings.titlebydefault) if (!settings.titlebydefault)
{ {
toggletitle(); toggletitle();
} }
}
} }
function simplifystring(str) function simplifystring(str)
@ -2961,18 +2905,16 @@ function togglepreviewwithsubs()
function bind(title, content, pos) function bind(title, content, pos)
{ {
var changed = title.value != title; previoustitle = title;
backup = content; backup = content;
window.title.value = title; window.title.value = title;
setwindowtitle(); setwindowtitle();
seteditorcontent(content || "", true); md.value = content || "";
applycolors();
if (changed)
{
md.style.height = "0px"; md.style.height = "0px";
}
resize(); resize();
setpos(pos || 0); setpos(pos || 0);
@ -3027,10 +2969,6 @@ function loadnote(title)
bind(item.title, content, item.pos); bind(item.title, content, item.pos);
stat.cur.q = 0;
stat.cur.d = 0;
stat.cur.t = timestamp();
if (!preview.hidden || (preview.hidden && (gettags(md.value).indexOf("preview") !== -1))) if (!preview.hidden || (preview.hidden && (gettags(md.value).indexOf("preview") !== -1)))
{ {
togglepreview(); togglepreview();