diff options
Diffstat (limited to 'core/js')
-rw-r--r-- | core/js/files/client.js | 36 | ||||
-rw-r--r-- | core/js/js.js | 46 | ||||
-rw-r--r-- | core/js/lostpassword.js | 2 | ||||
-rw-r--r-- | core/js/mimetypelist.js | 1 | ||||
-rw-r--r-- | core/js/sharedialoglinkshareview.js | 39 | ||||
-rw-r--r-- | core/js/sharedialogresharerinfoview.js | 2 | ||||
-rw-r--r-- | core/js/sharedialogshareelistview.js | 196 | ||||
-rw-r--r-- | core/js/shareitemmodel.js | 50 | ||||
-rw-r--r-- | core/js/systemtags/systemtagmodel.js | 21 | ||||
-rw-r--r-- | core/js/systemtags/systemtagsinputfield.js | 7 | ||||
-rw-r--r-- | core/js/tests/specs/coreSpec.js | 34 | ||||
-rw-r--r-- | core/js/tests/specs/l10nSpec.js | 7 | ||||
-rw-r--r-- | core/js/tests/specs/sharedialogviewSpec.js | 23 | ||||
-rw-r--r-- | core/js/tests/specs/shareitemmodelSpec.js | 5 | ||||
-rw-r--r-- | core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js | 87 | ||||
-rw-r--r-- | core/js/update.js | 2 |
16 files changed, 449 insertions, 109 deletions
diff --git a/core/js/files/client.js b/core/js/files/client.js index 87559b2084c..cde3afde9d7 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -31,9 +31,9 @@ this._root = this._root.substr(0, this._root.length - 1); } - var url = 'http://'; + var url = Client.PROTOCOL_HTTP + '://'; if (options.useHTTPS) { - url = 'https://'; + url = Client.PROTOCOL_HTTPS + '://'; } url += options.host + this._root; @@ -64,6 +64,19 @@ Client.NS_OWNCLOUD = 'http://owncloud.org/ns'; Client.NS_NEXTCLOUD = 'http://nextcloud.org/ns'; Client.NS_DAV = 'DAV:'; + + Client.PROPERTY_GETLASTMODIFIED = '{' + Client.NS_DAV + '}getlastmodified'; + Client.PROPERTY_GETETAG = '{' + Client.NS_DAV + '}getetag'; + Client.PROPERTY_GETCONTENTTYPE = '{' + Client.NS_DAV + '}getcontenttype'; + Client.PROPERTY_RESOURCETYPE = '{' + Client.NS_DAV + '}resourcetype'; + Client.PROPERTY_INTERNAL_FILEID = '{' + Client.NS_OWNCLOUD + '}fileid'; + Client.PROPERTY_PERMISSIONS = '{' + Client.NS_OWNCLOUD + '}permissions'; + Client.PROPERTY_SIZE = '{' + Client.NS_OWNCLOUD + '}size'; + Client.PROPERTY_GETCONTENTLENGTH = '{' + Client.NS_DAV + '}getcontentlength'; + + Client.PROTOCOL_HTTP = 'http'; + Client.PROTOCOL_HTTPS = 'https'; + Client._PROPFIND_PROPERTIES = [ /** * Modified time @@ -259,23 +272,23 @@ var props = response.propStat[0].properties; var data = { - id: props['{' + Client.NS_OWNCLOUD + '}fileid'], + id: props[Client.PROPERTY_INTERNAL_FILEID], path: OC.dirname(path) || '/', name: OC.basename(path), - mtime: (new Date(props['{' + Client.NS_DAV + '}getlastmodified'])).getTime() + mtime: (new Date(props[Client.PROPERTY_GETLASTMODIFIED])).getTime() }; - var etagProp = props['{' + Client.NS_DAV + '}getetag']; + var etagProp = props[Client.PROPERTY_GETETAG]; if (!_.isUndefined(etagProp)) { data.etag = this._parseEtag(etagProp); } - var sizeProp = props['{' + Client.NS_DAV + '}getcontentlength']; + var sizeProp = props[Client.PROPERTY_GETCONTENTLENGTH]; if (!_.isUndefined(sizeProp)) { data.size = parseInt(sizeProp, 10); } - sizeProp = props['{' + Client.NS_OWNCLOUD + '}size']; + sizeProp = props[Client.PROPERTY_SIZE]; if (!_.isUndefined(sizeProp)) { data.size = parseInt(sizeProp, 10); } @@ -294,12 +307,12 @@ data.isFavorite = false; } - var contentType = props['{' + Client.NS_DAV + '}getcontenttype']; + var contentType = props[Client.PROPERTY_GETCONTENTTYPE]; if (!_.isUndefined(contentType)) { data.mimetype = contentType; } - var resType = props['{' + Client.NS_DAV + '}resourcetype']; + var resType = props[Client.PROPERTY_RESOURCETYPE]; var isFile = true; if (!data.mimetype && resType) { var xmlvalue = resType[0]; @@ -310,7 +323,7 @@ } data.permissions = OC.PERMISSION_READ; - var permissionProp = props['{' + Client.NS_OWNCLOUD + '}permissions']; + var permissionProp = props[Client.PROPERTY_PERMISSIONS]; if (!_.isUndefined(permissionProp)) { var permString = permissionProp || ''; data.mountType = null; @@ -752,7 +765,7 @@ }, /** - * Returns the password + * Returns the password * * @since 11.0.0 * @return {String} password @@ -816,4 +829,3 @@ OC.Files.Client = Client; })(OC, OC.Files.FileInfo); - diff --git a/core/js/js.js b/core/js/js.js index 972f0e63144..3651635541a 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1669,6 +1669,52 @@ OC.Util = { humanFileSize: humanFileSize, /** + * Returns a file size in bytes from a humanly readable string + * Makes 2kB to 2048. + * Inspired by computerFileSize in helper.php + * @param {string} string file size in human readable format + * @return {number} or null if string could not be parsed + * + * + */ + computerFileSize: function (string) { + if (typeof string != 'string') { + return null; + } + + var s = string.toLowerCase(); + var bytes = parseFloat(s) + + if (!isNaN(bytes) && isFinite(s)) { + return bytes; + } + + var bytesArray = { + 'b' : 1, + 'k' : 1024, + 'kb': 1024, + 'mb': 1024 * 1024, + 'm' : 1024 * 1024, + 'gb': 1024 * 1024 * 1024, + 'g' : 1024 * 1024 * 1024, + 'tb': 1024 * 1024 * 1024 * 1024, + 't' : 1024 * 1024 * 1024 * 1024, + 'pb': 1024 * 1024 * 1024 * 1024 * 1024, + 'p' : 1024 * 1024 * 1024 * 1024 * 1024 + }; + + var matches = s.match(/([kmgtp]?b?)$/i); + if (matches[1]) { + bytes = bytes * bytesArray[matches[1]]; + } else { + return null; + } + + bytes = Math.round(bytes); + return bytes; + }, + + /** * @param timestamp * @param format * @returns {string} timestamp formatted as requested diff --git a/core/js/lostpassword.js b/core/js/lostpassword.js index 30d7b98f4e8..6e18dcc1f8b 100644 --- a/core/js/lostpassword.js +++ b/core/js/lostpassword.js @@ -4,7 +4,7 @@ OC.Lostpassword = { sendSuccessMsg : t('core', 'The link to reset your password has been sent to your email. If you do not receive it within a reasonable amount of time, check your spam/junk folders.<br>If it is not there ask your local administrator.'), - encryptedMsg : t('core', "Your files are encrypted. If you haven't enabled the recovery key, there will be no way to get your data back after your password is reset.<br />If you are not sure what to do, please contact your administrator before you continue. <br />Do you really want to continue?") + encryptedMsg : t('core', "Your files are encrypted. There will be no way to get your data back after your password is reset.<br />If you are not sure what to do, please contact your administrator before you continue. <br />Do you really want to continue?") + ('<br /><input type="checkbox" id="encrypted-continue" value="Yes" />') + '<label for="encrypted-continue">' + t('core', 'I know what I\'m doing') diff --git a/core/js/mimetypelist.js b/core/js/mimetypelist.js index e1b9dba14af..89558e21086 100644 --- a/core/js/mimetypelist.js +++ b/core/js/mimetypelist.js @@ -86,6 +86,7 @@ OC.MimeTypeList={ "text/x-c": "text/code", "text/x-c++src": "text/code", "text/x-h": "text/code", + "text/x-ldif": "text/code", "text/x-java-source": "text/code", "text/x-python": "text/code", "text/x-shellscript": "text/code", diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index cb553947de6..84a3d18942f 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -43,6 +43,13 @@ '</div>' + ' {{/if}}' + ' {{/if}}' + + ' {{#if publicEditing}}' + + '<div id="allowPublicEditingWrapper">' + + ' <span class="icon-loading-small hidden"></span>' + + ' <input type="checkbox" value="1" name="allowPublicEditing" id="sharingDialogAllowPublicEditing-{{cid}}" class="checkbox publicEditingCheckbox" {{{publicEditingChecked}}} />' + + '<label for="sharingDialogAllowPublicEditing-{{cid}}">{{publicEditingLabel}}</label>' + + '</div>' + + ' {{/if}}' + ' {{#if showPasswordCheckBox}}' + '<input type="checkbox" name="showPassword" id="showPassword-{{cid}}" class="checkbox showPasswordCheckbox" {{#if isPasswordSet}}checked="checked"{{/if}} value="1" />' + '<label for="showPassword-{{cid}}">{{enablePasswordLabel}}</label>' + @@ -87,6 +94,7 @@ 'click .linkCheckbox': 'onLinkCheckBoxChange', 'click .linkText': 'onLinkTextClick', 'change .publicUploadCheckbox': 'onAllowPublicUploadChange', + 'change .publicEditingCheckbox': 'onAllowPublicEditingChange', 'change .hideFileListCheckbox': 'onHideFileListChange', 'click .showPasswordCheckbox': 'onShowPasswordClick' }, @@ -128,7 +136,8 @@ 'onLinkTextClick', 'onShowPasswordClick', 'onHideFileListChange', - 'onAllowPublicUploadChange' + 'onAllowPublicUploadChange', + 'onAllowPublicEditingChange' ); var clipboard = new Clipboard('.clipboardButton'); @@ -266,6 +275,20 @@ }); }, + onAllowPublicEditingChange: function() { + var $checkbox = this.$('.publicEditingCheckbox'); + $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); + + var permissions = OC.PERMISSION_READ; + if($checkbox.is(':checked')) { + permissions = OC.PERMISSION_UPDATE | OC.PERMISSION_READ; + } + + this.model.saveLinkShare({ + permissions: permissions + }); + }, + onHideFileListChange: function () { var $checkbox = this.$('.hideFileListCheckbox'); $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); @@ -307,6 +330,12 @@ publicUploadChecked = 'checked="checked"'; } + var publicEditingChecked = ''; + if(this.model.isPublicEditingAllowed()) { + publicEditingChecked = 'checked="checked"'; + } + + var hideFileList = publicUploadChecked; var hideFileListChecked = ''; @@ -320,6 +349,11 @@ && ( !this.configModel.get('enforcePasswordForPublicLink') || !this.model.get('linkShare').password); + var publicEditable = + !this.model.isFolder() + && isLinkShare + && this.model.updatePermissionPossible(); + this.$el.html(linkShareTemplate({ cid: this.cid, shareAllowed: true, @@ -337,6 +371,9 @@ publicUploadChecked: publicUploadChecked, hideFileListChecked: hideFileListChecked, publicUploadLabel: t('core', 'Allow upload and editing'), + publicEditing: publicEditable, + publicEditingChecked: publicEditingChecked, + publicEditingLabel: t('core', 'Allow editing'), hideFileListLabel: t('core', 'File drop (upload only)'), mailPrivatePlaceholder: t('core', 'Email link to person'), mailButtonText: t('core', 'Send') diff --git a/core/js/sharedialogresharerinfoview.js b/core/js/sharedialogresharerinfoview.js index 654eebf4997..9a9d95cfb60 100644 --- a/core/js/sharedialogresharerinfoview.js +++ b/core/js/sharedialogresharerinfoview.js @@ -80,7 +80,7 @@ 'core', 'Shared with you and the group {group} by {owner}', { - group: this.model.getReshareWith(), + group: this.model.getReshareWithDisplayName(), owner: ownerDisplayName } ); diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js index a0a7bbfa2dc..4647dedd722 100644 --- a/core/js/sharedialogshareelistview.js +++ b/core/js/sharedialogshareelistview.js @@ -35,47 +35,7 @@ '{{/unless}}' + '{{/if}}' + '<a href="#"><span class="icon icon-more"></span></a>' + - '<div class="popovermenu bubble hidden menu">' + - '<ul>' + - '{{#if isResharingAllowed}} {{#if sharePermissionPossible}} {{#unless isMailShare}}' + - '<li>' + - '<span class="shareOption">' + - '<input id="canShare-{{cid}}-{{shareWith}}" type="checkbox" name="share" class="permissions checkbox" {{#if hasSharePermission}}checked="checked"{{/if}} data-permissions="{{sharePermission}}" />' + - '<label for="canShare-{{cid}}-{{shareWith}}">{{canShareLabel}}</label>' + - '</span>' + - '</li>' + - '{{/unless}} {{/if}} {{/if}}' + - '{{#if isFolder}}' + - '{{#if createPermissionPossible}}{{#unless isMailShare}}' + - '<li>' + - '<span class="shareOption">' + - '<input id="canCreate-{{cid}}-{{shareWith}}" type="checkbox" name="create" class="permissions checkbox" {{#if hasCreatePermission}}checked="checked"{{/if}} data-permissions="{{createPermission}}"/>' + - '<label for="canCreate-{{cid}}-{{shareWith}}">{{createPermissionLabel}}</label>' + - '</span>' + - '</li>' + - '{{/unless}}{{/if}}' + - '{{#if updatePermissionPossible}}{{#unless isMailShare}}' + - '<li>' + - '<span class="shareOption">' + - '<input id="canUpdate-{{cid}}-{{shareWith}}" type="checkbox" name="update" class="permissions checkbox" {{#if hasUpdatePermission}}checked="checked"{{/if}} data-permissions="{{updatePermission}}"/>' + - '<label for="canUpdate-{{cid}}-{{shareWith}}">{{updatePermissionLabel}}</label>' + - '</span>' + - '</li>' + - '{{/unless}}{{/if}}' + - '{{#if deletePermissionPossible}}{{#unless isMailShare}}' + - '<li>' + - '<span class="shareOption">' + - '<input id="canDelete-{{cid}}-{{shareWith}}" type="checkbox" name="delete" class="permissions checkbox" {{#if hasDeletePermission}}checked="checked"{{/if}} data-permissions="{{deletePermission}}"/>' + - '<label for="canDelete-{{cid}}-{{shareWith}}">{{deletePermissionLabel}}</label>' + - '</span>' + - '</li>' + - '{{/unless}}{{/if}}' + - '{{/if}}' + - '<li>' + - '<a href="#" class="unshare"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span>{{unshareLabel}}</span></a>' + - '</li>' + - '</ul>' + - '</div>' + + '{{{popoverMenu}}}' + '</span>' + '</li>' + '{{/each}}' + @@ -94,6 +54,49 @@ '</ul>' ; + var TEMPLATE_POPOVER_MENU = + '<div class="popovermenu bubble hidden menu">' + + '<ul>' + + '{{#if isResharingAllowed}} {{#if sharePermissionPossible}} {{#unless isMailShare}}' + + '<li>' + + '<span class="shareOption menuitem">' + + '<input id="canShare-{{cid}}-{{shareWith}}" type="checkbox" name="share" class="permissions checkbox" {{#if hasSharePermission}}checked="checked"{{/if}} data-permissions="{{sharePermission}}" />' + + '<label for="canShare-{{cid}}-{{shareWith}}">{{canShareLabel}}</label>' + + '</span>' + + '</li>' + + '{{/unless}} {{/if}} {{/if}}' + + '{{#if isFolder}}' + + '{{#if createPermissionPossible}}{{#unless isMailShare}}' + + '<li>' + + '<span class="shareOption menuitem">' + + '<input id="canCreate-{{cid}}-{{shareWith}}" type="checkbox" name="create" class="permissions checkbox" {{#if hasCreatePermission}}checked="checked"{{/if}} data-permissions="{{createPermission}}"/>' + + '<label for="canCreate-{{cid}}-{{shareWith}}">{{createPermissionLabel}}</label>' + + '</span>' + + '</li>' + + '{{/unless}}{{/if}}' + + '{{#if updatePermissionPossible}}{{#unless isMailShare}}' + + '<li>' + + '<span class="shareOption menuitem">' + + '<input id="canUpdate-{{cid}}-{{shareWith}}" type="checkbox" name="update" class="permissions checkbox" {{#if hasUpdatePermission}}checked="checked"{{/if}} data-permissions="{{updatePermission}}"/>' + + '<label for="canUpdate-{{cid}}-{{shareWith}}">{{updatePermissionLabel}}</label>' + + '</span>' + + '</li>' + + '{{/unless}}{{/if}}' + + '{{#if deletePermissionPossible}}{{#unless isMailShare}}' + + '<li>' + + '<span class="shareOption menuitem">' + + '<input id="canDelete-{{cid}}-{{shareWith}}" type="checkbox" name="delete" class="permissions checkbox" {{#if hasDeletePermission}}checked="checked"{{/if}} data-permissions="{{deletePermission}}"/>' + + '<label for="canDelete-{{cid}}-{{shareWith}}">{{deletePermissionLabel}}</label>' + + '</span>' + + '</li>' + + '{{/unless}}{{/if}}' + + '{{/if}}' + + '<li>' + + '<a href="#" class="unshare"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span>{{unshareLabel}}</span></a>' + + '</li>' + + '</ul>' + + '</div>'; + /** * @class OCA.Share.ShareDialogShareeListView * @member {OC.Share.ShareItemModel} model @@ -114,8 +117,14 @@ /** @type {Function} **/ _template: undefined, + /** @type {Function} **/ + _popoverMenuTemplate: undefined, + _menuOpen: false, + /** @type {boolean|number} **/ + _renderPermissionChange: false, + events: { 'click .unshare': 'onUnshare', 'click .icon-more': 'onToggleMenu', @@ -182,8 +191,8 @@ }); }, - getShareeList: function() { - var universal = { + getShareProperties: function() { + return { avatarEnabled: this.configModel.areAvatarsEnabled(), unshareLabel: t('core', 'Unshare'), canShareLabel: t('core', 'can reshare'), @@ -205,6 +214,15 @@ deletePermission: OC.PERMISSION_DELETE, isFolder: this.model.isFolder() }; + }, + + /** + * get an array of sharees' share properties + * + * @returns {Array} + */ + getShareeList: function() { + var universal = this.getShareProperties(); if(!this.model.hasUserShares()) { return []; @@ -256,29 +274,45 @@ }, render: function() { - this.$el.html(this.template({ - cid: this.cid, - sharees: this.getShareeList(), - linkReshares: this.getLinkReshares() - })); - - if(this.configModel.areAvatarsEnabled()) { - this.$('.avatar').each(function() { - var $this = $(this); - if ($this.hasClass('imageplaceholderseed')) { - $this.css({width: 32, height: 32}); - $this.imageplaceholder($this.data('seed')); - } else { - // user, size, ie8fix, hidedefault, callback, displayname - $this.avatar($this.data('username'), 32, undefined, undefined, undefined, $this.data('displayname')); - } + if(!this._renderPermissionChange) { + this.$el.html(this.template({ + cid: this.cid, + sharees: this.getShareeList(), + linkReshares: this.getLinkReshares() + })); + + if (this.configModel.areAvatarsEnabled()) { + this.$('.avatar').each(function () { + var $this = $(this); + if ($this.hasClass('imageplaceholderseed')) { + $this.css({width: 32, height: 32}); + $this.imageplaceholder($this.data('seed')); + } else { + // user, size, ie8fix, hidedefault, callback, displayname + $this.avatar($this.data('username'), 32, undefined, undefined, undefined, $this.data('displayname')); + } + }); + } + + this.$('.has-tooltip').tooltip({ + placement: 'bottom' }); + } else { + var permissionChangeShareId = parseInt(this._renderPermissionChange, 10); + var shareWithIndex = this.model.findShareWithIndex(permissionChangeShareId); + var sharee = this.getShareeObject(shareWithIndex); + $.extend(sharee, this.getShareProperties()); + var $li = this.$('li[data-share-id=' + permissionChangeShareId + ']'); + $li.find('.popovermenu').replaceWith(this.popoverMenuTemplate(sharee)); + + var checkBoxId = 'canEdit-' + this.cid + '-' + sharee.shareWith; + checkBoxId = '#' + checkBoxId.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1"); + var $edit = $li.parent().find(checkBoxId); + if($edit.length === 1) { + $edit.prop('checked', sharee.hasEditPermission); + } } - this.$('.has-tooltip').tooltip({ - placement: 'bottom' - }); - var _this = this; this.$('.popovermenu').on('afterHide', function() { _this._menuOpen = false; @@ -292,6 +326,8 @@ } } + this._renderPermissionChange = false; + this.delegateEvents(); return this; @@ -305,9 +341,28 @@ if (!this._template) { this._template = Handlebars.compile(TEMPLATE); } + var sharees = data.sharees; + if(_.isArray(sharees)) { + for (var i = 0; i < sharees.length; i++) { + data.sharees[i].popoverMenu = this.popoverMenuTemplate(sharees[i]); + } + } return this._template(data); }, + /** + * renders the popover template and returns the resulting HTML + * + * @param {Object} data + * @returns {string} + */ + popoverMenuTemplate: function(data) { + if(!this._popoverMenuTemplate) { + this._popoverMenuTemplate = Handlebars.compile(TEMPLATE_POPOVER_MENU); + } + return this._popoverMenuTemplate(data); + }, + onUnshare: function(event) { event.preventDefault(); event.stopPropagation(); @@ -385,7 +440,20 @@ permissions |= $(checkbox).data('permissions'); }); - this.model.updateShare(shareId, {permissions: permissions}); + + /** disable checkboxes during save operation to avoid race conditions **/ + $li.find('input[type=checkbox]').prop('disabled', true); + var enableCb = function() { + $li.find('input[type=checkbox]').prop('disabled', false); + }; + var errorCb = function(elem, msg) { + OC.dialogs.alert(msg, t('core', 'Error while sharing')); + enableCb(); + }; + + this.model.updateShare(shareId, {permissions: permissions}, {error: errorCb, success: enableCb}); + + this._renderPermissionChange = shareId; }, }); diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index a784f59f67f..ae4c07e3f4e 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -272,6 +272,10 @@ return this.get('allowPublicUploadStatus'); }, + isPublicEditingAllowed: function() { + return this.get('allowPublicEditingStatus'); + }, + /** * @returns {boolean} */ @@ -345,6 +349,14 @@ }, /** + * @returns {string} + */ + getReshareWithDisplayName: function() { + var reshare = this.get('reshare'); + return reshare.share_with_displayname || reshare.share_with; + }, + + /** * @returns {number} */ getReshareType: function() { @@ -391,6 +403,26 @@ return share.share_with_displayname; }, + /** + * returns the array index of a sharee for a provided shareId + * + * @param shareId + * @returns {number} + */ + findShareWithIndex: function(shareId) { + var shares = this.get('shares'); + if(!_.isArray(shares)) { + throw "Unknown Share"; + } + for(var i = 0; i < shares.length; i++) { + var shareWith = shares[i]; + if(shareWith.id === shareId) { + return i; + } + } + throw "Unknown Sharee"; + }, + getShareType: function(shareIndex) { /** @type OC.Share.Types.ShareInfo **/ var share = this.get('shares')[shareIndex]; @@ -553,7 +585,7 @@ return superShare; }, - fetch: function() { + fetch: function(options) { var model = this; this.trigger('request', this); @@ -577,6 +609,10 @@ shares: sharesMap, reshare: reshare })); + + if(!_.isUndefined(options) && _.isFunction(options.success)) { + options.success(); + } }); return deferred; @@ -647,6 +683,17 @@ }); } + var allowPublicEditingStatus = true; + if(!_.isUndefined(data.shares)) { + $.each(data.shares, function (key, value) { + if (value.share_type === OC.Share.SHARE_TYPE_LINK) { + allowPublicEditingStatus = (value.permissions & OC.PERMISSION_UPDATE) ? true : false; + return true; + } + }); + } + + var hideFileListStatus = false; if(!_.isUndefined(data.shares)) { $.each(data.shares, function (key, value) { @@ -730,6 +777,7 @@ linkShare: linkShare, permissions: permissions, allowPublicUploadStatus: allowPublicUploadStatus, + allowPublicEditingStatus: allowPublicEditingStatus, hideFileListStatus: hideFileListStatus }; }, diff --git a/core/js/systemtags/systemtagmodel.js b/core/js/systemtags/systemtagmodel.js index 89728357e25..72450a2abd0 100644 --- a/core/js/systemtags/systemtagmodel.js +++ b/core/js/systemtags/systemtagmodel.js @@ -9,7 +9,15 @@ */ (function(OC) { - var NS_OWNCLOUD = 'http://owncloud.org/ns'; + + _.extend(OC.Files.Client, { + PROPERTY_FILEID: '{' + OC.Files.Client.NS_OWNCLOUD + '}id', + PROPERTY_CAN_ASSIGN:'{' + OC.Files.Client.NS_OWNCLOUD + '}can-assign', + PROPERTY_DISPLAYNAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}display-name', + PROPERTY_USERVISIBLE: '{' + OC.Files.Client.NS_OWNCLOUD + '}user-visible', + PROPERTY_USERASSIGNABLE:'{' + OC.Files.Client.NS_OWNCLOUD + '}user-assignable', + }); + /** * @class OCA.SystemTags.SystemTagsCollection * @classdesc @@ -28,12 +36,12 @@ }, davProperties: { - 'id': '{' + NS_OWNCLOUD + '}id', - 'name': '{' + NS_OWNCLOUD + '}display-name', - 'userVisible': '{' + NS_OWNCLOUD + '}user-visible', - 'userAssignable': '{' + NS_OWNCLOUD + '}user-assignable', + 'id': OC.Files.Client.PROPERTY_FILEID, + 'name': OC.Files.Client.PROPERTY_DISPLAYNAME, + 'userVisible': OC.Files.Client.PROPERTY_USERVISIBLE, + 'userAssignable': OC.Files.Client.PROPERTY_USERASSIGNABLE, // read-only, effective permissions computed by the server, - 'canAssign': '{' + NS_OWNCLOUD + '}can-assign' + 'canAssign': OC.Files.Client.PROPERTY_CAN_ASSIGN }, parse: function(data) { @@ -50,4 +58,3 @@ OC.SystemTags = OC.SystemTags || {}; OC.SystemTags.SystemTagModel = SystemTagModel; })(OC); - diff --git a/core/js/systemtags/systemtagsinputfield.js b/core/js/systemtags/systemtagsinputfield.js index 690525c0ebb..d5f6bd5f97e 100644 --- a/core/js/systemtags/systemtagsinputfield.js +++ b/core/js/systemtags/systemtagsinputfield.js @@ -40,7 +40,9 @@ '<form class="systemtags-rename-form">' + ' <label class="hidden-visually" for="{{cid}}-rename-input">{{renameLabel}}</label>' + ' <input id="{{cid}}-rename-input" type="text" value="{{name}}">' + - ' <a href="#" class="delete icon icon-delete" title="{{deleteTooltip}}"></a>' + + ' {{#if isAdmin}}' + + ' <a href="#" class="delete icon icon-delete" title="{{deleteTooltip}}"></a>' + + ' {{/if}}' + '</form>'; /** @@ -148,7 +150,8 @@ cid: this.cid, name: oldName, deleteTooltip: t('core', 'Delete'), - renameLabel: t('core', 'Rename') + renameLabel: t('core', 'Rename'), + isAdmin: this._isAdmin })); $item.find('.label').after($renameForm); $item.find('.label, .systemtags-actions').addClass('hidden'); diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index d1734a9f3d1..d83c0cd9a38 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -590,6 +590,36 @@ describe('Core base tests', function() { } }); }); + describe('computerFileSize', function() { + it('correctly parses file sizes from a human readable formated string', function() { + var data = [ + ['125', 125], + ['125.25', 125.25], + ['0 B', 0], + ['125 B', 125], + ['125b', 125], + ['125 KB', 128000], + ['125kb', 128000], + ['122.1 MB', 128031130], + ['122.1mb', 128031130], + ['119.2 GB', 127990025421], + ['119.2gb', 127990025421], + ['116.4 TB', 127983153473126], + ['116.4tb', 127983153473126] + ]; + for (var i = 0; i < data.length; i++) { + expect(OC.Util.computerFileSize(data[i][0])).toEqual(data[i][1]); + } + }); + it('returns null if the parameter is not a string', function() { + expect(OC.Util.computerFileSize(NaN)).toEqual(null); + expect(OC.Util.computerFileSize(125)).toEqual(null); + }); + it('returns null if the string is unparsable', function() { + expect(OC.Util.computerFileSize('')).toEqual(null); + expect(OC.Util.computerFileSize('foobar')).toEqual(null); + }); + }); describe('stripTime', function() { it('strips time from dates', function() { expect(OC.Util.stripTime(new Date(2014, 2, 24, 15, 4, 45, 24))) @@ -937,8 +967,9 @@ describe('Core base tests', function() { fadeOutStub.restore(); }); it('hides the first notification when calling hide without arguments', function() { - var $row1 = OC.Notification.show('One'); + OC.Notification.show('One'); var $row2 = OC.Notification.show('Two'); + spyOn(console, 'warn'); var $el = $('#notification'); var $rows = $el.find('.row'); @@ -946,6 +977,7 @@ describe('Core base tests', function() { OC.Notification.hide(); + expect(console.warn).toHaveBeenCalled(); $rows = $el.find('.row'); expect($rows.length).toEqual(1); expect($rows.eq(0).is($row2)).toEqual(true); diff --git a/core/js/tests/specs/l10nSpec.js b/core/js/tests/specs/l10nSpec.js index 2ceb2f4a916..064b27aa34a 100644 --- a/core/js/tests/specs/l10nSpec.js +++ b/core/js/tests/specs/l10nSpec.js @@ -21,6 +21,7 @@ describe('OC.L10N tests', function() { describe('text translation', function() { beforeEach(function() { + spyOn(console, 'warn'); OC.L10N.register(TEST_APP, { 'Hello world!': 'Hallo Welt!', 'Hello {name}, the weather is {weather}': 'Hallo {name}, das Wetter ist {weather}', @@ -78,8 +79,10 @@ describe('OC.L10N tests', function() { } it('generates plural for default text when translation does not exist', function() { + spyOn(console, 'warn'); OC.L10N.register(TEST_APP, { }); + expect(console.warn).toHaveBeenCalled(); expect( n(TEST_APP, 'download %n file', 'download %n files', 0) ).toEqual('download 0 files'); @@ -94,10 +97,12 @@ describe('OC.L10N tests', function() { ).toEqual('download 1024 files'); }); it('generates plural with default function when no forms specified', function() { + spyOn(console, 'warn'); OC.L10N.register(TEST_APP, { '_download %n file_::_download %n files_': ['%n Datei herunterladen', '%n Dateien herunterladen'] }); + expect(console.warn).toHaveBeenCalled(); checkPlurals(); }); it('generates plural with generated function when forms is specified', function() { @@ -150,9 +155,11 @@ describe('OC.L10N tests', function() { it('calls callback if translation already available', function() { var promiseStub = sinon.stub(); var callbackStub = sinon.stub(); + spyOn(console, 'warn'); OC.L10N.register(TEST_APP, { 'Hello world!': 'Hallo Welt!' }); + expect(console.warn).toHaveBeenCalled(); OC.L10N.load(TEST_APP, callbackStub).then(promiseStub); expect(callbackStub.calledOnce).toEqual(true); expect(promiseStub.calledOnce).toEqual(true); diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js index 985610d51fb..cbb74714ff7 100644 --- a/core/js/tests/specs/sharedialogviewSpec.js +++ b/core/js/tests/specs/sharedialogviewSpec.js @@ -975,16 +975,35 @@ describe('OC.Share.ShareDialogView', function() { dialog.render(); expect(dialog.$el.find('.shareWithField').prop('disabled')).toEqual(true); }); - it('shows reshare owner', function() { + it('shows reshare owner for single user share', function() { shareModel.set({ reshare: { - uid_owner: 'user1' + uid_owner: 'user1', + displayname_owner: 'User One', + share_type: OC.Share.SHARE_TYPE_USER }, shares: [], permissions: OC.PERMISSION_READ }); dialog.render(); expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(1); + expect(dialog.$el.find('.resharerInfoView .reshare').text().trim()).toEqual('Shared with you by User One'); + }); + it('shows reshare owner for single user share', function() { + shareModel.set({ + reshare: { + uid_owner: 'user1', + displayname_owner: 'User One', + share_with: 'group2', + share_with_displayname: 'Group Two', + share_type: OC.Share.SHARE_TYPE_GROUP + }, + shares: [], + permissions: OC.PERMISSION_READ + }); + dialog.render(); + expect(dialog.$el.find('.resharerInfoView .reshare').length).toEqual(1); + expect(dialog.$el.find('.resharerInfoView .reshare').text().trim()).toEqual('Shared with you and the group Group Two by User One'); }); it('does not show reshare owner if owner is current user', function() { shareModel.set({ diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index 1cddcb2acaa..3d3baf75d15 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -190,6 +190,7 @@ describe('OC.Share.ShareItemModel', function() { uid_owner: 'owner', displayname_owner: 'Owner', share_with: 'root', + share_with_displayname: 'Wurzel', permissions: 1 }, { @@ -221,7 +222,11 @@ describe('OC.Share.ShareItemModel', function() { // user share has higher priority expect(reshare.share_type).toEqual(OC.Share.SHARE_TYPE_USER); expect(reshare.share_with).toEqual('root'); + expect(reshare.share_with_displayname).toEqual('Wurzel'); expect(reshare.id).toEqual('1'); + + expect(model.getReshareWith()).toEqual('root'); + expect(model.getReshareWithDisplayName()).toEqual('Wurzel'); }); it('does not parse link share when for a different file', function() { /* jshint camelcase: false */ diff --git a/core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js b/core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js index 503bef7cf2b..8d3f67bfa0d 100644 --- a/core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js +++ b/core/js/tests/specs/systemtags/systemtagsinputfieldSpec.js @@ -267,20 +267,6 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { saveStub.restore(); }); - it('deletes model and submits change when clicking delete', function() { - var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); - - expect($dropdown.find('.delete').length).toEqual(0); - $dropdown.find('.rename').mouseup(); - // delete button appears - expect($dropdown.find('.delete').length).toEqual(1); - $dropdown.find('.delete').mouseup(); - - expect(destroyStub.calledOnce).toEqual(true); - expect(destroyStub.calledOn(view.collection.get('1'))); - - destroyStub.restore(); - }); }); describe('setting data', function() { it('sets value when calling setValues', function() { @@ -299,12 +285,18 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { }); describe('as admin', function() { + var $dropdown; + beforeEach(function() { view = new OC.SystemTags.SystemTagsInputField({ isAdmin: true }); - view.render(); $('.testInputContainer').append(view.$el); + $dropdown = $('<div class="select2-dropdown"></div>'); + select2Stub.withArgs('dropdown').returns($dropdown); + $('#testArea').append($dropdown); + + view.render(); }); it('formatResult renders tag name with visibility', function() { var opts = select2Stub.getCall(0).args[0]; @@ -431,15 +423,50 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { ]); }); }); + describe('tag actions', function() { + var opts; + + beforeEach(function() { + + opts = select2Stub.getCall(0).args[0]; + + view.collection.add([ + new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), + ]); + + $dropdown.append(opts.formatResult(view.collection.get('1').toJSON())); + + }); + it('deletes model and submits change when clicking delete', function() { + var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); + + expect($dropdown.find('.delete').length).toEqual(0); + $dropdown.find('.rename').mouseup(); + // delete button appears + expect($dropdown.find('.delete').length).toEqual(1); + $dropdown.find('.delete').mouseup(); + + expect(destroyStub.calledOnce).toEqual(true); + expect(destroyStub.calledOn(view.collection.get('1'))); + + destroyStub.restore(); + }); + }); }); describe('as user', function() { + var $dropdown; + beforeEach(function() { view = new OC.SystemTags.SystemTagsInputField({ isAdmin: false }); - view.render(); $('.testInputContainer').append(view.$el); + $dropdown = $('<div class="select2-dropdown"></div>'); + select2Stub.withArgs('dropdown').returns($dropdown); + $('#testArea').append($dropdown); + + view.render(); }); it('formatResult renders tag name only', function() { var opts = select2Stub.getCall(0).args[0]; @@ -570,5 +597,33 @@ describe('OC.SystemTags.SystemTagsInputField tests', function() { ]); }); }); + describe('tag actions', function() { + var opts; + + beforeEach(function() { + + opts = select2Stub.getCall(0).args[0]; + + view.collection.add([ + new OC.SystemTags.SystemTagModel({id: '1', name: 'abc'}), + ]); + + $dropdown.append(opts.formatResult(view.collection.get('1').toJSON())); + + }); + it('deletes model and submits change when clicking delete', function() { + var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); + + expect($dropdown.find('.delete').length).toEqual(0); + $dropdown.find('.rename').mouseup(); + // delete button appears only for admins + expect($dropdown.find('.delete').length).toEqual(0); + $dropdown.find('.delete').mouseup(); + + expect(destroyStub.notCalled).toEqual(true); + + destroyStub.restore(); + }); + }); }); }); diff --git a/core/js/update.js b/core/js/update.js index 32cf2ce5ecc..e849d8a16ce 100644 --- a/core/js/update.js +++ b/core/js/update.js @@ -72,7 +72,7 @@ var span = $('<span>') .addClass('bold'); if(message === 'Exception: Updates between multiple major versions and downgrades are unsupported.') { - span.append(t('core', 'The update was unsuccessful. For more information <a href="{url}">check our forum post</a> covering this issue.', {'url': 'https://forum.owncloud.org/viewtopic.php?f=17&t=32087'})); + span.append(t('core', 'The update was unsuccessful. For more information <a href="{url}">check our forum post</a> covering this issue.', {'url': 'https://help.nextcloud.com/t/updates-between-multiple-major-versions-are-unsupported/7094'})); } else { span.append(t('core', 'The update was unsuccessful. ' + 'Please report this issue to the ' + |