diff options
author | Jan-Christoph Borchardt <hey@jancborchardt.net> | 2015-09-07 14:03:57 +0200 |
---|---|---|
committer | Jan-Christoph Borchardt <hey@jancborchardt.net> | 2015-09-07 14:03:57 +0200 |
commit | 004de1425e23649ca011a8d57d09ec2280583b8a (patch) | |
tree | 99e8043118dd73922f152b65da0d1ebc688bd4a9 | |
parent | a35bc974bbeb3f037af3dbc0a6acbd56599fa847 (diff) | |
parent | 4ba233350746b4287556d96bb9adbae039f93882 (diff) | |
download | nextcloud-server-004de1425e23649ca011a8d57d09ec2280583b8a.tar.gz nextcloud-server-004de1425e23649ca011a8d57d09ec2280583b8a.zip |
Merge pull request #18630 from owncloud/files-combineuploadbutton
Combine upload action into the "New" menu
-rw-r--r-- | apps/files/css/files.css | 140 | ||||
-rw-r--r-- | apps/files/css/upload.css | 14 | ||||
-rw-r--r-- | apps/files/index.php | 1 | ||||
-rw-r--r-- | apps/files/js/file-upload.js | 149 | ||||
-rw-r--r-- | apps/files/js/fileactionsmenu.js | 4 | ||||
-rw-r--r-- | apps/files/js/filelist.js | 152 | ||||
-rw-r--r-- | apps/files/js/newfilemenu.js | 235 | ||||
-rw-r--r-- | apps/files/templates/list.php | 52 | ||||
-rw-r--r-- | apps/files/tests/js/fileUploadSpec.js | 51 | ||||
-rw-r--r-- | apps/files/tests/js/filelistSpec.js | 119 | ||||
-rw-r--r-- | apps/files/tests/js/newfilemenuSpec.js | 119 | ||||
-rw-r--r-- | apps/files_sharing/templates/public.php | 1 | ||||
-rw-r--r-- | core/css/apps.css | 65 | ||||
-rw-r--r-- | core/css/styles.css | 10 |
14 files changed, 792 insertions, 320 deletions
diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 375666141ba..05033dc2fed 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -18,11 +18,6 @@ z-index: -30; } -#new { - z-index: 1010; - float: left; - padding: 0 !important; /* override default control bar button padding */ -} #trash { margin-right: 8px; float: right; @@ -30,49 +25,8 @@ padding: 10px; font-weight: normal; } -#new > a { - padding: 14px 10px; - position: relative; - top: 7px; -} -#new.active { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - border-bottom: none; - background: #f8f8f8; -} -#new > ul { - display: none; - position: fixed; - min-width: 112px; - z-index: -10; - padding: 8px; - padding-bottom: 0; - margin-top: 13.5px; - margin-left: -1px; - text-align: left; - background: #f8f8f8; - border: 1px solid #ddd; - border: 1px solid rgba(240, 240, 240, 0.9); - border-radius: 5px; - border-top-left-radius: 0; - box-shadow: 0 2px 7px rgba(170,170,170,.4); -} -#new > ul > li { - height: 36px; - margin: 5px; - padding-left: 42px; - padding-bottom: 2px; - background-position: left center; - cursor: pointer; -} -#new > ul > li > p { - cursor: pointer; - padding-top: 7px; - padding-bottom: 7px; -} -#new .error, #fileList .error { +.newFileMenu .error, #fileList .error { color: #e9322d; border-color: #e9322d; -webkit-box-shadow: 0 0 6px #f8b9b7; @@ -144,6 +98,30 @@ margin-bottom: 44px; } +#app-navigation .nav-files a.nav-icon-files { + width: auto; +} +/* button needs overrides due to navigation styles */ +#app-navigation .nav-files a.new { + width: 40px; + height: 32px; + padding: 0 10px; + margin: 0; + cursor: pointer; +} + +#app-navigation .nav-files a { + display: inline-block; +} + +#app-navigation .nav-files a.new.hidden { + display: none; +} + +#app-navigation .nav-files a.new.disabled { + opacity: 0.3; +} + #filestable tbody tr { background-color: #fff; height: 40px; @@ -600,7 +578,8 @@ a.action > img { #fileList a.action.action-menu { padding: 17px 14px; } -#fileList .fileActionsMenu { + +#fileList .popovermenu { margin-right: 21px; } @@ -655,13 +634,13 @@ a.action > img { } /* properly display actions in the popover menu */ -#fileList .fileActionsMenu .action { +#fileList .popovermenu .action { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)" !important; filter: alpha(opacity=50) !important; opacity: .5 !important; } -#fileList .fileActionsMenu .action:hover, -#fileList .fileActionsMenu .action:focus { +#fileList .popovermenu .action:hover, +#fileList .popovermenu .action:focus { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)" !important; filter: alpha(opacity=100) !important; opacity: 1 !important; @@ -745,43 +724,56 @@ table.dragshadow td.size { opacity: 0; } -.fileActionsMenu { - padding: 4px 12px; -} -.fileActionsMenu li { - padding: 5px 0; -} -#fileList .fileActionsMenu a.action img { +#fileList .popovermenu a.action img { padding: initial; } -#fileList .fileActionsMenu a.action { + +#fileList .popovermenu a.action { padding: 10px; margin: -10px; } -.fileActionsMenu.hidden { - display: none; +.newFileMenu { + width: 140px; + margin-left: -56px; + margin-top: 25px; } -#fileList .fileActionsMenu .action { - display: block; - line-height: 30px; - padding-left: 5px; - color: #000; +.newFileMenu .menuitem { + white-space: nowrap; + overflow: hidden; +} +.newFileMenu.popovermenu .menuitem .icon { + margin-bottom: -2px; +} +.newFileMenu.popovermenu a.menuitem, +.newFileMenu.popovermenu label.menuitem, +.newFileMenu.popovermenu .menuitem { padding: 0; + margin: 0; +} + +.newFileMenu.bubble:after { + left: 75px; + right: auto; +} +.newFileMenu.bubble:before { + left: 75px; + right: auto; } -.fileActionsMenu .action img, -.fileActionsMenu .action .no-icon { +.newFileMenu .filenameform { display: inline-block; - width: 16px; - margin-right: 5px; } -.fileActionsMenu .action { - opacity: 0.5; +.newFileMenu .filenameform input { + width: 100px; } -.fileActionsMenu li:hover .action { - opacity: 1; +#fileList .popovermenu .action { + display: block; + line-height: 30px; + padding-left: 5px; + color: #000; + padding: 0; } diff --git a/apps/files/css/upload.css b/apps/files/css/upload.css index bd60f831388..07b788b937f 100644 --- a/apps/files/css/upload.css +++ b/apps/files/css/upload.css @@ -24,20 +24,6 @@ } .file_upload_target { display:none; } .file_upload_form { display:inline; float:left; margin:0; padding:0; cursor:pointer; overflow:visible; } -#file_upload_start { - position: relative; - left: 0; - top: 0; - width: 44px; - height: 44px; - margin: -5px -3px; - padding: 0; - font-size: 16px; - -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; - z-index: 20; - cursor: pointer; - overflow: hidden; -} #uploadprogresswrapper, #uploadprogresswrapper * { -moz-box-sizing: border-box; diff --git a/apps/files/index.php b/apps/files/index.php index a73caa50fbe..cc007ebdb07 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -38,6 +38,7 @@ OCP\Util::addStyle('files', 'upload'); OCP\Util::addStyle('files', 'mobile'); OCP\Util::addscript('files', 'app'); OCP\Util::addscript('files', 'file-upload'); +OCP\Util::addscript('files', 'newfilemenu'); OCP\Util::addscript('files', 'jquery.iframe-transport'); OCP\Util::addscript('files', 'jquery.fileupload'); OCP\Util::addscript('files', 'jquery-visibility'); diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 6b6acdb5e01..17f0f777169 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -551,155 +551,6 @@ OC.Upload = { $('#file_upload_start').attr('multiple', 'multiple'); } - $(document).click(function(ev) { - // do not close when clicking in the dropdown - if ($(ev.target).closest('#new').length){ - return; - } - $('#new>ul').hide(); - $('#new').removeClass('active'); - if ($('#new .error').length > 0) { - $('#new .error').tipsy('hide'); - } - $('#new li').each(function(i,element) { - if ($(element).children('p').length === 0) { - $(element).children('form').remove(); - $(element).append('<p>' + $(element).data('text') + '</p>'); - } - }); - }); - $('#new').click(function(event) { - event.stopPropagation(); - }); - $('#new>a').click(function() { - $('#new>ul').toggle(); - $('#new').toggleClass('active'); - }); - $('#new li').click(function() { - if ($(this).children('p').length === 0) { - return; - } - - $('#new .error').tipsy('hide'); - - $('#new li').each(function(i, element) { - if ($(element).children('p').length === 0) { - $(element).children('form').remove(); - $(element).append('<p>' + $(element).data('text') + '</p>'); - } - }); - - var type = $(this).data('type'); - var text = $(this).children('p').text(); - $(this).data('text', text); - $(this).children('p').remove(); - - // add input field - var form = $('<form></form>'); - var input = $('<input type="text">'); - var newName = $(this).attr('data-newname') || ''; - var fileType = 'input-' + $(this).attr('data-type'); - if (newName) { - input.val(newName); - input.attr('id', fileType); - } - var label = $('<label class="hidden-visually" for="">' + escapeHTML(newName) + '</label>'); - label.attr('for', fileType); - - form.append(label).append(input); - $(this).append(form); - var lastPos; - var checkInput = function () { - var filename = input.val(); - if (!Files.isFileNameValid(filename)) { - // Files.isFileNameValid(filename) throws an exception itself - } else if (FileList.inList(filename)) { - throw t('files', '{new_name} already exists', {new_name: filename}); - } else { - return true; - } - }; - - // verify filename on typing - input.keyup(function(event) { - try { - checkInput(); - input.tipsy('hide'); - input.removeClass('error'); - } catch (error) { - input.attr('title', error); - input.tipsy({gravity: 'w', trigger: 'manual'}); - input.tipsy('show'); - input.addClass('error'); - } - }); - - input.focus(); - // pre select name up to the extension - lastPos = newName.lastIndexOf('.'); - if (lastPos === -1) { - lastPos = newName.length; - } - input.selectRange(0, lastPos); - form.submit(function(event) { - event.stopPropagation(); - event.preventDefault(); - try { - checkInput(); - var newname = input.val(); - if (FileList.lastAction) { - FileList.lastAction(); - } - var name = FileList.getUniqueName(newname); - switch(type) { - case 'file': - $.post( - OC.filePath('files', 'ajax', 'newfile.php'), - { - dir: FileList.getCurrentDirectory(), - filename: name - }, - function(result) { - if (result.status === 'success') { - FileList.add(result.data, {animate: true, scrollTo: true}); - } else { - OC.dialogs.alert(result.data.message, t('core', 'Could not create file')); - } - } - ); - break; - case 'folder': - $.post( - OC.filePath('files','ajax','newfolder.php'), - { - dir: FileList.getCurrentDirectory(), - foldername: name - }, - function(result) { - if (result.status === 'success') { - FileList.add(result.data, {animate: true, scrollTo: true}); - } else { - OC.dialogs.alert(result.data.message, t('core', 'Could not create folder')); - } - } - ); - break; - } - var li = form.parent(); - form.remove(); - /* workaround for IE 9&10 click event trap, 2 lines: */ - $('input').first().focus(); - $('#content').focus(); - li.append('<p>' + li.data('text') + '</p>'); - $('#new>a').click(); - } catch (error) { - input.attr('title', error); - input.tipsy({gravity: 'w', trigger: 'manual'}); - input.tipsy('show'); - input.addClass('error'); - } - }); - }); window.file_upload_param = file_upload_param; return file_upload_param; } diff --git a/apps/files/js/fileactionsmenu.js b/apps/files/js/fileactionsmenu.js index 623ebde5442..5ab0e42f93e 100644 --- a/apps/files/js/fileactionsmenu.js +++ b/apps/files/js/fileactionsmenu.js @@ -14,7 +14,7 @@ '<ul>' + '{{#each items}}' + '<li>' + - '<a href="#" class="action action-{{nameLowerCase}} permanent" data-action="{{name}}">{{#if icon}}<img src="{{icon}}"/>{{else}}<span class="no-icon"></span>{{/if}}<span>{{displayName}}</span></a>' + + '<a href="#" class="menuitem action action-{{nameLowerCase}} permanent" data-action="{{name}}">{{#if icon}}<img class="icon" src="{{icon}}"/>{{else}}<span class="no-icon"></span>{{/if}}<span>{{displayName}}</span></a>' + '</li>' + '{{/each}}' + '</ul>'; @@ -26,7 +26,7 @@ */ var FileActionsMenu = OC.Backbone.View.extend({ tagName: 'div', - className: 'fileActionsMenu bubble hidden open menu', + className: 'fileActionsMenu popovermenu bubble hidden open menu', /** * Current context diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 3f0ee932d1e..c52e414e3a7 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -9,6 +9,9 @@ */ (function() { + + var TEMPLATE_ADDBUTTON = '<a href="#" class="button new" title="{{addText}}"><img src="{{iconUrl}}"></img></a>'; + /** * @class OCA.Files.FileList * @classdesc @@ -220,6 +223,8 @@ this.$el.find('#controls').prepend(this.breadcrumb.$el); + this._renderNewButton(); + this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); this._onResize = _.debounce(_.bind(this._onResize, this), 100); @@ -262,6 +267,12 @@ * Destroy / uninitialize this instance. */ destroy: function() { + if (this._newFileMenu) { + this._newFileMenu.remove(); + } + if (this._newButton) { + this._newButton.remove(); + } // TODO: also unregister other event handlers this.fileActions.off('registerAction', this._onFileActionsUpdated); this.fileActions.off('setDefault', this._onFileActionsUpdated); @@ -1730,6 +1741,106 @@ form.trigger('submit'); }); }, + + /** + * Create an empty file inside the current directory. + * + * @param {string} name name of the file + * + * @return {Promise} promise that will be resolved after the + * file was created + * + * @since 8.2 + */ + createFile: function(name) { + var self = this; + var deferred = $.Deferred(); + var promise = deferred.promise(); + + OCA.Files.Files.isFileNameValid(name); + name = this.getUniqueName(name); + + if (this.lastAction) { + this.lastAction(); + } + + $.post( + OC.generateUrl('/apps/files/ajax/newfile.php'), + { + dir: this.getCurrentDirectory(), + filename: name + }, + function(result) { + if (result.status === 'success') { + self.add(result.data, {animate: true, scrollTo: true}); + deferred.resolve(result.status, result.data); + } else { + if (result.data && result.data.message) { + OC.Notification.showTemporary(result.data.message); + } else { + OC.Notification.showTemporary(t('core', 'Could not create file')); + } + deferred.reject(result.status, result.data); + } + } + ); + + return promise; + }, + + /** + * Create a directory inside the current directory. + * + * @param {string} name name of the directory + * + * @return {Promise} promise that will be resolved after the + * directory was created + * + * @since 8.2 + */ + createDirectory: function(name) { + var self = this; + var deferred = $.Deferred(); + var promise = deferred.promise(); + + OCA.Files.Files.isFileNameValid(name); + name = this.getUniqueName(name); + + if (this.lastAction) { + this.lastAction(); + } + + $.post( + OC.generateUrl('/apps/files/ajax/newfolder.php'), + { + dir: this.getCurrentDirectory(), + foldername: name + }, + function(result) { + if (result.status === 'success') { + self.add(result.data, {animate: true, scrollTo: true}); + deferred.resolve(result.status, result.data); + } else { + if (result.data && result.data.message) { + OC.Notification.showTemporary(result.data.message); + } else { + OC.Notification.showTemporary(t('core', 'Could not create folder')); + } + deferred.reject(result.status); + } + } + ); + + return promise; + }, + + /** + * Returns whether the given file name exists in the list + * + * @param {string} file file name + * + * @return {bool} true if the file exists in the list, false otherwise + */ inList:function(file) { return this.findFileEl(file).length; }, @@ -2391,6 +2502,47 @@ }); }, + _renderNewButton: function() { + // if an upload button (legacy) already exists, skip + if ($('#controls .button.upload').length) { + return; + } + if (!this._addButtonTemplate) { + this._addButtonTemplate = Handlebars.compile(TEMPLATE_ADDBUTTON); + } + var $newButton = $(this._addButtonTemplate({ + addText: t('files', 'New'), + iconUrl: OC.imagePath('core', 'actions/add') + })); + + $('#controls .actions').prepend($newButton); + $newButton.tooltip({'placement': 'bottom'}); + + $newButton.click(_.bind(this._onClickNewButton, this)); + this._newButton = $newButton; + }, + + _onClickNewButton: function(event) { + var $target = $(event.target); + if (!$target.hasClass('.button')) { + $target = $target.closest('.button'); + } + this._newButton.tooltip('hide'); + event.preventDefault(); + if ($target.hasClass('disabled')) { + return false; + } + if (!this._newFileMenu) { + this._newFileMenu = new OCA.Files.NewFileMenu({ + fileList: this + }); + $('body').append(this._newFileMenu.$el); + } + this._newFileMenu.showAt($target); + + return false; + }, + /** * Register a tab view to be added to all views */ diff --git a/apps/files/js/newfilemenu.js b/apps/files/js/newfilemenu.js new file mode 100644 index 00000000000..4c021e6b873 --- /dev/null +++ b/apps/files/js/newfilemenu.js @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2014 + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +/* global Files */ + +(function() { + + var TEMPLATE_MENU = + '<ul>' + + '<li>' + + '<label for="file_upload_start" class="menuitem" data-action="upload" title="{{uploadMaxHumanFilesize}}"><span class="svg icon icon-upload"></span><span class="displayname">{{uploadLabel}}</span></label>' + + '</li>' + + '{{#each items}}' + + '<li>' + + '<a href="#" class="menuitem" data-templatename="{{templateName}}" data-filetype="{{fileType}}" data-action="{{id}}"><span class="icon {{iconClass}} svg"></span><span class="displayname">{{displayName}}</span></a>' + + '</li>' + + '{{/each}}' + + '</ul>'; + + var TEMPLATE_FILENAME_FORM = + '<form class="filenameform">' + + '<label class="hidden-visually" for="{{cid}}-input-{{fileType}}">{{fileName}}</label>' + + '<input id="{{cid}}-input-{{fileType}}" type="text" value="{{fileName}}">' + + '</form>'; + + /** + * Construct a new NewFileMenu instance + * @constructs NewFileMenu + * + * @memberof OCA.Files + */ + var NewFileMenu = OC.Backbone.View.extend({ + tagName: 'div', + className: 'newFileMenu popovermenu bubble hidden open menu', + + events: { + 'click .menuitem': '_onClickAction' + }, + + initialize: function(options) { + var self = this; + var $uploadEl = $('#file_upload_start'); + if ($uploadEl.length) { + $uploadEl.on('fileuploadstart', function() { + self.trigger('actionPerformed', 'upload'); + }); + } else { + console.warn('Missing upload element "file_upload_start"'); + } + + this._fileList = options && options.fileList; + }, + + template: function(data) { + if (!OCA.Files.NewFileMenu._TEMPLATE) { + OCA.Files.NewFileMenu._TEMPLATE = Handlebars.compile(TEMPLATE_MENU); + } + return OCA.Files.NewFileMenu._TEMPLATE(data); + }, + + /** + * Event handler whenever an action has been clicked within the menu + * + * @param {Object} event event object + */ + _onClickAction: function(event) { + var $target = $(event.target); + if (!$target.hasClass('menuitem')) { + $target = $target.closest('.menuitem'); + } + var action = $target.attr('data-action'); + // note: clicking the upload label will automatically + // set the focus on the "file_upload_start" hidden field + // which itself triggers the upload dialog. + // Currently the upload logic is still in file-upload.js and filelist.js + if (action === 'upload') { + OC.hideMenus(); + } else { + event.preventDefault(); + this._promptFileName($target); + } + }, + + _promptFileName: function($target) { + var self = this; + if (!OCA.Files.NewFileMenu._TEMPLATE_FORM) { + OCA.Files.NewFileMenu._TEMPLATE_FORM = Handlebars.compile(TEMPLATE_FILENAME_FORM); + } + + if ($target.find('form').length) { + $target.find('input').focus(); + return; + } + + // discard other forms + this.$el.find('form').remove(); + this.$el.find('.displayname').removeClass('hidden'); + + $target.find('.displayname').addClass('hidden'); + + var newName = $target.attr('data-templatename'); + var fileType = $target.attr('data-filetype'); + var $form = $(OCA.Files.NewFileMenu._TEMPLATE_FORM({ + fileName: newName, + cid: this.cid, + fileType: fileType + })); + + //this.trigger('actionPerformed', action); + $target.append($form); + + // here comes the OLD code + var $input = $form.find('input'); + + var lastPos; + var checkInput = function () { + var filename = $input.val(); + try { + if (!Files.isFileNameValid(filename)) { + // Files.isFileNameValid(filename) throws an exception itself + } else if (self._fileList.inList(filename)) { + throw t('files', '{newname} already exists', {newname: filename}); + } else { + return true; + } + } catch (error) { + $input.attr('title', error); + $input.tooltip({placement: 'right', trigger: 'manual'}); + $input.tooltip('show'); + $input.addClass('error'); + } + return false; + }; + + // verify filename on typing + $input.keyup(function() { + if (checkInput()) { + $input.tooltip('hide'); + $input.removeClass('error'); + } + }); + + $input.focus(); + // pre select name up to the extension + lastPos = newName.lastIndexOf('.'); + if (lastPos === -1) { + lastPos = newName.length; + } + $input.selectRange(0, lastPos); + + $form.submit(function(event) { + event.stopPropagation(); + event.preventDefault(); + + if (checkInput()) { + var newname = $input.val(); + self._createFile(fileType, newname); + $form.remove(); + $target.find('.displayname').removeClass('hidden'); + OC.hideMenus(); + } + }); + }, + + /** + * Creates a file with the given type and name. + * This calls the matching methods on the attached file list. + * + * @param {string} fileType file type + * @param {string} name file name + */ + _createFile: function(fileType, name) { + switch(fileType) { + case 'file': + this._fileList.createFile(name); + break; + case 'folder': + this._fileList.createDirectory(name); + break; + default: + console.warn('Unknown file type "' + fileType + '"'); + } + }, + + /** + * Renders the menu with the currently set items + */ + render: function() { + this.$el.html(this.template({ + uploadMaxHumanFileSize: 'TODO', + uploadLabel: t('files', 'Upload'), + items: [{ + id: 'file', + displayName: t('files', 'Text file'), + templateName: t('files', 'New text file.txt'), + iconClass: 'icon-filetype-text', + fileType: 'file' + }, { + id: 'folder', + displayName: t('files', 'Folder'), + templateName: t('files', 'New folder'), + iconClass: 'icon-folder', + fileType: 'folder' + }] + })); + }, + + /** + * Displays the menu under the given element + * + * @param {Object} $target target element + */ + showAt: function($target) { + this.render(); + var targetOffset = $target.offset(); + this.$el.css({ + left: targetOffset.left, + top: targetOffset.top + $target.height() + }); + this.$el.removeClass('hidden'); + + OC.showMenu(null, this.$el); + } + }); + + OCA.Files.NewFileMenu = NewFileMenu; + +})(); diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php index 32651b261da..15af1970dc3 100644 --- a/apps/files/templates/list.php +++ b/apps/files/templates/list.php @@ -1,40 +1,16 @@ <div id="controls"> <div class="actions creatable hidden"> - <?php if(!isset($_['dirToken'])):?> - <div id="new" class="button"> - <a><?php p($l->t('New'));?></a> - <ul> - <li class="icon-filetype-text svg" - data-type="file" data-newname="<?php p($l->t('New text file')) ?>.txt"> - <p><?php p($l->t('Text file'));?></p> - </li> - <li class="icon-filetype-folder svg" - data-type="folder" data-newname="<?php p($l->t('New folder')) ?>"> - <p><?php p($l->t('Folder'));?></p> - </li> - </ul> - </div> - <?php endif;?> - <?php /* Note: the template attributes are here only for the public page. These are normally loaded - through ajax instead (updateStorageStatistics). - */ ?> - <div id="upload" class="button" + <?php /* + Only show upload button for public page + */ ?> + <?php if(isset($_['dirToken'])):?> + <div id="upload" class="button upload" title="<?php isset($_['uploadMaxHumanFilesize']) ? p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) : '' ?>"> - <input type="hidden" id="max_upload" name="MAX_FILE_SIZE" value="<?php isset($_['uploadMaxFilesize']) ? p($_['uploadMaxFilesize']) : '' ?>"> - <input type="hidden" id="upload_limit" value="<?php isset($_['uploadLimit']) ? p($_['uploadLimit']) : '' ?>"> - <input type="hidden" id="free_space" value="<?php isset($_['freeSpace']) ? p($_['freeSpace']) : '' ?>"> - <?php if(isset($_['dirToken'])):?> - <input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" /> - <input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" /> - <?php endif;?> - <input type="hidden" class="max_human_file_size" - value="(max <?php isset($_['uploadMaxHumanFilesize']) ? p($_['uploadMaxHumanFilesize']) : ''; ?>)"> - <input type="file" id="file_upload_start" name='files[]' - data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" /> <label for="file_upload_start" class="svg icon-upload"> <span class="hidden-visually"><?php p($l->t('Upload'))?></span> </label> </div> + <?php endif; ?> <div id="uploadprogresswrapper"> <div id="uploadprogressbar"></div> <button class="stop icon-close" style="display:none"> @@ -48,7 +24,19 @@ <div class="notCreatable notPublic hidden"> <?php p($l->t('You don’t have permission to upload or create files here'))?> </div> + <?php /* Note: the template attributes are here only for the public page. These are normally loaded + through ajax instead (updateStorageStatistics). + */ ?> <input type="hidden" name="permissions" value="" id="permissions"> + <input type="hidden" id="max_upload" name="MAX_FILE_SIZE" value="<?php isset($_['uploadMaxFilesize']) ? p($_['uploadMaxFilesize']) : '' ?>"> + <input type="hidden" id="upload_limit" value="<?php isset($_['uploadLimit']) ? p($_['uploadLimit']) : '' ?>"> + <input type="hidden" id="free_space" value="<?php isset($_['freeSpace']) ? p($_['freeSpace']) : '' ?>"> + <?php if(isset($_['dirToken'])):?> + <input type="hidden" id="publicUploadRequestToken" name="requesttoken" value="<?php p($_['requesttoken']) ?>" /> + <input type="hidden" id="dirToken" name="dirToken" value="<?php p($_['dirToken']) ?>" /> + <?php endif;?> + <input type="hidden" class="max_human_file_size" + value="(max <?php isset($_['uploadMaxHumanFilesize']) ? p($_['uploadMaxHumanFilesize']) : ''; ?>)"> </div> <div id="emptycontent" class="hidden"> @@ -101,6 +89,10 @@ </tfoot> </table> <input type="hidden" name="dir" id="dir" value="" /> +<div class="hiddenuploadfield"> + <input type="file" id="file_upload_start" class="hiddenuploadfield" name="files[]" + data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" /> +</div> <div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! --> <div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>"> <p> diff --git a/apps/files/tests/js/fileUploadSpec.js b/apps/files/tests/js/fileUploadSpec.js index 817654c4fa9..cad8468d1c8 100644 --- a/apps/files/tests/js/fileUploadSpec.js +++ b/apps/files/tests/js/fileUploadSpec.js @@ -19,7 +19,6 @@ * */ -/* global OC */ describe('OC.Upload tests', function() { var $dummyUploader; var testFile; @@ -118,54 +117,4 @@ describe('OC.Upload tests', function() { ); }); }); - describe('New file', function() { - var $input; - var currentDirStub; - - beforeEach(function() { - OC.Upload.init(); - $('#new>a').click(); - $('#new li[data-type=file]').click(); - $input = $('#new input[type=text]'); - - currentDirStub = sinon.stub(FileList, 'getCurrentDirectory'); - currentDirStub.returns('testdir'); - }); - afterEach(function() { - currentDirStub.restore(); - }); - it('sets default text in field', function() { - expect($input.length).toEqual(1); - expect($input.val()).toEqual('New text file.txt'); - }); - it('creates file when enter is pressed', function() { - $input.val('somefile.txt'); - $input.trigger(new $.Event('keyup', {keyCode: 13})); - $input.parent('form').submit(); - expect(fakeServer.requests.length).toEqual(2); - - var request = fakeServer.requests[1]; - expect(request.method).toEqual('POST'); - expect(request.url).toEqual(OC.webroot + '/index.php/apps/files/ajax/newfile.php'); - var query = OC.parseQueryString(request.requestBody); - expect(query).toEqual({ - dir: 'testdir', - filename: 'somefile.txt' - }); - }); - it('prevents entering invalid file names', function() { - $input.val('..'); - $input.trigger(new $.Event('keyup', {keyCode: 13})); - $input.parent('form').submit(); - expect(fakeServer.requests.length).toEqual(1); - }); - it('prevents entering file names that already exist', function() { - var inListStub = sinon.stub(FileList, 'inList').returns(true); - $input.val('existing.txt'); - $input.trigger(new $.Event('keyup', {keyCode: 13})); - $input.parent('form').submit(); - expect(fakeServer.requests.length).toEqual(1); - inListStub.restore(); - }); - }); }); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index a6d72a88efd..c05e7c37214 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -2157,6 +2157,93 @@ describe('OCA.Files.FileList tests', function() { expect(fileList.$fileList.find('tr').length).toEqual(5); }); }); + describe('create file', function() { + var deferredCreate; + + beforeEach(function() { + deferredCreate = $.Deferred(); + }); + + it('creates file with given name and adds it to the list', function() { + var deferred = fileList.createFile('test file.txt'); + var successStub = sinon.stub(); + var failureStub = sinon.stub(); + + deferred.done(successStub); + deferred.fail(failureStub); + + expect(fakeServer.requests.length).toEqual(1); + expect(fakeServer.requests[0].url).toEqual(OC.generateUrl('/apps/files/ajax/newfile.php')); + + var query = fakeServer.requests[0].requestBody; + expect(OC.parseQueryString(query)).toEqual({ + dir: '/subdir', + filename: 'test file.txt' + }); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + status: 'success', + data: { + path: '/subdir', + name: 'test file.txt', + mimetype: 'text/plain' + } + }) + ); + + var $tr = fileList.findFileEl('test file.txt'); + expect($tr.length).toEqual(1); + expect($tr.attr('data-mime')).toEqual('text/plain'); + + expect(successStub.calledOnce).toEqual(true); + expect(failureStub.notCalled).toEqual(true); + }); + // TODO: error cases + // TODO: unique name cases + }); + describe('create directory', function() { + it('creates directory with given name and adds it to the list', function() { + var deferred = fileList.createDirectory('test directory'); + var successStub = sinon.stub(); + var failureStub = sinon.stub(); + + deferred.done(successStub); + deferred.fail(failureStub); + + expect(fakeServer.requests.length).toEqual(1); + expect(fakeServer.requests[0].url).toEqual(OC.generateUrl('/apps/files/ajax/newfolder.php')); + var query = fakeServer.requests[0].requestBody; + expect(OC.parseQueryString(query)).toEqual({ + dir: '/subdir', + foldername: 'test directory' + }); + + fakeServer.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + status: 'success', + data: { + path: '/subdir', + name: 'test directory', + mimetype: 'httpd/unix-directory' + } + }) + ); + + var $tr = fileList.findFileEl('test directory'); + expect($tr.length).toEqual(1); + expect($tr.attr('data-mime')).toEqual('httpd/unix-directory'); + + expect(successStub.calledOnce).toEqual(true); + expect(failureStub.notCalled).toEqual(true); + }); + // TODO: error cases + // TODO: unique name cases + }); /** * Test upload mostly by testing the code inside the event handlers * that were registered on the magic upload object @@ -2359,4 +2446,36 @@ describe('OCA.Files.FileList tests', function() { expect(fileInfo.type).toEqual('file'); }); }); + describe('new file menu', function() { + var newFileMenuStub; + + beforeEach(function() { + newFileMenuStub = sinon.stub(OCA.Files.NewFileMenu.prototype, 'showAt'); + }); + afterEach(function() { + newFileMenuStub.restore(); + }) + it('renders new button when no legacy upload button exists', function() { + expect(fileList.$el.find('.button.upload').length).toEqual(0); + expect(fileList.$el.find('.button.new').length).toEqual(1); + }); + it('does not render new button when no legacy upload button exists (public page)', function() { + fileList.destroy(); + $('#controls').append('<input type="button" class="button upload" />'); + fileList = new OCA.Files.FileList($('#app-content-files')); + expect(fileList.$el.find('.button.upload').length).toEqual(1); + expect(fileList.$el.find('.button.new').length).toEqual(0); + }); + it('opens the new file menu when clicking on the "New" button', function() { + var $button = fileList.$el.find('.button.new'); + $button.click(); + expect(newFileMenuStub.calledOnce).toEqual(true); + }); + it('does not open the new file menu when button is disabled', function() { + var $button = fileList.$el.find('.button.new'); + $button.addClass('disabled'); + $button.click(); + expect(newFileMenuStub.notCalled).toEqual(true); + }); + }); }); diff --git a/apps/files/tests/js/newfilemenuSpec.js b/apps/files/tests/js/newfilemenuSpec.js new file mode 100644 index 00000000000..3d89a997eb2 --- /dev/null +++ b/apps/files/tests/js/newfilemenuSpec.js @@ -0,0 +1,119 @@ +/** +* ownCloud +* +* @author Vincent Petry +* @copyright 2015 Vincent Petry <pvince81@owncloud.com> +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +describe('OCA.Files.NewFileMenu', function() { + var FileList = OCA.Files.FileList; + var menu, fileList, $uploadField, $trigger; + + beforeEach(function() { + // dummy upload button + var $container = $('<div id="app-content-files"></div>'); + $uploadField = $('<input id="file_upload_start"></input>'); + $trigger = $('<a href="#">Menu</a>'); + $container.append($uploadField).append($trigger); + $('#testArea').append($container); + + fileList = new FileList($container); + menu = new OCA.Files.NewFileMenu({ + fileList: fileList + }); + menu.showAt($trigger); + }); + afterEach(function() { + OC.hideMenus(); + fileList = null; + menu = null; + }); + + describe('rendering', function() { + it('renders menu items', function() { + var $items = menu.$el.find('.menuitem'); + expect($items.length).toEqual(3); + // label points to the file_upload_start item + var $item = $items.eq(0); + expect($item.is('label')).toEqual(true); + expect($item.attr('for')).toEqual('file_upload_start'); + }); + }); + describe('New file/folder', function() { + var $input; + var createFileStub; + var createDirectoryStub; + + beforeEach(function() { + createFileStub = sinon.stub(FileList.prototype, 'createFile'); + createDirectoryStub = sinon.stub(FileList.prototype, 'createDirectory'); + menu.$el.find('.menuitem').eq(1).click(); + $input = menu.$el.find('form.filenameform input'); + }); + afterEach(function() { + createFileStub.restore(); + createDirectoryStub.restore(); + }); + + it('sets default text in field', function() { + expect($input.length).toEqual(1); + expect($input.val()).toEqual('New text file.txt'); + }); + it('creates file when enter is pressed', function() { + $input.val('somefile.txt'); + $input.trigger(new $.Event('keyup', {keyCode: 13})); + $input.parent('form').submit(); + + expect(createFileStub.calledOnce).toEqual(true); + expect(createFileStub.getCall(0).args[0]).toEqual('somefile.txt'); + expect(createDirectoryStub.notCalled).toEqual(true); + }); + it('prevents entering invalid file names', function() { + $input.val('..'); + $input.trigger(new $.Event('keyup', {keyCode: 13})); + $input.closest('form').submit(); + + expect(createFileStub.notCalled).toEqual(true); + expect(createDirectoryStub.notCalled).toEqual(true); + }); + it('prevents entering file names that already exist', function() { + var inListStub = sinon.stub(fileList, 'inList').returns(true); + $input.val('existing.txt'); + $input.trigger(new $.Event('keyup', {keyCode: 13})); + $input.closest('form').submit(); + + expect(createFileStub.notCalled).toEqual(true); + expect(createDirectoryStub.notCalled).toEqual(true); + inListStub.restore(); + }); + it('switching fields removes the previous form', function() { + menu.$el.find('.menuitem').eq(2).click(); + expect(menu.$el.find('form').length).toEqual(1); + }); + it('creates directory when clicking on create directory field', function() { + menu.$el.find('.menuitem').eq(2).click(); + $input = menu.$el.find('form.filenameform input'); + $input.val('some folder'); + $input.trigger(new $.Event('keyup', {keyCode: 13})); + $input.closest('form').submit(); + + expect(createDirectoryStub.calledOnce).toEqual(true); + expect(createDirectoryStub.getCall(0).args[0]).toEqual('some folder'); + expect(createFileStub.notCalled).toEqual(true); + }); + }); +}); diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 43c76125e16..cde28c80fc4 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -17,6 +17,7 @@ OCP\Util::addStyle('files', 'upload'); OCP\Util::addScript('files', 'filesummary'); OCP\Util::addScript('files', 'breadcrumb'); OCP\Util::addScript('files', 'fileinfomodel'); +OCP\Util::addScript('files', 'newfilemenu'); OCP\Util::addScript('files', 'files'); OCP\Util::addScript('files', 'filelist'); OCP\Util::addscript('files', 'keyboardshortcuts'); diff --git a/core/css/apps.css b/core/css/apps.css index 4118e6ea668..0371f2bbde7 100644 --- a/core/css/apps.css +++ b/core/css/apps.css @@ -629,3 +629,68 @@ em { .tabsContainer .tab { padding: 15px; } + +/* popover menu styles (use together with "bubble" class) */ +.popovermenu .menuitem, +.popovermenu .menuitem>span { + cursor: pointer; +} + +.popovermenu .menuitem { + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; + filter: alpha(opacity=50); + opacity: .5; +} + +.popovermenu .menuitem:hover, +.popovermenu .menuitem:focus { + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; + filter: alpha(opacity=100); + opacity: 1; +} + +.popovermenu { + padding: 4px 12px; +} + +.popovermenu li { + padding: 5px 0; +} + +.popovermenu .menuitem img { + padding: initial; +} + +.popovermenu a.menuitem, +.popovermenu label.menuitem, +.popovermenu .menuitem { + padding: 10px; + margin: -10px; +} + +.popovermenu.hidden { + display: none; +} + +.popovermenu .menuitem { + display: block; + line-height: 30px; + padding-left: 5px; + color: #000; + padding: 0; +} + +.popovermenu .menuitem .icon, +.popovermenu .menuitem .no-icon { + display: inline-block; + width: 16px; + margin-right: 10px; +} + +.popovermenu .menuitem { + opacity: 0.5; +} + +.popovermenu li:hover .menuitem { + opacity: 1; +} diff --git a/core/css/styles.css b/core/css/styles.css index dd0110ded3a..9219068dc38 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -955,6 +955,7 @@ a.bookmarklet { background-color:#ddd; border:1px solid #ccc; padding:5px;paddin .ui-icon-circle-triangle-e{ background-image:url('../img/actions/play-next.svg'); } .ui-icon-circle-triangle-w{ background-image:url('../img/actions/play-previous.svg'); } + .ui-datepicker-prev,.ui-datepicker-next{ border:1px solid #ddd; background:#fff; } /* ---- DIALOGS ---- */ @@ -1142,3 +1143,12 @@ fieldset.warning legend + p, fieldset.update legend + p { @-ms-viewport { width: device-width; } + +/* hidden input type=file field */ +.hiddenuploadfield { + width: 0; + height: 0; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; +} + |