Compare commits
No commits in common. "77489ca4ab16a3080634eae3f8d6bf69def0edae" and "852edd3600c5ee5c7087b627f707db2e881040ba" have entirely different histories.
77489ca4ab
...
852edd3600
|
@ -0,0 +1,174 @@
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import urllib.parse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
def filename(note):
|
||||||
|
return re.sub(r'[\?\"<>|\*:\/\\]', '_', note["title"]) + ".md"
|
||||||
|
|
||||||
|
def listnotes(filter = "", checkcontent = False):
|
||||||
|
matching = []
|
||||||
|
for i in reversed(range(len(data))):
|
||||||
|
|
||||||
|
if filter.lower() in data[i]["title"].lower():
|
||||||
|
print("[" + str(i) + "]", data[i]["title"])
|
||||||
|
matching.append(data[i])
|
||||||
|
|
||||||
|
elif checkcontent and filter.lower() in data[i]["content"].lower():
|
||||||
|
print("[" + str(i) + "]", data[i]["title"])
|
||||||
|
lines = data[i]["content"].split("\n")
|
||||||
|
for j in range(len(lines)):
|
||||||
|
line = lines[j]
|
||||||
|
if filter.lower() in line.lower():
|
||||||
|
index = line.lower().index(filter.lower())
|
||||||
|
print("\t" + str(j) + ":", line[:100])
|
||||||
|
matching.append(data[i])
|
||||||
|
|
||||||
|
return matching
|
||||||
|
|
||||||
|
def readtextfile(path):
|
||||||
|
with io.open(path, mode = "r", encoding = "utf-8") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def writetextfile(path, content):
|
||||||
|
with io.open(path, mode = "w", encoding = "utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
def editnote(note):
|
||||||
|
content = note["content"]
|
||||||
|
|
||||||
|
backupfilepath = "session/" + filename(note) + str(time.time())
|
||||||
|
writetextfile(backupfilepath, content)
|
||||||
|
writetextfile("session/" + filename(note), content)
|
||||||
|
|
||||||
|
subprocess.call(settings["commands"]["editor"] + ["session/" + filename(note)])
|
||||||
|
newcontent = readtextfile("session/" + filename(note) )
|
||||||
|
|
||||||
|
if newcontent != content:
|
||||||
|
subprocess.call(settings["commands"]["diff"] + [backupfilepath, "session/" + filename(note)])
|
||||||
|
note["content"] = newcontent
|
||||||
|
data.remove(note)
|
||||||
|
data.insert(0, note)
|
||||||
|
savedata()
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("no change")
|
||||||
|
|
||||||
|
def savedata():
|
||||||
|
if settings["mode"] == "remote":
|
||||||
|
writetextfile("session/data.json", json.dumps(data))
|
||||||
|
subprocess.call([settings["commands"]["gpg"], "-q", "--encrypt", "--yes", "--trust-model", "always", "--output", "session/data.acs", "--armor", "-r", settings["gpguser"], "session/data.json"]);
|
||||||
|
newdata = readtextfile("session/data.acs")
|
||||||
|
postdata = "action=push&password=" + settings["password"] + "&data=" + urllib.parse.quote_plus(newdata)
|
||||||
|
writetextfile("session/postdata", postdata)
|
||||||
|
output = subprocess.check_output(["curl", "-X", "POST", "-d", "@session/postdata", settings["url"] + "/handler.php"]).decode("utf-8")
|
||||||
|
print("curl output: " + output)
|
||||||
|
if output != '{"result": "ok"}':
|
||||||
|
if ask("Save failed. Try again?"):
|
||||||
|
savedata()
|
||||||
|
else:
|
||||||
|
writetextfile("session/local.json", json.dumps(data))
|
||||||
|
|
||||||
|
def loaddata():
|
||||||
|
if settings["mode"] == "remote":
|
||||||
|
|
||||||
|
subprocess.call(["curl", "-X", "POST", "-F", "action=fetch", "-F", "password=" + settings["password"], "-o", "session/data.acs", settings["url"] + "/handler.php"])
|
||||||
|
subprocess.call([settings["commands"]["gpg"], "-q", "--yes", "--output", "session/data.json", "--decrypt", "session/data.acs"])
|
||||||
|
|
||||||
|
return json.loads(readtextfile("session/data.json"))
|
||||||
|
|
||||||
|
else:
|
||||||
|
return json.loads(readtextfile("session/local.json"))
|
||||||
|
|
||||||
|
def ask(question):
|
||||||
|
answer = input(question + " [Y/n] ")
|
||||||
|
return answer == "y" or answer == "yes" or answer == ""
|
||||||
|
|
||||||
|
def initdatapath():
|
||||||
|
if not os.path.exists("history"):
|
||||||
|
os.mkdir("history")
|
||||||
|
if os.path.exists("session"):
|
||||||
|
if os.path.exists("session/postdata"):
|
||||||
|
os.remove("session/postdata")
|
||||||
|
shutil.make_archive("history/session" + str(time.time()), "zip", "session")
|
||||||
|
shutil.rmtree("session")
|
||||||
|
os.mkdir("session")
|
||||||
|
|
||||||
|
abspath = os.path.abspath(__file__)
|
||||||
|
dname = os.path.dirname(abspath)
|
||||||
|
os.chdir(dname)
|
||||||
|
|
||||||
|
initdatapath()
|
||||||
|
settings = json.loads(readtextfile("settings.json"))
|
||||||
|
data = loaddata()
|
||||||
|
|
||||||
|
command = ""
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
command = sys.argv[1]
|
||||||
|
if command.startswith("notes://"):
|
||||||
|
command = urllib.parse.unquote(command[8:-1])
|
||||||
|
|
||||||
|
while not (command == "quit" or command == "exit" or command == "q"):
|
||||||
|
|
||||||
|
action = None
|
||||||
|
if command[0:3] == "rm ":
|
||||||
|
action = "delete"
|
||||||
|
command = command[3:]
|
||||||
|
elif command[0:3] == "mv ":
|
||||||
|
action = "rename"
|
||||||
|
command = command[3:]
|
||||||
|
elif command[0:1] == "/":
|
||||||
|
action = "grep"
|
||||||
|
command = command[1:]
|
||||||
|
elif command[0:7] == "export ":
|
||||||
|
action = "export"
|
||||||
|
command = command[7:]
|
||||||
|
elif command == "settings":
|
||||||
|
action = "settings"
|
||||||
|
|
||||||
|
try:
|
||||||
|
index = int(command)
|
||||||
|
note = data[index]
|
||||||
|
except:
|
||||||
|
note = next((note for note in data if note["title"] == command), None)
|
||||||
|
|
||||||
|
if action == "delete":
|
||||||
|
if note and ask("delete '" + note["title"] + "'? "):
|
||||||
|
data.remove(note)
|
||||||
|
savedata()
|
||||||
|
elif action == "rename":
|
||||||
|
if note:
|
||||||
|
newname = input("new name: ")
|
||||||
|
if newname:
|
||||||
|
note["title"] = newname
|
||||||
|
savedata()
|
||||||
|
elif action == "export":
|
||||||
|
if note:
|
||||||
|
writetextfile("session/" + note["title"] + ".md", note["content"])
|
||||||
|
elif action == "settings":
|
||||||
|
subprocess.call(settings["commands"]["editor"] + ["settings.json"])
|
||||||
|
settings = json.loads(readtextfile("settings.json"))
|
||||||
|
elif note and not action == "grep":
|
||||||
|
editnote(note)
|
||||||
|
else:
|
||||||
|
matching = listnotes(command, action == "grep")
|
||||||
|
if len(matching) == 0 and not action == "grep":
|
||||||
|
if ask("create '" + command + "'? "):
|
||||||
|
note = {
|
||||||
|
"title": command,
|
||||||
|
"content": "---\ntitle: " + command + "\ndate: " + time.strftime("%Y-%m-%d") + "\ntags: \n---\n\n"
|
||||||
|
}
|
||||||
|
data.insert(0, note)
|
||||||
|
editnote(note)
|
||||||
|
elif len(matching) == 1:
|
||||||
|
note = matching.pop()
|
||||||
|
if ask("open '" + note["title"] + "'?"):
|
||||||
|
editnote(note)
|
||||||
|
|
||||||
|
command = input("> ")
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000",
|
||||||
|
"password": "",
|
||||||
|
"gpguser": "",
|
||||||
|
"commands":
|
||||||
|
{
|
||||||
|
"editor": ["vim"],
|
||||||
|
"gpg": "gpg",
|
||||||
|
"diff": ["diff", "--color"]
|
||||||
|
},
|
||||||
|
"mode": "remote"
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require 'settings.php';
|
||||||
|
|
||||||
|
// check authent
|
||||||
|
if ($password && (!isset($_POST['password']) || $_POST['password'] != $password))
|
||||||
|
{
|
||||||
|
echo 'error: authent';
|
||||||
|
}
|
||||||
|
else if (isset($_POST['action']))
|
||||||
|
{
|
||||||
|
$action = $_POST['action'];
|
||||||
|
$path = $datadir . $_POST['name'];
|
||||||
|
switch ($action)
|
||||||
|
{
|
||||||
|
case 'fetch':
|
||||||
|
if (file_exists($path))
|
||||||
|
{
|
||||||
|
echo file_get_contents($path);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'push':
|
||||||
|
$result = file_put_contents($path, $_POST['data']);
|
||||||
|
if ($result === false)
|
||||||
|
{
|
||||||
|
echo 'error: could not save ' . $_POST['name'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
echo 'error: unknown action ' . $action;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
echo 'error: missing action parameter';
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -13,6 +13,7 @@
|
||||||
<body onload="init()" onkeydown="mainkeydownhandler()" onresize="resize()" onstorage="loadstorage()">
|
<body onload="init()" onkeydown="mainkeydownhandler()" onresize="resize()" onstorage="loadstorage()">
|
||||||
<script src="libs/showdown.min.js"></script>
|
<script src="libs/showdown.min.js"></script>
|
||||||
<script src="libs/vis-network.min.js"></script>
|
<script src="libs/vis-network.min.js"></script>
|
||||||
|
<script src="libs/openpgp.min.js"></script>
|
||||||
<script src="libs/jszip.min.js"></script>
|
<script src="libs/jszip.min.js"></script>
|
||||||
<script src="libs/FileSaver.js"></script>
|
<script src="libs/FileSaver.js"></script>
|
||||||
<script src="main.js"></script>
|
<script src="main.js"></script>
|
||||||
|
|
File diff suppressed because one or more lines are too long
307
main.js
307
main.js
|
@ -11,6 +11,8 @@ var defaultsettings =
|
||||||
titlebydefault: false,
|
titlebydefault: false,
|
||||||
linksinnewtab: true,
|
linksinnewtab: true,
|
||||||
colors: true,
|
colors: true,
|
||||||
|
password: "",
|
||||||
|
sync: false,
|
||||||
tagsinlists: true,
|
tagsinlists: true,
|
||||||
uselinkpopup: true,
|
uselinkpopup: true,
|
||||||
downloadextension: ".md"
|
downloadextension: ".md"
|
||||||
|
@ -29,6 +31,7 @@ var workerid = null;
|
||||||
var backup = "";
|
var backup = "";
|
||||||
var settings = null;
|
var settings = null;
|
||||||
var tags = null;
|
var tags = null;
|
||||||
|
var pending = {};
|
||||||
|
|
||||||
var commands = [
|
var commands = [
|
||||||
{
|
{
|
||||||
|
@ -192,6 +195,14 @@ var commands = [
|
||||||
hint: "Insert text in todo",
|
hint: "Insert text in todo",
|
||||||
action: promptinserttodo
|
action: promptinserttodo
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
hint: "Edit pgp keys",
|
||||||
|
action: editpgpkeys
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hint: "Decrypt note",
|
||||||
|
action: decryptnote
|
||||||
|
},
|
||||||
{
|
{
|
||||||
hint: "Replace",
|
hint: "Replace",
|
||||||
shortcut: "ctrl+h",
|
shortcut: "ctrl+h",
|
||||||
|
@ -303,6 +314,56 @@ function seteditorcontent(content)
|
||||||
resize();
|
resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encryptstring(str)
|
||||||
|
{
|
||||||
|
var key = localStorage.getItem("pgpkeys").split("-----BEGIN PGP PRIVATE KEY BLOCK-----")[0];
|
||||||
|
var publicKey = null;
|
||||||
|
return openpgp.readKey({ armoredKey: key })
|
||||||
|
.then(res =>
|
||||||
|
{
|
||||||
|
publicKey = res;
|
||||||
|
return openpgp.createMessage({ text: str });
|
||||||
|
})
|
||||||
|
.then(message =>
|
||||||
|
{
|
||||||
|
return openpgp.encrypt({
|
||||||
|
message: message,
|
||||||
|
encryptionKeys: publicKey });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptstring(str)
|
||||||
|
{
|
||||||
|
if (!str.startsWith("-----BEGIN PGP MESSAGE-----"))
|
||||||
|
{
|
||||||
|
// console.log(str + ": string is not encrypted");
|
||||||
|
return Promise.resolve(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = localStorage.getItem("pgpkeys").split("-----END PGP PUBLIC KEY BLOCK-----")[1];
|
||||||
|
var privateKey = null;
|
||||||
|
return openpgp.readKey({ armoredKey: key })
|
||||||
|
.then(res =>
|
||||||
|
{
|
||||||
|
privateKey = res;
|
||||||
|
return openpgp.readMessage({ armoredMessage: str })
|
||||||
|
})
|
||||||
|
.then(message =>
|
||||||
|
{
|
||||||
|
return openpgp.decrypt({
|
||||||
|
message: message,
|
||||||
|
decryptionKeys: privateKey });
|
||||||
|
})
|
||||||
|
.then(decrypted =>
|
||||||
|
{
|
||||||
|
const chunks = [];
|
||||||
|
for (const chunk of decrypted.data) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
}
|
||||||
|
return chunks.join('');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getrangecontent(range)
|
function getrangecontent(range)
|
||||||
{
|
{
|
||||||
return md.value.substring(range.start, range.end);
|
return md.value.substring(range.start, range.end);
|
||||||
|
@ -316,12 +377,44 @@ function currentrange()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pushitem(key, value)
|
||||||
|
{
|
||||||
|
unsavedmark.hidden = false;
|
||||||
|
pending[key] = true;
|
||||||
|
var name = metadata && metadata[key] ? metadata[key].title : key;
|
||||||
|
|
||||||
|
queryremote({action: "push", name: key, data: value})
|
||||||
|
.then( () =>
|
||||||
|
{
|
||||||
|
console.log("'" + name + "' pushed to server");
|
||||||
|
delete pending[key];
|
||||||
|
})
|
||||||
|
.catch( err =>
|
||||||
|
{
|
||||||
|
console.error("error while pushing '" + name + "': " + err);
|
||||||
|
showtemporaryinfo(msg);
|
||||||
|
setTimeout( () =>
|
||||||
|
{
|
||||||
|
serialize(key, value);
|
||||||
|
}, settings.savedelay);
|
||||||
|
})
|
||||||
|
.finally( () =>
|
||||||
|
{
|
||||||
|
unsavedmark.hidden = Object.keys(pending) == 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function serialize(key, value)
|
function serialize(key, value)
|
||||||
{
|
{
|
||||||
localStorage.setItem(key, value);
|
localStorage.setItem(key, value);
|
||||||
|
|
||||||
var name = metadata && metadata[key] ? metadata[key].title : key;
|
var name = metadata && metadata[key] ? metadata[key].title : key;
|
||||||
console.log("'" + name + "' serialized locally");
|
console.log("'" + name + "' serialized locally");
|
||||||
|
|
||||||
|
if (settings.sync)
|
||||||
|
{
|
||||||
|
pushitem(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createsubnote()
|
function createsubnote()
|
||||||
|
@ -417,6 +510,8 @@ function showinfo()
|
||||||
var tags = gettags(md.value);
|
var tags = gettags(md.value);
|
||||||
showtemporaryinfo(
|
showtemporaryinfo(
|
||||||
[
|
[
|
||||||
|
"sync: " + (settings.sync ? "en" : "dis") + "abled",
|
||||||
|
"pending: " + Object.keys(pending).join(),
|
||||||
"title: " + title.value,
|
"title: " + title.value,
|
||||||
"line count: " + md.value.split("\n").length,
|
"line count: " + md.value.split("\n").length,
|
||||||
"word count: " + getwords(),
|
"word count: " + getwords(),
|
||||||
|
@ -808,6 +903,21 @@ function changesetting()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decryptnote()
|
||||||
|
{
|
||||||
|
decryptstring(md.value)
|
||||||
|
.then(decrypted =>
|
||||||
|
{
|
||||||
|
seteditorcontent(decrypted);
|
||||||
|
resize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function editpgpkeys()
|
||||||
|
{
|
||||||
|
bind("pgpkeys", localStorage.getItem("pgpkeys") || "");
|
||||||
|
}
|
||||||
|
|
||||||
function showtemporaryinfo(info)
|
function showtemporaryinfo(info)
|
||||||
{
|
{
|
||||||
alert(info);
|
alert(info);
|
||||||
|
@ -1228,6 +1338,102 @@ function migratelegacystorage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pushall()
|
||||||
|
{
|
||||||
|
var index = JSON.parse(localStorage.getItem("index"));
|
||||||
|
var list = [queryremote({action: "push", name: "index", data: localStorage.getItem("index")})];
|
||||||
|
Object.keys(index).forEach(guid =>
|
||||||
|
{
|
||||||
|
list.push(queryremote({action: "push", name: guid, data: localStorage.getItem(guid)}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchandserialize(guid)
|
||||||
|
{
|
||||||
|
var name = (metadata && metadata[guid]) ? metadata[guid] : guid;
|
||||||
|
console.log("fetching '" + name + "'");
|
||||||
|
return queryremote({action: "fetch", name: guid})
|
||||||
|
.then(content =>
|
||||||
|
{
|
||||||
|
localStorage.setItem(guid, content);
|
||||||
|
console.log("'" + name + "' fetched and serialized");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch()
|
||||||
|
{
|
||||||
|
return new Promise(function(resolve)
|
||||||
|
{
|
||||||
|
if (settings.sync)
|
||||||
|
{
|
||||||
|
var pgpkeys = localStorage.getItem("pgpkeys");
|
||||||
|
if (!pgpkeys)
|
||||||
|
{
|
||||||
|
loadstorage();
|
||||||
|
editpgpkeys();
|
||||||
|
showtemporaryinfo("Pgp key empty or invalid. Enter PGP keys and refresh.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
queryremote({action: "fetch", name: "index"})
|
||||||
|
.then(filecontent =>
|
||||||
|
{
|
||||||
|
if (!filecontent)
|
||||||
|
{
|
||||||
|
if (confirm("No remote index found. Init remote now?"))
|
||||||
|
{
|
||||||
|
pushall().then(resolve);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var localindex = JSON.parse(localStorage.getItem("index"));
|
||||||
|
var remoteindex = JSON.parse(filecontent);
|
||||||
|
|
||||||
|
var list = [];
|
||||||
|
Object.keys(remoteindex).forEach(guid =>
|
||||||
|
{
|
||||||
|
if (!localindex[guid] || localindex[guid].lastchanged < remoteindex[guid].lastchanged)
|
||||||
|
{
|
||||||
|
list.push(fetchandserialize(guid));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(list).then( () =>
|
||||||
|
{
|
||||||
|
localStorage.setItem("index", JSON.stringify(remoteindex));
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err =>
|
||||||
|
{
|
||||||
|
if (err == "error: authent")
|
||||||
|
{
|
||||||
|
settings.password = prompt("Password: ", settings.password);
|
||||||
|
savesettings();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
showtemporaryinfo(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function init()
|
function init()
|
||||||
{
|
{
|
||||||
migratelegacystorage();
|
migratelegacystorage();
|
||||||
|
@ -1239,19 +1445,94 @@ function init()
|
||||||
|
|
||||||
initsnippets();
|
initsnippets();
|
||||||
|
|
||||||
loadstorage();
|
fetch()
|
||||||
|
.then( () =>
|
||||||
if (issplit())
|
|
||||||
{
|
{
|
||||||
if (settings.defaultpreviewinsplit && name == "right")
|
loadstorage();
|
||||||
|
|
||||||
|
if (issplit())
|
||||||
{
|
{
|
||||||
togglepreview();
|
if (settings.defaultpreviewinsplit && name == "right")
|
||||||
|
{
|
||||||
|
togglepreview();
|
||||||
|
}
|
||||||
|
else if (name == "left")
|
||||||
|
{
|
||||||
|
md.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (name == "left")
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function encryptdata(params)
|
||||||
|
{
|
||||||
|
if (params.data)
|
||||||
|
{
|
||||||
|
return encryptstring(params.data)
|
||||||
|
.then(encrypted =>
|
||||||
{
|
{
|
||||||
md.focus();
|
params.data = encrypted;
|
||||||
}
|
return Promise.resolve(params);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Promise.resolve(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryremote(params)
|
||||||
|
{
|
||||||
|
return encryptdata(params)
|
||||||
|
.then(encparams =>
|
||||||
|
{
|
||||||
|
return new Promise ( (resolve, reject) =>
|
||||||
|
{
|
||||||
|
encparams.password = settings.password;
|
||||||
|
|
||||||
|
var paramlist = [];
|
||||||
|
for (var i in encparams)
|
||||||
|
{
|
||||||
|
paramlist.push(i + "=" + encodeURIComponent(encparams[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("POST", "handler.php");
|
||||||
|
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
xhr.onerror = function()
|
||||||
|
{
|
||||||
|
reject("XMLHttpRequest error");
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onload = function()
|
||||||
|
{
|
||||||
|
if (xhr.status !== 200)
|
||||||
|
{
|
||||||
|
reject("Http status " + xhr.status);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
decryptstring(xhr.responseText)
|
||||||
|
.then(decrypted =>
|
||||||
|
{
|
||||||
|
if (decrypted.startsWith("error: "))
|
||||||
|
{
|
||||||
|
reject(decrypted);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolve(decrypted);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var paramstring = paramlist.join("&");
|
||||||
|
console.log("http '" + encparams.action + "' request length: " + formatsize(paramstring.length));
|
||||||
|
xhr.send(paramstring);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getlinesrange()
|
function getlinesrange()
|
||||||
|
@ -1460,7 +1741,7 @@ function commandpalette()
|
||||||
return {
|
return {
|
||||||
prefix: "setting ",
|
prefix: "setting ",
|
||||||
text: s,
|
text: s,
|
||||||
suffix: [settings[s]]
|
suffix: s == "password" ? null : [settings[s]]
|
||||||
};
|
};
|
||||||
})))
|
})))
|
||||||
.then(selected =>
|
.then(selected =>
|
||||||
|
@ -1654,7 +1935,12 @@ function flush()
|
||||||
savesettings();
|
savesettings();
|
||||||
loadsettings();
|
loadsettings();
|
||||||
}
|
}
|
||||||
unsavedmark.hidden = true;
|
else if (title.value == "pgpkeys")
|
||||||
|
{
|
||||||
|
localStorage.setItem("pgpkeys", md.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsavedmark.hidden = Object.keys(pending).length == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(unsafe) {
|
function escapeHtml(unsafe) {
|
||||||
|
@ -1957,6 +2243,7 @@ function showhelp()
|
||||||
help.push("## Libs");
|
help.push("## Libs");
|
||||||
help.push("[Showdown](https://showdownjs.com/)");
|
help.push("[Showdown](https://showdownjs.com/)");
|
||||||
help.push("[vis-network](https://visjs.org/)");
|
help.push("[vis-network](https://visjs.org/)");
|
||||||
|
help.push("[openpgpjs](https://openpgpjs.org/)");
|
||||||
help.push("[jszip](https://stuk.github.io/jszip/)");
|
help.push("[jszip](https://stuk.github.io/jszip/)");
|
||||||
help.push("[FileSaver](http://eligrey.com)");
|
help.push("[FileSaver](http://eligrey.com)");
|
||||||
|
|
||||||
|
|
28
readme.md
28
readme.md
|
@ -2,21 +2,41 @@
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
Call index.html or try https://notes.ouvaton.org.
|
Launch index.html from your web server or try https://notes.ouvaton.org.
|
||||||
|
|
||||||
Your notes are stored in your browser local storage.
|
Your notes are stored in your browser local storage.
|
||||||
|
|
||||||
You can import a folder of markdown files.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
* command palette: ctrl+shift+p
|
* command palette: ctrl+shift+p
|
||||||
* notes list: ctrl+p
|
* notes list: ctrl+p
|
||||||
|
|
||||||
|
## Sync feature
|
||||||
|
|
||||||
|
To sync your notes in the cloud:
|
||||||
|
|
||||||
|
* put the source files on your php server
|
||||||
|
* browse index.html
|
||||||
|
* paste your public and private PGP keys as a single file (passphrase is not supported)
|
||||||
|
* refresh the page
|
||||||
|
|
||||||
|
Your data is always encrypted before reaching the server.
|
||||||
|
|
||||||
|
To protect your data file access by a password, edit settings.php and change `$password` variable. The password is sent from browser to server through a post http query, encrypted with ssl if enabled. It is stored unencrypted in your browser local storage and in the settings.php file on server side.
|
||||||
|
|
||||||
|
## Cli tool
|
||||||
|
|
||||||
|
```
|
||||||
|
cd cli
|
||||||
|
python3 app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires python3, curl, gnupg and a text editor.
|
||||||
|
|
||||||
## Web clipper
|
## Web clipper
|
||||||
|
|
||||||
Add `clipper\clipper.js` as bookmarklet on your web browser bookmark toolbar. Click to add current URL to a note named *todo*.
|
Add `clipper\clipper.js` as bookmarklet on your web browser bookmark toolbar. Click to add current URL to a note named *todo*.
|
||||||
|
|
||||||
## Export your data
|
## Export your data
|
||||||
|
|
||||||
You can download your notes as flat markdown files in a zip archive.
|
You can download your notes in a single json data file, or as flat markdown files in a zip archive.
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
$datadir = '../data/';
|
||||||
|
$password = '';
|
||||||
|
?>
|
Loading…
Reference in New Issue