You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SharingEntry.vue 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <!--
  2. - @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
  3. -
  4. - @author John Molakvoæ <skjnldsv@protonmail.com>
  5. -
  6. - @license GNU AGPL version 3 or any later version
  7. -
  8. - This program is free software: you can redistribute it and/or modify
  9. - it under the terms of the GNU Affero General Public License as
  10. - published by the Free Software Foundation, either version 3 of the
  11. - License, or (at your option) any later version.
  12. -
  13. - This program is distributed in the hope that it will be useful,
  14. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. - GNU Affero General Public License for more details.
  17. -
  18. - You should have received a copy of the GNU Affero General Public License
  19. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. -
  21. -->
  22. <template>
  23. <li class="sharing-entry">
  24. <Avatar class="sharing-entry__avatar"
  25. :is-no-user="share.type !== SHARE_TYPES.SHARE_TYPE_USER"
  26. :user="share.shareWith"
  27. :display-name="share.shareWithDisplayName"
  28. :tooltip-message="share.type === SHARE_TYPES.SHARE_TYPE_USER ? share.shareWith : ''"
  29. :menu-position="'left'"
  30. :url="share.shareWithAvatar" />
  31. <component :is="share.shareWithLink ? 'a' : 'div'"
  32. v-tooltip.auto="tooltip"
  33. :href="share.shareWithLink"
  34. class="sharing-entry__desc">
  35. <h5>{{ title }}<span v-if="!isUnique" class="sharing-entry__desc-unique"> ({{ share.shareWithDisplayNameUnique }})</span></h5>
  36. <p v-if="hasStatus">
  37. <span>{{ share.status.icon || '' }}</span>
  38. <span>{{ share.status.message || '' }}</span>
  39. </p>
  40. </component>
  41. <Actions
  42. menu-align="right"
  43. class="sharing-entry__actions"
  44. @close="onMenuClose">
  45. <template v-if="share.canEdit">
  46. <!-- edit permission -->
  47. <ActionCheckbox
  48. ref="canEdit"
  49. :checked.sync="canEdit"
  50. :value="permissionsEdit"
  51. :disabled="saving || !canSetEdit">
  52. {{ t('files_sharing', 'Allow editing') }}
  53. </ActionCheckbox>
  54. <!-- create permission -->
  55. <ActionCheckbox
  56. v-if="isFolder"
  57. ref="canCreate"
  58. :checked.sync="canCreate"
  59. :value="permissionsCreate"
  60. :disabled="saving || !canSetCreate">
  61. {{ t('files_sharing', 'Allow creating') }}
  62. </ActionCheckbox>
  63. <!-- delete permission -->
  64. <ActionCheckbox
  65. v-if="isFolder"
  66. ref="canDelete"
  67. :checked.sync="canDelete"
  68. :value="permissionsDelete"
  69. :disabled="saving || !canSetDelete">
  70. {{ t('files_sharing', 'Allow deleting') }}
  71. </ActionCheckbox>
  72. <!-- reshare permission -->
  73. <ActionCheckbox
  74. v-if="config.isResharingAllowed"
  75. ref="canReshare"
  76. :checked.sync="canReshare"
  77. :value="permissionsShare"
  78. :disabled="saving || !canSetReshare">
  79. {{ t('files_sharing', 'Allow resharing') }}
  80. </ActionCheckbox>
  81. <!-- expiration date -->
  82. <ActionCheckbox
  83. v-if="canHaveExpirationDate"
  84. :checked.sync="hasExpirationDate"
  85. :disabled="config.isDefaultInternalExpireDateEnforced || saving"
  86. @uncheck="onExpirationDisable">
  87. {{ config.isDefaultInternalExpireDateEnforced
  88. ? t('files_sharing', 'Expiration date enforced')
  89. : t('files_sharing', 'Set expiration date') }}
  90. </ActionCheckbox>
  91. <ActionInput v-if="canHaveExpirationDate && hasExpirationDate"
  92. ref="expireDate"
  93. v-tooltip.auto="{
  94. content: errors.expireDate,
  95. show: errors.expireDate,
  96. trigger: 'manual'
  97. }"
  98. :class="{ error: errors.expireDate}"
  99. :disabled="saving"
  100. :first-day-of-week="firstDay"
  101. :lang="lang"
  102. :value="share.expireDate"
  103. value-type="format"
  104. icon="icon-calendar-dark"
  105. type="date"
  106. :disabled-date="disabledDate"
  107. @update:value="onExpirationChange">
  108. {{ t('files_sharing', 'Enter a date') }}
  109. </ActionInput>
  110. <!-- note -->
  111. <template v-if="canHaveNote">
  112. <ActionCheckbox
  113. :checked.sync="hasNote"
  114. :disabled="saving"
  115. @uncheck="queueUpdate('note')">
  116. {{ t('files_sharing', 'Note to recipient') }}
  117. </ActionCheckbox>
  118. <ActionTextEditable v-if="hasNote"
  119. ref="note"
  120. v-tooltip.auto="{
  121. content: errors.note,
  122. show: errors.note,
  123. trigger: 'manual'
  124. }"
  125. :class="{ error: errors.note}"
  126. :disabled="saving"
  127. :value="share.newNote || share.note"
  128. icon="icon-edit"
  129. @update:value="onNoteChange"
  130. @submit="onNoteSubmit" />
  131. </template>
  132. </template>
  133. <ActionButton v-if="share.canDelete"
  134. icon="icon-close"
  135. :disabled="saving"
  136. @click.prevent="onDelete">
  137. {{ t('files_sharing', 'Unshare') }}
  138. </ActionButton>
  139. </Actions>
  140. </li>
  141. </template>
  142. <script>
  143. import Avatar from '@nextcloud/vue/dist/Components/Avatar'
  144. import Actions from '@nextcloud/vue/dist/Components/Actions'
  145. import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
  146. import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox'
  147. import ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
  148. import ActionTextEditable from '@nextcloud/vue/dist/Components/ActionTextEditable'
  149. import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
  150. import SharesMixin from '../mixins/SharesMixin'
  151. export default {
  152. name: 'SharingEntry',
  153. components: {
  154. Actions,
  155. ActionButton,
  156. ActionCheckbox,
  157. ActionInput,
  158. ActionTextEditable,
  159. Avatar,
  160. },
  161. directives: {
  162. Tooltip,
  163. },
  164. mixins: [SharesMixin],
  165. data() {
  166. return {
  167. permissionsEdit: OC.PERMISSION_UPDATE,
  168. permissionsCreate: OC.PERMISSION_CREATE,
  169. permissionsDelete: OC.PERMISSION_DELETE,
  170. permissionsRead: OC.PERMISSION_READ,
  171. permissionsShare: OC.PERMISSION_SHARE,
  172. }
  173. },
  174. computed: {
  175. title() {
  176. let title = this.share.shareWithDisplayName
  177. if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_GROUP) {
  178. title += ` (${t('files_sharing', 'group')})`
  179. } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_ROOM) {
  180. title += ` (${t('files_sharing', 'conversation')})`
  181. } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE) {
  182. title += ` (${t('files_sharing', 'remote')})`
  183. } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP) {
  184. title += ` (${t('files_sharing', 'remote group')})`
  185. } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_GUEST) {
  186. title += ` (${t('files_sharing', 'guest')})`
  187. }
  188. return title
  189. },
  190. tooltip() {
  191. if (this.share.owner !== this.share.uidFileOwner) {
  192. const data = {
  193. // todo: strong or italic?
  194. // but the t function escape any html from the data :/
  195. user: this.share.shareWithDisplayName,
  196. owner: this.share.ownerDisplayName,
  197. }
  198. if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_GROUP) {
  199. return t('files_sharing', 'Shared with the group {user} by {owner}', data)
  200. } else if (this.share.type === this.SHARE_TYPES.SHARE_TYPE_ROOM) {
  201. return t('files_sharing', 'Shared with the conversation {user} by {owner}', data)
  202. }
  203. return t('files_sharing', 'Shared with {user} by {owner}', data)
  204. }
  205. return null
  206. },
  207. canHaveNote() {
  208. return !this.isRemoteShare
  209. },
  210. canHaveExpirationDate() {
  211. return !this.isRemoteShare
  212. },
  213. isRemoteShare() {
  214. return this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE
  215. || this.share.type === this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP
  216. },
  217. /**
  218. * Can the sharer set whether the sharee can edit the file ?
  219. *
  220. * @returns {boolean}
  221. */
  222. canSetEdit() {
  223. // If the owner revoked the permission after the resharer granted it
  224. // the share still has the permission, and the resharer is still
  225. // allowed to revoke it too (but not to grant it again).
  226. return (this.fileInfo.sharePermissions & OC.PERMISSION_UPDATE) || this.canEdit
  227. },
  228. /**
  229. * Can the sharer set whether the sharee can create the file ?
  230. *
  231. * @returns {boolean}
  232. */
  233. canSetCreate() {
  234. // If the owner revoked the permission after the resharer granted it
  235. // the share still has the permission, and the resharer is still
  236. // allowed to revoke it too (but not to grant it again).
  237. return (this.fileInfo.sharePermissions & OC.PERMISSION_CREATE) || this.canCreate
  238. },
  239. /**
  240. * Can the sharer set whether the sharee can delete the file ?
  241. *
  242. * @returns {boolean}
  243. */
  244. canSetDelete() {
  245. // If the owner revoked the permission after the resharer granted it
  246. // the share still has the permission, and the resharer is still
  247. // allowed to revoke it too (but not to grant it again).
  248. return (this.fileInfo.sharePermissions & OC.PERMISSION_DELETE) || this.canDelete
  249. },
  250. /**
  251. * Can the sharer set whether the sharee can reshare the file ?
  252. *
  253. * @returns {boolean}
  254. */
  255. canSetReshare() {
  256. // If the owner revoked the permission after the resharer granted it
  257. // the share still has the permission, and the resharer is still
  258. // allowed to revoke it too (but not to grant it again).
  259. return (this.fileInfo.sharePermissions & OC.PERMISSION_SHARE) || this.canReshare
  260. },
  261. /**
  262. * Can the sharee edit the shared file ?
  263. */
  264. canEdit: {
  265. get() {
  266. return this.share.hasUpdatePermission
  267. },
  268. set(checked) {
  269. this.updatePermissions({ isEditChecked: checked })
  270. },
  271. },
  272. /**
  273. * Can the sharee create the shared file ?
  274. */
  275. canCreate: {
  276. get() {
  277. return this.share.hasCreatePermission
  278. },
  279. set(checked) {
  280. this.updatePermissions({ isCreateChecked: checked })
  281. },
  282. },
  283. /**
  284. * Can the sharee delete the shared file ?
  285. */
  286. canDelete: {
  287. get() {
  288. return this.share.hasDeletePermission
  289. },
  290. set(checked) {
  291. this.updatePermissions({ isDeleteChecked: checked })
  292. },
  293. },
  294. /**
  295. * Can the sharee reshare the file ?
  296. */
  297. canReshare: {
  298. get() {
  299. return this.share.hasSharePermission
  300. },
  301. set(checked) {
  302. this.updatePermissions({ isReshareChecked: checked })
  303. },
  304. },
  305. /**
  306. * Is the current share a folder ?
  307. * @returns {boolean}
  308. */
  309. isFolder() {
  310. return this.fileInfo.type === 'dir'
  311. },
  312. /**
  313. * Does the current share have an expiration date
  314. * @returns {boolean}
  315. */
  316. hasExpirationDate: {
  317. get() {
  318. return this.config.isDefaultInternalExpireDateEnforced || !!this.share.expireDate
  319. },
  320. set(enabled) {
  321. this.share.expireDate = enabled
  322. ? this.config.defaultInternalExpirationDateString !== ''
  323. ? this.config.defaultInternalExpirationDateString
  324. : moment().format('YYYY-MM-DD')
  325. : ''
  326. },
  327. },
  328. dateMaxEnforced() {
  329. return this.config.isDefaultInternalExpireDateEnforced
  330. && moment().add(1 + this.config.defaultInternalExpireDate, 'days')
  331. },
  332. /**
  333. * @returns {bool}
  334. */
  335. hasStatus() {
  336. if (this.share.type !== this.SHARE_TYPES.SHARE_TYPE_USER) {
  337. return false
  338. }
  339. return (typeof this.share.status === 'object' && !Array.isArray(this.share.status))
  340. },
  341. },
  342. methods: {
  343. updatePermissions({ isEditChecked = this.canEdit, isCreateChecked = this.canCreate, isDeleteChecked = this.canDelete, isReshareChecked = this.canReshare } = {}) {
  344. // calc permissions if checked
  345. const permissions = this.permissionsRead
  346. | (isCreateChecked ? this.permissionsCreate : 0)
  347. | (isDeleteChecked ? this.permissionsDelete : 0)
  348. | (isEditChecked ? this.permissionsEdit : 0)
  349. | (isReshareChecked ? this.permissionsShare : 0)
  350. this.share.permissions = permissions
  351. this.queueUpdate('permissions')
  352. },
  353. /**
  354. * Save potential changed data on menu close
  355. */
  356. onMenuClose() {
  357. this.onNoteSubmit()
  358. },
  359. },
  360. }
  361. </script>
  362. <style lang="scss" scoped>
  363. .sharing-entry {
  364. display: flex;
  365. align-items: center;
  366. height: 44px;
  367. &__desc {
  368. display: flex;
  369. flex-direction: column;
  370. justify-content: space-between;
  371. padding: 8px;
  372. line-height: 1.2em;
  373. p {
  374. color: var(--color-text-maxcontrast);
  375. }
  376. &-unique {
  377. color: var(--color-text-maxcontrast);
  378. }
  379. }
  380. &__actions {
  381. margin-left: auto;
  382. }
  383. }
  384. </style>