merge master
This commit is contained in:
@@ -3,8 +3,7 @@
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
|
||||
<style>
|
||||
.report {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
'jquery',
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'/common/cryptpad-common.js',
|
||||
], function (jQuery, Hyperjson, TextPatcher, Sortify, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad) {
|
||||
window.Hyperjson = Hyperjson;
|
||||
window.TextPatcher = TextPatcher;
|
||||
window.Sortify = Sortify;
|
||||
@@ -17,31 +15,43 @@ 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) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var $body = $('body');
|
||||
|
||||
var HJSON_list = [
|
||||
'["DIV",{"id":"target"},[["P",{"class":" alice bob charlie has.dot","id":"bang"},["pewpewpew"]]]]',
|
||||
|
||||
@@ -62,7 +72,7 @@ define([
|
||||
};
|
||||
|
||||
var HJSON_equal = function (shjson) {
|
||||
assert(function () {
|
||||
assert(function (cb) {
|
||||
// parse your stringified Hyperjson
|
||||
var hjson;
|
||||
|
||||
@@ -86,10 +96,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");
|
||||
};
|
||||
@@ -98,7 +108,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;
|
||||
@@ -115,7 +125,7 @@ define([
|
||||
TextPatcher.log(target.outerHTML, op);
|
||||
}
|
||||
|
||||
return success;
|
||||
return cb(success);
|
||||
}, "Round trip serialization introduced artifacts.");
|
||||
};
|
||||
|
||||
@@ -129,9 +139,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 + ")");
|
||||
};
|
||||
|
||||
@@ -141,6 +151,59 @@ define([
|
||||
strungJSON(orig);
|
||||
});
|
||||
|
||||
// check that old hashes parse correctly
|
||||
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 (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 (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 (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 (cb) {
|
||||
// TODO
|
||||
return cb(true);
|
||||
}, "version 2 hash failed to parse correctly");
|
||||
|
||||
var swap = function (str, dict) {
|
||||
return str.replace(/\{\{(.*?)\}\}/g, function (all, key) {
|
||||
return typeof dict[key] !== 'undefined'? dict[key] : all;
|
||||
@@ -155,7 +218,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:
|
||||
@@ -176,16 +239,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.
|
||||
@@ -198,12 +260,13 @@ The test returned:
|
||||
{{previous}}
|
||||
*/}), dict);
|
||||
|
||||
var report = SUCCESS;
|
||||
var report = SUCCESS;
|
||||
|
||||
return report;
|
||||
return report;
|
||||
});
|
||||
|
||||
var $report = $('.report');
|
||||
$report.addClass(failed?'failure':'success');
|
||||
});
|
||||
|
||||
var $report = $('.report');
|
||||
$report.addClass(failed?'failure':'success');
|
||||
|
||||
});
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
media {
|
||||
border: 1px solid black;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
display: block;
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<media> my media thing </media>
|
||||
<br />
|
||||
<a href="https://www.w3schools.com/html/html5_new_elements.asp">valid elements</a>
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
define([
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function () {
|
||||
var $ = window.jQuery;
|
||||
|
||||
$('media').each(function () {
|
||||
window.alert("media tag selection works!");
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
html{
|
||||
width: 100%;
|
||||
}
|
||||
body {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
.wrap {
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1,25 +0,0 @@
|
||||
define([
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Hyperjson) {
|
||||
var $ = window.jQuery;
|
||||
var shjson = '["BODY",{"class":"cke_editable cke_editable_themed cke_contents_ltr cke_show_borders","spellcheck":"false"},[["P",{},["This is ",["STRONG",{},["CryptPad"]],", the zero knowledge realtime collaborative editor.",["BR",{},[]],"What you type here is encrypted so only people who have the link can access it.",["BR",{},[]],"Even the server cannot see what you type."]],["P",{},[["SMALL",{},[["I",{},["What you see here, what you hear here, when you leave here, let it stay here"]]]],["BR",{"type":"_moz"},[]]]]]]';
|
||||
|
||||
var hjson = JSON.parse(shjson);
|
||||
|
||||
var pretty = Hyperjson.toString(hjson);
|
||||
|
||||
// set the body html to the rendered hyperjson
|
||||
$('body')[0].outerHTML = pretty;
|
||||
|
||||
$('body')
|
||||
// append the stringified-hyperjson source for reference
|
||||
.append('<hr>').append($('<pre>', {
|
||||
'class': 'wrap',
|
||||
}).text(shjson))
|
||||
// append the pretty-printed html source for reference
|
||||
.append('<hr>').append($('<pre>').text(pretty));
|
||||
|
||||
|
||||
// TODO write some tests to confirm whether the pretty printer is correct
|
||||
});
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
define([
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
'jquery',
|
||||
'/common/cryptpad-common.js',
|
||||
'/customize/translations/messages.js',
|
||||
], function (jQuery, Cryptpad, English) {
|
||||
var $ = window.jQuery;
|
||||
], function ($, Cryptpad, English) {
|
||||
|
||||
var $body = $('body');
|
||||
|
||||
|
||||
9
www/auth/index.html
Normal file
9
www/auth/index.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
</head>
|
||||
<body class="html">
|
||||
</body>
|
||||
</html>
|
||||
59
www/auth/main.js
Normal file
59
www/auth/main.js
Normal file
@@ -0,0 +1,59 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js'
|
||||
], function ($, Cryptpad) {
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var signMsg = function (msg, privKey) {
|
||||
var signKey = Nacl.util.decodeBase64(privKey);
|
||||
var buffer = Nacl.util.decodeUTF8(msg);
|
||||
return Nacl.util.encodeBase64(Nacl.sign(buffer, signKey));
|
||||
};
|
||||
|
||||
// TODO: Allow authing for any domain as long as the user clicks an "accept" button
|
||||
// inside of the iframe.
|
||||
var AUTHORIZED_DOMAINS = [
|
||||
/\.cryptpad\.fr$/,
|
||||
/^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');
|
||||
$(window).on("message", function (jqe) {
|
||||
var evt = jqe.originalEvent;
|
||||
var data = JSON.parse(evt.data);
|
||||
var domain = evt.origin;
|
||||
var srcWindow = evt.source;
|
||||
var ret = { txid: data.txid };
|
||||
if (data.cmd === 'PING') {
|
||||
ret.res = 'PONG';
|
||||
} else if (data.cmd === 'SIGN') {
|
||||
if (!AUTHORIZED_DOMAINS.filter(function (x) { return x.test(domain); }).length) {
|
||||
ret.error = "UNAUTH_DOMAIN";
|
||||
} else if (!Cryptpad.isLoggedIn()) {
|
||||
ret.error = "NOT_LOGGED_IN";
|
||||
} else {
|
||||
var proxy = Cryptpad.getStore().getProxy().proxy;
|
||||
var sig = signMsg(data.data, proxy.edPrivate);
|
||||
ret.res = {
|
||||
uname: proxy.login_name,
|
||||
edPublic: proxy.edPublic,
|
||||
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";
|
||||
}
|
||||
srcWindow.postMessage(JSON.stringify(ret), domain);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -32,32 +32,75 @@
|
||||
<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;
|
||||
}
|
||||
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%;
|
||||
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;
|
||||
}
|
||||
#preview {
|
||||
max-width: 40vw;
|
||||
margin: auto;
|
||||
}
|
||||
#preview table tr td, #preview table tr th {
|
||||
border: 1px solid black;
|
||||
padding: 15px;
|
||||
}
|
||||
#preview table tr th {
|
||||
border: 3px solid black;
|
||||
}
|
||||
</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>
|
||||
|
||||
|
||||
661
www/code/main.js
661
www/code/main.js
@@ -1,37 +1,45 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/toolbar2.js',
|
||||
'json.sortify',
|
||||
'/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',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify) {
|
||||
var $ = window.jQuery;
|
||||
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;
|
||||
@@ -39,115 +47,26 @@ define([
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var onConnectError = function (info) {
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
|
||||
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.codeInitialState;
|
||||
|
||||
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: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }},
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
mode: "javascript",
|
||||
readOnly: true
|
||||
});
|
||||
editor.setValue(Messages.codeInitialState);
|
||||
var isHistoryMode = false;
|
||||
|
||||
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 userData = module.userData = {}; // List of pretty name of all users (mapped with their server ID)
|
||||
var userList; // List of users still connected to the channel (server IDs)
|
||||
var addToUserData = function(data) {
|
||||
var users = module.users;
|
||||
for (var attrname in data) { userData[attrname] = data[attrname]; }
|
||||
|
||||
if (users && users.length) {
|
||||
for (var userKey in userData) {
|
||||
if (users.indexOf(userKey) === -1) {
|
||||
delete userData[userKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(userList && typeof userList.onChange === "function") {
|
||||
userList.onChange(userData);
|
||||
}
|
||||
};
|
||||
|
||||
var myData = {};
|
||||
var myUserName = ''; // My "pretty name"
|
||||
var myID; // My server ID
|
||||
|
||||
var setMyID = function(info) {
|
||||
myID = info.myID || null;
|
||||
myUserName = myID;
|
||||
};
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = {
|
||||
initialState: '{}',
|
||||
@@ -157,16 +76,18 @@ define([
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
readOnly: readOnly,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
setMyID: setMyID,
|
||||
network: Cryptpad.getNetwork(),
|
||||
transformFunction: JsonOT.validate,
|
||||
};
|
||||
|
||||
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
|
||||
|
||||
var isDefaultTitle = function () {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
return Cryptpad.isDefaultName(parsed, document.title);
|
||||
var setHistory = function (bool, update) {
|
||||
isHistoryMode = bool;
|
||||
setEditable(!bool);
|
||||
if (!bool && update) {
|
||||
config.onRemote();
|
||||
}
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
@@ -175,374 +96,183 @@ define([
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: userData,
|
||||
defaultTitle: defaultName
|
||||
users: UserList.userData,
|
||||
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; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
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 setName = module.setName = function (newName) {
|
||||
if (typeof(newName) !== 'string') { return; }
|
||||
var myUserNameTemp = newName.trim();
|
||||
if(newName.trim().length > 32) {
|
||||
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
||||
var onModeChanged = function (mode) {
|
||||
var $codeMirror = $iframe.find('.CodeMirror');
|
||||
if (mode === "markdown") {
|
||||
APP.$previewButton.show();
|
||||
$previewContainer.show();
|
||||
$codeMirror.removeClass('fullPage');
|
||||
return;
|
||||
}
|
||||
myUserName = myUserNameTemp;
|
||||
myData[myID] = {
|
||||
name: myUserName,
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
Cryptpad.setAttribute('username', myUserName, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set username");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
onLocal();
|
||||
});
|
||||
APP.$previewButton.hide();
|
||||
$previewContainer.hide();
|
||||
$codeMirror.addClass('fullPage');
|
||||
};
|
||||
|
||||
var getHeadingText = function () {
|
||||
var lines = editor.getValue().split(/\n/);
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
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;
|
||||
}
|
||||
var titleCfg = { getHeadingText: CodeMirror.getHeadingText };
|
||||
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
|
||||
|
||||
// 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;
|
||||
}
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title);
|
||||
|
||||
// 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 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 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
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
userList = info.userList;
|
||||
|
||||
var config = {
|
||||
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
|
||||
userData: userData,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
common: Cryptpad
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: $bar
|
||||
};
|
||||
if (readOnly) {delete config.changeNameID; }
|
||||
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, config);
|
||||
toolbar = APP.toolbar = Toolbar.create(configTb);
|
||||
|
||||
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
||||
var $userBlock = $bar.find('.' + Toolbar.constants.username);
|
||||
var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername));
|
||||
Title.setToolbar(toolbar);
|
||||
CodeMirror.init(config.onLocal, Title, toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
var editHash;
|
||||
var viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {
|
||||
onLocal: config.onLocal(),
|
||||
onRemote: config.onRemote(),
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var remoteDoc = JSON.parse(val || '{}').content;
|
||||
editor.setValue(remoteDoc || '');
|
||||
editor.save();
|
||||
},
|
||||
$toolbar: $bar
|
||||
};
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
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 */
|
||||
var forgetCb = function (err, title) {
|
||||
var forgetCb = function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
};
|
||||
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 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);
|
||||
var $button = $block.find('.buttonTitle');
|
||||
|
||||
$block.find('a').click(function (e) {
|
||||
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
|
||||
});
|
||||
});
|
||||
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);
|
||||
var $button = $block.find('.buttonTitle');
|
||||
|
||||
setTheme(lastTheme, $block);
|
||||
|
||||
$block.find('a').click(function (e) {
|
||||
var theme = $(this).attr('data-value');
|
||||
setTheme(theme, $block);
|
||||
localStorage.setItem(themeKey, theme);
|
||||
});
|
||||
|
||||
$rightside.append($block);
|
||||
};
|
||||
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')) {
|
||||
$codeMirror.removeClass('fullPage');
|
||||
} else {
|
||||
$codeMirror.addClass('fullPage');
|
||||
}
|
||||
});
|
||||
$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); }
|
||||
|
||||
Cryptpad.onDisplayNameChanged(setName);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
module.users = info.userList.users;
|
||||
if (module.realtime !== info.realtime) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
config.onReady = function (info) {
|
||||
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; }
|
||||
@@ -560,154 +290,80 @@ 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 (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) {
|
||||
if (yes) { unnotify(); }
|
||||
});
|
||||
if (Cryptpad.initialName && Title.isDefaultTitle()) {
|
||||
Title.updateTitle(Cryptpad.initialName);
|
||||
}
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
//Cryptpad.log("Your document is ready");
|
||||
|
||||
onLocal(); // push local state to avoid parse errors later.
|
||||
Cryptpad.getLastName(function (err, lastName) {
|
||||
if (err) {
|
||||
console.log("Could not get previous name");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
// Update the toolbar list:
|
||||
// Add the current user in the metadata if he has edit rights
|
||||
if (readOnly) { return; }
|
||||
if (typeof(lastName) === 'string') {
|
||||
setName(lastName);
|
||||
} else {
|
||||
myData[myID] = {
|
||||
name: "",
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
onLocal();
|
||||
module.$userNameButton.click();
|
||||
}
|
||||
if (isNew) {
|
||||
Cryptpad.selectTemplate('code', info.realtime, Cryptget);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function (info) {
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
var scroll = editor.getScrollInfo();
|
||||
if (isHistoryMode) { return; }
|
||||
|
||||
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(); }
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
config.onAbort = function () {
|
||||
// inform of network disconnect
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
var onConnectionChange = config.onConnectionChange = function (info) {
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
@@ -719,9 +375,9 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var onError = config.onError = onConnectError;
|
||||
config.onError = onConnectError;
|
||||
|
||||
var realtime = module.realtime = Realtime.start(config);
|
||||
APP.realtime = Realtime.start(config);
|
||||
|
||||
editor.on('change', onLocal);
|
||||
|
||||
@@ -731,8 +387,9 @@ define([
|
||||
var interval = 100;
|
||||
|
||||
var second = function (CM) {
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen(CM);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info && info.type === "store") {
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
// This is stage 1, it can be changed but you must bump the version of the project.
|
||||
define([], function () {
|
||||
// fix up locations so that relative urls work.
|
||||
require.config({ baseUrl: window.location.pathname });
|
||||
require.config({
|
||||
baseUrl: window.location.pathname,
|
||||
paths: {
|
||||
// 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"
|
||||
}
|
||||
});
|
||||
|
||||
// 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')]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
define([
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function () {
|
||||
var $ = window.jQuery;
|
||||
define(['jquery'], function ($) {
|
||||
var Clipboard = {};
|
||||
|
||||
// copy arbitrary text to the clipboard
|
||||
// return boolean indicating success
|
||||
var copy = Clipboard.copy = function (text) {
|
||||
Clipboard.copy = function (text) {
|
||||
var $ta = $('<input>', {
|
||||
type: 'text',
|
||||
}).val(text);
|
||||
|
||||
$('body').append($ta);
|
||||
|
||||
if (!($ta.length && $ta[0].select)) {
|
||||
if (!($ta.length && $ta[0].select)) {
|
||||
// console.log("oops");
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
322
www/common/common-hash.js
Normal file
322
www/common/common-hash.js
Normal file
@@ -0,0 +1,322 @@
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-interface.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js'
|
||||
], function (Util, UI, Crypto) {
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var Hash = {};
|
||||
|
||||
var uint8ArrayToHex = Util.uint8ArrayToHex;
|
||||
var hexToBase64 = Util.hexToBase64;
|
||||
var base64ToHex = Util.base64ToHex;
|
||||
|
||||
// This implementation must match that on the server
|
||||
// it's used for a checksum
|
||||
Hash.hashChannelList = function (list) {
|
||||
return Nacl.util.encodeBase64(Nacl.hash(Nacl.util
|
||||
.decodeUTF8(JSON.stringify(list))));
|
||||
};
|
||||
|
||||
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
|
||||
if (typeof keys === 'string') {
|
||||
return chanKey + keys;
|
||||
}
|
||||
if (!keys.editKeyStr) { return; }
|
||||
return '/1/edit/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.editKeyStr)+'/';
|
||||
};
|
||||
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) {
|
||||
if (typeof keys === 'string') {
|
||||
return;
|
||||
}
|
||||
return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/';
|
||||
};
|
||||
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, 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;
|
||||
|
||||
var ret = {};
|
||||
|
||||
if (!href) { return ret; }
|
||||
if (href.slice(-1) !== '/') { href += '/'; }
|
||||
|
||||
var idx;
|
||||
|
||||
if (!/^https*:\/\//.test(href)) {
|
||||
idx = href.indexOf('/#');
|
||||
ret.type = href.slice(1, idx);
|
||||
ret.hash = href.slice(idx + 2);
|
||||
ret.hashData = parseTypeHash(ret.type, ret.hash);
|
||||
return ret;
|
||||
}
|
||||
|
||||
href.replace(patt, function (a, domain, type) {
|
||||
ret.domain = domain;
|
||||
ret.type = type;
|
||||
return '';
|
||||
});
|
||||
idx = href.indexOf('/#');
|
||||
ret.hash = href.slice(idx + 2);
|
||||
ret.hashData = parseTypeHash(ret.type, ret.hash);
|
||||
return ret;
|
||||
};
|
||||
|
||||
var getRelativeHref = Hash.getRelativeHref = function (href) {
|
||||
if (!href) { return; }
|
||||
if (href.indexOf('#') === -1) { return; }
|
||||
var parsed = parsePadUrl(href);
|
||||
return '/' + parsed.type + '/#' + parsed.hash;
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns all needed keys for a realtime channel
|
||||
* - 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 (type, secretHash) {
|
||||
var secret = {};
|
||||
var generate = function () {
|
||||
secret.keys = Crypto.createEditCryptor();
|
||||
secret.key = Crypto.createEditCryptor().editKeyStr;
|
||||
};
|
||||
if (!secretHash && !/#/.test(window.location.href)) {
|
||||
generate();
|
||||
return secret;
|
||||
} else {
|
||||
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 (parsed.version === 0) {
|
||||
// Old hash
|
||||
secret.channel = parsed.channel;
|
||||
secret.key = parsed.key;
|
||||
}
|
||||
else if (parsed.version === 1) {
|
||||
// New hash
|
||||
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) {
|
||||
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 (parsed.mode === 'view') {
|
||||
secret.keys = Crypto.createViewCryptor(parsed.key);
|
||||
if (secret.channel.length !== 32) {
|
||||
UI.alert("The channel key is invalid");
|
||||
throw new Error("The channel key is invalid");
|
||||
}
|
||||
}
|
||||
} else if (parsed.type === "file") {
|
||||
// version 2 hashes are to be used for encrypted blobs
|
||||
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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
return secret;
|
||||
};
|
||||
|
||||
Hash.getHashes = function (channel, secret) {
|
||||
var hashes = {};
|
||||
if (secret.keys.editKeyStr) {
|
||||
hashes.editHash = getEditHashFromKeys(channel, secret.keys);
|
||||
}
|
||||
if (secret.keys.viewKeyStr) {
|
||||
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
|
||||
}
|
||||
if (secret.keys.fileKeyStr) {
|
||||
hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
|
||||
}
|
||||
return hashes;
|
||||
};
|
||||
|
||||
var createChannelId = Hash.createChannelId = function () {
|
||||
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
|
||||
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
|
||||
throw new Error('channel ids must consist of 32 hex characters');
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
Hash.createRandomHash = function () {
|
||||
// 16 byte channel Id
|
||||
var channelId = Util.hexToBase64(createChannelId());
|
||||
// 18 byte encryption key
|
||||
var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
|
||||
return '/1/edit/' + [channelId, key].join('/') + '/';
|
||||
};
|
||||
|
||||
// STORAGE
|
||||
Hash.findWeaker = function (href, recents) {
|
||||
var rHref = href || getRelativeHref(window.location.href);
|
||||
var parsed = parsePadUrl(rHref);
|
||||
if (!parsed.hash) { return false; }
|
||||
var weaker;
|
||||
recents.some(function (pad) {
|
||||
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 = 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') {
|
||||
weaker = pad.href;
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
});
|
||||
return weaker;
|
||||
};
|
||||
var findStronger = Hash.findStronger = function (href, recents) {
|
||||
var rHref = href || getRelativeHref(window.location.href);
|
||||
var parsed = parsePadUrl(rHref);
|
||||
if (!parsed.hash) { return false; }
|
||||
var stronger;
|
||||
recents.some(function (pad) {
|
||||
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 = 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') {
|
||||
stronger = pad.href;
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
});
|
||||
return stronger;
|
||||
};
|
||||
Hash.isNotStrongestStored = function (href, recents) {
|
||||
return findStronger(href, recents);
|
||||
};
|
||||
|
||||
Hash.hrefToHexChannelId = function (href) {
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
if (!parsed || !parsed.hash) { return; }
|
||||
|
||||
parsed = parsed.hashData;
|
||||
if (parsed.version === 0) {
|
||||
return parsed.channel;
|
||||
} else if (parsed.version !== 1 && parsed.version !== 2) {
|
||||
console.error("parsed href had no version");
|
||||
console.error(parsed);
|
||||
return;
|
||||
}
|
||||
|
||||
var channel = parsed.channel;
|
||||
if (!channel) { return; }
|
||||
|
||||
var hex = base64ToHex(channel);
|
||||
return hex;
|
||||
};
|
||||
|
||||
Hash.getBlobPathFromHex = function (id) {
|
||||
return '/blob/' + id.slice(0,2) + '/' + id;
|
||||
};
|
||||
|
||||
Hash.serializeHash = function (hash) {
|
||||
if (hash && hash.slice(-1) !== "/") { hash += "/"; }
|
||||
return hash;
|
||||
};
|
||||
|
||||
return Hash;
|
||||
});
|
||||
249
www/common/common-history.js
Normal file
249
www/common/common-history.js
Normal file
@@ -0,0 +1,249 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
], function ($, JsonOT, Crypto) {
|
||||
var ChainPad = window.ChainPad;
|
||||
var History = {};
|
||||
|
||||
var getStates = function (rt) {
|
||||
var states = [];
|
||||
var b = rt.getAuthBlock();
|
||||
if (b) { states.unshift(b); }
|
||||
while (b.getParent()) {
|
||||
b = b.getParent();
|
||||
states.unshift(b);
|
||||
}
|
||||
return states;
|
||||
};
|
||||
|
||||
var loadHistory = function (config, common, cb) {
|
||||
var network = common.getNetwork();
|
||||
var hkn = network.historyKeeper;
|
||||
|
||||
var wcId = common.hrefToHexChannelId(config.href || window.location.href);
|
||||
|
||||
var createRealtime = function () {
|
||||
return ChainPad.create({
|
||||
userName: 'history',
|
||||
initialState: '',
|
||||
transformFunction: JsonOT.validate,
|
||||
logLevel: 0,
|
||||
noPrune: true
|
||||
});
|
||||
};
|
||||
var realtime = createRealtime();
|
||||
|
||||
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 () {
|
||||
cb('[GET_FULL_HISTORY_TIMEOUT]');
|
||||
}, 30000);
|
||||
|
||||
var parse = function (msg) {
|
||||
try {
|
||||
return JSON.parse(msg);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
var onMsg = function (msg) {
|
||||
var parsed = parse(msg);
|
||||
if (parsed[0] === 'FULL_HISTORY_END') {
|
||||
console.log('END');
|
||||
window.clearTimeout(to);
|
||||
cb(null, realtime);
|
||||
return;
|
||||
}
|
||||
if (parsed[0] !== 'FULL_HISTORY') { return; }
|
||||
msg = parsed[1][4];
|
||||
if (msg) {
|
||||
msg = msg.replace(/^cp\|/, '');
|
||||
var decryptedMsg = crypto.decrypt(msg, secret.keys.validateKey);
|
||||
realtime.message(decryptedMsg);
|
||||
}
|
||||
};
|
||||
|
||||
network.on('message', function (msg) {
|
||||
onMsg(msg);
|
||||
});
|
||||
|
||||
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', wcId, secret.keys.validateKey]));
|
||||
};
|
||||
|
||||
History.create = function (common, config) {
|
||||
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
|
||||
if (History.loading) { return void console.error("History is already being loaded..."); }
|
||||
History.loading = true;
|
||||
var $toolbar = config.$toolbar;
|
||||
|
||||
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;
|
||||
|
||||
var realtime;
|
||||
|
||||
var states = [];
|
||||
var c = states.length - 1;
|
||||
|
||||
var $hist = $toolbar.find('.cryptpad-toolbar-history');
|
||||
var $left = $toolbar.find('.cryptpad-toolbar-leftside');
|
||||
var $right = $toolbar.find('.cryptpad-toolbar-rightside');
|
||||
var $cke = $toolbar.find('.cke_toolbox_main');
|
||||
|
||||
var onUpdate;
|
||||
|
||||
var update = function () {
|
||||
if (!realtime) { return []; }
|
||||
states = getStates(realtime);
|
||||
if (typeof onUpdate === "function") { onUpdate(); }
|
||||
return states;
|
||||
};
|
||||
|
||||
// Get the content of the selected version, and change the version number
|
||||
var get = function (i) {
|
||||
i = parseInt(i);
|
||||
if (isNaN(i)) { return; }
|
||||
if (i < 0) { i = 0; }
|
||||
if (i > states.length - 1) { i = states.length - 1; }
|
||||
var val = states[i].getContent().doc;
|
||||
c = i;
|
||||
if (typeof onUpdate === "function") { onUpdate(); }
|
||||
$hist.find('.next, .previous').css('visibility', '');
|
||||
if (c === states.length - 1) { $hist.find('.next').css('visibility', 'hidden'); }
|
||||
if (c === 0) { $hist.find('.previous').css('visibility', 'hidden'); }
|
||||
return val || '';
|
||||
};
|
||||
|
||||
var getNext = function (step) {
|
||||
return typeof step === "number" ? get(c + step) : get(c + 1);
|
||||
};
|
||||
var getPrevious = function (step) {
|
||||
return typeof step === "number" ? get(c - step) : get(c - 1);
|
||||
};
|
||||
|
||||
// Create the history toolbar
|
||||
var display = function () {
|
||||
$hist.html('').show();
|
||||
$left.hide();
|
||||
$right.hide();
|
||||
$cke.hide();
|
||||
var $prev =$('<button>', {
|
||||
'class': 'previous fa fa-step-backward buttonPrimary',
|
||||
title: Messages.history_prev
|
||||
}).appendTo($hist);
|
||||
var $nav = $('<div>', {'class': 'goto'}).appendTo($hist);
|
||||
var $next = $('<button>', {
|
||||
'class': 'next fa fa-step-forward buttonPrimary',
|
||||
title: Messages.history_next
|
||||
}).appendTo($hist);
|
||||
|
||||
$('<label>').text(Messages.history_version).appendTo($nav);
|
||||
var $cur = $('<input>', {
|
||||
'class' : 'gotoInput',
|
||||
'type' : 'number',
|
||||
'min' : '1',
|
||||
'max' : states.length
|
||||
}).val(c + 1).appendTo($nav).mousedown(function (e) {
|
||||
// stopPropagation because the event would be cancelled by the dropdown menus
|
||||
e.stopPropagation();
|
||||
});
|
||||
var $label2 = $('<label>').text(' / '+ states.length).appendTo($nav);
|
||||
$('<br>').appendTo($nav);
|
||||
var $close = $('<button>', {
|
||||
'class':'closeHistory',
|
||||
title: Messages.history_closeTitle
|
||||
}).text(Messages.history_close).appendTo($nav);
|
||||
var $rev = $('<button>', {
|
||||
'class':'revertHistory buttonSuccess',
|
||||
title: Messages.history_restoreTitle
|
||||
}).text(Messages.history_restore).appendTo($nav);
|
||||
|
||||
onUpdate = function () {
|
||||
$cur.attr('max', states.length);
|
||||
$cur.val(c+1);
|
||||
$label2.text(' / ' + states.length);
|
||||
};
|
||||
|
||||
var close = function () {
|
||||
$hist.hide();
|
||||
$left.show();
|
||||
$right.show();
|
||||
$cke.show();
|
||||
};
|
||||
|
||||
// Buttons actions
|
||||
$prev.click(function () { render(getPrevious()); });
|
||||
$next.click(function () { render(getNext()); });
|
||||
$cur.keydown(function (e) {
|
||||
var p = function () { e.preventDefault(); };
|
||||
if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
|
||||
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
|
||||
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
|
||||
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
|
||||
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
|
||||
if (e.which === 27) { p(); $close.click(); }
|
||||
}).focus();
|
||||
$cur.on('change', function () {
|
||||
render( get($cur.val() - 1) );
|
||||
});
|
||||
$close.click(function () {
|
||||
states = [];
|
||||
close();
|
||||
onClose();
|
||||
});
|
||||
$rev.click(function () {
|
||||
common.confirm(Messages.history_restorePrompt, function (yes) {
|
||||
if (!yes) { return; }
|
||||
close();
|
||||
onRevert();
|
||||
common.log(Messages.history_restoreDone);
|
||||
});
|
||||
});
|
||||
|
||||
// Display the latest content
|
||||
render(get(c));
|
||||
};
|
||||
|
||||
// Load all the history messages into a new chainpad object
|
||||
loadHistory(config, common, function (err, newRt) {
|
||||
History.loading = false;
|
||||
if (err) { throw new Error(err); }
|
||||
realtime = newRt;
|
||||
update();
|
||||
c = states.length - 1;
|
||||
display();
|
||||
onReady();
|
||||
});
|
||||
};
|
||||
|
||||
return History;
|
||||
});
|
||||
|
||||
244
www/common/common-interface.js
Normal file
244
www/common/common-interface.js
Normal file
@@ -0,0 +1,244 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/customize/messages.js',
|
||||
'/common/common-util.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/alertifyjs/dist/js/alertify.js',
|
||||
'/common/notify.js',
|
||||
'/common/visible.js'
|
||||
], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible) {
|
||||
|
||||
var UI = {};
|
||||
|
||||
/*
|
||||
* Alertifyjs
|
||||
*/
|
||||
UI.Alertify = Alertify;
|
||||
|
||||
// set notification timeout
|
||||
Alertify._$$alertify.delay = AppConfig.notificationTimeout || 5000;
|
||||
|
||||
var findCancelButton = UI.findCancelButton = function () {
|
||||
return $('button.cancel');
|
||||
};
|
||||
|
||||
var findOKButton = UI.findOKButton = function () {
|
||||
return $('button.ok');
|
||||
};
|
||||
|
||||
var listenForKeys = UI.listenForKeys = function (yes, no) {
|
||||
var handler = function (e) {
|
||||
switch (e.which) {
|
||||
case 27: // cancel
|
||||
if (typeof(no) === 'function') { no(e); }
|
||||
no();
|
||||
break;
|
||||
case 13: // enter
|
||||
if (typeof(yes) === 'function') { yes(e); }
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
$(window).keyup(handler);
|
||||
return handler;
|
||||
};
|
||||
|
||||
var stopListening = UI.stopListening = function (handler) {
|
||||
$(window).off('keyup', handler);
|
||||
};
|
||||
|
||||
UI.alert = function (msg, cb, force) {
|
||||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
var close = function () {
|
||||
findOKButton().click();
|
||||
};
|
||||
var keyHandler = listenForKeys(close, close);
|
||||
Alertify.alert(msg, function (ev) {
|
||||
cb(ev);
|
||||
stopListening(keyHandler);
|
||||
});
|
||||
window.setTimeout(function () {
|
||||
findOKButton().focus();
|
||||
});
|
||||
};
|
||||
|
||||
UI.prompt = function (msg, def, cb, opt, force) {
|
||||
opt = opt || {};
|
||||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
|
||||
var keyHandler = listenForKeys(function () { // yes
|
||||
findOKButton().click();
|
||||
}, function () { // no
|
||||
findCancelButton().click();
|
||||
});
|
||||
|
||||
Alertify
|
||||
.defaultValue(def || '')
|
||||
.okBtn(opt.ok || Messages.okButton || 'OK')
|
||||
.cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel')
|
||||
.prompt(msg, function (val, ev) {
|
||||
cb(val, ev);
|
||||
stopListening(keyHandler);
|
||||
}, function (ev) {
|
||||
cb(null, ev);
|
||||
stopListening(keyHandler);
|
||||
});
|
||||
};
|
||||
|
||||
UI.confirm = function (msg, cb, opt, force, styleCB) {
|
||||
opt = opt || {};
|
||||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
|
||||
var keyHandler = listenForKeys(function () {
|
||||
findOKButton().click();
|
||||
}, function () {
|
||||
findCancelButton().click();
|
||||
});
|
||||
|
||||
Alertify
|
||||
.okBtn(opt.ok || Messages.okButton || 'OK')
|
||||
.cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel')
|
||||
.confirm(msg, function () {
|
||||
cb(true);
|
||||
stopListening(keyHandler);
|
||||
}, function () {
|
||||
cb(false);
|
||||
stopListening(keyHandler);
|
||||
});
|
||||
|
||||
window.setTimeout(function () {
|
||||
var $ok = findOKButton();
|
||||
var $cancel = findCancelButton();
|
||||
if (opt.okClass) { $ok.addClass(opt.okClass); }
|
||||
if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); }
|
||||
if (opt.reverseOrder) {
|
||||
$ok.insertBefore($ok.prev());
|
||||
}
|
||||
if (typeof(styleCB) === 'function') {
|
||||
styleCB($ok.closest('.dialog'));
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
UI.log = function (msg) {
|
||||
Alertify.success(Util.fixHTML(msg));
|
||||
};
|
||||
|
||||
UI.warn = function (msg) {
|
||||
Alertify.error(Util.fixHTML(msg));
|
||||
};
|
||||
|
||||
/*
|
||||
* spinner
|
||||
*/
|
||||
UI.spinner = function (parent) {
|
||||
var $target = $('<span>', {
|
||||
'class': 'fa fa-spinner fa-pulse fa-4x fa-fw'
|
||||
}).hide();
|
||||
|
||||
$(parent).append($target);
|
||||
|
||||
return {
|
||||
show: function () {
|
||||
$target.css('display', 'inline');
|
||||
return this;
|
||||
},
|
||||
hide: function () {
|
||||
$target.hide();
|
||||
return this;
|
||||
},
|
||||
get: function () {
|
||||
return $target;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var LOADING = 'loading';
|
||||
|
||||
var getRandomTip = function () {
|
||||
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
|
||||
var keys = Object.keys(Messages.tips);
|
||||
var rdm = Math.floor(Math.random() * keys.length);
|
||||
return Messages.tips[keys[rdm]];
|
||||
};
|
||||
UI.addLoadingScreen = function (loadingText, hideTips) {
|
||||
var $loading, $container;
|
||||
if ($('#' + LOADING).length) {
|
||||
$loading = $('#' + LOADING).show();
|
||||
if (loadingText) {
|
||||
$('#' + LOADING).find('p').text(loadingText);
|
||||
}
|
||||
$container = $loading.find('.loadingContainer');
|
||||
} else {
|
||||
$loading = $('<div>', {id: LOADING});
|
||||
$container = $('<div>', {'class': 'loadingContainer'});
|
||||
$container.append('<img class="cryptofist" src="/customize/cryptofist_small.png" />');
|
||||
var $spinner = $('<div>', {'class': 'spinnerContainer'});
|
||||
UI.spinner($spinner).show();
|
||||
var $text = $('<p>').text(loadingText || Messages.loading);
|
||||
$container.append($spinner).append($text);
|
||||
$loading.append($container);
|
||||
$('body').append($loading);
|
||||
}
|
||||
if (Messages.tips && !hideTips) {
|
||||
var $loadingTip = $('<div>', {'id': 'loadingTip'});
|
||||
$('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
|
||||
$loadingTip.css({
|
||||
'top': $('body').height()/2 + $container.height()/2 + 20 + 'px'
|
||||
});
|
||||
$('body').append($loadingTip);
|
||||
}
|
||||
};
|
||||
UI.removeLoadingScreen = function (cb) {
|
||||
$('#' + LOADING).fadeOut(750, cb);
|
||||
$('#loadingTip').css('top', '');
|
||||
window.setTimeout(function () {
|
||||
$('#loadingTip').fadeOut(750);
|
||||
}, 3000);
|
||||
};
|
||||
UI.errorLoadingScreen = function (error, transparent) {
|
||||
if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen(undefined, true); }
|
||||
$('.spinnerContainer').hide();
|
||||
if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
|
||||
$('#' + 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();
|
||||
$files.on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) { f(e.target.result, file); };
|
||||
reader.readAsText(file, type);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return UI;
|
||||
});
|
||||
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;
|
||||
});
|
||||
|
||||
105
www/common/common-userlist.js
Normal file
105
www/common/common-userlist.js
Normal file
@@ -0,0 +1,105 @@
|
||||
define(function () {
|
||||
var module = {};
|
||||
|
||||
module.create = function (info, onLocal, Cryptget, Cryptpad) {
|
||||
var exp = {};
|
||||
|
||||
var userData = exp.userData = {};
|
||||
var userList = exp.userList = info.userList;
|
||||
var myData = exp.myData = {};
|
||||
exp.myUserName = info.myID;
|
||||
exp.myNetfluxId = info.myID;
|
||||
|
||||
var network = Cryptpad.getNetwork();
|
||||
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
var appType = parsed ? parsed.type : undefined;
|
||||
|
||||
var addToUserData = exp.addToUserData = function(data) {
|
||||
var users = userList.users;
|
||||
for (var attrname in data) { userData[attrname] = data[attrname]; }
|
||||
|
||||
if (users && users.length) {
|
||||
for (var userKey in userData) {
|
||||
if (users.indexOf(userKey) === -1) {
|
||||
delete userData[userKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(userList && typeof userList.onChange === "function") {
|
||||
userList.onChange(userData);
|
||||
}
|
||||
};
|
||||
|
||||
exp.getToolbarConfig = function () {
|
||||
return {
|
||||
data: userData,
|
||||
list: userList,
|
||||
userNetfluxId: exp.myNetfluxId
|
||||
};
|
||||
};
|
||||
|
||||
var setName = exp.setName = function (newName, cb) {
|
||||
if (typeof(newName) !== 'string') { return; }
|
||||
var myUserNameTemp = newName.trim();
|
||||
if(myUserNameTemp.length > 32) {
|
||||
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
||||
}
|
||||
exp.myUserName = myUserNameTemp;
|
||||
myData = {};
|
||||
myData[exp.myNetfluxId] = {
|
||||
name: exp.myUserName,
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
Cryptpad.setAttribute('username', exp.myUserName, function (err) {
|
||||
if (err) {
|
||||
console.log("Couldn't set username");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
if (typeof cb === "function") { cb(); }
|
||||
});
|
||||
};
|
||||
|
||||
exp.getLastName = function ($changeNameButton, isNew) {
|
||||
Cryptpad.getLastName(function (err, lastName) {
|
||||
if (err) {
|
||||
console.log("Could not get previous name");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
// Update the toolbar list:
|
||||
// Add the current user in the metadata
|
||||
if (typeof(lastName) === 'string') {
|
||||
setName(lastName, onLocal);
|
||||
} else {
|
||||
myData[exp.myNetfluxId] = {
|
||||
name: "",
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
onLocal();
|
||||
$changeNameButton.click();
|
||||
}
|
||||
if (isNew && appType) {
|
||||
Cryptpad.selectTemplate(appType, info.realtime, Cryptget);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.onDisplayNameChanged(function (newName) {
|
||||
setName(newName, onLocal);
|
||||
});
|
||||
|
||||
network.on('reconnect', function (uid) {
|
||||
exp.myNetfluxId = uid;
|
||||
exp.setName(exp.myUserName);
|
||||
});
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
return module;
|
||||
});
|
||||
@@ -1,30 +1,30 @@
|
||||
define([], function () {
|
||||
var Util = {};
|
||||
|
||||
var find = Util.find = function (map, path) {
|
||||
Util.find = function (map, path) {
|
||||
return (map && path.reduce(function (p, n) {
|
||||
return typeof(p[n]) !== 'undefined' && p[n];
|
||||
}, map));
|
||||
};
|
||||
|
||||
var fixHTML = Util.fixHTML = function (str) {
|
||||
Util.fixHTML = function (str) {
|
||||
if (!str) { return ''; }
|
||||
return str.replace(/[<>&"']/g, function (x) {
|
||||
return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x];
|
||||
});
|
||||
};
|
||||
|
||||
var hexToBase64 = Util.hexToBase64 = function (hex) {
|
||||
Util.hexToBase64 = function (hex) {
|
||||
var hexArray = hex
|
||||
.replace(/\r|\n/g, "")
|
||||
.replace(/([\da-fA-F]{2}) ?/g, "0x$1 ")
|
||||
.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(/=+$/, '');
|
||||
};
|
||||
|
||||
var base64ToHex = Util.base64ToHex = function (b64String) {
|
||||
Util.base64ToHex = function (b64String) {
|
||||
var hexArray = [];
|
||||
atob(b64String.replace(/-/g, '/')).split("").forEach(function(e){
|
||||
var h = e.charCodeAt(0).toString(16);
|
||||
@@ -34,9 +34,9 @@ define([], function () {
|
||||
return hexArray.join("");
|
||||
};
|
||||
|
||||
var uint8ArrayToHex = Util.uint8ArrayToHex = function (a) {
|
||||
Util.uint8ArrayToHex = function (a) {
|
||||
// call slice so Uint8Arrays work as expected
|
||||
return Array.prototype.slice.call(a).map(function (e, i) {
|
||||
return Array.prototype.slice.call(a).map(function (e) {
|
||||
var n = Number(e & 0xff).toString(16);
|
||||
if (n === 'NaN') {
|
||||
throw new Error('invalid input resulted in NaN');
|
||||
@@ -51,7 +51,7 @@ define([], function () {
|
||||
}).join('');
|
||||
};
|
||||
|
||||
var deduplicateString = Util.deduplicateString = function (array) {
|
||||
Util.deduplicateString = function (array) {
|
||||
var a = array.slice();
|
||||
for(var i=0; i<a.length; i++) {
|
||||
for(var j=i+1; j<a.length; j++) {
|
||||
@@ -61,11 +61,11 @@ define([], function () {
|
||||
return a;
|
||||
};
|
||||
|
||||
var getHash = Util.getHash = function () {
|
||||
Util.getHash = function () {
|
||||
return window.location.hash.slice(1);
|
||||
};
|
||||
|
||||
var replaceHash = Util.replaceHash = function (hash) {
|
||||
Util.replaceHash = function (hash) {
|
||||
if (window.history && window.history.replaceState) {
|
||||
if (!/^#/.test(hash)) { hash = '#' + hash; }
|
||||
return void window.history.replaceState({}, window.document.title, hash);
|
||||
@@ -76,10 +76,60 @@ define([], function () {
|
||||
/*
|
||||
* Saving files
|
||||
*/
|
||||
var fixFileName = Util.fixFileName = function (filename) {
|
||||
Util.fixFileName = function (filename) {
|
||||
return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_')
|
||||
.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.ceil(bytes / oneMegabyte * 100) / 100;
|
||||
};
|
||||
|
||||
Util.bytesToKilobytes = function (bytes) {
|
||||
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;
|
||||
};
|
||||
|
||||
return Util;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
define([
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/scrypt-async/scrypt-async.min.js',
|
||||
], function () {
|
||||
], function (AppConfig) {
|
||||
var Cred = {};
|
||||
var Scrypt = window.scrypt;
|
||||
|
||||
@@ -8,22 +9,26 @@ define([
|
||||
return typeof(x) === 'string';
|
||||
};
|
||||
|
||||
var isValidUsername = Cred.isValidUsername = function (name) {
|
||||
Cred.isValidUsername = function (name) {
|
||||
return !!(name && isString(name));
|
||||
};
|
||||
|
||||
var isValidPassword = Cred.isValidPassword = function (passwd) {
|
||||
Cred.isValidPassword = function (passwd) {
|
||||
return !!(passwd && isString(passwd));
|
||||
};
|
||||
|
||||
var passwordsMatch = Cred.passwordsMatch = function (a, b) {
|
||||
Cred.passwordsMatch = function (a, b) {
|
||||
return isString(a) && isString(b) && a === b;
|
||||
};
|
||||
|
||||
var deriveFromPassphrase = Cred.deriveFromPassphrase = function
|
||||
(username, password, len, cb) {
|
||||
Cred.customSalt = function () {
|
||||
return typeof(AppConfig.loginSalt) === 'string'?
|
||||
AppConfig.loginSalt: '';
|
||||
};
|
||||
|
||||
Cred.deriveFromPassphrase = function (username, password, len, cb) {
|
||||
Scrypt(password,
|
||||
username,
|
||||
username + Cred.customSalt(), // salt
|
||||
8, // memoryCost (n)
|
||||
1024, // block size parameter (r)
|
||||
len || 128, // dkLen
|
||||
@@ -32,7 +37,7 @@ define([
|
||||
undefined); // format, could be 'base64'
|
||||
};
|
||||
|
||||
var dispenser = Cred.dispenser = function (bytes) {
|
||||
Cred.dispenser = function (bytes) {
|
||||
var entropy = {
|
||||
used: 0,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Crypto, Realtime, Cryptpad, TextPatcher) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
var noop = function () {};
|
||||
'/bower_components/textpatcher/TextPatcher.js'
|
||||
], function ($, Crypto, Realtime, Cryptpad, TextPatcher) {
|
||||
//var Messages = Cryptpad.Messages;
|
||||
//var noop = function () {};
|
||||
var finish = function (S, err, doc) {
|
||||
if (S.done) { return; }
|
||||
S.cb(err, doc);
|
||||
@@ -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(),
|
||||
@@ -50,14 +51,14 @@ define([
|
||||
var Session = { cb: cb, };
|
||||
var config = makeConfig(hash);
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
config.onReady = function (info) {
|
||||
var rt = Session.session = info.realtime;
|
||||
Session.network = info.network;
|
||||
finish(Session, void 0, rt.getUserDoc());
|
||||
};
|
||||
overwrite(config, opt);
|
||||
|
||||
var realtime = Session.realtime = Realtime.start(config);
|
||||
Session.realtime = Realtime.start(config);
|
||||
};
|
||||
|
||||
var put = function (hash, doc, cb, opt) {
|
||||
@@ -87,7 +88,7 @@ define([
|
||||
};
|
||||
overwrite(config, opt);
|
||||
|
||||
var realtime = Session.session = Realtime.start(config);
|
||||
Session.session = Realtime.start(config);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,7 @@
|
||||
define([
|
||||
'/common/treesome.js',
|
||||
'/bower_components/rangy/rangy-core.min.js'
|
||||
], function (Tree, Rangy, saveRestore) {
|
||||
var log = function (x) { console.log(x); };
|
||||
var error = function (x) { console.log(x); };
|
||||
], function (Tree, Rangy) {
|
||||
var verbose = function (x) { if (window.verboseMode) { console.log(x); } };
|
||||
|
||||
/* accepts the document used by the editor */
|
||||
@@ -45,7 +43,7 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var exists = cursor.exists = function () {
|
||||
cursor.exists = function () {
|
||||
return (Range.start.el?1:0) | (Range.end.el?2:0);
|
||||
};
|
||||
|
||||
@@ -55,7 +53,7 @@ define([
|
||||
2 if end
|
||||
3 if start and end
|
||||
*/
|
||||
var inNode = cursor.inNode = function (el) {
|
||||
cursor.inNode = function (el) {
|
||||
var state = ['start', 'end'].map(function (pos, i) {
|
||||
return Tree.contains(el, Range[pos].el)? i +1: 0;
|
||||
});
|
||||
@@ -122,7 +120,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var pushDelta = cursor.pushDelta = function (oldVal, newVal, offset) {
|
||||
cursor.pushDelta = function (oldVal, newVal) {
|
||||
if (oldVal === newVal) { return; }
|
||||
var commonStart = 0;
|
||||
while (oldVal.charAt(commonStart) === newVal.charAt(commonStart)) {
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
define([], function () {
|
||||
var exports = {};
|
||||
|
||||
var hexToUint8Array = exports.hexToUint8Array = function (s) {
|
||||
exports.hexToUint8Array = function (s) {
|
||||
// if not hex or odd number of characters
|
||||
if (!/[a-fA-F0-9]+/.test(s) || s.length % 2) { throw new Error("string is not hex"); }
|
||||
return s.split(/([0-9a-fA-F]{2})/)
|
||||
@@ -9,7 +9,7 @@ define([], function () {
|
||||
.map(function (x) { return Number('0x' + x); });
|
||||
};
|
||||
|
||||
var uint8ArrayToHex = exports.uint8ArrayToHex = function (a) {
|
||||
exports.uint8ArrayToHex = function (a) {
|
||||
return a.reduce(function(memo, i) {
|
||||
return memo + ((i < 16) ? '0' : '') + i.toString(16);
|
||||
}, '');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>About our feedback api</title>
|
||||
<script data-main="feedback-main.js" src="/bower_components/requirejs/require.js"></script>
|
||||
<script data-bootload="feedback-main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
body {
|
||||
max-width: 60vw;
|
||||
@@ -11,7 +11,7 @@ body {
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p data-localization="feedback_about">If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions.</p>
|
||||
<p data-localization="feedback_about">If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions</p>
|
||||
<p data-localization="feedback_privacy">We care about your privacy, and at the same time we want CryptPad to be very easy to use.
|
||||
We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken.</p>
|
||||
<p data-localization="feedback_optout">If you would like to opt out, visit <a href="/settings/">your user settings page</a>, where you'll find a checkbox to enable or disable user feedback</p>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
define([
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function () {
|
||||
var $ = window.jQuery;
|
||||
'jquery',
|
||||
], function ($) {
|
||||
var module = {};
|
||||
|
||||
var Messages = {};
|
||||
@@ -11,7 +10,7 @@ define([
|
||||
var TRASH = module.TRASH = "trash";
|
||||
var TEMPLATE = module.TEMPLATE = "template";
|
||||
|
||||
var init = module.init = function (files, config) {
|
||||
module.init = function (files, config) {
|
||||
var Cryptpad = config.Cryptpad;
|
||||
Messages = Cryptpad.Messages;
|
||||
|
||||
@@ -19,7 +18,7 @@ define([
|
||||
var NEW_FOLDER_NAME = Messages.fm_newFolder;
|
||||
var NEW_FILE_NAME = Messages.fm_newFile;
|
||||
|
||||
var DEBUG = config.DEBUG || false;
|
||||
//var DEBUG = config.DEBUG || false;
|
||||
var logging = function () {
|
||||
console.log.apply(console, arguments);
|
||||
};
|
||||
@@ -35,7 +34,7 @@ define([
|
||||
console.error.apply(console, arguments);
|
||||
};
|
||||
|
||||
var getStructure = exp.getStructure = function () {
|
||||
exp.getStructure = function () {
|
||||
var a = {};
|
||||
a[ROOT] = {};
|
||||
a[UNSORTED] = [];
|
||||
@@ -93,7 +92,7 @@ define([
|
||||
return path[0] === TRASH && path.length === 4;
|
||||
};
|
||||
|
||||
var isPathInFilesData = exp.isPathInFilesData = function (path) {
|
||||
exp.isPathInFilesData = function (path) {
|
||||
return path[0] && path[0] === FILES_DATA;
|
||||
};
|
||||
|
||||
@@ -101,7 +100,7 @@ define([
|
||||
return typeof(element) === "string";
|
||||
};
|
||||
|
||||
var isReadOnlyFile = exp.isReadOnlyFile = function (element) {
|
||||
exp.isReadOnlyFile = function (element) {
|
||||
if (!isFile(element)) { return false; }
|
||||
var parsed = Cryptpad.parsePadUrl(element);
|
||||
if (!parsed) { return false; }
|
||||
@@ -115,15 +114,15 @@ define([
|
||||
return typeof(element) !== "string";
|
||||
};
|
||||
|
||||
var isFolderEmpty = exp.isFolderEmpty = function (element) {
|
||||
exp.isFolderEmpty = function (element) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
return Object.keys(element).length === 0;
|
||||
};
|
||||
|
||||
var hasSubfolder = exp.hasSubfolder = function (element, trashRoot) {
|
||||
exp.hasSubfolder = function (element, trashRoot) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
var subfolder = 0;
|
||||
var addSubfolder = function (el, idx) {
|
||||
var addSubfolder = function (el) {
|
||||
subfolder += isFolder(el.element) ? 1 : 0;
|
||||
};
|
||||
for (var f in element) {
|
||||
@@ -138,10 +137,10 @@ define([
|
||||
return subfolder;
|
||||
};
|
||||
|
||||
var hasFile = exp.hasFile = function (element, trashRoot) {
|
||||
exp.hasFile = function (element, trashRoot) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
var file = 0;
|
||||
var addFile = function (el, idx) {
|
||||
var addFile = function (el) {
|
||||
file += isFile(el.element) ? 1 : 0;
|
||||
};
|
||||
for (var f in element) {
|
||||
@@ -190,10 +189,10 @@ define([
|
||||
return inTree;
|
||||
};
|
||||
|
||||
var isFileInTrash = function (file) {
|
||||
/* var isFileInTrash = function (file) {
|
||||
var inTrash = false;
|
||||
var root = files[TRASH];
|
||||
var filter = function (trashEl, idx) {
|
||||
var filter = function (trashEl) {
|
||||
inTrash = isFileInTree(file, trashEl.element);
|
||||
return inTrash;
|
||||
};
|
||||
@@ -206,11 +205,7 @@ define([
|
||||
if (inTrash) { break; }
|
||||
}
|
||||
return inTrash;
|
||||
};
|
||||
|
||||
var isFileInUnsorted = function (file) {
|
||||
return files[UNSORTED].indexOf(file) !== -1;
|
||||
};
|
||||
};*/
|
||||
|
||||
var getUnsortedFiles = exp.getUnsortedFiles = function () {
|
||||
if (!files[UNSORTED]) {
|
||||
@@ -245,7 +240,7 @@ define([
|
||||
var getTrashFiles = exp.getTrashFiles = function () {
|
||||
var root = files[TRASH];
|
||||
var ret = [];
|
||||
var addFiles = function (el, idx) {
|
||||
var addFiles = function (el) {
|
||||
if (isFile(el.element)) {
|
||||
if(ret.indexOf(el.element) === -1) { ret.push(el.element); }
|
||||
} else {
|
||||
@@ -262,7 +257,7 @@ define([
|
||||
return ret;
|
||||
};
|
||||
|
||||
var getFilesDataFiles = exp.getFilesDataFiles = function () {
|
||||
exp.getFilesDataFiles = function () {
|
||||
var ret = [];
|
||||
files[FILES_DATA].forEach(function (el) {
|
||||
if (el.href && ret.indexOf(el.href) === -1) {
|
||||
@@ -352,7 +347,7 @@ define([
|
||||
return rootpaths.concat(unsortedpaths, templatepaths, trashpaths);
|
||||
};
|
||||
|
||||
var search = exp.search = function (value) {
|
||||
exp.search = function (value) {
|
||||
if (typeof(value) !== "string") { return []; }
|
||||
var res = [];
|
||||
// Search in ROOT
|
||||
@@ -403,7 +398,7 @@ define([
|
||||
|
||||
var ret = [];
|
||||
res.forEach(function (l) {
|
||||
var paths = findFile(l);
|
||||
//var paths = findFile(l);
|
||||
ret.push({
|
||||
paths: findFile(l),
|
||||
data: exp.getFileData(l)
|
||||
@@ -510,7 +505,7 @@ define([
|
||||
files[TRASH][obj.name].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var deleteMultiplePermanently = exp.deletePathsPermanently = function (paths) {
|
||||
exp.deletePathsPermanently = function (paths) {
|
||||
var hrefPaths = paths.filter(isPathInHrefArray);
|
||||
var rootPaths = paths.filter(isPathInRoot);
|
||||
var trashPaths = paths.filter(isPathInTrash);
|
||||
@@ -724,7 +719,7 @@ define([
|
||||
if (cb) { cb(); }
|
||||
};
|
||||
|
||||
var moveElements = exp.moveElements = function (paths, newParentPath, cb) {
|
||||
exp.moveElements = function (paths, newParentPath, cb) {
|
||||
var unsortedPaths = paths.filter(isPathInHrefArray);
|
||||
moveHrefArrayElements(unsortedPaths, newParentPath);
|
||||
// Copy the elements to their new location
|
||||
@@ -736,7 +731,7 @@ define([
|
||||
};
|
||||
|
||||
// Import elements in the file manager
|
||||
var importElements = exp.importElements = function (elements, path, cb) {
|
||||
exp.importElements = function (elements, path, cb) {
|
||||
if (!elements || elements.length === 0) { return; }
|
||||
var newParent = findElement(files, path);
|
||||
if (!newParent) { debug("Trying to import elements into a non-existing folder"); return; }
|
||||
@@ -749,7 +744,7 @@ define([
|
||||
if(cb) { cb(); }
|
||||
};
|
||||
|
||||
var createNewFolder = exp.createNewFolder = function (folderPath, name, cb) {
|
||||
exp.createNewFolder = function (folderPath, name, cb) {
|
||||
var parentEl = findElement(files, folderPath);
|
||||
var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME);
|
||||
parentEl[folderName] = {};
|
||||
@@ -768,7 +763,7 @@ define([
|
||||
ctime: +new Date()
|
||||
});
|
||||
};
|
||||
var createNewFile = exp.createNewFile = function (filePath, name, type, cb) {
|
||||
exp.createNewFile = function (filePath, name, type, cb) {
|
||||
var parentEl = findElement(files, filePath);
|
||||
var fileName = getAvailableName(parentEl, name || NEW_FILE_NAME);
|
||||
var href = '/' + type + '/#' + Cryptpad.createRandomHash();
|
||||
@@ -800,7 +795,7 @@ define([
|
||||
};
|
||||
|
||||
// Restore an element (copy it elsewhere and remove from the trash root)
|
||||
var restoreTrash = exp.restoreTrash = function (path, cb) {
|
||||
exp.restoreTrash = function (path, cb) {
|
||||
if (!path || path.length !== 4 || path[0] !== TRASH) {
|
||||
debug("restoreTrash was called from an element not in the trash root: ", path);
|
||||
return;
|
||||
@@ -839,7 +834,7 @@ define([
|
||||
// Remove the last element from the path to get the parent path and the element name
|
||||
var parentPath = path.slice();
|
||||
var name;
|
||||
var element = findElement(files, path);
|
||||
//var element = findElement(files, path);
|
||||
if (path.length === 4) { // Trash root
|
||||
name = path[1];
|
||||
parentPath.pop();
|
||||
@@ -861,13 +856,13 @@ define([
|
||||
if(cb) { cb(); }
|
||||
};
|
||||
|
||||
var emptyTrash = exp.emptyTrash = function (cb) {
|
||||
exp.emptyTrash = function (cb) {
|
||||
files[TRASH] = {};
|
||||
checkDeletedFiles();
|
||||
if(cb) { cb(); }
|
||||
};
|
||||
|
||||
var deleteFileData = exp.deleteFileData = function (href, cb) {
|
||||
exp.deleteFileData = function (href, cb) {
|
||||
if (workgroup) { return; }
|
||||
|
||||
var toRemove = [];
|
||||
@@ -890,7 +885,7 @@ define([
|
||||
if(cb) { cb(); }
|
||||
};
|
||||
|
||||
var renameElement = exp.renameElement = function (path, newName, cb) {
|
||||
exp.renameElement = function (path, newName, cb) {
|
||||
if (path.length <= 1) {
|
||||
logError('Renaming `root` is forbidden');
|
||||
return;
|
||||
@@ -915,7 +910,7 @@ define([
|
||||
};
|
||||
|
||||
|
||||
var forgetPad = exp.forgetPad = function (href) {
|
||||
exp.forgetPad = function (href) {
|
||||
if (workgroup) { return; }
|
||||
if (!href || !isFile(href)) { return; }
|
||||
var path;
|
||||
@@ -986,7 +981,7 @@ define([
|
||||
};
|
||||
|
||||
// Replace a href by a stronger one everywhere in the drive (except FILES_DATA)
|
||||
var replaceHref = exp.replaceHref = function (o, n) {
|
||||
exp.replaceHref = function (o, n) {
|
||||
if (!isFile(o) || !isFile(n)) { return; }
|
||||
var paths = findFile(o);
|
||||
|
||||
@@ -1013,7 +1008,7 @@ define([
|
||||
|
||||
// addTemplate is called when we want to add a new pad, never visited, to the templates list
|
||||
// first, we must add it to FILES_DATA, so the input has to be an fileDAta object
|
||||
var addTemplate = exp.addTemplate = function (fileData) {
|
||||
exp.addTemplate = function (fileData) {
|
||||
if (workgroup) { return; }
|
||||
if (typeof fileData !== "object" || !fileData.href || !fileData.title) {
|
||||
console.error("filedata object expected to add a new template");
|
||||
@@ -1032,7 +1027,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var listTemplates = exp.listTemplates = function (type) {
|
||||
exp.listTemplates = function () {
|
||||
if (workgroup) { return; }
|
||||
var templateFiles = getTemplateFiles();
|
||||
var res = [];
|
||||
@@ -1050,7 +1045,7 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var fixFiles = exp.fixFiles = function () {
|
||||
exp.fixFiles = function () {
|
||||
// Explore the tree and check that everything is correct:
|
||||
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
|
||||
// * ROOT: Folders are objects, files are href
|
||||
@@ -1139,7 +1134,7 @@ define([
|
||||
var templateFiles = getTemplateFiles();
|
||||
var trashFiles = getTrashFiles();
|
||||
var toClean = [];
|
||||
fd.forEach(function (el, idx) {
|
||||
fd.forEach(function (el) {
|
||||
if (!el || typeof(el) !== "object") {
|
||||
debug("An element in filesData was not an object.", el);
|
||||
toClean.push(el);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/fileObject.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Listmap, Crypto, TextPatcher, FO) {
|
||||
'/common/userObject.js',
|
||||
], function ($, Listmap, Crypto, TextPatcher, FO) {
|
||||
/*
|
||||
This module uses localStorage, which is synchronous, but exposes an
|
||||
asyncronous API. This is so that we can substitute other storage
|
||||
@@ -13,7 +13,6 @@ define([
|
||||
To override these methods, create another file at:
|
||||
/customize/storage.js
|
||||
*/
|
||||
var $ = window.jQuery;
|
||||
|
||||
var Store = {};
|
||||
var store;
|
||||
@@ -89,21 +88,23 @@ define([
|
||||
ret.removeData = filesOp.removeData;
|
||||
ret.pushData = filesOp.pushData;
|
||||
|
||||
ret.addPad = function (href, path, name) {
|
||||
filesOp.addPad(href, path, name);
|
||||
ret.addPad = function (data, path) {
|
||||
filesOp.add(data, path);
|
||||
};
|
||||
|
||||
ret.forgetPad = function (href, cb) {
|
||||
filesOp.forgetPad(href);
|
||||
filesOp.forget(href);
|
||||
cb();
|
||||
};
|
||||
|
||||
ret.addTemplate = function (href) {
|
||||
filesOp.addTemplate(href);
|
||||
};
|
||||
|
||||
ret.listTemplates = function () {
|
||||
return filesOp.listTemplates();
|
||||
var templateFiles = filesOp.getFiles(['template']);
|
||||
var res = [];
|
||||
templateFiles.forEach(function (f) {
|
||||
var data = filesOp.getFileData(f);
|
||||
res.push(JSON.parse(JSON.stringify(data)));
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
ret.getProxy = function () {
|
||||
@@ -123,16 +124,24 @@ define([
|
||||
};
|
||||
|
||||
ret.replaceHref = function (o, n) {
|
||||
return filesOp.replaceHref(o, n);
|
||||
return filesOp.replace(o, n);
|
||||
};
|
||||
|
||||
var changeHandlers = ret.changeHandlers = [];
|
||||
ret.changeHandlers = [];
|
||||
|
||||
ret.change = function (f) {};
|
||||
ret.change = function () {};
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
var tryParsing = function (x) {
|
||||
try { return JSON.parse(x); }
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
var onReady = function (f, proxy, Cryptpad, exp) {
|
||||
var fo = exp.fo = FO.init(proxy.drive, {
|
||||
Cryptpad: Cryptpad
|
||||
@@ -144,6 +153,37 @@ define([
|
||||
f(void 0, store);
|
||||
}
|
||||
|
||||
var requestLogin = function () {
|
||||
// log out so that you don't go into an endless loop...
|
||||
Cryptpad.logout();
|
||||
|
||||
// redirect them to log in, and come back when they're done.
|
||||
sessionStorage.redirectTo = window.location.href;
|
||||
window.location.href = '/login/';
|
||||
};
|
||||
|
||||
var tokenKey = 'loginToken';
|
||||
if (Cryptpad.isLoggedIn()) {
|
||||
/* This isn't truly secure, since anyone who can read the user's object can
|
||||
set their local loginToken to match that in the object. However, it exposes
|
||||
a UI that will work most of the time. */
|
||||
|
||||
// every user object should have a persistent, random number
|
||||
if (typeof(proxy.loginToken) !== 'number') {
|
||||
proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
|
||||
}
|
||||
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.
|
||||
localStorage.setItem(tokenKey, proxy.loginToken);
|
||||
} else if (localToken !== proxy[tokenKey]) {
|
||||
// if it has been, and the local number doesn't match that in
|
||||
// the user object, request that they reauthenticate.
|
||||
return void requestLogin();
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof(proxy.allowUserFeedback) !== 'boolean') {
|
||||
proxy.allowUserFeedback = true;
|
||||
}
|
||||
@@ -154,10 +194,22 @@ define([
|
||||
proxy.uid = Cryptpad.createChannelId();
|
||||
}
|
||||
|
||||
proxy.on('change', [Cryptpad.displayNameKey], function (o, n, p) {
|
||||
// if the user is logged in, but does not have signing keys...
|
||||
if (Cryptpad.isLoggedIn() && !Cryptpad.hasSigningKeys(proxy)) {
|
||||
return void requestLogin();
|
||||
}
|
||||
|
||||
proxy.on('change', [Cryptpad.displayNameKey], function (o, n) {
|
||||
if (typeof(n) !== "string") { return; }
|
||||
Cryptpad.changeDisplayName(n);
|
||||
});
|
||||
proxy.on('change', [tokenKey], function () {
|
||||
console.log('wut');
|
||||
var localToken = tryParsing(localStorage.getItem(tokenKey));
|
||||
if (localToken !== proxy[tokenKey]) {
|
||||
return void requestLogin();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var initialized = false;
|
||||
@@ -170,7 +222,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(),
|
||||
@@ -185,7 +237,6 @@ define([
|
||||
var exp = {};
|
||||
|
||||
window.addEventListener('storage', function (e) {
|
||||
var key = e.key;
|
||||
if (e.key !== Cryptpad.userHashKey) { return; }
|
||||
var o = e.oldValue;
|
||||
var n = e.newValue;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/credential.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'/bower_components/scrypt-async/scrypt-async.min.js', // better load speed
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Listmap, Crypto, Cryptpad, Cred) {
|
||||
], function ($, Listmap, Crypto, Cryptpad, Cred) {
|
||||
var Exports = {
|
||||
Cred: Cred,
|
||||
};
|
||||
@@ -22,7 +22,7 @@ define([
|
||||
// 16 bytes for a deterministic channel key
|
||||
var channelSeed = dispense(16);
|
||||
// 32 bytes for a curve key
|
||||
var curveSeed = opt.curveSeed = dispense(32);
|
||||
opt.curveSeed = dispense(32);
|
||||
// 32 more for a signing key
|
||||
var edSeed = opt.edSeed = dispense(32);
|
||||
|
||||
@@ -43,9 +43,9 @@ define([
|
||||
// should never happen
|
||||
if (channelHex.length !== 32) { throw new Error('invalid channel id'); }
|
||||
|
||||
var channel64 = opt.channel64 = Cryptpad.hexToBase64(channelHex);
|
||||
opt.channel64 = Cryptpad.hexToBase64(channelHex);
|
||||
|
||||
var userHash = opt.userHash = '/1/edit/' + [opt.channel64, opt.keys.editKeyStr].join('/');
|
||||
opt.userHash = '/1/edit/' + [opt.channel64, opt.keys.editKeyStr].join('/');
|
||||
|
||||
return opt;
|
||||
};
|
||||
@@ -62,7 +62,7 @@ define([
|
||||
|
||||
var rt = opt.rt = Listmap.create(config);
|
||||
rt.proxy
|
||||
.on('ready', function (info) {
|
||||
.on('ready', function () {
|
||||
cb(void 0, rt);
|
||||
})
|
||||
.on('disconnect', function (info) {
|
||||
|
||||
1
www/common/media-tag.js
Normal file
1
www/common/media-tag.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,8 +1,7 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/fileObject.js',
|
||||
'/common/userObject.js',
|
||||
'json.sortify'
|
||||
], function (Cryptpad, Crypt, FO, Sortify) {
|
||||
var exp = {};
|
||||
@@ -47,7 +46,7 @@ define([
|
||||
var merge = function (obj1, obj2, keepOld) {
|
||||
if (typeof (obj1) !== "object" || typeof (obj2) !== "object") { return; }
|
||||
Object.keys(obj2).forEach(function (k) {
|
||||
var v = obj2[k];
|
||||
//var v = obj2[k];
|
||||
// If one of them is not an object or if we have a map and a array, don't override, create a new key
|
||||
if (!obj1[k] || typeof(obj1[k]) !== "object" || typeof(obj2[k]) !== "object" ||
|
||||
(getType(obj1[k]) !== getType(obj2[k]))) {
|
||||
@@ -76,12 +75,12 @@ define([
|
||||
console.error(msg || "Unable to find that path", path);
|
||||
};
|
||||
|
||||
if (path[0] === FO.TRASH && path.length === 4) {
|
||||
href = oldFo.getTrashElementData(path);
|
||||
if (oldFo.isInTrashRoot(path)) {
|
||||
href = oldFo.find(path.slice(0,3));
|
||||
path.pop();
|
||||
}
|
||||
|
||||
var p, next, nextRoot;
|
||||
var next, nextRoot;
|
||||
path.forEach(function (p, i) {
|
||||
if (!root) { return; }
|
||||
if (typeof(p) === "string") {
|
||||
@@ -129,7 +128,7 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var mergeAnonDrive = exp.anonDriveIntoUser = function (proxy, cb) {
|
||||
exp.anonDriveIntoUser = function (proxy, cb) {
|
||||
// Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb
|
||||
if (!localStorage.FS_hash || !Cryptpad.isLoggedIn()) {
|
||||
if (typeof(cb) === "function") { cb(); }
|
||||
@@ -156,13 +155,13 @@ define([
|
||||
var newData = Cryptpad.getStore().getProxy();
|
||||
var newFo = newData.fo;
|
||||
var newRecentPads = proxy.drive[Cryptpad.storageKey];
|
||||
var newFiles = newFo.getFilesDataFiles();
|
||||
var oldFiles = oldFo.getFilesDataFiles();
|
||||
var newFiles = newFo.getFiles([newFo.FILES_DATA]);
|
||||
var oldFiles = oldFo.getFiles([newFo.FILES_DATA]);
|
||||
oldFiles.forEach(function (href) {
|
||||
// Do not migrate a pad if we already have it, it would create a duplicate in the drive
|
||||
if (newFiles.indexOf(href) !== -1) { return; }
|
||||
// If we have a stronger version, do not add the current href
|
||||
if (Cryptpad.findStronger(href, newRecentPads)) { return; }
|
||||
if (Cryptpad.findStronger(href, newRecentPads)) { console.log(href); return; }
|
||||
// If we have a weaker version, replace the href by the new one
|
||||
// NOTE: if that weaker version is in the trash, the strong one will be put in unsorted
|
||||
var weaker = Cryptpad.findWeaker(href, newRecentPads);
|
||||
@@ -176,7 +175,7 @@ define([
|
||||
return;
|
||||
});
|
||||
// Update the file in the drive
|
||||
newFo.replaceHref(weaker, href);
|
||||
newFo.replace(weaker, href);
|
||||
return;
|
||||
}
|
||||
// Here it means we have a new href, so we should add it to the drive at its old location
|
||||
@@ -189,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(); }
|
||||
};
|
||||
|
||||
@@ -132,7 +132,7 @@ define(function () {
|
||||
};
|
||||
});
|
||||
|
||||
var extensionOf = Modes.extensionOf = function (mode) {
|
||||
Modes.extensionOf = function (mode) {
|
||||
var ext = '';
|
||||
list.some(function (o) {
|
||||
if (o.mode !== mode) { return; }
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
});
|
||||
};
|
||||
|
||||
var create = Module.create = function (msg, title, icon) {
|
||||
var create = Module.create = function (msg, title) {
|
||||
return new Notification(title,{
|
||||
// icon: icon,
|
||||
body: msg,
|
||||
});
|
||||
};
|
||||
|
||||
var system = Module.system = function (msg, title, icon) {
|
||||
Module.system = function (msg, title, icon) {
|
||||
// Let's check if the browser supports notifications
|
||||
if (!isSupported()) { console.log("Notifications are not supported"); }
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
var tab = Module.tab = function (frequency, count) {
|
||||
Module.tab = function (frequency, count) {
|
||||
var key = '_pendingTabNotification';
|
||||
|
||||
var favicon = document.getElementById('favicon');
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
define([
|
||||
'/common/rpc.js',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js'
|
||||
], function (Rpc) {
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var create = function (network, proxy, cb) {
|
||||
if (!network) {
|
||||
window.setTimeout(function () {
|
||||
@@ -41,21 +38,26 @@ define([
|
||||
|
||||
// you can ask the server to pin a particular channel for you
|
||||
exp.pin = function (channels, cb) {
|
||||
if (!Array.isArray(channels)) {
|
||||
window.setTimeout(function () {
|
||||
cb('[TypeError] pin expects an array');
|
||||
});
|
||||
return;
|
||||
}
|
||||
rpc.send('PIN', channels, cb);
|
||||
};
|
||||
|
||||
// you can also ask to unpin a particular channel
|
||||
exp.unpin = function (channels, cb) {
|
||||
if (!Array.isArray(channels)) {
|
||||
window.setTimeout(function () {
|
||||
cb('[TypeError] pin expects an array');
|
||||
});
|
||||
return;
|
||||
}
|
||||
rpc.send('UNPIN', channels, cb);
|
||||
};
|
||||
|
||||
// This implementation must match that on the server
|
||||
// it's used for a checksum
|
||||
exp.hashChannelList = function (list) {
|
||||
return Nacl.util.encodeBase64(Nacl.hash(Nacl.util
|
||||
.decodeUTF8(JSON.stringify(list))));
|
||||
};
|
||||
|
||||
// ask the server what it thinks your hash is
|
||||
exp.getServerHash = function (cb) {
|
||||
rpc.send('GET_HASH', edPublic, function (e, hash) {
|
||||
@@ -67,21 +69,120 @@ define([
|
||||
};
|
||||
|
||||
// if local and remote hashes don't match, send a reset
|
||||
exp.reset = function (list, cb) {
|
||||
rpc.send('RESET', list, function (e, response) {
|
||||
exp.reset = function (channels, cb) {
|
||||
if (!Array.isArray(channels)) {
|
||||
window.setTimeout(function () {
|
||||
cb('[TypeError] pin expects an array');
|
||||
});
|
||||
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]);
|
||||
});
|
||||
};
|
||||
|
||||
// get the total stored size of a channel's patches (in bytes)
|
||||
exp.getFileSize = function (file, cb) {
|
||||
rpc.send('GET_FILE_SIZE', file, cb);
|
||||
rpc.send('GET_FILE_SIZE', file, function (e, response) {
|
||||
if (e) { return void cb(e); }
|
||||
if (response && response.length) {
|
||||
cb(void 0, response[0]);
|
||||
} else {
|
||||
cb('INVALID_RESPONSE');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// take a list of channels and return a dictionary of their sizes
|
||||
exp.getMultipleFileSize = function (files, cb) {
|
||||
if (!Array.isArray(files)) {
|
||||
return window.setTimeout(function () {
|
||||
cb('[TypeError] pin expects an array');
|
||||
});
|
||||
}
|
||||
rpc.send('GET_MULTIPLE_FILE_SIZE', files, function (e, res) {
|
||||
if (e) { return void cb(e); }
|
||||
if (typeof(res) !== 'object') {
|
||||
return void cb('INVALID_RESPONSE');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// get the combined size of all channels (in bytes) for all the
|
||||
// channels which the server has pinned for your publicKey
|
||||
exp.getFileListSize = function (cb) {
|
||||
rpc.send('GET_TOTAL_SIZE', undefined, cb);
|
||||
rpc.send('GET_TOTAL_SIZE', undefined, function (e, response) {
|
||||
if (e) { return void cb(e); }
|
||||
if (response && response.length) {
|
||||
cb(void 0, response[0]);
|
||||
} else {
|
||||
cb('INVALID_RESPONSE');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
define([
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
], function () {
|
||||
var MAX_LAG_BEFORE_TIMEOUT = 30000;
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var uid = function () {
|
||||
@@ -102,9 +101,16 @@ types of messages:
|
||||
timeouts: {}, // timeouts
|
||||
pending: {}, // callbacks
|
||||
cookie: null,
|
||||
connected: true,
|
||||
};
|
||||
|
||||
var send = function (type, msg, cb) {
|
||||
if (!ctx.connected && type !== 'COOKIE') {
|
||||
return void window.setTimeout(function () {
|
||||
cb('DISCONNECTED');
|
||||
});
|
||||
}
|
||||
|
||||
// construct a signed message...
|
||||
|
||||
var data = [type, msg];
|
||||
@@ -123,11 +129,22 @@ types of messages:
|
||||
return sendMsg(ctx, data, cb);
|
||||
};
|
||||
|
||||
network.on('message', function (msg, sender) {
|
||||
network.on('message', function (msg) {
|
||||
onMsg(ctx, msg);
|
||||
});
|
||||
|
||||
send('COOKIE', "", function (e, msg) {
|
||||
network.on('disconnect', function () {
|
||||
ctx.connected = false;
|
||||
});
|
||||
|
||||
network.on('reconnect', function () {
|
||||
send('COOKIE', "", function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
ctx.connected = true;
|
||||
});
|
||||
});
|
||||
|
||||
send('COOKIE', "", function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
// callback to provide 'send' method to whatever needs it
|
||||
cb(void 0, { send: send, });
|
||||
|
||||
@@ -22,7 +22,7 @@ define(function () {
|
||||
"isotope isotope.css",
|
||||
"lesser-dark lesser-dark.css",
|
||||
"liquibyte liquibyte.css",
|
||||
"lol lol.css",
|
||||
"LOL lol.css",
|
||||
"material material.css",
|
||||
"mbo mbo.css",
|
||||
"mdn-like mdn-like.css",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/customize/application_config.js',
|
||||
'/api/config',
|
||||
'/bower_components/jquery/dist/jquery.min.js'
|
||||
], function (Config, ApiConfig) {
|
||||
var $ = window.jQuery;
|
||||
'/api/config'
|
||||
], function ($, Config, ApiConfig) {
|
||||
|
||||
var Messages = {};
|
||||
|
||||
@@ -17,12 +16,15 @@ define([
|
||||
/** Id of the div containing the lag info. */
|
||||
var LAG_ELEM_CLS = Bar.constants.lag = 'cryptpad-lag';
|
||||
|
||||
var LIMIT_ELEM_CLS = Bar.constants.lag = 'cryptpad-limit';
|
||||
|
||||
/** The toolbar class which contains the user list, debug link and lag. */
|
||||
var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar';
|
||||
|
||||
var TOP_CLS = Bar.constants.top = 'cryptpad-toolbar-top';
|
||||
var LEFTSIDE_CLS = Bar.constants.leftside = 'cryptpad-toolbar-leftside';
|
||||
var RIGHTSIDE_CLS = Bar.constants.rightside = 'cryptpad-toolbar-rightside';
|
||||
var HISTORY_CLS = Bar.constants.history = 'cryptpad-toolbar-history';
|
||||
|
||||
var SPINNER_CLS = Bar.constants.spinner = 'cryptpad-spinner';
|
||||
|
||||
@@ -74,7 +76,8 @@ define([
|
||||
})
|
||||
.append($('<div>', {'class': TOP_CLS}))
|
||||
.append($('<div>', {'class': LEFTSIDE_CLS}))
|
||||
.append($('<div>', {'class': RIGHTSIDE_CLS}));
|
||||
.append($('<div>', {'class': RIGHTSIDE_CLS}))
|
||||
.append($('<div>', {'class': HISTORY_CLS}));
|
||||
|
||||
// The 'notitle' class removes the line added for the title with a small screen
|
||||
if (!config || typeof config !== "object") {
|
||||
@@ -93,14 +96,14 @@ define([
|
||||
|
||||
var createSpinner = function ($container, config) {
|
||||
if (config.displayed.indexOf('spinner') !== -1) {
|
||||
var $spin = $('<span>');
|
||||
var $spin = $('<span>', {'class':SPINNER_CLS});
|
||||
var $spinner = $('<span>', {
|
||||
id: uid(),
|
||||
'class': SPINNER_CLS + ' spin fa fa-spinner fa-pulse',
|
||||
'class': 'spin fa fa-spinner fa-pulse',
|
||||
}).appendTo($spin).hide();
|
||||
$('<span>', {
|
||||
id: uid(),
|
||||
'class': SPINNER_CLS + ' synced fa fa-check',
|
||||
'class': 'synced fa fa-check',
|
||||
title: Messages.synced
|
||||
}).appendTo($spin);
|
||||
$container.prepend($spin);
|
||||
@@ -204,6 +207,13 @@ define([
|
||||
});
|
||||
}
|
||||
}
|
||||
if (hashes.fileHash) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {title: Messages.viewShareTitle, 'class': 'fileShare'},
|
||||
content: '<span class="fa fa-eye"></span> ' + Messages.viewShare
|
||||
});
|
||||
}
|
||||
var dropdownConfigShare = {
|
||||
text: $('<div>').append($shareIcon).append($span).html(),
|
||||
options: options
|
||||
@@ -222,7 +232,14 @@ define([
|
||||
}
|
||||
if (hashes.viewHash) {
|
||||
$shareBlock.find('a.viewShare').click(function () {
|
||||
var url = window.location.origin + window.location.pathname + '#' + hashes.viewHash;
|
||||
var url = window.location.origin + window.location.pathname + '#' + hashes.viewHash ;
|
||||
var success = Cryptpad.Clipboard.copy(url);
|
||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
||||
});
|
||||
}
|
||||
if (hashes.fileHash) {
|
||||
$shareBlock.find('a.fileShare').click(function () {
|
||||
var url = window.location.origin + window.location.pathname + '#' + hashes.fileHash ;
|
||||
var success = Cryptpad.Clipboard.copy(url);
|
||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
||||
});
|
||||
@@ -371,7 +388,7 @@ define([
|
||||
'class': LAG_ELEM_CLS,
|
||||
id: uid(),
|
||||
});
|
||||
var $a = $('<span>', {id: 'newLag'});
|
||||
var $a = $('<span>', {'class': 'cryptpad-lag', id: 'newLag'});
|
||||
$('<span>', {'class': 'bar1'}).appendTo($a);
|
||||
$('<span>', {'class': 'bar2'}).appendTo($a);
|
||||
$('<span>', {'class': 'bar3'}).appendTo($a);
|
||||
@@ -390,7 +407,7 @@ define([
|
||||
var title;
|
||||
var $lag = $(lagElement);
|
||||
if (lag) {
|
||||
$lag.attr('class', '');
|
||||
$lag.attr('class', 'cryptpad-lag');
|
||||
firstConnection = false;
|
||||
title = Messages.lag + ' : ' + lag + ' ms\n';
|
||||
if (lag > 30000) {
|
||||
@@ -411,7 +428,7 @@ define([
|
||||
}
|
||||
}
|
||||
else if (!firstConnection) {
|
||||
$lag.attr('class', '');
|
||||
$lag.attr('class', 'cryptpad-lag');
|
||||
// Display the red light at the 2nd failed attemp to get the lag
|
||||
lagLight.addClass('lag-red');
|
||||
title = Messages.redLight;
|
||||
@@ -473,6 +490,24 @@ define([
|
||||
$userContainer.append($lag);
|
||||
}
|
||||
|
||||
if (config.displayed.indexOf('limit') !== -1 && Config.enablePinning) {
|
||||
var usage;
|
||||
var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'});
|
||||
var $limit = $('<span>', {
|
||||
'class': LIMIT_ELEM_CLS,
|
||||
'title': Messages.pinLimitReached
|
||||
}).append($limitIcon).hide().appendTo($userContainer);
|
||||
var todo = function (e, overLimit) {
|
||||
if (e) { return void console.error("Unable to get the pinned usage"); }
|
||||
if (overLimit) {
|
||||
$limit.show().click(function () {
|
||||
Cryptpad.alert(Messages.pinLimitReachedAlert, null, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
Cryptpad.isOverPinLimit(todo);
|
||||
}
|
||||
|
||||
if (config.displayed.indexOf('newpad') !== -1) {
|
||||
var pads_options = [];
|
||||
Config.availablePadTypes.forEach(function (p) {
|
||||
@@ -502,10 +537,13 @@ define([
|
||||
|
||||
// User dropdown
|
||||
if (config.displayed.indexOf('useradmin') !== -1) {
|
||||
var userMenuCfg = {
|
||||
displayNameCls: USERNAME_CLS,
|
||||
changeNameButtonCls: USERBUTTON_CLS,
|
||||
};
|
||||
var userMenuCfg = {};
|
||||
if (!config.hideDisplayName) {
|
||||
userMenuCfg = {
|
||||
displayNameCls: USERNAME_CLS,
|
||||
changeNameButtonCls: USERBUTTON_CLS,
|
||||
};
|
||||
}
|
||||
if (readOnly !== 1) {
|
||||
userMenuCfg.displayName = 1;
|
||||
userMenuCfg.displayChangeName = 1;
|
||||
@@ -520,7 +558,8 @@ define([
|
||||
$userButton.click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Cryptpad.getLastName(function (lastName) {
|
||||
Cryptpad.getLastName(function (err, lastName) {
|
||||
if (err) { return void console.error("Cannot get last name", err); }
|
||||
Cryptpad.prompt(Messages.changeNamePrompt, lastName || '', function (newName) {
|
||||
if (newName === null && typeof(lastName) === "string") { return; }
|
||||
if (newName === null) { newName = ''; }
|
||||
@@ -673,25 +712,27 @@ define([
|
||||
}
|
||||
|
||||
// Update user list
|
||||
if (userData) {
|
||||
userList.change.push(function (newUserData) {
|
||||
var users = userList.users;
|
||||
if (users.indexOf(myUserName) !== -1) { connected = true; }
|
||||
if (!connected) { return; }
|
||||
checkSynchronizing(users, myUserName, $stateElement);
|
||||
updateUserList(config, myUserName, userListElement, users, userData, readOnly, $userAdminElement);
|
||||
});
|
||||
} else {
|
||||
userList.change.push(function () {
|
||||
var users = userList.users;
|
||||
if (users.indexOf(myUserName) !== -1) { connected = true; }
|
||||
if (!connected) { return; }
|
||||
checkSynchronizing(users, myUserName, $stateElement);
|
||||
});
|
||||
if (userList) {
|
||||
if (userData) {
|
||||
userList.change.push(function (newUserData) {
|
||||
var users = userList.users;
|
||||
if (users.indexOf(myUserName) !== -1) { connected = true; }
|
||||
if (!connected) { return; }
|
||||
checkSynchronizing(users, myUserName, $stateElement);
|
||||
updateUserList(config, myUserName, userListElement, users, userData, readOnly, $userAdminElement);
|
||||
});
|
||||
} else {
|
||||
userList.change.push(function () {
|
||||
var users = userList.users;
|
||||
if (users.indexOf(myUserName) !== -1) { connected = true; }
|
||||
if (!connected) { return; }
|
||||
checkSynchronizing(users, myUserName, $stateElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Display notifications when users are joining/leaving the session
|
||||
var oldUserData;
|
||||
if (typeof Cryptpad !== "undefined") {
|
||||
if (typeof Cryptpad !== "undefined" && userList) {
|
||||
var notify = function(type, name, oldname) {
|
||||
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
|
||||
if (typeof name === "undefined") { return; }
|
||||
@@ -757,7 +798,7 @@ define([
|
||||
for (var k in newdata) {
|
||||
if (k !== myUserName && userList.users.indexOf(k) !== -1) {
|
||||
if (typeof oldUserData[k] === "undefined") {
|
||||
// if the same uid is already present in the userdata, don't notify
|
||||
// if the same uid is already present in the userdata, don't notify
|
||||
if (!userPresent(k, newdata[k], oldUserData)) {
|
||||
notify(1, newdata[k].name);
|
||||
}
|
||||
@@ -776,14 +817,16 @@ define([
|
||||
};
|
||||
};
|
||||
|
||||
realtime.onPatch(ks());
|
||||
realtime.onMessage(ks(true));
|
||||
if (realtime) {
|
||||
realtime.onPatch(ks());
|
||||
realtime.onMessage(ks(true));
|
||||
|
||||
checkLag(getLag, lagElement);
|
||||
setInterval(function () {
|
||||
if (!connected) { return; }
|
||||
checkLag(getLag, lagElement);
|
||||
}, 3000);
|
||||
setInterval(function () {
|
||||
if (!connected) { return; }
|
||||
checkLag(getLag, lagElement);
|
||||
}, 3000);
|
||||
} else { connected = true; }
|
||||
|
||||
var failed = function () {
|
||||
connected = false;
|
||||
|
||||
905
www/common/toolbar2.js
Normal file
905
www/common/toolbar2.js
Normal file
@@ -0,0 +1,905 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/customize/application_config.js',
|
||||
'/api/config'
|
||||
], function ($, Config, ApiConfig) {
|
||||
var Messages = {};
|
||||
var Cryptpad;
|
||||
|
||||
var Bar = {
|
||||
constants: {},
|
||||
};
|
||||
|
||||
var SPINNER_DISAPPEAR_TIME = 3000;
|
||||
|
||||
// Toolbar parts
|
||||
var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar';
|
||||
var TOP_CLS = Bar.constants.top = 'cryptpad-toolbar-top';
|
||||
var LEFTSIDE_CLS = Bar.constants.leftside = 'cryptpad-toolbar-leftside';
|
||||
var RIGHTSIDE_CLS = Bar.constants.rightside = 'cryptpad-toolbar-rightside';
|
||||
var HISTORY_CLS = Bar.constants.history = 'cryptpad-toolbar-history';
|
||||
|
||||
// Userlist
|
||||
var USERLIST_CLS = Bar.constants.userlist = "cryptpad-dropdown-users";
|
||||
var EDITSHARE_CLS = Bar.constants.editShare = "cryptpad-dropdown-editShare";
|
||||
var VIEWSHARE_CLS = Bar.constants.viewShare = "cryptpad-dropdown-viewShare";
|
||||
var SHARE_CLS = Bar.constants.viewShare = "cryptpad-dropdown-share";
|
||||
|
||||
// Top parts
|
||||
var USER_CLS = Bar.constants.userAdmin = "cryptpad-user";
|
||||
var SPINNER_CLS = Bar.constants.spinner = 'cryptpad-spinner';
|
||||
var STATE_CLS = Bar.constants.state = 'cryptpad-state';
|
||||
var LAG_CLS = Bar.constants.lag = 'cryptpad-lag';
|
||||
var LIMIT_CLS = Bar.constants.lag = 'cryptpad-limit';
|
||||
var TITLE_CLS = Bar.constants.title = "cryptpad-title";
|
||||
var NEWPAD_CLS = Bar.constants.newpad = "cryptpad-newpad";
|
||||
|
||||
// User admin menu
|
||||
var USERADMIN_CLS = Bar.constants.user = 'cryptpad-user-dropdown';
|
||||
var USERNAME_CLS = Bar.constants.username = 'cryptpad-toolbar-username';
|
||||
var READONLY_CLS = Bar.constants.readonly = 'cryptpad-readonly';
|
||||
var USERBUTTON_CLS = Bar.constants.changeUsername = "cryptpad-change-username";
|
||||
|
||||
// Create the toolbar element
|
||||
|
||||
var uid = function () {
|
||||
return 'cryptpad-uid-' + String(Math.random()).substring(2);
|
||||
};
|
||||
|
||||
var styleToolbar = function ($container, href, version) {
|
||||
href = href || '/customize/toolbar.css' + (version?('?' + version): '');
|
||||
|
||||
$.ajax({
|
||||
url: href,
|
||||
dataType: 'text',
|
||||
success: function (data) {
|
||||
$container.append($('<style>').text(data));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
var createRealtimeToolbar = function (config) {
|
||||
if (!config.$container) { return; }
|
||||
var $container = config.$container;
|
||||
var $toolbar = $('<div>', {
|
||||
'class': TOOLBAR_CLS,
|
||||
id: uid(),
|
||||
});
|
||||
|
||||
var $topContainer = $('<div>', {'class': TOP_CLS});
|
||||
var $userContainer = $('<span>', {
|
||||
'class': USER_CLS
|
||||
}).appendTo($topContainer);
|
||||
$('<span>', {'class': SPINNER_CLS}).hide().appendTo($userContainer);
|
||||
$('<span>', {'class': STATE_CLS}).hide().appendTo($userContainer);
|
||||
$('<span>', {'class': LAG_CLS}).hide().appendTo($userContainer);
|
||||
$('<span>', {'class': LIMIT_CLS}).hide().appendTo($userContainer);
|
||||
$('<span>', {'class': NEWPAD_CLS + ' dropdown-bar'}).hide().appendTo($userContainer);
|
||||
$('<span>', {'class': USERADMIN_CLS + ' dropdown-bar'}).hide().appendTo($userContainer);
|
||||
|
||||
$toolbar.append($topContainer)
|
||||
.append($('<div>', {'class': LEFTSIDE_CLS}))
|
||||
.append($('<div>', {'class': RIGHTSIDE_CLS}))
|
||||
.append($('<div>', {'class': HISTORY_CLS}));
|
||||
|
||||
// The 'notitle' class removes the line added for the title with a small screen
|
||||
if (!config.title || typeof config.title !== "object") {
|
||||
$toolbar.addClass('notitle');
|
||||
}
|
||||
|
||||
$container.prepend($toolbar);
|
||||
|
||||
if (ApiConfig && ApiConfig.requireConf && ApiConfig.requireConf.urlArgs) {
|
||||
styleToolbar($container, undefined, ApiConfig.requireConf.urlArgs);
|
||||
} else {
|
||||
styleToolbar($container);
|
||||
}
|
||||
return $toolbar;
|
||||
};
|
||||
|
||||
// Userlist elements
|
||||
|
||||
var checkSynchronizing = function (toolbar, config) {
|
||||
if (!toolbar.state) { return; }
|
||||
var userList = config.userList.list.users;
|
||||
var userNetfluxId = config.userList.userNetfluxId;
|
||||
var meIdx = userList.indexOf(userNetfluxId);
|
||||
if (meIdx === -1) {
|
||||
toolbar.state.text(Messages.synchronizing);
|
||||
return;
|
||||
}
|
||||
toolbar.state.text('');
|
||||
};
|
||||
var getOtherUsers = function(config) {
|
||||
var userList = config.userList.list.users;
|
||||
var userData = config.userList.data;
|
||||
var userNetfluxId = config.userList.userNetfluxId;
|
||||
|
||||
var i = 0; // duplicates counter
|
||||
var list = [];
|
||||
|
||||
// Display only one time each user (if he is connected in multiple tabs)
|
||||
var myUid = userData[userNetfluxId] ? userData[userNetfluxId].uid : undefined;
|
||||
var uids = [];
|
||||
userList.forEach(function(user) {
|
||||
if (user !== userNetfluxId) {
|
||||
var data = userData[user] || {};
|
||||
var userName = data.name;
|
||||
var userId = data.uid;
|
||||
if (userName && uids.indexOf(userId) === -1 && (!myUid || userId !== myUid)) {
|
||||
uids.push(userId);
|
||||
list.push(userName);
|
||||
} else if (userName) { i++; }
|
||||
}
|
||||
});
|
||||
return {
|
||||
list: list,
|
||||
duplicates: i
|
||||
};
|
||||
};
|
||||
var arrayIntersect = function(a, b) {
|
||||
return $.grep(a, function(i) {
|
||||
return $.inArray(i, b) > -1;
|
||||
});
|
||||
};
|
||||
var updateUserList = function (toolbar, config) {
|
||||
// Make sure the elements are displayed
|
||||
var $userButtons = toolbar.userlist;
|
||||
|
||||
var userList = config.userList.list.users;
|
||||
var userData = config.userList.data;
|
||||
var userNetfluxId = config.userList.userNetfluxId;
|
||||
|
||||
var numberOfUsers = userList.length;
|
||||
|
||||
// If we are using old pads (readonly unavailable), only editing users are in userList.
|
||||
// With new pads, we also have readonly users in userList, so we have to intersect with
|
||||
// the userData to have only the editing users. We can't use userData directly since it
|
||||
// may contain data about users that have already left the channel.
|
||||
userList = config.readOnly === -1 ? userList : arrayIntersect(userList, Object.keys(userData));
|
||||
|
||||
// Names of editing users
|
||||
var others = getOtherUsers(config);
|
||||
var editUsersNames = others.list;
|
||||
var duplicates = others.duplicates; // Number of duplicates
|
||||
|
||||
var numberOfEditUsers = userList.length - duplicates;
|
||||
var numberOfViewUsers = numberOfUsers - userList.length;
|
||||
|
||||
// Number of anonymous editing users
|
||||
var anonymous = numberOfEditUsers - editUsersNames.length;
|
||||
|
||||
// Update the userlist
|
||||
var $usersTitle = $('<h2>').text(Messages.users);
|
||||
var $editUsers = $userButtons.find('.' + USERLIST_CLS);
|
||||
$editUsers.html('').append($usersTitle);
|
||||
|
||||
var $editUsersList = $('<pre>');
|
||||
// Yourself (edit only)
|
||||
if (config.readOnly !== 1) {
|
||||
$editUsers.append('<span class="yourself">' + Messages.yourself + '</span>');
|
||||
anonymous--;
|
||||
}
|
||||
// Editors
|
||||
$editUsersList.text(editUsersNames.join('\n')); // .text() to avoid XSS
|
||||
$editUsers.append($editUsersList);
|
||||
// Anonymous editors
|
||||
if (anonymous > 0) {
|
||||
var text = anonymous === 1 ? Messages.anonymousUser : Messages.anonymousUsers;
|
||||
$editUsers.append('<span class="anonymous">' + anonymous + ' ' + text + '</span>');
|
||||
}
|
||||
// Viewers
|
||||
if (numberOfViewUsers > 0) {
|
||||
var viewText = '<span class="viewer">';
|
||||
if (numberOfEditUsers > 0) {
|
||||
$editUsers.append('<br>');
|
||||
viewText += Messages.and + ' ';
|
||||
}
|
||||
var viewerText = numberOfViewUsers !== 1 ? Messages.viewers : Messages.viewer;
|
||||
viewText += numberOfViewUsers + ' ' + viewerText + '</span>';
|
||||
$editUsers.append(viewText);
|
||||
}
|
||||
|
||||
// Update the buttons
|
||||
var fa_editusers = '<span class="fa fa-users"></span>';
|
||||
var fa_viewusers = '<span class="fa fa-eye"></span>';
|
||||
var viewersText = numberOfViewUsers !== 1 ? Messages.viewers : Messages.viewer;
|
||||
var editorsText = numberOfEditUsers !== 1 ? Messages.editors : Messages.editor;
|
||||
var $span = $('<span>', {'class': 'large'}).html(fa_editusers + ' ' + numberOfEditUsers + ' ' + editorsText + ' ' + fa_viewusers + ' ' + numberOfViewUsers + ' ' + viewersText);
|
||||
var $spansmall = $('<span>', {'class': 'narrow'}).html(fa_editusers + ' ' + numberOfEditUsers + ' ' + fa_viewusers + ' ' + numberOfViewUsers);
|
||||
$userButtons.find('.buttonTitle').html('').append($span).append($spansmall);
|
||||
|
||||
// Change username in useradmin dropdown
|
||||
if (config.displayed.indexOf('useradmin') !== -1) {
|
||||
var $userAdminElement = toolbar.$userAdmin;
|
||||
var $userElement = $userAdminElement.find('.' + USERNAME_CLS);
|
||||
$userElement.show();
|
||||
if (config.readOnly === 1) {
|
||||
$userElement.addClass(READONLY_CLS).text(Messages.readonly);
|
||||
}
|
||||
else {
|
||||
var name = userData[userNetfluxId] && userData[userNetfluxId].name;
|
||||
if (!name) {
|
||||
name = Messages.anonymous;
|
||||
}
|
||||
$userElement.removeClass(READONLY_CLS).text(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var initUserList = function (toolbar, config) {
|
||||
if (config.userList && config.userList.list && config.userList.userNetfluxId) {
|
||||
var userList = config.userList.list;
|
||||
userList.change.push(function () {
|
||||
var users = userList.users;
|
||||
if (users.indexOf(config.userList.userNetfluxId) !== -1) {toolbar.connected = true;}
|
||||
if (!toolbar.connected) { return; }
|
||||
checkSynchronizing(toolbar, config);
|
||||
if (config.userList.data) {
|
||||
updateUserList(toolbar, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Create sub-elements
|
||||
|
||||
var createUserList = function (toolbar, config) {
|
||||
if (!config.userList || !config.userList.list ||
|
||||
!config.userList.data || !config.userList.userNetfluxId) {
|
||||
throw new Error("You must provide a `userList` object to display the userlist");
|
||||
}
|
||||
var dropdownConfig = {
|
||||
options: [{
|
||||
tag: 'p',
|
||||
attributes: {'class': USERLIST_CLS},
|
||||
}]
|
||||
};
|
||||
var $block = Cryptpad.createDropdown(dropdownConfig);
|
||||
$block.attr('id', 'userButtons');
|
||||
toolbar.$leftside.prepend($block);
|
||||
|
||||
return $block;
|
||||
};
|
||||
|
||||
var createShare = function (toolbar, config) {
|
||||
var secret = Cryptpad.find(config, ['share', 'secret']);
|
||||
var channel = Cryptpad.find(config, ['share', 'channel']);
|
||||
if (!secret || !channel) {
|
||||
throw new Error("Unable to display the share button: share.secret and share.channel required");
|
||||
}
|
||||
Cryptpad.getRecentPads(function (err, recent) {
|
||||
var $shareIcon = $('<span>', {'class': 'fa fa-share-alt'});
|
||||
var $span = $('<span>', {'class': 'large'}).append(' ' +Messages.shareButton);
|
||||
var hashes = Cryptpad.getHashes(channel, secret);
|
||||
var options = [];
|
||||
|
||||
// If we have a stronger version in drive, add it and add a redirect button
|
||||
var stronger = recent && Cryptpad.findStronger(null, recent);
|
||||
if (stronger) {
|
||||
var parsed = Cryptpad.parsePadUrl(stronger);
|
||||
hashes.editHash = parsed.hash;
|
||||
}
|
||||
|
||||
if (hashes.editHash) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {title: Messages.editShareTitle, 'class': 'editShare'},
|
||||
content: '<span class="fa fa-users"></span> ' + Messages.editShare
|
||||
});
|
||||
if (stronger) {
|
||||
// We're in view mode, display the "open editing link" button
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
title: Messages.editOpenTitle,
|
||||
'class': 'editOpen',
|
||||
href: window.location.pathname + '#' + hashes.editHash,
|
||||
target: '_blank'
|
||||
},
|
||||
content: '<span class="fa fa-users"></span> ' + Messages.editOpen
|
||||
});
|
||||
}
|
||||
options.push({tag: 'hr'});
|
||||
}
|
||||
if (hashes.viewHash) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {title: Messages.viewShareTitle, 'class': 'viewShare'},
|
||||
content: '<span class="fa fa-eye"></span> ' + Messages.viewShare
|
||||
});
|
||||
if (hashes.editHash && !stronger) {
|
||||
// We're in edit mode, display the "open readonly" button
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
title: Messages.viewOpenTitle,
|
||||
'class': 'viewOpen',
|
||||
href: window.location.pathname + '#' + hashes.viewHash,
|
||||
target: '_blank'
|
||||
},
|
||||
content: '<span class="fa fa-eye"></span> ' + Messages.viewOpen
|
||||
});
|
||||
}
|
||||
}
|
||||
if (hashes.fileHash) {
|
||||
options.push({
|
||||
tag: 'a',
|
||||
attributes: {title: Messages.viewShareTitle, 'class': 'fileShare'},
|
||||
content: '<span class="fa fa-eye"></span> ' + Messages.viewShare
|
||||
});
|
||||
}
|
||||
var dropdownConfigShare = {
|
||||
text: $('<div>').append($shareIcon).append($span).html(),
|
||||
options: options
|
||||
};
|
||||
var $shareBlock = Cryptpad.createDropdown(dropdownConfigShare);
|
||||
$shareBlock.find('button').attr('id', 'shareButton');
|
||||
$shareBlock.find('.dropdown-bar-content').addClass(SHARE_CLS).addClass(EDITSHARE_CLS).addClass(VIEWSHARE_CLS);
|
||||
|
||||
if (hashes.editHash) {
|
||||
$shareBlock.find('a.editShare').click(function () {
|
||||
var url = window.location.origin + window.location.pathname + '#' + hashes.editHash;
|
||||
var success = Cryptpad.Clipboard.copy(url);
|
||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
||||
});
|
||||
}
|
||||
if (hashes.viewHash) {
|
||||
$shareBlock.find('a.viewShare').click(function () {
|
||||
var url = window.location.origin + window.location.pathname + '#' + hashes.viewHash ;
|
||||
var success = Cryptpad.Clipboard.copy(url);
|
||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
||||
});
|
||||
}
|
||||
if (hashes.fileHash) {
|
||||
$shareBlock.find('a.fileShare').click(function () {
|
||||
var url = window.location.origin + window.location.pathname + '#' + hashes.fileHash ;
|
||||
var success = Cryptpad.Clipboard.copy(url);
|
||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
||||
});
|
||||
}
|
||||
|
||||
toolbar.$leftside.append($shareBlock);
|
||||
toolbar.share = $shareBlock;
|
||||
});
|
||||
|
||||
return "Loading share button";
|
||||
};
|
||||
|
||||
var createFileShare = function (toolbar) {
|
||||
if (!window.location.hash) {
|
||||
throw new Error("Unable to display the share button: hash required in the URL");
|
||||
}
|
||||
var $shareIcon = $('<span>', {'class': 'fa fa-share-alt'});
|
||||
var $span = $('<span>', {'class': 'large'}).append(' ' +Messages.shareButton);
|
||||
var $button = $('<button>', {'id': 'shareButton'}).append($shareIcon).append($span);
|
||||
$button.click(function () {
|
||||
var url = window.location.href;
|
||||
var success = Cryptpad.Clipboard.copy(url);
|
||||
if (success) { Cryptpad.log(Messages.shareSuccess); }
|
||||
});
|
||||
|
||||
toolbar.$leftside.append($button);
|
||||
return $button;
|
||||
};
|
||||
|
||||
var createTitle = function (toolbar, config) {
|
||||
var $titleContainer = $('<span>', {
|
||||
id: 'toolbarTitle',
|
||||
'class': TITLE_CLS
|
||||
}).appendTo(toolbar.$top);
|
||||
|
||||
// TODO: move these functions to toolbar or common?
|
||||
if (typeof config.title !== "object") {
|
||||
console.error("config.title", config);
|
||||
throw new Error("config.title is not an object");
|
||||
}
|
||||
var callback = config.title.onRename;
|
||||
var placeholder = config.title.defaultName;
|
||||
var suggestName = config.title.suggestName;
|
||||
|
||||
// Buttons
|
||||
var $text = $('<span>', {
|
||||
'class': 'title'
|
||||
}).appendTo($titleContainer);
|
||||
var $pencilIcon = $('<span>', {
|
||||
'class': 'pencilIcon',
|
||||
'title': Messages.clickToEdit
|
||||
});
|
||||
if (config.readOnly === 1 || typeof(Cryptpad) === "undefined") { return $titleContainer; }
|
||||
var $input = $('<input>', {
|
||||
type: 'text',
|
||||
placeholder: placeholder
|
||||
}).appendTo($titleContainer).hide();
|
||||
if (config.readOnly !== 1) {
|
||||
$text.attr("title", Messages.clickToEdit);
|
||||
$text.addClass("editable");
|
||||
var $icon = $('<span>', {
|
||||
'class': 'fa fa-pencil readonly',
|
||||
style: 'font-family: FontAwesome;'
|
||||
});
|
||||
$pencilIcon.append($icon).appendTo($titleContainer);
|
||||
}
|
||||
|
||||
// Events
|
||||
$input.on('mousedown', function (e) {
|
||||
if (!$input.is(":focus")) {
|
||||
$input.focus();
|
||||
}
|
||||
e.stopPropagation();
|
||||
return true;
|
||||
});
|
||||
$input.on('keyup', function (e) {
|
||||
if (e.which === 13 && toolbar.connected === true) {
|
||||
var name = $input.val().trim();
|
||||
if (name === "") {
|
||||
name = $input.attr('placeholder');
|
||||
}
|
||||
Cryptpad.renamePad(name, function (err, newtitle) {
|
||||
if (err) { return; }
|
||||
$text.text(newtitle);
|
||||
callback(null, newtitle);
|
||||
$input.hide();
|
||||
$text.show();
|
||||
//$pencilIcon.css('display', '');
|
||||
});
|
||||
} else if (e.which === 27) {
|
||||
$input.hide();
|
||||
$text.show();
|
||||
//$pencilIcon.css('display', '');
|
||||
}
|
||||
});
|
||||
|
||||
var displayInput = function () {
|
||||
if (toolbar.connected === false) { return; }
|
||||
$text.hide();
|
||||
//$pencilIcon.css('display', 'none');
|
||||
var inputVal = suggestName() || "";
|
||||
$input.val(inputVal);
|
||||
$input.show();
|
||||
$input.focus();
|
||||
};
|
||||
$text.on('click', displayInput);
|
||||
$pencilIcon.on('click', displayInput);
|
||||
return $titleContainer;
|
||||
};
|
||||
|
||||
var createLinkToMain = function (toolbar) {
|
||||
var $linkContainer = $('<span>', {
|
||||
'class': "cryptpad-link"
|
||||
}).appendTo(toolbar.$top);
|
||||
var $imgTag = $('<img>', {
|
||||
src: "/customize/cryptofist_mini.png",
|
||||
alt: "Cryptpad"
|
||||
});
|
||||
|
||||
// We need to override the "a" tag action here because it is inside the iframe!
|
||||
var $aTagSmall = $('<a>', {
|
||||
href: "/",
|
||||
title: Messages.header_logoTitle,
|
||||
'class': "cryptpad-logo"
|
||||
}).append($imgTag);
|
||||
var $span = $('<span>').text('CryptPad');
|
||||
var $aTagBig = $aTagSmall.clone().addClass('large').append($span);
|
||||
$aTagSmall.addClass('narrow');
|
||||
var onClick = function (e) {
|
||||
e.preventDefault();
|
||||
if (e.ctrlKey) {
|
||||
window.open('/');
|
||||
return;
|
||||
}
|
||||
window.location = "/";
|
||||
};
|
||||
|
||||
var onContext = function (e) { e.stopPropagation(); };
|
||||
|
||||
$aTagBig.click(onClick).contextmenu(onContext);
|
||||
$aTagSmall.click(onClick).contextmenu(onContext);
|
||||
|
||||
$linkContainer.append($aTagSmall).append($aTagBig);
|
||||
|
||||
return $linkContainer;
|
||||
};
|
||||
|
||||
var checkLag = function (toolbar, config, $lagEl) {
|
||||
var lag;
|
||||
var $lag = $lagEl || toolbar.lag;
|
||||
if (!$lag) { return; }
|
||||
var getLag = config.network.getLag;
|
||||
if(typeof getLag === "function") {
|
||||
lag = getLag();
|
||||
}
|
||||
var lagLight = $('<div>', {
|
||||
'class': 'lag'
|
||||
});
|
||||
var title;
|
||||
if (lag && toolbar.connected) {
|
||||
$lag.attr('class', LAG_CLS);
|
||||
toolbar.firstConnection = false;
|
||||
title = Messages.lag + ' : ' + lag + ' ms\n';
|
||||
if (lag > 30000) {
|
||||
$lag.addClass('lag0');
|
||||
title = Messages.redLight;
|
||||
} else if (lag > 5000) {
|
||||
$lag.addClass('lag1');
|
||||
title += Messages.orangeLight;
|
||||
} else if (lag > 1000) {
|
||||
$lag.addClass('lag2');
|
||||
title += Messages.orangeLight;
|
||||
} else if (lag > 300) {
|
||||
$lag.addClass('lag3');
|
||||
title += Messages.greenLight;
|
||||
} else {
|
||||
$lag.addClass('lag4');
|
||||
title += Messages.greenLight;
|
||||
}
|
||||
}
|
||||
else if (!toolbar.firstConnection) {
|
||||
$lag.attr('class', LAG_CLS);
|
||||
// Display the red light at the 2nd failed attemp to get the lag
|
||||
lagLight.addClass('lag-red');
|
||||
title = Messages.redLight;
|
||||
}
|
||||
if (title) {
|
||||
$lag.attr('title', title);
|
||||
}
|
||||
};
|
||||
var createLag = function (toolbar, config) {
|
||||
var $a = toolbar.$userAdmin.find('.'+LAG_CLS).show();
|
||||
$('<span>', {'class': 'bar1'}).appendTo($a);
|
||||
$('<span>', {'class': 'bar2'}).appendTo($a);
|
||||
$('<span>', {'class': 'bar3'}).appendTo($a);
|
||||
$('<span>', {'class': 'bar4'}).appendTo($a);
|
||||
if (config.realtime) {
|
||||
checkLag(toolbar, config, $a);
|
||||
setInterval(function () {
|
||||
if (!toolbar.connected) { return; }
|
||||
checkLag(toolbar, config);
|
||||
}, 3000);
|
||||
}
|
||||
return $a;
|
||||
};
|
||||
|
||||
var kickSpinner = function (toolbar, config, local) {
|
||||
if (!toolbar.spinner) { return; }
|
||||
var $spin = toolbar.spinner;
|
||||
$spin.find('.spin').show();
|
||||
$spin.find('.synced').hide();
|
||||
var onSynced = function () {
|
||||
if ($spin.timeout) { clearTimeout($spin.timeout); }
|
||||
$spin.timeout = setTimeout(function () {
|
||||
$spin.find('.spin').hide();
|
||||
$spin.find('.synced').show();
|
||||
}, local ? 0 : SPINNER_DISAPPEAR_TIME);
|
||||
};
|
||||
if (Cryptpad) {
|
||||
Cryptpad.whenRealtimeSyncs(config.realtime, onSynced);
|
||||
return;
|
||||
}
|
||||
onSynced();
|
||||
};
|
||||
var ks = function (toolbar, config, local) {
|
||||
return function () {
|
||||
if (toolbar.connected) { kickSpinner(toolbar, config, local); }
|
||||
};
|
||||
};
|
||||
var createSpinner = function (toolbar, config) {
|
||||
var $spin = toolbar.$userAdmin.find('.'+SPINNER_CLS).show();
|
||||
$('<span>', {
|
||||
id: uid(),
|
||||
'class': 'spin fa fa-spinner fa-pulse',
|
||||
}).appendTo($spin).hide();
|
||||
$('<span>', {
|
||||
id: uid(),
|
||||
'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));
|
||||
}
|
||||
return $spin;
|
||||
};
|
||||
|
||||
var createState = function (toolbar) {
|
||||
return toolbar.$userAdmin.find('.'+STATE_CLS).text(Messages.synchronizing).show();
|
||||
};
|
||||
|
||||
var createLimit = function (toolbar) {
|
||||
if (!Config.enablePinning) { return; }
|
||||
var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'});
|
||||
var $limit = toolbar.$userAdmin.find('.'+LIMIT_CLS).attr({
|
||||
'title': Messages.pinLimitReached
|
||||
}).append($limitIcon).hide();
|
||||
var todo = function (e, overLimit) {
|
||||
if (e) { return void console.error("Unable to get the pinned usage"); }
|
||||
if (overLimit) {
|
||||
$limit.show().click(function () {
|
||||
Cryptpad.alert(Messages._getKey('pinLimitReachedAlert', [encodeURIComponent(window.location.hostname)]), null, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
Cryptpad.isOverPinLimit(todo);
|
||||
return $limit;
|
||||
};
|
||||
|
||||
var createNewPad = function (toolbar) {
|
||||
var $newPad = toolbar.$userAdmin.find('.'+NEWPAD_CLS).show();
|
||||
|
||||
var pads_options = [];
|
||||
Config.availablePadTypes.forEach(function (p) {
|
||||
if (p === 'drive') { return; }
|
||||
pads_options.push({
|
||||
tag: 'a',
|
||||
attributes: {
|
||||
'target': '_blank',
|
||||
'href': '/' + p + '/',
|
||||
},
|
||||
content: Messages.type[p]
|
||||
});
|
||||
});
|
||||
var $plusIcon = $('<span>', {'class': 'fa fa-plus'});
|
||||
var $newbig = $('<span>', {'class': 'big'}).append(' ' +Messages.newButton);
|
||||
var $newButton = $('<div>').append($plusIcon).append($newbig);
|
||||
var dropdownConfig = {
|
||||
text: $newButton.html(), // Button initial text
|
||||
options: pads_options, // Entries displayed in the menu
|
||||
left: true, // Open to the left of the button,
|
||||
container: $newPad
|
||||
};
|
||||
var $newPadBlock = Cryptpad.createDropdown(dropdownConfig);
|
||||
$newPadBlock.find('button').attr('title', Messages.newButtonTitle);
|
||||
$newPadBlock.find('button').attr('id', 'newdoc');
|
||||
return $newPadBlock;
|
||||
};
|
||||
|
||||
var createUserAdmin = function (toolbar, config) {
|
||||
var $userAdmin = toolbar.$userAdmin.find('.'+USERADMIN_CLS).show();
|
||||
var userMenuCfg = {
|
||||
$initBlock: $userAdmin
|
||||
};
|
||||
if (!config.hideDisplayName) { // TODO: config.userAdmin.hideDisplayName?
|
||||
$.extend(true, userMenuCfg, {
|
||||
displayNameCls: USERNAME_CLS,
|
||||
changeNameButtonCls: USERBUTTON_CLS,
|
||||
});
|
||||
}
|
||||
if (config.readOnly !== 1) {
|
||||
userMenuCfg.displayName = 1;
|
||||
userMenuCfg.displayChangeName = 1;
|
||||
}
|
||||
Cryptpad.createUserAdminMenu(userMenuCfg);
|
||||
|
||||
var $userButton = toolbar.$userNameButton = $userAdmin.find('a.' + USERBUTTON_CLS);
|
||||
$userButton.click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Cryptpad.getLastName(function (err, lastName) {
|
||||
if (err) { return void console.error("Cannot get last name", err); }
|
||||
Cryptpad.prompt(Messages.changeNamePrompt, lastName || '', function (newName) {
|
||||
if (newName === null && typeof(lastName) === "string") { return; }
|
||||
if (newName === null) { newName = ''; }
|
||||
Cryptpad.changeDisplayName(newName);
|
||||
});
|
||||
});
|
||||
});
|
||||
Cryptpad.onDisplayNameChanged(function () {
|
||||
Cryptpad.findCancelButton().click();
|
||||
});
|
||||
|
||||
return $userAdmin;
|
||||
};
|
||||
|
||||
// Events
|
||||
var initClickEvents = function (toolbar, config) {
|
||||
var removeDropdowns = function () {
|
||||
toolbar.$toolbar.find('.cryptpad-dropdown').hide();
|
||||
};
|
||||
var cancelEditTitle = function (e) {
|
||||
// Now we want to apply the title even if we click somewhere else
|
||||
if ($(e.target).parents('.' + TITLE_CLS).length || !toolbar.title) {
|
||||
return;
|
||||
}
|
||||
var $title = toolbar.title;
|
||||
if (!$title.find('input').is(':visible')) { return; }
|
||||
|
||||
// Press enter
|
||||
var ev = $.Event("keyup");
|
||||
ev.which = 13;
|
||||
$title.find('input').trigger(ev);
|
||||
};
|
||||
// Click in the main window
|
||||
var w = config.ifrw || window;
|
||||
$(w).on('click', removeDropdowns);
|
||||
$(w).on('click', cancelEditTitle);
|
||||
// Click in iframes
|
||||
try {
|
||||
if (w.$ && w.$('iframe').length) {
|
||||
config.ifrw.$('iframe').each(function (i, el) {
|
||||
$(el.contentWindow).on('click', removeDropdowns);
|
||||
$(el.contentWindow).on('click', cancelEditTitle);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// empty try catch in case this iframe is problematic
|
||||
}
|
||||
};
|
||||
|
||||
// Notifications
|
||||
var initNotifications = function (toolbar, config) {
|
||||
// Display notifications when users are joining/leaving the session
|
||||
var oldUserData;
|
||||
if (!config.userList || !config.userList.list || !config.userList.userNetfluxId) { return; }
|
||||
var userList = config.userList.list;
|
||||
var userNetfluxId = config.userList.userNetfluxId;
|
||||
if (typeof Cryptpad !== "undefined" && userList) {
|
||||
var notify = function(type, name, oldname) {
|
||||
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
|
||||
if (typeof name === "undefined") { return; }
|
||||
name = name || Messages.anonymous;
|
||||
switch(type) {
|
||||
case 1:
|
||||
Cryptpad.log(Messages._getKey("notifyJoined", [name]));
|
||||
break;
|
||||
case 0:
|
||||
oldname = (oldname === "") ? Messages.anonymous : oldname;
|
||||
Cryptpad.log(Messages._getKey("notifyRenamed", [oldname, name]));
|
||||
break;
|
||||
case -1:
|
||||
Cryptpad.log(Messages._getKey("notifyLeft", [name]));
|
||||
break;
|
||||
default:
|
||||
console.log("Invalid type of notification");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var userPresent = function (id, user, data) {
|
||||
if (!(user && user.uid)) {
|
||||
console.log('no uid');
|
||||
return 0;
|
||||
}
|
||||
if (!data) {
|
||||
console.log('no data');
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
Object.keys(data).forEach(function (k) {
|
||||
if (data[k] && data[k].uid === user.uid) { count++; }
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
userList.change.push(function (newdata) {
|
||||
// Notify for disconnected users
|
||||
if (typeof oldUserData !== "undefined") {
|
||||
for (var u in oldUserData) {
|
||||
// if a user's uid is still present after having left, don't notify
|
||||
if (userList.users.indexOf(u) === -1) {
|
||||
var temp = JSON.parse(JSON.stringify(oldUserData[u]));
|
||||
delete oldUserData[u];
|
||||
if (userPresent(u, temp, newdata || oldUserData) < 1) {
|
||||
notify(-1, temp.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the "oldUserData" object and notify for new users and names changed
|
||||
if (typeof newdata === "undefined") { return; }
|
||||
if (typeof oldUserData === "undefined") {
|
||||
oldUserData = JSON.parse(JSON.stringify(newdata));
|
||||
return;
|
||||
}
|
||||
if (config.readOnly === 0 && !oldUserData[userNetfluxId]) {
|
||||
oldUserData = JSON.parse(JSON.stringify(newdata));
|
||||
return;
|
||||
}
|
||||
for (var k in newdata) {
|
||||
if (k !== userNetfluxId && userList.users.indexOf(k) !== -1) {
|
||||
if (typeof oldUserData[k] === "undefined") {
|
||||
// if the same uid is already present in the userdata, don't notify
|
||||
if (!userPresent(k, newdata[k], oldUserData)) {
|
||||
notify(1, newdata[k].name);
|
||||
}
|
||||
} else if (oldUserData[k].name !== newdata[k].name) {
|
||||
notify(0, newdata[k].name, oldUserData[k].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
oldUserData = JSON.parse(JSON.stringify(newdata));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Main
|
||||
|
||||
Bar.create = function (cfg) {
|
||||
var config = cfg || {};
|
||||
Cryptpad = config.common;
|
||||
Messages = Cryptpad.Messages;
|
||||
config.readOnly = (typeof config.readOnly !== "undefined") ? (config.readOnly ? 1 : 0) : -1;
|
||||
config.displayed = config.displayed || [];
|
||||
config.network = cfg.network || Cryptpad.getNetwork();
|
||||
|
||||
var toolbar = {};
|
||||
|
||||
toolbar.connected = false;
|
||||
toolbar.firstConnection = true;
|
||||
|
||||
var $toolbar = toolbar.$toolbar = createRealtimeToolbar(config);
|
||||
toolbar.$leftside = $toolbar.find('.'+Bar.constants.leftside);
|
||||
toolbar.$rightside = $toolbar.find('.'+Bar.constants.rightside);
|
||||
toolbar.$top = $toolbar.find('.'+Bar.constants.top);
|
||||
toolbar.$history = $toolbar.find('.'+Bar.constants.history);
|
||||
|
||||
toolbar.$userAdmin = $toolbar.find('.'+Bar.constants.userAdmin);
|
||||
|
||||
// Create the subelements
|
||||
var tb = {};
|
||||
tb['userlist'] = createUserList;
|
||||
tb['share'] = createShare;
|
||||
tb['fileshare'] = createFileShare;
|
||||
tb['title'] = createTitle;
|
||||
tb['lag'] = createLag;
|
||||
tb['spinner'] = createSpinner;
|
||||
tb['state'] = createState;
|
||||
tb['limit'] = createLimit;
|
||||
tb['newpad'] = createNewPad;
|
||||
tb['useradmin'] = createUserAdmin;
|
||||
|
||||
|
||||
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; }
|
||||
if (typeof tb[el] === "function") {
|
||||
if (!init && config.displayed.indexOf(el) !== -1) { return; } // Already done
|
||||
toolbar[el] = tb[el](toolbar, config);
|
||||
if (!init) { config.displayed.push(el); }
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
addElement(config.displayed, {}, true);
|
||||
initUserList(toolbar, config);
|
||||
|
||||
toolbar['linkToMain'] = createLinkToMain(toolbar, config);
|
||||
|
||||
if (!config.realtime) { toolbar.connected = true; }
|
||||
|
||||
initClickEvents(toolbar, config);
|
||||
initNotifications(toolbar, config);
|
||||
|
||||
var failed = toolbar.failed = function () {
|
||||
toolbar.connected = false;
|
||||
if (toolbar.state) {
|
||||
toolbar.state.text(Messages.disconnected);
|
||||
}
|
||||
checkLag(toolbar, config);
|
||||
};
|
||||
toolbar.reconnecting = function (userId) {
|
||||
if (config.userList) { config.userList.userNetfluxId = userId; }
|
||||
toolbar.connected = false;
|
||||
if (toolbar.state) {
|
||||
toolbar.state.text(Messages.reconnecting);
|
||||
}
|
||||
checkLag(toolbar, config);
|
||||
};
|
||||
|
||||
// On log out, remove permanently the realtime elements of the toolbar
|
||||
Cryptpad.onLogout(function () {
|
||||
failed();
|
||||
if (toolbar.useradmin) { toolbar.useradmin.hide(); }
|
||||
if (toolbar.userlist) { toolbar.userlist.hide(); }
|
||||
});
|
||||
|
||||
return toolbar;
|
||||
};
|
||||
|
||||
return Bar;
|
||||
});
|
||||
@@ -71,7 +71,7 @@ define([], function () {
|
||||
}
|
||||
};
|
||||
|
||||
var orderOfNodes = tree.orderOfNodes = function (a, b, root) {
|
||||
tree.orderOfNodes = function (a, b, root) {
|
||||
// b might not be supplied
|
||||
if (!b) { return; }
|
||||
// a and b might be the same element
|
||||
|
||||
953
www/common/userObject.js
Normal file
953
www/common/userObject.js
Normal file
@@ -0,0 +1,953 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/customize/application_config.js'
|
||||
], function ($, AppConfig) {
|
||||
var module = {};
|
||||
|
||||
var ROOT = module.ROOT = "root";
|
||||
var UNSORTED = module.UNSORTED = "unsorted";
|
||||
var TRASH = module.TRASH = "trash";
|
||||
var TEMPLATE = module.TEMPLATE = "template";
|
||||
|
||||
module.init = function (files, config) {
|
||||
var exp = {};
|
||||
var Cryptpad = config.Cryptpad;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Cryptpad.storageKey;
|
||||
var NEW_FOLDER_NAME = Messages.fm_newFolder;
|
||||
var NEW_FILE_NAME = Messages.fm_newFile;
|
||||
|
||||
// Logging
|
||||
var logging = function () {
|
||||
console.log.apply(console, arguments);
|
||||
};
|
||||
var log = config.log || logging;
|
||||
var logError = config.logError || logging;
|
||||
var debug = config.debug || logging;
|
||||
var error = exp.error = function() {
|
||||
exp.fixFiles();
|
||||
console.error.apply(console, arguments);
|
||||
};
|
||||
|
||||
// TODO: workgroup
|
||||
var workgroup = config.workgroup;
|
||||
|
||||
|
||||
/*
|
||||
* UTILS
|
||||
*/
|
||||
|
||||
exp.getStructure = function () {
|
||||
var a = {};
|
||||
a[ROOT] = {};
|
||||
a[TRASH] = {};
|
||||
a[FILES_DATA] = [];
|
||||
a[TEMPLATE] = [];
|
||||
return a;
|
||||
};
|
||||
var getHrefArray = function () {
|
||||
return [TEMPLATE];
|
||||
};
|
||||
|
||||
|
||||
var compareFiles = function (fileA, fileB) { return fileA === fileB; };
|
||||
|
||||
var isFile = exp.isFile = function (element) {
|
||||
return typeof(element) === "string";
|
||||
};
|
||||
|
||||
exp.isReadOnlyFile = function (element) {
|
||||
if (!isFile(element)) { return false; }
|
||||
var parsed = Cryptpad.parsePadUrl(element);
|
||||
if (!parsed) { return false; }
|
||||
var pHash = parsed.hashData;
|
||||
return pHash && pHash.mode === 'view';
|
||||
};
|
||||
|
||||
var isFolder = exp.isFolder = function (element) {
|
||||
return typeof(element) === "object";
|
||||
};
|
||||
exp.isFolderEmpty = function (element) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
return Object.keys(element).length === 0;
|
||||
};
|
||||
|
||||
exp.hasSubfolder = function (element, trashRoot) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
var subfolder = 0;
|
||||
var addSubfolder = function (el) {
|
||||
subfolder += isFolder(el.element) ? 1 : 0;
|
||||
};
|
||||
for (var f in element) {
|
||||
if (trashRoot) {
|
||||
if ($.isArray(element[f])) {
|
||||
element[f].forEach(addSubfolder);
|
||||
}
|
||||
} else {
|
||||
subfolder += isFolder(element[f]) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
return subfolder;
|
||||
};
|
||||
|
||||
exp.hasFile = function (element, trashRoot) {
|
||||
if (typeof(element) !== "object") { return false; }
|
||||
var file = 0;
|
||||
var addFile = function (el) {
|
||||
file += isFile(el.element) ? 1 : 0;
|
||||
};
|
||||
for (var f in element) {
|
||||
if (trashRoot) {
|
||||
if ($.isArray(element[f])) {
|
||||
element[f].forEach(addFile);
|
||||
}
|
||||
} else {
|
||||
file += isFile(element[f]) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
};
|
||||
|
||||
// Get data from AllFiles (Cryptpad_RECENTPADS)
|
||||
var getFileData = exp.getFileData = function (file) {
|
||||
if (!file) { return; }
|
||||
var res;
|
||||
files[FILES_DATA].some(function(arr) {
|
||||
var href = arr.href;
|
||||
if (href === file) {
|
||||
res = arr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
// Data from filesData
|
||||
var getTitle = exp.getTitle = function (href) {
|
||||
if (workgroup) { debug("No titles in workgroups"); return; }
|
||||
var data = getFileData(href);
|
||||
if (!href || !data) {
|
||||
error("getTitle called with a non-existing href: ", href);
|
||||
return;
|
||||
}
|
||||
return data.title;
|
||||
};
|
||||
|
||||
|
||||
// PATHS
|
||||
|
||||
var comparePath = exp.comparePath = function (a, b) {
|
||||
if (!a || !b || !$.isArray(a) || !$.isArray(b)) { return false; }
|
||||
if (a.length !== b.length) { return false; }
|
||||
var result = true;
|
||||
var i = a.length - 1;
|
||||
while (result && i >= 0) {
|
||||
result = a[i] === b[i];
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
var isSubpath = exp.isSubpath = function (path, parentPath) {
|
||||
var pathA = parentPath.slice();
|
||||
var pathB = path.slice(0, pathA.length);
|
||||
return comparePath(pathA, pathB);
|
||||
};
|
||||
|
||||
var isPathIn = exp.isPathIn = function (path, categories) {
|
||||
if (!categories) { return; }
|
||||
var idx = categories.indexOf('hrefArray');
|
||||
if (idx !== -1) {
|
||||
categories.splice(idx, 1);
|
||||
categories = categories.concat(getHrefArray());
|
||||
}
|
||||
return categories.some(function (c) {
|
||||
return Array.isArray(path) && path[0] === c;
|
||||
});
|
||||
};
|
||||
|
||||
var isInTrashRoot = exp.isInTrashRoot = function (path) {
|
||||
return path[0] === TRASH && path.length === 4;
|
||||
};
|
||||
|
||||
|
||||
// FIND
|
||||
|
||||
var findElement = function (root, pathInput) {
|
||||
if (!pathInput) {
|
||||
error("Invalid path:\n", pathInput, "\nin root\n", root);
|
||||
return;
|
||||
}
|
||||
if (pathInput.length === 0) { return root; }
|
||||
var path = pathInput.slice();
|
||||
var key = path.shift();
|
||||
if (typeof root[key] === "undefined") {
|
||||
debug("Unable to find the key '" + key + "' in the root object provided:", root);
|
||||
return;
|
||||
}
|
||||
return findElement(root[key], path);
|
||||
};
|
||||
|
||||
var find = exp.find = function (path) {
|
||||
return findElement(files, path);
|
||||
};
|
||||
|
||||
|
||||
// GET FILES
|
||||
|
||||
var getFilesRecursively = function (root, arr) {
|
||||
for (var e in root) {
|
||||
if (isFile(root[e])) {
|
||||
if(arr.indexOf(root[e]) === -1) { arr.push(root[e]); }
|
||||
} else {
|
||||
getFilesRecursively(root[e], arr);
|
||||
}
|
||||
}
|
||||
};
|
||||
var _getFiles = {};
|
||||
_getFiles['array'] = function (cat) {
|
||||
if (!files[cat]) { files[cat] = []; }
|
||||
return files[cat].slice();
|
||||
};
|
||||
getHrefArray().forEach(function (c) {
|
||||
_getFiles[c] = function () { return _getFiles['array'](c); };
|
||||
});
|
||||
_getFiles['hrefArray'] = function () {
|
||||
var ret = [];
|
||||
getHrefArray().forEach(function (c) {
|
||||
ret = ret.concat(_getFiles[c]());
|
||||
});
|
||||
return Cryptpad.deduplicateString(ret);
|
||||
};
|
||||
_getFiles[ROOT] = function () {
|
||||
var ret = [];
|
||||
getFilesRecursively(files[ROOT], ret);
|
||||
return ret;
|
||||
};
|
||||
_getFiles[TRASH] = function () {
|
||||
var root = files[TRASH];
|
||||
var ret = [];
|
||||
var addFiles = function (el) {
|
||||
if (isFile(el.element)) {
|
||||
if(ret.indexOf(el.element) === -1) { ret.push(el.element); }
|
||||
} else {
|
||||
getFilesRecursively(el.element, ret);
|
||||
}
|
||||
};
|
||||
for (var e in root) {
|
||||
if (!$.isArray(root[e])) {
|
||||
error("Trash contains a non-array element");
|
||||
return;
|
||||
}
|
||||
root[e].forEach(addFiles);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
_getFiles[FILES_DATA] = function () {
|
||||
var ret = [];
|
||||
files[FILES_DATA].forEach(function (el) {
|
||||
if (el.href && ret.indexOf(el.href) === -1) {
|
||||
ret.push(el.href);
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
var getFiles = exp.getFiles = function (categories) {
|
||||
var ret = [];
|
||||
if (!categories || !categories.length) {
|
||||
categories = [ROOT, 'hrefArray', TRASH, FILES_DATA];
|
||||
}
|
||||
categories.forEach(function (c) {
|
||||
if (typeof _getFiles[c] === "function") {
|
||||
ret = ret.concat(_getFiles[c]());
|
||||
}
|
||||
});
|
||||
return Cryptpad.deduplicateString(ret);
|
||||
};
|
||||
|
||||
// SEARCH
|
||||
var _findFileInRoot = function (path, href) {
|
||||
if (!isPathIn(path, [ROOT, TRASH])) { return []; }
|
||||
var paths = [];
|
||||
var root = find(path);
|
||||
var addPaths = function (p) {
|
||||
if (paths.indexOf(p) === -1) {
|
||||
paths.push(p);
|
||||
}
|
||||
};
|
||||
|
||||
if (isFile(root)) {
|
||||
if (compareFiles(href, root)) {
|
||||
if (paths.indexOf(path) === -1) {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
for (var e in root) {
|
||||
var nPath = path.slice();
|
||||
nPath.push(e);
|
||||
_findFileInRoot(nPath, href).forEach(addPaths);
|
||||
}
|
||||
|
||||
return paths;
|
||||
};
|
||||
exp.findFileInRoot = function (href) {
|
||||
return _findFileInRoot([ROOT], href);
|
||||
};
|
||||
var _findFileInHrefArray = function (rootName, href) {
|
||||
var unsorted = files[rootName].slice();
|
||||
var ret = [];
|
||||
var i = -1;
|
||||
while ((i = unsorted.indexOf(href, i+1)) !== -1){
|
||||
ret.push([rootName, i]);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
var _findFileInTrash = function (path, href) {
|
||||
var root = find(path);
|
||||
var paths = [];
|
||||
var addPaths = function (p) {
|
||||
if (paths.indexOf(p) === -1) {
|
||||
paths.push(p);
|
||||
}
|
||||
};
|
||||
if (path.length === 1 && typeof(root) === 'object') {
|
||||
Object.keys(root).forEach(function (key) {
|
||||
var arr = root[key];
|
||||
if (!Array.isArray(arr)) { return; }
|
||||
var nPath = path.slice();
|
||||
nPath.push(key);
|
||||
_findFileInTrash(nPath, href).forEach(addPaths);
|
||||
});
|
||||
}
|
||||
if (path.length === 2) {
|
||||
if (!Array.isArray(root)) { return []; }
|
||||
root.forEach(function (el, i) {
|
||||
var nPath = path.slice();
|
||||
nPath.push(i);
|
||||
nPath.push('element');
|
||||
if (isFile(el.element)) {
|
||||
if (compareFiles(href, el.element)) {
|
||||
addPaths(nPath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_findFileInTrash(nPath, href).forEach(addPaths);
|
||||
});
|
||||
}
|
||||
if (path.length >= 4) {
|
||||
_findFileInRoot(path, href).forEach(addPaths);
|
||||
}
|
||||
return paths;
|
||||
};
|
||||
var findFile = exp.findFile = function (href) {
|
||||
var rootpaths = _findFileInRoot([ROOT], href);
|
||||
var templatepaths = _findFileInHrefArray(TEMPLATE, href);
|
||||
var trashpaths = _findFileInTrash([TRASH], href);
|
||||
return rootpaths.concat(templatepaths, trashpaths);
|
||||
};
|
||||
exp.search = function (value) {
|
||||
if (typeof(value) !== "string") { return []; }
|
||||
var res = [];
|
||||
// Search in ROOT
|
||||
var findIn = function (root) {
|
||||
Object.keys(root).forEach(function (k) {
|
||||
if (isFile(root[k])) {
|
||||
if (k.toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
||||
res.push(root[k]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
findIn(root[k]);
|
||||
});
|
||||
};
|
||||
findIn(files[ROOT]);
|
||||
// Search in TRASH
|
||||
var trash = files[TRASH];
|
||||
Object.keys(trash).forEach(function (k) {
|
||||
if (k.toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
||||
trash[k].forEach(function (el) {
|
||||
if (isFile(el.element)) {
|
||||
res.push(el.element);
|
||||
}
|
||||
});
|
||||
}
|
||||
trash[k].forEach(function (el) {
|
||||
if (isFolder(el.element)) {
|
||||
findIn(el.element);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Search title
|
||||
var allFilesList = files[FILES_DATA].slice();
|
||||
allFilesList.forEach(function (t) {
|
||||
if (t.title && t.title.toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
||||
res.push(t.href);
|
||||
}
|
||||
});
|
||||
|
||||
// Search Href
|
||||
var href = Cryptpad.getRelativeHref(value);
|
||||
if (href) {
|
||||
res.push(href);
|
||||
}
|
||||
|
||||
res = Cryptpad.deduplicateString(res);
|
||||
|
||||
var ret = [];
|
||||
res.forEach(function (l) {
|
||||
//var paths = findFile(l);
|
||||
ret.push({
|
||||
paths: findFile(l),
|
||||
data: exp.getFileData(l)
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* OPERATIONS
|
||||
*/
|
||||
|
||||
var getAvailableName = function (parentEl, name) {
|
||||
if (typeof(parentEl[name]) === "undefined") { return name; }
|
||||
var newName = name;
|
||||
var i = 1;
|
||||
while (typeof(parentEl[newName]) !== "undefined") {
|
||||
newName = name + "_" + i;
|
||||
i++;
|
||||
}
|
||||
return newName;
|
||||
};
|
||||
|
||||
// FILES DATA
|
||||
var pushFileData = exp.pushData = function (data, cb) {
|
||||
if (typeof cb !== "function") { cb = function () {}; }
|
||||
var todo = function () {
|
||||
files[FILES_DATA].push(data);
|
||||
cb();
|
||||
};
|
||||
if (!Cryptpad.isLoggedIn() || !AppConfig.enablePinning) { return void todo(); }
|
||||
Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
todo();
|
||||
});
|
||||
};
|
||||
var spliceFileData = exp.removeData = function (idx) {
|
||||
var data = files[FILES_DATA][idx];
|
||||
if (typeof data === "object" && Cryptpad.isLoggedIn() && AppConfig.enablePinning) {
|
||||
Cryptpad.unpinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) {
|
||||
if (e) { return void logError(e); }
|
||||
debug('UNPIN', hash);
|
||||
});
|
||||
}
|
||||
files[FILES_DATA].splice(idx, 1);
|
||||
};
|
||||
|
||||
// MOVE
|
||||
var pushToTrash = function (name, element, path) {
|
||||
var trash = files[TRASH];
|
||||
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
|
||||
var trashArray = trash[name];
|
||||
var trashElement = {
|
||||
element: element,
|
||||
path: path
|
||||
};
|
||||
trashArray.push(trashElement);
|
||||
};
|
||||
var copyElement = function (elementPath, newParentPath) {
|
||||
if (comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
|
||||
var element = find(elementPath);
|
||||
var newParent = find(newParentPath);
|
||||
|
||||
// Move to Trash
|
||||
if (isPathIn(newParentPath, [TRASH])) {
|
||||
if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
|
||||
debug("Can't move an element from the trash to the trash: ", elementPath);
|
||||
return;
|
||||
}
|
||||
var key = elementPath[elementPath.length - 1];
|
||||
var elName = isPathIn(elementPath, ['hrefArray']) ? getTitle(element) : key;
|
||||
var parentPath = elementPath.slice();
|
||||
parentPath.pop();
|
||||
pushToTrash(elName, element, parentPath);
|
||||
return true;
|
||||
}
|
||||
// Move to hrefArray
|
||||
if (isPathIn(newParentPath, ['hrefArray'])) {
|
||||
if (isFolder(element)) {
|
||||
log(Messages.fo_moveUnsortedError);
|
||||
return;
|
||||
} else {
|
||||
if (elementPath[0] === newParentPath[0]) { return; }
|
||||
var fileRoot = newParentPath[0];
|
||||
if (files[fileRoot].indexOf(element) === -1) {
|
||||
files[fileRoot].push(element);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Move to root
|
||||
var name;
|
||||
if (isPathIn(elementPath, ['hrefArray'])) {
|
||||
name = getTitle(element);
|
||||
} else if (isInTrashRoot(elementPath)) {
|
||||
// Element from the trash root: elementPath = [TRASH, "{dirName}", 0, 'element']
|
||||
name = elementPath[1];
|
||||
} else {
|
||||
name = elementPath[elementPath.length-1];
|
||||
}
|
||||
var newName = !isPathIn(elementPath, [ROOT]) ? getAvailableName(newParent, name) : name;
|
||||
|
||||
if (typeof(newParent[newName]) !== "undefined") {
|
||||
log(Messages.fo_unavailableName);
|
||||
return;
|
||||
}
|
||||
newParent[newName] = element;
|
||||
return true;
|
||||
};
|
||||
var move = exp.move = function (paths, newPath, cb) {
|
||||
// Copy the elements to their new location
|
||||
var toRemove = [];
|
||||
paths.forEach(function (p) {
|
||||
var parentPath = p.slice();
|
||||
parentPath.pop();
|
||||
if (comparePath(parentPath, newPath)) { return; }
|
||||
if (isSubpath(newPath, p)) {
|
||||
log(Messages.fo_moveFolderToChildError);
|
||||
return;
|
||||
}
|
||||
// Try to copy, and if success, remove the element from the old location
|
||||
if (copyElement(p, newPath)) {
|
||||
toRemove.push(p);
|
||||
}
|
||||
});
|
||||
exp.delete(toRemove, cb);
|
||||
};
|
||||
exp.restore = function (path, cb) {
|
||||
if (!isInTrashRoot(path)) { return; }
|
||||
var parentPath = path.slice();
|
||||
parentPath.pop();
|
||||
var oldPath = find(parentPath).path;
|
||||
move([path], oldPath, cb);
|
||||
};
|
||||
|
||||
|
||||
// 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;
|
||||
var newPath = path, parentEl;
|
||||
if (path && !Array.isArray(path)) {
|
||||
newPath = decodeURIComponent(path).split(',');
|
||||
}
|
||||
// Add to href array
|
||||
if (path && isPathIn(newPath, ['hrefArray'])) {
|
||||
parentEl = find(newPath);
|
||||
parentEl.push(href);
|
||||
return;
|
||||
}
|
||||
// Add to root if path is ROOT or if no path
|
||||
var filesList = getFiles([ROOT, TRASH, 'hrefArray']);
|
||||
if ((path && isPathIn(newPath, [ROOT]) || filesList.indexOf(href) === -1) && name) {
|
||||
parentEl = find(newPath || [ROOT]);
|
||||
if (parentEl) {
|
||||
var newName = getAvailableName(parentEl, name);
|
||||
parentEl[newName] = href;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
exp.addFile = function (filePath, name, type, cb) {
|
||||
var parentEl = findElement(files, filePath);
|
||||
var fileName = getAvailableName(parentEl, name || NEW_FILE_NAME);
|
||||
var href = '/' + type + '/#' + Cryptpad.createRandomHash();
|
||||
|
||||
pushFileData({
|
||||
href: href,
|
||||
title: fileName,
|
||||
atime: +new Date(),
|
||||
ctime: +new Date()
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
logError(err);
|
||||
return void cb(err);
|
||||
}
|
||||
parentEl[fileName] = href;
|
||||
var newPath = filePath.slice();
|
||||
newPath.push(fileName);
|
||||
cb(void 0, {
|
||||
newPath: newPath
|
||||
});
|
||||
});
|
||||
};
|
||||
exp.addFolder = function (folderPath, name, cb) {
|
||||
var parentEl = find(folderPath);
|
||||
var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME);
|
||||
parentEl[folderName] = {};
|
||||
var newPath = folderPath.slice();
|
||||
newPath.push(folderName);
|
||||
cb(void 0, {
|
||||
newPath: newPath
|
||||
});
|
||||
};
|
||||
|
||||
// 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]);
|
||||
};
|
||||
|
||||
// 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 = exp.removePadAttribute = function (f) {
|
||||
if (typeof(f) !== 'string') {
|
||||
console.error("Can't find pad attribute for an undefined pad");
|
||||
return;
|
||||
}
|
||||
Object.keys(files).forEach(function (key) {
|
||||
var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null;
|
||||
if (hash && key.indexOf(hash) === 0) {
|
||||
debug("Deleting pad attribute in the realtime object");
|
||||
files[key] = undefined;
|
||||
delete files[key];
|
||||
}
|
||||
});
|
||||
};
|
||||
var checkDeletedFiles = function () {
|
||||
// Nothing in FILES_DATA for workgroups
|
||||
if (workgroup || !Cryptpad.isLoggedIn()) { return; }
|
||||
|
||||
var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
|
||||
var toRemove = [];
|
||||
files[FILES_DATA].forEach(function (arr) {
|
||||
var f = arr.href;
|
||||
if (filesList.indexOf(f) === -1) {
|
||||
toRemove.push(arr);
|
||||
}
|
||||
});
|
||||
toRemove.forEach(function (f) {
|
||||
var idx = files[FILES_DATA].indexOf(f);
|
||||
if (idx !== -1) {
|
||||
debug("Removing", f, "from filesData");
|
||||
spliceFileData(idx);
|
||||
removePadAttribute(f.href);
|
||||
}
|
||||
});
|
||||
};
|
||||
var deleteHrefs = function (hrefs) {
|
||||
hrefs.forEach(function (obj) {
|
||||
var idx = files[obj.root].indexOf(obj.href);
|
||||
files[obj.root].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var deleteMultipleTrashRoot = function (roots) {
|
||||
roots.forEach(function (obj) {
|
||||
var idx = files[TRASH][obj.name].indexOf(obj.el);
|
||||
files[TRASH][obj.name].splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var deleteMultiplePermanently = function (paths, nocheck) {
|
||||
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) {
|
||||
var href = find(path);
|
||||
hrefs.push({
|
||||
root: path[0],
|
||||
href: href
|
||||
});
|
||||
});
|
||||
deleteHrefs(hrefs);
|
||||
|
||||
rootPaths.forEach(function (path) {
|
||||
var parentPath = path.slice();
|
||||
var key = parentPath.pop();
|
||||
var parentEl = find(parentPath);
|
||||
parentEl[key] = undefined;
|
||||
delete parentEl[key];
|
||||
});
|
||||
|
||||
var trashRoot = [];
|
||||
trashPaths.forEach(function (path) {
|
||||
var parentPath = path.slice();
|
||||
var key = parentPath.pop();
|
||||
var parentEl = find(parentPath);
|
||||
// Trash root: we have array here, we can't just splice with the path otherwise we might break the path
|
||||
// of another element in the loop
|
||||
if (path.length === 4) {
|
||||
trashRoot.push({
|
||||
name: path[1],
|
||||
el: parentEl
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Trash but not root: it's just a tree so remove the key
|
||||
parentEl[key] = undefined;
|
||||
delete parentEl[key];
|
||||
});
|
||||
deleteMultipleTrashRoot(trashRoot);
|
||||
|
||||
// In some cases, we want to remove pads from a location without removing them from
|
||||
// FILES_DATA (replaceHref)
|
||||
if (!nocheck) { checkDeletedFiles(); }
|
||||
};
|
||||
exp.delete = function (paths, cb, nocheck) {
|
||||
deleteMultiplePermanently(paths, nocheck);
|
||||
if (typeof cb === "function") { cb(); }
|
||||
};
|
||||
exp.emptyTrash = function (cb) {
|
||||
files[TRASH] = {};
|
||||
checkDeletedFiles();
|
||||
if(cb) { cb(); }
|
||||
};
|
||||
|
||||
// RENAME
|
||||
exp.rename = function (path, newName, cb) {
|
||||
if (path.length <= 1) {
|
||||
logError('Renaming `root` is forbidden');
|
||||
return;
|
||||
}
|
||||
if (!newName || newName.trim() === "") { return; }
|
||||
// Copy the element path and remove the last value to have the parent path and the old name
|
||||
var element = find(path);
|
||||
var parentPath = path.slice();
|
||||
var oldName = parentPath.pop();
|
||||
if (oldName === newName) {
|
||||
return;
|
||||
}
|
||||
var parentEl = find(parentPath);
|
||||
if (typeof(parentEl[newName]) !== "undefined") {
|
||||
log(Messages.fo_existingNameError);
|
||||
return;
|
||||
}
|
||||
parentEl[newName] = element;
|
||||
parentEl[oldName] = undefined;
|
||||
delete parentEl[oldName];
|
||||
if (typeof cb === "function") { cb(); }
|
||||
};
|
||||
|
||||
// REPLACE
|
||||
var replaceFile = function (path, o, n) {
|
||||
var root = find(path);
|
||||
|
||||
if (isFile(root)) { return; }
|
||||
for (var e in root) {
|
||||
if (isFile(root[e])) {
|
||||
if (compareFiles(o, root[e])) {
|
||||
root[e] = n;
|
||||
}
|
||||
} else {
|
||||
var nPath = path.slice();
|
||||
nPath.push(e);
|
||||
replaceFile(nPath, o, n);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Replace a href by a stronger one everywhere in the drive (except FILES_DATA)
|
||||
exp.replace = function (o, n) {
|
||||
if (!isFile(o) || !isFile(n)) { return; }
|
||||
var paths = findFile(o);
|
||||
|
||||
// Remove all the occurences in the trash
|
||||
// Replace all the occurences not in the trash
|
||||
// If all the occurences are in the trash or no occurence, add the pad to unsorted
|
||||
var allInTrash = true;
|
||||
paths.forEach(function (p) {
|
||||
if (p[0] === TRASH) {
|
||||
exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
|
||||
return;
|
||||
} else {
|
||||
allInTrash = false;
|
||||
var parentPath = p.slice();
|
||||
var key = parentPath.pop();
|
||||
var parentEl = find(parentPath);
|
||||
parentEl[key] = n;
|
||||
}
|
||||
});
|
||||
if (allInTrash) {
|
||||
add(n);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* INTEGRITY CHECK
|
||||
*/
|
||||
|
||||
exp.fixFiles = function () {
|
||||
// Explore the tree and check that everything is correct:
|
||||
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
|
||||
// * ROOT: Folders are objects, files are href
|
||||
// * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
|
||||
// * FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
|
||||
// - Dates (adate, cdate) can be parsed/formatted
|
||||
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
|
||||
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
|
||||
debug("Cleaning file system...");
|
||||
|
||||
var before = JSON.stringify(files);
|
||||
|
||||
var fixRoot = function (elem) {
|
||||
if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
|
||||
var element = elem || files[ROOT];
|
||||
for (var el in element) {
|
||||
if (!isFile(element[el]) && !isFolder(element[el])) {
|
||||
debug("An element in ROOT was not a folder nor a file. ", element[el]);
|
||||
element[el] = undefined;
|
||||
delete element[el];
|
||||
} else if (isFolder(element[el])) {
|
||||
fixRoot(element[el]);
|
||||
}
|
||||
}
|
||||
};
|
||||
var fixTrashRoot = function () {
|
||||
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
|
||||
var tr = files[TRASH];
|
||||
var toClean;
|
||||
var addToClean = function (obj, idx) {
|
||||
if (typeof(obj) !== "object") { toClean.push(idx); return; }
|
||||
if (!isFile(obj.element) && !isFolder(obj.element)) { toClean.push(idx); return; }
|
||||
if (!$.isArray(obj.path)) { toClean.push(idx); return; }
|
||||
};
|
||||
for (var el in tr) {
|
||||
if (!$.isArray(tr[el])) {
|
||||
debug("An element in TRASH root is not an array. ", tr[el]);
|
||||
tr[el] = undefined;
|
||||
delete tr[el];
|
||||
} else {
|
||||
toClean = [];
|
||||
tr[el].forEach(addToClean);
|
||||
for (var i = toClean.length-1; i>=0; i--) {
|
||||
tr[el].splice(toClean[i], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Make sure unsorted doesn't exist anymore
|
||||
var fixUnsorted = function () {
|
||||
if (!files[UNSORTED]) { return; }
|
||||
debug("UNSORTED still exists in the object, removing it...");
|
||||
var us = files[UNSORTED];
|
||||
if (us.length === 0) {
|
||||
delete files[UNSORTED];
|
||||
return;
|
||||
}
|
||||
var rootFiles = getFiles([ROOT, TEMPLATE]).slice();
|
||||
var root = find([ROOT]);
|
||||
us.forEach(function (el) {
|
||||
if (!isFile(el) || rootFiles.indexOf(el) !== -1) {
|
||||
return;
|
||||
}
|
||||
var data = getFileData(el);
|
||||
var name = data ? data.title : NEW_FILE_NAME;
|
||||
var newName = getAvailableName(root, name);
|
||||
root[newName] = el;
|
||||
});
|
||||
delete files[UNSORTED];
|
||||
};
|
||||
var fixTemplate = function () {
|
||||
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
|
||||
files[TEMPLATE] = Cryptpad.deduplicateString(files[TEMPLATE].slice());
|
||||
var us = files[TEMPLATE];
|
||||
var rootFiles = getFiles([ROOT]).slice();
|
||||
var toClean = [];
|
||||
us.forEach(function (el, idx) {
|
||||
if (!isFile(el) || rootFiles.indexOf(el) !== -1) {
|
||||
toClean.push(idx);
|
||||
}
|
||||
});
|
||||
toClean.forEach(function (idx) {
|
||||
us.splice(idx, 1);
|
||||
});
|
||||
};
|
||||
var fixFilesData = function () {
|
||||
if (!$.isArray(files[FILES_DATA])) { debug("FILES_DATA was not an array"); files[FILES_DATA] = []; }
|
||||
var fd = files[FILES_DATA];
|
||||
var rootFiles = getFiles([ROOT, TRASH, 'hrefArray']);
|
||||
var root = find([ROOT]);
|
||||
var toClean = [];
|
||||
fd.forEach(function (el) {
|
||||
if (!el || typeof(el) !== "object") {
|
||||
debug("An element in filesData was not an object.", el);
|
||||
toClean.push(el);
|
||||
return;
|
||||
}
|
||||
if (!el.href) {
|
||||
debug("Rmoving an element in filesData with a missing href.", el);
|
||||
toClean.push(el);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
root[newName] = el.href;
|
||||
return;
|
||||
}
|
||||
});
|
||||
toClean.forEach(function (el) {
|
||||
var idx = fd.indexOf(el);
|
||||
if (idx !== -1) {
|
||||
spliceFileData(idx);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fixRoot();
|
||||
fixTrashRoot();
|
||||
if (!workgroup) {
|
||||
fixUnsorted();
|
||||
fixTemplate();
|
||||
fixFilesData();
|
||||
}
|
||||
|
||||
if (JSON.stringify(files) !== before) {
|
||||
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely");
|
||||
return;
|
||||
}
|
||||
debug("File system was clean");
|
||||
};
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
return module;
|
||||
});
|
||||
@@ -20,18 +20,18 @@
|
||||
visibilityChange: visibilityChange,
|
||||
};
|
||||
|
||||
var isSupported = Visible.isSupported = function () {
|
||||
Visible.isSupported = function () {
|
||||
return !(typeof(document.addEventListener) === "undefined" ||
|
||||
typeof document[hidden] === "undefined");
|
||||
};
|
||||
|
||||
var onChange = Visible.onChange = function (f) {
|
||||
Visible.onChange = function (f) {
|
||||
document.addEventListener(visibilityChange, function (ev) {
|
||||
f(!document[hidden], ev);
|
||||
}, false);
|
||||
};
|
||||
|
||||
var currently = Visible.currently = function () {
|
||||
Visible.currently = function () {
|
||||
return !document[hidden];
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
body {
|
||||
@@ -43,6 +43,9 @@ body {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
div:focus {
|
||||
outline: none;
|
||||
}
|
||||
.fa {
|
||||
/*min-width: 17px;*/
|
||||
margin-right: 3px;
|
||||
@@ -66,7 +69,7 @@ li {
|
||||
.contextMenu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
z-index: 500;
|
||||
}
|
||||
.contextMenu li {
|
||||
padding: 0;
|
||||
@@ -89,6 +92,16 @@ li {
|
||||
.selected .fa-plus-square-o {
|
||||
color: #000;
|
||||
}
|
||||
.selectedTmp {
|
||||
border: 1px dotted #bbb;
|
||||
background: #AAA;
|
||||
color: #ddd;
|
||||
margin: -1px;
|
||||
}
|
||||
.selectedTmp .fa-minus-square-o,
|
||||
.selectedTmp .fa-plus-square-o {
|
||||
color: #000;
|
||||
}
|
||||
span.fa-folder,
|
||||
span.fa-folder-open {
|
||||
color: #FEDE8B;
|
||||
@@ -141,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;
|
||||
@@ -215,6 +231,13 @@ span.fa-folder-open {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
position: relative;
|
||||
}
|
||||
#content .selectBox {
|
||||
display: none;
|
||||
background-color: rgba(100, 100, 100, 0.7);
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
}
|
||||
#content.readonly {
|
||||
background: #e6e6e6;
|
||||
@@ -236,13 +259,16 @@ span.fa-folder-open {
|
||||
margin-left: 10px;
|
||||
float: right;
|
||||
}
|
||||
#content .info-box.noclose {
|
||||
padding-right: 10px;
|
||||
}
|
||||
#content li {
|
||||
cursor: default;
|
||||
}
|
||||
#content li:not(.header) *:not(input) {
|
||||
/*pointer-events: none;*/
|
||||
}
|
||||
#content li:not(.header):hover:not(.selected) {
|
||||
#content li:not(.header):hover:not(.selected, .selectedTmp) {
|
||||
background-color: #eee;
|
||||
}
|
||||
#content li:not(.header):hover .name {
|
||||
@@ -289,6 +315,9 @@ span.fa-folder-open {
|
||||
width: 50px;
|
||||
font-size: 40px;
|
||||
}
|
||||
#content .element .truncated {
|
||||
display: none;
|
||||
}
|
||||
#content div.grid {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -296,19 +325,35 @@ span.fa-folder-open {
|
||||
display: inline-block;
|
||||
margin: 10px 10px;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-top: 10px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
max-height: 145px;
|
||||
}
|
||||
#content div.grid li:not(.selected) {
|
||||
border: transparent 1px;
|
||||
#content div.grid li:not(.selected):not(.selectedTmp) {
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
#content div.grid li .name {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
margin: 8px 0;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
#content div.grid li.element {
|
||||
position: relative;
|
||||
}
|
||||
#content div.grid li .truncated {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
#content div.grid li input {
|
||||
width: 100%;
|
||||
@@ -317,7 +362,8 @@ span.fa-folder-open {
|
||||
#content div.grid li .fa {
|
||||
display: block;
|
||||
margin: auto;
|
||||
font-size: 40px;
|
||||
font-size: 48px;
|
||||
margin: 8px 0;
|
||||
text-align: center;
|
||||
}
|
||||
#content div.grid li .fa.listonly {
|
||||
@@ -326,6 +372,9 @@ span.fa-folder-open {
|
||||
#content div.grid .listElement {
|
||||
display: none;
|
||||
}
|
||||
#content .list {
|
||||
padding-left: 20px;
|
||||
}
|
||||
#content .list ul {
|
||||
display: table;
|
||||
width: 100%;
|
||||
@@ -401,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;
|
||||
@@ -409,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 {
|
||||
@@ -416,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;
|
||||
@@ -467,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;
|
||||
@@ -489,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;
|
||||
@@ -32,7 +36,7 @@ html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@@ -70,6 +74,10 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
div:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.fa {
|
||||
/*min-width: 17px;*/
|
||||
margin-right: 3px;
|
||||
@@ -96,7 +104,7 @@ li {
|
||||
.contextMenu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
z-index: 500;
|
||||
li {
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
@@ -121,6 +129,16 @@ li {
|
||||
}
|
||||
}
|
||||
|
||||
.selectedTmp {
|
||||
border: 1px dotted #bbb;
|
||||
background: #AAA;
|
||||
color: #ddd;
|
||||
margin: -1px;
|
||||
.fa-minus-square-o, .fa-plus-square-o {
|
||||
color: @tree-fg;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
&.fa-folder, &.fa-folder-open {
|
||||
color: #FEDE8B;
|
||||
@@ -180,6 +198,9 @@ span {
|
||||
}
|
||||
}
|
||||
}
|
||||
#allfilesTree {
|
||||
margin-top: 0;
|
||||
}
|
||||
#searchContainer {
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
@@ -260,6 +281,13 @@ span {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
position: relative;
|
||||
.selectBox {
|
||||
display: none;
|
||||
background-color: rgba(100, 100, 100, 0.7);
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
}
|
||||
&.readonly {
|
||||
background: @content-bg-ro;
|
||||
}
|
||||
@@ -279,6 +307,9 @@ span {
|
||||
margin-left: 10px;
|
||||
float: right;
|
||||
}
|
||||
&.noclose {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
li {
|
||||
cursor: default;
|
||||
@@ -287,7 +318,7 @@ span {
|
||||
/*pointer-events: none;*/
|
||||
}
|
||||
&:hover {
|
||||
&:not(.selected) {
|
||||
&:not(.selected, .selectedTmp) {
|
||||
background-color: @drive-hover;
|
||||
}
|
||||
.name {
|
||||
@@ -343,25 +374,45 @@ span {
|
||||
}
|
||||
}
|
||||
}
|
||||
.element {
|
||||
.truncated { display: none; }
|
||||
}
|
||||
div.grid {
|
||||
padding: 20px;
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 10px 10px;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-top: 10px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
max-height: 145px;
|
||||
|
||||
&:not(.selected) {
|
||||
border: transparent 1px;
|
||||
&:not(.selected):not(.selectedTmp) {
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
.name {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
margin: 8px 0;
|
||||
display: inline-flex;
|
||||
//align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
//text-overflow: ellipsis;
|
||||
}
|
||||
&.element {
|
||||
position: relative;
|
||||
}
|
||||
.truncated {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0; right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
@@ -370,7 +421,8 @@ span {
|
||||
.fa {
|
||||
display: block;
|
||||
margin: auto;
|
||||
font-size: 40px;
|
||||
font-size: 48px;
|
||||
margin: 8px 0;
|
||||
text-align: center;
|
||||
&.listonly {
|
||||
display: none;
|
||||
@@ -384,6 +436,7 @@ span {
|
||||
|
||||
.list {
|
||||
// Make it act as a table!
|
||||
padding-left: 20px;
|
||||
ul {
|
||||
display: table;
|
||||
width: 100%;
|
||||
@@ -466,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;
|
||||
@@ -474,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;
|
||||
@@ -481,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 {
|
||||
@@ -499,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;
|
||||
@@ -534,7 +595,7 @@ span {
|
||||
.path {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
line-height: 40px;
|
||||
line-height: 30px;
|
||||
cursor: default;
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
@@ -556,7 +617,7 @@ span {
|
||||
}
|
||||
#contextButtonsContainer {
|
||||
float: right;
|
||||
margin: 5px;
|
||||
margin: 2px;
|
||||
button {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="app-container" tabindex="0">
|
||||
<div id="tree">
|
||||
</div>
|
||||
<div id="content">
|
||||
<div id="content" tabindex="2">
|
||||
</div>
|
||||
<div id="treeContextMenu" class="contextMenu dropdown clearfix">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
@@ -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">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
define([
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
],function () {
|
||||
var $ = window.jQuery;
|
||||
'jquery'
|
||||
],function ($) {
|
||||
var Board = {};
|
||||
var proxy;
|
||||
|
||||
@@ -170,6 +169,7 @@ define([
|
||||
|
||||
var $input = Input({
|
||||
placeholder: 'card description',
|
||||
id: id,
|
||||
})
|
||||
.addClass('card-title');
|
||||
|
||||
@@ -207,7 +207,7 @@ define([
|
||||
|
||||
/*
|
||||
*/
|
||||
Card.move = function (uid, A, B) {
|
||||
Card.move = function (/*uid, A, B*/) {
|
||||
|
||||
};
|
||||
|
||||
@@ -229,11 +229,10 @@ define([
|
||||
}
|
||||
|
||||
var card = proxy.cards[cid];
|
||||
|
||||
|
||||
card = card; // TODO actually draw
|
||||
};
|
||||
|
||||
var Draw = Board.Draw = function ($lists) {
|
||||
Board.Draw = function ($lists) {
|
||||
proxy.listOrder.forEach(function (luid) {
|
||||
List.draw($lists, luid);
|
||||
});
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/customize/messages.js',
|
||||
'board.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, Messages, Board, TextPatcher, Listmap, Crypto, Cryptpad, Visible, Notify) {
|
||||
var $ = window.jQuery;
|
||||
var saveAs = window.saveAs;
|
||||
//'/common/visible.js',
|
||||
//'/common/notify.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, Config, Messages, Board, TextPatcher, Listmap, Crypto, Cryptpad /*, Visible, Notify*/) {
|
||||
|
||||
// var saveAs = window.saveAs;
|
||||
|
||||
Cryptpad.styleAlerts();
|
||||
console.log("Initializing your realtime session...");
|
||||
@@ -23,28 +23,28 @@ define([
|
||||
Board: Board,
|
||||
};
|
||||
|
||||
/*
|
||||
var unnotify = function () {
|
||||
if (!(module.tabNotification &&
|
||||
typeof(module.tabNotification.cancel) === 'function')) { return; }
|
||||
module.tabNotification.cancel();
|
||||
};
|
||||
|
||||
var notify = function () {
|
||||
if (!(Visible.isSupported() && !Visible.currently())) { return; }
|
||||
unnotify();
|
||||
module.tabNotification = Notify.tab(1000, 10);
|
||||
};
|
||||
*/
|
||||
|
||||
var setEditable = function (bool) {
|
||||
|
||||
bool = bool;
|
||||
};
|
||||
|
||||
setEditable(false);
|
||||
|
||||
|
||||
var $lists = $('#lists');
|
||||
|
||||
var $addList = $('#create-list').click(function () {
|
||||
$('#create-list').click(function () {
|
||||
Board.List.draw($lists);
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ define([
|
||||
Cryptpad.log("You are the first user to visit this board");
|
||||
};
|
||||
|
||||
var whenReady = function (opt) {
|
||||
var whenReady = function () {
|
||||
var rt = module.rt;
|
||||
var proxy = rt.proxy;
|
||||
|
||||
@@ -63,7 +63,6 @@ define([
|
||||
Board.Draw($lists);
|
||||
|
||||
if (first) { firstUser(); }
|
||||
|
||||
};
|
||||
|
||||
var config = {
|
||||
@@ -78,10 +77,10 @@ define([
|
||||
var proxy = rt.proxy;
|
||||
proxy
|
||||
.on('create', function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.realtime = info.realtime;
|
||||
window.location.hash = info.channel + secret.key;
|
||||
})
|
||||
.on('ready', function (info) {
|
||||
.on('ready', function () {
|
||||
Cryptpad.log("Ready!");
|
||||
whenReady({
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'ula.js',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, Realtime, Crypto, TextPatcher, Sortify, Formula, JsonOT, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
'/common/cryptpad-common.js'
|
||||
], function ($, Config, Realtime, Crypto, TextPatcher, Sortify, Formula, JsonOT, Cryptpad) {
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
@@ -130,7 +128,7 @@ define([
|
||||
|
||||
setEditable(false);
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
config.onInit = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
window.location.hash = info.channel + secret.key;
|
||||
|
||||
@@ -142,7 +140,7 @@ define([
|
||||
};
|
||||
|
||||
var readValues = function () {
|
||||
UI.each(function (ui, i, list) {
|
||||
UI.each(function (ui) {
|
||||
Map[ui.id] = ui.value();
|
||||
});
|
||||
};
|
||||
@@ -167,7 +165,7 @@ define([
|
||||
if (UI.ids.indexOf(key) === -1) { Map[key] = parsed[key]; }
|
||||
});
|
||||
|
||||
UI.each(function (ui, i, list) {
|
||||
UI.each(function (ui) {
|
||||
var newval = parsed[ui.id];
|
||||
var oldval = ui.value();
|
||||
|
||||
@@ -180,9 +178,7 @@ define([
|
||||
if (ui.preserveCursor) {
|
||||
op = TextPatcher.diff(oldval, newval);
|
||||
selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
var before = element[attr];
|
||||
var after = TextPatcher.transformCursor(element[attr], op);
|
||||
return after;
|
||||
return TextPatcher.transformCursor(element[attr], op);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,13 +193,13 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function (info) {
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
/* integrate remote changes */
|
||||
updateValues();
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
config.onReady = function () {
|
||||
updateValues();
|
||||
|
||||
console.log("READY");
|
||||
@@ -211,13 +207,13 @@ define([
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
config.onAbort = function () {
|
||||
window.alert("Network Connection Lost");
|
||||
};
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
Realtime.start(config);
|
||||
|
||||
UI.each(function (ui, i, list) {
|
||||
UI.each(function (ui) {
|
||||
var type = ui.type;
|
||||
var events = eventsByType[type];
|
||||
ui.$.on(events, onLocal);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
define([], function () {
|
||||
var ula = {};
|
||||
|
||||
var uid = ula.uid = (function () {
|
||||
ula.uid = (function () {
|
||||
var i = 0;
|
||||
var prefix = 'rt_';
|
||||
return function () { return prefix + i++; };
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
html, body{
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
textarea{
|
||||
position: absolute;
|
||||
top: 5vh;
|
||||
left: 0px;
|
||||
border: 0px;
|
||||
|
||||
padding-top: 15px;
|
||||
width: 100%;
|
||||
height: 95vh;
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
|
||||
font-size: 30px;
|
||||
background-color: #073642;
|
||||
color: #839496;
|
||||
|
||||
overflow-x: hidden;
|
||||
|
||||
/* disallow textarea resizes */
|
||||
resize: none;
|
||||
}
|
||||
|
||||
textarea[disabled] {
|
||||
background-color: #275662;
|
||||
color: #637476;
|
||||
}
|
||||
|
||||
#panel {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
height: 5vh;
|
||||
z-index: 95;
|
||||
background-color: #777;
|
||||
/* min-height: 75px; */
|
||||
}
|
||||
#run {
|
||||
display: block;
|
||||
float: right;
|
||||
height: 100%;
|
||||
width: 10vw;
|
||||
z-index: 100;
|
||||
line-height: 5vw;
|
||||
font-size: 1.5em;
|
||||
background-color: #222;
|
||||
color: #CCC;
|
||||
text-align: center;
|
||||
border-radius: 5%;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<textarea></textarea>
|
||||
<div id="panel">
|
||||
<!-- TODO update this element when new users join -->
|
||||
<span id="users"></span>
|
||||
<!-- what else should go in the panel? -->
|
||||
<a href="#" id="run">RUN</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,162 +0,0 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js'
|
||||
], function (Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
var $textarea = $('textarea'),
|
||||
$run = $('#run');
|
||||
|
||||
var module = {};
|
||||
|
||||
var config = {
|
||||
initialState: '',
|
||||
websocketURL: Config.websocketURL,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.key),
|
||||
};
|
||||
var initializing = true;
|
||||
|
||||
var setEditable = function (bool) { $textarea.attr('disabled', !bool); };
|
||||
var canonicalize = function (text) { return text.replace(/\r\n/g, '\n'); };
|
||||
|
||||
setEditable(false);
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
$(window).on('hashchange', function() { window.location.reload(); });
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function (info) {
|
||||
if (initializing) { return; }
|
||||
|
||||
var userDoc = info.realtime.getUserDoc();
|
||||
var current = canonicalize($textarea.val());
|
||||
|
||||
var op = TextPatcher.diff(current, userDoc);
|
||||
|
||||
var elem = $textarea[0];
|
||||
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(elem[attr], op);
|
||||
});
|
||||
|
||||
$textarea.val(userDoc);
|
||||
elem.selectionStart = selects[0];
|
||||
elem.selectionEnd = selects[1];
|
||||
|
||||
// TODO do something on external messages
|
||||
// http://webdesign.tutsplus.com/tutorials/how-to-display-update-notifications-in-the-browser-tab--cms-23458
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: info.realtime
|
||||
// logging: true
|
||||
});
|
||||
initializing = false;
|
||||
setEditable(true);
|
||||
$textarea.val(info.realtime.getUserDoc());
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
setEditable(false);
|
||||
window.alert("Server Connection Lost");
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
module.patchText(canonicalize($textarea.val()));
|
||||
};
|
||||
|
||||
var rt = window.rt = Realtime.start(config);
|
||||
|
||||
var splice = function (str, index, chars) {
|
||||
var count = chars.length;
|
||||
return str.slice(0, index) + chars + str.slice((index -1) + count);
|
||||
};
|
||||
|
||||
var setSelectionRange = function (input, start, end) {
|
||||
if (input.setSelectionRange) {
|
||||
input.focus();
|
||||
input.setSelectionRange(start, end);
|
||||
} else if (input.createTextRange) {
|
||||
var range = input.createTextRange();
|
||||
range.collapse(true);
|
||||
range.moveEnd('character', end);
|
||||
range.moveStart('character', start);
|
||||
range.select();
|
||||
}
|
||||
};
|
||||
|
||||
var setCursor = function (el, pos) {
|
||||
setSelectionRange(el, pos, pos);
|
||||
};
|
||||
|
||||
var state = {};
|
||||
|
||||
// TODO
|
||||
$textarea.on('keydown', function (e) {
|
||||
// track when control keys are pushed down
|
||||
//switch (e.key) { }
|
||||
});
|
||||
|
||||
// TODO
|
||||
$textarea.on('keyup', function (e) {
|
||||
// track when control keys are released
|
||||
});
|
||||
|
||||
//$textarea.on('change', onLocal);
|
||||
$textarea.on('keypress', function (e) {
|
||||
onLocal();
|
||||
switch (e.key) {
|
||||
case 'Tab':
|
||||
// insert a tab wherever the cursor is...
|
||||
var start = $textarea.prop('selectionStart');
|
||||
var end = $textarea.prop('selectionEnd');
|
||||
if (typeof start !== 'undefined') {
|
||||
if (start === end) {
|
||||
$textarea.val(function (i, val) {
|
||||
return splice(val, start, "\t");
|
||||
});
|
||||
setCursor($textarea[0], start +1);
|
||||
} else {
|
||||
// indentation?? this ought to be fun.
|
||||
|
||||
}
|
||||
}
|
||||
// simulate a keypress so the event goes through..
|
||||
// prevent default behaviour for tab
|
||||
e.preventDefault();
|
||||
|
||||
onLocal();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
['cut', 'paste', 'change', 'keyup', 'keydown', 'select', 'textInput']
|
||||
.forEach(function (evt) {
|
||||
$textarea.on(evt, onLocal);
|
||||
});
|
||||
|
||||
$run.click(function (e) {
|
||||
e.preventDefault();
|
||||
var content = $textarea.val();
|
||||
|
||||
try {
|
||||
eval(content); // jshint ignore:line
|
||||
} catch (err) {
|
||||
// FIXME don't use alert, make an errorbox
|
||||
window.alert(err.message);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -11,12 +11,10 @@
|
||||
<ul>
|
||||
<li><a href="/examples/form/">forms</a></li>
|
||||
<li><a href="/examples/text/">text</a></li>
|
||||
<li><a href="/examples/hack/">textareas with executable content</a></li>
|
||||
<li><a href="/examples/board/">kanban board</a></li>
|
||||
<li><a href="/examples/json/">json objects</a></li>
|
||||
<!-- <li><a href="/examples/json/">json objects</a></li> -->
|
||||
<li><a href="/examples/read/">ajax-like get/put behaviour</a></li>
|
||||
<li><a href="/examples/render/">render markdown content as html</a></li>
|
||||
<li><a href="/examples/style/">edit a page's style tag</a></li>
|
||||
<li><a href="/examples/upload/">upload content</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, RtListMap, Crypto, Common) {
|
||||
var $ = window.jQuery;
|
||||
'/common/cryptpad-common.js'
|
||||
], function ($, Config, RtListMap, Crypto, Common) {
|
||||
|
||||
var secret = Common.getSecrets();
|
||||
|
||||
@@ -27,15 +26,13 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
setEditable(false);
|
||||
|
||||
var rt = module.rt = RtListMap.create(config);
|
||||
rt.proxy.on('create', function (info) {
|
||||
console.log("initializing...");
|
||||
window.location.hash = info.channel + secret.key;
|
||||
}).on('ready', function (info) {
|
||||
}).on('ready', function () {
|
||||
console.log("...your realtime object is ready");
|
||||
|
||||
rt.proxy
|
||||
@@ -43,7 +40,7 @@ define([
|
||||
.on('change', [], function (o, n, p) {
|
||||
console.log("root change event firing for path [%s]: %s => %s", p.join(','), o, n);
|
||||
})
|
||||
.on('remove', [], function (o, p, root) {
|
||||
.on('remove', [], function (o, p) {
|
||||
console.log("Removal of value [%s] at path [%s]", o, p.join(','));
|
||||
})
|
||||
.on('change', ['a', 'b', 'c'], function (o, n, p) {
|
||||
@@ -52,7 +49,7 @@ define([
|
||||
return false;
|
||||
})
|
||||
// on(event, cb)
|
||||
.on('disconnect', function (info) {
|
||||
.on('disconnect', function () {
|
||||
setEditable(false);
|
||||
window.alert("Network connection lost");
|
||||
});
|
||||
@@ -66,6 +63,7 @@ define([
|
||||
|
||||
console.log("evaluating `%s`", value);
|
||||
var x = rt.proxy;
|
||||
x = x; // LOL jshint says this is unused otherwise <3
|
||||
|
||||
console.log('> ', eval(value)); // jshint ignore:line
|
||||
console.log();
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'jquery',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/pinpad.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Cryptpad, Pinpad) {
|
||||
var $ = window.jQuery;
|
||||
var APP = window.APP = {
|
||||
'/common/pinpad.js'
|
||||
], function ($, Cryptpad, Pinpad) {
|
||||
window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
};
|
||||
|
||||
@@ -39,7 +37,7 @@ define([
|
||||
};
|
||||
|
||||
$(function () {
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
var network = Cryptpad.getNetwork();
|
||||
var proxy = Cryptpad.getStore().getProxy().proxy;
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
define([
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Crypt) {
|
||||
var $ = window.jQuery;
|
||||
|
||||
'jquery',
|
||||
'/common/cryptget.js'
|
||||
], function ($, Crypt) {
|
||||
var $target = $('#target');
|
||||
var $dest = $('#dest');
|
||||
|
||||
var useDoc = function (err, doc) {
|
||||
if (err) { return console.error(err); }
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/marked/marked.min.js',
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
'/bower_components/diff-dom/diffDOM.js',
|
||||
], function (Config, Realtime, Crypto, Marked, Hyperjson, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
], function ($, Config, Realtime, Crypto, Marked, Hyperjson, Cryptpad) {
|
||||
var DiffDom = window.diffDOM;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
@@ -56,7 +55,7 @@ define([
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
module.realtime = info.realtime;
|
||||
};
|
||||
@@ -74,7 +73,7 @@ define([
|
||||
};
|
||||
|
||||
// when your editor is ready
|
||||
var onReady = config.onReady = function (info) {
|
||||
config.onReady = function () {
|
||||
console.log("Realtime is ready!");
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
lazyDraw(getContent(userDoc));
|
||||
@@ -82,13 +81,13 @@ define([
|
||||
};
|
||||
|
||||
// when remote editors do things...
|
||||
var onRemote = config.onRemote = function () {
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
lazyDraw(getContent(userDoc));
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
config.onLocal = function () {
|
||||
// we're not really expecting any local events for this editor...
|
||||
/* but we might add a second pane in the future so that you don't need
|
||||
a second window to edit your markdown */
|
||||
@@ -97,9 +96,9 @@ define([
|
||||
lazyDraw(userDoc);
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function () {
|
||||
config.onAbort = function () {
|
||||
window.alert("Network Connection Lost");
|
||||
};
|
||||
|
||||
var rts = Realtime.start(config);
|
||||
Realtime.start(config);
|
||||
});
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
'/common/cryptpad-common.js'
|
||||
], function ($, Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
// TODO consider adding support for less.js
|
||||
var $ = window.jQuery;
|
||||
|
||||
var $style = $('style').first(),
|
||||
$edit = $('#edit');
|
||||
@@ -21,8 +20,6 @@ define([
|
||||
crypto: Crypto.createEncryptor(secret.key),
|
||||
};
|
||||
|
||||
var userName = module.userName = config.userName = Crypto.rand64(8);
|
||||
|
||||
var lazyDraw = (function () {
|
||||
var to,
|
||||
delay = 500;
|
||||
@@ -38,7 +35,7 @@ define([
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
@@ -51,28 +48,28 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
config.onReady = function () {
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
draw(userDoc);
|
||||
console.log("Ready");
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function () {
|
||||
config.onRemote = function () {
|
||||
draw(module.realtime.getUserDoc());
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
config.onAbort = function () {
|
||||
// notify the user of the abort
|
||||
window.alert("Network Connection Lost");
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
config.onLocal = function () {
|
||||
// nope
|
||||
};
|
||||
|
||||
|
||||
$edit.attr('href', '/examples/text/'+ window.location.hash);
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
Realtime.start(config);
|
||||
});
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
'/common/cryptpad-common.js'
|
||||
], function ($, Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var module = window.APP = {
|
||||
TextPatcher: TextPatcher
|
||||
};
|
||||
|
||||
var userName = module.userName = Crypto.rand64(8);
|
||||
|
||||
var initializing = true;
|
||||
var $textarea = $('textarea');
|
||||
|
||||
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); };
|
||||
@@ -31,14 +32,15 @@ define([
|
||||
|
||||
setEditable(false);
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
config.onInit = function (info) {
|
||||
var editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
Cryptpad.replaceHash(editHash);
|
||||
$(window).on('hashchange', function() {
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function (info) {
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
var content = canonicalize($textarea.val());
|
||||
@@ -60,7 +62,7 @@ define([
|
||||
module.patchText(canonicalize($textarea.val()));
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
config.onReady = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime
|
||||
@@ -72,12 +74,12 @@ define([
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
config.onAbort = function () {
|
||||
setEditable(false);
|
||||
window.alert("Server Connection Lost");
|
||||
};
|
||||
|
||||
var onConnectionChange = config.onConnectionChange = function (info) {
|
||||
config.onConnectionChange = function (info) {
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
} else {
|
||||
@@ -86,7 +88,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
Realtime.start(config);
|
||||
|
||||
['cut', 'paste', 'change', 'keyup', 'keydown', 'select', 'textInput']
|
||||
.forEach(function (evt) {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
define([
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Crypt, Crypto) {
|
||||
var $ = window.jQuery;
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var key = Nacl.randomBytes(32);
|
||||
|
||||
var handleFile = function (body) {
|
||||
//console.log("plaintext");
|
||||
//console.log(body);
|
||||
|
||||
/*
|
||||
0 && Crypt.put(body, function (e, out) {
|
||||
if (e) { return void console.error(e); }
|
||||
if (out) {
|
||||
console.log(out);
|
||||
}
|
||||
}); */
|
||||
|
||||
var data = {};
|
||||
|
||||
(function () {
|
||||
var cyphertext = data.payload = Crypto.encrypt(body, key);
|
||||
console.log("encrypted");
|
||||
console.log(cyphertext);
|
||||
|
||||
console.log(data);
|
||||
|
||||
var decrypted = Crypto.decrypt(cyphertext, key);
|
||||
//console.log('decrypted');
|
||||
//console.log(decrypted);
|
||||
|
||||
|
||||
if (decrypted !== body) {
|
||||
throw new Error("failed to maintain integrity with round trip");
|
||||
}
|
||||
|
||||
// finding... files are entirely too large.
|
||||
|
||||
|
||||
console.log(data.payload.length, body.length); // 1491393, 588323
|
||||
console.log(body.length / data.payload.length); // 0.3944788529918003
|
||||
console.log(data.payload.length / body.length); // 2.534990132971174
|
||||
|
||||
/*
|
||||
|
||||
http://stackoverflow.com/questions/19959072/sending-binary-data-in-javascript-over-http
|
||||
|
||||
// Since we deal with Firefox and Chrome only
|
||||
var bytesToSend = [253, 0, 128, 1];
|
||||
var bytesArray = new Uint8Array(bytesToSend);
|
||||
|
||||
$.ajax({
|
||||
url: '%your_service_url%',
|
||||
type: 'POST',
|
||||
contentType: 'application/octet-stream',
|
||||
data: bytesArray,
|
||||
processData: false
|
||||
});
|
||||
*/
|
||||
})();
|
||||
};
|
||||
|
||||
var $file = $('input[type="file"]');
|
||||
$file.on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
handleFile(e.target.result);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
});
|
||||
BIN
www/file/assets/image.png-encrypted
Normal file
BIN
www/file/assets/image.png-encrypted
Normal file
Binary file not shown.
267
www/file/file-crypto.js
Normal file
267
www/file/file-crypto.js
Normal file
@@ -0,0 +1,267 @@
|
||||
define([
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
], function () {
|
||||
var Nacl = window.nacl;
|
||||
var PARANOIA = true;
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
var createNonce = function () {
|
||||
return new Uint8Array(new Array(24).fill(0));
|
||||
};
|
||||
|
||||
var increment = function (N) {
|
||||
var l = N.length;
|
||||
while (l-- > 1) {
|
||||
if (PARANOIA) {
|
||||
if (typeof(N[l]) !== 'number') {
|
||||
throw new Error('E_UNSAFE_TYPE');
|
||||
}
|
||||
if (N[l] > 255) {
|
||||
throw new Error('E_OUT_OF_BOUNDS');
|
||||
}
|
||||
}
|
||||
/* jshint probably suspects this is unsafe because we lack types
|
||||
but as long as this is only used on nonces, it should be safe */
|
||||
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
|
||||
N[l] = 0;
|
||||
|
||||
// you don't need to worry about this running out.
|
||||
// you'd need a REAAAALLY big file
|
||||
if (l === 0) {
|
||||
throw new Error('E_NONCE_TOO_LARGE');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var joinChunks = function (chunks) {
|
||||
return new Blob(chunks);
|
||||
};
|
||||
|
||||
var concatBuffer = function (a, b) { // TODO make this not so ugly
|
||||
return new Uint8Array(slice(a).concat(slice(b)));
|
||||
};
|
||||
|
||||
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 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));
|
||||
|
||||
// decrypt the chunk
|
||||
var plaintext = Nacl.secretbox.open(box, nonce, key);
|
||||
increment(nonce);
|
||||
|
||||
if (!plaintext) { return cb('DECRYPTION_ERROR'); }
|
||||
|
||||
_progress(end);
|
||||
cb(void 0, plaintext);
|
||||
};
|
||||
|
||||
var 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');
|
||||
});
|
||||
};
|
||||
|
||||
again();
|
||||
};
|
||||
|
||||
// metadata
|
||||
/* { filename: 'raccoon.jpg', type: 'image/jpeg' } */
|
||||
var encrypt = function (u8, metadata, key) {
|
||||
var nonce = createNonce();
|
||||
|
||||
// encode metadata
|
||||
var metaBuffer = Array.prototype.slice
|
||||
.call(Nacl.util.decodeUTF8(JSON.stringify(metadata)));
|
||||
|
||||
var plaintext = new Uint8Array(metaBuffer);
|
||||
|
||||
var i = 0;
|
||||
|
||||
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...
|
||||
part = new Uint8Array(plaintext);
|
||||
box = Nacl.secretbox(part, nonce, key);
|
||||
increment(nonce);
|
||||
|
||||
if (box.length > 65535) {
|
||||
return void cb('METADATA_TOO_LARGE');
|
||||
}
|
||||
var prefixed = new Uint8Array(encodePrefix(box.length)
|
||||
.concat(slice(box)));
|
||||
state++;
|
||||
|
||||
return void cb(void 0, prefixed);
|
||||
}
|
||||
|
||||
// encrypt the rest of the file...
|
||||
start = i * plainChunkLength;
|
||||
end = start + plainChunkLength;
|
||||
|
||||
part = u8.subarray(start, end);
|
||||
box = Nacl.secretbox(part, nonce, key);
|
||||
increment(nonce);
|
||||
i++;
|
||||
|
||||
// regular data is done
|
||||
if (i * plainChunkLength >= u8.length) { state = 2; }
|
||||
|
||||
return void cb(void 0, box);
|
||||
};
|
||||
|
||||
return next;
|
||||
};
|
||||
|
||||
return {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
47
www/file/index.html
Normal file
47
www/file/index.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!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" />
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#pad-iframe {
|
||||
position:fixed;
|
||||
top:0px;
|
||||
left:0px;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
||||
<div id="loading">
|
||||
<div class="loadingContainer">
|
||||
<img class="cryptofist" src="/customize/cryptofist_small.png" />
|
||||
<div class="spinnerContainer">
|
||||
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
|
||||
</div>
|
||||
<p data-localization="loading"></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
36
www/file/inner.html
Normal file
36
www/file/inner.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<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">
|
||||
<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" 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>
|
||||
|
||||
371
www/file/main.js
Normal file
371
www/file/main.js
Normal file
@@ -0,0 +1,371 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/common/toolbar2.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 Messages = Cryptpad.Messages;
|
||||
var saveAs = window.saveAs;
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var APP = {};
|
||||
|
||||
$(function () {
|
||||
|
||||
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 Title;
|
||||
|
||||
var myFile;
|
||||
var myDataType;
|
||||
|
||||
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 $cancelCell = $table.find('tr[id="'+id+'"]').find('.upCancel');
|
||||
$cancelCell.html('-');
|
||||
|
||||
var u8 = new Uint8Array(blob);
|
||||
|
||||
var key = Nacl.randomBytes(32);
|
||||
var next = FileCrypto.encrypt(u8, metadata, key);
|
||||
|
||||
var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata);
|
||||
var chunks = [];
|
||||
|
||||
var sendChunk = function (box, cb) {
|
||||
var enc = Nacl.util.encodeBase64(box);
|
||||
|
||||
chunks.push(box);
|
||||
Cryptpad.rpc.send('UPLOAD', enc, function (e, msg) {
|
||||
console.log(box);
|
||||
cb(e, msg);
|
||||
});
|
||||
};
|
||||
|
||||
var actual = 0;
|
||||
var again = function (err, box) {
|
||||
if (err) { throw new Error(err); }
|
||||
if (box) {
|
||||
actual += box.length;
|
||||
var progressValue = (actual / estimate * 100);
|
||||
|
||||
return void sendChunk(box, function (e) {
|
||||
if (e) { return console.error(e); }
|
||||
var $pv = $table.find('tr[id="'+id+'"]').find('.progressValue');
|
||||
$pv.text(Math.round(progressValue*100)/100 + '%');
|
||||
var $pb = $table.find('tr[id="'+id+'"]').find('.progressContainer');
|
||||
$pb.css({
|
||||
width: (progressValue/100)*188+'px'
|
||||
});
|
||||
|
||||
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.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(Messages.upload_serverError);
|
||||
}
|
||||
|
||||
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.uploadCancel(function (e, res) {
|
||||
if (e) {
|
||||
return void console.error(e);
|
||||
}
|
||||
console.log(res);
|
||||
next(again);
|
||||
});
|
||||
});
|
||||
}
|
||||
next(again);
|
||||
});
|
||||
};
|
||||
|
||||
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');
|
||||
|
||||
var secret;
|
||||
var hexFileName;
|
||||
if (window.location.hash) {
|
||||
secret = Cryptpad.getSecrets();
|
||||
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
|
||||
hexFileName = Cryptpad.base64ToHex(secret.channel);
|
||||
} else {
|
||||
uploadMode = true;
|
||||
}
|
||||
|
||||
var getTitle = function () {
|
||||
var pad = Cryptpad.getRelativeHref(window.location.href);
|
||||
var fo = Cryptpad.getStore().getProxy().fo;
|
||||
var data = fo.getFileData(pad);
|
||||
return data ? data.title : undefined;
|
||||
};
|
||||
|
||||
var exportFile = function () {
|
||||
var filename = Cryptpad.fixFileName(document.title);
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
var blob = new Blob([myFile], {type: myDataType});
|
||||
saveAs(blob, filename);
|
||||
};
|
||||
|
||||
Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
|
||||
|
||||
var displayed = ['title', 'useradmin', 'newpad', 'limit'];
|
||||
if (secret && hexFileName) {
|
||||
displayed.push('fileshare');
|
||||
}
|
||||
|
||||
var configTb = {
|
||||
displayed: displayed,
|
||||
ifrw: ifrw,
|
||||
common: Cryptpad,
|
||||
title: Title.getTitleConfig(),
|
||||
hideDisplayName: true,
|
||||
$container: $bar
|
||||
};
|
||||
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);
|
||||
|
||||
Title.updateTitle(Cryptpad.initialName || getTitle() || Title.defaultTitle);
|
||||
|
||||
if (!uploadMode) {
|
||||
$dlform.show();
|
||||
var src = Cryptpad.getBlobPathFromHex(hexFileName);
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var key = Nacl.util.decodeBase64(cryptKey);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
$form.css({
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
var handleFile = function (file) {
|
||||
console.log(file);
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
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
|
||||
Cryptpad.removeLoadingScreen();
|
||||
};
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -1,28 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<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" />
|
||||
<style>
|
||||
input {
|
||||
width: 50vw;
|
||||
padding: 15px;
|
||||
}
|
||||
pre {
|
||||
max-width: 90vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="/customize/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Upload</h1>
|
||||
|
||||
<input type="file">
|
||||
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();
|
||||
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@
|
||||
<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"/>
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
|
||||
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
|
||||
</head>
|
||||
@@ -19,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>
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/login.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Cryptpad, Login) {
|
||||
var $ = window.$;
|
||||
|
||||
var APP = window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
};
|
||||
|
||||
'/common/login.js'
|
||||
], function ($, Cryptpad, Login) {
|
||||
$(function () {
|
||||
var $main = $('#mainBlock');
|
||||
var Messages = Cryptpad.Messages;
|
||||
@@ -19,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();
|
||||
});
|
||||
@@ -62,66 +64,69 @@ define([
|
||||
$('button.login').click();
|
||||
});
|
||||
|
||||
$('button.login').click(function (e) {
|
||||
Cryptpad.addLoadingScreen(Messages.login_hashing);
|
||||
// We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password
|
||||
$('button.login').click(function () {
|
||||
// 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.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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
BIN
www/media/assets/image.png-encrypted
Normal file
BIN
www/media/assets/image.png-encrypted
Normal file
Binary file not shown.
47
www/media/index.html
Normal file
47
www/media/index.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!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" />
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#pad-iframe {
|
||||
position:fixed;
|
||||
top:0px;
|
||||
left:0px;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
||||
<div id="loading">
|
||||
<div class="loadingContainer">
|
||||
<img class="cryptofist" src="/customize/cryptofist_small.png" />
|
||||
<div class="spinnerContainer">
|
||||
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
|
||||
</div>
|
||||
<p data-localization="loading"></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
29
www/media/inner.html
Normal file
29
www/media/inner.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<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;
|
||||
}
|
||||
media-tag * {
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="toolbar" class="toolbar-container"></div>
|
||||
<media-tag id="encryptedFile"></media-tag>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
113
www/media/main.js
Normal file
113
www/media/main.js
Normal file
@@ -0,0 +1,113 @@
|
||||
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',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) {
|
||||
//var Messages = Cryptpad.Messages;
|
||||
//var saveAs = window.saveAs;
|
||||
//window.Nacl = window.nacl;
|
||||
$(function () {
|
||||
|
||||
var ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var $iframe = $('#pad-iframe').contents();
|
||||
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var andThen = function () {
|
||||
var $bar = $iframe.find('.toolbar-container');
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
|
||||
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var fileId = secret.channel;
|
||||
var hexFileName = Cryptpad.base64ToHex(fileId);
|
||||
var type = "image/png";
|
||||
|
||||
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;
|
||||
var data = fo.getFileData(pad);
|
||||
return data ? data.title : undefined;
|
||||
};
|
||||
|
||||
var updateTitle = function (newTitle) {
|
||||
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 () {
|
||||
return document.title || getTitle() || '';
|
||||
};
|
||||
|
||||
var renameCb = function (err, title) {
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
$(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 = {
|
||||
displayed: ['useradmin', 'share', 'newpad'],
|
||||
ifrw: ifrw,
|
||||
common: Cryptpad,
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: hexFileName
|
||||
}
|
||||
};
|
||||
Toolbar.create($bar, null, null, null, null, configTb);
|
||||
|
||||
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
|
||||
|
||||
MediaTag($mt[0]);
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,7 @@
|
||||
}
|
||||
#cke_1_top {
|
||||
overflow: visible;
|
||||
padding: 0 6px;
|
||||
}
|
||||
#cke_1_toolbox {
|
||||
display: inline-block;
|
||||
|
||||
@@ -42,7 +42,7 @@ define(function () {
|
||||
});
|
||||
}
|
||||
if (editor.contextMenu) {
|
||||
editor.contextMenu.addListener(function(startElement, selection, path) {
|
||||
editor.contextMenu.addListener(function(startElement) {
|
||||
if (startElement) {
|
||||
var anchor = getActiveLink(editor);
|
||||
if (anchor && anchor.getAttribute('href')) {
|
||||
|
||||
304
www/pad/main.js
304
www/pad/main.js
@@ -1,9 +1,9 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/toolbar2.js',
|
||||
'/common/cursor.js',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/TypingTests.js',
|
||||
@@ -11,16 +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',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Crypto, realtimeInput, Hyperjson,
|
||||
Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget,
|
||||
Visible, Notify, Links) {
|
||||
var $ = window.jQuery;
|
||||
'/bower_components/diff-dom/diffDOM.js'
|
||||
], function ($, Crypto, realtimeInput, Hyperjson,
|
||||
Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget, Links) {
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
@@ -89,7 +84,7 @@ define([
|
||||
return hj;
|
||||
};
|
||||
|
||||
var onConnectError = function (info) {
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
|
||||
@@ -105,10 +100,10 @@ define([
|
||||
});
|
||||
|
||||
editor.on('instanceReady', Links.addSupportForOpeningLinksInNewTab(Ckeditor));
|
||||
editor.on('instanceReady', function (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;
|
||||
|
||||
if (readOnly) {
|
||||
$('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox > .cke_toolbox_main').hide();
|
||||
@@ -167,7 +162,7 @@ define([
|
||||
if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) {
|
||||
if (info.diff.name === 'href') {
|
||||
// console.log(info.diff);
|
||||
var href = info.diff.newValue;
|
||||
//var href = info.diff.newValue;
|
||||
|
||||
// TODO normalize HTML entities
|
||||
if (/javascript *: */.test(info.diff.newValue)) {
|
||||
@@ -277,56 +272,10 @@ define([
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
var userData = module.userData = {}; // List of pretty names for all users (mapped with their ID)
|
||||
var userList; // List of users still connected to the channel (server IDs)
|
||||
var addToUserData = function(data) {
|
||||
var users = module.users;
|
||||
for (var attrname in data) { userData[attrname] = data[attrname]; }
|
||||
|
||||
if (users && users.length) {
|
||||
for (var userKey in userData) {
|
||||
if (users.indexOf(userKey) === -1) { delete userData[userKey]; }
|
||||
}
|
||||
}
|
||||
|
||||
if(userList && typeof userList.onChange === "function") {
|
||||
userList.onChange(userData);
|
||||
}
|
||||
};
|
||||
|
||||
var myData = {};
|
||||
var myUserName = ''; // My "pretty name"
|
||||
var myID; // My server ID
|
||||
|
||||
var setMyID = function(info) {
|
||||
myID = info.myID || null;
|
||||
};
|
||||
|
||||
var setName = module.setName = function (newName) {
|
||||
if (typeof(newName) !== 'string') { return; }
|
||||
var myUserNameTemp = newName.trim();
|
||||
if(myUserNameTemp.length > 32) {
|
||||
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
||||
}
|
||||
myUserName = myUserNameTemp;
|
||||
myData[myID] = {
|
||||
name: myUserName,
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
Cryptpad.setAttribute('username', newName, function (err, data) {
|
||||
if (err) {
|
||||
console.error("Couldn't set username");
|
||||
return;
|
||||
}
|
||||
editor.fire('change');
|
||||
});
|
||||
};
|
||||
|
||||
var isDefaultTitle = function () {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
return Cryptpad.isDefaultName(parsed, document.title);
|
||||
};
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var getHeadingText = function () {
|
||||
var text;
|
||||
@@ -339,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!
|
||||
@@ -364,12 +305,12 @@ define([
|
||||
var hjson = Hyperjson.fromDOM(dom, isNotMagicLine, brFilter);
|
||||
hjson[3] = {
|
||||
metadata: {
|
||||
users: userData,
|
||||
defaultTitle: defaultName
|
||||
users: UserList.userData,
|
||||
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;
|
||||
}
|
||||
@@ -390,9 +331,6 @@ define([
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
readOnly: readOnly,
|
||||
|
||||
// method which allows us to get the id of the user
|
||||
setMyID: setMyID,
|
||||
|
||||
// Pass in encrypt and decrypt methods
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
|
||||
@@ -413,80 +351,27 @@ 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
|
||||
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 setHistory = function (bool, update) {
|
||||
isHistoryMode = bool;
|
||||
setEditable(!bool);
|
||||
if (!bool && update) {
|
||||
realtimeOptions.onRemote();
|
||||
}
|
||||
};
|
||||
|
||||
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 = realtimeOptions.onRemote = function (info) {
|
||||
realtimeOptions.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
|
||||
var oldShjson = stringifyDOM(inner);
|
||||
|
||||
var shjson = info.realtime.getUserDoc();
|
||||
var shjson = module.realtime.getUserDoc();
|
||||
|
||||
// remember where the cursor is
|
||||
cursor.update();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(shjson);
|
||||
Metadata.update(shjson);
|
||||
|
||||
var newInner = JSON.parse(shjson);
|
||||
var newSInner;
|
||||
@@ -531,11 +416,11 @@ 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();
|
||||
}
|
||||
};
|
||||
|
||||
var getHTML = function (Dom) {
|
||||
var getHTML = function () {
|
||||
return ('<!DOCTYPE html>\n' + '<html>\n' + inner.innerHTML);
|
||||
};
|
||||
|
||||
@@ -545,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; }
|
||||
@@ -553,46 +438,42 @@ define([
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
};
|
||||
var importFile = function (content, file) {
|
||||
var importFile = function (content) {
|
||||
var shjson = stringify(Hyperjson.fromDOM(domFromHTML(content).body));
|
||||
applyHjson(shjson);
|
||||
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 onInit = realtimeOptions.onInit = function (info) {
|
||||
userList = info.userList;
|
||||
var titleCfg = { getHeadingText: getHeadingText };
|
||||
Title = Cryptpad.createTitle(titleCfg, realtimeOptions.onLocal, Cryptpad);
|
||||
|
||||
var config = {
|
||||
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
|
||||
userData: userData,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
common: Cryptpad
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: $bar
|
||||
};
|
||||
if (readOnly) {delete config.changeNameID; }
|
||||
toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, config);
|
||||
toolbar = info.realtime.toolbar = Toolbar.create(configTb);
|
||||
|
||||
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
||||
var $userBlock = $bar.find('.' + Toolbar.constants.username);
|
||||
var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername));
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
var editHash;
|
||||
var viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
@@ -617,6 +498,17 @@ define([
|
||||
$rightside.append($collapse);
|
||||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {
|
||||
onLocal: realtimeOptions.onLocal(),
|
||||
onRemote: realtimeOptions.onRemote(),
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) { applyHjson(val || '["BODY",{},[]]'); },
|
||||
$toolbar: $bar
|
||||
};
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
var templateObj = {
|
||||
@@ -636,14 +528,10 @@ define([
|
||||
/* add an import button */
|
||||
var $import = Cryptpad.createButton('import', true, {}, importFile);
|
||||
$rightside.append($import);
|
||||
|
||||
/* add a rename button */
|
||||
//var $setTitle = Cryptpad.createButton('rename', true, {suggestName: suggestName}, renameCb);
|
||||
//$rightside.append($setTitle);
|
||||
}
|
||||
|
||||
/* add a forget button */
|
||||
var forgetCb = function (err, title) {
|
||||
var forgetCb = function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
};
|
||||
@@ -652,12 +540,10 @@ define([
|
||||
|
||||
// set the hash
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
|
||||
Cryptpad.onDisplayNameChanged(setName);
|
||||
};
|
||||
|
||||
// this should only ever get called once, when the chain syncs
|
||||
var onReady = realtimeOptions.onReady = function (info) {
|
||||
realtimeOptions.onReady = function (info) {
|
||||
if (!module.isMaximized) {
|
||||
editor.execCommand('maximize');
|
||||
module.isMaximized = true;
|
||||
@@ -676,10 +562,9 @@ define([
|
||||
});
|
||||
}
|
||||
|
||||
module.users = info.userList.users;
|
||||
module.realtime = info.realtime;
|
||||
|
||||
var shjson = info.realtime.getUserDoc();
|
||||
var shjson = module.realtime.getUserDoc();
|
||||
|
||||
var newPad = false;
|
||||
if (shjson === '') { newPad = true; }
|
||||
@@ -688,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);
|
||||
@@ -708,42 +587,25 @@ define([
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateTitle(Cryptpad.initialName || defaultName);
|
||||
Title.updateTitle(Cryptpad.initialName || Title.defaultTitle);
|
||||
documentBody.innerHTML = Messages.initialState;
|
||||
}
|
||||
|
||||
Cryptpad.getLastName(function (err, lastName) {
|
||||
console.log("Unlocking editor");
|
||||
setEditable(!readOnly);
|
||||
initializing = false;
|
||||
Cryptpad.removeLoadingScreen(emitResize);
|
||||
Cryptpad.removeLoadingScreen(emitResize);
|
||||
setEditable(!readOnly);
|
||||
initializing = false;
|
||||
|
||||
// Update the toolbar list:
|
||||
// Add the current user in the metadata if he has edit rights
|
||||
if (readOnly) { return; }
|
||||
if (typeof(lastName) === 'string') {
|
||||
setName(lastName);
|
||||
} else {
|
||||
myData[myID] = {
|
||||
name: "",
|
||||
uid: Cryptpad.getUid()
|
||||
};
|
||||
addToUserData(myData);
|
||||
realtimeOptions.onLocal();
|
||||
module.$userNameButton.click();
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
if (newPad) {
|
||||
Cryptpad.selectTemplate('pad', info.realtime, Cryptget);
|
||||
cursor.setToEnd();
|
||||
} else {
|
||||
cursor.setToStart();
|
||||
}
|
||||
});
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(toolbar.$userNameButton, newPad);
|
||||
editor.focus();
|
||||
if (newPad) {
|
||||
cursor.setToEnd();
|
||||
} else {
|
||||
cursor.setToStart();
|
||||
}
|
||||
};
|
||||
|
||||
var onAbort = realtimeOptions.onAbort = function (info) {
|
||||
realtimeOptions.onAbort = function () {
|
||||
console.log("Aborting the session!");
|
||||
// stop the user from continuing to edit
|
||||
setEditable(false);
|
||||
@@ -751,7 +613,7 @@ define([
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
var onConnectionChange = realtimeOptions.onConnectionChange = function (info) {
|
||||
realtimeOptions.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
@@ -763,10 +625,11 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var onError = realtimeOptions.onError = onConnectError;
|
||||
realtimeOptions.onError = onConnectError;
|
||||
|
||||
var onLocal = realtimeOptions.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
// stringify the json and send it into chainpad
|
||||
@@ -778,7 +641,7 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var rti = module.realtimeInput = realtimeInput.start(realtimeOptions);
|
||||
module.realtimeInput = realtimeInput.start(realtimeOptions);
|
||||
|
||||
Cryptpad.onLogout(function () { setEditable(false); });
|
||||
|
||||
@@ -796,7 +659,7 @@ define([
|
||||
// export the typing tests to the window.
|
||||
// call like `test = easyTest()`
|
||||
// terminate the test like `test.cancel()`
|
||||
var easyTest = window.easyTest = function () {
|
||||
window.easyTest = function () {
|
||||
cursor.update();
|
||||
var start = cursor.Range.start;
|
||||
var test = TypingTest.testInput(inner, start.el, start.offset, onLocal);
|
||||
@@ -808,8 +671,9 @@ define([
|
||||
|
||||
var interval = 100;
|
||||
var second = function (Ckeditor) {
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen(Ckeditor);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info && info.type === "store") {
|
||||
|
||||
526
www/poll/main.js
526
www/poll/main.js
@@ -1,4 +1,5 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
@@ -6,13 +7,9 @@ define([
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'render.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar, Visible, Notify) {
|
||||
var $ = window.jQuery;
|
||||
'/common/toolbar2.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar) {
|
||||
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
@@ -35,10 +32,9 @@ define([
|
||||
if (!DEBUG) {
|
||||
debug = function() {};
|
||||
}
|
||||
var error = console.error;
|
||||
|
||||
Cryptpad.addLoadingScreen();
|
||||
var onConnectError = function (info) {
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
|
||||
@@ -98,15 +94,6 @@ define([
|
||||
return newObj;
|
||||
};
|
||||
|
||||
var setColumnDisabled = function (id, state) {
|
||||
if (!state) {
|
||||
$('input[data-rt-id^="' + id + '"]').removeAttr('disabled');
|
||||
return;
|
||||
}
|
||||
$('input[data-rt-id^="' + id + '"]').attr('disabled', 'disabled');
|
||||
};
|
||||
|
||||
|
||||
var styleUncommittedColumn = function () {
|
||||
var id = APP.userid;
|
||||
|
||||
@@ -196,20 +183,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)) {
|
||||
@@ -238,7 +211,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;
|
||||
@@ -287,7 +260,7 @@ define([
|
||||
};
|
||||
|
||||
/* Called whenever an event is fired on an input element */
|
||||
var handleInput = function (input, isKeyup) {
|
||||
var handleInput = function (input) {
|
||||
var type = input.type.toLowerCase();
|
||||
var id = getRealtimeId(input);
|
||||
|
||||
@@ -452,82 +425,8 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var userData = APP.userData = {}; // List of pretty names for all users (mapped with their ID)
|
||||
var userList; // List of users still connected to the channel (server IDs)
|
||||
var addToUserData = function(data) {
|
||||
var users = userList ? userList.users : undefined;
|
||||
//var userData = APP.proxy.info.userData;
|
||||
for (var attrname in data) { userData[attrname] = data[attrname]; }
|
||||
|
||||
if (users && users.length) {
|
||||
for (var userKey in userData) {
|
||||
if (users.indexOf(userKey) === -1) { delete userData[userKey]; }
|
||||
}
|
||||
}
|
||||
|
||||
if(userList && typeof userList.onChange === "function") {
|
||||
userList.onChange(userData);
|
||||
}
|
||||
|
||||
APP.proxy.info.userData = userData;
|
||||
};
|
||||
|
||||
var setName = APP.setName = function (newName) {
|
||||
if (typeof(newName) !== 'string') { return; }
|
||||
var myUserNameTemp = newName.trim();
|
||||
if(myUserNameTemp.length > 32) {
|
||||
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
||||
}
|
||||
var myUserName = myUserNameTemp;
|
||||
var myID = APP.myID;
|
||||
var myData = {};
|
||||
myData[myID] = {
|
||||
name: myUserName,
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
Cryptpad.setAttribute('username', newName, function (err, data) {
|
||||
if (err) {
|
||||
console.error("Couldn't set username");
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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 Title;
|
||||
var UserList;
|
||||
|
||||
var copyObject = function (obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
@@ -536,264 +435,229 @@ define([
|
||||
// special UI elements
|
||||
var $description = $('#description').attr('placeholder', Messages.poll_descriptionHint || 'description');
|
||||
|
||||
var ready = function (info, userid, readOnly) {
|
||||
debug("READY");
|
||||
debug('userid: %s', userid);
|
||||
var ready = function (info, userid, readOnly) {
|
||||
debug("READY");
|
||||
debug('userid: %s', userid);
|
||||
|
||||
var proxy = APP.proxy;
|
||||
var proxy = APP.proxy;
|
||||
|
||||
var isNew = false;
|
||||
var userDoc = JSON.stringify(proxy);
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
var isNew = false;
|
||||
var userDoc = JSON.stringify(proxy);
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
|
||||
var uncommitted = APP.uncommitted = {};
|
||||
prepareProxy(proxy, copyObject(Render.Example));
|
||||
prepareProxy(uncommitted, copyObject(Render.Example));
|
||||
if (!readOnly && proxy.table.colsOrder.indexOf(userid) === -1 &&
|
||||
uncommitted.table.colsOrder.indexOf(userid) === -1) {
|
||||
uncommitted.table.colsOrder.unshift(userid);
|
||||
}
|
||||
var uncommitted = APP.uncommitted = {};
|
||||
prepareProxy(proxy, copyObject(Render.Example));
|
||||
prepareProxy(uncommitted, copyObject(Render.Example));
|
||||
if (!readOnly && proxy.table.colsOrder.indexOf(userid) === -1 &&
|
||||
uncommitted.table.colsOrder.indexOf(userid) === -1) {
|
||||
uncommitted.table.colsOrder.unshift(userid);
|
||||
}
|
||||
|
||||
var displayedObj = mergeUncommitted(proxy, uncommitted, false);
|
||||
var displayedObj = mergeUncommitted(proxy, uncommitted, false);
|
||||
|
||||
var colsOrder = sortColumns(displayedObj.table.colsOrder, userid);
|
||||
var colsOrder = sortColumns(displayedObj.table.colsOrder, userid);
|
||||
|
||||
var $table = APP.$table = $(Render.asHTML(displayedObj, null, colsOrder, readOnly));
|
||||
var $createRow = 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();
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var $createCol = APP.$createCol = $('#create-user').click(function () {
|
||||
Render.createColumn(proxy, function (empty, id) {
|
||||
change(null, null, null, null, function() {
|
||||
$('.edit[data-rt-id="' + id + '"]').click();
|
||||
});
|
||||
APP.$createCol = $('#create-user').click(function () {
|
||||
Render.createColumn(proxy, function (empty, id) {
|
||||
change(null, null, null, null, function() {
|
||||
$('.edit[data-rt-id="' + id + '"]').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Commit button
|
||||
var $commit = APP.$commit = $('#commit').click(function () {
|
||||
var uncommittedCopy = JSON.parse(JSON.stringify(APP.uncommitted));
|
||||
APP.uncommitted = {};
|
||||
prepareProxy(APP.uncommitted, copyObject(Render.Example));
|
||||
mergeUncommitted(proxy, uncommittedCopy, true);
|
||||
APP.$commit.hide();
|
||||
change();
|
||||
// Commit button
|
||||
APP.$commit = $('#commit').click(function () {
|
||||
var uncommittedCopy = JSON.parse(JSON.stringify(APP.uncommitted));
|
||||
APP.uncommitted = {};
|
||||
prepareProxy(APP.uncommitted, copyObject(Render.Example));
|
||||
mergeUncommitted(proxy, uncommittedCopy, true);
|
||||
APP.$commit.hide();
|
||||
change();
|
||||
});
|
||||
|
||||
// #publish button is removed in readonly
|
||||
APP.$publish = $('#publish')
|
||||
.click(function () {
|
||||
publish(true);
|
||||
});
|
||||
|
||||
// #publish button is removed in readonly
|
||||
var $publish = APP.$publish = $('#publish')
|
||||
.click(function () {
|
||||
publish(true);
|
||||
});
|
||||
|
||||
// #publish button is removed in readonly
|
||||
var $admin = APP.$admin = $('#admin')
|
||||
.click(function () {
|
||||
publish(false);
|
||||
});
|
||||
|
||||
// Title
|
||||
if (APP.proxy.info.defaultTitle) {
|
||||
updateDefaultTitle(APP.proxy.info.defaultTitle);
|
||||
} else {
|
||||
APP.proxy.info.defaultTitle = defaultName;
|
||||
}
|
||||
if (Cryptpad.initialName && !APP.proxy.info.title) {
|
||||
APP.proxy.info.title = Cryptpad.initialName;
|
||||
updateTitle(Cryptpad.initialName);
|
||||
} else {
|
||||
updateTitle(APP.proxy.info.title || defaultName);
|
||||
}
|
||||
|
||||
// Description
|
||||
var resize = function () {
|
||||
var lineCount = $description.val().split('\n').length;
|
||||
$description.css('height', lineCount + 'rem');
|
||||
};
|
||||
$description.on('change keyup', function () {
|
||||
var val = $description.val();
|
||||
proxy.info.description = val;
|
||||
resize();
|
||||
// #publish button is removed in readonly
|
||||
APP.$admin = $('#admin')
|
||||
.click(function () {
|
||||
publish(false);
|
||||
});
|
||||
|
||||
// Title
|
||||
if (APP.proxy.info.defaultTitle) {
|
||||
Title.updateDefaultTitle(APP.proxy.info.defaultTitle);
|
||||
} else {
|
||||
APP.proxy.info.defaultTitle = Title.defaultTitle;
|
||||
}
|
||||
if (Cryptpad.initialName && !APP.proxy.info.title) {
|
||||
APP.proxy.info.title = Cryptpad.initialName;
|
||||
Title.updateTitle(Cryptpad.initialName);
|
||||
} else {
|
||||
Title.updateTitle(APP.proxy.info.title || Title.defaultTitle);
|
||||
}
|
||||
|
||||
// Description
|
||||
var resize = function () {
|
||||
var lineCount = $description.val().split('\n').length;
|
||||
$description.css('height', lineCount + 'rem');
|
||||
};
|
||||
$description.on('change keyup', function () {
|
||||
var val = $description.val();
|
||||
proxy.info.description = val;
|
||||
resize();
|
||||
if (typeof(proxy.info.description) !== 'undefined') {
|
||||
$description.val(proxy.info.description);
|
||||
}
|
||||
});
|
||||
resize();
|
||||
if (typeof(proxy.info.description) !== 'undefined') {
|
||||
$description.val(proxy.info.description);
|
||||
}
|
||||
|
||||
$('#tableScroll').html('').prepend($table);
|
||||
updateDisplayedTable();
|
||||
$('#tableScroll').html('').prepend($table);
|
||||
updateDisplayedTable();
|
||||
|
||||
$table
|
||||
.click(handleClick)
|
||||
.on('keyup', function (e) { handleClick(e, true); });
|
||||
$table
|
||||
.click(handleClick)
|
||||
.on('keyup', function (e) { handleClick(e, true); });
|
||||
|
||||
proxy
|
||||
.on('change', ['info'], function (o, n, p) {
|
||||
if (p[1] === 'title') {
|
||||
updateTitle(n);
|
||||
notify();
|
||||
} else if (p[1] === "userData") {
|
||||
addToUserData(APP.proxy.info.userData);
|
||||
} else if (p[1] === 'description') {
|
||||
var op = TextPatcher.diff(o, n);
|
||||
var el = $description[0];
|
||||
proxy
|
||||
.on('change', ['info'], function (o, n, p) {
|
||||
if (p[1] === 'title') {
|
||||
Title.updateTitle(n);
|
||||
Cryptpad.notify();
|
||||
} else if (p[1] === "userData") {
|
||||
UserList.addToUserData(APP.proxy.info.userData);
|
||||
} else if (p[1] === 'description') {
|
||||
var op = TextPatcher.diff(o, n);
|
||||
var el = $description[0];
|
||||
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
var before = el[attr];
|
||||
var after = TextPatcher.transformCursor(el[attr], op);
|
||||
return after;
|
||||
});
|
||||
$description.val(n);
|
||||
if (op) {
|
||||
el.selectionStart = selects[0];
|
||||
el.selectionEnd = selects[1];
|
||||
}
|
||||
notify();
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(el[attr], op);
|
||||
});
|
||||
$description.val(n);
|
||||
if (op) {
|
||||
el.selectionStart = selects[0];
|
||||
el.selectionEnd = selects[1];
|
||||
}
|
||||
|
||||
debug("change: (%s, %s, [%s])", o, n, p.join(', '));
|
||||
})
|
||||
.on('change', ['table'], change)
|
||||
.on('remove', [], change);
|
||||
|
||||
addToUserData(APP.proxy.info.userData);
|
||||
|
||||
if (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) {
|
||||
if (yes) { unnotify(); }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Cryptpad.getLastName(function (err, lastName) {
|
||||
APP.ready = true;
|
||||
|
||||
if (!proxy.published) {
|
||||
publish(false);
|
||||
} else {
|
||||
publish(true);
|
||||
Cryptpad.notify();
|
||||
}
|
||||
Cryptpad.removeLoadingScreen();
|
||||
|
||||
// Update the toolbar list:
|
||||
// Add the current user in the metadata if he has edit rights
|
||||
if (readOnly) { return; }
|
||||
if (typeof(lastName) === 'string') {
|
||||
setName(lastName);
|
||||
} else {
|
||||
var myData = {};
|
||||
myData[info.myId] = {
|
||||
name: "",
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
APP.$userNameButton.click();
|
||||
}
|
||||
if (isNew) {
|
||||
Cryptpad.selectTemplate('poll', info.realtime, Cryptget);
|
||||
}
|
||||
debug("change: (%s, %s, [%s])", o, n, p.join(', '));
|
||||
})
|
||||
.on('change', ['table'], change)
|
||||
.on('remove', [], change);
|
||||
|
||||
UserList.addToUserData(APP.proxy.info.userData);
|
||||
|
||||
APP.ready = true;
|
||||
if (!proxy.published) {
|
||||
publish(false);
|
||||
} else {
|
||||
publish(true);
|
||||
}
|
||||
Cryptpad.removeLoadingScreen();
|
||||
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(APP.toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
var disconnect = function () {
|
||||
//setEditable(false); // TODO
|
||||
APP.toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
var reconnect = function (info) {
|
||||
//setEditable(true); // TODO
|
||||
APP.toolbar.reconnecting(info.myId);
|
||||
Cryptpad.findOKButton().click();
|
||||
};
|
||||
|
||||
var create = function (info) {
|
||||
APP.myID = info.myID;
|
||||
|
||||
var editHash;
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
}
|
||||
|
||||
if (APP.realtime !== info.realtime) {
|
||||
APP.realtime = info.realtime;
|
||||
APP.patchText = TextPatcher.create({
|
||||
realtime: info.realtime,
|
||||
logging: true,
|
||||
});
|
||||
}
|
||||
|
||||
var onLocal = function () {
|
||||
APP.proxy.info.userData = UserList.userData;
|
||||
};
|
||||
UserList = Cryptpad.createUserList(info, onLocal, Cryptget, Cryptpad);
|
||||
|
||||
var disconnect = function (info) {
|
||||
//setEditable(false); // TODO
|
||||
APP.realtime.toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
var onLocalTitle = function () {
|
||||
APP.proxy.info.title = Title.isDefaultTitle() ? "" : Title.title;
|
||||
};
|
||||
Title = Cryptpad.createTitle({}, onLocalTitle, Cryptpad);
|
||||
|
||||
var reconnect = function (info) {
|
||||
//setEditable(true); // TODO
|
||||
APP.realtime.toolbar.reconnecting(info.myId);
|
||||
Cryptpad.findOKButton().click();
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: window,
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: APP.$bar
|
||||
};
|
||||
APP.toolbar = Toolbar.create(configTb);
|
||||
|
||||
var create = function (info) {
|
||||
var myID = APP.myID = info.myID;
|
||||
Title.setToolbar(APP.toolbar);
|
||||
|
||||
var editHash;
|
||||
var viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
||||
var $rightside = APP.toolbar.$rightside;
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
}
|
||||
/* add a forget button */
|
||||
var forgetCb = function (err) {
|
||||
if (err) { return; }
|
||||
disconnect();
|
||||
};
|
||||
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
|
||||
$rightside.append($forgetPad);
|
||||
|
||||
if (APP.realtime !== info.realtime) {
|
||||
APP.realtime = info.realtime;
|
||||
APP.patchText = TextPatcher.create({
|
||||
realtime: info.realtime,
|
||||
logging: true,
|
||||
});
|
||||
}
|
||||
// set the hash
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
|
||||
userList = APP.userList = info.userList;
|
||||
|
||||
var config = {
|
||||
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
|
||||
userData: userData,
|
||||
readOnly: readOnly,
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
ifrw: window,
|
||||
common: Cryptpad,
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
var templateObj = {
|
||||
rt: info.realtime,
|
||||
Crypt: Cryptget,
|
||||
getTitle: function () { return document.title; }
|
||||
};
|
||||
var toolbar = info.realtime.toolbar = Toolbar.create(APP.$bar, info.myID, info.realtime, info.getLag, userList, config);
|
||||
|
||||
var $bar = APP.$bar;
|
||||
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
||||
var $userBlock = $bar.find('.' + Toolbar.constants.username);
|
||||
var $editShare = $bar.find('.' + Toolbar.constants.editShare);
|
||||
var $viewShare = $bar.find('.' + Toolbar.constants.viewShare);
|
||||
var $usernameButton = APP.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername));
|
||||
|
||||
/* add a forget button */
|
||||
var forgetCb = function (err, title) {
|
||||
if (err) { return; }
|
||||
disconnect();
|
||||
};
|
||||
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
|
||||
$rightside.append($forgetPad);
|
||||
|
||||
// set the hash
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
var templateObj = {
|
||||
rt: info.realtime,
|
||||
Crypt: Cryptget,
|
||||
getTitle: function () { return document.title; }
|
||||
};
|
||||
var $templateButton = Cryptpad.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
|
||||
Cryptpad.onDisplayNameChanged(setName);
|
||||
|
||||
Cryptpad.getPadTitle(function (err, title) {
|
||||
if (err) {
|
||||
error(err);
|
||||
debug("Couldn't get pad title");
|
||||
return;
|
||||
}
|
||||
updateTitle(title || defaultName);
|
||||
});
|
||||
};
|
||||
var $templateButton = Cryptpad.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
};
|
||||
|
||||
// don't initialize until the store is ready.
|
||||
Cryptpad.ready(function () {
|
||||
Cryptpad.reportAppUsage();
|
||||
var config = {
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
channel: secret.channel,
|
||||
@@ -823,6 +687,7 @@ define([
|
||||
if (!userid) { userid = Render.coluid(); }
|
||||
APP.userid = userid;
|
||||
Cryptpad.setPadAttribute('userid', userid, function (e) {
|
||||
if (e) { console.error(e); }
|
||||
ready(info, userid, readOnly);
|
||||
});
|
||||
});
|
||||
@@ -851,4 +716,3 @@ define([
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ var Renderer = function (Cryptpad) {
|
||||
return null;
|
||||
};
|
||||
|
||||
var getCoordinates = Render.getCoordinates = function (id) {
|
||||
Render.getCoordinates = function (id) {
|
||||
return id.split('_');
|
||||
};
|
||||
|
||||
@@ -91,7 +91,7 @@ var Renderer = function (Cryptpad) {
|
||||
return null;
|
||||
};
|
||||
|
||||
var createColumn = Render.createColumn = function (obj, cb, id, value) {
|
||||
Render.createColumn = function (obj, cb, id, value) {
|
||||
var order = Cryptpad.find(obj, ['table', 'colsOrder']);
|
||||
if (!order) { throw new Error("Uninitialized realtime object!"); }
|
||||
id = id || coluid();
|
||||
@@ -101,7 +101,7 @@ var Renderer = function (Cryptpad) {
|
||||
if (typeof(cb) === 'function') { cb(void 0, id); }
|
||||
};
|
||||
|
||||
var removeColumn = Render.removeColumn = function (obj, id, cb) {
|
||||
Render.removeColumn = function (obj, id, cb) {
|
||||
var order = Cryptpad.find(obj, ['table', 'colsOrder']);
|
||||
var parent = Cryptpad.find(obj, ['table', 'cols']);
|
||||
|
||||
@@ -126,7 +126,7 @@ var Renderer = function (Cryptpad) {
|
||||
}
|
||||
};
|
||||
|
||||
var createRow = Render.createRow = function (obj, cb, id, value) {
|
||||
Render.createRow = function (obj, cb, id, value) {
|
||||
var order = Cryptpad.find(obj, ['table', 'rowsOrder']);
|
||||
if (!order) { throw new Error("Uninitialized realtime object!"); }
|
||||
id = id || rowuid();
|
||||
@@ -136,7 +136,7 @@ var Renderer = function (Cryptpad) {
|
||||
if (typeof(cb) === 'function') { cb(void 0, id); }
|
||||
};
|
||||
|
||||
var removeRow = Render.removeRow = function (obj, id, cb) {
|
||||
Render.removeRow = function (obj, id, cb) {
|
||||
var order = Cryptpad.find(obj, ['table', 'rowsOrder']);
|
||||
var parent = Cryptpad.find(obj, ['table', 'rows']);
|
||||
|
||||
@@ -153,7 +153,7 @@ var Renderer = function (Cryptpad) {
|
||||
if (typeof(cb) === 'function') { cb(); }
|
||||
};
|
||||
|
||||
var setValue = Render.setValue = function (obj, id, value) {
|
||||
Render.setValue = function (obj, id, value) {
|
||||
var type = typeofId(id);
|
||||
|
||||
switch (type) {
|
||||
@@ -167,7 +167,7 @@ var Renderer = function (Cryptpad) {
|
||||
}
|
||||
};
|
||||
|
||||
var getValue = Render.getValue = function (obj, id) {
|
||||
Render.getValue = function (obj, id) {
|
||||
switch (typeofId(id)) {
|
||||
case 'row': return getRowValue(obj, id);
|
||||
case 'col': return getColumnValue(obj, id);
|
||||
@@ -219,7 +219,7 @@ var Renderer = function (Cryptpad) {
|
||||
}));
|
||||
}
|
||||
if (i === rows.length) {
|
||||
return [null].concat(cols.map(function (col) {
|
||||
return [null].concat(cols.map(function () {
|
||||
return {
|
||||
'class': 'lastRow',
|
||||
};
|
||||
@@ -357,7 +357,7 @@ var Renderer = function (Cryptpad) {
|
||||
return ['TABLE', {id:'table'}, [head, foot, body]];
|
||||
};
|
||||
|
||||
var asHTML = Render.asHTML = function (obj, rows, cols, readOnly) {
|
||||
Render.asHTML = function (obj, rows, cols, readOnly) {
|
||||
return Hyperjson.toDOM(toHyperjson(cellMatrix(obj, rows, cols, readOnly), readOnly));
|
||||
};
|
||||
|
||||
@@ -382,9 +382,7 @@ var Renderer = function (Cryptpad) {
|
||||
var op = TextPatcher.diff(o, n);
|
||||
|
||||
info.selection = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
var before = element[attr];
|
||||
var after = TextPatcher.transformCursor(element[attr], op);
|
||||
return after;
|
||||
return TextPatcher.transformCursor(element[attr], op);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -430,7 +428,7 @@ var Renderer = function (Cryptpad) {
|
||||
}
|
||||
};
|
||||
|
||||
var updateTable = Render.updateTable = function (table, obj, conf) {
|
||||
Render.updateTable = function (table, obj, conf) {
|
||||
var DD = new DiffDOM(diffOptions);
|
||||
|
||||
var rows = conf ? conf.rows : null;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/login.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/credential.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Login, Cryptpad, Crypt) {
|
||||
var $ = window.jQuery;
|
||||
|
||||
var APP = window.APP = {
|
||||
Login: Login,
|
||||
};
|
||||
|
||||
'/common/credential.js' // preloaded for login.js
|
||||
], function ($, Login, Cryptpad) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
$(function () {
|
||||
@@ -22,6 +15,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();
|
||||
});
|
||||
@@ -68,6 +69,8 @@ define([
|
||||
proxy.edPublic = result.edPublic;
|
||||
proxy.edPrivate = result.edPrivate;
|
||||
|
||||
Cryptpad.feedback('REGISTRATION', true);
|
||||
|
||||
Cryptpad.whenRealtimeSyncs(result.realtime, function () {
|
||||
Cryptpad.login(result.userHash, result.userName, function () {
|
||||
if (sessionStorage.redirectTo) {
|
||||
@@ -106,57 +109,63 @@ 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;
|
||||
}
|
||||
Cryptpad.eraseTempSessionValues();
|
||||
if (shouldImport) {
|
||||
sessionStorage.migrateAnonDrive = 1;
|
||||
}
|
||||
|
||||
proxy.login_name = uname;
|
||||
proxy[Cryptpad.displayNameKey] = uname;
|
||||
sessionStorage.createReadme = 1;
|
||||
proxy.login_name = uname;
|
||||
proxy[Cryptpad.displayNameKey] = uname;
|
||||
sessionStorage.createReadme = 1;
|
||||
|
||||
logMeIn(result);
|
||||
});
|
||||
logMeIn(result);
|
||||
});
|
||||
}, 0);
|
||||
}, 100);
|
||||
}, {
|
||||
ok: Messages.register_writtenPassword,
|
||||
cancel: Messages.register_cancel,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<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"/>
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<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>
|
||||
@@ -98,7 +97,7 @@
|
||||
<div class="col">
|
||||
<ul class="list-unstyled">
|
||||
<li class="title" data-localization="footer_contact"><li>
|
||||
<li><a href="http://webchat.freenode.net?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7" target="_blank" rel="noopener noreferrer">IRC</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>
|
||||
@@ -106,7 +105,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.4.0 (Easter-Bunny)</div>
|
||||
<div class="version-footer">CryptPad v1.7.0 (Hodag)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
define([
|
||||
'jquery',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/mergeDrive.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Cryptpad, Crypt, Merge) {
|
||||
var $ = window.jQuery;
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, Cryptpad, Crypt, Merge) {
|
||||
var saveAs = window.saveAs;
|
||||
|
||||
var USERNAME_KEY = 'cryptpad.username';
|
||||
@@ -17,10 +16,6 @@ define([
|
||||
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var redirectToMain = function () {
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
// Manage changes in the realtime object made from another page
|
||||
var onRefresh = function (h) {
|
||||
if (typeof(h) !== "function") { return; }
|
||||
@@ -54,17 +49,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;
|
||||
};
|
||||
|
||||
@@ -72,7 +67,7 @@ define([
|
||||
var createDisplayNameInput = function (store) {
|
||||
var obj = store.proxy;
|
||||
var $div = $('<div>', {'class': 'displayName'});
|
||||
var $label = $('<label>', {'for' : 'displayName'}).text(Messages.user_displayName).appendTo($div);
|
||||
$('<label>', {'for' : 'displayName'}).text(Messages.user_displayName).appendTo($div);
|
||||
$('<br>').appendTo($div);
|
||||
var $input = $('<input>', {
|
||||
'type': 'text',
|
||||
@@ -115,7 +110,7 @@ define([
|
||||
};
|
||||
var createResetTips = function () {
|
||||
var $div = $('<div>', {'class': 'resetTips'});
|
||||
var $label = $('<label>', {'for' : 'resetTips'}).text(Messages.settings_resetTips).appendTo($div);
|
||||
$('<label>', {'for' : 'resetTips'}).text(Messages.settings_resetTips).appendTo($div);
|
||||
$('<br>').appendTo($div);
|
||||
var $button = $('<button>', {'id': 'resetTips', 'class': 'btn btn-primary'})
|
||||
.text(Messages.settings_resetTipsButton).appendTo($div);
|
||||
@@ -146,7 +141,7 @@ define([
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
};
|
||||
var importFile = function (content, file) {
|
||||
var importFile = function (content) {
|
||||
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).appendTo($div);
|
||||
Crypt.put(Cryptpad.getUserHash() || localStorage[Cryptpad.fileHashKey], content, function (e) {
|
||||
if (e) { console.error(e); }
|
||||
@@ -154,7 +149,7 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
var $label = $('<label>', {'for' : 'exportDrive'}).text(Messages.settings_backupTitle).appendTo($div);
|
||||
$('<label>', {'for' : 'exportDrive'}).text(Messages.settings_backupTitle).appendTo($div);
|
||||
$('<br>').appendTo($div);
|
||||
/* add an export button */
|
||||
var $export = Cryptpad.createButton('export', true, {}, exportFile);
|
||||
@@ -171,7 +166,7 @@ define([
|
||||
|
||||
var createResetDrive = function (obj) {
|
||||
var $div = $('<div>', {'class': 'resetDrive'});
|
||||
var $label = $('<label>', {'for' : 'resetDrive'}).text(Messages.settings_resetTitle).appendTo($div);
|
||||
$('<label>', {'for' : 'resetDrive'}).text(Messages.settings_resetTitle).appendTo($div);
|
||||
$('<br>').appendTo($div);
|
||||
var $button = $('<button>', {'id': 'resetDrive', 'class': 'btn btn-danger'})
|
||||
.text(Messages.settings_reset).appendTo($div);
|
||||
@@ -227,10 +222,60 @@ define([
|
||||
return $div;
|
||||
};
|
||||
|
||||
var createUsageButton = function () {
|
||||
var $div = $('<div>', { 'class': 'pinned-usage' })
|
||||
.text(Messages.settings_usageTitle)
|
||||
.append('<br>');
|
||||
|
||||
Cryptpad.createUsageBar(function (err, $bar) {
|
||||
$div.find('.limit-container').remove();
|
||||
$bar.find('.upgrade').addClass('btn btn-success');
|
||||
$div.append($bar);
|
||||
}, true);
|
||||
return $div;
|
||||
};
|
||||
|
||||
var createLogoutEverywhere = function (obj) {
|
||||
var proxy = obj.proxy;
|
||||
var $div = $('<div>', { 'class': 'logoutEverywhere', });
|
||||
$('<label>', { 'for': 'logoutEverywhere'})
|
||||
.text(Messages.settings_logoutEverywhereTitle).appendTo($div);
|
||||
$('<br>').appendTo($div);
|
||||
var $button = $('<button>', { id: 'logoutEverywhere', 'class': 'btn btn-primary' })
|
||||
.text(Messages.settings_logoutEverywhere)
|
||||
.appendTo($div);
|
||||
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide().appendTo($div);
|
||||
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide().appendTo($div);
|
||||
|
||||
$button.click(function () {
|
||||
var realtime = obj.info.realtime;
|
||||
console.log(realtime);
|
||||
|
||||
Cryptpad.confirm(Messages.settings_logoutEverywhereConfirm, function (yes) {
|
||||
if (!yes) { return; }
|
||||
$spinner.show();
|
||||
$ok.hide();
|
||||
|
||||
var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
|
||||
localStorage.setItem('loginToken', token);
|
||||
proxy.loginToken = token;
|
||||
|
||||
Cryptpad.whenRealtimeSyncs(realtime, function () {
|
||||
$spinner.hide();
|
||||
$ok.show();
|
||||
window.setTimeout(function () {
|
||||
$ok.fadeOut(1500);
|
||||
}, 2500);
|
||||
});
|
||||
});
|
||||
});
|
||||
return $div;
|
||||
};
|
||||
|
||||
var createImportLocalPads = function (obj) {
|
||||
if (!Cryptpad.isLoggedIn()) { return; }
|
||||
var $div = $('<div>', {'class': 'importLocalPads'});
|
||||
var $label = $('<label>', {'for' : 'importLocalPads'}).text(Messages.settings_importTitle).appendTo($div);
|
||||
$('<label>', {'for' : 'importLocalPads'}).text(Messages.settings_importTitle).appendTo($div);
|
||||
$('<br>').appendTo($div);
|
||||
var $button = $('<button>', {'id': 'importLocalPads', 'class': 'btn btn-primary'})
|
||||
.text(Messages.settings_import).appendTo($div);
|
||||
@@ -255,7 +300,7 @@ define([
|
||||
|
||||
var createLanguageSelector = function () {
|
||||
var $div = $('<div>', {'class': 'importLocalPads'});
|
||||
var $label = $('<label>').text(Messages.language).appendTo($div);
|
||||
$('<label>').text(Messages.language).appendTo($div);
|
||||
$('<br>').appendTo($div);
|
||||
var $b = Cryptpad.createLanguageSelector().appendTo($div);
|
||||
$b.find('button').addClass('btn btn-secondary');
|
||||
@@ -267,7 +312,12 @@ define([
|
||||
APP.$container.append(createInfoBlock(obj));
|
||||
APP.$container.append(createDisplayNameInput(obj));
|
||||
APP.$container.append(createLanguageSelector());
|
||||
|
||||
if (Cryptpad.isLoggedIn()) {
|
||||
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));
|
||||
@@ -308,11 +358,11 @@ define([
|
||||
? Cryptpad.getStore().getProxy() : undefined;
|
||||
|
||||
andThen(storeObj);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('storage', function (e) {
|
||||
var key = e.key;
|
||||
if (e.key !== Cryptpad.userHashKey) { return; }
|
||||
var o = e.oldValue;
|
||||
var n = e.newValue;
|
||||
@@ -322,4 +372,3 @@ define([
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<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">
|
||||
<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"
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/toolbar2.js',
|
||||
'json.sortify',
|
||||
'/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',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify, Slide) {
|
||||
var $ = window.jQuery;
|
||||
var saveAs = window.saveAs;
|
||||
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Slide) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = {
|
||||
@@ -32,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;
|
||||
@@ -59,93 +42,55 @@ define([
|
||||
|
||||
var presentMode = Slide.isPresentURL();
|
||||
|
||||
var onConnectError = function (info) {
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
|
||||
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 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: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }},
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
mode: "javascript",
|
||||
readOnly: true
|
||||
});
|
||||
editor.setValue(initialState);
|
||||
var isHistoryMode = false;
|
||||
|
||||
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,53 +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 userData = module.userData = {}; // List of pretty name of all users (mapped with their server ID)
|
||||
var userList; // List of users still connected to the channel (server IDs)
|
||||
var addToUserData = function(data) {
|
||||
var users = module.users;
|
||||
for (var attrname in data) { userData[attrname] = data[attrname]; }
|
||||
|
||||
if (users && users.length) {
|
||||
for (var userKey in userData) {
|
||||
if (users.indexOf(userKey) === -1) {
|
||||
delete userData[userKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(userList && typeof userList.onChange === "function") {
|
||||
userList.onChange(userData);
|
||||
}
|
||||
};
|
||||
|
||||
var textColor;
|
||||
var backColor;
|
||||
|
||||
var myData = {};
|
||||
var myUserName = ''; // My "pretty name"
|
||||
var myID; // My server ID
|
||||
|
||||
var setMyID = function(info) {
|
||||
myID = info.myID || null;
|
||||
myUserName = myID;
|
||||
};
|
||||
|
||||
var config = {
|
||||
//initialState: Messages.codeInitialState,
|
||||
initialState: '{}',
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
channel: secret.channel,
|
||||
@@ -207,16 +114,18 @@ define([
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
readOnly: readOnly,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
setMyID: setMyID,
|
||||
transformFunction: JsonOT.validate,
|
||||
network: Cryptpad.getNetwork()
|
||||
};
|
||||
|
||||
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
|
||||
|
||||
var isDefaultTitle = function () {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
return Cryptpad.isDefaultName(parsed, APP.title);
|
||||
var setHistory = function (bool, update) {
|
||||
isHistoryMode = bool;
|
||||
setEditable(!bool);
|
||||
if (!bool && update) {
|
||||
config.onRemote();
|
||||
}
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
@@ -225,13 +134,13 @@ define([
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: userData,
|
||||
defaultTitle: defaultName,
|
||||
users: UserList.userData,
|
||||
defaultTitle: Title.defaultTitle,
|
||||
slideOptions: slideOptions
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = APP.title;
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
if (textColor) {
|
||||
obj.metadata.color = textColor;
|
||||
@@ -245,11 +154,12 @@ define([
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
editor.save();
|
||||
|
||||
var textValue = canonicalize($textarea.val());
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = stringifyInner(textValue);
|
||||
|
||||
module.patchText(shjson);
|
||||
@@ -260,120 +170,16 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var setName = module.setName = function (newName) {
|
||||
if (typeof(newName) !== 'string') { return; }
|
||||
var myUserNameTemp = newName.trim();
|
||||
if(newName.trim().length > 32) {
|
||||
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
||||
}
|
||||
myUserName = myUserNameTemp;
|
||||
myData[myID] = {
|
||||
name: myUserName,
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
Cryptpad.setAttribute('username', myUserName, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set username");
|
||||
console.error(err);
|
||||
return;
|
||||
var metadataCfg = {
|
||||
slideOptions: function (newOpt) {
|
||||
if (stringify(newOpt) !== stringify(slideOptions)) {
|
||||
$.extend(slideOptions, newOpt);
|
||||
// TODO: manage realtime + cursor in the "options" modal ??
|
||||
Slide.updateOptions();
|
||||
}
|
||||
onLocal();
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
@@ -388,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
|
||||
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: ''
|
||||
};
|
||||
|
||||
@@ -470,10 +232,20 @@ 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>'));
|
||||
var $textarea = $('<textarea>', {'id':'cssPrint'}).css({'width':'100%', 'height':'100px'}).appendTo($p);
|
||||
var $textarea = $('<textarea>', {'id':'cssPrint'}).css({'width':'100%', 'height':'100px'}).appendTo($p)
|
||||
.on('keydown keyup', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
$textarea.val(slideOptionsTmp.style);
|
||||
window.setTimeout(function () { $textarea.focus(); }, 0);
|
||||
|
||||
@@ -494,45 +266,66 @@ define([
|
||||
h = Cryptpad.listenForKeys(todo, todoCancel);
|
||||
|
||||
var $nav = $('<nav>').appendTo($div);
|
||||
var $cancel = $('<button>', {'class': 'cancel'}).text(Messages.cancelButton).appendTo($nav).click(todoCancel);
|
||||
var $ok = $('<button>', {'class': 'ok'}).text(Messages.slideOptionsButton).appendTo($nav).click(todo);
|
||||
$('<button>', {'class': 'cancel'}).text(Messages.cancelButton).appendTo($nav).click(todoCancel);
|
||||
$('<button>', {'class': 'ok'}).text(Messages.settings_save).appendTo($nav).click(todo);
|
||||
|
||||
return $container;
|
||||
};
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
userList = info.userList;
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
var config = {
|
||||
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
|
||||
userData: userData,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
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'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
common: Cryptpad
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: $bar
|
||||
};
|
||||
if (readOnly) {delete config.changeNameID; }
|
||||
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, config);
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
||||
var $userBlock = $bar.find('.' + Toolbar.constants.username);
|
||||
var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername));
|
||||
Title.setToolbar(toolbar);
|
||||
CodeMirror.init(config.onLocal, Title, toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
var editHash;
|
||||
var viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {
|
||||
onLocal: config.onLocal(),
|
||||
onRemote: config.onRemote(),
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var remoteDoc = JSON.parse(val || '{}').content;
|
||||
editor.setValue(remoteDoc || '');
|
||||
editor.save();
|
||||
},
|
||||
$toolbar: $bar
|
||||
};
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
var templateObj = {
|
||||
@@ -545,21 +338,17 @@ 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);
|
||||
|
||||
/* add a rename button */
|
||||
//var $setTitle = Cryptpad.createButton('rename', true, {suggestName: suggestName}, renameCb);
|
||||
//$rightside.append($setTitle);
|
||||
}
|
||||
|
||||
/* add a forget button */
|
||||
var forgetCb = function (err, title) {
|
||||
var forgetCb = function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
};
|
||||
@@ -601,50 +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);
|
||||
var $button = $block.find('.buttonTitle');
|
||||
|
||||
setTheme(lastTheme, $block);
|
||||
|
||||
$block.find('a').click(function (e) {
|
||||
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,
|
||||
@@ -691,7 +436,7 @@ define([
|
||||
};
|
||||
|
||||
configureColors();
|
||||
configureTheme();
|
||||
CodeMirror.configureTheme();
|
||||
|
||||
if (presentMode) {
|
||||
$('#top-bar').hide();
|
||||
@@ -701,27 +446,9 @@ define([
|
||||
if (!window.location.hash || window.location.hash === '#') {
|
||||
Cryptpad.replaceHash(editHash);
|
||||
}
|
||||
|
||||
Cryptpad.onDisplayNameChanged(setName);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
module.users = info.userList.users;
|
||||
|
||||
config.onReady = function (info) {
|
||||
if (module.realtime !== info.realtime) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
@@ -747,141 +474,64 @@ 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();
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
//Cryptpad.log("Your document is ready");
|
||||
|
||||
onLocal(); // push local state to avoid parse errors later.
|
||||
Cryptpad.getLastName(function (err, lastName) {
|
||||
if (err) {
|
||||
console.log("Could not get previous name");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
// Update the toolbar list:
|
||||
// Add the current user in the metadata if he has edit rights
|
||||
if (readOnly) { return; }
|
||||
if (typeof(lastName) === 'string') {
|
||||
setName(lastName);
|
||||
} else {
|
||||
myData[myID] = {
|
||||
name: "",
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
onLocal();
|
||||
module.$userNameButton.click();
|
||||
}
|
||||
if (isNew) {
|
||||
Cryptpad.selectTemplate('slide', info.realtime, Cryptget);
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function (info) {
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
var scroll = editor.getScrollInfo();
|
||||
if (isHistoryMode) { return; }
|
||||
|
||||
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");
|
||||
@@ -892,18 +542,18 @@ define([
|
||||
Slide.update(remoteDoc);
|
||||
|
||||
if (oldDoc !== remoteDoc) {
|
||||
notify();
|
||||
Cryptpad.notify();
|
||||
}
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
config.onAbort = function () {
|
||||
// inform of network disconnect
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
var onConnectionChange = config.onConnectionChange = function (info) {
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
@@ -915,9 +565,9 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
var onError = config.onError = onConnectError;
|
||||
config.onError = onConnectError;
|
||||
|
||||
var realtime = module.realtime = Realtime.start(config);
|
||||
module.realtime = Realtime.start(config);
|
||||
|
||||
editor.on('change', onLocal);
|
||||
|
||||
@@ -927,8 +577,9 @@ define([
|
||||
var interval = 100;
|
||||
|
||||
var second = function (CM) {
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen(CM);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info && info.type === "store") {
|
||||
|
||||
@@ -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,35 +1,7 @@
|
||||
define([
|
||||
'/bower_components/marked/marked.min.js',
|
||||
'/bower_components/diff-dom/diffDOM.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
],function (Marked) {
|
||||
var $ = window.jQuery;
|
||||
var DiffDOM = window.diffDOM;
|
||||
|
||||
var renderer = new Marked.Renderer();
|
||||
|
||||
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
|
||||
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
|
||||
renderer.listitem = function (text, level) {
|
||||
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
|
||||
});
|
||||
|
||||
var truthy = function (x) { return x; };
|
||||
'jquery',
|
||||
'/common/diffMarked.js',
|
||||
],function ($, DiffMd) {
|
||||
|
||||
var Slide = {
|
||||
index: 0,
|
||||
@@ -60,85 +32,12 @@ define([
|
||||
|
||||
var change = function (oldIndex, newIndex) {
|
||||
if (Slide.changeHandlers.length) {
|
||||
Slide.changeHandlers.some(function (f, i) {
|
||||
// HERE
|
||||
Slide.changeHandlers.some(function (f) {
|
||||
f(oldIndex, newIndex, getNumberOfSlides());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
@@ -161,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) {
|
||||
@@ -188,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');
|
||||
@@ -195,7 +91,7 @@ define([
|
||||
change(Slide.lastIndex, Slide.index);
|
||||
};
|
||||
|
||||
var updateOptions = Slide.updateOptions = function () {
|
||||
Slide.updateOptions = function () {
|
||||
draw(Slide.index);
|
||||
};
|
||||
|
||||
@@ -215,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();
|
||||
@@ -224,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();
|
||||
@@ -234,7 +133,7 @@ define([
|
||||
$modal.removeClass('shown');
|
||||
};
|
||||
|
||||
var update = Slide.update = function (content, init) {
|
||||
Slide.update = function (content, init) {
|
||||
if (!Slide.shown && !init) { return; }
|
||||
if (!content) { content = ''; }
|
||||
var old = Slide.content;
|
||||
@@ -246,7 +145,7 @@ define([
|
||||
change(Slide.lastIndex, Slide.index);
|
||||
};
|
||||
|
||||
var left = Slide.left = function () {
|
||||
Slide.left = function () {
|
||||
console.log('left');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
@@ -254,7 +153,7 @@ define([
|
||||
Slide.draw(i);
|
||||
};
|
||||
|
||||
var right = Slide.right = function () {
|
||||
Slide.right = function () {
|
||||
console.log('right');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
@@ -262,7 +161,7 @@ define([
|
||||
Slide.draw(i);
|
||||
};
|
||||
|
||||
var first = Slide.first = function () {
|
||||
Slide.first = function () {
|
||||
console.log('first');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
@@ -270,7 +169,7 @@ define([
|
||||
Slide.draw(i);
|
||||
};
|
||||
|
||||
var last = Slide.last = function () {
|
||||
Slide.last = function () {
|
||||
console.log('end');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
@@ -280,7 +179,7 @@ define([
|
||||
|
||||
var addEvent = function () {
|
||||
var icon_to;
|
||||
$modal.mousemove(function (e) {
|
||||
$modal.mousemove(function () {
|
||||
var $buttons = $modal.find('.button');
|
||||
$buttons.show();
|
||||
if (icon_to) { window.clearTimeout(icon_to); }
|
||||
@@ -288,17 +187,17 @@ define([
|
||||
$buttons.fadeOut();
|
||||
}, 1000);
|
||||
});
|
||||
$modal.find('#button_exit').click(function (e) {
|
||||
$modal.find('#button_exit').click(function () {
|
||||
var ev = $.Event("keyup");
|
||||
ev.which = 27;
|
||||
$modal.trigger(ev);
|
||||
});
|
||||
$modal.find('#button_left').click(function (e) {
|
||||
$modal.find('#button_left').click(function () {
|
||||
var ev = $.Event("keyup");
|
||||
ev.which = 37;
|
||||
$modal.trigger(ev);
|
||||
});
|
||||
$modal.find('#button_right').click(function (e) {
|
||||
$modal.find('#button_right').click(function () {
|
||||
var ev = $.Event("keyup");
|
||||
ev.which = 39;
|
||||
$modal.trigger(ev);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
112
www/user/index.html
Normal file
112
www/user/index.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!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>
|
||||
</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.7.0 (Hodag)</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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,35 +1,28 @@
|
||||
require.config({ paths: {
|
||||
'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify'
|
||||
}});
|
||||
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/toolbar2.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/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/jquery/dist/jquery.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;
|
||||
|
||||
var module = window.APP = { };
|
||||
var $ = module.$ = window.jQuery;
|
||||
var module = window.APP = { $:$ };
|
||||
var Fabric = module.Fabric = window.fabric;
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
var onConnectError = function (info) {
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
var toolbar;
|
||||
@@ -217,35 +210,10 @@ window.canvas = canvas;
|
||||
var initializing = true;
|
||||
|
||||
var $bar = $('#toolbar');
|
||||
var parsedHash = Cryptpad.parsePadUrl(window.location.href);
|
||||
var defaultName = Cryptpad.getDefaultName(parsedHash);
|
||||
var userData = module.userData = {}; // List of pretty name of all users (mapped with their server ID)
|
||||
var userList; // List of users still connected to the channel (server IDs)
|
||||
var addToUserData = function(data) {
|
||||
var users = module.users;
|
||||
for (var attrname in data) { userData[attrname] = data[attrname]; }
|
||||
|
||||
if (users && users.length) {
|
||||
for (var userKey in userData) {
|
||||
if (users.indexOf(userKey) === -1) {
|
||||
delete userData[userKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(userList && typeof userList.onChange === "function") {
|
||||
userList.onChange(userData);
|
||||
}
|
||||
};
|
||||
|
||||
var myData = {};
|
||||
var myUserName = ''; // My "pretty name"
|
||||
var myID; // My server ID
|
||||
|
||||
var setMyID = function(info) {
|
||||
myID = info.myID || null;
|
||||
myUserName = myID;
|
||||
};
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = module.config = {
|
||||
initialState: '{}',
|
||||
@@ -254,7 +222,6 @@ window.canvas = canvas;
|
||||
readOnly: readOnly,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
setMyID: setMyID,
|
||||
transformFunction: JsonOT.transform,
|
||||
};
|
||||
|
||||
@@ -285,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: '!' });
|
||||
|
||||
@@ -335,31 +288,34 @@ window.canvas = canvas;
|
||||
return $color;
|
||||
};
|
||||
|
||||
var editHash;
|
||||
var onInit = config.onInit = function (info) {
|
||||
userList = info.userList;
|
||||
var config = {
|
||||
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
|
||||
userData: userData,
|
||||
readOnly: readOnly,
|
||||
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'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: window,
|
||||
title: {
|
||||
onRename: renameCb,
|
||||
defaultName: defaultName,
|
||||
suggestName: suggestName
|
||||
},
|
||||
common: Cryptpad
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: $bar
|
||||
};
|
||||
if (readOnly) {delete config.changeNameID; }
|
||||
|
||||
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, config);
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
||||
module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername));
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
@@ -375,7 +331,7 @@ window.canvas = canvas;
|
||||
var $export = Cryptpad.createButton('export', true, {}, saveImage);
|
||||
$rightside.append($export);
|
||||
|
||||
var $forget = Cryptpad.createButton('forget', true, {}, function (err, title) {
|
||||
var $forget = Cryptpad.createButton('forget', true, {}, function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
@@ -385,14 +341,11 @@ window.canvas = canvas;
|
||||
makeColorButton($rightside);
|
||||
|
||||
var editHash;
|
||||
var viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
}
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
|
||||
Cryptpad.onDisplayNameChanged(module.setName);
|
||||
};
|
||||
|
||||
// used for debugging, feel free to remove
|
||||
@@ -406,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
|
||||
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;
|
||||
|
||||
@@ -484,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);
|
||||
@@ -493,13 +382,13 @@ window.canvas = canvas;
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: userData,
|
||||
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);
|
||||
@@ -515,29 +404,7 @@ window.canvas = canvas;
|
||||
module.patchText(content);
|
||||
});
|
||||
|
||||
var setName = module.setName = function (newName) {
|
||||
if (typeof(newName) !== 'string') { return; }
|
||||
var myUserNameTemp = newName.trim();
|
||||
if(newName.trim().length > 32) {
|
||||
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
||||
}
|
||||
myUserName = myUserNameTemp;
|
||||
myData[myID] = {
|
||||
name: myUserName,
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
Cryptpad.setAttribute('username', myUserName, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set username");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
onLocal();
|
||||
});
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
config.onReady = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime
|
||||
@@ -552,45 +419,20 @@ window.canvas = canvas;
|
||||
initializing = false;
|
||||
onRemote();
|
||||
|
||||
if (Visible.isSupported()) {
|
||||
Visible.onChange(function (yes) { if (yes) { unnotify(); } });
|
||||
}
|
||||
|
||||
/* TODO: restore palette from metadata.palette */
|
||||
Cryptpad.getLastName(function (err, lastName) {
|
||||
if (err) {
|
||||
console.log("Could not get previous name");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
// Update the toolbar list:
|
||||
// Add the current user in the metadata if he has edit rights
|
||||
if (readOnly) { return; }
|
||||
if (typeof(lastName) === 'string') {
|
||||
setName(lastName);
|
||||
} else {
|
||||
myData[myID] = {
|
||||
name: "",
|
||||
uid: Cryptpad.getUid(),
|
||||
};
|
||||
addToUserData(myData);
|
||||
onLocal();
|
||||
module.$userNameButton.click();
|
||||
}
|
||||
if (isNew) {
|
||||
Cryptpad.selectTemplate('whiteboard', info.realtime, Cryptget);
|
||||
}
|
||||
});
|
||||
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
config.onAbort = function () {
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
// TODO onConnectionStateChange
|
||||
var onConnectionChange = config.onConnectionChange = function (info) {
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
@@ -602,7 +444,7 @@ window.canvas = canvas;
|
||||
}
|
||||
};
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
module.rt = Realtime.start(config);
|
||||
|
||||
canvas.on('mouse:up', onLocal);
|
||||
|
||||
@@ -616,8 +458,9 @@ window.canvas = canvas;
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info) {
|
||||
|
||||
Reference in New Issue
Block a user