Merge branch 'staging' into ooFix

pull/1/head
yflory 5 years ago
commit 800eb67934

@ -44,10 +44,11 @@
}
/* local mixins */
@drive_icon-margin: 10px;
.drive_fileIcon {
li {
display: inline-block;
margin: 10px 10px;
margin: @drive_icon-margin;
width: 140px;
height: 140px;
text-align: center;
@ -471,6 +472,7 @@
margin-top: 10px;
}
.cp-app-drive-content-info-box {
order: 10;
line-height: 2em;
padding: 0.25em 0.75em;
margin: 1em;
@ -493,6 +495,7 @@
}
}
#cp-app-drive-content-folder {
order: 20;
li {
&.cp-app-drive-search-result {
display: flex;
@ -546,7 +549,10 @@
.cp-app-drive-element-truncated { display: none; }
}
div.cp-app-drive-content-grid {
padding: 20px;
padding: 1em;
ul {
margin: -(@drive_icon-margin);
}
.drive_fileIcon;
li {
&.cp-app-drive-element {
@ -754,6 +760,7 @@
}
& > .cp-app-drive-path {
order: 1;
width: 100%;
height: @variables_bar-height;
line-height: @variables_bar-height;
@ -774,6 +781,7 @@
flex-grow: 1;
justify-content: flex-end;
.cp-app-drive-path-element {
.tools_unselectable();
display: inline-block;
flex-shrink: 0;
max-width: 100%;
@ -788,7 +796,6 @@
overflow: hidden;
text-overflow: ellipsis;
transition: all 0.15s;
cursor: pointer;
&:first-child {
flex-shrink: 1;
@ -806,12 +813,15 @@
&.cp-app-drive-element-droppable {
background-color: @drive_droppable-bg;
}
&:not(.cp-app-drive-element-droppable):hover {
&:not(.cp-app-drive-path-separator) {
text-decoration: underline;
}
& ~ .cp-app-drive-path-element:not(.cp-app-drive-path-separator) {
text-decoration: underline;
&.cp-app-drive-path-clickable {
cursor: pointer;
&:hover {
&:not(.cp-app-drive-path-separator) {
text-decoration: underline;
}
& ~ .cp-app-drive-path-element:not(.cp-app-drive-path-separator) {
text-decoration: underline;
}
}
}
}
@ -927,5 +937,11 @@
text-transform: uppercase;
cursor: default;
}
.cp-app-drive-button {
order: 15;
margin: 0 1em;
}
}

@ -713,8 +713,8 @@
box-sizing: border-box;
cursor: auto;
width: 300px;
font-size: 30px;
padding: 0 5px;
font-size: 30px !important;
padding: 0 5px !important;
height: 43px;
}
}

@ -2,7 +2,8 @@
/* globals Buffer*/
const Quota = module.exports;
const Util = require("../common-util");
//const Util = require("../common-util");
const Keys = require("../keys");
const Package = require('../../package.json');
const Https = require("https");
@ -19,11 +20,18 @@ Quota.applyCustomLimits = function (Env) {
var customLimits = (function (custom) {
var limits = {};
Object.keys(custom).forEach(function (k) {
k.replace(/\/([^\/]+)$/, function (all, safeKey) {
var id = Util.unescapeKeyCharacters(safeKey || '');
limits[id] = custom[k];
return '';
});
var user;
try {
user = Keys.parseUser(k);
} catch (err) {
return void Env.Log.error("PARSE_CUSTOM_LIMIT_BLOCK", {
user: k,
error: err.message,
});
}
var unsafeKey = user.pubkey;
limits[unsafeKey] = custom[k];
});
return limits;
}(Env.customLimits || {}));

@ -0,0 +1 @@
module.exports = require("../www/common/common-signing-keys");

@ -9,6 +9,7 @@ var Path = require("path");
var nThen = require("nthen");
var Util = require("./lib/common-util");
var Default = require("./lib/defaults");
var Keys = require("./lib/keys");
var config = require("./lib/load-config");
@ -201,9 +202,11 @@ app.use(/^\/[^\/]*$/, Express.static('customize.dist'));
var admins = [];
try {
admins = (config.adminKeys || []).map(function (k) {
k = k.replace(/\/+$/, '');
var s = k.split('/');
return s[s.length-1].replace(/-/g, '/');
// return each admin's "unsafeKey"
// this might throw and invalidate all the other admin's keys
// but we want to get the admin's attention anyway.
// breaking everything is a good way to accomplish that.
return Keys.parseUser(k).pubkey;
});
} catch (e) { console.error("Can't parse admin keys"); }

@ -1,5 +1,5 @@
(function (window) {
var factory = function (Util, Crypto, Nacl) {
var factory = function (Util, Crypto, Keys, Nacl) {
var Hash = window.CryptPad_Hash = {};
var uint8ArrayToHex = Util.uint8ArrayToHex;
@ -92,9 +92,7 @@ var factory = function (Util, Crypto, Nacl) {
}
};
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
Hash.getPublicSigningKeyString = Keys.serialize;
var fixDuplicateSlashes = function (s) {
return s.replace(/\/+/g, '/');
@ -568,14 +566,20 @@ Version 1
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require("./common-util"), require("chainpad-crypto"), require("tweetnacl/nacl-fast"));
module.exports = factory(
require("./common-util"),
require("chainpad-crypto"),
require("./common-signing-keys"),
require("tweetnacl/nacl-fast")
);
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/bower_components/chainpad-crypto/crypto.js',
'/common/common-signing-keys.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function (Util, Crypto) {
return factory(Util, Crypto, window.nacl);
], function (Util, Crypto, Keys) {
return factory(Util, Crypto, Keys, window.nacl);
});
} else {
// unsupported initialization

@ -0,0 +1,89 @@
(function () {
var factory = function () {
var Keys = {};
/* Parse the new format of "Signing Public Keys".
If anything about the input is found to be invalid, return;
this will fall back to the old parsing method
*/
var parseNewUser = function (userString) {
if (!/^\[.*?@.*\]$/.test(userString)) { return; }
var temp = userString.slice(1, -1);
var domain, username, pubkey;
temp = temp
.replace(/\/([a-zA-Z0-9+-]{43}=)$/, function (all, k) {
pubkey = k.replace(/-/g, '/');
return '';
});
if (!pubkey) { return; }
var index = temp.lastIndexOf('@');
if (index < 1) { return; }
domain = temp.slice(index + 1);
username = temp.slice(0, index);
return {
domain: domain,
user: username,
pubkey: pubkey
};
};
var isValidUser = function (parsed) {
if (!parsed) { return; }
if (!(parsed.domain && parsed.user && parsed.pubkey)) { return; }
return true;
};
Keys.parseUser = function (user) {
var parsed = parseNewUser(user);
if (isValidUser(parsed)) { return parsed; }
var domain, username, pubkey;
user.replace(/^https*:\/\/([^\/]+)\/user\/#\/1\/([^\/]+)\/([a-zA-Z0-9+-]{43}=)$/,
function (a, d, u, k) {
domain = d;
username = u;
pubkey = k.replace(/-/g, '/');
return '';
});
if (!domain) { throw new Error("Could not parse user id [" + user + "]"); }
return {
domain: domain,
user: username,
pubkey: pubkey
};
};
/*
0. usernames may contain spaces or many other wacky characters, so enclose the whole thing in square braces so we know its boundaries. If the formatted string does not include these we know it is either a _v1 public key string_ or _an incomplete string_. Start parsing by removing them.
1. public keys should have a fixed length, so slice them off of the end of the string.
2. domains cannot include `@`, so find the last occurence of it in the signing key and slice everything thereafter.
3. the username is everything before the `@`.
*/
Keys.serialize = function (origin, username, pubkey) {
return '[' +
username +
'@' +
origin.replace(/https*:\/\//, '') +
'/' +
pubkey.replace(/\//g, '-') +
']';
// return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
return Keys;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([], factory);
}
}());

@ -114,7 +114,6 @@ define([
var $trashEmptyIcon = $('<span>', {"class": "fa fa-trash-o"});
//var $collapseIcon = $('<span>', {"class": "fa fa-minus-square-o cp-app-drive-icon-expcol"});
var $expandIcon = $('<span>', {"class": "fa fa-plus-square-o cp-app-drive-icon-expcol"});
var $emptyTrashIcon = $('<button>', {"class": "fa fa-ban"});
var $listIcon = $('<button>', {"class": "fa fa-list"});
var $gridIcon = $('<button>', {"class": "fa fa-th-large"});
var $sortAscIcon = $('<span>', {"class": "fa fa-angle-up sortasc"});
@ -447,19 +446,19 @@ define([
h('li', h('a.cp-app-drive-context-delete.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faTrash,
}, Messages.fc_delete)),
}, Messages.fc_delete)), // "Move to trash"
h('li', h('a.cp-app-drive-context-deleteowned.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faDelete,
}, Messages.fc_delete_owned)),
}, Messages.fc_delete_owned)), // XXX update key? "Delete from the server"
h('li', h('a.cp-app-drive-context-remove.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faDelete,
}, Messages.fc_remove)),
'data-icon': faTrash,
}, Messages.fc_remove)), // XXX update key? "Remove from your CryptDrive"
h('li', h('a.cp-app-drive-context-removesf.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': faDelete,
}, Messages.fc_remove_sharedfolder)),
'data-icon': faTrash,
}, Messages.fc_remove_sharedfolder)), // XXX update key? "Remove"
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-properties.dropdown-item', {
'tabindex': '-1',
@ -1280,18 +1279,12 @@ define([
// If we're not in the trash nor in a shared folder, hide "remove"
if (!manager.isInSharedFolder(path)
&& !$element.is('.cp-app-drive-element-sharedf')) {
// This isn't a shared folder: can't delete shared folder
hide.push('removesf');
} else if (type === "tree") {
// This is a shared folder or an element inside a shsared folder
// ==> can't move to trash
hide.push('delete');
// Don't hide the deleteowned link if the element is a shared folder and
// it is owned
if (manager.isInSharedFolder(path) ||
!$element.is('.cp-app-drive-element-owned')) {
hide.push('deleteowned');
} else {
// This is a shared folder and it is owned
hide.push('removesf');
}
}
if ($element.closest('[data-ro]').length) {
editable = false;
@ -1341,7 +1334,7 @@ define([
break;
}
case 'trash': {
show = ['remove', 'restore', 'properties'];
show = ['remove', 'deleteowned', 'restore', 'properties'];
}
}
@ -1658,22 +1651,11 @@ define([
if (paths) {
paths.forEach(function (p) { pathsList.push(p.path); });
}
var hasOwned = pathsList.some(function (p) {
// NOTE: Owned pads in shared folders won't be removed from the server
// so we don't have to check, we can use the default message
if (manager.isInSharedFolder(p)) { return false; }
var el = manager.find(p);
var data = manager.isSharedFolder(el) ? manager.getSharedFolderData(el)
: manager.getFileData(el);
return data.owners && data.owners.indexOf(edPublic) !== -1;
});
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]);
if (pathsList.length === 1) {
msg = hasOwned ? Messages.fm_deleteOwnedPad : Messages.fm_removePermanentlyDialog;
} else if (hasOwned) {
msg = msg + '<br><em>' + Messages.fm_removePermanentlyNote + '</em>';
msg = Messages.fm_removePermanentlyDialog;
}
// XXX update key to tell the user that these pads will still be avialble to other users
UI.confirm(msg, function(res) {
$(window).focus();
if (!res) { return; }
@ -2397,17 +2379,55 @@ define([
$gridButton.attr('title', Messages.fm_viewGridButton);
$container.append($listButton).append($gridButton);
};
var createEmptyTrashButton = function ($container) {
var $button = $emptyTrashIcon.clone();
$button.addClass('cp-app-drive-toolbar-emptytrash');
$button.attr('title', Messages.fc_empty);
$button.click(function () {
UI.confirm(Messages.fm_emptyTrashDialog, function(res) {
if (!res) { return; }
manager.emptyTrash(refresh);
var emptyTrashModal = function () {
var ownedInTrash = manager.ownedInTrash();
var hasOwned = Array.isArray(ownedInTrash) && ownedInTrash.length;
Messages.fm_emptyTrashOwned = "Your trash contains documents you own. You can remove them for everyone or only from your drive"; // XXX
var content = h('p', [
Messages.fm_emptyTrashDialog,
hasOwned ? h('br') : undefined,
hasOwned ? Messages.fm_emptyTrashOwned : undefined // XXX update UI?
]);
var buttons = [{
className: 'cancel',
name: Messages.cancelButton,
onClick: function () {},
keys: [27]
}];
if (hasOwned) {
buttons.push({
className: 'secondary',
name: Messages.fc_delete_owned,
onClick: function () {
manager.emptyTrash(true, refresh);
},
keys: []
});
}
buttons.push({
className: 'primary',
// XXX fc_remove: Remove from your CryptDrive
// We may want to use a new key here
name: hasOwned ? Messages.fc_remove : Messages.okButton,
onClick: function () {
manager.emptyTrash(false, refresh);
},
keys: [13]
});
var m = UI.dialog.customModal(content, {
buttons: buttons
});
UI.openCustomModal(m);
};
var createEmptyTrashButton = function () {
var button = h('button.btn.btn-danger', [
h('i.fa.'+faTrash),
h('span', Messages.fc_empty)
]);
$(button).click(function () {
emptyTrashModal();
});
$container.append($button);
return $(h('div.cp-app-drive-button', button));
};
// Get the upload options
@ -3145,6 +3165,8 @@ define([
var displayTrashRoot = function ($list, $folderHeader, $fileHeader) {
var filesList = [];
var root = files[TRASH];
var isEmpty = true;
// Elements in the trash are JS arrays (several elements can have the same name)
Object.keys(root).forEach(function (key) {
if (!Array.isArray(root[key])) {
@ -3160,7 +3182,14 @@ define([
name: key
});
});
isEmpty = false;
});
if (!isEmpty) {
var $empty = createEmptyTrashButton();
$content.append($empty);
}
var sortedFolders = sortTrashElements(true, filesList, null, !getSortFolderDesc());
var sortedFiles = sortTrashElements(false, filesList, APP.store[SORT_FILE_BY], !getSortFileDesc());
if (manager.hasSubfolder(root, true)) { $list.append($folderHeader); }
@ -3555,9 +3584,7 @@ define([
createToolbar(path);
if (inTrash || isInRoot) {
createTitle($content, path);
}
if (!isSearch) { createTitle($content, path); }
var $info = createInfoBox(path);
var $dirContent = $('<div>', {id: FOLDER_CONTENT_ID});
@ -3569,9 +3596,6 @@ define([
}
createViewModeButton(APP.toolbar.$bottomR);
}
if (inTrash) {
createEmptyTrashButton(APP.toolbar.$bottomR);
}
var $list = $('<ul>').appendTo($dirContent);
@ -3587,7 +3611,6 @@ define([
}
$content.data('readOnlyFolder', readOnlyFolder);
// NewButton can be undefined if we're in read only mode
if (!readOnlyFolder) {
createNewButton(isInRoot, APP.toolbar.$bottomL);
}
@ -3599,6 +3622,7 @@ define([
}
*/
if (APP.mobile()) {
var $context = $('<button>', {
id: 'cp-app-drive-toolbar-context-mobile'
@ -3647,7 +3671,7 @@ define([
// ANON_SHARED_FOLDER
displaySharedFolder($list);
} else {
$dirContent.contextmenu(openContextMenu('content'));
if (!inTrash) { $dirContent.contextmenu(openContextMenu('content')); }
if (manager.hasSubfolder(root)) { $list.append($folderHeader); }
// display sub directories
var keys = Object.keys(root);
@ -3731,6 +3755,10 @@ define([
e.stopPropagation();
APP.displayDirectory(path);
});
if (isSharedFolder) {
var sfData = manager.getSharedFolderData(isSharedFolder);
_addOwnership($elementRow, $(), sfData);
}
var $element = $('<li>').append($elementRow);
if (draggable) { $elementRow.attr('draggable', true); }
if (collapsable) {
@ -3813,17 +3841,17 @@ define([
var sfId = manager.isInSharedFolder(newPath) || (isSharedFolder && root[key]);
var $icon, isCurrentFolder, subfolder;
if (isSharedFolder) {
var fId = root[key];
// Fix path
newPath.push(manager.user.userObject.ROOT);
isCurrentFolder = manager.comparePath(newPath, currentPath);
// Subfolders?
var newRoot = manager.folders[fId].proxy[manager.user.userObject.ROOT];
var newRoot = manager.folders[sfId].proxy[manager.user.userObject.ROOT];
subfolder = manager.hasSubfolder(newRoot);
// Fix name
key = manager.getSharedFolderData(fId).title;
key = manager.getSharedFolderData(sfId).title;
// Fix icon
$icon = isCurrentFolder ? $sharedFolderOpenedIcon : $sharedFolderIcon;
isSharedFolder = sfId;
} else {
var isEmpty = manager.isFolderEmpty(root[key]);
subfolder = manager.hasSubfolder(root[key]);
@ -4029,7 +4057,7 @@ define([
UI.confirm(msgD, function(res) {
$(window).focus();
if (!res) { return; }
manager.delete(pathsList, function () {
manager.deleteOwned(pathsList, function () {
pathsList.forEach(LS.removeFoldersOpened);
removeSelected();
refresh();
@ -4425,10 +4453,7 @@ define([
log(Messages.fm_forbidden);
return;
}
UI.confirm(Messages.fm_emptyTrashDialog, function(res) {
if (!res) { return; }
manager.emptyTrash(refresh);
});
emptyTrashModal();
}
else if ($this.hasClass("cp-app-drive-context-remove")) {
return void deletePaths(paths);

@ -898,6 +898,27 @@ define([
$d.append(changePass);
}
}
if (owned) {
var deleteOwned = h('button.btn.btn-danger-alt', Messages.fc_delete_owned);
var spinner = UI.makeSpinner();
UI.confirmButton(deleteOwned, {
classes: 'btn-danger'
}, function () {
spinner.spin();
sframeChan.query('Q_DELETE_OWNED', {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
channel: data.channel
}, function (err, obj) {
spinner.done();
if (err || (obj && obj.error)) { UI.warn(Messages.error); }
});
});
$d.append(h('br'));
$d.append(h('div', [
deleteOwned,
spinner.spinner
]));
}
return $d;
};
var drawRight = function () {

@ -224,13 +224,16 @@ define([
var now = function () { return +new Date(); };
var sortCpIndex = function (hashes) {
return Object.keys(hashes).map(Number).sort(function (a, b) {
return a-b;
});
};
var getLastCp = function (old, i) {
var hashes = old ? oldHashes : content.hashes;
if (!hashes || !Object.keys(hashes).length) { return {}; }
i = i || 0;
var idx = Object.keys(hashes).map(Number).sort(function (a, b) {
return a-b;
});
var idx = sortCpIndex(hashes);
var lastIndex = idx[idx.length - 1 - i];
var last = JSON.parse(JSON.stringify(hashes[lastIndex]));
return last;
@ -313,7 +316,7 @@ define([
return void UI.alert(Messages.oo_saveError);
}
// Get the last cp idx
var all = Object.keys(content.hashes || {}).map(Number).sort();
var all = sortCpIndex(content.hashes || {});
var current = all[all.length - 1] || 0;
// Get the expected cp idx
var _i = Math.floor(ev.index / CHECKPOINT_INTERVAL);

@ -2382,10 +2382,10 @@ define([
unpin: unpin,
loadSharedFolder: loadSharedFolder,
settings: proxy.settings,
removeOwnedChannel: function (channel, cb) { Store.removeOwnedChannel('', channel, cb); },
Store: Store
}, {
outer: true,
removeOwnedChannel: function (channel, cb) { Store.removeOwnedChannel('', channel, cb); },
edPublic: store.proxy.edPublic,
loggedIn: store.loggedIn,
log: function (msg) {

@ -285,9 +285,6 @@ define([
settings: {
drive: Util.find(ctx.store, ['proxy', 'settings', 'drive'])
},
Store: ctx.Store
}, {
outer: true,
removeOwnedChannel: function (channel, cb) {
var data;
if (typeof(channel) === "object") {
@ -301,6 +298,9 @@ define([
}
ctx.Store.removeOwnedChannel('', data, cb);
},
Store: ctx.Store
}, {
outer: true,
edPublic: keys.drive.edPublic,
loggedIn: true,
log: function (msg) {

@ -3,9 +3,8 @@ define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-feedback.js',
'/customize/messages.js'
], function (AppConfig, Util, Hash, Realtime, Feedback, Messages) {
], function (AppConfig, Util, Hash, Realtime, Messages) {
var module = {};
var clone = function (o) {
@ -14,12 +13,8 @@ define([
};
module.init = function (config, exp, files) {
var removeOwnedChannel = config.removeOwnedChannel || function () {
console.error("removeOwnedChannel was not provided");
};
var loggedIn = config.loggedIn;
var sharedFolder = config.sharedFolder;
var edPublic = config.edPublic;
var readOnly = config.readOnly;
@ -134,40 +129,17 @@ define([
};
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
// FILES_DATA. If there are owned pads, remove them from server too.
// FILES_DATA.
exp.checkDeletedFiles = function (cb) {
if (!loggedIn && !config.testMode) { return void cb(); }
if (readOnly) { return void cb('EFORBIDDEN'); }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
var ownedRemoved = [];
exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel;
// If trying to remove an owned pad, remove it from server also
if (!sharedFolder && fd.owners && fd.owners.indexOf(edPublic) !== -1
&& channelId) {
if (channelId) { ownedRemoved.push(channelId); }
Feedback.send('REMOVE_OWNED_CHANNEL');
removeOwnedChannel(channelId, function (obj) {
if (obj && obj.error) {
// If the error is that the file is already removed, nothing to
// report, it's a normal behavior (pad expired probably)
if (obj.error.code === 'ENOENT') { return; }
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId + '|' + obj.error, true);
}
});
// Also remove the realtime channel for onlyoffice
if (fd.rtChannel) {
removeOwnedChannel(fd.rtChannel, function () {});
}
}
if (fd.lastVersion) { toClean.push(Hash.hrefToHexChannelId(fd.lastVersion)); }
if (fd.rtChannel) { toClean.push(fd.rtChannel); }
if (channelId) { toClean.push(channelId); }
@ -180,7 +152,7 @@ define([
}
});
if (!toClean.length) { return void cb(); }
cb(null, toClean, ownedRemoved);
cb(null, toClean);
};
var deleteHrefs = function (ids) {
if (readOnly) { return; }

@ -4,8 +4,9 @@ define([
'/common/common-hash.js',
'/common/outer/sharedfolder.js',
'/customize/messages.js',
'/common/common-feedback.js',
'/bower_components/nthen/index.js',
], function (UserObject, Util, Hash, SF, Messages, nThen) {
], function (UserObject, Util, Hash, SF, Messages, Feedback, nThen) {
var getConfig = function (Env) {
@ -397,26 +398,13 @@ define([
// Copy the elements to the new location
var toCopy = _getCopyFromPaths(Env, resolved.main, Env.user.userObject);
var newUserObject = newResolved.userObject;
var ownedPads = [];
toCopy.forEach(function (obj) {
newUserObject.copyFromOtherDrive(newResolved.path, obj.el, obj.data, obj.key);
var _owned = Object.keys(obj.data).filter(function (id) {
var owners = obj.data[id].owners;
return _ownedByMe(Env, owners);
});
Array.prototype.push.apply(ownedPads, _owned);
});
if (copy) { return; }
if (resolved.main.length) {
var rootPath = resolved.main[0].slice();
rootPath.pop();
ownedPads = Util.deduplicateString(ownedPads);
ownedPads.forEach(function (id) {
Env.user.userObject.add(Number(id), rootPath);
});
// Remove the elements from the old location (without unpinning)
Env.user.userObject.delete(resolved.main, waitFor()); // FIXME waitFor() is called synchronously
}
@ -703,23 +691,22 @@ define([
});
};
// Delete permanently some pads or folders
var _delete = function (Env, data, cb) {
data = data || {};
var resolved = _resolvePaths(Env, data.paths);
var resolved = data.resolved || _resolvePaths(Env, data.paths);
if (!resolved.main.length && !Object.keys(resolved.folders).length) {
return void cb({error: 'E_NOTFOUND'});
}
// Deleted or password changed for a shared folder
if (data.paths.length === 1 && data.paths[0][0] === UserObject.SHARED_FOLDERS_TEMP) {
if (data.paths && data.paths.length === 1 &&
data.paths[0][0] === UserObject.SHARED_FOLDERS_TEMP) {
var temp = Util.find(Env, ['user', 'proxy', UserObject.SHARED_FOLDERS_TEMP]);
delete temp[data.paths[0][1]];
return void Env.onSync(cb);
}
var toUnpin = [];
var ownedRemoved;
nThen(function (waitFor)  {
// Delete paths from the main drive and get the list of pads to unpin
// We also get the list of owned pads that were removed
@ -742,8 +729,8 @@ define([
});
});
}
uo.delete(resolved.main, waitFor(function (err, _toUnpin, _ownedRemoved) {
ownedRemoved = _ownedRemoved;
uo.delete(resolved.main, waitFor(function (err, _toUnpin/*, _ownedRemoved*/) {
//ownedRemoved = _ownedRemoved;
if (!Env.unpinPads || !_toUnpin) { return; }
Array.prototype.push.apply(toUnpin, _toUnpin);
}));
@ -752,7 +739,7 @@ define([
// Check if removed owned pads are duplicated in some shared folders
// If that's the case, we have to remove them from the shared folders too
// We can do that by adding their paths to the list of pads to remove from shared folders
if (ownedRemoved) {
/*if (ownedRemoved) {
var ids = _findChannels(Env, ownedRemoved);
ids.forEach(function (id) {
var paths = findFile(Env, id);
@ -765,7 +752,7 @@ define([
}
});
});
}
}*/
// Delete paths from the shared folders
Object.keys(resolved.folders).forEach(function (id) {
Env.folders[id].userObject.delete(resolved.folders[id], waitFor(function (err, _toUnpin) {
@ -805,19 +792,121 @@ define([
cb();
});
};
// Empty the trash (main drive only)
var _emptyTrash = function (Env, data, cb) {
Env.user.userObject.emptyTrash(function (err, toClean) {
cb();
// Delete permanently some pads or folders
var _deleteOwned = function (Env, data, cb) {
data = data || {};
var resolved = _resolvePaths(Env, data.paths || []);
if (!data.channel && !resolved.main.length && !Object.keys(resolved.folders).length) {
return void cb({error: 'E_NOTFOUND'});
}
var toDelete = {
main: [],
folders: {}
};
var todo = function (channel, uo, p, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var chan = channel;
if (!chan && uo) {
var el = uo.find(p);
if (!uo.isFile(el) && !uo.isSharedFolder(el)) { return; }
var data = uo.isFile(el) ? uo.getFileData(el) : getSharedFolderData(Env, el);
chan = data.channel;
}
Env.removeOwnedChannel(chan, function (obj) {
// If the error is that the file is already removed, nothing to
// report, it's a normal behavior (pad expired probably)
if (obj && obj.error && obj.error.code !== "ENOENT") {
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error, chan);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + chan + '|' + obj.error, true);
return void cb();
}
// No error: delete the pads and all its copies from our drive and shared folders
var ids = _findChannels(Env, [chan]);
ids.forEach(function (id) {
var paths = findFile(Env, id);
var _resolved = _resolvePaths(Env, paths);
// Check if need need to restore a full hash (hidden hash deleted from drive)
if (!Array.isArray(toClean)) { return; }
var toCheck = Util.deduplicateString(toClean);
toCheck.forEach(function (chan) {
Env.Store.checkDeletedPad(chan);
Array.prototype.push.apply(toDelete.main, _resolved.main);
Object.keys(_resolved.folders).forEach(function (fId) {
if (toDelete.folders[fId]) {
Array.prototype.push.apply(toDelete.folders[fId], _resolved.folders[fId]);
} else {
toDelete.folders[fId] = _resolved.folders[fId];
}
});
});
cb();
});
};
nThen(function (w) {
// Delete owned pads from the server
if (data.channel) {
todo(data.channel, null, null, w());
}
resolved.main.forEach(function (p) {
todo(null, Env.user.userObject, p, w());
});
Object.keys(resolved.folders).forEach(function (id) {
var uo = Env.folders[id].userObject;
resolved.folders[id].forEach(function (p) {
todo(null, uo, p, w());
});
});
}).nThen(function () {
// Remove deleted pads from the drive
_delete(Env, { resolved: toDelete }, cb);
});
};
// Empty the trash (main drive only)
var _emptyTrash = function (Env, data, cb) {
nThen(function (waitFor) {
if (data && data.deleteOwned) {
// Delete owned pads in the trash from the server
var owned = Env.user.userObject.ownedInTrash(function (owners) {
return _ownedByMe(Env, owners);
});
owned.forEach(function (chan) {
Env.removeOwnedChannel(chan, waitFor(function (obj) {
// If the error is that the file is already removed, nothing to
// report, it's a normal behavior (pad expired probably)
if (obj && obj.error && obj.error.code !== "ENOENT") {
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error, chan);
Feedback.send('ERROR_EMPTYTRASH_OWNED=' + chan + '|' + obj.error, true);
}
console.warn('DELETED', chan);
}));
});
}
// Empty the trash
Env.user.userObject.emptyTrash(waitFor(function (err, toClean) {
cb();
// Don't block nThen for the lower-priority tasks
setTimeout(function () {
// Unpin deleted pads if needed
// Check if we need to restore a full hash (hidden hash deleted from drive)
if (!Array.isArray(toClean)) { return; }
var toCheck = Util.deduplicateString(toClean);
var toUnpin = [];
toCheck.forEach(function (channel) {
// Check unpin
var data = findChannel(Env, channel, true);
if (!data.length) { toUnpin.push(channel); }
// Check hidden hash
Env.Store.checkDeletedPad(channel);
});
Env.unpinPads(toUnpin, function () {});
});
}));
}).nThen(cb);
};
// Rename files or folders
var _rename = function (Env, data, cb) {
data = data || {};
@ -868,6 +957,8 @@ define([
_convertFolderToSharedFolder(Env, data, cb); break;
case 'delete':
_delete(Env, data, cb); break;
case 'deleteOwned':
_deleteOwned(Env, data, cb); break;
case 'emptyTrash':
_emptyTrash(Env, data, cb); break;
case 'rename':
@ -1076,14 +1167,6 @@ define([
if (e) { error = e; return; }
uo.add(id, p);
}));
if (uo.id && _ownedByMe(Env, pad.owners)) {
// Creating an owned pad in a shared folder:
// We must add a copy in the user's personnal drive
Env.user.userObject.pushData(pad, waitFor(function (e, id) {
if (e) { error = e; return; }
Env.user.userObject.add(id, ['root']);
}));
}
}).nThen(function () {
cb(error);
});
@ -1101,6 +1184,7 @@ define([
unpinPads: data.unpin,
onSync: data.onSync,
Store: data.Store,
removeOwnedChannel: data.removeOwnedChannel,
loadSharedFolder: data.loadSharedFolder,
cfg: uoConfig,
edPublic: data.edPublic,
@ -1140,6 +1224,7 @@ define([
getChannelsList: callWithEnv(getChannelsList),
addPad: callWithEnv(addPad),
delete: callWithEnv(_delete),
deleteOwned: callWithEnv(_deleteOwned),
// Tools
findChannel: callWithEnv(findChannel),
findHref: callWithEnv(findHref),
@ -1175,10 +1260,12 @@ define([
}
}, cb);
};
var emptyTrashInner = function (Env, cb) {
var emptyTrashInner = function (Env, deleteOwned, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "emptyTrash",
data: null
data: {
deleteOwned: deleteOwned
}
}, cb);
};
var addFolderInner = function (Env, path, name, cb) {
@ -1228,6 +1315,14 @@ define([
}
}, cb);
};
var deleteOwnedInner = function (Env, paths, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "deleteOwned",
data: {
paths: paths,
}
}, cb);
};
var restoreInner = function (Env, path, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "restore",
@ -1381,6 +1476,11 @@ define([
}
return Env.user.userObject.hasFile(el, trashRoot);
};
var ownedInTrash = function (Env) {
return Env.user.userObject.ownedInTrash(function (owners) {
return _ownedByMe(Env, owners);
});
};
var isDuplicateOwned = _isDuplicateOwned;
@ -1416,6 +1516,7 @@ define([
restoreSharedFolder: callWithEnv(restoreSharedFolderInner),
convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner),
delete: callWithEnv(deleteInner),
deleteOwned: callWithEnv(deleteOwnedInner),
restore: callWithEnv(restoreInner),
setFolderData: callWithEnv(setFolderDataInner),
// Tools
@ -1435,6 +1536,7 @@ define([
isInSharedFolder: callWithEnv(isInSharedFolder),
getUserObjectPath: callWithEnv(getUserObjectPath),
isDuplicateOwned: callWithEnv(isDuplicateOwned),
ownedInTrash: callWithEnv(ownedInTrash),
// Generic
isValidDrive: callWithEnv(isValidDrive),
isFile: callWithEnv(isFile),

@ -808,6 +808,15 @@ define([
Cryptpad.changePadPassword(Cryptget, Crypto, data, cb);
});
sframeChan.on('Q_DELETE_OWNED', function (data, cb) {
Cryptpad.userObjectCommand({
cmd: 'deleteOwned',
teamId: data.teamId,
data: {
channel: data.channel
}
}, cb);
});
};
addCommonRpc(sframeChan, isSafe);

@ -717,7 +717,7 @@ MessengerUI, Messages) {
var displayInput = function () {
if (toolbar.connected === false) { return; }
$input.width(Math.max($text.width(), 300)+'px');
$input.width(Math.max(($text.width() + 10), 300)+'px');
$text.hide();
//$pencilIcon.css('display', 'none');
var inputVal = suggestName() || "";

@ -844,6 +844,12 @@ define([
files[TRASH] = {};
exp.checkDeletedFiles(cb);
};
exp.ownedInTrash = function (isOwned) {
return getFiles([TRASH]).map(function (id) {
var data = exp.getFileData(id);
return isOwned(data.owners) ? data.channel : undefined;
}).filter(Boolean);
};
// RENAME
exp.rename = function (path, newName, cb) {

@ -761,6 +761,9 @@ define([
var isTop = $el.attr('data-top');
var boardId = $el.closest('.kanban-board').attr("data-id");
var $item = $('<div>', {'class': 'kanban-item new-item'});
if (isTop) {
$item.addClass('item-top');
}
var $input = getInput().val(name).appendTo($item);
kanban.addForm(boardId, $item[0], isTop);
$input.focus();
@ -1059,6 +1062,7 @@ define([
} else if (!$el.length) {
$el = $container.find('[data-eid="'+id+'"]');
}
var isTop = $el && $el.hasClass('item-top');
if (!$el.length) { return; }
var $input = $el.find('input');
if (!$input.length) { return; }
@ -1077,6 +1081,7 @@ define([
value: val,
start: start,
end: end,
isTop: isTop,
oldValue: oldVal
};
} catch (e) {
@ -1092,8 +1097,10 @@ define([
// An item was being added: add a new item
if (id === "new" && !data.oldValue) {
var $newBoard = $('.kanban-board[data-id="'+data.newBoard+'"]');
$newBoard.find('.kanban-title-button').click();
var $newInput = $newBoard.find('.kanban-item:last-child input');
var topSelector = ':not([data-top])';
if (data.isTop) { topSelector = '[data-top]'; }
$newBoard.find('.kanban-title-button' + topSelector).click();
var $newInput = $newBoard.find('.kanban-item.new-item input');
$newInput.val(data.value);
$newInput[0].selectionStart = data.start;
$newInput[0].selectionEnd = data.end;

@ -68,7 +68,7 @@
};
// Register the command.
var command = editor.plugins.comments.command = editor.addCommand('comment', {
editor.plugins.comments.command = editor.addCommand('comment', {
exec: function(editor) {
if (editor.readOnly) { return; }
editor.focus();

@ -1001,13 +1001,14 @@ define([
YAY!
*/
CKEDITOR.dom.element.prototype.setHtml = function(a){
Ckeditor.dom.element.prototype.setHtml = function(a){
if (/callFunction/.test(a)) {
a = a.replace(/on(mousedown|blur|keydown|focus|click|dragstart)/g, function (value) {
return 'o' + value;
});
}
return this.$.innerHTML=a;
this.$.innerHTML = a;
return a;
};
module.ckeditor = editor = Ckeditor.replace('editor1', {

@ -168,7 +168,7 @@ define([
var publicKey = privateData.edPublic;
if (publicKey) {
var $key = $('<div>', { 'class': 'cp-sidebarlayout-element' }).appendTo($div);
var userHref = Hash.getUserHrefFromKeys(privateData.origin, accountName, publicKey);
var userHref = Hash.getPublicSigningKeyString(privateData.origin, accountName, publicKey);
var $pubLabel = $('<span>', { 'class': 'label' })
.text(Messages.settings_publicSigningKey);
$key.append($pubLabel).append(UI.dialog.selectable(userHref));

@ -910,7 +910,7 @@ define([
var name = team.name;
if (publicKey) {
var $key = $('<div>', {'class': 'cp-sidebarlayout-element'}).appendTo($div);
var userHref = Hash.getUserHrefFromKeys(privateData.origin, name, publicKey);
var userHref = Hash.getPublicSigningKeyString(privateData.origin, name, publicKey);
var $pubLabel = $('<span>', {'class': 'label'})
.text(Messages.settings_publicSigningKey);
$key.append($pubLabel).append(UI.dialog.selectable(userHref));

Loading…
Cancel
Save