Merge branch 'master' of https://github.com/xwiki-labs/cryptpad
7
www/assert/frame/frame.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="respond.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
147
www/assert/frame/frame.js
Normal 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;
|
||||
}
|
||||
}());
|
||||
32
www/assert/frame/respond.js
Normal 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");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = '';
|
||||
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();
|
||||
});
|
||||
|
||||
559
www/code/main.js
@@ -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 = '';
|
||||
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
@@ -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));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
121
www/common/application_config_internal.js
Normal 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;
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
16
www/common/common-constants.js
Normal 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'
|
||||
};
|
||||
});
|
||||
56
www/common/common-feedback.js
Normal 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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
80
www/common/common-language.js
Normal 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;
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
29
www/common/common-notifier.js
Normal 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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
2153
www/common/common-ui-elements.js
Normal 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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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(); }
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
define([
|
||||
'/customize/messages.js',
|
||||
], function (Messages) {
|
||||
Messages._applyTranslation();
|
||||
'/common/common-language.js',
|
||||
], function (Language) {
|
||||
Language.applyTranslation();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
www/common/flat-dom.js
Normal 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;
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>',
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
106
www/common/migrate-user-object.js
Normal 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;
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -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 {
|
||||
|
||||
1262
www/common/outer/async-store.js
Normal file
267
www/common/outer/chainpad-netflux-worker.js
Normal 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);
|
||||
});
|
||||
}*/
|
||||
};
|
||||
});
|
||||
|
||||
136
www/common/outer/local-store.js
Normal 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;
|
||||
});
|
||||
19
www/common/outer/network-config.js
Normal 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;
|
||||
});
|
||||
193
www/common/outer/store-rpc.js
Normal 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;
|
||||
});
|
||||
|
||||
92
www/common/outer/upload.js
Normal 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;
|
||||
});
|
||||
618
www/common/outer/userObject.js
Normal 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;
|
||||
});
|
||||
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 293 B |
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 283 B |
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 283 B |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 293 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 603 B |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 915 B |
|
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 176 B |
|
Before Width: | Height: | Size: 266 B After Width: | Height: | Size: 254 B |
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 543 B |
|
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 174 B |
|
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 270 B |
|
Before Width: | Height: | Size: 731 B After Width: | Height: | Size: 702 B |
|
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 711 B |
|
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 336 B |
|
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 151 B |
|
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 417 B |
|
Before Width: | Height: | Size: 152 B After Width: | Height: | Size: 145 B |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 292 B |
|
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 536 B |
|
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 231 B |
|
Before Width: | Height: | Size: 398 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 232 B |
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 388 B |
|
Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 234 B |
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 378 B |
|
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 461 B |
|
Before Width: | Height: | Size: 653 B After Width: | Height: | Size: 627 B |
|
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 429 B |