diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..9f60b52 Binary files /dev/null and b/favicon.ico differ diff --git a/handler.php b/handler.php new file mode 100644 index 0000000..a0e4285 --- /dev/null +++ b/handler.php @@ -0,0 +1,50 @@ + 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%; +}