aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoeland Jago Douma <rullzer@users.noreply.github.com>2018-07-24 09:12:03 +0200
committerGitHub <noreply@github.com>2018-07-24 09:12:03 +0200
commitb41d0d32e2183b8b241d3764f06a3b0f7d6b86c1 (patch)
tree915e14ceafed060f95255e8d2d7d4ad3ab3df7b5
parent241e5705cb85d24a9c3479c133fcc4d9c069d8c7 (diff)
parentfee62fd20b01b08ffbc7120720ba3bad0a89be73 (diff)
downloadnextcloud-server-b41d0d32e2183b8b241d3764f06a3b0f7d6b86c1.tar.gz
nextcloud-server-b41d0d32e2183b8b241d3764f06a3b0f7d6b86c1.zip
Merge pull request #10218 from nextcloud/share-comments
allow to add a personal note to a share
-rw-r--r--.gitignore1
-rw-r--r--apps/files/css/detailsView.scss6
-rw-r--r--apps/files/css/files.scss3
-rw-r--r--apps/files_sharing/css/public.scss5
-rw-r--r--apps/files_sharing/css/sharetabview.scss367
-rw-r--r--apps/files_sharing/lib/Controller/ShareAPIController.php15
-rw-r--r--apps/files_sharing/lib/Controller/ShareController.php1
-rw-r--r--apps/files_sharing/templates/public.php6
-rw-r--r--apps/files_sharing/tests/Controller/ShareAPIControllerTest.php44
-rw-r--r--apps/files_sharing/tests/Controller/ShareControllerTest.php5
-rw-r--r--apps/sharebymail/lib/ShareByMailProvider.php61
-rw-r--r--apps/sharebymail/tests/ShareByMailProviderTest.php18
-rw-r--r--core/Migrations/Version14000Date20180712153140.php43
-rw-r--r--core/css/apps.scss38
-rw-r--r--core/css/ie.scss11
-rw-r--r--core/css/share.scss204
-rw-r--r--core/css/styles.scss1
-rw-r--r--core/img/actions/public-white.svg1
-rw-r--r--core/js/apps.js12
-rw-r--r--core/js/core.json1
-rw-r--r--core/js/merged-share-backend.json17
-rw-r--r--core/js/sharedialogexpirationview.js209
-rw-r--r--core/js/sharedialoglinkshareview.js408
-rw-r--r--core/js/sharedialogresharerinfoview.js12
-rw-r--r--core/js/sharedialogshareelistview.js148
-rw-r--r--core/js/sharedialogview.js8
-rw-r--r--core/js/shareitemmodel.js20
-rw-r--r--core/js/tests/specs/sharedialoglinkshareview.js2
-rw-r--r--core/js/tests/specs/sharedialogviewSpec.js186
-rw-r--r--lib/composer/composer/autoload_classmap.php1
-rw-r--r--lib/composer/composer/autoload_static.php1
-rw-r--r--lib/private/Share/Share.php1
-rw-r--r--lib/private/Share20/DefaultShareProvider.php137
-rw-r--r--lib/private/Share20/ProviderFactory.php6
-rw-r--r--lib/private/Share20/Share.php20
-rw-r--r--lib/private/TemplateLayout.php4
-rw-r--r--lib/private/legacy/util.php10
-rw-r--r--lib/public/Share/IShare.php18
-rw-r--r--lib/public/Util.php10
-rw-r--r--tests/acceptance/features/bootstrap/FilesAppContext.php90
-rw-r--r--tests/lib/Share20/DefaultShareProviderTest.php52
-rw-r--r--version.php2
42 files changed, 1252 insertions, 953 deletions
diff --git a/.gitignore b/.gitignore
index c7b6b396635..a11e3a14597 100644
--- a/.gitignore
+++ b/.gitignore
@@ -121,6 +121,7 @@ nbproject
/build/jsdocs/
/npm-debug.log
/PhantomJS_*
+/build/package-lock.json
# puphpet
puphpet
diff --git a/apps/files/css/detailsView.scss b/apps/files/css/detailsView.scss
index e0c1bbfa095..f64a3702850 100644
--- a/apps/files/css/detailsView.scss
+++ b/apps/files/css/detailsView.scss
@@ -7,12 +7,6 @@
clear: both;
}
-#app-sidebar .mainFileInfoView {
- margin-right: 20px; /* accommodate for close icon */
- float:left;
- display:block;
- width: 100%;
-}
#app-sidebar .mainFileInfoView .icon {
display: inline-block;
diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss
index 2a71af038cf..017253fdf8e 100644
--- a/apps/files/css/files.scss
+++ b/apps/files/css/files.scss
@@ -94,7 +94,8 @@
@include icon-color('star-dark', 'files', $color-black, 2, true);
}
.nav-icon-sharingin,
-.nav-icon-sharingout {
+.nav-icon-sharingout,
+.nav-icon-shareoverview {
@include icon-color('share', 'files', $color-black);
}
.nav-icon-sharinglinks {
diff --git a/apps/files_sharing/css/public.scss b/apps/files_sharing/css/public.scss
index 2e788a06c40..583912ad236 100644
--- a/apps/files_sharing/css/public.scss
+++ b/apps/files_sharing/css/public.scss
@@ -169,3 +169,8 @@ thead {
opacity: .57;
margin-top: 10px;
}
+
+#note {
+ text-align: center;
+ padding: 10px;
+}
diff --git a/apps/files_sharing/css/sharetabview.scss b/apps/files_sharing/css/sharetabview.scss
index b4b64daff2b..83790c9ec4f 100644
--- a/apps/files_sharing/css/sharetabview.scss
+++ b/apps/files_sharing/css/sharetabview.scss
@@ -2,143 +2,242 @@
min-height: 100px;
}
-.shareTabView .oneline {
- white-space: nowrap;
- position: relative;
-}
-
-.shareTabView .shareWithLoading {
- padding-left: 10px;
- right: 35px;
- top: 0px;
-}
-
-.shareTabView .shareWithConfirm,
-.shareTabView .clipboardButton,
-.shareTabView .linkPass .icon-loading-small {
- position: absolute;
- right: -7px;
- top: -2px;
- padding: 14px;
-}
-
-.shareTabView .shareWithConfirm {
- opacity: .5;
-}
-
-.shareTabView .shareWithField:focus ~ .shareWithConfirm {
- opacity: 1;
-}
-
-.shareTabView .linkMore {
- position: absolute;
- right: -7px;
- top: -4px;
- padding: 14px;
-}
-
-/* fix the popup menu because the button is shifted and then the menu is not aligned */
-.shareTabView .popovermenu.socialSharingMenu {
- right: -7px;
-}
-
-.shareTabView .popovermenu .clipboardButton {
- position: relative;
- top: initial;
- right: initial;
- padding: 0;
-}
-
-.shareTabView label {
- white-space: nowrap;
-}
-
-.shareTabView input[type="checkbox"] {
- margin: 0 3px 0 8px;
- vertical-align: middle;
-}
-
-.shareTabView input[type="text"].shareWithField,
-.shareTabView input[type="text"].emailField,
-.shareTabView input[type="text"].linkText,
-.shareTabView input[type="password"] {
- width: 100%;
- box-sizing: border-box;
- padding-right: 32px;
- text-overflow: ellipsis;
-}
-
-.shareTabView form {
- font-size: 100%;
- margin-left: 0;
- margin-right: 0;
-}
-
-#shareWithList {
+.share-autocomplete-item {
+ display: flex;
+ .autocomplete-item-text {
+ margin-left: 10px;
+ margin-right: 10px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ line-height: 32px;
+ vertical-align: middle;
+ }
+}
+
+.shareTabView {
+ .oneline {
+ white-space: nowrap;
+ position: relative;
+ }
+ .shareWithLoading {
+ padding-left: 10px;
+ right: 35px;
+ top: 0px;
+ }
+ .shareWithConfirm,
+ .clipboardButton,
+ .linkPass .icon-loading-small {
+ position: absolute;
+ right: 2px;
+ top: 6px;
+ padding: 14px;
+ }
+ .shareWithConfirm {
+ opacity: 0.5;
+ }
+ .shareWithField:focus ~ .shareWithConfirm {
+ opacity: 1;
+ }
+ .linkMore {
+ position: absolute;
+ right: -7px;
+ top: -4px;
+ padding: 14px;
+ }
+ .popovermenu {
+ .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;
+ }
+ }
+ // note
+ .share-note-form {
+ span.icon-note {
+ position: relative;
+ }
+ textarea.share-note {
+ margin: 0;
+ width: 200px;
+ min-height: 70px;
+ resize: none;
+ + input.share-note-submit {
+ position: absolute;
+ width: 44px !important;
+ height: 44px;
+ bottom: 0px;
+ right: 10px;
+ margin: 0;
+ background-color: transparent;
+ border: none;
+ opacity: .7;
+ &:hover,
+ &:focus,
+ &:active {
+ opacity: 1;
+ }
+ }
+ }
+ // fix for popover link share
+ &.share-note-link {
+ margin-bottom: 10px;
+ }
+ }
+ }
+ .linkPass .icon-loading-small {
+ margin-right: 0px;
+ }
+ .icon {
+ background-size: 16px 16px;
+ }
+ .shareWithList .icon-loading-small:not(.hidden) + span,
+ .linkShareView .icon-loading-small:not(.hidden) + input + label:before {
+ /* Hide if loader is visible */
+ display: none !important;
+ }
+ input {
+ &[type='checkbox'] {
+ margin: 0 3px 0 8px;
+ vertical-align: middle;
+ }
+ &[type='text'] {
+ &.shareWithField,
+ &.emailField {
+ width: 100%;
+ box-sizing: border-box;
+ padding-right: 32px;
+ text-overflow: ellipsis;
+ }
+ }
+ &[type='text'].linkText
+ &[type='password'].linkPassText,
+ &[type='password'].passwordField {
+ width: 180px !important;
+ }
+ }
+ form {
+ font-size: 100%;
+ margin-left: 0;
+ margin-right: 0;
+ }
+ // share note on the sidebar
+ .share-note {
+ border-radius: var(--border-radius);
+ margin-bottom: 10px;
+ margin-left: 37px;
+ }
+}
+
+// Sharing tab users list
+.shareWithList {
list-style-type: none;
- padding: 0 0 16px;
-}
-
-#shareWithList > li {
- padding-top: 5px;
- padding-bottom: 5px;
- white-space: normal;
+ display: flex;
+ flex-direction: column;
+ > li {
+ height: 44px;
+ white-space: normal;
+ display: inline-flex;
+ align-items: center;
+ position: relative;
+ .avatar {
+ width: 32px;
+ height: 32px;
+ background-color: var(--color-background-darker);
+ }
+ }
+ .unshare img {
+ vertical-align: text-bottom;
+ /* properly align icons */
+ }
+ .sharingOptionsGroup {
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ // can edit label
+ > .shareOption > label {
+ padding: 13px;
+ padding-right: 0;
+ }
+ // more menu
+ > .share-menu {
+ position: relative;
+ display: block;
+ .icon-more {
+ padding: 14px;
+ height: 16px;
+ width: 16px;
+ opacity: .5;
+ display: block;
+ cursor: pointer;
+ }
+ &:hover,
+ &:focus,
+ &:active {
+ .icon-more {
+ opacity: .7;;
+ }
+ }
+ }
+ }
+ .username {
+ padding: 0 8px;
+ }
+}
+
+.ui-autocomplete {
+ /* limit dropdown height to 4 1/2 entries */
+ max-height: 200px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ z-index: 1550 !important;
+}
+
+.notCreatable {
+ padding-left: 12px;
+ padding-top: 12px;
+ color: var(--color-text-lighter);
+}
+
+.contactsmenu-popover {
+ left: -6px;
+ right: auto;
+ padding: 3px 6px;
+ top: 100%;
+ margin-top: 0;
+ li.hidden {
+ display: none !important;
+ }
+ &:after {
+ left: 8px;
+ right: auto;
+ }
+}
+
+.reshare,
+#link label,
+#expiration label {
display: inline-flex;
align-items: center;
+ .avatar {
+ margin-right: 5px;
+ }
}
-#shareWithList .unshare img {
- vertical-align: text-bottom; /* properly align icons */
-}
-
-#shareWithList .sharingOptionsGroup > a .icon {
- padding: 7px;
- vertical-align: middle;
- opacity: .5;
-}
-
-#shareWithList .sharingOptionsGroup .popovermenu:after {
- right: 3px;
-}
-
-#shareWithList label input[type=checkbox] {
- margin-left: 0;
+.resharerInfoView.subView {
position: relative;
-}
-#shareWithList .username {
- padding-right: 8px;
- white-space: nowrap;
- text-overflow: ellipsis;
- display: inline-block;
- overflow: hidden;
- vertical-align: middle;
-}
-#shareWithList li .sharingOptionsGroup > .shareOption > label {
- padding: 6px;
- margin-right: 8px;
- vertical-align: text-top;
-}
-
-.shareTabView .icon-loading-small {
- display: inline-block;
- z-index: 1;
- vertical-align: text-top;
-}
-
-.shareTabView .shareWithList .icon-loading-small:not(.hidden) + span,
-.shareTabView .linkShareView .icon-loading-small:not(.hidden) + input + label:before {
- /* Hide if loader is visible */
- display: none !important;
-}
-
-.linkShareView {
- margin-top: 16px;
-}
-
-.shareTabView .linkPass .icon-loading-small {
- margin-right: 0px;
-}
-
-.shareTabView .icon {
- background-size: 16px 16px;
-}
+} \ No newline at end of file
diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php
index d30d5a05a22..33782d21b5f 100644
--- a/apps/files_sharing/lib/Controller/ShareAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareAPIController.php
@@ -144,6 +144,7 @@ class ShareAPIController extends OCSController {
'expiration' => null,
'token' => null,
'uid_file_owner' => $share->getShareOwner(),
+ 'note' => $share->getNote(),
'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(),
];
@@ -698,17 +699,21 @@ class ShareAPIController extends OCSController {
* @param string $password
* @param string $publicUpload
* @param string $expireDate
+ * @param string $note
* @return DataResponse
- * @throws OCSNotFoundException
+ * @throws LockedException
+ * @throws NotFoundException
* @throws OCSBadRequestException
* @throws OCSForbiddenException
+ * @throws OCSNotFoundException
*/
public function updateShare(
string $id,
int $permissions = null,
string $password = null,
string $publicUpload = null,
- string $expireDate = null
+ string $expireDate = null,
+ string $note = null
): DataResponse {
try {
$share = $this->getShareById($id);
@@ -722,10 +727,14 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist'));
}
- if ($permissions === null && $password === null && $publicUpload === null && $expireDate === null) {
+ if ($permissions === null && $password === null && $publicUpload === null && $expireDate === null && $note === null) {
throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
}
+ if($note !== null) {
+ $share->setNote($note);
+ }
+
/*
* expirationdate, password and publicUpload only make sense for link shares
*/
diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php
index 0b30a599c7f..bd1331a0908 100644
--- a/apps/files_sharing/lib/Controller/ShareController.php
+++ b/apps/files_sharing/lib/Controller/ShareController.php
@@ -262,6 +262,7 @@ class ShareController extends AuthPublicShareController {
$shareTmpl['owner'] = $share->getShareOwner();
$shareTmpl['filename'] = $share->getNode()->getName();
$shareTmpl['directory_path'] = $share->getTarget();
+ $shareTmpl['note'] = $share->getNote();
$shareTmpl['mimetype'] = $share->getNode()->getMimetype();
$shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($share->getNode()->getMimetype());
$shareTmpl['dirToken'] = $this->getToken();
diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php
index 476f0851547..81729c179fc 100644
--- a/apps/files_sharing/templates/public.php
+++ b/apps/files_sharing/templates/public.php
@@ -29,6 +29,12 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
<input type="hidden" name="filesize" value="<?php p($_['nonHumanFileSize']); ?>" id="filesize">
<?php endif; ?>
<input type="hidden" name="maxSizeAnimateGif" value="<?php p($_['maxSizeAnimateGif']); ?>" id="maxSizeAnimateGif">
+<?php if (isset($_['note']) && $_['note'] !== '') : ?>
+ <div id="note">
+ <?php p($l->t('Note:')); p(' ' . $_['note']); ?>
+ </div>
+<?php endif; ?>
+
<?php if (!isset($_['hideFileList']) || (isset($_['hideFileList']) && $_['hideFileList'] === false)) { ?>
<div id="files-public-content">
<div id="preview">
diff --git a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php
index 5d376f2d4f7..30041c3a27b 100644
--- a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php
+++ b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php
@@ -238,7 +238,7 @@ class ShareAPIControllerTest extends TestCase {
*/
public function createShare($id, $shareType, $sharedWith, $sharedBy, $shareOwner, $path, $permissions,
- $shareTime, $expiration, $parent, $target, $mail_send, $token=null,
+ $shareTime, $expiration, $parent, $target, $mail_send, $note = '', $token=null,
$password=null) {
$share = $this->getMockBuilder(IShare::class)->getMock();
$share->method('getId')->willReturn($id);
@@ -248,6 +248,7 @@ class ShareAPIControllerTest extends TestCase {
$share->method('getShareOwner')->willReturn($shareOwner);
$share->method('getNode')->willReturn($path);
$share->method('getPermissions')->willReturn($permissions);
+ $share->method('getNote')->willReturn($note);
$time = new \DateTime();
$time->setTimestamp($shareTime);
$share->method('getShareTime')->willReturn($time);
@@ -310,7 +311,8 @@ class ShareAPIControllerTest extends TestCase {
null,
6,
'target',
- 0
+ 0,
+ 'personal note'
);
$expected = [
'id' => 100,
@@ -334,6 +336,7 @@ class ShareAPIControllerTest extends TestCase {
'storage' => 101,
'mail_send' => 0,
'uid_file_owner' => 'ownerId',
+ 'note' => 'personal note',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myMimeType',
];
@@ -352,7 +355,8 @@ class ShareAPIControllerTest extends TestCase {
null,
6,
'target',
- 0
+ 0,
+ 'personal note'
);
$expected = [
'id' => 101,
@@ -376,6 +380,7 @@ class ShareAPIControllerTest extends TestCase {
'storage' => 101,
'mail_send' => 0,
'uid_file_owner' => 'ownerId',
+ 'note' => 'personal note',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myFolderMimeType',
];
@@ -396,6 +401,7 @@ class ShareAPIControllerTest extends TestCase {
6,
'target',
0,
+ 'personal note',
'token',
'password'
);
@@ -422,6 +428,7 @@ class ShareAPIControllerTest extends TestCase {
'mail_send' => 0,
'url' => 'url',
'uid_file_owner' => 'ownerId',
+ 'note' => 'personal note',
'displayname_file_owner' => 'ownerDisplay',
'mimetype' => 'myFolderMimeType',
];
@@ -455,7 +462,7 @@ class ShareAPIControllerTest extends TestCase {
->willReturn(true);
$this->shareManager
- ->expects($this->once())
+ ->expects($this->any())
->method('getShareById')
->with($share->getFullId(), 'currentUser')
->willReturn($share);
@@ -501,6 +508,8 @@ class ShareAPIControllerTest extends TestCase {
['group', $group],
]));
+ $d = $ocs->getShare($share->getId())->getData()[0];
+
$this->assertEquals($result, $ocs->getShare($share->getId())->getData()[0]);
}
@@ -1810,9 +1819,10 @@ class ShareAPIControllerTest extends TestCase {
->setNode($file)
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
->setTarget('myTarget')
+ ->setNote('personal note')
->setId(42);
- /* User backend down */
+ // User backend down
$result[] = [
[
'id' => 42,
@@ -1836,12 +1846,12 @@ class ShareAPIControllerTest extends TestCase {
'file_target' => 'myTarget',
'share_with' => 'recipient',
'share_with_displayname' => 'recipient',
+ 'note' => 'personal note',
'mail_send' => 0,
'mimetype' => 'myMimeType',
], $share, [], false
];
-
- /* User backend up */
+ // User backend up
$result[] = [
[
'id' => 42,
@@ -1855,6 +1865,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'ownerDN',
+ 'note' => 'personal note',
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -1883,9 +1894,9 @@ class ShareAPIControllerTest extends TestCase {
->setNode($file)
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
->setTarget('myTarget')
+ ->setNote('personal note')
->setId(42);
-
- /* User backend down */
+ // User backend down
$result[] = [
[
'id' => 42,
@@ -1899,6 +1910,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => 'personal note',
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -1915,6 +1927,7 @@ class ShareAPIControllerTest extends TestCase {
];
// with existing group
+
$share = \OC::$server->getShareManager()->newShare();
$share->setShareType(\OCP\Share::SHARE_TYPE_GROUP)
->setSharedWith('recipientGroup')
@@ -1924,6 +1937,7 @@ class ShareAPIControllerTest extends TestCase {
->setNode($file)
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
->setTarget('myTarget')
+ ->setNote('personal note')
->setId(42);
$result[] = [
@@ -1939,6 +1953,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => 'personal note',
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -1964,6 +1979,7 @@ class ShareAPIControllerTest extends TestCase {
->setNode($file)
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
->setTarget('myTarget')
+ ->setNote('personal note')
->setId(42);
$result[] = [
[
@@ -1978,6 +1994,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => 'personal note',
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -2004,6 +2021,7 @@ class ShareAPIControllerTest extends TestCase {
->setPassword('mypassword')
->setExpirationDate(new \DateTime('2001-01-02T00:00:00'))
->setToken('myToken')
+ ->setNote('personal note')
->setId(42);
$result[] = [
@@ -2019,6 +2037,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => 'myToken',
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => 'personal note',
'path' => 'file',
'item_type' => 'file',
'storage_id' => 'storageId',
@@ -2044,6 +2063,7 @@ class ShareAPIControllerTest extends TestCase {
->setNode($folder)
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
->setTarget('myTarget')
+ ->setNote('personal note')
->setId(42);
$result[] = [
@@ -2059,6 +2079,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => 'personal note',
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2101,6 +2122,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => '',
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2142,6 +2164,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => '',
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2183,6 +2206,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => '',
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
@@ -2207,6 +2231,7 @@ class ShareAPIControllerTest extends TestCase {
->setPermissions(\OCP\Constants::PERMISSION_READ)
->setShareTime(new \DateTime('2000-01-01T00:01:02'))
->setTarget('myTarget')
+ ->setNote('personal note')
->setId(42);
$result[] = [
@@ -2238,6 +2263,7 @@ class ShareAPIControllerTest extends TestCase {
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
+ 'note' => '',
'path' => 'folder',
'item_type' => 'folder',
'storage_id' => 'storageId',
diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php
index fb417878647..a01560d0288 100644
--- a/apps/files_sharing/tests/Controller/ShareControllerTest.php
+++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php
@@ -192,6 +192,9 @@ class ShareControllerTest extends \Test\TestCase {
public function testShowShare() {
+
+ $note = 'personal note';
+
$this->shareController->setToken('token');
$owner = $this->getMockBuilder(IUser::class)->getMock();
@@ -210,6 +213,7 @@ class ShareControllerTest extends \Test\TestCase {
$share->setPassword('password')
->setShareOwner('ownerUID')
->setNode($file)
+ ->setNote($note)
->setTarget('/file1.txt');
$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
@@ -283,6 +287,7 @@ class ShareControllerTest extends \Test\TestCase {
'shareUrl' => null,
'previewImage' => null,
'previewURL' => 'downloadURL',
+ 'note' => $note
);
$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
diff --git a/apps/sharebymail/lib/ShareByMailProvider.php b/apps/sharebymail/lib/ShareByMailProvider.php
index 1a1855b9c44..73e962e3292 100644
--- a/apps/sharebymail/lib/ShareByMailProvider.php
+++ b/apps/sharebymail/lib/ShareByMailProvider.php
@@ -506,6 +506,61 @@ class ShareByMailProvider implements IShareProvider {
return true;
}
+ protected function sendNote(IShare $share) {
+
+ $recipient = $share->getSharedWith();
+
+
+ $filename = $share->getNode()->getName();
+ $initiator = $share->getSharedBy();
+ $note = $share->getNote();
+
+ $initiatorUser = $this->userManager->get($initiator);
+ $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
+ $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
+
+ $plainHeading = $this->l->t('%1$s shared »%2$s« with you and wants to add:', [$initiatorDisplayName, $filename]);
+ $htmlHeading = $this->l->t('%1$s shared »%2$s« with you and wants to add', [$initiatorDisplayName, $filename]);
+
+ $message = $this->mailer->createMessage();
+
+ $emailTemplate = $this->mailer->createEMailTemplate('shareByMail.sendNote');
+
+ $emailTemplate->setSubject($this->l->t('»%s« added a note to a file shared with you', [$initiatorDisplayName]));
+ $emailTemplate->addHeader();
+ $emailTemplate->addHeading(htmlspecialchars($htmlHeading), $plainHeading);
+ $emailTemplate->addBodyText(htmlspecialchars($note), $note);
+
+ $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
+ ['token' => $share->getToken()]);
+ $emailTemplate->addBodyButton(
+ $this->l->t('Open »%s«', [$filename]),
+ $link
+ );
+
+ // The "From" contains the sharers name
+ $instanceName = $this->defaults->getName();
+ $senderName = $this->l->t(
+ '%1$s via %2$s',
+ [
+ $initiatorDisplayName,
+ $instanceName
+ ]
+ );
+ $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
+ if ($initiatorEmailAddress !== null) {
+ $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
+ $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
+ } else {
+ $emailTemplate->addFooter();
+ }
+
+ $message->setTo([$recipient]);
+ $message->useTemplate($emailTemplate);
+ $this->mailer->send($message);
+
+ }
+
/**
* send auto generated password to the owner. This happens if the admin enforces
* a password for mail shares and forbid to send the password by mail to the recipient
@@ -662,8 +717,13 @@ class ShareByMailProvider implements IShareProvider {
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
->set('password', $qb->createNamedParameter($share->getPassword()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
+ ->set('note', $qb->createNamedParameter($share->getNote()))
->execute();
+ if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
+ $this->sendNote($share);
+ }
+
return $share;
}
@@ -904,6 +964,7 @@ class ShareByMailProvider implements IShareProvider {
->setPermissions((int)$data['permissions'])
->setTarget($data['file_target'])
->setMailSend((bool)$data['mail_send'])
+ ->setNote($data['note'])
->setToken($data['token']);
$shareTime = new \DateTime();
diff --git a/apps/sharebymail/tests/ShareByMailProviderTest.php b/apps/sharebymail/tests/ShareByMailProviderTest.php
index 95d746cfb46..f0d99e6026c 100644
--- a/apps/sharebymail/tests/ShareByMailProviderTest.php
+++ b/apps/sharebymail/tests/ShareByMailProviderTest.php
@@ -342,15 +342,17 @@ class ShareByMailProviderTest extends TestCase {
$uidOwner = 'user2';
$permissions = 1;
$token = 'token';
+ $note = 'personal note';
$instance = $this->getInstance();
- $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
+ $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note);
$this->share->expects($this->once())->method('getPermissions')->willReturn($permissions + 1);
$this->share->expects($this->once())->method('getShareOwner')->willReturn($uidOwner);
$this->share->expects($this->once())->method('getSharedBy')->willReturn($sharedBy);
+ $this->share->expects($this->any())->method('getNote')->willReturn($note);
$this->share->expects($this->atLeastOnce())->method('getId')->willReturn($id);
$this->assertSame($this->share,
@@ -372,6 +374,7 @@ class ShareByMailProviderTest extends TestCase {
$this->assertSame($uidOwner, $result[0]['uid_owner']);
$this->assertSame($permissions + 1, (int)$result[0]['permissions']);
$this->assertSame($token, $result[0]['token']);
+ $this->assertSame($note, $result[0]['note']);
}
public function testDelete() {
@@ -478,7 +481,7 @@ class ShareByMailProviderTest extends TestCase {
$instance = $this->getInstance(['createShareObject']);
$idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
- $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, \OCP\Share::SHARE_TYPE_LINK);
+ $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, '', \OCP\Share::SHARE_TYPE_LINK);
$this->assertTrue($idMail !== $idPublic);
@@ -490,9 +493,9 @@ class ShareByMailProviderTest extends TestCase {
}
);
- $this->assertInstanceOf('OCP\Share\IShare',
- $instance->getShareByToken('token')
- );
+ $result = $instance->getShareByToken('token');
+
+ $this->assertInstanceOf('OCP\Share\IShare', $result);
}
/**
@@ -511,7 +514,7 @@ class ShareByMailProviderTest extends TestCase {
$instance = $this->getInstance(['createShareObject']);
$idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
- $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, "token2", \OCP\Share::SHARE_TYPE_LINK);
+ $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, "token2", '', \OCP\Share::SHARE_TYPE_LINK);
$this->assertTrue($idMail !== $idPublic);
@@ -631,7 +634,7 @@ class ShareByMailProviderTest extends TestCase {
$this->invokePrivate($instance, 'getRawShare', [$id+1]);
}
- private function createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $shareType = \OCP\Share::SHARE_TYPE_EMAIL) {
+ private function createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note='', $shareType = \OCP\Share::SHARE_TYPE_EMAIL) {
$qb = $this->connection->getQueryBuilder();
$qb->insert('share')
->setValue('share_type', $qb->createNamedParameter($shareType))
@@ -643,6 +646,7 @@ class ShareByMailProviderTest extends TestCase {
->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
->setValue('permissions', $qb->createNamedParameter($permissions))
->setValue('token', $qb->createNamedParameter($token))
+ ->setValue('note', $qb->createNamedParameter($note))
->setValue('stime', $qb->createNamedParameter(time()));
/*
diff --git a/core/Migrations/Version14000Date20180712153140.php b/core/Migrations/Version14000Date20180712153140.php
new file mode 100644
index 00000000000..268a479eaad
--- /dev/null
+++ b/core/Migrations/Version14000Date20180712153140.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 Bjoern Schiessle <bjoern@schiessle.org>
+ *
+ * @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 OCP\DB\ISchemaWrapper;
+use OCP\Migration\SimpleMigrationStep;
+
+/**
+ * add column for share notes
+ *
+ * Class Version14000Date20180712153140
+ */
+class Version14000Date20180712153140 extends SimpleMigrationStep {
+ public function changeSchema(\OCP\Migration\IOutput $output, \Closure $schemaClosure, array $options) {
+
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $table = $schema->getTable('share');
+ $table->addColumn('note', 'text', ['notnull' => false]);
+
+ return $schema;
+ }
+}
diff --git a/core/css/apps.scss b/core/css/apps.scss
index d524dd94bb7..86f0e622405 100644
--- a/core/css/apps.scss
+++ b/core/css/apps.scss
@@ -74,6 +74,13 @@ kbd {
/* Navigation: folder like structure */
#app-navigation {
width: $navigation-width;
+ position: sticky;
+ top: $header-height;
+ left: 0;
+ z-index: 1500;
+ overflow-y: auto;
+ overflow-x: hidden;
+ height: calc(100vh - #{$header-height});
box-sizing: border-box;
background-color: var(--color-main-background);
-webkit-user-select: none;
@@ -320,9 +327,6 @@ kbd {
&.hidden {
display: none;
}
- &.without-app-settings {
- padding-bottom: 0;
- }
/**
* Button styling for menu, edit and undo
@@ -581,12 +585,7 @@ kbd {
padding-top: $header-height;
box-sizing: border-box;
position: relative;
- overflow-x: hidden;
display: flex;
- /* trick: scroll #app-content and not the body
- * to avoid double scrollbar with sidebar
- */
- max-height: 100vh;
}
/* APP-CONTENT AND WRAPPER ------------------------------------------ */
@@ -637,17 +636,19 @@ kbd {
min-width: $sidebar-min-width;
max-width: $sidebar-max-width;
display: block;
- position: relative;
+ position: sticky;
+ top: $header-height;
+ right:0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ z-index: 1500;
+ height: calc(100vh - #{$header-height});
background: var(--color-main-background);
border-left: 1px solid var(--color-border);
- overflow-x: hidden;
- overflow-y: auto;
flex-shrink: 0;
- transition: 300ms width ease-in-out,
- 300ms min-width ease-in-out;
+ // no animations possible, use OC.Apps.showAppSidebar
&.disappear {
- width: 0;
- min-width: 0;
+ display: none;
}
}
@@ -880,6 +881,11 @@ $popovericon-size: 16px;
li {
display: flex;
flex: 0 0 auto;
+
+ &.hidden {
+ display: none;
+ }
+
> button,
> a,
> .menuitem {
@@ -895,6 +901,7 @@ $popovericon-size: 16px;
box-shadow: none;
width: 100%;
color: var(--color-main-text);
+ white-space: nowrap;
/* Override the app-navigation li opacity */
opacity: .7 !important;
span[class^='icon-'],
@@ -943,6 +950,7 @@ $popovericon-size: 16px;
width: 150px;
line-height: 1.6em;
padding: 8px 0;
+ white-space: normal;
}
> select {
margin: 0;
diff --git a/core/css/ie.scss b/core/css/ie.scss
new file mode 100644
index 00000000000..ec7f51065e4
--- /dev/null
+++ b/core/css/ie.scss
@@ -0,0 +1,11 @@
+
+#app-navigation,
+#app-sidebar {
+ position: fixed !important;
+}
+#app-content {
+ width: $navigation-width !important;
+}
+#app-sidebar.disappear {
+ right: -$sidebar-max-width !important;
+} \ No newline at end of file
diff --git a/core/css/share.scss b/core/css/share.scss
deleted file mode 100644
index 07489cd55a3..00000000000
--- a/core/css/share.scss
+++ /dev/null
@@ -1,204 +0,0 @@
-/**
- * @copyright Copyright (c) 2016, John Molakvoæ <skjnldsv@protonmail.com>
- * @copyright Copyright (c) 2016, Morris Jobke <hey@morrisjobke.de>
- * @copyright Copyright (c) 2016, Julia Bode <julia.bode@lulisaur.us>
- * @copyright Copyright (c) 2016, Christoph Wurst <christoph@winzerhof-wurst.at>
- * @copyright Copyright (c) 2015, Hendrik Leppelsack <hendrik@leppelsack.de>
- * @copyright Copyright (c) 2015, Jan-Christoph Borchardt <hey@jancborchardt.net>
- * @copyright Copyright (c) 2015, Vincent Petry <pvince81@owncloud.com>
- * @copyright Copyright (c) 2015, Arthur Schiwon <blizzz@owncloud.com>
- * @copyright Copyright (c) 2015, Roeland Jago Douma <roeland@famdouma.nl>
- * @copyright Copyright (c) 2015, Morris Jobke <hey@morrisjobke.de>
- *
- * @license GNU AGPL version 3 or any later version
- *
- */
-
-/* SHARE TAB STYLING -------------------------------------------------------- */
-.shareTabView {
- .unshare.icon-loading-small {
- margin-top: 1px;
- }
- .shareWithLoading, .linkShare .icon-loading-small {
- display: inline-block !important;
- padding-left: 10px;
- }
- .shareWithLoading {
- position: relative;
- right: 70px;
- top: 2px;
- }
- .icon-loading-small.hidden {
- display: none !important;
- }
- .avatar {
- margin-right: 8px;
- display: inline-block;
- overflow: hidden;
- vertical-align: middle;
- width: 32px;
- height: 32px;
- }
- label {
- font-weight: 400;
- white-space: nowrap;
- }
- input[type='radio'].radio + label {
- margin-left: -1px;
- }
- input[type='checkbox'] {
- margin: 0 3px 0 8px;
- vertical-align: middle;
- }
- input[type='submit'] {
- margin-left: 7px;
- }
- form {
- font-size: 100%;
- margin-left: 0;
- margin-right: 0;
- }
- .error {
- color: var(--color-error);
- border-color: var(--color-error);
- }
- .mailView .icon-mail {
- opacity: 0.5;
- }
-}
-
-.share-autocomplete-item {
- display: flex;
- .autocomplete-item-text {
- margin-left: 10px;
- margin-right: 10px;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- line-height: 32px;
- vertical-align: middle;
- }
-}
-
-.ui-autocomplete .autocomplete-note {
- padding: 5px 10px;
- color: rgba(0, 0, 0, .3);
-}
-
-#shareWithList {
- list-style-type: none;
- padding: 8px;
- > li {
- position: relative;
- padding-top: 10px;
- padding-bottom: 10px;
- font-weight: bold;
- line-height: 21px;
- white-space: normal;
- width: 100%;
- }
- .sharingOptionsGroup {
- flex-shrink: 0;
- position: relative;
- .popovermenu {
- right: -11px;
- top: 35px;
- }
- }
-
- .shareOption {
- white-space: nowrap;
- display: inline-block;
- }
- .unshare img {
- vertical-align: text-bottom;
- /* properly align icons */
- }
- label input[type=checkbox] {
- margin-left: 0;
- position: relative;
- }
- .username {
- padding-right: 8px;
- white-space: nowrap;
- text-overflow: ellipsis;
- display: inline-block;
- overflow: hidden;
- vertical-align: middle;
- flex-grow: 5;
- }
-}
-
-#link {
- border-top: 1px solid var(--color-border);
- padding-top: 8px;
- #showPassword img {
- padding-left: 5px;
- width: 12px;
- }
-}
-
-.reshare,
-#link label,
-#expiration label {
- display: inline-block;
- padding: 6px 4px;
-}
-
-.resharerInfoView.subView {
- position: relative;
-}
-
-#defaultExpireMessage, .reshare {
- /* fix shared by text going out of box */
- white-space: normal;
-}
-
-#defaultExpireMessage {
- /* show message on new line */
- display: block;
- padding-left: 4px;
- /* TODO: style the dropdown in a proper way - border-box, etc. */
- width: 90%;
-}
-
-.ui-autocomplete {
- /* limit dropdown height to 4 1/2 entries */
- max-height: 200px;
- overflow-y: auto;
- overflow-x: hidden;
-}
-
-.notCreatable {
- padding-left: 12px;
- padding-top: 12px;
- color: var(--color-text-lighter);
-}
-
-.contactsmenu-popover {
- left: -6px;
- right: auto;
- padding: 3px 6px;
- top: 100%;
- margin-top: 0;
- li.hidden {
- display: none !important;
- }
- &:after {
- left: 8px;
- right: auto;
- }
-}
-
-.popovermenu .datepicker {
- margin-left: 35px;
-}
-
-.popovermenu .passwordField {
- margin-left: 35px;
- width: inherit !important;
-}
-
-.ui-datepicker {
- z-index: 1111 !important;
-}
diff --git a/core/css/styles.scss b/core/css/styles.scss
index 30aa25d183c..9652b02e9d2 100644
--- a/core/css/styles.scss
+++ b/core/css/styles.scss
@@ -530,6 +530,7 @@ code {
width: auto;
border-radius: var(--border-radius);
border: none;
+ z-index: 500 !important;
.ui-state-default,
.ui-widget-content .ui-state-default,
diff --git a/core/img/actions/public-white.svg b/core/img/actions/public-white.svg
new file mode 100644
index 00000000000..d85defb6a09
--- /dev/null
+++ b/core/img/actions/public-white.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 16 16" height="16" width="16"><path fill="#fff" d="m9.2363 2.166-3.1816 3.1836c-0.7071 0.7072-1.0378 1.6182-0.9883 2.457 0.05 0.8389 0.4333 1.5841 0.9883 2.1387l1.4121-1.416c-0.5672-0.5672-0.5444-1.2192 0.002-1.7656l3.1812-3.1817c0.52536-0.52536 1.2507-0.52318 1.772-0.002 0.48245 0.5556 0.52732 1.2382-0.004 1.7695l-0.82 0.8203c0.555 0.785 0.645 1.3663 0.593 2.2344l1.641-1.6406c1.2374-1.2374 1.2371-3.3645 0-4.6016-1.236-1.2361-3.342-1.2113-4.5957 0.004zm0.7071 3.8848-1.4141 1.418c0 0 0.003-00 0.004 0 0.55 0.55 0.50736 1.2582-0.004 1.7695l-3.1816 3.1817c-0.696 0.59192-1.2985 0.47105-1.7696 0-0.62636-0.62636-0.5-1.2681 0-1.768l0.85-0.8473c-0.556-0.7835-0.6484-1.365-0.5976-2.2324l-1.666 1.666c-1.2393 1.2393-1.2357 3.36 0 4.5957 1.2353 1.2353 3.362 1.2356 4.5976 0l3.1817-3.182c0.7086-0.7083 1.0396-1.6184 0.9906-2.4586-0.048-0.8401-0.432-1.5864-0.9887-2.1407z"/></svg>
diff --git a/core/js/apps.js b/core/js/apps.js
index b40883e88cf..473fec313a1 100644
--- a/core/js/apps.js
+++ b/core/js/apps.js
@@ -27,8 +27,9 @@
*/
exports.Apps.showAppSidebar = function($el) {
var $appSidebar = $el || $('#app-sidebar');
- $appSidebar.removeClass('disappear');
- $('#content').addClass('with-app-sidebar').trigger(new $.Event('appresized'));
+ $appSidebar.removeClass('disappear')
+ .show('slide', { direction: 'right' }, 300);
+ $('#app-content').trigger(new $.Event('appresized'));
};
/**
@@ -39,8 +40,11 @@
*/
exports.Apps.hideAppSidebar = function($el) {
var $appSidebar = $el || $('#app-sidebar');
- $appSidebar.addClass('disappear');
- $('#content').removeClass('with-app-sidebar').trigger(new $.Event('appresized'));
+ $appSidebar.hide('slide', { direction: 'right' }, 300,
+ function() {
+ $appSidebar.addClass('disappear');
+ });
+ $('#app-content').trigger(new $.Event('appresized'));
};
/**
diff --git a/core/js/core.json b/core/js/core.json
index 502e3a57976..2ebc2e710ed 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -37,7 +37,6 @@
"shareconfigmodel.js",
"shareitemmodel.js",
"sharedialogview.js",
- "sharedialogexpirationview.js",
"sharedialoglinkshareview.js",
"sharedialogresharerinfoview.js",
"sharedialogshareelistview.js",
diff --git a/core/js/merged-share-backend.json b/core/js/merged-share-backend.json
index d39945b8f79..63c3575a666 100644
--- a/core/js/merged-share-backend.json
+++ b/core/js/merged-share-backend.json
@@ -1,11 +1,10 @@
[
- "shareconfigmodel.js",
- "shareitemmodel.js",
- "sharesocialmanager.js",
- "sharedialogresharerinfoview.js",
- "sharedialoglinkshareview.js",
- "sharedialogexpirationview.js",
- "sharedialogshareelistview.js",
- "sharedialogview.js",
- "share.js"
+ "shareconfigmodel.js",
+ "shareitemmodel.js",
+ "sharesocialmanager.js",
+ "sharedialogresharerinfoview.js",
+ "sharedialoglinkshareview.js",
+ "sharedialogshareelistview.js",
+ "sharedialogview.js",
+ "share.js"
]
diff --git a/core/js/sharedialogexpirationview.js b/core/js/sharedialogexpirationview.js
deleted file mode 100644
index a9849ef9161..00000000000
--- a/core/js/sharedialogexpirationview.js
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (c) 2015
- *
- * This file is licensed under the Affero General Public License version 3
- * or later.
- *
- * See the COPYING-README file.
- *
- */
-
-/* global moment, Handlebars */
-
-(function() {
- if (!OC.Share) {
- OC.Share = {};
- }
-
- var TEMPLATE =
- // currently expiration is only effective for link share.
- // this is about to change in future. Therefore this is not included
- // in the LinkShareView to ease reusing it in future. Then,
- // modifications (getting rid of IDs) are still necessary.
- '{{#if isLinkShare}}' +
- '<input type="checkbox" name="expirationCheckbox" class="expirationCheckbox checkbox" id="expirationCheckbox-{{cid}}" value="1" ' +
- '{{#if isExpirationSet}}checked="checked"{{/if}} {{#if disableCheckbox}}disabled="disabled"{{/if}} />' +
- '<label for="expirationCheckbox-{{cid}}">{{setExpirationLabel}}</label>' +
- '<div class="expirationDateContainer {{#unless isExpirationSet}}hidden{{/unless}}">' +
- ' <label for="expirationDate" class="hidden-visually" value="{{expirationDate}}">{{expirationLabel}}</label>' +
- ' <input id="expirationDate" class="datepicker" type="text" placeholder="{{expirationDatePlaceholder}}" value="{{expirationValue}}" />' +
- '</div>' +
- ' {{#if isExpirationEnforced}}' +
- // originally the expire message was shown when a default date was set, however it never had text
- '<em id="defaultExpireMessage">{{defaultExpireMessage}}</em>' +
- ' {{/if}}' +
- '{{/if}}'
- ;
-
- /**
- * @class OCA.Share.ShareDialogExpirationView
- * @member {OC.Share.ShareItemModel} model
- * @member {jQuery} $el
- * @memberof OCA.Sharing
- * @classdesc
- *
- * Represents the expiration part in the GUI of the share dialogue
- *
- */
- var ShareDialogExpirationView = OC.Backbone.View.extend({
- /** @type {string} **/
- id: 'shareDialogLinkShare',
-
- /** @type {OC.Share.ShareConfigModel} **/
- configModel: undefined,
-
- /** @type {Function} **/
- _template: undefined,
-
- /** @type {boolean} **/
- showLink: true,
-
- className: 'hidden',
-
- events: {
- 'change .expirationCheckbox': '_onToggleExpiration',
- 'change .datepicker': '_onChangeExpirationDate'
- },
-
- initialize: function(options) {
- if(!_.isUndefined(options.configModel)) {
- this.configModel = options.configModel;
- } else {
- throw 'missing OC.Share.ShareConfigModel';
- }
-
- var view = this;
- this.configModel.on('change:isDefaultExpireDateEnforced', function() {
- view.render();
- });
-
- this.model.on('change:itemType', function() {
- view.render();
- });
-
- this.model.on('change:linkShare', function() {
- view.render();
- });
- },
-
- _onToggleExpiration: function(event) {
- var $checkbox = $(event.target);
- var state = $checkbox.prop('checked');
- // TODO: slide animation
- this.$el.find('.expirationDateContainer').toggleClass('hidden', !state);
- if (!state) {
- // discard expiration date
- this.model.get('linkShare').expiration = '';
- this.model.saveLinkShare({
- expireDate: ''
- });
- } else {
- this.$el.find('#expirationDate').focus();
- }
- },
-
- _onChangeExpirationDate: function(event) {
- var $target = $(event.target);
- $target.tooltip('hide');
- $target.removeClass('error');
-
- var expiration = moment($target.val(), 'DD-MM-YYYY').format('YYYY-MM-DD');
- this.model.get('linkShare').expiration = expiration;
- this.model.saveLinkShare({
- expiration: expiration
- }, {
- error: function(model, message) {
- if (!message) {
- $target.attr('title', t('core', 'Error setting expiration date'));
- } else {
- $target.attr('title', message);
- }
- $target.tooltip({gravity: 'n'});
- $target.tooltip('show');
- $target.addClass('error');
- }
- });
- },
-
- render: function() {
- var defaultExpireMessage = '';
- var defaultExpireDays = this.configModel.get('defaultExpireDate');
- var isExpirationEnforced = this.configModel.get('isDefaultExpireDateEnforced');
-
- if( (this.model.isFolder() || this.model.isFile())
- && isExpirationEnforced) {
- defaultExpireMessage = t(
- 'core',
- 'The public link will expire no later than {days} days after it is created',
- {'days': defaultExpireDays }
- );
- }
-
- var isExpirationSet = !!this.model.get('linkShare').expiration || isExpirationEnforced;
-
- var expiration;
- if (isExpirationSet) {
- expiration = moment(this.model.get('linkShare').expiration, 'YYYY-MM-DD').format('DD-MM-YYYY');
- }
-
- this.$el.html(this.template({
- cid: this.cid,
- setExpirationLabel: t('core', 'Set expiration date'),
- expirationLabel: t('core', 'Expiration'),
- expirationDatePlaceholder: t('core', 'Expiration date'),
- defaultExpireMessage: defaultExpireMessage,
- isLinkShare: this.model.get('linkShare').isLinkShare,
- isExpirationSet: isExpirationSet,
- isExpirationEnforced: isExpirationEnforced,
- disableCheckbox: isExpirationEnforced && isExpirationSet,
- expirationValue: expiration
- }));
-
- // 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(isExpirationSet) {
- 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
- });
-
- this.$el.find('.datepicker').datepicker({dateFormat : 'dd-mm-yy'});
-
- this.delegateEvents();
-
- return this;
- },
-
- /**
- * @returns {Function} from Handlebars
- * @private
- */
- template: function (data) {
- if (!this._template) {
- this._template = Handlebars.compile(TEMPLATE);
- }
- return this._template(data);
- }
-
- });
-
- OC.Share.ShareDialogExpirationView = ShareDialogExpirationView;
-
-})();
diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js
index 5a78276a491..925d8ed918e 100644
--- a/core/js/sharedialoglinkshareview.js
+++ b/core/js/sharedialoglinkshareview.js
@@ -21,71 +21,101 @@
var TEMPLATE =
'{{#if shareAllowed}}' +
- '<span class="icon-loading-small hidden"></span>' +
- '<input type="checkbox" name="linkCheckbox" id="linkCheckbox-{{cid}}" class="checkbox linkCheckbox" value="1" {{#if isLinkShare}}checked="checked"{{/if}} />' +
- '<label for="linkCheckbox-{{cid}}">{{linkShareLabel}}</label>' +
- '<br />' +
- '<div class="oneline">' +
- '<label for="linkText-{{cid}}" class="hidden-visually">{{urlLabel}}</label>' +
- '<input id="linkText-{{cid}}" class="linkText {{#unless isLinkShare}}hidden{{/unless}}" type="text" readonly="readonly" value="{{shareLinkURL}}" />' +
- '{{#if singleAction}}' +
- '<a class="{{#unless isLinkShare}}hidden-visually{{/unless}} clipboardButton icon icon-clippy" data-clipboard-target="#linkText-{{cid}}"></a>' +
- '{{else}}' +
- '<a class="{{#unless isLinkShare}}hidden-visually{{/unless}}" href="#"><span class="linkMore icon icon-more"></span></a>' +
- '{{{popoverMenu}}}' +
- '{{/if}}' +
- '</div>' +
- '{{#if publicUpload}}' +
- '<div>' +
- '<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>' +
- '</div>' +
- '<div>' +
- '<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>' +
- '</div>' +
- '<div>' +
- '<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>' +
- '</div>' +
- '{{/if}}' +
- ' {{#if publicEditing}}' +
- '<div id="allowPublicEditingWrapper">' +
- ' <span class="icon-loading-small hidden"></span>' +
- ' <input type="checkbox" value="1" name="allowPublicEditing" id="sharingDialogAllowPublicEditing-{{cid}}" class="checkbox publicEditingCheckbox" {{{publicEditingChecked}}} />' +
- '<label for="sharingDialogAllowPublicEditing-{{cid}}">{{publicEditingLabel}}</label>' +
- '</div>' +
- ' {{/if}}' +
- ' {{#if showPasswordCheckBox}}' +
- '<input type="checkbox" name="showPassword" id="showPassword-{{cid}}" class="checkbox showPasswordCheckbox" {{#if isPasswordSet}}checked="checked"{{/if}} value="1" />' +
- '<label for="showPassword-{{cid}}">{{enablePasswordLabel}}</label>' +
- ' {{/if}}' +
- '<div id="linkPass" class="oneline linkPass {{#unless isPasswordSet}}hidden{{/unless}}">' +
- ' <label for="linkPassText-{{cid}}" class="hidden-visually">{{passwordLabel}}</label>' +
- ' {{#if showPasswordCheckBox}}' +
- ' <input id="linkPassText-{{cid}}" class="linkPassText" type="password" placeholder="{{passwordPlaceholder}}" autocomplete="new-password" />' +
- ' {{else}}' +
- ' <input id="linkPassText-{{cid}}" class="linkPassText" type="password" placeholder="{{passwordPlaceholderInitial}}" autocomplete="new-password" />' +
- ' {{/if}}' +
- ' <span class="icon icon-loading-small hidden"></span>' +
- '</div>' +
+ '<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>' +
+ ' </span>' +
+ ' {{#if isLinkShare}}' +
+ ' <div class="share-menu" tabindex="0"><span class="icon icon-more"></span>' +
+ ' {{{popoverMenu}}}' +
+ ' </div>' +
+ ' {{/if}}' +
+ ' </span>' +
+ ' </li>' +
+ '</ul>' +
'{{else}}' +
// FIXME: this doesn't belong in this view
'{{#if noSharingPlaceholder}}<input id="shareWith-{{cid}}" class="shareWithField" type="text" placeholder="{{noSharingPlaceholder}}" disabled="disabled"/>{{/if}}' +
'{{/if}}'
;
var TEMPLATE_POPOVER_MENU =
- '<div class="popovermenu bubble hidden menu socialSharingMenu">' +
+ '<div class="popovermenu menu">' +
'<ul>' +
'<li>' +
- '<a href="#" class="shareOption menuitem clipboardButton" data-clipboard-target="#linkText-{{cid}}">' +
+ '<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 publicUpload}}' +
+ '<li><span class="shareOption 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="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>' +
+ '<li><span class="shareOption 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>' +
+ '</span></li>' +
+ '{{/if}}' +
+ '{{#if publicEditing}}' +
+ ' <li id="allowPublicEditingWrapper"><span class="shareOption 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 showPasswordCheckBox}}' +
+ ' <li><span class="shareOption menuitem">' +
+ ' <input type="checkbox" name="showPassword" id="showPassword-{{cid}}" class="checkbox showPasswordCheckbox" {{#if isPasswordSet}}checked="checked"{{/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">' +
+ ' <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">' +
+ '<input id="expireDate-{{cid}}" type="checkbox" name="expirationDate" class="expireDate checkbox" {{#if hasExpireDate}}checked="checked"{{/if}}" />' +
+ '<label for="expireDate-{{cid}}">{{expireDateLabel}}</label>' +
+ '</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}}" />' +
+ '</span>' +
+ '</span>' +
+ '</li>' +
+ '<li>' +
+ '<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">' +
+ '</a>' +
+ '</li>' +
+ '<li class="share-note-form share-note-link hidden">' +
+ '<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>' +
+ '</li>' +
'{{#each social}}' +
'<li>' +
'<a href="#" class="shareOption menuitem pop-up" data-url="{{url}}" data-window="{{newWindow}}">' +
@@ -131,9 +161,15 @@
'click .linkText': 'onLinkTextClick',
'change .publicEditingCheckbox': 'onAllowPublicEditingChange',
'click .showPasswordCheckbox': 'onShowPasswordClick',
- 'click .icon-more': 'onToggleMenu',
+ 'click .share-menu .icon-more': 'onToggleMenu',
'click .pop-up': 'onPopUpClick',
- 'change .publicUploadRadio': 'onPublicUploadChange'
+ 'change .publicUploadRadio': 'onPublicUploadChange',
+ 'click .expireDate' : 'onExpireDateChange',
+ 'change .datepicker': 'onChangeExpirationDate',
+ 'click .datepicker' : 'showDatePicker',
+ 'click .share-add': 'showNoteForm',
+ 'click .share-note-delete': 'deleteNote',
+ 'click .share-note-submit': 'updateNote'
},
initialize: function(options) {
@@ -165,37 +201,26 @@
throw 'missing OC.Share.ShareConfigModel';
}
- _.bindAll(
- this,
- 'onLinkCheckBoxChange',
- 'onPasswordEntered',
- 'onPasswordKeyUp',
- 'onLinkTextClick',
- 'onShowPasswordClick',
- 'onAllowPublicEditingChange',
- 'onPublicUploadChange'
- );
-
var clipboard = new Clipboard('.clipboardButton');
clipboard.on('success', function(e) {
- var $input = $(e.trigger);
- $input.tooltip('hide')
+ var $menu = $(e.trigger);
+ var $linkTextMenu = $menu.parent().next('li.linkTextMenu')
+
+ $menu.tooltip('hide')
.attr('data-original-title', t('core', 'Copied!'))
.tooltip('fixTitle')
.tooltip({placement: 'bottom', trigger: 'manual'})
.tooltip('show');
_.delay(function() {
- $input.tooltip('hide');
- if (OC.Share.Social.Collection.size() == 0) {
- $input.attr('data-original-title', t('core', 'Copy'))
- .tooltip('fixTitle');
- } else {
- $input.tooltip("destroy");
- }
+ $menu.tooltip('hide');
+ $menu.tooltip('destroy');
}, 3000);
});
clipboard.on('error', function (e) {
- var $input = $(e.trigger);
+ var $menu = $(e.trigger);
+ var $linkTextMenu = $menu.parent().next('li.linkTextMenu')
+ var $input = $linkTextMenu.find('.linkText');
+
var actionMsg = '';
if (/iPhone|iPad/i.test(navigator.userAgent)) {
actionMsg = t('core', 'Not supported!');
@@ -205,6 +230,8 @@
actionMsg = t('core', 'Press Ctrl-C to copy.');
}
+ $linkTextMenu.removeClass('hidden');
+ $input.select();
$input.tooltip('hide')
.attr('data-original-title', actionMsg)
.tooltip('fixTitle')
@@ -212,15 +239,10 @@
.tooltip('show');
_.delay(function () {
$input.tooltip('hide');
- if (OC.Share.Social.Collection.size() == 0) {
- $input.attr('data-original-title', t('core', 'Copy'))
- .tooltip('fixTitle');
- } else {
- $input.tooltip("destroy");
- }
+ $input.attr('data-original-title', t('core', 'Copy'))
+ .tooltip('fixTitle');
}, 3000);
});
-
},
onLinkCheckBoxChange: function() {
@@ -257,6 +279,7 @@
onShowPasswordClick: function() {
this.$el.find('.linkPass').slideToggle(OC.menuSpeed);
+ this.$el.find('.linkPassMenu').toggleClass('hidden');
if(!this.$el.find('.showPasswordCheckbox').is(':checked')) {
this.model.saveLinkShare({
password: ''
@@ -275,7 +298,7 @@
},
onPasswordEntered: function() {
- var $loading = this.$el.find('.linkPass .icon-loading-small');
+ var $loading = this.$el.find('.linkPassMenu .icon-loading-small');
if (!$loading.hasClass('hidden')) {
// still in process
return;
@@ -334,12 +357,95 @@
},
- onPublicUploadChange: function(e) {
+ onPublicUploadChange: function(e) {
var permissions = e.currentTarget.value;
this.model.saveLinkShare({
permissions: permissions
});
},
+
+ showNoteForm: function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var self = this;
+ var $element = $(event.target);
+ var $li = $element.closest('li[data-share-id]');
+ var $menu = $element.closest('li');
+ var $form = $menu.next('li.share-note-form');
+
+ // show elements
+ $menu.find('.share-note-delete').toggle();
+ $form.toggleClass('hidden');
+ $form.find('textarea').focus();
+ },
+
+ deleteNote: function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var self = this;
+ var $element = $(event.target);
+ var $li = $element.closest('li[data-share-id]');
+ var shareId = $li.data('share-id');
+ var $menu = $element.closest('li');
+ var $form = $menu.next('li.share-note-form');
+
+ console.log($form.find('.share-note'));
+ $form.find('.share-note').val('');
+
+ $form.addClass('hidden');
+ $menu.find('.share-note-delete').hide();
+
+ self.sendNote('', shareId, $menu);
+ },
+
+ updateNote: function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var self = this;
+ var $element = $(event.target);
+ var $li = $element.closest('li[data-share-id]');
+ var shareId = $li.data('share-id');
+ var $form = $element.closest('li.share-note-form');
+ var $menu = $form.prev('li');
+ var message = $form.find('.share-note').val().trim();
+
+ if (message.length < 1) {
+ return;
+ }
+
+ self.sendNote(message, shareId, $menu);
+ },
+
+ sendNote: function(note, shareId, $menu) {
+ var $form = $menu.next('li.share-note-form');
+ var $submit = $form.find('input.share-note-submit');
+ var $error = $form.find('input.share-note-error');
+
+ $submit.prop('disabled', true);
+ $menu.find('.icon-loading-small').removeClass('hidden');
+ $menu.find('.icon-edit').hide();
+
+ var complete = function() {
+ $submit.prop('disabled', false);
+ $menu.find('.icon-loading-small').addClass('hidden');
+ $menu.find('.icon-edit').show();
+ };
+ var error = function() {
+ $error.show();
+ setTimeout(function() {
+ $error.hide();
+ }, 3000);
+ };
+
+ // send data
+ $.ajax({
+ method: 'PUT',
+ url: OC.linkToOCS('apps/files_sharing/api/v1/shares',2) + shareId + '?' + OC.buildQueryString({format: 'json'}),
+ data: { note: note },
+ complete : complete,
+ error: error
+ });
+ },
render: function() {
var linkShareTemplate = this.template();
@@ -412,18 +518,48 @@
});
});
- var popover = this.popoverMenuTemplate({
- cid: this.cid,
- copyLabel: t('core', 'Copy'),
- social: social
+ 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
});
- this.$el.html(linkShareTemplate({
- cid: this.cid,
- shareAllowed: true,
- isLinkShare: isLinkShare,
+ this.$el.find('.datepicker').datepicker({dateFormat : 'dd-mm-yy'});
+
+ var popover = this.popoverMenuTemplate({
+ cid: this.model.get('linkShare').id,
+ copyLabel: t('core', 'Copy URL'),
+ social: social,
+
shareLinkURL: this.model.get('linkShare').link,
- linkShareLabel: t('core', 'Share link'),
urlLabel: t('core', 'Link'),
enablePasswordLabel: t('core', 'Password protect'),
passwordLabel: t('core', 'Password'),
@@ -437,8 +573,6 @@
publicEditingLabel: t('core', 'Allow editing'),
mailPrivatePlaceholder: t('core', 'Email link to person'),
mailButtonText: t('core', 'Send'),
- singleAction: OC.Share.Social.Collection.size() == 0,
- popoverMenu: popover,
publicUploadRWLabel: t('core', 'Allow upload and editing'),
publicUploadRLabel: t('core', 'Read only'),
publicUploadWLabel: t('core', 'File drop (upload only)'),
@@ -447,19 +581,32 @@
publicUploadWValue: OC.PERMISSION_CREATE,
publicUploadRWChecked: publicUploadRWChecked,
publicUploadRChecked: publicUploadRChecked,
- publicUploadWChecked: publicUploadWChecked
- }));
+ publicUploadWChecked: publicUploadWChecked,
+ expireDateLabel: t('core', 'Set expiration date'),
+ expirationLabel: t('core', 'Expiration'),
+ expirationDatePlaceholder: t('core', 'Expiration date'),
+ hasExpireDate: hasExpireDate,
+ isExpirationEnforced: isExpirationEnforced,
+ expireDate: expireDate,
+ defaultExpireDate: moment().add(1, 'day').format('DD-MM-YYYY'), // Can't expire today
+ shareNote: this.model.get('linkShare').note,
+ addNoteLabel: t('core', 'Set share note'),
+ });
- if (OC.Share.Social.Collection.size() == 0) {
- this.$el.find('.clipboardButton').tooltip({
- placement: 'bottom',
- title: t('core', 'Copy'),
- trigger: 'hover'
- });
- }
+ this.$el.html(linkShareTemplate({
+ cid: this.model.get('linkShare').id,
+ shareAllowed: true,
+ isLinkShare: isLinkShare,
+ linkShareLabel: t('core', 'Share link'),
+ linkShareEnableLabel: t('core', 'Enable'),
+ popoverMenu: popover,
+ }));
this.delegateEvents();
+ // new note autosize
+ autosize(this.$el.find('.share-note-form .share-note'));
+
return this;
},
@@ -467,8 +614,8 @@
event.preventDefault();
event.stopPropagation();
var $element = $(event.target);
- var $li = $element.closest('.oneline');
- var $menu = $li.find('.popovermenu');
+ var $li = $element.closest('li[data-share-id]');
+ var $menu = $li.find('.sharingOptionsGroup .popovermenu');
OC.showMenu(null, $menu);
this._menuOpen = $li.data('share-id');
@@ -517,7 +664,52 @@
window.location.href = url;
}
}
- }
+ },
+
+ onExpireDateChange: function(event) {
+ var $element = $(event.target);
+ var li = $element.closest('li[data-share-id]');
+ var shareId = li.data('share-id');
+ var expirationDatePicker = '#expirationDateContainer-' + shareId;
+ var datePicker = $(expirationDatePicker);
+ var state = $element.prop('checked');
+ datePicker.toggleClass('hidden', !state);
+
+ if (!state) {
+ // disabled, let's hide the input and
+ // set the expireDate to nothing
+ $element.closest('li').next('li').addClass('hidden');
+ this.setExpirationDate('');
+ } else {
+ // enabled, show the input and the datepicker
+ $element.closest('li').next('li').removeClass('hidden');
+ this.showDatePicker(event);
+
+ }
+ },
+
+ showDatePicker: function(event) {
+ var $element = $(event.target);
+ var li = $element.closest('li[data-share-id]');
+ var shareId = li.data('share-id');
+ var expirationDatePicker = '#expirationDatePicker-' + shareId;
+ var self = this;
+
+ $(expirationDatePicker).datepicker({
+ dateFormat : 'dd-mm-yy',
+ onSelect: function (expireDate) {
+ self.setExpirationDate(expireDate);
+ }
+ });
+ console.log(event, $(expirationDatePicker));
+ $(expirationDatePicker).datepicker('show');
+ $(expirationDatePicker).focus();
+
+ },
+
+ setExpirationDate: function(expireDate) {
+ this.model.saveLinkShare({expireDate: expireDate});
+ },
});
diff --git a/core/js/sharedialogresharerinfoview.js b/core/js/sharedialogresharerinfoview.js
index f4bf9afa0b2..fadd0a41f7b 100644
--- a/core/js/sharedialogresharerinfoview.js
+++ b/core/js/sharedialogresharerinfoview.js
@@ -19,7 +19,8 @@
'<span class="reshare">' +
' <div class="avatar" data-userName="{{reshareOwner}}"></div>' +
' {{sharedByText}}' +
- '</span><br/>'
+ '</span>' +
+ '{{#if hasShareNote}}<div class="share-note">{{shareNote}}</div>{{/if}}'
;
/**
@@ -72,7 +73,10 @@
var reshareTemplate = this.template();
var ownerDisplayName = this.model.getReshareOwnerDisplayname();
+ var shareNote = this.model.getReshareNote();
+
var sharedByText = '';
+
if (this.model.getReshareType() === OC.Share.SHARE_TYPE_GROUP) {
sharedByText = t(
'core',
@@ -105,9 +109,13 @@
);
}
+
+
this.$el.html(reshareTemplate({
reshareOwner: this.model.getReshareOwner(),
- sharedByText: sharedByText
+ sharedByText: sharedByText,
+ shareNote: shareNote,
+ hasShareNote: shareNote !== ''
}));
this.$el.find('.avatar').each(function() {
diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js
index 53a65fcdf7a..574d47b4aac 100644
--- a/core/js/sharedialogshareelistview.js
+++ b/core/js/sharedialogshareelistview.js
@@ -34,8 +34,9 @@
'<label for="canEdit-{{cid}}-{{shareId}}">{{canEditLabel}}</label>' +
'</span>' +
'{{/if}}' +
- '<a href="#"><span class="icon icon-more"></span></a>' +
- '{{{popoverMenu}}}' +
+ '<div tabindex="0" class="share-menu"><span class="icon icon-more"></span>' +
+ '{{{popoverMenu}}}' +
+ '</div>' +
'</span>' +
'</li>' +
'{{/each}}' +
@@ -102,11 +103,13 @@
'<span class="shareOption 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>' +
- '<div class="passwordContainer-{{cid}}-{{shareId}} {{#unless isPasswordSet}}hidden{{/unless}}">' +
- ' <label for="passwordField-{{cid}}-{{shareId}}" class="hidden-visually" value="{{password}}">{{passwordLabel}}</label>' +
- ' <input id="passwordField-{{cid}}-{{shareId}}" class="passwordField" type="password" placeholder="{{passwordPlaceholder}}" value="{{passwordValue}}" autocomplete="new-password" />' +
- ' <span class="icon-loading-small hidden"></span>' +
- '</div>' +
+ '</span>' +
+ '</li>' +
+ '<li class="passwordMenu-{{cid}}-{{shareId}} {{#unless isPasswordSet}}hidden{{/unless}}">' +
+ '<span class="passwordContainer-{{cid}}-{{shareId}} icon-passwordmail menuitem">' +
+ ' <label for="passwordField-{{cid}}-{{shareId}}" class="hidden-visually" value="{{password}}">{{passwordLabel}}</label>' +
+ ' <input id="passwordField-{{cid}}-{{shareId}}" class="passwordField" type="password" placeholder="{{passwordPlaceholder}}" value="{{passwordValue}}" autocomplete="new-password" />' +
+ ' <span class="icon-loading-small hidden"></span>' +
'</span>' +
'</li>' +
'{{/if}}' +
@@ -114,12 +117,29 @@
'<span class="shareOption 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>' +
- '<div class="expirationDateContainer-{{cid}}-{{shareId}} {{#unless hasExpireDate}}hidden{{/unless}}">' +
- ' <label for="expirationDatePicker-{{cid}}-{{shareId}}" class="hidden-visually" value="{{expirationDate}}">{{expirationLabel}}</label>' +
- ' <input id="expirationDatePicker-{{cid}}-{{shareId}}" class="datepicker" type="text" placeholder="{{expirationDatePlaceholder}}" value="{{#if hasExpireDate}}{{expireDate}}{{else}}{{defaultExpireDate}}{{/if}}" />' +
- '</div>' +
'</span>' +
'</li>' +
+ '<li class="expirationDateMenu-{{cid}}-{{shareId}} {{#unless hasExpireDate}}hidden{{/unless}}">' +
+ '<span class="expirationDateContainer-{{cid}}-{{shareId}} icon-expiredate menuitem">' +
+ ' <label for="expirationDatePicker-{{cid}}-{{shareId}}" class="hidden-visually" value="{{expirationDate}}">{{expirationLabel}}</label>' +
+ ' <input id="expirationDatePicker-{{cid}}-{{shareId}}" class="datepicker" type="text" placeholder="{{expirationDatePlaceholder}}" value="{{#if hasExpireDate}}{{expireDate}}{{else}}{{defaultExpireDate}}{{/if}}" />' +
+ '</span>' +
+ '</li>' +
+ '{{#if isNoteAvailable}}' +
+ '<li>' +
+ '<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">' +
+ '</a>' +
+ '</li>' +
+ '<li class="share-note-form hidden">' +
+ '<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>' +
+ '</li>' +
+ '{{/if}}' +
'<li>' +
'<a href="#" class="unshare"><span class="icon-loading-small hidden"></span><span class="icon icon-delete"></span><span>{{unshareLabel}}</span></a>' +
'</li>' +
@@ -156,7 +176,10 @@
events: {
'click .unshare': 'onUnshare',
- 'click .icon-more': 'onToggleMenu',
+ 'click .share-add': 'showNoteForm',
+ 'click .share-note-delete': 'deleteNote',
+ 'click .share-note-submit': 'updateNote',
+ 'click .share-menu .icon-more': 'onToggleMenu',
'click .permissions': 'onPermissionChange',
'click .expireDate' : 'onExpireDateChange',
'click .password' : 'onMailSharePasswordProtectChange',
@@ -255,12 +278,14 @@
modSeed: shareType !== OC.Share.SHARE_TYPE_USER && (shareType !== OC.Share.SHARE_TYPE_CIRCLE || shareWithAvatar),
isRemoteShare: shareType === OC.Share.SHARE_TYPE_REMOTE,
isRemoteGroupShare: shareType === OC.Share.SHARE_TYPE_REMOTE_GROUP,
+ isNoteAvailable: shareType !== OC.Share.SHARE_TYPE_REMOTE && shareType !== OC.Share.SHARE_TYPE_REMOTE_GROUP,
isMailShare: shareType === OC.Share.SHARE_TYPE_EMAIL,
isCircleShare: shareType === OC.Share.SHARE_TYPE_CIRCLE,
isFileSharedByMail: shareType === OC.Share.SHARE_TYPE_EMAIL && !this.model.isFolder(),
isPasswordSet: hasPassword,
secureDropMode: !this.model.hasReadPermission(shareIndex),
hasExpireDate: this.model.getExpireDate(shareIndex) !== null,
+ shareNote: this.model.getNote(shareIndex),
expireDate: moment(this.model.getExpireDate(shareIndex), 'YYYY-MM-DD').format('DD-MM-YYYY'),
passwordPlaceholder: hasPassword ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE,
});
@@ -269,6 +294,7 @@
getShareProperties: function() {
return {
unshareLabel: t('core', 'Unshare'),
+ addNoteLabel: t('core', 'Set share note'),
canShareLabel: t('core', 'Can reshare'),
canEditLabel: t('core', 'Can edit'),
createPermissionLabel: t('core', 'Can create'),
@@ -435,6 +461,9 @@
this._renderPermissionChange = false;
+ // new note autosize
+ autosize(this.$el.find('.share-note-form .share-note'));
+
this.delegateEvents();
return this;
@@ -470,6 +499,88 @@
return this._popoverMenuTemplate(data);
},
+ showNoteForm: function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var $element = $(event.target);
+ var $menu = $element.closest('li');
+ var $form = $menu.next('li.share-note-form');
+
+ // show elements
+ $menu.find('.share-note-delete').toggle();
+ $form.toggleClass('hidden');
+ $form.find('textarea').focus();
+ },
+
+ deleteNote: function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var self = this;
+ var $element = $(event.target);
+ var $li = $element.closest('li[data-share-id]');
+ var shareId = $li.data('share-id');
+ var $menu = $element.closest('li');
+ var $form = $menu.next('li.share-note-form');
+
+ console.log($form.find('.share-note'));
+ $form.find('.share-note').val('');
+
+ $form.addClass('hidden');
+ $menu.find('.share-note-delete').hide();
+
+ self.sendNote('', shareId, $menu);
+ },
+
+ updateNote: function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ var self = this;
+ var $element = $(event.target);
+ var $li = $element.closest('li[data-share-id]');
+ var shareId = $li.data('share-id');
+ var $form = $element.closest('li.share-note-form');
+ var $menu = $form.prev('li');
+ var message = $form.find('.share-note').val().trim();
+
+ if (message.length < 1) {
+ return;
+ }
+
+ self.sendNote(message, shareId, $menu);
+
+ },
+
+ sendNote: function(note, shareId, $menu) {
+ var $form = $menu.next('li.share-note-form');
+ var $submit = $form.find('input.share-note-submit');
+ var $error = $form.find('input.share-note-error');
+
+ $submit.prop('disabled', true);
+ $menu.find('.icon-loading-small').removeClass('hidden');
+ $menu.find('.icon-edit').hide();
+
+ var complete = function() {
+ $submit.prop('disabled', false);
+ $menu.find('.icon-loading-small').addClass('hidden');
+ $menu.find('.icon-edit').show();
+ };
+ var error = function() {
+ $error.show();
+ setTimeout(function() {
+ $error.hide();
+ }, 3000);
+ };
+
+ // send data
+ $.ajax({
+ method: 'PUT',
+ url: OC.linkToOCS('apps/files_sharing/api/v1/shares',2) + shareId + '?' + OC.buildQueryString({format: 'json'}),
+ data: { note: note },
+ complete : complete,
+ error: error
+ });
+ },
+
onUnshare: function(event) {
event.preventDefault();
event.stopPropagation();
@@ -513,16 +624,21 @@
},
onExpireDateChange: function(event) {
- var element = $(event.target);
- var li = element.closest('li[data-share-id]');
+ var $element = $(event.target);
+ var li = $element.closest('li[data-share-id]');
var shareId = li.data('share-id');
var datePickerClass = '.expirationDateContainer-' + this.cid + '-' + shareId;
var datePicker = $(datePickerClass);
- var state = element.prop('checked');
+ var state = $element.prop('checked');
datePicker.toggleClass('hidden', !state);
if (!state) {
+ // disabled, let's hide the input and
+ // set the expireDate to nothing
+ $element.closest('li').next('li').addClass('hidden');
this.setExpirationDate(shareId, '');
} else {
+ // enabled, show the input and the datepicker
+ $element.closest('li').next('li').removeClass('hidden');
this.showDatePicker(event);
}
@@ -552,7 +668,7 @@
var element = $(event.target);
var li = element.closest('li[data-share-id]');
var shareId = li.data('share-id');
- var passwordContainerClass = '.passwordContainer-' + this.cid + '-' + shareId;
+ var passwordContainerClass = '.passwordMenu-' + this.cid + '-' + shareId;
var passwordContainer = $(passwordContainerClass);
var loading = this.$el.find(passwordContainerClass + ' .icon-loading-small');
var inputClass = '#passwordField-' + this.cid + '-' + shareId;
diff --git a/core/js/sharedialogview.js b/core/js/sharedialogview.js
index d886e45856f..9fa1847d3d5 100644
--- a/core/js/sharedialogview.js
+++ b/core/js/sharedialogview.js
@@ -27,7 +27,6 @@
'{{/if}}' +
'<div class="shareeListView subView"></div>' +
'<div class="linkShareView subView"></div>' +
- '<div class="expirationView subView"></div>' +
'<div class="loading hidden" style="height: 50px"></div>';
/**
@@ -60,9 +59,6 @@
linkShareView: undefined,
/** @type {object} **/
- expirationView: undefined,
-
- /** @type {object} **/
shareeListView: undefined,
/** @type {object} **/
@@ -111,7 +107,6 @@
var subViews = {
resharerInfoView: 'ShareDialogResharerInfoView',
linkShareView: 'ShareDialogLinkShareView',
- expirationView: 'ShareDialogExpirationView',
shareeListView: 'ShareDialogShareeListView'
};
@@ -671,9 +666,6 @@
this.linkShareView.$el = this.$el.find('.linkShareView');
this.linkShareView.render();
- this.expirationView.$el = this.$el.find('.expirationView');
- this.expirationView.render();
-
this.shareeListView.$el = this.$el.find('.shareeListView');
this.shareeListView.render();
diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js
index 93feba9c889..241b9c19c0d 100644
--- a/core/js/shareitemmodel.js
+++ b/core/js/shareitemmodel.js
@@ -343,6 +343,13 @@
/**
* @returns {string}
*/
+ getReshareNote: function() {
+ return this.get('reshare').note;
+ },
+
+ /**
+ * @returns {string}
+ */
getReshareWith: function() {
return this.get('reshare').share_with;
},
@@ -366,6 +373,10 @@
return this._shareExpireDate(shareIndex);
},
+ getNote: function(shareIndex) {
+ return this._shareNote(shareIndex);
+ },
+
/**
* Returns all share entries that only apply to the current item
* (file/folder)
@@ -502,6 +513,15 @@
return date2;
},
+
+ _shareNote: function(shareIndex) {
+ var share = this.get('shares')[shareIndex];
+ if(!_.isObject(share)) {
+ throw "Unknown Share";
+ }
+ return share.note;
+ },
+
/**
* @return {int}
*/
diff --git a/core/js/tests/specs/sharedialoglinkshareview.js b/core/js/tests/specs/sharedialoglinkshareview.js
index 12f5e762cee..9d07dcb479d 100644
--- a/core/js/tests/specs/sharedialoglinkshareview.js
+++ b/core/js/tests/specs/sharedialoglinkshareview.js
@@ -92,7 +92,7 @@ describe('OC.Share.ShareDialogLinkShareView', function () {
var $passwordDiv = view.$el.find('#linkPass');
$passwordText = view.$el.find('.linkPassText');
- $workingIcon = view.$el.find('.linkPass .icon-loading-small');
+ $workingIcon = view.$el.find('.linkPassMenu .icon-loading-small');
sinon.stub(shareModel, 'saveLinkShare');
diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js
index 5fd920a758c..33d3be51440 100644
--- a/core/js/tests/specs/sharedialogviewSpec.js
+++ b/core/js/tests/specs/sharedialogviewSpec.js
@@ -214,188 +214,6 @@ describe('OC.Share.ShareDialogView', function() {
focusStub.restore();
selectStub.restore();
});
- describe('password', function() {
- var slideToggleStub;
-
- beforeEach(function() {
- $('#allowShareWithLink').val('yes');
- configModel.set({
- enforcePasswordForPublicLink: false
- });
-
- slideToggleStub = sinon.stub($.fn, 'slideToggle');
- });
- afterEach(function() {
- slideToggleStub.restore();
- });
-
- it('enforced but toggled does not fire request', function() {
- configModel.set('enforcePasswordForPublicLink', true);
- dialog.render();
-
- dialog.$el.find('.linkCheckbox').click();
-
- // The password linkPass field is shown (slideToggle is called).
- // No request is made yet
- expect(slideToggleStub.callCount).toEqual(1);
- expect(slideToggleStub.getCall(0).thisValue.eq(0).attr('id')).toEqual('linkPass');
- expect(fakeServer.requests.length).toEqual(0);
-
- // Now untoggle share by link
- dialog.$el.find('.linkCheckbox').click();
- dialog.render();
-
- // Password field disappears and no ajax requests have been made
- expect(fakeServer.requests.length).toEqual(0);
- expect(slideToggleStub.callCount).toEqual(2);
- expect(slideToggleStub.getCall(1).thisValue.eq(0).attr('id')).toEqual('linkPass');
- });
- });
- describe('expiration date', function() {
- var shareData;
- var shareItem;
- var clock;
- var expectedMinDate;
-
- beforeEach(function() {
- // pick a fake date
- clock = sinon.useFakeTimers(new Date(2014, 0, 20, 14, 0, 0).getTime());
- expectedMinDate = new Date(2014, 0, 21, 14, 0, 0);
-
- configModel.set({
- enforcePasswordForPublicLink: false,
- isDefaultExpireDateEnabled: false,
- isDefaultExpireDateEnforced: false,
- defaultExpireDate: 7
- });
-
- shareModel.set('linkShare', {
- isLinkShare: true,
- token: 'tehtoken',
- permissions: OC.PERMISSION_READ,
- expiration: null
- });
- });
- afterEach(function() {
- clock.restore();
- });
-
- it('does not check expiration date checkbox when no date was set', function() {
- shareModel.get('linkShare').expiration = null;
- dialog.render();
-
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false);
- expect(dialog.$el.find('.datepicker').val()).toEqual('');
- });
- it('does not check expiration date checkbox for new share', function() {
- dialog.render();
-
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false);
- expect(dialog.$el.find('.datepicker').val()).toEqual('');
- });
- it('checks expiration date checkbox and populates field when expiration date was set', function() {
- shareModel.get('linkShare').expiration = '2014-02-01 00:00:00';
- dialog.render();
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true);
- expect(dialog.$el.find('.datepicker').val()).toEqual('01-02-2014');
- });
- it('sets default date when default date setting is enabled', function() {
- configModel.set('isDefaultExpireDateEnabled', true);
- dialog.render();
- dialog.$el.find('.linkCheckbox').click();
- // here fetch would be called and the server returns the expiration date
- shareModel.get('linkShare').expiration = '2014-1-27 00:00:00';
- dialog.render();
-
- // enabled by default
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true);
- expect(dialog.$el.find('.datepicker').val()).toEqual('27-01-2014');
-
- // disabling is allowed
- dialog.$el.find('[name=expirationCheckbox]').click();
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(false);
- });
- it('enforces default date when enforced date setting is enabled', function() {
- configModel.set({
- isDefaultExpireDateEnabled: true,
- isDefaultExpireDateEnforced: true
- });
- dialog.render();
- dialog.$el.find('.linkCheckbox').click();
- // here fetch would be called and the server returns the expiration date
- shareModel.get('linkShare').expiration = '2014-1-27 00:00:00';
- dialog.render();
-
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true);
- expect(dialog.$el.find('.datepicker').val()).toEqual('27-01-2014');
-
- // disabling is not allowed
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('disabled')).toEqual(true);
- dialog.$el.find('[name=expirationCheckbox]').click();
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true);
- });
- it('enforces default date when enforced date setting is enabled and password is enforced', function() {
- configModel.set({
- enforcePasswordForPublicLink: true,
- isDefaultExpireDateEnabled: true,
- isDefaultExpireDateEnforced: true
- });
- dialog.render();
- dialog.$el.find('.linkCheckbox').click();
- // here fetch would be called and the server returns the expiration date
- shareModel.get('linkShare').expiration = '2014-1-27 00:00:00';
- dialog.render();
-
- //Enter password
- dialog.$el.find('.linkPassText').val('foo');
- dialog.$el.find('.linkPassText').trigger(new $.Event('keyup', {keyCode: 13}));
- fakeServer.requests[0].respond(
- 200,
- { 'Content-Type': 'application/json' },
- JSON.stringify({data: {token: 'xyz'}, status: 'success'})
- );
-
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true);
- expect(dialog.$el.find('.datepicker').val()).toEqual('27-01-2014');
-
- // disabling is not allowed
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('disabled')).toEqual(true);
- dialog.$el.find('[name=expirationCheckbox]').click();
- expect(dialog.$el.find('[name=expirationCheckbox]').prop('checked')).toEqual(true);
- });
- it('sets picker minDate to today and no maxDate by default', function() {
- dialog.render();
- dialog.$el.find('.linkCheckbox').click();
- dialog.$el.find('[name=expirationCheckbox]').click();
- expect($.datepicker._defaults.minDate).toEqual(expectedMinDate);
- expect($.datepicker._defaults.maxDate).toEqual(null);
- });
- it('limits the date range to X days after share time when enforced', function() {
- configModel.set({
- isDefaultExpireDateEnabled: true,
- isDefaultExpireDateEnforced: true
- });
- dialog.render();
- dialog.$el.find('.linkCheckbox').click();
- expect($.datepicker._defaults.minDate).toEqual(expectedMinDate);
- expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0));
- });
- it('limits the date range to X days after share time when enforced, even when redisplayed the next days', function() {
- // item exists, was created two days ago
- var shareItem = shareModel.get('linkShare');
- shareItem.expiration = '2014-1-27';
- // share time has time component but must be stripped later
- shareItem.stime = new Date(2014, 0, 20, 11, 0, 25).getTime() / 1000;
- configModel.set({
- isDefaultExpireDateEnabled: true,
- isDefaultExpireDateEnforced: true
- });
- dialog.render();
- expect($.datepicker._defaults.minDate).toEqual(expectedMinDate);
- expect($.datepicker._defaults.maxDate).toEqual(new Date(2014, 0, 27, 0, 0, 0, 0));
- });
- });
-
});
describe('check for avatar', function() {
beforeEach(function() {
@@ -455,8 +273,8 @@ describe('OC.Share.ShareDialogView', function() {
it('test correct function calls', function() {
expect(avatarStub.calledThrice).toEqual(true);
expect(placeholderStub.callCount).toEqual(4);
- expect(dialog.$('.shareWithList').children().length).toEqual(5);
- expect(dialog.$('.avatar').length).toEqual(6);
+ expect(dialog.$('.shareWithList').children().length).toEqual(6);
+ expect(dialog.$('.avatar').length).toEqual(7);
});
it('test avatar owner', function() {
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 234aa418f56..a060131979d 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -611,6 +611,7 @@ return array(
'OC\\Core\\Migrations\\Version14000Date20180518120534' => $baseDir . '/core/Migrations/Version14000Date20180518120534.php',
'OC\\Core\\Migrations\\Version14000Date20180522074438' => $baseDir . '/core/Migrations/Version14000Date20180522074438.php',
'OC\\Core\\Migrations\\Version14000Date20180626223656' => $baseDir . '/core/Migrations/Version14000Date20180626223656.php',
+ 'OC\\Core\\Migrations\\Version14000Date20180712153140' => $baseDir . '/core/Migrations/Version14000Date20180712153140.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 c54120586f9..4c6c55a59ad 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -641,6 +641,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Migrations\\Version14000Date20180518120534' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180518120534.php',
'OC\\Core\\Migrations\\Version14000Date20180522074438' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180522074438.php',
'OC\\Core\\Migrations\\Version14000Date20180626223656' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180626223656.php',
+ 'OC\\Core\\Migrations\\Version14000Date20180712153140' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180712153140.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/Share/Share.php b/lib/private/Share/Share.php
index 52e0628987b..0a9371d35b8 100644
--- a/lib/private/Share/Share.php
+++ b/lib/private/Share/Share.php
@@ -86,7 +86,6 @@ class Share extends Constants {
);
if(count(self::$backendTypes) === 1) {
Util::addScript('core', 'merged-share-backend');
- \OC_Util::addStyle('core', 'share');
}
return true;
}
diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php
index 5e52156d1d0..9c5d78a5958 100644
--- a/lib/private/Share20/DefaultShareProvider.php
+++ b/lib/private/Share20/DefaultShareProvider.php
@@ -30,8 +30,14 @@
namespace OC\Share20;
use OC\Files\Cache\Cache;
+use OCP\Defaults;
use OCP\Files\Folder;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\Mail\IMailer;
use OCP\Share\IShare;
+use OCP\Share\IShareHelper;
use OCP\Share\IShareProvider;
use OC\Share20\Exception\InvalidShare;
use OC\Share20\Exception\ProviderException;
@@ -67,6 +73,18 @@ class DefaultShareProvider implements IShareProvider {
/** @var IRootFolder */
private $rootFolder;
+ /** @var IMailer */
+ private $mailer;
+
+ /** @var Defaults */
+ private $defaults;
+
+ /** @var IL10N */
+ private $l;
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
/**
* DefaultShareProvider constructor.
*
@@ -74,16 +92,28 @@ class DefaultShareProvider implements IShareProvider {
* @param IUserManager $userManager
* @param IGroupManager $groupManager
* @param IRootFolder $rootFolder
+ * @param IMailer $mailer ;
+ * @param Defaults $defaults
+ * @param IL10N $l
+ * @param IURLGenerator $urlGenerator
*/
public function __construct(
IDBConnection $connection,
IUserManager $userManager,
IGroupManager $groupManager,
- IRootFolder $rootFolder) {
+ IRootFolder $rootFolder,
+ IMailer $mailer,
+ Defaults $defaults,
+ IL10N $l,
+ IURLGenerator $urlGenerator) {
$this->dbConn = $connection;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->rootFolder = $rootFolder;
+ $this->mailer = $mailer;
+ $this->defaults = $defaults;
+ $this->l = $l;
+ $this->urlGenerator = $urlGenerator;
}
/**
@@ -197,6 +227,9 @@ class DefaultShareProvider implements IShareProvider {
* @return \OCP\Share\IShare The share object
*/
public function update(\OCP\Share\IShare $share) {
+
+ $originalShare = $this->getShareById($share->getId());
+
if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
/*
* We allow updating the recipient on user shares.
@@ -211,6 +244,7 @@ class DefaultShareProvider implements IShareProvider {
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
+ ->set('note', $qb->createNamedParameter($share->getNote()))
->execute();
} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
$qb = $this->dbConn->getQueryBuilder();
@@ -222,6 +256,7 @@ class DefaultShareProvider implements IShareProvider {
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
+ ->set('note', $qb->createNamedParameter($share->getNote()))
->execute();
/*
@@ -235,6 +270,7 @@ class DefaultShareProvider implements IShareProvider {
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
+ ->set('note', $qb->createNamedParameter($share->getNote()))
->execute();
/*
@@ -259,9 +295,15 @@ class DefaultShareProvider implements IShareProvider {
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('token', $qb->createNamedParameter($share->getToken()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
+ ->set('note', $qb->createNamedParameter($share->getNote()))
->execute();
}
+ if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
+ $this->propagateNote($share);
+ }
+
+
return $share;
}
@@ -875,6 +917,7 @@ class DefaultShareProvider implements IShareProvider {
->setShareType((int)$data['share_type'])
->setPermissions((int)$data['permissions'])
->setTarget($data['file_target'])
+ ->setNote($data['note'])
->setMailSend((bool)$data['mail_send']);
$shareTime = new \DateTime();
@@ -1227,4 +1270,96 @@ class DefaultShareProvider implements IShareProvider {
return $best;
}
+
+ /**
+ * propagate notes to the recipients
+ *
+ * @param IShare $share
+ * @throws \OCP\Files\NotFoundException
+ */
+ private function propagateNote(IShare $share) {
+ if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
+ $user = $this->userManager->get($share->getSharedWith());
+ $this->sendNote([$user], $share);
+ } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) {
+ $group = $this->groupManager->get($share->getSharedWith());
+ $groupMembers = $group->getUsers();
+ $this->sendNote($groupMembers, $share);
+ }
+ }
+
+ /**
+ * send note by mail
+ *
+ * @param array $recipients
+ * @param IShare $share
+ * @throws \OCP\Files\NotFoundException
+ */
+ private function sendNote(array $recipients, IShare $share) {
+
+ $toList = [];
+
+ foreach ($recipients as $recipient) {
+ /** @var IUser $recipient */
+ $email = $recipient->getEMailAddress();
+ if ($email) {
+ $toList[$email] = $recipient->getDisplayName();
+ }
+ }
+
+ if (!empty($toList)) {
+
+ $filename = $share->getNode()->getName();
+ $initiator = $share->getSharedBy();
+ $note = $share->getNote();
+
+ $initiatorUser = $this->userManager->get($initiator);
+ $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
+ $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
+ $plainHeading = $this->l->t('%1$s shared »%2$s« with you and wants to add:', [$initiatorDisplayName, $filename]);
+ $htmlHeading = $this->l->t('%1$s shared »%2$s« with you and wants to add', [$initiatorDisplayName, $filename]);
+ $message = $this->mailer->createMessage();
+
+ $emailTemplate = $this->mailer->createEMailTemplate('defaultShareProvider.sendNote');
+
+ $emailTemplate->setSubject($this->l->t('»%s« added a note to a file shared with you', [$initiatorDisplayName]));
+ $emailTemplate->addHeader();
+ $emailTemplate->addHeading($htmlHeading, $plainHeading);
+ $emailTemplate->addBodyText(htmlspecialchars($note), $note);
+
+ $link = $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]);
+ $emailTemplate->addBodyButton(
+ $this->l->t('Open »%s«', [$filename]),
+ $link
+ );
+
+
+ // The "From" contains the sharers name
+ $instanceName = $this->defaults->getName();
+ $senderName = $this->l->t(
+ '%1$s via %2$s',
+ [
+ $initiatorDisplayName,
+ $instanceName
+ ]
+ );
+ $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
+ if ($initiatorEmailAddress !== null) {
+ $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
+ $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
+ } else {
+ $emailTemplate->addFooter();
+ }
+
+ if (count($toList) === 1) {
+ $message->setTo($toList);
+ } else {
+ $message->setTo([]);
+ $message->setBcc($toList);
+ }
+ $message->useTemplate($emailTemplate);
+ $this->mailer->send($message);
+ }
+
+ }
}
diff --git a/lib/private/Share20/ProviderFactory.php b/lib/private/Share20/ProviderFactory.php
index e4d34146911..0aacca409d1 100644
--- a/lib/private/Share20/ProviderFactory.php
+++ b/lib/private/Share20/ProviderFactory.php
@@ -81,7 +81,11 @@ class ProviderFactory implements IProviderFactory {
$this->serverContainer->getDatabaseConnection(),
$this->serverContainer->getUserManager(),
$this->serverContainer->getGroupManager(),
- $this->serverContainer->getLazyRootFolder()
+ $this->serverContainer->getLazyRootFolder(),
+ $this->serverContainer->getMailer(),
+ $this->serverContainer->query(Defaults::class),
+ $this->serverContainer->getL10N('sharing'),
+ $this->serverContainer->getURLGenerator()
);
}
diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php
index d7810165dac..e54497c9b55 100644
--- a/lib/private/Share20/Share.php
+++ b/lib/private/Share20/Share.php
@@ -57,6 +57,8 @@ class Share implements \OCP\Share\IShare {
private $shareOwner;
/** @var int */
private $permissions;
+ /** @var string */
+ private $note = '';
/** @var \DateTime */
private $expireDate;
/** @var string */
@@ -311,6 +313,24 @@ class Share implements \OCP\Share\IShare {
/**
* @inheritdoc
*/
+ public function setNote($note) {
+ $this->note = $note;
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getNote() {
+ if (is_string($this->note)) {
+ return $this->note;
+ }
+ return '';
+ }
+
+ /**
+ * @inheritdoc
+ */
public function setExpirationDate($expireDate) {
//TODO checks
diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php
index 1efdf8b441c..fc2e5798e1c 100644
--- a/lib/private/TemplateLayout.php
+++ b/lib/private/TemplateLayout.php
@@ -42,6 +42,7 @@ use OC\Template\JSCombiner;
use OC\Template\JSConfigHelper;
use OC\Template\SCSSCacher;
use OCP\Defaults;
+use OC\AppFramework\Http\Request;
class TemplateLayout extends \OC_Template {
@@ -61,6 +62,9 @@ class TemplateLayout extends \OC_Template {
// yes - should be injected ....
$this->config = \OC::$server->getConfig();
+ if(\OCP\Util::isIE()) {
+ \OC_Util::addStyle('ie');
+ }
// Decide which page we show
if($renderAs == 'user') {
diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php
index b285eb382e7..a581397212a 100644
--- a/lib/private/legacy/util.php
+++ b/lib/private/legacy/util.php
@@ -64,6 +64,7 @@ use OCP\IConfig;
use OCP\IGroupManager;
use OCP\ILogger;
use OCP\IUser;
+use OC\AppFramework\Http\Request;
class OC_Util {
public static $scripts = array();
@@ -1528,4 +1529,13 @@ class OC_Util {
}
}
+ /**
+ * is this Internet explorer ?
+ *
+ * @return boolean
+ */
+ public static function isIe() {
+ return preg_match(Request::USER_AGENT_IE, $_SERVER['HTTP_USER_AGENT']) === 1;
+ }
+
}
diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php
index 870794d6536..5303cde45a6 100644
--- a/lib/public/Share/IShare.php
+++ b/lib/public/Share/IShare.php
@@ -223,6 +223,24 @@ interface IShare {
public function getPermissions();
/**
+ * Attach a note to a share
+ *
+ * @param string $note
+ * @return \OCP\Share\IShare The modified object
+ * @since 14.0.0
+ */
+ public function setNote($note);
+
+ /**
+ * Get note attached to a share
+ *
+ * @return string
+ * @since 14.0.0
+ */
+ public function getNote();
+
+
+ /**
* Set the expiration date
*
* @param null|\DateTime $expireDate
diff --git a/lib/public/Util.php b/lib/public/Util.php
index 27b703ea2d8..9892f4a1ecb 100644
--- a/lib/public/Util.php
+++ b/lib/public/Util.php
@@ -558,4 +558,14 @@ class Util {
}
return self::$needUpgradeCache;
}
+
+ /**
+ * is this Internet explorer ?
+ *
+ * @return boolean
+ * @since 14.0.0
+ */
+ public static function isIe() {
+ return \OC_Util::isIe();
+ }
}
diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php
index 53d365b1b09..ab9fc8fe29c 100644
--- a/tests/acceptance/features/bootstrap/FilesAppContext.php
+++ b/tests/acceptance/features/bootstrap/FilesAppContext.php
@@ -187,21 +187,49 @@ class FilesAppContext implements Context, ActorAwareInterface {
/**
* @return Locator
*/
- public static function shareLinkCheckbox() {
- // forThe()->checkbox("Share link") 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() = 'Share link']")->
+ public static function shareLinkRow() {
+ return Locator::forThe()->id("shareLink")->
descendantOf(self::detailsView())->
+ describedAs("Share link row in the details view in Files app");
+ }
+
+ /**
+ * @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']")->
+ descendantOf(self::shareLinkRow())->
describedAs("Share link checkbox in the details view in Files app");
}
/**
* @return Locator
*/
- public static function shareLinkField() {
- return Locator::forThe()->css(".linkText")->descendantOf(self::detailsView())->
- describedAs("Share link field in the details view in Files app");
+ public static function shareLinkMenuButton() {
+ return Locator::forThe()->css(".share-menu > .icon")->
+ descendantOf(self::shareLinkRow())->
+ describedAs("Share link menu button in the details view in Files app");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function shareLinkMenu() {
+ return Locator::forThe()->css(".share-menu > .menu")->
+ descendantOf(self::shareLinkRow())->
+ describedAs("Share link menu in the details view in Files app");
+ }
+
+ /**
+ * @return Locator
+ */
+ public static function copyUrlMenuItem() {
+ return Locator::forThe()->xpath("//a[normalize-space() = 'Copy URL']")->
+ descendantOf(self::shareLinkMenu())->
+ describedAs("Copy URL menu item in the share link menu in the details view in Files app");
}
/**
@@ -212,7 +240,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
// that would return the radio button itself, but the element that the
// user interacts with is the label.
return Locator::forThe()->xpath("//label[normalize-space() = 'Allow upload and editing']")->
- descendantOf(self::detailsView())->
+ descendantOf(self::shareLinkMenu())->
describedAs("Allow upload and editing radio button in the details view in Files app");
}
@@ -224,7 +252,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
// would return the checkbox itself, but the element that the user
// interacts with is the label.
return Locator::forThe()->xpath("//label[normalize-space() = 'Password protect']")->
- descendantOf(self::detailsView())->
+ descendantOf(self::shareLinkMenu())->
describedAs("Password protect checkbox in the details view in Files app");
}
@@ -232,7 +260,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @return Locator
*/
public static function passwordProtectField() {
- return Locator::forThe()->css(".linkPassText")->descendantOf(self::detailsView())->
+ return Locator::forThe()->css(".linkPassText")->descendantOf(self::shareLinkMenu())->
describedAs("Password protect field in the details view in Files app");
}
@@ -240,7 +268,7 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @return Locator
*/
public static function passwordProtectWorkingIcon() {
- return Locator::forThe()->css(".linkPass .icon-loading-small")->descendantOf(self::detailsView())->
+ return Locator::forThe()->css(".linkPassMenu .icon-loading-small")->descendantOf(self::shareLinkMenu())->
describedAs("Password protect working icon in the details view in Files app");
}
@@ -278,17 +306,14 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @Given I write down the shared link
*/
public function iWriteDownTheSharedLink() {
- // The shared link field always exists in the DOM (once the "Sharing"
- // tab is loaded), but its value is the actual shared link only when it
- // is visible.
- if (!WaitFor::elementToBeEventuallyShown(
- $this->actor,
- self::shareLinkField(),
- $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) {
- PHPUnit_Framework_Assert::fail("The shared link was not shown yet after $timeout seconds");
- }
+ $this->showShareLinkMenuIfNeeded();
- $this->actor->getSharedNotebook()["shared link"] = $this->actor->find(self::shareLinkField())->getValue();
+ $this->actor->find(self::copyUrlMenuItem(), 2)->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");
}
/**
@@ -313,14 +338,18 @@ class FilesAppContext implements Context, ActorAwareInterface {
* @When I set the shared link as editable
*/
public function iSetTheSharedLinkAsEditable() {
- $this->actor->find(self::allowUploadAndEditingRadioButton(), 10)->click();
+ $this->showShareLinkMenuIfNeeded();
+
+ $this->actor->find(self::allowUploadAndEditingRadioButton(), 2)->click();
}
/**
* @When I protect the shared link with the password :password
*/
public function iProtectTheSharedLinkWithThePassword($password) {
- $this->actor->find(self::passwordProtectCheckbox(), 10)->click();
+ $this->showShareLinkMenuIfNeeded();
+
+ $this->actor->find(self::passwordProtectCheckbox(), 2)->click();
$this->actor->find(self::passwordProtectField(), 2)->setValue($password . "\r");
}
@@ -460,4 +489,17 @@ class FilesAppContext implements Context, ActorAwareInterface {
$this->iSeeThatTheWorkingIconForPasswordProtectIsEventuallyNotShown();
}
+ private function showShareLinkMenuIfNeeded() {
+ // In some cases the share menu is hidden after clicking on an action of
+ // the menu. Therefore, if the menu is visible, wait a little just in
+ // case it is in the process of being hidden due to a previous action,
+ // in which case it is shown again.
+ if (WaitFor::elementToBeEventuallyNotShown(
+ $this->actor,
+ self::shareLinkMenu(),
+ $timeout = 2 * $this->actor->getFindTimeoutMultiplier())) {
+ $this->actor->find(self::shareLinkMenuButton(), 10)->click();
+ }
+ }
+
}
diff --git a/tests/lib/Share20/DefaultShareProviderTest.php b/tests/lib/Share20/DefaultShareProviderTest.php
index 230c8db40ce..19f37160627 100644
--- a/tests/lib/Share20/DefaultShareProviderTest.php
+++ b/tests/lib/Share20/DefaultShareProviderTest.php
@@ -22,15 +22,19 @@
namespace Test\Share20;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Defaults;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\IDBConnection;
use OCP\IGroup;
+use OCP\IL10N;
+use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IGroupManager;
use OCP\Files\IRootFolder;
use OC\Share20\DefaultShareProvider;
+use OCP\Mail\IMailer;
use OCP\Share\IShare;
/**
@@ -56,11 +60,27 @@ class DefaultShareProviderTest extends \Test\TestCase {
/** @var DefaultShareProvider */
protected $provider;
+ /** @var \PHPUnit_Framework_MockObject_MockObject|IMailer */
+ protected $mailer;
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject|IL10N */
+ protected $l10n;
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject|Defaults */
+ protected $defaults;
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject|IURLGenerator */
+ protected $urlGenerator;
+
public function setUp() {
$this->dbConn = \OC::$server->getDatabaseConnection();
$this->userManager = $this->createMock(IUserManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->rootFolder = $this->createMock(IRootFolder::class);
+ $this->mailer = $this->createMock(IMailer::class);
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->defaults = $this->getMockBuilder(Defaults::class)->disableOriginalConstructor()->getMock();
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->userManager->expects($this->any())->method('userExists')->willReturn(true);
@@ -71,7 +91,11 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->dbConn,
$this->userManager,
$this->groupManager,
- $this->rootFolder
+ $this->rootFolder,
+ $this->mailer,
+ $this->defaults,
+ $this->l10n,
+ $this->urlGenerator
);
}
@@ -403,6 +427,10 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->userManager,
$this->groupManager,
$this->rootFolder,
+ $this->mailer,
+ $this->defaults,
+ $this->l10n,
+ $this->urlGenerator
])
->setMethods(['getShareById'])
->getMock();
@@ -493,6 +521,10 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->userManager,
$this->groupManager,
$this->rootFolder,
+ $this->mailer,
+ $this->defaults,
+ $this->l10n,
+ $this->urlGenerator
])
->setMethods(['getShareById'])
->getMock();
@@ -2368,7 +2400,11 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->dbConn,
$userManager,
$groupManager,
- $rootFolder
+ $rootFolder,
+ $this->mailer,
+ $this->defaults,
+ $this->l10n,
+ $this->urlGenerator
);
$password = md5(time());
@@ -2461,7 +2497,11 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->dbConn,
$userManager,
$groupManager,
- $rootFolder
+ $rootFolder,
+ $this->mailer,
+ $this->defaults,
+ $this->l10n,
+ $this->urlGenerator
);
$u1 = $userManager->createUser('testShare1', 'test');
@@ -2552,7 +2592,11 @@ class DefaultShareProviderTest extends \Test\TestCase {
$this->dbConn,
$userManager,
$groupManager,
- $rootFolder
+ $rootFolder,
+ $this->mailer,
+ $this->defaults,
+ $this->l10n,
+ $this->urlGenerator
);
$u1 = $userManager->createUser('testShare1', 'test');
diff --git a/version.php b/version.php
index 5c288428c13..c18ca01286b 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(14, 0, 0, 10);
+$OC_Version = array(14, 0, 0, 11);
// The human readable string
$OC_VersionString = '14.0.0 alpha';