core/js/sharedialogshareelistview.js
/*
* Copyright (c) 2015
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function() {
if (!OC.Share) {
OC.Share = {};
}
var TEMPLATE =
'<ul id="shareWithList" class="shareWithList">' +
' {{#each sharees}}' +
' <li data-share-id="{{shareId}}" data-share-type="{{shareType}}" data-share-with="{{shareWith}}">' +
' <a href="#" class="unshare"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span class="hidden-visually">{{unshareLabel}}</span></a>' +
' <a href="#" class="toggleShareDetails"><span class="icon icon-settings-dark"></span><span class="hidden-visually">{{unshareLabel}}</span></a>' +
' {{#if expirationDate}}' +
' <a class="time"><span class="icon icon-time"></span><span class="hidden-visually">{{unshareLabel}}</span></a>' +
' {{/if}}' +
// avatar disabled case will be handled via js when it's rendered
' <div class="avatar {{#if modSeed}}imageplaceholderseed{{/if}}" data-username="{{shareWith}}" {{#if modSeed}}data-seed="{{shareWith}} {{shareType}}"{{/if}}></div>' +
' <span class="has-tooltip username" title="{{shareWith}}">{{shareWithDisplayName}}</span>' +
' {{#if shareWithAdditionalInfo}}' +
' <span class="has-tooltip user-additional-info">({{shareWithAdditionalInfo}})</span>' +
' {{/if}}' +
' {{#if mailNotificationEnabled}} {{#unless isRemoteShare}}' +
' <span class="shareOption">' +
' {{#unless wasMailSent}}' +
' <span class="mailNotificationSpinner icon-loading-small hidden"></span>' +
' <input id="mail-{{cid}}-{{shareWith}}-{{shareType}}" type="button" name="mailNotification" value="{{notifyByMailLabel}}" class="mailNotification checkbox" />' +
' {{/unless}}' +
' </span>' +
' {{/unless}} {{/if}}' +
' <div class="shareOption">' +
' {{#if isUserShare}}' +
' <label for="expiration-{{name}}-{{cid}}-{{shareWith}}-{{shareType}}">{{expirationLabel}}: ' +
' <input type="text" id="expiration-{{name}}-{{cid}}-{{shareWith}}-{{shareType}}" value="{{expirationDate}}" class="expiration expiration-user" placeholder="{{expirationDatePlaceholder}}" />' +
' {{#unless isDefaultExpireDateUserEnforced}}' +
' <button class="removeExpiration">Remove</button>' +
' {{/unless}}' +
' </label>' +
' {{/if}}' +
' {{#if isGroupShare}}' +
' <label for="expiration-{{name}}-{{cid}}-{{shareWith}}-{{shareType}}">{{expirationLabel}}: ' +
' <input type="text" id="expiration-{{name}}-{{cid}}-{{shareWith}}-{{shareType}}" value="{{expirationDate}}" class="expiration expiration-group" placeholder="{{expirationDatePlaceholder}}" />' +
' {{#unless isDefaultExpireDateGroupEnforced}}' +
' <button class="removeExpiration">Remove</button>' +
' {{/unless}}' +
' </label>' +
' {{/if}}' +
' {{#if isRemoteShare}}' +
' <label for="expiration-{{name}}-{{cid}}-{{shareWith}}-{{shareType}}">{{expirationLabel}}: ' +
' <input type="text" id="expiration-{{name}}-{{cid}}-{{shareWith}}-{{shareType}}" value="{{expirationDate}}" class="expiration expiration-remote" placeholder="{{expirationDatePlaceholder}}" />' +
' {{#unless isDefaultExpireDateRemoteEnforced}}' +
' <button class="removeExpiration">Remove</button>' +
' {{/unless}}' +
' </label>' +
' {{/if}}' +
' </div>' +
' <div class="coreShareOptions">' +
' {{#if isResharingAllowed}} {{#if sharePermissionPossible}}' +
' <span class="shareOption">' +
' <input id="canShare-{{cid}}-{{shareWith}}-{{shareType}}-{{shareType}}" type="checkbox" name="share" class="permissions checkbox" {{#if hasSharePermission}}checked="checked"{{/if}} data-permissions="{{sharePermission}}" />' +
' <label for="canShare-{{cid}}-{{shareWith}}-{{shareType}}-{{shareType}}">{{canShareLabel}}</label>' +
' </span>' +
' {{/if}} {{/if}}' +
' {{#if editPermissionPossible}}' +
' <span class="shareOption">' +
' <input id="canEdit-{{cid}}-{{shareWith}}-{{shareType}}" type="checkbox" name="edit" class="permissions checkbox" {{#if hasEditPermission}}checked="checked"{{/if}} />' +
' <label for="canEdit-{{cid}}-{{shareWith}}-{{shareType}}">{{canEditLabel}}</label>' +
' </span>' +
' {{/if}}' +
' {{#if createPermissionPossible}}' +
' <span class="shareOption">' +
' <input id="canCreate-{{cid}}-{{shareWith}}-{{shareType}}" type="checkbox" name="create" class="permissions checkbox" {{#if hasCreatePermission}}checked="checked"{{/if}} data-permissions="{{createPermission}}"/>' +
' <label for="canCreate-{{cid}}-{{shareWith}}-{{shareType}}">{{createPermissionLabel}}</label>' +
' </span>' +
' {{/if}}' +
' {{#if updatePermissionPossible}}' +
' <span class="shareOption">' +
' <input id="canUpdate-{{cid}}-{{shareWith}}-{{shareType}}" type="checkbox" name="update" class="permissions checkbox" {{#if hasUpdatePermission}}checked="checked"{{/if}} data-permissions="{{updatePermission}}"/>' +
' <label for="canUpdate-{{cid}}-{{shareWith}}-{{shareType}}">{{updatePermissionLabel}}</label>' +
' </span>' +
' {{/if}}' +
' {{#if deletePermissionPossible}}' +
' <span class="shareOption">' +
' <input id="canDelete-{{cid}}-{{shareWith}}-{{shareType}}" type="checkbox" name="delete" class="permissions checkbox" {{#if hasDeletePermission}}checked="checked"{{/if}} data-permissions="{{deletePermission}}"/>' +
' <label for="canDelete-{{cid}}-{{shareWith}}-{{shareType}}">{{deletePermissionLabel}}</label>' +
' </span>' +
' {{/if}}' +
' </div>' +
' <div class="shareAttributes"">' +
' {{#each shareAttributesV1}}' +
' <span class="shareOption">' +
' <input id="can-{{name}}-{{cid}}-{{shareWith}}-{{shareType}}" type="checkbox" name="{{name}}" class="attributes checkbox" {{#if isReshare}}disabled{{/if}} {{#if enabled}}checked="checked"{{/if}} data-scope="{{scope}}" data-enabled="{{enabled}}""/>' +
' <label for="can-{{name}}-{{cid}}-{{shareWith}}-{{shareType}}">{{label}}</label>' +
' </span>' +
' {{/each}}' +
' </div>' +
' <! –– shareAttributesV2 div would be appended to this li element ––>' +
' </li>' +
' {{/each}}' +
'</ul>';
/**
* @class OCA.Share.ShareDialogShareeListView
* @member {OC.Share.ShareItemModel} model
* @member {jQuery} $el
* @memberof OCA.Sharing
* @classdesc
*
* Represents the sharee list part in the GUI of the share dialogue
*
*/
var ShareDialogShareeListView = OC.Backbone.View.extend({
/** @type {string} **/
id: 'ShareDialogShareeList',
/** @type {OC.Share.ShareConfigModel} **/
configModel: undefined,
/** @type {Function} **/
_template: undefined,
/** @type {MutationObserver} **/
_toggleMutationObserver: undefined,
_currentlyToggled: {},
events: {
'click .unshare': 'onUnshare',
'click .permissions': 'onPermissionChange',
'click .attributes': 'onPermissionChange',
'click .mailNotification': 'onSendMailNotification',
'click .removeExpiration' : 'onRemoveExpiration',
'click .toggleShareDetails' : 'onToggleShareDetailsChange'
},
initialize: function(options) {
if(!_.isUndefined(options.configModel)) {
this.configModel = options.configModel;
} else {
throw 'missing OC.Share.ShareConfigModel';
}
var view = this;
this.model.on('change:shares', function() {
view.render();
});
},
/**
* Get shareAttributesApi v1 attributes and update checkboxes
* @param shareIndex
* @returns {object}
* @deprecated shareAttributesApi v2 requires apps to extend ShareItemModel
*/
getAttributesObject: function(shareIndex) {
var model = this.model;
var cid = this.cid;
var shareWith = model.getShareWith(shareIndex);
// Check if reshare, and if so disable the checkboxes
var isReshare = model.hasReshare();
// Returns OC.Share.Types.ShareAttribute[] which were set for this
// share (and stored in DB)
var attributes = model.getShareAttributes(shareIndex);
// Display shareAttributesV1 checkboxes (registered and with label)
var list = [];
attributes.map(function(attribute) {
// Display only shareAttributesApi v1 attributes ,
// other attributes should be handled by apps
var regAttr = model.getRegisteredShareAttribute(
attribute.scope,
attribute.key
);
if (regAttr && regAttr.label) {
list.push({
cid: cid,
isReshare: isReshare,
shareWith: shareWith,
enabled: attribute.enabled,
scope: attribute.scope,
name: attribute.key,
label: regAttr.label
});
}
});
return list;
},
/**
* @param shareIndex
* @returns {object}
*/
getShareeObject: function(shareIndex) {
var shareWith = this.model.getShareWith(shareIndex);
var shareWithDisplayName = this.model.getShareWithDisplayName(shareIndex);
var shareType = this.model.getShareType(shareIndex);
var shareWithAdditionalInfo = this.model.getShareWithAdditionalInfo(shareIndex);
var hasPermissionOverride = {};
if (shareType === OC.Share.SHARE_TYPE_GROUP) {
shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'group') + ')';
} else if (shareType === OC.Share.SHARE_TYPE_REMOTE) {
shareWithDisplayName = shareWithDisplayName + " (" + t('core', 'federated') + ')';
}
return _.extend(hasPermissionOverride, {
cid: this.cid,
hasSharePermission: this.model.hasSharePermission(shareIndex),
hasEditPermission: this.model.hasEditPermission(shareIndex),
hasCreatePermission: this.model.hasCreatePermission(shareIndex),
hasUpdatePermission: this.model.hasUpdatePermission(shareIndex),
hasDeletePermission: this.model.hasDeletePermission(shareIndex),
shareAttributesV1: this.getAttributesObject(shareIndex),
expirationLabel: t('core', 'Expiration'),
expirationDatePlaceholder: t('core', 'Choose an expiration date'),
isDefaultExpireDateUserEnforced: this.configModel.isDefaultExpireDateUserEnforced(),
isDefaultExpireDateGroupEnforced: this.configModel.isDefaultExpireDateGroupEnforced(),
isDefaultExpireDateRemoteEnforced: this.configModel.isDefaultExpireDateRemoteEnforced(),
expirationDate: this.model.getExpirationDate(shareIndex),
wasMailSent: this.model.notificationMailWasSent(shareIndex),
shareWith: shareWith,
shareWithDisplayName: shareWithDisplayName,
shareWithAdditionalInfo: shareWithAdditionalInfo,
shareType: shareType,
shareId: this.model.get('shares')[shareIndex].id,
modSeed: shareType !== OC.Share.SHARE_TYPE_USER,
isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE,
isUserShare: shareType === OC.Share.SHARE_TYPE_USER,
isGroupShare: shareType === OC.Share.SHARE_TYPE_GROUP
});
},
getShareeList: function() {
var universal = {
avatarEnabled: this.configModel.areAvatarsEnabled(),
mailNotificationEnabled: this.configModel.isMailNotificationEnabled(),
notifyByMailLabel: t('core', 'notify by email'),
unshareLabel: t('core', 'Unshare'),
canShareLabel: t('core', 'can share'),
canEditLabel: t('core', 'can edit'),
createPermissionLabel: t('core', 'create'),
updatePermissionLabel: t('core', 'change'),
deletePermissionLabel: t('core', 'delete'),
crudsLabel: t('core', 'access control'),
triangleSImage: OC.imagePath('core', 'actions/triangle-s'),
isResharingAllowed: this.configModel.get('isResharingAllowed'),
sharePermissionPossible: this.model.sharePermissionPossible(),
editPermissionPossible: this.model.editPermissionPossible(),
createPermissionPossible: this.model.createPermissionPossible(),
updatePermissionPossible: this.model.updatePermissionPossible(),
deletePermissionPossible: this.model.deletePermissionPossible(),
defaultExpireDateUserEnabled: this.configModel.isDefaultExpireDateUserEnabled(),
defaultExpireDateGroupEnabled: this.configModel.isDefaultExpireDateGroupEnabled(),
defaultExpireDateRemoteEnabled: this.configModel.isDefaultExpireDateRemoteEnabled(),
sharePermission: OC.PERMISSION_SHARE,
createPermission: OC.PERMISSION_CREATE,
updatePermission: OC.PERMISSION_UPDATE,
deletePermission: OC.PERMISSION_DELETE
};
if(!this.model.hasUserShares()) {
return [];
}
var shares = this.model.get('shares');
var list = [];
for(var index = 0; index < shares.length; index++) {
// first empty {} is necessary, otherwise we get in trouble
// with references
list.push(_.extend({}, universal, this.getShareeObject(index)));
}
return list;
},
render: function() {
var self = this;
// render shares list in a container
this.$el.html(this.template({
cid: this.cid,
sharees: this.getShareeList()
}));
if(this.configModel.areAvatarsEnabled()) {
this.$el.find('.avatar').each(function() {
var $this = $(this);
if ($this.hasClass('imageplaceholderseed')) {
$this.css({width: 32, height: 32});
$this.imageplaceholder($this.data('seed'));
} else {
$this.avatar($this.data('username'), 32);
}
});
} else {
this.$el.find('.avatar').css({visibility: 'hidden', width: 0});
}
this.$el.find('.has-tooltip').tooltip({
placement: 'bottom'
});
this.$el.find('.expiration-user:not(.hasDatepicker)').each(function(){
self._setDatepicker(this, {
maxDate : self.configModel.getDefaultExpireDateUser(),
enforced : self.configModel.isDefaultExpireDateUserEnforced()
});
});
this.$el.find('.expiration-group:not(.hasDatepicker)').each(function(){
self._setDatepicker(this, {
maxDate : self.configModel.getDefaultExpireDateGroup(),
enforced : self.configModel.isDefaultExpireDateGroupEnforced()
});
});
this.$el.find('.expiration-remote:not(.hasDatepicker)').each(function(){
self._setDatepicker(this, {
maxDate : self.configModel.getDefaultExpireDateRemote(),
enforced : self.configModel.isDefaultExpireDateRemoteEnforced()
});
});
var shareWithList = this.$el.get(0);
if (!_.isUndefined(shareWithList)) {
// make sure that toggled share options are shown, class .shareOption
// elements are not displayed by default and need to be
// toggled so they are rendered.
this._renderToggledShareDetails();
// use mutation observer to ensure that if shareWithList changes
// proper share details are also toggled
if (_.isUndefined(self._toggleMutationObserver)) {
self._toggleMutationObserver =
new MutationObserver(function() {
self._renderToggledShareDetails();
});
}
self._toggleMutationObserver.disconnect();
self._toggleMutationObserver.observe(shareWithList, { childList: true, subtree: true });
}
this.delegateEvents();
return this;
},
/**
* @returns {Function} from Handlebars
* @private
*/
template: function (data) {
if (!this._template) {
this._template = Handlebars.compile(TEMPLATE);
}
return this._template(data);
},
onUnshare: function(event) {
var self = this;
var $element = $(event.target);
if (!$element.is('a')) {
$element = $element.closest('a');
}
var $loading = $element.find('.icon-loading-small').eq(0);
if(!$loading.hasClass('hidden')) {
// in process
return false;
}
$loading.removeClass('hidden');
var $li = $element.closest('li');
var shareId = $li.data('share-id');
self.model.removeShare(shareId)
.done(function() {
$li.remove();
})
.fail(function() {
$loading.addClass('hidden');
OC.Notification.showTemporary(t('core', 'Could not unshare'));
});
return false;
},
onPermissionChange: function(event) {
var $element = $(event.target);
var $li = $element.closest('li');
var shareId = $li.data('share-id');
var shareType = $li.data('share-type');
var shareWith = $li.attr('data-share-with');
// adjust share permissions and their required checkbox states
var $checkboxes = $('.permissions', $li).not('input[name="edit"]').not('input[name="share"]');
var checked;
if ($element.attr('name') === 'edit') {
checked = $element.is(':checked');
// Check/uncheck Create, Update, and Delete checkboxes if Edit is checked/unck
$($checkboxes).prop('checked', checked);
} else {
var numberChecked = $checkboxes.filter(':checked').length;
checked = numberChecked > 0;
$('input[name="edit"]', $li).prop('checked', checked);
}
var permissions = OC.PERMISSION_READ;
$('.permissions', $li).not('input[name="edit"]').filter(':checked').each(function(index, checkbox) {
permissions |= $(checkbox).data('permissions');
});
/**
* ShareAttributesApi v1 attributes
*
* @deprecated attributes will be removed, shareAttributesApi v2 requires apps to extend ShareItemModel updateShare
*/
var attributes = [];
$('.attributes', $li).each(function(index, checkbox) {
var checked = $(checkbox).is(':checked');
$(checkbox).prop('enabled', checked);
attributes.push({
scope : $(checkbox).data('scope'),
key: $(checkbox).attr('name'),
enabled: checked
});
});
this.model.updateShare(shareId, {
permissions: permissions,
attributes: attributes
}, {}
);
},
onSendMailNotification: function(event) {
var $target = $(event.target);
var $li = $(event.target).closest('li');
var shareType = $li.data('share-type');
var shareWith = $li.attr('data-share-with');
var $loading = $target.prev('.icon-loading-small');
$target.addClass('hidden');
$loading.removeClass('hidden');
this.model.sendNotificationForShare(shareType, shareWith, true).then(function(result) {
if (result.ocs.data.status === 'success') {
OC.Notification.showTemporary(t('core', 'Email notification was sent!'));
$target.remove();
} else {
// sending was successful but some users might not have any email address
OC.dialogs.alert(t('core', result.ocs.meta.message), t('core', 'Email notification not sent'));
}
$target.removeClass('hidden');
$loading.addClass('hidden');
});
},
onRemoveExpiration: function(event) {
// make sure that click event is not propagated further
event.preventDefault();
// update share unsetting expiry date
var shareId = $(event.target).closest('li').data('share-id');
var share = this.model.getShareById(shareId);
this.model.updateShare( shareId, {
permissions: share.permissions,
attributes: share.attributes || {},
expireDate: ''
}, {});
},
onExpirationChange: function(el) {
var $el = $(el);
var shareId = $el.closest('li').data('share-id');
var expiration = moment($el.val(), 'DD-MM-YYYY').format();
var share = this.model.getShareById(shareId);
this.model.updateShare( shareId, {
permissions: share.permissions,
attributes: share.attributes || {},
expireDate: expiration
}, {});
},
onToggleShareDetailsChange: function(event) {
var $li = $(event.target).closest('li');
var shareId = $li.data('share-id');
if (!_.isUndefined(this._currentlyToggled[shareId]) && this._currentlyToggled[shareId] === true) {
delete(this._currentlyToggled[shareId]);
this._toggleShareOptions(shareId, false);
} else {
this._currentlyToggled[shareId] = true;
this._toggleShareOptions(shareId, true);
}
},
_renderToggledShareDetails: function() {
var view = this;
this.$el.find('li').each(function() {
var $li = $(this);
var shareId = $li.data('share-id');
if (!_.isUndefined(view._currentlyToggled[shareId]) && view._currentlyToggled[shareId] === true) {
view._toggleShareOptions(shareId, true);
} else {
view._toggleShareOptions(shareId, false);
}
});
},
_toggleShareOptions: function(shareId, enabled) {
var $li = this.$el.find('li[data-share-id=' + shareId + ']');
$li.find(".shareOption").each(function() {
var $option = $(this);
if (enabled) {
$option.css('display', 'inline-block');
} else {
$option.css('display', 'none');
}
});
},
_setDatepicker: function(el, params) {
var self = this;
var $el = $(el);
$el.datepicker({
minDate: "+0d",
dateFormat : 'dd-mm-yy',
onSelect : function() {
self.onExpirationChange(el);
}
});
if (params.enforced) {
$el.datepicker("option", "maxDate", "+" + params.maxDate + 'd');
}
}
});
OC.Share.ShareDialogShareeListView = ShareDialogShareeListView;
})();