mirror of https://github.com/xwiki-labs/cryptpad
Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
This commit is contained in:
commit
a9e4af1d12
|
@ -52,6 +52,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
div.cp-button-confirm {
|
||||
display: inline-block;
|
||||
button {
|
||||
margin: 0;
|
||||
}
|
||||
.cp-button-timer {
|
||||
height: 3px;
|
||||
& > div {
|
||||
height: 100%;
|
||||
background-color: @colortheme_alertify-primary;
|
||||
&.danger, &.btn-danger, &.danger-alt, &.btn-danger-alt {
|
||||
background-color: @colortheme_alertify-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button:not(.pure-button):not(.md-button):not(.mdl-button) {
|
||||
|
||||
|
@ -89,6 +105,7 @@
|
|||
white-space: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.danger, &.btn-danger {
|
||||
background-color: @colortheme_alertify-red;
|
||||
border-color: @colortheme_alertify-red-border;
|
||||
|
@ -98,6 +115,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.danger-alt, &.btn-danger-alt {
|
||||
border-color: @colortheme_alertify-red;
|
||||
color: @colortheme_alertify-red;
|
||||
&:hover, &:active {
|
||||
color: @colortheme_alertify-red-color;
|
||||
background-color: contrast(@colortheme_modal-bg, darken(@colortheme_alertify-red, 10%), lighten(@colortheme_alertify-red, 10%));
|
||||
}
|
||||
}
|
||||
|
||||
&.safe, &.btn-safe {
|
||||
background-color: @colortheme_alertify-green;
|
||||
border-color: @colortheme_alertify-green-border;
|
||||
|
|
|
@ -26,6 +26,42 @@
|
|||
// Properties modal
|
||||
.cp-app-prop {
|
||||
margin-bottom: 10px;
|
||||
.cp-app-prop-size-container {
|
||||
height: 20px;
|
||||
background-color: @colortheme_logo-2;
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
div {
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #CCCCCC;
|
||||
}
|
||||
}
|
||||
.cp-app-prop-size-legend {
|
||||
color: @colortheme_modal-fg;
|
||||
display: flex;
|
||||
margin: 10px 0;
|
||||
& > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-basis: 50%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.cp-app-prop-history-size-color, .cp-app-prop-contents-size-color {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.cp-app-prop-history-size-color {
|
||||
background-color: #CCCCCC;
|
||||
}
|
||||
.cp-app-prop-contents-size-color {
|
||||
background-color: @colortheme_logo-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-prop-content {
|
||||
|
|
|
@ -599,6 +599,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
|
|||
var config = parsed[2];
|
||||
var metadata = {};
|
||||
var lastKnownHash;
|
||||
var txid;
|
||||
|
||||
// clients can optionally pass a map of attributes
|
||||
// if the channel already exists this map will be ignored
|
||||
|
@ -606,6 +607,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
|
|||
if (config && typeof config === "object" && !Array.isArray(parsed[2])) {
|
||||
lastKnownHash = config.lastKnownHash;
|
||||
metadata = config.metadata || {};
|
||||
txid = config.txid;
|
||||
if (metadata.expire) {
|
||||
metadata.expire = +metadata.expire * 1000 + (+new Date());
|
||||
}
|
||||
|
@ -655,11 +657,12 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
|
|||
msgCount++;
|
||||
// avoid sending the metadata message a second time
|
||||
if (isMetadataMessage(msg) && metadata_cache[channelName]) { return readMore(); }
|
||||
if (txid) { msg[0] = txid; }
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore);
|
||||
}, (err) => {
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", err); }
|
||||
const parsedMsg = {error:err.message, channel: channelName};
|
||||
const parsedMsg = {error:err.message, channel: channelName, txid: txid};
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
|
||||
return;
|
||||
}
|
||||
|
@ -707,7 +710,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
|
|||
}
|
||||
|
||||
// End of history message:
|
||||
let parsedMsg = {state: 1, channel: channelName};
|
||||
let parsedMsg = {state: 1, channel: channelName, txid: txid};
|
||||
|
||||
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
|
||||
});
|
||||
|
|
|
@ -975,10 +975,11 @@ var trimChannel = function (env, channelName, hash, _cb) {
|
|||
if (msgHash === hash) {
|
||||
// everything from this point on should be retained
|
||||
retain = true;
|
||||
return void tempStream.write(msgObj.buff, function () {
|
||||
return void tempStream.write(s_msg + '\n', function () {
|
||||
readMore();
|
||||
});
|
||||
}
|
||||
readMore();
|
||||
};
|
||||
|
||||
readMessagesBin(env, channelName, 0, handler, w(function (err) {
|
||||
|
|
|
@ -599,6 +599,59 @@ define([
|
|||
}
|
||||
});
|
||||
};
|
||||
UI.confirmButton = function (originalBtn, config, _cb) {
|
||||
config = config || {};
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
var classes = 'btn ' + (config.classes || 'btn-primary');
|
||||
|
||||
var button = h('button', {
|
||||
"class": classes,
|
||||
title: config.title || ''
|
||||
}, Messages.areYouSure || "Are you sure?"); // XXX
|
||||
var $button = $(button);
|
||||
|
||||
var div = h('div', {
|
||||
"class": config.classes || ''
|
||||
});
|
||||
var timer = h('div.cp-button-timer', div);
|
||||
|
||||
var content = h('div.cp-button-confirm', [
|
||||
button,
|
||||
timer
|
||||
]);
|
||||
|
||||
var to;
|
||||
|
||||
var done = function (res) {
|
||||
cb(res);
|
||||
clearTimeout(to);
|
||||
$(content).remove();
|
||||
$(originalBtn).show();
|
||||
};
|
||||
|
||||
$button.click(function () {
|
||||
done(true);
|
||||
});
|
||||
|
||||
var TIMEOUT = 3000;
|
||||
var INTERVAL = 10;
|
||||
var i = 1;
|
||||
|
||||
var todo = function () {
|
||||
var p = 100 * ((TIMEOUT - (i * INTERVAL)) / TIMEOUT);
|
||||
if (i++ * INTERVAL >= TIMEOUT) {
|
||||
done(false);
|
||||
return;
|
||||
}
|
||||
$(div).css('width', p+'%');
|
||||
to = setTimeout(todo, INTERVAL);
|
||||
};
|
||||
to = setTimeout(todo, INTERVAL);
|
||||
|
||||
$(originalBtn).hide().after(content);
|
||||
};
|
||||
|
||||
|
||||
UI.proposal = function (content, cb) {
|
||||
var buttons = [{
|
||||
|
|
|
@ -30,6 +30,13 @@ define([
|
|||
});
|
||||
}
|
||||
|
||||
UIElements.prettySize = function (bytes) {
|
||||
var kB = Util.bytesToKilobytes(bytes);
|
||||
if (kB < 1024) { return kB + Messages.KB; }
|
||||
var mB = Util.bytesToMegabytes(bytes);
|
||||
return mB + Messages.MB;
|
||||
};
|
||||
|
||||
UIElements.updateTags = function (common, href) {
|
||||
var existing, tags;
|
||||
NThen(function(waitFor) {
|
||||
|
@ -704,6 +711,9 @@ define([
|
|||
var $d = $('<div>');
|
||||
if (!data) { return void cb(void 0, $d); }
|
||||
|
||||
var priv = common.getMetadataMgr().getPrivateData();
|
||||
var edPublic = priv.edPublic;
|
||||
|
||||
if (data.href) {
|
||||
$('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d);
|
||||
$d.append(UI.dialog.selectable(data.href, {
|
||||
|
@ -730,13 +740,30 @@ define([
|
|||
$d.append(h('div.cp-app-prop', [Messages.fm_lastAccess, h('br'), h('span.cp-app-prop-content', new Date(data.atime).toLocaleString())]));
|
||||
}
|
||||
|
||||
var owned = false;
|
||||
if (common.isLoggedIn()) {
|
||||
if (Array.isArray(data.owners)) {
|
||||
if (data.owners.indexOf(edPublic) !== -1) {
|
||||
owned = true;
|
||||
} else {
|
||||
Object.keys(priv.teams || {}).some(function (id) {
|
||||
var team = priv.teams[id] || {};
|
||||
if (team.viewer) { return; }
|
||||
if (data.owners.indexOf(team.edPublic) === -1) { return; }
|
||||
owned = id;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
// check the size of this file...
|
||||
var bytes = 0;
|
||||
var historyBytes;
|
||||
var chan = [data.channel];
|
||||
if (data.rtChannel) { chan.push(data.rtChannel); }
|
||||
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
|
||||
var history = common.makeUniversal('history');
|
||||
var trimChannels = [];
|
||||
NThen(function (waitFor) {
|
||||
var chan = [data.channel];
|
||||
if (data.rtChannel) { chan.push(data.rtChannel); }
|
||||
if (data.lastVersion) { chan.push(Hash.hrefToHexChannelId(data.lastVersion)); }
|
||||
chan.forEach(function (c) {
|
||||
common.getFileSize(c, waitFor(function (e, _bytes) {
|
||||
if (e) {
|
||||
|
@ -746,20 +773,88 @@ define([
|
|||
bytes += _bytes;
|
||||
}));
|
||||
});
|
||||
|
||||
if (!owned) { return; }
|
||||
history.execCommand('GET_HISTORY_SIZE', {
|
||||
pad: true,
|
||||
channels: chan.filter(function (c) { return c.length === 32; }),
|
||||
teamId: typeof(owned) === "number" && owned
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) { return; }
|
||||
historyBytes = obj.size;
|
||||
trimChannels = obj.channels;
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (bytes === 0) { return void cb(void 0, $d); }
|
||||
var KB = Util.bytesToKilobytes(bytes);
|
||||
var formatted = UIElements.prettySize(bytes);
|
||||
|
||||
var formatted = Messages._getKey('formattedKB', [KB]);
|
||||
$d.append(h('div.cp-app-prop', [Messages.upload_size, h('br'), h('span.cp-app-prop-content', formatted)]));
|
||||
if (!owned || !historyBytes || historyBytes > bytes || historyBytes < 0) {
|
||||
$d.append(h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('span.cp-app-prop-content', formatted)
|
||||
]));
|
||||
return void cb(void 0, $d);
|
||||
}
|
||||
|
||||
if (data.sharedFolder && false) {
|
||||
$('<label>', {'for': 'cp-app-prop-channel'}).text('Channel ID').appendTo($d);
|
||||
if (AppConfig.pinBugRecovery) { $d.append(h('p', AppConfig.pinBugRecovery)); }
|
||||
$d.append(UI.dialog.selectable(data.channel, {
|
||||
id: 'cp-app-prop-link',
|
||||
}));
|
||||
}
|
||||
Messages.historyTrim_historySize = 'History: {0}'; // XXX
|
||||
Messages.historyTrim_contentsSize = 'Contents: {0}'; // XXX
|
||||
|
||||
var p = Math.round((historyBytes / bytes) * 100);
|
||||
var historyPrettySize = UIElements.prettySize(historyBytes);
|
||||
var contentsPrettySize = UIElements.prettySize(bytes - historyBytes);
|
||||
var button;
|
||||
var spinner = UI.makeSpinner();
|
||||
var size = h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('div.cp-app-prop-size-container', [
|
||||
h('div.cp-app-prop-size-history', { style: 'width:'+p+'%;' })
|
||||
]),
|
||||
h('div.cp-app-prop-size-legend', [
|
||||
h('div.cp-app-prop-history-size', [
|
||||
h('span.cp-app-prop-history-size-color'),
|
||||
h('span.cp-app-prop-content', Messages._getKey('historyTrim_historySize', [historyPrettySize]))
|
||||
]),
|
||||
h('div.cp-app-prop-contents-size', [
|
||||
h('span.cp-app-prop-contents-size-color'),
|
||||
h('span.cp-app-prop-content', Messages._getKey('historyTrim_contentsSize', [contentsPrettySize]))
|
||||
]),
|
||||
]),
|
||||
button = h('button.btn.btn-danger-alt.no-margin', Messages.trimHistory_button || 'test'), // XXX
|
||||
spinner.spinner
|
||||
]);
|
||||
$d.append(size);
|
||||
|
||||
var $button = $(button);
|
||||
$button.click(function () {
|
||||
UI.confirmButton(button, {
|
||||
classes: 'btn-danger'
|
||||
}, function (yes) {
|
||||
if (!yes) { return; }
|
||||
|
||||
$button.remove();
|
||||
spinner.spin();
|
||||
history.execCommand('TRIM_HISTORY', {
|
||||
pad: true,
|
||||
channels: trimChannels,
|
||||
teamId: typeof(owned) === "number" && owned
|
||||
}, function (obj) {
|
||||
spinner.hide();
|
||||
if (obj && obj.error) {
|
||||
$(size).append(h('div.alert.alert-danger', Messages.trimHistory_error || 'error')); // XXX
|
||||
return;
|
||||
}
|
||||
$(size).remove();
|
||||
var formatted = UIElements.prettySize(bytes - historyBytes);
|
||||
$d.append(h('div.cp-app-prop', [
|
||||
Messages.upload_size,
|
||||
h('br'),
|
||||
h('span.cp-app-prop-content', formatted)
|
||||
]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
cb(void 0, $d);
|
||||
});
|
||||
|
|
|
@ -227,6 +227,7 @@
|
|||
else if (bytes >= oneMegabyte) { return 'MB'; }
|
||||
};
|
||||
|
||||
|
||||
// given a path, asynchronously return an arraybuffer
|
||||
Util.fetch = function (src, cb, progress) {
|
||||
var CB = Util.once(cb);
|
||||
|
|
|
@ -17,6 +17,7 @@ define([
|
|||
'/common/outer/profile.js',
|
||||
'/common/outer/team.js',
|
||||
'/common/outer/messenger.js',
|
||||
'/common/outer/history.js',
|
||||
'/common/outer/network-config.js',
|
||||
'/customize/application_config.js',
|
||||
|
||||
|
@ -28,7 +29,7 @@ define([
|
|||
'/bower_components/saferphore/index.js',
|
||||
], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback,
|
||||
Realtime, Messaging, Pinpad,
|
||||
SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger,
|
||||
SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger, History,
|
||||
NetConfig, AppConfig,
|
||||
Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) {
|
||||
|
||||
|
@ -49,8 +50,6 @@ define([
|
|||
var sendDriveEvent = function () {};
|
||||
var registerProxyEvents = function () {};
|
||||
|
||||
var storeHash, storeChannel;
|
||||
|
||||
var store = window.CryptPad_AsyncStore = {
|
||||
modules: {}
|
||||
};
|
||||
|
@ -159,13 +158,7 @@ define([
|
|||
};
|
||||
|
||||
var getUserChannelList = function () {
|
||||
// start with your userHash...
|
||||
var userHash = storeHash;
|
||||
if (!userHash) { return null; }
|
||||
|
||||
// No password for drive
|
||||
var secret = Hash.getSecrets('drive', userHash);
|
||||
var userChannel = secret.channel;
|
||||
var userChannel = store.driveChannel;
|
||||
if (!userChannel) { return null; }
|
||||
|
||||
// Get the list of pads' channel ID in your drive
|
||||
|
@ -173,7 +166,7 @@ define([
|
|||
// It now includes channels from shared folders
|
||||
var list = store.manager.getChannelsList('pin');
|
||||
|
||||
// Get the avatar
|
||||
// Get the avatar & profile
|
||||
var profile = store.proxy.profile;
|
||||
if (profile) {
|
||||
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
|
||||
|
@ -182,6 +175,10 @@ define([
|
|||
if (avatarChan) { list.push(avatarChan); }
|
||||
}
|
||||
|
||||
if (store.proxy.todo) {
|
||||
list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null));
|
||||
}
|
||||
|
||||
if (store.proxy.friends) {
|
||||
var fList = Messaging.getFriendChannelsList(store.proxy);
|
||||
list = list.concat(fList);
|
||||
|
@ -314,7 +311,7 @@ define([
|
|||
teamId = data.teamId;
|
||||
}
|
||||
|
||||
if (channel === storeChannel && !force) {
|
||||
if (channel === store.driveChannel && !force) {
|
||||
return void cb({error: 'User drive removal blocked!'});
|
||||
}
|
||||
|
||||
|
@ -446,10 +443,12 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
Store.getFileSize = function (clientId, data, cb) {
|
||||
Store.getFileSize = function (clientId, data, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
|
||||
|
||||
var channelId = Hash.hrefToHexChannelId(data.href, data.password);
|
||||
var channelId = data.channel || Hash.hrefToHexChannelId(data.href, data.password);
|
||||
store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) {
|
||||
if (e) { return void cb({error: e}); }
|
||||
if (response && response.length && typeof(response[0]) === 'number') {
|
||||
|
@ -713,11 +712,9 @@ define([
|
|||
|
||||
Store.deleteAccount = function (clientId, data, cb) {
|
||||
var edPublic = store.proxy.edPublic;
|
||||
// No password for drive
|
||||
var secret = Hash.getSecrets('drive', storeHash);
|
||||
Store.anonRpcMsg(clientId, {
|
||||
msg: 'GET_METADATA',
|
||||
data: secret.channel
|
||||
data: store.driveChannel
|
||||
}, function (data) {
|
||||
var metadata = data[0];
|
||||
// Owned drive
|
||||
|
@ -737,7 +734,7 @@ define([
|
|||
}).nThen(function (waitFor) {
|
||||
// Delete Drive
|
||||
Store.removeOwnedChannel(clientId, {
|
||||
channel: secret.channel,
|
||||
channel: store.driveChannel,
|
||||
force: true
|
||||
}, waitFor());
|
||||
}).nThen(function () {
|
||||
|
@ -753,7 +750,7 @@ define([
|
|||
var toSign = {
|
||||
intent: 'Please delete my account.'
|
||||
};
|
||||
toSign.drive = secret.channel;
|
||||
toSign.drive = store.driveChannel;
|
||||
toSign.edPublic = edPublic;
|
||||
var signKey = Crypto.Nacl.util.decodeBase64(store.proxy.edPrivate);
|
||||
var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey);
|
||||
|
@ -1768,7 +1765,9 @@ define([
|
|||
|
||||
// Fetch the latest version of the metadata on the server and return it.
|
||||
// If the pad is stored in our drive, update the local values of "owners" and "expire"
|
||||
Store.getPadMetadata = function (clientId, data, cb) {
|
||||
Store.getPadMetadata = function (clientId, data, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
|
||||
store.anon_rpc.send('GET_METADATA', data.channel, function (err, obj) {
|
||||
if (err) { return void cb({error: err}); }
|
||||
|
@ -1850,7 +1849,9 @@ define([
|
|||
network.sendto(hk, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
|
||||
};
|
||||
|
||||
Store.getHistory = function (clientId, data, cb) {
|
||||
Store.getHistory = function (clientId, data, _cb, full) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
var network = store.network;
|
||||
var hk = network.historyKeeper;
|
||||
|
||||
|
@ -1862,6 +1863,8 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var txid = Math.floor(Math.random() * 1000000);
|
||||
|
||||
var msgs = [];
|
||||
var completed = false;
|
||||
var onMsg = function (msg, sender) {
|
||||
|
@ -1870,6 +1873,8 @@ define([
|
|||
var parsed = parse(msg);
|
||||
if (!parsed) { return; }
|
||||
|
||||
if (parsed.txid && parsed.txid !== txid) { return; }
|
||||
|
||||
// Ignore the metadata message
|
||||
if (parsed.validateKey && parsed.channel) { return; }
|
||||
if (parsed.error && parsed.channel) {
|
||||
|
@ -1890,9 +1895,20 @@ define([
|
|||
return;
|
||||
}
|
||||
|
||||
msg = parsed[4];
|
||||
if (Array.isArray(parsed) && parsed[0] && parsed[0] !== txid) { return; }
|
||||
|
||||
// Keep only the history for our channel
|
||||
if (parsed[3] !== data.channel) { return; }
|
||||
// If we want the full messages, push the parsed data
|
||||
if (parsed[4] && full) {
|
||||
msgs.push({
|
||||
msg: msg,
|
||||
hash: parsed[4].slice(0,64)
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Otherwise, push the messages
|
||||
msg = parsed[4];
|
||||
if (msg) {
|
||||
msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
|
||||
msgs.push(msg);
|
||||
|
@ -1901,6 +1917,7 @@ define([
|
|||
network.on('message', onMsg);
|
||||
|
||||
var cfg = {
|
||||
txid: txid,
|
||||
lastKnownHash: data.lastKnownHash
|
||||
};
|
||||
var msg = ['GET_HISTORY', data.channel, cfg];
|
||||
|
@ -2071,6 +2088,7 @@ define([
|
|||
store.mailbox.removeClient(clientId);
|
||||
} catch (e) { console.error(e); }
|
||||
Object.keys(store.modules).forEach(function (key) {
|
||||
if (!store.modules[key].removeClient) { return; }
|
||||
try {
|
||||
store.modules[key].removeClient(clientId);
|
||||
} catch (e) { console.error(e); }
|
||||
|
@ -2332,6 +2350,7 @@ define([
|
|||
store.messenger = store.modules['messenger'];
|
||||
loadUniversal(Profile, 'profile', waitFor);
|
||||
loadUniversal(Team, 'team', waitFor);
|
||||
loadUniversal(History, 'history', waitFor);
|
||||
cleanFriendRequests();
|
||||
}).nThen(function () {
|
||||
var requestLogin = function () {
|
||||
|
@ -2427,13 +2446,12 @@ define([
|
|||
|
||||
var connect = function (clientId, data, cb) {
|
||||
var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive');
|
||||
storeHash = hash;
|
||||
if (!hash) {
|
||||
return void cb({error: '[Store.init] Unable to find or create a drive hash. Aborting...'});
|
||||
}
|
||||
// No password for drive
|
||||
var secret = Hash.getSecrets('drive', hash);
|
||||
storeChannel = secret.channel;
|
||||
store.driveChannel = secret.channel;
|
||||
var listmapConfig = {
|
||||
data: {},
|
||||
websocketURL: NetConfig.getWebsocketURL(),
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/userObject.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
], function (Util, Hash, UserObject, nThen) {
|
||||
var History = {};
|
||||
var commands = {};
|
||||
|
||||
var getAccountChannels = function (ctx) {
|
||||
var channels = [];
|
||||
var edPublic = Util.find(ctx.store, ['proxy', 'edPublic']);
|
||||
|
||||
// Drive
|
||||
var driveOwned = (Util.find(ctx.store, ['driveMetadata', 'owners']) || []).indexOf(edPublic) !== -1;
|
||||
if (driveOwned) {
|
||||
channels.push(ctx.store.driveChannel);
|
||||
}
|
||||
|
||||
// Profile
|
||||
var profile = ctx.store.proxy.profile;
|
||||
if (profile) {
|
||||
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null;
|
||||
if (profileChan) { channels.push(profileChan); }
|
||||
}
|
||||
|
||||
// Todo
|
||||
if (ctx.store.proxy.todo) {
|
||||
channels.push(Hash.hrefToHexChannelId('/todo/#' + ctx.store.proxy.todo, null));
|
||||
}
|
||||
|
||||
|
||||
// Mailboxes
|
||||
var mailboxes = ctx.store.proxy.mailboxes;
|
||||
if (mailboxes) {
|
||||
var mList = Object.keys(mailboxes).map(function (m) {
|
||||
return {
|
||||
lastKnownHash: mailboxes[m].lastKnownHash,
|
||||
channel: mailboxes[m].channel
|
||||
};
|
||||
});
|
||||
Array.prototype.push.apply(channels, mList);
|
||||
}
|
||||
|
||||
// Shared folders owned by me
|
||||
var sf = ctx.store.proxy[UserObject.SHARED_FOLDERS];
|
||||
if (sf) {
|
||||
var sfChannels = Object.keys(sf).map(function (fId) {
|
||||
var data = sf[fId];
|
||||
if (!data || !data.owners) { return; }
|
||||
var isOwner = Array.isArray(data.owners) && data.owners.indexOf(edPublic) !== -1;
|
||||
if (!isOwner) { return; }
|
||||
return data.channel;
|
||||
}).filter(Boolean);
|
||||
Array.prototype.push.apply(channels, sfChannels);
|
||||
}
|
||||
|
||||
return channels;
|
||||
};
|
||||
|
||||
var getEdPublic = function (ctx, teamId) {
|
||||
if (!teamId) { return Util.find(ctx.store, ['proxy', 'edPublic']); }
|
||||
|
||||
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
|
||||
return Util.find(teamData, ['keys', 'drive', 'edPublic']);
|
||||
};
|
||||
var getRpc = function (ctx, teamId) {
|
||||
if (!teamId) { return ctx.store.rpc; }
|
||||
var teams = ctx.store.modules['team'];
|
||||
if (!teams) { return; }
|
||||
var team = teams.getTeam(teamId);
|
||||
if (!team) { return; }
|
||||
return team.rpc;
|
||||
};
|
||||
|
||||
var getHistoryData = function (ctx, channel, lastKnownHash, teamId, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
var edPublic = getEdPublic(ctx, teamId);
|
||||
var Store = ctx.Store;
|
||||
|
||||
var total = 0;
|
||||
var history = 0;
|
||||
var metadata = 0;
|
||||
var hash;
|
||||
nThen(function (waitFor) {
|
||||
// Total size
|
||||
Store.getFileSize(null, {
|
||||
channel: channel
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
waitFor.abort();
|
||||
return void cb(obj);
|
||||
}
|
||||
if (typeof(obj.size) === "undefined") {
|
||||
waitFor.abort();
|
||||
return void cb({error: 'ENOENT'});
|
||||
}
|
||||
total = obj.size;
|
||||
}));
|
||||
// Pad
|
||||
Store.getHistory(null, {
|
||||
channel: channel,
|
||||
lastKnownHash: lastKnownHash
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
waitFor.abort();
|
||||
return void cb(obj);
|
||||
}
|
||||
if (!Array.isArray(obj)) {
|
||||
waitFor.abort();
|
||||
return void cb({error: 'EINVAL'});
|
||||
}
|
||||
|
||||
if (!obj.length) { return; }
|
||||
|
||||
hash = obj[0].hash;
|
||||
var messages = obj.map(function(data) {
|
||||
return data.msg;
|
||||
});
|
||||
history = messages.join('\n').length;
|
||||
}), true);
|
||||
// Metadata
|
||||
Store.getPadMetadata(null, {
|
||||
channel: channel
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) { return; }
|
||||
if (!obj || typeof(obj) !== "object") { return; }
|
||||
metadata = JSON.stringify(obj).length;
|
||||
if (!obj || !Array.isArray(obj.owners) ||
|
||||
obj.owners.indexOf(edPublic) === -1) {
|
||||
waitFor.abort();
|
||||
return void cb({error: 'INSUFFICIENT_PERMISSIONS'});
|
||||
}
|
||||
}));
|
||||
}).nThen(function () {
|
||||
cb({
|
||||
size: (total - metadata - history),
|
||||
hash: hash
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
commands.GET_HISTORY_SIZE = function (ctx, data, cId, cb) {
|
||||
if (!ctx.store.loggedIn || !ctx.store.rpc) { return void cb({ error: 'INSUFFICIENT_PERMISSIONS' }); }
|
||||
var channels = data.channels;
|
||||
if (!Array.isArray(channels)) { return void cb({ error: 'EINVAL' }); }
|
||||
|
||||
var warning = [];
|
||||
|
||||
// If account trim history, get the correct channels here
|
||||
if (data.account) {
|
||||
channels = getAccountChannels(ctx);
|
||||
}
|
||||
|
||||
var size = 0;
|
||||
var res = [];
|
||||
nThen(function (waitFor) {
|
||||
channels.forEach(function (chan) {
|
||||
var channel = chan;
|
||||
var lastKnownHash;
|
||||
if (typeof (chan) === "object" && chan.channel) {
|
||||
channel = chan.channel;
|
||||
lastKnownHash = chan.lastKnownHash;
|
||||
}
|
||||
getHistoryData(ctx, channel, lastKnownHash, data.teamId, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
warning.push(obj.error);
|
||||
return;
|
||||
}
|
||||
size += obj.size;
|
||||
if (!obj.hash) { return; }
|
||||
res.push({
|
||||
channel: channel,
|
||||
hash: obj.hash
|
||||
});
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
cb({
|
||||
warning: warning.length ? warning : undefined,
|
||||
channels: res,
|
||||
size: size
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
commands.TRIM_HISTORY = function (ctx, data, cId, cb) {
|
||||
if (!ctx.store.loggedIn || !ctx.store.rpc) { return void cb({ error: 'INSUFFICIENT_PERMISSIONS' }); }
|
||||
var channels = data.channels;
|
||||
if (!Array.isArray(channels)) { return void cb({ error: 'EINVAL' }); }
|
||||
|
||||
var rpc = getRpc(ctx, data.teamId);
|
||||
if (!rpc) { return void cb({ error: 'ENORPC'}); }
|
||||
|
||||
var warning = [];
|
||||
|
||||
nThen(function (waitFor) {
|
||||
channels.forEach(function (obj) {
|
||||
rpc.trimHistory(obj, waitFor(function (err) {
|
||||
if (err) {
|
||||
warning.push(err);
|
||||
return;
|
||||
}
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
// Only one channel and warning: error
|
||||
if (channels.length === 1 && warning.length) {
|
||||
return void cb({error: warning[0]});
|
||||
}
|
||||
cb({
|
||||
warning: warning.length ? warning : undefined
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
History.init = function (cfg, waitFor, emit) {
|
||||
var history = {};
|
||||
if (!cfg.store) { return; }
|
||||
var ctx = {
|
||||
store: cfg.store,
|
||||
Store: cfg.Store,
|
||||
pinPads: cfg.pinPads,
|
||||
updateMetadata: cfg.updateMetadata,
|
||||
emit: emit,
|
||||
};
|
||||
|
||||
history.execCommand = function (clientId, obj, cb) {
|
||||
var cmd = obj.cmd;
|
||||
var data = obj.data;
|
||||
try {
|
||||
commands[cmd](ctx, data, clientId, cb);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
return history;
|
||||
};
|
||||
|
||||
return History;
|
||||
});
|
||||
|
||||
|
||||
|
|
@ -125,6 +125,17 @@ var factory = function (Util, Rpc) {
|
|||
});
|
||||
};
|
||||
|
||||
exp.trimHistory = function (data, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
if (typeof(data) !== 'object' || !data.channel || !data.hash) {
|
||||
return void cb('INVALID_ARGUMENTS');
|
||||
}
|
||||
rpc.send('TRIM_HISTORY', data, function (e) {
|
||||
if (e) { return cb(e); }
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
exp.clearOwnedChannel = function (channel, cb) {
|
||||
if (typeof(channel) !== 'string' || channel.length !== 32) {
|
||||
return void cb('INVALID_ARGUMENTS');
|
||||
|
|
|
@ -188,13 +188,6 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var prettySize = function (bytes) {
|
||||
var kB = Util.bytesToKilobytes(bytes);
|
||||
if (kB < 1024) { return kB + Messages.KB; }
|
||||
var mB = Util.bytesToMegabytes(bytes);
|
||||
return mB + Messages.MB;
|
||||
};
|
||||
|
||||
queue.next = function () {
|
||||
if (queue.queue.length === 0) {
|
||||
clearTimeout(queue.to);
|
||||
|
@ -262,7 +255,7 @@ define([
|
|||
// name
|
||||
$('<td>').append($link).appendTo($tr);
|
||||
// size
|
||||
$('<td>').text(prettySize(estimate)).appendTo($tr);
|
||||
$('<td>').text(UIElements.prettySize(estimate)).appendTo($tr);
|
||||
// progress
|
||||
$('<td>', {'class': 'cp-fileupload-table-progress'}).append($progressContainer).appendTo($tr);
|
||||
// cancel
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
max-width: 650px;
|
||||
}
|
||||
|
||||
div.alert {
|
||||
font-size: @colortheme_app-font-size;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#cp-export-container {
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
|
@ -171,7 +176,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.cp-settings-change-password, .cp-settings-migrate {
|
||||
.cp-settings-change-password, .cp-settings-own-drive {
|
||||
[type="password"], [type="text"] {
|
||||
width: @sidebar_button-width;
|
||||
flex: unset;
|
||||
|
|
|
@ -48,12 +48,12 @@ define([
|
|||
|
||||
var categories = {
|
||||
'account': [
|
||||
'cp-settings-own-drive',
|
||||
'cp-settings-info-block',
|
||||
'cp-settings-displayname',
|
||||
'cp-settings-language-selector',
|
||||
'cp-settings-resettips',
|
||||
'cp-settings-change-password',
|
||||
'cp-settings-migrate',
|
||||
'cp-settings-delete'
|
||||
],
|
||||
'security': [
|
||||
|
@ -73,7 +73,8 @@ define([
|
|||
'cp-settings-thumbnails',
|
||||
'cp-settings-drive-backup',
|
||||
'cp-settings-drive-import-local',
|
||||
'cp-settings-drive-reset'
|
||||
'cp-settings-trim-history'
|
||||
//'cp-settings-drive-reset'
|
||||
],
|
||||
'cursor': [
|
||||
'cp-settings-cursor-color',
|
||||
|
@ -146,6 +147,11 @@ define([
|
|||
hintFunction(safeKey).appendTo($div);
|
||||
}
|
||||
getter(function (content) {
|
||||
if (content === false) {
|
||||
$div.remove();
|
||||
$div = undefined;
|
||||
return;
|
||||
}
|
||||
$div.append(content);
|
||||
}, $div);
|
||||
return $div;
|
||||
|
@ -520,19 +526,12 @@ define([
|
|||
return $div;
|
||||
};
|
||||
|
||||
create['migrate'] = function () {
|
||||
if (privateData.isDriveOwned) { return; }
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
makeBlock('own-drive', function (cb, $div) {
|
||||
if (privateData.isDriveOwned || !common.isLoggedIn()) {
|
||||
return void cb(false);
|
||||
}
|
||||
|
||||
var $div = $('<div>', { 'class': 'cp-settings-migrate cp-sidebarlayout-element'});
|
||||
|
||||
$('<span>', {'class': 'label'}).text(Messages.settings_ownDriveTitle).appendTo($div);
|
||||
|
||||
$('<span>', {'class': 'cp-sidebarlayout-description'})
|
||||
.append(Messages.settings_ownDriveHint).appendTo($div);
|
||||
|
||||
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
|
||||
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
|
||||
$div.addClass('alert alert-warning');
|
||||
|
||||
var form = h('div', [
|
||||
UI.passwordInput({
|
||||
|
@ -541,13 +540,13 @@ define([
|
|||
}, true),
|
||||
h('button.btn.btn-primary', Messages.settings_ownDriveButton)
|
||||
]);
|
||||
|
||||
$(form).appendTo($div);
|
||||
var $form = $(form);
|
||||
var spinner = UI.makeSpinner($form);
|
||||
|
||||
var todo = function () {
|
||||
var password = $(form).find('#cp-settings-migrate-password').val();
|
||||
var password = $form.find('#cp-settings-migrate-password').val();
|
||||
if (!password) { return; }
|
||||
$spinner.show();
|
||||
spinner.spin();
|
||||
UI.confirm(Messages.settings_ownDriveConfirm, function (yes) {
|
||||
if (!yes) { return; }
|
||||
var data = {
|
||||
|
@ -561,16 +560,15 @@ define([
|
|||
sframeChan.query('Q_CHANGE_USER_PASSWORD', data, function (err, obj) {
|
||||
UI.removeLoadingScreen();
|
||||
if (err || obj.error) { return UI.alert(Messages.settings_changePasswordError); }
|
||||
$ok.show();
|
||||
$spinner.hide();
|
||||
spinner.done();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$(form).find('button').click(function () {
|
||||
$form.find('button').click(function () {
|
||||
todo();
|
||||
});
|
||||
$(form).find('input').keydown(function (e) {
|
||||
$form.find('input').keydown(function (e) {
|
||||
// Save on Enter
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
|
@ -579,11 +577,9 @@ define([
|
|||
}
|
||||
});
|
||||
|
||||
$spinner.hide().appendTo($div);
|
||||
$ok.hide().appendTo($div);
|
||||
|
||||
return $div;
|
||||
};
|
||||
cb(form);
|
||||
}, true);
|
||||
|
||||
// Security
|
||||
|
||||
|
@ -1215,6 +1211,90 @@ define([
|
|||
return $div;
|
||||
};
|
||||
|
||||
var redrawTrimHistory = function (cb, $div) {
|
||||
var spinner = UI.makeSpinner();
|
||||
var button = h('button.btn.btn-danger-alt', {
|
||||
disabled: 'disabled'
|
||||
}, Messages.trimHistory_button || 'delete history... xxx'); // XXX
|
||||
var currentSize = h('p', $(spinner.spinner).clone()[0]);
|
||||
var content = h('div#cp-settings-trim-container', [
|
||||
currentSize,
|
||||
button,
|
||||
spinner.ok,
|
||||
spinner.spinner
|
||||
]);
|
||||
|
||||
if (!privateData.isDriveOwned) {
|
||||
var href = privateData.origin + privateData.pathname + '#' + 'account';
|
||||
$(currentSize).html(Messages.trimHistory_needMigration || 'Need migration <a>Click</a>'); // XXX
|
||||
$(currentSize).find('a').prop('href', href).click(function (e) {
|
||||
e.preventDefault();
|
||||
$('.cp-sidebarlayout-category[data-category="account"]').click();
|
||||
});
|
||||
return void cb(content);
|
||||
}
|
||||
|
||||
Messages.trimHistory_currentSize = 'Size XXX: <b>{0}</b>'; // XXX
|
||||
|
||||
var $button = $(button);
|
||||
var size;
|
||||
var channels = [];
|
||||
nThen(function (waitFor) {
|
||||
APP.history.execCommand('GET_HISTORY_SIZE', {
|
||||
account: true,
|
||||
channels: []
|
||||
}, waitFor(function (obj) {
|
||||
if (obj && obj.error) {
|
||||
waitFor.abort();
|
||||
var error = h('div.alert.alert-danger', Messages.trimHistory_error || 'error'); // XXX
|
||||
$(content).empty().append(error);
|
||||
return;
|
||||
}
|
||||
channels = obj.channels;
|
||||
size = Number(obj.size);
|
||||
}));
|
||||
}).nThen(function () {
|
||||
if (!size || size < 1024) {
|
||||
$(currentSize).html(Messages.trimHistory_noHistory || 'no history...'); // XXX
|
||||
return;
|
||||
}
|
||||
$(currentSize).html(Messages._getKey('trimHistory_currentSize', [UIElements.prettySize(size)]));
|
||||
$button.click(function () {
|
||||
//UI.confirm(Messages.trimHistory_confirm, function (yes) {
|
||||
UI.confirmButton(button, {
|
||||
classes: 'btn-danger'
|
||||
}, function (yes) {
|
||||
if (!yes) { return; }
|
||||
|
||||
$button.remove();
|
||||
spinner.spin();
|
||||
APP.history.execCommand('TRIM_HISTORY', {
|
||||
channels: channels
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) {
|
||||
var error = h('div.alert.alert-danger', Messages.trimHistory_error || 'error'); // XXX
|
||||
$(content).empty().append(error);
|
||||
return;
|
||||
}
|
||||
spinner.hide();
|
||||
redrawTrimHistory(cb, $div);
|
||||
});
|
||||
});
|
||||
}).prop('disabled', '');
|
||||
});
|
||||
|
||||
$div.find('#cp-settings-trim-container').remove();
|
||||
cb(content);
|
||||
};
|
||||
makeBlock('trim-history', function (cb, $div) {
|
||||
if (!common.isLoggedIn()) { return; }
|
||||
// XXX settings_trimHistoryTitle, settings_trimHistoryHint, trimHistory_button, trimHistory_error
|
||||
// XXX trimHistory_success, trimHistory_confirm, trimHistory_noHistory
|
||||
// XXX trimHistory_needMigration (clickable <a> tag (no attribute) to go to the "account" part of settings)
|
||||
redrawTrimHistory(cb, $div);
|
||||
}, true);
|
||||
|
||||
/*
|
||||
create['drive-reset'] = function () {
|
||||
var $div = $('<div>', {'class': 'cp-settings-drive-reset cp-sidebarlayout-element'});
|
||||
$('<label>').text(Messages.settings_resetNewTitle).appendTo($div);
|
||||
|
@ -1238,6 +1318,7 @@ define([
|
|||
|
||||
return $div;
|
||||
};
|
||||
*/
|
||||
|
||||
// Cursor settings
|
||||
|
||||
|
@ -1638,7 +1719,10 @@ define([
|
|||
APP.$usage = $('<div>', {'class': 'usage'}).appendTo(APP.$leftside);
|
||||
var active = privateData.category || 'account';
|
||||
Object.keys(categories).forEach(function (key) {
|
||||
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
|
||||
var $category = $('<div>', {
|
||||
'class': 'cp-sidebarlayout-category',
|
||||
'data-category': key
|
||||
}).appendTo($categories);
|
||||
if (key === 'account') { $category.append($('<span>', {'class': 'fa fa-user-o'})); }
|
||||
if (key === 'drive') { $category.append($('<span>', {'class': 'fa fa-hdd-o'})); }
|
||||
if (key === 'cursor') { $category.append($('<span>', {'class': 'fa fa-i-cursor' })); }
|
||||
|
@ -1697,6 +1781,7 @@ define([
|
|||
};
|
||||
APP.toolbar = Toolbar.create(configTb);
|
||||
APP.toolbar.$rightside.hide();
|
||||
APP.history = common.makeUniversal('history');
|
||||
|
||||
// Content
|
||||
var $rightside = APP.$rightside;
|
||||
|
@ -1715,6 +1800,7 @@ define([
|
|||
categories[cat].forEach(addItem);
|
||||
}
|
||||
|
||||
|
||||
// TODO RPC
|
||||
//obj.proxy.on('change', [], refresh);
|
||||
//obj.proxy.on('remove', [], refresh);
|
||||
|
|
Loading…
Reference in New Issue