summaryrefslogtreecommitdiffstats
path: root/apps/files/js
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files/js')
-rw-r--r--apps/files/js/file-upload.js15
-rw-r--r--apps/files/js/fileactions.js352
-rw-r--r--apps/files/js/filelist.js47
3 files changed, 285 insertions, 129 deletions
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index ab450dc5cac..9fe623075bc 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -175,7 +175,14 @@ OC.Upload = {
* @param {function} callbacks.onCancel
*/
checkExistingFiles: function (selection, callbacks) {
- // TODO check filelist before uploading and show dialog on conflicts, use callbacks
+ /*
+ $.each(selection.uploads, function(i, upload) {
+ var $row = OCA.Files.App.fileList.findFileEl(upload.files[0].name);
+ if ($row) {
+ // TODO check filelist before uploading and show dialog on conflicts, use callbacks
+ }
+ });
+ */
callbacks.onNoConflicts(selection);
},
@@ -417,11 +424,15 @@ OC.Upload = {
data.textStatus = 'servererror';
data.errorThrown = t('files', 'Could not get result from server.');
fu._trigger('fail', e, data);
+ } else if (result[0].status === 'readonly') {
+ var original = result[0];
+ var replacement = data.files[0];
+ OC.dialogs.fileexists(data, original, replacement, OC.Upload);
} else if (result[0].status === 'existserror') {
//show "file already exists" dialog
var original = result[0];
var replacement = data.files[0];
- OC.dialogs.fileexists(data, original, replacement, OC.Upload, fu);
+ OC.dialogs.fileexists(data, original, replacement, OC.Upload);
} else if (result[0].status !== 'success') {
//delete data.jqXHR;
data.textStatus = 'servererror';
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 5bf1618b0b8..da48cf29be0 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -8,7 +8,6 @@
*
*/
-/* global trashBinApp */
(function() {
/**
@@ -109,33 +108,37 @@
permissions: permissions,
icon: icon,
actionHandler: action,
- displayName: displayName
+ displayName: displayName || name
});
},
/**
* Register action
*
- * @param {Object} action action object
- * @param {String} action.name identifier of the action
- * @param {String} action.displayName display name of the action, defaults
- * to the name given in action.name
- * @param {String} action.mime mime type
- * @param {int} action.permissions permissions
- * @param {(Function|String)} action.icon icon path to the icon or function
- * that returns it
- * @param {OCA.Files.FileActions~actionHandler} action.actionHandler action handler function
+ * @param {OCA.Files.FileAction} action object
*/
registerAction: function (action) {
var mime = action.mime;
var name = action.name;
+ var actionSpec = {
+ action: action.actionHandler,
+ name: name,
+ displayName: action.displayName,
+ mime: mime,
+ icon: action.icon,
+ permissions: action.permissions
+ };
+ if (_.isUndefined(action.displayName)) {
+ actionSpec.displayName = t('files', name);
+ }
+ if (_.isFunction(action.render)) {
+ actionSpec.render = action.render;
+ } else {
+ actionSpec.render = _.bind(this._defaultRenderAction, this);
+ }
if (!this.actions[mime]) {
this.actions[mime] = {};
}
- this.actions[mime][name] = {
- action: action.actionHandler,
- permissions: action.permissions,
- displayName: action.displayName || t('files', name)
- };
+ this.actions[mime][name] = actionSpec;
this.icons[name] = action.icon;
this._notifyUpdateListeners('registerAction', {action: action});
},
@@ -213,6 +216,128 @@
return actions[name];
},
/**
+ * Default function to render actions
+ *
+ * @param {OCA.Files.FileAction} actionSpec file action spec
+ * @param {boolean} isDefault true if the action is a default one,
+ * false otherwise
+ * @param {OCA.Files.FileActionContext} context action context
+ */
+ _defaultRenderAction: function(actionSpec, isDefault, context) {
+ var name = actionSpec.name;
+ if (name === 'Download' || !isDefault) {
+ var $actionLink = this._makeActionLink(actionSpec, context);
+ context.$file.find('a.name>span.fileactions').append($actionLink);
+ return $actionLink;
+ }
+ },
+ /**
+ * Renders the action link element
+ *
+ * @param {OCA.Files.FileAction} actionSpec action object
+ * @param {OCA.Files.FileActionContext} context action context
+ */
+ _makeActionLink: function(actionSpec, context) {
+ var img = actionSpec.icon;
+ if (img && img.call) {
+ img = img(context.$file.attr('data-file'));
+ }
+ var html = '<a href="#">';
+ if (img) {
+ html += '<img class="svg" alt="" src="' + img + '" />';
+ }
+ if (actionSpec.displayName) {
+ html += '<span> ' + actionSpec.displayName + '</span></a>';
+ }
+
+ return $(html);
+ },
+ /**
+ * Custom renderer for the "Rename" action.
+ * Displays the rename action as an icon behind the file name.
+ *
+ * @param {OCA.Files.FileAction} actionSpec file action to render
+ * @param {boolean} isDefault true if the action is a default action,
+ * false otherwise
+ * @param {OCAFiles.FileActionContext} context rendering context
+ */
+ _renderRenameAction: function(actionSpec, isDefault, context) {
+ var $actionEl = this._makeActionLink(actionSpec, context);
+ var $container = context.$file.find('a.name span.nametext');
+ $actionEl.find('img').attr('alt', t('files', 'Rename'));
+ $container.find('.action-rename').remove();
+ $container.append($actionEl);
+ return $actionEl;
+ },
+ /**
+ * Custom renderer for the "Delete" action.
+ * Displays the "Delete" action as a trash icon at the end of
+ * the table row.
+ *
+ * @param {OCA.Files.FileAction} actionSpec file action to render
+ * @param {boolean} isDefault true if the action is a default action,
+ * false otherwise
+ * @param {OCAFiles.FileActionContext} context rendering context
+ */
+ _renderDeleteAction: function(actionSpec, isDefault, context) {
+ var mountType = context.$file.attr('data-mounttype');
+ var deleteTitle = t('files', 'Delete');
+ if (mountType === 'external-root') {
+ deleteTitle = t('files', 'Disconnect storage');
+ } else if (mountType === 'shared-root') {
+ deleteTitle = t('files', 'Unshare');
+ }
+ var $actionLink = $('<a href="#" original-title="' +
+ escapeHTML(deleteTitle) +
+ '" class="action delete icon-delete" />'
+ );
+ var $container = context.$file.find('td:last');
+ $container.find('.delete').remove();
+ $container.append($actionLink);
+ return $actionLink;
+ },
+ /**
+ * Renders the action element by calling actionSpec.render() and
+ * registers the click event to process the action.
+ *
+ * @param {OCA.Files.FileAction} actionSpec file action to render
+ * @param {boolean} isDefault true if the action is a default action,
+ * false otherwise
+ * @param {OCAFiles.FileActionContext} context rendering context
+ */
+ _renderAction: function(actionSpec, isDefault, context) {
+ var $actionEl = actionSpec.render(actionSpec, isDefault, context);
+ if (!$actionEl || !$actionEl.length) {
+ return;
+ }
+ $actionEl.addClass('action action-' + actionSpec.name.toLowerCase());
+ $actionEl.attr('data-action', actionSpec.name);
+ $actionEl.on(
+ 'click', {
+ a: null
+ },
+ function(event) {
+ var $file = $(event.target).closest('tr');
+ var currentFile = $file.find('td.filename');
+ var fileName = $file.attr('data-file');
+ event.stopPropagation();
+ event.preventDefault();
+
+ context.fileActions.currentFile = currentFile;
+ // also set on global object for legacy apps
+ window.FileActions.currentFile = currentFile;
+
+ actionSpec.action(
+ fileName,
+ _.extend(context, {
+ dir: $file.attr('data-path') || context.fileList.getCurrentDirectory()
+ })
+ );
+ }
+ );
+ return $actionEl;
+ },
+ /**
* Display file actions for the given element
* @param parent "td" element of the file for which to display actions
* @param triggerEvent if true, triggers the fileActionsReady on the file
@@ -226,107 +351,51 @@
return;
}
this.currentFile = parent;
- var $tr = parent.closest('tr');
var self = this;
- var actions = this.getActions(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions());
- var file = this.getCurrentFile();
+ var $tr = parent.closest('tr');
+ var actions = this.getActions(
+ this.getCurrentMimeType(),
+ this.getCurrentType(),
+ this.getCurrentPermissions()
+ );
var nameLinks;
if ($tr.data('renaming')) {
return;
}
- // recreate fileactions
+ // recreate fileactions container
nameLinks = parent.children('a.name');
nameLinks.find('.fileactions, .nametext .action').remove();
nameLinks.append('<span class="fileactions" />');
- var defaultAction = this.getDefault(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions());
-
- var actionHandler = function (event) {
- event.stopPropagation();
- event.preventDefault();
+ var defaultAction = this.getDefault(
+ this.getCurrentMimeType(),
+ this.getCurrentType(),
+ this.getCurrentPermissions()
+ );
- self.currentFile = event.data.elem;
- // also set on global object for legacy apps
- window.FileActions.currentFile = self.currentFile;
-
- var file = self.getCurrentFile();
- var $tr = $(this).closest('tr');
-
- event.data.actionFunc(file, {
- $file: $tr,
- fileList: fileList,
- fileActions: self,
- dir: $tr.attr('data-path') || fileList.getCurrentDirectory()
- });
- };
-
- var addAction = function (name, action, displayName) {
-
- if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') {
-
- var img = self.icons[name],
- actionText = displayName,
- actionContainer = 'a.name>span.fileactions';
-
- if (name === 'Rename') {
- // rename has only an icon which appears behind
- // the file name
- actionText = '';
- actionContainer = 'a.name span.nametext';
- }
- if (img.call) {
- img = img(file);
- }
- var html = '<a href="#" class="action action-' + name.toLowerCase() + '" data-action="' + name + '">';
- if (img) {
- html += '<img class ="svg" alt="" src="' + img + '" />';
- }
- html += '<span> ' + actionText + '</span></a>';
-
- var element = $(html);
- element.data('action', name);
- element.on('click', {a: null, elem: parent, actionFunc: actions[name].action}, actionHandler);
- parent.find(actionContainer).append(element);
- }
-
- };
-
- $.each(actions, function (name, action) {
+ $.each(actions, function (name, actionSpec) {
if (name !== 'Share') {
- displayName = action.displayName;
- ah = action.action;
-
- addAction(name, ah, displayName);
+ self._renderAction(
+ actionSpec,
+ actionSpec.action === defaultAction, {
+ $file: $tr,
+ fileActions: this,
+ fileList : fileList
+ }
+ );
}
});
- if(actions.Share){
- displayName = t('files', 'Share');
- addAction('Share', actions.Share, displayName);
- }
-
- // remove the existing delete action
- parent.parent().children().last().find('.action.delete').remove();
- if (actions['Delete']) {
- var img = self.icons['Delete'];
- var html;
- var mountType = $tr.attr('data-mounttype');
- var deleteTitle = t('files', 'Delete');
- if (mountType === 'external-root') {
- deleteTitle = t('files', 'Disconnect storage');
- } else if (mountType === 'shared-root') {
- deleteTitle = t('files', 'Unshare');
- } else if (fileList.id === 'trashbin') {
- deleteTitle = t('files', 'Delete permanently');
- }
-
- if (img.call) {
- img = img(file);
- }
- html = '<a href="#" original-title="' + escapeHTML(deleteTitle) + '" class="action delete icon-delete" />';
- var element = $(html);
- element.data('action', actions['Delete']);
- element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler);
- parent.parent().children().last().append(element);
+ // added here to make sure it's always the last action
+ var shareActionSpec = actions.Share;
+ if (shareActionSpec){
+ this._renderAction(
+ shareActionSpec,
+ shareActionSpec.action === defaultAction, {
+ $file: $tr,
+ fileActions: this,
+ fileList: fileList
+ }
+ );
}
if (triggerEvent){
@@ -350,18 +419,34 @@
* Register the actions that are used by default for the files app.
*/
registerDefaultActions: function() {
- this.register('all', 'Delete', OC.PERMISSION_DELETE, function () {
- return OC.imagePath('core', 'actions/delete');
- }, function (filename, context) {
- context.fileList.do_delete(filename, context.dir);
- $('.tipsy').remove();
+ this.registerAction({
+ name: 'Delete',
+ displayName: '',
+ mime: 'all',
+ permissions: OC.PERMISSION_DELETE,
+ icon: function() {
+ return OC.imagePath('core', 'actions/delete');
+ },
+ render: _.bind(this._renderDeleteAction, this),
+ actionHandler: function(fileName, context) {
+ context.fileList.do_delete(fileName, context.dir);
+ $('.tipsy').remove();
+ }
});
// t('files', 'Rename')
- this.register('all', 'Rename', OC.PERMISSION_UPDATE, function () {
- return OC.imagePath('core', 'actions/rename');
- }, function (filename, context) {
- context.fileList.rename(filename);
+ this.registerAction({
+ name: 'Rename',
+ displayName: '',
+ mime: 'all',
+ permissions: OC.PERMISSION_UPDATE,
+ icon: function() {
+ return OC.imagePath('core', 'actions/rename');
+ },
+ render: _.bind(this._renderRenameAction, this),
+ actionHandler: function (filename, context) {
+ context.fileList.rename(filename);
+ }
});
this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
@@ -389,6 +474,47 @@
OCA.Files.FileActions = FileActions;
/**
+ * File action attributes.
+ *
+ * @todo make this a real class in the future
+ * @typedef {Object} OCA.Files.FileAction
+ *
+ * @property {String} name identifier of the action
+ * @property {String} displayName display name of the action, defaults
+ * to the name given in name property
+ * @property {String} mime mime type
+ * @property {int} permissions permissions
+ * @property {(Function|String)} icon icon path to the icon or function
+ * that returns it
+ * @property {OCA.Files.FileActions~renderActionFunction} [render] optional rendering function
+ * @property {OCA.Files.FileActions~actionHandler} actionHandler action handler function
+ */
+
+ /**
+ * File action context attributes.
+ *
+ * @typedef {Object} OCA.Files.FileActionContext
+ *
+ * @property {Object} $file jQuery file row element
+ * @property {OCA.Files.FileActions} fileActions file actions object
+ * @property {OCA.Files.FileList} fileList file list object
+ */
+
+ /**
+ * Render function for actions.
+ * The function must render a link element somewhere in the DOM
+ * and return it. The function should NOT register the event handler
+ * as this will be done after the link was returned.
+ *
+ * @callback OCA.Files.FileActions~renderActionFunction
+ * @param {OCA.Files.FileAction} actionSpec action definition
+ * @param {Object} $row row container
+ * @param {boolean} isDefault true if the action is the default one,
+ * false otherwise
+ * @return {Object} jQuery link object
+ */
+
+ /**
* Action handler function for file actions
*
* @callback OCA.Files.FileActions~actionHandler
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index f4b45fc58bf..b4702ce4f2b 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -94,6 +94,12 @@
fileActions: null,
/**
+ * Whether selection is allowed, checkboxes and selection overlay will
+ * be rendered
+ */
+ _allowSelection: true,
+
+ /**
* Map of file id to file data
* @type Object.<int, Object>
*/
@@ -166,6 +172,9 @@
}
this.$el = $el;
+ if (options.id) {
+ this.id = options.id;
+ }
this.$container = options.scrollContainer || $(window);
this.$table = $el.find('table:first');
this.$fileList = $el.find('#fileList');
@@ -200,7 +209,7 @@
this.$el.on('show', this._onResize);
this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this));
- this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this));
+ this.$fileList.on('change', 'td.filename>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
this.$el.on('urlChanged', _.bind(this._onUrlChanged, this));
this.$el.find('.select-all').click(_.bind(this._onClickSelectAll, this));
this.$el.find('.download').click(_.bind(this._onClickDownloadSelected, this));
@@ -215,6 +224,8 @@
self.scrollTo(options.scrollTo);
});
}
+
+ OC.Plugins.attach('OCA.Files.FileList', this);
},
/**
@@ -224,6 +235,7 @@
// TODO: also unregister other event handlers
this.fileActions.off('registerAction', this._onFileActionsUpdated);
this.fileActions.off('setDefault', this._onFileActionsUpdated);
+ OC.Plugins.detach('OCA.Files.FileList', this);
},
/**
@@ -275,7 +287,7 @@
* @param state true to select, false to deselect
*/
_selectFileEl: function($tr, state) {
- var $checkbox = $tr.find('td.filename>input:checkbox');
+ var $checkbox = $tr.find('td.filename>.selectCheckBox');
var oldData = !!this._selectedFiles[$tr.data('id')];
var data;
$checkbox.prop('checked', state);
@@ -324,7 +336,7 @@
else {
this._lastChecked = $tr;
}
- var $checkbox = $tr.find('td.filename>input:checkbox');
+ var $checkbox = $tr.find('td.filename>.selectCheckBox');
this._selectFileEl($tr, !$checkbox.prop('checked'));
this.updateSelectionSummary();
} else {
@@ -366,7 +378,7 @@
*/
_onClickSelectAll: function(e) {
var checked = $(e.target).prop('checked');
- this.$fileList.find('td.filename>input:checkbox').prop('checked', checked)
+ this.$fileList.find('td.filename>.selectCheckBox').prop('checked', checked)
.closest('tr').toggleClass('selected', checked);
this._selectedFiles = {};
this._selectionSummary.clear();
@@ -554,7 +566,7 @@
this.$fileList.append(tr);
if (isAllSelected || this._selectedFiles[fileData.id]) {
tr.addClass('selected');
- tr.find('input:checkbox').prop('checked', true);
+ tr.find('.selectCheckBox').prop('checked', true);
}
if (animate) {
tr.addClass('appear transparent');
@@ -683,10 +695,8 @@
}
// filename td
- td = $('<td></td>').attr({
- "class": "filename",
- "style": 'background-image:url(' + icon + '); background-size: 32px;'
- });
+ td = $('<td class="filename"></td>');
+
// linkUrl
if (type === 'dir') {
@@ -695,8 +705,16 @@
else {
linkUrl = this.getDownloadUrl(name, path);
}
- td.append('<input id="select-' + this.id + '-' + fileData.id +
- '" type="checkbox" /><label for="select-' + this.id + '-' + fileData.id + '"></label>');
+ if (this._allowSelection) {
+ td.append(
+ '<input id="select-' + this.id + '-' + fileData.id +
+ '" type="checkbox" class="selectCheckBox"/><label for="select-' + this.id + '-' + fileData.id + '">' +
+ '<div class="thumbnail" style="background-image:url(' + icon + '); background-size: 32px;"></div>' +
+ '</label>'
+ );
+ } else {
+ td.append('<div class="thumbnail" style="background-image:url(' + icon + '); background-size: 32px;"></div>');
+ }
var linkElem = $('<a></a>').attr({
"class": "name",
"href": linkUrl
@@ -901,6 +919,7 @@
this.fileActions.display(filenameTd, !options.silent, this);
if (fileData.isPreviewAvailable) {
+ var iconDiv = filenameTd.find('.thumbnail');
// lazy load / newly inserted td ?
if (options.animate) {
this.lazyLoadPreview({
@@ -908,7 +927,7 @@
mime: mime,
etag: fileData.etag,
callback: function(url) {
- filenameTd.css('background-image', 'url(' + url + ')');
+ iconDiv.css('background-image', 'url(' + url + ')');
}
});
}
@@ -920,7 +939,7 @@
};
var previewUrl = this.generatePreviewUrl(urlSpec);
previewUrl = previewUrl.replace('(', '%28').replace(')', '%29');
- filenameTd.css('background-image', 'url(' + previewUrl + ')');
+ iconDiv.css('background-image', 'url(' + previewUrl + ')');
}
}
return tr;
@@ -1526,7 +1545,7 @@
var fileEl = self.remove(file, {updateSummary: false});
// FIXME: not sure why we need this after the
// element isn't even in the DOM any more
- fileEl.find('input[type="checkbox"]').prop('checked', false);
+ fileEl.find('.selectCheckBox').prop('checked', false);
fileEl.removeClass('selected');
self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
});