Merge branch 'staging' into ooFix

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

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

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

@ -2,7 +2,8 @@
/* globals Buffer*/ /* globals Buffer*/
const Quota = module.exports; const Quota = module.exports;
const Util = require("../common-util"); //const Util = require("../common-util");
const Keys = require("../keys");
const Package = require('../../package.json'); const Package = require('../../package.json');
const Https = require("https"); const Https = require("https");
@ -19,11 +20,18 @@ Quota.applyCustomLimits = function (Env) {
var customLimits = (function (custom) { var customLimits = (function (custom) {
var limits = {}; var limits = {};
Object.keys(custom).forEach(function (k) { Object.keys(custom).forEach(function (k) {
k.replace(/\/([^\/]+)$/, function (all, safeKey) { var user;
var id = Util.unescapeKeyCharacters(safeKey || ''); try {
limits[id] = custom[k]; user = Keys.parseUser(k);
return ''; } 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; return limits;
}(Env.customLimits || {})); }(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 nThen = require("nthen");
var Util = require("./lib/common-util"); var Util = require("./lib/common-util");
var Default = require("./lib/defaults"); var Default = require("./lib/defaults");
var Keys = require("./lib/keys");
var config = require("./lib/load-config"); var config = require("./lib/load-config");
@ -201,9 +202,11 @@ app.use(/^\/[^\/]*$/, Express.static('customize.dist'));
var admins = []; var admins = [];
try { try {
admins = (config.adminKeys || []).map(function (k) { admins = (config.adminKeys || []).map(function (k) {
k = k.replace(/\/+$/, ''); // return each admin's "unsafeKey"
var s = k.split('/'); // this might throw and invalidate all the other admin's keys
return s[s.length-1].replace(/-/g, '/'); // 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"); } } catch (e) { console.error("Can't parse admin keys"); }

@ -1,5 +1,5 @@
(function (window) { (function (window) {
var factory = function (Util, Crypto, Nacl) { var factory = function (Util, Crypto, Keys, Nacl) {
var Hash = window.CryptPad_Hash = {}; var Hash = window.CryptPad_Hash = {};
var uint8ArrayToHex = Util.uint8ArrayToHex; var uint8ArrayToHex = Util.uint8ArrayToHex;
@ -92,9 +92,7 @@ var factory = function (Util, Crypto, Nacl) {
} }
}; };
Hash.getUserHrefFromKeys = function (origin, username, pubkey) { Hash.getPublicSigningKeyString = Keys.serialize;
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
var fixDuplicateSlashes = function (s) { var fixDuplicateSlashes = function (s) {
return s.replace(/\/+/g, '/'); return s.replace(/\/+/g, '/');
@ -568,14 +566,20 @@ Version 1
}; };
if (typeof(module) !== 'undefined' && module.exports) { 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)) { } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([ define([
'/common/common-util.js', '/common/common-util.js',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/common/common-signing-keys.js',
'/bower_components/tweetnacl/nacl-fast.min.js' '/bower_components/tweetnacl/nacl-fast.min.js'
], function (Util, Crypto) { ], function (Util, Crypto, Keys) {
return factory(Util, Crypto, window.nacl); return factory(Util, Crypto, Keys, window.nacl);
}); });
} else { } else {
// unsupported initialization // 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 $trashEmptyIcon = $('<span>', {"class": "fa fa-trash-o"});
//var $collapseIcon = $('<span>', {"class": "fa fa-minus-square-o cp-app-drive-icon-expcol"}); //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 $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 $listIcon = $('<button>', {"class": "fa fa-list"});
var $gridIcon = $('<button>', {"class": "fa fa-th-large"}); var $gridIcon = $('<button>', {"class": "fa fa-th-large"});
var $sortAscIcon = $('<span>', {"class": "fa fa-angle-up sortasc"}); 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', { h('li', h('a.cp-app-drive-context-delete.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1', 'tabindex': '-1',
'data-icon': faTrash, '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', { h('li', h('a.cp-app-drive-context-deleteowned.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1', 'tabindex': '-1',
'data-icon': faDelete, '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', { h('li', h('a.cp-app-drive-context-remove.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1', 'tabindex': '-1',
'data-icon': faDelete, 'data-icon': faTrash,
}, Messages.fc_remove)), }, 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', { h('li', h('a.cp-app-drive-context-removesf.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1', 'tabindex': '-1',
'data-icon': faDelete, 'data-icon': faTrash,
}, Messages.fc_remove_sharedfolder)), }, Messages.fc_remove_sharedfolder)), // XXX update key? "Remove"
$separator.clone()[0], $separator.clone()[0],
h('li', h('a.cp-app-drive-context-properties.dropdown-item', { h('li', h('a.cp-app-drive-context-properties.dropdown-item', {
'tabindex': '-1', 'tabindex': '-1',
@ -1280,18 +1279,12 @@ define([
// If we're not in the trash nor in a shared folder, hide "remove" // If we're not in the trash nor in a shared folder, hide "remove"
if (!manager.isInSharedFolder(path) if (!manager.isInSharedFolder(path)
&& !$element.is('.cp-app-drive-element-sharedf')) { && !$element.is('.cp-app-drive-element-sharedf')) {
// This isn't a shared folder: can't delete shared folder
hide.push('removesf'); hide.push('removesf');
} else if (type === "tree") { } else if (type === "tree") {
// This is a shared folder or an element inside a shsared folder
// ==> can't move to trash
hide.push('delete'); 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) { if ($element.closest('[data-ro]').length) {
editable = false; editable = false;
@ -1341,7 +1334,7 @@ define([
break; break;
} }
case 'trash': { case 'trash': {
show = ['remove', 'restore', 'properties']; show = ['remove', 'deleteowned', 'restore', 'properties'];
} }
} }
@ -1658,22 +1651,11 @@ define([
if (paths) { if (paths) {
paths.forEach(function (p) { pathsList.push(p.path); }); 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]); var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]);
if (pathsList.length === 1) { if (pathsList.length === 1) {
msg = hasOwned ? Messages.fm_deleteOwnedPad : Messages.fm_removePermanentlyDialog; msg = Messages.fm_removePermanentlyDialog;
} else if (hasOwned) {
msg = msg + '<br><em>' + Messages.fm_removePermanentlyNote + '</em>';
} }
// XXX update key to tell the user that these pads will still be avialble to other users
UI.confirm(msg, function(res) { UI.confirm(msg, function(res) {
$(window).focus(); $(window).focus();
if (!res) { return; } if (!res) { return; }
@ -2397,17 +2379,55 @@ define([
$gridButton.attr('title', Messages.fm_viewGridButton); $gridButton.attr('title', Messages.fm_viewGridButton);
$container.append($listButton).append($gridButton); $container.append($listButton).append($gridButton);
}; };
var createEmptyTrashButton = function ($container) { var emptyTrashModal = function () {
var $button = $emptyTrashIcon.clone(); var ownedInTrash = manager.ownedInTrash();
$button.addClass('cp-app-drive-toolbar-emptytrash'); var hasOwned = Array.isArray(ownedInTrash) && ownedInTrash.length;
$button.attr('title', Messages.fc_empty); Messages.fm_emptyTrashOwned = "Your trash contains documents you own. You can remove them for everyone or only from your drive"; // XXX
$button.click(function () { var content = h('p', [
UI.confirm(Messages.fm_emptyTrashDialog, function(res) { Messages.fm_emptyTrashDialog,
if (!res) { return; } hasOwned ? h('br') : undefined,
manager.emptyTrash(refresh); 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 // Get the upload options
@ -3145,6 +3165,8 @@ define([
var displayTrashRoot = function ($list, $folderHeader, $fileHeader) { var displayTrashRoot = function ($list, $folderHeader, $fileHeader) {
var filesList = []; var filesList = [];
var root = files[TRASH]; var root = files[TRASH];
var isEmpty = true;
// Elements in the trash are JS arrays (several elements can have the same name) // Elements in the trash are JS arrays (several elements can have the same name)
Object.keys(root).forEach(function (key) { Object.keys(root).forEach(function (key) {
if (!Array.isArray(root[key])) { if (!Array.isArray(root[key])) {
@ -3160,7 +3182,14 @@ define([
name: key name: key
}); });
}); });
isEmpty = false;
}); });
if (!isEmpty) {
var $empty = createEmptyTrashButton();
$content.append($empty);
}
var sortedFolders = sortTrashElements(true, filesList, null, !getSortFolderDesc()); var sortedFolders = sortTrashElements(true, filesList, null, !getSortFolderDesc());
var sortedFiles = sortTrashElements(false, filesList, APP.store[SORT_FILE_BY], !getSortFileDesc()); var sortedFiles = sortTrashElements(false, filesList, APP.store[SORT_FILE_BY], !getSortFileDesc());
if (manager.hasSubfolder(root, true)) { $list.append($folderHeader); } if (manager.hasSubfolder(root, true)) { $list.append($folderHeader); }
@ -3555,9 +3584,7 @@ define([
createToolbar(path); createToolbar(path);
if (inTrash || isInRoot) { if (!isSearch) { createTitle($content, path); }
createTitle($content, path);
}
var $info = createInfoBox(path); var $info = createInfoBox(path);
var $dirContent = $('<div>', {id: FOLDER_CONTENT_ID}); var $dirContent = $('<div>', {id: FOLDER_CONTENT_ID});
@ -3569,9 +3596,6 @@ define([
} }
createViewModeButton(APP.toolbar.$bottomR); createViewModeButton(APP.toolbar.$bottomR);
} }
if (inTrash) {
createEmptyTrashButton(APP.toolbar.$bottomR);
}
var $list = $('<ul>').appendTo($dirContent); var $list = $('<ul>').appendTo($dirContent);
@ -3587,7 +3611,6 @@ define([
} }
$content.data('readOnlyFolder', readOnlyFolder); $content.data('readOnlyFolder', readOnlyFolder);
// NewButton can be undefined if we're in read only mode
if (!readOnlyFolder) { if (!readOnlyFolder) {
createNewButton(isInRoot, APP.toolbar.$bottomL); createNewButton(isInRoot, APP.toolbar.$bottomL);
} }
@ -3599,6 +3622,7 @@ define([
} }
*/ */
if (APP.mobile()) { if (APP.mobile()) {
var $context = $('<button>', { var $context = $('<button>', {
id: 'cp-app-drive-toolbar-context-mobile' id: 'cp-app-drive-toolbar-context-mobile'
@ -3647,7 +3671,7 @@ define([
// ANON_SHARED_FOLDER // ANON_SHARED_FOLDER
displaySharedFolder($list); displaySharedFolder($list);
} else { } else {
$dirContent.contextmenu(openContextMenu('content')); if (!inTrash) { $dirContent.contextmenu(openContextMenu('content')); }
if (manager.hasSubfolder(root)) { $list.append($folderHeader); } if (manager.hasSubfolder(root)) { $list.append($folderHeader); }
// display sub directories // display sub directories
var keys = Object.keys(root); var keys = Object.keys(root);
@ -3731,6 +3755,10 @@ define([
e.stopPropagation(); e.stopPropagation();
APP.displayDirectory(path); APP.displayDirectory(path);
}); });
if (isSharedFolder) {
var sfData = manager.getSharedFolderData(isSharedFolder);
_addOwnership($elementRow, $(), sfData);
}
var $element = $('<li>').append($elementRow); var $element = $('<li>').append($elementRow);
if (draggable) { $elementRow.attr('draggable', true); } if (draggable) { $elementRow.attr('draggable', true); }
if (collapsable) { if (collapsable) {
@ -3813,17 +3841,17 @@ define([
var sfId = manager.isInSharedFolder(newPath) || (isSharedFolder && root[key]); var sfId = manager.isInSharedFolder(newPath) || (isSharedFolder && root[key]);
var $icon, isCurrentFolder, subfolder; var $icon, isCurrentFolder, subfolder;
if (isSharedFolder) { if (isSharedFolder) {
var fId = root[key];
// Fix path // Fix path
newPath.push(manager.user.userObject.ROOT); newPath.push(manager.user.userObject.ROOT);
isCurrentFolder = manager.comparePath(newPath, currentPath); isCurrentFolder = manager.comparePath(newPath, currentPath);
// Subfolders? // 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); subfolder = manager.hasSubfolder(newRoot);
// Fix name // Fix name
key = manager.getSharedFolderData(fId).title; key = manager.getSharedFolderData(sfId).title;
// Fix icon // Fix icon
$icon = isCurrentFolder ? $sharedFolderOpenedIcon : $sharedFolderIcon; $icon = isCurrentFolder ? $sharedFolderOpenedIcon : $sharedFolderIcon;
isSharedFolder = sfId;
} else { } else {
var isEmpty = manager.isFolderEmpty(root[key]); var isEmpty = manager.isFolderEmpty(root[key]);
subfolder = manager.hasSubfolder(root[key]); subfolder = manager.hasSubfolder(root[key]);
@ -4029,7 +4057,7 @@ define([
UI.confirm(msgD, function(res) { UI.confirm(msgD, function(res) {
$(window).focus(); $(window).focus();
if (!res) { return; } if (!res) { return; }
manager.delete(pathsList, function () { manager.deleteOwned(pathsList, function () {
pathsList.forEach(LS.removeFoldersOpened); pathsList.forEach(LS.removeFoldersOpened);
removeSelected(); removeSelected();
refresh(); refresh();
@ -4425,10 +4453,7 @@ define([
log(Messages.fm_forbidden); log(Messages.fm_forbidden);
return; return;
} }
UI.confirm(Messages.fm_emptyTrashDialog, function(res) { emptyTrashModal();
if (!res) { return; }
manager.emptyTrash(refresh);
});
} }
else if ($this.hasClass("cp-app-drive-context-remove")) { else if ($this.hasClass("cp-app-drive-context-remove")) {
return void deletePaths(paths); return void deletePaths(paths);

@ -898,6 +898,27 @@ define([
$d.append(changePass); $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; return $d;
}; };
var drawRight = function () { var drawRight = function () {

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

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

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

@ -3,9 +3,8 @@ define([
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-realtime.js', '/common/common-realtime.js',
'/common/common-feedback.js',
'/customize/messages.js' '/customize/messages.js'
], function (AppConfig, Util, Hash, Realtime, Feedback, Messages) { ], function (AppConfig, Util, Hash, Realtime, Messages) {
var module = {}; var module = {};
var clone = function (o) { var clone = function (o) {
@ -14,12 +13,8 @@ define([
}; };
module.init = function (config, exp, files) { module.init = function (config, exp, files) {
var removeOwnedChannel = config.removeOwnedChannel || function () {
console.error("removeOwnedChannel was not provided");
};
var loggedIn = config.loggedIn; var loggedIn = config.loggedIn;
var sharedFolder = config.sharedFolder; var sharedFolder = config.sharedFolder;
var edPublic = config.edPublic;
var readOnly = config.readOnly; 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 // 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) { exp.checkDeletedFiles = function (cb) {
if (!loggedIn && !config.testMode) { return void cb(); } if (!loggedIn && !config.testMode) { return void cb(); }
if (readOnly) { return void cb('EFORBIDDEN'); } if (readOnly) { return void cb('EFORBIDDEN'); }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]); var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = []; var toClean = [];
var ownedRemoved = [];
exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) { exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) {
if (filesList.indexOf(id) === -1) { if (filesList.indexOf(id) === -1) {
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id); var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel; 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.lastVersion) { toClean.push(Hash.hrefToHexChannelId(fd.lastVersion)); }
if (fd.rtChannel) { toClean.push(fd.rtChannel); } if (fd.rtChannel) { toClean.push(fd.rtChannel); }
if (channelId) { toClean.push(channelId); } if (channelId) { toClean.push(channelId); }
@ -180,7 +152,7 @@ define([
} }
}); });
if (!toClean.length) { return void cb(); } if (!toClean.length) { return void cb(); }
cb(null, toClean, ownedRemoved); cb(null, toClean);
}; };
var deleteHrefs = function (ids) { var deleteHrefs = function (ids) {
if (readOnly) { return; } if (readOnly) { return; }

@ -4,8 +4,9 @@ define([
'/common/common-hash.js', '/common/common-hash.js',
'/common/outer/sharedfolder.js', '/common/outer/sharedfolder.js',
'/customize/messages.js', '/customize/messages.js',
'/common/common-feedback.js',
'/bower_components/nthen/index.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) { var getConfig = function (Env) {
@ -397,26 +398,13 @@ define([
// Copy the elements to the new location // Copy the elements to the new location
var toCopy = _getCopyFromPaths(Env, resolved.main, Env.user.userObject); var toCopy = _getCopyFromPaths(Env, resolved.main, Env.user.userObject);
var newUserObject = newResolved.userObject; var newUserObject = newResolved.userObject;
var ownedPads = [];
toCopy.forEach(function (obj) { toCopy.forEach(function (obj) {
newUserObject.copyFromOtherDrive(newResolved.path, obj.el, obj.data, obj.key); 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 (copy) { return; }
if (resolved.main.length) { 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) // Remove the elements from the old location (without unpinning)
Env.user.userObject.delete(resolved.main, waitFor()); // FIXME waitFor() is called synchronously 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) { var _delete = function (Env, data, cb) {
data = data || {}; 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) { if (!resolved.main.length && !Object.keys(resolved.folders).length) {
return void cb({error: 'E_NOTFOUND'}); return void cb({error: 'E_NOTFOUND'});
} }
// Deleted or password changed for a shared folder // 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]); var temp = Util.find(Env, ['user', 'proxy', UserObject.SHARED_FOLDERS_TEMP]);
delete temp[data.paths[0][1]]; delete temp[data.paths[0][1]];
return void Env.onSync(cb); return void Env.onSync(cb);
} }
var toUnpin = []; var toUnpin = [];
var ownedRemoved;
nThen(function (waitFor)  { nThen(function (waitFor)  {
// Delete paths from the main drive and get the list of pads to unpin // 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 // 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) { uo.delete(resolved.main, waitFor(function (err, _toUnpin/*, _ownedRemoved*/) {
ownedRemoved = _ownedRemoved; //ownedRemoved = _ownedRemoved;
if (!Env.unpinPads || !_toUnpin) { return; } if (!Env.unpinPads || !_toUnpin) { return; }
Array.prototype.push.apply(toUnpin, _toUnpin); Array.prototype.push.apply(toUnpin, _toUnpin);
})); }));
@ -752,7 +739,7 @@ define([
// Check if removed owned pads are duplicated in some shared folders // 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 // 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 // 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); var ids = _findChannels(Env, ownedRemoved);
ids.forEach(function (id) { ids.forEach(function (id) {
var paths = findFile(Env, id); var paths = findFile(Env, id);
@ -765,7 +752,7 @@ define([
} }
}); });
}); });
} }*/
// Delete paths from the shared folders // Delete paths from the shared folders
Object.keys(resolved.folders).forEach(function (id) { Object.keys(resolved.folders).forEach(function (id) {
Env.folders[id].userObject.delete(resolved.folders[id], waitFor(function (err, _toUnpin) { Env.folders[id].userObject.delete(resolved.folders[id], waitFor(function (err, _toUnpin) {
@ -805,19 +792,121 @@ define([
cb(); cb();
}); });
}; };
// Empty the trash (main drive only) // Delete permanently some pads or folders
var _emptyTrash = function (Env, data, cb) { var _deleteOwned = function (Env, data, cb) {
Env.user.userObject.emptyTrash(function (err, toClean) { data = data || {};
cb(); 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) Array.prototype.push.apply(toDelete.main, _resolved.main);
if (!Array.isArray(toClean)) { return; } Object.keys(_resolved.folders).forEach(function (fId) {
var toCheck = Util.deduplicateString(toClean); if (toDelete.folders[fId]) {
toCheck.forEach(function (chan) { Array.prototype.push.apply(toDelete.folders[fId], _resolved.folders[fId]);
Env.Store.checkDeletedPad(chan); } 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 // Rename files or folders
var _rename = function (Env, data, cb) { var _rename = function (Env, data, cb) {
data = data || {}; data = data || {};
@ -868,6 +957,8 @@ define([
_convertFolderToSharedFolder(Env, data, cb); break; _convertFolderToSharedFolder(Env, data, cb); break;
case 'delete': case 'delete':
_delete(Env, data, cb); break; _delete(Env, data, cb); break;
case 'deleteOwned':
_deleteOwned(Env, data, cb); break;
case 'emptyTrash': case 'emptyTrash':
_emptyTrash(Env, data, cb); break; _emptyTrash(Env, data, cb); break;
case 'rename': case 'rename':
@ -1076,14 +1167,6 @@ define([
if (e) { error = e; return; } if (e) { error = e; return; }
uo.add(id, p); 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 () { }).nThen(function () {
cb(error); cb(error);
}); });
@ -1101,6 +1184,7 @@ define([
unpinPads: data.unpin, unpinPads: data.unpin,
onSync: data.onSync, onSync: data.onSync,
Store: data.Store, Store: data.Store,
removeOwnedChannel: data.removeOwnedChannel,
loadSharedFolder: data.loadSharedFolder, loadSharedFolder: data.loadSharedFolder,
cfg: uoConfig, cfg: uoConfig,
edPublic: data.edPublic, edPublic: data.edPublic,
@ -1140,6 +1224,7 @@ define([
getChannelsList: callWithEnv(getChannelsList), getChannelsList: callWithEnv(getChannelsList),
addPad: callWithEnv(addPad), addPad: callWithEnv(addPad),
delete: callWithEnv(_delete), delete: callWithEnv(_delete),
deleteOwned: callWithEnv(_deleteOwned),
// Tools // Tools
findChannel: callWithEnv(findChannel), findChannel: callWithEnv(findChannel),
findHref: callWithEnv(findHref), findHref: callWithEnv(findHref),
@ -1175,10 +1260,12 @@ define([
} }
}, cb); }, cb);
}; };
var emptyTrashInner = function (Env, cb) { var emptyTrashInner = function (Env, deleteOwned, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "emptyTrash", cmd: "emptyTrash",
data: null data: {
deleteOwned: deleteOwned
}
}, cb); }, cb);
}; };
var addFolderInner = function (Env, path, name, cb) { var addFolderInner = function (Env, path, name, cb) {
@ -1228,6 +1315,14 @@ define([
} }
}, cb); }, 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) { var restoreInner = function (Env, path, cb) {
return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "restore", cmd: "restore",
@ -1381,6 +1476,11 @@ define([
} }
return Env.user.userObject.hasFile(el, trashRoot); 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; var isDuplicateOwned = _isDuplicateOwned;
@ -1416,6 +1516,7 @@ define([
restoreSharedFolder: callWithEnv(restoreSharedFolderInner), restoreSharedFolder: callWithEnv(restoreSharedFolderInner),
convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner), convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner),
delete: callWithEnv(deleteInner), delete: callWithEnv(deleteInner),
deleteOwned: callWithEnv(deleteOwnedInner),
restore: callWithEnv(restoreInner), restore: callWithEnv(restoreInner),
setFolderData: callWithEnv(setFolderDataInner), setFolderData: callWithEnv(setFolderDataInner),
// Tools // Tools
@ -1435,6 +1536,7 @@ define([
isInSharedFolder: callWithEnv(isInSharedFolder), isInSharedFolder: callWithEnv(isInSharedFolder),
getUserObjectPath: callWithEnv(getUserObjectPath), getUserObjectPath: callWithEnv(getUserObjectPath),
isDuplicateOwned: callWithEnv(isDuplicateOwned), isDuplicateOwned: callWithEnv(isDuplicateOwned),
ownedInTrash: callWithEnv(ownedInTrash),
// Generic // Generic
isValidDrive: callWithEnv(isValidDrive), isValidDrive: callWithEnv(isValidDrive),
isFile: callWithEnv(isFile), isFile: callWithEnv(isFile),

@ -808,6 +808,15 @@ define([
Cryptpad.changePadPassword(Cryptget, Crypto, data, cb); 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); addCommonRpc(sframeChan, isSafe);

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

@ -844,6 +844,12 @@ define([
files[TRASH] = {}; files[TRASH] = {};
exp.checkDeletedFiles(cb); 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 // RENAME
exp.rename = function (path, newName, cb) { exp.rename = function (path, newName, cb) {

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

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

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

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

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

Loading…
Cancel
Save