diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index a9c85127c6..97b2a0c451 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;
@@ -143,6 +97,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;
@@ -599,7 +577,8 @@ a.action > img {
#fileList a.action.action-menu {
padding: 17px 14px;
}
-#fileList .fileActionsMenu {
+
+#fileList .popovermenu {
margin-right: 21px;
}
@@ -654,13 +633,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;
@@ -744,25 +723,45 @@ 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 {
+.newFileMenu .menuitem {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.newFileMenu.bubble:after {
+ left: 75px;
+ right: auto;
+}
+.newFileMenu.bubble:before {
+ left: 75px;
+ right: auto;
+}
+
+.newFileMenu .filenameform {
+ display: inline-block;
+}
+
+.newFileMenu .filenameform input {
+ width: 100px;
+}
+
+#fileList .popovermenu .action {
display: block;
line-height: 30px;
padding-left: 5px;
@@ -770,17 +769,3 @@ table.dragshadow td.size {
padding: 0;
}
-.fileActionsMenu .action img,
-.fileActionsMenu .action .no-icon {
- display: inline-block;
- width: 16px;
- margin-right: 5px;
-}
-
-.fileActionsMenu .action {
- opacity: 0.5;
-}
-
-.fileActionsMenu li:hover .action {
- opacity: 1;
-}
diff --git a/apps/files/css/upload.css b/apps/files/css/upload.css
index bd60f83138..07b788b937 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 a73caa50fb..cc007ebdb0 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 6b6acdb5e0..17f0f77716 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('
' + $(element).data('text') + '
');
- }
- });
- });
- $('#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('' + $(element).data('text') + '
');
- }
- });
-
- var type = $(this).data('type');
- var text = $(this).children('p').text();
- $(this).data('text', text);
- $(this).children('p').remove();
-
- // add input field
- var form = $('');
- var input = $('');
- 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.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('' + li.data('text') + '
');
- $('#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 623ebde544..5ab0e42f93 100644
--- a/apps/files/js/fileactionsmenu.js
+++ b/apps/files/js/fileactionsmenu.js
@@ -14,7 +14,7 @@
'';
@@ -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 ac96d58701..5f55d42143 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -9,6 +9,9 @@
*/
(function() {
+
+ var TEMPLATE_ADDBUTTON = '';
+
/**
* @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,9 @@
* Destroy / uninitialize this instance.
*/
destroy: function() {
+ if (this._newFileMenu) {
+ this._newFileMenu.remove();
+ }
// TODO: also unregister other event handlers
this.fileActions.off('registerAction', this._onFileActionsUpdated);
this.fileActions.off('setDefault', this._onFileActionsUpdated);
@@ -1679,6 +1687,102 @@
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
+ */
+ 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.filePath('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
+ */
+ 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.filePath('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;
},
@@ -2340,6 +2444,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 0000000000..c851096bd8
--- /dev/null
+++ b/apps/files/js/newfilemenu.js
@@ -0,0 +1,237 @@
+/*
+ * 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 =
+ '' +
+ '- ' +
+ '' +
+ '
' +
+ '{{#each items}}' +
+ '- ' +
+ '' +
+ '
' +
+ '{{/each}}' +
+ '
';
+
+ var TEMPLATE_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();
+ $('#content').focus();
+ $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-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 32651b261d..15af1970dc 100644
--- a/apps/files/templates/list.php
+++ b/apps/files/templates/list.php
@@ -1,40 +1,16 @@