resolve merge conflicts

This commit is contained in:
ansuz
2017-11-23 16:56:49 +01:00
88 changed files with 2238 additions and 8645 deletions

View File

@@ -0,0 +1,14 @@
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',
};
});

View File

@@ -1,366 +0,0 @@
define([
'jquery',
'/file/file-crypto.js',
'/common/common-thumbnail.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto, Thumb) {
var Nacl = window.nacl;
var module = {};
var blobToArrayBuffer = module.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.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.rpc.send.unauthenticated('UPLOAD', 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 = common.getFileHashFromKeys(id, b64Key);
var href = '/file/#' + hash;
var title = metadata.name;
if (noStore) { return void onComplete(href); }
common.initialPath = path;
common.renamePad(title || "", href, 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);
});
};
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; // This is not a blob but an array buffer
var u8 = new Uint8Array(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 onComplete = function (href) {
$link.attr('href', href)
.click(function (e) {
e.preventDefault();
window.open($link.attr('href'), '_blank');
});
var title = metadata.name;
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();
};
var onError = function (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);
};
var onPending = function (cb) {
common.confirm(Messages.upload_uploadPending, function (yes) {
if (!yes) { return; }
cb();
});
};
file.blob = u8;
module.upload(file, config.noStore, common, updateProgress, onComplete, onError, onPending);
};
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 file_arraybuffer;
var finish = function () {
var metadata = {
name: file.name,
type: file.type,
};
if (thumb) { metadata.thumbnail = thumb; }
queue.push({
blob: file_arraybuffer,
metadata: metadata,
dropEvent: e
});
};
blobToArrayBuffer(file, function (e, buffer) {
if (e) { console.error(e); }
file_arraybuffer = buffer;
if (thumbnail) { // there is already a thumbnail
return blobToArrayBuffer(thumbnail, function (e, buffer) {
if (e) { console.error(e); }
thumb = arrayBufferToString(buffer);
finish();
});
}
if (!Thumb.isSupportedType(file.type)) { return finish(); }
// make a resized thumbnail from the image..
Thumb.fromBlob(file, function (e, thumb64) {
if (e) { console.error(e); }
if (!thumb64) { return finish(); }
thumb = thumb64;
finish();
});
});
};
var onFileDrop = File.onFileDrop = function (file, e) {
if (!common.isLoggedIn()) {
return common.alert(common.Messages.upload_mustLogin);
}
Array.prototype.slice.call(file).forEach(function (d) {
handleFile(d, e);
});
};
var createAreaHandlers = File.createDropArea = function ($area, $hoverArea) {
var counter = 0;
if (!$hoverArea) { $hoverArea = $area; }
if (!$area) { return; }
$hoverArea
.on('dragenter', function (e) {
e.preventDefault();
e.stopPropagation();
counter++;
$hoverArea.addClass('hovering');
})
.on('dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
counter--;
if (counter <= 0) {
$hoverArea.removeClass('hovering');
}
});
$area
.on('drag dragstart dragend dragover drop dragenter dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
})
.on('drop', function (e) {
e.stopPropagation();
var dropped = e.originalEvent.dataTransfer.files;
counter = 0;
$hoverArea.removeClass('hovering');
onFileDrop(dropped, e);
});
};
var createUploader = function ($area, $hover, $body) {
if (!config.noHandlers) {
createAreaHandlers($area, null);
}
createTableContainer($body);
};
createUploader(config.dropArea, config.hoverArea, config.body);
return File;
};
return module;
});

View File

@@ -1,9 +1,9 @@
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 = {};
@@ -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) {
@@ -114,6 +114,7 @@ Version 1
if (!href) { return ret; }
if (href.slice(-1) !== '/') { href += '/'; }
href = href.replace(/\/\?[^#]+#/, '/#');
var idx;
@@ -211,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");
}
}
@@ -363,5 +362,19 @@ Version 1
'/' + curvePublic.replace(/\//g, '-') + '/';
};
// Create untitled documents when no name is given
var getLocaleDate = function () {
if (window.Intl && window.Intl.DateTimeFormat) {
var options = {weekday: "short", year: "numeric", month: "long", day: "numeric"};
return new window.Intl.DateTimeFormat(undefined, options).format(new Date());
}
return new Date().toString().split(' ').slice(0,4).join(' ');
};
Hash.getDefaultName = function (parsed) {
var type = parsed.type;
var name = (Messages.type)[type] + ' - ' + getLocaleDate();
return name;
};
return Hash;
});

View File

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

View File

@@ -2,16 +2,17 @@ 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',
'/customize/pages.js',
'/common/hyperscript.js',
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
'css!/common/tippy.css',
], function ($, Messages, Util, AppConfig, Alertify, Notify, Visible, Tippy, Pages, h) {
], function ($, Messages, Util, Hash, Notifier, AppConfig,
Alertify, Tippy, Pages, h) {
var UI = {};
/*
@@ -270,7 +271,7 @@ define([
document.body.appendChild(frame);
setTimeout(function () {
$ok.focus();
UI.notify();
Notifier.notify();
});
};
@@ -318,7 +319,7 @@ define([
document.body.appendChild(frame);
setTimeout(function () {
$(input).select().focus();
UI.notify();
Notifier.notify();
});
};
@@ -365,7 +366,7 @@ define([
document.body.appendChild(frame);
setTimeout(function () {
UI.notify();
Notifier.notify();
$(frame).find('.ok').focus();
if (typeof(opt.done) === 'function') {
opt.done($ok.closest('.dialog'));
@@ -468,44 +469,6 @@ define([
$('#' + LOADING).find('p').html(error || Messages.error);
};
// Notify
var notify = {};
UI.unnotify = function () {
if (notify.tabNotification &&
typeof(notify.tabNotification.cancel) === 'function') {
notify.tabNotification.cancel();
}
};
UI.notify = function () {
if (Visible.isSupported() && !Visible.currently()) {
UI.unnotify();
notify.tabNotification = Notify.tab(1000, 10);
}
};
if (Visible.isSupported()) {
Visible.onChange(function (yes) {
if (yes) { UI.unnotify(); }
});
}
UI.importContent = function (type, f, 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);
});
};
};
var $defaultIcon = $('<span>', {"class": "fa fa-file-text-o"});
UI.getIcon = function (type) {
var $icon = $defaultIcon.clone();
@@ -517,6 +480,17 @@ define([
return $icon;
};
UI.getFileIcon = function (data) {
var $icon = UI.getIcon();
if (!data) { return $icon; }
var href = data.href;
if (!href) { return $icon; }
var type = Hash.parsePadUrl(href).type;
$icon = UI.getIcon(type);
return $icon;
};
// Tooltips
@@ -524,11 +498,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').length? $('#pad-iframe')[0].contentWindow: undefined;
if (!win) { return; }
if (win.$('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
if ($('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
el.remove();
}
});
@@ -536,52 +507,54 @@ 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;
$(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++) {
$(mutation.addedNodes[i]).find('[title]').each(addTippy);
}
for (var j = 0; j < mutation.removedNodes.length; j++) {
removed |= checkRemoved(mutation.removedNodes[j]);
}
}
if (mutation.type === "attributes" && mutation.attributeName === "title") {
addTippy(0, mutation.target);
}
});
if (removed) { UI.clearTooltips(); }
});
observer.observe($('body')[0], {
attributes: false,
attributes: true,
childList: true,
characterData: false,
subtree: true
});
if ($('#pad-iframe').length) {
observer.observe($('#pad-iframe').contents().find('body')[0], {
attributes: false,
childList: true,
characterData: false,
subtree: true
});
}
};
return UI;

View File

@@ -3,10 +3,13 @@ define([
'/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, Curve, Hash, Util, Constants, Messages, Marked, Realtime) {
var Msg = {
inputs: [],
};
@@ -80,13 +83,13 @@ define([
friends[pubKey] = data;
Realtime.whenRealtimeSyncs(common, common.getRealtime(), function () {
Realtime.whenRealtimeSyncs(common.getRealtime(), function () {
cb();
common.pinPads([data.channel], function (e) {
if (e) { console.error(e); }
});
});
common.changeDisplayName(proxy[common.displayNameKey]);
common.changeDisplayName(proxy[Constants.displayNameKey]);
};
/* Used to accept friend requests within apps other than /contacts/ */
@@ -98,7 +101,7 @@ define([
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
@@ -132,11 +135,11 @@ 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);
//UI.confirm(confirmMsg, todo, null, true);
return;
}
if (msg[0] === "FRIEND_REQ_OK") {
@@ -147,12 +150,12 @@ define([
addToFriendList(common, msgData, function (err) {
if (err) {
return void common.onFriendComplete({
logText: common.Messages.contacts_addError,
logText: Messages.contacts_addError,
netfluxId: sender
});
}
common.onFriendComplete({
logText: common.Messages.contacts_added,
logText: Messages.contacts_added,
netfluxId: sender
});
var msg = ["FRIEND_REQ_ACK", chan];
@@ -165,10 +168,10 @@ define([
var i = pendingRequests.indexOf(sender);
if (i !== -1) { pendingRequests.splice(i, 1); }
common.onFriendComplete({
logText: common.Messages.contacts_rejected,
logText: Messages.contacts_rejected,
netfluxId: sender
});
common.changeDisplayName(proxy[common.displayNameKey]);
common.changeDisplayName(proxy[Constants.displayNameKey]);
return;
}
if (msg[0] === "FRIEND_REQ_ACK") {
@@ -177,12 +180,12 @@ define([
addToFriendList(common, data, function (err) {
if (err) {
return void common.onFriendComplete({
logText: common.Messages.contacts_addError,
logText: Messages.contacts_addError,
netfluxId: sender
});
}
common.onFriendComplete({
logText: common.Messages.contacts_added,
logText: Messages.contacts_added,
netfluxId: sender
});
});
@@ -201,7 +204,7 @@ define([
Msg.inviteFromUserlist = function (common, netfluxId) {
var network = common.getNetwork();
var parsed = common.parsePadUrl(window.location.href);
var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; }
// Message
var chan = parsed.hashData.channel;
@@ -218,7 +221,7 @@ define([
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]);
common.changeDisplayName(proxy[Constants.displayNameKey]);
}
network.sendto(netfluxId, msgStr);
};

View File

@@ -3,7 +3,9 @@ define([
'/bower_components/chainpad-crypto/crypto.js',
'/common/curve.js',
'/common/common-hash.js',
], function ($, Crypto, Curve, Hash) {
'/common/common-util.js',
'/common/common-realtime.js',
], function ($, Crypto, Curve, Hash, Util, Realtime) {
'use strict';
var Msg = {
inputs: [],
@@ -48,21 +50,6 @@ 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;
@@ -149,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,
@@ -245,7 +232,7 @@ define([
if (!proxy.friends) { return; }
var friends = proxy.friends;
delete friends[curvePublic];
common.whenRealtimeSyncs(realtime, cb);
Realtime.whenRealtimeSyncs(realtime, cb);
};
var pushMsg = function (channel, cryptMsg) {
@@ -352,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);
@@ -443,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) {
@@ -476,7 +463,7 @@ define([
channel.wc.bcast(cryptMsg).then(function () {
delete friends[curvePublic];
delete channels[curvePublic];
common.whenRealtimeSyncs(realtime, function () {
Realtime.whenRealtimeSyncs(realtime, function () {
cb();
});
}, function (err) {
@@ -646,6 +633,13 @@ define([
});
};
messenger.clearOwnedChannel = function (channel, cb) {
common.clearOwnedChannel(channel, function (e) {
if (e) { return void cb(e); }
cb();
});
};
// TODO listen for changes to your friend list
// emit 'update' events for clients

View File

@@ -1,59 +0,0 @@
define(function () {
var module = {};
module.create = function (UserList, Title, cfg, Cryptpad) {
var exp = {};
exp.update = function (shjson) {
// Extract the user list (metadata) from the hyperjson
var json = (!shjson || typeof shjson !== "string") ? "" : JSON.parse(shjson);
var titleUpdated = false;
var metadata;
if (Array.isArray(json)) {
metadata = json[3] && json[3].metadata;
} else {
metadata = json.metadata;
}
if (typeof metadata === "object") {
if (Cryptpad) {
if (typeof(metadata.type) === 'undefined') {
// initialize pad type by location.pathname
metadata.type = Cryptpad.getAppType();
}
} else {
console.log("Cryptpad should exist but it does not");
}
if (metadata.users) {
var userData = metadata.users;
// Update the local user data
UserList.addToUserData(userData);
}
if (metadata.defaultTitle) {
Title.updateDefaultTitle(metadata.defaultTitle);
}
if (typeof metadata.title !== "undefined") {
Title.updateTitle(metadata.title || Title.defaultTitle);
titleUpdated = true;
}
if (metadata.slideOptions && cfg.slideOptions) {
cfg.slideOptions(metadata.slideOptions);
}
if (metadata.color && cfg.slideColors) {
cfg.slideColors(metadata.color, metadata.backColor);
}
if (typeof(metadata.palette) !== 'undefined' && cfg.updatePalette) {
cfg.updatePalette(metadata.palette);
}
}
if (!titleUpdated) {
Title.updateTitle(Title.defaultTitle);
}
};
return exp;
};
return module;
});

View File

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

View File

@@ -1,7 +1,8 @@
define([
'/customize/application_config.js',
'/customize/messages.js',
], function (AppConfig, Messages) {
'/common/common-interface.js',
], function (AppConfig, Messages, UI) {
var common = {};
common.infiniteSpinnerDetected = false;
@@ -15,7 +16,7 @@ define([
/*
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');
}
@@ -28,7 +29,7 @@ define([
}, 0);
};
common.beginDetectingInfiniteSpinner = function (Cryptpad, realtime) {
common.beginDetectingInfiniteSpinner = function (realtime) {
if (intr) { return; }
intr = window.setInterval(function () {
var l;
@@ -44,7 +45,7 @@ define([
infiniteSpinnerHandlers.forEach(function (ish) { ish(); });
// inform the user their session is in a bad state
Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) {
UI.confirm(Messages.realtime_unrecoverableError, function (yes) {
if (!yes) { return; }
window.parent.location.reload();
});

View File

@@ -105,6 +105,7 @@ define([
var ctx = c2.getContext('2d');
ctx.drawImage(canvas, D.x, D.y, D.w, D.h);
cb(void 0, c2.toDataURL());
};
@@ -124,19 +125,15 @@ define([
Thumb.fromVideoBlob = function (blob, cb) {
var url = URL.createObjectURL(blob);
var video = document.createElement("VIDEO");
video.src = url;
video.addEventListener('loadedmetadata', function() {
video.currentTime = Number(Math.floor(Math.min(video.duration/10, 5)));
video.addEventListener('loadeddata', function() {
var D = getResizedDimensions(video, 'video');
Thumb.fromCanvas(video, D, cb);
});
});
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'}});

View File

@@ -1,87 +0,0 @@
define(['jquery'], function ($) {
var module = {};
module.create = function (cfg, onLocal, Cryptpad) {
var exp = {};
var parsed = exp.parsedHref = Cryptpad.parsePadUrl(window.location.href);
exp.defaultTitle = Cryptpad.getDefaultName(parsed);
exp.title = document.title; // TOOD slides
cfg = cfg || {};
var getHeadingText = cfg.getHeadingText || function () { return; };
var updateLocalTitle = function (newTitle) {
exp.title = newTitle;
onLocal();
if (typeof cfg.updateLocalTitle === "function") {
cfg.updateLocalTitle(newTitle);
} else {
document.title = newTitle;
}
};
var $title;
exp.setToolbar = function (toolbar) {
$title = toolbar && toolbar.title;
};
exp.getTitle = function () { return exp.title; };
var isDefaultTitle = exp.isDefaultTitle = function (){return exp.title === exp.defaultTitle;};
var suggestTitle = exp.suggestTitle = function (fallback) {
if (isDefaultTitle()) {
return getHeadingText() || fallback || "";
} else {
return exp.title || getHeadingText() || exp.defaultTitle;
}
};
var renameCb = function (err, newTitle) {
if (err) { return; }
updateLocalTitle(newTitle);
onLocal();
};
// update title: href is optional; if not specified, we use window.location.href
exp.updateTitle = function (newTitle, href, cb) {
cb = cb || $.noop;
if (newTitle === exp.title) { return; }
// Change the title now, and set it back to the old value if there is an error
var oldTitle = exp.title;
Cryptpad.renamePad(newTitle, href, function (err, data) {
if (err) {
console.log("Couldn't set pad title");
console.error(err);
updateLocalTitle(oldTitle);
return void cb(err);
}
updateLocalTitle(data);
cb(null, data);
if (!$title) { return; }
$title.find('span.title').text(data);
$title.find('input').val(data);
});
};
exp.updateDefaultTitle = function (newDefaultTitle) {
exp.defaultTitle = newDefaultTitle;
if (!$title) { return; }
$title.find('input').attr("placeholder", exp.defaultTitle);
};
exp.getTitleConfig = function () {
return {
onRename: renameCb,
suggestName: suggestTitle,
defaultName: exp.defaultTitle
};
};
return exp;
};
return module;
});

View File

@@ -3,42 +3,26 @@ define([
'/api/config',
'/common/cryptpad-common.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-language.js',
'/common/common-interface.js',
'/common/media-tag.js',
'/common/tippy.min.js',
'/customize/application_config.js',
'css!/common/tippy.css',
], function ($, Config, Cryptpad, Util, Language, MediaTag, Tippy, AppConfig) {
var UI = {};
], function ($, Config, Cryptpad, Util, Hash, Language, UI, MediaTag) {
var UIElements = {};
var Messages = Cryptpad.Messages;
/**
* Requirements from cryptpad-common.js
* getFileSize
* - hrefToHexChannelId
* displayAvatar
* - getFirstEmojiOrCharacter
* - parsePadUrl
* - getSecrets
* - base64ToHex
* - getBlobPathFromHex
* - bytesToMegabytes
* createUserAdminMenu
* - fixHTML
* - createDropdown
*/
UI.updateTags = function (common, href) {
UIElements.updateTags = function (common, href) {
var sframeChan = common.getSframeChannel();
sframeChan.query('Q_TAGS_GET', href || null, function (err, res) {
if (err || res.error) {
if (res.error === 'NO_ENTRY') {
Cryptpad.alert(Messages.tags_noentry);
UI.alert(Messages.tags_noentry);
}
return void console.error(err || res.error);
}
Cryptpad.dialog.tagPrompt(res.data, function (tags) {
UI.dialog.tagPrompt(res.data, function (tags) {
if (!Array.isArray(tags)) { return; }
sframeChan.event('EV_TAGS_SET', {
tags: tags,
@@ -48,7 +32,23 @@ define([
});
};
UI.createButton = function (common, type, rightside, data, callback) {
var 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);
});
};
};
UIElements.createButton = function (common, type, rightside, data, callback) {
var AppConfig = common.getAppConfig();
var button;
var size = "17px";
@@ -73,7 +73,7 @@ define([
if (callback) {
button
.click(common.prepareFeedback(type))
.click(Cryptpad.importContent('text/plain', function (content, file) {
.click(importContent('text/plain', function (content, file) {
callback(content, file);
}, {accept: data ? data.accept : undefined}));
}
@@ -93,7 +93,7 @@ define([
target: data.target
};
if (data.filter && !data.filter(file)) {
Cryptpad.log('Invalid avatar (type or size)');
UI.log('Invalid avatar (type or size)');
return;
}
data.FM.handleFile(file, ev);
@@ -142,11 +142,11 @@ define([
title: title,
toSave: toSave
}, function () {
Cryptpad.alert(Messages.templateSaved);
UI.alert(Messages.templateSaved);
common.feedback('TEMPLATE_CREATED');
});
};
Cryptpad.prompt(Messages.saveTemplatePrompt, title, todo);
UI.prompt(Messages.saveTemplatePrompt, title, todo);
});
}
break;
@@ -162,12 +162,12 @@ define([
.click(common.prepareFeedback(type))
.click(function() {
var msg = common.isLoggedIn() ? Messages.forgetPrompt : Messages.fm_removePermanentlyDialog;
Cryptpad.confirm(msg, function (yes) {
UI.confirm(msg, function (yes) {
if (!yes) { return; }
sframeChan.query('Q_MOVE_TO_TRASH', null, function (err) {
if (err) { return void callback(err); }
var cMsg = common.isLoggedIn() ? Messages.movedToTrash : Messages.deleted;
Cryptpad.alert(cMsg, undefined, true);
UI.alert(cMsg, undefined, true);
callback();
return;
});
@@ -220,7 +220,7 @@ define([
title: Messages.tags_title,
})
.click(common.prepareFeedback(type))
.click(function () { UI.updateTags(common, null); });
.click(function () { UIElements.updateTags(common, null); });
break;
default:
button = $('<button>', {
@@ -236,7 +236,7 @@ define([
};
// Avatars
UI.displayMediatagImage = function (Common, $tag, cb) {
UIElements.displayMediatagImage = function (Common, $tag, cb) {
if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
@@ -267,31 +267,52 @@ define([
});
MediaTag($tag[0]);
};
UI.displayAvatar = function (Common, $container, href, name, cb) {
var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/;
var isEmoji = function (str) {
return emoji_patt.test(str);
};
var emojiStringToArray = function (str) {
var split = str.split(emoji_patt);
var arr = [];
for (var i=0; i<split.length; i++) {
var char = split[i];
if (char !== "") {
arr.push(char);
}
}
return arr;
};
var getFirstEmojiOrCharacter = function (str) {
if (!str || !str.trim()) { return '?'; }
var emojis = emojiStringToArray(str);
return isEmoji(emojis[0])? emojis[0]: str[0];
};
UIElements.displayAvatar = function (Common, $container, href, name, cb) {
var displayDefault = function () {
var text = Cryptpad.getFirstEmojiOrCharacter(name);
var text = getFirstEmojiOrCharacter(name);
var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text);
$container.append($avatar);
if (cb) { cb(); }
};
if (!href) { return void displayDefault(); }
var parsed = Cryptpad.parsePadUrl(href);
var secret = Cryptpad.getSecrets('file', parsed.hash);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash);
if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var hexFileName = Cryptpad.base64ToHex(secret.channel);
var src = Cryptpad.getBlobPathFromHex(hexFileName);
var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName);
Common.getFileSize(href, function (e, data) {
if (e) {
displayDefault();
return void console.error(e);
}
if (typeof data !== "number") { return void displayDefault(); }
if (Cryptpad.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
var $img = $('<media-tag>').appendTo($container);
$img.attr('src', src);
$img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
UI.displayMediatagImage(Common, $img, function (err, $image, img) {
UIElements.displayMediatagImage(Common, $img, function (err, $image, img) {
if (err) { return void console.error(err); }
var w = img.width;
var h = img.height;
@@ -317,13 +338,13 @@ define([
update.
*/
var LIMIT_REFRESH_RATE = 30000; // milliseconds
UI.createUsageBar = function (common, cb) {
UIElements.createUsageBar = function (common, cb) {
if (!common.isLoggedIn()) { return cb("NOT_LOGGED_IN"); }
// getPinnedUsage updates common.account.usage, and other values
// so we can just use those and only check for errors
var $container = $('<span>', {'class':'cp-limit-container'});
var todo;
var updateUsage = Cryptpad.notAgainForAnother(function () {
var updateUsage = Util.notAgainForAnother(function () {
common.getPinUsage(todo);
}, LIMIT_REFRESH_RATE);
@@ -404,23 +425,174 @@ define([
cb(null, $container);
};
UI.createUserAdminMenu = function (Common, config) {
// Create a button with a dropdown menu
// input is a config object with parameters:
// - container (optional): the dropdown container (span)
// - text (optional): the button text value
// - options: array of {tag: "", attributes: {}, content: "string"}
//
// allowed options tags: ['a', 'hr', 'p']
UIElements.createDropdown = function (config) {
if (typeof config !== "object" || !Array.isArray(config.options)) { return; }
var allowedTags = ['a', 'p', 'hr'];
var isValidOption = function (o) {
if (typeof o !== "object") { return false; }
if (!o.tag || allowedTags.indexOf(o.tag) === -1) { return false; }
return true;
};
// Container
var $container = $(config.container);
var containerConfig = {
'class': 'cp-dropdown-container'
};
if (config.buttonTitle) {
containerConfig.title = config.buttonTitle;
}
if (!config.container) {
$container = $('<span>', containerConfig);
}
// Button
var $button = $('<button>', {
'class': ''
}).append($('<span>', {'class': 'cp-dropdown-button-title'}).html(config.text || ""));
/*$('<span>', {
'class': 'fa fa-caret-down',
}).appendTo($button);*/
// Menu
var $innerblock = $('<div>', {'class': 'cp-dropdown-content'});
if (config.left) { $innerblock.addClass('cp-dropdown-left'); }
config.options.forEach(function (o) {
if (!isValidOption(o)) { return; }
$('<' + o.tag + '>', o.attributes || {}).html(o.content || '').appendTo($innerblock);
});
$container.append($button).append($innerblock);
var value = config.initialValue || '';
var setActive = function ($el) {
if ($el.length !== 1) { return; }
$innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
$el.addClass('cp-dropdown-element-active');
var scroll = $el.position().top + $innerblock.scrollTop();
if (scroll < $innerblock.scrollTop()) {
$innerblock.scrollTop(scroll);
} else if (scroll > ($innerblock.scrollTop() + 280)) {
$innerblock.scrollTop(scroll-270);
}
};
var hide = function () {
window.setTimeout(function () { $innerblock.hide(); }, 0);
};
var show = function () {
$innerblock.show();
$innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
if (config.isSelect && value) {
var $val = $innerblock.find('[data-value="'+value+'"]');
setActive($val);
$innerblock.scrollTop($val.position().top + $innerblock.scrollTop());
}
if (config.feedback) { Cryptpad.feedback(config.feedback); }
};
$container.click(function (e) {
e.stopPropagation();
var state = $innerblock.is(':visible');
$('.cp-dropdown-content').hide();
try {
$('iframe').each(function (idx, ifrw) {
$(ifrw).contents().find('.cp-dropdown-content').hide();
});
} catch (er) {
// empty try catch in case this iframe is problematic (cross-origin)
}
if (state) {
hide();
return;
}
show();
});
if (config.isSelect) {
var pressed = '';
var to;
$container.keydown(function (e) {
var $value = $innerblock.find('[data-value].cp-dropdown-element-active');
if (e.which === 38) { // Up
if ($value.length) {
var $prev = $value.prev();
setActive($prev);
}
}
if (e.which === 40) { // Down
if ($value.length) {
var $next = $value.next();
setActive($next);
}
}
if (e.which === 13) { //Enter
if ($value.length) {
$value.click();
hide();
}
}
if (e.which === 27) { // Esc
hide();
}
});
$container.keypress(function (e) {
window.clearTimeout(to);
var c = String.fromCharCode(e.which);
pressed += c;
var $value = $innerblock.find('[data-value^="'+pressed+'"]:first');
if ($value.length) {
setActive($value);
$innerblock.scrollTop($value.position().top + $innerblock.scrollTop());
}
to = window.setTimeout(function () {
pressed = '';
}, 1000);
});
$container.setValue = function (val, name) {
value = val;
var $val = $innerblock.find('[data-value="'+val+'"]');
var textValue = name || $val.html() || val;
$button.find('.cp-dropdown-button-title').html(textValue);
};
$container.getValue = function () {
return value || '';
};
}
return $container;
};
UIElements.createUserAdminMenu = function (Common, config) {
var metadataMgr = Common.getMetadataMgr();
var displayNameCls = config.displayNameCls || 'displayName';
var displayNameCls = config.displayNameCls || 'cp-toolbar-user-name';
var $displayedName = $('<span>', {'class': displayNameCls});
var accountName = metadataMgr.getPrivateData().accountName;
var origin = metadataMgr.getPrivateData().origin;
var padType = metadataMgr.getMetadata().type;
var $userName = $('<span>', {'class': 'userDisplayName'});
var $userName = $('<span>');
var options = [];
if (config.displayNameCls) {
var $userAdminContent = $('<p>');
if (accountName) {
var $userAccount = $('<span>', {'class': 'userAccount'}).append(Messages.user_accountName + ': ' + Cryptpad.fixHTML(accountName));
$userAdminContent.append($userAccount);
var $userAccount = $('<span>').append(Messages.user_accountName + ': ');
$userAdminContent.append($userAccount).append(Util.fixHTML(accountName));
$userAdminContent.append($('<br>'));
}
if (config.displayName) {
@@ -456,14 +628,14 @@ define([
if (accountName) {
options.push({
tag: 'a',
attributes: {'class': 'profile'},
attributes: {'class': 'cp-toolbar-menu-profile'},
content: Messages.profileButton
});
}
if (padType !== 'settings') {
options.push({
tag: 'a',
attributes: {'class': 'settings'},
attributes: {'class': 'cp-toolbar-menu-settings'},
content: Messages.settingsButton
});
}
@@ -471,18 +643,18 @@ define([
if (accountName) {
options.push({
tag: 'a',
attributes: {'class': 'logout'},
attributes: {'class': 'cp-toolbar-menu-logout'},
content: Messages.logoutButton
});
} else {
options.push({
tag: 'a',
attributes: {'class': 'login'},
attributes: {'class': 'cp-toolbar-menu-login'},
content: Messages.login_login
});
options.push({
tag: 'a',
attributes: {'class': 'register'},
attributes: {'class': 'cp-toolbar-menu-register'},
content: Messages.login_register
});
}
@@ -505,11 +677,18 @@ define([
container: config.$initBlock, // optional
feedback: "USER_ADMIN",
};
var $userAdmin = Cryptpad.createDropdown(dropdownConfigUser);
var $userAdmin = UIElements.createDropdown(dropdownConfigUser);
/*
// Uncomment these lines to have a language selector in the admin menu
// FIXME clicking on the inner menu hides the outer one
var $lang = UIElements.createLanguageSelector(Common);
$userAdmin.find('.cp-dropdown-content').append($lang);
*/
var $displayName = $userAdmin.find('.'+displayNameCls);
var $avatar = $userAdmin.find('.cp-dropdown-button-title');
var $avatar = $userAdmin.find('> button .cp-dropdown-button-title');
var loadingAvatar;
var to;
var oldUrl = '';
@@ -528,10 +707,11 @@ define([
$displayName.text(newName || Messages.anonymous);
if (accountName && oldUrl !== url) {
$avatar.html('');
UI.displayAvatar(Common, $avatar, url, newName || Messages.anonymous, function ($img) {
UIElements.displayAvatar(Common, $avatar, url,
newName || Messages.anonymous, function ($img) {
oldUrl = url;
if ($img) {
$userAdmin.find('button').addClass('cp-avatar');
$userAdmin.find('> button').addClass('cp-avatar');
}
loadingAvatar = false;
});
@@ -542,31 +722,31 @@ define([
metadataMgr.onChange(updateButton);
updateButton();
$userAdmin.find('a.logout').click(function () {
$userAdmin.find('a.cp-toolbar-menu-logout').click(function () {
Common.logout(function () {
window.parent.location = origin+'/';
});
});
$userAdmin.find('a.settings').click(function () {
$userAdmin.find('a.cp-toolbar-menu-settings').click(function () {
if (padType) {
window.open(origin+'/settings/');
} else {
window.parent.location = origin+'/settings/';
}
});
$userAdmin.find('a.profile').click(function () {
$userAdmin.find('a.cp-toolbar-menu-profile').click(function () {
if (padType) {
window.open(origin+'/profile/');
} else {
window.parent.location = origin+'/profile/';
}
});
$userAdmin.find('a.login').click(function () {
$userAdmin.find('a.cp-toolbar-menu-login').click(function () {
Common.setLoginRedirect(function () {
window.parent.location = origin+'/login/';
});
});
$userAdmin.find('a.register').click(function () {
$userAdmin.find('a.cp-toolbar-menu-register').click(function () {
Common.setLoginRedirect(function () {
window.parent.location = origin+'/register/';
});
@@ -577,7 +757,7 @@ define([
// Provide $container if you want to put the generated block in another element
// Provide $initBlock if you already have the menu block and you want the content inserted in it
UI.createLanguageSelector = function (common, $container, $initBlock) {
UIElements.createLanguageSelector = function (common, $container, $initBlock) {
var options = [];
var languages = Messages._languages;
var keys = Object.keys(languages).sort();
@@ -599,7 +779,7 @@ define([
container: $initBlock, // optional
isSelect: true
};
var $block = Cryptpad.createDropdown(dropdownConfig);
var $block = UIElements.createDropdown(dropdownConfig);
$block.attr('id', 'cp-language-selector');
if ($container) {
@@ -611,20 +791,51 @@ define([
return $block;
};
UIElements.createModal = function (cfg) {
var $body = cfg.$body || $('body');
var $blockContainer = $body.find('#'+cfg.id);
if (!$blockContainer.length) {
$blockContainer = $('<div>', {
'class': 'cp-modal-container',
'id': cfg.id
});
}
var hide = function () {
if (cfg.onClose) { return void cfg.onClose(); }
$blockContainer.hide();
};
$blockContainer.html('').appendTo($body);
var $block = $('<div>', {'class': 'cp-modal'}).appendTo($blockContainer);
$('<span>', {
'class': 'cp-modal-close fa fa-times',
'title': Messages.filePicker_close
}).click(hide).appendTo($block);
$body.click(hide);
$block.click(function (e) {
e.stopPropagation();
});
$body.keydown(function (e) {
if (e.which === 27) {
hide();
}
});
return $blockContainer;
};
UI.initFilePicker = function (common, cfg) {
UIElements.initFilePicker = function (common, cfg) {
var onSelect = cfg.onSelect || $.noop;
var sframeChan = common.getSframeChannel();
sframeChan.on("EV_FILE_PICKED", function (data) {
onSelect(data);
});
};
UI.openFilePicker = function (common, types) {
UIElements.openFilePicker = function (common, types) {
var sframeChan = common.getSframeChannel();
sframeChan.event("EV_FILE_PICKER_OPEN", types);
};
UI.openTemplatePicker = function (common) {
UIElements.openTemplatePicker = function (common) {
var metadataMgr = common.getMetadataMgr();
var type = metadataMgr.getMetadataLazy().type;
var sframeChan = common.getSframeChannel();
@@ -646,10 +857,10 @@ define([
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === type && first) {
Cryptpad.addLoadingScreen({hideTips: true});
UI.addLoadingScreen({hideTips: true});
sframeChan.query('Q_TEMPLATE_USE', data.href, function () {
first = false;
Cryptpad.removeLoadingScreen();
UI.removeLoadingScreen();
common.feedback('TEMPLATE_USED');
});
if (focus) { focus.focus(); }
@@ -664,7 +875,7 @@ define([
if (data) {
common.openFilePicker(pickerCfg);
focus = document.activeElement;
Cryptpad.confirm(Messages.useTemplate, onConfirm, {
UI.confirm(Messages.useTemplate, onConfirm, {
ok: Messages.useTemplateOK,
cancel: Messages.useTemplateCancel,
});
@@ -672,58 +883,5 @@ define([
});
};
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
var addTippy = function (i, el) {
if (el.nodeName === 'IFRAME') { return; }
Tippy(el, {
position: 'bottom',
distance: 0,
performance: true,
dynamicTitle: true,
delay: [delay, 0]
});
};
var clearTooltips = function () {
$('.tippy-popper').each(function (i, el) {
if ($('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
el.remove();
}
});
};
// This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes.
setInterval(clearTooltips, delay);
var checkRemoved = function (x) {
var out = false;
$(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) {
for (var i = 0; i < mutation.addedNodes.length; i++) {
$(mutation.addedNodes[i]).find('[title]').each(addTippy);
}
for (var j = 0; j < mutation.removedNodes.length; j++) {
removed |= checkRemoved(mutation.removedNodes[j]);
}
});
if (removed) { clearTooltips(); }
});
observer.observe($('body')[0], {
attributes: false,
childList: true,
characterData: false,
subtree: true
});
};
return UI;
return UIElements;
});

View File

@@ -1,117 +0,0 @@
define(['json.sortify'], function (Sortify) {
var module = {};
module.create = function (info, onLocal, Cryptget, Cryptpad) {
var exp = {};
var userData = exp.userData = {};
var userList = exp.userList = info.userList;
var myData = exp.myData = {};
exp.myUserName = info.myID;
exp.myNetfluxId = info.myID;
var network = Cryptpad.getNetwork();
var parsed = Cryptpad.parsePadUrl(window.location.href);
var appType = parsed ? parsed.type : undefined;
var oldUserData = {};
var addToUserData = exp.addToUserData = function(data) {
var users = userList.users;
for (var attrname in data) { userData[attrname] = data[attrname]; }
if (users && users.length) {
for (var userKey in userData) {
if (users.indexOf(userKey) === -1) {
delete userData[userKey];
}
}
}
if(userList && typeof userList.onChange === "function") {
// Make sure we don't update the userlist everytime someone makes a change to the pad
if (Sortify(oldUserData) === Sortify(userData)) { return; }
oldUserData = JSON.parse(JSON.stringify(userData));
userList.onChange(userData);
}
};
exp.getToolbarConfig = function () {
return {
data: userData,
list: userList,
userNetfluxId: exp.myNetfluxId
};
};
var setName = exp.setName = function (newName, cb) {
if (typeof(newName) !== 'string') { return; }
var myUserNameTemp = newName.trim();
if(myUserNameTemp.length > 32) {
myUserNameTemp = myUserNameTemp.substr(0, 32);
}
exp.myUserName = myUserNameTemp;
myData = {};
myData[exp.myNetfluxId] = {
name: exp.myUserName,
uid: Cryptpad.getUid(),
avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl(),
curvePublic: Cryptpad.getProxy().curvePublic
};
addToUserData(myData);
/*Cryptpad.setAttribute('username', exp.myUserName, function (err) {
if (err) {
console.log("Couldn't set username");
console.error(err);
return;
}
if (typeof cb === "function") { cb(); }
});*/
if (typeof cb === "function") { cb(); }
};
exp.getLastName = function ($changeNameButton, isNew) {
Cryptpad.getLastName(function (err, lastName) {
if (err) {
console.log("Could not get previous name");
console.error(err);
return;
}
// Update the toolbar list:
// Add the current user in the metadata
if (typeof(lastName) === 'string') {
setName(lastName, onLocal);
} else {
myData[exp.myNetfluxId] = {
name: "",
uid: Cryptpad.getUid(),
avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl(),
curvePublic: Cryptpad.getProxy().curvePublic
};
addToUserData(myData);
onLocal();
$changeNameButton.click();
}
if (isNew && appType) {
Cryptpad.selectTemplate(appType, info.realtime, Cryptget);
}
});
};
Cryptpad.onDisplayNameChanged(function (newName) {
setName(newName, onLocal);
});
network.on('reconnect', function (uid) {
exp.myNetfluxId = uid;
exp.setName(exp.myUserName);
});
return exp;
};
return module;
});

View File

@@ -1,112 +0,0 @@
define(function () {
var module = {};
module.create = function (info, onLocal, Cryptget, Cryptpad) {
var exp = {};
var userData = exp.userData = {};
var userList = exp.userList = info.userList;
var myData = exp.myData = {};
exp.myUserName = info.myID;
exp.myNetfluxId = info.myID;
var network = Cryptpad.getNetwork();
var parsed = Cryptpad.parsePadUrl(window.location.href);
var appType = parsed ? parsed.type : undefined;
var addToUserData = exp.addToUserData = function(data) {
var users = userList.users;
for (var attrname in data) { userData[attrname] = data[attrname]; }
if (users && users.length) {
for (var userKey in userData) {
if (users.indexOf(userKey) === -1) {
delete userData[userKey];
}
}
}
if(userList && typeof userList.onChange === "function") {
userList.onChange(userData);
}
};
exp.getToolbarConfig = function () {
return {
data: userData,
list: userList,
userNetfluxId: exp.myNetfluxId
};
};
var setName = exp.setName = function (newName, cb) {
if (typeof(newName) !== 'string') { return; }
var myUserNameTemp = newName.trim();
if(myUserNameTemp.length > 32) {
myUserNameTemp = myUserNameTemp.substr(0, 32);
}
exp.myUserName = myUserNameTemp;
myData = {};
myData[exp.myNetfluxId] = {
name: exp.myUserName,
uid: Cryptpad.getUid(),
avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl(),
curvePublic: Cryptpad.getProxy().curvePublic
};
addToUserData(myData);
/*Cryptpad.setAttribute('username', exp.myUserName, function (err) {
if (err) {
console.log("Couldn't set username");
console.error(err);
return;
}
if (typeof cb === "function") { cb(); }
});*/
if (typeof cb === "function") { cb(); }
};
exp.getLastName = function ($changeNameButton, isNew) {
Cryptpad.getLastName(function (err, lastName) {
if (err) {
console.log("Could not get previous name");
console.error(err);
return;
}
// Update the toolbar list:
// Add the current user in the metadata
if (typeof(lastName) === 'string') {
setName(lastName, onLocal);
} else {
myData[exp.myNetfluxId] = {
name: "",
uid: Cryptpad.getUid(),
avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl(),
curvePublic: Cryptpad.getProxy().curvePublic
};
addToUserData(myData);
onLocal();
$changeNameButton.click();
}
if (isNew && appType) {
Cryptpad.selectTemplate(appType, info.realtime, Cryptget);
}
});
};
Cryptpad.onDisplayNameChanged(function (newName) {
setName(newName, onLocal);
});
network.on('reconnect', function (uid) {
exp.myNetfluxId = uid;
exp.setName(exp.myUserName);
});
return exp;
};
return module;
});

View File

@@ -89,22 +89,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
*/
@@ -186,13 +170,6 @@ 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;

View File

@@ -1,19 +1,19 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/common/cryptpad-common.js',
], function ($, Crypto, Realtime, Cryptpad) {
//var Messages = Cryptpad.Messages;
//var noop = function () {};
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
//'/bower_components/textpatcher/TextPatcher.js'
], function (Crypto, CPNetflux, /* Cryptpad,*/ Util, Hash, Realtime) {
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();
@@ -22,7 +22,7 @@ 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(),
@@ -57,7 +57,7 @@ define([
};
overwrite(config, opt);
Session.realtime = Realtime.start(config);
Session.realtime = CPNetflux.start(config);
};
var put = function (hash, doc, cb, opt) {
@@ -77,7 +77,7 @@ define([
cb(new Error("Timeout"));
}, 5000);
Cryptpad.whenRealtimeSyncs(realtime, function () {
Realtime.whenRealtimeSyncs(realtime, function () {
window.clearTimeout(to);
realtime.abort();
finish(Session, void 0);
@@ -85,7 +85,7 @@ define([
};
overwrite(config, opt);
Session.session = Realtime.start(config);
Session.session = CPNetflux.start(config);
};
return {

File diff suppressed because it is too large Load Diff

View File

@@ -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;
@@ -40,8 +41,8 @@ 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]) {

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,12 @@ define([
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
'/common/userObject.js',
'/common/common-interface.js',
'/common/common-hash.js',
'/common/common-constants.js',
'/common/migrate-user-object.js',
], function ($, Listmap, Crypto, FO, Migrate) {
'/bower_components/chainpad/chainpad.dist.js',
], function ($, Listmap, Crypto, /* TextPatcher, */ FO, UI, Hash, Constants, Migrate, ChainPad) {
/*
This module uses localStorage, which is synchronous, but exposes an
asyncronous API. This is so that we can substitute other storage
@@ -188,7 +192,7 @@ define([
var onReady = function (f, proxy, Cryptpad, exp) {
var fo = exp.fo = FO.init(proxy.drive, {
Cryptpad: Cryptpad,
rt: exp.realtime
loggedIn: Cryptpad.isLoggedIn()
});
var todo = function () {
fo.fixFiles();
@@ -246,7 +250,7 @@ define([
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();
proxy.uid = Hash.createChannelId();
}
// if the user is logged in, but does not have signing keys...
@@ -255,17 +259,17 @@ define([
return void requestLogin();
}
proxy.on('change', [Cryptpad.displayNameKey], function (o, n) {
proxy.on('change', [Constants.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]);
Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]);
});
proxy.on('change', ['friends'], function () {
// Trigger userlist update when the avatar has changed
Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]);
});
proxy.on('change', [tokenKey], function () {
var localToken = tryParsing(localStorage.getItem(tokenKey));
@@ -283,11 +287,11 @@ define([
if (!Cryptpad || initialized) { return; }
initialized = true;
var hash = Cryptpad.getUserHash() || localStorage.FS_hash || Cryptpad.createRandomHash();
var hash = Cryptpad.getUserHash() || localStorage.FS_hash || Hash.createRandomHash();
if (!hash) {
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
}
var secret = Cryptpad.getSecrets('drive', hash);
var secret = Hash.getSecrets('drive', hash);
var listmapConfig = {
data: {},
websocketURL: Cryptpad.getWebsocketURL(),
@@ -297,31 +301,11 @@ define([
crypto: Crypto.createEncryptor(secret.keys),
userName: 'fs',
logLevel: 1,
ChainPad: ChainPad,
};
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({hideTips: 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;
@@ -329,38 +313,29 @@ define([
rt.proxy.on('create', function (info) {
exp.info = info;
if (!Cryptpad.getUserHash()) {
localStorage.FS_hash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
localStorage.FS_hash = Hash.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]))
if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey]))
&& !drive['filesData']) {
drive[Cryptpad.oldStorageKey] = [];
drive[Constants.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);
UI.alert(Cryptpad.Messages.fs_migration, null, true);
}
});
};

View File

@@ -3,10 +3,11 @@ define([
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad-crypto/crypto.js',
'/common/cryptpad-common.js',
'/common/common-util.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) {
], function ($, Listmap, Crypto, Cryptpad, Util, Cred) {
var Exports = {
Cred: Cred,
};
@@ -43,12 +44,12 @@ define([
keys.editKeyStr = keys.editKeyStr.replace(/\//g, '-');
// 32 bytes of hex
var channelHex = opt.channelHex = Cryptpad.uint8ArrayToHex(channelSeed);
var channelHex = opt.channelHex = Util.uint8ArrayToHex(channelSeed);
// should never happen
if (channelHex.length !== 32) { throw new Error('invalid channel id'); }
opt.channel64 = Cryptpad.hexToBase64(channelHex);
opt.channel64 = Util.hexToBase64(channelHex);
opt.userHash = '/1/edit/' + [opt.channel64, opt.keys.editKeyStr].join('/');

View File

@@ -2,7 +2,8 @@ define([
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/common/userObject.js',
], function (Cryptpad, Crypt, FO) {
'/common/common-hash.js',
], function (Cryptpad, Crypt, FO, Hash) {
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]);
@@ -104,7 +105,7 @@ define([
if (parsed) {
var proxy = proxyData.proxy;
var oldFo = FO.init(parsed.drive, {
Cryptpad: Cryptpad
loggedIn: Cryptpad.isLoggedIn()
});
var onMigrated = function () {
oldFo.fixFiles();
@@ -120,10 +121,10 @@ define([
// Do not migrate a pad if we already have it, it would create a duplicate in the drive
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) {

View File

@@ -80,5 +80,27 @@ define([], function () {
Cryptpad.feedback('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();
Cryptpad.feedback('Migrate-5', true);
userObject.version = version = 5;
}
};
});

View File

@@ -0,0 +1,93 @@
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.rpc.send.unauthenticated('UPLOAD', 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.initialPath = path;
common.renamePad(title || "", href, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
});
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
next(again);
});
});
}
next(again);
});
};
return module;
});

View File

@@ -3,15 +3,16 @@ define([
'/bower_components/hyperjson/hyperjson.js',
'/common/toolbar3.js',
'json.sortify',
'/common/cryptpad-common.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/customize/messages.js',
'/common/common-util.js',
'/common/common-interface.js',
'/common/common-thumbnail.js',
'/customize/application_config.js',
'/bower_components/chainpad/chainpad.dist.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
@@ -20,11 +21,11 @@ define([
Hyperjson,
Toolbar,
JSONSortify,
Cryptpad,
nThen,
SFCommon,
Messages,
Util,
UI,
Thumb,
AppConfig,
ChainPad)
@@ -45,10 +46,6 @@ define([
var badStateTimeout = typeof(AppConfig.badStateTimeout) === 'number' ?
AppConfig.badStateTimeout : 30000;
var onConnectError = function () {
Cryptpad.errorLoadingScreen(Messages.websocketError);
};
var create = function (options, cb) {
var evContentUpdate = Util.mkEvent();
var evEditableStateChange = Util.mkEvent();
@@ -148,7 +145,7 @@ define([
evContentUpdate.fire(newContent);
} catch (e) {
console.log(e.stack);
Cryptpad.errorLoadingScreen(e.message);
UI.errorLoadingScreen(e.message);
}
};
@@ -254,7 +251,7 @@ define([
newContent = normalize(newContent);
contentUpdate(newContent);
} else {
title.updateTitle(Cryptpad.initialName || title.defaultTitle);
title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire();
}
stateChange(STATE.READY);
@@ -262,7 +259,7 @@ define([
if (!readOnly) { onLocal(); }
evOnReady.fire(newPad);
Cryptpad.removeLoadingScreen(emitResize);
UI.removeLoadingScreen(emitResize);
var privateDat = cpNfInner.metadataMgr.getPrivateData();
if (options.thumbnail && privateDat.thumbnails) {
@@ -285,9 +282,9 @@ define([
var onConnectionChange = function (info) {
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED);
if (info.state) {
Cryptpad.findOKButton().click();
UI.findOKButton().click();
} else {
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
UI.alert(Messages.common_connectionLost, undefined, true);
}
};
@@ -295,8 +292,8 @@ define([
var $export = common.createButton('export', true, {}, function () {
var ext = (typeof(extension) === 'function') ? extension() : extension;
var suggestion = title.suggestTitle('cryptpad-document');
Cryptpad.prompt(Messages.exportPrompt,
Cryptpad.fixFileName(suggestion) + '.' + ext, function (filename)
UI.prompt(Messages.exportPrompt,
Util.fixFileName(suggestion) + '.' + ext, function (filename)
{
if (!(typeof(filename) === 'string' && filename)) { return; }
if (async) {
@@ -371,7 +368,7 @@ define([
};
nThen(function (waitFor) {
Cryptpad.addLoadingScreen();
UI.addLoadingScreen();
SFCommon.create(waitFor(function (c) { common = c; }));
}).nThen(function (waitFor) {
cpNfInner = common.startRealtime({
@@ -422,25 +419,19 @@ define([
if (infiniteSpinnerModal) { return; }
infiniteSpinnerModal = true;
stateChange(STATE.INFINITE_SPINNER);
Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) {
UI.confirm(Messages.realtime_unrecoverableError, function (yes) {
if (!yes) { return; }
common.gotoURL();
});
cpNfInner.chainpad.onSettle(function () {
infiniteSpinnerModal = false;
Cryptpad.findCancelButton().click();
UI.findCancelButton().click();
stateChange(STATE.READY);
onRemote();
});
}, 2000);
//Cryptpad.onLogout(function () { ... });
Cryptpad.onError(function (info) {
if (info && info.type === "store") {
onConnectError();
}
});
//common.onLogout(function () { ... });
}).nThen(function (waitFor) {
if (readOnly) { $('body').addClass('cp-readonly'); }
@@ -472,7 +463,6 @@ define([
metadataMgr: cpNfInner.metadataMgr,
readOnly: readOnly,
realtime: cpNfInner.chainpad,
common: Cryptpad,
sfCommon: common,
$container: $(toolbarContainer),
$contentContainer: $(contentContainer)

View File

@@ -2,10 +2,13 @@ define([
'jquery',
'/common/modes.js',
'/common/themes.js',
'/common/cryptpad-common.js',
'/customize/messages.js',
'/common/common-ui-elements.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/text-cursor.js',
'/bower_components/chainpad/chainpad.dist.js'
], function ($, Modes, Themes, Cryptpad, TextCursor, ChainPad) {
'/bower_components/chainpad/chainpad.dist.js',
], function ($, Modes, Themes, Messages, UIElements, Hash, Util, TextCursor, ChainPad) {
var module = {};
var cursorToPos = function(cursor, oldText) {
@@ -102,7 +105,6 @@ define([
module.create = function (Common, defaultMode, CMeditor) {
var exp = {};
var Messages = Cryptpad.Messages;
var CodeMirror = exp.CodeMirror = CMeditor;
CodeMirror.modeURL = "cm/mode/%N/%N";
@@ -212,7 +214,7 @@ define([
isSelect: true,
feedback: 'CODE_LANGUAGE',
};
var $block = exp.$language = Cryptpad.createDropdown(dropdownConfig);
var $block = exp.$language = UIElements.createDropdown(dropdownConfig);
$block.find('button').attr('title', Messages.languageButtonTitle);
$block.find('a').click(function () {
setMode($(this).attr('data-value'), onModeChanged);
@@ -249,7 +251,7 @@ define([
initialValue: lastTheme,
feedback: 'CODE_THEME',
};
var $block = exp.$theme = Cryptpad.createDropdown(dropdownConfig);
var $block = exp.$theme = UIElements.createDropdown(dropdownConfig);
$block.find('button').attr('title', Messages.themeButtonTitle);
setTheme(lastTheme, $block);
@@ -325,8 +327,8 @@ define([
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
var parsed = Cryptpad.parsePadUrl(data.url);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var 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>';

View File

@@ -2,8 +2,12 @@ define([
'jquery',
'/file/file-crypto.js',
'/common/common-thumbnail.js',
'/common/common-interface.js',
'/common/common-util.js',
'/customize/messages.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto, Thumb) {
], function ($, FileCrypto, Thumb, UI, Util, Messages) {
var Nacl = window.nacl;
var module = {};
@@ -15,15 +19,6 @@ define([
reader.readAsArrayBuffer(blob);
};
var arrayBufferToString = function (AB) {
try {
return Nacl.util.encodeBase64(new Uint8Array(AB));
} catch (e) {
console.error(e);
return null;
}
};
module.uploadFile = function (common, data, cb) {
var sframeChan = common.getSframeChannel();
sframeChan.query('Q_UPLOAD_FILE', data, cb);
@@ -31,9 +26,6 @@ define([
module.create = function (common, config) {
var File = {};
var Cryptpad = common.getCryptpadCommon();
var Messages = Cryptpad.Messages;
var queue = File.queue = {
queue: [],
@@ -94,7 +86,7 @@ define([
var id = file.id;
var dropEvent = file.dropEvent;
delete file.dropEvent;
if (dropEvent.path) { file.path = dropEvent.path; }
if (dropEvent && dropEvent.path) { file.path = dropEvent.path; }
if (queue.inProgress) { return; }
queue.inProgress = true;
@@ -123,7 +115,7 @@ define([
window.open(origin + $link.attr('href'), '_blank');
});
var title = metadata.name;
Cryptpad.log(Messages._getKey('upload_success', [title]));
UI.log(Messages._getKey('upload_success', [title]));
common.prepareFeedback('upload')();
if (config.onUploaded) {
@@ -140,18 +132,18 @@ define([
queue.next();
if (e === 'TOO_LARGE') {
// TODO update table to say too big?
return void Cryptpad.alert(Messages.upload_tooLarge);
return void UI.alert(Messages.upload_tooLarge);
}
if (e === 'NOT_ENOUGH_SPACE') {
// TODO update table to say not enough space?
return void Cryptpad.alert(Messages.upload_notEnoughSpace);
return void UI.alert(Messages.upload_notEnoughSpace);
}
console.error(e);
return void Cryptpad.alert(Messages.upload_serverError);
return void UI.alert(Messages.upload_serverError);
};
onPending = function (cb) {
Cryptpad.confirm(Messages.upload_uploadPending, cb);
UI.confirm(Messages.upload_uploadPending, cb);
};
file.noStore = config.noStore;
@@ -161,14 +153,14 @@ define([
console.log('Upload started...');
});
} catch (e) {
Cryptpad.alert(Messages.upload_serverError);
UI.alert(Messages.upload_serverError);
}
};
var prettySize = function (bytes) {
var kB = Cryptpad.bytesToKilobytes(bytes);
var kB = Util.bytesToKilobytes(bytes);
if (kB < 1024) { return kB + Messages.KB; }
var mB = Cryptpad.bytesToMegabytes(bytes);
var mB = Util.bytesToMegabytes(bytes);
return mB + Messages.MB;
};
@@ -220,12 +212,41 @@ define([
queue.next();
};
var handleFile = File.handleFile = function (file, e, thumbnail) {
var showNamePrompt = true;
var promptName = function (file, cb) {
var extIdx = file.name.lastIndexOf('.');
var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name;
var ext = extIdx !== -1 ? file.name.slice(extIdx) : "";
var msg = Messages._getKey('upload_rename', [
Util.fixHTML(file.name),
Util.fixHTML(ext)
]);
UI.prompt(msg, name, function (newName) {
if (newName === null) {
showNamePrompt = false;
return void cb (file.name);
}
if (!newName || !newName.trim()) { return void cb (file.name); }
var newExtIdx = newName.lastIndexOf('.');
var newExt = newExtIdx !== -1 ? newName.slice(newExtIdx) : "";
if (newExt !== ext) { newName += ext; }
cb(newName);
}, {cancel: Messages.doNotAskAgain}, true);
};
var handleFileState = {
queue: [],
inProgress: false
};
var handleFile = File.handleFile = function (file, e) {
//if (handleFileState.inProgress) { return void handleFileState.queue.push(file); }
handleFileState.inProgress = true;
var thumb;
var file_arraybuffer;
var name = file.name;
var finish = function () {
var metadata = {
name: file.name,
name: name,
type: file.type,
};
if (thumb) { metadata.thumbnail = thumb; }
@@ -234,33 +255,34 @@ define([
metadata: metadata,
dropEvent: e
});
handleFileState.inProgress = false;
if (handleFileState.queue.length) { handleFile(handleFileState.queue.shift()); }
};
var getName = function () {
if (!showNamePrompt) { return void finish(); }
promptName(file, function (newName) {
name = newName;
finish();
});
};
blobToArrayBuffer(file, function (e, buffer) {
if (e) { console.error(e); }
file_arraybuffer = buffer;
if (thumbnail) { // there is already a thumbnail
return blobToArrayBuffer(thumbnail, function (e, buffer) {
if (e) { console.error(e); }
thumb = arrayBufferToString(buffer);
finish();
});
}
if (!Thumb.isSupportedType(file.type)) { return finish(); }
if (!Thumb.isSupportedType(file.type)) { return getName(); }
// make a resized thumbnail from the image..
Thumb.fromBlob(file, function (e, thumb64) {
if (e) { console.error(e); }
if (!thumb64) { return finish(); }
if (!thumb64) { return getName(); }
thumb = thumb64;
finish();
getName();
});
});
};
var onFileDrop = File.onFileDrop = function (file, e) {
if (!common.isLoggedIn()) {
return Cryptpad.alert(common.Messages.upload_mustLogin);
return UI.alert(common.Messages.upload_mustLogin);
}
Array.prototype.slice.call(file).forEach(function (d) {

View File

@@ -1,7 +1,11 @@
define([
'jquery',
'/common/common-interface.js',
//'/bower_components/chainpad-json-validator/json-ot.js',
'/bower_components/chainpad/chainpad.dist.js',
], function ($, ChainPad) {
], function ($, UI, JsonOT) {
var ChainPad = window.ChainPad;
var History = {};
var getStates = function (rt) {
@@ -29,8 +33,10 @@ define([
}
},
initialState: '',
patchTransformer: ChainPad.NaiveJSONTransformer,
logLevel: 0,
//patchTransformer: ChainPad.NaiveJSONTransformer,
//logLevel: 0,
//transformFunction: JsonOT.validate,
logLevel: config.debug ? 1 : 0,
noPrune: true
});
};
@@ -81,7 +87,6 @@ define([
var onReady = function () { };
var Messages = common.Messages;
var Cryptpad = common.getCryptpadCommon();
var realtime;
@@ -98,7 +103,7 @@ define([
$right.hide();
$cke.hide();
Cryptpad.spinner($hist).get().show();
UI.spinner($hist).get().show();
var onUpdate;
@@ -121,6 +126,15 @@ define([
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous').css('visibility', '');
if (c === states.length - 1) { $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden'); }
if (c === 0) { $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden'); }
if (config.debug) {
console.log(states[i]);
var ops = states[i] && states[i].getPatch() && states[i].getPatch().operations;
if (Array.isArray(ops)) {
ops.forEach(function (op) { console.log(op); });
}
}
return val || '';
};
@@ -201,11 +215,11 @@ define([
onClose();
});
$rev.click(function () {
Cryptpad.confirm(Messages.history_restorePrompt, function (yes) {
UI.confirm(Messages.history_restorePrompt, function (yes) {
if (!yes) { return; }
close();
onRevert();
Cryptpad.log(Messages.history_restoreDone);
UI.log(Messages.history_restoreDone);
});
});

View File

@@ -15,9 +15,13 @@ define([
var Cryptpad;
var Crypto;
var Cryptget;
var SFrameChannel;
var sframeChan;
var FilePicker;
var Messenger;
var Messaging;
var Notifier;
var Utils = {};
nThen(function (waitFor) {
// Load #2, the loading screen is up so grab whatever you need...
@@ -29,14 +33,25 @@ define([
'/common/sframe-channel.js',
'/filepicker/main.js',
'/common/common-messenger.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, SFrameChannel,
_FilePicker, _Messenger) {
'/common/common-messaging.js',
'/common/common-notifier.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-realtime.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_FilePicker, _Messenger, _Messaging, _Notifier, _Hash, _Util, _Realtime) {
CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad;
Crypto = _Crypto;
Cryptget = _Cryptget;
SFrameChannel = _SFrameChannel;
FilePicker = _FilePicker;
Messenger = _Messenger;
Messaging = _Messaging;
Notifier = _Notifier;
Utils.Hash = _Hash;
Utils.Util = _Util;
Utils.Realtime = _Realtime;
if (localStorage.CRYPTPAD_URLARGS !== ApiConfig.requireConf.urlArgs) {
console.log("New version, flushing cache");
@@ -82,18 +97,19 @@ define([
});
});
secret = cfg.getSecrets ? cfg.getSecrets(Cryptpad) : Cryptpad.getSecrets();
secret = cfg.getSecrets ? cfg.getSecrets(Cryptpad, Utils) : Utils.Hash.getSecrets();
if (!secret.channel) {
// New pad: create a new random channel id
secret.channel = Cryptpad.createChannelId();
secret.channel = Utils.Hash.createChannelId();
}
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
}).nThen(function () {
var readOnly = secret.keys && !secret.keys.editKeyStr;
if (!secret.keys) { secret.keys = secret.key; }
var parsed = Cryptpad.parsePadUrl(window.location.href);
var parsed = Utils.Hash.parsePadUrl(window.location.href);
if (!parsed.type) { throw new Error(); }
var defaultTitle = Cryptpad.getDefaultName(parsed);
var defaultTitle = Utils.Hash.getDefaultName(parsed);
var proxy = Cryptpad.getProxy();
var updateMeta = function () {
//console.log('EV_METADATA_UPDATE');
@@ -148,19 +164,44 @@ define([
sframeChan.onReg('EV_METADATA_UPDATE', updateMeta);
proxy.on('change', 'settings', updateMeta);
Cryptpad.onError(function (info) {
console.log('error');
console.log(info);
if (info && info.type === "store") {
//onConnectError();
}
Cryptpad.onLogout(function () {
sframeChan.event('EV_LOGOUT');
});
sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
Cryptpad.anonRpcMsg(data.msg, data.content, function (err, response) {
cb({error: err, response: response});
// Put in the following function the RPC queries that should also work in filepicker
var addCommonRpc = function (sframeChan) {
sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
Cryptpad.anonRpcMsg(data.msg, data.content, function (err, response) {
cb({error: err, response: response});
});
});
});
sframeChan.on('Q_GET_PIN_LIMIT_STATUS', function (data, cb) {
Cryptpad.isOverPinLimit(function (e, overLimit, limits) {
cb({
error: e,
overLimit: overLimit,
limits: limits
});
});
});
sframeChan.on('Q_THUMBNAIL_GET', function (data, cb) {
Cryptpad.getThumbnail(data.key, function (e, data) {
cb({
error: e,
data: data
});
});
});
sframeChan.on('Q_THUMBNAIL_SET', function (data, cb) {
Cryptpad.setThumbnail(data.key, data.value, function (e) {
cb({error:e});
});
});
};
addCommonRpc(sframeChan);
var currentTitle;
var currentTabTitle;
@@ -176,7 +217,7 @@ define([
currentTitle = newTitle;
setDocumentTitle();
Cryptpad.renamePad(newTitle, undefined, function (err) {
if (err) { cb('ERROR'); } else { cb(); }
cb(err);
});
});
sframeChan.on('EV_SET_TAB_TITLE', function (newTabTitle) {
@@ -203,7 +244,7 @@ define([
});
sframeChan.on('EV_NOTIFY', function () {
Cryptpad.notify();
Notifier.notify();
});
sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) {
@@ -211,16 +252,6 @@ define([
cb();
});
sframeChan.on('Q_GET_PIN_LIMIT_STATUS', function (data, cb) {
Cryptpad.isOverPinLimit(function (e, overLimit, limits) {
cb({
error: e,
overLimit: overLimit,
limits: limits
});
});
});
sframeChan.on('Q_MOVE_TO_TRASH', function (data, cb) {
cb = cb || $.noop;
if (readOnly && hashes.editHash) {
@@ -236,7 +267,7 @@ define([
});
sframeChan.on('Q_SEND_FRIEND_REQUEST', function (netfluxId, cb) {
Cryptpad.inviteFromUserlist(Cryptpad, netfluxId);
Messaging.inviteFromUserlist(Cryptpad, netfluxId);
cb();
});
Cryptpad.onFriendRequest = function (confirmText, cb) {
@@ -310,20 +341,6 @@ define([
});
});
sframeChan.on('Q_THUMBNAIL_GET', function (data, cb) {
Cryptpad.getThumbnail(data.key, function (e, data) {
cb({
error: e,
data: data
});
});
});
sframeChan.on('Q_THUMBNAIL_SET', function (data, cb) {
Cryptpad.setThumbnail(data.key, data.value, function (e) {
cb({error:e});
});
});
sframeChan.on('Q_SESSIONSTORAGE_PUT', function (data, cb) {
sessionStorage[data.key] = data.value;
cb();
@@ -332,11 +349,11 @@ define([
// Present mode URL
sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) {
var parsed = Cryptpad.parsePadUrl(window.location.href);
var parsed = Utils.Hash.parsePadUrl(window.location.href);
cb(parsed.hashData && parsed.hashData.present);
});
sframeChan.on('EV_PRESENT_URL_SET_VALUE', function (data) {
var parsed = Cryptpad.parsePadUrl(window.location.href);
var parsed = Utils.Hash.parsePadUrl(window.location.href);
window.location.href = parsed.getUrl({
embed: parsed.hashData.embed,
present: data
@@ -346,35 +363,37 @@ define([
// File upload
var onFileUpload = function (sframeChan, data, cb) {
var sendEvent = function (data) {
sframeChan.event("EV_FILE_UPLOAD_STATE", data);
};
var updateProgress = function (progressValue) {
sendEvent({
progress: progressValue
});
};
var onComplete = function (href) {
sendEvent({
complete: true,
href: href
});
};
var onError = function (e) {
sendEvent({
error: e
});
};
var onPending = function (cb) {
sframeChan.query('Q_CANCEL_PENDING_FILE_UPLOAD', null, function (err, data) {
if (data) {
cb();
}
});
};
data.blob = Crypto.Nacl.util.decodeBase64(data.blob);
Cryptpad.uploadFileSecure(data, data.noStore, Cryptpad, updateProgress, onComplete, onError, onPending);
cb();
require(['/common/outer/upload.js'], function (Files) {
var sendEvent = function (data) {
sframeChan.event("EV_FILE_UPLOAD_STATE", data);
};
var updateProgress = function (progressValue) {
sendEvent({
progress: progressValue
});
};
var onComplete = function (href) {
sendEvent({
complete: true,
href: href
});
};
var onError = function (e) {
sendEvent({
error: e
});
};
var onPending = function (cb) {
sframeChan.query('Q_CANCEL_PENDING_FILE_UPLOAD', null, function (err, data) {
if (data) {
cb();
}
});
};
data.blob = Crypto.Nacl.util.decodeBase64(data.blob);
Files.upload(data, data.noStore, Cryptpad, updateProgress, onComplete, onError, onPending);
cb();
});
};
sframeChan.on('Q_UPLOAD_FILE', function (data, cb) {
onFileUpload(sframeChan, data, cb);
@@ -393,6 +412,11 @@ define([
};
config.onFileUpload = onFileUpload;
config.types = cfg;
config.addCommonRpc = addCommonRpc;
config.modules = {
Cryptpad: Cryptpad,
SFrameChannel: SFrameChannel
};
FP.$iframe = $('<iframe>', {id: 'sbox-filePicker-iframe'}).appendTo($('body'));
FP.picker = FilePicker.create(config);
} else {
@@ -458,7 +482,7 @@ define([
});
if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad);
cfg.addRpc(sframeChan, Cryptpad, Utils);
}
if (cfg.messaging) {
@@ -535,6 +559,13 @@ define([
});
});
});
sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) {
messenger.clearOwnedChannel(channel, function (e) {
cb({
error: e,
});
});
});
messenger.on('message', function (message) {
sframeChan.event('EV_CONTACTS_MESSAGE', message);
@@ -575,6 +606,18 @@ define([
if (!realtime) { return; }
var 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;
};
CpNfOuter.start({
sframeChan: sframeChan,
channel: secret.channel,
@@ -591,7 +634,7 @@ define([
return;
}
if (readOnly || cfg.noHash) { return; }
Cryptpad.replaceHash(Cryptpad.getEditHashFromKeys(wc.id, secret.keys));
replaceHash(Utils.Hash.getEditHashFromKeys(wc.id, secret.keys));
}
});
});

View File

@@ -1,7 +1,9 @@
define([
'jquery',
'/common/common-util.js'
], function ($, Util) {
'/common/common-util.js',
'/common/common-interface.js',
'/customize/messages.js'
], function ($, Util, UI, Messages) {
var module = {};
module.create = function (Common, cfg) {
@@ -54,7 +56,9 @@ define([
});
metadataMgr.onTitleChange(function (title) {
sframeChan.query('Q_SET_PAD_TITLE_IN_DRIVE', title, function (err) {
if (err) { return; }
if (err === 'E_OVER_LIMIT') {
return void UI.alert(Messages.pinLimitNotPinned, null, true);
} else if (err) { return; }
evTitleChange.fire(title);
if (titleUpdated) { titleUpdated(undefined, title); }
});

View File

@@ -5,17 +5,18 @@ define([
'/common/sframe-chainpad-netflux-inner.js',
'/common/sframe-channel.js',
'/common/sframe-common-title.js',
'/common/sframe-common-interface.js',
'/common/common-ui-elements.js',
'/common/sframe-common-history.js',
'/common/sframe-common-file.js',
'/common/sframe-common-codemirror.js',
'/common/metadata-manager.js',
'/customize/application_config.js',
'/common/cryptpad-common.js',
'/common/common-realtime.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-thumbnail.js',
'/common/common-interface.js',
'/bower_components/localforage/dist/localforage.min.js'
], function (
$,
@@ -24,16 +25,17 @@ define([
CpNfInner,
SFrameChannel,
Title,
UI,
UIElements,
History,
File,
CodeMirror,
MetadataMgr,
AppConfig,
Cryptpad,
CommonRealtime,
Util,
Hash,
Thumb,
UI,
localForage
) {
// Chainpad Netflux Inner
@@ -55,7 +57,6 @@ define([
};
funcs.getMetadataMgr = function () { return ctx.metadataMgr; };
funcs.getCryptpadCommon = function () { return Cryptpad; };
funcs.getSframeChannel = function () { return ctx.sframeChan; };
funcs.getAppConfig = function () { return AppConfig; };
@@ -74,16 +75,16 @@ define([
};
// UI
funcs.createUserAdminMenu = callWithCommon(UI.createUserAdminMenu);
funcs.initFilePicker = callWithCommon(UI.initFilePicker);
funcs.openFilePicker = callWithCommon(UI.openFilePicker);
funcs.openTemplatePicker = callWithCommon(UI.openTemplatePicker);
funcs.displayMediatagImage = callWithCommon(UI.displayMediatagImage);
funcs.displayAvatar = callWithCommon(UI.displayAvatar);
funcs.createButton = callWithCommon(UI.createButton);
funcs.createUsageBar = callWithCommon(UI.createUsageBar);
funcs.updateTags = callWithCommon(UI.updateTags);
funcs.createLanguageSelector = callWithCommon(UI.createLanguageSelector);
funcs.createUserAdminMenu = callWithCommon(UIElements.createUserAdminMenu);
funcs.initFilePicker = callWithCommon(UIElements.initFilePicker);
funcs.openFilePicker = callWithCommon(UIElements.openFilePicker);
funcs.openTemplatePicker = callWithCommon(UIElements.openTemplatePicker);
funcs.displayMediatagImage = callWithCommon(UIElements.displayMediatagImage);
funcs.displayAvatar = callWithCommon(UIElements.displayAvatar);
funcs.createButton = callWithCommon(UIElements.createButton);
funcs.createUsageBar = callWithCommon(UIElements.createUsageBar);
funcs.updateTags = callWithCommon(UIElements.updateTags);
funcs.createLanguageSelector = callWithCommon(UIElements.createLanguageSelector);
// Thumb
funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);
@@ -102,21 +103,21 @@ define([
return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>';
};
funcs.getMediatagFromHref = function (href) {
var parsed = Cryptpad.parsePadUrl(href);
var secret = Cryptpad.getSecrets('file', parsed.hash);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash);
var data = ctx.metadataMgr.getPrivateData();
if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var hexFileName = Cryptpad.base64ToHex(secret.channel);
var hexFileName = Util.base64ToHex(secret.channel);
var origin = data.fileHost || data.origin;
var src = origin + Cryptpad.getBlobPathFromHex(hexFileName);
var src = origin + Hash.getBlobPathFromHex(hexFileName);
return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + cryptKey + '">' +
'</media-tag>';
}
return;
};
funcs.getFileSize = function (href, cb) {
var channelId = Cryptpad.hrefToHexChannelId(href);
var channelId = Hash.hrefToHexChannelId(href);
funcs.sendAnonRpcMsg("GET_FILE_SIZE", channelId, function (data) {
if (!data) { return void cb("No response"); }
if (data.error) { return void cb(data.error); }
@@ -313,6 +314,14 @@ define([
funcs.whenRealtimeSyncs = evRealtimeSynced.reg;
var logoutHandlers = [];
funcs.onLogout = function (h) {
if (typeof (h) !== "function") { return; }
if (logoutHandlers.indexOf(h) !== -1) { return; }
logoutHandlers.push(h);
};
Object.freeze(funcs);
return { create: function (cb) {
@@ -359,17 +368,30 @@ define([
UI.addTooltips();
ctx.sframeChan.on('EV_LOGOUT', function () {
$(window).on('keyup', function (e) {
if (e.keyCode === 27) {
UI.removeLoadingScreen();
}
});
UI.addLoadingScreen({hideTips: true});
UI.errorLoadingScreen(Messages.onLogout, true);
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
});
ctx.sframeChan.on('EV_RT_CONNECT', function () { CommonRealtime.setConnectionState(true); });
ctx.sframeChan.on('EV_RT_DISCONNECT', function () { CommonRealtime.setConnectionState(false); });
ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) {
Cryptpad.confirm(confirmMsg, cb, null, true);
UI.confirm(confirmMsg, cb, null, true);
});
ctx.sframeChan.on('EV_FRIEND_REQUEST', function (data) {
var i = pendingFriends.indexOf(data.netfluxId);
if (i !== -1) { pendingFriends.splice(i, 1); }
Cryptpad.log(data.logText);
UI.log(data.logText);
});
ctx.sframeChan.ready();

View File

@@ -108,6 +108,12 @@ define([], function () {
});
};
messenger.clearOwnedChannel = function (channel, cb) {
sFrameChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel, function (e) {
cb(e);
});
};
return messenger;
};

View File

@@ -1,677 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/curve.js',
'/common/common-hash.js',
], function ($, Crypto, Curve, Hash) {
'use strict';
var Msg = {
inputs: [],
};
var Types = {
message: 'MSG',
update: 'UPDATE',
unfriend: 'UNFRIEND',
mapId: 'MAP_ID',
mapIdAck: 'MAP_ID_ACK'
};
var clone = function (o) {
return JSON.parse(JSON.stringify(o));
};
// TODO
// - mute a channel (hide notifications or don't open it?)
var createData = Msg.createData = function (proxy, hash) {
return {
channel: hash || Hash.createChannelId(),
displayName: proxy['cryptpad.username'],
profile: proxy.profile && proxy.profile.view,
edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic,
avatar: proxy.profile && proxy.profile.avatar
};
};
var getFriend = function (proxy, pubkey) {
if (pubkey === proxy.curvePublic) {
var data = createData(proxy);
delete data.channel;
return data;
}
return proxy.friends ? proxy.friends[pubkey] : undefined;
};
var getFriendList = Msg.getFriendList = function (proxy) {
if (!proxy.friends) { proxy.friends = {}; }
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) {
var messenger = {
handlers: {
message: [],
join: [],
leave: [],
update: [],
friend: [],
unfriend: [],
},
range_requests: {},
};
var eachHandler = function (type, g) {
messenger.handlers[type].forEach(g);
};
messenger.on = function (type, f) {
var stack = messenger.handlers[type];
if (!Array.isArray(stack)) {
return void console.error('unsupported message type');
}
if (typeof(f) !== 'function') {
return void console.error('expected function');
}
stack.push(f);
};
var channels = messenger.channels = {};
var joining = {};
// declare common variables
var network = common.getNetwork();
var proxy = common.getProxy();
var realtime = common.getRealtime();
Msg.hk = network.historyKeeper;
var friends = getFriendList(proxy);
var getChannel = function (curvePublic) {
var friend = friends[curvePublic];
if (!friend) { return; }
var chanId = friend.channel;
if (!chanId) { return; }
return channels[chanId];
};
var initRangeRequest = function (txid, curvePublic, sig, cb) {
messenger.range_requests[txid] = {
messages: [],
cb: cb,
curvePublic: curvePublic,
sig: sig,
};
};
var getRangeRequest = function (txid) {
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; }
if (typeof(hash) !== 'string') {
// FIXME hash is not necessarily defined.
// What does this mean?
console.error("not sure what to do here");
return;
}
var chan = getChannel(curvePublic);
if (typeof(chan) === 'undefined') {
console.error("chan is undefined. we're going to have a problem here");
return;
}
var txid = common.uid();
initRangeRequest(txid, curvePublic, hash, cb);
var msg = [ 'GET_HISTORY_RANGE', chan.id, {
from: hash,
count: count,
txid: txid,
}
];
network.sendto(network.historyKeeper, JSON.stringify(msg)).then(function () {
}, function (err) {
throw new Error(err);
});
};
var getCurveForChannel = function (id) {
var channel = channels[id];
if (!channel) { return; }
return channel.curve;
};
messenger.getChannelHead = function (curvePublic, cb) {
var friend = friends[curvePublic];
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
cb(void 0, friend.lastKnownHash);
};
messenger.setChannelHead = function (curvePublic, hash, cb) {
var friend = friends[curvePublic];
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
friend.lastKnownHash = hash;
cb();
};
// Id message allows us to map a netfluxId with a public curve key
var onIdMessage = function (msg, sender) {
var channel;
var isId = Object.keys(channels).some(function (chanId) {
if (channels[chanId].userList.indexOf(sender) !== -1) {
channel = channels[chanId];
return true;
}
});
if (!isId) { return; }
var decryptedMsg = channel.encryptor.decrypt(msg);
if (decryptedMsg === null) {
return void console.error("Failed to decrypt message");
}
if (!decryptedMsg) {
console.error('decrypted message was falsey but not null');
return;
}
var parsed;
try {
parsed = JSON.parse(decryptedMsg);
} catch (e) {
console.error(decryptedMsg);
return;
}
if (parsed[0] !== Types.mapId && parsed[0] !== Types.mapIdAck) { return; }
// check that the responding peer's encrypted netflux id matches
// the sender field. This is to prevent replay attacks.
if (parsed[2] !== sender || !parsed[1]) { return; }
channel.mapId[sender] = parsed[1];
eachHandler('join', function (f) {
f(parsed[1], channel.id);
});
if (parsed[0] !== Types.mapId) { return; } // Don't send your key if it's already an ACK
// Answer with your own key
var rMsg = [Types.mapIdAck, proxy.curvePublic, channel.wc.myID];
var rMsgStr = JSON.stringify(rMsg);
var cryptMsg = channel.encryptor.encrypt(rMsgStr);
network.sendto(sender, cryptMsg);
};
var orderMessages = function (curvePublic, new_messages /*, sig */) {
var channel = getChannel(curvePublic);
var messages = channel.messages;
// TODO improve performance, guarantee correct ordering
new_messages.reverse().forEach(function (msg) {
messages.unshift(msg);
});
};
var removeFromFriendList = function (curvePublic, cb) {
if (!proxy.friends) { return; }
var friends = proxy.friends;
delete friends[curvePublic];
common.whenRealtimeSyncs(realtime, cb);
};
var pushMsg = function (channel, cryptMsg) {
var msg = channel.encryptor.decrypt(cryptMsg);
var sig = cryptMsg.slice(0, 64);
if (msgAlreadyKnown(channel, sig)) { return; }
var parsedMsg = JSON.parse(msg);
var curvePublic;
if (parsedMsg[0] === Types.message) {
// TODO validate messages here
var res = {
type: parsedMsg[0],
sig: sig,
author: parsedMsg[1],
time: parsedMsg[2],
text: parsedMsg[3],
// this makes debugging a whole lot easier
curve: getCurveForChannel(channel.id),
};
channel.messages.push(res);
eachHandler('message', function (f) {
f(res);
});
return true;
}
if (parsedMsg[0] === Types.update) {
if (parsedMsg[1] === proxy.curvePublic) { return; }
curvePublic = parsedMsg[1];
var newdata = parsedMsg[3];
var data = getFriend(proxy, parsedMsg[1]);
var types = [];
Object.keys(newdata).forEach(function (k) {
if (data[k] !== newdata[k]) {
types.push(k);
data[k] = newdata[k];
}
});
eachHandler('update', function (f) {
f(clone(newdata), curvePublic);
});
return;
}
if (parsedMsg[0] === Types.unfriend) {
curvePublic = parsedMsg[1];
delete friends[curvePublic];
removeFromFriendList(parsedMsg[1], function () {
channel.wc.leave(Types.unfriend);
eachHandler('unfriend', function (f) {
f(curvePublic);
});
});
return;
}
};
/* Broadcast a display name, profile, or avatar change to all contacts
*/
// TODO send event...
messenger.updateMyData = function () {
var friends = getFriendList(proxy);
var mySyncData = friends.me;
var myData = createData(proxy);
if (!mySyncData || mySyncData.displayName !== myData.displayName
|| mySyncData.profile !== myData.profile
|| mySyncData.avatar !== myData.avatar) {
delete myData.channel;
Object.keys(channels).forEach(function (chan) {
var channel = channels[chan];
if (!channel) {
return void console.error('NO_SUCH_CHANNEL');
}
var msg = [Types.update, myData.curvePublic, +new Date(), myData];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
channel.wc.bcast(cryptMsg).then(function () {
// TODO send event
//channel.refresh();
}, function (err) {
console.error(err);
});
});
eachHandler('update', function (f) {
f(myData, myData.curvePublic);
});
friends.me = myData;
}
};
var onChannelReady = function (chanId) {
var cb = joining[chanId];
if (typeof(cb) !== 'function') {
return void console.error('channel ready without callback');
}
delete joining[chanId];
return cb();
};
var onDirectMessage = function (common, msg, sender) {
if (sender !== Msg.hk) { return void onIdMessage(msg, sender); }
var parsed = JSON.parse(msg);
if (/HISTORY_RANGE/.test(parsed[0])) {
//console.log(parsed);
var txid = parsed[1];
var req = getRangeRequest(txid);
var type = parsed[0];
if (!req) {
return void console.error("received response to unknown request");
}
if (type === 'HISTORY_RANGE') {
req.messages.push(parsed[2]);
} else if (type === 'HISTORY_RANGE_END') {
// process all the messages (decrypt)
var curvePublic = req.curvePublic;
var channel = getChannel(curvePublic);
var decrypted = req.messages.map(function (msg) {
if (msg[2] !== 'MSG') { return; }
try {
return {
d: JSON.parse(channel.encryptor.decrypt(msg[4])),
sig: msg[4].slice(0, 64),
};
} catch (e) {
console.log('failed to decrypt');
return null;
}
}).filter(function (decrypted) {
return decrypted;
}).map(function (O) {
return {
type: O.d[0],
sig: O.sig,
author: O.d[1],
time: O.d[2],
text: O.d[3],
curve: curvePublic,
};
});
orderMessages(curvePublic, decrypted, req.sig);
req.cb(void 0, decrypted);
return deleteRangeRequest(txid);
} else {
console.log(parsed);
}
return;
}
if ((parsed.validateKey || parsed.owners) && parsed.channel) {
return;
}
if (parsed.state && parsed.state === 1 && parsed.channel) {
if (channels[parsed.channel]) {
// parsed.channel is Ready
// channel[parsed.channel].ready();
channels[parsed.channel].ready = true;
onChannelReady(parsed.channel);
var updateTypes = channels[parsed.channel].updateOnReady;
if (updateTypes) {
//channels[parsed.channel].updateUI(updateTypes);
}
}
return;
}
var chan = parsed[3];
if (!chan || !channels[chan]) { return; }
pushMsg(channels[chan], parsed[4]);
};
var onMessage = function (msg, sender, chan) {
if (!channels[chan.id]) { return; }
var isMessage = pushMsg(channels[chan.id], msg);
if (isMessage) {
if (channels[chan.id].wc.myID !== sender) {
// Don't notify for your own messages
//channels[chan.id].notify();
}
//channels[chan.id].refresh();
// TODO emit message event
}
};
// listen for messages...
network.on('message', function(msg, sender) {
onDirectMessage(common, msg, sender);
});
messenger.removeFriend = function (curvePublic, cb) {
if (typeof(cb) !== 'function') { throw new Error('NO_CALLBACK'); }
var data = getFriend(proxy, curvePublic);
if (!data) {
// friend is not valid
console.error('friend is not valid');
return;
}
var channel = channels[data.channel];
if (!channel) {
return void cb("NO_SUCH_CHANNEL");
}
if (!network.webChannels.some(function (wc) {
return wc.id === channel.id;
})) {
console.error('bad channel: ', curvePublic);
}
var msg = [Types.unfriend, proxy.curvePublic, +new Date()];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
// TODO emit remove_friend event?
try {
channel.wc.bcast(cryptMsg).then(function () {
delete friends[curvePublic];
delete channels[curvePublic];
common.whenRealtimeSyncs(realtime, function () {
cb();
});
}, function (err) {
console.error(err);
cb(err);
});
} catch (e) {
cb(e);
}
};
var getChannelMessagesSince = function (chan, data, keys) {
console.log('Fetching [%s] messages since [%s]', data.curvePublic, data.lastKnownHash || '');
var cfg = {
validateKey: keys.validateKey,
owners: [proxy.edPublic, data.edPublic],
lastKnownHash: data.lastKnownHash
};
var msg = ['GET_HISTORY', chan.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg))
.then($.noop, function (err) {
throw new Error(err);
});
};
var openFriendChannel = function (data, f) {
var keys = Curve.deriveKeys(data.curvePublic, proxy.curvePrivate);
var encryptor = Curve.createEncryptor(keys);
network.join(data.channel).then(function (chan) {
var channel = channels[data.channel] = {
id: data.channel,
sending: false,
friendEd: f,
keys: keys,
curve: data.curvePublic,
encryptor: encryptor,
messages: [],
wc: chan,
userList: [],
mapId: {},
send: function (payload, cb) {
if (!network.webChannels.some(function (wc) {
if (wc.id === channel.wc.id) { return true; }
})) {
return void cb('NO_SUCH_CHANNEL');
}
var msg = [Types.message, proxy.curvePublic, +new Date(), payload];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
channel.wc.bcast(cryptMsg).then(function () {
pushMsg(channel, cryptMsg);
cb();
}, function (err) {
cb(err);
});
}
};
chan.on('message', function (msg, sender) {
onMessage(msg, sender, chan);
});
var onJoining = function (peer) {
if (peer === Msg.hk) { return; }
if (channel.userList.indexOf(peer) !== -1) { return; }
channel.userList.push(peer);
var msg = [Types.mapId, proxy.curvePublic, chan.myID];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
network.sendto(peer, cryptMsg);
};
chan.members.forEach(function (peer) {
if (peer === Msg.hk) { return; }
if (channel.userList.indexOf(peer) !== -1) { return; }
channel.userList.push(peer);
});
chan.on('join', onJoining);
chan.on('leave', function (peer) {
var curvePublic = channel.mapId[peer];
var i = channel.userList.indexOf(peer);
while (i !== -1) {
channel.userList.splice(i, 1);
i = channel.userList.indexOf(peer);
}
// update status
if (!curvePublic) { return; }
eachHandler('leave', function (f) {
f(curvePublic, channel.id);
});
});
// FIXME don't subscribe to the channel implicitly
getChannelMessagesSince(chan, data, keys);
}, function (err) {
console.error(err);
});
};
messenger.getFriendList = function (cb) {
var friends = proxy.friends;
if (!friends) { return void cb(void 0, []); }
cb(void 0, Object.keys(proxy.friends).filter(function (k) {
return k !== 'me';
}));
};
messenger.openFriendChannel = function (curvePublic, cb) {
if (typeof(curvePublic) !== 'string') { return void cb('INVALID_ID'); }
if (typeof(cb) !== 'function') { throw new Error('expected callback'); }
var friend = clone(friends[curvePublic]);
if (typeof(friend) !== 'object') {
return void cb('NO_FRIEND_DATA');
}
var channel = friend.channel;
if (!channel) { return void cb('E_NO_CHANNEL'); }
joining[channel] = cb;
openFriendChannel(friend, curvePublic);
};
messenger.sendMessage = function (curvePublic, payload, cb) {
var channel = getChannel(curvePublic);
if (!channel) { return void cb('NO_CHANNEL'); }
if (!network.webChannels.some(function (wc) {
if (wc.id === channel.wc.id) { return true; }
})) {
return void cb('NO_SUCH_CHANNEL');
}
var msg = [Types.message, proxy.curvePublic, +new Date(), payload];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
channel.wc.bcast(cryptMsg).then(function () {
pushMsg(channel, cryptMsg);
cb();
}, function (err) {
cb(err);
});
};
messenger.getStatus = function (curvePublic, cb) {
var channel = getChannel(curvePublic);
if (!channel) { return void cb('NO_SUCH_CHANNEL'); }
var online = channel.userList.some(function (nId) {
return channel.mapId[nId] === curvePublic;
});
cb(void 0, online);
};
messenger.getFriendInfo = function (curvePublic, cb) {
setTimeout(function () {
var friend = friends[curvePublic];
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
// this clone will be redundant when ui uses postmessage
cb(void 0, clone(friend));
});
};
messenger.getMyInfo = function (cb) {
cb(void 0, {
curvePublic: proxy.curvePublic,
displayName: common.getDisplayName(),
});
};
// TODO listen for changes to your friend list
// emit 'update' events for clients
//var update = function (curvePublic
proxy.on('change', ['friends'], function (o, n, p) {
var curvePublic;
if (o === undefined) {
// new friend added
curvePublic = p.slice(-1)[0];
eachHandler('friend', function (f) {
f(curvePublic, clone(n));
});
return;
}
console.error(o, n, p);
}).on('remove', ['friends'], function (o, p) {
eachHandler('unfriend', function (f) {
f(p[1]); // TODO
});
});
Object.freeze(messenger);
return messenger;
};
return Msg;
});

View File

@@ -56,6 +56,8 @@ define({
// Log the user out in all the tabs
'Q_LOGOUT': true,
// Tell the user that he has been logged out from outside (probably from another tab)
'EV_LOGOUT': true,
// When moving to the login or register page from a pad, we need to redirect to that pad at the
// end of the login process. This query set the current href to the sessionStorage.
@@ -154,6 +156,7 @@ define({
'Q_CONTACTS_GET_MORE_HISTORY': true,
'Q_CONTACTS_SEND_MESSAGE': true,
'Q_CONTACTS_SET_CHANNEL_HEAD': true,
'Q_CONTACTS_CLEAR_OWNED_CHANNEL': true,
// Put one or more entries to the localStore which will go in localStorage.
'EV_LOCALSTORE_PUT': true,

View File

@@ -1,865 +0,0 @@
define([
'jquery',
'/customize/application_config.js',
'/api/config'
], function ($, Config, ApiConfig) {
var urlArgs = ApiConfig.requireConf.urlArgs;
var Messages = {};
var Bar = {
constants: {},
};
/** Id of the div containing the user list. */
var USER_LIST_CLS = Bar.constants.userlist = 'cryptpad-user-list';
/** Id of the div containing the lag info. */
var LAG_ELEM_CLS = Bar.constants.lag = 'cryptpad-lag';
var LIMIT_ELEM_CLS = Bar.constants.lag = 'cryptpad-limit';
/** The toolbar class which contains the user list, debug link and lag. */
var TOOLBAR_CLS = Bar.constants.toolbar = 'cryptpad-toolbar';
var TOP_CLS = Bar.constants.top = 'cryptpad-toolbar-top';
var LEFTSIDE_CLS = Bar.constants.leftside = 'cryptpad-toolbar-leftside';
var RIGHTSIDE_CLS = Bar.constants.rightside = 'cryptpad-toolbar-rightside';
var HISTORY_CLS = Bar.constants.history = 'cryptpad-toolbar-history';
var SPINNER_CLS = Bar.constants.spinner = 'cryptpad-spinner';
var STATE_CLS = Bar.constants.state = 'cryptpad-state';
var USERNAME_CLS = Bar.constants.username = 'cryptpad-toolbar-username';
var READONLY_CLS = Bar.constants.readonly = 'cryptpad-readonly';
var USERBUTTONS_CONTAINER_CLS = Bar.constants.userButtonsContainer = "cryptpad-userbuttons-container";
var USERLIST_CLS = Bar.constants.userlist = "cryptpad-dropdown-users";
var EDITSHARE_CLS = Bar.constants.editShare = "cryptpad-dropdown-editShare";
var VIEWSHARE_CLS = Bar.constants.viewShare = "cryptpad-dropdown-viewShare";
var SHARE_CLS = Bar.constants.viewShare = "cryptpad-dropdown-share";
var DROPDOWN_CONTAINER_CLS = Bar.constants.dropdownContainer = "cryptpad-dropdown-container";
var DROPDOWN_CLS = Bar.constants.dropdown = "cryptpad-dropdown";
var TITLE_CLS = Bar.constants.title = "cryptpad-title";
var USER_CLS = Bar.constants.userAdmin = "cryptpad-user";
var USERBUTTON_CLS = Bar.constants.changeUsername = "cryptpad-change-username";
var SPINNER_DISAPPEAR_TIME = 3000;
var uid = function () {
return 'cryptpad-uid-' + String(Math.random()).substring(2);
};
var $style;
var connected = false;
var firstConnection = true;
var lagErrors = 0;
var styleToolbar = function ($container, href, version) {
href = href || '/customize/toolbar.css' + (version?('?' + version): '');
$.ajax({
url: href,
dataType: 'text',
success: function (data) {
$container.append($('<style>').text(data));
},
});
};
var createRealtimeToolbar = function ($container, config) {
var $toolbar = $('<div>', {
'class': TOOLBAR_CLS,
id: uid(),
})
.append($('<div>', {'class': TOP_CLS}))
.append($('<div>', {'class': LEFTSIDE_CLS}))
.append($('<div>', {'class': RIGHTSIDE_CLS}))
.append($('<div>', {'class': HISTORY_CLS}));
// The 'notitle' class removes the line added for the title with a small screen
if (!config || typeof config !== "object") {
$toolbar.addClass('notitle');
}
$container.prepend($toolbar);
if (ApiConfig && ApiConfig.requireConf && ApiConfig.requireConf.urlArgs) {
styleToolbar($container, undefined, ApiConfig.requireConf.urlArgs);
} else {
styleToolbar($container);
}
return $toolbar;
};
var createSpinner = function ($container, config) {
if (config.displayed.indexOf('spinner') !== -1) {
var $spin = $('<span>', {'class':SPINNER_CLS});
var $spinner = $('<span>', {
id: uid(),
'class': 'spin fa fa-spinner fa-pulse',
}).appendTo($spin).hide();
$('<span>', {
id: uid(),
'class': 'synced fa fa-check',
title: Messages.synced
}).appendTo($spin);
$container.prepend($spin);
return $spin[0];
}
};
var kickSpinner = function (Cryptpad, realtime, local, spinnerElement) {
if (!spinnerElement) { return; }
$(spinnerElement).find('.spin').show();
$(spinnerElement).find('.synced').hide();
var onSynced = function () {
if (spinnerElement.timeout) { clearTimeout(spinnerElement.timeout); }
spinnerElement.timeout = setTimeout(function () {
$(spinnerElement).find('.spin').hide();
$(spinnerElement).find('.synced').show();
}, local ? 0 : SPINNER_DISAPPEAR_TIME);
};
if (Cryptpad) {
Cryptpad.whenRealtimeSyncs(realtime, onSynced);
return;
}
onSynced();
};
var createUserButtons = function ($userlistElement, config, readOnly, Cryptpad) {
// User list button
if (config.displayed.indexOf('userlist') !== -1) {
if (!config.userData) {
throw new Error("You must provide a `userData` object to display the userlist");
}
var dropdownConfig = {
options: [{
tag: 'p',
attributes: {'class': USERLIST_CLS},
}] // Entries displayed in the menu
};
var $block = Cryptpad.createDropdown(dropdownConfig);
$block.attr('id', 'userButtons');
$userlistElement.append($block);
}
// Share button
if (config.displayed.indexOf('share') !== -1) {
var secret = Cryptpad.find(config, ['share', 'secret']);
var channel = Cryptpad.find(config, ['share', 'channel']);
if (!secret || !channel) {
throw new Error("Unable to display the share button: share.secret and share.channel required");
}
Cryptpad.getRecentPads(function (err, recent) {
var $shareIcon = $('<span>', {'class': 'fa fa-share-alt'});
var $span = $('<span>', {'class': 'large'}).append(' ' +Messages.shareButton);
var hashes = Cryptpad.getHashes(channel, secret);
var options = [];
// If we have a stronger version in drive, add it and add a redirect button
var stronger = recent && Cryptpad.findStronger(null, recent);
if (stronger) {
var parsed = Cryptpad.parsePadUrl(stronger);
hashes.editHash = parsed.hash;
}
if (hashes.editHash) {
options.push({
tag: 'a',
attributes: {title: Messages.editShareTitle, 'class': 'editShare'},
content: '<span class="fa fa-users"></span> ' + Messages.editShare
});
if (stronger) {
// We're in view mode, display the "open editing link" button
options.push({
tag: 'a',
attributes: {
title: Messages.editOpenTitle,
'class': 'editOpen',
href: window.location.pathname + '#' + hashes.editHash,
target: '_blank'
},
content: '<span class="fa fa-users"></span> ' + Messages.editOpen
});
}
options.push({tag: 'hr'});
}
if (hashes.viewHash) {
options.push({
tag: 'a',
attributes: {title: Messages.viewShareTitle, 'class': 'viewShare'},
content: '<span class="fa fa-eye"></span> ' + Messages.viewShare
});
if (hashes.editHash && !stronger) {
// We're in edit mode, display the "open readonly" button
options.push({
tag: 'a',
attributes: {
title: Messages.viewOpenTitle,
'class': 'viewOpen',
href: window.location.pathname + '#' + hashes.viewHash,
target: '_blank'
},
content: '<span class="fa fa-eye"></span> ' + Messages.viewOpen
});
}
}
if (hashes.fileHash) {
options.push({
tag: 'a',
attributes: {title: Messages.viewShareTitle, 'class': 'fileShare'},
content: '<span class="fa fa-eye"></span> ' + Messages.viewShare
});
}
var dropdownConfigShare = {
text: $('<div>').append($shareIcon).append($span).html(),
options: options
};
var $shareBlock = Cryptpad.createDropdown(dropdownConfigShare);
$shareBlock.find('button').attr('id', 'shareButton');
$shareBlock.find('.dropdown-bar-content').addClass(SHARE_CLS).addClass(EDITSHARE_CLS).addClass(VIEWSHARE_CLS);
$userlistElement.append($shareBlock);
if (hashes.editHash) {
$shareBlock.find('a.editShare').click(function () {
var url = window.location.origin + window.location.pathname + '#' + hashes.editHash;
var success = Cryptpad.Clipboard.copy(url);
if (success) { Cryptpad.log(Messages.shareSuccess); }
});
}
if (hashes.viewHash) {
$shareBlock.find('a.viewShare').click(function () {
var url = window.location.origin + window.location.pathname + '#' + hashes.viewHash ;
var success = Cryptpad.Clipboard.copy(url);
if (success) { Cryptpad.log(Messages.shareSuccess); }
});
}
if (hashes.fileHash) {
$shareBlock.find('a.fileShare').click(function () {
var url = window.location.origin + window.location.pathname + '#' + hashes.fileHash ;
var success = Cryptpad.Clipboard.copy(url);
if (success) { Cryptpad.log(Messages.shareSuccess); }
});
}
});
}
};
var createUserList = function ($container, config, readOnly, Cryptpad) {
if (config.displayed.indexOf('userlist') === -1 && config.displayed.indexOf('share') === -1) { return; }
var $userlist = $('<div>', {
'class': USER_LIST_CLS,
id: uid(),
});
createUserButtons($userlist, config, readOnly, Cryptpad);
$container.append($userlist);
return $userlist[0];
};
var getOtherUsers = function(myUserName, userList, userData) {
var i = 0; // duplicates counter
var list = [];
var myUid = userData[myUserName] ? userData[myUserName].uid : undefined;
var uids = [];
userList.forEach(function(user) {
if(user !== myUserName) {
var data = (userData) ? (userData[user] || null) : null;
var userName = (data) ? data.name : null;
var userId = (data) ? data.uid : null;
if (userName && uids.indexOf(userId) === -1 && (!myUid || userId !== myUid)) {
uids.push(userId);
list.push(userName);
} else { i++; }
}
});
return {
list: list,
duplicates: i
};
};
var arrayIntersect = function(a, b) {
return $.grep(a, function(i) {
return $.inArray(i, b) > -1;
});
};
var getViewers = function (n) {
if (!n || !parseInt(n) || n === 0) { return ''; }
if (n === 1) { return '; + ' + Messages.oneViewer; }
return '; + ' + Messages._getKey('viewers', [n]);
};
var checkSynchronizing = function (userList, myUserName, $stateElement) {
var meIdx = userList.indexOf(myUserName);
if (meIdx === -1) {
$stateElement.text(Messages.synchronizing);
return;
}
$stateElement.text('');
};
var updateUserList = function (config, myUserName, userlistElement, userList, userData, readOnly, $userAdminElement) {
// Make sure the elements are displayed
var $userButtons = $(userlistElement).find("#userButtons");
$userButtons.attr('display', 'inline');
if (config.displayed.indexOf('userlist') !== -1) {
var numberOfUsers = userList.length;
// If we are using old pads (readonly unavailable), only editing users are in userList.
// With new pads, we also have readonly users in userList, so we have to intersect with
// the userData to have only the editing users. We can't use userData directly since it
// may contain data about users that have already left the channel.
userList = readOnly === -1 ? userList : arrayIntersect(userList, Object.keys(userData));
// Names of editing users
var others = getOtherUsers(myUserName, userList, userData);
var editUsersNames = others.list;
var duplicates = others.duplicates;
var numberOfEditUsers = userList.length - duplicates;
var numberOfViewUsers = numberOfUsers - numberOfEditUsers - duplicates;
// Number of anonymous editing users
var anonymous = numberOfEditUsers - editUsersNames.length;
// Update the userlist
var $usersTitle = $('<h2>').text(Messages.users);
var $editUsers = $userButtons.find('.' + USERLIST_CLS);
$editUsers.html('').append($usersTitle);
var $editUsersList = $('<pre>');
if (readOnly !== 1) { // Yourself - edit
$editUsers.append('<span class="yourself">' + Messages.yourself + '</span>');
anonymous--;
}
// Editors
$editUsersList.text(editUsersNames.join('\n')); // .text() to avoid XSS
$editUsers.append($editUsersList);
if (anonymous > 0) { // Anonymous editors
var text = anonymous === 1 ? Messages.anonymousUser : Messages.anonymousUsers;
$editUsers.append('<span class="anonymous">' + anonymous + ' ' + text + '</span>');
}
if (numberOfViewUsers > 0) { // Viewers
var viewText = '<span class="viewer">';
if (numberOfEditUsers > 0) {
$editUsers.append('<br>');
viewText += Messages.and + ' ';
}
var viewerText = numberOfViewUsers !== 1 ? Messages.viewers : Messages.viewer;
viewText += numberOfViewUsers + ' ' + viewerText + '</span>';
$editUsers.append(viewText);
}
// Update the buttons
var fa_editusers = '<span class="fa fa-users"></span>';
var fa_viewusers = '<span class="fa fa-eye"></span>';
var viewersText = numberOfViewUsers !== 1 ? Messages.viewers : Messages.viewer;
var editorsText = numberOfEditUsers !== 1 ? Messages.editors : Messages.editor;
var $span = $('<span>', {'class': 'large'}).html(fa_editusers + ' ' + numberOfEditUsers + ' ' + editorsText + '&nbsp;&nbsp; ' + fa_viewusers + ' ' + numberOfViewUsers + ' ' + viewersText);
var $spansmall = $('<span>', {'class': 'narrow'}).html(fa_editusers + ' ' + numberOfEditUsers + '&nbsp;&nbsp; ' + fa_viewusers + ' ' + numberOfViewUsers);
$userButtons.find('.buttonTitle').html('').append($span).append($spansmall);
}
if (config.displayed.indexOf('useradmin') !== -1) {
// Change username in useradmin dropdown
var $userElement = $userAdminElement.find('.' + USERNAME_CLS);
$userElement.show();
if (readOnly === 1) {
$userElement.addClass(READONLY_CLS).text(Messages.readonly);
}
else {
var name = userData[myUserName] && userData[myUserName].name;
if (!name) {
name = Messages.anonymous;
}
$userElement.removeClass(READONLY_CLS).text(name);
}
}
};
var createLagElement = function () {
var $lag = $('<span>', {
'class': LAG_ELEM_CLS,
id: uid(),
});
var $a = $('<span>', {'class': 'cryptpad-lag', id: 'newLag'});
$('<span>', {'class': 'bar1'}).appendTo($a);
$('<span>', {'class': 'bar2'}).appendTo($a);
$('<span>', {'class': 'bar3'}).appendTo($a);
$('<span>', {'class': 'bar4'}).appendTo($a);
return $a[0];
};
var checkLag = function (getLag, lagElement) {
var lag;
if(typeof getLag === "function") {
lag = getLag();
}
var lagLight = $('<div>', {
'class': 'lag'
});
var title;
var $lag = $(lagElement);
if (lag) {
$lag.attr('class', 'cryptpad-lag');
firstConnection = false;
title = Messages.lag + ' : ' + lag + ' ms\n';
if (lag > 30000) {
$lag.addClass('lag0');
title = Messages.redLight;
} else if (lag > 5000) {
$lag.addClass('lag1');
title += Messages.orangeLight;
} else if (lag > 1000) {
$lag.addClass('lag2');
title += Messages.orangeLight;
} else if (lag > 300) {
$lag.addClass('lag3');
title += Messages.greenLight;
} else {
$lag.addClass('lag4');
title += Messages.greenLight;
}
}
else if (!firstConnection) {
$lag.attr('class', 'cryptpad-lag');
// Display the red light at the 2nd failed attemp to get the lag
lagLight.addClass('lag-red');
title = Messages.redLight;
}
if (title) {
$lag.attr('title', title);
}
};
var createLinkToMain = function ($topContainer) {
var $linkContainer = $('<span>', {
'class': "cryptpad-link"
}).appendTo($topContainer);
var $imgTag = $('<img>', {
src: "/customize/cryptofist_mini.png?" + urlArgs,
alt: "Cryptpad"
});
// We need to override the "a" tag action here because it is inside the iframe!
var $aTagSmall = $('<a>', {
href: "/",
title: Messages.header_logoTitle,
'class': "cryptpad-logo"
}).append($imgTag);
var $span = $('<span>').text('CryptPad');
var $aTagBig = $aTagSmall.clone().addClass('large').append($span);
$aTagSmall.addClass('narrow');
var onClick = function (e) {
e.preventDefault();
if (e.ctrlKey) {
window.open('/');
return;
}
window.location = "/";
};
var onContext = function (e) { e.stopPropagation(); };
$aTagBig.click(onClick).contextmenu(onContext);
$aTagSmall.click(onClick).contextmenu(onContext);
$linkContainer.append($aTagSmall).append($aTagBig);
};
var createUserAdmin = function ($topContainer, config, readOnly, lagElement, Cryptpad) {
var $lag = $(lagElement);
var $userContainer = $('<span>', {
'class': USER_CLS
}).appendTo($topContainer);
if (config.displayed.indexOf('state') !== -1) {
var $state = $('<span>', {
'class': STATE_CLS
}).text(Messages.synchronizing).appendTo($userContainer);
}
if (config.displayed.indexOf('lag') !== -1) {
$userContainer.append($lag);
}
if (config.displayed.indexOf('limit') !== -1 && Config.enablePinning) {
var usage;
var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'});
var $limit = $('<span>', {
'class': LIMIT_ELEM_CLS,
'title': Messages.pinLimitReached
}).append($limitIcon).hide().appendTo($userContainer);
var todo = function (e, overLimit) {
if (e) { return void console.error("Unable to get the pinned usage"); }
if (overLimit) {
var message = Messages.pinLimitReachedAlert;
if (ApiConfig.noSubscriptionButton === true) {
message = Messages.pinLimitReachedAlertNoAccounts;
}
$limit.show().click(function () {
Cryptpad.alert(message, null, true);
});
}
};
Cryptpad.isOverPinLimit(todo);
}
if (config.displayed.indexOf('newpad') !== -1) {
var pads_options = [];
Config.availablePadTypes.forEach(function (p) {
if (p === 'drive') { return; }
pads_options.push({
tag: 'a',
attributes: {
'target': '_blank',
'href': '/' + p + '/',
},
content: Messages.type[p]
});
});
var $plusIcon = $('<span>', {'class': 'fa fa-plus'});
var $newbig = $('<span>', {'class': 'big'}).append(' ' +Messages.newButton);
var $newButton = $('<div>').append($plusIcon).append($newbig);
var dropdownConfig = {
text: $newButton.html(), // Button initial text
options: pads_options, // Entries displayed in the menu
left: true, // Open to the left of the button
};
var $newPadBlock = Cryptpad.createDropdown(dropdownConfig);
$newPadBlock.find('button').attr('title', Messages.newButtonTitle);
$newPadBlock.find('button').attr('id', 'newdoc');
$newPadBlock.appendTo($userContainer);
}
// User dropdown
if (config.displayed.indexOf('useradmin') !== -1) {
var userMenuCfg = {};
if (!config.hideDisplayName) {
userMenuCfg = {
displayNameCls: USERNAME_CLS,
changeNameButtonCls: USERBUTTON_CLS,
};
}
if (readOnly !== 1) {
userMenuCfg.displayName = 1;
userMenuCfg.displayChangeName = 1;
}
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
$userAdmin.attr('id', 'userDropdown');
$userContainer.append($userAdmin);
var $userButton = $userAdmin.find('a.' + USERBUTTON_CLS);
var renameAlertOpened;
$userButton.click(function (e) {
e.preventDefault();
e.stopPropagation();
Cryptpad.getLastName(function (err, lastName) {
if (err) { return void console.error("Cannot get last name", err); }
Cryptpad.prompt(Messages.changeNamePrompt, lastName || '', function (newName) {
if (newName === null && typeof(lastName) === "string") { return; }
if (newName === null) { newName = ''; }
Cryptpad.changeDisplayName(newName);
});
});
});
Cryptpad.onDisplayNameChanged(function (newName) {
Cryptpad.findCancelButton().click();
});
}
return $userContainer;
};
var createTitle = function ($container, readOnly, config, Cryptpad) {
var $titleContainer = $('<span>', {
id: 'toolbarTitle',
'class': TITLE_CLS
}).appendTo($container);
if (!config || typeof config !== "object") { return; }
var callback = config.onRename;
var placeholder = config.defaultName;
var suggestName = config.suggestName;
// Buttons
var $text = $('<span>', {
'class': 'title'
}).appendTo($titleContainer);
var $pencilIcon = $('<span>', {
'class': 'pencilIcon',
'title': Messages.clickToEdit
});
if (readOnly === 1 || typeof(Cryptpad) === "undefined") { return $titleContainer; }
var $input = $('<input>', {
type: 'text',
placeholder: placeholder
}).appendTo($titleContainer).hide();
if (readOnly !== 1) {
$text.attr("title", Messages.clickToEdit);
$text.addClass("editable");
var $icon = $('<span>', {
'class': 'fa fa-pencil readonly',
style: 'font-family: FontAwesome;'
});
$pencilIcon.append($icon).appendTo($titleContainer);
}
// Events
$input.on('mousedown', function (e) {
if (!$input.is(":focus")) {
$input.focus();
}
e.stopPropagation();
return true;
});
$input.on('keyup', function (e) {
if (e.which === 13 && connected === true) {
var name = $input.val().trim();
if (name === "") {
name = $input.attr('placeholder');
}
Cryptpad.renamePad(name, function (err, newtitle) {
if (err) { return; }
$text.text(newtitle);
callback(null, newtitle);
$input.hide();
$text.show();
//$pencilIcon.css('display', '');
});
}
else if (e.which === 27) {
$input.hide();
$text.show();
//$pencilIcon.css('display', '');
}
});
var displayInput = function () {
if (connected === false) { return; }
$text.hide();
//$pencilIcon.css('display', 'none');
var inputVal = suggestName() || "";
$input.val(inputVal);
$input.show();
$input.focus();
};
$text.on('click', displayInput);
$pencilIcon.on('click', displayInput);
return $titleContainer;
};
var create = Bar.create = function ($container, myUserName, realtime, getLag, userList, config) {
config = config || {};
var readOnly = (typeof config.readOnly !== "undefined") ? (config.readOnly ? 1 : 0) : -1;
var Cryptpad = config.common;
Messages = Cryptpad.Messages;
config.displayed = config.displayed || [];
var toolbar = createRealtimeToolbar($container, config.title);
var userListElement = createUserList(toolbar.find('.' + LEFTSIDE_CLS), config, readOnly, Cryptpad);
var $titleElement = createTitle(toolbar.find('.' + TOP_CLS), readOnly, config.title, Cryptpad);
var $linkElement = createLinkToMain(toolbar.find('.' + TOP_CLS));
var lagElement = createLagElement();
var $userAdminElement = createUserAdmin(toolbar.find('.' + TOP_CLS), config, readOnly, lagElement, Cryptpad);
var spinner = createSpinner($userAdminElement, config);
var userData = config.userData;
// readOnly = 1 (readOnly enabled), 0 (disabled), -1 (old pad without readOnly mode)
var saveElement;
var loadElement;
var $stateElement = toolbar.find('.' + STATE_CLS);
if (config.ifrw) {
var removeDropdowns = function (e) {
$container.find('.cryptpad-dropdown').hide();
};
var cancelEditTitle = function (e) {
// Now we want to apply the title even if we click somewhere else
if ($(e.target).parents('.' + TITLE_CLS).length || !$titleElement) {
return;
}
if (!$titleElement.find('input').is(':visible')) { return; }
var ev = $.Event("keyup");
ev.which = 13;
$titleElement.find('input').trigger(ev);
/*
$titleElement.find('input').hide();
$titleElement.find('span.title').show();
$titleElement.find('span.pencilIcon').css('display', '');
*/
};
$(config.ifrw).on('click', removeDropdowns);
$(config.ifrw).on('click', cancelEditTitle);
try {
if (config.ifrw.$('iframe').length) {
var innerIfrw = config.ifrw.$('iframe').each(function (i, el) {
$(el.contentWindow).on('click', removeDropdowns);
$(el.contentWindow).on('click', cancelEditTitle);
});
}
} catch (e) {
// empty try catch in case this iframe is problematic
}
}
// Update user list
if (userList) {
if (userData) {
userList.change.push(function (newUserData) {
var users = userList.users;
if (users.indexOf(myUserName) !== -1) { connected = true; }
if (!connected) { return; }
checkSynchronizing(users, myUserName, $stateElement);
updateUserList(config, myUserName, userListElement, users, userData, readOnly, $userAdminElement);
});
} else {
userList.change.push(function () {
var users = userList.users;
if (users.indexOf(myUserName) !== -1) { connected = true; }
if (!connected) { return; }
checkSynchronizing(users, myUserName, $stateElement);
});
}
}
// Display notifications when users are joining/leaving the session
var oldUserData;
if (typeof Cryptpad !== "undefined" && userList) {
var notify = function(type, name, oldname) {
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
if (typeof name === "undefined") { return; }
name = (name === "") ? Messages.anonymous : name;
switch(type) {
case 1:
Cryptpad.log(Messages._getKey("notifyJoined", [name]));
break;
case 0:
oldname = (oldname === "") ? Messages.anonymous : oldname;
Cryptpad.log(Messages._getKey("notifyRenamed", [oldname, name]));
break;
case -1:
Cryptpad.log(Messages._getKey("notifyLeft", [name]));
break;
default:
console.log("Invalid type of notification");
break;
}
};
var userPresent = function (id, user, data) {
if (!(user && user.uid)) {
console.log('no uid');
return 0;
}
if (!data) {
console.log('no data');
return 0;
}
var count = 0;
Object.keys(data).forEach(function (k) {
if (data[k] && data[k].uid === user.uid) { count++; }
});
return count;
};
userList.change.push(function (newdata) {
// Notify for disconnected users
if (typeof oldUserData !== "undefined") {
for (var u in oldUserData) {
// if a user's uid is still present after having left, don't notify
if (userList.users.indexOf(u) === -1) {
var temp = JSON.parse(JSON.stringify(oldUserData[u]));
delete oldUserData[u];
if (userPresent(u, temp, newdata || oldUserData) < 1) {
notify(-1, temp.name);
}
}
}
}
// Update the "oldUserData" object and notify for new users and names changed
if (typeof newdata === "undefined") { return; }
if (typeof oldUserData === "undefined") {
oldUserData = JSON.parse(JSON.stringify(newdata));
return;
}
if (readOnly === 0 && !oldUserData[myUserName]) {
oldUserData = JSON.parse(JSON.stringify(newdata));
return;
}
for (var k in newdata) {
if (k !== myUserName && userList.users.indexOf(k) !== -1) {
if (typeof oldUserData[k] === "undefined") {
// if the same uid is already present in the userdata, don't notify
if (!userPresent(k, newdata[k], oldUserData)) {
notify(1, newdata[k].name);
}
} else if (oldUserData[k].name !== newdata[k].name) {
notify(0, newdata[k].name, oldUserData[k].name);
}
}
}
oldUserData = JSON.parse(JSON.stringify(newdata));
});
}
var ks = function (local) {
return function () {
if (connected) { kickSpinner(Cryptpad, realtime, local, spinner); }
};
};
if (realtime) {
realtime.onPatch(ks());
realtime.onMessage(ks(true));
checkLag(getLag, lagElement);
setInterval(function () {
if (!connected) { return; }
checkLag(getLag, lagElement);
}, 3000);
} else { connected = true; }
var failed = function () {
connected = false;
$stateElement.text(Messages.disconnected);
checkLag(undefined, lagElement);
};
// On log out, remove permanently the realtime elements of the toolbar
Cryptpad.onLogout(function () {
failed();
$userAdminElement.find('#userDropdown').hide();
$(userListElement).hide();
});
return {
failed: failed,
reconnecting: function (userId) {
myUserName = userId;
connected = false;
$stateElement.text(Messages.reconnecting);
checkLag(getLag, lagElement);
},
connected: function () {
connected = true;
}
};
};
return Bar;
});

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,12 @@ define([
'jquery',
'/customize/application_config.js',
'/api/config',
], function ($, Config, ApiConfig) {
var Messages = {};
var Cryptpad;
'/common/common-ui-elements.js',
'/common/common-interface.js',
'/common/common-hash.js',
'/customize/messages.js',
'/common/clipboard.js',
], function ($, Config, ApiConfig, UIElements, UI, Hash, Messages, Clipboard) {
var Common;
var Bar = {
@@ -148,6 +151,18 @@ define([
};
var avatars = {};
var editingUserName = {
state: false
};
var setDisplayName = function (newName) {
Common.setDisplayName(newName, function (err) {
if (err) {
console.log("Couldn't set username");
console.error(err);
return;
}
});
};
var updateUserList = function (toolbar, config) {
// Make sure the elements are displayed
var $userButtons = toolbar.userlist;
@@ -182,6 +197,16 @@ define([
var numberOfEditUsers = Object.keys(userData).length - duplicates;
var numberOfViewUsers = viewers;
// If the user was changing his username, do not reste the input, store the current value
// and cursor
if (editingUserName.state) {
var $input = $userlistContent.find('.cp-toolbar-userlist-name-input');
editingUserName.value = $input.val();
editingUserName.select = [$input[0].selectionStart, $input[0].selectionEnd];
}
// Update the userlist
var $editUsers = $userlistContent.find('.' + USERLIST_CLS).html('');
@@ -210,28 +235,76 @@ define([
var $span = $('<span>', {'class': 'cp-avatar'});
var $rightCol = $('<span>', {'class': 'cp-toolbar-userlist-rightcol'});
var $nameSpan = $('<span>', {'class': 'cp-toolbar-userlist-name'}).text(name).appendTo($rightCol);
var isMe = data.curvePublic === user.curvePublic;
if (Common.isLoggedIn() && data.curvePublic) {
if (isMe) {
$span.attr('title', Messages._getKey('userlist_thisIsYou', [
name
]));
$nameSpan.text(name);
} else if (!friends[data.curvePublic]) {
if (pendingFriends.indexOf(data.netfluxId) !== -1) {
$('<span>', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending)
.appendTo($rightCol);
} else {
$('<span>', {
'class': 'fa fa-user-plus cp-toolbar-userlist-friend',
'title': Messages._getKey('userlist_addAsFriendTitle', [
name
])
}).appendTo($rightCol).click(function (e) {
e.stopPropagation();
Common.sendFriendRequest(data.netfluxId);
});
var isMe = data.uid === user.uid;
if (isMe) {
$nameSpan.html('');
var $nameValue = $('<span>', {
'class': 'cp-toolbar-userlist-name-value'
}).text(name).appendTo($nameSpan);
var $button = $('<button>', {
'class': 'fa fa-pencil cp-toolbar-userlist-name-edit',
title: Messages.user_rename
}).appendTo($nameSpan);
$button.hover(function (e) { e.preventDefault(); e.stopPropagation(); });
$button.mouseenter(function (e) {
e.preventDefault();
e.stopPropagation();
window.setTimeout(function () {
$button.parents().mouseleave();
});
});
var $nameInput = $('<input>', {
'class': 'cp-toolbar-userlist-name-input'
}).val(name).appendTo($rightCol);
$button.click(function (e) {
e.stopPropagation();
$nameSpan.hide();
$nameInput.show().focus().select();
editingUserName.state = true;
editingUserName.oldName = $nameInput.val();
});
$nameInput.click(function (e) {
e.stopPropagation();
});
$nameInput.on('keydown', function (e) {
if (e.which === 13 || e.which === 27) {
$nameInput.hide();
$nameSpan.show();
$button.show();
editingUserName.state = false;
}
if (e.which === 13) {
var newName = $nameInput.val(); // TODO clean
$nameValue.text(newName);
setDisplayName(newName);
return;
}
if (e.which === 27) {
$nameValue.text(editingUserName.oldName);
return;
}
});
if (editingUserName.state) {
$button.click();
$nameInput.val(editingUserName.value);
$nameInput[0].setSelectionRange(editingUserName.select[0],
editingUserName.select[1]);
setTimeout(function () { $nameInput.focus(); });
}
} else if (Common.isLoggedIn() && data.curvePublic && !friends[data.curvePublic]) {
if (pendingFriends.indexOf(data.netfluxId) !== -1) {
$('<span>', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending)
.appendTo($rightCol);
} else {
$('<span>', {
'class': 'fa fa-user-plus cp-toolbar-userlist-friend',
'title': Messages._getKey('userlist_addAsFriendTitle', [
name
])
}).appendTo($rightCol).click(function (e) {
e.stopPropagation();
Common.sendFriendRequest(data.netfluxId);
});
}
}
if (data.profile) {
@@ -426,7 +499,7 @@ define([
options: options,
feedback: 'SHARE_MENU',
};
var $shareBlock = Cryptpad.createDropdown(dropdownConfigShare);
var $shareBlock = UIElements.createDropdown(dropdownConfigShare);
$shareBlock.find('.cp-dropdown-content').addClass(SHARE_CLS).addClass(EDITSHARE_CLS).addClass(VIEWSHARE_CLS);
$shareBlock.addClass('cp-toolbar-share-button');
$shareBlock.find('button').attr('title', Messages.shareButton);
@@ -434,25 +507,25 @@ define([
if (hashes.editHash) {
$shareBlock.find('a.cp-toolbar-share-edit-copy').click(function () {
/*Common.storeLinkToClipboard(false, function (err) {
if (!err) { Cryptpad.log(Messages.shareSuccess); }
if (!err) { UI.log(Messages.shareSuccess); }
});*/
var url = origin + pathname + '#' + hashes.editHash;
var success = Cryptpad.Clipboard.copy(url);
if (success) { Cryptpad.log(Messages.shareSuccess); }
var success = Clipboard.copy(url);
if (success) { UI.log(Messages.shareSuccess); }
});
}
if (hashes.viewHash) {
$shareBlock.find('a.cp-toolbar-share-view-copy').click(function () {
/*Common.storeLinkToClipboard(true, function (err) {
if (!err) { Cryptpad.log(Messages.shareSuccess); }
if (!err) { UI.log(Messages.shareSuccess); }
});*/
var url = origin + pathname + '#' + hashes.viewHash;
var success = Cryptpad.Clipboard.copy(url);
if (success) { Cryptpad.log(Messages.shareSuccess); }
var success = Clipboard.copy(url);
if (success) { UI.log(Messages.shareSuccess); }
});
$shareBlock.find('a.cp-toolbar-share-view-embed').click(function () {
var url = origin + pathname + '#' + hashes.viewHash;
var parsed = Cryptpad.parsePadUrl(url);
var parsed = Hash.parsePadUrl(url);
url = origin + parsed.getUrl({embed: true, present: true});
// Alertify content
var $content = $('<div>');
@@ -468,12 +541,12 @@ define([
readonly: 'readonly',
value: iframeEmbed,
}).appendTo($tag);
Cryptpad.alert($content.html(), null, true);
UI.alert($content.html(), null, true);
$('#'+iframeId).click(function () {
this.select();
});
//var success = Cryptpad.Clipboard.copy(url);
//if (success) { Cryptpad.log(Messages.shareSuccess); }
//var success = Clipboard.copy(url);
//if (success) { UI.log(Messages.shareSuccess); }
});
}
@@ -511,15 +584,15 @@ define([
options: options,
feedback: 'FILESHARE_MENU',
};
var $shareBlock = Cryptpad.createDropdown(dropdownConfigShare);
var $shareBlock = UIElements.createDropdown(dropdownConfigShare);
$shareBlock.find('.cp-dropdown-content').addClass(SHARE_CLS);
$shareBlock.addClass('cp-toolbar-share-button');
$shareBlock.find('button').attr('title', Messages.shareButton);
// Add handlers
$shareBlock.find('a.cp-toolbar-share-file-copy').click(function () {
var success = Cryptpad.Clipboard.copy(url);
if (success) { Cryptpad.log(Messages.shareSuccess); }
var success = Clipboard.copy(url);
if (success) { UI.log(Messages.shareSuccess); }
});
$shareBlock.find('a.cp-toolbar-share-file-embed').click(function () {
var $content = $('<div>');
@@ -527,11 +600,11 @@ define([
$('<h3>').text(Messages.fileEmbedTitle).appendTo($content);
var $script = $('<p>').text(Messages.fileEmbedScript).appendTo($content);
$('<br>').appendTo($script);
$script.append(Cryptpad.dialog.selectable(Common.getMediatagScript()));
$script.append(UI.dialog.selectable(Common.getMediatagScript()));
var $tag = $('<p>').text(Messages.fileEmbedTag).appendTo($content);
$('<br>').appendTo($tag);
$tag.append(Cryptpad.dialog.selectable(Common.getMediatagFromHref(url)));
Cryptpad.alert($content[0], null, true);
$tag.append(UI.dialog.selectable(Common.getMediatagFromHref(url)));
UI.alert($content[0], null, true);
});
toolbar.$leftside.append($shareBlock);
@@ -568,8 +641,8 @@ define([
if (config.readOnly === 1) {
$titleContainer.append($('<span>', {'class': 'cp-toolbar-title-readonly'})
.text('('+Messages.readonly+')'));
return $titleContainer;
}
if (config.readOnly === 1 || typeof(Cryptpad) === "undefined") { return $titleContainer; }
var $input = $('<input>', {
type: 'text',
placeholder: placeholder
@@ -760,7 +833,7 @@ define([
key = 'pinLimitReachedAlertNoAccounts';
}
$limit.show().click(function () {
Cryptpad.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true);
UI.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true);
});
}
};
@@ -785,7 +858,7 @@ define([
'target': '_blank',
'href': origin + '/' + p + '/',
},
content: $('<div>').append(Cryptpad.getIcon(p)).html() + Messages.type[p]
content: $('<div>').append(UI.getIcon(p)).html() + Messages.type[p]
});
});
var dropdownConfig = {
@@ -796,7 +869,7 @@ define([
feedback: /drive/.test(window.location.pathname)?
'DRIVE_NEWPAD': 'NEWPAD',
};
var $newPadBlock = Cryptpad.createDropdown(dropdownConfig);
var $newPadBlock = UIElements.createDropdown(dropdownConfig);
$newPadBlock.find('button').attr('title', Messages.newButtonTitle);
$newPadBlock.find('button').addClass('fa fa-th');
return $newPadBlock;
@@ -821,8 +894,11 @@ define([
userMenuCfg.displayName = 1;
userMenuCfg.displayChangeName = 1;
}
/*if (config.displayed.indexOf('userlist') !== -1) {
userMenuCfg.displayChangeName = 0;
}*/
Common.createUserAdminMenu(userMenuCfg);
$userAdmin.find('button').attr('title', Messages.userAccountButton);
$userAdmin.find('> button').attr('title', Messages.userAccountButton);
var $userButton = toolbar.$userNameButton = $userAdmin.find('a.' + USERBUTTON_CLS);
$userButton.click(function (e) {
@@ -830,18 +906,11 @@ define([
e.stopPropagation();
var myData = metadataMgr.getMetadata().users[metadataMgr.getNetfluxId()];
var lastName = myData.name;
Cryptpad.prompt(Messages.changeNamePrompt, lastName || '', function (newName) {
UI.prompt(Messages.changeNamePrompt, lastName || '', function (newName) {
if (newName === null && typeof(lastName) === "string") { return; }
if (newName === null) { newName = ''; }
else { Common.feedback('NAME_CHANGED'); }
Common.setDisplayName(newName, function (err) {
if (err) {
console.log("Couldn't set username");
console.error(err);
return;
}
//Cryptpad.changeDisplayName(newName, true); Already done?
});
setDisplayName(newName);
});
});
@@ -892,90 +961,88 @@ define([
if (!config.metadataMgr) { return; }
var metadataMgr = config.metadataMgr;
var userNetfluxId = metadataMgr.getNetfluxId();
if (typeof Cryptpad !== "undefined") {
var notify = function(type, name, oldname) {
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
if (typeof name === "undefined") { return; }
name = name || Messages.anonymous;
if (Config.disableUserlistNotifications) { return; }
switch(type) {
case 1:
Cryptpad.log(Messages._getKey("notifyJoined", [name]));
break;
case 0:
oldname = (!oldname) ? Messages.anonymous : oldname;
Cryptpad.log(Messages._getKey("notifyRenamed", [oldname, name]));
break;
case -1:
Cryptpad.log(Messages._getKey("notifyLeft", [name]));
break;
default:
console.log("Invalid type of notification");
break;
}
};
var notify = function(type, name, oldname) {
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
if (typeof name === "undefined") { return; }
name = name || Messages.anonymous;
if (Config.disableUserlistNotifications) { return; }
switch(type) {
case 1:
UI.log(Messages._getKey("notifyJoined", [name]));
break;
case 0:
oldname = (!oldname) ? Messages.anonymous : oldname;
UI.log(Messages._getKey("notifyRenamed", [oldname, name]));
break;
case -1:
UI.log(Messages._getKey("notifyLeft", [name]));
break;
default:
console.log("Invalid type of notification");
break;
}
};
var userPresent = function (id, user, data) {
if (!(user && user.uid)) {
console.log('no uid');
return 0;
}
if (!data) {
console.log('no data');
return 0;
}
var userPresent = function (id, user, data) {
if (!(user && user.uid)) {
console.log('no uid');
return 0;
}
if (!data) {
console.log('no data');
return 0;
}
var count = 0;
Object.keys(data).forEach(function (k) {
if (data[k] && data[k].uid === user.uid) { count++; }
});
return count;
};
var joined = false;
metadataMgr.onChange(function () {
var newdata = metadataMgr.getMetadata().users;
var netfluxIds = Object.keys(newdata);
// Notify for disconnected users
if (typeof oldUserData !== "undefined") {
for (var u in oldUserData) {
// if a user's uid is still present after having left, don't notify
if (netfluxIds.indexOf(u) === -1) {
var temp = JSON.parse(JSON.stringify(oldUserData[u]));
delete oldUserData[u];
if (temp && newdata[userNetfluxId] && temp.uid === newdata[userNetfluxId].uid) { return; }
if (userPresent(u, temp, newdata || oldUserData) < 1) {
notify(-1, temp.name);
}
}
}
}
// Update the "oldUserData" object and notify for new users and names changed
if (typeof newdata === "undefined") { return; }
if (typeof oldUserData === "undefined") {
oldUserData = JSON.parse(JSON.stringify(newdata));
return;
}
if (config.readOnly === 0 && !oldUserData[userNetfluxId]) {
oldUserData = JSON.parse(JSON.stringify(newdata));
return;
}
for (var k in newdata) {
if (joined && k !== userNetfluxId && netfluxIds.indexOf(k) !== -1) {
if (typeof oldUserData[k] === "undefined") {
// if the same uid is already present in the userdata, don't notify
if (!userPresent(k, newdata[k], oldUserData)) {
notify(1, newdata[k].name);
}
} else if (oldUserData[k].name !== newdata[k].name) {
notify(0, newdata[k].name, oldUserData[k].name);
}
}
}
joined = true;
oldUserData = JSON.parse(JSON.stringify(newdata));
var count = 0;
Object.keys(data).forEach(function (k) {
if (data[k] && data[k].uid === user.uid) { count++; }
});
}
return count;
};
var joined = false;
metadataMgr.onChange(function () {
var newdata = metadataMgr.getMetadata().users;
var netfluxIds = Object.keys(newdata);
// Notify for disconnected users
if (typeof oldUserData !== "undefined") {
for (var u in oldUserData) {
// if a user's uid is still present after having left, don't notify
if (netfluxIds.indexOf(u) === -1) {
var temp = JSON.parse(JSON.stringify(oldUserData[u]));
delete oldUserData[u];
if (temp && newdata[userNetfluxId] && temp.uid === newdata[userNetfluxId].uid) { return; }
if (userPresent(u, temp, newdata || oldUserData) < 1) {
notify(-1, temp.name);
}
}
}
}
// Update the "oldUserData" object and notify for new users and names changed
if (typeof newdata === "undefined") { return; }
if (typeof oldUserData === "undefined") {
oldUserData = JSON.parse(JSON.stringify(newdata));
return;
}
if (config.readOnly === 0 && !oldUserData[userNetfluxId]) {
oldUserData = JSON.parse(JSON.stringify(newdata));
return;
}
for (var k in newdata) {
if (joined && k !== userNetfluxId && netfluxIds.indexOf(k) !== -1) {
if (typeof oldUserData[k] === "undefined") {
// if the same uid is already present in the userdata, don't notify
if (!userPresent(k, newdata[k], oldUserData)) {
notify(1, newdata[k].name);
}
} else if (oldUserData[k].name !== newdata[k].name) {
notify(0, newdata[k].name, oldUserData[k].name);
}
}
}
joined = true;
oldUserData = JSON.parse(JSON.stringify(newdata));
});
};
@@ -984,9 +1051,7 @@ define([
Bar.create = function (cfg) {
var config = cfg || {};
Cryptpad = config.common;
Common = config.sfCommon;
Messages = Cryptpad.Messages;
config.readOnly = (typeof config.readOnly !== "undefined") ? (config.readOnly ? 1 : 0) : -1;
config.displayed = config.displayed || [];
@@ -1081,7 +1146,7 @@ define([
};
// On log out, remove permanently the realtime elements of the toolbar
Cryptpad.onLogout(function () {
Common.onLogout(function () {
failed();
if (toolbar.useradmin) { toolbar.useradmin.hide(); }
if (toolbar.userlist) { toolbar.userlist.hide(); }

View File

@@ -1,7 +1,12 @@
define([
'jquery',
'/customize/application_config.js'
], function ($, AppConfig) {
'/customize/application_config.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-constants.js',
'/customize/messages.js'
], function ($, AppConfig, Util, Hash, Realtime, Constants, Messages) {
var module = {};
var ROOT = module.ROOT = "root";
@@ -17,11 +22,10 @@ define([
module.init = function (files, config) {
var exp = {};
var Cryptpad = config.Cryptpad;
var Messages = Cryptpad.Messages;
var loggedIn = config.loggedIn || Cryptpad.isLoggedIn();
var loggedIn = config.loggedIn;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Cryptpad.storageKey;
var OLD_FILES_DATA = module.OLD_FILES_DATA = exp.OLD_FILES_DATA = Cryptpad.oldStorageKey;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
var OLD_FILES_DATA = module.OLD_FILES_DATA = exp.OLD_FILES_DATA = Constants.oldStorageKey;
var NEW_FOLDER_NAME = Messages.fm_newFolder;
var NEW_FILE_NAME = Messages.fm_newFile;
@@ -74,7 +78,7 @@ define([
exp.isReadOnlyFile = function (element) {
if (!isFile(element)) { return false; }
var data = exp.getFileData(element);
var parsed = Cryptpad.parsePadUrl(data.href);
var parsed = Hash.parsePadUrl(data.href);
if (!parsed) { return false; }
var pHash = parsed.hashData;
if (!pHash || pHash.type !== "pad") { return; }
@@ -243,7 +247,7 @@ define([
getHrefArray().forEach(function (c) {
ret = ret.concat(_getFiles[c]());
});
return Cryptpad.deduplicateString(ret);
return Util.deduplicateString(ret);
};
_getFiles[ROOT] = function () {
var ret = [];
@@ -294,7 +298,7 @@ define([
ret = ret.concat(_getFiles[c]());
}
});
return Cryptpad.deduplicateString(ret);
return Util.deduplicateString(ret);
};
var getIdFromHref = exp.getIdFromHref = function (href) {
@@ -437,13 +441,13 @@ define([
});
// Search Href
var href = Cryptpad.getRelativeHref(value);
var href = Hash.getRelativeHref(value);
if (href) {
var id = getIdFromHref(href);
if (id) { res.push(id); }
}
res = Cryptpad.deduplicateString(res);
res = Util.deduplicateString(res);
var ret = [];
res.forEach(function (l) {
@@ -484,16 +488,17 @@ define([
// FILES DATA
exp.pushData = function (data, cb) {
// TODO: can only be called from outside atm
if (!Cryptpad) { return; }
if (typeof cb !== "function") { cb = function () {}; }
var todo = function () {
var id = Cryptpad.createRandomInteger();
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
}
Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e) {
Cryptpad.pinPads([Hash.hrefToHexChannelId(data.href)], function (e) {
if (e) { return void cb(e); }
todo();
});
@@ -547,7 +552,7 @@ define([
}
// Move to root
var newName = isFile(element) ?
getAvailableName(newParent, Cryptpad.createChannelId()) :
getAvailableName(newParent, Hash.createChannelId()) :
isInTrashRoot(elementPath) ?
elementPath[1] : elementPath.pop();
@@ -605,7 +610,7 @@ define([
if (path && isPathIn(newPath, [ROOT]) || filesList.indexOf(id) === -1) {
parentEl = find(newPath || [ROOT]);
if (parentEl) {
var newName = getAvailableName(parentEl, Cryptpad.createChannelId());
var newName = getAvailableName(parentEl, Hash.createChannelId());
parentEl[newName] = id;
return;
}
@@ -852,8 +857,6 @@ define([
}
try {
debug("Migrating file system...");
// TODO
Cryptpad.feedback('Migrate-oldFilesData', true);
files.migrate = 1;
var next = function () {
var oldData = files[OLD_FILES_DATA].slice();
@@ -865,10 +868,10 @@ define([
oldData.forEach(function (obj) {
if (!obj || !obj.href) { return; }
var href = obj.href;
var id = Cryptpad.createRandomInteger();
var id = Util.createRandomInteger();
var paths = findFile(href);
var data = obj;
var key = Cryptpad.createChannelId();
var key = Hash.createChannelId();
if (data) {
newData[id] = data;
} else {
@@ -901,7 +904,7 @@ define([
if (exp.rt) {
exp.rt.sync();
// TODO
Cryptpad.whenRealtimeSyncs(exp.rt, next);
Realtime.whenRealtimeSyncs(exp.rt, next);
} else {
window.setTimeout(next, 1000);
}
@@ -943,8 +946,8 @@ define([
}
if (typeof element[el] === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Cryptpad.createRandomInteger();
var key = Cryptpad.createChannelId();
var id = Util.createRandomInteger();
var key = Hash.createChannelId();
files[FILES_DATA][id] = {href: element[el], filename: el};
element[key] = id;
delete element[el];
@@ -968,7 +971,7 @@ define([
if (!$.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 = Cryptpad.createRandomInteger();
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {href: obj.element, filename: el};
obj.element = id;
}
@@ -1002,7 +1005,7 @@ define([
};
var fixTemplate = function () {
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
files[TEMPLATE] = Cryptpad.deduplicateString(files[TEMPLATE].slice());
files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
var us = files[TEMPLATE];
var rootFiles = getFiles([ROOT]).slice();
var toClean = [];
@@ -1012,7 +1015,7 @@ define([
}
if (typeof el === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Cryptpad.createRandomInteger();
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {href: el};
us[idx] = id;
}
@@ -1050,16 +1053,25 @@ define([
toClean.push(id);
continue;
}
var parsed = Cryptpad.parsePadUrl(el.href);
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 = Cryptpad.createChannelId();
var newName = Hash.createChannelId();
root[newName] = id;
continue;
}