This commit is contained in:
Paul Libbrecht
2018-04-10 19:12:30 +02:00
400 changed files with 32754 additions and 27681 deletions

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<script src="respond.js"></script>
</head>
<body>

147
www/assert/frame/frame.js Normal file
View File

@@ -0,0 +1,147 @@
(function () {
var Frame = {};
var uid = function () {
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
.toString(32).replace(/\./g, '');
};
// create an invisible iframe with a given source
// append it to a parent element
// execute a callback when it has loaded
Frame.create = function (parent, src, onload, timeout) {
var iframe = document.createElement('iframe');
timeout = timeout || 10000;
var to = window.setTimeout(function () {
onload('[timeoutError] could not load iframe at ' + src);
}, timeout);
iframe.setAttribute('id', 'cors-store');
iframe.onload = function (e) {
onload(void 0, iframe, e);
window.clearTimeout(to);
};
// We must pass a unique parameter here to avoid cache problems in Firefox with
// the NoScript plugin: if the iframe's content is taken from the cache, the JS
// is not executed with NoScript....
iframe.setAttribute('src', src + '?t=' + new Date().getTime());
iframe.style.display = 'none';
parent.appendChild(iframe);
};
/* given an iframe with an rpc script loaded, create a frame object
with an asynchronous 'send' method */
Frame.open = function (e, A, timeout) {
var win = e.contentWindow;
var frame = {};
frame.id = uid();
var listeners = {};
var timeouts = {};
timeout = timeout || 5000;
frame.accepts = function (o) {
return A.some(function (e) {
switch (typeof(e)) {
case 'string': return e === o;
case 'object': return e.test(o);
}
});
};
var changeHandlers = frame.changeHandlers = [];
frame.change = function (f) {
if (typeof(f) !== 'function') {
throw new Error('[Frame.change] expected callback');
}
changeHandlers.push(f);
};
var _listener = function (e) {
if (!frame.accepts(e.origin)) {
console.log("message from %s rejected!", e.origin);
return;
}
var message = JSON.parse(e.data);
var uid = message._uid;
var error = message.error;
var data = message.data;
if (!uid) {
console.log("No uid!");
return;
}
if (uid === 'change' && changeHandlers.length) {
changeHandlers.forEach(function (f) {
f(data);
});
return;
}
if (timeouts[uid]) {
window.clearTimeout(timeouts[uid]);
}
if (listeners[uid]) {
listeners[uid](error, data, e);
delete listeners[uid];
}
};
window.addEventListener('message', _listener);
frame.close = function () {
window.removeEventListener('message', _listener);
};
/* method (string): (set|get|remove)
key (string)
data (string)
cb (function) */
frame.send = function (method, content, cb) {
var req = {
method: method,
//key: key,
data: content, //data,
};
var id = req._uid = uid();
// uid must not equal 'change'
while(id === 'change') {
id = req._uid = uid();
}
if (typeof(cb) === 'function') {
//console.log("setting callback!");
listeners[id] = cb;
//console.log("setting timeout of %sms", timeout);
timeouts[id] = window.setTimeout(function () {
// when the callback is executed it will clear this timeout
cb('[TimeoutError] request timed out after ' + timeout + 'ms');
}, timeout);
} else {
console.log(typeof(cb));
}
win.postMessage(JSON.stringify(req), '*');
};
return frame;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = Frame;
} else if (typeof(define) === 'function' && define.amd) {
define(['jquery'], function () {
return Frame;
});
} else {
window.Frame = Frame;
}
}());

View File

@@ -0,0 +1,32 @@
var validDomains = [ /.*/i, ];
var isValidDomain = function (o) {
return validDomains.some(function (e) {
switch (typeof(e)) {
case 'string': return e === o;
case 'object': return e.test(o);
}
});
};
window.addEventListener('message', function(e) {
if (!isValidDomain(e.origin)) { return; }
var payload = JSON.parse(e.data);
var parent = window.parent;
var respond = function (error, data) {
var res = {
_uid: payload._uid,
error: error,
data: data,
};
parent.postMessage(JSON.stringify(res), '*');
};
//console.error(payload);
switch(payload.method) {
case undefined:
return respond('No method supplied');
default:
return respond(void 0, "EHLO");
}
});

View File

@@ -19,6 +19,11 @@
.error {
border: 1px solid red;
}
.thumb {
max-height: 150px;
width: auto;
border: 3px solid black;
}
</style>
</head>
@@ -36,6 +41,7 @@
<!-- -->
<div id="quot"><p>"pewpewpew"</p></div>
<hr>
<h2>Test 2</h2>
@@ -45,3 +51,6 @@
<div id="widget"><div data-cke-widget-id="0" tabindex="-1" data-cke-widget-wrapper="1" data-cke-filter="off" class="cke_widget_wrapper cke_widget_block" data-cke-display-name="macro:velocity" contenteditable="false"><div class="macro cke_widget_element" data-macro="startmacro:velocity|-||-|Here is a macro" data-cke-widget-data="%7B%22classes%22%3A%7B%22macro%22%3A1%7D%7D" data-cke-widget-upcasted="1" data-cke-widget-keep-attr="0" data-widget="xwiki-macro"><p>Here is a macro</p></div><span style='background: rgba(220, 220, 220, 0.5) url("/customize/cryptofist_small.png") repeat scroll 0% 0%; top: -15px; left: 0px; display: block;' class="cke_reset cke_widget_drag_handler_container"><img title="Click and drag to move" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" data-cke-widget-drag-handler="1" class="cke_reset cke_widget_drag_handler" height="15" width="15"></span></div></div>
<hr>
<img id="thumb-orig" src="/customize/alt-favicon.png" />

View File

@@ -1,14 +1,16 @@
define([
'jquery',
'/bower_components/hyperjson/hyperjson.js',
'/bower_components/textpatcher/TextPatcher.amd.js',
'json.sortify',
'/common/cryptpad-common.js',
'/drive/tests.js',
'/common/test.js'
], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test) {
'/common/test.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-thumbnail.js',
'/common/wire.js',
'/common/flat-dom.js',
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) {
window.Hyperjson = Hyperjson;
window.TextPatcher = TextPatcher;
window.Sortify = Sortify;
var assertions = 0;
@@ -30,7 +32,7 @@ define([
ASSERTS.forEach(function (f, index) {
f(function (err) {
console.log("test " + index);
//console.log("test " + index);
done(err, index);
}, index);
});
@@ -92,18 +94,7 @@ define([
// turn it back into stringified Hyperjson, but apply filters
var shjson2 = Sortify(Hyperjson.fromDOM(DOM, elementFilter, attributeFilter));
var success = shjson === shjson2;
var op = TextPatcher.diff(shjson, shjson2);
var diff = TextPatcher.format(shjson, op);
if (success) {
return cb(true);
} else {
return cb('<br><br>insert: ' + diff.insert + '<br><br>' +
'remove: ' + diff.remove + '<br><br>');
}
return cb(shjson === shjson2);
}, "expected hyperjson equality");
};
@@ -114,21 +105,8 @@ define([
assert(function (cb) {
var hjson = Hyperjson.fromDOM(target);
var cloned = Hyperjson.toDOM(hjson);
var success = cloned.outerHTML === target.outerHTML;
if (!success) {
var op = TextPatcher.diff(target.outerHTML, cloned.outerHTML);
window.DEBUG = {
error: "Expected equality between A and B",
A: target.outerHTML,
B: cloned.outerHTML,
diff: op
};
console.log("DIFF:");
TextPatcher.log(target.outerHTML, op);
}
return cb(success);
return cb(cloned.outerHTML === target.outerHTML);
}, "Round trip serialization introduced artifacts.");
};
@@ -156,15 +134,17 @@ define([
// check that old hashes parse correctly
assert(function (cb) {
var secret = Cryptpad.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy');
//if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug
var secret = Hash.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy');
return cb(secret.hashData.channel === "67b8385b07352be53e40746d2be6ccd7" &&
secret.hashData.key === "XAYSuJYYqa9NfmInyHci7LNy" &&
secret.hashData.version === 0);
secret.hashData.version === 0 &&
typeof(secret.getUrl) === 'function');
}, "Old hash failed to parse");
// make sure version 1 hashes parse correctly
assert(function (cb) {
var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI');
var secret = Hash.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI');
return cb(secret.hashData.version === 1 &&
secret.hashData.mode === "edit" &&
secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
@@ -174,7 +154,7 @@ define([
// test support for present mode in hashes
assert(function (cb) {
var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present');
var secret = Hash.parsePadUrl('/pad/#/1/edit/CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present');
return cb(secret.hashData.version === 1
&& secret.hashData.mode === "edit"
&& secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
@@ -184,7 +164,7 @@ define([
// test support for present mode in hashes
assert(function (cb) {
var secret = Cryptpad.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G//present');
var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G//present');
return cb(secret.hashData.version === 1
&& secret.hashData.mode === "edit"
&& secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
@@ -192,9 +172,42 @@ define([
&& secret.hashData.present);
}, "Couldn't handle multiple successive slashes");
// test support for present & embed mode in hashes
assert(function (cb) {
var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/embed/present/');
return cb(secret.hashData.version === 1
&& secret.hashData.mode === "edit"
&& secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
&& secret.hashData.key === "DNZ2wcG683GscU4fyOyqA87G"
&& secret.hashData.present
&& secret.hashData.embed);
}, "Couldn't handle multiple successive slashes");
// test support for present & embed mode in hashes
assert(function (cb) {
var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G/present/embed');
return cb(secret.hashData.version === 1
&& secret.hashData.mode === "edit"
&& secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
&& secret.hashData.key === "DNZ2wcG683GscU4fyOyqA87G"
&& secret.hashData.present
&& secret.hashData.embed);
}, "Couldn't handle multiple successive slashes");
// test support for embed mode in hashes
assert(function (cb) {
var secret = Hash.parsePadUrl('/pad/#/1/edit//CmN5+YJkrHFS3NSBg-P7Sg/DNZ2wcG683GscU4fyOyqA87G///embed//');
return cb(secret.hashData.version === 1
&& secret.hashData.mode === "edit"
&& secret.hashData.channel === "CmN5+YJkrHFS3NSBg-P7Sg"
&& secret.hashData.key === "DNZ2wcG683GscU4fyOyqA87G"
&& !secret.hashData.present
&& secret.hashData.embed);
}, "Couldn't handle multiple successive slashes");
// test support for trailing slash
assert(function (cb) {
var secret = Cryptpad.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/');
var secret = Hash.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/');
return cb(secret.hashData.version === 1 &&
secret.hashData.mode === "edit" &&
secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
@@ -202,14 +215,140 @@ define([
!secret.hashData.present);
}, "test support for trailing slashes in version 1 hash failed to parse");
assert(function (cb) {
var secret = Hash.parsePadUrl('/invite/#/1/ilrOtygzDVoUSRpOOJrUuQ/e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=/');
var hd = secret.hashData;
cb(hd.channel === "ilrOtygzDVoUSRpOOJrUuQ" &&
hd.pubkey === "e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=" &&
hd.type === 'invite');
}, "test support for invite urls");
assert(function (cb) {
var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
var secret = Hash.parsePadUrl(url);
return cb(secret.hashData.version === 1 &&
secret.hashData.mode === "edit" &&
secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" &&
secret.hashData.key === "usn4+9CqVja8Q7RZOGTfRgqI" &&
!secret.hashData.present);
}, "test support for ugly tracking query paramaters in url");
assert(function (cb) {
// TODO
return cb(true);
}, "version 2 hash failed to parse correctly");
assert(function (cb) {
Wire.create({
constructor: function (cb) {
var service = function (type, data, cb) {
switch (type) {
case "HEY_BUDDY":
return cb(void 0, "SALUT!");
default:
cb("ERROR");
}
};
var evt = Util.mkEvent();
var respond = function (e, out) {
evt.fire(e, out);
};
cb(void 0, {
send: function (raw /*, cb */) {
try {
var parsed = JSON.parse(raw);
var txid = parsed.txid;
setTimeout(function () {
service(parsed.q, parsed.content, function (e, result) {
respond(JSON.stringify({
txid: txid,
error: e,
content: result,
}));
});
});
} catch (e) { console.error("PEWPEW"); }
},
receive: function (f) {
evt.reg(f);
},
});
},
}, function (e, rpc) {
if (e) { return cb(false); }
rpc.send('HEY_BUDDY', null, function (e, out) {
if (e) { return void cb(false); }
if (out === 'SALUT!') { cb(true); }
});
});
}, "Test rpc factory");
assert(function (cb) {
require([
'/assert/frame/frame.js',
], function (Frame) {
Frame.create(document.body, '/assert/frame/frame.html', function (e, frame) {
if (e) { return cb(false); }
var channel = Frame.open(frame, [
/.*/i,
], 5000);
channel.send('HELO', null, function (e, res) {
if (res === 'EHLO') { return cb(true); }
cb(false);
});
});
});
}, "PEWPEW");
(function () {
var guid = Wire.uid();
var t = Wire.tracker({
timeout: 1000,
hook: function (txid, q, content) {
console.info(JSON.stringify({
guid: guid,
txid: txid,
q: q,
content: content,
}));
},
});
assert(function (cb) {
t.call('SHOULD_TIMEOUT', null, function (e) {
if (e === 'TIMEOUT') { return cb(true); }
cb(false);
});
}, 'tracker should timeout');
assert(function (cb) {
var id = t.call('SHOULD_NOT_TIMEOUT', null, function (e, out) {
if (e) { return cb(false); }
if (out === 'YES') { return cb(true); }
cb(false);
});
t.respond(id, void 0, 'YES');
}, "tracker should not timeout");
}());
Drive.test(assert);
assert(function (cb) {
// extract dom elements into a flattened JSON representation
var flat = Flat.fromDOM(document.body);
// recreate a _mostly_ equivalent DOM
var dom = Flat.toDOM(flat);
// assume we don't care about comments
var bodyText = document.body.outerHTML.replace(/<!\-\-[\s\S]*?\-\->/g, '');
// check for equality
cb(dom.outerHTML === bodyText);
});
var swap = function (str, dict) {
return str.replace(/\{\{(.*?)\}\}/g, function (all, key) {
return typeof dict[key] !== 'undefined'? dict[key] : all;

View File

@@ -1,8 +1,9 @@
define([
'jquery',
'/common/cryptpad-common.js',
'/common/common-util.js',
'/customize/messages.js',
'/customize/translations/messages.js',
], function ($, Cryptpad, English) {
], function ($, Util, Messages, English) {
var $body = $('body');
@@ -11,38 +12,40 @@ define([
};
var todo = function (missing) {
var str = "";
var need = 1;
var currentLang = "";
var currentState = 1;
if (missing.length) {
$body.append(pre(missing.map(function (msg) {
var res = "";
var code = msg[0];
var key = msg[1];
var needed = msg[2];
var lang = msg[0];
var key = msg[1]; // Array
var state = msg[2]; // 0 === toDelete, 1 === missing, 2 === updated, 3 === invalid (wrong type)
var value = msg[3] || '""';
if (str !== code) {
if (str !== "")
if (currentLang !== lang) {
if (currentLang !== "")
{
res += '\n';
}
str = code;
res += '/*\n *\n * ' + code + '\n *\n */\n\n';
currentLang = lang;
res += '/*\n *\n * ' + lang + '\n *\n */\n\n';
}
if (need !== needed) {
need = needed;
if (need === 0)
if (currentState !== state) {
currentState = state;
if (currentState === 0)
{
res += '\n// TODO: These keys are not needed anymore and should be removed ('+ code + ')\n\n';
res += '\n// TODO: These keys are not needed anymore and should be removed ('+ lang + ')\n\n';
}
}
res += (need ? '' : '// ') + 'out.' + key + ' = ' + value + ';';
if (need === 1) {
res += ' // ' + JSON.stringify(English[key]);
} else if (need === 2) {
res += ' // TODO: Key updated --> make sure the updated key "'+ value +'" exists and is translated before that one.';
res += (currentState ? '' : '// ') + 'out.' + key.join('.') + ' = ' + value + ';';
if (currentState === 1) {
res += ' // ' + JSON.stringify(Util.find(English, key));
} else if (currentState === 2) {
res += ' // TODO: Key updated --> make sure the updated key "'+ value +'" exists and is translated before this one.';
} else if (currentState === 3) {
res += ' // NOTE: this key has an invalid type! Original value: ' + JSON.stringify(Util.find(English, key));
}
return res;
}).join('\n')));
@@ -50,5 +53,5 @@ define([
$body.text('// All keys are present in all translations');
}
};
Cryptpad.Messages._checkTranslationState(todo);
Messages._checkTranslationState(todo);
});

View File

@@ -1,9 +1,12 @@
define([
'jquery',
'/common/cryptpad-common.js',
'/common/common-constants.js',
'/common/outer/local-store.js',
'/common/test.js',
'/bower_components/nthen/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function ($, Cryptpad, Test) {
], function ($, Cryptpad, Constants, LocalStore, Test, nThen) {
var Nacl = window.nacl;
var signMsg = function (msg, privKey) {
@@ -20,13 +23,21 @@ define([
];
// Safari is weird about localStorage in iframes but seems to let sessionStorage slide.
localStorage.User_hash = localStorage.User_hash || sessionStorage.User_hash;
localStorage[Constants.userHashKey] = localStorage[Constants.userHashKey] ||
sessionStorage[Constants.userHashKey];
Cryptpad.ready(function () {
var proxy;
nThen(function (waitFor) {
Cryptpad.ready(waitFor());
}).nThen(function (waitFor) {
Cryptpad.getUserObject(waitFor(function (obj) {
proxy = obj;
}));
}).nThen(function () {
console.log('IFRAME READY');
Test(function () {
// This is only here to maybe trigger an error.
window.drive = Cryptpad.getStore().getProxy().proxy['drive'];
window.drive = proxy['drive'];
Test.passed();
});
$(window).on("message", function (jqe) {
@@ -40,10 +51,9 @@ define([
} else if (data.cmd === 'SIGN') {
if (!AUTHORIZED_DOMAINS.filter(function (x) { return x.test(domain); }).length) {
ret.error = "UNAUTH_DOMAIN";
} else if (!Cryptpad.isLoggedIn()) {
} else if (!LocalStore.isLoggedIn()) {
ret.error = "NOT_LOGGED_IN";
} else {
var proxy = Cryptpad.getStore().getProxy().proxy;
var sig = signMsg(data.data, proxy.edPrivate);
ret.res = {
uname: proxy.login_name,

View File

@@ -1,10 +1,14 @@
define([], function () {
if (window.localStorage && window.localStorage.FS_hash) {
define(['/api/config'], function (ApiConfig) {
if (ApiConfig.httpSafeOrigin !== window.location.origin) {
window.alert('The bounce application must only be used from the sandbox domain, ' +
'please report this issue on https://github.com/xwiki-labs/cryptpad');
return;
}
var bounceTo = decodeURIComponent(window.location.hash.slice(1));
if (!bounceTo) { return; }
if (!bounceTo) {
window.alert('The bounce application must only be used with a valid href to visit');
return;
}
window.opener = null;
window.location.href = bounceTo;
});
});

126
www/code/app-code.less Normal file
View File

@@ -0,0 +1,126 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) "../../customize/src/less2/include/framework.less";
.framework_main(
@bg-color: @colortheme_code-bg,
@warn-color: @colortheme_code-warn,
@color: @colortheme_code-color
);
// body
&.cp-app-code {
display: flex;
flex-flow: column;
max-height: 100%;
min-height: auto;
#cp-app-code-container {
display: inline-flex;
flex-flow: column;
height: 100%;
min-height: 100%;
width: 50%;
min-width: 20%;
max-width: 80%;
resize: horizontal;
overflow: hidden;
&.cp-app-code-fullpage {
max-width: 100%;
resize: none;
flex: 1;
}
}
.CodeMirror {
flex: 1;
font-size: initial;
width: 100%;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#cp-app-code-editor {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
&.cp-app-code-present {
#cp-app-code-container { display: none; }
#cp-app-code-preview { border: 0; }
}
}
#cp-app-code-preview {
flex: 1;
padding: 5px 20px;
overflow: auto;
display: inline-block;
height: 100%;
border-left: 1px solid black;
box-sizing: border-box;
font-family: Calibri,Ubuntu,sans-serif;
word-wrap: break-word;
position: relative;
media-tag {
* {
max-width:100%;
}
iframe[type="application/pdf"] {
max-height:50vh;
}
}
.markdown_main();
.cp-app-code-preview-empty {
display: none;
}
&.cp-app-code-preview-isempty {
display: flex;
align-items: center;
justify-content: center;
#cp-app-code-preview-content {
display: none;
}
.cp-app-code-preview-empty {
//flex: 1 1 auto;
max-height: 100%;
max-width: 100%;
display: block;
opacity: 0.2;
}
}
}
#cp-app-code-preview-content {
max-width: 40vw;
margin: 1em auto;
.markdown_preformatted-code;
.markdown_gfm-table(black);
}
.cp-splitter {
position: absolute;
height: 100%;
width: 8px;
top: 0;
left: 0;
cursor: col-resize;
}
@media (max-width: @browser_media-medium-screen) {
#cp-app-code-container {
flex: 1;
max-width: 100%;
resize: none;
}
#cp-app-code-preview {
display: none !important;
}
}
}

View File

@@ -1,102 +0,0 @@
@import "/customize/src/less/variables.less";
@import "/customize/src/less/mixins.less";
@import "/common/markdown.less";
@import "/common/file-dialog.less";
html, body{
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
max-height: 100%;
min-height: auto;
}
@slideTime: 500ms;
.CodeMirror {
display: inline-block;
height: 100%;
width: 50%;
&.transition {
transition: width @slideTime, min-width @slideTime, max-width @slideTime;
}
min-width: 20%;
max-width: 80%;
resize: horizontal;
font-size: initial;
}
.CodeMirror.fullPage {
//min-width: 100%;
max-width: 100%;
resize: none;
flex: 1;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
#previewContainer {
flex: 1;
padding: 5px 20px;
overflow: auto;
display: inline-block;
height: 100%;
border-left: 1px solid black;
box-sizing: border-box;
font-family: Calibri,Ubuntu,sans-serif;
word-wrap: break-word;
position: relative;
media-tag {
* {
max-width:100%;
}
iframe[type="application/pdf"] {
max-height:50vh;
}
}
}
#preview {
max-width: 40vw;
margin: 1em auto;
.markdown_preformatted-code;
.markdown_gfm-table(black);
}
.cp-splitter {
position: absolute;
height: 100%;
width: 8px;
top: 0;
left: 0;
cursor: col-resize;
}
@media (max-width: @media-medium-screen) {
.CodeMirror {
flex: 1;
max-width: 100%;
resize: none;
}
#previewContainer {
display: none !important;
}
}

View File

@@ -1,41 +1,38 @@
<!DOCTYPE html>
<html class="cp code">
<html>
<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">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
overflow-y: hidden;
}
#iframe-container {
position: fixed;
top: 0px;
bottom: 0px;
right: 0px;
left: 0px;
margin: 0px;
padding: 0px;
}
#pad-iframe {
#sbox-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
box-sizing: border-box;
}
/* We use !important here to override the 96% set to the element in DecorateToolbar.js
when we enter fullscreen mode. It allows us to avoid changing the iframe's size in JS */
#pad-iframe.fullscreen {
top: 0px;
height: 100% !important;
#sbox-filePicker-iframe {
position: fixed;
top:0; left:0;
bottom:0; right:0;
width:100%;
height: 100%;
border: 0;
}
</style>
</head>
<body>
<div id="iframe-container">
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
</div>
<iframe id="sbox-iframe">

View File

@@ -1,16 +1,23 @@
<!DOCTYPE html>
<html style="height: 100%;">
<html class="cp-app-noscroll">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script async data-bootload="inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style> .loading-hidden { display: none; } </style>
<script async data-bootload="/code/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
.loading-hidden { display: none; }
#editor1 { display: none; }
.html2canvas-container { width: 3000px !important; height: 3000px !important; }
</style>
</head>
<body class="loading-hidden">
<div id="cme_toolbox" class="toolbar-container"></div>
<div id="editorContainer">
<textarea id="editor1" name="editor1"></textarea>
<div id="previewContainer"><div id="preview"></div></div>
<body class="cp-app-code">
<div id="cme_toolbox" class="cp-toolbar-container"></div>
<div id="cp-app-code-editor">
<div id="cp-app-code-container">
<textarea id="editor1" name="editor1"></textarea>
</div>
<div id="cp-app-code-preview">
<div id="cp-app-code-preview-content"></div>
</div>
</div>
</body>
</html>

View File

@@ -1,19 +1,21 @@
define([
'jquery',
'/common/diffMarked.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/sframe-app-framework.js',
'/common/sframe-common-codemirror.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/modes.js',
'/customize/messages.js',
'cm/lib/codemirror',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/code/code.less',
'less!/customize/src/less/toolbar.less',
'less!/customize/src/less/cryptpad.less',
'css!cm/lib/codemirror.css',
'css!cm/addon/dialog/dialog.css',
'css!cm/addon/fold/foldgutter.css',
'cm/mode/markdown/markdown',
'cm/mode/gfm/gfm',
'cm/addon/mode/loadmode',
'cm/mode/meta',
'cm/addon/mode/overlay',
@@ -34,7 +36,378 @@ define([
'cm/addon/fold/markdown-fold',
'cm/addon/fold/comment-fold',
'cm/addon/display/placeholder',
], function ($, CMeditor) {
], function (
$,
DiffMd,
nThen,
SFCommon,
Framework,
SFCodeMirror,
Util,
Hash,
Modes,
Messages,
CMeditor)
{
window.CodeMirror = CMeditor;
$('.loading-hidden').removeClass('loading-hidden');
var MEDIA_TAG_MODES = Object.freeze([
'markdown',
'gfm',
'html',
'htmlembedded',
'htmlmixed',
'index.html',
'php',
'velocity',
'xml',
]);
var mkMarkdownTb = function (editor, framework) {
var $codeMirrorContainer = $('#cp-app-code-container');
var markdownTb = framework._.sfCommon.createMarkdownToolbar(editor);
$codeMirrorContainer.prepend(markdownTb.toolbar);
framework._.toolbar.$rightside.append(markdownTb.button);
var modeChange = function (mode) {
if (['markdown', 'gfm'].indexOf(mode) !== -1) { return void markdownTb.setState(true); }
markdownTb.setState(false);
};
return {
modeChange: modeChange
};
};
var mkHelpMenu = function (framework) {
var $codeMirrorContainer = $('#cp-app-code-container');
var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'code']);
$codeMirrorContainer.prepend(helpMenu.menu);
framework._.toolbar.$drawer.append(helpMenu.button);
};
var mkPreviewPane = function (editor, CodeMirror, framework, isPresentMode) {
var $previewContainer = $('#cp-app-code-preview');
var $preview = $('#cp-app-code-preview-content');
var $editorContainer = $('#cp-app-code-editor');
var $codeMirrorContainer = $('#cp-app-code-container');
var $codeMirror = $('.CodeMirror');
$('<img>', {
src: '/customize/main-favicon.png',
alt: '',
class: 'cp-app-code-preview-empty'
}).appendTo($previewContainer);
var $previewButton = framework._.sfCommon.createButton('preview', true);
var forceDrawPreview = function () {
try {
if (editor.getValue() === '') {
$previewContainer.addClass('cp-app-code-preview-isempty');
return;
}
$previewContainer.removeClass('cp-app-code-preview-isempty');
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
} catch (e) { console.error(e); }
};
var drawPreview = Util.throttle(function () {
if (['markdown', 'gfm'].indexOf(CodeMirror.highlightMode) === -1) { return; }
if (!$previewButton.is('.cp-toolbar-button-active')) { return; }
forceDrawPreview();
}, 150);
var previewTo;
$previewButton.click(function () {
clearTimeout(previewTo);
$codeMirror.addClass('transition');
previewTo = setTimeout(function () {
$codeMirror.removeClass('transition');
}, 500);
if (['markdown', 'gfm'].indexOf(CodeMirror.highlightMode) === -1) {
$previewContainer.show();
}
$previewContainer.toggle();
if ($previewContainer.is(':visible')) {
forceDrawPreview();
$codeMirrorContainer.removeClass('cp-app-code-fullpage');
$previewButton.addClass('cp-toolbar-button-active');
framework._.sfCommon.setPadAttribute('previewMode', true, function (e) {
if (e) { return console.log(e); }
});
} else {
$codeMirrorContainer.addClass('cp-app-code-fullpage');
$previewButton.removeClass('cp-toolbar-button-active');
framework._.sfCommon.setPadAttribute('previewMode', false, function (e) {
if (e) { return console.log(e); }
});
}
});
framework._.toolbar.$rightside.append($previewButton);
$preview.click(function (e) {
if (!e.target) { return; }
var $t = $(e.target);
if ($t.is('a') || $t.parents('a').length) {
e.preventDefault();
var $a = $t.is('a') ? $t : $t.parents('a').first();
var href = $a.attr('href');
framework._.sfCommon.openUnsafeURL(href);
}
});
var modeChange = function (mode) {
if (['markdown', 'gfm'].indexOf(mode) !== -1) {
$previewButton.show();
framework._.sfCommon.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if (data !== false) {
$previewContainer.show();
$previewButton.addClass('cp-toolbar-button-active');
$codeMirrorContainer.removeClass('cp-app-code-fullpage');
if (isPresentMode) {
$editorContainer.addClass('cp-app-code-present');
}
}
});
return;
}
$editorContainer.removeClass('cp-app-code-present');
$previewButton.hide();
$previewContainer.hide();
$previewButton.removeClass('active');
$codeMirrorContainer.addClass('cp-app-code-fullpage');
};
var isVisible = function () {
return $previewContainer.is(':visible');
};
framework.onReady(function () {
// add the splitter
var splitter = $('<div>', {
'class': 'cp-splitter'
}).appendTo($previewContainer);
$preview.on('scroll', function() {
splitter.css('top', $preview.scrollTop() + 'px');
});
var $target = $codeMirrorContainer;
splitter.on('mousedown', function (e) {
e.preventDefault();
var x = e.pageX;
var w = $target.width();
var handler = function (evt) {
if (evt.type === 'mouseup') {
$(window).off('mouseup mousemove', handler);
return;
}
$target.css('width', (w - x + evt.pageX) + 'px');
editor.refresh();
};
$(window).off('mouseup mousemove', handler);
$(window).on('mouseup mousemove', handler);
});
});
framework._.sfCommon.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if (data === false && $previewButton) {
$previewButton.click();
}
});
return {
forceDraw: forceDrawPreview,
draw: drawPreview,
modeChange: modeChange,
isVisible: isVisible
};
};
var mkIndentSettings = function (editor, metadataMgr) {
var setIndentation = function (units, useTabs) {
if (typeof(units) !== 'number') { return; }
editor.setOption('indentUnit', units);
editor.setOption('tabSize', units);
editor.setOption('indentWithTabs', useTabs);
};
var indentKey = 'indentUnit';
var useTabsKey = 'indentWithTabs';
var updateIndentSettings = function () {
if (!metadataMgr) { return; }
var data = metadataMgr.getPrivateData().settings;
data = data.codemirror || {};
var indentUnit = data[indentKey];
var useTabs = data[useTabsKey];
setIndentation(
typeof(indentUnit) === 'number'? indentUnit: 2,
typeof(useTabs) === 'boolean'? useTabs: false);
};
metadataMgr.onChangeLazy(updateIndentSettings);
updateIndentSettings();
};
var mkFilePicker = function (framework, editor, evModeChange) {
evModeChange.reg(function (mode) {
if (MEDIA_TAG_MODES.indexOf(mode) !== -1) {
// Embedding is endabled
framework.setMediaTagEmbedder(function (mt) {
editor.replaceSelection($(mt)[0].outerHTML);
});
} else {
// Embedding is disabled
framework.setMediaTagEmbedder();
}
});
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////
var andThen2 = function (editor, CodeMirror, framework, isPresentMode) {
var common = framework._.sfCommon;
var previewPane = mkPreviewPane(editor, CodeMirror, framework, isPresentMode);
var markdownTb = mkMarkdownTb(editor, framework);
mkHelpMenu(framework);
var evModeChange = Util.mkEvent();
evModeChange.reg(previewPane.modeChange);
evModeChange.reg(markdownTb.modeChange);
mkIndentSettings(editor, framework._.cpNfInner.metadataMgr);
CodeMirror.init(framework.localChange, framework._.title, framework._.toolbar);
mkFilePicker(framework, editor, evModeChange);
if (!framework.isReadOnly()) {
CodeMirror.configureTheme(common, function () {
CodeMirror.configureLanguage(common, null, evModeChange.fire);
});
} else {
CodeMirror.configureTheme(common);
}
////
framework.onContentUpdate(function (newContent) {
var highlightMode = newContent.highlightMode;
if (highlightMode && highlightMode !== CodeMirror.highlightMode) {
CodeMirror.setMode(highlightMode, evModeChange.fire);
}
CodeMirror.contentUpdate(newContent);
previewPane.draw();
});
framework.setContentGetter(function () {
var content = CodeMirror.getContent();
content.highlightMode = CodeMirror.highlightMode;
previewPane.draw();
return content;
});
framework.onEditableChange(function () {
editor.setOption('readOnly', framework.isLocked() || framework.isReadOnly());
});
framework.setTitleRecommender(CodeMirror.getHeadingText);
framework.onReady(function (newPad) {
editor.focus();
if (newPad && !CodeMirror.highlightMode) {
CodeMirror.setMode('gfm', evModeChange.fire);
//console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val());
}
var fmConfig = {
dropArea: $('.CodeMirror'),
body: $('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
}
};
common.createFileManager(fmConfig);
});
framework.onDefaultContentNeeded(function () {
editor.setValue(''); //Messages.codeInitialState);
});
framework.setFileExporter(CodeMirror.getContentExtension, CodeMirror.fileExporter);
framework.setFileImporter({}, CodeMirror.fileImporter);
framework.setNormalizer(function (c) {
return {
content: c.content,
highlightMode: c.highlightMode
};
});
editor.on('change', framework.localChange);
framework.start();
};
var getThumbnailContainer = function () {
var $preview = $('#cp-app-code-preview-content');
if ($preview.length && $preview.is(':visible')) {
return $preview[0];
}
};
var main = function () {
var CodeMirror;
var editor;
var framework;
nThen(function (waitFor) {
Framework.create({
toolbarContainer: '#cme_toolbox',
contentContainer: '#cp-app-code-editor',
thumbnail: {
getContainer: getThumbnailContainer,
filter: function (el, before) {
if (before) {
//$(el).parents().css('overflow', 'visible');
$(el).css('max-height', Math.max(600, $(el).width()) + 'px');
return;
}
$(el).parents().css('overflow', '');
$(el).css('max-height', '');
editor.refresh();
}
}
}, waitFor(function (fw) { framework = fw; }));
nThen(function (waitFor) {
$(waitFor());
}).nThen(function () {
CodeMirror = SFCodeMirror.create(null, CMeditor);
$('#cp-app-code-container').addClass('cp-app-code-fullpage');
editor = CodeMirror.editor;
}).nThen(waitFor());
}).nThen(function (/*waitFor*/) {
framework._.sfCommon.isPresentUrl(function (err, val) {
andThen2(editor, CodeMirror, framework, val);
});
});
};
main();
});

View File

@@ -1,559 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/bower_components/textpatcher/TextPatcher.js',
'/common/toolbar2.js',
'json.sortify',
'/bower_components/chainpad-json-validator/json-ot.js',
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/common/diffMarked.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less/cryptpad.less'
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad,
Cryptget, DiffMd) {
var Messages = Cryptpad.Messages;
var APP = window.APP = {
Cryptpad: Cryptpad,
};
$(function () {
Cryptpad.addLoadingScreen();
var ifrw = APP.ifrw = $('#pad-iframe')[0].contentWindow;
var stringify = function (obj) {
return JSONSortify(obj);
};
var toolbar;
var editor;
var secret = Cryptpad.getSecrets();
var readOnly = secret.keys && !secret.keys.editKeyStr;
if (!secret.keys) {
secret.keys = secret.key;
}
var onConnectError = function () {
Cryptpad.errorLoadingScreen(Messages.websocketError);
};
var andThen = function (CMeditor) {
var $iframe = $('#pad-iframe').contents();
var $contentContainer = $iframe.find('#editorContainer');
var $previewContainer = $iframe.find('#previewContainer');
var $preview = $iframe.find('#preview');
$preview.click(function (e) {
if (!e.target) { return; }
var $t = $(e.target);
if ($t.is('a') || $t.parents('a').length) {
e.preventDefault();
var $a = $t.is('a') ? $t : $t.parents('a').first();
var href = $a.attr('href');
window.open(href);
}
});
var CodeMirror = Cryptpad.createCodemirror(ifrw, Cryptpad, null, CMeditor);
$iframe.find('.CodeMirror').addClass('fullPage');
editor = CodeMirror.editor;
var setIndentation = APP.setIndentation = function (units, useTabs) {
if (typeof(units) !== 'number') { return; }
editor.setOption('indentUnit', units);
editor.setOption('tabSize', units);
editor.setOption('indentWithTabs', useTabs);
};
var indentKey = 'cryptpad.indentUnit';
var useTabsKey = 'cryptpad.indentWithTabs';
var proxy = Cryptpad.getProxy();
var updateIndentSettings = APP.updateIndentSettings = function () {
var indentUnit = proxy[indentKey];
var useTabs = proxy[useTabsKey];
setIndentation(
typeof(indentUnit) === 'number'? indentUnit: 2,
typeof(useTabs) === 'boolean'? useTabs: false);
};
proxy.on('change', [indentKey], updateIndentSettings);
proxy.on('change', [useTabsKey], updateIndentSettings);
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
var isHistoryMode = false;
var setEditable = APP.setEditable = function (bool) {
if (readOnly && bool) { return; }
editor.setOption('readOnly', !bool);
};
var Title;
var UserList;
var Metadata;
var config = {
initialState: '{}',
websocketURL: Cryptpad.getWebsocketURL(),
channel: secret.channel,
// our public key
validateKey: secret.keys.validateKey || undefined,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
network: Cryptpad.getNetwork(),
transformFunction: JsonOT.validate,
};
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
var setHistory = function (bool, update) {
isHistoryMode = bool;
setEditable(!bool);
if (!bool && update) {
config.onRemote();
}
};
var initializing = true;
var stringifyInner = function (textValue) {
var obj = {
content: textValue,
metadata: {
users: UserList.userData,
defaultTitle: Title.defaultTitle
}
};
if (!initializing) {
obj.metadata.title = Title.title;
}
// set mode too...
obj.highlightMode = CodeMirror.highlightMode;
// stringify the json and send it into chainpad
return stringify(obj);
};
var forceDrawPreview = function () {
try {
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
} catch (e) { console.error(e); }
};
var drawPreview = Cryptpad.throttle(function () {
if (CodeMirror.highlightMode !== 'markdown') { return; }
if (!$previewContainer.is(':visible')) { return; }
forceDrawPreview();
}, 150);
var onLocal = config.onLocal = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
if (readOnly) { return; }
editor.save();
drawPreview();
var textValue = canonicalize(CodeMirror.$textarea.val());
var shjson = stringifyInner(textValue);
APP.patchText(shjson);
if (APP.realtime.getUserDoc() !== shjson) {
console.error("realtime.getUserDoc() !== shjson");
}
};
var mediaTagModes = [
'markdown',
'html',
'htmlembedded',
'htmlmixed',
'index.html',
'php',
'velocity',
'xml',
];
var onModeChanged = function (mode) {
var $codeMirror = $iframe.find('.CodeMirror');
window.clearTimeout(APP.previewTo);
$codeMirror.addClass('transition');
APP.previewTo = window.setTimeout(function () {
$codeMirror.removeClass('transition');
}, 500);
if (mediaTagModes.indexOf(mode) !== -1) {
APP.$mediaTagButton.show();
} else { APP.$mediaTagButton.hide(); }
if (mode === "markdown") {
APP.$previewButton.show();
Cryptpad.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if (data !== false) {
$previewContainer.show();
APP.$previewButton.addClass('active');
$codeMirror.removeClass('fullPage');
}
});
return;
}
APP.$previewButton.hide();
$previewContainer.hide();
APP.$previewButton.removeClass('active');
$codeMirror.addClass('fullPage');
if (typeof(APP.updateIndentSettings) === 'function') {
APP.updateIndentSettings();
}
};
config.onInit = function (info) {
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
var titleCfg = { getHeadingText: CodeMirror.getHeadingText };
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
Metadata = Cryptpad.createMetadata(UserList, Title, null, Cryptpad);
var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
userList: UserList.getToolbarConfig(),
share: {
secret: secret,
channel: info.channel
},
title: Title.getTitleConfig(),
common: Cryptpad,
readOnly: readOnly,
ifrw: ifrw,
realtime: info.realtime,
network: info.network,
$container: $bar,
$contentContainer: $contentContainer
};
toolbar = APP.toolbar = Toolbar.create(configTb);
Title.setToolbar(toolbar);
CodeMirror.init(config.onLocal, Title, toolbar);
var $rightside = toolbar.$rightside;
var $drawer = toolbar.$drawer;
var editHash;
if (!readOnly) {
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
}
/* add a history button */
var histConfig = {
onLocal: config.onLocal,
onRemote: config.onRemote,
setHistory: setHistory,
applyVal: function (val) {
var remoteDoc = JSON.parse(val || '{}').content;
editor.setValue(remoteDoc || '');
editor.save();
},
$toolbar: $bar
};
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
$drawer.append($hist);
/* save as template */
if (!Cryptpad.isTemplate(window.location.href)) {
var templateObj = {
rt: info.realtime,
Crypt: Cryptget,
getTitle: Title.getTitle
};
var $templateButton = Cryptpad.createButton('template', true, templateObj);
$rightside.append($templateButton);
}
/* add an export button */
var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText);
$drawer.append($export);
if (!readOnly) {
/* add an import button */
var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText);
$drawer.append($import);
}
/* add a forget button */
var forgetCb = function (err) {
if (err) { return; }
setEditable(false);
};
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
var fileDialogCfg = {
$body: $iframe.find('body'),
onSelect: function (href) {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
},
data: APP
};
APP.$mediaTagButton = $('<button>', {
title: Messages.filePickerButton,
'class': 'rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
Cryptpad.createFileDialog(fileDialogCfg);
}).appendTo($rightside);
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
$previewButton.removeClass('fa-question').addClass('fa-eye');
$previewButton.attr('title', Messages.previewButtonTitle);
$previewButton.click(function () {
var $codeMirror = $iframe.find('.CodeMirror');
window.clearTimeout(APP.previewTo);
$codeMirror.addClass('transition');
APP.previewTo = window.setTimeout(function () {
$codeMirror.removeClass('transition');
}, 500);
if (CodeMirror.highlightMode !== 'markdown') {
$previewContainer.show();
}
$previewContainer.toggle();
if ($previewContainer.is(':visible')) {
forceDrawPreview();
$codeMirror.removeClass('fullPage');
Cryptpad.setPadAttribute('previewMode', true, function (e) {
if (e) { return console.log(e); }
});
$previewButton.addClass('active');
} else {
$codeMirror.addClass('fullPage');
$previewButton.removeClass('active');
Cryptpad.setPadAttribute('previewMode', false, function (e) {
if (e) { return console.log(e); }
});
}
});
$rightside.append($previewButton);
if (!readOnly) {
CodeMirror.configureTheme(function () {
CodeMirror.configureLanguage(null, onModeChanged);
});
}
else {
CodeMirror.configureTheme();
}
// set the hash
if (!readOnly) { Cryptpad.replaceHash(editHash); }
};
config.onReady = function (info) {
if (APP.realtime !== info.realtime) {
var realtime = APP.realtime = info.realtime;
APP.patchText = TextPatcher.create({
realtime: realtime,
//logging: true
});
}
var userDoc = APP.realtime.getUserDoc();
var isNew = false;
if (userDoc === "" || userDoc === "{}") { isNew = true; }
var newDoc = "";
if(userDoc !== "") {
var hjson = JSON.parse(userDoc);
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'code')) {
var errorText = Messages.typeError;
Cryptpad.errorLoadingScreen(errorText);
throw new Error(errorText);
}
newDoc = hjson.content;
if (hjson.highlightMode) {
CodeMirror.setMode(hjson.highlightMode, onModeChanged);
}
}
if (!CodeMirror.highlightMode) {
CodeMirror.setMode('markdown', onModeChanged);
console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val());
}
// Update the user list (metadata) from the hyperjson
Metadata.update(userDoc);
if (newDoc) {
editor.setValue(newDoc);
}
if (Cryptpad.initialName && Title.isDefaultTitle()) {
Title.updateTitle(Cryptpad.initialName);
}
Cryptpad.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if (data === false && APP.$previewButton) {
APP.$previewButton.click();
}
});
/*
// add the splitter
if (!$iframe.has('.cp-splitter').length) {
var $preview = $iframe.find('#previewContainer');
var splitter = $('<div>', {
'class': 'cp-splitter'
}).appendTo($preview);
$preview.on('scroll', function() {
splitter.css('top', $preview.scrollTop() + 'px');
});
var $target = $iframe.find('.CodeMirror');
splitter.on('mousedown', function (e) {
e.preventDefault();
var x = e.pageX;
var w = $target.width();
$iframe.on('mouseup mousemove', function handler(evt) {
if (evt.type === 'mouseup') {
$iframe.off('mouseup mousemove', handler);
return;
}
$target.css('width', (w - x + evt.pageX) + 'px');
});
});
}
*/
Cryptpad.removeLoadingScreen();
setEditable(true);
initializing = false;
onLocal(); // push local state to avoid parse errors later.
if (readOnly) {
config.onRemote();
return;
}
UserList.getLastName(toolbar.$userNameButton, isNew);
var fmConfig = {
dropArea: $iframe.find('.CodeMirror'),
body: $iframe.find('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
var parsed = Cryptpad.parsePadUrl(data.url);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
}
};
APP.FM = Cryptpad.createFileManager(fmConfig);
};
config.onRemote = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
var oldDoc = canonicalize(CodeMirror.$textarea.val());
var shjson = APP.realtime.getUserDoc();
// Update the user list (metadata) from the hyperjson
Metadata.update(shjson);
var hjson = JSON.parse(shjson);
var remoteDoc = hjson.content;
var highlightMode = hjson.highlightMode;
if (highlightMode && highlightMode !== APP.highlightMode) {
CodeMirror.setMode(highlightMode, onModeChanged);
}
CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
drawPreview();
if (!readOnly) {
var textValue = canonicalize(CodeMirror.$textarea.val());
var shjson2 = stringifyInner(textValue);
if (shjson2 !== shjson) {
console.error("shjson2 !== shjson");
TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2));
APP.patchText(shjson2);
}
}
if (oldDoc !== remoteDoc) { Cryptpad.notify(); }
};
config.onAbort = function () {
// inform of network disconnect
setEditable(false);
toolbar.failed();
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
};
config.onConnectionChange = function (info) {
setEditable(info.state);
toolbar.failed();
if (info.state) {
initializing = true;
toolbar.reconnecting(info.myId);
Cryptpad.findOKButton().click();
} else {
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
}
};
config.onError = onConnectError;
APP.realtime = Realtime.start(config);
editor.on('change', onLocal);
Cryptpad.onLogout(function () { setEditable(false); });
};
var interval = 100;
var second = function (CM) {
Cryptpad.ready(function () {
andThen(CM);
Cryptpad.reportAppUsage();
});
Cryptpad.onError(function (info) {
if (info && info.type === "store") {
onConnectError();
}
});
};
var first = function () {
if (ifrw.CodeMirror) {
second(ifrw.CodeMirror);
} else {
console.log("CodeMirror was not defined. Trying again in %sms", interval);
setTimeout(first, interval);
}
};
first();
});
});

156
www/code/orgmode.js Normal file
View File

@@ -0,0 +1,156 @@
define([
'cm/lib/codemirror',
'cm/addon/mode/simple'
], function (CodeMirror) {
CodeMirror.__mode = 'orgmode';
CodeMirror.defineSimpleMode("orgmode", {
start: [
{regex: /^(^\*{1,6}\s)(TODO|DOING|WAITING|NEXT){0,1}(CANCELLED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED){0,1}(.*)$/, token: ["header org-level-star", "header org-todo", "header org-done", "header"]},
{regex: /(\+[^\+]+\+)/, token: ["strikethrough"]},
{regex: /(\*[^\*]+\*)/, token: ["strong"]},
{regex: /(\/[^\/]+\/)/, token: ["em"]},
{regex: /(\_[^\_]+\_)/, token: ["link"]},
{regex: /(\~[^\~]+\~)/, token: ["comment"]},
{regex: /(\=[^\=]+\=)/, token: ["comment"]},
{regex: /\[\[[^\[\]]*\]\[[^\[\]]*\]\]/, token: "url"}, // links
{regex: /\[[xX\s]?\]/, token: 'qualifier'}, // checkbox
{regex: /\#\+BEGIN_[A-Z]*/, token: "comment", next: "env"}, // comments
{regex: /:?[A-Z_]+\:.*/, token: "comment"}, // property drawers
{regex: /(\#\+[A-Z_]*)(\:.*)/, token: ["keyword", 'qualifier']}, // environments
{regex: /(CLOCK\:|SHEDULED\:)(\s.+)/, token: ["comment", "keyword"]}
],
env: [
{regex: /.*?\#\+END_[A-Z]*/, token: "comment", next: "start"},
{regex: /.*/, token: "comment"}
]
});
CodeMirror.registerHelper("fold", "orgmode", function(cm, start) {
function headerLevel (lineNo) {
var line = cm.getLine(lineNo);
var match = /^\*+/.exec(line);
if (match && match.length === 1 && /header/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0)))) {
return match[0].length;
}
return null;
}
// init
var levelToMatch = headerLevel(start.line);
// no folding needed
if(levelToMatch === null) { return; }
// find folding limits
var lastLine = cm.lastLine();
var end = start.line;
while (end < lastLine){
end += 1;
var level = headerLevel(end);
if (level && level <= levelToMatch) {
end = end - 1;
break;
}
}
return {
from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: CodeMirror.Pos(end, cm.getLine(end).length)
};
});
CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
return mode.name === 'orgmode' ? true : false;
}, function(cm, start) {
function isBeginningOfADrawer(lineNo) {
var line = cm.getLine(lineNo);
var match = /^\:.*\:$/.exec(line);
if(match && match.length === 1 && match[0] !== ':END:'){
return true;
}
return false;
}
function isEndOfADrawer(lineNo){
var line = cm.getLine(lineNo);
return line.trim() === ':END:' ? true : false;
}
var drawer = isBeginningOfADrawer(start.line);
if (drawer === false) { return; }
// find folding limits
var lastLine = cm.lastLine();
var end = start.line;
while(end < lastLine){
end += 1;
if (isEndOfADrawer(end)) {
break;
}
}
return {
from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: CodeMirror.Pos(end, cm.getLine(end).length)
};
});
CodeMirror.afterInit = function(editor){
function fold(cm, start){
cm.foldCode(start, null, "fold");
}
function unfold(cm, start){
cm.foldCode(start, null, "unfold");
}
function isFold(cm, start){
var line = start.line;
var marks = cm.findMarks(CodeMirror.Pos(line, 0), CodeMirror.Pos(line + 1, 0));
for (var i = 0; i < marks.length; ++i) {
if (marks[i].__isFold && marks[i].find().from.line === line) { return marks[i]; }
}
return false;
}
var state = {
stab: 'OVERVIEW'
};
editor.setOption("extraKeys", {
"Tab": function(cm) {
var pos = cm.getCursor();
return isFold(cm, pos) ? unfold(cm, pos) : fold(cm, pos);
},
"Shift-Tab": function(cm){
if(state.stab === "SHOW_ALL"){
// fold everything that can be fold
state.stab = 'OVERVIEW';
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
fold(cm, CodeMirror.Pos(i, 0));
}
});
}else{
// unfold all headers
state.stab = 'SHOW_ALL';
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
if(/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true){
unfold(cm, CodeMirror.Pos(i, 0));
}
}
});
}
}
});
editor.on('touchstart', function(cm){
setTimeout(function () {
return isFold(cm, cm.getCursor()) ? unfold(cm, cm.getCursor()) : fold(cm, cm.getCursor());
}, 150);
});
// fold everything except headers by default
editor.operation(function() {
for (var i = 0; i < editor.lineCount() ; i++) {
if(/header/.test(editor.getTokenTypeAt(CodeMirror.Pos(i, 0))) === false){
fold(editor, CodeMirror.Pos(i, 0));
}
}
});
};
});

View File

@@ -1,36 +1,60 @@
/*@flow*/
/*:: const define = () => {}; */
/*::
const define = (x:any, y:any) => {};
const require = define;
*/
define([
'/api/config',
'/bower_components/less/dist/less.min.js'
], function (Config, Less) { /*::});module.exports = (function() {
'/api/config'
], function (Config) { /*::});module.exports = (function() {
const Config = (undefined:any);
const Less = (undefined:any);
*/
var module = { exports: {} };
var key = Config.requireConf.urlArgs;
var localStorage;
try {
localStorage = window.localStorage || {};
} catch (e) {
console.error(e);
localStorage = {};
var localStorage = {};
if (!window.cryptpadCache) {
try {
localStorage = window.localStorage || {};
if (localStorage['LESS_CACHE'] !== key) {
Object.keys(localStorage).forEach(function (k) {
if (k.indexOf('LESS_CACHE|') !== 0) { return; }
delete localStorage[k];
});
localStorage['LESS_CACHE'] = key;
}
} catch (e) {
console.error(e);
localStorage = {};
}
}
var fixURL = function (url) {
var mark = (url.indexOf('?') !== -1) ? '&' : '?';
return url + mark + key;
var cacheGet = function (k, cb) {
if (window.cryptpadCache) { return void window.cryptpadCache.get(k, cb); }
setTimeout(function () { cb(localStorage['LESS_CACHE|' + key + '|' + k]); });
};
var cachePut = function (k, v, cb) {
if (window.cryptpadCache) { return void window.cryptpadCache.put(k, v, cb); }
setTimeout(function () {
localStorage['LESS_CACHE|' + key + '|' + k] = v;
if (cb) { cb(); }
});
};
var doXHR = Less.FileManager.prototype.doXHR;
Less.FileManager.prototype.doXHR = function (url, type, callback, errback) {
url = fixURL(url);
//console.log("xhr: " + url);
return doXHR(url, type, callback, errback);
var fixURL = function (url, parent) {
// data: blob: etc
if (/^[a-zA-Z0-9]*:/.test(url)) { return url; }
var ua = url.split('#');
var mark = (ua[0].indexOf('?') !== -1) ? '&' : '?';
ua[0] = ua[0] + mark + key;
if (ua[0].indexOf(':') === -1 && ua[0].indexOf('/') && parent) {
ua[0] = parent.replace(/\/[^\/]*$/, '/') + ua[0];
}
var out = ua.join('#');
//console.log(url + " --> " + out);
return out;
};
var inject = function (cssText, url) {
var inject = function (cssText /*:string*/, url) {
var curStyle = document.createElement('style');
curStyle.setAttribute('data-original-src', url);
curStyle.type = 'text/css';
@@ -39,31 +63,75 @@ define([
document.head.appendChild(curStyle);
};
var checkCache = function () {
if (localStorage['LESS_CACHE'] === key) { return; }
Object.keys(localStorage).forEach(function (k) {
if (k.indexOf('LESS_CACHE|') !== 0) { return; }
delete localStorage[k];
var fixAllURLs = function (source /*:string*/, parent) {
var urlRegEx = /@import\s*("([^"]*)"|'([^']*)')|url\s*\(\s*(\s*"([^"]*)"|'([^']*)'|[^\)]*\s*)\s*\)/ig;
var result, url;
while (!!(result = urlRegEx.exec(source))) {
url = result[3] || result[2] || result[5] || result[6] || result[4];
var newUrl = fixURL(url, parent);
var quoteLen = result[5] || result[6] ? 1 : 0;
source = source.substr(0, urlRegEx.lastIndex - url.length - quoteLen - 1)
+ newUrl + source.substr(urlRegEx.lastIndex - quoteLen - 1);
urlRegEx.lastIndex = urlRegEx.lastIndex + (newUrl.length - url.length);
}
return source;
};
var loadCSS = function (url, cb) {
var xhr = new window.XMLHttpRequest();
xhr.open("GET", fixURL(url), true);
xhr.responseType = 'text';
xhr.onload = function () {
if (/^4/.test('' + this.status)) { return cb("error loading " + url); }
cb(undefined, xhr.response);
};
xhr.send(null);
};
var lessEngine;
var getLessEngine = function (cb) {
if (lessEngine) {
cb(lessEngine);
} else {
require(['/bower_components/less/dist/less.min.js'], function (Less) {
lessEngine = Less;
var doXHR = lessEngine.FileManager.prototype.doXHR;
lessEngine.FileManager.prototype.doXHR = function (url, type, callback, errback) {
url = fixURL(url);
//console.log("xhr: " + url);
return doXHR(url, type, callback, errback);
};
cb(lessEngine);
});
}
};
var loadLess = function (url, cb) {
getLessEngine(function (less) {
less.render('@import (multiple) "' + url + '";', {}, function(err, css) {
if (err) { return void cb(err); }
cb(undefined, css.css);
}, window.less);
});
localStorage['LESS_CACHE'] = key;
};
module.exports.load = function (url /*:string*/, cb /*:()=>void*/) {
checkCache();
if (localStorage['LESS_CACHE|' + key + '|' + url]) {
inject(localStorage['LESS_CACHE|' + key + '|' + url], url);
cb();
return;
}
Less.render('@import (multiple) "' + url + '";', {}, function(err, css) {
if (err) {
console.log(err);
return;
cacheGet(url, function (css) {
if (css) {
inject(css, url);
return void cb();
}
localStorage['LESS_CACHE|' + key + '|' + url] = css.css;
inject(css.css, url);
cb();
}, window.less);
console.log('CACHE MISS ' + url);
((/\.less([\?\#].*)?$/.test(url)) ? loadLess : loadCSS)(url, function (err, css) {
if (!css) { return void console.error(err); }
var output = fixAllURLs(css, url);
cachePut(url, output);
inject(output, url);
cb();
});
});
};
return module.exports;

View File

@@ -0,0 +1,121 @@
/*
* This is an internal configuration file.
* If you want to change some configurable values, use the '/customize/application_config.js'
* file (make a copy from /customize.dist/application_config.js)
*/
define(function() {
var config = {};
/* Select the buttons displayed on the main page to create new collaborative sessions
* Existing types : pad, code, poll, slide
*/
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard', 'file', 'todo', 'contacts'];
config.registeredOnlyTypes = ['file', 'contacts'];
/* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds
* You can change their duration here (measured in milliseconds)
*/
config.notificationTimeout = 5000;
config.disableUserlistNotifications = false;
config.hideLoadingScreenTips = false;
config.enablePinning = true;
// Update the default colors available in the whiteboard application
config.whiteboardPalette = [
'#000000', // black
'#FFFFFF', // white
'#848484', // grey
'#8B4513', // saddlebrown
'#FF0000', // red
'#FF8080', // peach?
'#FF8000', // orange
'#FFFF00', // yellow
'#80FF80', // light green
'#00FF00', // green
'#00FFFF', // cyan
'#008B8B', // dark cyan
'#0000FF', // blue
'#FF00FF', // fuschia
'#FF00C0', // hot pink
'#800080', // purple
];
// Background color in the apps with centered content:
// - file app in view mode
// - rich text app when editor's width reduced in settings
config.appBackgroundColor = '#666';
// Set enableTemplates to false to remove the button allowing users to save a pad as a template
// and remove the template category in CryptDrive
config.enableTemplates = true;
// Set enableHistory to false to remove the "History" button in all the apps.
config.enableHistory = true;
/* 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 = '';
config.minimumPasswordLength = 8;
// Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad
config.badStateTimeout = 30000;
// Customize the icon used for each application.
// You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less
config.applicationsIcon = {
file: 'fa-file-text-o',
pad: 'fa-file-word-o',
code: 'fa-file-code-o',
slide: 'fa-file-powerpoint-o',
poll: 'fa-calendar',
whiteboard: 'fa-paint-brush',
todo: 'fa-tasks',
contacts: 'fa-users',
};
// Ability to create owned pads and expiring pads through a new pad creation screen.
// The new screen can be disabled by the users in their settings page
config.displayCreationScreen = true;
// Prevent anonymous users from storing pads in their drive
config.disableAnonymousStore = false;
// Hide the usage bar in settings and drive
//config.hideUsageBar = true;
// Disable feedback for all the users and hide the settings part about feedback
//config.disableFeedback = true;
// Add new options in the share modal (extend an existing tab or add a new tab).
// More info about how to use it on the wiki:
// https://github.com/xwiki-labs/cryptpad/wiki/Application-config#configcustomizeshareoptions
//config.customizeShareOptions = function (hashes, tabs, config) {};
// Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is
// indicating if the user is registered or anonymous. Here you can change the way anonymous users
// work in CryptPad, use an external SSO or even force registration
// *NOTE*: You have to call the `callback` function to continue the loading process
//config.beforeLogin = function(isLoggedIn, callback) {};
// Add code to be executed on every page after the user object is loaded (also work for
// unregistered users). This allows you to interact with your users' drive
// *NOTE*: You have to call the `callback` function to continue the loading process
//config.afterLogin = function(api, callback) {};
// Disabling the profile app allows you to import the profile informations (display name, avatar)
// from an external source and make sure the users can't change them from CryptPad.
// You can use config.afterLogin to import these values in the users' drive.
//config.disableProfile = true;
return config;
});

View File

@@ -21,6 +21,11 @@ define([
};
}
// RPC breaks if you don't support Number.MAX_SAFE_INTEGER
if (Number && !Number.MAX_SAFE_INTEGER) {
Number.MAX_SAFE_INTEGER = 9007199254740991;
}
var failStore = function () {
console.error(new Error('wut'));
require(['jquery'], function ($) {
@@ -29,7 +34,16 @@ define([
url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()),
});
});
window.alert("CryptPad needs localStorage to work, try a different browser");
window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser");
};
window.onerror = function (e) {
if (/requirejs\.org/.test(e)) {
console.log();
console.error("Require.js threw a Script Error. This probably means you're missing a dependency for CryptPad.\nIt is recommended that the admin of this server runs `bower install && bower update` to get the latest code, then modify their cache version.\nBest of luck,\nThe CryptPad Developers");
return void console.log();
}
throw e;
};
try {

View File

@@ -1,306 +0,0 @@
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 (ifrw, Cryptpad, defaultMode, CMeditor) {
var exp = {};
var Messages = Cryptpad.Messages;
var CodeMirror = exp.CodeMirror = CMeditor;
CodeMirror.modeURL = "cm/mode/%N/%N";
var $pad = $('#pad-iframe');
var $textarea = exp.$textarea = $pad.contents().find('#editor1');
var Title;
var onLocal = function () {};
var $rightside;
var $drawer;
exp.init = function (local, title, toolbar) {
if (typeof local === "function") {
onLocal = local;
}
Title = title;
$rightside = toolbar.$rightside;
$drawer = toolbar.$drawer;
};
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: defaultMode || "javascript",
readOnly: true
});
editor.setValue(Messages.codeInitialState);
var setMode = exp.setMode = function (mode, cb) {
exp.highlightMode = mode;
if (mode !== "text") {
CMeditor.autoLoadMode(editor, mode);
}
editor.setOption('mode', mode);
if (exp.$language) {
var name = exp.$language.find('a[data-value="' + mode + '"]').text() || undefined;
name = name ? Messages.languageButton + ' ('+name+')' : Messages.languageButton;
exp.$language.setValue(mode, name);
}
if(cb) { cb(mode); }
};
var setTheme = exp.setTheme = (function () {
var path = '/common/theme/';
var $head = $(ifrw.document.head);
var themeLoaded = exp.themeLoaded = function (theme) {
return $head.find('link[href*="'+theme+'"]').length;
};
var loadTheme = exp.loadTheme = function (theme) {
$head.append($('<link />', {
rel: 'stylesheet',
href: path + theme + '.css',
}));
};
return function (theme, $select) {
if (!theme) {
editor.setOption('theme', 'default');
} else {
if (!themeLoaded(theme)) {
loadTheme(theme);
}
editor.setOption('theme', theme);
}
if ($select) {
var name = theme || undefined;
name = name ? Messages.themeButton + ' ('+theme+')' : Messages.themeButton;
$select.setValue(theme, name);
}
};
}());
exp.getHeadingText = function () {
var lines = editor.getValue().split(/\n/);
var text = '';
lines.some(function (line) {
// 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;
}
// 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;
}
// TODO make one more pass for multiline comments
});
return text.trim();
};
exp.configureLanguage = function (cb, onModeChanged) {
var options = [];
Modes.list.forEach(function (l) {
options.push({
tag: 'a',
attributes: {
'data-value': l.mode,
'href': '#',
},
content: l.language // Pretty name of the language value
});
});
var dropdownConfig = {
text: 'Mode', // Button initial text
options: options, // Entries displayed in the menu
left: true, // Open to the left of the button
isSelect: true,
feedback: 'CODE_LANGUAGE',
};
var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig);
$block.find('button').attr('title', Messages.languageButtonTitle);
$block.find('a').click(function () {
setMode($(this).attr('data-value'), onModeChanged);
onLocal();
});
if ($drawer) { $drawer.append($block); }
if (cb) { cb(); }
};
exp.configureTheme = function (cb) {
/* Remember the user's last choice of theme using localStorage */
var themeKey = 'CRYPTPAD_CODE_THEME';
var lastTheme = localStorage.getItem(themeKey) || 'default';
var options = [];
Themes.forEach(function (l) {
options.push({
tag: 'a',
attributes: {
'data-value': l.name,
'href': '#',
},
content: l.name // Pretty name of the language value
});
});
var dropdownConfig = {
text: 'Theme', // Button initial text
options: options, // Entries displayed in the menu
left: true, // Open to the left of the button
isSelect: true,
initialValue: lastTheme,
feedback: 'CODE_THEME',
};
var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig);
$block.find('button').attr('title', Messages.themeButtonTitle);
setTheme(lastTheme, $block);
$block.find('a').click(function () {
var theme = $(this).attr('data-value');
setTheme(theme, $block);
localStorage.setItem(themeKey, theme);
});
if ($drawer) { $drawer.append($block); }
if (cb) { cb(); }
};
exp.exportText = function () {
var text = editor.getValue();
var ext = Modes.extensionOf(exp.highlightMode);
var title = Cryptpad.fixFileName(Title ? Title.suggestTitle('cryptpad') : "?") + (ext || '.txt');
Cryptpad.prompt(Messages.exportPrompt, title, function (filename) {
if (filename === null) { return; }
var blob = new Blob([text], {
type: 'text/plain;charset=utf-8'
});
saveAs(blob, filename);
});
};
exp.importText = function (content, file) {
var $bar = ifrw.$('#cme_toolbox');
var mode;
var mime = CodeMirror.findModeByMIME(file.type);
if (!mime) {
var ext = /.+\.([^.]+)$/.exec(file.name);
if (ext[1]) {
mode = CMeditor.findModeByExtension(ext[1]);
}
} else {
mode = mime && mime.mode || null;
}
if (mode && Modes.list.some(function (o) { return o.mode === mode; })) {
setMode(mode);
$bar.find('#language-mode').val(mode);
} else {
console.log("Couldn't find a suitable highlighting mode: %s", mode);
setMode('text');
$bar.find('#language-mode').val('text');
}
editor.setValue(content);
onLocal();
};
var cursorToPos = function(cursor, oldText) {
var cLine = cursor.line;
var cCh = cursor.ch;
var pos = 0;
var textLines = oldText.split("\n");
for (var line = 0; line <= cLine; line++) {
if(line < cLine) {
pos += textLines[line].length+1;
}
else if(line === cLine) {
pos += cCh;
}
}
return pos;
};
var posToCursor = function(position, newText) {
var cursor = {
line: 0,
ch: 0
};
var textLines = newText.substr(0, position).split("\n");
cursor.line = textLines.length - 1;
cursor.ch = textLines[cursor.line].length;
return cursor;
};
exp.setValueAndCursor = function (oldDoc, remoteDoc, TextPatcher) {
var scroll = editor.getScrollInfo();
//get old cursor here
var oldCursor = {};
oldCursor.selectionStart = cursorToPos(editor.getCursor('from'), oldDoc);
oldCursor.selectionEnd = cursorToPos(editor.getCursor('to'), oldDoc);
editor.setValue(remoteDoc);
editor.save();
var op = TextPatcher.diff(oldDoc, remoteDoc);
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
return TextPatcher.transformCursor(oldCursor[attr], op);
});
if(selects[0] === selects[1]) {
editor.setCursor(posToCursor(selects[0], remoteDoc));
}
else {
editor.setSelection(posToCursor(selects[0], remoteDoc), posToCursor(selects[1], remoteDoc));
}
editor.scrollTo(scroll.left, scroll.top);
};
return exp;
};
return module;
});

View File

@@ -0,0 +1,16 @@
define(function () {
return {
// localStorage
userHashKey: 'User_hash',
userNameKey: 'User_name',
fileHashKey: 'FS_hash',
// sessionStorage
newPadPathKey: "newPadPath",
// Store
displayNameKey: 'cryptpad.username',
oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData',
tokenKey: 'loginToken',
displayPadCreationScreen: 'displayPadCreationScreen'
};
});

View File

@@ -0,0 +1,56 @@
define([
'/customize/messages.js',
'/customize/application_config.js'
], function (Messages, AppConfig) {
var Feedback = {};
Feedback.init = function (state) {
Feedback.state = state;
};
var randomToken = function () {
return Math.random().toString(16).replace(/0./, '');
};
var ajax = function (url, cb) {
var http = new XMLHttpRequest();
http.open('HEAD', url);
http.onreadystatechange = function() {
if (this.readyState === this.DONE) {
if (cb) { cb(); }
}
};
http.send();
};
Feedback.send = function (action, force) {
if (AppConfig.disableFeedback) { return; }
if (!action) { return; }
if (force !== true) {
if (!Feedback.state) { return; }
}
var href = '/common/feedback.html?' + action + '=' + randomToken();
ajax(href);
};
Feedback.reportAppUsage = function () {
var pattern = window.location.pathname.split('/')
.filter(function (x) { return x; }).join('.');
if (/^#\/1\/view\//.test(window.location.hash)) {
Feedback.send(pattern + '_VIEW');
} else {
Feedback.send(pattern);
}
};
Feedback.reportScreenDimensions = function () {
var h = window.innerHeight;
var w = window.innerWidth;
Feedback.send('DIMENSIONS:' + h + 'x' + w);
};
Feedback.reportLanguage = function () {
Feedback.send('LANG_' + Messages._languageUsed);
};
return Feedback;
});

View File

@@ -1,334 +0,0 @@
define([
'jquery',
'/file/file-crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto) {
var Nacl = window.nacl;
var module = {};
var blobToArrayBuffer = function (blob, cb) {
var reader = new FileReader();
reader.onloadend = function () {
cb(void 0, this.result);
};
reader.readAsArrayBuffer(blob);
};
var arrayBufferToString = function (AB) {
try {
return Nacl.util.encodeBase64(new Uint8Array(AB));
} catch (e) {
console.error(e);
return null;
}
};
module.create = function (common, config) {
var File = {};
var Messages = common.Messages;
var queue = File.queue = {
queue: [],
inProgress: false
};
var uid = function () {
return 'file-' + String(Math.random()).substring(2);
};
var $table = File.$table = $('<table>', { id: 'uploadStatus' });
var $thead = $('<tr>').appendTo($table);
$('<td>').text(Messages.upload_name).appendTo($thead);
$('<td>').text(Messages.upload_size).appendTo($thead);
$('<td>').text(Messages.upload_progress).appendTo($thead);
$('<td>').text(Messages.cancel).appendTo($thead);
var createTableContainer = function ($body) {
File.$container = $('<div>', { id: 'uploadStatusContainer' }).append($table).appendTo($body);
return File.$container;
};
var getData = function (file, href) {
var data = {};
data.name = file.metadata.name;
data.url = href;
if (file.metadata.type.slice(0,6) === 'image/') {
data.mediatag = true;
}
return data;
};
var upload = function (file) {
var blob = file.blob;
var metadata = file.metadata;
var id = file.id;
if (queue.inProgress) { return; }
queue.inProgress = true;
var $row = $table.find('tr[id="'+id+'"]');
$row.find('.upCancel').html('-');
var $pv = $row.find('.progressValue');
var $pb = $row.find('.progressContainer');
var $pc = $row.find('.upProgress');
var $link = $row.find('.upLink');
var updateProgress = function (progressValue) {
$pv.text(Math.round(progressValue*100)/100 + '%');
$pb.css({
width: (progressValue/100)*$pc.width()+'px'
});
};
var u8 = new Uint8Array(blob);
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata);
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
console.log(box);
cb(e, msg);
});
};
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
});
}
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
// if not box then done
common.uploadComplete(function (e, id) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var b64Key = Nacl.util.encodeBase64(key);
var hash = common.getFileHashFromKeys(id, b64Key);
var href = '/file/#' + hash;
$link.attr('href', href)
.click(function (e) {
e.preventDefault();
window.open($link.attr('href'), '_blank');
});
var title = metadata.name;
var onComplete = function () {
common.log(Messages._getKey('upload_success', [title]));
common.prepareFeedback('upload')();
if (config.onUploaded) {
var data = getData(file, href);
config.onUploaded(file.dropEvent, data);
}
queue.inProgress = false;
queue.next();
};
if (config.noStore) { return void onComplete(); }
common.renamePad(title || "", href, function (err) {
if (err) { return void console.error(err); } // TODO
onComplete();
});
//Title.updateTitle(title || "", href);
//APP.toolbar.title.show();
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
queue.inProgress = false;
queue.next();
if (e === 'TOO_LARGE') {
// TODO update table to say too big?
return void common.alert(Messages.upload_tooLarge);
}
if (e === 'NOT_ENOUGH_SPACE') {
// TODO update table to say not enough space?
return void common.alert(Messages.upload_notEnoughSpace);
}
console.error(e);
return void common.alert(Messages.upload_serverError);
}
if (pending) {
// TODO keep this message in case of pending files in another window?
return void common.confirm(Messages.upload_uploadPending, function (yes) {
if (!yes) { return; }
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
next(again);
});
});
}
next(again);
});
};
var prettySize = function (bytes) {
var kB = common.bytesToKilobytes(bytes);
if (kB < 1024) { return kB + Messages.KB; }
var mB = common.bytesToMegabytes(bytes);
return mB + Messages.MB;
};
queue.next = function () {
if (queue.queue.length === 0) {
queue.to = window.setTimeout(function () {
if (config.keepTable) { return; }
File.$container.fadeOut();
}, 3000);
return;
}
if (queue.inProgress) { return; }
File.$container.show();
var file = queue.queue.shift();
upload(file);
};
queue.push = function (obj) {
var id = uid();
obj.id = id;
queue.queue.push(obj);
$table.show();
var estimate = FileCrypto.computeEncryptedSize(obj.blob.byteLength, obj.metadata);
var $progressBar = $('<div>', {'class':'progressContainer'});
var $progressValue = $('<span>', {'class':'progressValue'}).text(Messages.upload_pending);
var $tr = $('<tr>', {id: id}).appendTo($table);
var $cancel = $('<span>', {'class': 'cancel fa fa-times'}).click(function () {
queue.queue = queue.queue.filter(function (el) { return el.id !== id; });
$cancel.remove();
$tr.find('.upCancel').text('-');
$tr.find('.progressValue').text(Messages.upload_cancelled);
});
var $link = $('<a>', {
'class': 'upLink',
'rel': 'noopener noreferrer'
}).text(obj.metadata.name);
$('<td>').append($link).appendTo($tr);
$('<td>').text(prettySize(estimate)).appendTo($tr);
$('<td>', {'class': 'upProgress'}).append($progressBar).append($progressValue).appendTo($tr);
$('<td>', {'class': 'upCancel'}).append($cancel).appendTo($tr);
queue.next();
};
var handleFile = File.handleFile = function (file, e, thumbnail) {
var thumb;
var finish = function (arrayBuffer) {
var metadata = {
name: file.name,
type: file.type,
};
if (thumb) { metadata.thumbnail = thumb; }
queue.push({
blob: arrayBuffer,
metadata: metadata,
dropEvent: e
});
};
var processFile = function () {
blobToArrayBuffer(file, function (e, buffer) {
finish(buffer);
});
};
if (!thumbnail) { return void processFile(); }
blobToArrayBuffer(thumbnail, function (e, buffer) {
if (e) { console.error(e); }
thumb = arrayBufferToString(buffer);
processFile();
});
};
var onFileDrop = File.onFileDrop = function (file, e) {
if (!common.isLoggedIn()) {
return common.alert(common.Messages.upload_mustLogin);
}
Array.prototype.slice.call(file).forEach(function (d) {
handleFile(d, e);
});
};
var createAreaHandlers = File.createDropArea = function ($area, $hoverArea) {
var counter = 0;
if (!$hoverArea) { $hoverArea = $area; }
if (!$area) { return; }
$hoverArea
.on('dragenter', function (e) {
e.preventDefault();
e.stopPropagation();
counter++;
$hoverArea.addClass('hovering');
})
.on('dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
counter--;
if (counter <= 0) {
$hoverArea.removeClass('hovering');
}
});
$area
.on('drag dragstart dragend dragover drop dragenter dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
})
.on('drop', function (e) {
e.stopPropagation();
var dropped = e.originalEvent.dataTransfer.files;
counter = 0;
$hoverArea.removeClass('hovering');
onFileDrop(dropped, e);
});
};
var createUploader = function ($area, $hover, $body) {
if (!config.noHandlers) {
createAreaHandlers($area, null);
}
createTableContainer($body);
};
createUploader(config.dropArea, config.hoverArea, config.body);
return File;
};
return module;
});

View File

@@ -1,12 +1,12 @@
define([
'/common/common-util.js',
'/common/common-interface.js',
'/customize/messages.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function (Util, UI, Crypto) {
], function (Util, Messages, Crypto) {
var Nacl = window.nacl;
var Hash = {};
var Hash = window.CryptPad_Hash = {};
var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64;
@@ -35,8 +35,8 @@ define([
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
};
Hash.getUserHrefFromKeys = function (username, pubkey) {
return window.location.origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
var fixDuplicateSlashes = function (s) {
@@ -68,7 +68,9 @@ Version 1
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
parsed.key = hashArr[4].replace(/-/g, '/');
parsed.present = typeof(hashArr[5]) === "string" && hashArr[5] === 'present';
var options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
return parsed;
}
return parsed;
@@ -112,9 +114,31 @@ Version 1
if (!href) { return ret; }
if (href.slice(-1) !== '/') { href += '/'; }
href = href.replace(/\/\?[^#]+#/, '/#');
var idx;
ret.getUrl = function (options) {
options = options || {};
var url = '/';
if (!ret.type) { return url; }
url += ret.type + '/';
if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
url += '#/' + ret.hashData.version +
'/' + ret.hashData.mode +
'/' + ret.hashData.channel.replace(/\//g, '-') +
'/' + ret.hashData.key.replace(/\//g, '-') +'/';
if (options.embed) {
url += 'embed/';
}
if (options.present) {
url += 'present/';
}
return url;
};
if (!/^https*:\/\//.test(href)) {
idx = href.indexOf('/#');
ret.type = href.slice(1, idx);
@@ -152,7 +176,7 @@ Version 1
secret.keys = Crypto.createEditCryptor();
secret.key = Crypto.createEditCryptor().editKeyStr;
};
if (!secretHash && !/#/.test(window.location.href)) {
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate();
return secret;
} else {
@@ -188,14 +212,12 @@ Version 1
secret.keys = Crypto.createEditCryptor(parsed.key);
secret.key = secret.keys.editKeyStr;
if (secret.channel.length !== 32 || secret.key.length !== 24) {
UI.alert("The channel key and/or the encryption key is invalid");
throw new Error("The channel key and/or the encryption key is invalid");
}
}
else if (parsed.mode === 'view') {
secret.keys = Crypto.createViewCryptor(parsed.key);
if (secret.channel.length !== 32) {
UI.alert("The channel key is invalid");
throw new Error("The channel key is invalid");
}
}
@@ -278,6 +300,8 @@ Version 1
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
if (!parsed.hash) { return false; }
// We can't have a stronger hash if we're already in edit mode
if (parsed.hashData && parsed.hashData.mode === 'edit') { return; }
var stronger;
Object.keys(recents).some(function (id) {
var pad = recents[id];
@@ -340,5 +364,19 @@ Version 1
'/' + curvePublic.replace(/\//g, '-') + '/';
};
// Create untitled documents when no name is given
var getLocaleDate = function () {
if (window.Intl && window.Intl.DateTimeFormat) {
var options = {weekday: "short", year: "numeric", month: "long", day: "numeric"};
return new window.Intl.DateTimeFormat(undefined, options).format(new Date());
}
return new Date().toString().split(' ').slice(0,4).join(' ');
};
Hash.getDefaultName = function (parsed) {
var type = parsed.type;
var name = (Messages.type)[type] + ' - ' + getLocaleDate();
return name;
};
return Hash;
});

View File

@@ -1,268 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-json-validator/json-ot.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad/chainpad.dist.js',
], function ($, JsonOT, Crypto) {
var ChainPad = window.ChainPad;
var History = {};
var getStates = function (rt) {
var states = [];
var b = rt.getAuthBlock();
if (b) { states.unshift(b); }
while (b.getParent()) {
b = b.getParent();
states.unshift(b);
}
return states;
};
var loadHistory = function (config, common, cb) {
var network = common.getNetwork();
var hkn = network.historyKeeper;
var wcId = common.hrefToHexChannelId(config.href || window.location.href);
var createRealtime = function () {
return ChainPad.create({
userName: 'history',
initialState: '',
transformFunction: JsonOT.validate,
logLevel: 0,
noPrune: true
});
};
var realtime = createRealtime();
var parsed = config.href ? common.parsePadUrl(config.href) : {};
var secret = common.getSecrets(parsed.type, parsed.hash);
History.readOnly = 0;
if (!secret.keys) {
secret.keys = secret.key;
History.readOnly = 0;
}
else if (!secret.keys.validateKey) {
History.readOnly = 1;
}
var crypto = Crypto.createEncryptor(secret.keys);
var to = window.setTimeout(function () {
cb('[GET_FULL_HISTORY_TIMEOUT]');
}, 30000);
var parse = function (msg) {
try {
return JSON.parse(msg);
} catch (e) {
return null;
}
};
var onMsg = function (msg) {
var parsed = parse(msg);
if (parsed[0] === 'FULL_HISTORY_END') {
console.log('END');
window.clearTimeout(to);
cb(null, realtime);
return;
}
if (parsed[0] !== 'FULL_HISTORY') { return; }
if (parsed[1] && parsed[1].validateKey) { // First message
secret.keys.validateKey = parsed[1].validateKey;
return;
}
msg = parsed[1][4];
if (msg) {
msg = msg.replace(/^cp\|/, '');
var decryptedMsg = crypto.decrypt(msg, secret.keys.validateKey);
realtime.message(decryptedMsg);
}
};
network.on('message', function (msg) {
onMsg(msg);
});
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', wcId, secret.keys.validateKey]));
};
History.create = function (common, config) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
if (History.loading) { return void console.error("History is already being loaded..."); }
History.loading = true;
var $toolbar = config.$toolbar;
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
}
// config.setHistory(bool, bool)
// - bool1: history value
// - bool2: reset old content?
var render = function (val) {
if (typeof val === "undefined") { return; }
try {
config.applyVal(val);
} catch (e) {
// Probably a parse error
console.error(e);
}
};
var onClose = function () { config.setHistory(false, true); };
var onRevert = function () {
config.setHistory(false, false);
config.onLocal();
config.onRemote();
};
var onReady = function () {
config.setHistory(true);
};
var Messages = common.Messages;
var realtime;
var states = [];
var c = states.length - 1;
var $hist = $toolbar.find('.cryptpad-toolbar-history');
var $left = $toolbar.find('.cryptpad-toolbar-leftside');
var $right = $toolbar.find('.cryptpad-toolbar-rightside');
var $cke = $toolbar.find('.cke_toolbox_main');
$hist.html('').show();
$left.hide();
$right.hide();
$cke.hide();
common.spinner($hist).get().show();
var onUpdate;
var update = function () {
if (!realtime) { return []; }
states = getStates(realtime);
if (typeof onUpdate === "function") { onUpdate(); }
return states;
};
// Get the content of the selected version, and change the version number
var get = function (i) {
i = parseInt(i);
if (isNaN(i)) { return; }
if (i < 0) { i = 0; }
if (i > states.length - 1) { i = states.length - 1; }
var val = states[i].getContent().doc;
c = i;
if (typeof onUpdate === "function") { onUpdate(); }
$hist.find('.next, .previous').css('visibility', '');
if (c === states.length - 1) { $hist.find('.next').css('visibility', 'hidden'); }
if (c === 0) { $hist.find('.previous').css('visibility', 'hidden'); }
return val || '';
};
var getNext = function (step) {
return typeof step === "number" ? get(c + step) : get(c + 1);
};
var getPrevious = function (step) {
return typeof step === "number" ? get(c - step) : get(c - 1);
};
// Create the history toolbar
var display = function () {
$hist.html('');
var $prev =$('<button>', {
'class': 'previous fa fa-step-backward buttonPrimary',
title: Messages.history_prev
}).appendTo($hist);
var $nav = $('<div>', {'class': 'goto'}).appendTo($hist);
var $next = $('<button>', {
'class': 'next fa fa-step-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
$('<label>').text(Messages.history_version).appendTo($nav);
var $cur = $('<input>', {
'class' : 'gotoInput',
'type' : 'number',
'min' : '1',
'max' : states.length
}).val(c + 1).appendTo($nav).mousedown(function (e) {
// stopPropagation because the event would be cancelled by the dropdown menus
e.stopPropagation();
});
var $label2 = $('<label>').text(' / '+ states.length).appendTo($nav);
$('<br>').appendTo($nav);
var $close = $('<button>', {
'class':'closeHistory',
title: Messages.history_closeTitle
}).text(Messages.history_closeTitle).appendTo($nav);
var $rev = $('<button>', {
'class':'revertHistory buttonSuccess',
title: Messages.history_restoreTitle
}).text(Messages.history_restore).appendTo($nav);
if (History.readOnly) { $rev.hide(); }
onUpdate = function () {
$cur.attr('max', states.length);
$cur.val(c+1);
$label2.text(' / ' + states.length);
};
var close = function () {
$hist.hide();
$left.show();
$right.show();
$cke.show();
};
// Buttons actions
$prev.click(function () { render(getPrevious()); });
$next.click(function () { render(getNext()); });
$cur.keydown(function (e) {
var p = function () { e.preventDefault(); };
if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
if (e.which === 27) { p(); $close.click(); }
}).keyup(function (e) { e.stopPropagation(); }).focus();
$cur.on('change', function () {
render( get($cur.val() - 1) );
});
$close.click(function () {
states = [];
close();
onClose();
});
$rev.click(function () {
common.confirm(Messages.history_restorePrompt, function (yes) {
if (!yes) { return; }
close();
onRevert();
common.log(Messages.history_restoreDone);
});
});
// Display the latest content
render(get(c));
};
// Load all the history messages into a new chainpad object
loadHistory(config, common, function (err, newRt) {
History.loading = false;
if (err) { throw new Error(err); }
realtime = newRt;
update();
c = states.length - 1;
display();
onReady();
});
};
return History;
});

View File

@@ -2,14 +2,19 @@ define([
'jquery',
'/customize/messages.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-notifier.js',
'/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js',
'/common/notify.js',
'/common/visible.js',
'/common/tippy.min.js',
'css!/common/tippy.css',
], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible, Tippy) {
'/customize/pages.js',
'/common/hyperscript.js',
'/common/test.js',
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
'css!/common/tippy.css',
], function ($, Messages, Util, Hash, Notifier, AppConfig,
Alertify, Tippy, Pages, h, Test) {
var UI = {};
/*
@@ -20,20 +25,26 @@ define([
// set notification timeout
Alertify._$$alertify.delay = AppConfig.notificationTimeout || 5000;
var findCancelButton = UI.findCancelButton = function () {
var findCancelButton = UI.findCancelButton = function (root) {
if (root) {
return $(root).find('button.cancel').last();
}
return $('button.cancel').last();
};
var findOKButton = UI.findOKButton = function () {
var findOKButton = UI.findOKButton = function (root) {
if (root) {
return $(root).find('button.ok').last();
}
return $('button.ok').last();
};
var listenForKeys = UI.listenForKeys = function (yes, no) {
var listenForKeys = UI.listenForKeys = function (yes, no, el) {
var handler = function (e) {
e.stopPropagation();
switch (e.which) {
case 27: // cancel
if (typeof(no) === 'function') { no(e); }
no();
break;
case 13: // enter
if (typeof(yes) === 'function') { yes(e); }
@@ -41,115 +52,456 @@ define([
}
};
$(window).keyup(handler);
$(el || window).keydown(handler);
return handler;
};
var customListenForKeys = function (keys, cb, el) {
if (!keys || !keys.length || typeof cb !== "function") { return; }
var handler = function (e) {
e.stopPropagation();
keys.some(function (k) {
// k is number or array
// if it's an array, it should be [keyCode, "{ctrl|alt|shift|meta}"]
if (Array.isArray(k) && e.which === k[0] && e[k[1] + 'Key']) {
cb();
return true;
}
if (e.which === k && !e.shiftKey && !e.altKey && !e.metaKey && !e.ctrlKey) {
cb();
return true;
}
});
};
$(el || window).keydown(handler);
return handler;
};
var stopListening = UI.stopListening = function (handler) {
if (!handler) { return; } // we don't want to stop all the 'keyup' listeners
$(window).off('keyup', handler);
};
UI.alert = function (msg, cb, force) {
cb = cb || function () {};
if (force !== true) { msg = Util.fixHTML(msg); }
var close = function () {
findOKButton().click();
};
var keyHandler = listenForKeys(close, close);
Alertify
.okBtn(Messages.okButton || 'OK')
.alert(msg, function (ev) {
cb(ev);
stopListening(keyHandler);
var dialog = UI.dialog = {};
var merge = function (a, b) {
var c = {};
if (a) {
Object.keys(a).forEach(function (k) {
c[k] = a[k];
});
window.setTimeout(function () {
findOKButton().focus();
if (typeof(UI.notify) === 'function') {
UI.notify();
}
if (b) {
Object.keys(b).forEach(function (k) {
c[k] = b[k];
});
}
return c;
};
dialog.selectable = function (value, opt) {
var attrs = merge({
type: 'text',
readonly: 'readonly',
}, opt);
var input = h('input', attrs);
$(input).val(value).click(function () {
input.select();
});
return input;
};
dialog.okButton = function (content, classString) {
var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.ok.primary';
return h(sel, { tabindex: '2', }, content || Messages.okButton);
};
dialog.cancelButton = function (content, classString) {
var sel = typeof(classString) === 'string'? 'button.' + classString:'button.cancel';
return h(sel, { tabindex: '1'}, content || Messages.cancelButton);
};
dialog.message = function (text) {
return h('p.msg', text);
};
dialog.textInput = function (opt) {
var attrs = merge({
type: 'text',
'class': 'cp-text-input',
}, opt);
return h('input', attrs);
};
dialog.nav = function (content) {
return h('nav', content || [
dialog.cancelButton(),
dialog.okButton(),
]);
};
dialog.frame = function (content) {
return h('div.alertify', {
tabindex: 1,
}, [
h('div.dialog', [
h('div', content),
])
]);
};
/**
* tabs is an array containing objects
* each object must have the following attributes:
* - title: String
* - content: DOMElement
*/
dialog.tabs = function (tabs) {
var contents = [];
var titles = [];
tabs.forEach(function (tab) {
if (!tab.content || !tab.title) { return; }
var content = h('div.alertify-tabs-content', tab.content);
var title = h('span.alertify-tabs-title', tab.title);
$(title).click(function () {
titles.forEach(function (t) { $(t).removeClass('alertify-tabs-active'); });
contents.forEach(function (c) { $(c).removeClass('alertify-tabs-content-active'); });
$(title).addClass('alertify-tabs-active');
$(content).addClass('alertify-tabs-content-active');
});
titles.push(title);
contents.push(content);
});
if (contents.length) {
$(contents[0]).addClass('alertify-tabs-content-active');
$(titles[0]).addClass('alertify-tabs-active');
}
return h('div.alertify-tabs', [
h('div.alertify-tabs-titles', titles),
h('div.alertify-tabs-contents', contents),
]);
};
UI.tokenField = function (target) {
var t = {
element: target || h('input'),
};
var $t = t.tokenfield = $(t.element).tokenfield();
t.getTokens = function (ignorePending) {
var tokens = $t.tokenfield('getTokens').map(function (token) {
return token.value.toLowerCase();
});
if (ignorePending) { return tokens; }
var $pendingEl = $($t.parent().find('.token-input')[0]);
var val = ($pendingEl.val() || "").trim();
if (val && tokens.indexOf(val) === -1) {
return tokens.concat(val);
}
return tokens;
};
var $root = $t.parent();
$t.on('tokenfield:removetoken', function () {
$root.find('.token-input').focus();
});
t.preventDuplicates = function (cb) {
$t.on('tokenfield:createtoken', function (ev) {
var val;
ev.attrs.value = ev.attrs.value.toLowerCase();
if (t.getTokens(true).some(function (t) {
if (t === ev.attrs.value) { return ((val = t)); }
})) {
ev.preventDefault();
if (typeof(cb) === 'function') { cb(val); }
}
});
return t;
};
t.setTokens = function (tokens) {
$t.tokenfield('setTokens',
tokens.map(function (token) {
return {
value: token.toLowerCase(),
label: token.toLowerCase(),
};
}));
};
t.focus = function () {
var $temp = $t.closest('.tokenfield').find('.token-input');
$temp.css('width', '20%');
$t.tokenfield('focusInput', $temp[0]);
};
return t;
};
dialog.tagPrompt = function (tags, cb) {
var input = dialog.textInput();
var tagger = dialog.frame([
dialog.message([
Messages.tags_add,
h('br'),
Messages.tags_searchHint,
]),
input,
h('center', h('small', Messages.tags_notShared)),
dialog.nav(),
]);
var field = UI.tokenField(input).preventDuplicates(function (val) {
UI.warn(Messages._getKey('tags_duplicate', [val]));
});
var listener;
var close = Util.once(function (result, ev) {
ev.stopPropagation();
ev.preventDefault();
var $frame = $(tagger).fadeOut(150, function () {
stopListening(listener);
$frame.remove();
cb(result, ev);
});
});
var $ok = findOKButton(tagger).click(function (e) {
var tokens = field.getTokens();
close(tokens, e);
});
var $cancel = findCancelButton(tagger).click(function (e) {
close(null, e);
});
listener = listenForKeys(function () {
$ok.click();
}, function () {
$cancel.click();
}, tagger);
$(tagger).on('click submit', function (e) {
e.stopPropagation();
});
document.body.appendChild(tagger);
// :(
setTimeout(function () {
field.setTokens(tags);
field.focus();
});
return tagger;
};
dialog.customModal = function (msg, opt) {
var force = false;
if (typeof(opt) === 'object') {
force = opt.force || false;
} else if (typeof(opt) === 'boolean') {
force = opt;
}
if (typeof(opt) !== 'object') {
opt = {};
}
var message;
if (typeof(msg) === 'string') {
// sanitize
if (!force) { msg = Util.fixHTML(msg); }
message = dialog.message();
message.innerHTML = msg;
} else {
message = dialog.message(msg);
}
var close = function (el) {
var $el = $(el).fadeOut(150, function () {
$el.detach();
});
};
var navs = [];
opt.buttons.forEach(function (b) {
if (!b.name || !b.onClick) { return; }
var button = h('button', { tabindex: '1', 'class': b.className || '' }, b.name);
$(button).click(function () {
b.onClick();
close($(button).parents('.alertify').first());
});
if (b.keys && b.keys.length) { $(button).attr('data-keys', JSON.stringify(b.keys)); }
navs.push(button);
});
var frame = h('div', [
message,
dialog.nav(navs),
]);
if (opt.forefront) { $(frame).addClass('forefront'); }
return frame;
};
UI.openCustomModal = function (content) {
var frame = dialog.frame([
content
]);
$(frame).find('button[data-keys]').each(function (i, el) {
var keys = JSON.parse($(el).attr('data-keys'));
customListenForKeys(keys, function () {
if (!$(el).is(':visible')) { return; }
$(el).click();
}, frame);
});
document.body.appendChild(frame);
$(frame).focus();
setTimeout(function () {
Notifier.notify();
});
};
UI.alert = function (msg, cb, opt) {
var force = false;
if (typeof(opt) === 'object') {
force = opt.force || false;
} else if (typeof(opt) === 'boolean') {
force = opt;
}
if (typeof(opt) !== 'object') {
opt = {};
}
cb = cb || function () {};
var message;
if (typeof(msg) === 'string') {
// sanitize
if (!force) { msg = Util.fixHTML(msg); }
message = dialog.message();
message.innerHTML = msg;
} else {
message = dialog.message(msg);
}
var ok = dialog.okButton();
var frame = dialog.frame([
message,
dialog.nav(ok),
]);
if (opt.forefront) { $(frame).addClass('forefront'); }
var listener;
var close = Util.once(function () {
$(frame).fadeOut(150, function () { $(this).remove(); });
stopListening(listener);
cb();
});
listener = listenForKeys(close, close, ok);
var $ok = $(ok).click(close);
document.body.appendChild(frame);
setTimeout(function () {
$ok.focus();
Notifier.notify();
});
};
UI.prompt = function (msg, def, cb, opt, force) {
opt = opt || {};
cb = cb || function () {};
if (force !== true) { msg = Util.fixHTML(msg); }
opt = opt || {};
var keyHandler = listenForKeys(function () { // yes
findOKButton().click();
}, function () { // no
findCancelButton().click();
var input = dialog.textInput();
input.value = typeof(def) === 'string'? def: '';
var message;
if (typeof(msg) === 'string') {
if (!force) { msg = Util.fixHTML(msg); }
message = dialog.message();
message.innerHTML = msg;
} else {
message = dialog.message(msg);
}
var ok = dialog.okButton(opt.ok);
var cancel = dialog.cancelButton(opt.cancel);
var frame = dialog.frame([
message,
input,
dialog.nav([ cancel, ok, ]),
]);
var listener;
var close = Util.once(function (result, ev) {
var $frame = $(frame).fadeOut(150, function () {
stopListening(listener);
$frame.remove();
cb(result, ev);
});
});
// Make sure we don't call both the "yes" and "no" handlers if we use "findOKButton().click()"
// in the callback
var isClicked = false;
var $ok = $(ok).click(function (ev) { close(input.value, ev); });
var $cancel = $(cancel).click(function (ev) { close(null, ev); });
listener = listenForKeys(function () { // yes
$ok.click();
}, function () { // no
$cancel.click();
}, input);
Alertify
.defaultValue(def || '')
.okBtn(opt.ok || Messages.okButton || 'OK')
.cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel')
.prompt(msg, function (val, ev) {
if (isClicked) { return; }
isClicked = true;
cb(val, ev);
stopListening(keyHandler);
}, function (ev) {
if (isClicked) { return; }
isClicked = true;
cb(null, ev);
stopListening(keyHandler);
});
if (typeof(UI.notify) === 'function') {
UI.notify();
}
document.body.appendChild(frame);
setTimeout(function () {
$(input).select().focus();
Notifier.notify();
});
};
UI.confirm = function (msg, cb, opt, force, styleCB) {
opt = opt || {};
UI.confirm = function (msg, cb, opt, force) {
cb = cb || function () {};
if (force !== true) { msg = Util.fixHTML(msg); }
opt = opt || {};
var keyHandler = listenForKeys(function () {
findOKButton().click();
}, function () {
findCancelButton().click();
var message;
if (typeof(msg) === 'string') {
if (!force) { msg = Util.fixHTML(msg); }
message = dialog.message();
message.innerHTML = msg;
} else {
message = dialog.message(msg);
}
var ok = dialog.okButton(opt.ok, opt.okClass);
var cancel = dialog.cancelButton(opt.cancel, opt.cancelClass);
var frame = dialog.frame([
message,
dialog.nav(opt.reverseOrder?
[ok, cancel]: [cancel, ok]),
]);
var listener;
var close = Util.once(function (bool, ev) {
$(frame).fadeOut(150, function () { $(this).remove(); });
stopListening(listener);
cb(bool, ev);
});
// Make sure we don't call both the "yes" and "no" handlers if we use "findOKButton().click()"
// in the callback
var isClicked = false;
var $ok = $(ok).click(function (ev) { close(true, ev); });
var $cancel = $(cancel).click(function (ev) { close(false, ev); });
Alertify
.okBtn(opt.ok || Messages.okButton || 'OK')
.cancelBtn(opt.cancel || Messages.cancelButton || 'Cancel')
.confirm(msg, function () {
if (isClicked) { return; }
isClicked = true;
cb(true);
stopListening(keyHandler);
}, function () {
if (isClicked) { return; }
isClicked = true;
cb(false);
stopListening(keyHandler);
});
if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); }
if (opt.okClass) { $ok.addClass(opt.okClass); }
window.setTimeout(function () {
var $ok = findOKButton();
var $cancel = findCancelButton();
if (opt.okClass) { $ok.addClass(opt.okClass); }
if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); }
if (opt.reverseOrder) {
$ok.insertBefore($ok.prev());
listener = listenForKeys(function () {
$ok.click();
}, function () {
$cancel.click();
}, ok);
document.body.appendChild(frame);
setTimeout(function () {
Notifier.notify();
$(frame).find('.ok').focus();
if (typeof(opt.done) === 'function') {
opt.done($ok.closest('.dialog'));
}
if (typeof(styleCB) === 'function') {
styleCB($ok.closest('.dialog'));
}
}, 0);
if (typeof(UI.notify) === 'function') {
UI.notify();
}
});
};
UI.log = function (msg) {
@@ -165,7 +517,7 @@ define([
*/
UI.spinner = function (parent) {
var $target = $('<span>', {
'class': 'fa fa-spinner fa-pulse fa-4x fa-fw'
'class': 'fa fa-circle-o-notch fa-spin fa-4x fa-fw',
}).hide();
$(parent).append($target);
@@ -185,7 +537,7 @@ define([
};
};
var LOADING = 'loading';
var LOADING = 'cp-loading';
var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
@@ -193,27 +545,37 @@ define([
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
};
UI.addLoadingScreen = function (loadingText, hideTips) {
UI.addLoadingScreen = function (config) {
config = config || {};
var loadingText = config.loadingText;
var hideTips = config.hideTips || AppConfig.hideLoadingScreenTips;
var hideLogo = config.hideLogo;
var $loading, $container;
if ($('#' + LOADING).length) {
$loading = $('#' + LOADING).show();
$loading = $('#' + LOADING); //.show();
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show();
if (loadingText) {
$('#' + LOADING).find('p').text(loadingText);
} else {
$('#' + LOADING).find('p').text('');
}
$container = $loading.find('.loadingContainer');
$container = $loading.find('.cp-loading-container');
} else {
$loading = $('<div>', {id: LOADING});
$container = $('<div>', {'class': 'loadingContainer'});
$container.append('<img class="cryptofist" src="/customize/cryptofist_small.png" />');
var $spinner = $('<div>', {'class': 'spinnerContainer'});
UI.spinner($spinner).show();
var $text = $('<p>').text(loadingText || Messages.loading);
$container.append($spinner).append($text);
$loading.append($container);
$loading = $(Pages.loadingScreen());
$container = $loading.find('.cp-loading-container');
if (hideLogo) {
$loading.find('img').hide();
} else {
$loading.find('img').show();
}
var $spinner = $loading.find('.cp-loading-spinner-container');
$spinner.show();
$('body').append($loading);
}
if (Messages.tips && !hideTips) {
var $loadingTip = $('<div>', {'id': 'loadingTip'});
var $loadingTip = $('<div>', {'id': 'cp-loading-tip'});
$('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
$loadingTip.css({
'bottom': $('body').height()/2 - $container.height()/2 + 20 + 'px'
@@ -222,90 +584,68 @@ define([
}
};
UI.removeLoadingScreen = function (cb) {
$('#' + LOADING).fadeOut(750, cb);
var $tip = $('#loadingTip').css('top', '')
// Release the test blocker, hopefully every test has been registered.
// This test is created in sframe-boot2.js
cb = cb || function () {};
if (Test.__ASYNC_BLOCKER__) { Test.__ASYNC_BLOCKER__.pass(); }
$('#' + LOADING).addClass("cp-loading-hidden");
setTimeout(cb, 750);
//$('#' + LOADING).fadeOut(750, cb);
var $tip = $('#cp-loading-tip').css('top', '')
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
.css({
'opacity': 0,
'pointer-events': 'none',
});
setTimeout(function () {
$tip.remove();
}, 3750);
window.setTimeout(function () {
$tip.remove();
}, 3750);
// jquery.fadeout can get stuck
};
UI.errorLoadingScreen = function (error, transparent) {
if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen(undefined, true); }
$('.spinnerContainer').hide();
UI.errorLoadingScreen = function (error, transparent, exitable) {
if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
$('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
$('#' + LOADING).find('p').html(error || Messages.error);
};
// Notify
var notify = {};
UI.unnotify = function () {
if (notify.tabNotification &&
typeof(notify.tabNotification.cancel) === 'function') {
notify.tabNotification.cancel();
}
};
UI.notify = function () {
if (Visible.isSupported() && !Visible.currently()) {
UI.unnotify();
notify.tabNotification = Notify.tab(1000, 10);
}
};
if (Visible.isSupported()) {
Visible.onChange(function (yes) {
if (yes) { UI.unnotify(); }
});
}
UI.importContent = function (type, f, cfg) {
return function () {
var $files = $('<input>', {type:"file"});
if (cfg && cfg.accept) {
$files.attr('accept', cfg.accept);
}
$files.click();
$files.on('change', function (e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function (e) { f(e.target.result, file); };
reader.readAsText(file, type);
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) {
$('#' + LOADING).hide();
if (typeof(exitable) === "function") { exitable(); }
}
});
};
}
};
var $fileIcon = $('<span>', {"class": "fa fa-file-text-o file icon"});
var $fileAppIcon = $('<span>', {"class": "fa fa-file-text-o file icon fileColor"});
var $padIcon = $('<span>', {"class": "fa fa-file-word-o file icon padColor"});
var $codeIcon = $('<span>', {"class": "fa fa-file-code-o file icon codeColor"});
var $slideIcon = $('<span>', {"class": "fa fa-file-powerpoint-o file icon slideColor"});
var $pollIcon = $('<span>', {"class": "fa fa-calendar file icon pollColor"});
var $whiteboardIcon = $('<span>', {"class": "fa fa-paint-brush whiteboardColor"});
var $todoIcon = $('<span>', {"class": "fa fa-tasks todoColor"});
var $contactsIcon = $('<span>', {"class": "fa fa-users friendsColor"});
var $defaultIcon = $('<span>', {"class": "fa fa-file-text-o"});
UI.getIcon = function (type) {
var $icon;
var $icon = $defaultIcon.clone();
switch(type) {
case 'pad': $icon = $padIcon.clone(); break;
case 'file': $icon = $fileAppIcon.clone(); break;
case 'code': $icon = $codeIcon.clone(); break;
case 'slide': $icon = $slideIcon.clone(); break;
case 'poll': $icon = $pollIcon.clone(); break;
case 'whiteboard': $icon = $whiteboardIcon.clone(); break;
case 'todo': $icon = $todoIcon.clone(); break;
case 'contacts': $icon = $contactsIcon.clone(); break;
default: $icon = $fileIcon.clone();
if (AppConfig.applicationsIcon && AppConfig.applicationsIcon[type]) {
var appClass = ' cp-icon cp-icon-color-'+type;
$icon = $('<span>', {'class': 'fa ' + AppConfig.applicationsIcon[type] + appClass});
}
return $icon;
};
UI.getFileIcon = function (data) {
var $icon = UI.getIcon();
if (!data) { return $icon; }
var href = data.href;
var type = data.type;
if (!href && !type) { return $icon; }
if (!type) { type = Hash.parsePadUrl(href).type; }
$icon = UI.getIcon(type);
return $icon;
};
// Tooltips
@@ -313,10 +653,8 @@ define([
// If an element is removed from the UI while a tooltip is applied on that element, the tooltip will get hung
// forever, this is a solution which just searches for tooltips which have no corrisponding element and removes
// them.
var win;
$('.tippy-popper').each(function (i, el) {
win = win || $('#pad-iframe')[0].contentWindow;
if (win.$('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
if ($('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
el.remove();
}
});
@@ -324,52 +662,62 @@ define([
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
var addTippy = function (el) {
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
var addTippy = function (i, el) {
if (el.nodeName === 'IFRAME') { return; }
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
Tippy(el, {
position: 'bottom',
distance: 0,
performance: true,
dynamicTitle: true,
delay: [delay, 0]
delay: [delay, 0],
sticky: true
});
};
var $body = $('body');
var $padIframe = $('#pad-iframe').contents().find('body');
$('[title]').each(function (i, el) {
addTippy(el);
});
$('#pad-iframe').contents().find('[title]').each(function (i, el) {
addTippy(el);
});
// This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes.
//setInterval(UI.clearTooltips, delay);
var checkRemoved = function (x) {
var out = false;
var xId = $(x).attr('aria-describedby');
if (xId) {
if (xId.indexOf('tippy-tooltip-') === 0) {
return true;
}
}
$(x).find('[aria-describedby]').each(function (i, el) {
var id = el.getAttribute('aria-describedby');
if (id.indexOf('tippy-tooltip-') !== 0) { return; }
out = true;
});
return out;
};
$('[title]').each(addTippy);
var observer = new MutationObserver(function(mutations) {
var removed = false;
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length) {
$body.find('[title]').each(function (i, el) {
addTippy(el);
});
if (!$padIframe.length) { return; }
$padIframe.find('[title]').each(function (i, el) {
addTippy(el);
});
if (mutation.type === "childList") {
for (var i = 0; i < mutation.addedNodes.length; i++) {
if ($(mutation.addedNodes[i]).attr('title')) {
addTippy(0, mutation.addedNodes[i]);
}
$(mutation.addedNodes[i]).find('[title]').each(addTippy);
}
for (var j = 0; j < mutation.removedNodes.length; j++) {
removed |= checkRemoved(mutation.removedNodes[j]);
}
}
if (mutation.type === "attributes" && mutation.attributeName === "title") {
addTippy(0, mutation.target);
}
});
if (removed) { UI.clearTooltips(); }
});
observer.observe($('body')[0], {
attributes: false,
attributes: true,
childList: true,
characterData: false,
subtree: true
});
if ($('#pad-iframe').length) {
observer.observe($('#pad-iframe').contents().find('body')[0], {
attributes: false,
childList: true,
characterData: false,
subtree: true
});
}
};
return UI;

View File

@@ -0,0 +1,80 @@
define([
'jquery',
'/customize/messages.js'
], function($, Messages) {
var LS_LANG = "CRYPTPAD_LANG";
var Msg = {};
Msg.getLanguage = Messages._getLanguage;
// Add handler to the language selector
Msg.setLanguage = function (l, sframeChan, cb) {
if (sframeChan) {
// We're in the sandbox
sframeChan.query("Q_LANGUAGE_SET", l, cb);
return;
}
localStorage.setItem(LS_LANG, l);
cb();
};
Msg.initSelector = function ($select, sfcommon) {
var selector = $select || $('#cp-language-selector');
if (!selector.length) { return; }
var language = Messages._getLanguage();
// Select the current language in the list
selector.setValue(language || 'en');
// Listen for language change
$(selector).find('a.cp-language-value').on('click', function () {
var newLanguage = $(this).attr('data-value');
Msg.setLanguage(newLanguage, sfcommon && sfcommon.getSframeChannel(), function () {
if (newLanguage !== language) {
if (sfcommon) {
sfcommon.gotoURL();
return;
}
window.location.reload();
}
});
});
};
Msg.applyTranslation = function () {
var translateText = function (i, e) {
var $el = $(e);
var key = $el.data('localization');
$el.html(Messages[key]);
};
var translateAppend = function (i, e) {
var $el = $(e);
var key = $el.data('localization-append');
$el.append(Messages[key]);
};
var translateTitle = function () {
var $el = $(this);
var key = $el.data('localization-title');
$el.attr('title', Messages[key]);
};
var translatePlaceholder = function () {
var $el = $(this);
var key = $el.data('localization-placeholder');
$el.attr('placeholder', Messages[key]);
};
$('[data-localization]').each(translateText);
$('[data-localization-append]').each(translateAppend);
$('[data-localization-title]').each(translateTitle);
$('[data-localization-placeholder]').each(translatePlaceholder);
$('#pad-iframe').contents().find('[data-localization]').each(translateText);
$('#pad-iframe').contents().find('[data-localization-append]').each(translateAppend);
$('#pad-iframe').contents().find('[data-localization-title]').each(translateTitle);
$('#pad-iframe').contents().find('[data-localization-placeholder]').each(translatePlaceholder);
};
return Msg;
});

View File

@@ -1,12 +1,12 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/curve.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-constants.js',
'/customize/messages.js',
'/bower_components/marked/marked.min.js',
'/common/common-realtime.js',
], function ($, Crypto, Curve, Hash, Marked, Realtime) {
], function (Crypto, Hash, Util, Constants, Messages, Realtime) {
var Msg = {
inputs: [],
};
@@ -50,14 +50,14 @@ define([
Msg.getFriendChannelsList = function (proxy) {
var list = [];
eachFriend(proxy, function (friend) {
eachFriend(proxy.friends, function (friend) {
list.push(friend.channel);
});
return list;
};
// TODO make this internal to the messenger
var channels = Msg.channels = window.channels = {};
var channels = Msg.channels = {};
Msg.getLatestMessages = function () {
Object.keys(channels).forEach(function (id) {
@@ -70,8 +70,8 @@ define([
// Invitation
// FIXME there are too many functions with this name
var addToFriendList = Msg.addToFriendList = function (common, data, cb) {
var proxy = common.getProxy();
var addToFriendList = Msg.addToFriendList = function (cfg, data, cb) {
var proxy = cfg.proxy;
var friends = getFriendList(proxy);
var pubKey = data.curvePublic; // todo validata data
@@ -79,25 +79,25 @@ define([
friends[pubKey] = data;
Realtime.whenRealtimeSyncs(common, common.getRealtime(), function () {
Realtime.whenRealtimeSyncs(cfg.realtime, function () {
cb();
common.pinPads([data.channel], function (e) {
if (e) { console.error(e); }
cfg.pinPads([data.channel], function (res) {
if (res.error) { console.error(res.error); }
});
});
common.changeDisplayName(proxy[common.displayNameKey]);
cfg.updateMetadata();
};
/* Used to accept friend requests within apps other than /contacts/ */
Msg.addDirectMessageHandler = function (common) {
var network = common.getNetwork();
var proxy = common.getProxy();
Msg.addDirectMessageHandler = function (cfg) {
var network = cfg.network;
var proxy = cfg.proxy;
if (!network) { return void console.error('Network not ready'); }
network.on('message', function (message, sender) {
var msg;
if (sender === network.historyKeeper) { return; }
try {
var parsed = common.parsePadUrl(window.location.href);
var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; }
var chan = parsed.hashData.channel;
// Decrypt
@@ -131,11 +131,10 @@ define([
todo(true);
return;
}
var confirmMsg = common.Messages._getKey('contacts_request', [
common.fixHTML(msgData.displayName)
var confirmMsg = Messages._getKey('contacts_request', [
Util.fixHTML(msgData.displayName)
]);
common.onFriendRequest(confirmMsg, todo);
//common.confirm(confirmMsg, todo, null, true);
cfg.friendRequest(confirmMsg, todo);
return;
}
if (msg[0] === "FRIEND_REQ_OK") {
@@ -143,15 +142,15 @@ define([
if (idx !== -1) { pendingRequests.splice(idx, 1); }
// FIXME clarify this function's name
addToFriendList(common, msgData, function (err) {
addToFriendList(cfg, msgData, function (err) {
if (err) {
return void common.onFriendComplete({
logText: common.Messages.contacts_addError,
return void cfg.friendComplete({
logText: Messages.contacts_addError,
netfluxId: sender
});
}
common.onFriendComplete({
logText: common.Messages.contacts_added,
cfg.friendComplete({
logText: Messages.contacts_added,
netfluxId: sender
});
var msg = ["FRIEND_REQ_ACK", chan];
@@ -163,25 +162,25 @@ define([
if (msg[0] === "FRIEND_REQ_NOK") {
var i = pendingRequests.indexOf(sender);
if (i !== -1) { pendingRequests.splice(i, 1); }
common.onFriendComplete({
logText: common.Messages.contacts_rejected,
cfg.friendComplete({
logText: Messages.contacts_rejected,
netfluxId: sender
});
common.changeDisplayName(proxy[common.displayNameKey]);
cfg.updateMetadata();
return;
}
if (msg[0] === "FRIEND_REQ_ACK") {
var data = pending[sender];
if (!data) { return; }
addToFriendList(common, data, function (err) {
addToFriendList(cfg, data, function (err) {
if (err) {
return void common.onFriendComplete({
logText: common.Messages.contacts_addError,
return void cfg.friendComplete({
logText: Messages.contacts_addError,
netfluxId: sender
});
}
common.onFriendComplete({
logText: common.Messages.contacts_added,
cfg.friendComplete({
logText: Messages.contacts_added,
netfluxId: sender
});
});
@@ -194,17 +193,14 @@ define([
});
};
Msg.getPending = function () {
return pendingRequests;
};
Msg.inviteFromUserlist = function (common, netfluxId) {
var network = common.getNetwork();
var parsed = common.parsePadUrl(window.location.href);
Msg.inviteFromUserlist = function (cfg, data, cb) {
var network = cfg.network;
var netfluxId = data.netfluxId;
var parsed = Hash.parsePadUrl(data.href);
if (!parsed.hashData) { return; }
// Message
var chan = parsed.hashData.channel;
var myData = createData(common.getProxy());
var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
var keyStr = parsed.hashData.key;
@@ -214,12 +210,10 @@ define([
// Send encrypted message
if (pendingRequests.indexOf(netfluxId) === -1) {
pendingRequests.push(netfluxId);
var proxy = common.getProxy();
// this redraws the userlist after a change has occurred
// TODO rename this function to reflect its purpose
common.changeDisplayName(proxy[common.displayNameKey]);
cfg.updateMetadata(); // redraws the userlist in pad
}
network.sendto(netfluxId, msgStr);
cb();
};
return Msg;

View File

@@ -1,10 +1,11 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/curve.js',
'/common/common-hash.js',
'/common/common-realtime.js'
], function ($, Crypto, Curve, Hash, Realtime) {
'/common/common-util.js',
'/common/common-realtime.js',
'/common/common-constants.js',
], function (Crypto, Curve, Hash, Util, Realtime, Constants) {
'use strict';
var Msg = {
inputs: [],
@@ -27,7 +28,7 @@ define([
var createData = Msg.createData = function (proxy, hash) {
return {
channel: hash || Hash.createChannelId(),
displayName: proxy['cryptpad.username'],
displayName: proxy[Constants.displayNameKey],
profile: proxy.profile && proxy.profile.view,
edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic,
@@ -49,28 +50,13 @@ define([
return proxy.friends;
};
var eachFriend = function (friends, cb) {
Object.keys(friends).forEach(function (id) {
if (id === 'me') { return; }
cb(friends[id], id, friends);
});
};
Msg.getFriendChannelsList = function (proxy) {
var list = [];
eachFriend(proxy, function (friend) {
list.push(friend.channel);
});
return list;
};
var msgAlreadyKnown = function (channel, sig) {
return channel.messages.some(function (message) {
return message[0] === sig;
});
};
Msg.messenger = function (common) {
Msg.messenger = function (store) {
var messenger = {
handlers: {
message: [],
@@ -103,9 +89,9 @@ define([
var joining = {};
// declare common variables
var network = common.getNetwork();
var proxy = common.getProxy();
var realtime = common.getRealtime();
var network = store.network;
var proxy = store.proxy;
var realtime = store.realtime;
Msg.hk = network.historyKeeper;
var friends = getFriendList(proxy);
@@ -130,6 +116,10 @@ define([
return messenger.range_requests[txid];
};
var deleteRangeRequest = function (txid) {
delete messenger.range_requests[txid];
};
messenger.getMoreHistory = function (curvePublic, hash, count, cb) {
if (typeof(cb) !== 'function') { return; }
@@ -146,7 +136,7 @@ define([
return;
}
var txid = common.uid();
var txid = Util.uid();
initRangeRequest(txid, curvePublic, hash, cb);
var msg = [ 'GET_HISTORY_RANGE', chan.id, {
from: hash,
@@ -242,7 +232,7 @@ define([
if (!proxy.friends) { return; }
var friends = proxy.friends;
delete friends[curvePublic];
Realtime.whenRealtimeSyncs(common, realtime, cb);
Realtime.whenRealtimeSyncs(realtime, cb);
};
var pushMsg = function (channel, cryptMsg) {
@@ -349,7 +339,7 @@ define([
return cb();
};
var onDirectMessage = function (common, msg, sender) {
var onDirectMessage = function (msg, sender) {
if (sender !== Msg.hk) { return void onIdMessage(msg, sender); }
var parsed = JSON.parse(msg);
@@ -394,7 +384,8 @@ define([
});
orderMessages(curvePublic, decrypted, req.sig);
return void req.cb(void 0, decrypted);
req.cb(void 0, decrypted);
return deleteRangeRequest(txid);
} else {
console.log(parsed);
}
@@ -439,7 +430,7 @@ define([
// listen for messages...
network.on('message', function(msg, sender) {
onDirectMessage(common, msg, sender);
onDirectMessage(msg, sender);
});
messenger.removeFriend = function (curvePublic, cb) {
@@ -472,7 +463,7 @@ define([
channel.wc.bcast(cryptMsg).then(function () {
delete friends[curvePublic];
delete channels[curvePublic];
Realtime.whenRealtimeSyncs(common, realtime, function () {
Realtime.whenRealtimeSyncs(realtime, function () {
cb();
});
}, function (err) {
@@ -493,7 +484,7 @@ define([
};
var msg = ['GET_HISTORY', chan.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg))
.then($.noop, function (err) {
.then(function () {}, function (err) {
throw new Error(err);
});
};
@@ -638,7 +629,7 @@ define([
messenger.getMyInfo = function (cb) {
cb(void 0, {
curvePublic: proxy.curvePublic,
displayName: common.getDisplayName(),
displayName: proxy[Constants.displayNameKey]
});
};

View File

@@ -1,59 +0,0 @@
define(function () {
var module = {};
module.create = function (UserList, Title, cfg, Cryptpad) {
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 (Cryptpad) {
if (typeof(metadata.type) === 'undefined') {
// initialize pad type by location.pathname
metadata.type = Cryptpad.getAppType();
}
} else {
console.log("Cryptpad should exist but it does not");
}
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;
});

View File

@@ -0,0 +1,29 @@
define([
'/common/visible.js',
'/common/notify.js'
], function (Visible, Notify) {
var Notifier = {};
var notify = {};
Notifier.unnotify = function () {
if (notify.tabNotification &&
typeof(notify.tabNotification.cancel) === 'function') {
notify.tabNotification.cancel();
}
};
Notifier.notify = function () {
if (Visible.isSupported() && !Visible.currently()) {
Notifier.unnotify();
notify.tabNotification = Notify.tab(1000, 10);
}
};
if (Visible.isSupported()) {
Visible.onChange(function (yes) {
if (yes) { Notifier.unnotify(); }
});
}
return Notifier;
});

View File

@@ -1,62 +1,21 @@
define([
'/customize/application_config.js',
'/customize/messages.js',
], function (AppConfig, Messages) {
define([], function () {
var common = {};
common.infiniteSpinnerDetected = false;
var BAD_STATE_TIMEOUT = typeof(AppConfig.badStateTimeout) === 'number'?
AppConfig.badStateTimeout: 30000;
var connected = false;
var intr;
var infiniteSpinnerHandlers = [];
/*
TODO make this not blow up when disconnected or lagging...
*/
common.whenRealtimeSyncs = function (Cryptpad, realtime, cb) {
common.whenRealtimeSyncs = function (realtime, cb) {
if (typeof(realtime.getAuthDoc) !== 'function') {
return void console.error('improper use of this function');
}
window.setTimeout(function () {
setTimeout(function () {
if (realtime.getAuthDoc() === realtime.getUserDoc()) {
return void cb();
} else {
realtime.onSettle(cb);
}
if (intr) { return; }
intr = window.setInterval(function () {
var l;
try {
l = realtime.getLag();
} catch (e) {
throw new Error("ChainPad.getLag() does not exist, please `bower update`");
}
if (l.lag < BAD_STATE_TIMEOUT || !connected) { return; }
realtime.abort();
// don't launch more than one popup
if (common.infiniteSpinnerDetected) { return; }
infiniteSpinnerHandlers.forEach(function (ish) { ish(); });
// inform the user their session is in a bad state
Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) {
if (!yes) { return; }
window.parent.location.reload();
});
common.infiniteSpinnerDetected = true;
}, 2000);
}, 0);
};
common.onInfiniteSpinner = function (f) { infiniteSpinnerHandlers.push(f); };
common.setConnectionState = function (bool) {
if (typeof(bool) !== 'boolean') { return; }
connected = bool;
};
return common;
});

View File

@@ -1,9 +1,32 @@
define([
'jquery',
'/common/common-util.js',
'/common/visible.js',
'/common/common-hash.js',
'/file/file-crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function () {
], function ($, Util, Visible, Hash, FileCrypto) {
var Nacl = window.nacl;
var Thumb = {
dimension: 150, // thumbnails are all 150px
dimension: 100,
padDimension: 200,
UPDATE_INTERVAL: 60000,
UPDATE_FIRST: 5000
};
var supportedTypes = [
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
'video/',
'application/pdf'
];
Thumb.isSupportedType = function (type) {
return supportedTypes.some(function (t) {
return type.indexOf(t) !== -1;
});
};
// create thumbnail image from metadata
@@ -27,24 +50,241 @@ define([
}
};
// assumes that your canvas is square
// nodeback returning blob
Thumb.fromCanvas = function (canvas, cb) {
canvas = canvas;
var c2 = document.createElement('canvas');
var d = Thumb.dimension;
c2.width = d;
c2.height = 2;
var getResizedDimensions = Thumb.getResizedDimensions = function (img, type) {
var h = type === 'video' ? img.videoHeight : img.height;
var w = type === 'video' ? img.videoWidth : img.width;
var ctx = c2.getContext('2d');
ctx.drawImage(canvas, 0, 0, d, d);
c2.toBlob(function (blob) {
cb(void 0, blob);
});
var dim = type === 'pad' ? Thumb.padDimension : Thumb.dimension;
// if the image is too small, don't bother making a thumbnail
/*if (h <= dim && w <= dim) {
return {
x: Math.floor((dim - w) / 2),
w: w,
y: Math.floor((dim - h) / 2),
h : h
};
}*/
// the image is taller than it is wide, so scale to that.
var r = dim / (h > w? h: w); // ratio
if (h <= dim && w <= dim) { r = 1; }
var d;
if (h > w) {
var newW = Math.floor(w*r);
d = Math.floor((dim - newW) / 2);
return {
dim: dim,
x: d,
w: newW,
y: 0,
h: dim,
};
} else {
var newH = Math.floor(h*r);
d = Math.floor((dim - newH) / 2);
return {
dim: dim,
x: 0,
w: dim,
y: d,
h: newH
};
}
};
Thumb.fromVideo = function (video, cb) {
cb = cb; // WIP
// assumes that your canvas is square
// nodeback returning blob
Thumb.fromCanvas = function (canvas, D, cb) {
var c2 = document.createElement('canvas');
if (!D) { return void cb('ERROR'); }
c2.width = D.dim;
c2.height = D.dim;
var ctx = c2.getContext('2d');
ctx.drawImage(canvas, D.x, D.y, D.w, D.h);
cb(void 0, c2.toDataURL());
};
Thumb.fromImageBlob = function (blob, cb) {
var url = URL.createObjectURL(blob);
var img = new Image();
img.onload = function () {
var D = getResizedDimensions(img, 'image');
Thumb.fromCanvas(img, D, cb);
};
img.onerror = function () {
cb('ERROR');
};
img.src = url;
};
Thumb.fromVideoBlob = function (blob, cb) {
var url = URL.createObjectURL(blob);
var video = document.createElement("VIDEO");
video.addEventListener('loadeddata', function() {
var D = getResizedDimensions(video, 'video');
Thumb.fromCanvas(video, D, cb);
}, false);
video.addEventListener('error', function (e) {
console.error(e);
cb('ERROR');
});
video.src = url;
};
Thumb.fromPdfBlob = function (blob, cb) {
require.config({paths: {'pdfjs-dist': '/common/pdfjs'}});
require(['pdfjs-dist/build/pdf'], function (PDFJS) {
var url = URL.createObjectURL(blob);
var makeThumb = function (page) {
var vp = page.getViewport(1);
var canvas = document.createElement("canvas");
canvas.width = canvas.height = Thumb.dimension;
var scale = Math.min(canvas.width / vp.width, canvas.height / vp.height);
canvas.width = Math.floor(vp.width * scale);
canvas.height = Math.floor(vp.height * scale);
return page.render({
canvasContext: canvas.getContext("2d"),
viewport: page.getViewport(scale)
}).promise.then(function () {
return canvas;
});
};
PDFJS.getDocument(url).promise
.then(function (doc) {
return doc.getPage(1).then(makeThumb).then(function (canvas) {
var D = getResizedDimensions(canvas, 'pdf');
Thumb.fromCanvas(canvas, D, cb);
});
}).catch(function () {
cb('ERROR');
});
});
};
Thumb.fromBlob = function (blob, cb) {
if (blob.type.indexOf('video/') !== -1) {
return void Thumb.fromVideoBlob(blob, cb);
}
if (blob.type.indexOf('application/pdf') !== -1) {
return void Thumb.fromPdfBlob(blob, cb);
}
Thumb.fromImageBlob(blob, cb);
};
window.html2canvas = undefined;
Thumb.fromDOM = function (opts, cb) {
var element = opts.getContainer();
if (!element) { return; }
var todo = function () {
if (opts.filter) { opts.filter(element, true); }
window.html2canvas(element, {
allowTaint: true,
onrendered: function (canvas) {
if (opts.filter) { opts.filter(element, false); }
setTimeout(function () {
var D = getResizedDimensions(canvas, 'pad');
Thumb.fromCanvas(canvas, D, cb);
}, 10);
}
});
};
if (window.html2canvas) { return void todo(); }
require(['/bower_components/html2canvas/build/html2canvas.min.js'], todo);
};
Thumb.initPadThumbnails = function (common, opts) {
if (!opts.href || !opts.getContent) {
throw new Error("href and getContent are needed for thumbnails");
}
var oldThumbnailState;
var mkThumbnail = function () {
var content = opts.getContent();
if (content === oldThumbnailState) { return; }
oldThumbnailState = content;
Thumb.fromDOM(opts, function (err, b64) {
Thumb.setPadThumbnail(common, opts.href, b64);
});
};
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
var to;
var tUntil;
var interval = function () {
tUntil = nafa();
if (tUntil) {
window.clearTimeout(to);
to = window.setTimeout(interval, tUntil+1);
return;
}
to = window.setTimeout(interval, Thumb.UPDATE_INTERVAL+1);
};
Visible.onChange(function (v) {
if (v) {
window.clearTimeout(to);
return;
}
interval();
});
if (!Visible.currently()) { to = window.setTimeout(interval, Thumb.UPDATE_FIRST); }
};
var addThumbnail = function (err, thumb, $span, cb) {
var img = new Image();
img.src = thumb.slice(0,5) === 'data:' ? thumb : 'data:image/png;base64,'+thumb;
$span.find('.cp-icon').hide();
$span.prepend(img);
cb($(img));
};
Thumb.addThumbnail = function(thumb, $span, cb) {
return addThumbnail(null, thumb, $span, cb);
};
var getKey = function (href) {
var parsed = Hash.parsePadUrl(href);
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
};
Thumb.setPadThumbnail = function (common, href, b64, cb) {
cb = cb || function () {};
var k = getKey(href);
common.setThumbnail(k, b64, cb);
};
Thumb.displayThumbnail = function (common, href, $container, cb) {
cb = cb || function () {};
var parsed = Hash.parsePadUrl(href);
var k = getKey(href);
var whenNewThumb = function () {
var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) {
if (e === 'XHR_ERROR') { return; }
return console.error(e);
}
if (!metadata) { return console.error("NO_METADATA"); }
var v = metadata.thumbnail;
if (!v) {
v = 'EMPTY';
}
Thumb.setPadThumbnail(common, href, v, function (err) {
if (!metadata.thumbnail) { return; }
addThumbnail(err, metadata.thumbnail, $container, cb);
});
});
};
common.getThumbnail(k, function (err, v) {
if (!v && parsed.type === 'file') {
// We can only create thumbnails for files here since we can't easily decrypt pads
return void whenNewThumb();
}
if (!v) { return; }
if (v === 'EMPTY') { return; }
addThumbnail(err, v, $container, cb);
});
};
return Thumb;

View File

@@ -1,87 +0,0 @@
define(['jquery'], 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;
onLocal();
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);
onLocal();
};
// update title: href is optional; if not specified, we use window.location.href
exp.updateTitle = function (newTitle, href, cb) {
cb = cb || $.noop;
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, href, function (err, data) {
if (err) {
console.log("Couldn't set pad title");
console.error(err);
updateLocalTitle(oldTitle);
return void cb(err);
}
updateLocalTitle(data);
cb(null, 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;
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,117 +0,0 @@
define(['json.sortify'], function (Sortify) {
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 oldUserData = {};
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") {
// Make sure we don't update the userlist everytime someone makes a change to the pad
if (Sortify(oldUserData) === Sortify(userData)) { return; }
oldUserData = JSON.parse(JSON.stringify(userData));
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(),
avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl(),
curvePublic: Cryptpad.getProxy().curvePublic
};
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(); }
});*/
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(),
avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl(),
curvePublic: Cryptpad.getProxy().curvePublic
};
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;
});

View File

@@ -1,112 +0,0 @@
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(),
avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl(),
curvePublic: Cryptpad.getProxy().curvePublic
};
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(); }
});*/
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(),
avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl(),
curvePublic: Cryptpad.getProxy().curvePublic
};
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;
});

View File

@@ -1,10 +1,37 @@
(function (window) {
define([], function () {
var Util = {};
var Util = window.CryptPad_Util = {};
// If once is true, after the event has been fired, any further handlers which are
// registered will fire immediately, and this type of event cannot be fired twice.
Util.mkEvent = function (once) {
var handlers = [];
var fired = false;
return {
reg: function (cb) {
if (once && fired) { return void setTimeout(cb); }
handlers.push(cb);
},
unreg: function (cb) {
if (handlers.indexOf(cb) === -1) { throw new Error("Not registered"); }
handlers.splice(handlers.indexOf(cb), 1);
},
fire: function () {
if (once && fired) { return; }
fired = true;
var args = Array.prototype.slice.call(arguments);
handlers.forEach(function (h) { h.apply(null, args); });
}
};
};
Util.find = function (map, path) {
return (map && path.reduce(function (p, n) {
return typeof(p[n]) !== 'undefined' && p[n];
}, map));
var l = path.length;
for (var i = 0; i < l; i++) {
if (typeof(map[path[i]]) === 'undefined') { return; }
map = map[path[i]];
}
return map;
};
Util.uid = function () {
@@ -66,22 +93,6 @@ define([], function () {
return a;
};
Util.getHash = function () {
return window.location.hash.slice(1);
};
Util.replaceHash = function (hash) {
if (window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; }
void window.history.replaceState({}, window.document.title, hash);
if (typeof(window.onhashchange) === 'function') {
window.onhashchange();
}
return;
}
window.location.hash = hash;
};
/*
* Saving files
*/
@@ -163,12 +174,93 @@ define([], function () {
return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
};
Util.getAppType = function () {
var parts = window.location.pathname.split('/')
.filter(function (x) { return x; });
if (!parts[0]) { return ''; }
return parts[0];
/* for wrapping async functions such that they can only be called once */
Util.once = function (f) {
var called;
return function () {
if (called) { return; }
called = true;
f.apply(this, Array.prototype.slice.call(arguments));
};
};
Util.slice = function (A) {
return Array.prototype.slice.call(A);
};
Util.blobToImage = function (blob, cb) {
var reader = new FileReader();
reader.onloadend = function() {
cb(reader.result);
};
reader.readAsDataURL(blob);
};
Util.blobURLToImage = function (url, cb) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var reader = new FileReader();
reader.onloadend = function() {
cb(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
};
// Check if an element is a plain object
Util.isObject = function (o) {
return typeof (o) === "object" &&
Object.prototype.toString.call(o) === '[object Object]';
};
Util.isCircular = function (o) {
try {
JSON.stringify(o);
return false;
} catch (e) { return true; }
};
/* recursively adds the properties of an object 'b' to 'a'
arrays are only shallow copies, so references to the original
might still be present. Be mindful if you will modify 'a' in the future */
Util.extend = function (a, b) {
if (!Util.isObject(a) || !Util.isObject(b)) {
return void console.log("Extend only works with 2 objects");
}
if (Util.isCircular(b)) {
return void console.log("Extend doesn't accept circular objects");
}
for (var k in b) {
if (Util.isObject(b[k])) {
a[k] = {};
Util.extend(a[k], b[k]);
continue;
}
if (Array.isArray(b[k])) {
a[k] = b[k].slice();
continue;
}
a[k] = b[k];
}
};
Util.isChecked = function (el) {
// could be nothing...
if (!el) { return false; }
// check if it's a dom element
if (typeof(el.tagName) !== 'undefined') {
return Boolean(el.checked);
}
// sketchy test to see if it's jquery
if (typeof(el.prop) === 'function') {
return Boolean(el.prop('checked'));
}
// else just say it's not checked
return false;
};
return Util;
});
}(self));

View File

@@ -1,70 +0,0 @@
define([
'/customize/application_config.js',
'/bower_components/scrypt-async/scrypt-async.min.js',
], function (AppConfig) {
var Cred = {};
var Scrypt = window.scrypt;
var isString = Cred.isString = function (x) {
return typeof(x) === 'string';
};
Cred.isValidUsername = function (name) {
return !!(name && isString(name));
};
Cred.isValidPassword = function (passwd) {
return !!(passwd && isString(passwd));
};
Cred.passwordsMatch = function (a, b) {
return isString(a) && isString(b) && a === b;
};
Cred.customSalt = function () {
return typeof(AppConfig.loginSalt) === 'string'?
AppConfig.loginSalt: '';
};
Cred.deriveFromPassphrase = function (username, password, len, cb) {
Scrypt(password,
username + Cred.customSalt(), // salt
8, // memoryCost (n)
1024, // block size parameter (r)
len || 128, // dkLen
200, // interruptStep
cb,
undefined); // format, could be 'base64'
};
Cred.dispenser = function (bytes) {
var entropy = {
used: 0,
};
// crypto hygeine
var consume = function (n) {
// explode if you run out of bytes
if (entropy.used + n > bytes.length) {
throw new Error('exceeded available entropy');
}
if (typeof(n) !== 'number') { throw new Error('expected a number'); }
if (n <= 0) {
throw new Error('expected to consume a positive number of bytes');
}
// grab an unused slice of the entropy
var A = bytes.slice(entropy.used, entropy.used + n);
// account for the bytes you used so you don't reuse bytes
entropy.used += n;
//console.info("%s bytes of entropy remaining", bytes.length - entropy.used);
return A;
};
return consume;
};
return Cred;
});

View File

@@ -1,20 +1,19 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/common/cryptpad-common.js',
'/bower_components/textpatcher/TextPatcher.js'
], function ($, Crypto, Realtime, Cryptpad, TextPatcher) {
//var Messages = Cryptpad.Messages;
//var noop = function () {};
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
var finish = function (S, err, doc) {
if (S.done) { return; }
S.cb(err, doc);
S.done = true;
var disconnect = Cryptpad.find(S, ['network', 'disconnect']);
var disconnect = Util.find(S, ['network', 'disconnect']);
if (typeof(disconnect) === 'function') { disconnect(); }
var abort = Cryptpad.find(S, ['realtime', 'realtime', 'abort']);
var abort = Util.find(S, ['realtime', 'realtime', 'abort']);
if (typeof(abort) === 'function') {
S.realtime.realtime.sync();
abort();
@@ -23,10 +22,10 @@ define([
var makeConfig = function (hash) {
// We can't use cryptget with a file or a user so we can use 'pad' as hash type
var secret = Cryptpad.getSecrets('pad', hash);
var secret = Hash.getSecrets('pad', hash);
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = {
websocketURL: Cryptpad.getWebsocketURL(),
websocketURL: NetConfig.getWebsocketURL(),
channel: secret.channel,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
@@ -58,7 +57,7 @@ define([
};
overwrite(config, opt);
Session.realtime = Realtime.start(config);
Session.realtime = CPNetflux.start(config);
};
var put = function (hash, doc, cb, opt) {
@@ -72,23 +71,21 @@ define([
var realtime = Session.session = info.realtime;
Session.network = info.network;
TextPatcher.create({
realtime: realtime,
})(doc);
realtime.contentUpdate(doc);
var to = window.setTimeout(function () {
var to = setTimeout(function () {
cb(new Error("Timeout"));
}, 5000);
Cryptpad.whenRealtimeSyncs(realtime, function () {
window.clearTimeout(to);
Realtime.whenRealtimeSyncs(realtime, function () {
clearTimeout(to);
realtime.abort();
finish(Session, void 0);
});
};
overwrite(config, opt);
Session.session = Realtime.start(config);
Session.session = CPNetflux.start(config);
};
return {

File diff suppressed because it is too large Load Diff

View File

@@ -86,6 +86,7 @@ define([
};
var fixSelection = cursor.fixSelection = function (sel, range) {
try {
if (Tree.contains(Range.start.el, inner) && Tree.contains(Range.end.el, inner)) {
var order = Tree.orderOfNodes(Range.start.el, Range.end.el, inner);
var backward;
@@ -115,9 +116,10 @@ define([
} else {
var errText = "[cursor.fixSelection] At least one of the " +
"cursor nodes did not exist, could not fix selection";
console.error(errText);
//console.error(errText);
return errText;
}
} catch (e) { console.error(e); }
};
cursor.pushDelta = function (oldVal, newVal) {

View File

@@ -1,11 +1,12 @@
define([
'jquery',
'/bower_components/marked/marked.min.js',
'/common/cryptpad-common.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/media-tag.js',
'/bower_components/diff-dom/diffDOM.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
],function ($, Marked, Cryptpad, MediaTag) {
],function ($, Marked, Hash, Util, MediaTag) {
var DiffMd = {};
var DiffDOM = window.diffDOM;
@@ -22,8 +23,8 @@ define([
var mediaMap = {};
// Tasks list
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
var checkedTaskItemPtn = /^\s*(<p>)?\[[xX]\](<\/p>)?\s*/;
var uncheckedTaskItemPtn = /^\s*(<p>)?\[ ?\](<\/p>)?\s*/;
renderer.listitem = function (text) {
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
@@ -40,14 +41,12 @@ define([
};
renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var parsed = Hash.parsePadUrl(href);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
if (mediaMap[src]) {
mediaMap[src].forEach(function (n) {
mt += n.outerHTML;
});
mt += mediaMap[src];
}
mt += '</media-tag>';
return mt;
@@ -117,6 +116,7 @@ define([
/* remove listeners from the DOM */
var removeListeners = function (root) {
if (!root) { return; }
slice(root.attributes).map(function (attr) {
if (/^on/i.test(attr.name)) {
console.log('removing attribute', attr.name, root.attributes[attr.name]);
@@ -129,6 +129,7 @@ define([
var domFromHTML = function (html) {
var Dom = new DOMParser().parseFromString(html, "text/html");
Dom.normalize();
removeForbiddenTags(Dom.body);
removeListeners(Dom.body);
return Dom;
@@ -167,18 +168,17 @@ define([
var unsafe_newHtmlFixed = newHtml.replace(pattern, function (all, tag, src) {
var mt = tag;
if (mediaMap[src]) {
mediaMap[src].forEach(function (n) {
mt += n.outerHTML;
});
}
if (mediaMap[src]) { mt += mediaMap[src]; }
return mt + '</media-tag>';
});
var safe_newHtmlFixed = domFromHTML(unsafe_newHtmlFixed).body.outerHTML;
var newDomFixed = domFromHTML(unsafe_newHtmlFixed);
if (!newDomFixed || !newDomFixed.body) { return; }
var safe_newHtmlFixed = newDomFixed.body.outerHTML;
var $div = $('<div>', {id: id}).append(safe_newHtmlFixed);
var Dom = domFromHTML($('<div>').append($div).html());
$content[0].normalize();
var oldDom = domFromHTML($content[0].outerHTML);
var patch = makeDiff(oldDom, Dom, id);
if (typeof(patch) === 'string') {
@@ -191,9 +191,11 @@ define([
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {
//console.log(el.outerHTML);
var list_values = [].slice.call(el.children);
mediaMap[el.getAttribute('src')] = list_values;
var list_values = [].slice.call(mutation.target.children)
.map(function (el) { return el.outerHTML; })
.join('');
mediaMap[mutation.target.getAttribute('src')] = list_values;
observer.disconnect();
}
});
});

10
www/common/dom-ready.js Normal file
View File

@@ -0,0 +1,10 @@
define(function () {
return {
onReady: function (cb) {
if (document.readyState === 'complete') { return void cb(); }
document.onreadystatechange = function () {
if (document.readyState === 'complete') { cb(); }
};
}
};
});

View File

@@ -1,5 +1,5 @@
define([
'/customize/messages.js',
], function (Messages) {
Messages._applyTranslation();
'/common/common-language.js',
], function (Language) {
Language.applyTranslation();
});

View File

@@ -1,47 +1,50 @@
@import (once) '../customize/src/less2/include/colortheme.less';
@import (once) '../customize/src/less2/include/colortheme-all.less';
@import '../customize/src/less2/include/modal.less';
#fileDialog {
.cp-modal {
.fileContainer {
display: flex;
flex-wrap: wrap;
justify-content: center;
overflow-y: auto;
}
.element {
@darker: darken(@colortheme_modal-fg, 30%);
width: 200px;
min-width: 200px;
height: 1em;
padding: 0.5em;
margin: 5px;
box-sizing: content-box;
text-align: left;
line-height: 1em;
cursor: pointer;
background-color: #111;
color: @darker;
transition: all 0.1s;
&:hover {
color: @colortheme_modal-fg;
.fileDialog_main () {
#fileDialog {
display: none;
.cp-modal {
.fileContainer {
display: flex;
flex-wrap: wrap;
justify-content: center;
overflow-y: auto;
}
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.element {
@darker: darken(@colortheme_modal-fg, 30%);
align-items: center;
width: 200px;
min-width: 200px;
height: 1em;
padding: 0.5em;
margin: 5px;
box-sizing: content-box;
.fa {
text-align: left;
line-height: 1em;
cursor: pointer;
margin-right: 0.5em;
background-color: #111;
color: @darker;
transition: all 0.1s;
&:hover {
color: @colortheme_modal-fg;
}
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
align-items: center;
.fa {
cursor: pointer;
margin-right: 0.5em;
}
}
}
}

File diff suppressed because it is too large Load Diff

83
www/common/flat-dom.js Normal file
View File

@@ -0,0 +1,83 @@
define([], function () {
var Flat = {};
var slice = function (coll) {
return Array.prototype.slice.call(coll);
};
var getAttrs = function (el) {
var i = 0;
var l = el.attributes.length;
var attr;
var data = {};
for (;i < l;i++) {
attr = el.attributes[i];
if (attr.name && attr.value) { data[attr.name] = attr.value; }
}
return data;
};
var identity = function (x) { return x; };
Flat.fromDOM = function (dom) {
var data = {
map: {},
};
var i = 1; // start from 1 so we're always truthey
var uid = function () { return i++; };
var process = function (el) {
var id;
if (!el.tagName && el.nodeType === Node.TEXT_NODE) {
id = uid();
data.map[id] = el.textContent;
return id;
}
if (!el || !el.attributes) { return; }
id = uid();
data.map[id] = [
el.tagName,
getAttrs(el),
slice(el.childNodes).map(function (e) {
return process(e);
}).filter(identity)
];
return id;
};
data.root = process(dom);
return data;
};
Flat.toDOM = function (data) {
var visited = {};
var process = function (key) {
if (!key) { return; } // ignore falsey keys
if (visited[key]) {
// TODO handle this more gracefully.
throw new Error('duplicate id or loop detected');
}
visited[key] = true; // mark paths as visited.
var hj = data.map[key];
if (typeof(hj) === 'string') { return document.createTextNode(hj); }
if (typeof(hj) === 'undefined') { return; }
if (!Array.isArray(hj)) { console.error(hj); throw new Error('expected array'); }
var e = document.createElement(hj[0]);
for (var x in hj[1]) { e.setAttribute(x, hj[1][x]); }
var child;
for (var i = 0; i < hj[2].length; i++) {
child = process(hj[2][i]);
if (child) {
e.appendChild(child);
}
}
return e;
};
return process(data.root);
};
return Flat;
});

View File

@@ -1,335 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
'/bower_components/textpatcher/TextPatcher.amd.js',
'/common/userObject.js',
], function ($, Listmap, Crypto, TextPatcher, FO) {
/*
This module uses localStorage, which is synchronous, but exposes an
asyncronous API. This is so that we can substitute other storage
methods.
To override these methods, create another file at:
/customize/storage.js
*/
var Store = {};
var store;
var initStore = function (filesOp, storeObj, exp) {
var ret = {};
var safeSet = function (key, val) {
storeObj[key] = val;
};
// Store uses nodebacks...
ret.set = function (key, val, cb) {
safeSet(key, val);
cb();
};
// implement in alternative store
ret.setBatch = function (map, cb) {
Object.keys(map).forEach(function (key) {
safeSet(key, map[key]);
});
cb(void 0, map);
};
ret.setDrive = function (key, val, cb) {
storeObj.drive[key] = val;
cb();
};
var safeGet = function (key) {
return storeObj[key];
};
ret.get = function (key, cb) {
cb(void 0, safeGet(key));
};
// implement in alternative store
ret.getBatch = function (keys, cb) {
var res = {};
keys.forEach(function (key) {
res[key] = safeGet(key);
});
cb(void 0, res);
};
ret.setPadAttribute = filesOp.setAttribute;
ret.getPadAttribute = filesOp.getAttribute;
ret.getDrive = function (key, cb) {
cb(void 0, storeObj.drive[key]);
};
var safeRemove = function (key) {
delete storeObj[key];
};
ret.remove = function (key, cb) {
safeRemove(key);
cb();
};
// implement in alternative store
ret.removeBatch = function (keys, cb) {
keys.forEach(function (key) {
safeRemove(key);
});
cb();
};
ret.keys = function (cb) {
cb(void 0, Object.keys(storeObj));
};
ret.removeData = filesOp.removeData;
ret.pushData = filesOp.pushData;
ret.addPad = filesOp.add;
ret.forgetPad = function (href, cb) {
filesOp.forget(href);
cb();
};
ret.listTemplates = function () {
var templateFiles = filesOp.getFiles(['template']);
var res = [];
templateFiles.forEach(function (f) {
var data = filesOp.getFileData(f);
res.push(JSON.parse(JSON.stringify(data)));
});
return res;
};
ret.getProxy = function () {
return exp;
};
ret.getLoginName = function () {
return storeObj.login_name;
};
ret.repairDrive = function () {
filesOp.fixFiles();
};
ret.getEmptyObject = function () {
return filesOp.getStructure();
};
ret.replace = filesOp.replace;
ret.restoreHref = filesOp.restoreHref;
ret.changeHandlers = [];
ret.change = function () {};
ret.getProfile = function () {
return storeObj.profile;
};
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,
rt: exp.realtime
});
var todo = function () {
fo.fixFiles();
//storeObj = proxy;
store = initStore(fo, proxy, exp);
if (typeof(f) === 'function') {
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);
}
// copy User_hash into sessionStorage because cross-domain iframes
// on safari replaces localStorage with sessionStorage or something
if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); }
var localToken = tryParsing(localStorage.getItem(tokenKey));
if (localToken === null) {
// if that number hasn't been set to localStorage, do so.
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;
}
if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) {
// even anonymous users should have a persistent, unique-ish id
console.log('generating a persistent identifier');
proxy.uid = Cryptpad.createChannelId();
}
// if the user is logged in, but does not have signing keys...
if (Cryptpad.isLoggedIn() && (!Cryptpad.hasSigningKeys(proxy) ||
!Cryptpad.hasCurveKeys(proxy))) {
return void requestLogin();
}
proxy.on('change', [Cryptpad.displayNameKey], function (o, n) {
if (typeof(n) !== "string") { return; }
Cryptpad.changeDisplayName(n);
});
proxy.on('change', ['profile'], function () {
// Trigger userlist update when the avatar has changed
Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
});
proxy.on('change', ['friends'], function () {
// Trigger userlist update when the avatar has changed
Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
});
proxy.on('change', [tokenKey], function () {
var localToken = tryParsing(localStorage.getItem(tokenKey));
if (localToken !== proxy[tokenKey]) {
return void requestLogin();
}
});
};
fo.migrate(todo);
};
var initialized = false;
var init = function (f, Cryptpad) {
if (!Cryptpad || initialized) { return; }
initialized = true;
var hash = Cryptpad.getUserHash() || localStorage.FS_hash || Cryptpad.createRandomHash();
if (!hash) {
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
}
var secret = Cryptpad.getSecrets('drive', hash);
var listmapConfig = {
data: {},
websocketURL: Cryptpad.getWebsocketURL(),
channel: secret.channel,
readOnly: false,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
userName: 'fs',
logLevel: 1,
};
var exp = {};
window.addEventListener('storage', function (e) {
if (e.key !== Cryptpad.userHashKey) { return; }
var o = e.oldValue;
var n = e.newValue;
if (!o && n) {
window.location.reload();
} else if (o && !n) {
$(window).on('keyup', function (e) {
if (e.keyCode === 27) {
Cryptpad.removeLoadingScreen();
}
});
Cryptpad.logout();
Cryptpad.addLoadingScreen(undefined, true);
Cryptpad.errorLoadingScreen(Cryptpad.Messages.onLogout, true);
if (exp.info) {
exp.info.network.disconnect();
}
}
});
var rt = window.rt = Listmap.create(listmapConfig);
exp.realtime = rt.realtime;
exp.proxy = rt.proxy;
rt.proxy.on('create', function (info) {
exp.info = info;
if (!Cryptpad.getUserHash()) {
localStorage.FS_hash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
}
}).on('ready', function () {
if (store) { return; } // the store is already ready, it is a reconnection
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
var drive = rt.proxy.drive;
// Creating a new anon drive: import anon pads from localStorage
if ((!drive[Cryptpad.oldStorageKey] || !Cryptpad.isArray(drive[Cryptpad.oldStorageKey]))
&& !drive['filesData']) {
drive[Cryptpad.oldStorageKey] = [];
onReady(f, rt.proxy, Cryptpad, exp);
return;
}
// Drive already exist: return the existing drive, don't load data from legacy store
onReady(f, rt.proxy, Cryptpad, exp);
})
.on('disconnect', function (info) {
// We only manage errors during the loading screen here. Other websocket errors are handled by the apps
if (info.error) {
if (typeof Cryptpad.storeError === "function") {
Cryptpad.storeError();
}
return;
}
})
.on('change', ['drive', 'migrate'], function () {
var path = arguments[2];
var value = arguments[1];
if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
rt.network.disconnect();
rt.realtime.abort();
Cryptpad.alert(Cryptpad.Messages.fs_migration, null, true);
}
});
};
Store.ready = function (f, Cryptpad) {
if (store) { // Store.ready probably called twice, store already ready
if (typeof(f) === 'function') {
f(void 0, store);
}
} else {
init(f, Cryptpad);
}
};
return Store;
});

View File

@@ -99,7 +99,7 @@ function context () {
} else if (k.substr(0, 5) === "data-") {
e.setAttribute(k, l[k])
} else {
e[k] = l[k]
e.setAttribute(k, l[k]);
}
}
} else if ('function' === typeof l) {

View File

@@ -1,13 +1,83 @@
define([
'less!/customize/src/less/loading.less'
], function () {
define([], function () {
var loadingStyle = (function(){/*
#cp-loading {
transition: opacity 0.75s, visibility 0s 0.75s;
visibility: visible;
position: fixed;
z-index: 10000000;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 1;
}
#cp-loading.cp-loading-hidden {
opacity: 0;
visibility: hidden;
}
#cp-loading .cp-loading-container {
margin-top: 50vh;
transform: translateY(-50%);
}
#cp-loading .cp-loading-cryptofist {
margin-left: auto;
margin-right: auto;
height: 300px;
margin-bottom: 2em;
}
@media screen and (max-height: 450px) {
#cp-loading .cp-loading-cryptofist {
display: none;
}
}
#cp-loading .cp-loading-spinner-container {
position: relative;
height: 100px;
}
#cp-loading .cp-loading-spinner-container > div {
height: 100px;
}
#cp-loading-tip {
position: fixed;
z-index: 10000000;
top: 80%;
left: 0;
right: 0;
text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
}
@media screen and (max-height: 600px) {
#cp-loading-tip {
display: none;
}
}
#cp-loading-tip span {
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 15px;
max-width: 60%;
display: inline-block;
}
*/}).toString().slice(14, -3);
var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; });
var elem = document.createElement('div');
elem.setAttribute('id', 'loading');
elem.setAttribute('id', 'cp-loading');
elem.innerHTML = [
'<div class="loadingContainer">',
'<img class="cryptofist" src="/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs + '">',
'<div class="spinnerContainer">',
'<style>',
loadingStyle,
'</style>',
'<div class="cp-loading-container">',
'<img class="cp-loading-cryptofist" src="/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs + '">',
'<div class="cp-loading-spinner-container">',
'<span class="fa fa-circle-o-notch fa-spin fa-4x fa-fw"></span>',
'</div>',
'<p id="cp-loading-message"></p>',

View File

@@ -1,138 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad-crypto/crypto.js',
'/common/cryptpad-common.js',
'/common/credential.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/scrypt-async/scrypt-async.min.js', // better load speed
], function ($, Listmap, Crypto, Cryptpad, Cred) {
var Exports = {
Cred: Cred,
};
var Nacl = window.nacl;
var allocateBytes = function (bytes) {
var dispense = Cred.dispenser(bytes);
var opt = {};
// dispense 18 bytes of entropy for your encryption key
var encryptionSeed = dispense(18);
// 16 bytes for a deterministic channel key
var channelSeed = dispense(16);
// 32 bytes for a curve key
var curveSeed = dispense(32);
var curvePair = Nacl.box.keyPair.fromSecretKey(new Uint8Array(curveSeed));
opt.curvePrivate = Nacl.util.encodeBase64(curvePair.secretKey);
opt.curvePublic = Nacl.util.encodeBase64(curvePair.publicKey);
// 32 more for a signing key
var edSeed = opt.edSeed = dispense(32);
// derive a private key from the ed seed
var signingKeypair = Nacl.sign.keyPair.fromSeed(new Uint8Array(edSeed));
opt.edPrivate = Nacl.util.encodeBase64(signingKeypair.secretKey);
opt.edPublic = Nacl.util.encodeBase64(signingKeypair.publicKey);
var keys = opt.keys = Crypto.createEditCryptor(null, encryptionSeed);
// 24 bytes of base64
keys.editKeyStr = keys.editKeyStr.replace(/\//g, '-');
// 32 bytes of hex
var channelHex = opt.channelHex = Cryptpad.uint8ArrayToHex(channelSeed);
// should never happen
if (channelHex.length !== 32) { throw new Error('invalid channel id'); }
opt.channel64 = Cryptpad.hexToBase64(channelHex);
opt.userHash = '/1/edit/' + [opt.channel64, opt.keys.editKeyStr].join('/');
return opt;
};
var loadUserObject = function (opt, cb) {
var config = {
websocketURL: Cryptpad.getWebsocketURL(),
channel: opt.channelHex,
data: {},
validateKey: opt.keys.validateKey, // derived validation key
crypto: Crypto.createEncryptor(opt.keys),
logLevel: 1,
};
var rt = opt.rt = Listmap.create(config);
rt.proxy
.on('ready', function () {
cb(void 0, rt);
})
.on('disconnect', function (info) {
cb('E_DISCONNECT', info);
});
};
var isProxyEmpty = function (proxy) {
return Object.keys(proxy).length === 0;
};
Exports.loginOrRegister = function (uname, passwd, isRegister, cb) {
if (typeof(cb) !== 'function') { return; }
// Usernames are all lowercase. No going back on this one
uname = uname.toLowerCase();
// validate inputs
if (!Cred.isValidUsername(uname)) { return void cb('INVAL_USER'); }
if (!Cred.isValidPassword(passwd)) { return void cb('INVAL_PASS'); }
Cred.deriveFromPassphrase(uname, passwd, 128, function (bytes) {
// results...
var res = {
register: isRegister,
};
// run scrypt to derive the user's keys
var opt = res.opt = allocateBytes(bytes);
// use the derived key to generate an object
loadUserObject(opt, function (err, rt) {
if (err) { return void cb(err); }
res.proxy = rt.proxy;
res.realtime = rt.realtime;
res.network = rt.network;
// they're registering...
res.userHash = opt.userHash;
res.userName = uname;
// export their signing key
res.edPrivate = opt.edPrivate;
res.edPublic = opt.edPublic;
res.curvePrivate = opt.curvePrivate;
res.curvePublic = opt.curvePublic;
// they tried to just log in but there's no such user
if (!isRegister && isProxyEmpty(rt.proxy)) {
rt.network.disconnect(); // clean up after yourself
return void cb('NO_SUCH_USER', res);
}
// they tried to register, but those exact credentials exist
if (isRegister && !isProxyEmpty(rt.proxy)) {
rt.network.disconnect();
return void cb('ALREADY_REGISTERED', res);
}
cb(void 0, res);
});
});
};
return Exports;
});

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,9 @@
define([
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/common/userObject.js',
], function (Cryptpad, Crypt, FO) {
'/common/common-hash.js',
'/common/common-realtime.js',
], function (Crypt, FO, Hash, Realtime) {
var exp = {};
var getType = function (el) {
@@ -41,7 +42,7 @@ define([
if (typeof(p) === "string") {
if (getType(root) !== "object") { root = undefined; error(); return; }
if (i === path.length - 1) {
root[Cryptpad.createChannelId()] = id;
root[Hash.createChannelId()] = id;
return;
}
next = getType(path[i+1]);
@@ -83,9 +84,9 @@ define([
});
};
exp.anonDriveIntoUser = function (proxy, cb) {
exp.anonDriveIntoUser = function (proxyData, fsHash, 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 (!fsHash || !proxyData.loggedIn) {
if (typeof(cb) === "function") { return void cb(); }
}
// Get the content of FS_hash and then merge the objects, remove the migration key and cb
@@ -102,26 +103,29 @@ define([
return;
}
if (parsed) {
var proxy = proxyData.proxy;
var oldFo = FO.init(parsed.drive, {
Cryptpad: Cryptpad
loggedIn: proxyData.loggedIn,
pinPads: function () {} // without pinPads /outer/userObject.js won't be loaded
});
var onMigrated = function () {
oldFo.fixFiles();
var newData = Cryptpad.getStore().getProxy();
var newFo = newData.fo;
var newFo = proxyData.userObject;
var oldRecentPads = parsed.drive[newFo.FILES_DATA];
var newRecentPads = proxy.drive[newFo.FILES_DATA];
var newFiles = newFo.getFiles([newFo.FILES_DATA]);
var oldFiles = oldFo.getFiles([newFo.FILES_DATA]);
var newHrefs = Object.keys(newRecentPads).map(function (id) {
return newRecentPads[id].href;
});
oldFiles.forEach(function (id) {
var href = oldRecentPads[id].href;
// Do not migrate a pad if we already have it, it would create a duplicate in the drive
if (newFiles.indexOf(id) !== -1) { return; }
if (newHrefs.indexOf(href) !== -1) { return; }
// If we have a stronger version, do not add the current href
if (Cryptpad.findStronger(href, newRecentPads)) { return; }
if (Hash.findStronger(href, newRecentPads)) { return; }
// If we have a weaker version, replace the href by the new one
// NOTE: if that weaker version is in the trash, the strong one will be put in unsorted
var weaker = Cryptpad.findWeaker(href, newRecentPads);
var weaker = Hash.findWeaker(href, newRecentPads);
if (weaker) {
// Update RECENTPADS
newRecentPads.some(function (pad) {
@@ -150,15 +154,17 @@ define([
if (!proxy.FS_hashes || !Array.isArray(proxy.FS_hashes)) {
proxy.FS_hashes = [];
}
proxy.FS_hashes.push(localStorage.FS_hash);
if (typeof(cb) === "function") { cb(); }
proxy.FS_hashes.push(fsHash);
if (typeof(cb) === "function") {
Realtime.whenRealtimeSyncs(proxyData.realtime, cb);
}
};
oldFo.migrate(onMigrated);
return;
}
if (typeof(cb) === "function") { cb(); }
};
Crypt.get(localStorage.FS_hash, todo);
Crypt.get(fsHash, todo);
};
return exp;

View File

@@ -34,6 +34,10 @@ define(['json.sortify'], function (Sortify) {
}
if (!metadataObj.users) { metadataObj.users = {}; }
if (!metadataLazyObj.users) { metadataLazyObj.users = {}; }
if (!metadataObj.type) { metadataObj.type = meta.doc.type; }
if (!metadataLazyObj.type) { metadataLazyObj.type = meta.doc.type; }
var mdo = {};
// We don't want to add our user data to the object multiple times.
//var containsYou = false;
@@ -51,15 +55,14 @@ define(['json.sortify'], function (Sortify) {
mdo[meta.user.netfluxId] = meta.user;
}
metadataObj.users = mdo;
var lazyUserStr = JSON.stringify(metadataLazyObj.users[meta.user.netfluxId]);
var lazyUserStr = Sortify(metadataLazyObj.users[meta.user.netfluxId]);
dirty = false;
if (lazy || lazyUserStr !== JSON.stringify(meta.user)) {
if (lazy || lazyUserStr !== Sortify(meta.user)) {
metadataLazyObj = JSON.parse(JSON.stringify(metadataObj));
lazyChangeHandlers.forEach(function (f) { f(); });
}
if (metadataObj.title !== rememberedTitle) {
console.log("Title update\n" + metadataObj.title + '\n');
rememberedTitle = metadataObj.title;
titleChangeHandlers.forEach(function (f) { f(metadataObj.title); });
}
@@ -73,30 +76,53 @@ define(['json.sortify'], function (Sortify) {
});
};
var netfluxId;
var isReady = false;
var readyHandlers = [];
sframeChan.on('EV_METADATA_UPDATE', function (ev) {
meta = ev;
if (ev.priv) {
priv = ev.priv;
}
if (netfluxId) {
meta.user.netfluxId = netfluxId;
}
if (!isReady) {
isReady = true;
readyHandlers.forEach(function (f) { f(); });
}
change(true);
});
sframeChan.on('EV_RT_CONNECT', function (ev) {
meta.user.netfluxId = ev.myID;
netfluxId = ev.myID;
members = ev.members;
if (!meta.user) { return; }
meta.user.netfluxId = netfluxId;
change(true);
});
sframeChan.on('EV_RT_JOIN', function (ev) {
var idx = members.indexOf(ev);
if (idx !== -1) { console.log('Error: ' + ev + ' is already in members'); return; }
members.push(ev);
if (!meta.user) { return; }
change(false);
});
sframeChan.on('EV_RT_LEAVE', function (ev) {
var idx = members.indexOf(ev);
if (idx === -1) { console.log('Error: ' + ev + ' not in members'); return; }
members.splice(idx, 1);
if (!meta.user) { return; }
change(false);
});
sframeChan.on('EV_RT_DISCONNECT', function () {
members = [];
if (!meta.user) { return; }
change(true);
});
sframeChan.on('EV_RT_ERROR', function (err) {
if (err.type !== 'EEXPIRED' && err.type !== 'EDELETED') { return; }
members = [];
if (!meta.user) { return; }
change(true);
});
@@ -104,6 +130,7 @@ define(['json.sortify'], function (Sortify) {
updateMetadata: function (m) {
// JSON.parse(JSON.stringify()) reorders the json, so we have to use sortify even
// if it's on our own computer
if (!m) { return; }
if (Sortify(metadataLazyObj) === Sortify(m)) { return; }
metadataObj = JSON.parse(JSON.stringify(m));
metadataLazyObj = JSON.parse(JSON.stringify(m));
@@ -139,6 +166,10 @@ define(['json.sortify'], function (Sortify) {
},
getNetfluxId : function () {
return meta.user.netfluxId;
},
onReady: function (f) {
if (isReady) { return void f(); }
readyHandlers.push(f);
}
});
};

View File

@@ -0,0 +1,106 @@
define(['/common/common-feedback.js'], function (Feedback) {
// Start migration check
// Versions:
// 1: migrate pad attributes
// 2: migrate indent settings (codemirror)
return function (userObject) {
var version = userObject.version || 0;
// DEPRECATED
// Migration 1: pad attributes moved to filesData
var migratePadAttributesToData = function () {
return true;
};
if (version < 1) {
migratePadAttributesToData();
}
// Migration 2: global attributes from root to 'settings' subobjects
var migrateAttributes = function () {
var drawer = 'cryptpad.userlist-drawer';
var polls = 'cryptpad.hide_poll_text';
var indentKey = 'cryptpad.indentUnit';
var useTabsKey = 'cryptpad.indentWithTabs';
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject[indentKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentUnit = userObject[indentKey];
delete userObject[indentKey];
}
if (typeof(userObject[useTabsKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentWithTabs = userObject[useTabsKey];
delete userObject[useTabsKey];
}
if (typeof(userObject[drawer]) !== "undefined") {
settings.toolbar = settings.toolbar || {};
settings.toolbar['userlist-drawer'] = userObject[drawer];
delete userObject[drawer];
}
if (typeof(userObject[polls]) !== "undefined") {
settings.poll = settings.poll || {};
settings.poll['hide-text'] = userObject[polls];
delete userObject[polls];
}
};
if (version < 2) {
migrateAttributes();
Feedback.send('Migrate-2', true);
userObject.version = version = 2;
}
// Migration 3: language from localStorage to settings
var migrateLanguage = function () {
if (!localStorage.CRYPTPAD_LANG) { return; }
var l = localStorage.CRYPTPAD_LANG;
userObject.settings.language = l;
};
if (version < 3) {
migrateLanguage();
Feedback.send('Migrate-3', true);
userObject.version = version = 3;
}
// Migration 4: allowUserFeedback to settings
var migrateFeedback = function () {
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject['allowUserFeedback']) !== "undefined") {
settings.general = settings.general || {};
settings.general.allowUserFeedback = userObject['allowUserFeedback'];
delete userObject['allowUserFeedback'];
}
};
if (version < 4) {
migrateFeedback();
Feedback.send('Migrate-4', true);
userObject.version = version = 4;
}
// Migration 5: dates to Number
var migrateDates = function () {
var data = userObject.drive && userObject.drive.filesData;
if (data) {
for (var id in data) {
if (typeof data[id].ctime !== "number") {
data[id].ctime = +new Date(data[id].ctime);
}
if (typeof data[id].atime !== "number") {
data[id].atime = +new Date(data[id].atime);
}
}
}
};
if (version < 5) {
migrateDates();
Feedback.send('Migrate-5', true);
userObject.version = version = 5;
}
};
});

View File

@@ -1,126 +1,127 @@
define(function () {
define([
'/code/orgmode.js'
], function () {
var Modes = {};
// mode language (extension)
var list = Modes.list = [
"apl apl .apl",
"asciiarmor asciiarmor",
"asn.1 asn.1",
"asterisk asterisk",
"brainfuck brainfuck .b",
"clike clike",
"clojure clojure",
"cmake cmake",
"cobol cobol",
"coffeescript coffeescript",
"commonlisp commonlisp",
"crystal crystal",
"css css .css",
"cypher cypher",
"d d",
"dart dart",
"diff diff",
"django django",
"dockerfile dockerfile",
"dtd dtd",
"dylan dylan",
"ebnf ebnf",
"ecl ecl",
"eiffel eiffel",
"elm elm .elm",
"erlang erlang",
"factor factor",
"fcl fcl",
"forth forth",
"fortran fortran",
"gas gas",
"gfm gfm .md",
"gherkin gherkin",
"go go",
"groovy groovy",
"haml haml",
"handlebars handlebars",
"haskell haskell .hs",
"haskell-literate haskell-literate",
"haxe haxe",
"htmlembedded htmlembedded",
"htmlmixed htmlmixed .html",
"http http",
"idl idl",
"index.html index.html",
"jade jade",
"javascript javascript .js",
"jinja2 jinja2",
"jsx jsx .jsx",
"julia julia",
"livescript livescript",
"lua lua",
"markdown markdown .md",
"mathematica mathematica",
"mirc mirc",
"mllike mllike",
"modelica modelica",
"mscgen mscgen",
"mumps mumps",
"nginx nginx",
"nsis nsis",
"ntriples ntriples",
"octave octave",
"oz oz",
"pascal pascal",
"pegjs pegjs",
"perl perl",
"php php",
"pig pig",
"properties properties",
"protobuf protobuf",
"puppet puppet",
"python python .py",
"q q",
"r r",
"rpm rpm",
"rst rst",
"ruby ruby",
"rust rust",
"sass sass",
"scheme scheme .scm",
"shell shell .sh",
"sieve sieve",
"slim slim",
"smalltalk smalltalk",
"smarty smarty",
"solr solr",
"soy soy",
"sparql sparql",
"spreadsheet spreadsheet",
"sql sql",
"stex stex",
"stylus stylus",
"swift swift",
"tcl tcl",
"text text .txt",
"textile textile",
"tiddlywiki tiddlywiki",
"tiki tiki",
"toml toml",
"tornado tornado",
"APL apl .apl",
"ASCII-Armor asciiarmor",
"ASN.1 asn.1",
"Asterisk asterisk",
"Brainfuck brainfuck .b",
"C-like clike",
"Clojure clojure",
"CMake cmake",
"COBOL cobol",
"CoffeeScript coffeescript",
"Common_Lisp commonlisp",
"Crystal crystal",
"CSS css .css",
"Cypher cypher",
"D d",
"Dart dart",
"Diff diff",
"Django django",
"Dockerfile dockerfile",
"DTD dtd",
"Dylan dylan",
"EBNF ebnf",
"ECL ecl",
"Eiffel eiffel",
"Elm elm .elm",
"Erlang erlang",
"Factor factor",
"FCL fcl",
"Forth forth",
"Fortran fortran",
"GAS gas",
"Gherkin gherkin",
"Go go",
"Groovy groovy",
"Haml haml",
"Handlebars handlebars",
"Haskell haskell .hs",
"Haskell-Literate haskell-literate",
"Haxe haxe",
"HTML htmlmixed .html",
"HTTP http",
"IDL idl",
"JADE jade",
"JavaScript javascript .js",
"Jinja2 jinja2",
"JSX jsx .jsx",
"Julia julia",
"LiveScript livescript",
"Lua lua",
"Markdown gfm .md",
//"markdown markdown .md",
"Mathematica mathematica",
"mIRC mirc",
"ML mllike",
"Modelica modelica",
"MscGen mscgen",
"MUMPS mumps",
"Nginx nginx",
"NSIS nsis",
"N-Triples ntriples",
"Octave octave",
"Org-mode orgmode .org",
"Oz oz",
"Pascal pascal",
"PEG.js pegjs",
"Perl perl",
"PHP php",
"Pig pig",
"Properties properties",
"Protocol_Buffers protobuf",
"Puppet puppet",
"Python python .py",
"Q q",
"R r",
"RPM rpm",
"RST rst",
"Ruby ruby",
"Rust rust",
"Sass sass",
"Scheme scheme .scm",
"Shell shell .sh",
"Sieve sieve",
"Slim slim",
"Smalltalk smalltalk",
"Smarty smarty",
"Solr solr",
"Soy soy",
"SPARQL sparql",
"Spreadsheet spreadsheet",
"SQL sql",
"sTeX stex",
"Stylus stylus",
"Swift swift",
"Tcl tcl",
"Text text .txt",
"Textile textile",
"TiddlyWiki tiddlywiki",
"Tiki tiki",
"TOML toml",
"Tornado tornado",
"troff troff",
"ttcn ttcn",
"ttcn-cfg ttcn-cfg",
"turtle turtle",
"twig twig",
"vb vb",
"vbscript vbscript",
"velocity velocity",
"verilog verilog",
"vhdl vhdl",
"vue vue",
"xml xml",
"TTCN ttcn",
"TTCN-cfg ttcn-cfg",
"Turtle turtle",
"Twig twig",
"Visual_Basic vb",
"VBScript vbscript",
"Velocity velocity",
"Verilog verilog",
"VHDL vhdl",
"Vue vue",
"XML xml",
//"xwiki xwiki21",
"xquery xquery",
"yaml yaml .yaml",
"yaml-frontmatter yaml-frontmatter",
"z80 z80"
"XQuery xquery",
"YAML yaml .yaml",
"YAML_Frontmatter yaml-frontmatter",
"Z80 z80"
].map(function (line) {
var kv = line.split(/\s/);
return {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
/*
* Copyright 2014 XWiki SAS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define([], function () {
var USE_HISTORY = true;
var verbose = function (x) { console.log(x); };
verbose = function () {}; // comment out to enable verbose logging
var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
var start = function (conf) {
var channel = conf.channel;
var validateKey = conf.validateKey;
var readOnly = conf.readOnly || false;
var network = conf.network;
var onConnect = conf.onConnect || function () { };
var onMessage = conf.onMessage;
var onJoin = conf.onJoin;
var onLeave = conf.onLeave;
var onReady = conf.onReady;
var onDisconnect = conf.onDisconnect;
var onError = conf.onError;
var owners = conf.owners;
var password = conf.password;
var expire = conf.expire;
var padData;
conf = undefined;
var initializing = true;
var lastKnownHash;
var messageFromOuter = function () {};
var error = function (err, wc) {
if (onError) {
onError({
type: err,
loaded: !initializing
});
if (wc && (err === "EEXPIRED" || err === "EDELETED")) { wc.leave(); }
}
else { console.error(err); }
};
var onRdy = function (padData) {
// Trigger onReady only if not ready yet. This is important because the history keeper sends a direct
// message through "network" when it is synced, and it triggers onReady for each channel joined.
if (!initializing) { return; }
onReady(padData);
//sframeChan.event('EV_RT_READY', null);
// we're fully synced
initializing = false;
};
// shim between chainpad and netflux
var msgIn = function (peerId, msg) {
return msg.replace(/^cp\|/, '');
};
var msgOut = function (msg) {
if (readOnly) { return; }
return msg;
};
var onMsg = function(peer, msg, wc, network, direct) {
// unpack the history keeper from the webchannel
var hk = network.historyKeeper;
if (direct && peer !== hk) {
return;
}
if (direct) {
var parsed = JSON.parse(msg);
if (parsed.validateKey && parsed.channel) {
if (parsed.channel === wc.id && !validateKey) {
validateKey = parsed.validateKey;
}
if (parsed.channel === wc.id) {
padData = parsed;
}
// We have to return even if it is not the current channel:
// we don't want to continue with other channels messages here
return;
}
if (parsed.state && parsed.state === 1 && parsed.channel) {
if (parsed.channel === wc.id) {
onRdy(padData);
}
// We have to return even if it is not the current channel:
// we don't want to continue with other channels messages here
return;
}
}
if (peer === hk) {
// if the peer is the 'history keeper', extract their message
var parsed1 = JSON.parse(msg);
// First check if it is an error message (EXPIRED/DELETED)
if (parsed1.channel === wc.id && parsed1.error) {
return void error(parsed1.error, wc);
}
msg = parsed1[4];
// Check that this is a message for our channel
if (parsed1[3] !== wc.id) { return; }
}
lastKnownHash = msg.slice(0,64);
var message = msgIn(peer, msg);
verbose(message);
// slice off the bencoded header
// Why are we getting bencoded stuff to begin with?
// FIXME this shouldn't be necessary
message = unBencode(message);//.slice(message.indexOf(':[') + 1);
// pass the message into Chainpad
onMessage(message);
//sframeChan.query('Q_RT_MESSAGE', message, function () { });
};
// We use an object to store the webchannel so that we don't have to push new handlers to chainpad
// and remove the old ones when reconnecting and keeping the same 'realtime' object
// See realtime.onMessage below: we call wc.bcast(...) but wc may change
var wcObject = {};
var onOpen = function(wc, network, firstConnection) {
wcObject.wc = wc;
channel = wc.id;
// Add the existing peers in the userList
//TODO sframeChan.event('EV_RT_CONNECT', { myID: wc.myID, members: wc.members, readOnly: readOnly });
// Add the handlers to the WebChannel
wc.on('message', function (msg, sender) { //Channel msg
onMsg(sender, msg, wc, network);
});
wc.on('join', function (m) { onJoin(m); /*sframeChan.event('EV_RT_JOIN', m);*/ });
wc.on('leave', function (m) { onLeave(m); /*sframeChan.event('EV_RT_LEAVE', m);*/ });
if (firstConnection) {
// Sending a message...
messageFromOuter = function(message, cb) {
// Filter messages sent by Chainpad to make it compatible with Netflux
message = msgOut(message);
if (message) {
// Do not remove wcObject, it allows us to use a new 'wc' without changing the handler if we
// want to keep the same chainpad (realtime) object
try {
wcObject.wc.bcast(message).then(function() {
cb();
}, function(err) {
// The message has not been sent, display the error.
console.error(err);
});
} catch (e) {
console.log(e);
// Just skip calling back and it will fail on the inside.
}
}
};
}
onConnect(wc, messageFromOuter);
// Get the channel history
if (USE_HISTORY) {
var hk;
wc.members.forEach(function (p) {
if (p.length === 16) { hk = p; }
});
network.historyKeeper = hk;
var cfg = {
validateKey: validateKey,
lastKnownHash: lastKnownHash,
owners: owners,
expire: expire,
password: password
};
var msg = ['GET_HISTORY', wc.id, cfg];
// Add the validateKey if we are the channel creator and we have a validateKey
if (hk) {
network.sendto(hk, JSON.stringify(msg)).then(function () {
}, function (err) {
console.error(err);
});
}
} else {
onRdy();
}
};
/*var isIntentionallyLeaving = false;
window.addEventListener("beforeunload", function () {
isIntentionallyLeaving = true;
});*/
var findChannelById = function (webChannels, channelId) {
var webChannel;
// Array.some terminates once a truthy value is returned
// best case is faster than forEach, though webchannel arrays seem
// to consistently have a length of 1
webChannels.some(function(chan) {
if(chan.id === channelId) { webChannel = chan; return true;}
});
return webChannel;
};
var connectTo = function (network, firstConnection) {
// join the netflux network, promise to handle opening of the channel
network.join(channel || null).then(function(wc) {
onOpen(wc, network, firstConnection);
}, function(err) {
console.error(err);
});
};
network.on('disconnect', function (reason) {
console.log('disconnect');
//if (isIntentionallyLeaving) { return; }
if (reason === "network.disconnect() called") { return; }
onDisconnect();
//sframeChan.event('EV_RT_DISCONNECT');
});
network.on('reconnect', function () {
initializing = true;
connectTo(network, false);
});
network.on('message', function (msg, sender) { // Direct message
var wchan = findChannelById(network.webChannels, channel);
if (wchan) {
onMsg(sender, msg, wchan, network, true);
}
});
connectTo(network, true);
};
return {
start: start
/*function (config) {
config.sframeChan.whenReg('EV_RT_READY', function () {
start(config);
});
}*/
};
});

View File

@@ -0,0 +1,136 @@
define([
'/common/common-constants.js',
'/common/common-hash.js',
'/bower_components/localforage/dist/localforage.min.js',
'/customize/application_config.js',
], function (Constants, Hash, localForage, AppConfig) {
var LocalStore = {};
LocalStore.setThumbnail = function (key, value, cb) {
localForage.setItem(key, value, cb);
};
LocalStore.getThumbnail = function (key, cb) {
localForage.getItem(key, cb);
};
LocalStore.clearThumbnail = function (cb) {
cb = cb || function () {};
localForage.clear(cb);
};
LocalStore.setFSHash = function (hash) {
var sHash = Hash.serializeHash(hash);
localStorage[Constants.fileHashKey] = sHash;
};
LocalStore.getFSHash = function () {
var hash = localStorage[Constants.fileHashKey];
if (['undefined', 'undefined/'].indexOf(hash) !== -1) {
localStorage.removeItem(Constants.fileHashKey);
return;
}
if (hash) {
var sHash = Hash.serializeHash(hash);
if (sHash !== hash) { localStorage[Constants.fileHashKey] = sHash; }
}
return hash;
};
var getUserHash = LocalStore.getUserHash = function () {
var hash = localStorage[Constants.userHashKey];
if (['undefined', 'undefined/'].indexOf(hash) !== -1) {
localStorage.removeItem(Constants.userHashKey);
return;
}
if (hash) {
var sHash = Hash.serializeHash(hash);
if (sHash !== hash) { localStorage[Constants.userHashKey] = sHash; }
}
return hash;
};
LocalStore.setUserHash = function (hash) {
var sHash = Hash.serializeHash(hash);
localStorage[Constants.userHashKey] = sHash;
};
LocalStore.getAccountName = function () {
return localStorage[Constants.userNameKey];
};
LocalStore.isLoggedIn = function () {
return typeof getUserHash() === "string";
};
LocalStore.login = function (hash, name, cb) {
if (!hash) { throw new Error('expected a user hash'); }
if (!name) { throw new Error('expected a user name'); }
hash = Hash.serializeHash(hash);
localStorage.setItem(Constants.userHashKey, hash);
localStorage.setItem(Constants.userNameKey, name);
if (cb) { cb(); }
};
var eraseTempSessionValues = LocalStore.eraseTempSessionValues = function () {
// delete sessionStorage values that might have been left over
// from the main page's /user redirect
[
'login',
'login_user',
'login_pass',
'login_rmb',
'register'
].forEach(function (k) {
delete sessionStorage[k];
});
};
var logoutHandlers = [];
LocalStore.logout = function (cb, isDeletion) {
[
Constants.userNameKey,
Constants.userHashKey,
'loginToken',
'plan',
].forEach(function (k) {
sessionStorage.removeItem(k);
localStorage.removeItem(k);
delete localStorage[k];
delete sessionStorage[k];
});
LocalStore.clearThumbnail();
// Make sure we have an FS_hash in localStorage before reloading all the tabs
// so that we don't end up with tabs using different anon hashes
if (!LocalStore.getFSHash()) {
LocalStore.setFSHash(Hash.createRandomHash());
}
eraseTempSessionValues();
if (!isDeletion) {
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
}
if (typeof(AppConfig.customizeLogout) === 'function') {
return void AppConfig.customizeLogout(cb);
}
if (cb) { cb(); }
};
LocalStore.onLogout = function (h) {
if (typeof (h) !== "function") { return; }
if (logoutHandlers.indexOf(h) !== -1) { return; }
logoutHandlers.push(h);
};
return LocalStore;
});

View File

@@ -0,0 +1,19 @@
define([
'/api/config'
], function (ApiConfig) {
var Config = {};
Config.getWebsocketURL = function () {
if (!ApiConfig.websocketPath) { return ApiConfig.websocketURL; }
var path = ApiConfig.websocketPath;
if (/^ws{1,2}:\/\//.test(path)) { return path; }
var protocol = window.location.protocol.replace(/http/, 'ws');
var host = window.location.host;
var url = protocol + '//' + host + path;
return url;
};
return Config;
});

View File

@@ -0,0 +1,193 @@
define([
'/common/outer/async-store.js'
], function (Store) {
var Rpc = {};
Rpc.query = function (cmd, data, cb) {
switch (cmd) {
// READY
case 'CONNECT': {
Store.init(data, cb); break;
}
case 'DISCONNECT': {
Store.disconnect(data, cb); break;
}
case 'CREATE_README': {
Store.createReadme(data, cb); break;
}
case 'MIGRATE_ANON_DRIVE': {
Store.migrateAnonDrive(data, cb); break;
}
// RPC
case 'INIT_RPC': {
Store.initRpc(data, cb); break;
}
case 'UPDATE_PIN_LIMIT': {
Store.updatePinLimit(data, cb); break;
}
case 'GET_PIN_LIMIT': {
Store.getPinLimit(data, cb); break;
}
case 'CLEAR_OWNED_CHANNEL': {
Store.clearOwnedChannel(data, cb); break;
}
case 'REMOVE_OWNED_CHANNEL': {
Store.removeOwnedChannel(data, cb); break;
}
case 'UPLOAD_CHUNK': {
Store.uploadChunk(data, cb); break;
}
case 'UPLOAD_COMPLETE': {
Store.uploadComplete(data, cb); break;
}
case 'UPLOAD_STATUS': {
Store.uploadStatus(data, cb); break;
}
case 'UPLOAD_CANCEL': {
Store.uploadCancel(data, cb); break;
}
case 'PIN_PADS': {
Store.pinPads(data, cb); break;
}
case 'UNPIN_PADS': {
Store.unpinPads(data, cb); break;
}
case 'GET_DELETED_PADS': {
Store.getDeletedPads(data, cb); break;
}
case 'GET_PINNED_USAGE': {
Store.getPinnedUsage(data, cb); break;
}
// ANON RPC
case 'INIT_ANON_RPC': {
Store.initAnonRpc(data, cb); break;
}
case 'ANON_RPC_MESSAGE': {
Store.anonRpcMsg(data, cb); break;
}
case 'GET_FILE_SIZE': {
Store.getFileSize(data, cb); break;
}
case 'GET_MULTIPLE_FILE_SIZE': {
Store.getMultipleFileSize(data, cb); break;
}
// Store
case 'GET': {
Store.get(data, cb); break;
}
case 'SET': {
Store.set(data, cb); break;
}
case 'ADD_PAD': {
Store.addPad(data, cb); break;
}
case 'SET_PAD_TITLE': {
Store.setPadTitle(data, cb); break;
}
case 'MOVE_TO_TRASH': {
Store.moveToTrash(data, cb); break;
}
case 'RESET_DRIVE': {
Store.resetDrive(data, cb); break;
}
case 'GET_METADATA': {
Store.getMetadata(data, cb); break;
}
case 'SET_DISPLAY_NAME': {
Store.setDisplayName(data, cb); break;
}
case 'SET_PAD_ATTRIBUTE': {
Store.setPadAttribute(data, cb); break;
}
case 'GET_PAD_ATTRIBUTE': {
Store.getPadAttribute(data, cb); break;
}
case 'SET_ATTRIBUTE': {
Store.setAttribute(data, cb); break;
}
case 'GET_ATTRIBUTE': {
Store.getAttribute(data, cb); break;
}
case 'LIST_ALL_TAGS': {
Store.listAllTags(data, cb); break;
}
case 'GET_TEMPLATES': {
Store.getTemplates(data, cb); break;
}
case 'GET_SECURE_FILES_LIST': {
Store.getSecureFilesList(data, cb); break;
}
case 'GET_PAD_DATA': {
Store.getPadData(data, cb); break;
}
case 'SET_INITIAL_PATH': {
Store.setInitialPath(data); break;
}
case 'GET_STRONGER_HASH': {
Store.getStrongerHash(data, cb); break;
}
// Messaging
case 'INVITE_FROM_USERLIST': {
Store.inviteFromUserlist(data, cb); break;
}
// Messenger
case 'CONTACTS_GET_FRIEND_LIST': {
Store.messenger.getFriendList(data, cb); break;
}
case 'CONTACTS_GET_MY_INFO': {
Store.messenger.getMyInfo(data, cb); break;
}
case 'CONTACTS_GET_FRIEND_INFO': {
Store.messenger.getFriendInfo(data, cb); break;
}
case 'CONTACTS_REMOVE_FRIEND': {
Store.messenger.removeFriend(data, cb); break;
}
case 'CONTACTS_OPEN_FRIEND_CHANNEL': {
Store.messenger.openFriendChannel(data, cb); break;
}
case 'CONTACTS_GET_FRIEND_STATUS': {
Store.messenger.getFriendStatus(data, cb); break;
}
case 'CONTACTS_GET_MORE_HISTORY': {
Store.messenger.getMoreHistory(data, cb); break;
}
case 'CONTACTS_SEND_MESSAGE': {
Store.messenger.sendMessage(data, cb); break;
}
case 'CONTACTS_SET_CHANNEL_HEAD': {
Store.messenger.setChannelHead(data, cb); break;
}
// Pad
case 'SEND_PAD_MSG': {
Store.sendPadMsg(data, cb); break;
}
case 'JOIN_PAD': {
Store.joinPad(data, cb); break;
}
case 'GET_FULL_HISTORY': {
Store.getFullHistory(data, cb); break;
}
// Drive
case 'DRIVE_USEROBJECT': {
Store.userObjectCommand(data, cb); break;
}
// Settings
case 'DELETE_ACCOUNT': {
Store.deleteAccount(data, cb); break;
}
case 'IS_NEW_CHANNEL': {
Store.isNewChannel(data, cb); break;
}
default: {
console.error("UNHANDLED_STORE_RPC");
break;
}
}
};
return Rpc;
});

View File

@@ -0,0 +1,92 @@
define([
'/file/file-crypto.js',
'/common/common-hash.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (FileCrypto, Hash) {
var Nacl = window.nacl;
var module = {};
module.upload = function (file, noStore, common, updateProgress, onComplete, onError, onPending) {
var u8 = file.blob; // This is not a blob but a uint8array
var metadata = file.metadata;
// if it exists, path contains the new pad location in the drive
var path = file.path;
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
});
};
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
});
}
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
// if not box then done
common.uploadComplete(function (e, id) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var b64Key = Nacl.util.encodeBase64(key);
var hash = Hash.getFileHashFromKeys(id, b64Key);
var href = '/file/#' + hash;
var title = metadata.name;
if (noStore) { return void onComplete(href); }
common.setPadTitle(title || "", href, path, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
});
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
next(again);
});
});
}
next(again);
});
};
return module;
});

View File

@@ -0,0 +1,618 @@
define([
'/customize/application_config.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-feedback.js',
'/customize/messages.js'
], function (AppConfig, Util, Hash, Realtime, Feedback, Messages) {
var module = {};
var clone = function (o) {
try { return JSON.parse(JSON.stringify(o)); }
catch (e) { return undefined; }
};
module.init = function (config, exp, files) {
var unpinPads = config.unpinPads || function () {
console.error("unpinPads was not provided");
};
var pinPads = config.pinPads;
var removeOwnedChannel = config.removeOwnedChannel || function () {
console.error("removeOwnedChannel was not provided");
};
var loggedIn = config.loggedIn;
var workgroup = config.workgroup;
var edPublic = config.edPublic;
var ROOT = exp.ROOT;
var FILES_DATA = exp.FILES_DATA;
var OLD_FILES_DATA = exp.OLD_FILES_DATA;
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
var TEMPLATE = exp.TEMPLATE;
var debug = exp.debug;
exp.setPadAttribute = function (href, attr, value, cb) {
cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb("E_INVAL_HREF"); }
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
var data = exp.getFileData(id);
data[attr] = clone(value);
cb(null);
};
exp.getPadAttribute = function (href, attr, cb) {
cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb(null, undefined); }
var data = exp.getFileData(id);
cb(null, clone(data[attr]));
};
var removePadAttribute = exp.removePadAttribute = function (f) {
if (typeof(f) !== 'string') {
console.error("Can't find pad attribute for an undefined pad");
return;
}
Object.keys(files).forEach(function (key) {
var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null;
if (hash && key.indexOf(hash) === 0) {
exp.debug("Deleting pad attribute in the realtime object");
delete files[key];
}
});
};
exp.pushData = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
var todo = function () {
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
}
if (!pinPads) { return; }
pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
};
// FILES DATA
var spliceFileData = function (id) {
delete files[FILES_DATA][id];
};
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
// FILES_DATA. If there are owned pads, remove them from server too, unless the flag tells
// us they're already removed
exp.checkDeletedFiles = function (isOwnPadRemoved) {
// Nothing in FILES_DATA for workgroups
if (workgroup || (!loggedIn && !config.testMode)) { return; }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
exp.getFiles([FILES_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.getFileData(id);
var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href);
// If trying to remove an owned pad, remove it from server also
if (!isOwnPadRemoved &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
removeOwnedChannel(channelId, function (obj) {
if (obj && obj.error) {
// If the error is that the file is already removed, nothing to
// report, it's a normal behavior (pad expired probably)
if (obj.error.code === 'ENOENT') { return; }
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true);
}
});
}
if (channelId) { toClean.push(channelId); }
spliceFileData(id);
}
});
if (!toClean.length) { return; }
unpinPads(toClean, function (response) {
if (response && response.error) { return console.error(response.error); }
// console.error(response);
});
};
var deleteHrefs = function (ids) {
ids.forEach(function (obj) {
var idx = files[obj.root].indexOf(obj.id);
files[obj.root].splice(idx, 1);
});
};
var deleteMultipleTrashRoot = function (roots) {
roots.forEach(function (obj) {
var idx = files[TRASH][obj.name].indexOf(obj.el);
files[TRASH][obj.name].splice(idx, 1);
});
};
exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved) {
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
var allFilesPaths = paths.filter(function(x) { return exp.isPathIn(x, [FILES_DATA]); });
if (!loggedIn && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = exp.find(path);
if (!el) { return; }
var id = exp.getIdFromHref(el.href);
if (!id) { return; }
spliceFileData(id);
removePadAttribute(el.href);
});
return;
}
var ids = [];
hrefPaths.forEach(function (path) {
var id = exp.find(path);
ids.push({
root: path[0],
id: id
});
});
deleteHrefs(ids);
rootPaths.forEach(function (path) {
var parentPath = path.slice();
var key = parentPath.pop();
var parentEl = exp.find(parentPath);
delete parentEl[key];
});
var trashRoot = [];
trashPaths.forEach(function (path) {
var parentPath = path.slice();
var key = parentPath.pop();
var parentEl = exp.find(parentPath);
// Trash root: we have array here, we can't just splice with the path otherwise we might break the path
// of another element in the loop
if (path.length === 4) {
trashRoot.push({
name: path[1],
el: parentEl
});
return;
}
// Trash but not root: it's just a tree so remove the key
delete parentEl[key];
});
deleteMultipleTrashRoot(trashRoot);
// In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved); }
};
// Move
var pushToTrash = function (name, element, path) {
var trash = files[TRASH];
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
var trashArray = trash[name];
var trashElement = {
element: element,
path: path
};
trashArray.push(trashElement);
};
exp.copyElement = function (elementPath, newParentPath) {
if (exp.comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
var element = exp.find(elementPath);
var newParent = exp.find(newParentPath);
// Move to Trash
if (exp.isPathIn(newParentPath, [TRASH])) {
if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
debug("Can't move an element from the trash to the trash: ", elementPath);
return;
}
var key = elementPath[elementPath.length - 1];
var elName = exp.isPathIn(elementPath, ['hrefArray']) ? exp.getTitle(element) : key;
var parentPath = elementPath.slice();
parentPath.pop();
pushToTrash(elName, element, parentPath);
return true;
}
// Move to hrefArray
if (exp.isPathIn(newParentPath, ['hrefArray'])) {
if (exp.isFolder(element)) {
exp.log(Messages.fo_moveUnsortedError);
return;
} else {
if (elementPath[0] === newParentPath[0]) { return; }
var fileRoot = newParentPath[0];
if (files[fileRoot].indexOf(element) === -1) {
files[fileRoot].push(element);
}
return true;
}
}
// Move to root
var newName = exp.isFile(element) ?
exp.getAvailableName(newParent, Hash.createChannelId()) :
exp.isInTrashRoot(elementPath) ?
elementPath[1] : elementPath.pop();
if (typeof(newParent[newName]) !== "undefined") {
exp.log(Messages.fo_unavailableName);
return;
}
newParent[newName] = element;
return true;
};
// FORGET (move with href not path)
exp.forget = function (href) {
var id = exp.getIdFromHref(href);
if (!id) { return; }
if (!loggedIn && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
return;
}
var paths = exp.findFile(id);
exp.move(paths, [TRASH]);
};
// REPLACE
exp.replace = function (o, n) {
var idO = exp.getIdFromHref(o);
if (!idO || !exp.isFile(idO)) { return; }
var data = exp.getFileData(idO);
if (!data) { return; }
data.href = n;
};
// If all the occurences of an href are in the trash, remvoe them and add the file in root.
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp.restoreHref = function (href) {
var idO = exp.getIdFromHref(href);
if (!idO || !exp.isFile(idO)) { return; }
var paths = exp.findFile(idO);
// Remove all the occurences in the trash
// If all the occurences are in the trash or no occurence, add the pad to root
var allInTrash = true;
paths.forEach(function (p) {
if (p[0] === TRASH) {
exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
return;
}
allInTrash = false;
});
if (allInTrash) {
exp.add(idO);
}
};
exp.add = function (id, path) {
// TODO WW
if (!loggedIn && !config.testMode) { return; }
var data = files[FILES_DATA][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
newPath = decodeURIComponent(path).split(',');
}
// Add to href array
if (path && exp.isPathIn(newPath, ['hrefArray'])) {
parentEl = exp.find(newPath);
parentEl.push(id);
return;
}
// Add to root if path is ROOT or if no path
var filesList = exp.getFiles([ROOT, TRASH, 'hrefArray']);
if (path && exp.isPathIn(newPath, [ROOT]) || filesList.indexOf(id) === -1) {
parentEl = exp.find(newPath || [ROOT]);
if (parentEl) {
var newName = exp.getAvailableName(parentEl, Hash.createChannelId());
parentEl[newName] = id;
return;
}
}
};
/**
* INTEGRITY CHECK
*/
exp.migrate = function (cb) {
// Make sure unsorted doesn't exist anymore
// Note: Unsorted only works with the old structure where pads are href
// It should be called before the migration code
var fixUnsorted = function () {
if (!files[UNSORTED] || !files[OLD_FILES_DATA]) { return; }
debug("UNSORTED still exists in the object, removing it...");
var us = files[UNSORTED];
if (us.length === 0) {
delete files[UNSORTED];
return;
}
us.forEach(function (el) {
if (typeof el !== "string") {
return;
}
var data = files[OLD_FILES_DATA].filter(function (x) {
return x.href === el;
});
if (data.length === 0) {
files[OLD_FILES_DATA].push({
href: el
});
}
return;
});
delete files[UNSORTED];
};
// mergeDrive...
var migrateToNewFormat = function (todo) {
if (!files[OLD_FILES_DATA]) {
return void todo();
}
try {
debug("Migrating file system...");
files.migrate = 1;
var next = function () {
var oldData = files[OLD_FILES_DATA].slice();
if (!files[FILES_DATA]) {
files[FILES_DATA] = {};
}
var newData = files[FILES_DATA];
//var oldFiles = oldData.map(function (o) { return o.href; });
oldData.forEach(function (obj) {
if (!obj || !obj.href) { return; }
var href = obj.href;
var id = Util.createRandomInteger();
var paths = exp.findFile(href);
var data = obj;
var key = Hash.createChannelId();
if (data) {
newData[id] = data;
} else {
newData[id] = {href: href};
}
paths.forEach(function (p) {
var parentPath = p.slice();
var okey = parentPath.pop(); // get the parent
var parent = exp.find(parentPath);
if (exp.isInTrashRoot(p)) {
parent.element = id;
newData[id].filename = p[1];
return;
}
if (exp.isPathIn(p, ['hrefArray'])) {
parent[okey] = id;
return;
}
// else root or trash (not trashroot)
parent[key] = id;
newData[id].filename = okey;
delete parent[okey];
});
});
delete files[OLD_FILES_DATA];
delete files.migrate;
console.log('done');
todo();
};
if (exp.rt) {
exp.rt.sync();
// TODO
Realtime.whenRealtimeSyncs(exp.rt, next);
} else {
window.setTimeout(next, 1000);
}
} catch(e) {
console.error(e);
todo();
}
};
fixUnsorted();
migrateToNewFormat(cb);
};
exp.fixFiles = function () {
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
// * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
// * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
debug("Cleaning file system...");
var before = JSON.stringify(files);
var fixRoot = function (elem) {
if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
var element = elem || files[ROOT];
for (var el in element) {
if (!exp.isFile(element[el], true) && !exp.isFolder(element[el])) {
debug("An element in ROOT was not a folder nor a file. ", element[el]);
delete element[el];
continue;
}
if (exp.isFolder(element[el])) {
fixRoot(element[el]);
continue;
}
if (typeof element[el] === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
var key = Hash.createChannelId();
files[FILES_DATA][id] = {href: element[el], filename: el};
element[key] = id;
delete element[el];
}
if (typeof element[el] === "number") {
var data = files[FILES_DATA][element[el]];
if (!data) {
debug("An element in ROOT doesn't have associated data", element[el], el);
delete element[el];
}
}
}
};
var fixTrashRoot = function () {
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
var tr = files[TRASH];
var toClean;
var addToClean = function (obj, idx, el) {
if (typeof(obj) !== "object") { toClean.push(idx); return; }
if (!exp.isFile(obj.element, true) &&
!exp.isFolder(obj.element)) { toClean.push(idx); return; }
if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
if (typeof obj.element === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {href: obj.element, filename: el};
obj.element = id;
}
if (exp.isFolder(obj.element)) { fixRoot(obj.element); }
if (typeof obj.element === "number") {
var data = files[FILES_DATA][obj.element];
if (!data) {
debug("An element in TRASH doesn't have associated data", obj.element, el);
toClean.push(idx);
}
}
};
for (var el in tr) {
if (!Array.isArray(tr[el])) {
debug("An element in TRASH root is not an array. ", tr[el]);
delete tr[el];
} else if (tr[el].length === 0) {
debug("Empty array in TRASH root. ", tr[el]);
delete tr[el];
} else {
toClean = [];
for (var j=0; j<tr[el].length; j++) {
addToClean(tr[el][j], j, el);
}
for (var i = toClean.length-1; i>=0; i--) {
tr[el].splice(toClean[i], 1);
}
}
}
};
var fixTemplate = function () {
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
var us = files[TEMPLATE];
var rootFiles = exp.getFiles([ROOT]).slice();
var toClean = [];
us.forEach(function (el, idx) {
if (!exp.isFile(el, true) || rootFiles.indexOf(el) !== -1) {
toClean.push(el);
}
if (typeof el === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {href: el};
us[idx] = id;
}
if (typeof el === "number") {
var data = files[FILES_DATA][el];
if (!data) {
debug("An element in TEMPLATE doesn't have associated data", el);
toClean.push(el);
}
}
});
toClean.forEach(function (el) {
var idx = us.indexOf(el);
if (idx !== -1) {
us.splice(idx, 1);
}
});
};
var fixFilesData = function () {
if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; }
var fd = files[FILES_DATA];
var rootFiles = exp.getFiles([ROOT, TRASH, 'hrefArray']);
var root = exp.find([ROOT]);
var toClean = [];
for (var id in fd) {
id = Number(id);
var el = fd[id];
if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el);
toClean.push(id);
continue;
}
if (!el.href) {
debug("Removing an element in filesData with a missing href.", el);
toClean.push(id);
continue;
}
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
if (!el.ctime) { el.ctime = el.atime; }
var parsed = Hash.parsePadUrl(el.href);
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id);
continue;
}
if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id);
continue;
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId();
root[newName] = id;
continue;
}
}
toClean.forEach(function (id) {
spliceFileData(id);
});
};
var fixDrive = function () {
Object.keys(files).forEach(function (key) {
if (key.slice(0,1) === '/') { delete files[key]; }
});
};
fixRoot();
fixTrashRoot();
if (!workgroup) {
fixTemplate();
fixFilesData();
}
fixDrive();
if (JSON.stringify(files) !== before) {
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely");
return;
}
debug("File system was clean");
};
return exp;
};
return module;
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 933 B

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 B

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 B

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 B

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 425 B

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 B

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 B

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 B

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 B

After

Width:  |  Height:  |  Size: 429 B

Some files were not shown because too many files have changed in this diff Show More