diff options
author | Morris Jobke <hey@morrisjobke.de> | 2018-11-01 22:36:09 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-01 22:36:09 +0100 |
commit | f28691c26edb5b6f01330946445316d25d175fcb (patch) | |
tree | 77bc0b87e611677611750cd668be7e106a8b84a5 | |
parent | 35e3d40e803653e2fdfcd775eefc2d8a9a183d80 (diff) | |
parent | 876d6ec8e6b1e38215659fd606b7d7022bdb8460 (diff) | |
download | nextcloud-server-f28691c26edb5b6f01330946445316d25d175fcb.tar.gz nextcloud-server-f28691c26edb5b6f01330946445316d25d175fcb.zip |
Merge pull request #11844 from nextcloud/multiple-link-shares
allow to create multiple link shares via share api
26 files changed, 893 insertions, 500 deletions
diff --git a/apps/files_sharing/css/sharetabview.scss b/apps/files_sharing/css/sharetabview.scss index 0d277c58bd7..0ca99ddc8f7 100644 --- a/apps/files_sharing/css/sharetabview.scss +++ b/apps/files_sharing/css/sharetabview.scss @@ -51,7 +51,6 @@ top: 0px; } .shareWithConfirm, - .clipboardButton, .linkPass .icon-loading-small { position: absolute; right: 2px; @@ -74,21 +73,17 @@ .datepicker { margin-left: 35px; } - .clipboardButton { - position: relative; - top: initial; - right: initial; - padding: 0; - } .share-add { input.share-note-delete { - display: none; border: none; background-color: transparent; width: 44px !important; padding: 0; flex: 0 0 44px; margin-left: auto; + &.hidden { + display: none; + } } } // note @@ -123,6 +118,11 @@ margin-bottom: 10px; } } + + /* Border above last entry '+ Add another share' to separate it from current link settings */ + .new-share { + border-top: 1px solid var(--color-border); + } } .linkPass .icon-loading-small { margin-right: 0px; @@ -182,7 +182,7 @@ .avatar { width: 32px; height: 32px; - background-color: var(--color-background-darker); + background-color: var(--color-primary); } } .unshare img { @@ -194,31 +194,29 @@ display: flex; align-items: center; white-space: nowrap; - // can edit label - > .shareOption > label { - padding: 13px; - padding-right: 0; - } - // more menu - > .share-menu { - position: relative; + + // icons + > .icon:not(.hidden), + .share-menu > .icon:not(.hidden) { + padding: 14px; + height: 44px; + width: 44px; + opacity: .5; display: block; - .icon-more { - padding: 14px; - height: 44px; - width: 44px; - opacity: .5; - display: block; - cursor: pointer; - } + cursor: pointer; + &:hover, &:focus, &:active { - .icon-more { - opacity: .7;; - } + opacity: .7;; } } + + // more menu + > .share-menu { + position: relative; + display: block; + } } .username { padding: 0 8px; @@ -269,4 +267,4 @@ .resharerInfoView.subView { position: relative; -}
\ No newline at end of file +} diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 68529fd882f..b02c6e3d9ee 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -193,15 +193,15 @@ var $tr = fileList.findFileEl(fileInfoModel.get('name')); // We count email shares as link share - var hasLinkShare = shareModel.hasLinkShare(); + var hasLinkShares = shareModel.hasLinkShares(); shareModel.get('shares').forEach(function (share) { if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) { - hasLinkShare = true; + hasLinkShares = true; } }); OCA.Sharing.Util._updateFileListDataAttributes(fileList, $tr, shareModel); - if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), hasLinkShare)) { + if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), hasLinkShares)) { // remove icon, if applicable OC.Share.markFileAsShared($tr, false, false); } @@ -249,15 +249,15 @@ * * @param $tr file element of the file to update * @param {boolean} hasUserShares true if a user share exists - * @param {boolean} hasLinkShare true if a link share exists + * @param {boolean} hasLinkShares true if a link share exists * * @return {boolean} true if the icon was set, false otherwise */ - _updateFileActionIcon: function($tr, hasUserShares, hasLinkShare) { + _updateFileActionIcon: function($tr, hasUserShares, hasLinkShares) { // if the statuses are loaded already, use them for the icon // (needed when scrolling to the next page) - if (hasUserShares || hasLinkShare || $tr.attr('data-share-recipient-data') || $tr.attr('data-share-owner')) { - OC.Share.markFileAsShared($tr, true, hasLinkShare); + if (hasUserShares || hasLinkShares || $tr.attr('data-share-recipient-data') || $tr.attr('data-share-owner')) { + OC.Share.markFileAsShared($tr, true, hasLinkShares); return true; } return false; diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index a935189491e..61fad5d2b14 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -160,6 +160,7 @@ class ShareAPIController extends OCSController { 'token' => null, 'uid_file_owner' => $share->getShareOwner(), 'note' => $share->getNote(), + 'label' => $share->getLabel(), 'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(), ]; @@ -354,15 +355,17 @@ class ShareAPIController extends OCSController { * @param string $shareWith * @param string $publicUpload * @param string $password - * @param bool $sendPasswordByTalk + * @param string $sendPasswordByTalk * @param string $expireDate + * @param string $label * * @return DataResponse - * @throws OCSNotFoundException - * @throws OCSForbiddenException + * @throws NotFoundException * @throws OCSBadRequestException * @throws OCSException - * + * @throws OCSForbiddenException + * @throws OCSNotFoundException + * @throws \OCP\Files\InvalidPathException * @suppress PhanUndeclaredClassMethod */ public function createShare( @@ -373,7 +376,8 @@ class ShareAPIController extends OCSController { string $publicUpload = 'false', string $password = '', string $sendPasswordByTalk = null, - string $expireDate = '' + string $expireDate = '', + string $label = '' ): DataResponse { $share = $this->shareManager->newShare(); @@ -447,15 +451,6 @@ class ShareAPIController extends OCSController { throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator')); } - /* - * For now we only allow 1 link share. - * Return the existing link share if this is a duplicate - */ - $existingShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_LINK, $path, false, 1, 0); - if (!empty($existingShares)) { - return new DataResponse($this->formatShare($existingShares[0])); - } - if ($publicUpload === 'true') { // Check if public upload is allowed if (!$this->shareManager->shareApiLinkAllowPublicUpload()) { @@ -482,6 +477,10 @@ class ShareAPIController extends OCSController { $share->setPassword($password); } + if (!empty($label)) { + $share->setLabel($label); + } + //Expire date if ($expireDate !== '') { try { @@ -746,6 +745,7 @@ class ShareAPIController extends OCSController { * @param string $publicUpload * @param string $expireDate * @param string $note + * @param string $label * @param string $hideDownload * @return DataResponse * @throws LockedException @@ -762,6 +762,7 @@ class ShareAPIController extends OCSController { string $publicUpload = null, string $expireDate = null, string $note = null, + string $label = null, string $hideDownload = null ): DataResponse { try { @@ -776,7 +777,15 @@ class ShareAPIController extends OCSController { throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist')); } - if ($permissions === null && $password === null && $sendPasswordByTalk === null && $publicUpload === null && $expireDate === null && $note === null && $hideDownload === null) { + if ($permissions === null && + $password === null && + $sendPasswordByTalk === null && + $publicUpload === null && + $expireDate === null && + $note === null && + $label === null && + $hideDownload === null + ) { throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given')); } @@ -860,6 +869,10 @@ class ShareAPIController extends OCSController { $share->setPassword($password); } + if ($label !== null) { + $share->setLabel($label); + } + } else { if ($permissions !== null) { $permissions = (int)$permissions; diff --git a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php index bd263de3f62..eb39dd7926f 100644 --- a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php @@ -253,7 +253,7 @@ class ShareAPIControllerTest extends TestCase { public function createShare($id, $shareType, $sharedWith, $sharedBy, $shareOwner, $path, $permissions, $shareTime, $expiration, $parent, $target, $mail_send, $note = '', $token=null, - $password=null) { + $password=null, $label = '') { $share = $this->getMockBuilder(IShare::class)->getMock(); $share->method('getId')->willReturn($id); $share->method('getShareType')->willReturn($shareType); @@ -263,6 +263,7 @@ class ShareAPIControllerTest extends TestCase { $share->method('getNode')->willReturn($path); $share->method('getPermissions')->willReturn($permissions); $share->method('getNote')->willReturn($note); + $share->method('getLabel')->willReturn($label); $time = new \DateTime(); $time->setTimestamp($shareTime); $share->method('getShareTime')->willReturn($time); @@ -351,6 +352,7 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'uid_file_owner' => 'ownerId', 'note' => 'personal note', + 'label' => '', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myMimeType', 'hide_download' => 0, @@ -396,6 +398,7 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'uid_file_owner' => 'ownerId', 'note' => 'personal note', + 'label' => '', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myFolderMimeType', 'hide_download' => 0, @@ -419,7 +422,8 @@ class ShareAPIControllerTest extends TestCase { 0, 'personal note', 'token', - 'password' + 'password', + 'first link share' ); $expected = [ 'id' => 101, @@ -445,6 +449,7 @@ class ShareAPIControllerTest extends TestCase { 'url' => 'url', 'uid_file_owner' => 'ownerId', 'note' => 'personal note', + 'label' => 'first link share', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myFolderMimeType', 'hide_download' => 0, @@ -2176,6 +2181,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with' => 'recipient', 'share_with_displayname' => 'recipient', 'note' => 'personal note', + 'label' => null, 'mail_send' => 0, 'mimetype' => 'myMimeType', 'hide_download' => 0, @@ -2196,6 +2202,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'ownerDN', 'note' => 'personal note', + 'label' => null, 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2242,6 +2249,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => null, 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2286,6 +2294,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => null, 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2328,6 +2337,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => null, 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2356,6 +2366,7 @@ class ShareAPIControllerTest extends TestCase { ->setExpirationDate(new \DateTime('2001-01-02T00:00:00')) ->setToken('myToken') ->setNote('personal note') + ->setLabel('new link share') ->setId(42); $result[] = [ @@ -2372,6 +2383,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => 'new link share', 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2415,6 +2427,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2459,6 +2472,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2502,6 +2516,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2545,6 +2560,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2603,6 +2619,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2648,6 +2665,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2803,6 +2821,7 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'mimetype' => 'myMimeType', 'hide_download' => 0, + 'label' => '', ], $share, false, [] ]; @@ -2845,6 +2864,7 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'mimetype' => 'myMimeType', 'hide_download' => 0, + 'label' => '', ], $share, true, [ 'share_with_displayname' => 'recipientRoomName' ] diff --git a/core/Migrations/Version15000Date20181029084625.php b/core/Migrations/Version15000Date20181029084625.php new file mode 100644 index 00000000000..f3e12ddbb9b --- /dev/null +++ b/core/Migrations/Version15000Date20181029084625.php @@ -0,0 +1,53 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, John Molakvoæ (skjnldsv@protonmail.com) + * + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Core\Migrations; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +class Version15000Date20181029084625 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('share'); + $table->addColumn('label', 'string', [ + 'notnull' => false, + 'length' => 255, + ]); + + return $schema; + } + +} diff --git a/core/js/share/sharedialoglinkshareview.handlebars b/core/js/share/sharedialoglinkshareview.handlebars index bc7051683a2..64f52704290 100644 --- a/core/js/share/sharedialoglinkshareview.handlebars +++ b/core/js/share/sharedialoglinkshareview.handlebars @@ -1,25 +1,39 @@ {{#if shareAllowed}} -<ul id="shareLink" class="shareWithList"> - <li data-share-id="{{cid}}"> - <div class="avatar icon-public-white"></div><span class="username" title="{{linkShareLabel}}">{{linkShareLabel}}</span> - <span class="sharingOptionsGroup"> - <span class="shareOption"> - <span class="icon-loading-small hidden"></span> - <input id="linkCheckbox-{{cid}}" {{#if isLinkShare}}checked="checked"{{/if}} type="checkbox" name="linkCheckbox" class="linkCheckbox permissions checkbox"> - <label for="linkCheckbox-{{cid}}">{{linkShareEnableLabel}}</label> +<ul class="shareWithList"> + {{#if nolinkShares}} + <li data-share-id="new-share"> + <div class="avatar icon-public-white"></div> + <span class="username">{{newShareLabel}}</span> + <span class="sharingOptionsGroup"> + <div class="share-menu"> + <a href="#" class="icon icon-add new-share has-tooltip {{#if showPending}}hidden{{/if}}" title="{{newShareTitle}}"></a> + <span class="icon icon-loading-small {{#unless showPending}}hidden{{/unless}}"></span> + {{#if showPending}} + {{{pendingPopoverMenu}}} + {{/if}} + </div> </span> - {{#if showMenu}} - <div class="share-menu" tabindex="0"><span class="icon icon-more"></span> + </li> + {{/if}} + {{#each linkShares}} + <li data-share-id="{{cid}}"> + <div class="avatar icon-public-white"></div><span class="username" title="{{linkShareLabel}}">{{linkShareLabel}}</span> + + <span class="sharingOptionsGroup"> + <a href="#" class="clipboard-button icon icon-clippy has-tooltip" data-clipboard-text="{{shareLinkURL}}" title="{{copyLabel}}"></a> + <div class="share-menu"> + <a href="#" class="icon icon-more {{#if showPending}}hidden{{/if}}"></a> + <span class="icon icon-loading-small {{#unless showPending}}hidden{{/unless}}"></span> {{#if showPending}} {{{pendingPopoverMenu}}} {{else}} {{{popoverMenu}}} {{/if}} </div> - {{/if}} - </span> - </li> + </span> + </li> + {{/each}} </ul> {{else}} -{{#if noSharingPlaceholder}}<input id="shareWith-{{cid}}" class="shareWithField" type="text" placeholder="{{noSharingPlaceholder}}" disabled="disabled"/>{{/if}} +{{#if noSharingPlaceholder}}<input id="shareWith-{{cid}}" class="shareWithField" type="text" placeholder="{{noSharingPlaceholder}}" disabled="disabled" />{{/if}} {{/if}} diff --git a/core/js/share/sharedialoglinkshareview_popover_menu.handlebars b/core/js/share/sharedialoglinkshareview_popover_menu.handlebars index baee3aa6630..cc951ce047d 100644 --- a/core/js/share/sharedialoglinkshareview_popover_menu.handlebars +++ b/core/js/share/sharedialoglinkshareview_popover_menu.handlebars @@ -1,42 +1,27 @@ <div class="popovermenu menu"> <ul> - <li> - <a href="#" class="menuitem clipboardButton" data-clipboard-text="{{shareLinkURL}}"> - <span class="icon icon-clippy" ></span> - <span>{{copyLabel}}</span> - </a> - </li> <li class="hidden linkTextMenu"> <span class="menuitem icon-link-text"> <input id="linkText-{{cid}}" class="linkText" type="text" readonly="readonly" value="{{shareLinkURL}}" /> </span> </li> - {{#if showHideDownloadCheckbox}} - <li> - <span class="shareOption menuitem"> - <span class="icon-loading-small hidden"></span> - <input type="checkbox" name="hideDownload" id="sharingDialogHideDownload-{{cid}}" class="checkbox hideDownloadCheckbox" - {{#if hideDownload}}checked="checked"{{/if}} /> - <label for="sharingDialogHideDownload-{{cid}}">{{hideDownloadLabel}}</label> - </span> - </li> - {{/if}} {{#if publicUpload}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <span class="icon-loading-small hidden"></span> <input type="radio" name="publicUpload" value="{{publicUploadRValue}}" id="sharingDialogAllowPublicUpload-r-{{cid}}" class="radio publicUploadRadio" {{{publicUploadRChecked}}} /> <label for="sharingDialogAllowPublicUpload-r-{{cid}}">{{publicUploadRLabel}}</label> </span> </li> <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <span class="icon-loading-small hidden"></span> <input type="radio" name="publicUpload" value="{{publicUploadRWValue}}" id="sharingDialogAllowPublicUpload-rw-{{cid}}" class="radio publicUploadRadio" {{{publicUploadRWChecked}}} /> <label for="sharingDialogAllowPublicUpload-rw-{{cid}}">{{publicUploadRWLabel}}</label> - </span></li> + </span> + </li> <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <span class="icon-loading-small hidden"></span> <input type="radio" name="publicUpload" value="{{publicUploadWValue}}" id="sharingDialogAllowPublicUpload-w-{{cid}}" class="radio publicUploadRadio" {{{publicUploadWChecked}}} /> <label for="sharingDialogAllowPublicUpload-w-{{cid}}">{{publicUploadWLabel}}</label> @@ -45,39 +30,52 @@ {{/if}} {{#if publicEditing}} <li id="allowPublicEditingWrapper"> - <span class="shareOption menuitem"> + <span class="menuitem"> <span class="icon-loading-small hidden"></span> <input type="checkbox" name="allowPublicEditing" id="sharingDialogAllowPublicEditing-{{cid}}" class="checkbox publicEditingCheckbox" {{{publicEditingChecked}}} /> <label for="sharingDialogAllowPublicEditing-{{cid}}">{{publicEditingLabel}}</label> </span> </li> {{/if}} + {{#if showHideDownloadCheckbox}} + <li> + <span class="menuitem"> + <span class="icon-loading-small hidden"></span> + <input type="checkbox" name="hideDownload" id="sharingDialogHideDownload-{{cid}}" class="checkbox hideDownloadCheckbox" + {{#if hideDownload}}checked="checked"{{/if}} /> + <label for="sharingDialogHideDownload-{{cid}}">{{hideDownloadLabel}}</label> + </span> + </li> + {{/if}} {{#if showPasswordCheckBox}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input type="checkbox" name="showPassword" id="showPassword-{{cid}}" class="checkbox showPasswordCheckbox" {{#if isPasswordSet}}checked="checked"{{/if}} {{#if isPasswordEnforced}}disabled="disabled"{{/if}} value="1" /> <label for="showPassword-{{cid}}">{{enablePasswordLabel}}</label> </span> </li> <li class="{{#unless isPasswordSet}}hidden{{/unless}} linkPassMenu"> - <span class="shareOption menuitem icon-share-pass"> + <span class="menuitem icon-share-pass"> <input id="linkPassText-{{cid}}" class="linkPassText" type="password" placeholder="{{passwordPlaceholder}}" autocomplete="new-password" /> <span class="icon icon-loading-small hidden"></span> </span> </li> {{/if}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="expireDate-{{cid}}" type="checkbox" name="expirationDate" class="expireDate checkbox" - {{#if hasExpireDate}}checked="checked"{{/if}} {{#if isExpirationEnforced}}disabled="disabled"{{/if}}" /> + {{#if hasExpireDate}}checked="checked"{{/if}} {{#if isExpirationEnforced}}disabled="disabled"{{/if}} /> <label for="expireDate-{{cid}}">{{expireDateLabel}}</label> </span> </li> <li class="{{#unless hasExpireDate}}hidden{{/unless}}"> <span class="menuitem icon-expiredate expirationDateContainer-{{cid}}"> <label for="expirationDatePicker-{{cid}}" class="hidden-visually" value="{{expirationDate}}">{{expirationLabel}}</label> - <input id="expirationDatePicker-{{cid}}" class="datepicker" type="text" placeholder="{{expirationDatePlaceholder}}" value="{{#if hasExpireDate}}{{expireDate}}{{else}}{{defaultExpireDate}}{{/if}}" /> + <!-- do not use the datepicker if enforced --> + <input id="expirationDatePicker-{{cid}}" class="{{#unless isExpirationEnforced}}datepicker{{/unless}}" type="text" + placeholder="{{expirationDatePlaceholder}}" value="{{#if hasExpireDate}}{{expireDate}}{{else}}{{defaultExpireDate}}{{/if}}" + data-max-date="{{maxDate}}" {{#if isExpirationEnforced}}readonly{{/if}} /> </span> </li> <li> @@ -85,22 +83,32 @@ <span class="icon-loading-small hidden"></span> <span class="icon icon-edit"></span> <span>{{addNoteLabel}}</span> - <input type="button" class="share-note-delete icon-delete"> + <input type="button" class="share-note-delete icon-delete {{#unless hasNote}}hidden{{/unless}}"> </a> </li> - <li class="share-note-form share-note-link hidden"> + <li class="share-note-form share-note-link {{#unless hasNote}}hidden{{/unless}}"> <span class="menuitem icon-note"> <textarea class="share-note">{{shareNote}}</textarea> <input type="submit" class="icon-confirm share-note-submit" value="" id="add-note-{{shareId}}" /> - </span> + </span> </li> {{#each social}} <li> - <a href="#" class="shareOption menuitem pop-up" data-url="{{url}}" data-window="{{newWindow}}"> + <a href="#" class="menuitem pop-up" data-url="{{url}}" data-window="{{newWindow}}"> <span class="icon {{iconClass}}"></span> <span>{{label}}</span> </a> </li> {{/each}} + <li> + <a href="#" class="unshare"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span>{{unshareLinkLabel}}</span></a> + </li> + <li> + <a href="#" class="new-share"> + <span class="icon-loading-small hidden"></span> + <span class="icon icon-add"></span> + <span>{{newShareLabel}}</span> + </a> + </li> </ul> </div> diff --git a/core/js/share/sharedialoglinkshareview_popover_menu_pending.handlebars b/core/js/share/sharedialoglinkshareview_popover_menu_pending.handlebars index e39c082315b..787f50c6c1e 100644 --- a/core/js/share/sharedialoglinkshareview_popover_menu_pending.handlebars +++ b/core/js/share/sharedialoglinkshareview_popover_menu_pending.handlebars @@ -1,14 +1,20 @@ -<div class="popovermenu pendingpopover menu"> +<div class="popovermenu open menu"> <ul> {{#if isPasswordEnforced}} - <li><span class="shareOption menuitem"> - <input type="checkbox" name="showPassword" id="showPassword-{{cid}}" checked="checked" disabled class="checkbox showPasswordCheckbox" value="1" /> - <label for="showPassword-{{cid}}">{{enablePasswordLabel}}</label> - </span></li> - <li class="linkPassMenu"><span class="shareOption menuitem icon-share-pass"> - <input id="linkPassText-{{cid}}" class="linkPassText" type="password" placeholder="{{passwordPlaceholder}}" autocomplete="new-password" /> - <span class="icon icon-loading-small hidden"></span> - </span></li> + <li> + <span class="menuitem icon-info"> + <p>{{enforcedPasswordLabel}}</p> + </span> + </li> + <li class="linkPassMenu"> + <span class="menuitem"> + <form autocomplete="off" class="enforcedPassForm"> + <input id="enforcedPassText" required class="enforcedPassText" type="password" + placeholder="{{passwordPlaceholder}}" autocomplete="enforcedPassText" minlength="{{minPasswordLength}}" /> + <input type="submit" value=" " class="primary icon-checkmark-white"> + </form> + </span> + </li> {{/if}} </ul> </div> diff --git a/core/js/share/sharedialogshareelistview.handlebars b/core/js/share/sharedialogshareelistview.handlebars index a95949c8157..92c07f80290 100644 --- a/core/js/share/sharedialogshareelistview.handlebars +++ b/core/js/share/sharedialogshareelistview.handlebars @@ -5,7 +5,7 @@ <span class="username" title="{{shareWithTitle}}">{{shareWithDisplayName}}</span> <span class="sharingOptionsGroup"> {{#if editPermissionPossible}} - <span class="shareOption"> + <span> <input id="canEdit-{{cid}}-{{shareId}}" type="checkbox" name="edit" class="permissions checkbox" /> <label for="canEdit-{{cid}}-{{shareId}}">{{canEditLabel}}</label> </span> @@ -15,7 +15,7 @@ </div> </span> </li> - {{/each}} + {{/each}} {{#each linkReshares}} <li data-share-id="{{shareId}}" data-share-type="{{shareType}}"> <div class="avatar" data-username="{{shareInitiator}}"></div> diff --git a/core/js/share/sharedialogshareelistview_popover_menu.handlebars b/core/js/share/sharedialogshareelistview_popover_menu.handlebars index c135d31b081..64fe51a3d24 100644 --- a/core/js/share/sharedialogshareelistview_popover_menu.handlebars +++ b/core/js/share/sharedialogshareelistview_popover_menu.handlebars @@ -2,7 +2,7 @@ <ul> {{#if isResharingAllowed}} {{#if sharePermissionPossible}} {{#unless isMailShare}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="canShare-{{cid}}-{{shareId}}" type="checkbox" name="share" class="permissions checkbox" {{#if hasSharePermission}}checked="checked"{{/if}} data-permissions="{{sharePermission}}" /> <label for="canShare-{{cid}}-{{shareId}}">{{canShareLabel}}</label> </span> @@ -11,7 +11,7 @@ {{#if isFolder}} {{#if createPermissionPossible}}{{#unless isMailShare}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="canCreate-{{cid}}-{{shareId}}" type="checkbox" name="create" class="permissions checkbox" {{#if hasCreatePermission}}checked="checked"{{/if}} data-permissions="{{createPermission}}"/> <label for="canCreate-{{cid}}-{{shareId}}">{{createPermissionLabel}}</label> </span> @@ -19,7 +19,7 @@ {{/unless}}{{/if}} {{#if updatePermissionPossible}}{{#unless isMailShare}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="canUpdate-{{cid}}-{{shareId}}" type="checkbox" name="update" class="permissions checkbox" {{#if hasUpdatePermission}}checked="checked"{{/if}} data-permissions="{{updatePermission}}"/> <label for="canUpdate-{{cid}}-{{shareId}}">{{updatePermissionLabel}}</label> </span> @@ -27,7 +27,7 @@ {{/unless}}{{/if}} {{#if deletePermissionPossible}}{{#unless isMailShare}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="canDelete-{{cid}}-{{shareId}}" type="checkbox" name="delete" class="permissions checkbox" {{#if hasDeletePermission}}checked="checked"{{/if}} data-permissions="{{deletePermission}}"/> <label for="canDelete-{{cid}}-{{shareId}}">{{deletePermissionLabel}}</label> </span> @@ -37,14 +37,14 @@ {{#if isMailShare}} {{#if hasCreatePermission}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="secureDrop-{{cid}}-{{shareId}}" type="checkbox" name="secureDrop" class="checkbox secureDrop" {{#if secureDropMode}}checked="checked"{{/if}} data-permissions="{{readPermission}}"/> <label for="secureDrop-{{cid}}-{{shareId}}">{{secureDropLabel}}</label> </span> </li> {{/if}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="password-{{cid}}-{{shareId}}" type="checkbox" name="password" class="password checkbox" {{#if isPasswordSet}}checked="checked"{{/if}}{{#if isPasswordSet}}{{#if isPasswordForMailSharesRequired}}disabled=""{{/if}}{{/if}}" /> <label for="password-{{cid}}-{{shareId}}">{{passwordLabel}}</label> </span> @@ -58,7 +58,7 @@ </li> {{#if isTalkEnabled}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="passwordByTalk-{{cid}}-{{shareId}}" type="checkbox" name="passwordByTalk" class="passwordByTalk checkbox" {{#if isPasswordByTalkSet}}checked="checked"{{/if}} /> <label for="passwordByTalk-{{cid}}-{{shareId}}">{{passwordByTalkLabel}}</label> </span> @@ -73,7 +73,7 @@ {{/if}} {{/if}} <li> - <span class="shareOption menuitem"> + <span class="menuitem"> <input id="expireDate-{{cid}}-{{shareId}}" type="checkbox" name="expirationDate" class="expireDate checkbox" {{#if hasExpireDate}}checked="checked"{{/if}}" /> <label for="expireDate-{{cid}}-{{shareId}}">{{expireDateLabel}}</label> </span> @@ -86,13 +86,14 @@ </li> {{#if isNoteAvailable}} <li> - <a href="#" class="share-add"><span class="icon-loading-small hidden"></span> + <a href="#" class="share-add"> + <span class="icon-loading-small hidden"></span> <span class="icon icon-edit"></span> <span>{{addNoteLabel}}</span> - <input type="button" class="share-note-delete icon-delete"> + <input type="button" class="share-note-delete icon-delete {{#unless hasNote}}hidden{{/unless}}"> </a> </li> - <li class="share-note-form hidden"> + <li class="share-note-form {{#unless hasNote}}hidden{{/unless}}"> <span class="menuitem icon-note"> <textarea class="share-note">{{shareNote}}</textarea> <input type="submit" class="icon-confirm share-note-submit" value="" id="add-note-{{shareId}}" /> diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index 7603b058a96..4ea8c0fa153 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -42,9 +42,10 @@ /** @type {boolean} **/ showPending: false, + /** @type {string} **/ + password: '', + events: { - // enable/disable - 'change .linkCheckbox': 'onLinkCheckBoxChange', // open menu 'click .share-menu .icon-more': 'onToggleMenu', // hide download @@ -67,7 +68,13 @@ // note 'click .share-add': 'showNoteForm', 'click .share-note-delete': 'deleteNote', - 'click .share-note-submit': 'updateNote' + 'click .share-note-submit': 'updateNote', + // remove + 'click .unshare': 'onUnshare', + // new share + 'click .new-share': 'newShare', + // enforced pass set + 'submit .enforcedPassForm': 'enforcedPasswordSet', }, initialize: function(options) { @@ -89,36 +96,39 @@ view.render(); }); - this.model.on('change:linkShare', function() { - view.render(); - }); - if(!_.isUndefined(options.configModel)) { this.configModel = options.configModel; } else { throw 'missing OC.Share.ShareConfigModel'; } - var clipboard = new Clipboard('.clipboardButton'); + var clipboard = new Clipboard('.clipboard-button'); clipboard.on('success', function(e) { - var $menu = $(e.trigger); - var $linkTextMenu = $menu.parent().next('li.linkTextMenu') + var $trigger = $(e.trigger); - $menu.tooltip('hide') + $trigger.tooltip('hide') .attr('data-original-title', t('core', 'Copied!')) .tooltip('fixTitle') .tooltip({placement: 'bottom', trigger: 'manual'}) .tooltip('show'); _.delay(function() { - $menu.tooltip('hide'); - $menu.tooltip('destroy'); + $trigger.tooltip('hide') + .attr('data-original-title', t('core', 'Copy link')) + .tooltip('fixTitle') }, 3000); }); clipboard.on('error', function (e) { - var $menu = $(e.trigger); - var $linkTextMenu = $menu.parent().next('li.linkTextMenu'); + var $trigger = $(e.trigger); + var $menu = $trigger.next('.share-menu').find('.popovermenu'); + var $linkTextMenu = $menu.find('li.linkTextMenu'); var $input = $linkTextMenu.find('.linkText'); + var $li = $trigger.closest('li[data-share-id]'); + var shareId = $li.data('share-id'); + + // show menu + OC.showMenu(null, $menu); + var actionMsg = ''; if (/iPhone|iPad/i.test(navigator.userAgent)) { actionMsg = t('core', 'Not supported!'); @@ -143,46 +153,102 @@ }); }, - onLinkCheckBoxChange: function() { - var $checkBox = this.$el.find('.linkCheckbox'); - var $loading = $checkBox.siblings('.icon-loading-small'); - if(!$loading.hasClass('hidden')) { + newShare: function(event) { + var self = this; + var $target = $(event.target); + var $li = $target.closest('li[data-share-id]'); + var shareId = $li.data('share-id'); + var $loading = $li.find('.share-menu > .icon-loading-small'); + + if(!$loading.hasClass('hidden') && this.password === '') { + // in process return false; } - if($checkBox.is(':checked')) { - if(this.configModel.get('enforcePasswordForPublicLink') === false) { - $loading.removeClass('hidden'); - // this will create it - this.model.saveLinkShare(); - $('.share-menu .icon-more').click(); - $('.share-menu .icon-more + .popovermenu .clipboardButton').click(); - } else { - // force the rendering of the menu - this.showPending = true; - this.render() - $('.share-menu .icon-more').click(); - $('.share-menu .icon-more + .popovermenu input:eq(1)').focus() - } + // hide all icons and show loading + $li.find('.icon').addClass('hidden'); + $loading.removeClass('hidden'); + + // hide menu + OC.hideMenus(); + + var shareData = {} + + var isPasswordEnforced = this.configModel.get('enforcePasswordForPublicLink'); + var isExpirationEnforced = this.configModel.get('isDefaultExpireDateEnforced'); + + // set default expire date + if (isExpirationEnforced) { + var defaultExpireDays = this.configModel.get('defaultExpireDate'); + var expireDate = moment().add(defaultExpireDays, 'day').format('DD-MM-YYYY') + shareData.expireDate = expireDate; + } + + // if password is set, add to data + if (isPasswordEnforced && this.password !== '') { + shareData.password = this.password + } + + var newShareId = false; + + // We need a password before the share creation + if (isPasswordEnforced && !this.showPending && this.password === '') { + this.showPending = shareId; + this.render(); + $li.find('#enforcedPassText').focus(); } else { - if (this.model.get('linkShare').isLinkShare) { - $loading.removeClass('hidden'); - this.model.removeLinkShare(); - } else { - this.showPending = false; - this.render() - } + // else, we have a password or it is not enforced + $.when(this.model.saveLinkShare(shareData, { + success: function() { + $loading.addClass('hidden'); + $li.find('.icon').removeClass('hidden'); + self.render(); + // open the menu by default + // we can only do that after the render + if (newShareId) { + var shares = self.$el.find('li[data-share-id]'); + var $newShare = self.$el.find('li[data-share-id="'+newShareId+'"]'); + // only open the menu by default if this is the first share + if ($newShare && shares.length === 1) { + $menu = $newShare.find('.popovermenu'); + OC.showMenu(null, $menu); + } + } + }, + error: function() { + OC.Notification.showTemporary(t('core', 'Unable to create a link share')); + $loading.addClass('hidden'); + $li.find('.icon').removeClass('hidden'); + } + })).then(function(response) { + // resolve before success + newShareId = response.ocs.data.id + }); } }, - onLinkTextClick: function() { - var $el = this.$el.find('.linkText'); + enforcedPasswordSet: function(event) { + event.preventDefault(); + var $form = $(event.target); + var $input = $form.find('input.enforcedPassText'); + this.password = $input.val(); + this.showPending = false; + this.newShare(event); + }, + + onLinkTextClick: function(event) { + var $element = $(event.target); + var $li = $element.closest('li[data-share-id]'); + var $el = $li.find('.linkText'); $el.focus(); $el.select(); }, - onHideDownloadChange: function() { - var $checkbox = this.$('.hideDownloadCheckbox'); + onHideDownloadChange: function(event) { + var $element = $(event.target); + var $li = $element.closest('li[data-share-id]'); + var shareId = $li.data('share-id'); + var $checkbox = $li.find('.hideDownloadCheckbox'); $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); var hideDownload = false; @@ -191,41 +257,57 @@ } this.model.saveLinkShare({ - hideDownload: hideDownload + hideDownload: hideDownload, + 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'); + } }); }, - onShowPasswordClick: function() { - this.$el.find('.linkPass').slideToggle(OC.menuSpeed); - this.$el.find('.linkPassMenu').toggleClass('hidden'); - if(!this.$el.find('.showPasswordCheckbox').is(':checked')) { + onShowPasswordClick: function(event) { + var $element = $(event.target); + var $li = $element.closest('li[data-share-id]'); + var shareId = $li.data('share-id'); + $li.find('.linkPass').slideToggle(OC.menuSpeed); + $li.find('.linkPassMenu').toggleClass('hidden'); + if(!$li.find('.showPasswordCheckbox').is(':checked')) { this.model.saveLinkShare({ - password: '' + password: '', + cid: shareId }); } else { if (!OC.Util.isIE()) { - this.$el.find('.linkPassText').focus(); + $li.find('.linkPassText').focus(); } } }, onPasswordKeyUp: function(event) { if(event.keyCode === 13) { - this.onPasswordEntered(); + this.onPasswordEntered(event); } }, - onPasswordEntered: function() { - var $loading = this.$el.find('.linkPassMenu .icon-loading-small'); + onPasswordEntered: function(event) { + var $element = $(event.target); + var $li = $element.closest('li[data-share-id]'); + var shareId = $li.data('share-id'); + var $loading = $li.find('.linkPassMenu .icon-loading-small'); if (!$loading.hasClass('hidden')) { // still in process return; } - var $input = this.$el.find('.linkPassText'); + var $input = $li.find('.linkPassText'); $input.removeClass('error'); var password = $input.val(); - if (this.$el.find('.linkPassText').attr('placeholder') === PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL) { + if ($li.find('.linkPassText').attr('placeholder') === PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL) { // in IE9 the password might be the placeholder due to bugs in the placeholders polyfill if(password === PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL) { @@ -244,7 +326,8 @@ .addClass('inlineblock'); this.model.saveLinkShare({ - password: password + password: password, + cid: shareId }, { complete: function(model) { $loading.removeClass('inlineblock').addClass('hidden'); @@ -260,8 +343,11 @@ }); }, - onAllowPublicEditingChange: function() { - var $checkbox = this.$('.publicEditingCheckbox'); + onAllowPublicEditingChange: function(event) { + var $element = $(event.target); + var $li = $element.closest('li[data-share-id]'); + var shareId = $li.data('share-id'); + var $checkbox = $li.find('.publicEditingCheckbox'); $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); var permissions = OC.PERMISSION_READ; @@ -270,15 +356,28 @@ } this.model.saveLinkShare({ - permissions: permissions + permissions: permissions, + 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'); + } }); }, - onPublicUploadChange: function(e) { - var permissions = e.currentTarget.value; + onPublicUploadChange: function(event) { + var $element = $(event.target); + var $li = $element.closest('li[data-share-id]'); + var shareId = $li.data('share-id'); + var permissions = event.currentTarget.value; this.model.saveLinkShare({ - permissions: permissions + permissions: permissions, + cid: shareId }); }, @@ -292,7 +391,7 @@ var $form = $menu.next('li.share-note-form'); // show elements - $menu.find('.share-note-delete').toggle(); + $menu.find('.share-note-delete').toggleClass('hidden'); $form.toggleClass('hidden'); $form.find('textarea').focus(); }, @@ -310,7 +409,7 @@ $form.find('.share-note').val(''); $form.addClass('hidden'); - $menu.find('.share-note-delete').hide(); + $menu.find('.share-note-delete').addClass('hidden'); self.sendNote('', shareId, $menu); }, @@ -365,6 +464,11 @@ }, render: function() { + this.$el.find('.has-tooltip').tooltip(); + + // reset previously set passwords + this.password = ''; + var linkShareTemplate = this.template(); var resharingAllowed = this.model.sharePermissionPossible(); @@ -386,46 +490,21 @@ && this.model.createPermissionPossible() && this.configModel.isPublicUploadEnabled(); - var publicUploadRWChecked = ''; - var publicUploadRChecked = ''; - var publicUploadWChecked = ''; - - switch (this.model.linkSharePermissions()) { - case OC.PERMISSION_READ: - publicUploadRChecked = 'checked'; - break; - case OC.PERMISSION_CREATE: - publicUploadWChecked = 'checked'; - break; - case OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE: - publicUploadRWChecked = 'checked'; - break; - } var publicEditingChecked = ''; if(this.model.isPublicEditingAllowed()) { publicEditingChecked = 'checked="checked"'; } - var isLinkShare = this.model.get('linkShare').isLinkShare; - var isPasswordSet = !!this.model.get('linkShare').password; var isPasswordEnforced = this.configModel.get('enforcePasswordForPublicLink'); var isPasswordEnabledByDefault = this.configModel.get('enableLinkPasswordByDefault') === true; - var showPasswordCheckBox = isLinkShare - && ( !this.configModel.get('enforcePasswordForPublicLink') - || !this.model.get('linkShare').password); var passwordPlaceholderInitial = this.configModel.get('enforcePasswordForPublicLink') ? PASSWORD_PLACEHOLDER_MESSAGE : PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL; - var showHideDownloadCheckbox = !this.model.isFolder(); - var hideDownload = this.model.get('linkShare').hideDownload; - var publicEditable = !this.model.isFolder() - && isLinkShare && this.model.updatePermissionPossible(); - var link = this.model.get('linkShare').link; var social = []; OC.Share.Social.Collection.each(function(model) { var url = model.get('url'); @@ -439,60 +518,33 @@ newWindow: model.get('newWindow') }); }); - - var defaultExpireDays = this.configModel.get('defaultExpireDate'); var isExpirationEnforced = this.configModel.get('isDefaultExpireDateEnforced'); - var hasExpireDate = !!this.model.get('linkShare').expiration || isExpirationEnforced; - - var expireDate; - if (hasExpireDate) { - expireDate = moment(this.model.get('linkShare').expiration, 'YYYY-MM-DD').format('DD-MM-YYYY'); - } // what if there is another date picker on that page? var minDate = new Date(); - var maxDate = null; // min date should always be the next day minDate.setDate(minDate.getDate()+1); - if(hasExpireDate) { - if(isExpirationEnforced) { - // TODO: hack: backend returns string instead of integer - var shareTime = this.model.get('linkShare').stime; - if (_.isNumber(shareTime)) { - shareTime = new Date(shareTime * 1000); - } - if (!shareTime) { - shareTime = new Date(); // now - } - shareTime = OC.Util.stripTime(shareTime).getTime(); - maxDate = new Date(shareTime + defaultExpireDays * 24 * 3600 * 1000); - } - } $.datepicker.setDefaults({ - minDate: minDate, - maxDate: maxDate + minDate: minDate }); this.$el.find('.datepicker').datepicker({dateFormat : 'dd-mm-yy'}); - var popover = this.popoverMenuTemplate({ - cid: this.model.get('linkShare').id, - copyLabel: t('core', 'Copy link'), - social: social, + var minPasswordLength = 4 + // password policy? + if(oc_capabilities.password_policy && oc_capabilities.password_policy.minLength) { + minPasswordLength = oc_capabilities.password_policy.minLength; + } - shareLinkURL: this.model.get('linkShare').link, + var popoverBase = { + social: social, urlLabel: t('core', 'Link'), - showHideDownloadCheckbox: showHideDownloadCheckbox, - hideDownload: hideDownload, hideDownloadLabel: t('core', 'Hide download'), - enablePasswordLabel: t('core', 'Password protect'), + enablePasswordLabel: isPasswordEnforced ? t('core', 'Password protection enforced') : t('core', 'Password protect'), passwordLabel: t('core', 'Password'), - passwordPlaceholder: isPasswordSet ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE, passwordPlaceholderInitial: passwordPlaceholderInitial, - isPasswordSet: isPasswordSet || isPasswordEnabledByDefault || isPasswordEnforced, - showPasswordCheckBox: showPasswordCheckBox, - publicUpload: publicUpload && isLinkShare, + publicUpload: publicUpload, publicEditing: publicEditable, publicEditingChecked: publicEditingChecked, publicEditingLabel: t('core', 'Allow editing'), @@ -504,41 +556,43 @@ publicUploadRWValue: OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE, publicUploadRValue: OC.PERMISSION_READ, publicUploadWValue: OC.PERMISSION_CREATE, - publicUploadRWChecked: publicUploadRWChecked, - publicUploadRChecked: publicUploadRChecked, - publicUploadWChecked: publicUploadWChecked, - expireDateLabel: t('core', 'Set expiration date'), + expireDateLabel: isExpirationEnforced ? t('core', 'Expiration date enforced') : t('core', 'Set expiration date'), expirationLabel: t('core', 'Expiration'), expirationDatePlaceholder: t('core', 'Expiration date'), - hasExpireDate: hasExpireDate, isExpirationEnforced: isExpirationEnforced, isPasswordEnforced: isPasswordEnforced, - expireDate: expireDate, defaultExpireDate: moment().add(1, 'day').format('DD-MM-YYYY'), // Can't expire today - shareNote: this.model.get('linkShare').note, addNoteLabel: t('core', 'Note to recipient'), - }); + unshareLabel: t('core', 'Unshare'), + unshareLinkLabel: t('core', 'Delete share link'), + newShareLabel: t('core', 'Add another link'), + }; - var pendingPopover = this.pendingPopoverMenuTemplate({ - cid: this.model.get('linkShare').id, - enablePasswordLabel: t('core', 'Password protect'), - passwordLabel: t('core', 'Password'), - passwordPlaceholder: isPasswordSet ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE, - passwordPlaceholderInitial: passwordPlaceholderInitial, - showPasswordCheckBox: showPasswordCheckBox, + var pendingPopover = { isPasswordEnforced: isPasswordEnforced, - }); + enforcedPasswordLabel: t('core', 'Password protection for links is mandatory'), + passwordPlaceholder: passwordPlaceholderInitial, + minPasswordLength: minPasswordLength, + }; + var pendingPopoverMenu = this.pendingPopoverMenuTemplate(_.extend({}, pendingPopover)) + + var linkShares = this.getShareeList(); + if(_.isArray(linkShares)) { + for (var i = 0; i < linkShares.length; i++) { + var popover = this.getPopoverObject(linkShares[i]) + linkShares[i].popoverMenu = this.popoverMenuTemplate(_.extend({}, popoverBase, popover)); + linkShares[i].pendingPopoverMenu = pendingPopoverMenu + } + } this.$el.html(linkShareTemplate({ - cid: this.model.get('linkShare').id, + linkShares: linkShares, shareAllowed: true, - isLinkShare: isLinkShare, - linkShareLabel: t('core', 'Share link'), - linkShareEnableLabel: t('core', 'Enable'), - popoverMenu: popover, - pendingPopoverMenu: pendingPopover, - showMenu: isLinkShare || this.showPending, - showPending: this.showPending && !isLinkShare + nolinkShares: linkShares.length === 0, + newShareLabel: t('core', 'Share link'), + newShareTitle: t('core', 'New share link'), + pendingPopoverMenu: pendingPopoverMenu, + showPending: this.showPending === 'new', })); this.delegateEvents(); @@ -555,9 +609,17 @@ var $element = $(event.target); var $li = $element.closest('li[data-share-id]'); var $menu = $li.find('.sharingOptionsGroup .popovermenu'); + var shareId = $li.data('share-id'); OC.showMenu(null, $menu); - this._menuOpen = $li.data('share-id'); + + // focus the password if not set and enforced + var isPasswordEnabledByDefault = this.configModel.get('enableLinkPasswordByDefault') === true; + var haspassword = $menu.find('.linkPassText').val() !== ''; + + if (!haspassword && isPasswordEnabledByDefault) { + $menu.find('.linkPassText').focus(); + } }, /** @@ -635,24 +697,173 @@ var $element = $(event.target); var li = $element.closest('li[data-share-id]'); var shareId = li.data('share-id'); + var maxDate = $element.data('max-date'); var expirationDatePicker = '#expirationDatePicker-' + shareId; var self = this; $(expirationDatePicker).datepicker({ dateFormat : 'dd-mm-yy', onSelect: function (expireDate) { - self.setExpirationDate(expireDate); - } + self.setExpirationDate(expireDate, shareId); + }, + maxDate: maxDate }); $(expirationDatePicker).datepicker('show'); $(expirationDatePicker).focus(); }, - setExpirationDate: function(expireDate) { - this.model.saveLinkShare({expireDate: expireDate}); + setExpirationDate: function(expireDate, shareId) { + this.model.saveLinkShare({expireDate: expireDate, cid: shareId}); + }, + + /** + * get an array of sharees' share properties + * + * @returns {Array} + */ + getShareeList: function() { + var shares = this.model.get('linkShares'); + + if(!this.model.hasLinkShares()) { + return []; + } + + var list = []; + for(var index = 0; index < shares.length; index++) { + var share = this.getShareeObject(index); + // first empty {} is necessary, otherwise we get in trouble + // with references + list.push(_.extend({}, share)); + } + + return list; + }, + + /** + * + * @param {OC.Share.Types.ShareInfo} shareInfo + * @returns {object} + */ + getShareeObject: function(shareIndex) { + var share = this.model.get('linkShares')[shareIndex]; + + return _.extend({}, share, { + cid: share.id, + shareAllowed: true, + linkShareLabel: share.label !== '' ? share.label : t('core', 'Share link'), + popoverMenu: {}, + shareLinkURL: share.url, + newShareTitle: t('core', 'New share link'), + copyLabel: t('core', 'Copy link'), + showPending: this.showPending === share.id, + }) + }, + + getPopoverObject: function(share) { + var publicUploadRWChecked = ''; + var publicUploadRChecked = ''; + var publicUploadWChecked = ''; + + switch (this.model.linkSharePermissions(share.id)) { + case OC.PERMISSION_READ: + publicUploadRChecked = 'checked'; + break; + case OC.PERMISSION_CREATE: + publicUploadWChecked = 'checked'; + break; + case OC.PERMISSION_UPDATE | OC.PERMISSION_CREATE | OC.PERMISSION_READ | OC.PERMISSION_DELETE: + publicUploadRWChecked = 'checked'; + break; + } + + var isPasswordSet = !!share.password; + var isPasswordEnabledByDefault = this.configModel.get('enableLinkPasswordByDefault') === true; + var isPasswordEnforced = this.configModel.get('enforcePasswordForPublicLink'); + var showPasswordCheckBox = !isPasswordEnforced || !share.password; + var isExpirationEnforced = this.configModel.get('isDefaultExpireDateEnforced'); + var defaultExpireDays = this.configModel.get('defaultExpireDate'); + var hasExpireDate = !!share.expiration || isExpirationEnforced; + + var expireDate; + if (hasExpireDate) { + expireDate = moment(share.expiration, 'YYYY-MM-DD').format('DD-MM-YYYY'); + } + + var showHideDownloadCheckbox = !this.model.isFolder(); + var hideDownload = share.hideDownload; + + var maxDate = null; + + if(hasExpireDate) { + if(isExpirationEnforced) { + // TODO: hack: backend returns string instead of integer + var shareTime = share.stime; + if (_.isNumber(shareTime)) { + shareTime = new Date(shareTime * 1000); + } + if (!shareTime) { + shareTime = new Date(); // now + } + shareTime = OC.Util.stripTime(shareTime).getTime(); + maxDate = new Date(shareTime + defaultExpireDays * 24 * 3600 * 1000); + } + } + + return { + cid: share.id, + shareLinkURL: share.url, + passwordPlaceholder: isPasswordSet ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE, + isPasswordSet: isPasswordSet || isPasswordEnabledByDefault || isPasswordEnforced, + showPasswordCheckBox: showPasswordCheckBox, + publicUploadRWChecked: publicUploadRWChecked, + publicUploadRChecked: publicUploadRChecked, + publicUploadWChecked: publicUploadWChecked, + hasExpireDate: hasExpireDate, + expireDate: expireDate, + shareNote: share.note, + hasNote: share.note !== '', + maxDate: maxDate, + showHideDownloadCheckbox: showHideDownloadCheckbox, + hideDownload: hideDownload, + isExpirationEnforced: isExpirationEnforced, + } + }, + + onUnshare: function(event) { + event.preventDefault(); + event.stopPropagation(); + 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[data-share-id]'); + + var shareId = $li.data('share-id'); + + self.model.removeShare(shareId, { + success: function() { + $li.remove(); + self.render() + }, + error: function() { + $loading.addClass('hidden'); + OC.Notification.showTemporary(t('core', 'Could not unshare')); + } + }); + return false; }, + }); OC.Share.ShareDialogLinkShareView = ShareDialogLinkShareView; diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js index 94abf55a358..8b709f0d74d 100644 --- a/core/js/sharedialogshareelistview.js +++ b/core/js/sharedialogshareelistview.js @@ -134,6 +134,8 @@ var hasPassword = password !== null && password !== ''; var sendPasswordByTalk = share.send_password_by_talk; + var shareNote = this.model.getNote(shareIndex); + return _.extend(hasPermissionOverride, { cid: this.cid, hasSharePermission: this.model.hasSharePermission(shareIndex), @@ -159,7 +161,8 @@ isTalkEnabled: oc_appswebroots['spreed'] !== undefined, secureDropMode: !this.model.hasReadPermission(shareIndex), hasExpireDate: this.model.getExpireDate(shareIndex) !== null, - shareNote: this.model.getNote(shareIndex), + shareNote: shareNote, + hasNote: shareNote !== '', expireDate: moment(this.model.getExpireDate(shareIndex), 'YYYY-MM-DD').format('DD-MM-YYYY'), // The password placeholder does not take into account if // sending the password by Talk is enabled or not; when @@ -386,7 +389,7 @@ var $form = $menu.next('li.share-note-form'); // show elements - $menu.find('.share-note-delete').toggle(); + $menu.find('.share-note-delete').toggleClass('hidden'); $form.toggleClass('hidden'); $form.find('textarea').focus(); }, @@ -405,7 +408,7 @@ $form.find('.share-note').val(''); $form.addClass('hidden'); - $menu.find('.share-note-delete').hide(); + $menu.find('.share-note-delete').addClass('hidden'); self.sendNote('', shareId, $menu); }, diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index 84715ec87c1..1bbdb2448ab 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -16,11 +16,9 @@ /** * @typedef {object} OC.Share.Types.LinkShareInfo - * @property {bool} isLinkShare * @property {string} token * @property {bool} hideDownload * @property {string|null} password - * @property {string} link * @property {number} permissions * @property {Date} expiration * @property {number} stime share time @@ -100,7 +98,7 @@ defaults: { allowPublicUploadStatus: false, permissions: 0, - linkShare: {} + linkShares: [] }, /** @@ -130,8 +128,11 @@ delete attributes.expiration; } - if (this.get('linkShare') && this.get('linkShare').isLinkShare) { - shareId = this.get('linkShare').id; + var linkShares = this.get('linkShares'); + var shareIndex = _.findIndex(linkShares, function(share) {return share.id === attributes.cid}) + + if (linkShares.length > 0 && shareIndex !== -1) { + shareId = linkShares[shareIndex].id; // note: update can only update a single value at a time call = this.updateShare(shareId, attributes, options); @@ -151,12 +152,6 @@ return call; }, - removeLinkShare: function() { - if (this.get('linkShare')) { - return this.removeShare(this.get('linkShare').id); - } - }, - addShare: function(attributes, options) { var shareType = attributes.shareType; attributes = _.extend({}, attributes); @@ -316,13 +311,13 @@ }, /** - * Returns whether this item has a link share + * Returns whether this item has link shares * * @return {bool} true if a link share exists, false otherwise */ - hasLinkShare: function() { - var linkShare = this.get('linkShare'); - if (linkShare && linkShare.isLinkShare) { + hasLinkShares: function() { + var linkShares = this.get('linkShares'); + if (linkShares && linkShares.length > 0) { return true; } return false; @@ -636,12 +631,16 @@ /** * @returns {int} */ - linkSharePermissions: function() { - if (!this.hasLinkShare()) { + linkSharePermissions: function(shareId) { + var linkShares = this.get('linkShares'); + var shareIndex = _.findIndex(linkShares, function(share) {return share.id === shareId}) + + if (!this.hasLinkShares()) { return -1; - } else { - return this.get('linkShare').permissions; + } else if (linkShares.length > 0 && shareIndex !== -1) { + return linkShares[shareIndex].permissions; } + return -1; }, _getUrl: function(base, params) { @@ -837,7 +836,7 @@ this._legacyFillCurrentShares(shares); - var linkShare = { isLinkShare: false }; + var linkShares = []; // filter out the share by link shares = _.reject(shares, /** @@ -850,7 +849,7 @@ || share.item_source === this.get('itemSource')); if (isShareLink) { - /* + /** * Ignore reshared link shares for now * FIXME: Find a way to display properly */ @@ -870,20 +869,12 @@ } else { link += OC.generateUrl('/s/') + share.token; } - linkShare = { - isLinkShare: true, - id: share.id, - token: share.token, + linkShares.push(_.extend({}, share, { // hide_download is returned as an int, so force it // to a boolean hideDownload: !!share.hide_download, - password: share.share_with, - link: link, - permissions: share.permissions, - // currently expiration is only effective for link shares. - expiration: share.expiration, - stime: share.stime - }; + password: share.share_with + })); return share; } @@ -894,7 +885,7 @@ return { reshare: data.reshare, shares: shares, - linkShare: linkShare, + linkShares: linkShares, permissions: permissions, allowPublicUploadStatus: allowPublicUploadStatus, allowPublicEditingStatus: allowPublicEditingStatus, @@ -930,7 +921,7 @@ getShareTypes: function() { var result; result = _.pluck(this.getSharesWithCurrentItem(), 'share_type'); - if (this.hasLinkShare()) { + if (this.hasLinkShares()) { result.push(OC.Share.SHARE_TYPE_LINK); } return _.uniq(result); diff --git a/core/js/sharetemplates.js b/core/js/sharetemplates.js index 0c1fee37455..bd9886e6afd 100644 --- a/core/js/sharetemplates.js +++ b/core/js/sharetemplates.js @@ -1,33 +1,28 @@ (function() { var template = Handlebars.template, templates = OC.Share.Templates = OC.Share.Templates || {}; templates['sharedialoglinkshareview'] = template({"1":function(container,depth0,helpers,partials,data) { - var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - return "<ul id=\"shareLink\" class=\"shareWithList\">\n <li data-share-id=\"" - + 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))) - + "\">\n <div class=\"avatar icon-public-white\"></div><span class=\"username\" title=\"" - + alias4(((helper = (helper = helpers.linkShareLabel || (depth0 != null ? depth0.linkShareLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"linkShareLabel","hash":{},"data":data}) : helper))) - + "\">" - + alias4(((helper = (helper = helpers.linkShareLabel || (depth0 != null ? depth0.linkShareLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"linkShareLabel","hash":{},"data":data}) : helper))) - + "</span>\n <span class=\"sharingOptionsGroup\">\n <span class=\"shareOption\">\n <span class=\"icon-loading-small hidden\"></span>\n <input id=\"linkCheckbox-" - + 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))) - + "\" " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isLinkShare : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " type=\"checkbox\" name=\"linkCheckbox\" class=\"linkCheckbox permissions checkbox\">\n <label for=\"linkCheckbox-" - + 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.linkShareEnableLabel || (depth0 != null ? depth0.linkShareEnableLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"linkShareEnableLabel","hash":{},"data":data}) : helper))) - + "</label>\n </span>\n" - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showMenu : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " </span>\n </li>\n</ul>\n"; + return "<ul class=\"shareWithList\">\n" + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.nolinkShares : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.linkShares : depth0),{"name":"each","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "</ul>\n"; },"2":function(container,depth0,helpers,partials,data) { - return "checked=\"checked\""; -},"4":function(container,depth0,helpers,partials,data) { - var stack1; + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return " <div class=\"share-menu\" tabindex=\"0\"><span class=\"icon icon-more\"></span>\n" - + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.showPending : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.program(7, data, 0),"data":data})) != null ? stack1 : "") - + " </div>\n"; + return " <li data-share-id=\"new-share\">\n <div class=\"avatar icon-public-white\"></div>\n <span class=\"username\">" + + alias4(((helper = (helper = helpers.newShareLabel || (depth0 != null ? depth0.newShareLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"newShareLabel","hash":{},"data":data}) : helper))) + + "</span>\n <span class=\"sharingOptionsGroup\">\n <div class=\"share-menu\">\n <a href=\"#\" class=\"icon icon-add new-share has-tooltip " + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPending : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\" title=\"" + + alias4(((helper = (helper = helpers.newShareTitle || (depth0 != null ? depth0.newShareTitle : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"newShareTitle","hash":{},"data":data}) : helper))) + + "\"></a>\n <span class=\"icon icon-loading-small " + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.showPending : depth0),{"name":"unless","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\"></span>\n" + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPending : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + " </div>\n </span>\n </li>\n"; +},"3":function(container,depth0,helpers,partials,data) { + return "hidden"; },"5":function(container,depth0,helpers,partials,data) { var stack1, helper; @@ -35,47 +30,53 @@ templates['sharedialoglinkshareview'] = template({"1":function(container,depth0, + ((stack1 = ((helper = (helper = helpers.pendingPopoverMenu || (depth0 != null ? depth0.pendingPopoverMenu : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"pendingPopoverMenu","hash":{},"data":data}) : helper))) != null ? stack1 : "") + "\n"; },"7":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return " <li data-share-id=\"" + + 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))) + + "\">\n <div class=\"avatar icon-public-white\"></div><span class=\"username\" title=\"" + + alias4(((helper = (helper = helpers.linkShareLabel || (depth0 != null ? depth0.linkShareLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"linkShareLabel","hash":{},"data":data}) : helper))) + + "\">" + + alias4(((helper = (helper = helpers.linkShareLabel || (depth0 != null ? depth0.linkShareLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"linkShareLabel","hash":{},"data":data}) : helper))) + + "</span>\n \n <span class=\"sharingOptionsGroup\">\n <a href=\"#\" class=\"clipboard-button icon icon-clippy has-tooltip\" data-clipboard-text=\"" + + alias4(((helper = (helper = helpers.shareLinkURL || (depth0 != null ? depth0.shareLinkURL : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareLinkURL","hash":{},"data":data}) : helper))) + + "\" title=\"" + + alias4(((helper = (helper = helpers.copyLabel || (depth0 != null ? depth0.copyLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"copyLabel","hash":{},"data":data}) : helper))) + + "\"></a>\n <div class=\"share-menu\">\n <a href=\"#\" class=\"icon icon-more " + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPending : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\"></a>\n <span class=\"icon icon-loading-small " + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.showPending : depth0),{"name":"unless","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\"></span>\n" + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPending : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.program(8, data, 0),"data":data})) != null ? stack1 : "") + + " </div>\n </span>\n </li>\n"; +},"8":function(container,depth0,helpers,partials,data) { var stack1, helper; return " " + ((stack1 = ((helper = (helper = helpers.popoverMenu || (depth0 != null ? depth0.popoverMenu : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"popoverMenu","hash":{},"data":data}) : helper))) != null ? stack1 : "") + "\n"; -},"9":function(container,depth0,helpers,partials,data) { +},"10":function(container,depth0,helpers,partials,data) { var stack1; - return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.noSharingPlaceholder : depth0),{"name":"if","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.noSharingPlaceholder : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + "\n"; -},"10":function(container,depth0,helpers,partials,data) { +},"11":function(container,depth0,helpers,partials,data) { var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; return "<input id=\"shareWith-" + 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=\"shareWithField\" type=\"text\" placeholder=\"" + alias4(((helper = (helper = helpers.noSharingPlaceholder || (depth0 != null ? depth0.noSharingPlaceholder : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"noSharingPlaceholder","hash":{},"data":data}) : helper))) - + "\" disabled=\"disabled\"/>"; + + "\" disabled=\"disabled\" />"; },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { var stack1; - return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.shareAllowed : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(9, data, 0),"data":data})) != null ? stack1 : ""); + return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.shareAllowed : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(10, data, 0),"data":data})) != null ? stack1 : ""); },"useData":true}); templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(container,depth0,helpers,partials,data) { 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=\"hideDownload\" id=\"sharingDialogHideDownload-" - + 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 hideDownloadCheckbox\"\n " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hideDownload : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " />\n <label for=\"sharingDialogHideDownload-" - + 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.hideDownloadLabel || (depth0 != null ? depth0.hideDownloadLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"hideDownloadLabel","hash":{},"data":data}) : helper))) - + "</label>\n </span>\n </li>\n"; -},"2":function(container,depth0,helpers,partials,data) { - return "checked=\"checked\""; -},"4":function(container,depth0,helpers,partials,data) { - 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=\"radio\" name=\"publicUpload\" value=\"" + return " <li>\n <span class=\"menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"radio\" name=\"publicUpload\" value=\"" + alias4(((helper = (helper = helpers.publicUploadRValue || (depth0 != null ? depth0.publicUploadRValue : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadRValue","hash":{},"data":data}) : helper))) + "\" id=\"sharingDialogAllowPublicUpload-r-" + 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))) @@ -85,7 +86,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + 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.publicUploadRLabel || (depth0 != null ? depth0.publicUploadRLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadRLabel","hash":{},"data":data}) : helper))) - + "</label>\n </span>\n </li>\n <li>\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"radio\" name=\"publicUpload\" value=\"" + + "</label>\n </span>\n </li>\n <li>\n <span class=\"menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"radio\" name=\"publicUpload\" value=\"" + alias4(((helper = (helper = helpers.publicUploadRWValue || (depth0 != null ? depth0.publicUploadRWValue : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadRWValue","hash":{},"data":data}) : helper))) + "\" id=\"sharingDialogAllowPublicUpload-rw-" + 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))) @@ -95,7 +96,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + 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.publicUploadRWLabel || (depth0 != null ? depth0.publicUploadRWLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadRWLabel","hash":{},"data":data}) : helper))) - + "</label>\n </span></li>\n <li>\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"radio\" name=\"publicUpload\" value=\"" + + "</label>\n </span>\n </li>\n <li>\n <span class=\"menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"radio\" name=\"publicUpload\" value=\"" + alias4(((helper = (helper = helpers.publicUploadWValue || (depth0 != null ? depth0.publicUploadWValue : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadWValue","hash":{},"data":data}) : helper))) + "\" id=\"sharingDialogAllowPublicUpload-w-" + 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))) @@ -106,10 +107,10 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "\">" + alias4(((helper = (helper = helpers.publicUploadWLabel || (depth0 != null ? depth0.publicUploadWLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadWLabel","hash":{},"data":data}) : helper))) + "</label>\n </span>\n </li>\n"; -},"6":function(container,depth0,helpers,partials,data) { +},"3":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return " <li id=\"allowPublicEditingWrapper\">\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"checkbox\" name=\"allowPublicEditing\" id=\"sharingDialogAllowPublicEditing-" + return " <li id=\"allowPublicEditingWrapper\">\n <span class=\"menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"checkbox\" name=\"allowPublicEditing\" id=\"sharingDialogAllowPublicEditing-" + 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 publicEditingCheckbox\" " + ((stack1 = ((helper = (helper = helpers.publicEditingChecked || (depth0 != null ? depth0.publicEditingChecked : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicEditingChecked","hash":{},"data":data}) : helper))) != null ? stack1 : "") @@ -118,13 +119,27 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "\">" + alias4(((helper = (helper = helpers.publicEditingLabel || (depth0 != null ? depth0.publicEditingLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicEditingLabel","hash":{},"data":data}) : helper))) + "</label>\n </span>\n </li>\n"; +},"5":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return " <li>\n <span class=\"menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"checkbox\" name=\"hideDownload\" id=\"sharingDialogHideDownload-" + + 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 hideDownloadCheckbox\"\n " + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hideDownload : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + " />\n <label for=\"sharingDialogHideDownload-" + + 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.hideDownloadLabel || (depth0 != null ? depth0.hideDownloadLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"hideDownloadLabel","hash":{},"data":data}) : helper))) + + "</label>\n </span>\n </li>\n"; +},"6":function(container,depth0,helpers,partials,data) { + return "checked=\"checked\""; },"8":function(container,depth0,helpers,partials,data) { 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 <input type=\"checkbox\" name=\"showPassword\" id=\"showPassword-" + return " <li>\n <span class=\"menuitem\">\n <input type=\"checkbox\" name=\"showPassword\" id=\"showPassword-" + 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 showPasswordCheckbox\"\n " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " " + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordEnforced : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " value=\"1\" />\n <label for=\"showPassword-" @@ -133,7 +148,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + alias4(((helper = (helper = helpers.enablePasswordLabel || (depth0 != null ? depth0.enablePasswordLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"enablePasswordLabel","hash":{},"data":data}) : helper))) + "</label>\n </span>\n </li>\n <li class=\"" + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " linkPassMenu\">\n <span class=\"shareOption menuitem icon-share-pass\">\n <input id=\"linkPassText-" + + " linkPassMenu\">\n <span class=\"menuitem icon-share-pass\">\n <input id=\"linkPassText-" + 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=\"linkPassText\" type=\"password\" placeholder=\"" + alias4(((helper = (helper = helpers.passwordPlaceholder || (depth0 != null ? depth0.passwordPlaceholder : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"passwordPlaceholder","hash":{},"data":data}) : helper))) @@ -143,17 +158,21 @@ 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"; +},"15":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))); -},"15":function(container,depth0,helpers,partials,data) { +},"17":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))); -},"17":function(container,depth0,helpers,partials,data) { +},"19":function(container,depth0,helpers,partials,data) { + return "readonly"; +},"21":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=\"shareOption menuitem pop-up\" data-url=\"" + return " <li>\n <a href=\"#\" class=\"menuitem pop-up\" data-url=\"" + alias4(((helper = (helper = helpers.url || (depth0 != null ? depth0.url : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"url","hash":{},"data":data}) : helper))) + "\" data-window=\"" + alias4(((helper = (helper = helpers.newWindow || (depth0 != null ? depth0.newWindow : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"newWindow","hash":{},"data":data}) : helper))) @@ -165,26 +184,22 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return "<div class=\"popovermenu menu\">\n <ul>\n <li>\n <a href=\"#\" class=\"menuitem clipboardButton\" data-clipboard-text=\"" - + alias4(((helper = (helper = helpers.shareLinkURL || (depth0 != null ? depth0.shareLinkURL : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareLinkURL","hash":{},"data":data}) : helper))) - + "\">\n <span class=\"icon icon-clippy\" ></span>\n <span>" - + alias4(((helper = (helper = helpers.copyLabel || (depth0 != null ? depth0.copyLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"copyLabel","hash":{},"data":data}) : helper))) - + "</span>\n </a>\n </li>\n <li class=\"hidden linkTextMenu\">\n <span class=\"menuitem icon-link-text\">\n <input id=\"linkText-" + return "<div class=\"popovermenu menu\">\n <ul>\n <li class=\"hidden linkTextMenu\">\n <span class=\"menuitem icon-link-text\">\n <input id=\"linkText-" + 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=\"linkText\" type=\"text\" readonly=\"readonly\" value=\"" + alias4(((helper = (helper = helpers.shareLinkURL || (depth0 != null ? depth0.shareLinkURL : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareLinkURL","hash":{},"data":data}) : helper))) + "\" />\n </span>\n </li>\n" - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showHideDownloadCheckbox : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicUpload : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicEditing : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicUpload : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((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 : "") - + " <li>\n <span class=\"shareOption menuitem\">\n <input id=\"expireDate-" + + " <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 " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(6, 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(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "\" />\n <label for=\"expireDate-" + + " />\n <label for=\"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))) + "\">" + alias4(((helper = (helper = helpers.expireDateLabel || (depth0 != null ? depth0.expireDateLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expireDateLabel","hash":{},"data":data}) : helper))) @@ -198,40 +213,50 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + alias4(((helper = (helper = helpers.expirationDate || (depth0 != null ? depth0.expirationDate : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expirationDate","hash":{},"data":data}) : helper))) + "\">" + alias4(((helper = (helper = helpers.expirationLabel || (depth0 != null ? depth0.expirationLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expirationLabel","hash":{},"data":data}) : helper))) - + "</label>\n <input id=\"expirationDatePicker-" + + "</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=\"datepicker\" type=\"text\" placeholder=\"" + + "\" 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 : "") + + "\" 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(13, data, 0),"inverse":container.program(15, data, 0),"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>" + + ((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 : "") + + "\"\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 : "") + + " />\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\">\n </a>\n </li>\n <li class=\"share-note-form share-note-link hidden\">\n <span class=\"menuitem icon-note\">\n <textarea class=\"share-note\">" + + "</span>\n <input type=\"button\" class=\"share-note-delete icon-delete " + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasNote : depth0),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\">\n </a>\n </li>\n <li class=\"share-note-form share-note-link " + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasNote : depth0),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\">\n <span class=\"menuitem icon-note\">\n <textarea class=\"share-note\">" + alias4(((helper = (helper = helpers.shareNote || (depth0 != null ? depth0.shareNote : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareNote","hash":{},"data":data}) : helper))) + "</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(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " </ul>\n</div>\n"; + + "\" />\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 : "") + + " <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>" + + alias4(((helper = (helper = helpers.newShareLabel || (depth0 != null ? depth0.newShareLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"newShareLabel","hash":{},"data":data}) : helper))) + + "</span>\n </a>\n </li>\n </ul>\n</div>\n"; },"useData":true}); templates['sharedialoglinkshareview_popover_menu_pending'] = template({"1":function(container,depth0,helpers,partials,data) { var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return " <li><span class=\"shareOption menuitem\">\n <input type=\"checkbox\" name=\"showPassword\" id=\"showPassword-" - + 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))) - + "\" checked=\"checked\" disabled class=\"checkbox showPasswordCheckbox\" value=\"1\" />\n <label for=\"showPassword-" - + 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.enablePasswordLabel || (depth0 != null ? depth0.enablePasswordLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"enablePasswordLabel","hash":{},"data":data}) : helper))) - + "</label>\n </span></li>\n <li class=\"linkPassMenu\"><span class=\"shareOption menuitem icon-share-pass\">\n <input id=\"linkPassText-" - + 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=\"linkPassText\" type=\"password\" placeholder=\"" + return " <li>\n <span class=\"menuitem icon-info\">\n <p>" + + alias4(((helper = (helper = helpers.enforcedPasswordLabel || (depth0 != null ? depth0.enforcedPasswordLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"enforcedPasswordLabel","hash":{},"data":data}) : helper))) + + "</p>\n </span>\n </li>\n <li class=\"linkPassMenu\">\n <span class=\"menuitem\">\n <form autocomplete=\"off\" class=\"enforcedPassForm\">\n <input id=\"enforcedPassText\" required class=\"enforcedPassText\" type=\"password\"\n placeholder=\"" + alias4(((helper = (helper = helpers.passwordPlaceholder || (depth0 != null ? depth0.passwordPlaceholder : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"passwordPlaceholder","hash":{},"data":data}) : helper))) - + "\" autocomplete=\"new-password\" />\n <span class=\"icon icon-loading-small hidden\"></span>\n </span></li>\n"; + + "\" autocomplete=\"enforcedPassText\" minlength=\"" + + alias4(((helper = (helper = helpers.minPasswordLength || (depth0 != null ? depth0.minPasswordLength : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"minPasswordLength","hash":{},"data":data}) : helper))) + + "\" />\n <input type=\"submit\" value=\" \" class=\"primary icon-checkmark-white\">\n </form>\n </span>\n </li>\n"; },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { var stack1; - return "<div class=\"popovermenu pendingpopover menu\">\n <ul>\n" + return "<div class=\"popovermenu open menu\">\n <ul>\n" + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.isPasswordEnforced : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " </ul>\n</div>\n"; },"useData":true}); @@ -293,7 +318,7 @@ templates['sharedialogshareelistview'] = template({"1":function(container,depth0 },"6":function(container,depth0,helpers,partials,data) { var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return " <span class=\"shareOption\">\n <input id=\"canEdit-" + return " <span>\n <input id=\"canEdit-" + 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.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) @@ -343,7 +368,7 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con },"3":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return "\n <li>\n <span class=\"shareOption menuitem\">\n <input id=\"canShare-" + return "\n <li>\n <span class=\"menuitem\">\n <input id=\"canShare-" + 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.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) @@ -377,7 +402,7 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con },"8":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return "\n <li>\n <span class=\"shareOption menuitem\">\n <input id=\"canCreate-" + return "\n <li>\n <span class=\"menuitem\">\n <input id=\"canCreate-" + 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.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) @@ -399,7 +424,7 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con },"11":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return "\n <li>\n <span class=\"shareOption menuitem\">\n <input id=\"canUpdate-" + return "\n <li>\n <span class=\"menuitem\">\n <input id=\"canUpdate-" + 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.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) @@ -421,7 +446,7 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con },"14":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return "\n <li>\n <span class=\"shareOption menuitem\">\n <input id=\"canDelete-" + return "\n <li>\n <span class=\"menuitem\">\n <input id=\"canDelete-" + 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.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) @@ -440,7 +465,7 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasCreatePermission : depth0),{"name":"if","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " <li>\n <span class=\"shareOption menuitem\">\n <input id=\"password-" + + " <li>\n <span class=\"menuitem\">\n <input id=\"password-" + 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.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) @@ -484,7 +509,7 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con },"17":function(container,depth0,helpers,partials,data) { 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 <input id=\"secureDrop-" + return " <li>\n <span class=\"menuitem\">\n <input id=\"secureDrop-" + 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.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) @@ -510,7 +535,7 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con },"24":function(container,depth0,helpers,partials,data) { 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 <input id=\"passwordByTalk-" + return " <li>\n <span class=\"menuitem\">\n <input 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))) + "-" + 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))) @@ -558,11 +583,15 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con 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))); },"30":function(container,depth0,helpers,partials,data) { - var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; - return " <li>\n <a href=\"#\" class=\"share-add\"><span class=\"icon-loading-small hidden\"></span>\n <span class=\"icon icon-edit\"></span>\n <span>" + return " <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\">\n </a>\n </li>\n <li class=\"share-note-form hidden\">\n <span class=\"menuitem icon-note\">\n <textarea class=\"share-note\">" + + "</span>\n <input type=\"button\" class=\"share-note-delete icon-delete " + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasNote : depth0),{"name":"unless","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\">\n </a>\n </li>\n <li class=\"share-note-form " + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasNote : depth0),{"name":"unless","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + "\">\n <span class=\"menuitem icon-note\">\n <textarea class=\"share-note\">" + alias4(((helper = (helper = helpers.shareNote || (depth0 != null ? depth0.shareNote : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareNote","hash":{},"data":data}) : helper))) + "</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))) @@ -575,7 +604,7 @@ templates['sharedialogshareelistview_popover_menu'] = template({"1":function(con + "\n" + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isFolder : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isMailShare : depth0),{"name":"if","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " <li>\n <span class=\"shareOption menuitem\">\n <input id=\"expireDate-" + + " <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))) + "-" + 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))) diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js index f2fc2888448..45a0f04db00 100644 --- a/core/js/tests/specHelper.js +++ b/core/js/tests/specHelper.js @@ -103,6 +103,8 @@ window.oc_appconfig = { window.oc_defaults = { docPlaceholderUrl: 'https://docs.example.org/PLACEHOLDER' }; +window.oc_capabilities = { +} /* jshint camelcase: true */ diff --git a/core/js/tests/specs/sharedialoglinkshareview.js b/core/js/tests/specs/sharedialoglinkshareview.js index d8dec3968e3..f5fe8725c03 100644 --- a/core/js/tests/specs/sharedialoglinkshareview.js +++ b/core/js/tests/specs/sharedialoglinkshareview.js @@ -81,12 +81,12 @@ describe('OC.Share.ShareDialogLinkShareView', function () { // Needed to render the view configModel.isShareWithLinkAllowed.returns(true); - // Setting the share also triggers the rendering shareModel.set({ - linkShare: { - isLinkShare: true, - } + linkShares: [{ + id: 123 + }] }); + view.render(); $hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox'); $workingIcon = $hideDownloadCheckbox.prev('.icon-loading-small'); @@ -119,11 +119,12 @@ describe('OC.Share.ShareDialogLinkShareView', function () { it('checkbox is checked when the setting is enabled', function () { shareModel.set({ - linkShare: { - isLinkShare: true, + linkShares: [{ + id: 123, hideDownload: true - } + }] }); + view.render(); $hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox'); @@ -141,16 +142,17 @@ describe('OC.Share.ShareDialogLinkShareView', function () { $hideDownloadCheckbox.change(); expect($workingIcon.hasClass('hidden')).toBeFalsy(); - expect(shareModel.saveLinkShare.withArgs({ hideDownload: true }).calledOnce).toBeTruthy(); + expect(shareModel.saveLinkShare.withArgs({ hideDownload: true, cid: 123 }).calledOnce).toBeTruthy(); }); it('disables the setting if clicked when checked', function () { shareModel.set({ - linkShare: { - isLinkShare: true, + linkShares: [{ + id: 123, hideDownload: true - } + }] }); + view.render(); $hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox'); $workingIcon = $hideDownloadCheckbox.prev('.icon-loading-small'); @@ -161,7 +163,7 @@ describe('OC.Share.ShareDialogLinkShareView', function () { $hideDownloadCheckbox.change(); expect($workingIcon.hasClass('hidden')).toBeFalsy(); - expect(shareModel.saveLinkShare.withArgs({ hideDownload: false }).calledOnce).toBeTruthy(); + expect(shareModel.saveLinkShare.withArgs({ hideDownload: false, cid: 123 }).calledOnce).toBeTruthy(); }); }); @@ -176,13 +178,13 @@ describe('OC.Share.ShareDialogLinkShareView', function () { // Needed to render the view configModel.isShareWithLinkAllowed.returns(true); - // Setting the share also triggers the rendering shareModel.set({ - linkShare: { - isLinkShare: true, + linkShares: [{ + id: 123, password: 'password' - } + }] }); + view.render(); var $passwordDiv = view.$el.find('#linkPass'); $passwordText = view.$el.find('.linkPassText'); @@ -202,17 +204,17 @@ describe('OC.Share.ShareDialogLinkShareView', function () { }); it('shows the working icon when called', function () { - view.onPasswordEntered(); + view.onPasswordEntered({target: view.$el.find('.linkPassText')}); expect($workingIcon.hasClass('hidden')).toBeFalsy(); - expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy(); + expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword', cid: 123 }).calledOnce).toBeTruthy(); }); it('hides the working icon when saving the password succeeds', function () { - view.onPasswordEntered(); + view.onPasswordEntered({target: view.$el.find('.linkPassText')}); expect($workingIcon.hasClass('hidden')).toBeFalsy(); - expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy(); + expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword', cid: 123 }).calledOnce).toBeTruthy(); shareModel.saveLinkShare.yieldTo("complete", [shareModel]); @@ -220,10 +222,10 @@ describe('OC.Share.ShareDialogLinkShareView', function () { }); it('hides the working icon when saving the password fails', function () { - view.onPasswordEntered(); + view.onPasswordEntered({target: view.$el.find('.linkPassText')}); expect($workingIcon.hasClass('hidden')).toBeFalsy(); - expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy(); + expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword', cid: 123 }).calledOnce).toBeTruthy(); shareModel.saveLinkShare.yieldTo("complete", [shareModel]); shareModel.saveLinkShare.yieldTo("error", [shareModel, "The error message"]); diff --git a/core/js/tests/specs/sharedialogshareelistview.js b/core/js/tests/specs/sharedialogshareelistview.js index e51fc2df72d..4f84fa0e08f 100644 --- a/core/js/tests/specs/sharedialogshareelistview.js +++ b/core/js/tests/specs/sharedialogshareelistview.js @@ -73,7 +73,7 @@ describe('OC.Share.ShareDialogShareeListView', function () { $('#testArea').append(listView.$el); shareModel.set({ - linkShare: {isLinkShare: false} + linkShares: [] }); oldCurrentUser = OC.currentUser; diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js index efe50c415c8..8d5a2ae434d 100644 --- a/core/js/tests/specs/sharedialogviewSpec.js +++ b/core/js/tests/specs/sharedialogviewSpec.js @@ -89,7 +89,7 @@ describe('OC.Share.ShareDialogView', function() { // triggers rendering shareModel.set({ shares: [], - linkShare: {isLinkShare: false} + linkShares: [] }); autocompleteStub = sinon.stub($.fn, 'autocomplete').callsFake(function() { @@ -130,8 +130,10 @@ describe('OC.Share.ShareDialogView', function() { it('update password on focus out', function() { $('#allowShareWithLink').val('yes'); - dialog.model.set('linkShare', { - isLinkShare: true + dialog.model.set({ + linkShares: [{ + id: 123 + }] }); dialog.render(); @@ -143,20 +145,20 @@ describe('OC.Share.ShareDialogView', function() { expect(saveLinkShareStub.calledOnce).toEqual(true); expect(saveLinkShareStub.firstCall.args[0]).toEqual({ + cid: 123, password: 'foo' }); }); it('update password on enter', function() { $('#allowShareWithLink').val('yes'); - dialog.model.set('linkShare', { - isLinkShare: true + dialog.model.set({ + linkShares: [{ + id: 123 + }] }); dialog.render(); - // Toggle linkshare - dialog.$el.find('.linkCheckbox').click(); - // Enable password and enter password dialog.$el.find('[name=showPassword]').click(); dialog.$el.find('.linkPassText').focus(); @@ -165,47 +167,48 @@ describe('OC.Share.ShareDialogView', function() { expect(saveLinkShareStub.calledOnce).toEqual(true); expect(saveLinkShareStub.firstCall.args[0]).toEqual({ + cid: 123, password: 'foo' }); }); - it('shows share with link checkbox when allowed', function() { + it('shows add share with link button when allowed', function() { $('#allowShareWithLink').val('yes'); dialog.render(); - expect(dialog.$el.find('.linkCheckbox').length).toEqual(1); + expect(dialog.$el.find('.new-share').length).toEqual(1); }); - it('does not show share with link checkbox when not allowed', function() { + it('does not show add share with link button when not allowed', function() { $('#allowShareWithLink').val('no'); dialog.render(); - expect(dialog.$el.find('.linkCheckbox').length).toEqual(0); + expect(dialog.$el.find('.new-share').length).toEqual(0); expect(dialog.$el.find('.shareWithField').length).toEqual(1); }); it('shows populated link share when a link share exists', function() { // this is how the OC.Share class does it... var link = parent.location.protocol + '//' + location.host + - OC.generateUrl('/s/') + 'tehtoken'; - shareModel.set('linkShare', { - isLinkShare: true, - token: 'tehtoken', - link: link, - expiration: '', - permissions: OC.PERMISSION_READ, - stime: 1403884258, + OC.generateUrl('/s/') + 'thetoken'; + shareModel.set({ + linkShares: [{ + id: 123, + url: link + }] }); dialog.render(); - expect(dialog.$el.find('.linkCheckbox').prop('checked')).toEqual(true); + expect(dialog.$el.find('.share-menu .icon-more').length).toEqual(1); expect(dialog.$el.find('.linkText').val()).toEqual(link); }); it('autofocus link text when clicked', function() { $('#allowShareWithLink').val('yes'); - dialog.model.set('linkShare', { - isLinkShare: true + dialog.model.set({ + linkShares: [{ + id: 123 + }] }); dialog.render(); diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index a2eabbf4ae4..3b4dc5a960f 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -185,8 +185,9 @@ describe('OC.Share.ShareItemModel', function() { expect(shares[0].share_with).toEqual('user1'); expect(shares[0].share_with_displayname).toEqual('User One'); - var linkShare = model.get('linkShare'); - expect(linkShare.isLinkShare).toEqual(true); + var linkShares = model.get('linkShares'); + expect(linkShares.length).toEqual(1); + var linkShare = linkShares[0]; expect(linkShare.hideDownload).toEqual(true); // TODO: check more attributes @@ -268,8 +269,8 @@ describe('OC.Share.ShareItemModel', function() { // remaining share appears in this list expect(shares.length).toEqual(1); - var linkShare = model.get('linkShare'); - expect(linkShare.isLinkShare).toEqual(false); + var linkShares = model.get('linkShares'); + expect(linkShares.length).toEqual(0); }); it('parses correct link share when a nested link share exists along with parent one', function() { /* jshint camelcase: false */ @@ -321,8 +322,9 @@ describe('OC.Share.ShareItemModel', function() { // the parent share remains in the list expect(shares.length).toEqual(1); - var linkShare = model.get('linkShare'); - expect(linkShare.isLinkShare).toEqual(true); + var linkShares = model.get('linkShares'); + expect(linkShares.length).toEqual(1); + var linkShare = linkShares[0]; expect(linkShare.token).toEqual('tehtoken'); expect(linkShare.hideDownload).toEqual(false); @@ -575,9 +577,8 @@ describe('OC.Share.ShareItemModel', function() { it('creates a new share if no link share exists', function() { model.set({ - linkShare: { - isLinkShare: false - } + linkShares: [ + ] }); model.saveLinkShare(); @@ -600,9 +601,8 @@ describe('OC.Share.ShareItemModel', function() { defaultExpireDate: 7 }); model.set({ - linkShare: { - isLinkShare: false - } + linkShares: [ + ] }); model.saveLinkShare(); @@ -621,13 +621,13 @@ describe('OC.Share.ShareItemModel', function() { }); it('updates link share if it exists', function() { model.set({ - linkShare: { - isLinkShare: true, + linkShares: [{ id: 123 - } + }] }); model.saveLinkShare({ + cid: 123, password: 'test' }); @@ -635,20 +635,19 @@ describe('OC.Share.ShareItemModel', function() { expect(updateShareStub.calledOnce).toEqual(true); expect(updateShareStub.firstCall.args[0]).toEqual(123); expect(updateShareStub.firstCall.args[1]).toEqual({ + cid: 123, password: 'test' }); }); it('forwards error message on add', function() { var errorStub = sinon.stub(); model.set({ - linkShare: { - isLinkShare: false - } + linkShares: [ + ] }, { }); model.saveLinkShare({ - password: 'test' }, { error: errorStub }); @@ -661,14 +660,14 @@ describe('OC.Share.ShareItemModel', function() { it('forwards error message on update', function() { var errorStub = sinon.stub(); model.set({ - linkShare: { - isLinkShare: true, - id: '123' - } + linkShares: [{ + id: 123 + }] }, { }); model.saveLinkShare({ + cid: 123, password: 'test' }, { error: errorStub diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 8c6dc502487..5a571e7f5fc 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -671,6 +671,7 @@ return array( 'OC\\Core\\Migrations\\Version14000Date20180712153140' => $baseDir . '/core/Migrations/Version14000Date20180712153140.php', 'OC\\Core\\Migrations\\Version15000Date20180926101451' => $baseDir . '/core/Migrations/Version15000Date20180926101451.php', 'OC\\Core\\Migrations\\Version15000Date20181015062942' => $baseDir . '/core/Migrations/Version15000Date20181015062942.php', + 'OC\\Core\\Migrations\\Version15000Date20181029084625' => $baseDir . '/core/Migrations/Version15000Date20181029084625.php', 'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php', 'OC\\DB\\AdapterMySQL' => $baseDir . '/lib/private/DB/AdapterMySQL.php', 'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 2a46e99e020..6518569da04 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -701,6 +701,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Migrations\\Version14000Date20180712153140' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180712153140.php', 'OC\\Core\\Migrations\\Version15000Date20180926101451' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20180926101451.php', 'OC\\Core\\Migrations\\Version15000Date20181015062942' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20181015062942.php', + 'OC\\Core\\Migrations\\Version15000Date20181029084625' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20181029084625.php', 'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php', 'OC\\DB\\AdapterMySQL' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterMySQL.php', 'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php', diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 3dcca0facbc..50111054546 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -146,6 +146,8 @@ class DefaultShareProvider implements IShareProvider { //Set the GID of the group we share with $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith())); } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { + //set label for public link + $qb->setValue('label', $qb->createNamedParameter($share->getLabel())); //Set the token of the share $qb->setValue('token', $qb->createNamedParameter($share->getToken())); @@ -225,6 +227,9 @@ class DefaultShareProvider implements IShareProvider { * * @param \OCP\Share\IShare $share * @return \OCP\Share\IShare The share object + * @throws ShareNotFound + * @throws \OCP\Files\InvalidPathException + * @throws \OCP\Files\NotFoundException */ public function update(\OCP\Share\IShare $share) { @@ -296,6 +301,7 @@ class DefaultShareProvider implements IShareProvider { ->set('token', $qb->createNamedParameter($share->getToken())) ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) ->set('note', $qb->createNamedParameter($share->getNote())) + ->set('label', $qb->createNamedParameter($share->getLabel())) ->set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0), IQueryBuilder::PARAM_INT) ->execute(); } @@ -919,7 +925,8 @@ class DefaultShareProvider implements IShareProvider { ->setPermissions((int)$data['permissions']) ->setTarget($data['file_target']) ->setNote($data['note']) - ->setMailSend((bool)$data['mail_send']); + ->setMailSend((bool)$data['mail_send']) + ->setLabel($data['label']); $shareTime = new \DateTime(); $shareTime->setTimestamp((int)$data['stime']); diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index e218360f87b..f9b548c1adf 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -76,6 +76,8 @@ class Share implements \OCP\Share\IShare { private $shareTime; /** @var bool */ private $mailSend; + /** @var string */ + private $label = ''; /** @var IRootFolder */ private $rootFolder; @@ -337,6 +339,21 @@ class Share implements \OCP\Share\IShare { /** * @inheritdoc */ + public function setLabel($label) { + $this->label = $label; + return $this; + } + + /** + * @inheritdoc + */ + public function getLabel() { + return $this->label; + } + + /** + * @inheritdoc + */ public function setExpirationDate($expireDate) { //TODO checks diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php index dcd5fdecbea..7bcecbdc872 100644 --- a/lib/public/Share/IShare.php +++ b/lib/public/Share/IShare.php @@ -258,6 +258,23 @@ interface IShare { public function getExpirationDate(); /** + * set a label for a share, some shares, e.g. public links can have a label + * + * @param string $label + * @return \OCP\Share\IShare The modified object + * @since 15.0.0 + */ + public function setLabel($label); + + /** + * get label for the share, some shares, e.g. public links can have a label + * + * @return string + * @since 15.0.0 + */ + public function getLabel(); + + /** * Set the sharer of the path. * * @param string $sharedBy diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 4b648bfc544..459028813b5 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -188,7 +188,7 @@ class FilesAppContext implements Context, ActorAwareInterface { * @return Locator */ public static function shareLinkRow() { - return Locator::forThe()->id("shareLink")-> + return Locator::forThe()->css(".linkShareView .shareWithList:first-child")-> descendantOf(self::detailsView())-> describedAs("Share link row in the details view in Files app"); } @@ -196,13 +196,21 @@ class FilesAppContext implements Context, ActorAwareInterface { /** * @return Locator */ - public static function shareLinkCheckbox() { - // forThe()->checkbox("Enable") can not be used here; that would return - // the checkbox itself, but the element that the user interacts with is - // the label. - return Locator::forThe()->xpath("//label[normalize-space() = 'Enable']")-> + public static function shareLinkAddNewButton() { + // When there is no link share the "Add new share" item is shown instead + // of the menu button as a direct child of ".share-menu". + return Locator::forThe()->css(".share-menu > .new-share")-> descendantOf(self::shareLinkRow())-> - describedAs("Share link checkbox in the details view in Files app"); + describedAs("Add new share link button in the details view in Files app"); + } + + /** + * @return Locator + */ + public static function copyLinkButton() { + return Locator::forThe()->css("a.clipboard-button")-> + descendantOf(self::shareLinkRow())-> + describedAs("Copy link button in the details view in Files app"); } /** @@ -226,15 +234,6 @@ class FilesAppContext implements Context, ActorAwareInterface { /** * @return Locator */ - public static function copyUrlMenuItem() { - return Locator::forThe()->xpath("//a[normalize-space() = 'Copy link']")-> - descendantOf(self::shareLinkMenu())-> - describedAs("Copy link menu item in the share link menu in the details view in Files app"); - } - - /** - * @return Locator - */ public static function hideDownloadCheckbox() { // forThe()->checkbox("Hide download") can not be used here; that would // return the checkbox itself, but the element that the user interacts @@ -320,21 +319,19 @@ class FilesAppContext implements Context, ActorAwareInterface { public function iShareTheLinkFor($fileName) { $this->actor->find(FileListContext::shareActionForFile(self::currentSectionMainView(), $fileName), 10)->click(); - $this->actor->find(self::shareLinkCheckbox(), 5)->click(); + $this->actor->find(self::shareLinkAddNewButton(), 5)->click(); } /** * @Given I write down the shared link */ public function iWriteDownTheSharedLink() { - $this->showShareLinkMenuIfNeeded(); - - $this->actor->find(self::copyUrlMenuItem(), 2)->click(); + $this->actor->find(self::copyLinkButton(), 10)->click(); // Clicking on the menu item copies the link to the clipboard, but it is // not possible to access that value from the acceptance tests. Due to // this the value of the attribute that holds the URL is used instead. - $this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::copyUrlMenuItem(), 2)->getWrappedElement()->getAttribute("data-clipboard-text"); + $this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::copyLinkButton(), 2)->getWrappedElement()->getAttribute("data-clipboard-text"); } /** diff --git a/version.php b/version.php index 93a8b5fd53e..235e18bf61d 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = array(15, 0, 0, 2); +$OC_Version = array(15, 0, 0, 3); // The human readable string $OC_VersionString = '15.0.0 alpha'; |