Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
commit
b2b1f08d01
customize.dist
storage
www
assert
code
common
login
register
settings
whiteboard
|
@ -12,3 +12,5 @@ www/scratch
|
|||
data
|
||||
npm-debug.log
|
||||
pins/
|
||||
blob/
|
||||
privileged.conf
|
||||
|
|
|
@ -9,4 +9,7 @@ server.js
|
|||
NetFluxWebsocketSrv.js
|
||||
NetFluxWebsocketServer.js
|
||||
WebRTCSrv.js
|
||||
www/common/media-tag.js
|
||||
www/scratch
|
||||
|
||||
www/common/toolbar.js
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"notypeof": true,
|
||||
"shadow": false,
|
||||
"undef": true,
|
||||
"unused": false,
|
||||
"unused": true,
|
||||
"futurehostile":true,
|
||||
"browser": true,
|
||||
"predef": [
|
||||
|
|
|
@ -39,10 +39,10 @@ module.exports = {
|
|||
if you are deploying to production, you'll probably want to remove
|
||||
the ws://* directive, and change '*' to your domain
|
||||
*/
|
||||
"connect-src 'self' ws://* wss://*",
|
||||
"connect-src 'self' ws: wss:",
|
||||
|
||||
// data: is used by codemirror
|
||||
"img-src 'self' data:",
|
||||
"img-src 'self' data: blob:",
|
||||
].join('; '),
|
||||
|
||||
// CKEditor requires significantly more lax content security policy in order to function.
|
||||
|
@ -59,7 +59,7 @@ module.exports = {
|
|||
"child-src 'self' *",
|
||||
|
||||
// see the comment above in the 'contentSecurity' section
|
||||
"connect-src 'self' ws://* wss://*",
|
||||
"connect-src 'self' ws: wss:",
|
||||
|
||||
// (insecure remote) images are included by users of the wysiwyg who embed photos in their pads
|
||||
"img-src *",
|
||||
|
@ -141,6 +141,23 @@ module.exports = {
|
|||
*/
|
||||
filePath: './datastore/',
|
||||
|
||||
/* CryptPad allows logged in users to request that particular documents be
|
||||
* stored by the server indefinitely. This is called 'pinning'.
|
||||
* Pin requests are stored in a pin-store. The location of this store is
|
||||
* defined here.
|
||||
*/
|
||||
pinPath: './pins',
|
||||
|
||||
/* CryptPad allows logged in users to upload encrypted files. Files/blobs
|
||||
* are stored in a 'blob-store'. Set its location here.
|
||||
*/
|
||||
blobPath: './blob',
|
||||
|
||||
/* CryptPad stores incomplete blobs in a 'staging' area until they are
|
||||
* fully uploaded. Set its location here.
|
||||
*/
|
||||
blobStagingPath: './blobstage',
|
||||
|
||||
/* Cryptpad's file storage adaptor closes unused files after a configurale
|
||||
* number of milliseconds (default 30000 (30 seconds))
|
||||
*/
|
||||
|
@ -163,6 +180,31 @@ module.exports = {
|
|||
*/
|
||||
suppressRPCErrors: false,
|
||||
|
||||
|
||||
/* WARNING: EXPERIMENTAL
|
||||
*
|
||||
* CryptPad features experimental support for encrypted file upload.
|
||||
* Our encryption format is still liable to change. As such, we do not
|
||||
* guarantee that files uploaded now will be supported in the future
|
||||
*/
|
||||
|
||||
/* Setting this value to anything other than true will cause file upload
|
||||
* attempts to be rejected outright.
|
||||
*/
|
||||
enableUploads: true,
|
||||
|
||||
/* If you have enabled file upload, you have the option of restricting it
|
||||
* to a list of users identified by their public keys. If this value is set
|
||||
* to true, your server will query a file (cryptpad/privileged.conf) when
|
||||
* users connect via RPC. Only users whose public keys can be found within
|
||||
* the file will be allowed to upload.
|
||||
*
|
||||
* privileged.conf uses '#' for line comments, and splits keys by newline.
|
||||
* This is a temporary measure until a better quota system is in place.
|
||||
* registered users' public keys can be found on the settings page.
|
||||
*/
|
||||
restrictUploads: true,
|
||||
|
||||
/* it is recommended that you serve cryptpad over https
|
||||
* the filepaths below are used to configure your certificates
|
||||
*/
|
||||
|
|
|
@ -106,7 +106,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>
|
||||
|
@ -114,7 +114,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.5.0 (Fenrir)</div>
|
||||
<div class="version-footer">CryptPad v1.6.0 (Grootslang)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -37,5 +37,20 @@ define(function() {
|
|||
|
||||
config.enableHistory = true;
|
||||
|
||||
//config.enablePinLimit = true;
|
||||
//config.pinLimit = 1000;
|
||||
|
||||
/* user passwords are hashed with scrypt, and salted with their username.
|
||||
this value will be appended to the username, causing the resulting hash
|
||||
to differ from other CryptPad instances if customized. This makes it
|
||||
such that anyone who wants to bruteforce common credentials must do so
|
||||
again on each CryptPad instance that they wish to attack.
|
||||
|
||||
WARNING: this should only be set when your CryptPad instance is first
|
||||
created. Changing it at a later time will break logins for all existing
|
||||
users.
|
||||
*/
|
||||
config.loginSalt = '';
|
||||
|
||||
return config;
|
||||
});
|
||||
|
|
|
@ -103,7 +103,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>
|
||||
|
@ -111,7 +111,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.5.0 (Fenrir)</div>
|
||||
<div class="version-footer">CryptPad v1.6.0 (Grootslang)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -225,7 +225,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>
|
||||
|
@ -233,7 +233,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.5.0 (Fenrir)</div>
|
||||
<div class="version-footer">CryptPad v1.6.0 (Grootslang)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -388,6 +388,11 @@
|
|||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
@media screen and (max-height: 600px) {
|
||||
.cp #loadingTip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.cp #loadingTip span {
|
||||
background-color: #302B28;
|
||||
color: #fafafa;
|
||||
|
|
|
@ -4,7 +4,7 @@ define([
|
|||
'/common/cryptpad-common.js'
|
||||
], function ($, Config, Cryptpad) {
|
||||
|
||||
var APP = window.APP = {
|
||||
window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
};
|
||||
|
||||
|
@ -118,68 +118,70 @@ 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 && !proxy.login_name) {
|
||||
proxy.login_name = result.userName;
|
||||
// successful validation and user already exists
|
||||
// set user hash in localStorage and redirect to drive
|
||||
if (proxy && !proxy.login_name) {
|
||||
proxy.login_name = result.userName;
|
||||
}
|
||||
|
||||
proxy.edPrivate = result.edPrivate;
|
||||
proxy.edPublic = result.edPublic;
|
||||
|
||||
Cryptpad.whenRealtimeSyncs(result.realtime, function () {
|
||||
Cryptpad.login(result.userHash, result.userName, function () {
|
||||
document.location.href = '/drive/';
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
proxy.edPrivate = result.edPrivate;
|
||||
proxy.edPublic = result.edPublic;
|
||||
|
||||
Cryptpad.whenRealtimeSyncs(result.realtime, function () {
|
||||
Cryptpad.login(result.userHash, result.userName, function () {
|
||||
document.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);
|
||||
}
|
||||
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);
|
||||
});
|
||||
/* End Log in UI */
|
||||
|
||||
var addButtonHandlers = function () {
|
||||
$('button.register').click(function (e) {
|
||||
$('button.register').click(function () {
|
||||
var username = $('#name').val();
|
||||
var passwd = $('#password').val();
|
||||
var remember = $('#rememberme').is(':checked');
|
||||
sessionStorage.login_user = username;
|
||||
sessionStorage.login_pass = passwd;
|
||||
document.location.href = '/register/';
|
||||
});
|
||||
$('button.gotodrive').click(function (e) {
|
||||
$('button.gotodrive').click(function () {
|
||||
document.location.href = '/drive/';
|
||||
});
|
||||
};
|
||||
|
|
|
@ -112,9 +112,7 @@ define(req, function($, Default, Language) {
|
|||
|
||||
if (!selector.length) { return; }
|
||||
|
||||
var $button = $(selector).find('button .buttonTitle');
|
||||
// Select the current language in the list
|
||||
var option = $(selector).find('[data-value="' + language + '"]');
|
||||
selector.setValue(language || 'English');
|
||||
|
||||
// Listen for language change
|
||||
|
@ -137,12 +135,12 @@ define(req, function($, Default, Language) {
|
|||
var key = $el.data('localization-append');
|
||||
$el.append(messages[key]);
|
||||
};
|
||||
var translateTitle = function (i, e) {
|
||||
var translateTitle = function () {
|
||||
var $el = $(this);
|
||||
var key = $el.data('localization-title');
|
||||
$el.attr('title', messages[key]);
|
||||
};
|
||||
var translatePlaceholder = function (i, e) {
|
||||
var translatePlaceholder = function () {
|
||||
var $el = $(this);
|
||||
var key = $el.data('localization-placeholder');
|
||||
$el.attr('placeholder', messages[key]);
|
||||
|
|
|
@ -124,7 +124,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>
|
||||
|
@ -132,7 +132,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.5.0 (Fenrir)</div>
|
||||
<div class="version-footer">CryptPad v1.6.0 (Grootslang)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
// create an invisible iframe with a given source
|
||||
// append it to a parent element
|
||||
// execute a callback when it has loaded
|
||||
var create = Frame.create = function (parent, src, onload, timeout) {
|
||||
Frame.create = function (parent, src, onload, timeout) {
|
||||
var iframe = document.createElement('iframe');
|
||||
|
||||
timeout = timeout || 10000;
|
||||
|
@ -34,7 +34,7 @@
|
|||
|
||||
/* given an iframe with an rpc script loaded, create a frame object
|
||||
with an asynchronous 'send' method */
|
||||
var open = Frame.open = function (e, A, timeout) {
|
||||
Frame.open = function (e, A, timeout) {
|
||||
var win = e.contentWindow;
|
||||
|
||||
var frame = {};
|
||||
|
@ -44,7 +44,7 @@
|
|||
|
||||
timeout = timeout || 5000;
|
||||
|
||||
var accepts = frame.accepts = function (o) {
|
||||
frame.accepts = function (o) {
|
||||
return A.some(function (e) {
|
||||
switch (typeof(e)) {
|
||||
case 'string': return e === o;
|
||||
|
@ -55,7 +55,7 @@
|
|||
|
||||
var changeHandlers = frame.changeHandlers = [];
|
||||
|
||||
var change = frame.change = function (f) {
|
||||
frame.change = function (f) {
|
||||
if (typeof(f) !== 'function') {
|
||||
throw new Error('[Frame.change] expected callback');
|
||||
}
|
||||
|
@ -94,7 +94,7 @@
|
|||
};
|
||||
window.addEventListener('message', _listener);
|
||||
|
||||
var close = frame.close = function () {
|
||||
frame.close = function () {
|
||||
window.removeEventListener('message', _listener);
|
||||
};
|
||||
|
||||
|
@ -130,31 +130,31 @@
|
|||
win.postMessage(JSON.stringify(req), '*');
|
||||
};
|
||||
|
||||
var set = frame.set = function (key, val, cb) {
|
||||
frame.set = function (key, val, cb) {
|
||||
send('set', key, val, cb);
|
||||
};
|
||||
|
||||
var batchset = frame.setBatch = function (map, cb) {
|
||||
frame.setBatch = function (map, cb) {
|
||||
send('batchset', void 0, map, cb);
|
||||
};
|
||||
|
||||
var get = frame.get = function (key, cb) {
|
||||
frame.get = function (key, cb) {
|
||||
send('get', key, void 0, cb);
|
||||
};
|
||||
|
||||
var batchget = frame.getBatch = function (keys, cb) {
|
||||
frame.getBatch = function (keys, cb) {
|
||||
send('batchget', void 0, keys, cb);
|
||||
};
|
||||
|
||||
var remove = frame.remove = function (key, cb) {
|
||||
frame.remove = function (key, cb) {
|
||||
send('remove', key, void 0, cb);
|
||||
};
|
||||
|
||||
var batchremove = frame.removeBatch = function (keys, cb) {
|
||||
frame.removeBatch = function (keys, cb) {
|
||||
send('batchremove', void 0, keys, cb);
|
||||
};
|
||||
|
||||
var keys = frame.keys = function (cb) {
|
||||
frame.keys = function (cb) {
|
||||
send('keys', void 0, void 0, cb);
|
||||
};
|
||||
|
||||
|
@ -164,7 +164,7 @@
|
|||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = Frame;
|
||||
} else if (typeof(define) === 'function' && define.amd) {
|
||||
define(['jquery'], function ($) {
|
||||
define(['jquery'], function () {
|
||||
return Frame;
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -39,7 +39,7 @@ define([
|
|||
return !keys.some(function (k) { return data[k] !== null; });
|
||||
};
|
||||
|
||||
Frame.create(document.body, domain + path, function (err, iframe, loadEvent) {
|
||||
Frame.create(document.body, domain + path, function (err, iframe) {
|
||||
if (handleErr(err)) { return; }
|
||||
console.log("Created iframe");
|
||||
|
||||
|
@ -50,7 +50,7 @@ define([
|
|||
|
||||
[function (i) { // test #1
|
||||
var pew = randInt();
|
||||
frame.set('pew', pew, function (err, data) {
|
||||
frame.set('pew', pew, function (err) {
|
||||
if (handleErr(err)) { return; }
|
||||
frame.get('pew', function (err, num) {
|
||||
if (handleErr(err)) { return; }
|
||||
|
@ -76,9 +76,9 @@ define([
|
|||
|
||||
var keys = Object.keys(map);
|
||||
|
||||
frame.setBatch(map, function (err, data) {
|
||||
frame.setBatch(map, function (err) {
|
||||
if (handleErr(err)) { return; }
|
||||
frame.getBatch(keys, function (err, data) {
|
||||
frame.getBatch(keys, function (err) {
|
||||
if (handleErr(err)) { return; }
|
||||
frame.removeBatch(Object.keys(map), function (err) {
|
||||
if (handleErr(err)) { return; }
|
||||
|
|
|
@ -31,7 +31,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>
|
||||
|
@ -39,5 +39,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.5.0 (Fenrir)</div>
|
||||
<div class="version-footer">CryptPad v1.6.0 (Grootslang)</div>
|
||||
</footer>
|
||||
|
|
|
@ -36,6 +36,9 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
@media screen and (max-height: @media-medium-screen) {
|
||||
display: none;
|
||||
}
|
||||
span {
|
||||
background-color: @bg-loading;
|
||||
color: @color-loading;
|
||||
|
|
|
@ -42,12 +42,13 @@
|
|||
}
|
||||
|
||||
button {
|
||||
&#shareButton {
|
||||
&#shareButton, &.buttonSuccess {
|
||||
// Bootstrap 4 colors
|
||||
color: #fff;
|
||||
background: @toolbar-green;
|
||||
border-color: @toolbar-green;
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: #449d44;
|
||||
border: 1px solid #419641;
|
||||
}
|
||||
|
@ -58,12 +59,13 @@
|
|||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
&#newdoc {
|
||||
&#newdoc, &.buttonPrimary {
|
||||
// Bootstrap 4 colors
|
||||
color: #fff;
|
||||
background: #0275d8;
|
||||
border-color: #0275d8;
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: #025aa5;
|
||||
border: 1px solid #01549b;
|
||||
}
|
||||
|
@ -77,26 +79,84 @@
|
|||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cryptpad-lag {
|
||||
box-sizing: content-box;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
margin: 3px 0;
|
||||
div {
|
||||
margin: auto;
|
||||
// Bootstrap 4 colors (btn-secondary)
|
||||
border: 1px solid transparent;
|
||||
border-radius: .25rem;
|
||||
color: #292b2c;
|
||||
background-color: #fff;
|
||||
border-color: #ccc;
|
||||
&:hover {
|
||||
color: #292b2c;
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
}
|
||||
|
||||
.clag () {
|
||||
background: transparent;
|
||||
}
|
||||
button.upgrade {
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.cryptpad-drive-limit {
|
||||
display: inline-block;
|
||||
height: 26px;
|
||||
width: 200px;
|
||||
margin: 2px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #999;
|
||||
background: white;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
.usage {
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
background: blue;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index:1;
|
||||
&.normal {
|
||||
background: @toolbar-green;
|
||||
}
|
||||
&.warning {
|
||||
background: orange;
|
||||
}
|
||||
&.above {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
.usageText {
|
||||
position: relative;
|
||||
color: black;
|
||||
text-shadow: 1px 0 2px white, 0 1px 2px white, -1px 0 2px white, 0 -1px 2px white;
|
||||
z-index: 2;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.cryptpad-limit {
|
||||
box-sizing: border-box;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
margin: 0px;
|
||||
margin-right: 3px;
|
||||
vertical-align: middle;
|
||||
span {
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
margin: auto;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.clag () {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#newLag {
|
||||
.cryptpad-lag {
|
||||
height: 20px;
|
||||
width: 23px;
|
||||
background: transparent;
|
||||
|
@ -179,17 +239,6 @@
|
|||
margin-right: 2px;
|
||||
}
|
||||
|
||||
button {
|
||||
color: #000;
|
||||
background-color: inherit;
|
||||
background-image: linear-gradient(to bottom,#fff,#e4e4e4);
|
||||
border: 1px solid #A6A6A6;
|
||||
border-bottom-color: #979797;
|
||||
border-radius: 3px;
|
||||
&:hover {
|
||||
background-image:linear-gradient(to bottom,#f2f2f2,#ccc);
|
||||
}
|
||||
}
|
||||
.cryptpad-state {
|
||||
line-height: 32px; /* equivalent to 26px + 2*2px margin used for buttons */
|
||||
}
|
||||
|
@ -378,9 +427,8 @@
|
|||
.cryptpad-user {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
span:not(.cryptpad-lag) {
|
||||
:not(.cryptpad-lag) span {
|
||||
vertical-align: top;
|
||||
//display: inline-block;
|
||||
}
|
||||
button {
|
||||
span.fa {
|
||||
|
@ -392,11 +440,9 @@
|
|||
.cryptpad-toolbar-leftside {
|
||||
float: left;
|
||||
margin-bottom: -1px;
|
||||
.cryptpad-user-list {
|
||||
//float: right;
|
||||
.cryptpad-dropdown-users {
|
||||
pre {
|
||||
white-space: pre;
|
||||
margin: 0;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
}
|
||||
button {
|
||||
|
@ -413,14 +459,20 @@
|
|||
display: none;
|
||||
text-align: center;
|
||||
.next {
|
||||
float: right;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 20px;
|
||||
}
|
||||
.previous {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 20px;
|
||||
}
|
||||
.goto {
|
||||
display: inline-block;
|
||||
input { width: 50px; }
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
input { width: 75px; }
|
||||
}
|
||||
.gotoInput {
|
||||
vertical-align: middle;
|
||||
|
@ -434,7 +486,7 @@
|
|||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
.cryptpad-spinner {
|
||||
.cryptpad-spinner > span {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin: 8px;
|
||||
|
|
|
@ -107,7 +107,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>
|
||||
|
@ -115,7 +115,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.5.0 (Fenrir)</div>
|
||||
<div class="version-footer">CryptPad v1.6.0 (Grootslang)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -117,51 +117,120 @@
|
|||
.cryptpad-toolbar a {
|
||||
float: right;
|
||||
}
|
||||
.cryptpad-toolbar button#shareButton {
|
||||
.cryptpad-toolbar button {
|
||||
border: 1px solid transparent;
|
||||
border-radius: .25rem;
|
||||
color: #292b2c;
|
||||
background-color: #fff;
|
||||
border-color: #ccc;
|
||||
}
|
||||
.cryptpad-toolbar button#shareButton,
|
||||
.cryptpad-toolbar button.buttonSuccess {
|
||||
color: #fff;
|
||||
background: #5cb85c;
|
||||
border-color: #5cb85c;
|
||||
}
|
||||
.cryptpad-toolbar button#shareButton:hover {
|
||||
.cryptpad-toolbar button#shareButton:hover,
|
||||
.cryptpad-toolbar button.buttonSuccess:hover {
|
||||
color: #fff;
|
||||
background: #449d44;
|
||||
border: 1px solid #419641;
|
||||
}
|
||||
.cryptpad-toolbar button#shareButton span {
|
||||
.cryptpad-toolbar button#shareButton span,
|
||||
.cryptpad-toolbar button.buttonSuccess span {
|
||||
color: #fff;
|
||||
}
|
||||
.cryptpad-toolbar button#shareButton .large {
|
||||
.cryptpad-toolbar button#shareButton .large,
|
||||
.cryptpad-toolbar button.buttonSuccess .large {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.cryptpad-toolbar button#newdoc {
|
||||
.cryptpad-toolbar button#newdoc,
|
||||
.cryptpad-toolbar button.buttonPrimary {
|
||||
color: #fff;
|
||||
background: #0275d8;
|
||||
border-color: #0275d8;
|
||||
}
|
||||
.cryptpad-toolbar button#newdoc:hover {
|
||||
.cryptpad-toolbar button#newdoc:hover,
|
||||
.cryptpad-toolbar button.buttonPrimary:hover {
|
||||
color: #fff;
|
||||
background: #025aa5;
|
||||
border: 1px solid #01549b;
|
||||
}
|
||||
.cryptpad-toolbar button#newdoc span {
|
||||
.cryptpad-toolbar button#newdoc span,
|
||||
.cryptpad-toolbar button.buttonPrimary span {
|
||||
color: #fff;
|
||||
}
|
||||
.cryptpad-toolbar button#newdoc .large {
|
||||
.cryptpad-toolbar button#newdoc .large,
|
||||
.cryptpad-toolbar button.buttonPrimary .large {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.cryptpad-toolbar button.hidden {
|
||||
display: none;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-lag {
|
||||
box-sizing: content-box;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
.cryptpad-toolbar button:hover {
|
||||
color: #292b2c;
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
.cryptpad-toolbar button.upgrade {
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-drive-limit {
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
margin: 3px 0;
|
||||
height: 26px;
|
||||
width: 200px;
|
||||
margin: 2px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #999;
|
||||
background: white;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-lag div {
|
||||
.cryptpad-toolbar .cryptpad-drive-limit .usage {
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
background: blue;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-drive-limit .usage.normal {
|
||||
background: #5cb85c;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-drive-limit .usage.warning {
|
||||
background: orange;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-drive-limit .usage.above {
|
||||
background: red;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-drive-limit .usageText {
|
||||
position: relative;
|
||||
color: black;
|
||||
text-shadow: 1px 0 2px white, 0 1px 2px white, -1px 0 2px white, 0 -1px 2px white;
|
||||
z-index: 2;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-limit {
|
||||
box-sizing: border-box;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
margin: 0px;
|
||||
margin-right: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-limit span {
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
margin: auto;
|
||||
font-size: 20px;
|
||||
}
|
||||
.cryptpad-toolbar #newLag {
|
||||
.cryptpad-toolbar .cryptpad-lag {
|
||||
height: 20px;
|
||||
width: 23px;
|
||||
background: transparent;
|
||||
|
@ -171,7 +240,7 @@
|
|||
vertical-align: top;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.cryptpad-toolbar #newLag span {
|
||||
.cryptpad-toolbar .cryptpad-lag span {
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
margin: 0;
|
||||
|
@ -182,50 +251,50 @@
|
|||
border: 1px solid black;
|
||||
transition: background 1s, border 1s;
|
||||
}
|
||||
.cryptpad-toolbar #newLag span:last-child {
|
||||
.cryptpad-toolbar .cryptpad-lag span:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.cryptpad-toolbar #newLag span.bar1 {
|
||||
.cryptpad-toolbar .cryptpad-lag span.bar1 {
|
||||
height: 5px;
|
||||
}
|
||||
.cryptpad-toolbar #newLag span.bar2 {
|
||||
.cryptpad-toolbar .cryptpad-lag span.bar2 {
|
||||
height: 10px;
|
||||
}
|
||||
.cryptpad-toolbar #newLag span.bar3 {
|
||||
.cryptpad-toolbar .cryptpad-lag span.bar3 {
|
||||
height: 15px;
|
||||
}
|
||||
.cryptpad-toolbar #newLag span.bar4 {
|
||||
.cryptpad-toolbar .cryptpad-lag span.bar4 {
|
||||
height: 20px;
|
||||
}
|
||||
.cryptpad-toolbar #newLag.lag0 span {
|
||||
.cryptpad-toolbar .cryptpad-lag.lag0 span {
|
||||
background: transparent;
|
||||
border-color: red;
|
||||
}
|
||||
.cryptpad-toolbar #newLag.lag1 .bar2,
|
||||
.cryptpad-toolbar #newLag.lag1 .bar3,
|
||||
.cryptpad-toolbar #newLag.lag1 .bar4 {
|
||||
.cryptpad-toolbar .cryptpad-lag.lag1 .bar2,
|
||||
.cryptpad-toolbar .cryptpad-lag.lag1 .bar3,
|
||||
.cryptpad-toolbar .cryptpad-lag.lag1 .bar4 {
|
||||
background: transparent;
|
||||
}
|
||||
.cryptpad-toolbar #newLag.lag1 span {
|
||||
.cryptpad-toolbar .cryptpad-lag.lag1 span {
|
||||
background-color: orange;
|
||||
border-color: orange;
|
||||
}
|
||||
.cryptpad-toolbar #newLag.lag2 .bar3,
|
||||
.cryptpad-toolbar #newLag.lag2 .bar4 {
|
||||
.cryptpad-toolbar .cryptpad-lag.lag2 .bar3,
|
||||
.cryptpad-toolbar .cryptpad-lag.lag2 .bar4 {
|
||||
background: transparent;
|
||||
}
|
||||
.cryptpad-toolbar #newLag.lag2 span {
|
||||
.cryptpad-toolbar .cryptpad-lag.lag2 span {
|
||||
background-color: orange;
|
||||
border-color: orange;
|
||||
}
|
||||
.cryptpad-toolbar #newLag.lag3 .bar4 {
|
||||
.cryptpad-toolbar .cryptpad-lag.lag3 .bar4 {
|
||||
background: transparent;
|
||||
}
|
||||
.cryptpad-toolbar #newLag.lag3 span {
|
||||
.cryptpad-toolbar .cryptpad-lag.lag3 span {
|
||||
background-color: #5cb85c;
|
||||
border-color: #5cb85c;
|
||||
}
|
||||
.cryptpad-toolbar #newLag.lag4 span {
|
||||
.cryptpad-toolbar .cryptpad-lag.lag4 span {
|
||||
background-color: #5cb85c;
|
||||
border-color: #5cb85c;
|
||||
}
|
||||
|
@ -250,17 +319,6 @@
|
|||
margin-top: -3px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.cryptpad-toolbar button {
|
||||
color: #000;
|
||||
background-color: inherit;
|
||||
background-image: linear-gradient(to bottom, #fff, #e4e4e4);
|
||||
border: 1px solid #A6A6A6;
|
||||
border-bottom-color: #979797;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.cryptpad-toolbar button:hover {
|
||||
background-image: linear-gradient(to bottom, #f2f2f2, #ccc);
|
||||
}
|
||||
.cryptpad-toolbar .cryptpad-state {
|
||||
line-height: 32px;
|
||||
/* equivalent to 26px + 2*2px margin used for buttons */
|
||||
|
@ -449,7 +507,7 @@
|
|||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
.cryptpad-toolbar-top .cryptpad-user span:not(.cryptpad-lag) {
|
||||
.cryptpad-toolbar-top .cryptpad-user :not(.cryptpad-lag) span {
|
||||
vertical-align: top;
|
||||
}
|
||||
.cryptpad-toolbar-top .cryptpad-user button span.fa {
|
||||
|
@ -459,9 +517,8 @@
|
|||
float: left;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.cryptpad-toolbar-leftside .cryptpad-user-list pre {
|
||||
white-space: pre;
|
||||
margin: 0;
|
||||
.cryptpad-toolbar-leftside .cryptpad-dropdown-users pre {
|
||||
margin: 5px 0px;
|
||||
}
|
||||
.cryptpad-toolbar-leftside button {
|
||||
margin: 2px 4px 2px 0px;
|
||||
|
@ -477,16 +534,22 @@
|
|||
text-align: center;
|
||||
}
|
||||
.cryptpad-toolbar-history .next {
|
||||
float: right;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 20px;
|
||||
}
|
||||
.cryptpad-toolbar-history .previous {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 20px;
|
||||
}
|
||||
.cryptpad-toolbar-history .goto {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
.cryptpad-toolbar-history .goto input {
|
||||
width: 50px;
|
||||
width: 75px;
|
||||
}
|
||||
.cryptpad-toolbar-history .gotoInput {
|
||||
vertical-align: middle;
|
||||
|
@ -497,7 +560,7 @@
|
|||
padding: 3px 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.cryptpad-spinner {
|
||||
.cryptpad-spinner > span {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin: 8px;
|
||||
|
|
|
@ -11,6 +11,8 @@ define(function () {
|
|||
out.type.slide = 'Présentation';
|
||||
out.type.drive = 'Drive';
|
||||
out.type.whiteboard = "Tableau Blanc";
|
||||
out.type.file = "Fichier";
|
||||
out.type.media = "Média";
|
||||
|
||||
out.button_newpad = 'Nouveau document texte';
|
||||
out.button_newcode = 'Nouvelle page de code';
|
||||
|
@ -49,10 +51,22 @@ define(function () {
|
|||
|
||||
out.language = "Langue";
|
||||
|
||||
out.upgrade = "Améliorer";
|
||||
out.upgradeTitle = "Améliorer votre compte pour augmenter la limite de stockage";
|
||||
out.MB = "Mo";
|
||||
|
||||
out.greenLight = "Tout fonctionne bien";
|
||||
out.orangeLight = "Votre connexion est lente, ce qui réduit la qualité de l'éditeur";
|
||||
out.redLight = "Vous êtes déconnectés de la session";
|
||||
|
||||
out.pinLimitReached = "Vous avez atteint votre limite de stockage";
|
||||
out.pinLimitReachedAlert = "Vous avez atteint votre limite de stockage. Les nouveaux pads ne seront pas enregistrés dans votre CrypDrive.<br>" +
|
||||
"Pour résoudre ce problème, vous pouvez soit supprimer des pads de votre CryptDrive (y compris la corbeille), soit vous abonner à une offre premium pour augmenter la limite maximale.";
|
||||
out.pinLimitNotPinned = "Vous avez atteint votre limite de stockage.<br>"+
|
||||
"Ce pad n'est pas enregistré dans votre CryptDrive.";
|
||||
out.pinLimitDrive = out.pinLimitReached+ ".<br>" +
|
||||
"Vous ne pouvez pas créer de nouveaux pads.";
|
||||
|
||||
out.importButtonTitle = 'Importer un pad depuis un fichier local';
|
||||
|
||||
out.exportButtonTitle = 'Exporter ce pad vers un fichier local';
|
||||
|
@ -93,6 +107,7 @@ define(function () {
|
|||
out.printDate = "Afficher la date";
|
||||
out.printTitle = "Afficher le titre du pad";
|
||||
out.printCSS = "Personnaliser l'apparence (CSS):";
|
||||
out.printTransition = "Activer les animations de transition";
|
||||
|
||||
out.slideOptionsTitle = "Personnaliser la présentation";
|
||||
out.slideOptionsButton = "Enregistrer (Entrée)";
|
||||
|
@ -125,6 +140,7 @@ define(function () {
|
|||
out.history_restoreTitle = "Restaurer la version du document sélectionnée";
|
||||
out.history_restorePrompt = "Êtes-vous sûr de vouloir remplacer la version actuelle du document par la version affichée ?";
|
||||
out.history_restoreDone = "Document restauré";
|
||||
out.history_version = "Version :";
|
||||
|
||||
// Polls
|
||||
|
||||
|
@ -313,6 +329,10 @@ define(function () {
|
|||
out.settings_pinningError = "Un problème est survenu";
|
||||
out.settings_usageAmount = "Vos pads épinglés occupent {0} Mo";
|
||||
|
||||
out.settings_logoutEverywhereTitle = "Se déconnecter partout";
|
||||
out.settings_logoutEverywhere = "Se déconnecter de toutes les autres sessions.";
|
||||
out.settings_logoutEverywhereConfirm = "Êtes-vous sûr ? Vous devrez vous reconnecter sur tous vos autres appareils.";
|
||||
|
||||
// index.html
|
||||
|
||||
//about.html
|
||||
|
|
|
@ -11,6 +11,8 @@ define(function () {
|
|||
out.type.slide = 'Presentation';
|
||||
out.type.drive = 'Drive';
|
||||
out.type.whiteboard = 'Whiteboard';
|
||||
out.type.file = 'File';
|
||||
out.type.media = 'Media';
|
||||
|
||||
out.button_newpad = 'New Rich Text pad';
|
||||
out.button_newcode = 'New Code pad';
|
||||
|
@ -51,10 +53,22 @@ define(function () {
|
|||
|
||||
out.language = "Language";
|
||||
|
||||
out.upgrade = "Upgrade";
|
||||
out.upgradeTitle = "Upgrade your account to increase the storage limit";
|
||||
out.MB = "MB";
|
||||
|
||||
out.greenLight = "Everything is working fine";
|
||||
out.orangeLight = "Your slow connection may impact your experience";
|
||||
out.redLight = "You are disconnected from the session";
|
||||
|
||||
out.pinLimitReached = "You've reached your storage limit";
|
||||
out.pinLimitReachedAlert = "You've reached your storage limit. New pads won't be stored in your CryptDrive.<br>" +
|
||||
"To fix this problem, you can either remove pads from your CryptDrive (including the trash) or subscribe to a premium offer to increase your limit.";
|
||||
out.pinLimitNotPinned = "You've reached your storage limit.<br>"+
|
||||
"This pad is not stored in your CryptDrive.";
|
||||
out.pinLimitDrive = "You've reached your storage limit.<br>" +
|
||||
"You can't create new pads.";
|
||||
|
||||
out.importButtonTitle = 'Import a pad from a local file';
|
||||
|
||||
out.exportButtonTitle = 'Export this pad to a local file';
|
||||
|
@ -95,6 +109,7 @@ define(function () {
|
|||
out.printDate = "Display the date";
|
||||
out.printTitle = "Display the pad title";
|
||||
out.printCSS = "Custom style rules (CSS):";
|
||||
out.printTransition = "Enable transition animations";
|
||||
|
||||
out.slideOptionsTitle = "Customize your slides";
|
||||
out.slideOptionsButton = "Save (enter)";
|
||||
|
@ -127,6 +142,7 @@ define(function () {
|
|||
out.history_restoreTitle = "Restore the selected version of the document";
|
||||
out.history_restorePrompt = "Are you sure you want to replace the current version of the document by the displayed one?";
|
||||
out.history_restoreDone = "Document restored";
|
||||
out.history_version = "Version:";
|
||||
|
||||
// Polls
|
||||
|
||||
|
@ -318,6 +334,10 @@ define(function () {
|
|||
out.settings_pinningError = "Something went wrong";
|
||||
out.settings_usageAmount = "Your pinned pads occupy {0}MB";
|
||||
|
||||
out.settings_logoutEverywhereTitle = "Log out everywhere";
|
||||
out.settings_logoutEverywhere = "Log out of all other web sessions";
|
||||
out.settings_logoutEverywhereConfirm = "Are you sure? You will need to log in with all your devices.";
|
||||
|
||||
// index.html
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ server {
|
|||
set $scriptSrc "'self'";
|
||||
set $connectSrc "'self' wss://cryptpad.fr wss://api.cryptpad.fr";
|
||||
set $fontSrc "'self'";
|
||||
set $imgSrc "data: *";
|
||||
set $imgSrc "data: * blob:";
|
||||
set $frameSrc "'self' beta.cryptpad.fr";
|
||||
|
||||
if ($uri = /pad/inner.html) {
|
||||
|
@ -65,8 +65,12 @@ server {
|
|||
rewrite ^.*$ /customize/api/config break;
|
||||
}
|
||||
|
||||
location ^~ /blob/ {
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
## TODO fix in the code so that we don't need this
|
||||
location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard)$ {
|
||||
location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media)$ {
|
||||
rewrite ^(.*)$ $1/ redirect;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "cryptpad",
|
||||
"description": "realtime collaborative visual editor with zero knowlege server",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"dependencies": {
|
||||
"chainpad-server": "^1.0.1",
|
||||
"express": "~4.10.1",
|
||||
|
|
|
@ -54,6 +54,9 @@ These settings can be found in your configuration file in the `contentSecurity`
|
|||
|
||||
## Maintenance
|
||||
|
||||
Before upgrading your CryptPad instance to the latest version, we recommend that you check what has changed since your last update.
|
||||
You can do so by checking which version you have (see package.json), and comparing it against newer [release notes](https://github.com/xwiki-labs/cryptpad/releases).
|
||||
|
||||
To get access to the most recent codebase:
|
||||
|
||||
```
|
||||
|
@ -70,9 +73,12 @@ bower update;
|
|||
# serverside dependencies
|
||||
npm update;
|
||||
```
|
||||
## Deleting all data and resetting Cryptpad
|
||||
|
||||
|
||||
To reset your instance of Cryptpad and remove all the data that is being stored:
|
||||
|
||||
**WARNING: This will reset your Cryptpad instance and remove all data**
|
||||
```
|
||||
# change into your cryptpad directory
|
||||
cd /your/cryptpad/instance/location;
|
||||
|
|
339
rpc.js
339
rpc.js
|
@ -2,12 +2,44 @@
|
|||
/* Use Nacl for checking signatures of messages */
|
||||
var Nacl = require("tweetnacl");
|
||||
|
||||
/* globals Buffer*/
|
||||
/* globals process */
|
||||
|
||||
var Fs = require("fs");
|
||||
var Path = require("path");
|
||||
|
||||
var RPC = module.exports;
|
||||
|
||||
var Store = require("./storage/file");
|
||||
|
||||
var isValidChannel = function (chan) {
|
||||
return /^[a-fA-F0-9]/.test(chan);
|
||||
return /^[a-fA-F0-9]/.test(chan) ||
|
||||
[32, 48].indexOf(chan.length) !== -1;
|
||||
};
|
||||
|
||||
var uint8ArrayToHex = function (a) {
|
||||
// call slice so Uint8Arrays work as expected
|
||||
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');
|
||||
}
|
||||
|
||||
switch (n.length) {
|
||||
case 0: return '00'; // just being careful, shouldn't happen
|
||||
case 1: return '0' + n;
|
||||
case 2: return n;
|
||||
default: throw new Error('unexpected value');
|
||||
}
|
||||
}).join('');
|
||||
};
|
||||
|
||||
var createFileId = function () {
|
||||
var id = uint8ArrayToHex(Nacl.randomBytes(24));
|
||||
if (id.length !== 48 || /[^a-f0-9]/.test(id)) {
|
||||
throw new Error('file ids must consist of 48 hex characters');
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
var makeToken = function () {
|
||||
|
@ -21,7 +53,7 @@ var makeCookie = function (token) {
|
|||
|
||||
return [
|
||||
time,
|
||||
process.pid, // jshint ignore:line
|
||||
process.pid,
|
||||
token
|
||||
];
|
||||
};
|
||||
|
@ -59,7 +91,11 @@ var isTooOld = function (time, now) {
|
|||
var expireSessions = function (Sessions) {
|
||||
var now = +new Date();
|
||||
Object.keys(Sessions).forEach(function (key) {
|
||||
var session = Sessions[key];
|
||||
if (isTooOld(Sessions[key].atime, now)) {
|
||||
if (session.blobstage) {
|
||||
session.blobstage.close();
|
||||
}
|
||||
delete Sessions[key];
|
||||
}
|
||||
});
|
||||
|
@ -86,7 +122,7 @@ var isValidCookie = function (Sessions, publicKey, cookie) {
|
|||
}
|
||||
|
||||
// different process. try harder
|
||||
if (process.pid !== parsed.pid) { // jshint ignore:line
|
||||
if (process.pid !== parsed.pid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -96,7 +132,6 @@ var isValidCookie = function (Sessions, publicKey, cookie) {
|
|||
var idx = user.tokens.indexOf(parsed.seq);
|
||||
if (idx === -1) { return false; }
|
||||
|
||||
var next;
|
||||
if (idx > 0) {
|
||||
// make a new token
|
||||
addTokenForKey(Sessions, publicKey, makeToken());
|
||||
|
@ -211,14 +246,32 @@ var getChannelList = function (store, Sessions, publicKey, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
var getUploadSize = function (store, channel, cb) {
|
||||
var path = '';
|
||||
|
||||
Fs.stat(path, function (err, stats) {
|
||||
if (err) { return void cb(err); }
|
||||
cb(void 0, stats.size);
|
||||
});
|
||||
};
|
||||
|
||||
var getFileSize = function (store, channel, cb) {
|
||||
if (!isValidChannel(channel)) { return void cb('INVALID_CHAN'); }
|
||||
if (typeof(store.getChannelSize) !== 'function') {
|
||||
return cb('GET_CHANNEL_SIZE_UNSUPPORTED');
|
||||
|
||||
if (channel.length === 32) {
|
||||
if (typeof(store.getChannelSize) !== 'function') {
|
||||
return cb('GET_CHANNEL_SIZE_UNSUPPORTED');
|
||||
}
|
||||
|
||||
return void store.getChannelSize(channel, function (e, size) {
|
||||
if (e) { return void cb(e.code); }
|
||||
cb(void 0, size);
|
||||
});
|
||||
}
|
||||
|
||||
return void store.getChannelSize(channel, function (e, size) {
|
||||
if (e) { return void cb(e.code); }
|
||||
// 'channel' refers to a file, so you need anoter API
|
||||
getUploadSize(null, channel, function (e, size) {
|
||||
if (e) { return void cb(e); }
|
||||
cb(void 0, size);
|
||||
});
|
||||
};
|
||||
|
@ -294,10 +347,11 @@ var getHash = function (store, Sessions, publicKey, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
var storeMessage = function (store, publicKey, msg, cb) {
|
||||
/* var storeMessage = function (store, publicKey, msg, cb) {
|
||||
store.message(publicKey, JSON.stringify(msg), cb);
|
||||
};
|
||||
}; */
|
||||
|
||||
// TODO check if new pinned size exceeds user quota
|
||||
var pinChannel = function (store, Sessions, publicKey, channels, cb) {
|
||||
if (!channels && channels.filter) {
|
||||
// expected array
|
||||
|
@ -349,8 +403,7 @@ var unpinChannel = function (store, Sessions, publicKey, channels, cb) {
|
|||
function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
toStore.forEach(function (channel) {
|
||||
// TODO actually delete
|
||||
session.channels[channel] = false;
|
||||
delete session.channels[channel];
|
||||
});
|
||||
|
||||
getHash(store, Sessions, publicKey, cb);
|
||||
|
@ -358,6 +411,7 @@ var unpinChannel = function (store, Sessions, publicKey, channels, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
// TODO check if new pinned size exceeds user quota
|
||||
var resetUserPins = function (store, Sessions, publicKey, channelList, cb) {
|
||||
var session = beginSession(Sessions, publicKey);
|
||||
|
||||
|
@ -376,14 +430,191 @@ var resetUserPins = function (store, Sessions, publicKey, channelList, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
var getPrivilegedUserList = function (cb) {
|
||||
Fs.readFile('./privileged.conf', 'utf8', function (e, body) {
|
||||
if (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
return void cb(void 0, []);
|
||||
}
|
||||
return void (e.code);
|
||||
}
|
||||
var list = body.split(/\n/)
|
||||
.map(function (line) {
|
||||
return line.replace(/#.*$/, '').trim();
|
||||
})
|
||||
.filter(function (x) { return x; });
|
||||
cb(void 0, list);
|
||||
});
|
||||
};
|
||||
|
||||
var isPrivilegedUser = function (publicKey, cb) {
|
||||
getPrivilegedUserList(function (e, list) {
|
||||
if (e) { return void cb(false); }
|
||||
cb(list.indexOf(publicKey) !== -1);
|
||||
});
|
||||
};
|
||||
|
||||
var getLimit = function (cb) {
|
||||
cb = cb; // TODO
|
||||
};
|
||||
|
||||
var safeMkdir = function (path, cb) {
|
||||
Fs.mkdir(path, function (e) {
|
||||
if (!e || e.code === 'EEXIST') { return void cb(); }
|
||||
cb(e);
|
||||
});
|
||||
};
|
||||
|
||||
var makeFilePath = function (root, id) {
|
||||
if (typeof(id) !== 'string' || id.length <= 2) { return null; }
|
||||
return Path.join(root, id.slice(0, 2), id);
|
||||
};
|
||||
|
||||
var makeFileStream = function (root, id, cb) {
|
||||
var stub = id.slice(0, 2);
|
||||
var full = makeFilePath(root, id);
|
||||
safeMkdir(Path.join(root, stub), function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
|
||||
try {
|
||||
var stream = Fs.createWriteStream(full, {
|
||||
flags: 'a',
|
||||
encoding: 'binary',
|
||||
});
|
||||
stream.on('open', function () {
|
||||
cb(void 0, stream);
|
||||
});
|
||||
} catch (err) {
|
||||
cb('BAD_STREAM');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var upload = function (paths, Sessions, publicKey, content, cb) {
|
||||
var dec = new Buffer(Nacl.util.decodeBase64(content)); // jshint ignore:line
|
||||
|
||||
var session = Sessions[publicKey];
|
||||
session.atime = +new Date();
|
||||
if (!session.blobstage) {
|
||||
makeFileStream(paths.staging, publicKey, function (e, stream) {
|
||||
if (e) { return void cb(e); }
|
||||
|
||||
var blobstage = session.blobstage = stream;
|
||||
blobstage.write(dec);
|
||||
cb(void 0, dec.length);
|
||||
});
|
||||
} else {
|
||||
session.blobstage.write(dec);
|
||||
cb(void 0, dec.length);
|
||||
}
|
||||
};
|
||||
|
||||
var upload_cancel = function (paths, Sessions, publicKey, cb) {
|
||||
var path = makeFilePath(paths.staging, publicKey);
|
||||
if (!path) {
|
||||
console.log(paths.staging, publicKey);
|
||||
console.log(path);
|
||||
return void cb('NO_FILE');
|
||||
}
|
||||
|
||||
Fs.unlink(path, function (e) {
|
||||
if (e) { return void cb('E_UNLINK'); }
|
||||
cb(void 0);
|
||||
});
|
||||
};
|
||||
|
||||
var isFile = function (filePath, cb) {
|
||||
Fs.stat(filePath, function (e, stats) {
|
||||
if (e) {
|
||||
if (e.code === 'ENOENT') { return void cb(void 0, false); }
|
||||
return void cb(e.message);
|
||||
}
|
||||
return void cb(void 0, stats.isFile());
|
||||
});
|
||||
};
|
||||
|
||||
/* TODO
|
||||
change channel IDs to a different length so that when we pin, we will be able
|
||||
to tell that it is not a channel, but a file, just by its length.
|
||||
|
||||
also, when your upload is complete, pin the resulting file.
|
||||
*/
|
||||
var upload_complete = function (paths, Sessions, publicKey, cb) {
|
||||
var session = Sessions[publicKey];
|
||||
|
||||
if (session.blobstage && session.blobstage.close) {
|
||||
session.blobstage.close();
|
||||
delete session.blobstage;
|
||||
}
|
||||
|
||||
var oldPath = makeFilePath(paths.staging, publicKey);
|
||||
|
||||
var tryRandomLocation = function (cb) {
|
||||
var id = createFileId();
|
||||
var prefix = id.slice(0, 2);
|
||||
var newPath = makeFilePath(paths.blob, id);
|
||||
|
||||
safeMkdir(Path.join(paths.blob, prefix), function (e) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
return void cb('RENAME_ERR');
|
||||
}
|
||||
isFile(newPath, function (e, yes) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
return void cb(e);
|
||||
}
|
||||
if (yes) {
|
||||
return void tryRandomLocation(cb);
|
||||
}
|
||||
|
||||
cb(void 0, newPath, id);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
tryRandomLocation(function (e, newPath, id) {
|
||||
Fs.rename(oldPath, newPath, function (e) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
return cb(e);
|
||||
}
|
||||
|
||||
cb(void 0, id);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* TODO
|
||||
when asking about your upload status, also send some information about how big
|
||||
your upload is going to be. if that would exceed your limit, return TOO_LARGE
|
||||
error.
|
||||
|
||||
*/
|
||||
var upload_status = function (paths, Sessions, publicKey, cb) {
|
||||
var filePath = makeFilePath(paths.staging, publicKey);
|
||||
if (!filePath) { return void cb('E_INVALID_PATH'); }
|
||||
isFile(filePath, function (e, yes) {
|
||||
cb(e, yes);
|
||||
});
|
||||
};
|
||||
|
||||
/*::const ConfigType = require('./config.example.js');*/
|
||||
RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) {
|
||||
// load pin-store...
|
||||
|
||||
console.log('loading rpc module...');
|
||||
|
||||
var Sessions = {};
|
||||
|
||||
var keyOrDefaultString = function (key, def) {
|
||||
return typeof(config[key]) === 'string'? config[key]: def;
|
||||
};
|
||||
|
||||
var paths = {};
|
||||
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
|
||||
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
|
||||
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
||||
|
||||
var store;
|
||||
|
||||
var rpc = function (
|
||||
|
@ -428,7 +659,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
|
|||
return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY');
|
||||
}
|
||||
|
||||
|
||||
if (checkSignature(serialized, signature, publicKey) !== true) {
|
||||
return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY");
|
||||
}
|
||||
|
@ -446,20 +676,26 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
|
|||
var Respond = function (e, msg) {
|
||||
var token = Sessions[publicKey].tokens.slice(-1)[0];
|
||||
var cookie = makeCookie(token).join('|');
|
||||
respond(e, [cookie].concat(msg||[]));
|
||||
respond(e, [cookie].concat(typeof(msg) !== 'undefined' ?msg: []));
|
||||
};
|
||||
|
||||
if (typeof(msg) !== 'object' || !msg.length) {
|
||||
return void Respond('INVALID_MSG');
|
||||
}
|
||||
|
||||
var deny = function () {
|
||||
Respond('E_ACCESS_DENIED');
|
||||
};
|
||||
|
||||
var handleMessage = function (privileged) {
|
||||
switch (msg[0]) {
|
||||
case 'COOKIE': return void Respond(void 0);
|
||||
case 'RESET':
|
||||
return resetUserPins(store, Sessions, safeKey, msg[1], function (e, hash) {
|
||||
return void Respond(e, hash);
|
||||
});
|
||||
case 'PIN':
|
||||
case 'PIN': // TODO don't pin if over the limit
|
||||
// if over, send error E_OVER_LIMIT
|
||||
return pinChannel(store, Sessions, safeKey, msg[1], function (e, hash) {
|
||||
Respond(e, hash);
|
||||
});
|
||||
|
@ -471,32 +707,89 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
|
|||
return void getHash(store, Sessions, safeKey, function (e, hash) {
|
||||
Respond(e, hash);
|
||||
});
|
||||
case 'GET_TOTAL_SIZE':
|
||||
case 'GET_TOTAL_SIZE': // TODO cache this, since it will get called quite a bit
|
||||
return getTotalSize(store, ctx.store, Sessions, safeKey, function (e, size) {
|
||||
if (e) { return void Respond(e); }
|
||||
Respond(e, size);
|
||||
});
|
||||
case 'GET_FILE_SIZE':
|
||||
return void getFileSize(ctx.store, msg[1], Respond);
|
||||
case 'GET_LIMIT': // TODO implement this and cache it per-user
|
||||
return void getLimit(function (e, limit) {
|
||||
limit = limit;
|
||||
Respond('NOT_IMPLEMENTED');
|
||||
});
|
||||
case 'GET_MULTIPLE_FILE_SIZE':
|
||||
return void getMultipleFileSize(ctx.store, msg[1], function (e, dict) {
|
||||
if (e) { return void Respond(e); }
|
||||
Respond(void 0, dict);
|
||||
});
|
||||
|
||||
|
||||
// restricted to privileged users...
|
||||
case 'UPLOAD':
|
||||
if (!privileged) { return deny(); }
|
||||
return void upload(paths, Sessions, safeKey, msg[1], function (e, len) {
|
||||
Respond(e, len);
|
||||
});
|
||||
case 'UPLOAD_STATUS':
|
||||
if (!privileged) { return deny(); }
|
||||
return void upload_status(paths, Sessions, safeKey, function (e, stat) {
|
||||
Respond(e, stat);
|
||||
});
|
||||
case 'UPLOAD_COMPLETE':
|
||||
if (!privileged) { return deny(); }
|
||||
return void upload_complete(paths, Sessions, safeKey, function (e, hash) {
|
||||
Respond(e, hash);
|
||||
});
|
||||
case 'UPLOAD_CANCEL':
|
||||
if (!privileged) { return deny(); }
|
||||
return void upload_cancel(paths, Sessions, safeKey, function (e) {
|
||||
Respond(e);
|
||||
});
|
||||
default:
|
||||
return void Respond('UNSUPPORTED_RPC_CALL', msg);
|
||||
}
|
||||
};
|
||||
|
||||
// reject uploads unless explicitly enabled
|
||||
if (config.enableUploads !== true) {
|
||||
return void handleMessage(false);
|
||||
}
|
||||
|
||||
// restrict upload capability unless explicitly disabled
|
||||
if (config.restrictUploads === false) {
|
||||
return void handleMessage(true);
|
||||
}
|
||||
|
||||
// if session has not been authenticated, do so
|
||||
var session = Sessions[publicKey];
|
||||
if (typeof(session.privilege) !== 'boolean') {
|
||||
return void isPrivilegedUser(publicKey, function (yes) {
|
||||
session.privilege = yes;
|
||||
handleMessage(yes);
|
||||
});
|
||||
}
|
||||
|
||||
// if authenticated, proceed
|
||||
handleMessage(session.privilege);
|
||||
};
|
||||
|
||||
Store.create({
|
||||
filePath: './pins'
|
||||
filePath: pinPath,
|
||||
}, function (s) {
|
||||
store = s;
|
||||
cb(void 0, rpc);
|
||||
|
||||
// expire old sessions once per minute
|
||||
setInterval(function () {
|
||||
expireSessions(Sessions);
|
||||
}, 60000);
|
||||
safeMkdir(blobPath, function (e) {
|
||||
if (e) { throw e; }
|
||||
safeMkdir(blobStagingPath, function (e) {
|
||||
if (e) { throw e; }
|
||||
cb(void 0, rpc);
|
||||
// expire old sessions once per minute
|
||||
setInterval(function () {
|
||||
expireSessions(Sessions);
|
||||
}, 60000);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -82,6 +82,8 @@ var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'cont
|
|||
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
|
||||
app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
|
||||
|
||||
app.use("/blob", Express.static(__dirname + '/blob'));
|
||||
|
||||
app.use("/customize", Express.static(__dirname + '/customize'));
|
||||
app.use("/customize", Express.static(__dirname + '/customize.dist'));
|
||||
app.use(/^\/[^\/]*$/, Express.static('customize'));
|
||||
|
|
|
@ -28,7 +28,8 @@ var readMessages = function (path, msgHandler, cb) {
|
|||
};
|
||||
|
||||
var checkPath = function (path, callback) {
|
||||
Fs.stat(path, function (err, stats) {
|
||||
// TODO check if we actually need to use stat at all
|
||||
Fs.stat(path, function (err) {
|
||||
if (!err) {
|
||||
callback(undefined, true);
|
||||
return;
|
||||
|
@ -166,7 +167,7 @@ var getChannel = function (env, id, callback) {
|
|||
});
|
||||
}
|
||||
});
|
||||
}).nThen(function (waitFor) {
|
||||
}).nThen(function () {
|
||||
if (errorState) { return; }
|
||||
complete();
|
||||
});
|
||||
|
|
|
@ -15,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"]]]]',
|
||||
|
||||
|
@ -60,7 +72,7 @@ define([
|
|||
};
|
||||
|
||||
var HJSON_equal = function (shjson) {
|
||||
assert(function () {
|
||||
assert(function (cb) {
|
||||
// parse your stringified Hyperjson
|
||||
var hjson;
|
||||
|
||||
|
@ -84,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");
|
||||
};
|
||||
|
@ -96,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;
|
||||
|
@ -113,7 +125,7 @@ define([
|
|||
TextPatcher.log(target.outerHTML, op);
|
||||
}
|
||||
|
||||
return success;
|
||||
return cb(success);
|
||||
}, "Round trip serialization introduced artifacts.");
|
||||
};
|
||||
|
||||
|
@ -127,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 + ")");
|
||||
};
|
||||
|
||||
|
@ -139,6 +151,59 @@ define([
|
|||
strungJSON(orig);
|
||||
});
|
||||
|
||||
// check that old hashes parse correctly
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parseHash('67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy');
|
||||
return cb(secret.channel === "67b8385b07352be53e40746d2be6ccd7" &&
|
||||
secret.key === "XAYSuJYYqa9NfmInyHci7LNy" &&
|
||||
secret.version === 0);
|
||||
}, "Old hash failed to parse");
|
||||
|
||||
// make sure version 1 hashes parse correctly
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parseHash('/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI');
|
||||
return cb(secret.version === 1 &&
|
||||
secret.mode === "edit" &&
|
||||
secret.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
|
||||
secret.key === "usn4+9CqVja8Q7RZOGTfRgqI" &&
|
||||
!secret.present);
|
||||
}, "version 1 hash failed to parse");
|
||||
|
||||
// test support for present mode in hashes
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parseHash('/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present');
|
||||
return cb(secret.version === 1
|
||||
&& secret.mode === "edit"
|
||||
&& secret.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
|
||||
&& secret.key === "DNZ2wcG683GscU4fyOyqA87G"
|
||||
&& secret.present);
|
||||
}, "version 1 hash failed to parse");
|
||||
|
||||
// test support for present mode in hashes
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parseHash('/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G//present');
|
||||
return cb(secret.version === 1
|
||||
&& secret.mode === "edit"
|
||||
&& secret.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
|
||||
&& secret.key === "DNZ2wcG683GscU4fyOyqA87G"
|
||||
&& secret.present);
|
||||
}, "Couldn't handle multiple successive slashes");
|
||||
|
||||
// test support for trailing slash
|
||||
assert(function (cb) {
|
||||
var secret = Cryptpad.parseHash('/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/');
|
||||
return cb(secret.version === 1 &&
|
||||
secret.mode === "edit" &&
|
||||
secret.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
|
||||
secret.key === "usn4+9CqVja8Q7RZOGTfRgqI" &&
|
||||
!secret.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;
|
||||
|
@ -153,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:
|
||||
|
@ -174,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.
|
||||
|
@ -196,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');
|
||||
|
||||
});
|
||||
|
|
588
www/code/main.js
588
www/code/main.js
|
@ -3,18 +3,12 @@ define([
|
|||
'/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'
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify) {
|
||||
var saveAs = window.saveAs;
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = {
|
||||
|
@ -30,6 +24,7 @@ define([
|
|||
};
|
||||
|
||||
var toolbar;
|
||||
var editor;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
|
@ -37,117 +32,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 isHistoryMode = false;
|
||||
|
||||
var editor = module.editor = CMeditor.fromTextArea($textarea[0], {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets : true,
|
||||
showTrailingSpace : true,
|
||||
styleActiveLine : true,
|
||||
search: true,
|
||||
highlightSelectionMatches: {showToken: /\w+/},
|
||||
extraKeys: {"Shift-Ctrl-R": undefined},
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
mode: "javascript",
|
||||
readOnly: true
|
||||
});
|
||||
editor.setValue(Messages.codeInitialState);
|
||||
|
||||
var setMode = module.setMode = function (mode, $select) {
|
||||
module.highlightMode = mode;
|
||||
if (mode === 'text') {
|
||||
editor.setOption('mode', 'text');
|
||||
return;
|
||||
}
|
||||
CodeMirror.autoLoadMode(editor, mode);
|
||||
editor.setOption('mode', mode);
|
||||
if ($select) {
|
||||
var name = $select.find('a[data-value="' + mode + '"]').text() || 'Mode';
|
||||
$select.setValue(name);
|
||||
}
|
||||
};
|
||||
|
||||
var setTheme = module.setTheme = (function () {
|
||||
var path = '/common/theme/';
|
||||
|
||||
var $head = $(ifrw.document.head);
|
||||
|
||||
var themeLoaded = module.themeLoaded = function (theme) {
|
||||
return $head.find('link[href*="'+theme+'"]').length;
|
||||
};
|
||||
|
||||
var loadTheme = module.loadTheme = function (theme) {
|
||||
$head.append($('<link />', {
|
||||
rel: 'stylesheet',
|
||||
href: path + theme + '.css',
|
||||
}));
|
||||
};
|
||||
|
||||
return function (theme, $select) {
|
||||
if (!theme) {
|
||||
editor.setOption('theme', 'default');
|
||||
} else {
|
||||
if (!themeLoaded(theme)) {
|
||||
loadTheme(theme);
|
||||
}
|
||||
editor.setOption('theme', theme);
|
||||
}
|
||||
if ($select) {
|
||||
$select.setValue(theme || 'Theme');
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
var setEditable = module.setEditable = function (bool) {
|
||||
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,7 +61,6 @@ define([
|
|||
validateKey: secret.keys.validateKey || undefined,
|
||||
readOnly: readOnly,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
setMyID: setMyID,
|
||||
network: Cryptpad.getNetwork(),
|
||||
transformFunction: JsonOT.validate,
|
||||
};
|
||||
|
@ -172,26 +75,21 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var isDefaultTitle = function () {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
return Cryptpad.isDefaultName(parsed, document.title);
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
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);
|
||||
|
@ -204,7 +102,7 @@ define([
|
|||
|
||||
editor.save();
|
||||
|
||||
var textValue = canonicalize($textarea.val());
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = stringifyInner(textValue);
|
||||
|
||||
module.patchText(shjson);
|
||||
|
@ -214,231 +112,55 @@ 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;
|
||||
}
|
||||
onLocal();
|
||||
});
|
||||
};
|
||||
|
||||
var getHeadingText = function () {
|
||||
var lines = editor.getValue().split(/\n/);
|
||||
|
||||
var text = '';
|
||||
lines.some(function (line) {
|
||||
// lisps?
|
||||
var lispy = /^\s*(;|#\|)(.*?)$/;
|
||||
if (lispy.test(line)) {
|
||||
line.replace(lispy, function (a, one, two) {
|
||||
text = two;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, 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;
|
||||
}
|
||||
var titleCfg = { getHeadingText: CodeMirror.getHeadingText };
|
||||
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
|
||||
|
||||
// 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;
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
|
||||
userData: userData,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
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
|
||||
};
|
||||
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, configTb);
|
||||
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 = {};
|
||||
histConfig.onRender = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
var hjson = JSON.parse(val || '{}');
|
||||
var remoteDoc = hjson.content;
|
||||
var histConfig = {
|
||||
onLocal: config.onLocal(),
|
||||
onRemote: config.onRemote(),
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var remoteDoc = JSON.parse(val || '{}').content;
|
||||
editor.setValue(remoteDoc || '');
|
||||
editor.save();
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
$toolbar: $bar
|
||||
};
|
||||
histConfig.onClose = function () {
|
||||
// Close button clicked
|
||||
setHistory(false, true);
|
||||
};
|
||||
histConfig.onRevert = function () {
|
||||
// Revert button clicked
|
||||
setHistory(false, false);
|
||||
config.onLocal();
|
||||
config.onRemote();
|
||||
};
|
||||
histConfig.onReady = function () {
|
||||
// Called when the history is loaded and the UI displayed
|
||||
setHistory(true);
|
||||
};
|
||||
histConfig.$toolbar = $bar;
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
|
@ -447,133 +169,42 @@ define([
|
|||
var templateObj = {
|
||||
rt: info.realtime,
|
||||
Crypt: Cryptget,
|
||||
getTitle: function () { return document.title; }
|
||||
getTitle: Title.getTitle
|
||||
};
|
||||
var $templateButton = Cryptpad.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
|
||||
/* add an export button */
|
||||
var $export = Cryptpad.createButton('export', true, {}, exportText);
|
||||
var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText);
|
||||
$rightside.append($export);
|
||||
|
||||
if (!readOnly) {
|
||||
/* add an import button */
|
||||
var $import = Cryptpad.createButton('import', true, {}, importText);
|
||||
var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText);
|
||||
$rightside.append($import);
|
||||
|
||||
/* add a rename button */
|
||||
//var $setTitle = Cryptpad.createButton('rename', true, {suggestName: suggestName}, renameCb);
|
||||
//$rightside.append($setTitle);
|
||||
}
|
||||
|
||||
/* add a forget button */
|
||||
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);
|
||||
};
|
||||
|
||||
if (!readOnly) {
|
||||
configureLanguage(function () {
|
||||
configureTheme();
|
||||
});
|
||||
CodeMirror.configureLanguage(CodeMirror.configureTheme);
|
||||
}
|
||||
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;
|
||||
config.onReady = function (info) {
|
||||
if (module.realtime !== info.realtime) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
|
@ -600,135 +231,58 @@ define([
|
|||
newDoc = hjson.content;
|
||||
|
||||
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('javascript');
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
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 () {
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
var scroll = editor.getScrollInfo();
|
||||
|
||||
var oldDoc = canonicalize($textarea.val());
|
||||
var oldDoc = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = module.realtime.getUserDoc();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(shjson);
|
||||
Metadata.update(shjson);
|
||||
|
||||
var hjson = JSON.parse(shjson);
|
||||
var remoteDoc = hjson.content;
|
||||
|
||||
var highlightMode = hjson.highlightMode;
|
||||
if (highlightMode && highlightMode !== module.highlightMode) {
|
||||
setMode(highlightMode, module.$language);
|
||||
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");
|
||||
|
@ -736,19 +290,17 @@ define([
|
|||
module.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) {
|
||||
|
@ -760,9 +312,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);
|
||||
|
||||
|
@ -772,7 +324,7 @@ define([
|
|||
var interval = 100;
|
||||
|
||||
var second = function (CM) {
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen(CM);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ define(['jquery'], function ($) {
|
|||
|
||||
// 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);
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
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) {
|
||||
exp.highlightMode = mode;
|
||||
if (mode === 'text') {
|
||||
editor.setOption('mode', '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);
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
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,
|
||||
};
|
||||
console.log('here');
|
||||
var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig);
|
||||
console.log(exp);
|
||||
$block.find('a').click(function () {
|
||||
setMode($(this).attr('data-value'), $block);
|
||||
onLocal();
|
||||
});
|
||||
|
||||
if ($rightside) { $rightside.append($block); }
|
||||
cb();
|
||||
};
|
||||
|
||||
exp.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 = 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); }
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
|
|
@ -23,13 +23,16 @@ define([
|
|||
return chanKey + keys;
|
||||
}
|
||||
if (!keys.editKeyStr) { return; }
|
||||
return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr);
|
||||
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);
|
||||
return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/';
|
||||
};
|
||||
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
|
||||
return '/2/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
|
||||
};
|
||||
|
||||
var parsePadUrl = Hash.parsePadUrl = function (href) {
|
||||
|
@ -38,6 +41,7 @@ define([
|
|||
var ret = {};
|
||||
|
||||
if (!href) { return ret; }
|
||||
if (href.slice(-1) !== '/') { href += '/'; }
|
||||
|
||||
if (!/^https*:\/\//.test(href)) {
|
||||
var idx = href.indexOf('/#');
|
||||
|
@ -46,7 +50,7 @@ define([
|
|||
return ret;
|
||||
}
|
||||
|
||||
var hash = href.replace(patt, function (a, domain, type, hash) {
|
||||
var hash = href.replace(patt, function (a, domain, type) {
|
||||
ret.domain = domain;
|
||||
ret.type = type;
|
||||
return '';
|
||||
|
@ -62,12 +66,16 @@ define([
|
|||
return '/' + parsed.type + '/#' + parsed.hash;
|
||||
};
|
||||
|
||||
var fixDuplicateSlashes = function (s) {
|
||||
return s.replace(/\/+/g, '/');
|
||||
};
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
var getSecrets = Hash.getSecrets = function (secretHash) {
|
||||
Hash.getSecrets = function (secretHash) {
|
||||
var secret = {};
|
||||
var generate = function () {
|
||||
secret.keys = Crypto.createEditCryptor();
|
||||
|
@ -91,7 +99,7 @@ define([
|
|||
}
|
||||
else {
|
||||
// New hash
|
||||
var hashArray = hash.split('/');
|
||||
var hashArray = fixDuplicateSlashes(hash).split('/');
|
||||
if (hashArray.length < 4) {
|
||||
Hash.alert("Unable to parse the key");
|
||||
throw new Error("Unable to parse the key");
|
||||
|
@ -119,14 +127,15 @@ define([
|
|||
}
|
||||
} else if (version === "2") {
|
||||
// version 2 hashes are to be used for encrypted blobs
|
||||
// TODO
|
||||
secret.channel = hashArray[2].replace(/-/g, '/');
|
||||
secret.keys = { fileKeyStr: hashArray[3].replace(/-/g, '/') };
|
||||
}
|
||||
}
|
||||
}
|
||||
return secret;
|
||||
};
|
||||
|
||||
var getHashes = Hash.getHashes = function (channel, secret) {
|
||||
Hash.getHashes = function (channel, secret) {
|
||||
var hashes = {};
|
||||
if (secret.keys.editKeyStr) {
|
||||
hashes.editHash = getEditHashFromKeys(channel, secret.keys);
|
||||
|
@ -134,6 +143,9 @@ define([
|
|||
if (secret.keys.viewKeyStr) {
|
||||
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
|
||||
}
|
||||
if (secret.keys.fileKeyStr) {
|
||||
hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
|
||||
}
|
||||
return hashes;
|
||||
};
|
||||
|
||||
|
@ -145,12 +157,12 @@ define([
|
|||
return id;
|
||||
};
|
||||
|
||||
var createRandomHash = Hash.createRandomHash = function () {
|
||||
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('/');
|
||||
return '/1/edit/' + [channelId, key].join('/') + '/';
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -159,8 +171,8 @@ Version 0
|
|||
Version 1
|
||||
/code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
|
||||
Version 2
|
||||
/file/<fileId>/#/2/<cryptKey>/<contentType>
|
||||
/file/<fileId>/#/2/ajExFODrFH4lVBwxxsrOKw/pdf
|
||||
/file/#/2/<fileId>/<cryptKey>/<contentType>
|
||||
/file/#/2/K6xWU-LT9BJHCQcDCT-DcQ/ajExFODrFH4lVBwxxsrOKw/image-png
|
||||
*/
|
||||
var parseHash = Hash.parseHash = function (hash) {
|
||||
var parsed = {};
|
||||
|
@ -171,20 +183,26 @@ Version 2
|
|||
parsed.version = 0;
|
||||
return parsed;
|
||||
}
|
||||
var hashArr = hash.split('/');
|
||||
var hashArr = fixDuplicateSlashes(hash).split('/');
|
||||
if (hashArr[1] && hashArr[1] === '1') {
|
||||
parsed.version = 1;
|
||||
parsed.mode = hashArr[2];
|
||||
parsed.channel = hashArr[3];
|
||||
parsed.key = hashArr[4];
|
||||
parsed.present = hashArr[5] && hashArr[5] === 'present';
|
||||
parsed.present = typeof(hashArr[5]) === "string" && hashArr[5] === 'present';
|
||||
return parsed;
|
||||
}
|
||||
if (hashArr[1] && hashArr[1] === '2') {
|
||||
parsed.version = 2;
|
||||
parsed.channel = hashArr[2].replace(/-/g, '/');
|
||||
parsed.key = hashArr[3].replace(/-/g, '/');
|
||||
return parsed;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// STORAGE
|
||||
var findWeaker = Hash.findWeaker = function (href, recents) {
|
||||
Hash.findWeaker = function (href, recents) {
|
||||
var rHref = href || getRelativeHref(window.location.href);
|
||||
var parsed = parsePadUrl(rHref);
|
||||
if (!parsed.hash) { return false; }
|
||||
|
@ -228,11 +246,11 @@ Version 2
|
|||
});
|
||||
return stronger;
|
||||
};
|
||||
var isNotStrongestStored = Hash.isNotStrongestStored = function (href, recents) {
|
||||
Hash.isNotStrongestStored = function (href, recents) {
|
||||
return findStronger(href, recents);
|
||||
};
|
||||
|
||||
var hrefToHexChannelId = Hash.hrefToHexChannelId = function (href) {
|
||||
Hash.hrefToHexChannelId = function (href) {
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
if (!parsed || !parsed.hash) { return; }
|
||||
|
||||
|
@ -240,7 +258,7 @@ Version 2
|
|||
|
||||
if (parsed.version === 0) {
|
||||
return parsed.channel;
|
||||
} else if (parsed.version !== 1) {
|
||||
} else if (parsed.version !== 1 && parsed.version !== 2) {
|
||||
console.error("parsed href had no version");
|
||||
console.error(parsed);
|
||||
return;
|
||||
|
@ -253,5 +271,14 @@ Version 2
|
|||
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;
|
||||
});
|
||||
|
|
|
@ -18,13 +18,13 @@ define([
|
|||
return states;
|
||||
};
|
||||
|
||||
var loadHistory = function (common, cb) {
|
||||
var loadHistory = function (config, common, cb) {
|
||||
var network = common.getNetwork();
|
||||
var hkn = network.historyKeeper;
|
||||
|
||||
var wcId = common.hrefToHexChannelId(window.location.href);
|
||||
var wcId = common.hrefToHexChannelId(config.href || window.location.href);
|
||||
|
||||
var createRealtime = function(chan) {
|
||||
var createRealtime = function () {
|
||||
return ChainPad.create({
|
||||
userName: 'history',
|
||||
initialState: '',
|
||||
|
@ -35,7 +35,8 @@ define([
|
|||
};
|
||||
var realtime = createRealtime();
|
||||
|
||||
var secret = common.getSecrets();
|
||||
var hash = config.href ? common.parsePadUrl(config.href).hash : undefined;
|
||||
var secret = common.getSecrets(hash);
|
||||
var crypto = Crypto.createEncryptor(secret.keys);
|
||||
|
||||
var to = window.setTimeout(function () {
|
||||
|
@ -66,21 +67,44 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
network.on('message', function (msg, sender) {
|
||||
network.on('message', function (msg) {
|
||||
onMsg(msg);
|
||||
});
|
||||
|
||||
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', wcId, secret.keys.validateKey]));
|
||||
};
|
||||
|
||||
var create = History.create = function (common, config) {
|
||||
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;
|
||||
var noFunc = function () {};
|
||||
var render = config.onRender || noFunc;
|
||||
var onClose = config.onClose || noFunc;
|
||||
var onRevert = config.onRevert || noFunc;
|
||||
var onReady = config.onReady || noFunc;
|
||||
|
||||
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
|
||||
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
|
||||
}
|
||||
|
||||
// config.setHistory(bool, bool)
|
||||
// - bool1: history value
|
||||
// - bool2: reset old content?
|
||||
var render = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
config.applyVal(val);
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
var onClose = function () { config.setHistory(false, true); };
|
||||
var onRevert = function () {
|
||||
config.setHistory(false, false);
|
||||
config.onLocal();
|
||||
config.onRemote();
|
||||
};
|
||||
var onReady = function () {
|
||||
config.setHistory(true);
|
||||
};
|
||||
|
||||
var Messages = common.Messages;
|
||||
|
||||
|
@ -112,9 +136,9 @@ define([
|
|||
var val = states[i].getContent().doc;
|
||||
c = i;
|
||||
if (typeof onUpdate === "function") { onUpdate(); }
|
||||
$hist.find('.next, .previous').show();
|
||||
if (c === states.length - 1) { $hist.find('.next').hide(); }
|
||||
if (c === 0) { $hist.find('.previous').hide(); }
|
||||
$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 || '';
|
||||
};
|
||||
|
||||
|
@ -132,15 +156,16 @@ define([
|
|||
$right.hide();
|
||||
$cke.hide();
|
||||
var $prev =$('<button>', {
|
||||
'class': 'previous fa fa-step-backward',
|
||||
'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',
|
||||
'class': 'next fa fa-step-forward buttonPrimary',
|
||||
title: Messages.history_next
|
||||
}).appendTo($hist);
|
||||
|
||||
var $nav = $('<div>', {'class': 'goto'}).appendTo($hist);
|
||||
$('<label>').text(Messages.history_version).appendTo($nav);
|
||||
var $cur = $('<input>', {
|
||||
'class' : 'gotoInput',
|
||||
'type' : 'number',
|
||||
|
@ -150,25 +175,21 @@ define([
|
|||
// stopPropagation because the event would be cancelled by the dropdown menus
|
||||
e.stopPropagation();
|
||||
});
|
||||
var $label = $('<label>').text(' / '+ states.length).appendTo($nav);
|
||||
var $goTo = $('<button>', {
|
||||
'class': 'fa fa-check',
|
||||
'title': Messages.history_goTo
|
||||
}).appendTo($nav);
|
||||
var $label2 = $('<label>').text(' / '+ states.length).appendTo($nav);
|
||||
$('<br>').appendTo($nav);
|
||||
var $rev = $('<button>', {
|
||||
'class':'revertHistory',
|
||||
title: Messages.history_restoreTitle
|
||||
}).text(Messages.history_restore).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);
|
||||
$label.text(' / ' + states.length);
|
||||
$label2.text(' / ' + states.length);
|
||||
};
|
||||
|
||||
var close = function () {
|
||||
|
@ -181,7 +202,6 @@ define([
|
|||
// Buttons actions
|
||||
$prev.click(function () { render(getPrevious()); });
|
||||
$next.click(function () { render(getNext()); });
|
||||
$goTo.click(function () { render( get($cur.val() - 1) ); });
|
||||
$cur.keydown(function (e) {
|
||||
var p = function () { e.preventDefault(); };
|
||||
if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
|
||||
|
@ -192,7 +212,7 @@ define([
|
|||
if (e.which === 27) { p(); $close.click(); }
|
||||
}).focus();
|
||||
$cur.on('change', function () {
|
||||
$goTo.click();
|
||||
render( get($cur.val() - 1) );
|
||||
});
|
||||
$close.click(function () {
|
||||
states = [];
|
||||
|
@ -213,7 +233,8 @@ define([
|
|||
};
|
||||
|
||||
// Load all the history messages into a new chainpad object
|
||||
loadHistory(common, function (err, newRt) {
|
||||
loadHistory(config, common, function (err, newRt) {
|
||||
History.loading = false;
|
||||
if (err) { throw new Error(err); }
|
||||
realtime = newRt;
|
||||
update();
|
||||
|
|
|
@ -3,8 +3,10 @@ define([
|
|||
'/customize/messages.js',
|
||||
'/common/common-util.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/alertifyjs/dist/js/alertify.js'
|
||||
], function ($, Messages, Util, AppConfig, Alertify) {
|
||||
'/bower_components/alertifyjs/dist/js/alertify.js',
|
||||
'/common/notify.js',
|
||||
'/common/visible.js'
|
||||
], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible) {
|
||||
|
||||
var UI = {};
|
||||
|
||||
|
@ -48,7 +50,7 @@ define([
|
|||
UI.alert = function (msg, cb, force) {
|
||||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
var close = function (e) {
|
||||
var close = function () {
|
||||
findOKButton().click();
|
||||
};
|
||||
var keyHandler = listenForKeys(close, close);
|
||||
|
@ -66,9 +68,9 @@ define([
|
|||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
|
||||
var keyHandler = listenForKeys(function (e) { // yes
|
||||
var keyHandler = listenForKeys(function () { // yes
|
||||
findOKButton().click();
|
||||
}, function (e) { // no
|
||||
}, function () { // no
|
||||
findCancelButton().click();
|
||||
});
|
||||
|
||||
|
@ -90,9 +92,9 @@ define([
|
|||
cb = cb || function () {};
|
||||
if (force !== true) { msg = Util.fixHTML(msg); }
|
||||
|
||||
var keyHandler = listenForKeys(function (e) {
|
||||
var keyHandler = listenForKeys(function () {
|
||||
findOKButton().click();
|
||||
}, function (e) {
|
||||
}, function () {
|
||||
findCancelButton().click();
|
||||
});
|
||||
|
||||
|
@ -141,7 +143,7 @@ define([
|
|||
|
||||
return {
|
||||
show: function () {
|
||||
$target.show();
|
||||
$target.css('display', 'inline');
|
||||
return this;
|
||||
},
|
||||
hide: function () {
|
||||
|
@ -183,7 +185,7 @@ define([
|
|||
}
|
||||
if (Messages.tips && !hideTips) {
|
||||
var $loadingTip = $('<div>', {'id': 'loadingTip'});
|
||||
var $tip = $('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
|
||||
$('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
|
||||
$loadingTip.css({
|
||||
'top': $('body').height()/2 + $container.height()/2 + 20 + 'px'
|
||||
});
|
||||
|
@ -204,7 +206,29 @@ define([
|
|||
$('#' + LOADING).find('p').html(error || Messages.error);
|
||||
};
|
||||
|
||||
var importContent = UI.importContent = function (type, f) {
|
||||
// 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) {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -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,20 +1,20 @@
|
|||
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 ")
|
||||
|
@ -24,7 +24,7 @@ define([], function () {
|
|||
return window.btoa(byteString).replace(/\//g, '-').slice(0,-2);
|
||||
};
|
||||
|
||||
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,18 +76,28 @@ define([], function () {
|
|||
/*
|
||||
* Saving files
|
||||
*/
|
||||
var fixFileName = Util.fixFileName = function (filename) {
|
||||
Util.fixFileName = function (filename) {
|
||||
return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_')
|
||||
.replace(/_+/g, '_');
|
||||
};
|
||||
|
||||
var bytesToMegabytes = Util.bytesToMegabytes = function (bytes) {
|
||||
Util.bytesToMegabytes = function (bytes) {
|
||||
return Math.floor((bytes / (1024 * 1024) * 100)) / 100;
|
||||
};
|
||||
|
||||
var bytesToKilobytes = Util.bytesToKilobytes = function (bytes) {
|
||||
Util.bytesToKilobytes = function (bytes) {
|
||||
return Math.floor(bytes / 1024 * 100) / 100;
|
||||
};
|
||||
|
||||
Util.fetch = function (src, cb) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", src, true);
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.onload = function () {
|
||||
return void cb(void 0, new Uint8Array(xhr.response));
|
||||
};
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -5,8 +5,8 @@ define([
|
|||
'/common/cryptpad-common.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js'
|
||||
], function ($, Crypto, Realtime, Cryptpad, TextPatcher) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
var noop = function () {};
|
||||
//var Messages = Cryptpad.Messages;
|
||||
//var noop = function () {};
|
||||
var finish = function (S, err, doc) {
|
||||
if (S.done) { return; }
|
||||
S.cb(err, doc);
|
||||
|
@ -50,14 +50,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 +87,7 @@ define([
|
|||
};
|
||||
overwrite(config, opt);
|
||||
|
||||
var realtime = Session.session = Realtime.start(config);
|
||||
Session.session = Realtime.start(config);
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -7,11 +7,15 @@ define([
|
|||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-history.js',
|
||||
'/common/common-userlist.js',
|
||||
'/common/common-title.js',
|
||||
'/common/common-metadata.js',
|
||||
'/common/common-codemirror.js',
|
||||
|
||||
'/common/clipboard.js',
|
||||
'/common/pinpad.js',
|
||||
'/customize/application_config.js'
|
||||
], function ($, Config, Messages, Store, Util, Hash, UI, History, Clipboard, Pinpad, AppConfig) {
|
||||
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, CodeMirror, Clipboard, Pinpad, AppConfig) {
|
||||
|
||||
/* This file exposes functionality which is specific to Cryptpad, but not to
|
||||
any particular pad type. This includes functions for committing metadata
|
||||
|
@ -29,7 +33,7 @@ define([
|
|||
var userHashKey = common.userHashKey = 'User_hash';
|
||||
var userNameKey = common.userNameKey = 'User_name';
|
||||
var fileHashKey = common.fileHashKey = 'FS_hash';
|
||||
var displayNameKey = common.displayNameKey = 'cryptpad.username';
|
||||
common.displayNameKey = 'cryptpad.username';
|
||||
var newPadNameKey = common.newPadNameKey = "newPadName";
|
||||
var newPadPathKey = common.newPadPathKey = "newPadPath";
|
||||
var storageKey = common.storageKey = 'CryptPad_RECENTPADS';
|
||||
|
@ -39,10 +43,10 @@ define([
|
|||
var rpc;
|
||||
|
||||
// import UI elements
|
||||
var findCancelButton = common.findCancelButton = UI.findCancelButton;
|
||||
var findOKButton = common.findOKButton = UI.findOKButton;
|
||||
var listenForKeys = common.listenForKeys = UI.listenForKeys;
|
||||
var stopListening = common.stopListening = UI.stopListening;
|
||||
common.findCancelButton = UI.findCancelButton;
|
||||
common.findOKButton = UI.findOKButton;
|
||||
common.listenForKeys = UI.listenForKeys;
|
||||
common.stopListening = UI.stopListening;
|
||||
common.prompt = UI.prompt;
|
||||
common.confirm = UI.confirm;
|
||||
common.alert = UI.alert;
|
||||
|
@ -52,19 +56,22 @@ define([
|
|||
common.addLoadingScreen = UI.addLoadingScreen;
|
||||
common.removeLoadingScreen = UI.removeLoadingScreen;
|
||||
common.errorLoadingScreen = UI.errorLoadingScreen;
|
||||
common.notify = UI.notify;
|
||||
common.unnotify = UI.unnotify;
|
||||
|
||||
// import common utilities for export
|
||||
var find = common.find = Util.find;
|
||||
common.find = Util.find;
|
||||
var fixHTML = common.fixHTML = Util.fixHTML;
|
||||
var hexToBase64 = common.hexToBase64 = Util.hexToBase64;
|
||||
var base64ToHex = common.base64ToHex = Util.base64ToHex;
|
||||
common.hexToBase64 = Util.hexToBase64;
|
||||
common.base64ToHex = Util.base64ToHex;
|
||||
var deduplicateString = common.deduplicateString = Util.deduplicateString;
|
||||
var uint8ArrayToHex = common.uint8ArrayToHex = Util.uint8ArrayToHex;
|
||||
var replaceHash = common.replaceHash = Util.replaceHash;
|
||||
common.uint8ArrayToHex = Util.uint8ArrayToHex;
|
||||
common.replaceHash = Util.replaceHash;
|
||||
var getHash = common.getHash = Util.getHash;
|
||||
var fixFileName = common.fixFileName = Util.fixFileName;
|
||||
common.fixFileName = Util.fixFileName;
|
||||
common.bytesToMegabytes = Util.bytesToMegabytes;
|
||||
common.bytesToKilobytes = Util.bytesToKilobytes;
|
||||
common.fetch = Util.fetch;
|
||||
|
||||
// import hash utilities for export
|
||||
var createRandomHash = common.createRandomHash = Hash.createRandomHash;
|
||||
|
@ -73,14 +80,29 @@ define([
|
|||
var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId;
|
||||
var parseHash = common.parseHash = Hash.parseHash;
|
||||
var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref;
|
||||
common.getBlobPathFromHex = Hash.getBlobPathFromHex;
|
||||
|
||||
common.getEditHashFromKeys = Hash.getEditHashFromKeys;
|
||||
common.getViewHashFromKeys = Hash.getViewHashFromKeys;
|
||||
common.getFileHashFromKeys = Hash.getFileHashFromKeys;
|
||||
common.getSecrets = Hash.getSecrets;
|
||||
common.getHashes = Hash.getHashes;
|
||||
common.createChannelId = Hash.createChannelId;
|
||||
common.findWeaker = Hash.findWeaker;
|
||||
common.findStronger = Hash.findStronger;
|
||||
common.serializeHash = Hash.serializeHash;
|
||||
|
||||
// Userlist
|
||||
common.createUserList = UserList.create;
|
||||
|
||||
// Title
|
||||
common.createTitle = Title.create;
|
||||
|
||||
// Metadata
|
||||
common.createMetadata = Metadata.create;
|
||||
|
||||
// CodeMirror
|
||||
common.createCodemirror = CodeMirror.create;
|
||||
|
||||
// History
|
||||
common.getHistory = function (config) { return History.create(common, config); };
|
||||
|
@ -119,13 +141,13 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var reportAppUsage = common.reportAppUsage = function () {
|
||||
common.reportAppUsage = function () {
|
||||
var pattern = window.location.pathname.split('/')
|
||||
.filter(function (x) { return x; }).join('.');
|
||||
feedback(pattern);
|
||||
};
|
||||
|
||||
var getUid = common.getUid = function () {
|
||||
common.getUid = function () {
|
||||
if (store && store.getProxy() && store.getProxy().proxy) {
|
||||
return store.getProxy().proxy.uid;
|
||||
}
|
||||
|
@ -150,7 +172,7 @@ define([
|
|||
}, 0);
|
||||
};
|
||||
|
||||
var getWebsocketURL = common.getWebsocketURL = function () {
|
||||
common.getWebsocketURL = function () {
|
||||
if (!Config.websocketPath) { return Config.websocketURL; }
|
||||
var path = Config.websocketPath;
|
||||
if (/^ws{1,2}:\/\//.test(path)) { return path; }
|
||||
|
@ -162,9 +184,10 @@ define([
|
|||
return url;
|
||||
};
|
||||
|
||||
var login = common.login = function (hash, name, cb) {
|
||||
common.login = function (hash, name, cb) {
|
||||
if (!hash) { throw new Error('expected a user hash'); }
|
||||
if (!name) { throw new Error('expected a user name'); }
|
||||
hash = common.serializeHash(hash);
|
||||
localStorage.setItem(userHashKey, hash);
|
||||
localStorage.setItem(userNameKey, name);
|
||||
if (cb) { cb(); }
|
||||
|
@ -185,10 +208,11 @@ define([
|
|||
};
|
||||
|
||||
var logoutHandlers = [];
|
||||
var logout = common.logout = function (cb) {
|
||||
common.logout = function (cb) {
|
||||
[
|
||||
userNameKey,
|
||||
userHashKey,
|
||||
'loginToken',
|
||||
].forEach(function (k) {
|
||||
sessionStorage.removeItem(k);
|
||||
localStorage.removeItem(k);
|
||||
|
@ -208,18 +232,19 @@ define([
|
|||
|
||||
if (cb) { cb(); }
|
||||
};
|
||||
var onLogout = common.onLogout = function (h) {
|
||||
common.onLogout = function (h) {
|
||||
if (typeof (h) !== "function") { return; }
|
||||
if (logoutHandlers.indexOf(h) !== -1) { return; }
|
||||
logoutHandlers.push(h);
|
||||
};
|
||||
|
||||
var getUserHash = common.getUserHash = function () {
|
||||
var hash;
|
||||
[sessionStorage, localStorage].some(function (s) {
|
||||
var h = s[userHashKey];
|
||||
if (h) { return (hash = h); }
|
||||
});
|
||||
var hash = localStorage[userHashKey];
|
||||
|
||||
if (hash) {
|
||||
var sHash = common.serializeHash(hash);
|
||||
if (sHash !== hash) { localStorage[userHashKey] = sHash; }
|
||||
}
|
||||
|
||||
return hash;
|
||||
};
|
||||
|
@ -228,7 +253,7 @@ define([
|
|||
return typeof getUserHash() === "string";
|
||||
};
|
||||
|
||||
var hasSigningKeys = common.hasSigningKeys = function (proxy) {
|
||||
common.hasSigningKeys = function (proxy) {
|
||||
return typeof(proxy) === 'object' &&
|
||||
typeof(proxy.edPrivate) === 'string' &&
|
||||
typeof(proxy.edPublic) === 'string';
|
||||
|
@ -295,16 +320,20 @@ define([
|
|||
pads.forEach(function (pad, i) {
|
||||
if (pad && typeof(pad) === 'object') {
|
||||
var hash = checkObjectData(pad);
|
||||
if (!hash || !common.parseHash(hash)) { return; }
|
||||
if (!hash || !common.parseHash(hash)) {
|
||||
console.error("[Cryptpad.checkRecentPads] pad had unexpected value", pad);
|
||||
getStore().removeData(i);
|
||||
return;
|
||||
}
|
||||
return pad;
|
||||
}
|
||||
console.error("[Cryptpad.migrateRecentPads] pad had unexpected value");
|
||||
console.error("[Cryptpad.checkRecentPads] pad had unexpected value", pad);
|
||||
getStore().removeData(i);
|
||||
});
|
||||
};
|
||||
|
||||
// Get the pads from localStorage to migrate them to the object store
|
||||
var getLegacyPads = common.getLegacyPads = function (cb) {
|
||||
common.getLegacyPads = function (cb) {
|
||||
require(['/customize/store.js'], function(Legacy) { // TODO DEPRECATE_F
|
||||
Legacy.ready(function (err, legacy) {
|
||||
if (err) { cb(err, null); return; }
|
||||
|
@ -324,7 +353,6 @@ define([
|
|||
// Create untitled documents when no name is given
|
||||
var getDefaultName = common.getDefaultName = function (parsed) {
|
||||
var type = parsed.type;
|
||||
var untitledIndex = 1;
|
||||
var name = (Messages.type)[type] + ' - ' + new Date().toString().split(' ').slice(0,4).join(' ');
|
||||
return name;
|
||||
};
|
||||
|
@ -344,37 +372,37 @@ define([
|
|||
};
|
||||
|
||||
/* Sort pads according to how recently they were accessed */
|
||||
var mostRecent = common.mostRecent = function (a, b) {
|
||||
common.mostRecent = function (a, b) {
|
||||
return new Date(b.atime).getTime() - new Date(a.atime).getTime();
|
||||
};
|
||||
|
||||
// STORAGE
|
||||
var setPadAttribute = common.setPadAttribute = function (attr, value, cb) {
|
||||
common.setPadAttribute = function (attr, value, cb) {
|
||||
getStore().setDrive([getHash(), attr].join('.'), value, function (err, data) {
|
||||
cb(err, data);
|
||||
});
|
||||
};
|
||||
var setAttribute = common.setAttribute = function (attr, value, cb) {
|
||||
common.setAttribute = function (attr, value, cb) {
|
||||
getStore().set(["cryptpad", attr].join('.'), value, function (err, data) {
|
||||
cb(err, data);
|
||||
});
|
||||
};
|
||||
var setLSAttribute = common.setLSAttribute = function (attr, value) {
|
||||
common.setLSAttribute = function (attr, value) {
|
||||
localStorage[attr] = value;
|
||||
};
|
||||
|
||||
// STORAGE
|
||||
var getPadAttribute = common.getPadAttribute = function (attr, cb) {
|
||||
common.getPadAttribute = function (attr, cb) {
|
||||
getStore().getDrive([getHash(), attr].join('.'), function (err, data) {
|
||||
cb(err, data);
|
||||
});
|
||||
};
|
||||
var getAttribute = common.getAttribute = function (attr, cb) {
|
||||
common.getAttribute = function (attr, cb) {
|
||||
getStore().get(["cryptpad", attr].join('.'), function (err, data) {
|
||||
cb(err, data);
|
||||
});
|
||||
};
|
||||
var getLSAttribute = common.getLSAttribute = function (attr) {
|
||||
common.getLSAttribute = function (attr) {
|
||||
return localStorage[attr];
|
||||
};
|
||||
|
||||
|
@ -389,19 +417,19 @@ define([
|
|||
});
|
||||
return templates;
|
||||
};
|
||||
var addTemplate = common.addTemplate = function (data) {
|
||||
common.addTemplate = function (data) {
|
||||
getStore().pushData(data);
|
||||
getStore().addPad(data.href, ['template']);
|
||||
};
|
||||
|
||||
var isTemplate = common.isTemplate = function (href) {
|
||||
common.isTemplate = function (href) {
|
||||
var rhref = getRelativeHref(href);
|
||||
var templates = listTemplates();
|
||||
return templates.some(function (t) {
|
||||
return t.href === rhref;
|
||||
});
|
||||
};
|
||||
var selectTemplate = common.selectTemplate = function (type, rt, Crypt) {
|
||||
common.selectTemplate = function (type, rt, Crypt) {
|
||||
if (!AppConfig.enableTemplates) { return; }
|
||||
var temps = listTemplates(type);
|
||||
if (temps.length === 0) { return; }
|
||||
|
@ -419,7 +447,7 @@ define([
|
|||
Crypt.get(parsed.hash, function (err, val) {
|
||||
if (err) { throw new Error(err); }
|
||||
var p = parsePadUrl(window.location.href);
|
||||
Crypt.put(p.hash, val, function (e) {
|
||||
Crypt.put(p.hash, val, function () {
|
||||
common.findOKButton().click();
|
||||
common.removeLoadingScreen();
|
||||
});
|
||||
|
@ -444,28 +472,28 @@ define([
|
|||
};
|
||||
|
||||
// STORAGE: Display Name
|
||||
var getLastName = common.getLastName = function (cb) {
|
||||
common.getLastName = function (cb) {
|
||||
common.getAttribute('username', function (err, userName) {
|
||||
cb(err, userName);
|
||||
});
|
||||
};
|
||||
var _onDisplayNameChanged = [];
|
||||
var onDisplayNameChanged = common.onDisplayNameChanged = function (h) {
|
||||
common.onDisplayNameChanged = function (h) {
|
||||
if (typeof(h) !== "function") { return; }
|
||||
if (_onDisplayNameChanged.indexOf(h) !== -1) { return; }
|
||||
_onDisplayNameChanged.push(h);
|
||||
};
|
||||
var changeDisplayName = common.changeDisplayName = function (newName) {
|
||||
common.changeDisplayName = function (newName) {
|
||||
_onDisplayNameChanged.forEach(function (h) {
|
||||
h(newName);
|
||||
});
|
||||
};
|
||||
|
||||
// STORAGE
|
||||
var forgetPad = common.forgetPad = function (href, cb) {
|
||||
common.forgetPad = function (href, cb) {
|
||||
var parsed = parsePadUrl(href);
|
||||
|
||||
var callback = function (err, data) {
|
||||
var callback = function (err) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
|
@ -495,7 +523,19 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var setPadTitle = common.setPadTitle = function (name, cb) {
|
||||
var updateFileName = function (href, oldName, newName) {
|
||||
var fo = getStore().getProxy().fo;
|
||||
var paths = fo.findFileInRoot(href);
|
||||
paths.forEach(function (path) {
|
||||
if (path.length !== 2) { return; }
|
||||
var name = path[1].split('_')[0];
|
||||
var parsed = parsePadUrl(href);
|
||||
if (path.length === 2 && name === oldName && isDefaultName(parsed, name)) {
|
||||
fo.rename(path, newName);
|
||||
}
|
||||
});
|
||||
};
|
||||
common.setPadTitle = function (name, cb) {
|
||||
var href = window.location.href;
|
||||
var parsed = parsePadUrl(href);
|
||||
href = getRelativeHref(href);
|
||||
|
@ -509,12 +549,12 @@ define([
|
|||
|
||||
var updateWeaker = [];
|
||||
var contains;
|
||||
var renamed = recent.map(function (pad) {
|
||||
recent.forEach(function (pad) {
|
||||
var p = parsePadUrl(pad.href);
|
||||
|
||||
if (p.type !== parsed.type) { return pad; }
|
||||
|
||||
var shouldUpdate = p.hash === parsed.hash;
|
||||
var shouldUpdate = p.hash.replace(/\/$/, '') === parsed.hash.replace(/\/$/, '');
|
||||
|
||||
// Version 1 : we have up to 4 differents hash for 1 pad, keep the strongest :
|
||||
// Edit > Edit (present) > View > View (present)
|
||||
|
@ -540,6 +580,7 @@ define([
|
|||
pad.atime = +new Date();
|
||||
|
||||
// set the name
|
||||
var old = pad.title;
|
||||
pad.title = name;
|
||||
|
||||
// If we now have a stronger version of a stored href, replace the weaker one by the strong one
|
||||
|
@ -550,14 +591,23 @@ define([
|
|||
});
|
||||
}
|
||||
pad.href = href;
|
||||
updateFileName(href, old, name);
|
||||
}
|
||||
return pad;
|
||||
});
|
||||
|
||||
if (!contains) {
|
||||
if (!contains && href) {
|
||||
var data = makePad(href, name);
|
||||
getStore().pushData(data);
|
||||
getStore().addPad(href, common.initialPath, common.initialName || name);
|
||||
getStore().pushData(data, function (e) {
|
||||
if (e) {
|
||||
if (e === 'E_OVER_LIMIT' && AppConfig.enablePinLimit) {
|
||||
common.alert(Messages.pinLimitNotPinned, null, true);
|
||||
return;
|
||||
}
|
||||
else { throw new Error("Cannot push this pad to CryptDrive", e); }
|
||||
}
|
||||
getStore().addPad(data, common.initialPath);
|
||||
});
|
||||
}
|
||||
if (updateWeaker.length > 0) {
|
||||
updateWeaker.forEach(function (obj) {
|
||||
|
@ -584,7 +634,7 @@ define([
|
|||
/*
|
||||
* Buttons
|
||||
*/
|
||||
var renamePad = common.renamePad = function (title, callback) {
|
||||
common.renamePad = function (title, callback) {
|
||||
if (title === null) { return; }
|
||||
|
||||
if (title.trim() === "") {
|
||||
|
@ -592,7 +642,7 @@ define([
|
|||
title = getDefaultName(parsed);
|
||||
}
|
||||
|
||||
common.setPadTitle(title, function (err, data) {
|
||||
common.setPadTitle(title, function (err) {
|
||||
if (err) {
|
||||
console.log("unable to set pad title");
|
||||
console.log(err);
|
||||
|
@ -642,7 +692,7 @@ define([
|
|||
return true;
|
||||
};
|
||||
|
||||
var arePinsSynced = common.arePinsSynced = function (cb) {
|
||||
common.arePinsSynced = function (cb) {
|
||||
if (!pinsReady()) { return void cb ('[RPC_NOT_READY]'); }
|
||||
|
||||
var list = getCanonicalChannelList();
|
||||
|
@ -653,7 +703,7 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var resetPins = common.resetPins = function (cb) {
|
||||
common.resetPins = function (cb) {
|
||||
if (!pinsReady()) { return void cb ('[RPC_NOT_READY]'); }
|
||||
|
||||
var list = getCanonicalChannelList();
|
||||
|
@ -663,7 +713,7 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var pinPads = common.pinPads = function (pads, cb) {
|
||||
common.pinPads = function (pads, cb) {
|
||||
if (!pinsReady()) { return void cb ('[RPC_NOT_READY]'); }
|
||||
|
||||
rpc.pin(pads, function (e, hash) {
|
||||
|
@ -672,7 +722,7 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var unpinPads = common.unpinPads = function (pads, cb) {
|
||||
common.unpinPads = function (pads, cb) {
|
||||
if (!pinsReady()) { return void cb ('[RPC_NOT_READY]'); }
|
||||
|
||||
rpc.unpin(pads, function (e, hash) {
|
||||
|
@ -681,12 +731,12 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var getPinnedUsage = common.getPinnedUsage = function (cb) {
|
||||
common.getPinnedUsage = function (cb) {
|
||||
if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); }
|
||||
rpc.getFileListSize(cb);
|
||||
};
|
||||
|
||||
var getFileSize = common.getFileSize = function (href, cb) {
|
||||
common.getFileSize = function (href, cb) {
|
||||
var channelId = Hash.hrefToHexChannelId(href);
|
||||
rpc.getFileSize(channelId, function (e, bytes) {
|
||||
if (e) { return void cb(e); }
|
||||
|
@ -694,7 +744,30 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var createButton = common.createButton = function (type, rightside, data, callback) {
|
||||
common.getPinLimit = function (cb) {
|
||||
cb(void 0, typeof(AppConfig.pinLimit) === 'number'? AppConfig.pinLimit: 1000);
|
||||
};
|
||||
|
||||
common.isOverPinLimit = function (cb) {
|
||||
if (!common.isLoggedIn() || !AppConfig.enablePinLimit) { return void cb(null, false); }
|
||||
var usage;
|
||||
var andThen = function (e, limit) {
|
||||
if (e) { return void cb(e); }
|
||||
var data = {usage: usage, limit: limit};
|
||||
if (usage > limit) {
|
||||
return void cb (null, true, data);
|
||||
}
|
||||
return void cb (null, false, data);
|
||||
};
|
||||
var todo = function (e, used) {
|
||||
usage = common.bytesToMegabytes(used);
|
||||
if (e) { return void cb(e); }
|
||||
common.getPinLimit(andThen);
|
||||
};
|
||||
common.getPinnedUsage(todo);
|
||||
};
|
||||
|
||||
common.createButton = function (type, rightside, data, callback) {
|
||||
var button;
|
||||
var size = "17px";
|
||||
switch (type) {
|
||||
|
@ -783,7 +856,7 @@ define([
|
|||
var href = window.location.href;
|
||||
common.confirm(Messages.forgetPrompt, function (yes) {
|
||||
if (!yes) { return; }
|
||||
common.forgetPad(href, function (err, data) {
|
||||
common.forgetPad(href, function (err) {
|
||||
if (err) {
|
||||
console.log("unable to forget pad");
|
||||
console.error(err);
|
||||
|
@ -1002,7 +1075,7 @@ define([
|
|||
|
||||
// Provide $container if you want to put the generated block in another element
|
||||
// Provide $initBlock if you already have the menu block and you want the content inserted in it
|
||||
var createLanguageSelector = common.createLanguageSelector = function ($container, $initBlock) {
|
||||
common.createLanguageSelector = function ($container, $initBlock) {
|
||||
var options = [];
|
||||
var languages = Messages._languages;
|
||||
var keys = Object.keys(languages).sort();
|
||||
|
@ -1036,7 +1109,7 @@ define([
|
|||
return $block;
|
||||
};
|
||||
|
||||
var createUserAdminMenu = common.createUserAdminMenu = function (config) {
|
||||
common.createUserAdminMenu = function (config) {
|
||||
var $displayedName = $('<span>', {'class': config.displayNameCls || 'displayName'});
|
||||
var accountName = localStorage[common.userNameKey];
|
||||
var account = isLoggedIn();
|
||||
|
@ -1123,24 +1196,24 @@ define([
|
|||
};
|
||||
var $userAdmin = createDropdown(dropdownConfigUser);
|
||||
|
||||
$userAdmin.find('a.logout').click(function (e) {
|
||||
$userAdmin.find('a.logout').click(function () {
|
||||
common.logout();
|
||||
window.location.href = '/';
|
||||
});
|
||||
$userAdmin.find('a.settings').click(function (e) {
|
||||
$userAdmin.find('a.settings').click(function () {
|
||||
if (parsed && parsed.type) {
|
||||
window.open('/settings/');
|
||||
} else {
|
||||
window.location.href = '/settings/';
|
||||
}
|
||||
});
|
||||
$userAdmin.find('a.login').click(function (e) {
|
||||
$userAdmin.find('a.login').click(function () {
|
||||
if (window.location.pathname !== "/") {
|
||||
sessionStorage.redirectTo = window.location.href;
|
||||
}
|
||||
window.location.href = '/login/';
|
||||
});
|
||||
$userAdmin.find('a.register').click(function (e) {
|
||||
$userAdmin.find('a.register').click(function () {
|
||||
if (window.location.pathname !== "/") {
|
||||
sessionStorage.redirectTo = window.location.href;
|
||||
}
|
||||
|
@ -1189,6 +1262,11 @@ define([
|
|||
if (typeof(window.Proxy) === 'undefined') {
|
||||
feedback("NO_PROXIES");
|
||||
}
|
||||
|
||||
if (typeof(Array.isArray) !== 'function') {
|
||||
feedback("NO_ISARRAY");
|
||||
}
|
||||
|
||||
$(function() {
|
||||
// Race condition : if document.body is undefined when alertify.js is loaded, Alertify
|
||||
// won't work. We have to reset it now to make sure it uses a correct "body"
|
||||
|
@ -1227,7 +1305,8 @@ define([
|
|||
|
||||
common.arePinsSynced(function (err, yes) {
|
||||
if (!yes) {
|
||||
common.resetPins(function (err, hash) {
|
||||
common.resetPins(function (err) {
|
||||
if (err) { console.error(err); }
|
||||
console.log('RESET DONE');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}, '');
|
||||
|
|
|
@ -10,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;
|
||||
|
||||
|
@ -18,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);
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ define([
|
|||
console.error.apply(console, arguments);
|
||||
};
|
||||
|
||||
var getStructure = exp.getStructure = function () {
|
||||
exp.getStructure = function () {
|
||||
var a = {};
|
||||
a[ROOT] = {};
|
||||
a[UNSORTED] = [];
|
||||
|
@ -92,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;
|
||||
};
|
||||
|
||||
|
@ -100,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; }
|
||||
|
@ -114,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) {
|
||||
|
@ -137,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) {
|
||||
|
@ -189,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;
|
||||
};
|
||||
|
@ -205,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]) {
|
||||
|
@ -244,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 {
|
||||
|
@ -261,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) {
|
||||
|
@ -351,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
|
||||
|
@ -402,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)
|
||||
|
@ -509,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);
|
||||
|
@ -723,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
|
||||
|
@ -735,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; }
|
||||
|
@ -748,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] = {};
|
||||
|
@ -767,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();
|
||||
|
@ -799,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;
|
||||
|
@ -838,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();
|
||||
|
@ -860,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 = [];
|
||||
|
@ -889,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;
|
||||
|
@ -914,7 +910,7 @@ define([
|
|||
};
|
||||
|
||||
|
||||
var forgetPad = exp.forgetPad = function (href) {
|
||||
exp.forgetPad = function (href) {
|
||||
if (workgroup) { return; }
|
||||
if (!href || !isFile(href)) { return; }
|
||||
var path;
|
||||
|
@ -985,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);
|
||||
|
||||
|
@ -1012,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");
|
||||
|
@ -1031,7 +1027,7 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var listTemplates = exp.listTemplates = function (type) {
|
||||
exp.listTemplates = function () {
|
||||
if (workgroup) { return; }
|
||||
var templateFiles = getTemplateFiles();
|
||||
var res = [];
|
||||
|
@ -1049,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
|
||||
|
@ -1138,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);
|
||||
|
|
|
@ -88,8 +88,8 @@ define([
|
|||
ret.removeData = filesOp.removeData;
|
||||
ret.pushData = filesOp.pushData;
|
||||
|
||||
ret.addPad = function (href, path, name) {
|
||||
filesOp.add(href, path, name);
|
||||
ret.addPad = function (data, path) {
|
||||
filesOp.add(data, path);
|
||||
};
|
||||
|
||||
ret.forgetPad = function (href, cb) {
|
||||
|
@ -127,13 +127,21 @@ define([
|
|||
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
|
||||
|
@ -145,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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -157,19 +196,20 @@ define([
|
|||
|
||||
// if the user is logged in, but does not have signing keys...
|
||||
if (Cryptpad.isLoggedIn() && !Cryptpad.hasSigningKeys(proxy)) {
|
||||
// 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/';
|
||||
return;
|
||||
return void requestLogin();
|
||||
}
|
||||
|
||||
proxy.on('change', [Cryptpad.displayNameKey], function (o, n, p) {
|
||||
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;
|
||||
|
@ -197,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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -46,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]))) {
|
||||
|
@ -80,7 +80,7 @@ define([
|
|||
path.pop();
|
||||
}
|
||||
|
||||
var p, next, nextRoot;
|
||||
var next, nextRoot;
|
||||
path.forEach(function (p, i) {
|
||||
if (!root) { return; }
|
||||
if (typeof(p) === "string") {
|
||||
|
@ -128,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(); }
|
||||
|
|
|
@ -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,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, });
|
||||
|
|
|
@ -16,6 +16,8 @@ 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';
|
||||
|
||||
|
@ -94,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);
|
||||
|
@ -205,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
|
||||
|
@ -223,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); }
|
||||
});
|
||||
|
@ -372,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);
|
||||
|
@ -391,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) {
|
||||
|
@ -412,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;
|
||||
|
@ -474,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) {
|
||||
|
@ -504,7 +538,7 @@ define([
|
|||
// User dropdown
|
||||
if (config.displayed.indexOf('useradmin') !== -1) {
|
||||
var userMenuCfg = {};
|
||||
if (config.userData) {
|
||||
if (!config.hideDisplayName) {
|
||||
userMenuCfg = {
|
||||
displayNameCls: USERNAME_CLS,
|
||||
changeNameButtonCls: USERBUTTON_CLS,
|
||||
|
@ -524,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 = ''; }
|
||||
|
@ -791,7 +826,7 @@ define([
|
|||
if (!connected) { return; }
|
||||
checkLag(getLag, lagElement);
|
||||
}, 3000);
|
||||
}
|
||||
} else { connected = true; }
|
||||
|
||||
var failed = function () {
|
||||
connected = false;
|
||||
|
|
|
@ -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.pinLimitReachedAlert, 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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
define([
|
||||
'jquery',
|
||||
], function ($) {
|
||||
'/customize/application_config.js'
|
||||
], function ($, AppConfig) {
|
||||
var module = {};
|
||||
|
||||
var ROOT = module.ROOT = "root";
|
||||
|
@ -8,7 +9,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 exp = {};
|
||||
var Cryptpad = config.Cryptpad;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
@ -18,7 +19,6 @@ define([
|
|||
var NEW_FILE_NAME = Messages.fm_newFile;
|
||||
|
||||
// Logging
|
||||
var DEBUG = config.DEBUG || false;
|
||||
var logging = function () {
|
||||
console.log.apply(console, arguments);
|
||||
};
|
||||
|
@ -38,17 +38,16 @@ define([
|
|||
* UTILS
|
||||
*/
|
||||
|
||||
var getStructure = exp.getStructure = function () {
|
||||
exp.getStructure = function () {
|
||||
var a = {};
|
||||
a[ROOT] = {};
|
||||
a[UNSORTED] = [];
|
||||
a[TRASH] = {};
|
||||
a[FILES_DATA] = [];
|
||||
a[TEMPLATE] = [];
|
||||
return a;
|
||||
};
|
||||
var getHrefArray = function () {
|
||||
return [UNSORTED, TEMPLATE];
|
||||
return [TEMPLATE];
|
||||
};
|
||||
|
||||
|
||||
|
@ -58,7 +57,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; }
|
||||
|
@ -69,17 +68,17 @@ define([
|
|||
};
|
||||
|
||||
var isFolder = exp.isFolder = function (element) {
|
||||
return typeof(element) !== "string";
|
||||
return typeof(element) === "object";
|
||||
};
|
||||
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) {
|
||||
|
@ -94,10 +93,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) {
|
||||
|
@ -232,7 +231,7 @@ define([
|
|||
_getFiles[TRASH] = 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 {
|
||||
|
@ -297,6 +296,9 @@ define([
|
|||
|
||||
return paths;
|
||||
};
|
||||
exp.findFileInRoot = function (href) {
|
||||
return _findFileInRoot([ROOT], href);
|
||||
};
|
||||
var _findFileInHrefArray = function (rootName, href) {
|
||||
var unsorted = files[rootName].slice();
|
||||
var ret = [];
|
||||
|
@ -345,12 +347,11 @@ define([
|
|||
};
|
||||
var findFile = exp.findFile = function (href) {
|
||||
var rootpaths = _findFileInRoot([ROOT], href);
|
||||
var unsortedpaths = _findFileInHrefArray(UNSORTED, href);
|
||||
var templatepaths = _findFileInHrefArray(TEMPLATE, href);
|
||||
var trashpaths = _findFileInTrash([TRASH], href);
|
||||
return rootpaths.concat(unsortedpaths, templatepaths, trashpaths);
|
||||
return rootpaths.concat(templatepaths, trashpaths);
|
||||
};
|
||||
var search = exp.search = function (value) {
|
||||
exp.search = function (value) {
|
||||
if (typeof(value) !== "string") { return []; }
|
||||
var res = [];
|
||||
// Search in ROOT
|
||||
|
@ -401,7 +402,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)
|
||||
|
@ -426,19 +427,24 @@ define([
|
|||
};
|
||||
|
||||
// FILES DATA
|
||||
var pushFileData = exp.pushData = function (data) {
|
||||
Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) {
|
||||
if (e) { console.log(e); return; }
|
||||
console.log(hash);
|
||||
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();
|
||||
});
|
||||
files[FILES_DATA].push(data);
|
||||
};
|
||||
var spliceFileData = exp.removeData = function (idx) {
|
||||
var data = files[FILES_DATA][idx];
|
||||
if (typeof data === "object") {
|
||||
if (typeof data === "object" && Cryptpad.isLoggedIn() && AppConfig.enablePinning) {
|
||||
Cryptpad.unpinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e, hash) {
|
||||
if (e) { console.log(e); return; }
|
||||
console.log(hash);
|
||||
if (e) { return void logError(e); }
|
||||
debug('UNPIN', hash);
|
||||
});
|
||||
}
|
||||
files[FILES_DATA].splice(idx, 1);
|
||||
|
@ -524,7 +530,7 @@ define([
|
|||
});
|
||||
exp.delete(toRemove, cb);
|
||||
};
|
||||
var restore = exp.restore = function (path, cb) {
|
||||
exp.restore = function (path, cb) {
|
||||
if (!isInTrashRoot(path)) { return; }
|
||||
var parentPath = path.slice();
|
||||
parentPath.pop();
|
||||
|
@ -534,8 +540,10 @@ define([
|
|||
|
||||
|
||||
// ADD
|
||||
var add = exp.add = function (href, path, name, cb) {
|
||||
if (!href) { return; }
|
||||
var add = exp.add = function (data, path) {
|
||||
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(',');
|
||||
|
@ -546,53 +554,50 @@ define([
|
|||
parentEl.push(href);
|
||||
return;
|
||||
}
|
||||
// Add to root
|
||||
if (path && isPathIn(newPath, [ROOT]) && name) {
|
||||
parentEl = find(newPath);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// No path: push to unsorted
|
||||
var filesList = getFiles([ROOT, TRASH, 'hrefArray']);
|
||||
if (filesList.indexOf(href) === -1) { files[UNSORTED].push(href); }
|
||||
|
||||
if (typeof cb === "function") { cb(); }
|
||||
};
|
||||
var addFile = exp.addFile = function (filePath, name, type, cb) {
|
||||
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();
|
||||
parentEl[fileName] = href;
|
||||
|
||||
pushFileData({
|
||||
href: href,
|
||||
title: fileName,
|
||||
atime: +new Date(),
|
||||
ctime: +new Date()
|
||||
});
|
||||
|
||||
var newPath = filePath.slice();
|
||||
newPath.push(fileName);
|
||||
cb({
|
||||
newPath: newPath
|
||||
}, function (err) {
|
||||
if (err) { return void cb(err); }
|
||||
parentEl[fileName] = href;
|
||||
var newPath = filePath.slice();
|
||||
newPath.push(fileName);
|
||||
cb(void 0, {
|
||||
newPath: newPath
|
||||
});
|
||||
});
|
||||
};
|
||||
var addFolder = exp.addFolder = function (folderPath, name, cb) {
|
||||
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({
|
||||
cb(void 0, {
|
||||
newPath: newPath
|
||||
});
|
||||
};
|
||||
|
||||
// FORGET (move with href not path)
|
||||
var forget = exp.forget = function (href) {
|
||||
exp.forget = function (href) {
|
||||
var paths = findFile(href);
|
||||
move(paths, [TRASH]);
|
||||
};
|
||||
|
@ -601,6 +606,10 @@ define([
|
|||
// Permanently delete multiple files at once using a list of paths
|
||||
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
|
||||
var removePadAttribute = function (f) {
|
||||
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) {
|
||||
|
@ -690,18 +699,18 @@ define([
|
|||
// FILES_DATA (replaceHref)
|
||||
if (!nocheck) { checkDeletedFiles(); }
|
||||
};
|
||||
var deletePath = exp.delete = function (paths, cb, nocheck) {
|
||||
exp.delete = function (paths, cb, nocheck) {
|
||||
deleteMultiplePermanently(paths, nocheck);
|
||||
if (typeof cb === "function") { cb(); }
|
||||
};
|
||||
var emptyTrash = exp.emptyTrash = function (cb) {
|
||||
exp.emptyTrash = function (cb) {
|
||||
files[TRASH] = {};
|
||||
checkDeletedFiles();
|
||||
if(cb) { cb(); }
|
||||
};
|
||||
|
||||
// RENAME
|
||||
var rename = exp.rename = function (path, newName, cb) {
|
||||
exp.rename = function (path, newName, cb) {
|
||||
if (path.length <= 1) {
|
||||
logError('Renaming `root` is forbidden');
|
||||
return;
|
||||
|
@ -722,7 +731,7 @@ define([
|
|||
parentEl[newName] = element;
|
||||
parentEl[oldName] = undefined;
|
||||
delete parentEl[oldName];
|
||||
cb();
|
||||
if (typeof cb === "function") { cb(); }
|
||||
};
|
||||
|
||||
// REPLACE
|
||||
|
@ -743,7 +752,7 @@ define([
|
|||
}
|
||||
};
|
||||
// Replace a href by a stronger one everywhere in the drive (except FILES_DATA)
|
||||
var replaceHref = exp.replace = function (o, n) {
|
||||
exp.replace = function (o, n) {
|
||||
if (!isFile(o) || !isFile(n)) { return; }
|
||||
var paths = findFile(o);
|
||||
|
||||
|
@ -772,7 +781,7 @@ define([
|
|||
* INTEGRITY CHECK
|
||||
*/
|
||||
|
||||
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
|
||||
|
@ -780,7 +789,7 @@ define([
|
|||
// * 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'
|
||||
// * UNSORTED: Contains only files (href), and does not contains files that are in ROOT
|
||||
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
|
||||
debug("Cleaning file system...");
|
||||
|
||||
var before = JSON.stringify(files);
|
||||
|
@ -821,26 +830,37 @@ define([
|
|||
}
|
||||
}
|
||||
};
|
||||
// Make sure unsorted doesn't exist anymore
|
||||
var fixUnsorted = function () {
|
||||
if (!Array.isArray(files[UNSORTED])) { debug("UNSORTED was not an array"); files[UNSORTED] = []; }
|
||||
files[UNSORTED] = Cryptpad.deduplicateString(files[UNSORTED].slice());
|
||||
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 toClean = [];
|
||||
us.forEach(function (el, idx) {
|
||||
//var toClean = [];
|
||||
var root = find([ROOT]);
|
||||
us.forEach(function (el) {
|
||||
if (!isFile(el) || rootFiles.indexOf(el) !== -1) {
|
||||
toClean.push(idx);
|
||||
return;
|
||||
//toClean.push(idx);
|
||||
}
|
||||
var name = getFileData(el).title || NEW_FILE_NAME;
|
||||
var newName = getAvailableName(root, name);
|
||||
root[newName] = el;
|
||||
});
|
||||
toClean.forEach(function (idx) {
|
||||
delete files[UNSORTED];
|
||||
/*toClean.forEach(function (idx) {
|
||||
us.splice(idx, 1);
|
||||
});
|
||||
});*/
|
||||
};
|
||||
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, UNSORTED]).slice();
|
||||
var rootFiles = getFiles([ROOT]).slice();
|
||||
var toClean = [];
|
||||
us.forEach(function (el, idx) {
|
||||
if (!isFile(el) || rootFiles.indexOf(el) !== -1) {
|
||||
|
@ -855,16 +875,24 @@ define([
|
|||
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, idx) {
|
||||
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 (rootFiles.indexOf(el.href) === -1) {
|
||||
debug("An element in filesData was not in ROOT, UNSORTED or TRASH.", el);
|
||||
files[UNSORTED].push(el.href);
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
@ -215,6 +228,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;
|
||||
|
@ -242,7 +262,7 @@ span.fa-folder-open {
|
|||
#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 +309,9 @@ span.fa-folder-open {
|
|||
width: 50px;
|
||||
font-size: 40px;
|
||||
}
|
||||
#content .element .truncated {
|
||||
display: none;
|
||||
}
|
||||
#content div.grid {
|
||||
padding: 20px;
|
||||
}
|
||||
|
@ -296,19 +319,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 +356,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 +366,9 @@ span.fa-folder-open {
|
|||
#content div.grid .listElement {
|
||||
display: none;
|
||||
}
|
||||
#content .list {
|
||||
padding-left: 20px;
|
||||
}
|
||||
#content .list ul {
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
|
|
@ -32,7 +32,7 @@ html, body {
|
|||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
font-size: 20px;
|
||||
font-size: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
@ -70,6 +70,10 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
div:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.fa {
|
||||
/*min-width: 17px;*/
|
||||
margin-right: 3px;
|
||||
|
@ -96,7 +100,7 @@ li {
|
|||
.contextMenu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
z-index: 500;
|
||||
li {
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
|
@ -121,6 +125,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;
|
||||
|
@ -260,6 +274,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;
|
||||
}
|
||||
|
@ -287,7 +308,7 @@ span {
|
|||
/*pointer-events: none;*/
|
||||
}
|
||||
&:hover {
|
||||
&:not(.selected) {
|
||||
&:not(.selected, .selectedTmp) {
|
||||
background-color: @drive-hover;
|
||||
}
|
||||
.name {
|
||||
|
@ -343,25 +364,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 +411,8 @@ span {
|
|||
.fa {
|
||||
display: block;
|
||||
margin: auto;
|
||||
font-size: 40px;
|
||||
font-size: 48px;
|
||||
margin: 8px 0;
|
||||
text-align: center;
|
||||
&.listonly {
|
||||
display: none;
|
||||
|
@ -384,6 +426,7 @@ span {
|
|||
|
||||
.list {
|
||||
// Make it act as a table!
|
||||
padding-left: 20px;
|
||||
ul {
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
|
|
@ -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
|
@ -169,6 +169,7 @@ define([
|
|||
|
||||
var $input = Input({
|
||||
placeholder: 'card description',
|
||||
id: id,
|
||||
})
|
||||
.addClass('card-title');
|
||||
|
||||
|
@ -206,7 +207,7 @@ define([
|
|||
|
||||
/*
|
||||
*/
|
||||
Card.move = function (uid, A, B) {
|
||||
Card.move = function (/*uid, A, B*/) {
|
||||
|
||||
};
|
||||
|
||||
|
@ -228,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);
|
||||
});
|
||||
|
|
|
@ -7,12 +7,12 @@ define([
|
|||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
//'/common/visible.js',
|
||||
//'/common/notify.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, Config, Messages, Board, TextPatcher, Listmap, Crypto, Cryptpad, Visible, Notify) {
|
||||
], function ($, Config, Messages, Board, TextPatcher, Listmap, Crypto, Cryptpad /*, Visible, Notify*/) {
|
||||
|
||||
var saveAs = window.saveAs;
|
||||
// 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,52 +0,0 @@
|
|||
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',
|
||||
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad, Visible, Notify) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
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();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var $mt = $iframe.find('#encryptedFile');
|
||||
$mt.attr('src', './assets/image.png-encrypted');
|
||||
$mt.attr('data-crypto-key', 'TBo77200c0e/FdldQFcnQx4Y');
|
||||
$mt.attr('data-type', 'image/png');
|
||||
require(['/common/media-tag.js'], function (MediaTag) {
|
||||
MediaTag($mt[0]);
|
||||
Cryptpad.removeLoadingScreen();
|
||||
var configTb = {
|
||||
displayed: ['useradmin', 'newpad'],
|
||||
ifrw: ifrw,
|
||||
common: Cryptpad
|
||||
};
|
||||
toolbar = Toolbar.create($bar, null, null, null, null, configTb);
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
Cryptpad.ready(function (err, anv) {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -128,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;
|
||||
|
||||
|
@ -140,7 +140,7 @@ define([
|
|||
};
|
||||
|
||||
var readValues = function () {
|
||||
UI.each(function (ui, i, list) {
|
||||
UI.each(function (ui) {
|
||||
Map[ui.id] = ui.value();
|
||||
});
|
||||
};
|
||||
|
@ -165,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();
|
||||
|
||||
|
@ -178,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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -195,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");
|
||||
|
@ -209,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,161 +0,0 @@
|
|||
define([
|
||||
'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'
|
||||
], function ($, Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
|
||||
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>
|
||||
|
||||
|
|
|
@ -26,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
|
||||
|
@ -42,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) {
|
||||
|
@ -51,7 +49,7 @@ define([
|
|||
return false;
|
||||
})
|
||||
// on(event, cb)
|
||||
.on('disconnect', function (info) {
|
||||
.on('disconnect', function () {
|
||||
setEditable(false);
|
||||
window.alert("Network connection lost");
|
||||
});
|
||||
|
@ -65,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();
|
||||
|
|
|
@ -3,7 +3,7 @@ define([
|
|||
'/common/cryptpad-common.js',
|
||||
'/common/pinpad.js'
|
||||
], function ($, Cryptpad, Pinpad) {
|
||||
var APP = window.APP = {
|
||||
window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
};
|
||||
|
||||
|
@ -37,7 +37,7 @@ define([
|
|||
};
|
||||
|
||||
$(function () {
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
var network = Cryptpad.getNetwork();
|
||||
var proxy = Cryptpad.getStore().getProxy().proxy;
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@ define([
|
|||
'jquery',
|
||||
'/common/cryptget.js'
|
||||
], function ($, Crypt) {
|
||||
|
||||
var $target = $('#target');
|
||||
var $dest = $('#dest');
|
||||
|
||||
var useDoc = function (err, doc) {
|
||||
if (err) { return console.error(err); }
|
||||
|
|
|
@ -55,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;
|
||||
};
|
||||
|
@ -73,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));
|
||||
|
@ -81,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 */
|
||||
|
@ -96,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);
|
||||
});
|
||||
|
|
|
@ -20,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;
|
||||
|
@ -37,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({
|
||||
|
@ -50,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);
|
||||
});
|
||||
|
|
|
@ -13,8 +13,6 @@ define([
|
|||
TextPatcher: TextPatcher
|
||||
};
|
||||
|
||||
var userName = module.userName = Crypto.rand64(8);
|
||||
|
||||
var initializing = true;
|
||||
var $textarea = $('textarea');
|
||||
|
||||
|
@ -30,14 +28,14 @@ define([
|
|||
|
||||
setEditable(false);
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
$(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());
|
||||
|
@ -59,7 +57,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
|
||||
|
@ -71,12 +69,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 {
|
||||
|
@ -85,7 +83,7 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
Realtime.start(config);
|
||||
|
||||
['cut', 'paste', 'change', 'keyup', 'keydown', 'select', 'textInput']
|
||||
.forEach(function (evt) {
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js'
|
||||
], function ($, Crypt, Crypto) {
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,188 @@
|
|||
define([
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
], function () {
|
||||
var Nacl = window.nacl;
|
||||
var PARANOIA = true;
|
||||
|
||||
var plainChunkLength = 128 * 1024;
|
||||
var cypherChunkLength = 131088;
|
||||
|
||||
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 Uint8Array(chunks.reduce(function (A, B) {
|
||||
return slice(A).concat(slice(B));
|
||||
}, []));
|
||||
};
|
||||
|
||||
var decrypt = function (u8, key, cb) {
|
||||
var fail = function (e) {
|
||||
cb(e || "DECRYPTION_ERROR");
|
||||
};
|
||||
|
||||
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 fail('E_METADATA_DECRYPTION');
|
||||
}
|
||||
|
||||
if (!res.metadata) {
|
||||
return void setTimeout(function () {
|
||||
cb('NO_METADATA');
|
||||
});
|
||||
}
|
||||
|
||||
var takeChunk = function () {
|
||||
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);
|
||||
return plaintext;
|
||||
};
|
||||
|
||||
var chunks = [];
|
||||
// decrypt file contents
|
||||
var chunk;
|
||||
for (;i * cypherChunkLength < u8.length;) {
|
||||
chunk = takeChunk();
|
||||
if (!chunk) {
|
||||
return window.setTimeout(fail);
|
||||
}
|
||||
chunks.push(chunk);
|
||||
}
|
||||
|
||||
// send chunks
|
||||
res.content = joinChunks(chunks);
|
||||
|
||||
cb(void 0, res);
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
/*
|
||||
0: metadata
|
||||
1: u8
|
||||
2: done
|
||||
*/
|
||||
|
||||
var state = 0;
|
||||
|
||||
var next = function (cb) {
|
||||
var start;
|
||||
var end;
|
||||
var part;
|
||||
var box;
|
||||
|
||||
// DONE
|
||||
if (state === 2) { return void cb(); }
|
||||
|
||||
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,
|
||||
};
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
<!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;
|
||||
}
|
||||
#file {
|
||||
display: block;
|
||||
height: 300px;
|
||||
width: 300px;
|
||||
border: 2px solid black;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
.inputfile {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
height: 500px;
|
||||
width: 500px;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.inputfile + label {
|
||||
border: 2px solid black;
|
||||
background-color: rgba(50, 50, 50, .10);
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
.inputfile:focus + label,
|
||||
.inputfile + label:hover {
|
||||
background-color: rgba(50, 50, 50, 0.30);
|
||||
}
|
||||
|
||||
</style>
|
||||
</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">Choose a file</label>
|
||||
</div>
|
||||
<div id="feedback" class="block hidden">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
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');
|
||||
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var Title;
|
||||
|
||||
var myFile;
|
||||
var myDataType;
|
||||
|
||||
var upload = function (blob, metadata) {
|
||||
console.log(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) {
|
||||
var enc = Nacl.util.encodeBase64(box);
|
||||
|
||||
chunks.push(box);
|
||||
Cryptpad.rpc.send('UPLOAD', enc, function (e, msg) {
|
||||
cb(e, msg);
|
||||
});
|
||||
};
|
||||
|
||||
var again = function (err, box) {
|
||||
if (err) { throw new Error(err); }
|
||||
if (box) {
|
||||
return void sendChunk(box, function (e) {
|
||||
if (e) { return console.error(e); }
|
||||
next(again);
|
||||
});
|
||||
}
|
||||
|
||||
// if not box then done
|
||||
Cryptpad.rpc.send('UPLOAD_COMPLETE', '', function (e, res) {
|
||||
if (e) { return void console.error(e); }
|
||||
var id = res[0];
|
||||
var uri = ['', 'blob', id.slice(0,2), id].join('/');
|
||||
console.log("encrypted blob is now available as %s", uri);
|
||||
|
||||
var b64Key = Nacl.util.encodeBase64(key);
|
||||
window.location.hash = Cryptpad.getFileHashFromKeys(id, b64Key);
|
||||
|
||||
$form.hide();
|
||||
|
||||
APP.toolbar.addElement(['fileshare'], {});
|
||||
|
||||
// check if the uploaded file can be decrypted
|
||||
var newU8 = FileCrypto.joinChunks(chunks);
|
||||
FileCrypto.decrypt(newU8, key, function (e, res) {
|
||||
if (e) { return console.error(e); }
|
||||
var title = document.title = res.metadata.name;
|
||||
myFile = res.content;
|
||||
myDataType = res.metadata.type;
|
||||
|
||||
var defaultName = Cryptpad.getDefaultName(Cryptpad.parsePadUrl(window.location.href));
|
||||
Title.updateTitle(title || defaultName);
|
||||
APP.toolbar.title.show();
|
||||
Cryptpad.alert("successfully uploaded: " + title);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.rpc.send('UPLOAD_STATUS', '', function (e, pending) {
|
||||
if (e) {
|
||||
console.error(e);
|
||||
return void Cryptpad.alert("something went wrong");
|
||||
}
|
||||
|
||||
if (pending[0]) {
|
||||
return void Cryptpad.confirm('upload pending, abort?', function (yes) {
|
||||
if (!yes) { return; }
|
||||
Cryptpad.rpc.send('UPLOAD_CANCEL', '', function (e, res) {
|
||||
if (e) { return void console.error(e); }
|
||||
console.log(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
next(again);
|
||||
});
|
||||
};
|
||||
|
||||
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 suggestion = document.title;
|
||||
Cryptpad.prompt(Messages.exportPrompt,
|
||||
Cryptpad.fixFileName(suggestion), function (filename) {
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
var blob = new Blob([myFile], {type: myDataType});
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
};
|
||||
|
||||
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) {
|
||||
var src = Cryptpad.getBlobPathFromHex(hexFileName);
|
||||
return Cryptpad.fetch(src, function (e, u8) {
|
||||
// now decrypt the u8
|
||||
if (e) { return window.alert('error'); }
|
||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||
var key = Nacl.util.decodeBase64(cryptKey);
|
||||
|
||||
FileCrypto.decrypt(u8, key, function (e, data) {
|
||||
if (e) {
|
||||
Cryptpad.removeLoadingScreen();
|
||||
return console.error(e);
|
||||
}
|
||||
console.log(data);
|
||||
var title = document.title = data.metadata.name;
|
||||
myFile = data.content;
|
||||
myDataType = data.metadata.type;
|
||||
Title.updateTitle(title || Title.defaultTitle);
|
||||
Cryptpad.removeLoadingScreen();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 () {
|
||||
upload(this.result, {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
};
|
||||
|
||||
$form.find("#file").on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
handleFile(file);
|
||||
});
|
||||
|
||||
$form
|
||||
.on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
})
|
||||
.on('drop', function (e) {
|
||||
var dropped = e.originalEvent.dataTransfer.files;
|
||||
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">
|
|
@ -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();
|
||||
|
||||
});
|
||||
});
|
|
@ -3,11 +3,6 @@ define([
|
|||
'/common/cryptpad-common.js',
|
||||
'/common/login.js'
|
||||
], function ($, Cryptpad, Login) {
|
||||
|
||||
var APP = window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
};
|
||||
|
||||
$(function () {
|
||||
var $main = $('#mainBlock');
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
@ -61,66 +56,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.feedback('LOGIN', true);
|
||||
Cryptpad.whenRealtimeSyncs(result.realtime, function() {
|
||||
Cryptpad.login(result.userHash, result.userName, function () {
|
||||
if (sessionStorage.redirectTo) {
|
||||
var h = sessionStorage.redirectTo;
|
||||
var parser = document.createElement('a');
|
||||
parser.href = h;
|
||||
if (parser.origin === window.location.origin) {
|
||||
delete sessionStorage.redirectTo;
|
||||
window.location.href = h;
|
||||
return;
|
||||
Cryptpad.feedback('LOGIN', true);
|
||||
Cryptpad.whenRealtimeSyncs(result.realtime, function() {
|
||||
Cryptpad.login(result.userHash, result.userName, function () {
|
||||
if (sessionStorage.redirectTo) {
|
||||
var h = sessionStorage.redirectTo;
|
||||
var parser = document.createElement('a');
|
||||
parser.href = h;
|
||||
if (parser.origin === window.location.origin) {
|
||||
delete sessionStorage.redirectTo;
|
||||
window.location.href = h;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.location.href = '/drive/';
|
||||
window.location.href = '/drive/';
|
||||
});
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
switch (err) {
|
||||
case 'NO_SUCH_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_noSuchUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_PASS':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalPass);
|
||||
});
|
||||
break;
|
||||
default: // UNHANDLED ERROR
|
||||
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (err) {
|
||||
case 'NO_SUCH_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_noSuchUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_USER':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalUser);
|
||||
});
|
||||
break;
|
||||
case 'INVAL_PASS':
|
||||
Cryptpad.removeLoadingScreen(function () {
|
||||
Cryptpad.alert(Messages.login_invalPass);
|
||||
});
|
||||
break;
|
||||
default: // UNHANDLED ERROR
|
||||
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Binary file not shown.
|
@ -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>
|
|
@ -5,7 +5,6 @@
|
|||
<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">
|
||||
<script src="/bower_components/ckeditor/ckeditor.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
|
@ -15,6 +14,9 @@
|
|||
padding: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
media-tag * {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
|
@ -0,0 +1,106 @@
|
|||
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) {
|
||||
Cryptpad.renamePad(newTitle, function (err, data) {
|
||||
if (err) {
|
||||
console.log("Couldn't set pad title");
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
document.title = newTitle;
|
||||
$bar.find('.' + Toolbar.constants.title).find('span.title').text(data);
|
||||
$bar.find('.' + Toolbar.constants.title).find('input').val(data);
|
||||
});
|
||||
};
|
||||
|
||||
var suggestName = function () {
|
||||
return document.title || getTitle() || '';
|
||||
};
|
||||
|
||||
var renameCb = function (err, title) {
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
var $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.onMediaMetadata = function (metadata) {
|
||||
if (!metadata || metadata.name !== defaultName) { return; }
|
||||
var title = document.title = metadata.name;
|
||||
updateTitle(title || defaultName);
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -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')) {
|
||||
|
|
301
www/pad/main.js
301
www/pad/main.js
|
@ -3,7 +3,7 @@ define([
|
|||
'/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,14 +11,11 @@ define([
|
|||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/pad/links.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'/bower_components/diff-dom/diffDOM.js'
|
||||
], function ($, Crypto, realtimeInput, Hyperjson,
|
||||
Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget,
|
||||
Visible, Notify, Links) {
|
||||
Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad, Cryptget, Links) {
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
|
@ -87,7 +84,7 @@ define([
|
|||
return hj;
|
||||
};
|
||||
|
||||
var onConnectError = function (info) {
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
|
||||
|
@ -103,10 +100,8 @@ 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;
|
||||
|
||||
|
@ -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),
|
||||
|
||||
|
@ -421,69 +359,7 @@ 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 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 () {
|
||||
realtimeOptions.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
|
||||
|
@ -495,7 +371,7 @@ define([
|
|||
cursor.update();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(shjson);
|
||||
Metadata.update(shjson);
|
||||
|
||||
var newInner = JSON.parse(shjson);
|
||||
var newSInner;
|
||||
|
@ -540,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);
|
||||
};
|
||||
|
||||
|
@ -554,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; }
|
||||
|
@ -562,45 +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);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
|
||||
userData: userData,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
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
|
||||
};
|
||||
toolbar = info.realtime.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, userList, configTb);
|
||||
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);
|
||||
|
@ -626,31 +499,13 @@ define([
|
|||
}
|
||||
|
||||
/* add a history button */
|
||||
var histConfig = {};
|
||||
histConfig.onRender = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
applyHjson(val || '["BODY",{},[]]');
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
console.error(e);
|
||||
}
|
||||
var histConfig = {
|
||||
onLocal: realtimeOptions.onLocal(),
|
||||
onRemote: realtimeOptions.onRemote(),
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) { applyHjson(val || '["BODY",{},[]]'); },
|
||||
$toolbar: $bar
|
||||
};
|
||||
histConfig.onClose = function () {
|
||||
// Close button clicked
|
||||
setHistory(false, true);
|
||||
};
|
||||
histConfig.onRevert = function () {
|
||||
// Revert button clicked
|
||||
setHistory(false, false);
|
||||
realtimeOptions.onLocal();
|
||||
realtimeOptions.onRemote();
|
||||
};
|
||||
histConfig.onReady = function () {
|
||||
// Called when the history is loaded and the UI displayed
|
||||
setHistory(true);
|
||||
};
|
||||
histConfig.$toolbar = $bar;
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
|
@ -673,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);
|
||||
};
|
||||
|
@ -689,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;
|
||||
|
@ -713,7 +562,6 @@ define([
|
|||
});
|
||||
}
|
||||
|
||||
module.users = info.userList.users;
|
||||
module.realtime = info.realtime;
|
||||
|
||||
var shjson = module.realtime.getUserDoc();
|
||||
|
@ -725,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);
|
||||
|
@ -745,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);
|
||||
|
@ -788,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) {
|
||||
|
@ -800,7 +625,7 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var onError = realtimeOptions.onError = onConnectError;
|
||||
realtimeOptions.onError = onConnectError;
|
||||
|
||||
var onLocal = realtimeOptions.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
|
@ -816,7 +641,7 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var rti = module.realtimeInput = realtimeInput.start(realtimeOptions);
|
||||
module.realtimeInput = realtimeInput.start(realtimeOptions);
|
||||
|
||||
Cryptpad.onLogout(function () { setEditable(false); });
|
||||
|
||||
|
@ -834,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);
|
||||
|
@ -846,7 +671,7 @@ define([
|
|||
|
||||
var interval = 100;
|
||||
var second = function (Ckeditor) {
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen(Ckeditor);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
|
|
510
www/poll/main.js
510
www/poll/main.js
|
@ -7,11 +7,9 @@ define([
|
|||
'/common/cryptget.js',
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'render.js',
|
||||
'/common/toolbar.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/common/toolbar2.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js'
|
||||
], function ($, TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar, Visible, Notify) {
|
||||
], function ($, TextPatcher, Listmap, Crypto, Cryptpad, Cryptget, Hyperjson, Renderer, Toolbar) {
|
||||
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
|
@ -34,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);
|
||||
};
|
||||
|
||||
|
@ -97,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;
|
||||
|
||||
|
@ -195,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)) {
|
||||
|
@ -237,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;
|
||||
|
@ -286,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);
|
||||
|
||||
|
@ -451,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));
|
||||
|
@ -535,252 +435,225 @@ 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);
|
||||
};
|
||||
var $templateButton = Cryptpad.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
};
|
||||
|
||||
// don't initialize until the store is ready.
|
||||
Cryptpad.ready(function () {
|
||||
|
@ -814,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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
@ -359,7 +359,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));
|
||||
};
|
||||
|
||||
|
@ -384,9 +384,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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -432,7 +430,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;
|
||||
|
|
|
@ -2,14 +2,8 @@ define([
|
|||
'jquery',
|
||||
'/common/login.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/common/credential.js'
|
||||
], function ($, Login, Cryptpad, Crypt) {
|
||||
|
||||
var APP = window.APP = {
|
||||
Login: Login,
|
||||
};
|
||||
|
||||
'/common/credential.js' // preloaded for login.js
|
||||
], function ($, Login, Cryptpad) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
$(function () {
|
||||
|
@ -107,57 +101,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,
|
||||
|
|
|
@ -97,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>
|
||||
|
@ -105,7 +105,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-footer">CryptPad v1.5.0 (Fenrir)</div>
|
||||
<div class="version-footer">CryptPad v1.6.0 (Grootslang)</div>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -16,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; }
|
||||
|
@ -71,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',
|
||||
|
@ -114,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);
|
||||
|
@ -145,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); }
|
||||
|
@ -153,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);
|
||||
|
@ -170,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);
|
||||
|
@ -256,10 +252,47 @@ define([
|
|||
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);
|
||||
|
@ -284,7 +317,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');
|
||||
|
@ -296,6 +329,10 @@ 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(createBackupDrive(obj));
|
||||
APP.$container.append(createImportLocalPads(obj));
|
||||
|
@ -343,7 +380,6 @@ define([
|
|||
});
|
||||
|
||||
window.addEventListener('storage', function (e) {
|
||||
var key = e.key;
|
||||
if (e.key !== Cryptpad.userHashKey) { return; }
|
||||
var o = e.oldValue;
|
||||
var n = e.newValue;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -3,20 +3,13 @@ define([
|
|||
'/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'
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Modes, Themes, Visible, Notify, Slide) {
|
||||
var saveAs = window.saveAs;
|
||||
|
||||
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Slide) {
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = {
|
||||
|
@ -30,23 +23,15 @@ define([
|
|||
var SLIDE_COLOR_ID = "cryptpad-color";
|
||||
|
||||
|
||||
var stringify = function (obj) {
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
var setTabTitle = function () {
|
||||
var slideNumber = '';
|
||||
if (Slide.index && Slide.content.length) {
|
||||
slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')';
|
||||
}
|
||||
document.title = APP.title + slideNumber;
|
||||
};
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
|
||||
var stringify = function (obj) {
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var toolbar;
|
||||
var editor;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
|
@ -57,82 +42,37 @@ 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 isHistoryMode = false;
|
||||
|
||||
var editor = module.editor = CMeditor.fromTextArea($textarea[0], {
|
||||
lineNumbers: true,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
matchBrackets : true,
|
||||
showTrailingSpace : true,
|
||||
styleActiveLine : true,
|
||||
search: true,
|
||||
highlightSelectionMatches: {showToken: /\w+/},
|
||||
extraKeys: {"Shift-Ctrl-R": undefined},
|
||||
foldGutter: true,
|
||||
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||
mode: "javascript",
|
||||
readOnly: true
|
||||
});
|
||||
editor.setValue(initialState);
|
||||
|
||||
var setMode = module.setMode = function (mode, $select) {
|
||||
module.highlightMode = mode;
|
||||
if (mode === 'text') {
|
||||
editor.setOption('mode', 'text');
|
||||
return;
|
||||
}
|
||||
CodeMirror.autoLoadMode(editor, mode);
|
||||
editor.setOption('mode', mode);
|
||||
if ($select && $select.val) { $select.val(mode); }
|
||||
var setEditable = module.setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
editor.setOption('readOnly', !bool);
|
||||
};
|
||||
setMode('markdown');
|
||||
|
||||
var setTheme = module.setTheme = (function () {
|
||||
var path = '/common/theme/';
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var $head = $(ifrw.document.head);
|
||||
var setTabTitle = function (title) {
|
||||
var slideNumber = '';
|
||||
if (Slide.index && Slide.content.length) {
|
||||
slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')';
|
||||
}
|
||||
document.title = title + slideNumber;
|
||||
};
|
||||
|
||||
var themeLoaded = module.themeLoaded = function (theme) {
|
||||
return $head.find('link[href*="'+theme+'"]').length;
|
||||
};
|
||||
|
||||
var loadTheme = module.loadTheme = function (theme) {
|
||||
$head.append($('<link />', {
|
||||
rel: 'stylesheet',
|
||||
href: path + theme + '.css',
|
||||
}));
|
||||
};
|
||||
|
||||
return function (theme, $select) {
|
||||
if (!theme) {
|
||||
editor.setOption('theme', 'default');
|
||||
} else {
|
||||
if (!themeLoaded(theme)) {
|
||||
loadTheme(theme);
|
||||
}
|
||||
editor.setOption('theme', theme);
|
||||
}
|
||||
if ($select) {
|
||||
$select.setValue(theme || 'Theme');
|
||||
}
|
||||
};
|
||||
}());
|
||||
var initialState = Messages.slideInitialState;
|
||||
|
||||
var $modal = $pad.contents().find('#modal');
|
||||
var $content = $pad.contents().find('#content');
|
||||
|
@ -141,65 +81,21 @@ define([
|
|||
|
||||
Slide.setModal(APP, $modal, $content, $pad, ifrw, slideOptions, initialState);
|
||||
|
||||
var setStyleState = function (state) {
|
||||
$pad.contents().find('#print, #content').find('style').each(function (i, el) {
|
||||
el.disabled = !state;
|
||||
});
|
||||
};
|
||||
|
||||
var enterPresentationMode = function (shouldLog) {
|
||||
Slide.show(true, editor.getValue());
|
||||
if (shouldLog) {
|
||||
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,7 +103,6 @@ define([
|
|||
validateKey: secret.keys.validateKey || undefined,
|
||||
readOnly: readOnly,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
setMyID: setMyID,
|
||||
transformFunction: JsonOT.validate,
|
||||
network: Cryptpad.getNetwork()
|
||||
};
|
||||
|
@ -222,24 +117,19 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var isDefaultTitle = function () {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
return Cryptpad.isDefaultName(parsed, APP.title);
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
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;
|
||||
|
@ -258,7 +148,7 @@ define([
|
|||
|
||||
editor.save();
|
||||
|
||||
var textValue = canonicalize($textarea.val());
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = stringifyInner(textValue);
|
||||
|
||||
module.patchText(shjson);
|
||||
|
@ -269,120 +159,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);
|
||||
|
@ -397,56 +183,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: ''
|
||||
};
|
||||
|
||||
|
@ -479,10 +221,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);
|
||||
|
||||
|
@ -503,73 +255,63 @@ 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 titleCfg = {
|
||||
updateLocalTitle: setTabTitle,
|
||||
getHeadingText: CodeMirror.getHeadingText
|
||||
};
|
||||
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
|
||||
userData: userData,
|
||||
readOnly: readOnly,
|
||||
ifrw: ifrw,
|
||||
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
|
||||
};
|
||||
toolbar = module.toolbar = Toolbar.create($bar, info.myID, info.realtime, info.getLag, info.userList, configTb);
|
||||
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 = {};
|
||||
histConfig.onRender = function (val) {
|
||||
if (typeof val === "undefined") { return; }
|
||||
try {
|
||||
var hjson = JSON.parse(val || '{}');
|
||||
var remoteDoc = hjson.content;
|
||||
var histConfig = {
|
||||
onLocal: config.onLocal(),
|
||||
onRemote: config.onRemote(),
|
||||
setHistory: setHistory,
|
||||
applyVal: function (val) {
|
||||
var remoteDoc = JSON.parse(val || '{}').content;
|
||||
editor.setValue(remoteDoc || '');
|
||||
editor.save();
|
||||
} catch (e) {
|
||||
// Probably a parse error
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
$toolbar: $bar
|
||||
};
|
||||
histConfig.onClose = function () {
|
||||
// Close button clicked
|
||||
setHistory(false, true);
|
||||
};
|
||||
histConfig.onRevert = function () {
|
||||
// Revert button clicked
|
||||
setHistory(false, false);
|
||||
config.onLocal();
|
||||
config.onRemote();
|
||||
};
|
||||
histConfig.onReady = function () {
|
||||
// Called when the history is loaded and the UI displayed
|
||||
setHistory(true);
|
||||
};
|
||||
histConfig.$toolbar = $bar;
|
||||
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
|
||||
$rightside.append($hist);
|
||||
|
||||
|
@ -585,21 +327,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);
|
||||
};
|
||||
|
@ -641,50 +379,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,
|
||||
|
@ -731,7 +425,7 @@ define([
|
|||
};
|
||||
|
||||
configureColors();
|
||||
configureTheme();
|
||||
CodeMirror.configureTheme();
|
||||
|
||||
if (presentMode) {
|
||||
$('#top-bar').hide();
|
||||
|
@ -741,27 +435,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({
|
||||
|
@ -787,142 +463,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 () {
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
if (isHistoryMode) { return; }
|
||||
var scroll = editor.getScrollInfo();
|
||||
|
||||
var oldDoc = canonicalize($textarea.val());
|
||||
var oldDoc = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson = module.realtime.getUserDoc();
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
updateMetadata(shjson);
|
||||
Metadata.update(shjson);
|
||||
|
||||
var hjson = JSON.parse(shjson);
|
||||
var remoteDoc = hjson.content;
|
||||
|
||||
var highlightMode = hjson.highlightMode;
|
||||
if (highlightMode && highlightMode !== module.highlightMode) {
|
||||
setMode(highlightMode, module.$language);
|
||||
if (highlightMode && highlightMode !== CodeMirror.highlightMode) {
|
||||
CodeMirror.setMode(highlightMode);
|
||||
}
|
||||
|
||||
//get old cursor here
|
||||
var oldCursor = {};
|
||||
oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc);
|
||||
oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc);
|
||||
|
||||
editor.setValue(remoteDoc);
|
||||
editor.save();
|
||||
|
||||
var op = TextPatcher.diff(oldDoc, remoteDoc);
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(oldCursor[attr], op);
|
||||
});
|
||||
|
||||
if(selects[0] === selects[1]) {
|
||||
editor.setCursor(posToCursor(selects[0], remoteDoc));
|
||||
}
|
||||
else {
|
||||
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
|
||||
}
|
||||
|
||||
editor.scrollTo(scroll.left, scroll.top);
|
||||
CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
|
||||
|
||||
if (!readOnly) {
|
||||
var textValue = canonicalize($textarea.val());
|
||||
var textValue = canonicalize(CodeMirror.$textarea.val());
|
||||
var shjson2 = stringifyInner(textValue);
|
||||
if (shjson2 !== shjson) {
|
||||
console.error("shjson2 !== shjson");
|
||||
|
@ -933,18 +531,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) {
|
||||
|
@ -956,9 +554,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);
|
||||
|
||||
|
@ -968,7 +566,7 @@ define([
|
|||
var interval = 100;
|
||||
|
||||
var second = function (CM) {
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen(CM);
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -9,7 +9,7 @@ define([
|
|||
|
||||
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
|
||||
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
|
||||
renderer.listitem = function (text, level) {
|
||||
renderer.listitem = function (text) {
|
||||
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
|
||||
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
|
||||
if (isCheckedTaskItem) {
|
||||
|
@ -28,8 +28,6 @@ define([
|
|||
renderer: renderer
|
||||
});
|
||||
|
||||
var truthy = function (x) { return x; };
|
||||
|
||||
var Slide = {
|
||||
index: 0,
|
||||
lastIndex: 0,
|
||||
|
@ -59,8 +57,7 @@ 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());
|
||||
});
|
||||
}
|
||||
|
@ -187,6 +184,10 @@ define([
|
|||
$('<div>', {'class': 'slideTitle'}).text(APP.title).appendTo($(el));
|
||||
}
|
||||
});
|
||||
$content.removeClass('transition');
|
||||
if (options.transition) {
|
||||
$content.addClass('transition');
|
||||
}
|
||||
//$content.find('.' + slideClass).hide();
|
||||
//$content.find('.' + slideClass + ':eq( ' + i + ' )').show();
|
||||
$content.css('margin-left', -(i*100)+'vw');
|
||||
|
@ -194,7 +195,7 @@ define([
|
|||
change(Slide.lastIndex, Slide.index);
|
||||
};
|
||||
|
||||
var updateOptions = Slide.updateOptions = function () {
|
||||
Slide.updateOptions = function () {
|
||||
draw(Slide.index);
|
||||
};
|
||||
|
||||
|
@ -214,7 +215,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();
|
||||
|
@ -223,7 +227,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();
|
||||
|
@ -233,7 +237,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;
|
||||
|
@ -245,7 +249,7 @@ define([
|
|||
change(Slide.lastIndex, Slide.index);
|
||||
};
|
||||
|
||||
var left = Slide.left = function () {
|
||||
Slide.left = function () {
|
||||
console.log('left');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
|
@ -253,7 +257,7 @@ define([
|
|||
Slide.draw(i);
|
||||
};
|
||||
|
||||
var right = Slide.right = function () {
|
||||
Slide.right = function () {
|
||||
console.log('right');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
|
@ -261,7 +265,7 @@ define([
|
|||
Slide.draw(i);
|
||||
};
|
||||
|
||||
var first = Slide.first = function () {
|
||||
Slide.first = function () {
|
||||
console.log('first');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
|
@ -269,7 +273,7 @@ define([
|
|||
Slide.draw(i);
|
||||
};
|
||||
|
||||
var last = Slide.last = function () {
|
||||
Slide.last = function () {
|
||||
console.log('end');
|
||||
Slide.lastIndex = Slide.index;
|
||||
|
||||
|
@ -279,7 +283,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); }
|
||||
|
@ -287,17 +291,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,19 +3,17 @@ define([
|
|||
'/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/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;
|
||||
|
||||
|
@ -24,7 +22,7 @@ define([
|
|||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
var onConnectError = function (info) {
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
var toolbar;
|
||||
|
@ -212,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: '{}',
|
||||
|
@ -249,7 +222,6 @@ window.canvas = canvas;
|
|||
readOnly: readOnly,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
setMyID: setMyID,
|
||||
transformFunction: JsonOT.transform,
|
||||
};
|
||||
|
||||
|
@ -280,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: '!' });
|
||||
|
||||
|
@ -330,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)) {
|
||||
|
@ -370,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();
|
||||
|
@ -380,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
|
||||
|
@ -401,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;
|
||||
|
||||
|
@ -479,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);
|
||||
|
@ -488,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);
|
||||
|
@ -510,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
|
||||
|
@ -547,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) {
|
||||
|
@ -597,7 +444,7 @@ window.canvas = canvas;
|
|||
}
|
||||
};
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
module.rt = Realtime.start(config);
|
||||
|
||||
canvas.on('mouse:up', onLocal);
|
||||
|
||||
|
@ -611,7 +458,7 @@ window.canvas = canvas;
|
|||
});
|
||||
};
|
||||
|
||||
Cryptpad.ready(function (err, env) {
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue