From 9e059cc5b46d214040e758984824336401d4cfbb Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 30 Jun 2020 16:44:43 +0200 Subject: [PATCH 001/106] Fix OO issues --- www/common/onlyoffice/inner.js | 95 +++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 64dbf857f..64c41b21d 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -54,7 +54,7 @@ define([ var CHECKPOINT_INTERVAL = 100; var DISPLAY_RESTORE_BUTTON = false; - var NEW_VERSION = 2; + var NEW_VERSION = 3; var PENDING_TIMEOUT = 30000; var READ_ONLY_REFRESH_DATA_DELAY = 15000; @@ -278,11 +278,16 @@ define([ }); }; + // DEPRECATED from version 3 // Loading a checkpoint reorder the sheet starting from ID "5". // We have to reorder it manually when a checkpoint is created // so that the messages we send to the realtime channel are // loadable by users joining after the checkpoint var fixSheets = function () { + // Starting from version 3, we don't need to fix the sheet IDs anymore + // because we reload onlyoffice whenever we receive a checkpoint + if (!APP.migrate || (content && content.version > 2)) { return; } + try { var editor = getEditor(); // if we are not in the sheet app @@ -742,12 +747,24 @@ define([ }; var handleAuth = function (obj, send) { - setEditable(false); - ooChannel.lastHash = getLastCp().hash; + //setEditable(false); + + var changes = []; + if (content.version > 2) { + ooChannel.queue.forEach(function (data) { + Array.prototype.push.apply(changes, data.msg.changes); + }); + ooChannel.ready = true; + + ooChannel.cpIndex += ooChannel.queue.length; + var last = ooChannel.queue.pop(); + if (last) { ooChannel.lastHash = last.hash; } + } send({ type: "authChanges", - changes: [] + changes: changes }); + // Answer to the auth command var p = getParticipants(); send({ @@ -1006,6 +1023,9 @@ define([ var url = URL.createObjectURL(blob); var lock = readOnly || APP.migrate; + // Starting from version 3, we can use the view mode again + var mode = (content && content.version > 2 && lock) ? "view" : "edit"; + // Config APP.ooconfig = { "document": { @@ -1072,31 +1092,35 @@ define([ } }, "onDocumentReady": function () { - // The doc is ready, fix the worksheets IDs and push the queue - fixSheets(); - // Push changes since last cp - ooChannel.ready = true; - ooChannel.queue.forEach(function (data) { - ooChannel.send(data.msg); - }); - if (!readOnly) { - var last = ooChannel.queue.pop(); - if (last) { ooChannel.lastHash = last.hash; } - } - ooChannel.cpIndex += ooChannel.queue.length; - // Apply existing locks - deleteOfflineLocks(); - APP.onLocal(); - handleNewLocks(oldLocks, content.locks || {}); - // Allow edition - - if (lock) { - setTimeout(function () { + // DEPRECATED: from version 3, the queue is sent again during init + if (APP.migrate && ((content.version || 1) <= 2)) { + // The doc is ready, fix the worksheets IDs and push the queue + fixSheets(); + // Push changes since last cp + ooChannel.ready = true; + ooChannel.queue.forEach(function (data) { + ooChannel.send(data.msg); + }); + if (!readOnly) { + var last = ooChannel.queue.pop(); + if (last) { ooChannel.lastHash = last.hash; } + } + ooChannel.cpIndex += ooChannel.queue.length; + + // Apply existing locks + deleteOfflineLocks(); + APP.onLocal(); + handleNewLocks(oldLocks, content.locks || {}); + // Allow edition + + if (lock) { + setTimeout(function () { + setEditable(true); + getEditor().setViewModeDisconnect(); + }, 60000); + } else { setEditable(true); - getEditor().setViewModeDisconnect(); - }, 5000); - } else { - setEditable(true); + } } if (isLockedModal.modal && force) { @@ -1108,12 +1132,12 @@ define([ if (APP.migrate && !readOnly) { var div = h('div.cp-oo-x2tXls', [ h('span.fa.fa-spin.fa-spinner'), - h('span', Messages.oo_sheetMigration_loading) + h('span', Messages.oo_sheetMigration_loading) // XXX tell them that it will take ~ 1min) ]); UI.openCustomModal(UI.dialog.customModal(div, {buttons: []})); setTimeout(function () { makeCheckpoint(true); - }, 5000); + }, 60000); } } } @@ -1773,6 +1797,17 @@ define([ $(APP.helpMenu.menu).after(msg); readOnly = true; } + } else if (content && content.version === 2) { + APP.migrate = true; + // Registedred users can start the migration + if (common.isLoggedIn()) { + content.migration = true; + APP.onLocal(); + } else { + var msg = h('div.alert.alert-warning.cp-burn-after-reading', Messages.oo_sheetMigration_anonymousEditor); + $(APP.helpMenu.menu).after(msg); + readOnly = true; + } } // If the sheet is locked by an offline user, remove it From a0b7f39fd355ba4958842df9412da7f9b7adccc8 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 1 Jul 2020 18:06:00 +0200 Subject: [PATCH 002/106] Fix migration race condition and handle readonly users --- www/common/onlyoffice/inner.js | 142 +++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 2cecd4cc7..bfedb980c 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -56,7 +56,7 @@ define([ var DISPLAY_RESTORE_BUTTON = false; var NEW_VERSION = 3; var PENDING_TIMEOUT = 30000; - var READ_ONLY_REFRESH_DATA_DELAY = 15000; + var READONLY_REFRESH_TO = 15000; var debug = function (x) { if (!window.CP_DEV_MODE) { return; } @@ -112,7 +112,7 @@ define([ return window.frames[0].editor || window.frames[0].editorCell; }; - var setEditable = function (state) { + var setEditable = function (state, force) { $('#cp-app-oo-editor').find('#cp-app-oo-offline').remove(); /* try { @@ -120,7 +120,7 @@ define([ //window.frames[0].editor.setViewModeDisconnect(true); } catch (e) {} */ - if (!state && !readOnly) { + if (!state && (!readOnly || force)) { $('#cp-app-oo-editor').append(h('div#cp-app-oo-offline')); } }; @@ -272,6 +272,7 @@ define([ } }; + /* var checkDrawings = function () { var editor = getEditor(); if (!editor || !editor.GetSheets) { return false; } @@ -280,6 +281,7 @@ define([ return obj.worksheet.Drawings.length; }); }; + */ // DEPRECATED from version 3 // Loading a checkpoint reorder the sheet starting from ID "5". @@ -328,11 +330,8 @@ define([ index: ev.index }; oldHashes = JSON.parse(JSON.stringify(content.hashes)); - var hasDrawings = checkDrawings(); - if (hasDrawings) { - content.locks = {}; - content.ids = {}; - } + content.locks = {}; + content.ids = {}; // If this is a migration, set the new version if (APP.migrate) { delete content.migration; @@ -388,6 +387,9 @@ define([ }; var resetData = function (blob, type) { + // If a read-only refresh popup was planned, abort it + clearTimeout(APP.refreshRoTo); + if (!isLockedModal.modal) { isLockedModal.modal = UI.openCustomModal(isLockedModal.content); } @@ -415,14 +417,11 @@ define([ }; fixSheets(); - var hasDrawings = checkDrawings(); - if (hasDrawings) { - ooChannel.ready = false; - ooChannel.queue = []; - data.callback = function () { - resetData(blob, file); - }; - } + ooChannel.ready = false; + ooChannel.queue = []; + data.callback = function () { + resetData(blob, file); + }; APP.FM.handleFile(blob, data); }; @@ -612,6 +611,7 @@ define([ sframeChan.on('EV_OO_EVENT', function (obj) { switch (obj.ev) { case 'READY': + cb(); break; case 'LEAVE': removeClient(obj.data); @@ -627,7 +627,7 @@ define([ // Don't "spam" the user instantly and no more than // 1 popup every 15s - setTimeout(refreshReadOnly, READ_ONLY_REFRESH_DATA_DELAY); + APP.refreshRoTo = setTimeout(refreshReadOnly, READONLY_REFRESH_TO); return; } ooChannel.send(obj.data.msg); @@ -639,7 +639,6 @@ define([ break; } }); - cb(); }; var getParticipants = function () { @@ -762,6 +761,8 @@ define([ ooChannel.cpIndex += ooChannel.queue.length; var last = ooChannel.queue.pop(); if (last) { ooChannel.lastHash = last.hash; } + } else { + setEditable(false, true); } send({ type: "authChanges", @@ -1015,6 +1016,10 @@ define([ index: -1 }); } + if (APP.onDocumentUnlock) { + APP.onDocumentUnlock(); + APP.onDocumentUnlock = undefined; + } break; } }); @@ -1095,35 +1100,65 @@ define([ } }, "onDocumentReady": function () { + var onMigrateRdy = Util.mkEvent(); + onMigrateRdy.reg(function () { + var div = h('div.cp-oo-x2tXls', [ + h('span.fa.fa-spin.fa-spinner'), + h('span', Messages.oo_sheetMigration_loading) // XXX tell them that it will take ~ 1min) + ]); + UI.openCustomModal(UI.dialog.customModal(div, {buttons: []})); + makeCheckpoint(true); + }); // DEPRECATED: from version 3, the queue is sent again during init if (APP.migrate && ((content.version || 1) <= 2)) { // The doc is ready, fix the worksheets IDs and push the queue fixSheets(); // Push changes since last cp ooChannel.ready = true; + var changes = []; + var changesIndex; ooChannel.queue.forEach(function (data) { - ooChannel.send(data.msg); + Array.prototype.push.apply(changes, data.msg.changes); + changesIndex = data.msg.changesIndex; + //ooChannel.send(data.msg); }); - if (!readOnly) { - var last = ooChannel.queue.pop(); - if (last) { ooChannel.lastHash = last.hash; } - } ooChannel.cpIndex += ooChannel.queue.length; + var last = ooChannel.queue.pop(); + if (last) { ooChannel.lastHash = last.hash; } - // Apply existing locks - deleteOfflineLocks(); - APP.onLocal(); - handleNewLocks(oldLocks, content.locks || {}); - // Allow edition - - if (lock) { - setTimeout(function () { + var onDocUnlock = function () { + // Migration required but read-only: continue... + if (readOnly) { setEditable(true); getEditor().setViewModeDisconnect(); - }, 60000); - } else { - setEditable(true); + } else { + // No changes after the cp: migrate now + onMigrateRdy.fire(); + } + }; + + + // Send the changes all at once + if (changes.length) { + setTimeout(function () { + ooChannel.send({ + type: 'saveChanges', + changesIndex: changesIndex, + changes: changes, + locks: [] + }); + APP.onDocumentUnlock = onDocUnlock; + }, 5000); + return; } + onDocUnlock(); + return; + } + + if (lock) { + getEditor().setViewModeDisconnect(); + } else { + setEditable(true); } if (isLockedModal.modal && force) { @@ -1133,14 +1168,7 @@ define([ } if (APP.migrate && !readOnly) { - var div = h('div.cp-oo-x2tXls', [ - h('span.fa.fa-spin.fa-spinner'), - h('span', Messages.oo_sheetMigration_loading) // XXX tell them that it will take ~ 1min) - ]); - UI.openCustomModal(UI.dialog.customModal(div, {buttons: []})); - setTimeout(function () { - makeCheckpoint(true); - }, 60000); + onMigrateRdy.fire(); } } } @@ -1791,19 +1819,19 @@ define([ } else if (content && (!content.version || content.version === 1)) { version = 'v1/'; APP.migrate = true; - // Registedred users can start the migration - if (common.isLoggedIn()) { + // Registedred ~~users~~ editors can start the migration + if (common.isLoggedIn() && !readOnly) { content.migration = true; APP.onLocal(); } else { - var msg = h('div.alert.alert-warning.cp-burn-after-reading', Messages.oo_sheetMigration_anonymousEditor); + var msg = h('div.alert.alert-warning.cp-burn-after-reading', Messages.oo_sheetMigration_anonymousEditor); // XXX update: "anonymous users or viewers" $(APP.helpMenu.menu).after(msg); readOnly = true; } } else if (content && content.version === 2) { APP.migrate = true; - // Registedred users can start the migration - if (common.isLoggedIn()) { + // Registedred ~~users~~ editors can start the migration + if (common.isLoggedIn() && !readOnly) { content.migration = true; APP.onLocal(); } else { @@ -1854,15 +1882,12 @@ define([ var reloadPopup = false; var checkNewCheckpoint = function () { - var hasDrawings = checkDrawings(); - if (hasDrawings) { - var lastCp = getLastCp(); - loadLastDocument(lastCp, function () { - // On error, do nothing - }, function (blob, type) { - resetData(blob, type); - }); - } + var lastCp = getLastCp(); + loadLastDocument(lastCp, function () { + // On error, do nothing + }, function (blob, type) { + resetData(blob, type); + }); }; config.onRemote = function () { @@ -1891,10 +1916,7 @@ define([ var newLatest = getLastCp(); if (newLatest.index > latest.index) { ooChannel.queue = []; - var hasDrawings = checkDrawings(); - if (hasDrawings) { - ooChannel.ready = false; - } + ooChannel.ready = false; // New checkpoint sframeChan.query('Q_OO_SAVE', { hash: newLatest.hash, From abbdb496c34af5fa7a417716f38aec5b25af5d35 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Jul 2020 10:48:26 +0200 Subject: [PATCH 003/106] Allow shared folders in the trash --- www/common/drive-ui.js | 51 +++++++++++++++------------------- www/common/outer/userObject.js | 6 +++- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 7793b48c8..86600adda 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1202,7 +1202,7 @@ define([ hide.push('collapseall'); } if (path.length === 1) { - // Can't rename, share, delete, or change the color of root elements + // Can't rename, share, delete, or change the color of categories hide.push('delete'); hide.push('rename'); hide.push('share'); @@ -1257,7 +1257,7 @@ define([ hide.push('openro'); hide.push('openincode'); hide.push('hashtag'); - hide.push('delete'); + //hide.push('delete'); hide.push('makeacopy'); //hide.push('deleteowned'); } else { // it's a folder @@ -1772,25 +1772,15 @@ define([ }); if (sharedF && manager.isPathIn(newPath, [TRASH])) { - return void deletePaths(null, movedPaths); + // XXX create a key here? + // You can't move to YOUR trash documents stored in a shared folder + // XXX or keep deletePaths: trigger the "Remove from cryptdrive" modal + return void UI.warn(Messages.error); + //return void deletePaths(null, movedPaths); } var copy = false; - if (manager.isPathIn(newPath, [TRASH])) { - // Filter the selection to remove shared folders. - // Shared folders can't be moved to the trash! - var filteredPaths = movedPaths.filter(function (p) { - var el = manager.find(p); - return !manager.isSharedFolder(el); - }); - - if (!filteredPaths.length) { - // We only have shared folder, delete them - return void deletePaths(null, movedPaths); - } - - movedPaths = filteredPaths; - } else if (ev.ctrlKey || (ev.metaKey && APP.isMac)) { + if (ev.ctrlKey || (ev.metaKey && APP.isMac)) { copy = true; } @@ -2258,15 +2248,25 @@ define([ var skipNext = false; // When encountering a shared folder, skip a key in the path path.forEach(function (p, idx) { - if (skipNext) { skipNext = false; return; } if (isTrash && [2,3].indexOf(idx) !== -1) { return; } + if (skipNext) { skipNext = false; return; } var name = p; if (manager.isFile(el) && isInTrashRoot && idx === 1) { idx = 3; } + // Check if the current element is a shared folder. If it is, get its + // name and skip the next itme in the path (it will be "root") var currentEl = isVirtual ? undefined : manager.find(path.slice(0, idx+1)); + + // If we're in trash root, check if the "element" is a shared folder + if (isTrash && idx === 1) { + currentEl = manager.find(path.slice(0, idx+3)); + } + + // Name and skip next... + // "p === SHARED_FOLDER" for anonymous shared folders if (p === SHARED_FOLDER || (currentEl && manager.isSharedFolder(currentEl))) { name = manager.getSharedFolderData(currentEl || APP.newSharedFolder).title; skipNext = true; @@ -4560,23 +4560,18 @@ define([ paths.push($(elmt).data('path')); }); if (!paths.length) { return; } - // Remove shared folders from the selection (they can't be moved to the trash) - // unless the selection is only shared folders - var paths2 = paths.filter(function (p) { - var el = manager.find(p); - return !manager.isSharedFolder(el); - }); - // If we are in the trash or anon pad or if we are holding the "shift" key, + + // If we are in the trash or anon USER or if we are holding the "shift" key, // delete permanently // Or if we are in a shared folder // Or if the selection is only shared folders if (!APP.loggedIn || isTrash || manager.isInSharedFolder(currentPath) - || e.shiftKey || !paths2.length) { + || e.shiftKey) { deletePaths(null, paths); return; } // else move to trash - moveElements(paths2, [TRASH], false, refresh); + moveElements(paths, [TRASH], false, refresh); return; } }); diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 0637a91ea..3bd016585 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -649,6 +649,10 @@ define([ var toClean; var addToClean = function (obj, idx, el) { if (typeof(obj) !== "object") { toClean.push(idx); return; } + + // shared folders have their own userObject + if (exp.isSharedFolder(obj.element)) { return; } + if (!exp.isFile(obj.element, true) && !exp.isFolder(obj.element)) { toClean.push(idx); return; } if (!Array.isArray(obj.path)) { toClean.push(idx); return; } @@ -841,7 +845,7 @@ define([ if (sharedFolder) { return; } if (typeof(files[SHARED_FOLDERS]) !== "object") { debug("SHARED_FOLDER was not an object"); files[SHARED_FOLDERS] = {}; } var sf = files[SHARED_FOLDERS]; - var rootFiles = exp.getFiles([ROOT]); + var rootFiles = exp.getFiles([ROOT, TRASH]); var root = exp.find([ROOT]); var parsed /*, secret */, el; for (var id in sf) { From f612f3ad68bb393dde02680ba3fb6e2f4e5c38db Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Jul 2020 11:38:02 +0200 Subject: [PATCH 004/106] Add a '+ new' ghost icon in list mode and in anon drive --- customize.dist/src/less2/include/drive.less | 35 +++++++++++++-------- www/common/drive-ui.js | 7 +++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index e1790787f..1266f3c61 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -552,6 +552,21 @@ .cp-app-drive-element { .cp-app-drive-element-truncated { display: none; } } + + .cp-app-drive-new-ghost { + cursor: pointer; + opacity: 0.5; + padding: 0; + align-items: center; + justify-content: center; + display: inline-flex; + &:hover { + opacity: 0.7; + } + .fa, .cptools { + cursor: pointer; + } + } div.cp-app-drive-content-grid { padding: 1em; ul { @@ -598,27 +613,17 @@ } } } - .cp-app-drive-element-list { - display: none; - } .cp-app-drive-new-ghost { - cursor: pointer; - opacity: 0.5; - padding: 0; flex-flow: column; - align-items: center; - justify-content: center; - display: inline-flex; - &:hover { - opacity: 0.7; - } .fa, .cptools { - cursor: pointer; font-size: 90px; margin-top: 5px; margin-bottom: 0; } } + .cp-app-drive-element-list { + display: none; + } } .cp-app-drive-content-list { @@ -626,6 +631,10 @@ display: none; } // Make it act as a table! + .cp-app-drive-new-ghost { + padding: 0 5px; + margin-top: 20px; + } padding-left: 10px; ul { width: 100%; diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 0d3e49169..de4568179 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -3031,7 +3031,7 @@ define([ if (APP.$content.data('readOnlyFolder') || !APP.editable) { return; } var isInRoot = currentPath[0] === ROOT; var $element = $('
  • ', { - 'class': 'cp-app-drive-element-row cp-app-drive-element-grid cp-app-drive-new-ghost' + 'class': 'cp-app-drive-element-row cp-app-drive-new-ghost' }).prepend($addIcon.clone()).appendTo($list); $element.append($('', {'class': 'cp-app-drive-element-name'}) .text(Messages.fm_newFile)); @@ -3148,7 +3148,10 @@ define([ return; } var allfiles = files[FILES_DATA]; - if (allfiles.length === 0) { return; } + if (Object.keys(allfiles || {}).length === 0) { + createGhostIcon($container); + return; + } var $fileHeader = getFileListHeader(true); $container.append($fileHeader); var keys = manager.getFiles([FILES_DATA]); From f3f1fd1c8171351810434f74b81d4876b8fef0b1 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Jul 2020 15:07:19 +0200 Subject: [PATCH 005/106] Search spinner and 'no result' --- .../src/less2/include/alertify.less | 11 +- customize.dist/src/less2/include/drive.less | 18 ++- customize.dist/src/less2/include/tools.less | 8 +- www/common/drive-ui.js | 152 ++++++++++-------- 4 files changed, 103 insertions(+), 86 deletions(-) diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 2a7fd3761..db951613c 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -227,16 +227,7 @@ } } - ::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ - color: @cryptpad_color_grey; - opacity: 1; /* Firefox */ - } - :-ms-input-placeholder { /* Internet Explorer 10-11 */ - color: @cryptpad_color_grey; - } - ::-ms-input-placeholder { /* Microsoft Edge */ - color: @cryptpad_color_grey; - } + .tools_placeholder-color(@cryptpad_color_grey); span.cp-password-container { display: flex; diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index e1790787f..5b98d026b 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -222,7 +222,7 @@ #cp-app-drive-search { - display: flex; + display: inline-flex; align-items: center; max-width: 400px; font-size: 30px; @@ -230,7 +230,7 @@ input { background: transparent; color: @colortheme_drive-color; - .tools_placeholder-color(@colortheme_drive-color); + .tools_placeholder-color(@cryptpad_color_grey); outline-width: 0px; border-radius: 0; width: 100%; @@ -256,7 +256,19 @@ color: @colortheme_drive-color; } } - + .cp-app-drive-search-spinner { + display: inline-flex; + color: @colortheme_drive-color; + font-size: 40px; + align-items: center; + justify-content: center; + } + .cp-app-drive-search-noresult { + font-size: 30px; + padding: 15px; + font-style: italic; + color: @cryptpad_color_grey; + } /* TREE */ diff --git a/customize.dist/src/less2/include/tools.less b/customize.dist/src/less2/include/tools.less index b8c36cbcd..68feb9314 100644 --- a/customize.dist/src/less2/include/tools.less +++ b/customize.dist/src/less2/include/tools.less @@ -2,13 +2,9 @@ &::-webkit-input-placeholder { /* WebKit, Blink, Edge */ color: @color;; } - &:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + &::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ color: @color; - opacity: 1; - } - &::-moz-placeholder { /* Mozilla Firefox 19+ */ - color: @color; - opacity: 1; + opacity: 1; /* Firefox */ } &:-ms-input-placeholder { /* Internet Explorer 10-11 */ color: @color; diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 0d3e49169..958e948bb 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -234,9 +234,9 @@ define([ return LS; }; - var getViewModeClass = function () { + var getViewModeClass = function (forceList) { var mode = APP.store[LS_VIEWMODE]; - if (mode === 'list') { return 'cp-app-drive-content-list'; } + if (mode === 'list' || forceList) { return 'cp-app-drive-content-list'; } return 'cp-app-drive-content-grid'; }; var getViewMode = function () { @@ -3230,6 +3230,7 @@ define([ var $input = APP.Search.$input = $('', { id: 'cp-app-drive-search-input', + placeholder: Messages.fm_searchName, type: 'text', draggable: false, tabindex: 1, @@ -3288,69 +3289,87 @@ define([ $input.focus(); }); - $list.closest('#cp-app-drive-content-folder').addClass('cp-app-drive-content-list'); - var filesList = manager.search(value); - var sortable = {}; - var sortableFolders = []; - 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; } - var _path = path.slice(); - var key = path.pop(); - var root = manager.find(path); - var obj = { - path: path, - _path: _path, - key: key, - root: root, - data: r.data - }; - if (manager.isFolder(root[key])) { - sortableFolders.push(obj); - return; - } - sortable[root[key]] = obj; - }); - }); - var _folders = sortElements(true, [ROOT], sortableFolders, null, !getSortFolderDesc(), true); - var sortableKeys = Object.keys(sortable).map(Number); - var _files = sortElements(false, [ROOT], sortableKeys, APP.store[SORT_FILE_BY], !getSortFileDesc(), true); - - var addEl = function (obj, folder) { - var $element = createElement(obj.path, obj.key, obj.root, folder); - $element.addClass('cp-app-drive-element-notrash cp-app-drive-search-result'); - $element.off('contextmenu'); - $element.contextmenu(openContextMenu('default')); - $element.data('context', 'default'); - if (folder) { - $element.find('.cp-app-drive-element-list').css({ - visibility: 'hidden' - }).text(''); - } - if (manager.isPathIn(obj._path, ['hrefArray'])) { - obj._path.pop(); - obj._path.push(obj.data.title); + var $spinnerContainer = $(h('div.cp-app-drive-search-spinner')).appendTo($list); + + var spinner = UI.makeSpinner($spinnerContainer); + if (typeof(value) == "string" && value.trim()) { + spinner.spin(); + } else { + return; + } + + setTimeout(function () { + //$list.closest('#cp-app-drive-content-folder').addClass('cp-app-drive-content-list'); + var filesList = manager.search(value); + if (!filesList.length) { + Messages.fm_noResult = "No result found"; // XXX + $list.append(h('div.cp-app-drive-search-noresult', Messages.fm_noResult)); + spinner.hide(); + return; } - var $path = $('', { - 'class': 'cp-app-drive-search-path' - }).appendTo($element.find('.cp-app-drive-element-name')); - createTitle($path, obj._path); + var sortable = {}; + var sortableFolders = []; + 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; } + var _path = path.slice(); + var key = path.pop(); + var root = manager.find(path); + var obj = { + path: path, + _path: _path, + key: key, + root: root, + data: r.data + }; + if (manager.isFolder(root[key])) { + sortableFolders.push(obj); + return; + } + sortable[root[key]] = obj; + }); + }); + var _folders = sortElements(true, [ROOT], sortableFolders, null, !getSortFolderDesc(), true); + var sortableKeys = Object.keys(sortable).map(Number); + var _files = sortElements(false, [ROOT], sortableKeys, APP.store[SORT_FILE_BY], !getSortFileDesc(), true); + + var addEl = function (obj, folder) { + var $element = createElement(obj.path, obj.key, obj.root, folder); + $element.addClass('cp-app-drive-element-notrash cp-app-drive-search-result'); + $element.off('contextmenu'); + $element.contextmenu(openContextMenu('default')); + $element.data('context', 'default'); + if (folder) { + $element.find('.cp-app-drive-element-list').css({ + visibility: 'hidden' + }).text(''); + } + if (manager.isPathIn(obj._path, ['hrefArray'])) { + obj._path.pop(); + obj._path.push(obj.data.title); + } + var $path = $('', { + 'class': 'cp-app-drive-search-path' + }).appendTo($element.find('.cp-app-drive-element-name')); + createTitle($path, obj._path); - $list.append($element); - }; - if (_folders.length) { getFolderListHeader(true, true).appendTo($list); } - _folders.forEach(function (el) { - var obj = el; - addEl(obj, true); - }); - if (_files.length) { getFileListHeader(true).appendTo($list); } - _files.forEach(function (el) { - var obj = sortable[el]; - addEl(obj, false); + $list.append($element); + }; + if (_folders.length) { getFolderListHeader(true, true).appendTo($list); } + _folders.forEach(function (el) { + var obj = el; + addEl(obj, true); + }); + if (_files.length) { getFileListHeader(true).appendTo($list); } + _files.forEach(function (el) { + var obj = sortable[el]; + addEl(obj, false); + }); + setTimeout(collapseDrivePath); + spinner.hide(); }); - setTimeout(collapseDrivePath); }; var displayRecent = function ($list) { @@ -3606,12 +3625,11 @@ define([ var $dirContent = $('
    ', {id: FOLDER_CONTENT_ID}); $dirContent.data('path', path); - if (!isSearch && !isTags) { - var mode = getViewMode(); - if (mode) { - $dirContent.addClass(getViewModeClass()); + if (!isTags) { + $dirContent.addClass(getViewModeClass(isSearch)); + if (!isSearch) { + createViewModeButton(APP.toolbar.$bottomR); } - createViewModeButton(APP.toolbar.$bottomR); } var $list = $('