Add basic D1 support
This commit is contained in:
403
static/index.html
Normal file
403
static/index.html
Normal file
@@ -0,0 +1,403 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CryptoPad</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="lib/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="cryptopad.css" rel="stylesheet" />
|
||||
|
||||
<script type="text/javascript" src="lib/sjcl.js"></script>
|
||||
<script type="text/javascript" src="lib/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="lib/bootstrap.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
var key;
|
||||
var pads;
|
||||
var curSel = null;
|
||||
var backup;
|
||||
var backupWaiting;
|
||||
var curSelName = null;
|
||||
var useRemote = true;
|
||||
var waitingCount = 0;
|
||||
|
||||
function getKVSItem(key, func) {
|
||||
if (useRemote) {
|
||||
showLoader();
|
||||
return $.getJSON("storage/" + key, createGetKVSCallback(func));
|
||||
} else {
|
||||
return func(localStorage.getItem(key));
|
||||
}
|
||||
}
|
||||
|
||||
function createGetKVSCallback(func) {
|
||||
return function (arg) {
|
||||
hideOnCompletion();
|
||||
func(arg);
|
||||
}
|
||||
}
|
||||
|
||||
function showLoader() {
|
||||
if (waitingCount == 0)
|
||||
$("#loader").fadeIn();
|
||||
waitingCount++;
|
||||
}
|
||||
|
||||
function hideOnCompletion() {
|
||||
waitingCount--;
|
||||
if (waitingCount == 0)
|
||||
$("#loader").fadeOut();
|
||||
}
|
||||
|
||||
function deleteKVSItem(key) {
|
||||
if (useRemote) {
|
||||
showLoader();
|
||||
return $.getJSON("storage/delete/" + key, hideOnCompletion);
|
||||
} else {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
function setKVSItem(key, value) {
|
||||
if (useRemote) {
|
||||
showLoader();
|
||||
$.post("storage/" + key, {value: value}, hideOnCompletion);
|
||||
} else {
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function hashSomething(something) {
|
||||
var hmackey = sjcl.hash.sha256.hash(key);
|
||||
var hmacr = new sjcl.misc.hmac(hmackey);
|
||||
return sjcl.codec.hex.fromBits(hmacr.encrypt(something));
|
||||
}
|
||||
|
||||
function hashTitle(title) {
|
||||
return hashSomething("pad:"+title)
|
||||
}
|
||||
|
||||
function supportsStorage() {
|
||||
try {
|
||||
return 'localStorage' in window && window['localStorage'] !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function navbarHandler(e) {
|
||||
loadNote(e.data);
|
||||
}
|
||||
|
||||
function genPadItem(title) {
|
||||
var titlehash = hashTitle(title);
|
||||
var link = $('<a />').attr("href", "#").click(title, navbarHandler).text(title);
|
||||
var item = $('<li/>').attr("id", titlehash).append(link);
|
||||
return item;
|
||||
}
|
||||
|
||||
function addPadList(title) {
|
||||
$("#padList").append(genPadItem(title));
|
||||
}
|
||||
|
||||
function saveNote() {
|
||||
if ($("#title").val() == "") {
|
||||
$("#title").val(new Date().toString())
|
||||
}
|
||||
var title = $("#title").val();
|
||||
if (($.inArray(title, pads) != -1) && (title != curSelName)) {
|
||||
alert("An item with this title already exists! Cannot save until this is corrected.");
|
||||
return;
|
||||
}
|
||||
var titlehash = hashTitle(title);
|
||||
var crypted = sjcl.encrypt(key, $("#data").val(), {iter: 2000, ks: 256});
|
||||
setKVSItem(titlehash, crypted);
|
||||
if(curSel != null && curSelName != title) {
|
||||
pads[$.inArray(curSelName, pads)] = title;
|
||||
var newSel = genPadItem(title);
|
||||
curSel.replaceWith(newSel);
|
||||
curSel = newSel;
|
||||
curSel.addClass("active");
|
||||
curSelName = title;
|
||||
}
|
||||
if ($.inArray(title, pads) == -1) {
|
||||
pads.push(title);
|
||||
addPadList(title);
|
||||
setActiveTab(titlehash);
|
||||
curSelName = title;
|
||||
}
|
||||
savePads();
|
||||
$("#searchtext").attr("data-source", JSON.stringify(pads));
|
||||
}
|
||||
|
||||
function savePads() {
|
||||
var crypted = sjcl.encrypt(key, JSON.stringify(pads), {iter: 2000, ks: 256});
|
||||
setKVSItem(hashSomething("pads"), crypted);
|
||||
}
|
||||
|
||||
function setActiveTab(titlehash) {
|
||||
if (curSel != null)
|
||||
curSel.removeClass("active");
|
||||
$("#" + titlehash).addClass("active");
|
||||
curSel = $("#" + titlehash);
|
||||
}
|
||||
|
||||
function createNoteCallback(title, titlehash) {
|
||||
return function(notedata) {
|
||||
if (notedata != null) {
|
||||
try {
|
||||
$("#data").val(sjcl.decrypt(key, notedata));
|
||||
$("#title").val(title);
|
||||
$("#searchtext").val("");
|
||||
} catch (err) {
|
||||
alert("Cryptography error: it's likely you entered the wrong passphrase. If you update, it WILL be overwritten.")
|
||||
}
|
||||
setActiveTab(titlehash);
|
||||
curSelName = title;
|
||||
} else {
|
||||
alert("Pad does not exist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadNote(title) {
|
||||
var titlehash = hashTitle(title);
|
||||
getKVSItem(titlehash, createNoteCallback(title, titlehash));
|
||||
}
|
||||
|
||||
function searchNote() {
|
||||
var title = $("#searchtext").val();
|
||||
loadNote(title);
|
||||
return false;
|
||||
}
|
||||
|
||||
function padsLoaded(paddata) {
|
||||
if (paddata == null)
|
||||
pads = new Array();
|
||||
else {
|
||||
try {
|
||||
pads = JSON.parse(sjcl.decrypt(key, paddata));
|
||||
} catch (err) {
|
||||
alert("Cryptography error: it's likely you entered the wrong passphrase. If you update, it WILL be overwritten.")
|
||||
}
|
||||
}
|
||||
|
||||
for (var pad in pads) {
|
||||
if (pads[pad] != null) {
|
||||
addPadList(pads[pad]);
|
||||
}
|
||||
}
|
||||
$("#searchtext").attr("data-source", JSON.stringify(pads));
|
||||
}
|
||||
|
||||
function login() {
|
||||
key = $("#password").val();
|
||||
getKVSItem(hashSomething("pads"),padsLoaded);
|
||||
$('#myModal').modal('hide')
|
||||
return false; // for form.
|
||||
}
|
||||
|
||||
|
||||
function restoreKVS() {
|
||||
var data = prompt("Please enter backup blob (any already existing pads will be overwritten!)");
|
||||
var d = JSON.parse(data)
|
||||
for (var k in d) {
|
||||
|
||||
setKVSItem(hashTitle(k), sjcl.encrypt(key, d[k], {iter: 2000, ks: 256}));
|
||||
if ($.inArray(k, pads) == -1) {
|
||||
pads.push(k);
|
||||
addPadList(k);
|
||||
}
|
||||
}
|
||||
savePads();
|
||||
}
|
||||
|
||||
|
||||
function backupKVS(pad) {
|
||||
getKVSItem(hashTitle(pads[pad]), function(data) {
|
||||
backup[pads[pad]] = sjcl.decrypt(key, data);
|
||||
backupWaiting--;
|
||||
doShowBackupDlg();
|
||||
})
|
||||
}
|
||||
|
||||
function doShowBackupDlg() {
|
||||
if (backupWaiting == 0) {
|
||||
$("#backupDlg").modal();
|
||||
$("#backupText").val(JSON.stringify(backup))
|
||||
}
|
||||
}
|
||||
|
||||
function doBackup() {
|
||||
backup = new Object();
|
||||
backupWaiting = pads.length;
|
||||
for (var pad = 0; pad < pads.length; pad++) {
|
||||
if ((pads[pad] == null) || (typeof pads[pad] === 'undefined')) {
|
||||
backupWaiting--;
|
||||
doShowBackupDlg();
|
||||
} else {
|
||||
backupKVS(pad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteNote() {
|
||||
var index = $.inArray($("#title").val(), pads);
|
||||
if ((index != -1) && (curSel != null)) {
|
||||
curSel.remove();
|
||||
curSel = null;
|
||||
deleteKVSItem(hashTitle(pads[index]));
|
||||
delete pads[index];
|
||||
savePads();
|
||||
$("#title").val("");
|
||||
$("#data").val("");
|
||||
}
|
||||
}
|
||||
|
||||
function newNote() {
|
||||
if (curSel != null)
|
||||
curSel.removeClass("active");
|
||||
$("#title").val("");
|
||||
$("#data").val("");
|
||||
curSel = null;
|
||||
curSelName = null;
|
||||
}
|
||||
|
||||
function init() {
|
||||
sjcl.random.startCollectors();
|
||||
|
||||
$("#data").keydown(function(e) {
|
||||
if(e.keyCode === 9) { // from http://stackoverflow.com/questions/6140632/how-to-handle-tab-in-textarea
|
||||
var start = this.selectionStart;
|
||||
var end = this.selectionEnd;
|
||||
|
||||
var $this = $(this);
|
||||
$this.val($this.val().substring(0, start) + "\t" + $this.val().substring(end));
|
||||
this.selectionStart = this.selectionEnd = start + 1;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!supportsStorage()) {
|
||||
alert("Your browser does not have Local Storage support. Come back when you've upgraded.");
|
||||
}
|
||||
|
||||
// for web browsers refilling this on refresh.
|
||||
$("#title").val("");
|
||||
$("#data").val("");
|
||||
|
||||
$('.tabs').button()
|
||||
$("#remoteButton").button('toggle');
|
||||
|
||||
|
||||
$("#remoteButton").click(function() {
|
||||
useRemote = true;
|
||||
});
|
||||
$("#localButton").click(function() {
|
||||
useRemote = false;
|
||||
});
|
||||
|
||||
$("#myModal").modal({backdrop: "static", keyboard: false}); // undocumented trick to not hide the dialog
|
||||
|
||||
var crypt_key=window.location.hash.substring(1);
|
||||
|
||||
if (crypt_key != "") {
|
||||
console.log("poopfuck")
|
||||
$("#password").val(crypt_key)
|
||||
login()
|
||||
}
|
||||
|
||||
|
||||
// set up buttons
|
||||
$("#backupCloseBtn").click(function () {
|
||||
$("#backupDlg").modal("hide")
|
||||
})
|
||||
$("#backuper").click(doBackup);
|
||||
$("#restorer").click(restoreKVS);
|
||||
$("#submitter").click(saveNote);
|
||||
$("#deleter").click(deleteNote);
|
||||
$("#newer").click(newNote);
|
||||
$("#searchform").submit(searchNote);
|
||||
$("#loginForm").submit(login);
|
||||
$("#loginBtn").click(login);
|
||||
// place cursor in password field for quick access.
|
||||
$("#password").select();
|
||||
}
|
||||
|
||||
$(document).ready(init);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="modal hide" id="backupDlg">
|
||||
<div class="modal-header">
|
||||
<h3>Backup</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<textarea rows="10" id="backupText">
|
||||
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="backupCloseBtn" class="btn btn-primary">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal" id="myModal">
|
||||
<div class="modal-header">
|
||||
<h3>Login to CryptoPad</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Login to:
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<button class="btn" id="localButton">Local</button>
|
||||
<button class="btn" id="remoteButton">Server</button>
|
||||
</div> <br />
|
||||
<form id="loginForm">
|
||||
<input name="password" type="password" id="password" placeholder="Password" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="loginBtn" class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<a class="brand" href="#">CryptoPad</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid main">
|
||||
<div class="main-inner row-fluid">
|
||||
<div class="span2">
|
||||
<form id="searchform" class="form-search">
|
||||
<input autocomplete="off" id="searchtext" type="text" class="input-medium search-query" placeholder="Search" data-provide="typeahead" />
|
||||
</form>
|
||||
<ul id="padList" class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
<button id="backuper" class="btn btn-inverse">Backup</button>
|
||||
<button id="restorer" class="btn btn-inverse">Restore</button>
|
||||
</div>
|
||||
<div class="span10">
|
||||
<div class="well">
|
||||
<input type="text" id="title" class="input-medium" placeholder="Title" />
|
||||
<div class="textarea-wrapper"><textarea id="data" rows="10"></textarea></div>
|
||||
<button id="submitter" class="btn btn-primary">Save</button>
|
||||
<button id="newer" class="btn btn-success">New</button>
|
||||
<span id="loader" class="middle"><img alt="Working..." class="middle" src="loading.gif"></span>
|
||||
<button id="deleter" class="btn btn-danger">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer id="footer">
|
||||
Powered by CryptoPad - AGPL - <a href="https://gitorious.org/cryptopad">Source</a><br />
|
||||
Secured using <a href="http://crypto.stanford.edu/sjcl/">SJCL</a> with AES-256 in CCM mode and PBKDF2 using SHA256 with 2000 iterations
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user