diff options
-rw-r--r-- | core/js/share/sharedialoglinkshareview_popover_menu.handlebars | 10 | ||||
-rw-r--r-- | core/js/sharedialoglinkshareview.js | 64 | ||||
-rw-r--r-- | core/js/shareitemmodel.js | 5 | ||||
-rw-r--r-- | core/js/sharetemplates.js | 29 | ||||
-rw-r--r-- | core/js/tests/specs/sharedialoglinkshareview.js | 113 | ||||
-rw-r--r-- | core/js/tests/specs/shareitemmodelSpec.js | 13 |
6 files changed, 222 insertions, 12 deletions
diff --git a/core/js/share/sharedialoglinkshareview_popover_menu.handlebars b/core/js/share/sharedialoglinkshareview_popover_menu.handlebars index cc951ce047d..59312bc70b0 100644 --- a/core/js/share/sharedialoglinkshareview_popover_menu.handlebars +++ b/core/js/share/sharedialoglinkshareview_popover_menu.handlebars @@ -62,6 +62,16 @@ </span> </li> {{/if}} + {{#if showPasswordByTalkCheckBox}} + <li> + <span class="shareOption menuitem"> + <span class="icon-loading-small hidden"></span> + <input type="checkbox" name="passwordByTalk" id="passwordByTalk-{{cid}}" class="checkbox passwordByTalkCheckbox" + {{#if isPasswordByTalkSet}}checked="checked"{{/if}} /> + <label for="passwordByTalk-{{cid}}">{{passwordByTalkLabel}}</label> + </span> + </li> + {{/if}} <li> <span class="menuitem"> <input id="expireDate-{{cid}}" type="checkbox" name="expirationDate" class="expireDate checkbox" diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index 4ea8c0fa153..e5af4ad1f17 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -54,6 +54,7 @@ 'focusout input.linkPassText': 'onPasswordEntered', 'keyup input.linkPassText': 'onPasswordKeyUp', 'change .showPasswordCheckbox': 'onShowPasswordClick', + 'change .passwordByTalkCheckbox': 'onPasswordByTalkChange', 'change .publicEditingCheckbox': 'onAllowPublicEditingChange', // copy link url 'click .linkText': 'onLinkTextClick', @@ -96,6 +97,37 @@ view.render(); }); + this.model.on('change:linkShares', function(model, linkShares) { + // The "Password protect by Talk" item is shown only when there + // is a password. Unfortunately there is no fine grained + // rendering of items in the link shares, so the whole view + // needs to be rendered again when the password of a share + // changes. + // Note that this event handler is concerned only about password + // changes; other changes in the link shares does not trigger + // a rendering, so the view must be rendered again as needed in + // those cases (for example, when a link share is removed). + + var previousLinkShares = model.previous('linkShares'); + if (previousLinkShares.length !== linkShares.length) { + return; + } + + var i; + for (i = 0; i < linkShares.length; i++) { + if (linkShares[i].id !== previousLinkShares[i].id) { + // A resorting should never happen, but just in case. + return; + } + + if (linkShares[i].password !== previousLinkShares[i].password) { + view.render(); + + return; + } + } + }); + if(!_.isUndefined(options.configModel)) { this.configModel = options.configModel; } else { @@ -343,6 +375,32 @@ }); }, + onPasswordByTalkChange: function(event) { + var $element = $(event.target); + var $li = $element.closest('li[data-share-id]'); + var shareId = $li.data('share-id'); + var $checkbox = $li.find('.passwordByTalkCheckbox'); + $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); + + var sendPasswordByTalk = false; + if($checkbox.is(':checked')) { + sendPasswordByTalk = true; + } + + this.model.saveLinkShare({ + sendPasswordByTalk: sendPasswordByTalk, + cid: shareId + }, { + success: function() { + $checkbox.siblings('.icon-loading-small').addClass('hidden').removeClass('inlineblock'); + }, + error: function(obj, msg) { + OC.Notification.showTemporary(t('core', 'Unable to toggle this option')); + $checkbox.siblings('.icon-loading-small').addClass('hidden').removeClass('inlineblock'); + } + }); + }, + onAllowPublicEditingChange: function(event) { var $element = $(event.target); var $li = $element.closest('li[data-share-id]'); @@ -790,6 +848,9 @@ expireDate = moment(share.expiration, 'YYYY-MM-DD').format('DD-MM-YYYY'); } + var isTalkEnabled = oc_appswebroots['spreed'] !== undefined; + var sendPasswordByTalk = share.sendPasswordByTalk; + var showHideDownloadCheckbox = !this.model.isFolder(); var hideDownload = share.hideDownload; @@ -816,6 +877,9 @@ passwordPlaceholder: isPasswordSet ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE, isPasswordSet: isPasswordSet || isPasswordEnabledByDefault || isPasswordEnforced, showPasswordCheckBox: showPasswordCheckBox, + showPasswordByTalkCheckBox: isTalkEnabled && isPasswordSet, + passwordByTalkLabel: t('core', 'Password protect by Talk'), + isPasswordByTalkSet: sendPasswordByTalk, publicUploadRWChecked: publicUploadRWChecked, publicUploadRChecked: publicUploadRChecked, publicUploadWChecked: publicUploadWChecked, diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index 1bbdb2448ab..f4ac03e1c18 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -19,6 +19,7 @@ * @property {string} token * @property {bool} hideDownload * @property {string|null} password + * @property {bool} sendPasswordByTalk * @property {number} permissions * @property {Date} expiration * @property {number} stime share time @@ -141,6 +142,7 @@ hideDownload: false, password: '', passwordChanged: false, + sendPasswordByTalk: false, permissions: OC.PERMISSION_READ, expireDate: this.configModel.getDefaultExpirationDateString(), shareType: OC.Share.SHARE_TYPE_LINK @@ -873,7 +875,8 @@ // hide_download is returned as an int, so force it // to a boolean hideDownload: !!share.hide_download, - password: share.share_with + password: share.share_with, + sendPasswordByTalk: share.send_password_by_talk })); return share; diff --git a/core/js/sharetemplates.js b/core/js/sharetemplates.js index bd9886e6afd..6469e264d75 100644 --- a/core/js/sharetemplates.js +++ b/core/js/sharetemplates.js @@ -158,18 +158,30 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont },"11":function(container,depth0,helpers,partials,data) { return "hidden"; },"13":function(container,depth0,helpers,partials,data) { - return "datepicker"; + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return " <li>\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"checkbox\" name=\"passwordByTalk\" id=\"passwordByTalk-" + + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + + "\" class=\"checkbox passwordByTalkCheckbox\"\n " + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordByTalkSet : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + " />\n <label for=\"passwordByTalk-" + + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + + "\">" + + alias4(((helper = (helper = helpers.passwordByTalkLabel || (depth0 != null ? depth0.passwordByTalkLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"passwordByTalkLabel","hash":{},"data":data}) : helper))) + + "</label>\n </span>\n </li>\n"; },"15":function(container,depth0,helpers,partials,data) { + return "datepicker"; +},"17":function(container,depth0,helpers,partials,data) { var helper; return container.escapeExpression(((helper = (helper = helpers.expireDate || (depth0 != null ? depth0.expireDate : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"expireDate","hash":{},"data":data}) : helper))); -},"17":function(container,depth0,helpers,partials,data) { +},"19":function(container,depth0,helpers,partials,data) { var helper; return container.escapeExpression(((helper = (helper = helpers.defaultExpireDate || (depth0 != null ? depth0.defaultExpireDate : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"defaultExpireDate","hash":{},"data":data}) : helper))); -},"19":function(container,depth0,helpers,partials,data) { - return "readonly"; },"21":function(container,depth0,helpers,partials,data) { + return "readonly"; +},"23":function(container,depth0,helpers,partials,data) { var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; return " <li>\n <a href=\"#\" class=\"menuitem pop-up\" data-url=\"" @@ -193,6 +205,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicEditing : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showHideDownloadCheckbox : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPasswordCheckBox : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPasswordByTalkCheckBox : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " <li>\n <span class=\"menuitem\">\n <input id=\"expireDate-" + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + "\" type=\"checkbox\" name=\"expirationDate\" class=\"expireDate checkbox\"\n " @@ -216,15 +229,15 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "</label>\n <!-- do not use the datepicker if enforced -->\n <input id=\"expirationDatePicker-" + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + "\" class=\"" - + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isExpirationEnforced : depth0),{"name":"unless","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isExpirationEnforced : depth0),{"name":"unless","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + "\" type=\"text\"\n placeholder=\"" + alias4(((helper = (helper = helpers.expirationDatePlaceholder || (depth0 != null ? depth0.expirationDatePlaceholder : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expirationDatePlaceholder","hash":{},"data":data}) : helper))) + "\" value=\"" - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.program(17, data, 0),"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(17, data, 0),"inverse":container.program(19, data, 0),"data":data})) != null ? stack1 : "") + "\"\n data-max-date=\"" + alias4(((helper = (helper = helpers.maxDate || (depth0 != null ? depth0.maxDate : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"maxDate","hash":{},"data":data}) : helper))) + "\" " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isExpirationEnforced : depth0),{"name":"if","hash":{},"fn":container.program(19, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isExpirationEnforced : depth0),{"name":"if","hash":{},"fn":container.program(21, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " />\n </span>\n </li>\n <li>\n <a href=\"#\" class=\"share-add\">\n <span class=\"icon-loading-small hidden\"></span>\n <span class=\"icon icon-edit\"></span>\n <span>" + alias4(((helper = (helper = helpers.addNoteLabel || (depth0 != null ? depth0.addNoteLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"addNoteLabel","hash":{},"data":data}) : helper))) + "</span>\n <input type=\"button\" class=\"share-note-delete icon-delete " @@ -236,7 +249,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "</textarea>\n <input type=\"submit\" class=\"icon-confirm share-note-submit\" value=\"\" id=\"add-note-" + alias4(((helper = (helper = helpers.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) + "\" />\n </span>\n </li>\n" - + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.social : depth0),{"name":"each","hash":{},"fn":container.program(21, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.social : depth0),{"name":"each","hash":{},"fn":container.program(23, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " <li>\n <a href=\"#\" class=\"unshare\"><span class=\"icon-loading-small hidden\"></span><span class=\"icon icon-delete\"></span><span>" + alias4(((helper = (helper = helpers.unshareLinkLabel || (depth0 != null ? depth0.unshareLinkLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"unshareLinkLabel","hash":{},"data":data}) : helper))) + "</span></a>\n </li>\n <li>\n <a href=\"#\" class=\"new-share\">\n <span class=\"icon-loading-small hidden\"></span>\n <span class=\"icon icon-add\"></span>\n <span>" diff --git a/core/js/tests/specs/sharedialoglinkshareview.js b/core/js/tests/specs/sharedialoglinkshareview.js index f5fe8725c03..c2d84fd2e87 100644 --- a/core/js/tests/specs/sharedialoglinkshareview.js +++ b/core/js/tests/specs/sharedialoglinkshareview.js @@ -235,4 +235,117 @@ describe('OC.Share.ShareDialogLinkShareView', function () { }); + describe('protect password by Talk', function () { + + var $passwordByTalkCheckbox; + var $workingIcon; + + beforeEach(function () { + // Needed to render the view + configModel.isShareWithLinkAllowed.returns(true); + + // "Enable" Talk + window.oc_appswebroots['spreed'] = window.oc_webroot + '/apps/files/'; + + shareModel.set({ + linkShares: [{ + id: 123, + password: 'password' + }] + }); + view.render(); + + $passwordByTalkCheckbox = view.$el.find('.passwordByTalkCheckbox'); + $workingIcon = $passwordByTalkCheckbox.prev('.icon-loading-small'); + + sinon.stub(shareModel, 'saveLinkShare'); + + expect($workingIcon.hasClass('hidden')).toBeTruthy(); + }); + + afterEach(function () { + shareModel.saveLinkShare.restore(); + }); + + it('is shown if Talk is enabled and there is a password set', function() { + expect($passwordByTalkCheckbox.length).toBeTruthy(); + }); + + it('is not shown if Talk is enabled but there is no password set', function() { + // Changing the password value also triggers the rendering + shareModel.set({ + linkShares: [{ + id: 123 + }] + }); + + $passwordByTalkCheckbox = view.$el.find('.passwordByTalkCheckbox'); + + expect($passwordByTalkCheckbox.length).toBeFalsy(); + }); + + it('is not shown if there is a password set but Talk is not enabled', function() { + // "Disable" Talk + delete window.oc_appswebroots['spreed']; + + view.render(); + + $passwordByTalkCheckbox = view.$el.find('.passwordByTalkCheckbox'); + + expect($passwordByTalkCheckbox.length).toBeFalsy(); + }); + + it('checkbox is checked when the setting is enabled', function () { + shareModel.set({ + linkShares: [{ + id: 123, + password: 'password', + sendPasswordByTalk: true + }] + }); + view.render(); + + $passwordByTalkCheckbox = view.$el.find('.passwordByTalkCheckbox'); + + expect($passwordByTalkCheckbox.is(':checked')).toEqual(true); + }); + + it('checkbox is not checked when the setting is disabled', function () { + expect($passwordByTalkCheckbox.is(':checked')).toEqual(false); + }); + + it('enables the setting if clicked when unchecked', function () { + // Simulate the click by checking the checkbox and then triggering + // the "change" event. + $passwordByTalkCheckbox.prop('checked', true); + $passwordByTalkCheckbox.change(); + + expect($workingIcon.hasClass('hidden')).toBeFalsy(); + expect(shareModel.saveLinkShare.withArgs({ sendPasswordByTalk: true, cid: 123 }).calledOnce).toBeTruthy(); + }); + + it('disables the setting if clicked when checked', function () { + shareModel.set({ + linkShares: [{ + id: 123, + password: 'password', + sendPasswordByTalk: true + }] + }); + view.render(); + + $passwordByTalkCheckbox = view.$el.find('.passwordByTalkCheckbox'); + $workingIcon = $passwordByTalkCheckbox.prev('.icon-loading-small'); + + // Simulate the click by unchecking the checkbox and then triggering + // the "change" event. + $passwordByTalkCheckbox.prop('checked', false); + $passwordByTalkCheckbox.change(); + + expect($workingIcon.hasClass('hidden')).toBeFalsy(); + expect(shareModel.saveLinkShare.withArgs({ sendPasswordByTalk: false, cid: 123 }).calledOnce).toBeTruthy(); + }); + + }); + }); diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index 3b4dc5a960f..e8016950094 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -169,7 +169,8 @@ describe('OC.Share.ShareItemModel', function() { storage: 1, token: 'tehtoken', uid_owner: 'root', - hide_download: 1 + hide_download: 1, + send_password_by_talk: true } ])); @@ -189,6 +190,7 @@ describe('OC.Share.ShareItemModel', function() { expect(linkShares.length).toEqual(1); var linkShare = linkShares[0]; expect(linkShare.hideDownload).toEqual(true); + expect(linkShare.sendPasswordByTalk).toEqual(true); // TODO: check more attributes }); @@ -293,7 +295,8 @@ describe('OC.Share.ShareItemModel', function() { storage: 1, token: 'tehtoken', uid_owner: 'root', - hide_download: 0 + hide_download: 0, + send_password_by_talk: false }, { displayname_owner: 'root', expiration: '2015-10-15 00:00:00', @@ -312,7 +315,8 @@ describe('OC.Share.ShareItemModel', function() { storage: 1, token: 'anothertoken', uid_owner: 'root', - hide_download: 1 + hide_download: 1, + send_password_by_talk: true }] )); OC.currentUser = 'root'; @@ -327,6 +331,7 @@ describe('OC.Share.ShareItemModel', function() { var linkShare = linkShares[0]; expect(linkShare.token).toEqual('tehtoken'); expect(linkShare.hideDownload).toEqual(false); + expect(linkShare.sendPasswordByTalk).toEqual(false); // TODO: check child too }); @@ -588,6 +593,7 @@ describe('OC.Share.ShareItemModel', function() { hideDownload: false, password: '', passwordChanged: false, + sendPasswordByTalk: false, permissions: OC.PERMISSION_READ, expireDate: '', shareType: OC.Share.SHARE_TYPE_LINK @@ -612,6 +618,7 @@ describe('OC.Share.ShareItemModel', function() { hideDownload: false, password: '', passwordChanged: false, + sendPasswordByTalk: false, permissions: OC.PERMISSION_READ, expireDate: '2015-07-24 00:00:00', shareType: OC.Share.SHARE_TYPE_LINK |