From b2972d5707fc427532aa1a22a2c5e0ecf0c679fa Mon Sep 17 00:00:00 2001 From: ClemDee Date: Thu, 20 Jun 2019 10:21:42 +0200 Subject: [PATCH 001/116] Add rename F2 shortcut in drive --- www/drive/inner.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/www/drive/inner.js b/www/drive/inner.js index 1d90beb49..151ef76d2 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -3636,6 +3636,14 @@ define([ APP.hideMenu(); }); + $content.on("keydown", function (e) { + if (e.which === 113) { + var paths = $contextMenu.data('paths'); + if (paths.length !== 1) { return; } + displayRenameInput(paths[0].element, paths[0].path); + } + }); + // Chrome considers the double-click means "select all" in the window $content.on('mousedown', function (e) { $content.focus(); From 0af7824a3ea3bf063ca691764ef0017cfae213ba Mon Sep 17 00:00:00 2001 From: ClemDee Date: Thu, 20 Jun 2019 16:51:04 +0200 Subject: [PATCH 002/116] Add context menu separators --- www/drive/inner.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/www/drive/inner.js b/www/drive/inner.js index 151ef76d2..d7629d52c 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -124,6 +124,7 @@ define([ var $tagsIcon = $('', {"class": "fa " + faTags}); var $passwordIcon = $('', {"class": "fa fa-lock"}); var $expirableIcon = $('', {"class": "fa fa-clock-o"}); + var $separator = $('
', {"class": "dropdown-divider"}); var LS_LAST = "app-drive-lastOpened"; var LS_OPENED = "app-drive-openedFolders"; @@ -311,6 +312,7 @@ define([ 'tabindex': '-1', 'data-icon': faReadOnly, }, Messages.fc_open_ro)), + $separator.clone()[0], h('li', h('a.cp-app-drive-context-expandall.dropdown-item', { 'tabindex': '-1', 'data-icon': "expandAll", @@ -319,6 +321,7 @@ define([ 'tabindex': '-1', 'data-icon': "collapseAll", }, Messages.fc_collapseAll)), + $separator.clone()[0], h('li', h('a.cp-app-drive-context-color.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': faColor, @@ -347,6 +350,7 @@ define([ 'tabindex': '-1', 'data-icon': faTags, }, Messages.fc_hashtag)), + $separator.clone()[0], h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': AppConfig.applicationsIcon.pad, @@ -372,6 +376,7 @@ define([ 'data-icon': AppConfig.applicationsIcon.whiteboard, 'data-type': 'whiteboard' }, Messages.button_newwhiteboard)), + $separator.clone()[0], h('li', h('a.cp-app-drive-context-empty.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': faEmpty, @@ -380,6 +385,7 @@ define([ 'tabindex': '-1', 'data-icon': faRestore, }, Messages.fc_restore)), + $separator.clone()[0], h('li', h('a.cp-app-drive-context-rename.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': faRename, @@ -1238,6 +1244,20 @@ define([ var displayMenu = function (e) { var $menu = $contextMenu; + var showSep = false; + var $lastVisibleSep = null; + $menu.find(".dropdown-menu").children().each(function (i, el) { + var $el = $(el); + if ($el.is(".dropdown-divider")) { + $el.css("display", showSep ? "list-item" : "none"); + if (showSep) { $lastVisibleSep = $el; } + showSep = false; + } + else if ($el.is("li") && $el.css("display") !== "none") { + showSep = true; + } + }); + if (!showSep && $lastVisibleSep) { $lastVisibleSep.css("display", "none"); } // remove last divider if no options after $menu.css({ display: "block" }); if (APP.mobile()) { return; } var h = $menu.outerHeight(); From 7af53cc0e7ccb2706080ae8d42ab17ea75e279ea Mon Sep 17 00:00:00 2001 From: ClemDee Date: Thu, 20 Jun 2019 16:56:37 +0200 Subject: [PATCH 003/116] Remove New Folder option in context menu when clicking on folder --- www/drive/inner.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/www/drive/inner.js b/www/drive/inner.js index d7629d52c..d6669d1c7 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1059,8 +1059,7 @@ define([ show = ['newfolder', 'newsharedfolder', 'newdoc']; break; case 'tree': - show = ['open', 'openro', 'expandall', 'collapseall', 'color', 'download', 'share', 'rename', 'delete', 'deleteowned', 'removesf', - 'newfolder', 'properties', 'hashtag']; + show = ['open', 'openro', 'expandall', 'collapseall', 'color', 'download', 'share', 'rename', 'delete', 'deleteowned', 'removesf', 'properties', 'hashtag']; break; case 'default': show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'hashtag']; From 29b7c2c2955edc60b9b2d0c884f7aa87839d5610 Mon Sep 17 00:00:00 2001 From: ClemDee Date: Thu, 20 Jun 2019 17:17:28 +0200 Subject: [PATCH 004/116] Display "No Action" in context menu when empty --- customize.dist/src/less2/include/contextmenu.less | 6 ++++++ www/drive/inner.js | 7 ++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/customize.dist/src/less2/include/contextmenu.less b/customize.dist/src/less2/include/contextmenu.less index 1a9049b54..e60394bd7 100644 --- a/customize.dist/src/less2/include/contextmenu.less +++ b/customize.dist/src/less2/include/contextmenu.less @@ -15,5 +15,11 @@ cursor: pointer; } } + .cp-app-drive-context-noAction { + font-style: italic; + color: #aaa; + cursor: default; + display: none; + } } } diff --git a/www/drive/inner.js b/www/drive/inner.js index d6669d1c7..94f7d9e5e 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -304,6 +304,7 @@ define([ 'aria-labelledby': 'dropdownMenu', 'style': 'display:block;position:static;margin-bottom:5px;' }, [ + h('span.cp-app-drive-context-noAction.dropdown-item.disabled', Messages.fc_noAction || "No action possible"), h('li', h('a.cp-app-drive-context-open.dropdown-item', { 'tabindex': '-1', 'data-icon': faFolderOpen, @@ -1332,11 +1333,7 @@ define([ displayMenu(e); - if ($contextMenu.find('li:visible').length === 0) { - debug("No visible element in the context menu. Abort."); - $contextMenu.hide(); - return true; - } + $(".cp-app-drive-context-noAction").toggle($contextMenu.find('li:visible').length === 0); $contextMenu.data('paths', paths); return false; From 9b8fed55e976804010e6da6d6ae50e60d5d13a01 Mon Sep 17 00:00:00 2001 From: ClemDee Date: Fri, 21 Jun 2019 17:25:00 +0200 Subject: [PATCH 005/116] Show folders in the results of drive search --- www/common/userObject.js | 34 +++++++++++++++++++++++ www/drive/inner.js | 59 ++++++++++++++++++++++++---------------- 2 files changed, 70 insertions(+), 23 deletions(-) diff --git a/www/common/userObject.js b/www/common/userObject.js index a9068afe6..096cc5057 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -514,6 +514,40 @@ define([ data: exp.getFileData(l) }); }); + + // find folders + var resFolders = []; + var findFoldersRec = function (folder, path) { + for (var key in folder) { + if (isFolder(folder[key])) { + if (isSharedFolder(folder[key])) { +// var name = getSharedFolderData(folder[key]).title || ""; +// if (name.toLowerCase().indexOf(lValue) !== -1) { +// resFolders.push(path.concat([key, ROOT])); +// } + findFoldersRec(folder[key], path.concat([key, ROOT])); + } + else { + if (key.toLowerCase().indexOf(lValue) !== -1) { + resFolders.push({ + id: null, + paths: [path.concat(key)], + data: { + title: key + } + }); + } + findFoldersRec(folder[key], path.concat(key)); + } + } + } + }; + findFoldersRec(files[ROOT], [ROOT]); + resFolders = resFolders.sort(function (a, b) { + return a.data.title.toLowerCase() > b.data.title.toLowerCase(); + }); + ret = resFolders.concat(ret); + return ret; }; exp.getRecentPads = function () { diff --git a/www/drive/inner.js b/www/drive/inner.js index 94f7d9e5e..5937b6aed 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2610,6 +2610,7 @@ define([ var displaySearch = function ($list, value) { var filesList = manager.search(value); filesList.forEach(function (r) { + // if r.id === null, then it's a folder, not a file r.paths.forEach(function (path) { if (!r.inSharedFolder && APP.hideDuplicateOwned && manager.isDuplicateOwned(path)) { return; } @@ -2617,25 +2618,27 @@ define([ var parsed = Hash.parsePadUrl(href); var $table = $(''); var $icon = $('').append($icon).append($title).append($typeName).append($type).appendTo($table); @@ -3208,7 +3221,7 @@ define([ placeholder: Messages.fm_searchPlaceholder }).keyup(function (e) { if (search.to) { window.clearTimeout(search.to); } - if ([38, 39, 40, 41].indexOf(e.which) !== -1) { + if ([37, 38, 39, 40].indexOf(e.which) !== -1) { if (!$input.val()) { $input.blur(); $content.focus(); From d34d517e270d58324567113ffa248df6ff68b164 Mon Sep 17 00:00:00 2001 From: ClemDee Date: Fri, 21 Jun 2019 17:26:32 +0200 Subject: [PATCH 006/116] Fix flex direction in results path of drive search --- www/drive/app-drive.less | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/www/drive/app-drive.less b/www/drive/app-drive.less index ba59e9124..c85cd3c88 100644 --- a/www/drive/app-drive.less +++ b/www/drive/app-drive.less @@ -513,12 +513,18 @@ } .cp-app-drive-search-path { font-style: italic; - display: flex; - flex-flow: row-reverse; - justify-content: right; - .cp-app-drive-path-element { - display: inline-block; - margin-right: 5px; + .cp-app-drive-path-inner { + display: flex; + flex-flow: row-reverse wrap-reverse; + justify-content: flex-end; + .cp-app-drive-path-element { + flex-shrink: 0; + display: inline-block; + margin-right: 5px; + white-space: normal; + word-wrap: break-word; + max-width: 100%; + } } } .cp-app-drive-search-title { From dd62533467c5abe4ba2c38e9e03e5e8b573384f6 Mon Sep 17 00:00:00 2001 From: ClemDee Date: Mon, 24 Jun 2019 10:07:16 +0200 Subject: [PATCH 007/116] Better code for displaying result of search in drive --- www/drive/inner.js | 61 +++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/www/drive/inner.js b/www/drive/inner.js index 5937b6aed..fb2b0bcd9 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2617,28 +2617,10 @@ define([ var href = r.data.href; var parsed = Hash.parsePadUrl(href); var $table = $('
', {'rowspan': '3', 'class': 'cp-app-drive-search-icon'}) - .append(getFileIcon(r.id)); + .append(r.id ? getFileIcon(r.id) : $folderIcon.clone()); var $title = $('', { 'class': 'cp-app-drive-search-col1 cp-app-drive-search-title' - }).text(r.data.title) - .click(function () { - openFile(null, r.data.href); - }); + }).text(r.data.title); + if (r.id) { + $title.click(function () { + openFile(null, r.data.href); + }); + } var $typeName = $('', {'class': 'cp-app-drive-search-label2'}) .text(Messages.fm_type); var $type = $('', {'class': 'cp-app-drive-search-col2'}) - .text(Messages.type[parsed.type] || parsed.type); + .text(r.id ? Messages.type[parsed.type] || parsed.type : Messages.fm_folder); var $atimeName = $('', {'class': 'cp-app-drive-search-label2'}) - .text(Messages.fm_lastAccess); + .text(r.id ? Messages.fm_lastAccess : ""); var $atime = $('', {'class': 'cp-app-drive-search-col2'}) - .text(new Date(r.data.atime).toLocaleString()); + .text(r.id ? new Date(r.data.atime).toLocaleString() : ""); var $ctimeName = $('', {'class': 'cp-app-drive-search-label2'}) - .text(Messages.fm_creation); + .text(r.id ? Messages.fm_creation : ""); var $ctime = $('', {'class': 'cp-app-drive-search-col2'}) - .text(new Date(r.data.ctime).toLocaleString()); + .text(r.id ? new Date(r.data.ctime).toLocaleString() : ""); if (manager.isPathIn(path, ['hrefArray'])) { path.pop(); path.push(r.data.title); @@ -2646,23 +2649,33 @@ define([ createTitle($path, path, true); var parentPath = path.slice(); var $a; - if (parentPath) { - $a = $('').text(Messages.fm_openParent).click(function (e) { + if (r.id) { + if (parentPath) { + $a = $('').text(Messages.fm_openParent).click(function (e) { + e.preventDefault(); + if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } + else { parentPath.pop(); } + APP.selectedFiles = [r.id]; + APP.displayDirectory(parentPath); + }); + } + } + else { + $a = $('').text(Messages.fm_OpenFolder || "Open folder").click(function (e) { e.preventDefault(); - if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } - else { parentPath.pop(); } - APP.selectedFiles = [r.id]; - APP.displayDirectory(parentPath); + APP.displayDirectory(path); }); } var $openDir = $('', {'class': 'cp-app-drive-search-opendir'}).append($a); - $('').text(Messages.fc_prop).click(function () { - APP.getProperties(r.id, function (e, $prop) { - if (e) { return void logError(e); } - UI.alert($prop[0], undefined, true); - }); - }).appendTo($openDir); + if (r.id) { + $('').text(Messages.fc_prop).click(function () { + APP.getProperties(r.id, function (e, $prop) { + if (e) { return void logError(e); } + UI.alert($prop[0], undefined, true); + }); + }).appendTo($openDir); + } // rows 1-3 $('
'); - var $icon = $('').append($icon).append($title).append($typeName).append($type).appendTo($table); From f5858f524d1c40e362dc791b64731cbff849c84a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 24 Jun 2019 12:17:08 +0200 Subject: [PATCH 008/116] Fix invalid file ID in the drive --- www/common/outer/userObject.js | 5 +++++ www/common/proxy-manager.js | 1 + www/common/userObject.js | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index e67dd5f01..1f8b73da9 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -626,6 +626,11 @@ define([ var toClean = []; for (var id in fd) { id = Number(id); + if (!id && id !== 0) { + debug("Invalid file ID in filesData.", id); + toClean.push(id); + continue; + } var el = fd[id]; // Clean corrupted data diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 017a403bb..8bbf641f6 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -729,6 +729,7 @@ define([ if (type === "pin") { return function (fileId) { var data = userObject.getFileData(fileId); + if (!data) { return; } // Don't pin pads owned by someone else if (_ownedByOther(Env, data.owners)) { return; } // Don't push duplicates diff --git a/www/common/userObject.js b/www/common/userObject.js index a9068afe6..f39e80aee 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -311,12 +311,12 @@ define([ _getFiles[FILES_DATA] = function () { var ret = []; if (!files[FILES_DATA]) { return ret; } - return Object.keys(files[FILES_DATA]).map(Number); + return Object.keys(files[FILES_DATA]).map(Number).filter(Boolean); }; _getFiles[SHARED_FOLDERS] = function () { var ret = []; if (!files[SHARED_FOLDERS]) { return ret; } - return Object.keys(files[SHARED_FOLDERS]).map(Number); + return Object.keys(files[SHARED_FOLDERS]).map(Number).filter(Boolean); }; var getFiles = exp.getFiles = function (categories) { var ret = []; From bba3e355d00ec3c11e062ee0c6d7c9657df58c36 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 24 Jun 2019 12:28:46 +0200 Subject: [PATCH 009/116] Invalid ID fix --- www/common/outer/userObject.js | 4 ++-- www/common/proxy-manager.js | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 1f8b73da9..e12883eb5 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -625,12 +625,12 @@ define([ var root = exp.find([ROOT]); var toClean = []; for (var id in fd) { - id = Number(id); - if (!id && id !== 0) { + if (String(id) !== String(Number(id))) { debug("Invalid file ID in filesData.", id); toClean.push(id); continue; } + id = Number(id); var el = fd[id]; // Clean corrupted data diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 8bbf641f6..fdfde9263 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -707,6 +707,7 @@ define([ if (type === 'expirable') { return function (fileId) { var data = userObject.getFileData(fileId); + if (!data) { return; } // Don't push duplicates if (result.indexOf(data.channel) !== -1) { return; } // Return pads owned by someone else or expired by time @@ -718,6 +719,7 @@ define([ if (type === 'owned') { return function (fileId) { var data = userObject.getFileData(fileId); + if (!data) { return; } // Don't push duplicates if (result.indexOf(data.channel) !== -1) { return; } // Return owned pads From 905bbef8238017ace07af812cfa50c6803e54158 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 24 Jun 2019 18:15:53 +0200 Subject: [PATCH 010/116] Add FIXME comments --- www/common/common-ui-elements.js | 2 +- www/common/proxy-manager.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index c6668e41f..24e50863f 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -162,7 +162,7 @@ define([ } var parsed = Hash.parsePadUrl(data.href || data.roHref); - if (!data.noEditPassword && owned && parsed.hashData.type === 'pad') { + if (!data.noEditPassword && owned && parsed.hashData.type === 'pad' && parsed.type !== "sheet") { // FIXME SHEET fix password change for sheets var sframeChan = common.getSframeChannel(); var changePwTitle = Messages.properties_changePassword; var changePwConfirm = Messages.properties_confirmChange; diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index fdfde9263..9d8bbe6ba 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -342,7 +342,7 @@ define([ }); // Remove the elements from the old location (without unpinning) - Env.user.userObject.delete(resolved.main, waitFor()); + Env.user.userObject.delete(resolved.main, waitFor()); // FIXME waitFor() is called synchronously } } } @@ -369,7 +369,7 @@ define([ if (copy) { return; } // Remove the elements from the old location (without unpinning) - uoFrom.delete(paths, waitFor()); + uoFrom.delete(paths, waitFor()); // FIXME waitFor() is called synchronously } }); } From 59d5723f3e7e7d61d3643205b821007b2f471848 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 24 Jun 2019 18:16:32 +0200 Subject: [PATCH 011/116] Fix read-only spreadsheets --- www/common/onlyoffice/main.js | 7 +++++-- www/common/outer/onlyoffice.js | 29 +++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index d056cad42..6bd6c7df5 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -102,9 +102,12 @@ define([ Cryptpad.onlyoffice.onEvent.reg(function (obj) { if (obj.ev === 'MESSAGE' && !/^cp\|/.test(obj.data)) { try { + var validateKey = obj.data.validateKey || true; + var skipCheck = validateKey === true; + var msg = obj.data.msg; obj.data = { - msg: JSON.parse(Utils.crypto.decrypt(obj.data, Utils.secret.keys.validateKey)), - hash: obj.data.slice(0,64) + msg: JSON.parse(Utils.crypto.decrypt(msg, validateKey, skipCheck)), + hash: msg.slice(0,64) }; } catch (e) { console.error(e); diff --git a/www/common/outer/onlyoffice.js b/www/common/outer/onlyoffice.js index 3b57b4123..70bc413ec 100644 --- a/www/common/outer/onlyoffice.js +++ b/www/common/outer/onlyoffice.js @@ -26,7 +26,10 @@ define([ if (!c.id) { c.id = chan.wc.myID + '-' + client; } chan.history.forEach(function (msg) { - ctx.emit('MESSAGE', msg, [client]); + ctx.emit('MESSAGE', { + msg: msg, + validateKey: chan.validateKey + }, [client]); }); // ==> And push the new tab to the list @@ -37,7 +40,8 @@ define([ var onOpen = function (wc) { ctx.channels[channel] = ctx.channels[channel] || { - history: [] + history: [], + validateKey: obj.validateKey }; chan = ctx.channels[channel]; @@ -61,7 +65,10 @@ define([ }); wc.on('message', function (msg) { chan.history.push(msg); - ctx.emit('MESSAGE', msg, chan.clients); + ctx.emit('MESSAGE', { + msg: msg, + validateKey: chan.validateKey + }, chan.clients); }); chan.wc = wc; @@ -101,6 +108,7 @@ define([ }; network.on('message', function (msg, sender) { + if (!ctx.channels[channel]) { return; } var hk = network.historyKeeper; if (sender !== hk) { return; } @@ -115,7 +123,12 @@ define([ // Keep only metadata messages for the current channel if (parsed.channel && parsed.channel !== channel) { return; } // Ignore the metadata message - if (parsed.validateKey && parsed.channel) { return; } + if (parsed.validateKey && parsed.channel) { + if (!chan.validateKey) { + chan.validateKey = parsed.validateKey; + } + return; + } // End of history: emit READY if (parsed.state && parsed.state === 1 && parsed.channel) { ctx.emit('READY', '', chan.clients); @@ -132,7 +145,9 @@ define([ if (hash === chan.lastKnownHash || hash === chan.lastCpHash) { return; } chan.lastKnownHash = hash; - ctx.emit('MESSAGE', msg, chan.clients); + ctx.emit('MESSAGE', { + msg: msg, + }, chan.clients); chan.history.push(msg); }); @@ -176,7 +191,9 @@ define([ return void chan.sendMsg(data.isCp, cb); } chan.sendMsg(data.msg, cb); - ctx.emit('MESSAGE', data.msg, chan.clients.filter(function (cl) { + ctx.emit('MESSAGE', { + msg: data.msg + }, chan.clients.filter(function (cl) { return cl !== clientId; })); }; From ba95ad6bf5e94d787b00707b61b369c0b97faffd Mon Sep 17 00:00:00 2001 From: ClemDee Date: Tue, 25 Jun 2019 09:45:30 +0200 Subject: [PATCH 012/116] Do not move folders if destination is one of the moved folders --- www/drive/inner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/drive/inner.js b/www/drive/inner.js index fb2b0bcd9..7224d729d 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1361,6 +1361,7 @@ define([ }); cb(); }; + if (paths.some(function (p) { return manager.comparePath(newPath, p) })) { return void cb(); } manager.move(paths, newPath, newCb, copy); }; // Delete paths from the drive and/or shared folders (without moving them to the trash) From 29a2e3a0ea8ae49f5bb5780f50dd8e9d45ee5fd1 Mon Sep 17 00:00:00 2001 From: ClemDee Date: Tue, 25 Jun 2019 10:43:40 +0200 Subject: [PATCH 013/116] Add icons in context menu --- customize.dist/src/less2/include/colortheme.less | 2 ++ customize.dist/src/less2/include/contextmenu.less | 4 ++++ www/drive/inner.js | 10 ++++++++++ 3 files changed, 16 insertions(+) diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index abbe07955..b2076034c 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -26,6 +26,8 @@ @colortheme_form-warning: #f49842; @colortheme_form-warning-hov: darken(@colortheme_form-warning, 5%); +@colortheme_context-menu-icon-color: #7b7b7b; + @colortheme_modal-bg: @colortheme_form-bg-alt; // TODO Modals bg @colortheme_modal-fg: @colortheme_form-color-alt; @colortheme_modal-link: @colortheme_link-color; diff --git a/customize.dist/src/less2/include/contextmenu.less b/customize.dist/src/less2/include/contextmenu.less index e60394bd7..2063a239e 100644 --- a/customize.dist/src/less2/include/contextmenu.less +++ b/customize.dist/src/less2/include/contextmenu.less @@ -13,6 +13,10 @@ font-size: @colortheme_app-font-size; a { cursor: pointer; + .fa, .cptools { + margin-right: 1rem; + color: @colortheme_context-menu-icon-color; + } } } .cp-app-drive-context-noAction { diff --git a/www/drive/inner.js b/www/drive/inner.js index 7224d729d..fb9bd4063 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -413,6 +413,16 @@ define([ }, Messages.fc_prop)), ]) ]); + $(menu).find("li a.dropdown-item").each(function (i, el) { + var $icon = $(""); + if ($(el).attr('data-icon')) { + var font = $(el).attr('data-icon').indexOf('cptools') === 0 ? 'cptools' : 'fa'; + $icon.addClass(font).addClass($(el).attr('data-icon')); + } else { + $icon.text($(el).text()); + } + $(el).prepend($icon); + }); return $(menu); }; From e50f554774cd65ea023e46286282c1c70773c46b Mon Sep 17 00:00:00 2001 From: ClemDee Date: Tue, 25 Jun 2019 13:37:20 +0200 Subject: [PATCH 014/116] Add Dropdown-submenu (beta) --- .../src/less2/include/contextmenu.less | 13 +++++++ www/drive/inner.js | 39 +++++++++++++++---- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/customize.dist/src/less2/include/contextmenu.less b/customize.dist/src/less2/include/contextmenu.less index 2063a239e..30a9f7d98 100644 --- a/customize.dist/src/less2/include/contextmenu.less +++ b/customize.dist/src/less2/include/contextmenu.less @@ -19,6 +19,19 @@ } } } + .dropdown-submenu { + position: relative; + & .dropdown-menu { + top: -0.6rem; + left: 100%; + &.left { + left: -10rem; + } + } + &:hover .dropdown-menu { + display: block; + } + } .cp-app-drive-context-noAction { font-style: italic; color: #aaa; diff --git a/www/drive/inner.js b/www/drive/inner.js index fb9bd4063..b38aa78a3 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -305,10 +305,10 @@ define([ 'style': 'display:block;position:static;margin-bottom:5px;' }, [ h('span.cp-app-drive-context-noAction.dropdown-item.disabled', Messages.fc_noAction || "No action possible"), - h('li', h('a.cp-app-drive-context-open.dropdown-item', { - 'tabindex': '-1', - 'data-icon': faFolderOpen, - }, Messages.fc_open)), +// h('li', h('a.cp-app-drive-context-open.dropdown-item', { +// 'tabindex': '-1', +// 'data-icon': faFolderOpen, +// }, Messages.fc_open)), h('li', h('a.cp-app-drive-context-openro.dropdown-item', { 'tabindex': '-1', 'data-icon': faReadOnly, @@ -323,10 +323,22 @@ define([ 'data-icon': "collapseAll", }, Messages.fc_collapseAll)), $separator.clone()[0], - h('li', h('a.cp-app-drive-context-color.dropdown-item.cp-app-drive-context-editable', { - 'tabindex': '-1', - 'data-icon': faColor, - }, Messages.fc_color)), +// h('li', h('a.cp-app-drive-context-color.dropdown-item.cp-app-drive-context-editable', { +// 'tabindex': '-1', +// 'data-icon': faColor, +// }, Messages.fc_color)), + h('li.dropdown-submenu', [ + h('a.cp-app-drive-context-color.dropdown-item.dropdown-toggle', { + 'tabindex': '-1', + 'data-icon': faColor, + }, Messages.fc_color), + h("ul.dropdown-menu", [ + h('li', h('a.cp-app-drive-context-open.dropdown-item', { + 'tabindex': '-1', + 'data-icon': faFolderOpen, + }, Messages.fc_open)) + ]) + ]), h('li', h('a.cp-app-drive-context-download.dropdown-item', { 'tabindex': '-1', 'data-icon': faDownload, @@ -423,6 +435,17 @@ define([ } $(el).prepend($icon); }); + $(menu).find(".dropdown-submenu").each(function (i, el) { + var $el = $(el); + var $sub = $el.find(".dropdown-menu"); + $el.hover(function () { + setTimeout(function () { // wait for dom to update + $sub.toggleClass("left", $el.offset().left + $el.outerWidth() + $sub.outerWidth() > $(window).width()); + }); + }, function () { + $sub.removeClass("left"); + }); + }); return $(menu); }; From 2d881caaeba7ef602f6ff504acd4563d1123b989 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 25 Jun 2019 15:07:43 +0200 Subject: [PATCH 015/116] Fix accounts href in limit popup --- www/common/toolbar3.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 303a983e0..2da2fc7d8 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -832,11 +832,17 @@ MessengerUI, Messages) { return $spin; }; - var createLimit = function (toolbar) { + var createLimit = function (toolbar, config) { var $limitIcon = $('', {'class': 'fa fa-exclamation-triangle'}); var $limit = toolbar.$userAdmin.find('.'+LIMIT_CLS).attr({ 'title': Messages.pinLimitReached }).append($limitIcon).hide(); + + var priv = config.metadataMgr.getPrivateData(); + var origin = priv.origin; + var l = document.createElement("a"); + l.href = origin; + var todo = function (e, overLimit) { if (e) { return void console.error("Unable to get the pinned usage", e); } if (overLimit) { @@ -845,7 +851,7 @@ MessengerUI, Messages) { key = 'pinLimitReachedAlertNoAccounts'; } $limit.show().click(function () { - UI.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true); + UI.alert(Messages._getKey(key, [encodeURIComponent(l.hostname)]), null, true); }); } }; From 22c9af696134dbaa0d010d79b55e7c2365e5a658 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 24 Jun 2019 12:15:34 +0200 Subject: [PATCH 016/116] Support page --- .../src/less2/include/colortheme.less | 4 + scripts/generateAdminKeys.js | 26 ++ server.js | 1 + www/common/common-constants.js | 2 +- www/common/common-hash.js | 5 + www/common/outer/async-store.js | 1 + www/common/outer/mailbox.js | 33 +- www/common/sframe-common-mailbox.js | 24 +- www/common/toolbar3.js | 2 +- www/support/app-support.less | 19 + www/support/index.html | 12 + www/support/inner.html | 18 + www/support/inner.js | 370 ++++++++++++++++++ www/support/main.js | 55 +++ 14 files changed, 561 insertions(+), 11 deletions(-) create mode 100644 scripts/generateAdminKeys.js create mode 100644 www/support/app-support.less create mode 100644 www/support/index.html create mode 100644 www/support/inner.html create mode 100644 www/support/inner.js create mode 100644 www/support/main.js diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less index abbe07955..16608fe51 100644 --- a/customize.dist/src/less2/include/colortheme.less +++ b/customize.dist/src/less2/include/colortheme.less @@ -135,6 +135,10 @@ @colortheme_admin-color: #FFF; @colortheme_admin-warn: #ffae00; +@colortheme_support-bg: #42d1f4; +@colortheme_support-color: #000; +@colortheme_support-warn: #9A37F7; + // Sidebar layout (profile / settings) @colortheme_sidebar-active: #fff; @colortheme_sidebar-left-bg: #eee; diff --git a/scripts/generateAdminKeys.js b/scripts/generateAdminKeys.js new file mode 100644 index 000000000..072dd8b3d --- /dev/null +++ b/scripts/generateAdminKeys.js @@ -0,0 +1,26 @@ +const Nacl = require('tweetnacl'); +const Crypto = require('crypto'); + +const keyPair = Nacl.box.keyPair(); +console.log(keyPair); + +console.log("You've just generated a new key pair for your support mailbox."); + +console.log("The public key should first be added to your config.js file ('supportMailboxPublicKey'), then save and restart the server.") +console.log("Once restarted, administrators (specified with 'adminKeys' in config.js too) will be able to add the private key into their account. This can be done using the administration panel."); +console.log("You will have to send the private key to each administrator manually so that they can add it to their account."); +console.log(); +console.log("WARNING: the public and private keys must come from the same key pair to have a working encrypted support mailbox."); +console.log(); +console.log("NOTE: You can change the key pair at any time if you want to revoke access to the support mailbox. You just have to generate a new key pair using this file, and replace the value in config.js, and then send the new private key to the administrators of your choice."); + + +console.log(); +console.log(); +console.log("Your public key (add it to config.js):"); +console.log(Nacl.util.encodeBase64(keyPair.publicKey)); + +console.log(); +console.log(); +console.log("Your private key (store it in a safe place and send it to your instance's admins):"); +console.log(Nacl.util.encodeBase64(keyPair.secretKey)); diff --git a/server.js b/server.js index b10b32da8..b8c2b8163 100644 --- a/server.js +++ b/server.js @@ -193,6 +193,7 @@ app.get('/api/config', function(req, res){ httpUnsafeOrigin: config.httpUnsafeOrigin, adminEmail: config.adminEmail, adminKeys: admins, + supportMailbox: config.supportMailboxPublicKey }, null, '\t'), 'obj.httpSafeOrigin = ' + (function () { if (config.httpSafeOrigin) { return '"' + config.httpSafeOrigin + '"'; } diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 9eb3c4075..caad16bc8 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -17,6 +17,6 @@ define(function () { // Sub plan: 'CryptPad_plan', // Apps - criticalApps: ['profile', 'settings', 'debug', 'admin'] + criticalApps: ['profile', 'settings', 'debug', 'admin', 'support'] }; }); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 3ef802681..1b588db60 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -85,6 +85,11 @@ define([ return id; }; + Hash.getChannelIdFromKey = function (publicKey) { + if (!publicKey) { return; } + return uint8ArrayToHex(Hash.decodeBase64(publicKey).subarray(0,16)); + }; + Hash.createRandomHash = function (type, password) { var cryptor; if (type === 'file') { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 492446781..9c0dfb864 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -478,6 +478,7 @@ define([ settings: store.proxy.settings, thumbnails: disableThumbnails === false, isDriveOwned: Boolean(Util.find(store, ['driveMetadata', 'owners'])), + support: Util.find(store.proxy, ['mailboxes', 'support', 'channel']), pendingFriends: store.proxy.friends_pending || {} } }; diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 550573411..cb4a4a5fe 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -10,6 +10,7 @@ define([ var TYPES = [ 'notifications', + 'support' ]; var BLOCKING_TYPES = [ ]; @@ -25,6 +26,16 @@ define([ if (res.error) { console.error(res); } }); } + if (!mailboxes['support']) { + mailboxes.support = { + channel: Hash.createChannelId(), + lastKnownHash: '', + viewed: [] + }; + ctx.pinPads([mailboxes.support.channel], function (res) { + if (res.error) { console.error(res); } + }); + } }; /* @@ -76,15 +87,30 @@ proxy.mailboxes = { var crypto = Crypto.Mailbox.createEncryptor(keys); var network = ctx.store.network; - var ciphertext = crypto.encrypt(JSON.stringify({ + var text = JSON.stringify({ type: type, content: msg - }), user.curvePublic); + }); + var ciphertext = crypto.encrypt(text, user.curvePublic); network.join(user.channel).then(function (wc) { wc.bcast(ciphertext).then(function () { cb(); wc.leave(); + + // If we've just sent a message to one of our mailboxes, we have to trigger the handler manually + // (the server won't send back our message to us) + var box; + if (Object.keys(ctx.boxes).some(function (t) { + var _box = ctx.boxes[t]; + if (_box.channel === user.channel) { + box = _box; + return true; + } + })) { + var hash = ciphertext.slice(0, 64); + box.onMessage(text, null, null, null, hash, user.curvePublic); + } }); }, function (err) { cb({error: err}); @@ -157,6 +183,7 @@ proxy.mailboxes = { var openChannel = function (ctx, type, m, onReady) { var box = ctx.boxes[type] = { + channel: m.channel, queue: [], // Store the messages to send when the channel is ready history: [], // All the hashes loaded from the server in corretc order content: {}, // Content of the messages that should be displayed @@ -213,7 +240,7 @@ proxy.mailboxes = { }); box.queue = []; }; - cfg.onMessage = function (msg, user, vKey, isCp, hash, author) { + box.onMessage = cfg.onMessage = function (msg, user, vKey, isCp, hash, author) { if (hash === m.lastKnownHash) { return; } try { msg = JSON.parse(msg); diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index 814966a22..4bcee18af 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -90,8 +90,11 @@ define([ var pushMessage = function (data, handler) { var todo = function (f) { try { - var el = createElement(data); - Notifications.add(Common, data, el); + var el; + if (data.type === 'notifications') { + el = createElement(data); + Notifications.add(Common, data, el); + } f(data, el); } catch (e) { console.error(e); @@ -108,7 +111,9 @@ define([ onViewedHandlers.forEach(function (f) { try { f(data); - Notifications.remove(Common, data); + if (data.type === 'notifications') { + Notifications.remove(Common, data); + } } catch (e) { console.error(e); } @@ -139,18 +144,25 @@ define([ var subscribed = false; // Get all existing notifications + the new ones when they come - mailbox.subscribe = function (cfg) { + mailbox.subscribe = function (types, cfg) { if (!subscribed) { execCommand('SUBSCRIBE', null, function () {}); subscribed = true; } if (typeof(cfg.onViewed) === "function") { - onViewedHandlers.push(cfg.onViewed); + onViewedHandlers.push(function (data) { + if (types.indexOf(data.type) === -1) { return; } + cfg.onViewed(data); + }); } if (typeof(cfg.onMessage) === "function") { - onMessageHandlers.push(cfg.onMessage); + onMessageHandlers.push(function (data, el) { + if (types.indexOf(data.type) === -1) { return; } + cfg.onMessage(data, el); + }); } Object.keys(history).forEach(function (type) { + if (types.indexOf(type) === -1) { return; } history[type].forEach(function (data) { pushMessage({ type: type, diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 303a983e0..589dec496 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -983,7 +983,7 @@ MessengerUI, Messages) { $button.addClass('fa-bell'); }; - Common.mailbox.subscribe({ + Common.mailbox.subscribe(['notifications'], { onMessage: function (data, el) { if (el) { div.appendChild(el); diff --git a/www/support/app-support.less b/www/support/app-support.less new file mode 100644 index 000000000..60091a0d9 --- /dev/null +++ b/www/support/app-support.less @@ -0,0 +1,19 @@ +@import (reference) '../../customize/src/less2/include/framework.less'; +@import (reference) '../../customize/src/less2/include/sidebar-layout.less'; + +&.cp-app-support { + .framework_min_main( + @bg-color: @colortheme_support-bg, + @warn-color: @colortheme_support-warn, + @color: @colortheme_support-color + ); + .sidebar-layout_main(); + + .cp-hidden { + display: none !important; + } + + display: flex; + flex-flow: column; +} + diff --git a/www/support/index.html b/www/support/index.html new file mode 100644 index 000000000..79a96c97b --- /dev/null +++ b/www/support/index.html @@ -0,0 +1,12 @@ + + + + CryptPad + + + + + + + +
', {'rowspan': '3', 'class': 'cp-app-drive-search-icon'}) - .append(r.id ? getFileIcon(r.id) : $folderIcon.clone()); + var $icon = $('', {'rowspan': '3', 'class': 'cp-app-drive-search-icon'}); var $title = $('', { 'class': 'cp-app-drive-search-col1 cp-app-drive-search-title' }).text(r.data.title); - if (r.id) { - $title.click(function () { - openFile(null, r.data.href); - }); - } - var $typeName = $('', {'class': 'cp-app-drive-search-label2'}) - .text(Messages.fm_type); - var $type = $('', {'class': 'cp-app-drive-search-col2'}) - .text(r.id ? Messages.type[parsed.type] || parsed.type : Messages.fm_folder); - var $atimeName = $('', {'class': 'cp-app-drive-search-label2'}) - .text(r.id ? Messages.fm_lastAccess : ""); - var $atime = $('', {'class': 'cp-app-drive-search-col2'}) - .text(r.id ? new Date(r.data.atime).toLocaleString() : ""); - var $ctimeName = $('', {'class': 'cp-app-drive-search-label2'}) - .text(r.id ? Messages.fm_creation : ""); - var $ctime = $('', {'class': 'cp-app-drive-search-col2'}) - .text(r.id ? new Date(r.data.ctime).toLocaleString() : ""); if (manager.isPathIn(path, ['hrefArray'])) { path.pop(); path.push(r.data.title); @@ -2647,28 +2629,33 @@ define([ 'class': 'cp-app-drive-search-col1 cp-app-drive-search-path' }); createTitle($path, path, true); - var parentPath = path.slice(); - var $a; + var $typeName = $('', {'class': 'cp-app-drive-search-label2'}).text(Messages.fm_type); + var $type = $('', {'class': 'cp-app-drive-search-col2'}); + var $atimeName = $('', {'class': 'cp-app-drive-search-label2'}); + var $atime = $('', {'class': 'cp-app-drive-search-col2'}); + var $ctimeName = $('', {'class': 'cp-app-drive-search-label2'}); + var $ctime = $('', {'class': 'cp-app-drive-search-col2'}); + var $openDir = $('', {'class': 'cp-app-drive-search-opendir'}); if (r.id) { + $icon.append(getFileIcon(r.id)); + $type.text(Messages.type[parsed.type] || parsed.type); + $title.click(function () { + openFile(null, r.data.href); + }); + $atimeName.text(Messages.fm_lastAccess); + $atime.text(new Date(r.data.atime).toLocaleString()); + $ctimeName.text(Messages.fm_creation); + $ctime.text(new Date(r.data.ctime).toLocaleString()); + var parentPath = path.slice(); if (parentPath) { - $a = $('').text(Messages.fm_openParent).click(function (e) { + $('').text(Messages.fm_openParent).click(function (e) { e.preventDefault(); if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; } else { parentPath.pop(); } APP.selectedFiles = [r.id]; APP.displayDirectory(parentPath); - }); + }).appendTo($openDir); } - } - else { - $a = $('').text(Messages.fm_OpenFolder || "Open folder").click(function (e) { - e.preventDefault(); - APP.displayDirectory(path); - }); - } - var $openDir = $('', {'class': 'cp-app-drive-search-opendir'}).append($a); - - if (r.id) { $('').text(Messages.fc_prop).click(function () { APP.getProperties(r.id, function (e, $prop) { if (e) { return void logError(e); } @@ -2676,6 +2663,14 @@ define([ }); }).appendTo($openDir); } + else { + $icon.append($folderIcon.clone()); + $type.text(Messages.fm_folder); + $('').text(Messages.fm_OpenFolder || "Open folder").click(function (e) { + e.preventDefault(); + APP.displayDirectory(path); + }).appendTo($openDir); + } // rows 1-3 $('