From 7ed60f58e6d18b5ed00295356eb28bb02d38872b Mon Sep 17 00:00:00 2001 From: quenousimporte <76260127+quenousimporte@users.noreply.github.com> Date: Wed, 18 Jan 2023 12:34:55 +0100 Subject: [PATCH] initial commit --- favicon.ico | Bin 0 -> 15406 bytes handler.php | 50 ++ index.html | 45 ++ main.js | 1478 +++++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 38 ++ split.css | 21 + split.html | 17 + style.css | 106 ++++ 8 files changed, 1755 insertions(+) create mode 100644 favicon.ico create mode 100644 handler.php create mode 100644 index.html create mode 100644 main.js create mode 100644 readme.md create mode 100644 split.css create mode 100644 split.html create mode 100644 style.css diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9f60b52e8ec6837de078b1d619fc979224327c70 GIT binary patch literal 15406 zcmeHNJ4jqX6rF4o6%jZ75ET4j{2{JQz(Q241V4>}MT&q;3bjx{1xce87J{NtP)Mtd zS}0g(W3z<{f+7gwU(*N{7XI~Q?lUtyb=SA9yZiPfbKx>?X6~JP=j@%?`7!o^ePnh! z<7q1U9>Ulc##n0VV?O&6W50Nr!|}KNGh@zp#+qz`>V!qmO?)j{VymeKu=E(Mn*=EnVE_D`g$WZS>M*yhTYv=^!E0mwzd{sU0paoKbQ4E z_VJ!E@SZWK>AIX0-cU&MOe*J6&NM%{BaXR^a~J2|0mPqdkxgMcfTo|A3Ut4p80VQl zV*cE=$}0psMqqSw6hlKpa5|lCbyikZG#QffHAO^3X#drnots>3 zWnbY>ai!u)x?IdZ`T6;{xVUiHPDx3LWC*GxryT9Vri2wAT)%~fhkN>S`&>>RKeU!UIXN-3L3dqgX=&2`l#~=h z9e)Yv{inNKdcSycbMvaXnVFd<>!zBTnpfrhS*iD*)^Sl$Q5YK=!`a!{i@g}_&Gz>8 zP+3_iv8TJ%@$qqgy7+3_djAs>6A>RDkI>LiR99DHaBvXw^Yfs$ymNDN=()`(%nI+8ZV$Bp@axM(daGn^KY+7#IMJXR}D}e`{+i?(Xh1 zpOTZ4U-&D0C;a2$;?UmSj;kwkkL{b9n$XbDpz-YNtb}oWeI4{>jAEVMRb*sjAU8J` z<>lpQX=%a2!a`8osndH8!O!D>@-&WVzTDs6*WNyyo}PNV$CqHJ4~2k2Kp~(I@D~DB uzJnC+3H*hPzuOgWA diff --git a/index.html b/index.html new file mode 100644 index 0000000..118d248 --- /dev/null +++ b/index.html @@ -0,0 +1,45 @@ + + + + + + + + notes + + + + + + + + + + + +
+
+ + + +   +
+ +
+ + +
+
+ + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..390b6a8 --- /dev/null +++ b/main.js @@ -0,0 +1,1478 @@ +var defaultsettings = +{ + savedelay: 1000, + bgcolor: "white", + fontfamily: "'Inconsolata', 'Consolas', monospace", + foldmarkstart: 22232, + defaultpreviewinsplit: false, + remote: false, + enablefolding: false +}; + +//builtin +var markerslist = ["* ", "- ", " * ", " - ", ">> ", "> ", "=> "]; +var sectionmarks = ["---", "### ", "## ", "# ", "```"]; +var codelanguages = ["xml", "js", "sql"]; + +// globals +var currentnote = null; +var fileindex = 0; +var timeoutid = null; +var folds = []; +var backup = ""; +var localdata = null; +var saved = true; +var settings = null; + +var commands = [ +{ + hint: "Close menu" +}, +{ + shortcut: "ctrl+p", + hint: "Show notes list", + savedonly: true, + action: searchandloadnote +}, +{ + hint: "Share note", + savedonly: true, + action: share +}/*, +{ + hint: "Share note (html)", + savedonly: true, + action: sharehtml +}*/, +{ + shortcut: "ctrl+n", + hint: "New note", + savedonly: true, + action: startnewnote +}, +{ + shortcut: "ctrl+shift+P", + hint: "Command palette", + savedonly: false, + action: commandpalette, + excludepalette: true +}, +{ + shortcut: "ctrl+t", + hint: "Open todo", + savedonly: true, + action: loadtodo +}, +{ + shortcut: "ctrl+q", + hint: "Open quick note", + savedonly: true, + action: loadquicknote +}, +{ + shortcut: "ctrl+shift+F", + hint: "Find in notes", + savedonly: true, + action: showgrep +}, +{ + shortcut: "ctrl+i", + hint: "Toggle title", + savedonly: true, + action: toggletitle +}, +{ + shortcut: "ctrl+m", + hint: "Toggle preview", + savedonly: true, + action: togglepreview +}, +{ + shortcut: "ctrl+d", + hint: "Delete note", + savedonly: true, + action: deletenote +}, +{ + hint: "Restore note", + savedonly: true, + action: restore +}, +{ + shortcut: "ctrl+h", + hint: "Insert markdown header", + action: insertheader +}, +{ + savedonly: true, + shortcut: "F1", + hint: "Show help", + action: showhelp +}, +{ + savedonly: true, + shortcut: "ctrl+shift+C", + hint: "Fold", + action: fold +}, +{ + savedonly: true, + shortcut: "ctrl+shift+O", + hint: "Unfold", + action: unfold +}, +{ + savedonly: true, + hint: "Unfold all", + action: unfoldall +}, +{ + savedonly: true, + hint: "Download note", + action: downloadnote +}, +{ + savedonly: true, + hint: "Download local data", + action: downloadlocal, + shortcut: "ctrl+shift+S" +}/*, +{ + savedonly: true, + hint: "Download all notes", + action: downloadnotes +}*/, +{ + hint: "Force push", + action: push, + shortcut: "ctrl+s" +}, +{ + hint: "Search tags", + action: searchtags, + savedonly: true, + shortcut: "ctrl+shift+T" +}, +{ + hint: "Log out", + action: logout, + savedonly: true +}, +{ + hint: "Toggle split view", + savedonly: true, + action: togglesplit +}, +{ + hint: "Open last modified note", + savedonly: true, + action: loadlast, + shortcut: "ctrl+l" +}, +{ + hint: "Sort text", + savedonly: true, + action: sortselection +}, +{ + hint: "Word count", + action: wordcount, + shortcut: "ctrl+w" +}, +{ + hint: "Settings", + savedonly: true, + action: editsettings +}, +{ + hint: "Restore default settings", + savedonly: true, + action: restoresettings +}, +{ + hint: "Document outline", + savedonly: true, + action: showoutline +}, +{ + hint: "Internal links", + savedonly: true, + action: showinternallinks +}]; + +var snippets = [ +{ + command: "/code", + hint: "Code block", + insert: "```\n\n```", + cursor: -4 +}, +{ + command: "/date", + hint: "Current date", + insert: (new Date).toISOString().substring(0, 10) + " ", + cursor: 0 +}, +{ + command: "/bonjour", + hint: "Standard answer (fr)", + insert: "Bonjour ,\n\n\n\nBien cordialement,\nSimon", + cursor: -29 +}, +{ + command: "/hello", + hint: "Standard answer (en)", + insert: "Hello ,\n\n\n\nKind regards,\nSimon", + cursor: -24 +}]; + +function showinternallinks() +{ + searchinlist( + getnotecontent() + .match(/\[\[([^\]]*)\]\]/g || []) + .map(l => l.replace("[[", "").replace("]]", ""))) + .then(loadfile); +} + +function showoutline() +{ + var outline = {}; + var pos = 0; + getnotecontent().split("\n").forEach(line => + { + pos += line.length + 1; + if (line.startsWith("#")) + { + line = line + .replace("# ", "") + .replace(/#/g, "\xa0\xa0\xa0\xa0"); + outline[line] = pos; + } + }); + searchinlist(Object.keys(outline)) + .then(line => + { + md.setSelectionRange(outline[line], outline[line]); + md.focus(); + }); +} + +function restoresettings() +{ + if (confirm("Restore default settings?")) + { + window.localStorage.setItem("settings", JSON.stringify(defaultsettings)); + loadsettings(); + } +} + +function editsettings() +{ + bindfile( + { + title: "settings.json", + content: JSON.stringify(settings, null, " ") + }); +} + +function showtemporaryinfo(str) +{ + /*var prev = mark.textContent; + mark.textContent = str; + setTimeout(function() + { + mark.textContent = prev; + }, 5000);*/ + alert(str); +} + +function wordcount() +{ + showtemporaryinfo(getnotecontent().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 logout() +{ + window.localStorage.removeItem("password"); + togglepassword(); +} + +function searchtags() +{ + var tags = {}; + + localdata + .filter(n => !n.title.startsWith(".")) + .forEach(n => + { + var ts = gettags(n); + ts.forEach(t => + { + tags[t] = tags[t] || []; + tags[t].push(n.title); + }); + }); + + searchinlist(Object.keys(tags).sort()) + .then(tag => searchinlist(tags[tag])) + .then(loadfile); +} + +function gettags(note) +{ + var i = note.content.indexOf("tags: "); + if (i > -1) + { + var j = note.content.indexOf("\n", i); + return note.content.substring(i + 6, j).split(",").map(t => t.toLowerCase().trim()); + } + return []; +} + +function marksaved() +{ + saved = true; + mark.textContent = "\xa0"; +} + +function markunsaved(text) +{ + saved = false; + if (text) + { + mark.textContent = text; + } +} + +function share(html) +{ + if (navigator.share) + { + navigator.share( + { + text: html ? "" + md2html(getnotecontent()) + "": getnotecontent(), + title: currentnote.title + }); + } +} + +function sharehtml() +{ + share(true); +} + +function download(filename, content) +{ + // trick: https://www.bitdegree.org/learn/javascript-download + // to improve... + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content)); + element.setAttribute('download', filename); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); +} + +function downloadnotes() +{ + localdata + .filter(note => !note.title.startsWith(".")) + .forEach(note => + { + download(note.title + ".md", note.content); + }); +} + +function downloadlocal() +{ + download(timestamp() + " " + (window.location.hostname || "serverless") + " notes.json", JSON.stringify(localdata)); +} + +function downloadnote() +{ + download(currentnote.title + ".md", getnotecontent()); +} + +function serialize() +{ + putontop(); + + window.localStorage.setItem("data", JSON.stringify(localdata)); + + if (currentnote.title == "settings.json") + { + window.localStorage.setItem("settings", getnotecontent()); + } + + if (settings.remote) + { + clearTimeout(timeoutid); + timeoutid = setTimeout(push, settings.savedelay); + } + else + { + marksaved(); + } +} + +function remotecallfailed(error) +{ + markunsaved("!"); + if (error) + { + console.warn(error); + showtemporaryinfo(error); + } +} + +function loadstorage() +{ + var item = window.localStorage.getItem("data"); + localdata = item ? JSON.parse(item) : []; + + if (currentnote) + { + currentnote = localdata.find(n => n.title == currentnote.title); + } + + if (currentnote) + { + bindfile(currentnote); + } + else + { + loadlast(); + } +} + +function loadsettings() +{ + settings = {...defaultsettings}; + var item = window.localStorage.getItem("settings"); + if (item) + { + item = JSON.parse(item); + for (var key in item) + { + settings[key] = item[key]; + } + } + + document.body.style.background = settings.bgcolor; + document.body.style.fontFamily = settings.fontfamily; + + if (!settings.enablefolding) + { + commands = commands.filter(c => !c.hint.toLowerCase().includes("fold")); + } +} + +function checksaved() +{ + if (!saved) + { + return "not saved"; + } +} + +function init() +{ + loadsettings(); + + window.onbeforeunload = checksaved; + window.onclick = focuseditor; + window.onstorage = loadstorage; + + codelanguages.forEach(lang => + { + if (!snippets.includes(s => s.command == "/" + lang)) + { + snippets.push( + { + command: "/" + lang, + hint: lang + " code block", + insert: "```" + lang + "\n\n```", + cursor: -4 + }); + } + }); + + if (settings.remote) + { + markunsaved(); + queryremote({action: "fetch"}) + .then(data => + { + marksaved(); + localdata = data; + loadlast(); + }) + .catch(remotecallfailed); + } + else + { + delete commands.find(c => c.action == push).action; + loadstorage(); + } + + if (issplit()) + { + if (settings.defaultpreviewinsplit && name == "right") + { + togglepreview(); + } + else if (name == "left") + { + md.focus(); + } + } +} + +function togglepassword() +{ + password.value = ""; + authentpage.hidden = false; + notepage.style.display = "none"; + document.title = "notes"; + password.focus(); +} + +function push() +{ + if (localdata) + { + clearTimeout(timeoutid); + var content = getnotecontent(); + queryremote({action: "push", data: JSON.stringify(localdata)}) + .then(() => { + console.log("Data sent to server"); + if (getnotecontent() == content) + { + marksaved(); + } + else + { + console.warn("Content changed"); + timeoutid = setTimeout(push, settings.savedelay); + } + }) + .catch(remotecallfailed); + } + else + { + console.warn("Cannot push empty data"); + } +} + +function queryremote(params) +{ + return new Promise( (apply, failed) => { + + params.password = window.localStorage.getItem("password"); + + var paramlist = []; + for (var i in params) + { + paramlist.push(i + "=" + encodeURIComponent(params[i])); + } + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "handler.php"); + xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + + xhr.onerror = function() + { + failed("XMLHttpRequest error"); + } + + xhr.onload = function() + { + if (xhr.status !== 200) + { + failed("Http status " + xhr.status); + } + else + { + var data = {}; + try + { + data = JSON.parse(xhr.responseText); + + if (data.error) + { + if (data.error == "authent") + { + failed(null); + togglepassword(); + } + else + { + failed("Remote handler returned an error: " + data.error); + } + } + else + { + authentpage.hidden = true; + notepage.style.display = "table"; + apply(data); + } + } + catch(error) + { + failed("Handler result is not valid. JS error: " + error); + } + } + } + xhr.send(paramlist.join("&")); + }); +} + +function applyfolds(content) +{ + for (var i = folds.length - 1; i >= 0; i--) + { + content = content.replace(String.fromCodePoint(settings.foldmarkstart + i), folds[i]); + } + return content; +} + +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 sortselection() +{ + var content = getnotecontent(); + 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"); + md.value = content.substring(0, range.start) + sorted + content.substring(range.end); + notecontentchanged(); +} + +function selectlines() +{ + var range = getlinesrange(); + md.selectionStart = range.start; + md.selectionEnd = range.end; +} + +function isfold(linecontent) +{ + var res = linecontent.length == 1 && linecontent.codePointAt(0) >= settings.foldmarkstart; + if (res) + { + //security: if > 100, probably not a fold. Maybe an emoji. To improve. + res &= foldindex(linecontent) < 100; + } + return res; +} + +function foldindex(foldmark) +{ + return foldmark.codePointAt(0) - settings.foldmarkstart; +} + +function fold() +{ + // todo: forbid if > 100? + var start = md.selectionStart; + selectlines(); + + var content = md.value; + var char = String.fromCodePoint(settings.foldmarkstart + folds.length); + var value = content.substring(md.selectionStart, md.selectionEnd) + + folds.push(value); + + setnotecontent(content.substring(0, md.selectionStart) + + char + + content.substring(md.selectionEnd)); + + md.focus(); + setpos(start); + + resize(); +} + +function unfold() +{ + var range = getlinesrange(); + var linecontent = md.value.substring(range.start, range.end); + if (isfold(linecontent)) + { + var i = foldindex(linecontent); + md.value = md.value.replace(linecontent, folds[i]); + md.focus(); + setpos(range.start + folds[i].length); + } + + resize(); +} + +function unfoldall() +{ + md.value = getnotecontent(); + resetfolds(); + setpos(0); + md.focus(); + + resize(); +} + +function checkfolding() +{ + if (!settings.enablefolding) + { + console.log("folding is disabled."); + return; + } + + var range = getlinesrange(); + var line = md.value.substring(range.start, range.end); + var sectionmark = sectionmarks.find(m => line.startsWith(m)); + if (sectionmark) + { + event.preventDefault(); + + // move to next line + setpos(range.end + 1); + range = getlinesrange(); + var nextline = md.value.substring(range.start, range.end); + + if (isfold(nextline)) + { + unfold(); + } + else + { + // find next occurence. If not found take all the remaining file. + if (md.value.includes("\n" + sectionmark, range.end)) + { + sectionend = md.value.indexOf("\n" + sectionmark, range.end); + } + else + { + sectionend = md.value.length; + } + + // keep last empty line if any + if (md.value[sectionend] == "\n") + { + sectionend--; + } + + md.setSelectionRange(range.start, sectionend); + fold(); + } + } + else if (isfold(line)) + { + unfold(); + } +} + +function setnotecontent(content) +{ + md.value = content; +} + +function getnotecontent() +{ + return applyfolds(md.value); +} + +function ontopbarclick() +{ + if (title.hidden) + { + commandpalette(); + } +} + +/*function checkfoldmismatch() +{ + start = settings.foldmarkstart.toString(16); + end = (settings.foldmarkstart + 100).toString(16); + var match = md.value.match(new RegExp("[\\u" + start + "-\\u" + end + "]", "g")); + var markcount = 0; + if (match) + { + markcount = match.length; + } + var diff = folds.length - markcount; + if (diff) + { + console.warn(diff + " fold(s) missing."); + } +}*/ + +function md2html(content) +{ + // dumb fallback for offline mode + if (typeof showdown == "undefined") return content + .replace(/\*\*([^\*]*)\*\*/g, "$1") + .replace(/\*([^\*]*)\*/g, "$1") + .replace(/\## (.*)/g, "

$1

") + .replace(/\# (.*)/g, "

$1

") + .replace(/\n/g, "
"); + var converter = new showdown.Converter(); + converter.setOption("simplifiedAutoLink", true); + converter.setOption("simpleLineBreaks", true); + converter.setOption("metadata", true); + + var html = converter.makeHtml(content); + + // internal links + html = html.replace(/\[\[([^\]]*)\]\]/g, "$1"); + + return html; +} + +function list() +{ + return localdata.map(n => n.title).filter(t => !t.startsWith(".")); +} + +function loadlast() +{ + loadfile(list().shift() || timestamp()); +} + +function grep(needle) +{ + var result = {}; + + localdata + .filter(n => !n.title.startsWith(".")) + .forEach(note => + { + if (note.title.toLowerCase().includes(needle.toLowerCase())) + { + result[note.title] = {}; + } + note.content.split("\n") + .forEach((line, nb) => { + if (line.toLowerCase().includes(needle.toLowerCase())) + { + result[note.title] = result[note.title] || {}; + result[note.title][nb] = line; + } + }); + }); + + return result; +} + +function showgrepresult(grepresult) +{ + var grepcontent = ["# Search results: \"" + filter.value + "\""]; + for (var file in grepresult) + { + grepcontent.push("[[" + file + "]]"); + for (var l in grepresult[file]) + { + grepcontent.push("[" + l + "] " + grepresult[file][l].replace(new RegExp("(" + filter.value + ")", "gi"), "**$1**")); + } + grepcontent.push(""); + } + + if (grepcontent.length == 0) + { + grepcontent.push("No result."); + } + + bindfile( + { + title: "Search result", + content: grepcontent.join("\n") + }); + + if (preview.hidden) + { + togglepreview(); + } +} + +function showgrep() +{ + filteredlist.hidden = true; + searchdialog.hidden = false; + filter.focus(); + filter.select(); + + filter.onkeydown = function() + { + if (event.key === "Enter") + { + event.preventDefault(); + searchdialog.hidden = true; + showgrepresult(grep(filter.value)); + } + } + + // live search + /*filter.oninput = function() + { + if (filter.value.length > 1) + { + showgrepresult(grep(filter.value)); + } + }*/ +} + +function commandpalette() +{ + searchinlist(commands + .filter(c => !c.excludepalette) + .map(c => c.hint) + .concat(snippets.map(s => "Insert snippet: " + s.hint))) + .then(hint => + { + var command = commands.find(c => c.hint == hint); + if (command && command.action) + { + command.action(); + } + else + { + var snippet = snippets.find(s => "Insert snippet: " + s.hint == hint); + if (snippet) + { + insert(snippet.insert, snippet.cursor); + notecontentchanged(); + md.focus(); + } + } + }); +} + +function insert(text, cursoroffset = 0, nbtodelete = 0) +{ + var pos = md.selectionStart; + var content = md.value; + md.value = + content.substring(0, pos - nbtodelete) + + text + + content.substring(pos); + setpos(pos - nbtodelete + text.length + cursoroffset); + resize(); +} + +function searchinlist(list) +{ + return new Promise(selectitem => + { + fileindex = 0; + searchdialog.hidden = false; + filteredlist.hidden = false; + + filteredlist.innerHTML = ""; + filter.value = ""; + filter.focus(); + + list.forEach(item => + { + var elt = document.createElement("div"); + elt.textContent = item; + elt.onclick = function() + { + searchdialog.hidden = true; + selectitem(item); + } + 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.textContent : filter.value); + } + } + }); +} + +function applyfileindex() +{ + var i = 0; + [...filteredlist.children].forEach(child => + { + if (child.nodeName == "DIV") + { + child.className = ""; + if(!child.hidden) + { + if (i++ == fileindex) + { + child.className = "selected"; + } + } + } + }); +} + +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) return; + + md.rows = (md.value.match(/\n/g) || []).length + 1; + while (md.scrollHeight > md.clientHeight) + { + md.rows *= 1.5; + } +} + +function putontop() +{ + if (localdata.find(n => n == currentnote)) + { + localdata.splice(localdata.indexOf(currentnote), 1); + localdata.unshift(currentnote); + } +} + +function notecontentchanged() +{ + markunsaved("*"); + + // check snippets and autocomplete + if (before(2) == "[[") + { + searchautocomplete(); + } + else + { + var snippet = snippets.find(s => before(s.command.length) == s.command); + if (snippet) + { + insert(snippet.insert, snippet.cursor, snippet.command.length); + } + } + resize(); + + // save + var content = getnotecontent(); + if (content == "" || content == "null" || content == "undefined") + { + console.warn("Invalid content '" + content + "', file '" + currentnote.title + "' not saved"); + } + else + { + currentnote.content = content; + serialize(); + } +} + +function loadtodo() +{ + loadfile("todo"); +} + +function loadquicknote() +{ + loadfile("Quick note"); +} + +function timestamp() +{ + return (new Date).toISOString().replace("T", " ").replace(/\..*/, "").replace(/:/g, "."); +} + +function startnewnote() +{ + loadfile(timestamp()); +} + +function showhelp() +{ + var help = ["# Notes"]; + help.push("## Shortcuts"); + + commands + .filter(command => Boolean(command.shortcut)) + .forEach(command => help.push(command.hint + ": " + command.shortcut)); + + help.push("## Snippets"); + snippets.forEach(snippet => + { + help.push(snippet.hint + ": " + snippet.command); + }); + + help.push("## Libs"); + help.push("[Showdown](https://showdownjs.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/)"); + + bindfile( + { + title: "Help", + content: 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(list()); +} + +function searchautocomplete() +{ + selectnote().then(insertautocomplete); +} + +function searchandloadnote() +{ + selectnote().then(loadfile); +} + +function rename(newname) +{ + if (localdata.find(n => n.title == newname)) + { + var error = newname + " alreday exists"; + console.warn(error); + return error; + } + + // rename internal references + localdata + .filter(note => note != currentnote) + .forEach(note => + { + note.content = note.content.replaceAll("[[" + currentnote.title + "]]", "[[" + newname + "]]"); + }); + + currentnote.title = newname; + + serialize(); + return ""; +} + +function deletenote() +{ + if (confirm('delete "' + currentnote.title + '"?')) + { + var error = rename(".deleted_" + currentnote.title) + if (!error) + { + loadlast(); + } + else + { + console.warn("Failed to delete '" + currentnote.title + "'"); + } + } +} + +function restore() +{ + if (confirm('restore "' + currentnote.title + '"?')) + { + setnotecontent(backup); + notecontentchanged(); + } +} + +function insertheader() +{ + if (!getnotecontent().startsWith("---")) + { + var headers = "---\ndate: " + (new Date).toISOString().substring(0, 10) + "\ntags: \n---\n\n"; + md.value = 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 mainkeydownhandler() +{ + if (event.key == "Escape") + { + if (!searchdialog.hidden) + { + event.preventDefault(); + searchdialog.hidden = true; + md.focus(); + } + else if (preview.hidden == false) + { + togglepreview(); + } + } + + 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(); + } + + commands.filter(c => c.shortcut) + .forEach(command => + { + var s = splitshortcut(command.shortcut); + if (event.key == s.key && !(s.ctrl && !event.ctrlKey && !event.altKey) && !(s.shift && !event.shiftKey)) + { + event.preventDefault(); + if (command.savedonly && !saved) + { + console.log("Data note saved, try again") + } + else if (command.action) + { + command.action(); + } + } + }); +} + +function ontitlechange() +{ + var oldname = currentnote.title; + var error = rename(title.value); + + if (!error) + { + console.log("'" + oldname + "' renamed to '" + currentnote.title + "'"); + document.title = title.value; + } + else + { + title.value = currentnote.title; + } +} + +function applyfilter() +{ + [...filteredlist.children].forEach(div => + { + div.hidden = div.textContent.toLowerCase().indexOf(filter.value.toLowerCase()) < 0; + }); + + fileindex = 0; + applyfileindex(); +} + +function backspace(nb) +{ + var pos = getpos(); + var c = md.value; + md.value = c.substring(0, pos - nb) + c.substring(pos); + setpos(pos - nb); +} + +function editorkeydown() +{ + if (event.key == "Enter") + { + var currentline = md.value.substring(0, getpos()).split("\n").pop(); + markerslist.filter(marker => currentline.startsWith(marker)) + .forEach(marker => + { + event.preventDefault(); + if (currentline != marker) + { + insert("\n" + marker); + } + else + { + backspace(marker.length); + } + notecontentchanged(); + }); + } + else if (event.key === "Tab") + { + event.preventDefault(); + // todo: reverse with shift + if (before(2) == "* " || before(2) == "- ") + { + setpos(getpos() - 2); + insert(" ", 2); + } + // disable tab + /*else + { + insert(" "); + }*/ + } +} + +function insertautocomplete(selectednote) +{ + md.focus(); + insert(selectednote + "]] "); + notecontentchanged(); +} + +function togglepreview() +{ + preview.innerHTML = md2html(getnotecontent()); + md.hidden = !md.hidden; + preview.hidden = !preview.hidden; + + if (preview.hidden) + { + resize(); + md.focus(); + } +} + +function resetfolds() +{ + folds = []; +} + +function bindfile(note) +{ + if (currentnote && currentnote.title == "settings.json") + { + loadsettings(); + } + + backup = note.content; + currentnote = note; + title.value = note.title; + document.title = note.title; + + setnotecontent(note.content || ""); + preview.innerHTML = md2html(getnotecontent()); + + resetfolds(); + resize(); + setpos(0); + + // to improve... + if (!issplit()) + { + md.focus(); + } +} + +function loadfile(name) +{ + var note = localdata.find(n => n.title == name); + if (!note) + { + note = {title: name, content: ""}; + localdata.unshift(note); + } + + if (!preview.hidden) + { + togglepreview(); + } + + bindfile(note); +} + +function sendpassword() +{ + if (event.type == "blur" || event.key == "Enter") + { + event.preventDefault(); + window.localStorage.setItem("password", password.value); + init(); + } +} + +function focuseditor() +{ + if (document.documentElement == event.srcElement) + { + md.focus(); + console.log("Forced focus"); + } +} \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..b581382 --- /dev/null +++ b/readme.md @@ -0,0 +1,38 @@ +# notes + +## getting started + +### local + +* download files +* launch index.html + +for a more app-feel on windows, try: + +``` +chrome_proxy.exe --app=index.html +``` + +### remote + +* put files on your php web server +* navigate to index.html +* edit settings (ctrl+shift+p, settings) +* change: remote=true +* reload page + +to protect your data by a password, edit handler.php and change `$password` variable. + +your password will be sent from browser to server through a POST http query with no more encryption than ssl, if enabled. it is stored unencrypted in your browser local storage. + +## usage + +* help: f1 +* notes list: ctrl+p +* command palette: ctrl+shift+p + +## reclaim you data + +your notes are stored in your browser local storage. download them in a single json file with ctrl+shift+s. + +write a moulinette to flatten them as md files. \ No newline at end of file diff --git a/split.css b/split.css new file mode 100644 index 0000000..83c7805 --- /dev/null +++ b/split.css @@ -0,0 +1,21 @@ +body { + margin: 0; +} + +div { + width: 100%; +} + +iframe { + width: 50%; + height: 100%; + border: none; +} + +#left { + float: left; +} + +#right { + float: right; +} \ No newline at end of file diff --git a/split.html b/split.html new file mode 100644 index 0000000..4b2e3be --- /dev/null +++ b/split.html @@ -0,0 +1,17 @@ + + + + + + + notes + + + +
+ + +
+ + + \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..ea6ab1a --- /dev/null +++ b/style.css @@ -0,0 +1,106 @@ +/* globals */ + +body { + margin-left: 5%; + margin-right: 5%; + font-size: 90%; + line-height: 130%; + caret-color: #5AA7CE; +} + +input { + outline: none; + border: none; + + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +/* scrollbars */ +:root +{ + scrollbar-width: thin; + scrollbar-color: RGBA(90, 167, 206, 0.5) lightgray; +} + +body::-webkit-scrollbar { + height: 5px; + width: 5px; + background-color: lightgray; +} + +body::-webkit-scrollbar-thumb { + background-color: RGBA(90, 167, 206, 0.5); + border-radius: 10px; +} + +/* note page and editor */ + +#notepage { + width: 100%; +} + +#md { + outline: none; + border: none; + width: 100%; + + resize: none; + + font-family: inherit; + font-size: inherit; + line-height: inherit; + background-color: inherit; +} + +/* top bar content */ + +#topbar { + height: 50px; + width: 100%; +} + +#topbar > span { + display: table-cell; +} + +#titlecontainer { + width: 100%; +} + +#title { + width: 100%; + padding-top: 10px; + font-size: 20px; + background-color: inherit; +} + +/* search file dialog */ + +.selected { + background-color: darkgray; +} + +#filter { + width: 100%; + + /* wm style + background-color: #f0f0f0; + */ +} + +#searchdialog { + position: absolute; + top: 0; + background-color: lightgray; + opacity: 1; + width: 90%; +} + +/* authent */ + +#bigtitle { + font-size: 50px; + line-height: 100%; +}