Merge branch 'soon' of github.com:xwiki-labs/cryptpad into soon
This commit is contained in:
@@ -4,7 +4,8 @@ define([
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'/common/cryptpad-common.js',
|
||||
], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad) {
|
||||
'/common/test.js'
|
||||
], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Test) {
|
||||
window.Hyperjson = Hyperjson;
|
||||
window.TextPatcher = TextPatcher;
|
||||
window.Sortify = Sortify;
|
||||
@@ -15,26 +16,41 @@ define([
|
||||
var failMessages = [];
|
||||
|
||||
var ASSERTS = [];
|
||||
var runASSERTS = function () {
|
||||
var runASSERTS = function (cb) {
|
||||
var count = ASSERTS.length;
|
||||
var successes = 0;
|
||||
|
||||
var done = function (err) {
|
||||
count--;
|
||||
if (err) { failMessages.push(err); }
|
||||
else { successes++; }
|
||||
if (count === 0) { cb(); }
|
||||
};
|
||||
|
||||
ASSERTS.forEach(function (f, index) {
|
||||
f(index);
|
||||
f(function (err) {
|
||||
console.log("test " + index);
|
||||
done(err, index);
|
||||
}, index);
|
||||
});
|
||||
};
|
||||
|
||||
var assert = function (test, msg) {
|
||||
ASSERTS.push(function (i) {
|
||||
var returned = test();
|
||||
if (returned === true) {
|
||||
assertions++;
|
||||
} else {
|
||||
failed = true;
|
||||
failedOn = assertions;
|
||||
failMessages.push({
|
||||
test: i,
|
||||
message: msg,
|
||||
output: returned,
|
||||
});
|
||||
}
|
||||
ASSERTS.push(function (cb, i) {
|
||||
test(function (result) {
|
||||
if (result === true) {
|
||||
assertions++;
|
||||
cb();
|
||||
} else {
|
||||
failed = true;
|
||||
failedOn = assertions;
|
||||
cb({
|
||||
test: i,
|
||||
message: msg,
|
||||
output: result,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,7 +74,7 @@ define([
|
||||
};
|
||||
|
||||
var HJSON_equal = function (shjson) {
|
||||
assert(function () {
|
||||
assert(function (cb) {
|
||||
// parse your stringified Hyperjson
|
||||
var hjson;
|
||||
|
||||
@@ -82,10 +98,10 @@ define([
|
||||
var diff = TextPatcher.format(shjson, op);
|
||||
|
||||
if (success) {
|
||||
return true;
|
||||
return cb(true);
|
||||
} else {
|
||||
return '<br><br>insert: ' + diff.insert + '<br><br>' +
|
||||
'remove: ' + diff.remove + '<br><br>';
|
||||
return cb('<br><br>insert: ' + diff.insert + '<br><br>' +
|
||||
'remove: ' + diff.remove + '<br><br>');
|
||||
}
|
||||
}, "expected hyperjson equality");
|
||||
};
|
||||
@@ -94,7 +110,7 @@ define([
|
||||
|
||||
var roundTrip = function (sel) {
|
||||
var target = $(sel)[0];
|
||||
assert(function () {
|
||||
assert(function (cb) {
|
||||
var hjson = Hyperjson.fromDOM(target);
|
||||
var cloned = Hyperjson.toDOM(hjson);
|
||||
var success = cloned.outerHTML === target.outerHTML;
|
||||
@@ -111,7 +127,7 @@ define([
|
||||
TextPatcher.log(target.outerHTML, op);
|
||||
}
|
||||
|
||||
return success;
|
||||
return cb(success);
|
||||
}, "Round trip serialization introduced artifacts.");
|
||||
};
|
||||
|
||||
@@ -125,9 +141,9 @@ define([
|
||||
|
||||
var strungJSON = function (orig) {
|
||||
var result;
|
||||
assert(function () {
|
||||
assert(function (cb) {
|
||||
result = JSON.stringify(JSON.parse(orig));
|
||||
return result === orig;
|
||||
return cb(result === orig);
|
||||
}, "expected result (" + result + ") to equal original (" + orig + ")");
|
||||
};
|
||||
|
||||
@@ -138,46 +154,56 @@ define([
|
||||
});
|
||||
|
||||
// check that old hashes parse correctly
|
||||
assert(function () {
|
||||
var secret = Cryptpad.parseHash('67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy');
|
||||
return secret.channel === "67b8385b07352be53e40746d2be6ccd7" &&
|
||||
secret.key === "XAYSuJYYqa9NfmInyHci7LNy" &&
|
||||
secret.version === 0;
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy');
|
||||
return cb(secret.hashData.channel === "67b8385b07352be53e40746d2be6ccd7" &&
|
||||
secret.hashData.key === "XAYSuJYYqa9NfmInyHci7LNy" &&
|
||||
secret.hashData.version === 0);
|
||||
}, "Old hash failed to parse");
|
||||
|
||||
// make sure version 1 hashes parse correctly
|
||||
assert(function () {
|
||||
var secret = Cryptpad.parseHash('/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI');
|
||||
return secret.version === 1 &&
|
||||
secret.mode === "edit" &&
|
||||
secret.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
|
||||
secret.key === "usn4+9CqVja8Q7RZOGTfRgqI" &&
|
||||
!secret.present;
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI');
|
||||
return cb(secret.hashData.version === 1 &&
|
||||
secret.hashData.mode === "edit" &&
|
||||
secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
|
||||
secret.hashData.key === "usn4+9CqVja8Q7RZOGTfRgqI" &&
|
||||
!secret.hashData.present);
|
||||
}, "version 1 hash (without present mode) failed to parse");
|
||||
|
||||
// test support for present mode in hashes
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present');
|
||||
return cb(secret.hashData.version === 1
|
||||
&& secret.hashData.mode === "edit"
|
||||
&& secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
|
||||
&& secret.hashData.key === "DNZ2wcG683GscU4fyOyqA87G"
|
||||
&& secret.hashData.present);
|
||||
}, "version 1 hash failed to parse");
|
||||
|
||||
// test support for present mode in hashes
|
||||
assert(function () {
|
||||
var secret = Cryptpad.parseHash('/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present');
|
||||
return secret.version === 1
|
||||
&& secret.mode === "edit"
|
||||
&& secret.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
|
||||
&& secret.key === "DNZ2wcG683GscU4fyOyqA87G"
|
||||
&& secret.present;
|
||||
}, "version 1 hash failed to parse");
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G//present');
|
||||
return cb(secret.hashData.version === 1
|
||||
&& secret.hashData.mode === "edit"
|
||||
&& secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
|
||||
&& secret.hashData.key === "DNZ2wcG683GscU4fyOyqA87G"
|
||||
&& secret.hashData.present);
|
||||
}, "Couldn't handle multiple successive slashes");
|
||||
|
||||
// test support for trailing slash
|
||||
assert(function () {
|
||||
var secret = Cryptpad.parseHash('/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/');
|
||||
return secret.version === 1 &&
|
||||
secret.mode === "edit" &&
|
||||
secret.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
|
||||
secret.key === "usn4+9CqVja8Q7RZOGTfRgqI" &&
|
||||
!secret.present;
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/');
|
||||
return cb(secret.hashData.version === 1 &&
|
||||
secret.hashData.mode === "edit" &&
|
||||
secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
|
||||
secret.hashData.key === "usn4+9CqVja8Q7RZOGTfRgqI" &&
|
||||
!secret.hashData.present);
|
||||
}, "test support for trailing slashes in version 1 hash failed to parse");
|
||||
|
||||
assert(function () {
|
||||
assert(function (cb) {
|
||||
// TODO
|
||||
return true;
|
||||
return cb(true);
|
||||
}, "version 2 hash failed to parse correctly");
|
||||
|
||||
var swap = function (str, dict) {
|
||||
@@ -194,7 +220,7 @@ define([
|
||||
return str || '';
|
||||
};
|
||||
|
||||
var formatFailures = function () {
|
||||
var formatFailures = function () {
|
||||
var template = multiline(function () { /*
|
||||
<p class="error">
|
||||
Failed on test number {{test}} with error message:
|
||||
@@ -215,16 +241,15 @@ The test returned:
|
||||
}).join("\n");
|
||||
};
|
||||
|
||||
runASSERTS();
|
||||
|
||||
$("body").html(function (i, val) {
|
||||
var dict = {
|
||||
previous: val,
|
||||
totalAssertions: ASSERTS.length,
|
||||
passedAssertions: assertions,
|
||||
plural: (assertions === 1? '' : 's'),
|
||||
failMessages: formatFailures()
|
||||
};
|
||||
runASSERTS(function () {
|
||||
$("body").html(function (i, val) {
|
||||
var dict = {
|
||||
previous: val,
|
||||
totalAssertions: ASSERTS.length,
|
||||
passedAssertions: assertions,
|
||||
plural: (assertions === 1? '' : 's'),
|
||||
failMessages: formatFailures()
|
||||
};
|
||||
|
||||
var SUCCESS = swap(multiline(function(){/*
|
||||
<div class="report">{{passedAssertions}} / {{totalAssertions}} test{{plural}} passed.
|
||||
@@ -237,12 +262,19 @@ The test returned:
|
||||
{{previous}}
|
||||
*/}), dict);
|
||||
|
||||
var report = SUCCESS;
|
||||
var report = SUCCESS;
|
||||
|
||||
return report;
|
||||
return report;
|
||||
});
|
||||
|
||||
var $report = $('.report');
|
||||
$report.addClass(failed?'failure':'success');
|
||||
|
||||
if (failed) {
|
||||
Test.failed();
|
||||
} else {
|
||||
Test.passed();
|
||||
}
|
||||
});
|
||||
|
||||
var $report = $('.report');
|
||||
$report.addClass(failed?'failure':'success');
|
||||
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/test.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js'
|
||||
], function ($, Cryptpad) {
|
||||
], function ($, Cryptpad, Test) {
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var signMsg = function (msg, privKey) {
|
||||
@@ -18,8 +19,16 @@ define([
|
||||
/^http(s)?:\/\/localhost\:/
|
||||
];
|
||||
|
||||
// Safari is weird about localStorage in iframes but seems to let sessionStorage slide.
|
||||
localStorage.User_hash = localStorage.User_hash || sessionStorage.User_hash;
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
console.log('IFRAME READY');
|
||||
Test(function () {
|
||||
// This is only here to maybe trigger an error.
|
||||
window.drive = Cryptpad.getStore().getProxy().proxy['drive'];
|
||||
Test.passed();
|
||||
});
|
||||
$(window).on("message", function (jqe) {
|
||||
var evt = jqe.originalEvent;
|
||||
var data = JSON.parse(evt.data);
|
||||
@@ -42,6 +51,11 @@ define([
|
||||
sig: sig
|
||||
};
|
||||
}
|
||||
} else if (data.cmd === 'UPDATE_LIMIT') {
|
||||
return Cryptpad.updatePinLimit(function (e, limit, plan, note) {
|
||||
ret.res = [limit, plan, note];
|
||||
srcWindow.postMessage(JSON.stringify(ret), domain);
|
||||
});
|
||||
} else {
|
||||
ret.error = "UNKNOWN_CMD";
|
||||
}
|
||||
|
||||
74
www/code/code.css
Normal file
74
www/code/code.css
Normal file
@@ -0,0 +1,74 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100%;
|
||||
min-height: auto;
|
||||
}
|
||||
.CodeMirror {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
transition: width 500ms, min-width 500ms, max-width 500ms;
|
||||
min-width: 20%;
|
||||
max-width: 80%;
|
||||
resize: horizontal;
|
||||
}
|
||||
.CodeMirror.fullPage {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
.CodeMirror-focused .cm-matchhighlight {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
|
||||
background-position: bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
#editorContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#previewContainer {
|
||||
flex: 1;
|
||||
padding: 5px 20px;
|
||||
overflow: auto;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
border-left: 1px solid black;
|
||||
box-sizing: border-box;
|
||||
font-family: Calibri, Ubuntu, sans-serif;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
#preview {
|
||||
max-width: 40vw;
|
||||
margin: auto;
|
||||
}
|
||||
#preview table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#preview table tr th {
|
||||
border: 3px solid black;
|
||||
padding: 15px;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.CodeMirror {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
#previewContainer {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
84
www/code/code.less
Normal file
84
www/code/code.less
Normal file
@@ -0,0 +1,84 @@
|
||||
@import "../../customize.dist/src/less/variables.less";
|
||||
@import "../../customize.dist/src/less/mixins.less";
|
||||
|
||||
html, body{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100%;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
@slideTime: 500ms;
|
||||
.CodeMirror {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
transition: width @slideTime, min-width @slideTime, max-width @slideTime;
|
||||
min-width: 20%;
|
||||
max-width: 80%;
|
||||
resize: horizontal;
|
||||
}
|
||||
.CodeMirror.fullPage {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
.CodeMirror-focused .cm-matchhighlight {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
|
||||
background-position: bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
#editorContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#previewContainer {
|
||||
flex: 1;
|
||||
padding: 5px 20px;
|
||||
overflow: auto;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
border-left: 1px solid black;
|
||||
box-sizing: border-box;
|
||||
font-family: Calibri,Ubuntu,sans-serif;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#preview {
|
||||
max-width: 40vw;
|
||||
margin: auto;
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
tr {
|
||||
th {
|
||||
border: 3px solid black;
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: @media-medium-screen) {
|
||||
.CodeMirror {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
#previewContainer {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
<link rel="icon" type="image/png"
|
||||
href="/customize/main-favicon.png"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css">
|
||||
<link rel="stylesheet" href="/bower_components/codemirror/addon/dialog/dialog.css">
|
||||
<link rel="stylesheet" href="/bower_components/codemirror/addon/fold/foldgutter.css" />
|
||||
<link rel="stylesheet" href="/code/code.css" />
|
||||
<script src="/bower_components/codemirror/mode/javascript/javascript.js"></script>
|
||||
<script src="/bower_components/codemirror/addon/mode/loadmode.js"></script>
|
||||
<script src="/bower_components/codemirror/mode/meta.js"></script>
|
||||
@@ -31,33 +32,13 @@
|
||||
<script src="/bower_components/codemirror/addon/fold/markdown-fold.js"></script>
|
||||
<script src="/bower_components/codemirror/addon/fold/comment-fold.js"></script>
|
||||
<script src="/bower_components/codemirror/addon/display/placeholder.js"></script>
|
||||
<style>
|
||||
html, body{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
.CodeMirror {
|
||||
height: 100%;
|
||||
}
|
||||
.CodeMirror-focused .cm-matchhighlight {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
|
||||
background-position: bottom;
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cme_toolbox" class="toolbar-container"></div>
|
||||
<textarea id="editor1" name="editor1"></textarea>
|
||||
<div id="editorContainer">
|
||||
<textarea id="editor1" name="editor1"></textarea>
|
||||
<div id="previewContainer"><div id="preview"></div></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
563
www/code/main.js
563
www/code/main.js
@@ -8,28 +8,38 @@ define([
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/modes.js',
|
||||
'/common/themes.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify) {
|
||||
var saveAs = window.saveAs;
|
||||
'/common/diffMarked.js',
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad,
|
||||
Cryptget, DiffMd) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = {
|
||||
var APP = window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
};
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var ifrw = APP.ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var stringify = function (obj) {
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
var toolbar;
|
||||
var editor;
|
||||
var $iframe = $('#pad-iframe').contents();
|
||||
var $previewContainer = $iframe.find('#previewContainer');
|
||||
var $preview = $iframe.find('#preview');
|
||||
$preview.click(function (e) {
|
||||
if (!e.target) { return; }
|
||||
var $t = $(e.target);
|
||||
if ($t.is('a') || $t.parents('a').length) {
|
||||
e.preventDefault();
|
||||
var $a = $t.is('a') ? $t : $t.parents('a').first();
|
||||
var href = $a.attr('href');
|
||||
window.open(href);
|
||||
}
|
||||
});
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
@@ -42,85 +52,22 @@ define([
|
||||
};
|
||||
|
||||
var andThen = function (CMeditor) {
|
||||
var CodeMirror = module.CodeMirror = CMeditor;
|
||||
CodeMirror.modeURL = "/bower_components/codemirror/mode/%N/%N.js";
|
||||
var $pad = $('#pad-iframe');
|
||||
var $textarea = $pad.contents().find('#editor1');
|
||||
var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad);
|
||||
$iframe.find('.CodeMirror').addClass('fullPage');
|
||||
editor = CodeMirror.editor;
|
||||
|
||||
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
|
||||
var parsedHash = Cryptpad.parsePadUrl(window.location.href);
|
||||
var defaultName = Cryptpad.getDefaultName(parsedHash);
|
||||
|
||||
var isHistoryMode = false;
|
||||
|
||||
var editor = module.editor = CMeditor.fromTextArea($textarea[0], {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets : true,
|
||||
showTrailingSpace : true,
|
||||
styleActiveLine : true,
|
||||
search: true,
|
||||
highlightSelectionMatches: {showToken: /\w+/},
|
||||
extraKeys: {"Shift-Ctrl-R": undefined},
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
mode: "javascript",
|
||||
readOnly: true
|
||||
});
|
||||
editor.setValue(Messages.codeInitialState);
|
||||
|
||||
var setMode = module.setMode = function (mode, $select) {
|
||||
module.highlightMode = mode;
|
||||
if (mode === 'text') {
|
||||
editor.setOption('mode', 'text');
|
||||
return;
|
||||
}
|
||||
CodeMirror.autoLoadMode(editor, mode);
|
||||
editor.setOption('mode', mode);
|
||||
if ($select) {
|
||||
var name = $select.find('a[data-value="' + mode + '"]').text() || 'Mode';
|
||||
$select.setValue(name);
|
||||
}
|
||||
};
|
||||
|
||||
var setTheme = module.setTheme = (function () {
|
||||
var path = '/common/theme/';
|
||||
|
||||
var $head = $(ifrw.document.head);
|
||||
|
||||
var themeLoaded = module.themeLoaded = function (theme) {
|
||||
return $head.find('link[href*="'+theme+'"]').length;
|
||||
};
|
||||
|
||||
var loadTheme = module.loadTheme = function (theme) {
|
||||
$head.append($('<link />', {
|
||||
rel: 'stylesheet',
|
||||
href: path + theme + '.css',
|
||||
}));
|
||||
};
|
||||
|
||||
return function (theme, $select) {
|
||||
if (!theme) {
|
||||
editor.setOption('theme', 'default');
|
||||
} else {
|
||||
if (!themeLoaded(theme)) {
|
||||
loadTheme(theme);
|
||||
}
|
||||
editor.setOption('theme', theme);
|
||||
}
|
||||
if ($select) {
|
||||
$select.setValue(theme || 'Theme');
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
var setEditable = module.setEditable = function (bool) {
|
||||
var setEditable = APP.setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
editor.setOption('readOnly', !bool);
|
||||
};
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = {
|
||||
initialState: '{}',
|
||||
@@ -144,11 +91,6 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
/* var isDefaultTitle = function () {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
return Cryptpad.isDefaultName(parsed, document.title);
|
||||
};*/
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
@@ -156,19 +98,31 @@ define([
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
defaultTitle: defaultName
|
||||
defaultTitle: Title.defaultTitle
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = document.title;
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
// set mode too...
|
||||
obj.highlightMode = module.highlightMode;
|
||||
obj.highlightMode = CodeMirror.highlightMode;
|
||||
|
||||
// stringify the json and send it into chainpad
|
||||
return stringify(obj);
|
||||
};
|
||||
|
||||
var forceDrawPreview = function () {
|
||||
try {
|
||||
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
|
||||
var drawPreview = Cryptpad.throttle(function () {
|
||||
if (CodeMirror.highlightMode !== 'markdown') { return; }
|
||||
if (!$previewContainer.is(':visible')) { return; }
|
||||
forceDrawPreview();
|
||||
}, 150);
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
@@ -176,173 +130,52 @@ define([
|
||||
|
||||
editor.save();
|
||||
|
||||
var textValue = canonicalize($textarea.val());
|
||||
drawPreview();
|
||||
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = stringifyInner(textValue);
|
||||
|
||||
module.patchText(shjson);
|
||||
APP.patchText(shjson);
|
||||
|
||||
if (module.realtime.getUserDoc() !== shjson) {
|
||||
if (APP.realtime.getUserDoc() !== shjson) {
|
||||
console.error("realtime.getUserDoc() !== shjson");
|
||||
}
|
||||
};
|
||||
|
||||
var getHeadingText = function () {
|
||||
var lines = editor.getValue().split(/\n/);
|
||||
|
||||
var text = '';
|
||||
lines.some(function (line) {
|
||||
// lisps?
|
||||
var lispy = /^\s*(;|#\|)(.*?)$/;
|
||||
if (lispy.test(line)) {
|
||||
line.replace(lispy, function (a, one, two) {
|
||||
text = two;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// lines beginning with a hash are potentially valuable
|
||||
// works for markdown, python, bash, etc.
|
||||
var hash = /^#(.*?)$/;
|
||||
if (hash.test(line)) {
|
||||
line.replace(hash, function (a, one) {
|
||||
text = one;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// lines including a c-style comment are also valuable
|
||||
var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/;
|
||||
if (clike.test(line)) {
|
||||
line.replace(clike, function (a, one, two) {
|
||||
if (!(two && two.replace)) { return; }
|
||||
text = two.replace(/\*\/\s*$/, '').trim();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO make one more pass for multiline comments
|
||||
});
|
||||
|
||||
return text.trim();
|
||||
};
|
||||
|
||||
var suggestName = function (fallback) {
|
||||
if (document.title === defaultName) {
|
||||
return getHeadingText() || fallback || "";
|
||||
} else {
|
||||
return document.title || getHeadingText() || defaultName;
|
||||
}
|
||||
};
|
||||
|
||||
var exportText = module.exportText = function () {
|
||||
var text = editor.getValue();
|
||||
|
||||
var ext = Modes.extensionOf(module.highlightMode);
|
||||
|
||||
var title = Cryptpad.fixFileName(suggestName('cryptpad')) + (ext || '.txt');
|
||||
|
||||
Cryptpad.prompt(Messages.exportPrompt, title, function (filename) {
|
||||
if (filename === null) { return; }
|
||||
var blob = new Blob([text], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
});
|
||||
saveAs(blob, filename);
|
||||
var onModeChanged = function (mode) {
|
||||
var $codeMirror = $iframe.find('.CodeMirror');
|
||||
if (mode === "markdown") {
|
||||
APP.$previewButton.show();
|
||||
Cryptpad.getPadAttribute('previewMode', function (e, data) {
|
||||
if (e) { return void console.error(e); }
|
||||
if (data !== false) {
|
||||
$previewContainer.show();
|
||||
$codeMirror.removeClass('fullPage');
|
||||
}
|
||||
});
|
||||
};
|
||||
var importText = function (content, file) {
|
||||
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
|
||||
var mode;
|
||||
var mime = CodeMirror.findModeByMIME(file.type);
|
||||
|
||||
if (!mime) {
|
||||
var ext = /.+\.([^.]+)$/.exec(file.name);
|
||||
if (ext[1]) {
|
||||
mode = CodeMirror.findModeByExtension(ext[1]);
|
||||
}
|
||||
} else {
|
||||
mode = mime && mime.mode || null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
|
||||
setMode(mode);
|
||||
$bar.find('#language-mode').val(mode);
|
||||
} else {
|
||||
console.log("Couldn't find a suitable highlighting mode: %s", mode);
|
||||
setMode('text');
|
||||
$bar.find('#language-mode').val('text');
|
||||
}
|
||||
|
||||
editor.setValue(content);
|
||||
onLocal();
|
||||
APP.$previewButton.hide();
|
||||
$previewContainer.hide();
|
||||
$codeMirror.addClass('fullPage');
|
||||
};
|
||||
|
||||
var renameCb = function (err, title) {
|
||||
if (err) { return; }
|
||||
document.title = title;
|
||||
onLocal();
|
||||
};
|
||||
|
||||
var updateTitle = function (newTitle) {
|
||||
if (newTitle === document.title) { return; }
|
||||
// Change the title now, and set it back to the old value if there is an error
|
||||
var oldTitle = document.title;
|
||||
document.title = newTitle;
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set pad title");
|
||||
console.error(err);
|
||||
document.title = oldTitle;
|
||||
return;
|
||||
}
|
||||
document.title = data;
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||
});
|
||||
};
|
||||
|
||||
var updateDefaultTitle = function (defaultTitle) {
|
||||
defaultName = defaultTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName);
|
||||
};
|
||||
|
||||
var updateMetadata = function(shjson) {
|
||||
// Extract the user list (metadata) from the hyperjson
|
||||
var json = (shjson === "") ? "" : JSON.parse(shjson);
|
||||
var titleUpdated = false;
|
||||
if (json && json.metadata) {
|
||||
if (json.metadata.users) {
|
||||
var userData = json.metadata.users;
|
||||
// Update the local user data
|
||||
UserList.addToUserData(userData);
|
||||
}
|
||||
if (json.metadata.defaultTitle) {
|
||||
updateDefaultTitle(json.metadata.defaultTitle);
|
||||
}
|
||||
if (typeof json.metadata.title !== "undefined") {
|
||||
updateTitle(json.metadata.title || defaultName);
|
||||
titleUpdated = true;
|
||||
}
|
||||
}
|
||||
if (!titleUpdated) {
|
||||
updateTitle(defaultName);
|
||||
}
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
var titleCfg = { getHeadingText: CodeMirror.getHeadingText };
|
||||
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'],
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
@@ -350,7 +183,10 @@ define([
|
||||
network: info.network,
|
||||
$container: $bar
|
||||
};
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
toolbar = APP.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
CodeMirror.init(config.onLocal, Title, toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
@@ -360,34 +196,17 @@ define([
|
||||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {};
|
||||
histConfig.onRender = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
var hjson = JSON.parse(val || '{}');
|
||||
var remoteDoc = hjson.content;
|
||||
var histConfig = {
|
||||
onLocal: config.onLocal,
|
||||
onRemote: config.onRemote,
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var remoteDoc = JSON.parse(val || '{}').content;
|
||||
editor.setValue(remoteDoc || '');
|
||||
editor.save();
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
$toolbar: $bar
|
||||
};
|
||||
histConfig.onClose = function () {
|
||||
// Close button clicked
|
||||
setHistory(false, true);
|
||||
};
|
||||
histConfig.onRevert = function () {
|
||||
// Revert button clicked
|
||||
setHistory(false, false);
|
||||
config.onLocal();
|
||||
config.onRemote();
|
||||
};
|
||||
histConfig.onReady = function () {
|
||||
// Called when the history is loaded and the UI displayed
|
||||
setHistory(true);
|
||||
};
|
||||
histConfig.$toolbar = $bar;
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
@@ -396,24 +215,20 @@ define([
|
||||
var templateObj = {
|
||||
rt: info.realtime,
|
||||
Crypt: Cryptget,
|
||||
getTitle: function () { return document.title; }
|
||||
getTitle: Title.getTitle
|
||||
};
|
||||
var $templateButton = Cryptpad.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
|
||||
/* add an export button */
|
||||
var $export = Cryptpad.createButton('export', true, {}, exportText);
|
||||
var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText);
|
||||
$rightside.append($export);
|
||||
|
||||
if (!readOnly) {
|
||||
/* add an import button */
|
||||
var $import = Cryptpad.createButton('import', true, {}, importText);
|
||||
var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText);
|
||||
$rightside.append($import);
|
||||
|
||||
/* add a rename button */
|
||||
//var $setTitle = Cryptpad.createButton('rename', true, {suggestName: suggestName}, renameCb);
|
||||
//$rightside.append($setTitle);
|
||||
}
|
||||
|
||||
/* add a forget button */
|
||||
@@ -424,108 +239,54 @@ define([
|
||||
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
|
||||
$rightside.append($forgetPad);
|
||||
|
||||
var configureLanguage = function (cb) {
|
||||
// FIXME this is async so make it happen as early as possible
|
||||
var options = [];
|
||||
Modes.list.forEach(function (l) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'data-value': l.mode,
|
||||
'href': '#',
|
||||
},
|
||||
content: l.language // Pretty name of the language value
|
||||
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
|
||||
$previewButton.removeClass('fa-question').addClass('fa-eye');
|
||||
$previewButton.attr('title', Messages.previewButtonTitle);
|
||||
$previewButton.click(function () {
|
||||
var $codeMirror = $iframe.find('.CodeMirror');
|
||||
if (CodeMirror.highlightMode !== 'markdown') {
|
||||
$previewContainer.show();
|
||||
}
|
||||
$previewContainer.toggle();
|
||||
if ($previewContainer.is(':visible')) {
|
||||
forceDrawPreview();
|
||||
$codeMirror.removeClass('fullPage');
|
||||
Cryptpad.setPadAttribute('previewMode', true, function (e) {
|
||||
if (e) { return console.log(e); }
|
||||
});
|
||||
});
|
||||
var dropdownConfig = {
|
||||
text: 'Mode', // Button initial text
|
||||
options: options, // Entries displayed in the menu
|
||||
left: true, // Open to the left of the button
|
||||
isSelect: true,
|
||||
};
|
||||
var $block = module.$language = Cryptpad.createDropdown(dropdownConfig);
|
||||
$block.find('a').click(function () {
|
||||
setMode($(this).attr('data-value'), $block);
|
||||
onLocal();
|
||||
});
|
||||
|
||||
$rightside.append($block);
|
||||
cb();
|
||||
};
|
||||
|
||||
var configureTheme = function () {
|
||||
/* Remember the user's last choice of theme using localStorage */
|
||||
var themeKey = 'CRYPTPAD_CODE_THEME';
|
||||
var lastTheme = localStorage.getItem(themeKey) || 'default';
|
||||
|
||||
var options = [];
|
||||
Themes.forEach(function (l) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'data-value': l.name,
|
||||
'href': '#',
|
||||
},
|
||||
content: l.name // Pretty name of the language value
|
||||
} else {
|
||||
$codeMirror.addClass('fullPage');
|
||||
Cryptpad.setPadAttribute('previewMode', false, function (e) {
|
||||
if (e) { return console.log(e); }
|
||||
});
|
||||
});
|
||||
var dropdownConfig = {
|
||||
text: 'Theme', // Button initial text
|
||||
options: options, // Entries displayed in the menu
|
||||
left: true, // Open to the left of the button
|
||||
isSelect: true,
|
||||
initialValue: lastTheme
|
||||
};
|
||||
var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig);
|
||||
|
||||
setTheme(lastTheme, $block);
|
||||
|
||||
$block.find('a').click(function () {
|
||||
var theme = $(this).attr('data-value');
|
||||
setTheme(theme, $block);
|
||||
localStorage.setItem(themeKey, theme);
|
||||
});
|
||||
|
||||
$rightside.append($block);
|
||||
};
|
||||
}
|
||||
});
|
||||
$rightside.append($previewButton);
|
||||
|
||||
if (!readOnly) {
|
||||
configureLanguage(function () {
|
||||
configureTheme();
|
||||
CodeMirror.configureTheme(function () {
|
||||
CodeMirror.configureLanguage(null, onModeChanged);
|
||||
});
|
||||
}
|
||||
else {
|
||||
configureTheme();
|
||||
CodeMirror.configureTheme();
|
||||
}
|
||||
|
||||
|
||||
// set the hash
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
};
|
||||
|
||||
var unnotify = module.unnotify = function () {
|
||||
if (module.tabNotification &&
|
||||
typeof(module.tabNotification.cancel) === 'function') {
|
||||
module.tabNotification.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
var notify = module.notify = function () {
|
||||
if (Visible.isSupported() && !Visible.currently()) {
|
||||
unnotify();
|
||||
module.tabNotification = Notify.tab(1000, 10);
|
||||
}
|
||||
};
|
||||
|
||||
config.onReady = function (info) {
|
||||
if (module.realtime !== info.realtime) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
if (APP.realtime !== info.realtime) {
|
||||
var realtime = APP.realtime = info.realtime;
|
||||
APP.patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
//logging: true
|
||||
});
|
||||
}
|
||||
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
var userDoc = APP.realtime.getUserDoc();
|
||||
|
||||
var isNew = false;
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
@@ -543,32 +304,32 @@ define([
|
||||
newDoc = hjson.content;
|
||||
|
||||
if (hjson.highlightMode) {
|
||||
setMode(hjson.highlightMode, module.$language);
|
||||
CodeMirror.setMode(hjson.highlightMode, onModeChanged);
|
||||
}
|
||||
}
|
||||
|
||||
if (!module.highlightMode) {
|
||||
setMode('javascript', module.$language);
|
||||
console.log("%s => %s", module.highlightMode, module.$language.val());
|
||||
if (!CodeMirror.highlightMode) {
|
||||
CodeMirror.setMode('markdown', onModeChanged);
|
||||
console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val());
|
||||
}
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(userDoc);
|
||||
Metadata.update(userDoc);
|
||||
|
||||
if (newDoc) {
|
||||
editor.setValue(newDoc);
|
||||
}
|
||||
|
||||
if (Cryptpad.initialName && document.title === defaultName) {
|
||||
updateTitle(Cryptpad.initialName);
|
||||
onLocal();
|
||||
if (Cryptpad.initialName && Title.isDefaultTitle()) {
|
||||
Title.updateTitle(Cryptpad.initialName);
|
||||
}
|
||||
|
||||
if (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) {
|
||||
if (yes) { unnotify(); }
|
||||
});
|
||||
}
|
||||
Cryptpad.getPadAttribute('previewMode', function (e, data) {
|
||||
if (e) { return void console.error(e); }
|
||||
if (data === false && APP.$previewButton) {
|
||||
APP.$previewButton.click();
|
||||
}
|
||||
});
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(true);
|
||||
@@ -576,90 +337,44 @@ define([
|
||||
|
||||
onLocal(); // push local state to avoid parse errors later.
|
||||
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
var cursorToPos = function(cursor, oldText) {
|
||||
var cLine = cursor.line;
|
||||
var cCh = cursor.ch;
|
||||
var pos = 0;
|
||||
var textLines = oldText.split("\n");
|
||||
for (var line = 0; line <= cLine; line++) {
|
||||
if(line < cLine) {
|
||||
pos += textLines[line].length+1;
|
||||
}
|
||||
else if(line === cLine) {
|
||||
pos += cCh;
|
||||
}
|
||||
if (readOnly) {
|
||||
config.onRemote();
|
||||
return;
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
var posToCursor = function(position, newText) {
|
||||
var cursor = {
|
||||
line: 0,
|
||||
ch: 0
|
||||
};
|
||||
var textLines = newText.substr(0, position).split("\n");
|
||||
cursor.line = textLines.length - 1;
|
||||
cursor.ch = textLines[cursor.line].length;
|
||||
return cursor;
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
var scroll = editor.getScrollInfo();
|
||||
|
||||
var oldDoc = canonicalize($textarea.val());
|
||||
var shjson = module.realtime.getUserDoc();
|
||||
var oldDoc = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = APP.realtime.getUserDoc();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(shjson);
|
||||
Metadata.update(shjson);
|
||||
|
||||
var hjson = JSON.parse(shjson);
|
||||
var remoteDoc = hjson.content;
|
||||
|
||||
var highlightMode = hjson.highlightMode;
|
||||
if (highlightMode && highlightMode !== module.highlightMode) {
|
||||
setMode(highlightMode, module.$language);
|
||||
if (highlightMode && highlightMode !== APP.highlightMode) {
|
||||
CodeMirror.setMode(highlightMode, onModeChanged);
|
||||
}
|
||||
|
||||
//get old cursor here
|
||||
var oldCursor = {};
|
||||
oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc);
|
||||
oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc);
|
||||
|
||||
editor.setValue(remoteDoc);
|
||||
editor.save();
|
||||
|
||||
var op = TextPatcher.diff(oldDoc, remoteDoc);
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(oldCursor[attr], op);
|
||||
});
|
||||
|
||||
if(selects[0] === selects[1]) {
|
||||
editor.setCursor(posToCursor(selects[0], remoteDoc));
|
||||
}
|
||||
else {
|
||||
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
|
||||
}
|
||||
|
||||
editor.scrollTo(scroll.left, scroll.top);
|
||||
CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
|
||||
drawPreview();
|
||||
|
||||
if (!readOnly) {
|
||||
var textValue = canonicalize($textarea.val());
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson2 = stringifyInner(textValue);
|
||||
if (shjson2 !== shjson) {
|
||||
console.error("shjson2 !== shjson");
|
||||
TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2));
|
||||
module.patchText(shjson2);
|
||||
APP.patchText(shjson2);
|
||||
}
|
||||
}
|
||||
if (oldDoc !== remoteDoc) {
|
||||
notify();
|
||||
}
|
||||
if (oldDoc !== remoteDoc) { Cryptpad.notify(); }
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
@@ -683,7 +398,7 @@ define([
|
||||
|
||||
config.onError = onConnectError;
|
||||
|
||||
module.realtime = Realtime.start(config);
|
||||
APP.realtime = Realtime.start(config);
|
||||
|
||||
editor.on('change', onLocal);
|
||||
|
||||
|
||||
@@ -7,8 +7,18 @@ define([], function () {
|
||||
// jquery declares itself as literally "jquery" so it cannot be pulled by path :(
|
||||
"jquery": "/bower_components/jquery/dist/jquery.min",
|
||||
// json.sortify same
|
||||
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify"
|
||||
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify",
|
||||
"pdfjs-dist/build/pdf": "/bower_components/pdfjs-dist/build/pdf",
|
||||
"pdfjs-dist/build/pdf.worker": "/bower_components/pdfjs-dist/build/pdf.worker"
|
||||
}
|
||||
});
|
||||
|
||||
// most of CryptPad breaks if you don't support isArray
|
||||
if (!Array.isArray) {
|
||||
Array.isArray = function(arg) { // CRYPTPAD_SHIM
|
||||
return Object.prototype.toString.call(arg) === '[object Array]';
|
||||
};
|
||||
}
|
||||
|
||||
require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]);
|
||||
});
|
||||
|
||||
300
www/common/common-codemirror.js
Normal file
300
www/common/common-codemirror.js
Normal file
@@ -0,0 +1,300 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/modes.js',
|
||||
'/common/themes.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, Modes, Themes) {
|
||||
var saveAs = window.saveAs;
|
||||
var module = {};
|
||||
|
||||
module.create = function (CMeditor, ifrw, Cryptpad) {
|
||||
var exp = {};
|
||||
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var CodeMirror = exp.CodeMirror = CMeditor;
|
||||
CodeMirror.modeURL = "/bower_components/codemirror/mode/%N/%N.js";
|
||||
|
||||
var $pad = $('#pad-iframe');
|
||||
var $textarea = exp.$textarea = $pad.contents().find('#editor1');
|
||||
|
||||
var Title;
|
||||
var onLocal = function () {};
|
||||
var $rightside;
|
||||
exp.init = function (local, title, toolbar) {
|
||||
if (typeof local === "function") {
|
||||
onLocal = local;
|
||||
}
|
||||
Title = title;
|
||||
$rightside = toolbar.$rightside;
|
||||
};
|
||||
|
||||
var editor = exp.editor = CMeditor.fromTextArea($textarea[0], {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets : true,
|
||||
showTrailingSpace : true,
|
||||
styleActiveLine : true,
|
||||
search: true,
|
||||
highlightSelectionMatches: {showToken: /\w+/},
|
||||
extraKeys: {"Shift-Ctrl-R": undefined},
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
mode: "javascript",
|
||||
readOnly: true
|
||||
});
|
||||
editor.setValue(Messages.codeInitialState);
|
||||
|
||||
var setMode = exp.setMode = function (mode, cb) {
|
||||
exp.highlightMode = mode;
|
||||
if (mode === 'text') {
|
||||
editor.setOption('mode', 'text');
|
||||
if (cb) { cb('text'); }
|
||||
return;
|
||||
}
|
||||
CMeditor.autoLoadMode(editor, mode);
|
||||
editor.setOption('mode', mode);
|
||||
if (exp.$language) {
|
||||
var name = exp.$language.find('a[data-value="' + mode + '"]').text() || 'Mode';
|
||||
exp.$language.setValue(name);
|
||||
}
|
||||
if(cb) { cb(mode); }
|
||||
};
|
||||
|
||||
var setTheme = exp.setTheme = (function () {
|
||||
var path = '/common/theme/';
|
||||
|
||||
var $head = $(ifrw.document.head);
|
||||
|
||||
var themeLoaded = exp.themeLoaded = function (theme) {
|
||||
return $head.find('link[href*="'+theme+'"]').length;
|
||||
};
|
||||
|
||||
var loadTheme = exp.loadTheme = function (theme) {
|
||||
$head.append($('<link />', {
|
||||
rel: 'stylesheet',
|
||||
href: path + theme + '.css',
|
||||
}));
|
||||
};
|
||||
|
||||
return function (theme, $select) {
|
||||
if (!theme) {
|
||||
editor.setOption('theme', 'default');
|
||||
} else {
|
||||
if (!themeLoaded(theme)) {
|
||||
loadTheme(theme);
|
||||
}
|
||||
editor.setOption('theme', theme);
|
||||
}
|
||||
if ($select) {
|
||||
$select.setValue(theme || 'Theme');
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
exp.getHeadingText = function () {
|
||||
var lines = editor.getValue().split(/\n/);
|
||||
|
||||
var text = '';
|
||||
lines.some(function (line) {
|
||||
// lisps?
|
||||
var lispy = /^\s*(;|#\|)(.*?)$/;
|
||||
if (lispy.test(line)) {
|
||||
line.replace(lispy, function (a, one, two) {
|
||||
text = two;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// lines beginning with a hash are potentially valuable
|
||||
// works for markdown, python, bash, etc.
|
||||
var hash = /^#(.*?)$/;
|
||||
if (hash.test(line)) {
|
||||
line.replace(hash, function (a, one) {
|
||||
text = one;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// lines including a c-style comment are also valuable
|
||||
var clike = /^\s*(\/\*|\/\/)(.*)?(\*\/)*$/;
|
||||
if (clike.test(line)) {
|
||||
line.replace(clike, function (a, one, two) {
|
||||
if (!(two && two.replace)) { return; }
|
||||
text = two.replace(/\*\/\s*$/, '').trim();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO make one more pass for multiline comments
|
||||
});
|
||||
|
||||
return text.trim();
|
||||
};
|
||||
|
||||
exp.configureLanguage = function (cb, onModeChanged) {
|
||||
var options = [];
|
||||
Modes.list.forEach(function (l) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'data-value': l.mode,
|
||||
'href': '#',
|
||||
},
|
||||
content: l.language // Pretty name of the language value
|
||||
});
|
||||
});
|
||||
var dropdownConfig = {
|
||||
text: 'Mode', // Button initial text
|
||||
options: options, // Entries displayed in the menu
|
||||
left: true, // Open to the left of the button
|
||||
isSelect: true,
|
||||
};
|
||||
var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig);
|
||||
$block.find('a').click(function () {
|
||||
setMode($(this).attr('data-value'), onModeChanged);
|
||||
onLocal();
|
||||
});
|
||||
|
||||
if ($rightside) { $rightside.append($block); }
|
||||
if (cb) { cb(); }
|
||||
};
|
||||
|
||||
exp.configureTheme = function (cb) {
|
||||
/* Remember the user's last choice of theme using localStorage */
|
||||
var themeKey = 'CRYPTPAD_CODE_THEME';
|
||||
var lastTheme = localStorage.getItem(themeKey) || 'default';
|
||||
|
||||
var options = [];
|
||||
Themes.forEach(function (l) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'data-value': l.name,
|
||||
'href': '#',
|
||||
},
|
||||
content: l.name // Pretty name of the language value
|
||||
});
|
||||
});
|
||||
var dropdownConfig = {
|
||||
text: 'Theme', // Button initial text
|
||||
options: options, // Entries displayed in the menu
|
||||
left: true, // Open to the left of the button
|
||||
isSelect: true,
|
||||
initialValue: lastTheme
|
||||
};
|
||||
var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig);
|
||||
|
||||
setTheme(lastTheme, $block);
|
||||
|
||||
$block.find('a').click(function () {
|
||||
var theme = $(this).attr('data-value');
|
||||
setTheme(theme, $block);
|
||||
localStorage.setItem(themeKey, theme);
|
||||
});
|
||||
|
||||
if ($rightside) { $rightside.append($block); }
|
||||
if (cb) { cb(); }
|
||||
};
|
||||
|
||||
exp.exportText = function () {
|
||||
var text = editor.getValue();
|
||||
|
||||
var ext = Modes.extensionOf(exp.highlightMode);
|
||||
|
||||
var title = Cryptpad.fixFileName(Title ? Title.suggestTitle('cryptpad') : "?") + (ext || '.txt');
|
||||
|
||||
Cryptpad.prompt(Messages.exportPrompt, title, function (filename) {
|
||||
if (filename === null) { return; }
|
||||
var blob = new Blob([text], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
});
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
};
|
||||
exp.importText = function (content, file) {
|
||||
var $bar = ifrw.$('#cme_toolbox');
|
||||
var mode;
|
||||
var mime = CodeMirror.findModeByMIME(file.type);
|
||||
|
||||
if (!mime) {
|
||||
var ext = /.+\.([^.]+)$/.exec(file.name);
|
||||
if (ext[1]) {
|
||||
mode = CMeditor.findModeByExtension(ext[1]);
|
||||
}
|
||||
} else {
|
||||
mode = mime && mime.mode || null;
|
||||
}
|
||||
|
||||
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
|
||||
setMode(mode);
|
||||
$bar.find('#language-mode').val(mode);
|
||||
} else {
|
||||
console.log("Couldn't find a suitable highlighting mode: %s", mode);
|
||||
setMode('text');
|
||||
$bar.find('#language-mode').val('text');
|
||||
}
|
||||
|
||||
editor.setValue(content);
|
||||
onLocal();
|
||||
};
|
||||
|
||||
var cursorToPos = function(cursor, oldText) {
|
||||
var cLine = cursor.line;
|
||||
var cCh = cursor.ch;
|
||||
var pos = 0;
|
||||
var textLines = oldText.split("\n");
|
||||
for (var line = 0; line <= cLine; line++) {
|
||||
if(line < cLine) {
|
||||
pos += textLines[line].length+1;
|
||||
}
|
||||
else if(line === cLine) {
|
||||
pos += cCh;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
var posToCursor = function(position, newText) {
|
||||
var cursor = {
|
||||
line: 0,
|
||||
ch: 0
|
||||
};
|
||||
var textLines = newText.substr(0, position).split("\n");
|
||||
cursor.line = textLines.length - 1;
|
||||
cursor.ch = textLines[cursor.line].length;
|
||||
return cursor;
|
||||
};
|
||||
|
||||
exp.setValueAndCursor = function (oldDoc, remoteDoc, TextPatcher) {
|
||||
var scroll = editor.getScrollInfo();
|
||||
//get old cursor here
|
||||
var oldCursor = {};
|
||||
oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc);
|
||||
oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc);
|
||||
|
||||
editor.setValue(remoteDoc);
|
||||
editor.save();
|
||||
|
||||
var op = TextPatcher.diff(oldDoc, remoteDoc);
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(oldCursor[attr], op);
|
||||
});
|
||||
|
||||
if(selects[0] === selects[1]) {
|
||||
editor.setCursor(posToCursor(selects[0], remoteDoc));
|
||||
}
|
||||
else {
|
||||
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
|
||||
}
|
||||
|
||||
editor.scrollTo(scroll.left, scroll.top);
|
||||
};
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
return module;
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-interface.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js'
|
||||
], function (Util, Crypto) {
|
||||
], function (Util, UI, Crypto) {
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var Hash = {};
|
||||
@@ -32,9 +33,68 @@ define([
|
||||
return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/';
|
||||
};
|
||||
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
|
||||
return '/2/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
|
||||
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
|
||||
};
|
||||
Hash.getUserHrefFromKeys = function (username, pubkey) {
|
||||
return window.location.origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
|
||||
};
|
||||
|
||||
var fixDuplicateSlashes = function (s) {
|
||||
return s.replace(/\/+/g, '/');
|
||||
};
|
||||
|
||||
/*
|
||||
Version 0
|
||||
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
|
||||
Version 1
|
||||
/code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
|
||||
*/
|
||||
|
||||
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
|
||||
if (!hash) { return; }
|
||||
var parsed = {};
|
||||
var hashArr = fixDuplicateSlashes(hash).split('/');
|
||||
if (['media', 'file', 'user'].indexOf(type) === -1) {
|
||||
parsed.type = 'pad';
|
||||
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
|
||||
// Old hash
|
||||
parsed.channel = hash.slice(0, 32);
|
||||
parsed.key = hash.slice(32, 56);
|
||||
parsed.version = 0;
|
||||
return parsed;
|
||||
}
|
||||
if (hashArr[1] && hashArr[1] === '1') {
|
||||
parsed.version = 1;
|
||||
parsed.mode = hashArr[2];
|
||||
parsed.channel = hashArr[3];
|
||||
parsed.key = hashArr[4].replace(/-/g, '/');
|
||||
parsed.present = typeof(hashArr[5]) === "string" && hashArr[5] === 'present';
|
||||
return parsed;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
if (['media', 'file'].indexOf(type) !== -1) {
|
||||
parsed.type = 'file';
|
||||
if (hashArr[1] && hashArr[1] === '1') {
|
||||
parsed.version = 1;
|
||||
parsed.channel = hashArr[2].replace(/-/g, '/');
|
||||
parsed.key = hashArr[3].replace(/-/g, '/');
|
||||
return parsed;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
if (['user'].indexOf(type) !== -1) {
|
||||
parsed.type = 'user';
|
||||
if (hashArr[1] && hashArr[1] === '1') {
|
||||
parsed.version = 1;
|
||||
parsed.user = hashArr[2];
|
||||
parsed.pubkey = hashArr[3].replace(/-/g, '/');
|
||||
return parsed;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
return;
|
||||
};
|
||||
var parsePadUrl = Hash.parsePadUrl = function (href) {
|
||||
var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i;
|
||||
|
||||
@@ -43,19 +103,24 @@ define([
|
||||
if (!href) { return ret; }
|
||||
if (href.slice(-1) !== '/') { href += '/'; }
|
||||
|
||||
var idx;
|
||||
|
||||
if (!/^https*:\/\//.test(href)) {
|
||||
var idx = href.indexOf('/#');
|
||||
idx = href.indexOf('/#');
|
||||
ret.type = href.slice(1, idx);
|
||||
ret.hash = href.slice(idx + 2);
|
||||
ret.hashData = parseTypeHash(ret.type, ret.hash);
|
||||
return ret;
|
||||
}
|
||||
|
||||
var hash = href.replace(patt, function (a, domain, type) {
|
||||
href.replace(patt, function (a, domain, type) {
|
||||
ret.domain = domain;
|
||||
ret.type = type;
|
||||
return '';
|
||||
});
|
||||
ret.hash = hash.replace(/#/g, '');
|
||||
idx = href.indexOf('/#');
|
||||
ret.hash = href.slice(idx + 2);
|
||||
ret.hashData = parseTypeHash(ret.type, ret.hash);
|
||||
return ret;
|
||||
};
|
||||
|
||||
@@ -71,7 +136,7 @@ define([
|
||||
* - no argument: use the URL hash or create one if it doesn't exist
|
||||
* - secretHash provided: use secretHash to find the keys
|
||||
*/
|
||||
Hash.getSecrets = function (secretHash) {
|
||||
Hash.getSecrets = function (type, secretHash) {
|
||||
var secret = {};
|
||||
var generate = function () {
|
||||
secret.keys = Crypto.createEditCryptor();
|
||||
@@ -81,50 +146,56 @@ define([
|
||||
generate();
|
||||
return secret;
|
||||
} else {
|
||||
var hash = secretHash || window.location.hash.slice(1);
|
||||
var parsed;
|
||||
var hash;
|
||||
if (secretHash) {
|
||||
if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); }
|
||||
parsed = parseTypeHash(type, secretHash);
|
||||
hash = secretHash;
|
||||
} else {
|
||||
var pHref = parsePadUrl(window.location.href);
|
||||
parsed = pHref.hashData;
|
||||
hash = pHref.hash;
|
||||
}
|
||||
//var parsed = parsePadUrl(window.location.href);
|
||||
//var hash = secretHash || window.location.hash.slice(1);
|
||||
if (hash.length === 0) {
|
||||
generate();
|
||||
return secret;
|
||||
}
|
||||
// old hash system : #{hexChanKey}{cryptKey}
|
||||
// new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey}
|
||||
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
|
||||
if (parsed.version === 0) {
|
||||
// Old hash
|
||||
secret.channel = hash.slice(0, 32);
|
||||
secret.key = hash.slice(32);
|
||||
secret.channel = parsed.channel;
|
||||
secret.key = parsed.key;
|
||||
}
|
||||
else {
|
||||
else if (parsed.version === 1) {
|
||||
// New hash
|
||||
var hashArray = hash.split('/');
|
||||
if (hashArray.length < 4) {
|
||||
Hash.alert("Unable to parse the key");
|
||||
throw new Error("Unable to parse the key");
|
||||
}
|
||||
var version = hashArray[1];
|
||||
if (version === "1") {
|
||||
var mode = hashArray[2];
|
||||
if (mode === 'edit') {
|
||||
secret.channel = base64ToHex(hashArray[3]);
|
||||
var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/'));
|
||||
secret.keys = keys;
|
||||
secret.key = keys.editKeyStr;
|
||||
if (parsed.type === "pad") {
|
||||
secret.channel = base64ToHex(parsed.channel);
|
||||
if (parsed.mode === 'edit') {
|
||||
secret.keys = Crypto.createEditCryptor(parsed.key);
|
||||
secret.key = secret.keys.editKeyStr;
|
||||
if (secret.channel.length !== 32 || secret.key.length !== 24) {
|
||||
Hash.alert("The channel key and/or the encryption key is invalid");
|
||||
UI.alert("The channel key and/or the encryption key is invalid");
|
||||
throw new Error("The channel key and/or the encryption key is invalid");
|
||||
}
|
||||
}
|
||||
else if (mode === 'view') {
|
||||
secret.channel = base64ToHex(hashArray[3]);
|
||||
secret.keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/'));
|
||||
else if (parsed.mode === 'view') {
|
||||
secret.keys = Crypto.createViewCryptor(parsed.key);
|
||||
if (secret.channel.length !== 32) {
|
||||
Hash.alert("The channel key is invalid");
|
||||
UI.alert("The channel key is invalid");
|
||||
throw new Error("The channel key is invalid");
|
||||
}
|
||||
}
|
||||
} else if (version === "2") {
|
||||
} else if (parsed.type === "file") {
|
||||
// version 2 hashes are to be used for encrypted blobs
|
||||
secret.channel = hashArray[2].replace(/-/g, '/');
|
||||
secret.keys = { fileKeyStr: hashArray[3].replace(/-/g, '/') };
|
||||
secret.channel = parsed.channel;
|
||||
secret.keys = { fileKeyStr: parsed.key };
|
||||
} else if (parsed.type === "user") {
|
||||
// version 2 hashes are to be used for encrypted blobs
|
||||
throw new Error("User hashes can't be opened (yet)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,42 +232,6 @@ define([
|
||||
return '/1/edit/' + [channelId, key].join('/') + '/';
|
||||
};
|
||||
|
||||
/*
|
||||
Version 0
|
||||
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
|
||||
Version 1
|
||||
/code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
|
||||
Version 2
|
||||
/file/#/2/<fileId>/<cryptKey>/<contentType>
|
||||
/file/#/2/K6xWU-LT9BJHCQcDCT-DcQ/ajExFODrFH4lVBwxxsrOKw/image-png
|
||||
*/
|
||||
var parseHash = Hash.parseHash = function (hash) {
|
||||
var parsed = {};
|
||||
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
|
||||
// Old hash
|
||||
parsed.channel = hash.slice(0, 32);
|
||||
parsed.key = hash.slice(32);
|
||||
parsed.version = 0;
|
||||
return parsed;
|
||||
}
|
||||
var hashArr = hash.split('/');
|
||||
if (hashArr[1] && hashArr[1] === '1') {
|
||||
parsed.version = 1;
|
||||
parsed.mode = hashArr[2];
|
||||
parsed.channel = hashArr[3];
|
||||
parsed.key = hashArr[4];
|
||||
parsed.present = typeof(hashArr[5]) === "string" && hashArr[5] === 'present';
|
||||
return parsed;
|
||||
}
|
||||
if (hashArr[1] && hashArr[1] === '2') {
|
||||
parsed.version = 2;
|
||||
parsed.channel = hashArr[2].replace(/-/g, '/');
|
||||
parsed.key = hashArr[3].replace(/-/g, '/');
|
||||
return parsed;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// STORAGE
|
||||
Hash.findWeaker = function (href, recents) {
|
||||
var rHref = href || getRelativeHref(window.location.href);
|
||||
@@ -207,9 +242,13 @@ Version 2
|
||||
var p = parsePadUrl(pad.href);
|
||||
if (p.type !== parsed.type) { return; } // Not the same type
|
||||
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
|
||||
var pHash = parseHash(p.hash);
|
||||
var parsedHash = parseHash(parsed.hash);
|
||||
var pHash = p.hashData;
|
||||
var parsedHash = parsed.hashData;
|
||||
if (!parsedHash || !pHash) { return; }
|
||||
|
||||
// We don't have stronger/weaker versions of files or users
|
||||
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
|
||||
|
||||
if (pHash.version !== parsedHash.version) { return; }
|
||||
if (pHash.channel !== parsedHash.channel) { return; }
|
||||
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
|
||||
@@ -229,9 +268,13 @@ Version 2
|
||||
var p = parsePadUrl(pad.href);
|
||||
if (p.type !== parsed.type) { return; } // Not the same type
|
||||
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
|
||||
var pHash = parseHash(p.hash);
|
||||
var parsedHash = parseHash(parsed.hash);
|
||||
var pHash = p.hashData;
|
||||
var parsedHash = parsed.hashData;
|
||||
if (!parsedHash || !pHash) { return; }
|
||||
|
||||
// We don't have stronger/weaker versions of files or users
|
||||
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
|
||||
|
||||
if (pHash.version !== parsedHash.version) { return; }
|
||||
if (pHash.channel !== parsedHash.channel) { return; }
|
||||
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
|
||||
@@ -250,8 +293,7 @@ Version 2
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
if (!parsed || !parsed.hash) { return; }
|
||||
|
||||
parsed = Hash.parseHash(parsed.hash);
|
||||
|
||||
parsed = parsed.hashData;
|
||||
if (parsed.version === 0) {
|
||||
return parsed.channel;
|
||||
} else if (parsed.version !== 1 && parsed.version !== 2) {
|
||||
|
||||
@@ -24,7 +24,6 @@ define([
|
||||
|
||||
var wcId = common.hrefToHexChannelId(config.href || window.location.href);
|
||||
|
||||
console.log(wcId);
|
||||
var createRealtime = function () {
|
||||
return ChainPad.create({
|
||||
userName: 'history',
|
||||
@@ -36,8 +35,8 @@ define([
|
||||
};
|
||||
var realtime = createRealtime();
|
||||
|
||||
var hash = config.href ? common.parsePadUrl(config.href).hash : undefined;
|
||||
var secret = common.getSecrets(hash);
|
||||
var parsed = config.href ? common.parsePadUrl(config.href) : {};
|
||||
var secret = common.getSecrets(parsed.type, parsed.hash);
|
||||
var crypto = Crypto.createEncryptor(secret.keys);
|
||||
|
||||
var to = window.setTimeout(function () {
|
||||
@@ -80,11 +79,32 @@ define([
|
||||
if (History.loading) { return void console.error("History is already being loaded..."); }
|
||||
History.loading = true;
|
||||
var $toolbar = config.$toolbar;
|
||||
var noFunc = function () {};
|
||||
var render = config.onRender || noFunc;
|
||||
var onClose = config.onClose || noFunc;
|
||||
var onRevert = config.onRevert || noFunc;
|
||||
var onReady = config.onReady || noFunc;
|
||||
|
||||
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
|
||||
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
|
||||
}
|
||||
|
||||
// config.setHistory(bool, bool)
|
||||
// - bool1: history value
|
||||
// - bool2: reset old content?
|
||||
var render = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
config.applyVal(val);
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
var onClose = function () { config.setHistory(false, true); };
|
||||
var onRevert = function () {
|
||||
config.setHistory(false, false);
|
||||
config.onLocal();
|
||||
config.onRemote();
|
||||
};
|
||||
var onReady = function () {
|
||||
config.setHistory(true);
|
||||
};
|
||||
|
||||
var Messages = common.Messages;
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ define([
|
||||
'/customize/messages.js',
|
||||
'/common/common-util.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/alertifyjs/dist/js/alertify.js'
|
||||
], function ($, Messages, Util, AppConfig, Alertify) {
|
||||
'/bower_components/alertifyjs/dist/js/alertify.js',
|
||||
'/common/notify.js',
|
||||
'/common/visible.js'
|
||||
], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible) {
|
||||
|
||||
var UI = {};
|
||||
|
||||
@@ -141,7 +143,7 @@ define([
|
||||
|
||||
return {
|
||||
show: function () {
|
||||
$target.show();
|
||||
$target.css('display', 'inline');
|
||||
return this;
|
||||
},
|
||||
hide: function () {
|
||||
@@ -192,10 +194,17 @@ define([
|
||||
};
|
||||
UI.removeLoadingScreen = function (cb) {
|
||||
$('#' + LOADING).fadeOut(750, cb);
|
||||
$('#loadingTip').css('top', '');
|
||||
window.setTimeout(function () {
|
||||
$('#loadingTip').fadeOut(750);
|
||||
}, 3000);
|
||||
var $tip = $('#loadingTip').css('top', '')
|
||||
// loading.less sets transition-delay: $wait-time
|
||||
// and transition: opacity $fadeout-time
|
||||
.css({
|
||||
'opacity': 0,
|
||||
'pointer-events': 'none',
|
||||
});
|
||||
setTimeout(function () {
|
||||
$tip.remove();
|
||||
}, 3750);
|
||||
// jquery.fadeout can get stuck
|
||||
};
|
||||
UI.errorLoadingScreen = function (error, transparent) {
|
||||
if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen(undefined, true); }
|
||||
@@ -204,6 +213,28 @@ define([
|
||||
$('#' + LOADING).find('p').html(error || Messages.error);
|
||||
};
|
||||
|
||||
// Notify
|
||||
var notify = {};
|
||||
UI.unnotify = function () {
|
||||
if (notify.tabNotification &&
|
||||
typeof(notify.tabNotification.cancel) === 'function') {
|
||||
notify.tabNotification.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
UI.notify = function () {
|
||||
if (Visible.isSupported() && !Visible.currently()) {
|
||||
UI.unnotify();
|
||||
notify.tabNotification = Notify.tab(1000, 10);
|
||||
}
|
||||
};
|
||||
|
||||
if (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) {
|
||||
if (yes) { UI.unnotify(); }
|
||||
});
|
||||
}
|
||||
|
||||
UI.importContent = function (type, f) {
|
||||
return function () {
|
||||
var $files = $('<input type="file">').click();
|
||||
|
||||
51
www/common/common-metadata.js
Normal file
51
www/common/common-metadata.js
Normal file
@@ -0,0 +1,51 @@
|
||||
define(function () {
|
||||
var module = {};
|
||||
|
||||
module.create = function (UserList, Title, cfg) {
|
||||
var exp = {};
|
||||
|
||||
exp.update = function (shjson) {
|
||||
// Extract the user list (metadata) from the hyperjson
|
||||
var json = (!shjson || typeof shjson !== "string") ? "" : JSON.parse(shjson);
|
||||
var titleUpdated = false;
|
||||
var metadata;
|
||||
if (Array.isArray(json)) {
|
||||
metadata = json[3] && json[3].metadata;
|
||||
} else {
|
||||
metadata = json.metadata;
|
||||
}
|
||||
if (typeof metadata === "object") {
|
||||
if (metadata.users) {
|
||||
var userData = metadata.users;
|
||||
// Update the local user data
|
||||
UserList.addToUserData(userData);
|
||||
}
|
||||
if (metadata.defaultTitle) {
|
||||
Title.updateDefaultTitle(metadata.defaultTitle);
|
||||
}
|
||||
if (typeof metadata.title !== "undefined") {
|
||||
Title.updateTitle(metadata.title || Title.defaultTitle);
|
||||
titleUpdated = true;
|
||||
}
|
||||
if (metadata.slideOptions && cfg.slideOptions) {
|
||||
cfg.slideOptions(metadata.slideOptions);
|
||||
}
|
||||
if (metadata.color && cfg.slideColors) {
|
||||
cfg.slideColors(metadata.color, metadata.backColor);
|
||||
}
|
||||
if (typeof(metadata.palette) !== 'undefined' && cfg.updatePalette) {
|
||||
cfg.updatePalette(metadata.palette);
|
||||
}
|
||||
}
|
||||
if (!titleUpdated) {
|
||||
Title.updateTitle(Title.defaultTitle);
|
||||
}
|
||||
};
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
return module;
|
||||
});
|
||||
|
||||
|
||||
84
www/common/common-title.js
Normal file
84
www/common/common-title.js
Normal file
@@ -0,0 +1,84 @@
|
||||
define(function () {
|
||||
var module = {};
|
||||
|
||||
module.create = function (cfg, onLocal, Cryptpad) {
|
||||
var exp = {};
|
||||
|
||||
var parsed = exp.parsedHref = Cryptpad.parsePadUrl(window.location.href);
|
||||
exp.defaultTitle = Cryptpad.getDefaultName(parsed);
|
||||
|
||||
exp.title = document.title; // TOOD slides
|
||||
|
||||
cfg = cfg || {};
|
||||
|
||||
var getHeadingText = cfg.getHeadingText || function () { return; };
|
||||
var updateLocalTitle = function (newTitle) {
|
||||
exp.title = newTitle;
|
||||
if (typeof cfg.updateLocalTitle === "function") {
|
||||
cfg.updateLocalTitle(newTitle);
|
||||
} else {
|
||||
document.title = newTitle;
|
||||
}
|
||||
};
|
||||
|
||||
var $title;
|
||||
exp.setToolbar = function (toolbar) {
|
||||
$title = toolbar && toolbar.title;
|
||||
};
|
||||
|
||||
exp.getTitle = function () { return exp.title; };
|
||||
var isDefaultTitle = exp.isDefaultTitle = function (){return exp.title === exp.defaultTitle;};
|
||||
|
||||
var suggestTitle = exp.suggestTitle = function (fallback) {
|
||||
if (isDefaultTitle()) {
|
||||
return getHeadingText() || fallback || "";
|
||||
} else {
|
||||
return exp.title || getHeadingText() || exp.defaultTitle;
|
||||
}
|
||||
};
|
||||
|
||||
var renameCb = function (err, newTitle) {
|
||||
if (err) { return; }
|
||||
updateLocalTitle(newTitle);
|
||||
console.log('here');
|
||||
onLocal();
|
||||
};
|
||||
|
||||
exp.updateTitle = function (newTitle) {
|
||||
if (newTitle === exp.title) { return; }
|
||||
// Change the title now, and set it back to the old value if there is an error
|
||||
var oldTitle = exp.title;
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set pad title");
|
||||
console.error(err);
|
||||
updateLocalTitle(oldTitle);
|
||||
return;
|
||||
}
|
||||
updateLocalTitle(data);
|
||||
if (!$title) { return; }
|
||||
$title.find('span.title').text(data);
|
||||
$title.find('input').val(data);
|
||||
});
|
||||
};
|
||||
|
||||
exp.updateDefaultTitle = function (newDefaultTitle) {
|
||||
exp.defaultTitle = newDefaultTitle;
|
||||
if (!$title) { return; }
|
||||
$title.find('input').attr("placeholder", exp.defaultTitle);
|
||||
};
|
||||
|
||||
exp.getTitleConfig = function () {
|
||||
return {
|
||||
onRename: renameCb,
|
||||
suggestName: suggestTitle,
|
||||
defaultName: exp.defaultTitle
|
||||
};
|
||||
};
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
return module;
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ define([], function () {
|
||||
.replace(/ +$/, "")
|
||||
.split(" ");
|
||||
var byteString = String.fromCharCode.apply(null, hexArray);
|
||||
return window.btoa(byteString).replace(/\//g, '-').slice(0,-2);
|
||||
return window.btoa(byteString).replace(/\//g, '-').replace(/=+$/, '');
|
||||
};
|
||||
|
||||
Util.base64ToHex = function (b64String) {
|
||||
@@ -81,12 +81,58 @@ define([], function () {
|
||||
.replace(/_+/g, '_');
|
||||
};
|
||||
|
||||
var oneKilobyte = 1024;
|
||||
var oneMegabyte = 1024 * oneKilobyte;
|
||||
var oneGigabyte = 1024 * oneMegabyte;
|
||||
|
||||
Util.bytesToGigabytes = function (bytes) {
|
||||
return Math.ceil(bytes / oneGigabyte * 100) / 100;
|
||||
};
|
||||
|
||||
Util.bytesToMegabytes = function (bytes) {
|
||||
return Math.floor((bytes / (1024 * 1024) * 100)) / 100;
|
||||
return Math.ceil(bytes / oneMegabyte * 100) / 100;
|
||||
};
|
||||
|
||||
Util.bytesToKilobytes = function (bytes) {
|
||||
return Math.floor(bytes / 1024 * 100) / 100;
|
||||
return Math.ceil(bytes / oneKilobyte * 100) / 100;
|
||||
};
|
||||
|
||||
Util.magnitudeOfBytes = function (bytes) {
|
||||
if (bytes >= oneGigabyte) { return 'GB'; }
|
||||
else if (bytes >= oneMegabyte) { return 'MB'; }
|
||||
};
|
||||
|
||||
Util.fetch = function (src, cb) {
|
||||
var done = false;
|
||||
var CB = function (err, res) {
|
||||
if (done) { return; }
|
||||
done = true;
|
||||
cb(err, res);
|
||||
};
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", src, true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = function () {
|
||||
if (/^4/.test(''+this.status)) {
|
||||
return CB('XHR_ERROR');
|
||||
}
|
||||
return void CB(void 0, new Uint8Array(xhr.response));
|
||||
};
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
Util.throttle = function (f, ms) {
|
||||
var to;
|
||||
var g = function () {
|
||||
window.clearTimeout(to);
|
||||
to = window.setTimeout(f, ms);
|
||||
};
|
||||
return g;
|
||||
};
|
||||
|
||||
Util.createRandomInteger = function () {
|
||||
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
};
|
||||
|
||||
return Util;
|
||||
|
||||
@@ -22,7 +22,8 @@ define([
|
||||
};
|
||||
|
||||
var makeConfig = function (hash) {
|
||||
var secret = Cryptpad.getSecrets(hash);
|
||||
// We can't use cryptget with a file or a user so we can use 'pad' as hash type
|
||||
var secret = Cryptpad.getSecrets('pad', hash);
|
||||
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
|
||||
var config = {
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
|
||||
@@ -8,11 +8,14 @@ define([
|
||||
'/common/common-interface.js',
|
||||
'/common/common-history.js',
|
||||
'/common/common-userlist.js',
|
||||
'/common/common-title.js',
|
||||
'/common/common-metadata.js',
|
||||
'/common/common-codemirror.js',
|
||||
|
||||
'/common/clipboard.js',
|
||||
'/common/pinpad.js',
|
||||
'/customize/application_config.js'
|
||||
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Clipboard, Pinpad, AppConfig) {
|
||||
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, CodeMirror, Clipboard, Pinpad, AppConfig) {
|
||||
|
||||
/* This file exposes functionality which is specific to Cryptpad, but not to
|
||||
any particular pad type. This includes functions for committing metadata
|
||||
@@ -20,10 +23,12 @@ define([
|
||||
|
||||
Additionally, there is some basic functionality for import/export.
|
||||
*/
|
||||
|
||||
var common = window.Cryptpad = {
|
||||
Messages: Messages,
|
||||
Clipboard: Clipboard
|
||||
Clipboard: Clipboard,
|
||||
donateURL: 'https://accounts.cryptpad.fr/#/donate?on=' + window.location.hostname,
|
||||
upgradeURL: 'https://accounts.cryptpad.fr/#/?on=' + window.location.hostname,
|
||||
account: {},
|
||||
};
|
||||
|
||||
// constants
|
||||
@@ -53,6 +58,8 @@ define([
|
||||
common.addLoadingScreen = UI.addLoadingScreen;
|
||||
common.removeLoadingScreen = UI.removeLoadingScreen;
|
||||
common.errorLoadingScreen = UI.errorLoadingScreen;
|
||||
common.notify = UI.notify;
|
||||
common.unnotify = UI.unnotify;
|
||||
|
||||
// import common utilities for export
|
||||
common.find = Util.find;
|
||||
@@ -66,18 +73,23 @@ define([
|
||||
common.fixFileName = Util.fixFileName;
|
||||
common.bytesToMegabytes = Util.bytesToMegabytes;
|
||||
common.bytesToKilobytes = Util.bytesToKilobytes;
|
||||
common.fetch = Util.fetch;
|
||||
common.throttle = Util.throttle;
|
||||
common.createRandomInteger = Util.createRandomInteger;
|
||||
|
||||
// import hash utilities for export
|
||||
var createRandomHash = common.createRandomHash = Hash.createRandomHash;
|
||||
common.parseTypeHash = Hash.parseTypeHash;
|
||||
var parsePadUrl = common.parsePadUrl = Hash.parsePadUrl;
|
||||
var isNotStrongestStored = common.isNotStrongestStored = Hash.isNotStrongestStored;
|
||||
var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId;
|
||||
var parseHash = common.parseHash = Hash.parseHash;
|
||||
var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref;
|
||||
common.getBlobPathFromHex = Hash.getBlobPathFromHex;
|
||||
|
||||
common.getEditHashFromKeys = Hash.getEditHashFromKeys;
|
||||
common.getViewHashFromKeys = Hash.getViewHashFromKeys;
|
||||
common.getFileHashFromKeys = Hash.getFileHashFromKeys;
|
||||
common.getUserHrefFromKeys = Hash.getUserHrefFromKeys;
|
||||
common.getSecrets = Hash.getSecrets;
|
||||
common.getHashes = Hash.getHashes;
|
||||
common.createChannelId = Hash.createChannelId;
|
||||
@@ -88,6 +100,15 @@ define([
|
||||
// Userlist
|
||||
common.createUserList = UserList.create;
|
||||
|
||||
// Title
|
||||
common.createTitle = Title.create;
|
||||
|
||||
// Metadata
|
||||
common.createMetadata = Metadata.create;
|
||||
|
||||
// CodeMirror
|
||||
common.createCodemirror = CodeMirror.create;
|
||||
|
||||
// History
|
||||
common.getHistory = function (config) { return History.create(common, config); };
|
||||
|
||||
@@ -197,6 +218,7 @@ define([
|
||||
userNameKey,
|
||||
userHashKey,
|
||||
'loginToken',
|
||||
'plan',
|
||||
].forEach(function (k) {
|
||||
sessionStorage.removeItem(k);
|
||||
localStorage.removeItem(k);
|
||||
@@ -225,6 +247,11 @@ define([
|
||||
var getUserHash = common.getUserHash = function () {
|
||||
var hash = localStorage[userHashKey];
|
||||
|
||||
if (['undefined', 'undefined/'].indexOf(hash) !== -1) {
|
||||
localStorage.removeItem(userHashKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hash) {
|
||||
var sHash = common.serializeHash(hash);
|
||||
if (sHash !== hash) { localStorage[userHashKey] = sHash; }
|
||||
@@ -270,27 +297,22 @@ define([
|
||||
if (!pad.title) {
|
||||
pad.title = common.getDefaultname(parsed);
|
||||
}
|
||||
return parsed.hash;
|
||||
return parsed.hashData;
|
||||
};
|
||||
// Migrate from legacy store (localStorage)
|
||||
var migrateRecentPads = common.migrateRecentPads = function (pads) {
|
||||
return pads.map(function (pad) {
|
||||
var hash;
|
||||
var parsedHash;
|
||||
if (Array.isArray(pad)) { // TODO DEPRECATE_F
|
||||
var href = pad[0];
|
||||
href.replace(/\#(.*)$/, function (a, h) {
|
||||
hash = h;
|
||||
});
|
||||
|
||||
return {
|
||||
href: pad[0],
|
||||
atime: pad[1],
|
||||
title: pad[2] || hash && hash.slice(0,8),
|
||||
title: pad[2] || '',
|
||||
ctime: pad[1],
|
||||
};
|
||||
} else if (pad && typeof(pad) === 'object') {
|
||||
hash = checkObjectData(pad);
|
||||
if (!hash || !common.parseHash(hash)) { return; }
|
||||
parsedHash = checkObjectData(pad);
|
||||
if (!parsedHash || !parsedHash.type) { return; }
|
||||
return pad;
|
||||
} else {
|
||||
console.error("[Cryptpad.migrateRecentPads] pad had unexpected value");
|
||||
@@ -303,8 +325,8 @@ define([
|
||||
var checkRecentPads = common.checkRecentPads = function (pads) {
|
||||
pads.forEach(function (pad, i) {
|
||||
if (pad && typeof(pad) === 'object') {
|
||||
var hash = checkObjectData(pad);
|
||||
if (!hash || !common.parseHash(hash)) {
|
||||
var parsedHash = checkObjectData(pad);
|
||||
if (!parsedHash || !parsedHash.type) {
|
||||
console.error("[Cryptpad.checkRecentPads] pad had unexpected value", pad);
|
||||
getStore().removeData(i);
|
||||
return;
|
||||
@@ -434,6 +456,7 @@ define([
|
||||
Crypt.put(p.hash, val, function () {
|
||||
common.findOKButton().click();
|
||||
common.removeLoadingScreen();
|
||||
common.feedback('TEMPLATE_USED');
|
||||
});
|
||||
});
|
||||
}).appendTo($p);
|
||||
@@ -522,6 +545,7 @@ define([
|
||||
common.setPadTitle = function (name, cb) {
|
||||
var href = window.location.href;
|
||||
var parsed = parsePadUrl(href);
|
||||
if (!parsed.hash) { return; }
|
||||
href = getRelativeHref(href);
|
||||
// getRecentPads return the array from the drive, not a copy
|
||||
// We don't have to call "set..." at the end, everything is stored with listmap
|
||||
@@ -542,8 +566,8 @@ define([
|
||||
|
||||
// Version 1 : we have up to 4 differents hash for 1 pad, keep the strongest :
|
||||
// Edit > Edit (present) > View > View (present)
|
||||
var pHash = parseHash(p.hash);
|
||||
var parsedHash = parseHash(parsed.hash);
|
||||
var pHash = p.hashData;
|
||||
var parsedHash = parsed.hashData;
|
||||
|
||||
if (!pHash) { return; } // We may have a corrupted pad in our storage, abort here in that case
|
||||
|
||||
@@ -584,7 +608,7 @@ define([
|
||||
var data = makePad(href, name);
|
||||
getStore().pushData(data, function (e) {
|
||||
if (e) {
|
||||
if (e === 'E_OVER_LIMIT' && AppConfig.enablePinLimit) {
|
||||
if (e === 'E_OVER_LIMIT') {
|
||||
common.alert(Messages.pinLimitNotPinned, null, true);
|
||||
return;
|
||||
}
|
||||
@@ -645,7 +669,8 @@ define([
|
||||
var userHash = localStorage && localStorage.User_hash;
|
||||
if (!userHash) { return null; }
|
||||
|
||||
var userChannel = common.parseHash(userHash).channel;
|
||||
var userParsedHash = common.parseTypeHash('drive', userHash);
|
||||
var userChannel = userParsedHash && userParsedHash.channel;
|
||||
if (!userChannel) { return null; }
|
||||
|
||||
var list = fo.getFiles([fo.FILES_DATA]).map(hrefToHexChannelId)
|
||||
@@ -728,29 +753,119 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
common.updatePinLimit = function (cb) {
|
||||
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
|
||||
rpc.updatePinLimits(function (e, limit, plan, note) {
|
||||
if (e) { return cb(e); }
|
||||
cb(e, limit, plan, note);
|
||||
});
|
||||
};
|
||||
|
||||
common.getPinLimit = function (cb) {
|
||||
cb(void 0, typeof(AppConfig.pinLimit) === 'number'? AppConfig.pinLimit: 1000);
|
||||
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
|
||||
rpc.getLimit(function (e, limit, plan, note) {
|
||||
if (e) { return cb(e); }
|
||||
cb(void 0, limit, plan, note);
|
||||
});
|
||||
};
|
||||
|
||||
common.isOverPinLimit = function (cb) {
|
||||
if (!common.isLoggedIn() || !AppConfig.enablePinLimit) { return void cb(null, false); }
|
||||
if (!common.isLoggedIn()) { return void cb(null, false); }
|
||||
var usage;
|
||||
var andThen = function (e, limit) {
|
||||
var andThen = function (e, limit, plan) {
|
||||
if (e) { return void cb(e); }
|
||||
var data = {usage: usage, limit: limit};
|
||||
var data = {usage: usage, limit: limit, plan: plan};
|
||||
if (usage > limit) {
|
||||
return void cb (null, true, data);
|
||||
}
|
||||
return void cb (null, false, data);
|
||||
};
|
||||
var todo = function (e, used) {
|
||||
usage = common.bytesToMegabytes(used);
|
||||
usage = used; //common.bytesToMegabytes(used);
|
||||
if (e) { return void cb(e); }
|
||||
common.getPinLimit(andThen);
|
||||
};
|
||||
common.getPinnedUsage(todo);
|
||||
};
|
||||
|
||||
common.uploadComplete = function (cb) {
|
||||
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
|
||||
rpc.uploadComplete(cb);
|
||||
};
|
||||
|
||||
common.uploadStatus = function (size, cb) {
|
||||
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
|
||||
rpc.uploadStatus(size, cb);
|
||||
};
|
||||
|
||||
common.uploadCancel = function (cb) {
|
||||
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
|
||||
rpc.uploadCancel(cb);
|
||||
};
|
||||
|
||||
var LIMIT_REFRESH_RATE = 30000; // milliseconds
|
||||
common.createUsageBar = function (cb, alwaysDisplayUpgrade) {
|
||||
var todo = function (err, state, data) {
|
||||
var $container = $('<span>', {'class':'limit-container'});
|
||||
if (!data) {
|
||||
return void window.setTimeout(function () {
|
||||
common.isOverPinLimit(todo);
|
||||
}, LIMIT_REFRESH_RATE);
|
||||
}
|
||||
|
||||
var unit = Util.magnitudeOfBytes(data.limit);
|
||||
|
||||
var usage = unit === 'GB'? Util.bytesToGigabytes(data.usage):
|
||||
Util.bytesToMegabytes(data.usage);
|
||||
var limit = unit === 'GB'? Util.bytesToGigabytes(data.limit):
|
||||
Util.bytesToMegabytes(data.limit);
|
||||
|
||||
var $limit = $('<span>', {'class': 'cryptpad-limit-bar'}).appendTo($container);
|
||||
var quota = usage/limit;
|
||||
var width = Math.floor(Math.min(quota, 1)*200); // the bar is 200px width
|
||||
var $usage = $('<span>', {'class': 'usage'}).css('width', width+'px');
|
||||
|
||||
if (Config.noSubscriptionButton !== true &&
|
||||
(quota >= 0.8 || alwaysDisplayUpgrade) &&
|
||||
data.plan !== "power")
|
||||
{
|
||||
var origin = encodeURIComponent(window.location.hostname);
|
||||
var $upgradeLink = $('<a>', {
|
||||
href: "https://accounts.cryptpad.fr/#!on=" + origin,
|
||||
rel: "noreferrer noopener",
|
||||
target: "_blank",
|
||||
}).appendTo($container);
|
||||
$('<button>', {
|
||||
'class': 'upgrade buttonSuccess',
|
||||
title: Messages.upgradeTitle
|
||||
}).text(Messages.upgrade).appendTo($upgradeLink);
|
||||
}
|
||||
|
||||
var prettyUsage;
|
||||
var prettyLimit;
|
||||
|
||||
if (unit === 'GB') {
|
||||
prettyUsage = Messages._getKey('formattedGB', [usage]);
|
||||
prettyLimit = Messages._getKey('formattedGB', [limit]);
|
||||
} else {
|
||||
prettyUsage = Messages._getKey('formattedMB', [usage]);
|
||||
prettyLimit = Messages._getKey('formattedMB', [limit]);
|
||||
}
|
||||
|
||||
if (quota < 0.8) { $usage.addClass('normal'); }
|
||||
else if (quota < 1) { $usage.addClass('warning'); }
|
||||
else { $usage.addClass('above'); }
|
||||
var $text = $('<span>', {'class': 'usageText'});
|
||||
$text.text(usage + ' / ' + prettyLimit);
|
||||
$limit.append($usage).append($text);
|
||||
window.setTimeout(function () {
|
||||
common.isOverPinLimit(todo);
|
||||
}, LIMIT_REFRESH_RATE);
|
||||
cb(err, $container);
|
||||
};
|
||||
common.isOverPinLimit(todo);
|
||||
};
|
||||
|
||||
common.createButton = function (type, rightside, data, callback) {
|
||||
var button;
|
||||
var size = "17px";
|
||||
@@ -816,6 +931,7 @@ define([
|
||||
common.addTemplate(makePad(href, title));
|
||||
whenRealtimeSyncs(getStore().getProxy().info.realtime, function () {
|
||||
common.alert(Messages.templateSaved);
|
||||
common.feedback('TEMPLATE_CREATED');
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -838,7 +954,8 @@ define([
|
||||
if (callback) {
|
||||
button.click(function() {
|
||||
var href = window.location.href;
|
||||
common.confirm(Messages.forgetPrompt, function (yes) {
|
||||
var msg = isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog;
|
||||
common.confirm(msg, function (yes) {
|
||||
if (!yes) { return; }
|
||||
common.forgetPad(href, function (err) {
|
||||
if (err) {
|
||||
@@ -857,7 +974,8 @@ define([
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
common.alert(Messages.movedToTrash, undefined, true);
|
||||
var cMsg = isLoggedIn() ? Messages.movedToTrash : Messages.deleted;
|
||||
common.alert(cMsg, undefined, true);
|
||||
return;
|
||||
});
|
||||
});
|
||||
@@ -1207,6 +1325,27 @@ define([
|
||||
return $userAdmin;
|
||||
};
|
||||
|
||||
var CRYPTPAD_VERSION = 'cryptpad-version';
|
||||
var updateLocalVersion = function () {
|
||||
// Check for CryptPad updates
|
||||
var urlArgs = Config.requireConf ? Config.requireConf.urlArgs : null;
|
||||
if (!urlArgs) { return; }
|
||||
var arr = /ver=([0-9.]+)(-[0-9]*)?/.exec(urlArgs);
|
||||
var ver = arr[1];
|
||||
if (!ver) { return; }
|
||||
var verArr = ver.split('.');
|
||||
verArr[2] = 0;
|
||||
if (verArr.length !== 3) { return; }
|
||||
var stored = localStorage[CRYPTPAD_VERSION] || '0.0.0';
|
||||
var storedArr = stored.split('.');
|
||||
storedArr[2] = 0;
|
||||
var shouldUpdate = parseInt(verArr[0]) > parseInt(storedArr[0]) ||
|
||||
(parseInt(verArr[0]) === parseInt(storedArr[0]) &&
|
||||
parseInt(verArr[1]) > parseInt(storedArr[1]));
|
||||
if (!shouldUpdate) { return; }
|
||||
common.alert(Messages._getKey('newVersion', [verArr.join('.')]), null, true);
|
||||
localStorage[CRYPTPAD_VERSION] = ver;
|
||||
};
|
||||
|
||||
common.ready = (function () {
|
||||
var env = {};
|
||||
@@ -1224,6 +1363,9 @@ define([
|
||||
block--;
|
||||
if (!block) {
|
||||
initialized = true;
|
||||
|
||||
updateLocalVersion();
|
||||
|
||||
f(void 0, env);
|
||||
}
|
||||
};
|
||||
@@ -1247,7 +1389,7 @@ define([
|
||||
feedback("NO_PROXIES");
|
||||
}
|
||||
|
||||
if (typeof(Array.isArray) !== 'function') {
|
||||
if (/CRYPTPAD_SHIM/.test(Array.isArray.toString())) {
|
||||
feedback("NO_ISARRAY");
|
||||
}
|
||||
|
||||
@@ -1257,20 +1399,21 @@ define([
|
||||
UI.Alertify.reset();
|
||||
|
||||
// Load the new pad when the hash has changed
|
||||
var oldHash = document.location.hash.slice(1);
|
||||
var oldHref = document.location.href;
|
||||
window.onhashchange = function () {
|
||||
var newHash = document.location.hash.slice(1);
|
||||
var parsedOld = parseHash(oldHash);
|
||||
var parsedNew = parseHash(newHash);
|
||||
var newHref = document.location.href;
|
||||
var parsedOld = parsePadUrl(oldHref).hashData;
|
||||
var parsedNew = parsePadUrl(newHref).hashData;
|
||||
if (parsedOld && parsedNew && (
|
||||
parsedOld.channel !== parsedNew.channel
|
||||
parsedOld.type !== parsedNew.type
|
||||
|| parsedOld.channel !== parsedNew.channel
|
||||
|| parsedOld.mode !== parsedNew.mode
|
||||
|| parsedOld.key !== parsedNew.key)) {
|
||||
document.location.reload();
|
||||
return;
|
||||
}
|
||||
if (parsedNew) {
|
||||
oldHash = newHash;
|
||||
oldHref = newHref;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1287,6 +1430,14 @@ define([
|
||||
console.log('RPC handshake complete');
|
||||
rpc = common.rpc = env.rpc = call;
|
||||
|
||||
common.getPinLimit(function (e, limit, plan, note) {
|
||||
if (e) { return void console.error(e); }
|
||||
common.account.limit = limit;
|
||||
localStorage.plan = common.account.plan = plan;
|
||||
common.account.note = note;
|
||||
cb();
|
||||
});
|
||||
|
||||
common.arePinsSynced(function (err, yes) {
|
||||
if (!yes) {
|
||||
common.resetPins(function (err) {
|
||||
@@ -1295,7 +1446,6 @@ define([
|
||||
});
|
||||
}
|
||||
});
|
||||
cb();
|
||||
});
|
||||
} else if (PINNING_ENABLED) {
|
||||
console.log('not logged in. pads will not be pinned');
|
||||
|
||||
127
www/common/diffMarked.js
Normal file
127
www/common/diffMarked.js
Normal file
@@ -0,0 +1,127 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/marked/marked.min.js',
|
||||
'/bower_components/diff-dom/diffDOM.js'
|
||||
],function ($, Marked) {
|
||||
var DiffMd = {};
|
||||
|
||||
var DiffDOM = window.diffDOM;
|
||||
var renderer = new Marked.Renderer();
|
||||
|
||||
Marked.setOptions({
|
||||
renderer: renderer
|
||||
});
|
||||
|
||||
DiffMd.render = function (md) {
|
||||
return Marked(md);
|
||||
};
|
||||
|
||||
// Tasks list
|
||||
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
|
||||
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
|
||||
renderer.listitem = function (text) {
|
||||
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
|
||||
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
|
||||
if (isCheckedTaskItem) {
|
||||
text = text.replace(checkedTaskItemPtn,
|
||||
'<i class="fa fa-check-square" aria-hidden="true"></i> ') + '\n';
|
||||
}
|
||||
if (isUncheckedTaskItem) {
|
||||
text = text.replace(uncheckedTaskItemPtn,
|
||||
'<i class="fa fa-square-o" aria-hidden="true"></i> ') + '\n';
|
||||
}
|
||||
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
|
||||
return '<li'+ cls + '>' + text + '</li>\n';
|
||||
};
|
||||
|
||||
var forbiddenTags = [
|
||||
'SCRIPT',
|
||||
'IFRAME',
|
||||
'OBJECT',
|
||||
'APPLET',
|
||||
'VIDEO',
|
||||
'AUDIO',
|
||||
];
|
||||
var unsafeTag = function (info) {
|
||||
if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) {
|
||||
if (/^on/.test(info.diff.name)) {
|
||||
console.log("Rejecting forbidden element attribute with name", info.diff.name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (['addElement', 'replaceElement'].indexOf(info.diff.action) !== -1) {
|
||||
var msg = "Rejecting forbidden tag of type (%s)";
|
||||
if (info.diff.element && forbiddenTags.indexOf(info.diff.element.nodeName) !== -1) {
|
||||
console.log(msg, info.diff.element.nodeName);
|
||||
return true;
|
||||
} else if (info.diff.newValue && forbiddenTags.indexOf(info.diff.newValue.nodeName) !== -1) {
|
||||
console.log("Replacing restricted element type (%s) with PRE", info.diff.newValue.nodeName);
|
||||
info.diff.newValue.nodeName = 'PRE';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var slice = function (coll) {
|
||||
return Array.prototype.slice.call(coll);
|
||||
};
|
||||
|
||||
/* remove listeners from the DOM */
|
||||
var removeListeners = function (root) {
|
||||
slice(root.attributes).map(function (attr) {
|
||||
if (/^on/.test(attr.name)) {
|
||||
root.attributes.removeNamedItem(attr.name);
|
||||
}
|
||||
});
|
||||
// all the way down
|
||||
slice(root.children).forEach(removeListeners);
|
||||
};
|
||||
|
||||
var domFromHTML = function (html) {
|
||||
var Dom = new DOMParser().parseFromString(html, "text/html");
|
||||
removeListeners(Dom.body);
|
||||
return Dom;
|
||||
};
|
||||
|
||||
var DD = new DiffDOM({
|
||||
preDiffApply: function (info) {
|
||||
if (unsafeTag(info)) { return true; }
|
||||
}
|
||||
});
|
||||
|
||||
var makeDiff = function (A, B, id) {
|
||||
var Err;
|
||||
var Els = [A, B].map(function (frag) {
|
||||
if (typeof(frag) === 'object') {
|
||||
if (!frag || (frag && !frag.body)) {
|
||||
Err = "No body";
|
||||
return;
|
||||
}
|
||||
var els = frag.body.querySelectorAll('#'+id);
|
||||
if (els.length) {
|
||||
return els[0];
|
||||
}
|
||||
}
|
||||
Err = 'No candidate found';
|
||||
});
|
||||
if (Err) { return Err; }
|
||||
var patch = DD.diff(Els[0], Els[1]);
|
||||
return patch;
|
||||
};
|
||||
|
||||
DiffMd.apply = function (newHtml, $content) {
|
||||
var id = $content.attr('id');
|
||||
if (!id) { throw new Error("The element must have a valid id"); }
|
||||
var $div = $('<div>', {id: id}).append(newHtml);
|
||||
var Dom = domFromHTML($('<div>').append($div).html());
|
||||
var oldDom = domFromHTML($content[0].outerHTML);
|
||||
var patch = makeDiff(oldDom, Dom, id);
|
||||
if (typeof(patch) === 'string') {
|
||||
throw new Error(patch);
|
||||
} else {
|
||||
DD.apply($content[0], patch);
|
||||
}
|
||||
};
|
||||
|
||||
return DiffMd;
|
||||
});
|
||||
|
||||
@@ -173,6 +173,10 @@ define([
|
||||
proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
|
||||
}
|
||||
|
||||
// copy User_hash into sessionStorage because cross-domain iframes
|
||||
// on safari replaces localStorage with sessionStorage or something
|
||||
if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); }
|
||||
|
||||
var localToken = tryParsing(localStorage.getItem(tokenKey));
|
||||
if (localToken === null) {
|
||||
// if that number hasn't been set to localStorage, do so.
|
||||
@@ -222,7 +226,7 @@ define([
|
||||
if (!hash) {
|
||||
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
|
||||
}
|
||||
var secret = Cryptpad.getSecrets(hash);
|
||||
var secret = Cryptpad.getSecrets('drive', hash);
|
||||
var listmapConfig = {
|
||||
data: {},
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -188,6 +188,10 @@ define([
|
||||
newRecentPads.push(data);
|
||||
}
|
||||
});
|
||||
if (!proxy.FS_hashes || !Array.isArray(proxy.FS_hashes)) {
|
||||
proxy.FS_hashes = [];
|
||||
}
|
||||
proxy.FS_hashes.push(localStorage.FS_hash);
|
||||
}
|
||||
if (typeof(cb) === "function") { cb(); }
|
||||
};
|
||||
|
||||
@@ -77,6 +77,13 @@ define([
|
||||
return;
|
||||
}
|
||||
rpc.send('RESET', channels, function (e, response) {
|
||||
if (e) {
|
||||
return void cb(e);
|
||||
}
|
||||
if (!response.length) {
|
||||
console.log(response);
|
||||
return void cb('INVALID_RESPONSE');
|
||||
}
|
||||
cb(e, response[0]);
|
||||
});
|
||||
};
|
||||
@@ -121,6 +128,63 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
// Update the limit value for all the users and return the limit for your publicKey
|
||||
exp.updatePinLimits = function (cb) {
|
||||
rpc.send('UPDATE_LIMITS', undefined, function (e, response) {
|
||||
if (e) { return void cb(e); }
|
||||
if (response && response.length && typeof(response[0]) === "number") {
|
||||
cb (void 0, response[0], response[1], response[2]);
|
||||
} else {
|
||||
cb('INVALID_RESPONSE');
|
||||
}
|
||||
});
|
||||
};
|
||||
// Get the storage limit associated with your publicKey
|
||||
exp.getLimit = function (cb) {
|
||||
rpc.send('GET_LIMIT', undefined, function (e, response) {
|
||||
if (e) { return void cb(e); }
|
||||
if (response && response.length && typeof(response[0]) === "number") {
|
||||
cb (void 0, response[0], response[1], response[2]);
|
||||
} else {
|
||||
cb('INVALID_RESPONSE');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exp.uploadComplete = function (cb) {
|
||||
rpc.send('UPLOAD_COMPLETE', null, function (e, res) {
|
||||
if (e) { return void cb(e); }
|
||||
var id = res[0];
|
||||
if (typeof(id) !== 'string') {
|
||||
return void cb('INVALID_ID');
|
||||
}
|
||||
cb(void 0, id);
|
||||
});
|
||||
};
|
||||
|
||||
exp.uploadStatus = function (size, cb) {
|
||||
if (typeof(size) !== 'number') {
|
||||
return void window.setTimeout(function () {
|
||||
cb('INVALID_SIZE');
|
||||
});
|
||||
}
|
||||
rpc.send('UPLOAD_STATUS', size, function (e, res) {
|
||||
if (e) { return void cb(e); }
|
||||
var pending = res[0];
|
||||
if (typeof(pending) !== 'boolean') {
|
||||
return void cb('INVALID_RESPONSE');
|
||||
}
|
||||
cb(void 0, pending);
|
||||
});
|
||||
};
|
||||
|
||||
exp.uploadCancel = function (cb) {
|
||||
rpc.send('UPLOAD_CANCEL', void 0, function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
cb(e, exp);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -129,6 +129,24 @@ types of messages:
|
||||
return sendMsg(ctx, data, cb);
|
||||
};
|
||||
|
||||
send.unauthenticated = function (type, msg, cb) {
|
||||
if (!ctx.connected) {
|
||||
return void window.setTimeout(function () {
|
||||
cb('DISCONNECTED');
|
||||
});
|
||||
}
|
||||
|
||||
// construct an unsigned message
|
||||
var data = [null, edPublicKey, null, type, msg];
|
||||
if (ctx.cookie && ctx.cookie.join) {
|
||||
data[2] = ctx.cookie.join('|');
|
||||
} else {
|
||||
data[2] = ctx.cookie;
|
||||
}
|
||||
|
||||
return sendMsg(ctx, data, cb);
|
||||
};
|
||||
|
||||
network.on('message', function (msg) {
|
||||
onMsg(ctx, msg);
|
||||
});
|
||||
|
||||
75
www/common/test.js
Normal file
75
www/common/test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
define([], function () {
|
||||
var out = function () { };
|
||||
out.passed = out.failed = out;
|
||||
if (window.location.hash.indexOf("?test=test") > -1) {
|
||||
var cpt = window.__CRYPTPAD_TEST__ = {
|
||||
data: [],
|
||||
getData: function () {
|
||||
var data = JSON.stringify(cpt.data);
|
||||
cpt.data = [];
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
// jshint -W103
|
||||
var errProto = (new Error()).__proto__;
|
||||
var doLog = function (o) {
|
||||
var s;
|
||||
if (typeof(o) === 'object' && o.__proto__ === errProto) {
|
||||
s = JSON.stringify([ o.message, o.stack ]);
|
||||
} else if (typeof(s) !== 'string') {
|
||||
try {
|
||||
s = JSON.stringify(o);
|
||||
} catch (e) {
|
||||
s = String(o);
|
||||
}
|
||||
}
|
||||
var out = [s];
|
||||
try { throw new Error(); } catch (e) { out.push(e.stack.split('\n')[3]); }
|
||||
cpt.data.push({ type: 'log', val: out.join('') });
|
||||
};
|
||||
|
||||
window.console._error = window.console.error;
|
||||
window.console._log = window.console.log;
|
||||
window.console.error = function (e) { window.console._error(e); doLog(e); };
|
||||
window.console.log = function (l) { window.console._log(l); doLog(l); };
|
||||
|
||||
window.onerror = function (msg, url, lineNo, columnNo, e) {
|
||||
cpt.data.push({
|
||||
type: 'report',
|
||||
val: 'failed',
|
||||
error: {
|
||||
message: e ? e.message : msg,
|
||||
stack: e ? e.stack : (url + ":" + lineNo)
|
||||
}
|
||||
});
|
||||
};
|
||||
require.onError = function (e) {
|
||||
cpt.data.push({
|
||||
type: 'report',
|
||||
val: 'failed',
|
||||
error: { message: e.message, stack: e.stack }
|
||||
});
|
||||
};
|
||||
out = function (f) { f(); };
|
||||
out.testing = true;
|
||||
out.passed = function () {
|
||||
cpt.data.push({
|
||||
type: 'report',
|
||||
val: 'passed'
|
||||
});
|
||||
};
|
||||
out.failed = function (reason) {
|
||||
var e;
|
||||
try { throw new Error(reason); } catch (err) { e = err; }
|
||||
cpt.data.push({
|
||||
type: 'report',
|
||||
val: 'failed',
|
||||
error: { message: e.message, stack: e.stack }
|
||||
});
|
||||
};
|
||||
} else {
|
||||
out.testing = false;
|
||||
}
|
||||
return out;
|
||||
});
|
||||
@@ -500,8 +500,12 @@ define([
|
||||
var todo = function (e, overLimit) {
|
||||
if (e) { return void console.error("Unable to get the pinned usage"); }
|
||||
if (overLimit) {
|
||||
var message = Messages.pinLimitReachedAlert;
|
||||
if (ApiConfig.noSubscriptionButton === true) {
|
||||
message = Messages.pinLimitReachedAlertNoAccounts;
|
||||
}
|
||||
$limit.show().click(function () {
|
||||
Cryptpad.alert(Messages.pinLimitReachedAlert, null, true);
|
||||
Cryptpad.alert(message, null, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ define([
|
||||
constants: {},
|
||||
};
|
||||
|
||||
var SPINNER_DISAPPEAR_TIME = 3000;
|
||||
var SPINNER_DISAPPEAR_TIME = 1000;
|
||||
|
||||
// Toolbar parts
|
||||
var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar';
|
||||
@@ -33,6 +33,7 @@ define([
|
||||
var LIMIT_CLS = Bar.constants.lag = 'cryptpad-limit';
|
||||
var TITLE_CLS = Bar.constants.title = "cryptpad-title";
|
||||
var NEWPAD_CLS = Bar.constants.newpad = "cryptpad-newpad";
|
||||
var UPGRADE_CLS = Bar.constants.upgrade = "cryptpad-upgrade";
|
||||
|
||||
// User admin menu
|
||||
var USERADMIN_CLS = Bar.constants.user = 'cryptpad-user-dropdown';
|
||||
@@ -70,6 +71,7 @@ define([
|
||||
var $userContainer = $('<span>', {
|
||||
'class': USER_CLS
|
||||
}).appendTo($topContainer);
|
||||
$('<button>', {'class': UPGRADE_CLS + ' buttonSuccess'}).hide().appendTo($userContainer);
|
||||
$('<span>', {'class': SPINNER_CLS}).hide().appendTo($userContainer);
|
||||
$('<span>', {'class': STATE_CLS}).hide().appendTo($userContainer);
|
||||
$('<span>', {'class': LAG_CLS}).hide().appendTo($userContainer);
|
||||
@@ -367,7 +369,7 @@ define([
|
||||
return "Loading share button";
|
||||
};
|
||||
|
||||
var createFileShare = function () {
|
||||
var createFileShare = function (toolbar) {
|
||||
if (!window.location.hash) {
|
||||
throw new Error("Unable to display the share button: hash required in the URL");
|
||||
}
|
||||
@@ -380,6 +382,7 @@ define([
|
||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
||||
});
|
||||
|
||||
toolbar.$leftside.append($button);
|
||||
return $button;
|
||||
};
|
||||
|
||||
@@ -594,7 +597,6 @@ define([
|
||||
'class': 'synced fa fa-check',
|
||||
title: Messages.synced
|
||||
}).appendTo($spin);
|
||||
toolbar.$userAdmin.prepend($spin);
|
||||
if (config.realtime) {
|
||||
config.realtime.onPatch(ks(toolbar, config));
|
||||
config.realtime.onMessage(ks(toolbar, config, true));
|
||||
@@ -615,8 +617,12 @@ define([
|
||||
var todo = function (e, overLimit) {
|
||||
if (e) { return void console.error("Unable to get the pinned usage"); }
|
||||
if (overLimit) {
|
||||
var key = 'pinLimitReachedAlert';
|
||||
if (ApiConfig.noSubscriptionButton === true) {
|
||||
key = 'pinLimitReachedAlertNoAccounts';
|
||||
}
|
||||
$limit.show().click(function () {
|
||||
Cryptpad.alert(Messages.pinLimitReachedAlert, null, true);
|
||||
Cryptpad.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -630,6 +636,8 @@ define([
|
||||
var pads_options = [];
|
||||
Config.availablePadTypes.forEach(function (p) {
|
||||
if (p === 'drive') { return; }
|
||||
if (!Cryptpad.isLoggedIn() && Config.registeredOnlyTypes &&
|
||||
Config.registeredOnlyTypes.indexOf(p) !== -1) { return; }
|
||||
pads_options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
@@ -691,6 +699,33 @@ define([
|
||||
return $userAdmin;
|
||||
};
|
||||
|
||||
var createUpgrade = function (toolbar) {
|
||||
if (ApiConfig.removeDonateButton) { return; }
|
||||
if (Cryptpad.account.plan) { return; }
|
||||
|
||||
var text;
|
||||
var feedback;
|
||||
var url;
|
||||
if (ApiConfig.allowSubscriptions && Cryptpad.isLoggedIn()) {
|
||||
text = Messages.upgradeAccount;
|
||||
feedback = "UPGRADE_ACCOUNT";
|
||||
url = Cryptpad.upgradeURL;
|
||||
} else {
|
||||
text = Messages.supportCryptpad;
|
||||
feedback = "SUPPORT_CRYPTPAD";
|
||||
url = Cryptpad.donateURL;
|
||||
}
|
||||
|
||||
var $upgrade = toolbar.$top.find('.' + UPGRADE_CLS).attr({
|
||||
'title': Messages.supportCryptpad
|
||||
}).text(text).show()
|
||||
.click(function () {
|
||||
Cryptpad.feedback(feedback);
|
||||
window.open(url,'_blank');
|
||||
});
|
||||
return $upgrade;
|
||||
};
|
||||
|
||||
// Events
|
||||
var initClickEvents = function (toolbar, config) {
|
||||
var removeDropdowns = function () {
|
||||
@@ -848,11 +883,11 @@ define([
|
||||
tb['spinner'] = createSpinner;
|
||||
tb['state'] = createState;
|
||||
tb['limit'] = createLimit;
|
||||
tb['upgrade'] = createUpgrade;
|
||||
tb['newpad'] = createNewPad;
|
||||
tb['useradmin'] = createUserAdmin;
|
||||
|
||||
|
||||
var addElement = function (arr, additionnalCfg, init) {
|
||||
var addElement = toolbar.addElement = function (arr, additionnalCfg, init) {
|
||||
if (typeof additionnalCfg === "object") { $.extend(true, config, additionnalCfg); }
|
||||
arr.forEach(function (el) {
|
||||
if (typeof el !== "string" || !el.trim()) { return; }
|
||||
|
||||
@@ -61,9 +61,7 @@ define([
|
||||
if (!isFile(element)) { return false; }
|
||||
var parsed = Cryptpad.parsePadUrl(element);
|
||||
if (!parsed) { return false; }
|
||||
var hash = parsed.hash;
|
||||
var pHash = Cryptpad.parseHash(hash);
|
||||
if (pHash && !pHash.mode) { return; }
|
||||
var pHash = parsed.hashData;
|
||||
return pHash && pHash.mode === 'view';
|
||||
};
|
||||
|
||||
@@ -541,6 +539,7 @@ define([
|
||||
|
||||
// ADD
|
||||
var add = exp.add = function (data, path) {
|
||||
if (!Cryptpad.isLoggedIn()) { return; }
|
||||
if (!data || typeof(data) !== "object") { return; }
|
||||
var href = data.href;
|
||||
var name = data.title;
|
||||
@@ -576,7 +575,10 @@ define([
|
||||
atime: +new Date(),
|
||||
ctime: +new Date()
|
||||
}, function (err) {
|
||||
if (err) { return void cb(err); }
|
||||
if (err) {
|
||||
logError(err);
|
||||
return void cb(err);
|
||||
}
|
||||
parentEl[fileName] = href;
|
||||
var newPath = filePath.slice();
|
||||
newPath.push(fileName);
|
||||
@@ -598,6 +600,18 @@ define([
|
||||
|
||||
// FORGET (move with href not path)
|
||||
exp.forget = function (href) {
|
||||
if (!Cryptpad.isLoggedIn()) {
|
||||
// delete permanently
|
||||
var data = getFileData(href);
|
||||
if (data) {
|
||||
var i = find([FILES_DATA]).indexOf(data);
|
||||
if (i !== -1) {
|
||||
exp.removePadAttribute(href);
|
||||
spliceFileData(i);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
var paths = findFile(href);
|
||||
move(paths, [TRASH]);
|
||||
};
|
||||
@@ -605,7 +619,7 @@ define([
|
||||
// DELETE
|
||||
// Permanently delete multiple files at once using a list of paths
|
||||
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
|
||||
var removePadAttribute = function (f) {
|
||||
var removePadAttribute = exp.removePadAttribute = function (f) {
|
||||
if (typeof(f) !== 'string') {
|
||||
console.error("Can't find pad attribute for an undefined pad");
|
||||
return;
|
||||
@@ -621,7 +635,7 @@ define([
|
||||
};
|
||||
var checkDeletedFiles = function () {
|
||||
// Nothing in FILES_DATA for workgroups
|
||||
if (workgroup) { return; }
|
||||
if (workgroup || !Cryptpad.isLoggedIn()) { return; }
|
||||
|
||||
var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
|
||||
var toRemove = [];
|
||||
@@ -656,6 +670,23 @@ define([
|
||||
var hrefPaths = paths.filter(function(x) { return isPathIn(x, ['hrefArray']); });
|
||||
var rootPaths = paths.filter(function(x) { return isPathIn(x, [ROOT]); });
|
||||
var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); });
|
||||
var allFilesPaths = paths.filter(function(x) { return isPathIn(x, [FILES_DATA]); });
|
||||
|
||||
if (!Cryptpad.isLoggedIn()) {
|
||||
var toSplice = [];
|
||||
allFilesPaths.forEach(function (path) {
|
||||
var el = find(path);
|
||||
toSplice.push(el);
|
||||
});
|
||||
toSplice.forEach(function (el) {
|
||||
var i = find([FILES_DATA]).indexOf(el);
|
||||
if (i === -1) { return; }
|
||||
removePadAttribute(el.href);
|
||||
console.log(el.href);
|
||||
spliceFileData(i);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var hrefs = [];
|
||||
hrefPaths.forEach(function (path) {
|
||||
@@ -884,7 +915,7 @@ define([
|
||||
toClean.push(el);
|
||||
return;
|
||||
}
|
||||
if (rootFiles.indexOf(el.href) === -1) {
|
||||
if (Cryptpad.isLoggedIn() && rootFiles.indexOf(el.href) === -1) {
|
||||
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", el);
|
||||
var name = el.title || NEW_FILE_NAME;
|
||||
var newName = getAvailableName(root, name);
|
||||
|
||||
@@ -154,6 +154,9 @@ span.fa-folder-open {
|
||||
min-width: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#tree #allfilesTree {
|
||||
margin-top: 0;
|
||||
}
|
||||
#tree #searchContainer {
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
@@ -256,6 +259,9 @@ span.fa-folder-open {
|
||||
margin-left: 10px;
|
||||
float: right;
|
||||
}
|
||||
#content .info-box.noclose {
|
||||
padding-right: 10px;
|
||||
}
|
||||
#content li {
|
||||
cursor: default;
|
||||
}
|
||||
@@ -444,7 +450,7 @@ span.fa-folder-open {
|
||||
#driveToolbar {
|
||||
background: #ddd;
|
||||
color: #555;
|
||||
height: 40px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
border-top: 1px solid #ccc;
|
||||
@@ -452,6 +458,7 @@ span.fa-folder-open {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 100;
|
||||
box-sizing: content-box;
|
||||
padding: 0 6px;
|
||||
/* The container <div> - needed to position the dropdown content */
|
||||
}
|
||||
#driveToolbar .newPadContainer {
|
||||
@@ -459,24 +466,32 @@ span.fa-folder-open {
|
||||
height: 100%;
|
||||
}
|
||||
#driveToolbar button {
|
||||
height: 30px;
|
||||
height: 24px;
|
||||
font: 12px Ubuntu, Arial, sans-serif;
|
||||
}
|
||||
#driveToolbar button span {
|
||||
font: 12px Ubuntu, Arial, sans-serif;
|
||||
}
|
||||
#driveToolbar button .fa,
|
||||
#driveToolbar button.fa {
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
#driveToolbar button.element {
|
||||
border-radius: 2px;
|
||||
background: #888;
|
||||
color: #eee;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
border: 1px solid #888;
|
||||
font-weight: bold;
|
||||
}
|
||||
#driveToolbar button.element:hover {
|
||||
box-shadow: 0px 0px 2px #000;
|
||||
background: #777;
|
||||
}
|
||||
#driveToolbar button.new {
|
||||
padding: 0 5px;
|
||||
}
|
||||
#driveToolbar .dropdown-bar {
|
||||
margin: 5px 5px;
|
||||
margin: 2px 2px;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@@ -510,7 +525,7 @@ span.fa-folder-open {
|
||||
#driveToolbar .path {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
line-height: 30px;
|
||||
cursor: default;
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
@@ -532,7 +547,7 @@ span.fa-folder-open {
|
||||
}
|
||||
#driveToolbar #contextButtonsContainer {
|
||||
float: right;
|
||||
margin: 5px;
|
||||
margin: 2px;
|
||||
}
|
||||
#driveToolbar #contextButtonsContainer button {
|
||||
vertical-align: top;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "../../customize.dist/src/less/variables.less";
|
||||
|
||||
@tree-bg: #fff;
|
||||
@tree-fg: #000;
|
||||
@tree-lines-col: #888;
|
||||
@@ -17,6 +19,8 @@
|
||||
@toolbar-fg: #555;
|
||||
@toolbar-border-col: #ccc;
|
||||
@toolbar-button-bg: #888;
|
||||
@toolbar-button-border: #888;
|
||||
@toolbar-button-bg-hover: #777;
|
||||
@toolbar-button-fg: #eee;
|
||||
@toolbar-path-bg: #fff;
|
||||
@toolbar-path-border: #888;
|
||||
@@ -194,6 +198,9 @@ span {
|
||||
}
|
||||
}
|
||||
}
|
||||
#allfilesTree {
|
||||
margin-top: 0;
|
||||
}
|
||||
#searchContainer {
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
@@ -300,6 +307,9 @@ span {
|
||||
margin-left: 10px;
|
||||
float: right;
|
||||
}
|
||||
&.noclose {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
li {
|
||||
cursor: default;
|
||||
@@ -509,7 +519,7 @@ span {
|
||||
#driveToolbar {
|
||||
background: @toolbar-bg;
|
||||
color: @toolbar-fg;
|
||||
height: 40px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
border-top: 1px solid @toolbar-border-col;
|
||||
@@ -517,6 +527,7 @@ span {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
z-index: 100;
|
||||
box-sizing: content-box;
|
||||
padding: 0 6px;
|
||||
|
||||
.newPadContainer {
|
||||
display: inline-block;
|
||||
@@ -524,16 +535,23 @@ span {
|
||||
}
|
||||
|
||||
button {
|
||||
height: 30px;
|
||||
height: 24px;
|
||||
font: @toolbar-button-font;
|
||||
span {
|
||||
font: @toolbar-button-font;
|
||||
}
|
||||
.fa, &.fa {
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
&.element {
|
||||
border-radius: 2px;
|
||||
background: @toolbar-button-bg;
|
||||
color: @toolbar-button-fg;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
border: 1px solid @toolbar-button-border;
|
||||
font-weight: bold;
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 2px #000;
|
||||
background: @toolbar-button-bg-hover;
|
||||
}
|
||||
}
|
||||
&.new {
|
||||
@@ -542,7 +560,7 @@ span {
|
||||
}
|
||||
/* The container <div> - needed to position the dropdown content */
|
||||
.dropdown-bar {
|
||||
margin: 5px 5px;
|
||||
margin: 2px 2px;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@@ -577,7 +595,7 @@ span {
|
||||
.path {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
line-height: 30px;
|
||||
cursor: default;
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
@@ -599,7 +617,7 @@ span {
|
||||
}
|
||||
#contextButtonsContainer {
|
||||
float: right;
|
||||
margin: 5px;
|
||||
margin: 2px;
|
||||
button {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<li><a tabindex="-1" data-icon="fa-file-code-o" class="newdoc own editable dropdown-item" data-type="code" data-localization="button_newcode">New code</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-powerpoint-o" class="newdoc own editable dropdown-item" data-type="slide" data-localization="button_newslide">New slide</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-calendar" class="newdoc own editable dropdown-item" data-type="poll" data-localization="button_newpoll">New poll</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-calendar" class="newdoc own editable dropdown-item" data-type="whiteboard" data-localization="button_newwhiteboard">New whiteboard</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-paint-brush" class="newdoc own editable dropdown-item" data-type="whiteboard" data-localization="button_newwhiteboard">New whiteboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="defaultContextMenu" class="contextMenu dropdown clearfix">
|
||||
|
||||
@@ -37,7 +37,6 @@ define([
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
var LIMIT_REFRESH_RATE = 30000; // milliseconds
|
||||
var E_OVER_LIMIT = 'E_OVER_LIMIT';
|
||||
|
||||
var SEARCH = "search";
|
||||
@@ -205,7 +204,6 @@ define([
|
||||
var $trashTreeContextMenu = $iframe.find("#trashTreeContextMenu");
|
||||
var $trashContextMenu = $iframe.find("#trashContextMenu");
|
||||
|
||||
|
||||
// TOOLBAR
|
||||
|
||||
/* add a "change username" button */
|
||||
@@ -227,10 +225,19 @@ define([
|
||||
if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); }
|
||||
if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; }
|
||||
|
||||
if (!APP.loggedIn) {
|
||||
displayedCategories = [FILES_DATA];
|
||||
currentPath = [FILES_DATA];
|
||||
$tree.hide();
|
||||
if (Object.keys(files.root).length && !proxy.anonymousAlert) {
|
||||
Cryptpad.alert(Messages.fm_alert_anonymous, null, true);
|
||||
proxy.anonymousAlert = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!APP.readOnly) {
|
||||
setEditable(true);
|
||||
}
|
||||
|
||||
var appStatus = {
|
||||
isReady: true,
|
||||
_onReady: [],
|
||||
@@ -371,6 +378,7 @@ define([
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Arrow keys to modify the selection
|
||||
$(ifrw).keydown(function (e) {
|
||||
var $searchBar = $tree.find('#searchInput');
|
||||
if ($searchBar.is(':focus') && $searchBar.val()) { return; }
|
||||
@@ -381,6 +389,7 @@ define([
|
||||
if (e.ctrlKey) { ev.ctrlKey = true; }
|
||||
if (e.shiftKey) { ev.shiftKey = true; }
|
||||
var click = function (el) {
|
||||
if (!el) { return; }
|
||||
module.onElementClick(ev, $(el));
|
||||
};
|
||||
|
||||
@@ -402,6 +411,7 @@ define([
|
||||
|
||||
// [Left, Up, Right, Down]
|
||||
if ([37, 38, 39, 40].indexOf(e.which) === -1) { return; }
|
||||
e.preventDefault();
|
||||
|
||||
var $selection = $content.find('.element.selected');
|
||||
if ($selection.length === 0) { return void click($elements.first()[0]); }
|
||||
@@ -523,6 +533,10 @@ define([
|
||||
placeholder: name,
|
||||
value: name
|
||||
}).data('path', path);
|
||||
|
||||
// Stop propagation on keydown to avoid issues with arrow keys
|
||||
$input.on('keydown', function (e) { e.stopPropagation(); });
|
||||
|
||||
$input.on('keyup', function (e) {
|
||||
if (e.which === 13) {
|
||||
removeInput(true);
|
||||
@@ -597,7 +611,6 @@ define([
|
||||
}
|
||||
hasFolder = true;
|
||||
hide.push($menu.find('a.open_ro'));
|
||||
// TODO: folder properties in the future?
|
||||
hide.push($menu.find('a.properties'));
|
||||
}
|
||||
// If we're in the trash, hide restore and properties for non-root elements
|
||||
@@ -711,6 +724,21 @@ define([
|
||||
updatePathSize();
|
||||
};
|
||||
|
||||
var scrollTo = function ($element) {
|
||||
// Current scroll position
|
||||
var st = $content.scrollTop();
|
||||
// Block height
|
||||
var h = $content.height();
|
||||
// Current top position of the element relative to the scroll position
|
||||
var pos = Math.round($element.offset().top - $content.position().top);
|
||||
// Element height
|
||||
var eh = $element.outerHeight();
|
||||
// New scroll value
|
||||
var v = st + pos + eh - h;
|
||||
// If the element is completely visile, don't change the scroll position
|
||||
if (pos+eh <= h && pos >= 0) { return; }
|
||||
$content.scrollTop(v);
|
||||
};
|
||||
// Add the "selected" class to the "li" corresponding to the clicked element
|
||||
var onElementClick = module.onElementClick = function (e, $element) {
|
||||
// If "Ctrl" is pressed, do not remove the current selection
|
||||
@@ -726,6 +754,7 @@ define([
|
||||
log(Messages.fm_selectError);
|
||||
return;
|
||||
}
|
||||
scrollTo($element);
|
||||
// Add the selected class to the clicked / right-clicked element
|
||||
// Remove the class if it already has it
|
||||
// If ctrlKey, add to the selection
|
||||
@@ -766,6 +795,7 @@ define([
|
||||
|
||||
var displayMenu = function (e, $menu) {
|
||||
$menu.css({ display: "block" });
|
||||
if (APP.mobile()) { return; }
|
||||
var h = $menu.outerHeight();
|
||||
var w = $menu.outerWidth();
|
||||
var wH = window.innerHeight;
|
||||
@@ -1079,7 +1109,7 @@ define([
|
||||
var type = Messages.type[hrefData.type] || hrefData.type;
|
||||
var $title = $('<span>', {'class': 'title listElement', title: data.title}).text(data.title);
|
||||
var $type = $('<span>', {'class': 'type listElement', title: type}).text(type);
|
||||
if (hrefData.hash && Cryptpad.parseHash(hrefData.hash) && Cryptpad.parseHash(hrefData.hash).mode === 'view') {
|
||||
if (hrefData.hashData && hrefData.hashData.mode === 'view') {
|
||||
$type.append(' (' + Messages.readonly+ ')');
|
||||
}
|
||||
var $adate = $('<span>', {'class': 'atime listElement', title: getDate(data.atime)}).text(getDate(data.atime));
|
||||
@@ -1265,6 +1295,12 @@ define([
|
||||
default:
|
||||
msg = undefined;
|
||||
}
|
||||
if (!APP.loggedIn) {
|
||||
msg = Messages.fm_info_anonymous;
|
||||
$box.html(msg);
|
||||
$box.addClass('noclose');
|
||||
return $box;
|
||||
}
|
||||
if (!msg || Cryptpad.getLSAttribute('hide-info-' + path[0]) === '1') {
|
||||
$box.hide();
|
||||
} else {
|
||||
@@ -1334,6 +1370,10 @@ define([
|
||||
}
|
||||
AppConfig.availablePadTypes.forEach(function (type) {
|
||||
if (type === 'drive') { return; }
|
||||
if (!Cryptpad.isLoggedIn() && AppConfig.registeredOnlyTypes &&
|
||||
AppConfig.registeredOnlyTypes.indexOf(type) !== -1) {
|
||||
return;
|
||||
}
|
||||
var attributes = {
|
||||
'class': 'newdoc',
|
||||
'data-type': type,
|
||||
@@ -1361,8 +1401,11 @@ define([
|
||||
// Handlers
|
||||
if (isInRoot) {
|
||||
var onCreated = function (err, info) {
|
||||
if (err && err === E_OVER_LIMIT) {
|
||||
return void Cryptpad.alert(Messages.pinLimitDrive, null, true);
|
||||
if (err) {
|
||||
if (err === E_OVER_LIMIT) {
|
||||
return void Cryptpad.alert(Messages.pinLimitDrive, null, true);
|
||||
}
|
||||
return void Cryptpad.alert(Messages.fm_error_cantPin);
|
||||
}
|
||||
module.newFolder = info.newPath;
|
||||
refresh();
|
||||
@@ -1372,6 +1415,12 @@ define([
|
||||
});
|
||||
$block.find('a.newdoc').click(function () {
|
||||
var type = $(this).attr('data-type') || 'pad';
|
||||
// We can't create a hash for files before uploading the file
|
||||
if (type === 'file') { // TODO: remove when filename are gone?
|
||||
sessionStorage[Cryptpad.newPadPathKey] = filesOp.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
|
||||
window.open('/' + type + '/');
|
||||
return;
|
||||
}
|
||||
var name = Cryptpad.getDefaultName({type: type});
|
||||
filesOp.addFile(currentPath, name, type, onCreated);
|
||||
});
|
||||
@@ -1732,7 +1781,8 @@ define([
|
||||
if (parentPath) {
|
||||
$a = $('<a>').text(Messages.fm_openParent).click(function (e) {
|
||||
e.preventDefault();
|
||||
parentPath.pop();
|
||||
if (filesOp.isInTrashRoot(parentPath)) { parentPath = [TRASH]; }
|
||||
else { parentPath.pop(); }
|
||||
module.displayDirectory(parentPath);
|
||||
});
|
||||
}
|
||||
@@ -1784,13 +1834,20 @@ define([
|
||||
displayDirectory(parentPath, true);
|
||||
return;
|
||||
}
|
||||
if (!isSearch) { delete APP.Search.oldLocation; }
|
||||
|
||||
module.resetTree();
|
||||
|
||||
// in history mode we want to focus the version number input
|
||||
if (!history.isHistoryMode) { $tree.find('#searchInput').focus(); }
|
||||
$tree.find('#searchInput')[0].selectionStart = getSearchCursor();
|
||||
$tree.find('#searchInput')[0].selectionEnd = getSearchCursor();
|
||||
if (displayedCategories.indexOf(SEARCH) !== -1) {
|
||||
// in history mode we want to focus the version number input
|
||||
if (!history.isHistoryMode && !APP.mobile()) {
|
||||
var st = $tree.scrollTop() || 0;
|
||||
$tree.find('#searchInput').focus();
|
||||
$tree.scrollTop(st);
|
||||
}
|
||||
$tree.find('#searchInput')[0].selectionStart = getSearchCursor();
|
||||
$tree.find('#searchInput')[0].selectionEnd = getSearchCursor();
|
||||
}
|
||||
|
||||
if (!isWorkgroup()) {
|
||||
setLastOpenedFolder(path);
|
||||
@@ -2062,6 +2119,7 @@ define([
|
||||
if (!filesOp.comparePath(newLocation, currentPath.slice())) { displayDirectory(newLocation); }
|
||||
return;
|
||||
}
|
||||
if (APP.mobile()) { return; }
|
||||
search.to = window.setTimeout(function () {
|
||||
if (!isInSearchTmp) { search.oldLocation = currentPath.slice(); }
|
||||
var newLocation = [SEARCH, $input.val()];
|
||||
@@ -2074,12 +2132,14 @@ define([
|
||||
};
|
||||
|
||||
module.resetTree = function () {
|
||||
var s = $tree.scrollTop() || 0;
|
||||
$tree.html('');
|
||||
if (displayedCategories.indexOf(SEARCH) !== -1) { createSearch($tree); }
|
||||
if (displayedCategories.indexOf(ROOT) !== -1) { createTree($tree, [ROOT]); }
|
||||
if (displayedCategories.indexOf(TEMPLATE) !== -1) { createTemplate($tree, [TEMPLATE]); }
|
||||
if (displayedCategories.indexOf(FILES_DATA) !== -1) { createAllFiles($tree, [FILES_DATA]); }
|
||||
if (displayedCategories.indexOf(TRASH) !== -1) { createTrash($tree, [TRASH]); }
|
||||
$tree.scrollTop(s);
|
||||
};
|
||||
|
||||
module.hideMenu = function () {
|
||||
@@ -2124,9 +2184,9 @@ define([
|
||||
var getReadOnlyUrl = APP.getRO = function (href) {
|
||||
if (!filesOp.isFile(href)) { return; }
|
||||
var i = href.indexOf('#') + 1;
|
||||
var hash = href.slice(i);
|
||||
var parsed = Cryptpad.parsePadUrl(href);
|
||||
var base = href.slice(0, i);
|
||||
var hrefsecret = Cryptpad.getSecrets(hash);
|
||||
var hrefsecret = Cryptpad.getSecrets(parsed.type, parsed.hash);
|
||||
if (!hrefsecret.keys) { return; }
|
||||
var viewHash = Cryptpad.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys);
|
||||
return base + viewHash;
|
||||
@@ -2156,15 +2216,18 @@ define([
|
||||
.click(function () { $(this).select(); })
|
||||
.appendTo($d);
|
||||
}
|
||||
var roLink = ro ? base + el : getReadOnlyUrl(base + el);
|
||||
if (roLink) {
|
||||
$('<label>', {'for': 'propROLink'}).text(Messages.viewShare).appendTo($d);
|
||||
$('<input>', {'id': 'propROLink', 'readonly': 'readonly', 'value': roLink})
|
||||
.click(function () { $(this).select(); })
|
||||
.appendTo($d);
|
||||
var parsed = Cryptpad.parsePadUrl(el);
|
||||
if (parsed.hashData && parsed.hashData.type === 'pad') {
|
||||
var roLink = ro ? base + el : getReadOnlyUrl(base + el);
|
||||
if (roLink) {
|
||||
$('<label>', {'for': 'propROLink'}).text(Messages.viewShare).appendTo($d);
|
||||
$('<input>', {'id': 'propROLink', 'readonly': 'readonly', 'value': roLink})
|
||||
.click(function () { $(this).select(); })
|
||||
.appendTo($d);
|
||||
}
|
||||
}
|
||||
|
||||
if (Cryptpad.isLoggedIn() && AppConfig.enablePinning) {
|
||||
if (APP.loggedIn && AppConfig.enablePinning) {
|
||||
// check the size of this file...
|
||||
Cryptpad.getFileSize(el, function (e, bytes) {
|
||||
if (e) {
|
||||
@@ -2279,6 +2342,18 @@ define([
|
||||
else if ($(this).hasClass('delete')) {
|
||||
var pathsList = [];
|
||||
paths.forEach(function (p) { pathsList.push(p.path); });
|
||||
if (!APP.loggedIn) {
|
||||
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]);
|
||||
if (paths.length === 1) {
|
||||
msg = Messages.fm_removePermanentlyDialog;
|
||||
}
|
||||
Cryptpad.confirm(msg, function(res) {
|
||||
$(ifrw).focus();
|
||||
if (!res) { return; }
|
||||
filesOp.delete(pathsList, refresh);
|
||||
});
|
||||
return;
|
||||
}
|
||||
moveElements(pathsList, [TRASH], false, refresh);
|
||||
}
|
||||
else if ($(this).hasClass("properties")) {
|
||||
@@ -2297,9 +2372,12 @@ define([
|
||||
e.stopPropagation();
|
||||
var path = $(this).data('path');
|
||||
var onCreated = function (err, info) {
|
||||
if (err && err === E_OVER_LIMIT) {
|
||||
if (err === E_OVER_LIMIT) {
|
||||
return void Cryptpad.alert(Messages.pinLimitDrive, null, true);
|
||||
}
|
||||
if (err) {
|
||||
return void Cryptpad.alert(Messages.fm_error_cantPin);
|
||||
}
|
||||
module.newFolder = info.newPath;
|
||||
refresh();
|
||||
};
|
||||
@@ -2383,12 +2461,12 @@ define([
|
||||
$appContainer.on('mouseup', function (e) {
|
||||
if (sel.down) { return; }
|
||||
if (e.which !== 1) { return ; }
|
||||
removeSelected(e);
|
||||
module.hideMenu(e);
|
||||
//removeSelected(e);
|
||||
});
|
||||
$appContainer.on('click', function (e) {
|
||||
if (e.which !== 1) { return ; }
|
||||
removeInput();
|
||||
module.hideMenu(e);
|
||||
hideNewButton();
|
||||
});
|
||||
$appContainer.on('drag drop', function (e) {
|
||||
@@ -2401,7 +2479,9 @@ define([
|
||||
$appContainer.on('keydown', function (e) {
|
||||
// "Del"
|
||||
if (e.which === 46) {
|
||||
if (filesOp.isPathIn(currentPath, [FILES_DATA])) { return; } // We can't remove elements directly from filesData
|
||||
if (filesOp.isPathIn(currentPath, [FILES_DATA]) && APP.loggedIn) {
|
||||
return; // We can't remove elements directly from filesData
|
||||
}
|
||||
var $selected = $iframe.find('.selected');
|
||||
if (!$selected.length) { return; }
|
||||
var paths = [];
|
||||
@@ -2411,7 +2491,7 @@ define([
|
||||
paths.push($(elmt).data('path'));
|
||||
});
|
||||
// If we are in the trash or anon pad or if we are holding the "shift" key, delete permanently,
|
||||
if (isTrash || e.shiftKey) {
|
||||
if (!APP.loggedIn || isTrash || e.shiftKey) {
|
||||
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]);
|
||||
if (paths.length === 1) {
|
||||
msg = Messages.fm_removePermanentlyDialog;
|
||||
@@ -2529,7 +2609,6 @@ define([
|
||||
setEditable(!bool);
|
||||
if (!bool && update) {
|
||||
history.onLeaveHistory();
|
||||
//init(); //TODO real proxy here
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2566,7 +2645,7 @@ define([
|
||||
// don't initialize until the store is ready.
|
||||
Cryptpad.ready(function () {
|
||||
Cryptpad.reportAppUsage();
|
||||
if (!Cryptpad.isLoggedIn()) { Cryptpad.feedback('ANONYMOUS_DRIVE'); }
|
||||
if (!APP.loggedIn) { Cryptpad.feedback('ANONYMOUS_DRIVE'); }
|
||||
APP.$bar = $iframe.find('#toolbar');
|
||||
|
||||
var storeObj = Cryptpad.getStore().getProxy && Cryptpad.getStore().getProxy().proxy ? Cryptpad.getStore().getProxy() : undefined;
|
||||
@@ -2578,7 +2657,7 @@ define([
|
||||
}
|
||||
|
||||
var hash = window.location.hash.slice(1) || Cryptpad.getUserHash() || localStorage.FS_hash;
|
||||
var secret = Cryptpad.getSecrets(hash);
|
||||
var secret = Cryptpad.getSecrets('drive', hash);
|
||||
var readOnly = APP.readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
|
||||
var listmapConfig = module.config = {
|
||||
@@ -2643,74 +2722,30 @@ define([
|
||||
}
|
||||
|
||||
/* add the usage */
|
||||
if (AppConfig.enablePinLimit) {
|
||||
var todo = function (err, state, data) {
|
||||
$leftside.html('');
|
||||
if (!data) {
|
||||
return void window.setTimeout(function () {
|
||||
Cryptpad.isOverPinLimit(todo);
|
||||
}, LIMIT_REFRESH_RATE);
|
||||
}
|
||||
var usage = data.usage;
|
||||
var limit = data.limit;
|
||||
var unit = Messages.MB;
|
||||
var $limit = $('<span>', {'class': 'cryptpad-drive-limit'}).appendTo($leftside);
|
||||
var quota = usage/limit;
|
||||
var width = Math.floor(Math.min(quota, 1)*$limit.width());
|
||||
var $usage = $('<span>', {'class': 'usage'}).css('width', width+'px');
|
||||
|
||||
if (quota >= 0.8) {
|
||||
$('<button>', {
|
||||
'class': 'upgrade buttonSuccess',
|
||||
title: Messages.upgradeTitle
|
||||
}).text(Messages.upgrade).click(function () {
|
||||
// TODO
|
||||
}).appendTo($leftside);
|
||||
}
|
||||
|
||||
if (quota < 0.8) { $usage.addClass('normal'); }
|
||||
else if (quota < 1) { $usage.addClass('warning'); }
|
||||
else { $usage.addClass('above'); }
|
||||
var $text = $('<span>', {'class': 'usageText'});
|
||||
$text.text(usage + ' / ' + limit + ' ' + unit);
|
||||
$limit.append($usage).append($text);
|
||||
window.setTimeout(function () {
|
||||
Cryptpad.isOverPinLimit(todo);
|
||||
}, LIMIT_REFRESH_RATE);
|
||||
};
|
||||
Cryptpad.isOverPinLimit(todo);
|
||||
}
|
||||
Cryptpad.createUsageBar(function (err, $limitContainer) {
|
||||
if (err) { return void logError(err); }
|
||||
$leftside.html('');
|
||||
$leftside.append($limitContainer);
|
||||
});
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {};
|
||||
histConfig.onRender = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
var histConfig = {
|
||||
onLocal: function () {
|
||||
proxy.drive = history.currentObj.drive;
|
||||
},
|
||||
onRemote: function () {},
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var obj = JSON.parse(val || '{}');
|
||||
history.currentObj = obj;
|
||||
history.onEnterHistory(obj);
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
logError(e);
|
||||
}
|
||||
},
|
||||
$toolbar: APP.$bar,
|
||||
href: window.location.origin + window.location.pathname + APP.hash
|
||||
};
|
||||
histConfig.onClose = function () {
|
||||
// Close button clicked
|
||||
setHistory(false, true);
|
||||
};
|
||||
histConfig.onRevert = function () {
|
||||
// Revert button clicked
|
||||
setHistory(false, false);
|
||||
proxy.drive = history.currentObj.drive;
|
||||
};
|
||||
histConfig.onReady = function () {
|
||||
// Called when the history is loaded and the UI displayed
|
||||
setHistory(true);
|
||||
};
|
||||
histConfig.$toolbar = APP.$bar;
|
||||
histConfig.href = window.location.origin + window.location.pathname + APP.hash;
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
if (!APP.loggedIn) { $hist.hide(); }
|
||||
|
||||
if (!readOnly && !APP.loggedIn) {
|
||||
var $backupButton = Cryptpad.createButton('', true).removeClass('fa').removeClass('fa-question');
|
||||
|
||||
@@ -8,6 +8,9 @@ define([
|
||||
], function ($, Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var module = window.APP = {
|
||||
TextPatcher: TextPatcher
|
||||
@@ -19,8 +22,9 @@ define([
|
||||
var config = module.config = {
|
||||
initialState: '',
|
||||
websocketURL: Config.websocketURL,
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.key),
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
};
|
||||
|
||||
var setEditable = function (bool) { $textarea.attr('disabled', !bool); };
|
||||
@@ -29,7 +33,8 @@ define([
|
||||
setEditable(false);
|
||||
|
||||
config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
var editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
Cryptpad.replaceHash(editHash);
|
||||
$(window).on('hashchange', function() {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,24 @@ define([
|
||||
var plainChunkLength = 128 * 1024;
|
||||
var cypherChunkLength = 131088;
|
||||
|
||||
var computeEncryptedSize = function (bytes, meta) {
|
||||
var metasize = Nacl.util.decodeUTF8(JSON.stringify(meta)).length;
|
||||
var chunks = Math.ceil(bytes / plainChunkLength);
|
||||
return metasize + 18 + (chunks * 16) + bytes;
|
||||
};
|
||||
|
||||
var encodePrefix = function (p) {
|
||||
return [
|
||||
65280, // 255 << 8
|
||||
255,
|
||||
].map(function (n, i) {
|
||||
return (p & n) >> ((1 - i) * 8);
|
||||
});
|
||||
};
|
||||
var decodePrefix = function (A) {
|
||||
return (A[0] << 8) | A[1];
|
||||
};
|
||||
|
||||
var slice = function (A) {
|
||||
return Array.prototype.slice.call(A);
|
||||
};
|
||||
@@ -40,32 +58,109 @@ define([
|
||||
};
|
||||
|
||||
var joinChunks = function (chunks) {
|
||||
return new Uint8Array(chunks.reduce(function (A, B) {
|
||||
return slice(A).concat(slice(B));
|
||||
}, []));
|
||||
return new Blob(chunks);
|
||||
};
|
||||
|
||||
var padChunk = function (A) {
|
||||
var padding;
|
||||
if (A.length === plainChunkLength) { return A; }
|
||||
if (A.length < plainChunkLength) {
|
||||
padding = new Array(plainChunkLength - A.length).fill(32);
|
||||
return A.concat(padding);
|
||||
}
|
||||
if (A.length > plainChunkLength) {
|
||||
// how many times larger is it?
|
||||
var chunks = Math.ceil(A.length / plainChunkLength);
|
||||
padding = new Array((plainChunkLength * chunks) - A.length).fill(32);
|
||||
return A.concat(padding);
|
||||
}
|
||||
var concatBuffer = function (a, b) { // TODO make this not so ugly
|
||||
return new Uint8Array(slice(a).concat(slice(b)));
|
||||
};
|
||||
|
||||
var decrypt = function (u8, key, cb) {
|
||||
var fetchMetadata = function (src, cb) {
|
||||
var done = false;
|
||||
var CB = function (err, res) {
|
||||
if (done) { return; }
|
||||
done = true;
|
||||
cb(err, res);
|
||||
};
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", src, true);
|
||||
xhr.setRequestHeader('Range', 'bytes=0-1');
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
xhr.onload = function () {
|
||||
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
|
||||
var res = new Uint8Array(xhr.response);
|
||||
var size = decodePrefix(res);
|
||||
var xhr2 = new XMLHttpRequest();
|
||||
|
||||
xhr2.open("GET", src, true);
|
||||
xhr2.setRequestHeader('Range', 'bytes=2-' + (size + 2));
|
||||
xhr2.responseType = 'arraybuffer';
|
||||
xhr2.onload = function () {
|
||||
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
|
||||
var res2 = new Uint8Array(xhr2.response);
|
||||
var all = concatBuffer(res, res2);
|
||||
CB(void 0, all);
|
||||
};
|
||||
xhr2.send(null);
|
||||
};
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
var decryptMetadata = function (u8, key) {
|
||||
var prefix = u8.subarray(0, 2);
|
||||
var metadataLength = decodePrefix(prefix);
|
||||
|
||||
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
|
||||
var metaChunk = Nacl.secretbox.open(metaBox, createNonce(), key);
|
||||
|
||||
try {
|
||||
return JSON.parse(Nacl.util.encodeUTF8(metaChunk));
|
||||
}
|
||||
catch (e) { return null; }
|
||||
};
|
||||
|
||||
var fetchDecryptedMetadata = function (src, key, cb) {
|
||||
if (typeof(src) !== 'string') {
|
||||
return window.setTimeout(function () {
|
||||
cb('NO_SOURCE');
|
||||
});
|
||||
}
|
||||
fetchMetadata(src, function (e, buffer) {
|
||||
if (e) { return cb(e); }
|
||||
cb(void 0, decryptMetadata(buffer, key));
|
||||
});
|
||||
};
|
||||
|
||||
var decrypt = function (u8, key, done, progress) {
|
||||
var MAX = u8.length;
|
||||
var _progress = function (offset) {
|
||||
if (typeof(progress) !== 'function') { return; }
|
||||
progress(Math.min(1, offset / MAX));
|
||||
};
|
||||
|
||||
var nonce = createNonce();
|
||||
var i = 0;
|
||||
|
||||
var takeChunk = function () {
|
||||
var start = i * cypherChunkLength;
|
||||
var prefix = u8.subarray(0, 2);
|
||||
var metadataLength = decodePrefix(prefix);
|
||||
|
||||
var res = {
|
||||
metadata: undefined,
|
||||
};
|
||||
|
||||
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
|
||||
|
||||
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
|
||||
increment(nonce);
|
||||
|
||||
try {
|
||||
res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk));
|
||||
} catch (e) {
|
||||
return window.setTimeout(function () {
|
||||
done('E_METADATA_DECRYPTION');
|
||||
});
|
||||
}
|
||||
|
||||
if (!res.metadata) {
|
||||
return void setTimeout(function () {
|
||||
done('NO_METADATA');
|
||||
});
|
||||
}
|
||||
|
||||
var takeChunk = function (cb) {
|
||||
var start = i * cypherChunkLength + 2 + metadataLength;
|
||||
var end = start + cypherChunkLength;
|
||||
i++;
|
||||
var box = new Uint8Array(u8.subarray(start, end));
|
||||
@@ -73,52 +168,36 @@ define([
|
||||
// decrypt the chunk
|
||||
var plaintext = Nacl.secretbox.open(box, nonce, key);
|
||||
increment(nonce);
|
||||
return plaintext;
|
||||
};
|
||||
|
||||
var buffer = '';
|
||||
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
|
||||
|
||||
var res = {
|
||||
metadata: undefined,
|
||||
};
|
||||
|
||||
// decrypt metadata
|
||||
var chunk;
|
||||
for (; !res.metadata && i * cypherChunkLength < u8.length;) {
|
||||
chunk = takeChunk();
|
||||
buffer += Nacl.util.encodeUTF8(chunk);
|
||||
try {
|
||||
res.metadata = JSON.parse(buffer);
|
||||
//console.log(res.metadata);
|
||||
} catch (e) {
|
||||
console.log('buffering another chunk for metadata');
|
||||
}
|
||||
}
|
||||
|
||||
if (!res.metadata) {
|
||||
return void setTimeout(function () {
|
||||
cb('NO_METADATA');
|
||||
});
|
||||
}
|
||||
|
||||
var fail = function () {
|
||||
cb("DECRYPTION_ERROR");
|
||||
_progress(end);
|
||||
cb(void 0, plaintext);
|
||||
};
|
||||
|
||||
var chunks = [];
|
||||
// decrypt file contents
|
||||
for (;i * cypherChunkLength < u8.length;) {
|
||||
chunk = takeChunk();
|
||||
if (!chunk) {
|
||||
return window.setTimeout(fail);
|
||||
}
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
// send chunks
|
||||
res.content = joinChunks(chunks);
|
||||
var again = function () {
|
||||
takeChunk(function (e, plaintext) {
|
||||
if (e) {
|
||||
return setTimeout(function () {
|
||||
done(e);
|
||||
});
|
||||
}
|
||||
if (plaintext) {
|
||||
if (i * cypherChunkLength < u8.length) { // not done
|
||||
chunks.push(plaintext);
|
||||
return setTimeout(again);
|
||||
}
|
||||
chunks.push(plaintext);
|
||||
res.content = joinChunks(chunks);
|
||||
return done(void 0, res);
|
||||
}
|
||||
done('UNEXPECTED_ENDING');
|
||||
});
|
||||
};
|
||||
|
||||
cb(void 0, res);
|
||||
again();
|
||||
};
|
||||
|
||||
// metadata
|
||||
@@ -130,41 +209,32 @@ define([
|
||||
var metaBuffer = Array.prototype.slice
|
||||
.call(Nacl.util.decodeUTF8(JSON.stringify(metadata)));
|
||||
|
||||
var plaintext = new Uint8Array(padChunk(metaBuffer));
|
||||
var plaintext = new Uint8Array(metaBuffer);
|
||||
|
||||
var j = 0;
|
||||
var i = 0;
|
||||
|
||||
/*
|
||||
0: metadata
|
||||
1: u8
|
||||
2: done
|
||||
*/
|
||||
|
||||
var state = 0;
|
||||
|
||||
var next = function (cb) {
|
||||
if (state === 2) { return void cb(); }
|
||||
|
||||
var start;
|
||||
var end;
|
||||
var part;
|
||||
var box;
|
||||
|
||||
if (state === 0) { // metadata...
|
||||
start = j * plainChunkLength;
|
||||
end = start + plainChunkLength;
|
||||
|
||||
part = plaintext.subarray(start, end);
|
||||
part = new Uint8Array(plaintext);
|
||||
box = Nacl.secretbox(part, nonce, key);
|
||||
increment(nonce);
|
||||
|
||||
j++;
|
||||
|
||||
// metadata is done
|
||||
if (j * plainChunkLength >= plaintext.length) {
|
||||
return void cb(state++, box);
|
||||
if (box.length > 65535) {
|
||||
return void cb('METADATA_TOO_LARGE');
|
||||
}
|
||||
var prefixed = new Uint8Array(encodePrefix(box.length)
|
||||
.concat(slice(box)));
|
||||
state++;
|
||||
|
||||
return void cb(state, box);
|
||||
return void cb(void 0, prefixed);
|
||||
}
|
||||
|
||||
// encrypt the rest of the file...
|
||||
@@ -179,7 +249,7 @@ define([
|
||||
// regular data is done
|
||||
if (i * plainChunkLength >= u8.length) { state = 2; }
|
||||
|
||||
return void cb(state, box);
|
||||
return void cb(void 0, box);
|
||||
};
|
||||
|
||||
return next;
|
||||
@@ -189,5 +259,9 @@ define([
|
||||
decrypt: decrypt,
|
||||
encrypt: encrypt,
|
||||
joinChunks: joinChunks,
|
||||
computeEncryptedSize: computeEncryptedSize,
|
||||
decryptMetadata: decryptMetadata,
|
||||
fetchMetadata: fetchMetadata,
|
||||
fetchDecryptedMetadata: fetchDecryptedMetadata,
|
||||
};
|
||||
});
|
||||
|
||||
118
www/file/file.css
Normal file
118
www/file/file.css
Normal file
@@ -0,0 +1,118 @@
|
||||
html,
|
||||
body {
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
.cryptpad-toolbar {
|
||||
margin-bottom: 1px;
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
#file,
|
||||
#dl {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 2px solid black;
|
||||
}
|
||||
.inputfile {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
#upload-form,
|
||||
#download-form {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
position: relative;
|
||||
width: 50vh;
|
||||
height: 50vh;
|
||||
display: block;
|
||||
margin: 50px auto;
|
||||
max-width: 80vw;
|
||||
}
|
||||
#upload-form label,
|
||||
#download-form label {
|
||||
line-height: calc(50vh - 20px);
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 50vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.hovering {
|
||||
background-color: rgba(255, 0, 115, 0.5) !important;
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.inputfile + label {
|
||||
border: 2px solid black;
|
||||
background-color: rgba(50, 50, 50, 0.1);
|
||||
display: block;
|
||||
}
|
||||
.inputfile:focus + label,
|
||||
.inputfile + label:hover {
|
||||
background-color: rgba(50, 50, 50, 0.3);
|
||||
}
|
||||
#progress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
transition: width 500ms;
|
||||
width: 0%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
background-color: rgba(255, 0, 115, 0.75);
|
||||
z-index: 10000;
|
||||
display: block;
|
||||
}
|
||||
#status {
|
||||
display: none;
|
||||
width: 80vw;
|
||||
margin-top: 50px;
|
||||
margin-left: 10vw;
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#status tr:nth-child(1) {
|
||||
background-color: #ccc;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
#status tr:nth-child(1) td {
|
||||
text-align: center;
|
||||
}
|
||||
#status td {
|
||||
border-left: 1px solid #BBB;
|
||||
border-right: 1px solid #BBB;
|
||||
padding: 0 10px;
|
||||
}
|
||||
#status .upProgress {
|
||||
width: 200px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
#status .progressContainer {
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
left: 5px;
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
background-color: rgba(0, 0, 255, 0.3);
|
||||
}
|
||||
#status .upCancel {
|
||||
text-align: center;
|
||||
}
|
||||
#status .fa.cancel {
|
||||
color: #ff0073;
|
||||
}
|
||||
124
www/file/file.less
Normal file
124
www/file/file.less
Normal file
@@ -0,0 +1,124 @@
|
||||
@import "../../customize.dist/src/less/variables.less";
|
||||
@import "../../customize.dist/src/less/mixins.less";
|
||||
|
||||
@button-border: 2px;
|
||||
|
||||
html, body {
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
.cryptpad-toolbar {
|
||||
margin-bottom: 1px;
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
#file, #dl {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: @button-border solid black;
|
||||
}
|
||||
|
||||
.inputfile {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#upload-form, #download-form {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
position: relative;
|
||||
width: 50vh;
|
||||
height: 50vh;
|
||||
display: block;
|
||||
margin: 50px auto;
|
||||
max-width: 80vw;
|
||||
label {
|
||||
line-height: ~"calc(50vh - 20px)";
|
||||
text-align: center;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 50vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.hovering {
|
||||
background-color: rgba(255, 0, 115, 0.5) !important;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.inputfile + label {
|
||||
border: 2px solid black;
|
||||
background-color: rgba(50, 50, 50, .10);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.inputfile:focus + label,
|
||||
.inputfile + label:hover {
|
||||
background-color: rgba(50, 50, 50, 0.30);
|
||||
}
|
||||
|
||||
#progress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
|
||||
|
||||
transition: width 500ms;
|
||||
width: 0%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
background-color: rgba(255, 0, 115, 0.75);
|
||||
z-index: 10000;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#status {
|
||||
display: none;
|
||||
width: 80vw;
|
||||
margin-top: 50px;
|
||||
margin-left: 10vw;
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
tr:nth-child(1) {
|
||||
background-color: #ccc;
|
||||
border: 1px solid #999;
|
||||
td { text-align: center; }
|
||||
}
|
||||
td {
|
||||
border-left: 1px solid #BBB;
|
||||
border-right: 1px solid #BBB;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.upProgress {
|
||||
width: 200px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
.progressContainer {
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
left: 5px;
|
||||
top: 1px; bottom: 1px;
|
||||
background-color: rgba(0,0,255,0.3);
|
||||
}
|
||||
.upCancel { text-align: center; }
|
||||
.fa.cancel {
|
||||
color: rgb(255, 0, 115);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,52 +5,31 @@
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
}
|
||||
.cryptpad-toolbar {
|
||||
margin-bottom: 1px;
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
#file {
|
||||
display: block;
|
||||
height: 300px;
|
||||
width: 300px;
|
||||
border: 2px solid black;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
.inputfile {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
.inputfile + label {
|
||||
border: 2px solid black;
|
||||
display: block;
|
||||
height: 500px;
|
||||
width: 500px;
|
||||
background-color: rgba(50, 50, 50, .10);
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
.inputfile:focus + label,
|
||||
.inputfile + label:hover {
|
||||
background-color: rgba(50, 50, 50, 0.30);
|
||||
}
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="/file/file.css">
|
||||
<link rel="stylesheet" href="/customize/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="toolbar" class="toolbar-container"></div>
|
||||
<div id="upload-form" style="display: none;">
|
||||
<input type="file" name="file" id="file" class="inputfile" />
|
||||
<label for="file">Choose a file</label>
|
||||
<label for="file" class="block unselectable" data-localization-title="upload_choose"
|
||||
data-localization="upload_choose"></label>
|
||||
</div>
|
||||
<div id="download-form" style="display: none;">
|
||||
<input type="button" name="dl" id="dl" class="inputfile" />
|
||||
<label for="dl" class="block unselectable" data-localization-title="download_button"
|
||||
data-localization="download_button"></label>
|
||||
<span class="block" id="progress"></span>
|
||||
</div>
|
||||
<table id="status" style="display: none;">
|
||||
<tr>
|
||||
<td data-localization="upload_name">File name</td>
|
||||
<td data-localization="upload_size">Size</td>
|
||||
<td data-localization="upload_progress">Progress</td>
|
||||
<td data-localization="cancel">Cancel</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="feedback" class="block hidden">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
388
www/file/main.js
388
www/file/main.js
@@ -2,7 +2,7 @@ define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/toolbar2.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
@@ -20,103 +20,130 @@ define([
|
||||
|
||||
var ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var $iframe = $('#pad-iframe').contents();
|
||||
var $form = $iframe.find('#upload-form');
|
||||
var $dlform = $iframe.find('#download-form');
|
||||
var $label = $form.find('label');
|
||||
var $table = $iframe.find('#status');
|
||||
var $progress = $iframe.find('#progress');
|
||||
|
||||
$iframe.find('body').on('dragover', function (e) { e.preventDefault(); });
|
||||
$iframe.find('body').on('drop', function (e) { e.preventDefault(); });
|
||||
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var fetch = function (src, cb) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", src, true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = function () {
|
||||
return void cb(void 0, new Uint8Array(xhr.response));
|
||||
};
|
||||
xhr.send(null);
|
||||
};
|
||||
var Title;
|
||||
|
||||
var myFile;
|
||||
var myDataType;
|
||||
|
||||
var upload = function (blob, metadata) {
|
||||
var queue = {
|
||||
queue: [],
|
||||
inProgress: false
|
||||
};
|
||||
|
||||
var uid = function () {
|
||||
return 'file-' + String(Math.random()).substring(2);
|
||||
};
|
||||
|
||||
var upload = function (blob, metadata, id) {
|
||||
console.log(metadata);
|
||||
if (queue.inProgress) { return; }
|
||||
queue.inProgress = true;
|
||||
|
||||
var $row = $table.find('tr[id="'+id+'"]');
|
||||
|
||||
$row.find('.upCancel').html('-');
|
||||
var $pv = $row.find('.progressValue');
|
||||
var $pb = $row.find('.progressContainer');
|
||||
|
||||
var updateProgress = function (progressValue) {
|
||||
$pv.text(Math.round(progressValue*100)/100 + '%');
|
||||
$pb.css({
|
||||
width: (progressValue/100)*188+'px'
|
||||
});
|
||||
};
|
||||
|
||||
var u8 = new Uint8Array(blob);
|
||||
|
||||
var key = Nacl.randomBytes(32);
|
||||
var next = FileCrypto.encrypt(u8, metadata, key);
|
||||
|
||||
var chunks = [];
|
||||
var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata);
|
||||
|
||||
var sendChunk = function (box, cb) {
|
||||
var enc = Nacl.util.encodeBase64(box);
|
||||
|
||||
chunks.push(box);
|
||||
Cryptpad.rpc.send('UPLOAD', enc, function (e, msg) {
|
||||
Cryptpad.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
|
||||
console.log(box);
|
||||
cb(e, msg);
|
||||
});
|
||||
};
|
||||
|
||||
var again = function (state, box) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
sendChunk(box, function (e) {
|
||||
if (e) { return console.error(e); }
|
||||
next(again);
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
sendChunk(box, function (e) {
|
||||
if (e) { return console.error(e); }
|
||||
next(again);
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
sendChunk(box, function (e) {
|
||||
if (e) { return console.error(e); }
|
||||
Cryptpad.rpc.send('UPLOAD_COMPLETE', '', function (e, res) {
|
||||
if (e) { return void console.error(e); }
|
||||
var id = res[0];
|
||||
var uri = ['', 'blob', id.slice(0,2), id].join('/');
|
||||
console.log("encrypted blob is now available as %s", uri);
|
||||
var actual = 0;
|
||||
var again = function (err, box) {
|
||||
if (err) { throw new Error(err); }
|
||||
if (box) {
|
||||
actual += box.length;
|
||||
var progressValue = (actual / estimate * 100);
|
||||
updateProgress(progressValue);
|
||||
|
||||
window.location.hash = [
|
||||
'',
|
||||
2,
|
||||
Cryptpad.hexToBase64(id).replace(/\//g, '-'),
|
||||
Nacl.util.encodeBase64(key).replace(/\//g, '-'),
|
||||
''
|
||||
].join('/');
|
||||
|
||||
APP.$form.hide();
|
||||
|
||||
var newU8 = FileCrypto.joinChunks(chunks);
|
||||
FileCrypto.decrypt(newU8, key, function (e, res) {
|
||||
if (e) { return console.error(e); }
|
||||
var title = document.title = res.metadata.name;
|
||||
myFile = res.content;
|
||||
myDataType = res.metadata.type;
|
||||
|
||||
var defaultName = Cryptpad.getDefaultName(Cryptpad.parsePadUrl(window.location.href));
|
||||
APP.updateTitle(title || defaultName);
|
||||
});
|
||||
});
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error("E_INVAL_STATE");
|
||||
return void sendChunk(box, function (e) {
|
||||
if (e) { return console.error(e); }
|
||||
next(again);
|
||||
});
|
||||
}
|
||||
|
||||
if (actual !== estimate) {
|
||||
console.error('Estimated size does not match actual size');
|
||||
}
|
||||
|
||||
// if not box then done
|
||||
Cryptpad.uploadComplete(function (e, id) {
|
||||
if (e) { return void console.error(e); }
|
||||
var uri = ['', 'blob', id.slice(0,2), id].join('/');
|
||||
console.log("encrypted blob is now available as %s", uri);
|
||||
|
||||
var b64Key = Nacl.util.encodeBase64(key);
|
||||
Cryptpad.replaceHash(Cryptpad.getFileHashFromKeys(id, b64Key));
|
||||
|
||||
APP.toolbar.addElement(['fileshare'], {});
|
||||
|
||||
var title = document.title = metadata.name;
|
||||
myFile = blob;
|
||||
myDataType = metadata.type;
|
||||
var defaultName = Cryptpad.getDefaultName(Cryptpad.parsePadUrl(window.location.href));
|
||||
Title.updateTitle(title || defaultName);
|
||||
APP.toolbar.title.show();
|
||||
console.log(title);
|
||||
Cryptpad.alert(Messages._getKey('upload_success', [title]));
|
||||
queue.inProgress = false;
|
||||
queue.next();
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.rpc.send('UPLOAD_STATUS', '', function (e, pending) {
|
||||
Cryptpad.uploadStatus(estimate, function (e, pending) {
|
||||
if (e) {
|
||||
queue.inProgress = false;
|
||||
queue.next();
|
||||
if (e === 'TOO_LARGE') {
|
||||
return void Cryptpad.alert(Messages.upload_tooLarge);
|
||||
}
|
||||
if (e === 'NOT_ENOUGH_SPACE') {
|
||||
return void Cryptpad.alert(Messages.upload_notEnoughSpace);
|
||||
}
|
||||
console.error(e);
|
||||
return void Cryptpad.alert("something went wrong");
|
||||
return void Cryptpad.alert(Messages.upload_serverError);
|
||||
}
|
||||
|
||||
if (pending[0]) {
|
||||
return void Cryptpad.confirm('upload pending, abort?', function (yes) {
|
||||
if (pending) {
|
||||
// TODO keep this message in case of pending files in another window?
|
||||
return void Cryptpad.confirm(Messages.upload_uploadPending, function (yes) {
|
||||
if (!yes) { return; }
|
||||
Cryptpad.rpc.send('UPLOAD_CANCEL', '', function (e, res) {
|
||||
if (e) { return void console.error(e); }
|
||||
Cryptpad.uploadCancel(function (e, res) {
|
||||
if (e) {
|
||||
return void console.error(e);
|
||||
}
|
||||
console.log(res);
|
||||
next(again);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -124,13 +151,52 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var prettySize = function (bytes) {
|
||||
var kB = Cryptpad.bytesToKilobytes(bytes);
|
||||
if (kB < 1024) { return kB + Messages.KB; }
|
||||
var mB = Cryptpad.bytesToMegabytes(bytes);
|
||||
return mB + Messages.MB;
|
||||
};
|
||||
|
||||
queue.next = function () {
|
||||
if (queue.queue.length === 0) { return; }
|
||||
if (queue.inProgress) { return; }
|
||||
var file = queue.queue.shift();
|
||||
upload(file.blob, file.metadata, file.id);
|
||||
};
|
||||
queue.push = function (obj) {
|
||||
var id = uid();
|
||||
obj.id = id;
|
||||
queue.queue.push(obj);
|
||||
|
||||
$table.show();
|
||||
var estimate = FileCrypto.computeEncryptedSize(obj.blob.byteLength, obj.metadata);
|
||||
|
||||
var $progressBar = $('<div>', {'class':'progressContainer'});
|
||||
var $progressValue = $('<span>', {'class':'progressValue'}).text(Messages.upload_pending);
|
||||
|
||||
var $tr = $('<tr>', {id: id}).appendTo($table);
|
||||
|
||||
var $cancel = $('<span>', {'class': 'cancel fa fa-times'}).click(function () {
|
||||
queue.queue = queue.queue.filter(function (el) { return el.id !== id; });
|
||||
$cancel.remove();
|
||||
$tr.find('.upCancel').text('-');
|
||||
$tr.find('.progressValue').text(Messages.upload_cancelled);
|
||||
});
|
||||
|
||||
$('<td>').text(obj.metadata.name).appendTo($tr);
|
||||
$('<td>').text(prettySize(estimate)).appendTo($tr);
|
||||
$('<td>', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr);
|
||||
$('<td>', {'class': 'upCancel'}).append($cancel).appendTo($tr);
|
||||
|
||||
queue.next();
|
||||
};
|
||||
|
||||
var uploadMode = false;
|
||||
|
||||
var andThen = function () {
|
||||
var $bar = $iframe.find('.toolbar-container');
|
||||
|
||||
// Test hash:
|
||||
// #/2/K6xWU-LT9BJHCQcDCT-DcQ/TBo77200c0e-FdldQFcnQx4Y/
|
||||
var secret;
|
||||
var hexFileName;
|
||||
if (window.location.hash) {
|
||||
@@ -141,9 +207,6 @@ define([
|
||||
uploadMode = true;
|
||||
}
|
||||
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
var defaultName = Cryptpad.getDefaultName(parsed);
|
||||
|
||||
var getTitle = function () {
|
||||
var pad = Cryptpad.getRelativeHref(window.location.href);
|
||||
var fo = Cryptpad.getStore().getProxy().fo;
|
||||
@@ -151,107 +214,154 @@ define([
|
||||
return data ? data.title : undefined;
|
||||
};
|
||||
|
||||
var updateTitle = APP.updateTitle = function (newTitle) {
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set pad title");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
document.title = newTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||
});
|
||||
};
|
||||
|
||||
var suggestName = function () {
|
||||
return document.title || getTitle() || '';
|
||||
};
|
||||
|
||||
var renameCb = function (err, title) {
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
var exportFile = function () {
|
||||
var suggestion = document.title;
|
||||
Cryptpad.prompt(Messages.exportPrompt,
|
||||
Cryptpad.fixFileName(suggestion), function (filename) {
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
var blob = new Blob([myFile], {type: myDataType});
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
var filename = Cryptpad.fixFileName(document.title);
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
var blob = new Blob([myFile], {type: myDataType});
|
||||
saveAs(blob, filename);
|
||||
};
|
||||
|
||||
var displayed = ['useradmin', 'newpad', 'limit'];
|
||||
Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
|
||||
|
||||
var displayed = ['title', 'useradmin', 'newpad', 'limit', 'upgrade'];
|
||||
if (secret && hexFileName) {
|
||||
displayed.push('share');
|
||||
displayed.push('fileshare');
|
||||
}
|
||||
|
||||
var configTb = {
|
||||
displayed: displayed,
|
||||
ifrw: ifrw,
|
||||
common: Cryptpad,
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: hexFileName
|
||||
},
|
||||
hideDisplayName: true
|
||||
title: Title.getTitleConfig(),
|
||||
hideDisplayName: true,
|
||||
$container: $bar
|
||||
};
|
||||
Toolbar.create($bar, null, null, null, null, configTb);
|
||||
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
||||
var toolbar = APP.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
if (uploadMode) { toolbar.title.hide(); }
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
var $export = Cryptpad.createButton('export', true, {}, exportFile);
|
||||
$rightside.append($export);
|
||||
|
||||
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
|
||||
Title.updateTitle(Cryptpad.initialName || getTitle() || Title.defaultTitle);
|
||||
|
||||
if (!uploadMode) {
|
||||
$dlform.show();
|
||||
var src = Cryptpad.getBlobPathFromHex(hexFileName);
|
||||
return fetch(src, function (e, u8) {
|
||||
// now decrypt the u8
|
||||
if (e) { return window.alert('error'); }
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var key = Nacl.util.decodeBase64(cryptKey);
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var key = Nacl.util.decodeBase64(cryptKey);
|
||||
|
||||
FileCrypto.decrypt(u8, key, function (e, data) {
|
||||
if (e) {
|
||||
Cryptpad.removeLoadingScreen();
|
||||
return console.error(e);
|
||||
}
|
||||
console.log(data);
|
||||
var title = document.title = data.metadata.name;
|
||||
myFile = data.content;
|
||||
myDataType = data.metadata.type;
|
||||
updateTitle(title || defaultName);
|
||||
Cryptpad.removeLoadingScreen();
|
||||
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
|
||||
if (e) { return void console.error(e); }
|
||||
var title = document.title = metadata.name;
|
||||
Title.updateTitle(title || Title.defaultTitle);
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
var decrypting = false;
|
||||
$dlform.find('#dl, #progress').click(function () {
|
||||
if (decrypting) { return; }
|
||||
if (myFile) { return void exportFile(); }
|
||||
decrypting = true;
|
||||
|
||||
return Cryptpad.fetch(src, function (e, u8) {
|
||||
if (e) {
|
||||
decrypting = false;
|
||||
return void Cryptpad.alert(e);
|
||||
}
|
||||
|
||||
// now decrypt the u8
|
||||
if (!u8 || !u8.length) {
|
||||
return void Cryptpad.errorLoadingScreen(e);
|
||||
}
|
||||
|
||||
FileCrypto.decrypt(u8, key, function (e, data) {
|
||||
if (e) {
|
||||
decrypting = false;
|
||||
return console.error(e);
|
||||
}
|
||||
console.log(data);
|
||||
var title = document.title = data.metadata.name;
|
||||
myFile = data.content;
|
||||
myDataType = data.metadata.type;
|
||||
Title.updateTitle(title || Title.defaultTitle);
|
||||
exportFile();
|
||||
decrypting = false;
|
||||
}, function (progress) {
|
||||
var p = progress * 100 +'%';
|
||||
$progress.width(p);
|
||||
console.error(progress);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Cryptpad.isLoggedIn()) {
|
||||
return Cryptpad.alert("You must be logged in to upload files");
|
||||
return Cryptpad.alert(Messages.upload_mustLogin, function () {
|
||||
if (sessionStorage) {
|
||||
sessionStorage.redirectTo = window.location.href;
|
||||
}
|
||||
window.location.href = '/login/';
|
||||
});
|
||||
}
|
||||
|
||||
var $form = APP.$form = $iframe.find('#upload-form');
|
||||
$form.css({
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
$form.find("#file").on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
var handleFile = function (file) {
|
||||
console.log(file);
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
upload(this.result, {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
queue.push({
|
||||
blob: this.result,
|
||||
metadata: {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
}
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
};
|
||||
|
||||
$form.find("#file").on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
handleFile(file);
|
||||
});
|
||||
|
||||
var counter = 0;
|
||||
$label
|
||||
.on('dragenter', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
counter++;
|
||||
$label.addClass('hovering');
|
||||
})
|
||||
.on('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
counter--;
|
||||
if (counter <= 0) {
|
||||
$label.removeClass('hovering');
|
||||
}
|
||||
});
|
||||
|
||||
$form
|
||||
.on('drag dragstart dragend dragover drop dragenter dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
})
|
||||
.on('drop', function (e) {
|
||||
e.stopPropagation();
|
||||
var dropped = e.originalEvent.dataTransfer.files;
|
||||
counter = 0;
|
||||
$label.removeClass('hovering');
|
||||
handleFile(dropped[0]);
|
||||
});
|
||||
|
||||
// we're in upload mode
|
||||
|
||||
16
www/file/test/index.html
Normal file
16
www/file/test/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp pad">
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
<link rel="icon" type="image/png"
|
||||
href="/customize/main-favicon.png"
|
||||
data-main-favicon="/customize/main-favicon.png"
|
||||
data-alt-favicon="/customize/alt-favicon.png"
|
||||
id="favicon" />
|
||||
<link rel="stylesheet" href="/customize/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
87
www/file/test/main.js
Normal file
87
www/file/test/main.js
Normal file
@@ -0,0 +1,87 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/file/file-crypto.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify, FileCrypto) {
|
||||
var Nacl = window.nacl;
|
||||
$(function () {
|
||||
|
||||
var filesAreSame = function (a, b) {
|
||||
var l = a.length;
|
||||
if (l !== b.length) { return false; }
|
||||
|
||||
var i = 0;
|
||||
for (; i < l; i++) { if (a[i] !== b[i]) { return false; } }
|
||||
return true;
|
||||
};
|
||||
|
||||
var metadataIsSame = function (A, B) {
|
||||
return !Object.keys(A).some(function (k) {
|
||||
return A[k] !== B[k];
|
||||
});
|
||||
};
|
||||
|
||||
var upload = function (blob, metadata) {
|
||||
var u8 = new Uint8Array(blob);
|
||||
var key = Nacl.randomBytes(32);
|
||||
|
||||
var next = FileCrypto.encrypt(u8, metadata, key);
|
||||
|
||||
var chunks = [];
|
||||
var sendChunk = function (box, cb) {
|
||||
chunks.push(box);
|
||||
cb();
|
||||
};
|
||||
|
||||
var again = function (err, box) {
|
||||
if (err) { throw new Error(err); }
|
||||
|
||||
if (box) {
|
||||
return void sendChunk(box, function (e) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
return Cryptpad.alert('Something went wrong');
|
||||
}
|
||||
next(again);
|
||||
});
|
||||
}
|
||||
// check if the uploaded file can be decrypted
|
||||
var newU8 = FileCrypto.joinChunks(chunks);
|
||||
|
||||
console.log('encrypted file with metadata is %s uint8s', newU8.length);
|
||||
FileCrypto.decrypt(newU8, key, function (e, res) {
|
||||
if (e) { return Cryptpad.alert(e); }
|
||||
|
||||
if (filesAreSame(blob, res.content) &&
|
||||
metadataIsSame(res.metadata, metadata)) {
|
||||
Cryptpad.alert("successfully uploaded");
|
||||
} else {
|
||||
Cryptpad.alert('encryption failure!');
|
||||
}
|
||||
});
|
||||
};
|
||||
next(again);
|
||||
};
|
||||
|
||||
var andThen = function () {
|
||||
var src = '/customize/cryptofist_mini.png';
|
||||
Cryptpad.fetch(src, function (e, file) {
|
||||
console.log('original file is %s uint8s', file.length);
|
||||
upload(file, {
|
||||
pew: 'pew',
|
||||
bang: 'bang',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
andThen();
|
||||
|
||||
});
|
||||
});
|
||||
@@ -18,6 +18,7 @@
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span id="user-menu" class="right dropdown-bar"></span>
|
||||
<span id="language-selector" class="right dropdown-bar"></span>
|
||||
<span class="right">
|
||||
<a href="/about.html" data-localization="about">About</a>
|
||||
@@ -61,6 +62,10 @@
|
||||
<input type="text" id="name" name="name" class="form-control" data-localization-placeholder="login_username" autofocus>
|
||||
<input type="password" id="password" name="password" class="form-control" data-localization-placeholder="login_password">
|
||||
<button class="btn btn-primary login first" data-localization="login_login"></button>
|
||||
<div class="extra">
|
||||
<p data-localization="login_notRegistered"></p>
|
||||
<button id="register" class="btn btn-success register first" data-localization="login_register"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,14 @@ define([
|
||||
$sel.find('button').addClass('btn').addClass('btn-secondary');
|
||||
$sel.show();
|
||||
|
||||
// User admin menu
|
||||
var $userMenu = $('#user-menu');
|
||||
var userMenuCfg = {
|
||||
$initBlock: $userMenu
|
||||
};
|
||||
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
|
||||
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
|
||||
|
||||
$(window).click(function () {
|
||||
$('.cryptpad-dropdown').hide();
|
||||
});
|
||||
@@ -57,65 +65,79 @@ define([
|
||||
});
|
||||
|
||||
$('button.login').click(function () {
|
||||
Cryptpad.addLoadingScreen(Messages.login_hashing);
|
||||
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password
|
||||
// setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up
|
||||
window.setTimeout(function () {
|
||||
loginReady(function () {
|
||||
var uname = $uname.val();
|
||||
var passwd = $passwd.val();
|
||||
Login.loginOrRegister(uname, passwd, false, function (err, result) {
|
||||
if (!err) {
|
||||
var proxy = result.proxy;
|
||||
Cryptpad.addLoadingScreen(Messages.login_hashing);
|
||||
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password
|
||||
window.setTimeout(function () {
|
||||
loginReady(function () {
|
||||
var uname = $uname.val();
|
||||
var passwd = $passwd.val();
|
||||
Login.loginOrRegister(uname, passwd, false, function (err, result) {
|
||||
if (!err) {
|
||||
var proxy = result.proxy;
|
||||
|
||||
// successful validation and user already exists
|
||||
// set user hash in localStorage and redirect to drive
|
||||
if (!proxy.login_name) {
|
||||
result.proxy.login_name = result.userName;
|
||||
}
|
||||
// successful validation and user already exists
|
||||
// set user hash in localStorage and redirect to drive
|
||||
if (!proxy.login_name) {
|
||||
result.proxy.login_name = result.userName;
|
||||
}
|
||||
|
||||
proxy.edPrivate = result.edPrivate;
|
||||
proxy.edPublic = result.edPublic;
|
||||
proxy.edPrivate = result.edPrivate;
|
||||
proxy.edPublic = result.edPublic;
|
||||
|
||||
Cryptpad.feedback('LOGIN', true);
|
||||
Cryptpad.whenRealtimeSyncs(result.realtime, function() {
|
||||
Cryptpad.login(result.userHash, result.userName, function () {
|
||||
if (sessionStorage.redirectTo) {
|
||||
var h = sessionStorage.redirectTo;
|
||||
var parser = document.createElement('a');
|
||||
parser.href = h;
|
||||
if (parser.origin === window.location.origin) {
|
||||
delete sessionStorage.redirectTo;
|
||||
window.location.href = h;
|
||||
return;
|
||||
Cryptpad.feedback('LOGIN', true);
|
||||
Cryptpad.whenRealtimeSyncs(result.realtime, function() {
|
||||
Cryptpad.login(result.userHash, result.userName, function () {
|
||||
if (sessionStorage.redirectTo) {
|
||||
var h = sessionStorage.redirectTo;
|
||||
var parser = document.createElement('a');
|
||||
parser.href = h;
|
||||
if (parser.origin === window.location.origin) {
|
||||
delete sessionStorage.redirectTo;
|
||||
window.location.href = h;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.location.href = '/drive/';
|
||||
window.location.href = '/drive/';
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
switch (err) {
|
||||
case 'NO_SUCH_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_noSuchUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_PASS':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalPass);
|
||||
});
|
||||
break;
|
||||
default: // UNHANDLED ERROR
|
||||
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (err) {
|
||||
case 'NO_SUCH_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_noSuchUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_PASS':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalPass);
|
||||
});
|
||||
break;
|
||||
default: // UNHANDLED ERROR
|
||||
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 100);
|
||||
});
|
||||
$('#register').on('click', function () {
|
||||
if (sessionStorage) {
|
||||
if ($uname.val()) {
|
||||
sessionStorage.login_user = $uname.val();
|
||||
}
|
||||
if ($passwd.val()) {
|
||||
sessionStorage.login_pass = $passwd.val();
|
||||
}
|
||||
}
|
||||
window.location.href = '/register/';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
}
|
||||
media-tag * {
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -6,6 +6,8 @@ define([
|
||||
'/common/cryptpad-common.js',
|
||||
//'/common/visible.js',
|
||||
//'/common/notify.js',
|
||||
'pdfjs-dist/build/pdf',
|
||||
'pdfjs-dist/build/pdf.worker',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) {
|
||||
@@ -28,7 +30,7 @@ define([
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var fileId = secret.channel;
|
||||
var hexFileName = Cryptpad.base64ToHex(fileId);
|
||||
var type = "image/png";
|
||||
// var type = "image/png";
|
||||
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
var defaultName = Cryptpad.getDefaultName(parsed);
|
||||
@@ -41,16 +43,9 @@ define([
|
||||
};
|
||||
|
||||
var updateTitle = function (newTitle) {
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set pad title");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
document.title = newTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||
});
|
||||
var title = document.title = newTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(title);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(title);
|
||||
};
|
||||
|
||||
var suggestName = function () {
|
||||
@@ -64,13 +59,27 @@ define([
|
||||
var $mt = $iframe.find('#encryptedFile');
|
||||
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
|
||||
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
|
||||
$mt.attr('data-type', type);
|
||||
// $mt.attr('data-type', type);
|
||||
|
||||
window.onMediaMetadata = function (metadata) {
|
||||
$(window.document).on('decryption', function (e) {
|
||||
var decrypted = e.originalEvent;
|
||||
var metadata = decrypted.metadata;
|
||||
|
||||
if (decrypted.callback) { decrypted.callback(); }
|
||||
//console.log(metadata);
|
||||
//console.log(defaultName);
|
||||
if (!metadata || metadata.name !== defaultName) { return; }
|
||||
var title = document.title = metadata.name;
|
||||
updateTitle(title || defaultName);
|
||||
};
|
||||
})
|
||||
.on('decryptionError', function (e) {
|
||||
var error = e.originalEvent;
|
||||
Cryptpad.alert(error.message);
|
||||
})
|
||||
.on('decryptionProgress', function (e) {
|
||||
var progress = e.originalEvent;
|
||||
console.log(progress.percent);
|
||||
});
|
||||
|
||||
require(['/common/media-tag.js'], function (MediaTag) {
|
||||
var configTb = {
|
||||
@@ -91,6 +100,30 @@ define([
|
||||
|
||||
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
|
||||
|
||||
/**
|
||||
* Allowed mime types that have to be set for a rendering after a decryption.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
var allowedMediaTypes = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
'audio/mp3',
|
||||
'audio/ogg',
|
||||
'audio/wav',
|
||||
'audio/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/webm',
|
||||
'application/pdf',
|
||||
'application/dash+xml',
|
||||
'download'
|
||||
];
|
||||
|
||||
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
|
||||
|
||||
MediaTag($mt[0]);
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
<link rel="icon" type="image/png"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
}
|
||||
#cke_1_top {
|
||||
overflow: visible;
|
||||
padding: 0 6px;
|
||||
}
|
||||
#cke_1_toolbox {
|
||||
display: inline-block;
|
||||
|
||||
151
www/pad/main.js
151
www/pad/main.js
@@ -11,14 +11,11 @@ define([
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/pad/links.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'/bower_components/diff-dom/diffDOM.js'
|
||||
], function ($, Crypto, realtimeInput, Hyperjson,
|
||||
Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget,
|
||||
Visible, Notify, Links) {
|
||||
Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget, Links) {
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
@@ -105,8 +102,6 @@ define([
|
||||
editor.on('instanceReady', Links.addSupportForOpeningLinksInNewTab(Ckeditor));
|
||||
editor.on('instanceReady', function () {
|
||||
var $bar = $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox');
|
||||
var parsedHash = Cryptpad.parsePadUrl(window.location.href);
|
||||
var defaultName = Cryptpad.getDefaultName(parsedHash);
|
||||
|
||||
var isHistoryMode = false;
|
||||
|
||||
@@ -277,7 +272,10 @@ define([
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var getHeadingText = function () {
|
||||
var text;
|
||||
@@ -290,14 +288,6 @@ define([
|
||||
})) { return text; }
|
||||
};
|
||||
|
||||
var suggestName = function (fallback) {
|
||||
if (document.title === defaultName) {
|
||||
return getHeadingText() || fallback || "";
|
||||
} else {
|
||||
return document.title || getHeadingText() || defaultName;
|
||||
}
|
||||
};
|
||||
|
||||
var DD = new DiffDom(diffOptions);
|
||||
|
||||
// apply patches, and try not to lose the cursor in the process!
|
||||
@@ -316,11 +306,11 @@ define([
|
||||
hjson[3] = {
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
defaultTitle: defaultName
|
||||
defaultTitle: Title.defaultTitle
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
hjson[3].metadata.title = document.title;
|
||||
hjson[3].metadata.title = Title.title;
|
||||
} else if (Cryptpad.initialName && !hjson[3].metadata.title) {
|
||||
hjson[3].metadata.title = Cryptpad.initialName;
|
||||
}
|
||||
@@ -369,68 +359,6 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var updateTitle = function (newTitle) {
|
||||
if (newTitle === document.title) { return; }
|
||||
// Change the title now, and set it back to the old value if there is an error
|
||||
var oldTitle = document.title;
|
||||
document.title = newTitle;
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set pad title");
|
||||
console.error(err);
|
||||
document.title = oldTitle;
|
||||
return;
|
||||
}
|
||||
document.title = data;
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||
});
|
||||
};
|
||||
|
||||
var updateDefaultTitle = function (defaultTitle) {
|
||||
defaultName = defaultTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName);
|
||||
};
|
||||
|
||||
var updateMetadata = function(shjson) {
|
||||
// Extract the user list (metadata) from the hyperjson
|
||||
if (!shjson || typeof (shjson) !== "string") { updateTitle(defaultName); return; }
|
||||
var hjson = JSON.parse(shjson);
|
||||
var peerMetadata = hjson[3];
|
||||
var titleUpdated = false;
|
||||
if (peerMetadata && peerMetadata.metadata) {
|
||||
if (peerMetadata.metadata.users) {
|
||||
var userData = peerMetadata.metadata.users;
|
||||
// Update the local user data
|
||||
UserList.addToUserData(userData);
|
||||
}
|
||||
if (peerMetadata.metadata.defaultTitle) {
|
||||
updateDefaultTitle(peerMetadata.metadata.defaultTitle);
|
||||
}
|
||||
if (typeof peerMetadata.metadata.title !== "undefined") {
|
||||
updateTitle(peerMetadata.metadata.title || defaultName);
|
||||
titleUpdated = true;
|
||||
}
|
||||
}
|
||||
if (!titleUpdated) {
|
||||
updateTitle(defaultName);
|
||||
}
|
||||
};
|
||||
|
||||
var unnotify = function () {
|
||||
if (module.tabNotification &&
|
||||
typeof(module.tabNotification.cancel) === 'function') {
|
||||
module.tabNotification.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
var notify = function () {
|
||||
if (Visible.isSupported() && !Visible.currently()) {
|
||||
unnotify();
|
||||
module.tabNotification = Notify.tab(1000, 10);
|
||||
}
|
||||
};
|
||||
|
||||
realtimeOptions.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
@@ -443,7 +371,7 @@ define([
|
||||
cursor.update();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(shjson);
|
||||
Metadata.update(shjson);
|
||||
|
||||
var newInner = JSON.parse(shjson);
|
||||
var newSInner;
|
||||
@@ -488,7 +416,7 @@ define([
|
||||
// Notify only when the content has changed, not when someone has joined/left
|
||||
var oldSInner = stringify(JSON.parse(oldShjson)[2]);
|
||||
if (newSInner && newSInner !== oldSInner) {
|
||||
notify();
|
||||
Cryptpad.notify();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -502,7 +430,7 @@ define([
|
||||
|
||||
var exportFile = function () {
|
||||
var html = getHTML();
|
||||
var suggestion = suggestName('cryptpad-document');
|
||||
var suggestion = Title.suggestTitle('cryptpad-document');
|
||||
Cryptpad.prompt(Messages.exportPrompt,
|
||||
Cryptpad.fixFileName(suggestion) + '.html', function (filename) {
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
@@ -516,27 +444,22 @@ define([
|
||||
realtimeOptions.onLocal();
|
||||
};
|
||||
|
||||
var renameCb = function (err, title) {
|
||||
if (err) { return; }
|
||||
document.title = title;
|
||||
editor.fire('change');
|
||||
};
|
||||
|
||||
realtimeOptions.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, realtimeOptions.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
var titleCfg = { getHeadingText: getHeadingText };
|
||||
Title = Cryptpad.createTitle(titleCfg, realtimeOptions.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'],
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
@@ -546,6 +469,8 @@ define([
|
||||
};
|
||||
toolbar = info.realtime.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
var editHash;
|
||||
@@ -574,31 +499,13 @@ define([
|
||||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {};
|
||||
histConfig.onRender = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
applyHjson(val || '["BODY",{},[]]');
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
console.error(e);
|
||||
}
|
||||
var histConfig = {
|
||||
onLocal: realtimeOptions.onLocal,
|
||||
onRemote: realtimeOptions.onRemote,
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) { applyHjson(val || '["BODY",{},[]]'); },
|
||||
$toolbar: $bar
|
||||
};
|
||||
histConfig.onClose = function () {
|
||||
// Close button clicked
|
||||
setHistory(false, true);
|
||||
};
|
||||
histConfig.onRevert = function () {
|
||||
// Revert button clicked
|
||||
setHistory(false, false);
|
||||
realtimeOptions.onLocal();
|
||||
realtimeOptions.onRemote();
|
||||
};
|
||||
histConfig.onReady = function () {
|
||||
// Called when the history is loaded and the UI displayed
|
||||
setHistory(true);
|
||||
};
|
||||
histConfig.$toolbar = $bar;
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
@@ -666,13 +573,7 @@ define([
|
||||
applyHjson(shjson);
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(shjson);
|
||||
|
||||
if (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) {
|
||||
if (yes) { unnotify(); }
|
||||
});
|
||||
}
|
||||
Metadata.update(shjson);
|
||||
|
||||
if (!readOnly) {
|
||||
var shjson2 = stringifyDOM(inner);
|
||||
@@ -686,7 +587,7 @@ define([
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateTitle(Cryptpad.initialName || defaultName);
|
||||
Title.updateTitle(Cryptpad.initialName || Title.defaultTitle);
|
||||
documentBody.innerHTML = Messages.initialState;
|
||||
}
|
||||
|
||||
|
||||
154
www/poll/main.js
154
www/poll/main.js
@@ -8,17 +8,13 @@ define([
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'render.js',
|
||||
'/common/toolbar2.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar, Visible, Notify) {
|
||||
], function ($, TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar) {
|
||||
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
$(function () {
|
||||
|
||||
var unlockHTML = '<i class="fa fa-unlock" aria-hidden="true"></i>';
|
||||
var lockHTML = '<i class="fa fa-lock" aria-hidden="true"></i>';
|
||||
var HIDE_INTRODUCTION_TEXT = "hide_poll_text";
|
||||
var defaultName;
|
||||
|
||||
@@ -34,7 +30,6 @@ define([
|
||||
if (!DEBUG) {
|
||||
debug = function() {};
|
||||
}
|
||||
var error = console.error;
|
||||
|
||||
Cryptpad.addLoadingScreen();
|
||||
var onConnectError = function () {
|
||||
@@ -103,12 +98,10 @@ define([
|
||||
// Enable the checkboxes for the user's column (committed or not)
|
||||
$('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled');
|
||||
$('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled');
|
||||
$('[data-rt-id="' + id + '"] ~ .edit').css('visibility', 'hidden');
|
||||
$('.lock[data-rt-id="' + id + '"]').html(unlockHTML);
|
||||
$('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked);
|
||||
|
||||
if (isOwnColumnCommitted()) { return; }
|
||||
$('[data-rt-id^="' + id + '"]').closest('td').addClass("uncommitted");
|
||||
$('td.uncommitted .remove, td.uncommitted .edit').css('visibility', 'hidden');
|
||||
$('td.uncommitted .cover').addClass("uncommitted");
|
||||
$('.uncommitted input[type="text"]').attr("placeholder", Messages.poll_userPlaceholder);
|
||||
};
|
||||
@@ -121,8 +114,7 @@ define([
|
||||
APP.editable.col.forEach(function (id) {
|
||||
$('input[disabled="disabled"][data-rt-id^="' + id + '"]').removeAttr('disabled');
|
||||
$('input[type="checkbox"][data-rt-id^="' + id + '"]').addClass('enabled');
|
||||
$('span.edit[data-rt-id="' + id + '"]').css('visibility', 'hidden');
|
||||
$('.lock[data-rt-id="' + id + '"]').html(unlockHTML);
|
||||
$('.lock[data-rt-id="' + id + '"]').addClass('fa-unlock').removeClass('fa-lock').attr('title', Messages.poll_unlocked);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -186,20 +178,6 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var unnotify = function () {
|
||||
if (APP.tabNotification &&
|
||||
typeof(APP.tabNotification.cancel) === 'function') {
|
||||
APP.tabNotification.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
var notify = function () {
|
||||
if (Visible.isSupported() && !Visible.currently()) {
|
||||
unnotify();
|
||||
APP.tabNotification = Notify.tab(1000, 10);
|
||||
}
|
||||
};
|
||||
|
||||
/* Any time the realtime object changes, call this function */
|
||||
var change = function (o, n, path, throttle, cb) {
|
||||
if (path && !Cryptpad.isArray(path)) {
|
||||
@@ -228,7 +206,7 @@ define([
|
||||
|
||||
https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
|
||||
*/
|
||||
notify();
|
||||
Cryptpad.notify();
|
||||
|
||||
var getFocus = function () {
|
||||
var active = document.activeElement;
|
||||
@@ -293,7 +271,6 @@ define([
|
||||
switch (type) {
|
||||
case 'text':
|
||||
debug("text[rt-id='%s'] [%s]", id, input.value);
|
||||
if (!input.value) { return void debug("Hit enter?"); }
|
||||
Render.setValue(object, id, input.value);
|
||||
change(null, null, null, 50);
|
||||
break;
|
||||
@@ -312,12 +289,26 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var hideInputs = function (target, isKeyup) {
|
||||
if (!isKeyup && $(target).is('[type="text"]')) {
|
||||
return;
|
||||
}
|
||||
$('.lock[data-rt-id!="' + APP.userid + '"]').addClass('fa-lock').removeClass('fa-unlock').attr('title', Messages.poll_locked);
|
||||
var $cells = APP.$table.find('thead td:not(.uncommitted), tbody td');
|
||||
$cells.find('[type="text"][data-rt-id!="' + APP.userid + '"]').attr('disabled', true);
|
||||
$('.edit[data-rt-id!="' + APP.userid + '"]').css('visibility', 'visible');
|
||||
APP.editable.col = [APP.userid];
|
||||
APP.editable.row = [];
|
||||
};
|
||||
|
||||
/* Called whenever an event is fired on a span */
|
||||
var handleSpan = function (span) {
|
||||
var id = span.getAttribute('data-rt-id');
|
||||
var type = Render.typeofId(id);
|
||||
var isRemove = span.className && span.className.split(' ').indexOf('remove') !== -1;
|
||||
var isEdit = span.className && span.className.split(' ').indexOf('edit') !== -1;
|
||||
var isLock = span.className && span.className.split(' ').indexOf('lock') !== -1;
|
||||
var isLocked = span.className && span.className.split(' ').indexOf('fa-lock') !== -1;
|
||||
if (type === 'row') {
|
||||
if (isRemove) {
|
||||
Cryptpad.confirm(Messages.poll_removeOption, function (res) {
|
||||
@@ -327,6 +318,7 @@ define([
|
||||
});
|
||||
});
|
||||
} else if (isEdit) {
|
||||
hideInputs(span);
|
||||
unlockRow(id, function () {
|
||||
change(null, null, null, null, function() {
|
||||
$('input[data-rt-id="' + id + '"]').focus();
|
||||
@@ -341,7 +333,8 @@ define([
|
||||
change();
|
||||
});
|
||||
});
|
||||
} else if (isEdit) {
|
||||
} else if (isLock && isLocked) {
|
||||
hideInputs(span);
|
||||
unlockColumn(id, function () {
|
||||
change(null, null, null, null, function() {
|
||||
$('input[data-rt-id="' + id + '"]').focus();
|
||||
@@ -355,48 +348,34 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var hideInputs = function (e, isKeyup) {
|
||||
if (!isKeyup && $(e.target).is('[type="text"]')) {
|
||||
return;
|
||||
}
|
||||
$('.lock[data-rt-id!="' + APP.userid + '"]').html(lockHTML);
|
||||
var $cells = APP.$table.find('thead td:not(.uncommitted), tbody td');
|
||||
$cells.find('[type="text"][data-rt-id!="' + APP.userid + '"]').attr('disabled', true);
|
||||
$('.edit[data-rt-id!="' + APP.userid + '"]').css('visibility', 'visible');
|
||||
APP.editable.col = [APP.userid];
|
||||
APP.editable.row = [];
|
||||
};
|
||||
|
||||
$(window).click(hideInputs);
|
||||
|
||||
var handleClick = function (e, isKeyup) {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!APP.ready) { return; }
|
||||
var target = e && e.target;
|
||||
|
||||
if (isKeyup) {
|
||||
debug("Keyup!");
|
||||
}
|
||||
|
||||
if (!target) { return void debug("NO TARGET"); }
|
||||
|
||||
var nodeName = target && target.nodeName;
|
||||
var shouldLock = $(target).hasClass('fa-unlock');
|
||||
|
||||
if (!$(target).parents('#table tbody').length || $(target).hasClass('edit')) {
|
||||
if ((!$(target).parents('#table tbody').length && $(target).hasClass('lock'))) {
|
||||
hideInputs(e);
|
||||
}
|
||||
|
||||
switch (nodeName) {
|
||||
case 'INPUT':
|
||||
if (isKeyup && (e.keyCode === 13 || e.keyCode === 27)) {
|
||||
hideInputs(e, isKeyup);
|
||||
return;
|
||||
hideInputs(target, isKeyup);
|
||||
break;
|
||||
}
|
||||
handleInput(target);
|
||||
break;
|
||||
case 'SPAN':
|
||||
//case 'LABEL':
|
||||
if (shouldLock) {
|
||||
break;
|
||||
}
|
||||
handleSpan(target);
|
||||
break;
|
||||
case undefined:
|
||||
@@ -442,43 +421,9 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
|
||||
var updateTitle = function (newTitle) {
|
||||
if (newTitle === document.title) { return; }
|
||||
// Change the title now, and set it back to the old value if there is an error
|
||||
var oldTitle = document.title;
|
||||
document.title = newTitle;
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
debug("Couldn't set pad title");
|
||||
error(err);
|
||||
document.title = oldTitle;
|
||||
return;
|
||||
}
|
||||
document.title = data;
|
||||
APP.$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||
APP.$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||
});
|
||||
};
|
||||
|
||||
var updateDefaultTitle = function (defaultTitle) {
|
||||
defaultName = defaultTitle;
|
||||
APP.$bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName);
|
||||
};
|
||||
var renameCb = function (err, title) {
|
||||
if (err) { return; }
|
||||
document.title = title;
|
||||
APP.proxy.info.title = title === defaultName ? "" : title;
|
||||
};
|
||||
|
||||
var suggestName = function (fallback) {
|
||||
if (document.title === defaultName) {
|
||||
return fallback || "";
|
||||
}
|
||||
return document.title || defaultName || "";
|
||||
};
|
||||
|
||||
var copyObject = function (obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
};
|
||||
@@ -510,7 +455,6 @@ var ready = function (info, userid, readOnly) {
|
||||
|
||||
var $table = APP.$table = $(Render.asHTML(displayedObj, null, colsOrder, readOnly));
|
||||
APP.$createRow = $('#create-option').click(function () {
|
||||
//console.error("BUTTON CLICKED! LOL");
|
||||
Render.createRow(proxy, function (empty, id) {
|
||||
change(null, null, null, null, function() {
|
||||
$('.edit[data-rt-id="' + id + '"]').click();
|
||||
@@ -521,7 +465,7 @@ var ready = function (info, userid, readOnly) {
|
||||
APP.$createCol = $('#create-user').click(function () {
|
||||
Render.createColumn(proxy, function (empty, id) {
|
||||
change(null, null, null, null, function() {
|
||||
$('.edit[data-rt-id="' + id + '"]').click();
|
||||
$('.lock[data-rt-id="' + id + '"]').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -550,15 +494,15 @@ var ready = function (info, userid, readOnly) {
|
||||
|
||||
// Title
|
||||
if (APP.proxy.info.defaultTitle) {
|
||||
updateDefaultTitle(APP.proxy.info.defaultTitle);
|
||||
Title.updateDefaultTitle(APP.proxy.info.defaultTitle);
|
||||
} else {
|
||||
APP.proxy.info.defaultTitle = defaultName;
|
||||
APP.proxy.info.defaultTitle = Title.defaultTitle;
|
||||
}
|
||||
if (Cryptpad.initialName && !APP.proxy.info.title) {
|
||||
APP.proxy.info.title = Cryptpad.initialName;
|
||||
updateTitle(Cryptpad.initialName);
|
||||
Title.updateTitle(Cryptpad.initialName);
|
||||
} else {
|
||||
updateTitle(APP.proxy.info.title || defaultName);
|
||||
Title.updateTitle(APP.proxy.info.title || Title.defaultTitle);
|
||||
}
|
||||
|
||||
// Description
|
||||
@@ -583,11 +527,13 @@ var ready = function (info, userid, readOnly) {
|
||||
.click(handleClick)
|
||||
.on('keyup', function (e) { handleClick(e, true); });
|
||||
|
||||
$(window).click(hideInputs);
|
||||
|
||||
proxy
|
||||
.on('change', ['info'], function (o, n, p) {
|
||||
if (p[1] === 'title') {
|
||||
updateTitle(n);
|
||||
notify();
|
||||
Title.updateTitle(n);
|
||||
Cryptpad.notify();
|
||||
} else if (p[1] === "userData") {
|
||||
UserList.addToUserData(APP.proxy.info.userData);
|
||||
} else if (p[1] === 'description') {
|
||||
@@ -602,7 +548,7 @@ var ready = function (info, userid, readOnly) {
|
||||
el.selectionStart = selects[0];
|
||||
el.selectionEnd = selects[1];
|
||||
}
|
||||
notify();
|
||||
Cryptpad.notify();
|
||||
}
|
||||
|
||||
debug("change: (%s, %s, [%s])", o, n, p.join(', '));
|
||||
@@ -612,13 +558,6 @@ var ready = function (info, userid, readOnly) {
|
||||
|
||||
UserList.addToUserData(APP.proxy.info.userData);
|
||||
|
||||
if (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) {
|
||||
if (yes) { unnotify(); }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
APP.ready = true;
|
||||
if (!proxy.published) {
|
||||
publish(false);
|
||||
@@ -664,18 +603,19 @@ var create = function (info) {
|
||||
};
|
||||
UserList = Cryptpad.createUserList(info, onLocal, Cryptget, Cryptpad);
|
||||
|
||||
var onLocalTitle = function () {
|
||||
APP.proxy.info.title = Title.isDefaultTitle() ? "" : Title.title;
|
||||
};
|
||||
Title = Cryptpad.createTitle({}, onLocalTitle, Cryptpad);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'],
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: window,
|
||||
@@ -685,6 +625,8 @@ var create = function (info) {
|
||||
};
|
||||
APP.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(APP.toolbar);
|
||||
|
||||
var $rightside = APP.toolbar.$rightside;
|
||||
|
||||
/* add a forget button */
|
||||
|
||||
@@ -32,6 +32,9 @@ textarea[disabled] {
|
||||
font: white;
|
||||
border: 0px;
|
||||
}
|
||||
input[type="text"]::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
table#table {
|
||||
margin: 0px;
|
||||
}
|
||||
@@ -66,7 +69,7 @@ table#table {
|
||||
#tableScroll {
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
margin-left: calc(30% - 50px + 29px);
|
||||
margin-left: calc(30% - 50px + 31px);
|
||||
max-width: 70%;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
@@ -104,6 +107,9 @@ table {
|
||||
tbody {
|
||||
border: 1px solid #555;
|
||||
}
|
||||
tbody * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
tbody tr {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -260,23 +266,37 @@ div.realtime table input[type="text"] {
|
||||
border: 1px solid #fff;
|
||||
width: 80%;
|
||||
}
|
||||
form.realtime table span,
|
||||
div.realtime table span {
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
form.realtime table thead td,
|
||||
div.realtime table thead td {
|
||||
padding: 0px 5px;
|
||||
background: #aaa;
|
||||
border-radius: 20px 20px 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
form.realtime table thead td:nth-of-type(2),
|
||||
div.realtime table thead td:nth-of-type(2) {
|
||||
background: #999;
|
||||
}
|
||||
form.realtime table thead td:nth-of-type(2) .lock,
|
||||
div.realtime table thead td:nth-of-type(2) .lock {
|
||||
cursor: default;
|
||||
}
|
||||
form.realtime table thead td input[type="text"],
|
||||
div.realtime table thead td input[type="text"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
form.realtime table thead td input[type="text"][disabled],
|
||||
div.realtime table thead td input[type="text"][disabled] {
|
||||
color: #000;
|
||||
padding: 1px 5px;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
form.realtime table tbody .text-cell,
|
||||
div.realtime table tbody .text-cell {
|
||||
@@ -296,9 +316,9 @@ div.realtime table tbody .text-cell .remove {
|
||||
float: left;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
form.realtime table tbody td label,
|
||||
div.realtime table tbody td label {
|
||||
border: 0.5px solid #555;
|
||||
form.realtime table tbody tr:not(:first-child) td:not(:first-child) label,
|
||||
div.realtime table tbody tr:not(:first-child) td:not(:first-child) label {
|
||||
border-top: 1px solid #555;
|
||||
}
|
||||
form.realtime table .edit,
|
||||
div.realtime table .edit {
|
||||
@@ -307,6 +327,13 @@ div.realtime table .edit {
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
form.realtime table .lock,
|
||||
div.realtime table .lock {
|
||||
margin-left: calc(50% - 0.5em);
|
||||
cursor: pointer;
|
||||
width: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
form.realtime table .remove,
|
||||
div.realtime table .remove {
|
||||
float: right;
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
@import "../../customize.dist/src/less/mixins.less";
|
||||
|
||||
@poll-th-bg: #aaa;
|
||||
@poll-th-user-bg: #999;
|
||||
@poll-td-bg: #aaa;
|
||||
@poll-placeholder: #666;
|
||||
@poll-border-color: #555;
|
||||
@poll-cover-color: #000;
|
||||
@poll-fg: #000;
|
||||
@@ -42,6 +44,13 @@ input[type="text"][disabled], textarea[disabled] {
|
||||
font: white;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
// The placeholder color only seems to effect Safari when not set
|
||||
|
||||
input[type="text"]::placeholder {
|
||||
color: @poll-placeholder;
|
||||
}
|
||||
|
||||
table#table {
|
||||
margin: 0px;
|
||||
}
|
||||
@@ -75,7 +84,7 @@ table#table {
|
||||
#tableScroll {
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
margin-left: calc(~"30% - 50px + 29px");
|
||||
margin-left: calc(~"30% - 50px + 31px");
|
||||
max-width: 70%;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
@@ -118,6 +127,9 @@ table {
|
||||
}
|
||||
tbody {
|
||||
border: 1px solid @poll-border-color;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
tr {
|
||||
text-align: center;
|
||||
&:first-of-type th{
|
||||
@@ -282,20 +294,32 @@ form.realtime, div.realtime {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
span {
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
thead {
|
||||
td {
|
||||
padding: 0px 5px;
|
||||
background: @poll-th-bg;
|
||||
border-radius: 20px 20px 0 0;
|
||||
text-align: center;
|
||||
//text-align: center;
|
||||
&:nth-of-type(2) {
|
||||
background: @poll-th-user-bg;
|
||||
.lock {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
input {
|
||||
&[type="text"] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 1px 5px;
|
||||
&[disabled] {
|
||||
color: @poll-fg;
|
||||
padding: 1px 5px;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,9 +342,11 @@ form.realtime, div.realtime {
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
td {
|
||||
label {
|
||||
border: .5px solid @poll-border-color;
|
||||
tr:not(:first-child) {
|
||||
td:not(:first-child) {
|
||||
label {
|
||||
border-top: 1px solid @poll-border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,6 +357,12 @@ form.realtime, div.realtime {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.lock {
|
||||
margin-left: ~"calc(50% - 0.5em)";
|
||||
cursor: pointer;
|
||||
width: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
.remove {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
|
||||
@@ -252,6 +252,7 @@ var Renderer = function (Cryptpad) {
|
||||
var makeRemoveElement = Render.makeRemoveElement = function (id) {
|
||||
return ['SPAN', {
|
||||
'data-rt-id': id,
|
||||
'title': Cryptpad.Messages.poll_remove,
|
||||
class: 'remove',
|
||||
}, ['✖']];
|
||||
};
|
||||
@@ -259,6 +260,7 @@ var Renderer = function (Cryptpad) {
|
||||
var makeEditElement = Render.makeEditElement = function (id) {
|
||||
return ['SPAN', {
|
||||
'data-rt-id': id,
|
||||
'title': Cryptpad.Messages.poll_edit,
|
||||
class: 'edit',
|
||||
}, ['✐']];
|
||||
};
|
||||
@@ -266,25 +268,18 @@ var Renderer = function (Cryptpad) {
|
||||
var makeLockElement = Render.makeLockElement = function (id) {
|
||||
return ['SPAN', {
|
||||
'data-rt-id': id,
|
||||
class: 'lock',
|
||||
}, [['i', {
|
||||
class: 'fa fa-lock',
|
||||
'aria-hidden': true,
|
||||
}, []]
|
||||
]];
|
||||
'title': Cryptpad.Messages.poll_locked,
|
||||
class: 'lock fa fa-lock',
|
||||
}, []];
|
||||
};
|
||||
|
||||
var makeHeadingCell = Render.makeHeadingCell = function (cell, readOnly) {
|
||||
if (!cell) { return ['TD', {}, []]; }
|
||||
if (cell.type === 'text') {
|
||||
var removeElement = makeRemoveElement(cell['data-rt-id']);
|
||||
var editElement = makeEditElement(cell['data-rt-id']);
|
||||
var lockElement = makeLockElement(cell['data-rt-id']);
|
||||
var elements = [['INPUT', cell, []]];
|
||||
if (!readOnly) {
|
||||
elements.unshift(removeElement);
|
||||
elements.unshift(lockElement);
|
||||
elements.unshift(editElement);
|
||||
elements.unshift(makeRemoveElement(cell['data-rt-id']));
|
||||
elements.unshift(makeLockElement(cell['data-rt-id']));
|
||||
}
|
||||
return ['TD', {}, elements];
|
||||
}
|
||||
@@ -321,12 +316,10 @@ var Renderer = function (Cryptpad) {
|
||||
|
||||
var makeBodyCell = Render.makeBodyCell = function (cell, readOnly) {
|
||||
if (cell && cell.type === 'text') {
|
||||
var removeElement = makeRemoveElement(cell['data-rt-id']);
|
||||
var editElement = makeEditElement(cell['data-rt-id']);
|
||||
var elements = [['INPUT', cell, []]];
|
||||
if (!readOnly) {
|
||||
elements.push(removeElement);
|
||||
elements.push(editElement);
|
||||
elements.push(makeRemoveElement(cell['data-rt-id']));
|
||||
elements.push(makeEditElement(cell['data-rt-id']));
|
||||
}
|
||||
return ['TD', {}, [
|
||||
['DIV', {class: 'text-cell'}, elements]
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span id="user-menu" class="right dropdown-bar"></span>
|
||||
<span id="language-selector" class="right dropdown-bar"></span>
|
||||
<span class="right">
|
||||
<a href="/about.html" data-localization="about">About</a>
|
||||
|
||||
@@ -2,8 +2,9 @@ define([
|
||||
'jquery',
|
||||
'/common/login.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/credential.js' // preloaded for login.js
|
||||
], function ($, Login, Cryptpad) {
|
||||
'/common/test.js',
|
||||
'/common/credential.js', // preloaded for login.js
|
||||
], function ($, Login, Cryptpad, Test) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
$(function () {
|
||||
@@ -15,6 +16,14 @@ define([
|
||||
$sel.find('button').addClass('btn').addClass('btn-secondary');
|
||||
$sel.show();
|
||||
|
||||
// User admin menu
|
||||
var $userMenu = $('#user-menu');
|
||||
var userMenuCfg = {
|
||||
$initBlock: $userMenu
|
||||
};
|
||||
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
|
||||
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
|
||||
|
||||
$(window).click(function () {
|
||||
$('.cryptpad-dropdown').hide();
|
||||
});
|
||||
@@ -55,6 +64,11 @@ define([
|
||||
var $register = $('button#register');
|
||||
|
||||
var logMeIn = function (result) {
|
||||
if (Test.testing) {
|
||||
Test.passed();
|
||||
window.alert("Test passed!");
|
||||
return;
|
||||
}
|
||||
localStorage.User_hash = result.userHash;
|
||||
|
||||
var proxy = result.proxy;
|
||||
@@ -101,57 +115,66 @@ define([
|
||||
function (yes) {
|
||||
if (!yes) { return; }
|
||||
|
||||
Cryptpad.addLoadingScreen(Messages.login_hashing);
|
||||
Login.loginOrRegister(uname, passwd, true, function (err, result) {
|
||||
var proxy = result.proxy;
|
||||
// setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up
|
||||
window.setTimeout(function () {
|
||||
Cryptpad.addLoadingScreen(Messages.login_hashing);
|
||||
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password
|
||||
window.setTimeout(function () {
|
||||
Login.loginOrRegister(uname, passwd, true, function (err, result) {
|
||||
var proxy = result.proxy;
|
||||
|
||||
if (err) {
|
||||
switch (err) {
|
||||
case 'NO_SUCH_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_noSuchUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_PASS':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalPass);
|
||||
});
|
||||
break;
|
||||
case 'ALREADY_REGISTERED':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.confirm(Messages.register_alreadyRegistered, function (yes) {
|
||||
if (!yes) { return; }
|
||||
proxy.login_name = uname;
|
||||
if (err) {
|
||||
switch (err) {
|
||||
case 'NO_SUCH_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_noSuchUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_PASS':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalPass);
|
||||
});
|
||||
break;
|
||||
case 'ALREADY_REGISTERED':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.confirm(Messages.register_alreadyRegistered, function (yes) {
|
||||
if (!yes) { return; }
|
||||
proxy.login_name = uname;
|
||||
|
||||
if (!proxy[Cryptpad.displayNameKey]) {
|
||||
proxy[Cryptpad.displayNameKey] = uname;
|
||||
}
|
||||
Cryptpad.eraseTempSessionValues();
|
||||
logMeIn(result);
|
||||
});
|
||||
});
|
||||
break;
|
||||
default: // UNHANDLED ERROR
|
||||
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Cryptpad.eraseTempSessionValues();
|
||||
if (shouldImport) {
|
||||
sessionStorage.migrateAnonDrive = 1;
|
||||
}
|
||||
if (!proxy[Cryptpad.displayNameKey]) {
|
||||
proxy[Cryptpad.displayNameKey] = uname;
|
||||
}
|
||||
Cryptpad.eraseTempSessionValues();
|
||||
logMeIn(result);
|
||||
});
|
||||
});
|
||||
break;
|
||||
default: // UNHANDLED ERROR
|
||||
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
proxy.login_name = uname;
|
||||
proxy[Cryptpad.displayNameKey] = uname;
|
||||
sessionStorage.createReadme = 1;
|
||||
if (Test.testing) { return void logMeIn(result); }
|
||||
|
||||
logMeIn(result);
|
||||
});
|
||||
Cryptpad.eraseTempSessionValues();
|
||||
if (shouldImport) {
|
||||
sessionStorage.migrateAnonDrive = 1;
|
||||
}
|
||||
|
||||
proxy.login_name = uname;
|
||||
proxy[Cryptpad.displayNameKey] = uname;
|
||||
sessionStorage.createReadme = 1;
|
||||
|
||||
logMeIn(result);
|
||||
});
|
||||
}, 0);
|
||||
}, 100);
|
||||
}, {
|
||||
ok: Messages.register_writtenPassword,
|
||||
cancel: Messages.register_cancel,
|
||||
@@ -162,5 +185,18 @@ define([
|
||||
$dialog.find('> div').addClass('half');
|
||||
});
|
||||
});
|
||||
|
||||
Test(function () {
|
||||
$uname.val('test' + Math.random());
|
||||
$passwd.val('test');
|
||||
$confirm.val('test');
|
||||
$checkImport[0].checked = true;
|
||||
$checkAcceptTerms[0].checked = true;
|
||||
$register.click();
|
||||
|
||||
window.setTimeout(function () {
|
||||
Cryptpad.findOKButton().click();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
<span class="link right">
|
||||
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
|
||||
</span>
|
||||
<span class="link right">
|
||||
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -97,7 +100,7 @@
|
||||
<div class="col">
|
||||
<ul class="list-unstyled">
|
||||
<li class="title" data-localization="footer_contact"><li>
|
||||
<li><a href="https://riot.im/app/#/room/!cryptpad:matrix.org" target="_blank" rel="noopener noreferrer">Chat</a></li>
|
||||
<li><a href="https://riot.im/app/#/room/#cryptpad:matrix.org" target="_blank" rel="noopener noreferrer">Chat</a></li>
|
||||
<li><a href="https://twitter.com/cryptpad" target="_blank" rel="noopener noreferrer">Twitter</a></li>
|
||||
<li><a href="https://github.com/xwiki-labs/cryptpad" target="_blank" rel="noopener noreferrer">GitHub</a></li>
|
||||
<li><a href="/contact.html">Email</a></li>
|
||||
@@ -105,7 +108,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.6.0 (Grootslang)</div>
|
||||
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -3,7 +3,8 @@ define([
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/mergeDrive.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'/customize/header.js',
|
||||
], function ($, Cryptpad, Crypt, Merge) {
|
||||
var saveAs = window.saveAs;
|
||||
|
||||
@@ -49,17 +50,17 @@ define([
|
||||
|
||||
var publicKey = obj.edPublic;
|
||||
if (publicKey) {
|
||||
var userHref = Cryptpad.getUserHrefFromKeys(accountName, publicKey);
|
||||
var $pubLabel = $('<span>', {'class': 'label'})
|
||||
.text(Messages.settings_publicSigningKey + ':');
|
||||
var $pubKey = $('<input>', {type: 'text', readonly: true})
|
||||
.css({
|
||||
width: '28em'
|
||||
})
|
||||
.val(publicKey);
|
||||
.val(userHref);
|
||||
$div.append('<br>').append($pubLabel).append($pubKey);
|
||||
}
|
||||
|
||||
|
||||
return $div;
|
||||
};
|
||||
|
||||
@@ -222,33 +223,16 @@ define([
|
||||
return $div;
|
||||
};
|
||||
|
||||
var createUsageButton = function (obj) {
|
||||
var proxy = obj.proxy;
|
||||
|
||||
var createUsageButton = function () {
|
||||
var $div = $('<div>', { 'class': 'pinned-usage' })
|
||||
.text(Messages.settings_usageTitle)
|
||||
.append('<br>');
|
||||
|
||||
$('<button>', {
|
||||
'class': 'btn btn-primary', // fa fa-hdd-o ?
|
||||
})
|
||||
.text(Messages.settings_usage)
|
||||
.click(function () {
|
||||
if (!(proxy.edPublic && proxy.edPrivate)) {
|
||||
// suggest that they login/register
|
||||
Cryptpad.alert(Messages.settings_pinningNotAvailable);
|
||||
return;
|
||||
}
|
||||
Cryptpad.getPinnedUsage(function (e, bytes) {
|
||||
if (e) {
|
||||
Cryptpad.alert(Messages.settings_pinningError);
|
||||
return;
|
||||
}
|
||||
Cryptpad.alert(Messages._getKey('settings_usageAmount', [Cryptpad.bytesToMegabytes(bytes)]));
|
||||
});
|
||||
})
|
||||
.appendTo($div);
|
||||
|
||||
Cryptpad.createUsageBar(function (err, $bar) {
|
||||
$div.find('.limit-container').remove();
|
||||
$bar.find('.upgrade').addClass('btn btn-success');
|
||||
$div.append($bar);
|
||||
}, true);
|
||||
return $div;
|
||||
};
|
||||
|
||||
@@ -334,10 +318,10 @@ define([
|
||||
APP.$container.append(createLogoutEverywhere(obj));
|
||||
}
|
||||
APP.$container.append(createResetTips());
|
||||
APP.$container.append(createUsageButton(obj));
|
||||
APP.$container.append(createBackupDrive(obj));
|
||||
APP.$container.append(createImportLocalPads(obj));
|
||||
APP.$container.append(createResetDrive(obj));
|
||||
APP.$container.append(createUsageButton(obj));
|
||||
APP.$container.append(createUserFeedbackToggle(obj));
|
||||
obj.proxy.on('change', [], refresh);
|
||||
obj.proxy.on('remove', [], refresh);
|
||||
@@ -345,19 +329,6 @@ define([
|
||||
|
||||
$(function () {
|
||||
var $main = $('#mainBlock');
|
||||
// Language selector
|
||||
var $sel = $('#language-selector');
|
||||
Cryptpad.createLanguageSelector(undefined, $sel);
|
||||
$sel.find('button').addClass('btn').addClass('btn-secondary');
|
||||
$sel.show();
|
||||
|
||||
// User admin menu
|
||||
var $userMenu = $('#user-menu');
|
||||
var userMenuCfg = {
|
||||
$initBlock: $userMenu
|
||||
};
|
||||
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
|
||||
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
|
||||
|
||||
$(window).click(function () {
|
||||
$('.cryptpad-dropdown').hide();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
<link rel="icon" type="image/png"
|
||||
href="/customize/main-favicon.png"
|
||||
|
||||
@@ -8,15 +8,8 @@ define([
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/modes.js',
|
||||
'/common/themes.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/slide/slide.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify, Slide) {
|
||||
var saveAs = window.saveAs;
|
||||
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Slide) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = {
|
||||
@@ -30,23 +23,15 @@ define([
|
||||
var SLIDE_COLOR_ID = "cryptpad-color";
|
||||
|
||||
|
||||
var stringify = function (obj) {
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
var setTabTitle = function () {
|
||||
var slideNumber = '';
|
||||
if (Slide.index && Slide.content.length) {
|
||||
slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')';
|
||||
}
|
||||
document.title = APP.title + slideNumber;
|
||||
};
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var stringify = function (obj) {
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var toolbar;
|
||||
var editor;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
@@ -62,90 +47,50 @@ define([
|
||||
};
|
||||
|
||||
var andThen = function (CMeditor) {
|
||||
var CodeMirror = module.CodeMirror = CMeditor;
|
||||
CodeMirror.modeURL = "/bower_components/codemirror/mode/%N/%N.js";
|
||||
var $pad = $('#pad-iframe');
|
||||
var $textarea = $pad.contents().find('#editor1');
|
||||
var CodeMirror = Cryptpad.createCodemirror(CMeditor, ifrw, Cryptpad);
|
||||
editor = CodeMirror.editor;
|
||||
|
||||
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
|
||||
var parsedHash = Cryptpad.parsePadUrl(window.location.href);
|
||||
var defaultName = Cryptpad.getDefaultName(parsedHash);
|
||||
var initialState = Messages.slideInitialState;
|
||||
var $pad = $('#pad-iframe');
|
||||
|
||||
var isHistoryMode = false;
|
||||
|
||||
var editor = module.editor = CMeditor.fromTextArea($textarea[0], {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets : true,
|
||||
showTrailingSpace : true,
|
||||
styleActiveLine : true,
|
||||
search: true,
|
||||
highlightSelectionMatches: {showToken: /\w+/},
|
||||
extraKeys: {"Shift-Ctrl-R": undefined},
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
mode: "javascript",
|
||||
readOnly: true
|
||||
});
|
||||
editor.setValue(initialState);
|
||||
|
||||
var setMode = module.setMode = function (mode, $select) {
|
||||
module.highlightMode = mode;
|
||||
if (mode === 'text') {
|
||||
editor.setOption('mode', 'text');
|
||||
return;
|
||||
}
|
||||
CodeMirror.autoLoadMode(editor, mode);
|
||||
editor.setOption('mode', mode);
|
||||
if ($select && $select.val) { $select.val(mode); }
|
||||
var setEditable = module.setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
editor.setOption('readOnly', !bool);
|
||||
};
|
||||
setMode('markdown');
|
||||
|
||||
var setTheme = module.setTheme = (function () {
|
||||
var path = '/common/theme/';
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var $head = $(ifrw.document.head);
|
||||
var setTabTitle = function (title) {
|
||||
var slideNumber = '';
|
||||
if (Slide.index && Slide.content.length) {
|
||||
slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')';
|
||||
}
|
||||
document.title = title + slideNumber;
|
||||
};
|
||||
|
||||
var themeLoaded = module.themeLoaded = function (theme) {
|
||||
return $head.find('link[href*="'+theme+'"]').length;
|
||||
};
|
||||
|
||||
var loadTheme = module.loadTheme = function (theme) {
|
||||
$head.append($('<link />', {
|
||||
rel: 'stylesheet',
|
||||
href: path + theme + '.css',
|
||||
}));
|
||||
};
|
||||
|
||||
return function (theme, $select) {
|
||||
if (!theme) {
|
||||
editor.setOption('theme', 'default');
|
||||
} else {
|
||||
if (!themeLoaded(theme)) {
|
||||
loadTheme(theme);
|
||||
}
|
||||
editor.setOption('theme', theme);
|
||||
}
|
||||
if ($select) {
|
||||
$select.setValue(theme || 'Theme');
|
||||
}
|
||||
};
|
||||
}());
|
||||
var initialState = Messages.slideInitialState;
|
||||
|
||||
var $modal = $pad.contents().find('#modal');
|
||||
var $content = $pad.contents().find('#content');
|
||||
var $print = $pad.contents().find('#print');
|
||||
var slideOptions = {};
|
||||
|
||||
Slide.setModal(APP, $modal, $content, $pad, ifrw, slideOptions, initialState);
|
||||
$content.click(function (e) {
|
||||
if (!e.target) { return; }
|
||||
var $t = $(e.target);
|
||||
if ($t.is('a') || $t.parents('a').length) {
|
||||
e.preventDefault();
|
||||
var $a = $t.is('a') ? $t : $t.parents('a').first();
|
||||
var href = $a.attr('href');
|
||||
window.open(href);
|
||||
}
|
||||
});
|
||||
|
||||
var setStyleState = function (state) {
|
||||
$pad.contents().find('#print, #content').find('style').each(function (i, el) {
|
||||
el.disabled = !state;
|
||||
});
|
||||
};
|
||||
Slide.setModal(APP, $modal, $content, $pad, ifrw, slideOptions, initialState);
|
||||
|
||||
var enterPresentationMode = function (shouldLog) {
|
||||
Slide.show(true, editor.getValue());
|
||||
@@ -153,27 +98,15 @@ define([
|
||||
Cryptpad.log(Messages.presentSuccess);
|
||||
}
|
||||
};
|
||||
var leavePresentationMode = function () {
|
||||
setStyleState(false);
|
||||
Slide.show(false);
|
||||
};
|
||||
|
||||
if (presentMode) {
|
||||
enterPresentationMode(true);
|
||||
}
|
||||
|
||||
var setEditable = module.setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
editor.setOption('readOnly', !bool);
|
||||
};
|
||||
|
||||
var UserList;
|
||||
|
||||
var textColor;
|
||||
var backColor;
|
||||
|
||||
var config = {
|
||||
//initialState: Messages.codeInitialState,
|
||||
initialState: '{}',
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
channel: secret.channel,
|
||||
@@ -202,12 +135,12 @@ define([
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
defaultTitle: defaultName,
|
||||
defaultTitle: Title.defaultTitle,
|
||||
slideOptions: slideOptions
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = APP.title;
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
if (textColor) {
|
||||
obj.metadata.color = textColor;
|
||||
@@ -226,7 +159,7 @@ define([
|
||||
|
||||
editor.save();
|
||||
|
||||
var textValue = canonicalize($textarea.val());
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = stringifyInner(textValue);
|
||||
|
||||
module.patchText(shjson);
|
||||
@@ -237,98 +170,16 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var getHeadingText = function () {
|
||||
var lines = editor.getValue().split(/\n/);
|
||||
|
||||
var text = '';
|
||||
lines.some(function (line) {
|
||||
// lines beginning with a hash are potentially valuable
|
||||
// works for markdown, python, bash, etc.
|
||||
var hash = /^#(.*?)$/;
|
||||
if (hash.test(line)) {
|
||||
line.replace(hash, function (a, one) {
|
||||
text = one;
|
||||
});
|
||||
return true;
|
||||
var metadataCfg = {
|
||||
slideOptions: function (newOpt) {
|
||||
if (stringify(newOpt) !== stringify(slideOptions)) {
|
||||
$.extend(slideOptions, newOpt);
|
||||
// TODO: manage realtime + cursor in the "options" modal ??
|
||||
Slide.updateOptions();
|
||||
}
|
||||
});
|
||||
|
||||
return text.trim();
|
||||
};
|
||||
|
||||
var suggestName = function () {
|
||||
if (APP.title === defaultName) {
|
||||
return getHeadingText() || "";
|
||||
} else {
|
||||
return APP.title || getHeadingText() || defaultName;
|
||||
}
|
||||
};
|
||||
|
||||
var exportText = module.exportText = function () {
|
||||
var text = editor.getValue();
|
||||
|
||||
var ext = Modes.extensionOf(module.highlightMode);
|
||||
|
||||
var title = Cryptpad.fixFileName(suggestName()) + ext;
|
||||
|
||||
Cryptpad.prompt(Messages.exportPrompt, title, function (filename) {
|
||||
if (filename === null) { return; }
|
||||
var blob = new Blob([text], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
});
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
};
|
||||
var importText = function (content, file) {
|
||||
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
|
||||
var mode;
|
||||
var mime = CodeMirror.findModeByMIME(file.type);
|
||||
|
||||
if (!mime) {
|
||||
var ext = /.+\.([^.]+)$/.exec(file.name);
|
||||
if (ext[1]) {
|
||||
mode = CodeMirror.findModeByExtension(ext[1]);
|
||||
}
|
||||
} else {
|
||||
mode = mime && mime.mode || null;
|
||||
}
|
||||
|
||||
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
|
||||
setMode(mode);
|
||||
$bar.find('#language-mode').val(mode);
|
||||
} else {
|
||||
console.log("Couldn't find a suitable highlighting mode: %s", mode);
|
||||
setMode('text');
|
||||
$bar.find('#language-mode').val('text');
|
||||
}
|
||||
|
||||
editor.setValue(content);
|
||||
onLocal();
|
||||
};
|
||||
|
||||
var updateTitle = function (newTitle) {
|
||||
if (newTitle === APP.title) { return; }
|
||||
// Change the title now, and set it back to the old value if there is an error
|
||||
var oldTitle = APP.title;
|
||||
APP.title = newTitle;
|
||||
setTabTitle();
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set pad title");
|
||||
console.error(err);
|
||||
APP.title = oldTitle;
|
||||
setTabTitle();
|
||||
return;
|
||||
}
|
||||
APP.title = data;
|
||||
setTabTitle();
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||
if (slideOptions.title) { Slide.updateOptions(); }
|
||||
});
|
||||
};
|
||||
|
||||
var updateColors = function (text, back) {
|
||||
var updateColors = metadataCfg.slideColors = function (text, back) {
|
||||
if (text) {
|
||||
textColor = text;
|
||||
$modal.css('color', text);
|
||||
@@ -343,56 +194,12 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var updateOptions = function (newOpt) {
|
||||
if (stringify(newOpt) !== stringify(slideOptions)) {
|
||||
$.extend(slideOptions, newOpt);
|
||||
// TODO: manage realtime + cursor in the "options" modal ??
|
||||
Slide.updateOptions();
|
||||
}
|
||||
};
|
||||
|
||||
var updateDefaultTitle = function (defaultTitle) {
|
||||
defaultName = defaultTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName);
|
||||
};
|
||||
|
||||
var updateMetadata = function(shjson) {
|
||||
// Extract the user list (metadata) from the hyperjson
|
||||
var json = (shjson === "") ? "" : JSON.parse(shjson);
|
||||
var titleUpdated = false;
|
||||
if (json && json.metadata) {
|
||||
if (json.metadata.users) {
|
||||
var userData = json.metadata.users;
|
||||
// Update the local user data
|
||||
UserList.addToUserData(userData);
|
||||
}
|
||||
if (json.metadata.defaultTitle) {
|
||||
updateDefaultTitle(json.metadata.defaultTitle);
|
||||
}
|
||||
if (typeof json.metadata.title !== "undefined") {
|
||||
updateTitle(json.metadata.title || defaultName);
|
||||
titleUpdated = true;
|
||||
}
|
||||
updateOptions(json.metadata.slideOptions);
|
||||
updateColors(json.metadata.color, json.metadata.backColor);
|
||||
}
|
||||
if (!titleUpdated) {
|
||||
updateTitle(defaultName);
|
||||
}
|
||||
};
|
||||
|
||||
var renameCb = function (err, title) {
|
||||
if (err) { return; }
|
||||
APP.title = title;
|
||||
setTabTitle();
|
||||
onLocal();
|
||||
};
|
||||
|
||||
var createPrintDialog = function () {
|
||||
var slideOptionsTmp = {
|
||||
title: false,
|
||||
slide: false,
|
||||
date: false,
|
||||
transition: true,
|
||||
style: ''
|
||||
};
|
||||
|
||||
@@ -425,6 +232,13 @@ define([
|
||||
}).appendTo($p).css('width', 'auto');
|
||||
$('<label>', {'for': 'checkTitle'}).text(Messages.printTitle).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
// Transition
|
||||
$('<input>', {type: 'checkbox', id: 'checkTransition', checked: slideOptionsTmp.transition}).on('change', function () {
|
||||
var c = this.checked;
|
||||
slideOptionsTmp.transition = c;
|
||||
}).appendTo($p).css('width', 'auto');
|
||||
$('<label>', {'for': 'checkTransition'}).text(Messages.printTransition).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
// CSS
|
||||
$('<label>', {'for': 'cssPrint'}).text(Messages.printCSS).appendTo($p);
|
||||
$p.append($('<br>'));
|
||||
@@ -461,18 +275,22 @@ define([
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
var titleCfg = {
|
||||
updateLocalTitle: setTabTitle,
|
||||
getHeadingText: CodeMirror.getHeadingText
|
||||
};
|
||||
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'],
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
@@ -482,6 +300,9 @@ define([
|
||||
};
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
CodeMirror.init(config.onLocal, Title, toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
var editHash;
|
||||
@@ -491,34 +312,17 @@ define([
|
||||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {};
|
||||
histConfig.onRender = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
var hjson = JSON.parse(val || '{}');
|
||||
var remoteDoc = hjson.content;
|
||||
var histConfig = {
|
||||
onLocal: config.onLocal,
|
||||
onRemote: config.onRemote,
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var remoteDoc = JSON.parse(val || '{}').content;
|
||||
editor.setValue(remoteDoc || '');
|
||||
editor.save();
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
$toolbar: $bar
|
||||
};
|
||||
histConfig.onClose = function () {
|
||||
// Close button clicked
|
||||
setHistory(false, true);
|
||||
};
|
||||
histConfig.onRevert = function () {
|
||||
// Revert button clicked
|
||||
setHistory(false, false);
|
||||
config.onLocal();
|
||||
config.onRemote();
|
||||
};
|
||||
histConfig.onReady = function () {
|
||||
// Called when the history is loaded and the UI displayed
|
||||
setHistory(true);
|
||||
};
|
||||
histConfig.$toolbar = $bar;
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
@@ -534,12 +338,12 @@ define([
|
||||
}
|
||||
|
||||
/* add an export button */
|
||||
var $export = Cryptpad.createButton('export', true, {}, exportText);
|
||||
var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText);
|
||||
$rightside.append($export);
|
||||
|
||||
if (!readOnly) {
|
||||
/* add an import button */
|
||||
var $import = Cryptpad.createButton('import', true, {}, importText);
|
||||
var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText);
|
||||
$rightside.append($import);
|
||||
}
|
||||
|
||||
@@ -586,49 +390,6 @@ define([
|
||||
}
|
||||
$rightside.append($present);
|
||||
|
||||
var $leavePresent = Cryptpad.createButton('source', true)
|
||||
.click(leavePresentationMode);
|
||||
if (!presentMode) {
|
||||
$leavePresent.hide();
|
||||
}
|
||||
$rightside.append($leavePresent);
|
||||
|
||||
var configureTheme = function () {
|
||||
/* Remember the user's last choice of theme using localStorage */
|
||||
var themeKey = 'CRYPTPAD_CODE_THEME';
|
||||
var lastTheme = localStorage.getItem(themeKey) || 'default';
|
||||
|
||||
var options = [];
|
||||
Themes.forEach(function (l) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'data-value': l.name,
|
||||
'href': '#',
|
||||
},
|
||||
content: l.name // Pretty name of the language value
|
||||
});
|
||||
});
|
||||
var dropdownConfig = {
|
||||
text: 'Theme', // Button initial text
|
||||
options: options, // Entries displayed in the menu
|
||||
left: true, // Open to the left of the button
|
||||
isSelect: true,
|
||||
initialValue: lastTheme
|
||||
};
|
||||
var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig);
|
||||
|
||||
setTheme(lastTheme, $block);
|
||||
|
||||
$block.find('a').click(function () {
|
||||
var theme = $(this).attr('data-value');
|
||||
setTheme(theme, $block);
|
||||
localStorage.setItem(themeKey, theme);
|
||||
});
|
||||
|
||||
$rightside.append($block);
|
||||
};
|
||||
|
||||
var configureColors = function () {
|
||||
var $back = $('<button>', {
|
||||
id: SLIDE_BACKCOLOR_ID,
|
||||
@@ -675,7 +436,7 @@ define([
|
||||
};
|
||||
|
||||
configureColors();
|
||||
configureTheme();
|
||||
CodeMirror.configureTheme();
|
||||
|
||||
if (presentMode) {
|
||||
$('#top-bar').hide();
|
||||
@@ -687,20 +448,6 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var unnotify = module.unnotify = function () {
|
||||
if (module.tabNotification &&
|
||||
typeof(module.tabNotification.cancel) === 'function') {
|
||||
module.tabNotification.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
var notify = module.notify = function () {
|
||||
if (Visible.isSupported() && !Visible.currently()) {
|
||||
unnotify();
|
||||
module.tabNotification = Notify.tab(1000, 10);
|
||||
}
|
||||
};
|
||||
|
||||
config.onReady = function (info) {
|
||||
if (module.realtime !== info.realtime) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
@@ -727,38 +474,30 @@ define([
|
||||
}
|
||||
|
||||
if (hjson.highlightMode) {
|
||||
setMode(hjson.highlightMode, module.$language);
|
||||
CodeMirror.setMode(hjson.highlightMode);
|
||||
}
|
||||
}
|
||||
|
||||
if (!module.highlightMode) {
|
||||
setMode('javascript', module.$language);
|
||||
console.log("%s => %s", module.highlightMode, module.$language.val());
|
||||
if (!CodeMirror.highlightMode) {
|
||||
CodeMirror.setMode('markdown');
|
||||
}
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(userDoc);
|
||||
Metadata.update(userDoc);
|
||||
|
||||
editor.setValue(newDoc || initialState);
|
||||
|
||||
if (Cryptpad.initialName && APP.title === defaultName) {
|
||||
updateTitle(Cryptpad.initialName);
|
||||
if (Cryptpad.initialName && Title.isDefaultTitle()) {
|
||||
Title.updateTitle(Cryptpad.initialName);
|
||||
onLocal();
|
||||
}
|
||||
|
||||
if (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) {
|
||||
if (yes) { unnotify(); }
|
||||
});
|
||||
}
|
||||
|
||||
Slide.onChange(function (o, n, l) {
|
||||
if (n !== null) {
|
||||
document.title = APP.title + ' (' + (++n) + '/' + l + ')';
|
||||
document.title = Title.title + ' (' + (++n) + '/' + l + ')';
|
||||
return;
|
||||
}
|
||||
console.log("Exiting presentation mode");
|
||||
document.title = APP.title;
|
||||
document.title = Title.title;
|
||||
});
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
@@ -771,76 +510,28 @@ define([
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
var cursorToPos = function(cursor, oldText) {
|
||||
var cLine = cursor.line;
|
||||
var cCh = cursor.ch;
|
||||
var pos = 0;
|
||||
var textLines = oldText.split("\n");
|
||||
for (var line = 0; line <= cLine; line++) {
|
||||
if(line < cLine) {
|
||||
pos += textLines[line].length+1;
|
||||
}
|
||||
else if(line === cLine) {
|
||||
pos += cCh;
|
||||
}
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
var posToCursor = function(position, newText) {
|
||||
var cursor = {
|
||||
line: 0,
|
||||
ch: 0
|
||||
};
|
||||
var textLines = newText.substr(0, position).split("\n");
|
||||
cursor.line = textLines.length - 1;
|
||||
cursor.ch = textLines[cursor.line].length;
|
||||
return cursor;
|
||||
};
|
||||
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
var scroll = editor.getScrollInfo();
|
||||
|
||||
var oldDoc = canonicalize($textarea.val());
|
||||
var oldDoc = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = module.realtime.getUserDoc();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(shjson);
|
||||
Metadata.update(shjson);
|
||||
|
||||
var hjson = JSON.parse(shjson);
|
||||
var remoteDoc = hjson.content;
|
||||
|
||||
var highlightMode = hjson.highlightMode;
|
||||
if (highlightMode && highlightMode !== module.highlightMode) {
|
||||
setMode(highlightMode, module.$language);
|
||||
if (highlightMode && highlightMode !== CodeMirror.highlightMode) {
|
||||
CodeMirror.setMode(highlightMode);
|
||||
}
|
||||
|
||||
//get old cursor here
|
||||
var oldCursor = {};
|
||||
oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc);
|
||||
oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc);
|
||||
|
||||
editor.setValue(remoteDoc);
|
||||
editor.save();
|
||||
|
||||
var op = TextPatcher.diff(oldDoc, remoteDoc);
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(oldCursor[attr], op);
|
||||
});
|
||||
|
||||
if(selects[0] === selects[1]) {
|
||||
editor.setCursor(posToCursor(selects[0], remoteDoc));
|
||||
}
|
||||
else {
|
||||
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
|
||||
}
|
||||
|
||||
editor.scrollTo(scroll.left, scroll.top);
|
||||
CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
|
||||
|
||||
if (!readOnly) {
|
||||
var textValue = canonicalize($textarea.val());
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson2 = stringifyInner(textValue);
|
||||
if (shjson2 !== shjson) {
|
||||
console.error("shjson2 !== shjson");
|
||||
@@ -851,7 +542,7 @@ define([
|
||||
Slide.update(remoteDoc);
|
||||
|
||||
if (oldDoc !== remoteDoc) {
|
||||
notify();
|
||||
Cryptpad.notify();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -168,13 +168,16 @@ body .CodeMirror-focused .cm-matchhighlight {
|
||||
}
|
||||
.cp div.modal #content,
|
||||
.cp div#modal #content {
|
||||
transition: margin-left 1s;
|
||||
font-size: 20vh;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cp div.modal #content.transition,
|
||||
.cp div#modal #content.transition {
|
||||
transition: margin-left 1s;
|
||||
}
|
||||
.cp div.modal #content .slide-frame,
|
||||
.cp div#modal #content .slide-frame {
|
||||
display: inline-block;
|
||||
@@ -198,10 +201,11 @@ body .CodeMirror-focused .cm-matchhighlight {
|
||||
}
|
||||
.cp div.modal #content .slide-container,
|
||||
.cp div#modal #content .slide-container {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
.cp div.modal .center,
|
||||
.cp div#modal .center {
|
||||
|
||||
@@ -1,32 +1,7 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/marked/marked.min.js',
|
||||
'/bower_components/diff-dom/diffDOM.js'
|
||||
],function ($, Marked) {
|
||||
var DiffDOM = window.diffDOM;
|
||||
|
||||
var renderer = new Marked.Renderer();
|
||||
|
||||
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
|
||||
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
|
||||
renderer.listitem = function (text) {
|
||||
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
|
||||
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
|
||||
if (isCheckedTaskItem) {
|
||||
text = text.replace(checkedTaskItemPtn,
|
||||
'<i class="fa fa-check-square" aria-hidden="true"></i> ') + '\n';
|
||||
}
|
||||
if (isUncheckedTaskItem) {
|
||||
text = text.replace(uncheckedTaskItemPtn,
|
||||
'<i class="fa fa-square-o" aria-hidden="true"></i> ') + '\n';
|
||||
}
|
||||
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
|
||||
return '<li'+ cls + '>' + text + '</li>\n';
|
||||
};
|
||||
|
||||
Marked.setOptions({
|
||||
renderer: renderer
|
||||
});
|
||||
'/common/diffMarked.js',
|
||||
],function ($, DiffMd) {
|
||||
|
||||
var Slide = {
|
||||
index: 0,
|
||||
@@ -63,78 +38,6 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var forbiddenTags = Slide.forbiddenTags = [
|
||||
'SCRIPT',
|
||||
'IFRAME',
|
||||
'OBJECT',
|
||||
'APPLET',
|
||||
'VIDEO',
|
||||
'AUDIO',
|
||||
];
|
||||
var unsafeTag = function (info) {
|
||||
if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) {
|
||||
if (/^on/.test(info.diff.name)) {
|
||||
console.log("Rejecting forbidden element attribute with name", info.diff.name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (['addElement', 'replaceElement'].indexOf(info.diff.action) !== -1) {
|
||||
var msg = "Rejecting forbidden tag of type (%s)";
|
||||
if (info.diff.element && forbiddenTags.indexOf(info.diff.element.nodeName) !== -1) {
|
||||
console.log(msg, info.diff.element.nodeName);
|
||||
return true;
|
||||
} else if (info.diff.newValue && forbiddenTags.indexOf(info.diff.newValue.nodeName) !== -1) {
|
||||
console.log("Replacing restricted element type (%s) with PRE", info.diff.newValue.nodeName);
|
||||
info.diff.newValue.nodeName = 'PRE';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var domFromHTML = Slide.domFromHTML = function (html) {
|
||||
return new DOMParser().parseFromString(html, "text/html");
|
||||
};
|
||||
|
||||
var DD = new DiffDOM({
|
||||
preDiffApply: function (info) {
|
||||
if (unsafeTag(info)) { return true; }
|
||||
}
|
||||
});
|
||||
|
||||
var makeDiff = function (A, B) {
|
||||
var Err;
|
||||
var Els = [A, B].map(function (frag) {
|
||||
if (typeof(frag) === 'object') {
|
||||
if (!frag || (frag && !frag.body)) {
|
||||
Err = "No body";
|
||||
return;
|
||||
}
|
||||
var els = frag.body.querySelectorAll('#content');
|
||||
if (els.length) {
|
||||
return els[0];
|
||||
}
|
||||
}
|
||||
Err = 'No candidate found';
|
||||
});
|
||||
if (Err) { return Err; }
|
||||
var patch = DD.diff(Els[0], Els[1]);
|
||||
return patch;
|
||||
};
|
||||
|
||||
var slice = function (coll) {
|
||||
return Array.prototype.slice.call(coll);
|
||||
};
|
||||
|
||||
/* remove listeners from the DOM */
|
||||
var removeListeners = function (root) {
|
||||
slice(root.attributes).map(function (attr) {
|
||||
if (/^on/.test(attr.name)) {
|
||||
root.attributes.removeNamedItem(attr.name);
|
||||
}
|
||||
});
|
||||
// all the way down
|
||||
slice(root.children).forEach(removeListeners);
|
||||
};
|
||||
|
||||
var updateFontSize = Slide.updateFontSize = function() {
|
||||
// 20vh
|
||||
// 20 * 16 / 9vw
|
||||
@@ -157,17 +60,10 @@ define([
|
||||
if (typeof(Slide.content) !== 'string') { return; }
|
||||
|
||||
var c = Slide.content;
|
||||
var m = '<span class="slide-container"><span class="'+slideClass+'">'+Marked(c).replace(separatorReg, '</span></span><span class="slide-container"><span class="'+slideClass+'">')+'</span></span>';
|
||||
var m = '<span class="slide-container"><span class="'+slideClass+'">'+DiffMd.render(c).replace(separatorReg, '</span></span><span class="slide-container"><span class="'+slideClass+'">')+'</span></span>';
|
||||
|
||||
var Dom = domFromHTML('<div id="content">' + m + '</div>');
|
||||
removeListeners(Dom.body);
|
||||
var patch = makeDiff(domFromHTML($content[0].outerHTML), Dom);
|
||||
DiffMd.apply(m, $content);
|
||||
|
||||
if (typeof(patch) === 'string') {
|
||||
$content.html(m);
|
||||
} else {
|
||||
DD.apply($content[0], patch);
|
||||
}
|
||||
var length = getNumberOfSlides();
|
||||
$modal.find('style.slideStyle').remove();
|
||||
if (options.style && Slide.shown) {
|
||||
@@ -184,6 +80,10 @@ define([
|
||||
$('<div>', {'class': 'slideTitle'}).text(APP.title).appendTo($(el));
|
||||
}
|
||||
});
|
||||
$content.removeClass('transition');
|
||||
if (options.transition || typeof(options.transition) === "undefined") {
|
||||
$content.addClass('transition');
|
||||
}
|
||||
//$content.find('.' + slideClass).hide();
|
||||
//$content.find('.' + slideClass + ':eq( ' + i + ' )').show();
|
||||
$content.css('margin-left', -(i*100)+'vw');
|
||||
@@ -211,7 +111,10 @@ define([
|
||||
$(ifrw).focus();
|
||||
change(null, Slide.index);
|
||||
if (!isPresentURL()) {
|
||||
window.location.hash += '/present';
|
||||
if (window.location.href.slice(-1) !== '/') {
|
||||
window.location.hash += '/';
|
||||
}
|
||||
window.location.hash += 'present';
|
||||
}
|
||||
$pad.contents().find('.cryptpad-present-button').hide();
|
||||
$pad.contents().find('.cryptpad-source-button').show();
|
||||
@@ -220,7 +123,7 @@ define([
|
||||
$('.top-bar').hide();
|
||||
return;
|
||||
}
|
||||
window.location.hash = window.location.hash.replace(/\/present$/, '');
|
||||
window.location.hash = window.location.hash.replace(/\/present$/, '/');
|
||||
change(Slide.index, null);
|
||||
$pad.contents().find('.cryptpad-present-button').show();
|
||||
$pad.contents().find('.cryptpad-source-button').hide();
|
||||
|
||||
@@ -161,7 +161,9 @@ div.modal, div#modal {
|
||||
width: 100%;
|
||||
}
|
||||
#content {
|
||||
transition: margin-left 1s;
|
||||
&.transition {
|
||||
transition: margin-left 1s;
|
||||
}
|
||||
font-size: 20vh;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
@@ -191,9 +193,10 @@ div.modal, div#modal {
|
||||
margin: auto;
|
||||
}
|
||||
.slide-container {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
height: 100%; width: 100vw;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
115
www/user/index.html
Normal file
115
www/user/index.html
Normal file
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
|
||||
<head>
|
||||
<title data-localization="main_title">Cryptpad: Zero Knowledge, Collaborative Real Time Editing</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="stylesheet" type="text/css" href="/customize/main.css" />
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/>
|
||||
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="main.css" />
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
|
||||
</head>
|
||||
<body class="html">
|
||||
<div id="cryptpadTopBar">
|
||||
<span>
|
||||
<a class="gotoMain" href="/">
|
||||
<img src="/customize/cryptofist_mini.png" class="cryptpad-logo" alt="" /> CryptPad
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<!--<span class="slogan" data-localization="main_slogan"></span>-->
|
||||
|
||||
<span id="user-menu" class="right dropdown-bar"></span>
|
||||
<span id="language-selector" class="right dropdown-bar"></span>
|
||||
<span class="link right">
|
||||
<a href="/about.html" data-localization="about">About</a>
|
||||
</span>
|
||||
<span class="link right">
|
||||
<a href="/privacy.html" data-localization="privacy">Privacy</a>
|
||||
</span>
|
||||
<span class="link right">
|
||||
<a href="/terms.html" data-localization="terms">ToS</a>
|
||||
</span>
|
||||
<span class="link right">
|
||||
<a href="/contact.html" data-localization="contact">Contact</a>
|
||||
</span>
|
||||
<span class="link right">
|
||||
<a href="https://blog.cryptpad.fr/" data-localization="blog">Blog</a>
|
||||
</span>
|
||||
<span class="link right">
|
||||
<button id="upgrade" class="upgrade btn buttonSuccess" style="display: none;"></button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<noscript>
|
||||
<div id="noscriptContainer">
|
||||
<div class="mainOverlay"></div>
|
||||
<div id="noscript">
|
||||
<p>
|
||||
<strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.
|
||||
</p>
|
||||
<hr>
|
||||
<p>
|
||||
<strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
|
||||
<div id="mainBlock" class="hidden">
|
||||
<div id="container"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ul class="list-unstyled">
|
||||
<li class="title">CryptPad</li>
|
||||
<li><a href="/about.html" data-localization="about"></a></li>
|
||||
<li><a href="/terms.html" data-localization="terms"></a></li>
|
||||
<li><a href="/privacy.html" data-localization="privacy"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ul class="list-unstyled">
|
||||
<li class="title" data-localization="footer_applications"><li>
|
||||
<li><a href="/pad/" data-localization="main_richText"></a></li>
|
||||
<li><a href="/code/" data-localization="main_code"></a></li>
|
||||
<li><a href="/slide/" data-localization="main_slide"></a></li>
|
||||
<li><a href="/poll/" data-localization="main_poll"></a></li>
|
||||
<li><a href="/drive/" data-localization="main_drive"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ul class="list-unstyled">
|
||||
<li class="title" data-localization="footer_aboutUs"><li>
|
||||
<li><a href="https://blog.cryptpad.fr" target="_blank" data-localization="blog" rel="noopener noreferrer">Blog</a></li>
|
||||
<li><a href="https://labs.xwiki.com" target="_blank" rel="noopener noreferrer">XWiki Labs</a></li>
|
||||
<li><a href="http://www.xwiki.com" target="_blank" rel="noopener noreferrer">XWiki SAS</a></li>
|
||||
<li><a href="https://www.open-paas.org/" target="_blank" rel="noopener noreferrer">OpenPaaS</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col">
|
||||
<ul class="list-unstyled">
|
||||
<li class="title" data-localization="footer_contact"><li>
|
||||
<li><a href="https://riot.im/app/#/room/#cryptpad:matrix.org" target="_blank" rel="noopener noreferrer">Chat</a></li>
|
||||
<li><a href="https://twitter.com/cryptpad" target="_blank" rel="noopener noreferrer">Twitter</a></li>
|
||||
<li><a href="https://github.com/xwiki-labs/cryptpad" target="_blank" rel="noopener noreferrer">GitHub</a></li>
|
||||
<li><a href="/contact.html">Email</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.8.0 (Igopogo)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
14
www/user/main.css
Normal file
14
www/user/main.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.cp #mainBlock {
|
||||
z-index: 1;
|
||||
width: 1000px;
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.cp #mainBlock #container {
|
||||
text-align: center;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
63
www/user/main.js
Normal file
63
www/user/main.js
Normal file
@@ -0,0 +1,63 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/cryptpad-common.js',
|
||||
], function ($, Cryptpad) {
|
||||
|
||||
var APP = window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
_onRefresh: []
|
||||
};
|
||||
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var comingSoon = function () {
|
||||
var $div = $('<div>', { 'class': 'coming-soon' })
|
||||
.text(Messages.comingSoon)
|
||||
.append('<br>');
|
||||
console.log($div);
|
||||
return $div;
|
||||
};
|
||||
|
||||
var andThen = function () {
|
||||
console.log(APP.$container);
|
||||
APP.$container.append(comingSoon());
|
||||
};
|
||||
|
||||
$(function () {
|
||||
var $main = $('#mainBlock');
|
||||
// Language selector
|
||||
var $sel = $('#language-selector');
|
||||
Cryptpad.createLanguageSelector(undefined, $sel);
|
||||
$sel.find('button').addClass('btn').addClass('btn-secondary');
|
||||
$sel.show();
|
||||
|
||||
// User admin menu
|
||||
var $userMenu = $('#user-menu');
|
||||
var userMenuCfg = {
|
||||
$initBlock: $userMenu
|
||||
};
|
||||
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
|
||||
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
|
||||
|
||||
$(window).click(function () {
|
||||
$('.cryptpad-dropdown').hide();
|
||||
});
|
||||
|
||||
// main block is hidden in case javascript is disabled
|
||||
$main.removeClass('hidden');
|
||||
|
||||
APP.$container = $('#container');
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
//if (!Cryptpad.getUserHash()) { return redirectToMain(); }
|
||||
|
||||
//var storeObj = Cryptpad.getStore().getProxy && Cryptpad.getStore().getProxy().proxy
|
||||
// ? Cryptpad.getStore().getProxy() : undefined;
|
||||
|
||||
//andThen(storeObj);
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -10,12 +10,10 @@ define([
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/whiteboard/colors.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/secure-fabric.js/dist/fabric.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
], function ($, Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Cryptget, Colors, Visible, Notify, AppConfig) {
|
||||
], function ($, Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Cryptget, Colors, AppConfig) {
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
@@ -212,9 +210,10 @@ window.canvas = canvas;
|
||||
var initializing = true;
|
||||
|
||||
var $bar = $('#toolbar');
|
||||
var parsedHash = Cryptpad.parsePadUrl(window.location.href);
|
||||
var defaultName = Cryptpad.getDefaultName(parsedHash);
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = module.config = {
|
||||
initialState: '{}',
|
||||
@@ -253,28 +252,14 @@ window.canvas = canvas;
|
||||
$colors.append($color);
|
||||
};
|
||||
|
||||
var updatePalette = function (newPalette) {
|
||||
var metadataCfg = {};
|
||||
var updatePalette = metadataCfg.updatePalette = function (newPalette) {
|
||||
palette = newPalette;
|
||||
$colors.html('<div class="hidden"> </div>');
|
||||
palette.forEach(addColorToPalette);
|
||||
};
|
||||
updatePalette(palette);
|
||||
|
||||
var suggestName = function (fallback) {
|
||||
if (document.title === defaultName) {
|
||||
return fallback || "";
|
||||
} else {
|
||||
return document.title || defaultName;
|
||||
}
|
||||
};
|
||||
|
||||
var renameCb = function (err, title) {
|
||||
if (err) { return; }
|
||||
document.title = title;
|
||||
config.onLocal();
|
||||
};
|
||||
|
||||
|
||||
var makeColorButton = function ($container) {
|
||||
var $testColor = $('<input>', { type: 'color', value: '!' });
|
||||
|
||||
@@ -305,18 +290,19 @@ window.canvas = canvas;
|
||||
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
Title = Cryptpad.createTitle({}, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'],
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: window,
|
||||
@@ -327,6 +313,8 @@ window.canvas = canvas;
|
||||
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
/* save as template */
|
||||
@@ -371,75 +359,11 @@ window.canvas = canvas;
|
||||
};
|
||||
};
|
||||
|
||||
var updateTitle = function (newTitle) {
|
||||
if (newTitle === document.title) { return; }
|
||||
// Change the title now, and set it back to the old value if there is an error
|
||||
var oldTitle = document.title;
|
||||
document.title = newTitle;
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set pad title");
|
||||
console.error(err);
|
||||
document.title = oldTitle;
|
||||
return;
|
||||
}
|
||||
document.title = data;
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||
});
|
||||
};
|
||||
|
||||
var updateDefaultTitle = function (defaultTitle) {
|
||||
defaultName = defaultTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').attr("placeholder", defaultName);
|
||||
};
|
||||
|
||||
|
||||
var updateMetadata = function(shjson) {
|
||||
// Extract the user list (metadata) from the hyperjson
|
||||
var json = (shjson === "") ? "" : JSON.parse(shjson);
|
||||
var titleUpdated = false;
|
||||
if (json && json.metadata) {
|
||||
if (json.metadata.users) {
|
||||
var userData = json.metadata.users;
|
||||
// Update the local user data
|
||||
UserList.addToUserData(userData);
|
||||
}
|
||||
if (json.metadata.defaultTitle) {
|
||||
updateDefaultTitle(json.metadata.defaultTitle);
|
||||
}
|
||||
if (typeof json.metadata.title !== "undefined") {
|
||||
updateTitle(json.metadata.title || defaultName);
|
||||
titleUpdated = true;
|
||||
}
|
||||
if (typeof(json.metadata.palette) !== 'undefined') {
|
||||
updatePalette(json.metadata.palette);
|
||||
}
|
||||
}
|
||||
if (!titleUpdated) {
|
||||
updateTitle(defaultName);
|
||||
}
|
||||
};
|
||||
|
||||
var unnotify = function () {
|
||||
if (module.tabNotification &&
|
||||
typeof(module.tabNotification.cancel) === 'function') {
|
||||
module.tabNotification.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
var notify = function () {
|
||||
if (Visible.isSupported() && !Visible.currently()) {
|
||||
unnotify();
|
||||
module.tabNotification = Notify.tab(1000, 10);
|
||||
}
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
|
||||
updateMetadata(userDoc);
|
||||
Metadata.update(userDoc);
|
||||
var json = JSON.parse(userDoc);
|
||||
var remoteDoc = json.content;
|
||||
|
||||
@@ -449,7 +373,7 @@ window.canvas = canvas;
|
||||
canvas.renderAll();
|
||||
|
||||
var content = canvas.toDatalessJSON();
|
||||
if (content !== remoteDoc) { notify(); }
|
||||
if (content !== remoteDoc) { Cryptpad.notify(); }
|
||||
if (readOnly) { setEditable(false); }
|
||||
});
|
||||
setEditable(false);
|
||||
@@ -460,11 +384,11 @@ window.canvas = canvas;
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
palette: palette,
|
||||
defaultTitle: defaultName
|
||||
defaultTitle: Title.defaultTitle
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = document.title;
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
// stringify the json and send it into chainpad
|
||||
return JSONSortify(obj);
|
||||
@@ -495,10 +419,6 @@ window.canvas = canvas;
|
||||
initializing = false;
|
||||
onRemote();
|
||||
|
||||
if (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) { if (yes) { unnotify(); } });
|
||||
}
|
||||
|
||||
/* TODO: restore palette from metadata.palette */
|
||||
|
||||
if (readOnly) { return; }
|
||||
|
||||
Reference in New Issue
Block a user