Merge pull request #7196 from nextcloud/sharing-confirm-button

Add confirmation button to share input
This commit is contained in:
Morris Jobke 2018-03-21 15:08:39 +01:00 committed by GitHub
commit 6f455d5c41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 1920 additions and 242 deletions

View file

@ -13,15 +13,23 @@
top: 0px;
}
.shareTabView .shareWithRemoteInfo,
.shareTabView .shareWithConfirm,
.shareTabView .clipboardButton,
.shareTabView .linkPass .icon-loading-small {
position: absolute;
right: -7px;
top: -4px;
top: -2px;
padding: 14px;
}
.shareTabView .shareWithConfirm {
opacity: .5;
}
.shareTabView .shareWithField:focus ~ .shareWithConfirm {
opacity: 1;
}
.shareTabView .linkMore {
position: absolute;
right: -7px;

View file

@ -22,7 +22,7 @@
'<div class="oneline">' +
' <input id="shareWith-{{cid}}" class="shareWithField" type="text" placeholder="{{sharePlaceholder}}" />' +
' <span class="shareWithLoading icon-loading-small hidden"></span>'+
'{{{shareInfo}}}' +
' <span class="shareWithConfirm icon icon-confirm"></span>' +
'</div>' +
'{{/if}}' +
'<div class="shareeListView subView"></div>' +
@ -30,10 +30,6 @@
'<div class="expirationView subView"></div>' +
'<div class="loading hidden" style="height: 50px"></div>';
var TEMPLATE_SHARE_INFO =
'<span class="icon icon-info shareWithRemoteInfo hasTooltip" ' +
'title="{{tooltip}}"></span>';
/**
* @class OCA.Share.ShareDialogView
* @member {OC.Share.ShareItemModel} model
@ -69,9 +65,16 @@
/** @type {object} **/
shareeListView: undefined,
/** @type {object} **/
_lastSuggestions: undefined,
/** @type {int} **/
_pendingOperationsCount: 0,
events: {
'focus .shareWithField': 'onShareWithFieldFocus',
'input .shareWithField': 'onShareWithFieldChanged'
'input .shareWithField': 'onShareWithFieldChanged',
'click .shareWithConfirm': '_confirmShare'
},
initialize: function(options) {
@ -138,11 +141,182 @@
this.$el.find('.shareWithField').autocomplete("search");
},
_getSuggestions: function(searchTerm, perPage, model) {
if (this._lastSuggestions &&
this._lastSuggestions.searchTerm === searchTerm &&
this._lastSuggestions.perPage === perPage &&
this._lastSuggestions.model === model) {
return this._lastSuggestions.promise;
}
var deferred = $.Deferred();
$.get(
OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
{
format: 'json',
search: searchTerm,
perPage: perPage,
itemType: model.get('itemType')
},
function (result) {
if (result.ocs.meta.statuscode === 100) {
var filter = function(users, groups, remotes, emails, circles) {
if (typeof(emails) === 'undefined') {
emails = [];
}
if (typeof(circles) === 'undefined') {
circles = [];
}
var usersLength;
var groupsLength;
var remotesLength;
var emailsLength;
var circlesLength;
var i, j;
//Filter out the current user
usersLength = users.length;
for (i = 0; i < usersLength; i++) {
if (users[i].value.shareWith === OC.currentUser) {
users.splice(i, 1);
break;
}
}
// Filter out the owner of the share
if (model.hasReshare()) {
usersLength = users.length;
for (i = 0 ; i < usersLength; i++) {
if (users[i].value.shareWith === model.getReshareOwner()) {
users.splice(i, 1);
break;
}
}
}
var shares = model.get('shares');
var sharesLength = shares.length;
// Now filter out all sharees that are already shared with
for (i = 0; i < sharesLength; i++) {
var share = shares[i];
if (share.share_type === OC.Share.SHARE_TYPE_USER) {
usersLength = users.length;
for (j = 0; j < usersLength; j++) {
if (users[j].value.shareWith === share.share_with) {
users.splice(j, 1);
break;
}
}
} else if (share.share_type === OC.Share.SHARE_TYPE_GROUP) {
groupsLength = groups.length;
for (j = 0; j < groupsLength; j++) {
if (groups[j].value.shareWith === share.share_with) {
groups.splice(j, 1);
break;
}
}
} else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE) {
remotesLength = remotes.length;
for (j = 0; j < remotesLength; j++) {
if (remotes[j].value.shareWith === share.share_with) {
remotes.splice(j, 1);
break;
}
}
} else if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
emailsLength = emails.length;
for (j = 0; j < emailsLength; j++) {
if (emails[j].value.shareWith === share.share_with) {
emails.splice(j, 1);
break;
}
}
} else if (share.share_type === OC.Share.SHARE_TYPE_CIRCLE) {
circlesLength = circles.length;
for (j = 0; j < circlesLength; j++) {
if (circles[j].value.shareWith === share.share_with) {
circles.splice(j, 1);
break;
}
}
}
}
};
filter(
result.ocs.data.exact.users,
result.ocs.data.exact.groups,
result.ocs.data.exact.remotes,
result.ocs.data.exact.emails,
result.ocs.data.exact.circles
);
var exactUsers = result.ocs.data.exact.users;
var exactGroups = result.ocs.data.exact.groups;
var exactRemotes = result.ocs.data.exact.remotes;
var exactEmails = [];
if (typeof(result.ocs.data.emails) !== 'undefined') {
exactEmails = result.ocs.data.exact.emails;
}
var exactCircles = [];
if (typeof(result.ocs.data.circles) !== 'undefined') {
exactCircles = result.ocs.data.exact.circles;
}
var exactMatches = exactUsers.concat(exactGroups).concat(exactRemotes).concat(exactEmails).concat(exactCircles);
filter(
result.ocs.data.users,
result.ocs.data.groups,
result.ocs.data.remotes,
result.ocs.data.emails,
result.ocs.data.circles
);
var users = result.ocs.data.users;
var groups = result.ocs.data.groups;
var remotes = result.ocs.data.remotes;
var lookup = result.ocs.data.lookup;
var emails = [];
if (typeof(result.ocs.data.emails) !== 'undefined') {
emails = result.ocs.data.emails;
}
var circles = [];
if (typeof(result.ocs.data.circles) !== 'undefined') {
circles = result.ocs.data.circles;
}
var suggestions = exactMatches.concat(users).concat(groups).concat(remotes).concat(emails).concat(circles).concat(lookup);
deferred.resolve(suggestions, exactMatches);
} else {
deferred.reject(result.ocs.meta.message);
}
}
).fail(function() {
deferred.reject();
});
this._lastSuggestions = {
searchTerm: searchTerm,
perPage: perPage,
model: model,
promise: deferred.promise()
};
return this._lastSuggestions.promise;
},
autocompleteHandler: function (search, response) {
var $shareWithField = $('.shareWithField'),
view = this,
$loading = this.$el.find('.shareWithLoading'),
$shareInfo = this.$el.find('.shareWithRemoteInfo');
$confirm = this.$el.find('.shareWithConfirm');
var count = oc_config['sharing.minSearchStringLength'];
if (search.term.trim().length < count) {
@ -167,160 +341,70 @@
$loading.removeClass('hidden');
$loading.addClass('inlineblock');
$shareInfo.addClass('hidden');
$confirm.addClass('hidden');
this._pendingOperationsCount++;
$shareWithField.removeClass('error')
.tooltip('hide');
var perPage = 200;
$.get(
OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
{
format: 'json',
search: search.term.trim(),
perPage: perPage,
itemType: view.model.get('itemType')
},
function (result) {
this._getSuggestions(
search.term.trim(),
perPage,
view.model
).done(function(suggestions) {
view._pendingOperationsCount--;
if (view._pendingOperationsCount === 0) {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
$shareInfo.removeClass('hidden');
if (result.ocs.meta.statuscode === 100) {
var users = result.ocs.data.exact.users.concat(result.ocs.data.users);
var groups = result.ocs.data.exact.groups.concat(result.ocs.data.groups);
var remotes = result.ocs.data.exact.remotes.concat(result.ocs.data.remotes);
var lookup = result.ocs.data.lookup;
var emails = [],
circles = [];
if (typeof(result.ocs.data.emails) !== 'undefined') {
emails = result.ocs.data.exact.emails.concat(result.ocs.data.emails);
}
if (typeof(result.ocs.data.circles) !== 'undefined') {
circles = result.ocs.data.exact.circles.concat(result.ocs.data.circles);
}
var usersLength;
var groupsLength;
var remotesLength;
var emailsLength;
var circlesLength;
var i, j;
//Filter out the current user
usersLength = users.length;
for (i = 0; i < usersLength; i++) {
if (users[i].value.shareWith === OC.currentUser) {
users.splice(i, 1);
break;
}
}
// Filter out the owner of the share
if (view.model.hasReshare()) {
usersLength = users.length;
for (i = 0 ; i < usersLength; i++) {
if (users[i].value.shareWith === view.model.getReshareOwner()) {
users.splice(i, 1);
break;
}
}
}
var shares = view.model.get('shares');
var sharesLength = shares.length;
// Now filter out all sharees that are already shared with
for (i = 0; i < sharesLength; i++) {
var share = shares[i];
if (share.share_type === OC.Share.SHARE_TYPE_USER) {
usersLength = users.length;
for (j = 0; j < usersLength; j++) {
if (users[j].value.shareWith === share.share_with) {
users.splice(j, 1);
break;
}
}
} else if (share.share_type === OC.Share.SHARE_TYPE_GROUP) {
groupsLength = groups.length;
for (j = 0; j < groupsLength; j++) {
if (groups[j].value.shareWith === share.share_with) {
groups.splice(j, 1);
break;
}
}
} else if (share.share_type === OC.Share.SHARE_TYPE_REMOTE) {
remotesLength = remotes.length;
for (j = 0; j < remotesLength; j++) {
if (remotes[j].value.shareWith === share.share_with) {
remotes.splice(j, 1);
break;
}
}
} else if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
emailsLength = emails.length;
for (j = 0; j < emailsLength; j++) {
if (emails[j].value.shareWith === share.share_with) {
emails.splice(j, 1);
break;
}
}
} else if (share.share_type === OC.Share.SHARE_TYPE_CIRCLE) {
circlesLength = circles.length;
for (j = 0; j < circlesLength; j++) {
if (circles[j].value.shareWith === share.share_with) {
circles.splice(j, 1);
break;
}
}
}
}
var suggestions = users.concat(groups).concat(remotes).concat(emails).concat(circles).concat(lookup);
if (suggestions.length > 0) {
$shareWithField
.autocomplete("option", "autoFocus", true);
response(suggestions);
// show a notice that the list is truncated
// this is the case if one of the search results is at least as long as the max result config option
if(oc_config['sharing.maxAutocompleteResults'] > 0 &&
Math.min(perPage, oc_config['sharing.maxAutocompleteResults'])
<= Math.max(users.length, groups.length, remotes.length, emails.length, lookup.length)) {
var message = t('core', 'This list is maybe truncated - please refine your search term to see more results.');
$('.ui-autocomplete').append('<li class="autocomplete-note">' + message + '</li>');
}
} else {
var title = t('core', 'No users or groups found for {search}', {search: $shareWithField.val()});
if (!view.configModel.get('allowGroupSharing')) {
title = t('core', 'No users found for {search}', {search: $('.shareWithField').val()});
}
$shareWithField.addClass('error')
.attr('data-original-title', title)
.tooltip('hide')
.tooltip({
placement: 'bottom',
trigger: 'manual'
})
.tooltip('fixTitle')
.tooltip('show');
response();
}
} else {
response();
}
$confirm.removeClass('hidden');
}
if (suggestions.length > 0) {
$shareWithField
.autocomplete("option", "autoFocus", true);
response(suggestions);
// show a notice that the list is truncated
// this is the case if one of the search results is at least as long as the max result config option
if(oc_config['sharing.maxAutocompleteResults'] > 0 &&
Math.min(perPage, oc_config['sharing.maxAutocompleteResults'])
<= Math.max(users.length, groups.length, remotes.length, emails.length, lookup.length)) {
var message = t('core', 'This list is maybe truncated - please refine your search term to see more results.');
$('.ui-autocomplete').append('<li class="autocomplete-note">' + message + '</li>');
}
} else {
var title = t('core', 'No users or groups found for {search}', {search: $shareWithField.val()});
if (!view.configModel.get('allowGroupSharing')) {
title = t('core', 'No users found for {search}', {search: $('.shareWithField').val()});
}
$shareWithField.addClass('error')
.attr('data-original-title', title)
.tooltip('hide')
.tooltip({
placement: 'bottom',
trigger: 'manual'
})
.tooltip('fixTitle')
.tooltip('show');
response();
}
}).fail(function(message) {
view._pendingOperationsCount--;
if (view._pendingOperationsCount === 0) {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
$confirm.removeClass('hidden');
}
if (message) {
OC.Notification.showTemporary(t('core', 'An error occurred ("{message}"). Please try again', { message: message }));
} else {
OC.Notification.showTemporary(t('core', 'An error occurred. Please try again'));
}
).fail(function() {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
$shareInfo.removeClass('hidden');
OC.Notification.show(t('core', 'An error occurred. Please try again'));
window.setTimeout(OC.Notification.hide, 5000);
});
},
@ -357,31 +441,149 @@
},
_onSelectRecipient: function(e, s) {
var self = this;
e.preventDefault();
// Ensure that the keydown handler for the input field is not
// called; otherwise it would try to add the recipient again, which
// would fail.
e.stopImmediatePropagation();
$(e.target).attr('disabled', true)
.val(s.item.label);
var $loading = this.$el.find('.shareWithLoading');
$loading.removeClass('hidden')
.addClass('inlineblock');
var $shareInfo = this.$el.find('.shareWithRemoteInfo');
$shareInfo.addClass('hidden');
var $confirm = this.$el.find('.shareWithConfirm');
$loading.removeClass('hidden');
$loading.addClass('inlineblock');
$confirm.addClass('hidden');
this._pendingOperationsCount++;
this.model.addShare(s.item.value, {success: function() {
// Adding a share changes the suggestions.
self._lastSuggestions = undefined;
$(e.target).val('')
.attr('disabled', false);
$loading.addClass('hidden')
.removeClass('inlineblock');
$shareInfo.removeClass('hidden');
self._pendingOperationsCount--;
if (self._pendingOperationsCount === 0) {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
$confirm.removeClass('hidden');
}
}, error: function(obj, msg) {
OC.Notification.showTemporary(msg);
$(e.target).attr('disabled', false)
.autocomplete('search', $(e.target).val());
$loading.addClass('hidden')
.removeClass('inlineblock');
$shareInfo.removeClass('hidden');
self._pendingOperationsCount--;
if (self._pendingOperationsCount === 0) {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
$confirm.removeClass('hidden');
}
}});
},
_confirmShare: function() {
var self = this;
var $shareWithField = $('.shareWithField');
var $loading = this.$el.find('.shareWithLoading');
var $confirm = this.$el.find('.shareWithConfirm');
$loading.removeClass('hidden');
$loading.addClass('inlineblock');
$confirm.addClass('hidden');
this._pendingOperationsCount++;
$shareWithField.prop('disabled', true);
// Disabling the autocompletion does not clear its search timeout;
// removing the focus from the input field does, but only if the
// autocompletion is not disabled when the field loses the focus.
// Thus, the field has to be disabled before disabling the
// autocompletion to prevent an old pending search result from
// appearing once the field is enabled again.
$shareWithField.autocomplete('close');
$shareWithField.autocomplete('disable');
var restoreUI = function() {
self._pendingOperationsCount--;
if (self._pendingOperationsCount === 0) {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
$confirm.removeClass('hidden');
}
$shareWithField.prop('disabled', false);
$shareWithField.focus();
};
var perPage = 200;
var onlyExactMatches = true;
this._getSuggestions(
$shareWithField.val(),
perPage,
this.model,
onlyExactMatches
).done(function(suggestions, exactMatches) {
if (suggestions.length === 0) {
restoreUI();
$shareWithField.autocomplete('enable');
// There is no need to show an error message here; it will
// be automatically shown when the autocomplete is activated
// again (due to the focus on the field) and it finds no
// matches.
return;
}
if (exactMatches.length !== 1) {
restoreUI();
$shareWithField.autocomplete('enable');
return;
}
var actionSuccess = function() {
// Adding a share changes the suggestions.
self._lastSuggestions = undefined;
$shareWithField.val('');
restoreUI();
$shareWithField.autocomplete('enable');
};
var actionError = function(obj, msg) {
restoreUI();
$shareWithField.autocomplete('enable');
OC.Notification.showTemporary(msg);
};
self.model.addShare(exactMatches[0].value, {
success: actionSuccess,
error: actionError
});
}).fail(function(message) {
restoreUI();
$shareWithField.autocomplete('enable');
// There is no need to show an error message here; it will be
// automatically shown when the autocomplete is activated again
// (due to the focus on the field) and getting the suggestions
// fail.
});
},
_toggleLoading: function(state) {
this._loading = state;
this.$el.find('.subView').toggleClass('hidden', state);
@ -410,18 +612,28 @@
},
render: function() {
var self = this;
var baseTemplate = this._getTemplate('base', TEMPLATE_BASE);
this.$el.html(baseTemplate({
cid: this.cid,
shareLabel: t('core', 'Share'),
sharePlaceholder: this._renderSharePlaceholderPart(),
shareInfo: this._renderShareInfoPart(),
isSharingAllowed: this.model.sharePermissionPossible()
}));
var $shareField = this.$el.find('.shareWithField');
if ($shareField.length) {
var shareFieldKeydownHandler = function(event) {
if (event.keyCode !== 13) {
return true;
}
self._confirmShare();
return false;
};
$shareField.autocomplete({
minLength: 1,
delay: 750,
@ -431,6 +643,8 @@
source: this.autocompleteHandler,
select: this._onSelectRecipient
}).data('ui-autocomplete')._renderItem = this.autocompleteRenderItem;
$shareField.on('keydown', null, shareFieldKeydownHandler);
}
this.resharerInfoView.$el = this.$el.find('.resharerInfoView');
@ -461,27 +675,6 @@
this.linkShareView.showLink = this._showLink;
},
_renderShareInfoPart: function() {
var shareInfo = '';
var infoTemplate = this._getShareInfoTemplate();
if(this.configModel.get('isMailShareAllowed') && this.configModel.get('isRemoteShareAllowed')) {
shareInfo = infoTemplate({
tooltip: t('core', 'Share with other people by entering a user or group, a federated cloud ID or an email address.')
});
} else if(this.configModel.get('isRemoteShareAllowed')) {
shareInfo = infoTemplate({
tooltip: t('core', 'Share with other people by entering a user or group or a federated cloud ID.')
});
} else if(this.configModel.get('isMailShareAllowed')) {
shareInfo = infoTemplate({
tooltip: t('core', 'Share with other people by entering a user or group or an email address.')
});
}
return shareInfo;
},
_renderSharePlaceholderPart: function () {
var allowRemoteSharing = this.configModel.get('isRemoteShareAllowed');
var allowMailSharing = this.configModel.get('isMailShareAllowed');
@ -513,15 +706,6 @@
return this._templates[key];
},
/**
* returns the info template for remote sharing
*
* @returns {Function}
* @private
*/
_getShareInfoTemplate: function() {
return this._getTemplate('shareInfo', TEMPLATE_SHARE_INFO);
}
});
OC.Share.ShareDialogView = ShareDialogView;

File diff suppressed because it is too large Load diff