Merge pull request #1025 from nextcloud/consolidate-ncselect

enh: Consolidate user/group search code
This commit is contained in:
Julius Härtl
2024-06-27 12:26:48 +02:00
committed by GitHub
9 changed files with 185 additions and 433 deletions

View File

@ -99,7 +99,7 @@ describe('Manage a table', () => {
cy.get('[data-cy="editTableModal"]').should('not.exist')
cy.get('[data-cy="transferTableModal"]').should('be.visible')
cy.get('[data-cy="transferTableModal"] input[type="search"]').clear().type(targetUserTransfer.userId)
cy.get(`.vs__dropdown-menu [user="${targetUserTransfer.userId}"]`).click()
cy.get(`.vs__dropdown-menu [id="${targetUserTransfer.userId}"]`).click()
cy.get('[data-cy="transferTableButton"]').should('be.enabled').click()
cy.get('.toastify.toast-success').should('be.visible')
cy.get('.app-navigation__list').contains('test table').should('not.exist')

View File

@ -186,7 +186,7 @@ export default {
sharing = sharing.filter((share) => getCurrentUser().uid !== share.receiver)
const receivers = sharing.map((share) => {
return {
user: share.receiver,
id: share.receiver,
displayName: share.receiver_display_name,
icon: share.receiver_type === 'user' ? 'icon-user' : 'icon-group',
isUser: share.receiver_type === 'user',

View File

@ -10,7 +10,7 @@
</div>
<div class="row">
<h3>{{ t('tables', 'Transfer this application to another user') }}</h3>
<NcUserAndGroupPicker :select-users="true" :select-groups="false" :new-owner-user-id.sync="newOwnerId" />
<NcUserPicker :select-users="true" :select-groups="false" :selected-user-id.sync="newOwnerId" />
</div>
<div class="row">
<div class="fix-col-4 space-T end">
@ -28,7 +28,7 @@ import { NcModal, NcButton } from '@nextcloud/vue'
import { showSuccess } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/dist/index.css'
import permissionsMixin from '../../shared/components/ncTable/mixins/permissionsMixin.js'
import NcUserAndGroupPicker from '../../shared/components/ncUserAndGroupPicker/NcUserAndGroupPicker.vue'
import NcUserPicker from '../../shared/components/ncUserPicker/NcUserPicker.vue'
import { mapGetters, mapState } from 'vuex'
import { getCurrentUser } from '@nextcloud/auth'
@ -37,7 +37,7 @@ export default {
components: {
NcModal,
NcButton,
NcUserAndGroupPicker,
NcUserPicker,
},
mixins: [permissionsMixin],
props: {

View File

@ -10,11 +10,11 @@
</div>
<div class="row">
<h3>{{ t('tables', 'Transfer this table to another user') }}</h3>
<NcUserAndGroupPicker :select-users="true" :select-groups="false" :new-owner-user-id.sync="newOwnerUserId" />
<NcUserPicker :select-users="true" :select-groups="false" :selected-user-id.sync="selectedUserId" />
</div>
<div class="row">
<div class="fix-col-4 space-T end">
<NcButton type="warning" :disabled="newOwnerUserId === ''" data-cy="transferTableButton" @click="transferMe">
<NcButton type="warning" :disabled="selectedUserId === ''" data-cy="transferTableButton" @click="transferMe">
{{ t('tables', 'Transfer') }}
</NcButton>
</div>
@ -28,7 +28,7 @@ import { NcModal, NcButton } from '@nextcloud/vue'
import { showSuccess } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/dist/index.css'
import permissionsMixin from '../../shared/components/ncTable/mixins/permissionsMixin.js'
import NcUserAndGroupPicker from '../../shared/components/ncUserAndGroupPicker/NcUserAndGroupPicker.vue'
import NcUserPicker from '../../shared/components/ncUserPicker/NcUserPicker.vue'
import { mapGetters } from 'vuex'
import { getCurrentUser } from '@nextcloud/auth'
@ -37,7 +37,7 @@ export default {
components: {
NcModal,
NcButton,
NcUserAndGroupPicker,
NcUserPicker,
},
mixins: [permissionsMixin],
props: {
@ -53,7 +53,7 @@ export default {
data() {
return {
loading: false,
newOwnerUserId: '',
selectedUserId: '',
}
},
computed: {
@ -84,9 +84,9 @@ export default {
if (this.activeTable) {
activeTableId = !this.isView ? this.activeTable.id : null
}
const res = await this.$store.dispatch('transferTable', { id: this.table.id, data: { newOwnerUserId: this.newOwnerUserId } })
const res = await this.$store.dispatch('transferTable', { id: this.table.id, data: { newOwnerUserId: this.selectedUserId } })
if (res) {
showSuccess(t('tables', 'Table "{emoji}{table}" transferred to {user}', { emoji: this.table?.emoji ? this.table?.emoji + ' ' : '', table: this.table?.title, user: this.newOwnerUserId }))
showSuccess(t('tables', 'Table "{emoji}{table}" transferred to {user}', { emoji: this.table?.emoji ? this.table?.emoji + ' ' : '', table: this.table?.title, user: this.selectedUserId }))
if (transferId === activeTableId) {
await this.$router.push('/').catch(err => err)

View File

@ -25,23 +25,11 @@
<template>
<div class="row space-B">
<h3>{{ t('tables', 'Share with accounts or groups') }}</h3>
<NcSelect id="ajax"
style="width: 100%;"
:clear-on-select="true"
:hide-selected="true"
:internal-search="false"
:loading="loading"
:options="options"
:placeholder="t('tables', 'User or group name …')"
:preselect-first="true"
:preserve-search="true"
:searchable="true"
:user-select="true"
:get-option-key="(option) => option.key"
:aria-label-combobox="t('tables', 'User or group name …')"
label="displayName"
@search="asyncFind"
@input="addShare">
<NcSelect id="ajax" style="width: 100%;" :clear-on-select="true" :hide-selected="true" :internal-search="false"
:loading="loading" :options="options" :placeholder="t('tables', 'User or group name …')"
:preselect-first="true" :preserve-search="true" :searchable="true" :user-select="true"
:get-option-key="(option) => option.key" :aria-label-combobox="t('tables', 'User or group name …')"
label="displayName" @search="asyncFind" @input="addShare">
<template #no-options>
{{ t('tables', 'No recommendations. Start typing.') }}
</template>
@ -54,13 +42,13 @@
<script>
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import debounce from 'debounce'
import { NcSelect } from '@nextcloud/vue'
import { mapState } from 'vuex'
import formatting from '../../../shared/mixins/formatting.js'
import ShareTypes from '../../../shared/mixins/shareTypesMixin.js'
import searchUserGroup from '../../../shared/mixins/searchUserGroup.js'
import { showError } from '@nextcloud/dialogs'
export default {
name: 'ShareForm',
@ -68,66 +56,25 @@ export default {
NcSelect,
},
mixins: [ShareTypes, formatting],
mixins: [formatting, ShareTypes, searchUserGroup],
props: {
shares: {
type: Array,
default: () => ([]),
},
},
data() {
return {
query: '',
loading: false,
minSearchStringLength: 1,
maxAutocompleteResults: 20,
// Search data
recommendations: [],
suggestions: [],
}
selectUsers: {
type: Boolean,
default: true,
},
selectGroups: {
type: Boolean,
default: true,
},
},
computed: {
...mapState(['tables', 'tablesLoading', 'showSidebar']),
/**
* Is the search valid ?
*
* @return {boolean}
*/
isValidQuery() {
return this.query && this.query.trim() !== '' && this.query.length > this.minSearchStringLength
},
/**
* Multiselect options. Recommendations by default,
* direct search when search query is valid.
* Filter out existing shares
*
* @return {Array}
*/
options() {
const shareTypes = { 0: 'user', 1: 'group' }
// const shares = [...this.userShares, ...this.groupShares]
const shares = this.shares
if (this.isValidQuery) {
// Filter out existing shares
return this.suggestions.filter(item => !shares.find(share => share.receiver === item.shareWith && share.receiverType === shareTypes[item.shareType]))
}
// Filter out existing shares
return this.recommendations.filter(item => !shares.find(share => share.receiver === item.shareWith && share.receiverType === shareTypes[item.shareType]))
},
noResultText() {
if (this.loading) {
return t('tables', 'Searching …')
}
return t('tables', 'No elements found.')
},
},
mounted() {
@ -139,128 +86,61 @@ export default {
this.$emit('add', share)
},
async asyncFind(query) {
// save current query to check if we display
// recommendations or search results
this.query = query.trim()
if (this.isValidQuery) {
// start loading now to have proper ux feedback
// during the debounce
this.loading = true
await this.debounceGetSuggestions(query)
}
},
/**
* Get suggestions
*
* @param {string} search the search query
*/
async getSuggestions(search) {
this.loading = true
const shareType = [
this.SHARE_TYPES.SHARE_TYPE_USER,
this.SHARE_TYPES.SHARE_TYPE_GROUP,
]
const request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees'), {
params: {
format: 'json',
itemType: 'file',
search,
perPage: this.maxAutocompleteResults,
shareType,
},
})
const data = request.data.ocs.data
const exact = data.exact
data.exact = [] // removing exact from general results
// flatten array of arrays
const rawExactSuggestions = Object.values(exact).reduce((arr, elem) => arr.concat(elem), [])
const rawSuggestions = Object.values(data).reduce((arr, elem) => arr.concat(elem), [])
// remove invalid data and format to user-select layout
const exactSuggestions = this.filterOutUnwantedShares(rawExactSuggestions)
.map(share => this.formatForMultiselect(share))
// sort by type so we can get user&groups first...
.sort((a, b) => a.shareType - b.shareType)
const suggestions = this.filterOutUnwantedShares(rawSuggestions)
.map(share => this.formatForMultiselect(share))
// sort by type so we can get user&groups first...
.sort((a, b) => a.shareType - b.shareType)
this.suggestions = exactSuggestions.concat(suggestions)
this.loading = false
// console.info('suggestions', this.suggestions)
},
/**
* Debounce getSuggestions
*
* @param {...*} args the arguments
*/
debounceGetSuggestions: debounce(function(...args) {
this.getSuggestions(...args)
}, 300),
/**
* Get the sharing recommendations
*/
async getRecommendations() {
this.loading = true
const request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees_recommended'), {
params: {
format: 'json',
itemType: 'file',
},
})
try {
const request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees_recommended'), {
params: {
format: 'json',
itemType: 'file',
},
})
const exact = request.data.ocs.data.exact
const exact = request.data.ocs.data.exact
// flatten array of arrays
const rawRecommendations = Object.values(exact).reduce((arr, elem) => arr.concat(elem), [])
// flatten array of arrays
let rawRecommendations = Object.values(exact).reduce((arr, elem) => arr.concat(elem), [])
console.info('recommendations', rawRecommendations)
// remove invalid data and format to user-select layout
this.recommendations = this.filterOutUnwantedShares(rawRecommendations)
.map(share => this.formatForMultiselect(share))
this.loading = false
console.info('recommendations', this.recommendations)
rawRecommendations = rawRecommendations.map(result => {
return this.formatRecommendations(result)
})
this.recommendations = this.filterOutUnwantedItems(rawRecommendations)
this.loading = false
} catch (err) {
console.debug(err)
showError(t('tables', 'Failed to fetch share recommendations'))
}
},
/**
* Filter out unwanted shares
*
* @param {object[]} shares the array of shares objects
* @return {object}
*/
filterOutUnwantedShares(shares) {
return shares.reduce((arr, share) => {
// only check proper objects
if (typeof share !== 'object') {
return arr
}
filterOutUnwantedItems(items) {
const shareTypesList = this.getShareTypes()
const shareTypes = { 0: 'user', 1: 'group' }
try {
// filter out current user
if (share.value.shareType === this.SHARE_TYPES.SHARE_TYPE_USER
&& share.value.shareWith === getCurrentUser().uid) {
return arr
}
// Filter out current user and sort
items = items.filter((item) => !(item.shareType === this.SHARE_TYPES.SHARE_TYPE_USER && item.shareWith === this.currentUserId)).sort((a, b) => a.shareType - b.shareType)
// ALL GOOD
// let's add the suggestion
arr.push(share)
} catch {
return arr
}
return arr
}, [])
// Filter out non-valid share types
items = items.filter((item) => (shareTypesList.includes(item.shareType)))
// Filter out existing shares
return items.filter(item => !this.shares.find(share => share.receiver === item.shareWith && share.receiverType === shareTypes[item.shareType]))
},
formatResult(result) {
return {
shareWith: result.id,
shareType: result.source.startsWith('users') ? this.SHARE_TYPES.SHARE_TYPE_USER : this.SHARE_TYPES.SHARE_TYPE_GROUP,
user: result.id,
isNoUser: !result.source.startsWith('users'),
displayName: result.label,
icon: result.icon || result.source.startsWith('users') ? 'icon-user' : 'icon-group',
key: result.source + '-' + result.id,
}
},
/**
@ -269,57 +149,26 @@ export default {
* @param {object} result select entry item
* @return {object}
*/
formatForMultiselect(result) {
formatRecommendations(result) {
return {
shareWith: result.value.shareWith,
shareType: result.value.shareType,
user: result.uuid || result.value.shareWith,
isNoUser: result.value.shareType !== this.SHARE_TYPES.SHARE_TYPE_USER,
displayName: result.name || result.label,
icon: this.shareTypeToIcon(result.value.shareType),
icon: result.value.shareType === this.SHARE_TYPES.SHARE_TYPE_USER ? 'icon-user' : 'icon-group',
// Vue unique binding to render within Multiselect's AvatarSelectOption
key: result.uuid || result.value.shareWith + '-' + result.value.shareType + '-' + result.name || result.label,
}
},
/**
* Get the icon based on the share type
*
* @param {number} type the share type
* @return {string} the icon class
*/
shareTypeToIcon(type) {
switch (type) {
case this.SHARE_TYPES.SHARE_TYPE_GUEST:
// default is a user, other icons are here to differenciate
// themselves from it, so let's not display the user icon
// case this.SHARE_TYPES.SHARE_TYPE_REMOTE:
// case this.SHARE_TYPES.SHARE_TYPE_USER:
return 'icon-user'
case this.SHARE_TYPES.SHARE_TYPE_REMOTE_GROUP:
case this.SHARE_TYPES.SHARE_TYPE_GROUP:
return 'icon-group'
case this.SHARE_TYPES.SHARE_TYPE_EMAIL:
return 'icon-mail'
case this.SHARE_TYPES.SHARE_TYPE_CIRCLE:
return 'icon-circle'
case this.SHARE_TYPES.SHARE_TYPE_ROOM:
return 'icon-room'
default:
return ''
}
},
},
}
</script>
<style lang="scss" scoped>
.multiselect {
width: 100% !important;
max-width: 100% !important;
}
</style>

View File

@ -19,14 +19,9 @@
</template>
<script>
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import debounce from 'debounce'
import { NcSelect } from '@nextcloud/vue'
import formatting from '../../../shared/mixins/formatting.js'
import ShareTypes from '../../mixins/shareTypesMixin.js'
import { showError } from '@nextcloud/dialogs'
import searchUserGroup from '../../../shared/mixins/searchUserGroup.js'
import '@nextcloud/dialogs/style.css'
export default {
@ -35,7 +30,7 @@ export default {
NcSelect,
},
mixins: [ShareTypes, formatting],
mixins: [formatting, searchUserGroup],
props: {
receivers: {
@ -54,14 +49,8 @@ export default {
data() {
return {
query: '',
value: '',
loading: false,
minSearchStringLength: 1,
maxAutocompleteResults: 20,
suggestions: [],
preExistingSharees: [...this.receivers],
localSharees: this.receivers.map(userObject => userObject.user),
localSharees: this.receivers.map(userObject => userObject.id),
}
},
@ -71,117 +60,35 @@ export default {
return this.localSharees
},
set(v) {
this.localSharees = v.map(userObject => userObject.user)
this.localSharees = v.map(userObject => userObject.id)
this.$emit('update', v)
},
},
isValidQuery() {
return this.query?.trim() && this.query.length >= this.minSearchStringLength
},
options() {
if (this.isValidQuery) {
return this.suggestions
}
return []
},
noResultText() {
if (this.loading) {
return t('tables', 'Searching …')
}
return t('tables', 'No elements found.')
},
userId() {
return getCurrentUser().uid
},
},
methods: {
addShare(selectedItem) {
if (selectedItem) {
this.localValue = selectedItem
} else {
this.localValue = []
this.localValue = selectedItem
},
filterOutUnwantedItems(items) {
// Filter out existing items
items = items.filter((item) => !(item.isUser && this.localSharees.includes(item.id)))
// Filter out current user
return items.filter((item) => !(item.isUser && item.id === this.currentUserId))
},
formatResult(autocompleteResult) {
return {
id: autocompleteResult.id,
displayName: autocompleteResult.label,
icon: autocompleteResult.icon,
isUser: autocompleteResult.source.startsWith('users'),
key: autocompleteResult.source + '-' + autocompleteResult.id,
}
},
getShareTypes() {
const types = []
if (this.selectUsers) {
types.push(this.SHARE_TYPES.SHARE_TYPE_USER)
}
if (this.selectGroups) {
types.push(this.SHARE_TYPES.SHARE_TYPE_GROUP)
}
return types
},
getShareTypeString() {
if (this.selectUsers && !this.selectGroups) {
return 'User'
} else if (!this.selectUsers && this.selectGroups) {
return 'Group'
} else {
return 'User or group'
}
},
getPlaceholder() {
return t('tables', '{shareTypeString}...', { shareTypeString: this.getShareTypeString() })
},
async asyncFind(query) {
this.query = query.trim()
if (this.isValidQuery) {
this.loading = true
await this.debounceGetSuggestions(query)
}
},
async getSuggestions(search) {
this.loading = true
const shareTypes = this.getShareTypes()
let shareTypeQueryString = ''
shareTypes.forEach(shareType => {
shareTypeQueryString += `&shareTypes[]=${shareType}`
})
const url = generateOcsUrl(`core/autocomplete/get?search=${search}${shareTypeQueryString}&limit=${this.maxAutocompleteResults}`)
try {
const res = await axios.get(url)
const rawSuggestions = res.data.ocs.data.map(autocompleteResult => {
return {
user: autocompleteResult.id,
displayName: autocompleteResult.label,
icon: autocompleteResult.icon,
isUser: autocompleteResult.source.startsWith('users'),
key: autocompleteResult.source + '-' + autocompleteResult.id,
}
})
this.suggestions = this.filterOutCurrentUser(rawSuggestions)
this.suggestions = this.filterOutSelectedUsers(this.suggestions)
this.loading = false
} catch (err) {
console.debug(err)
showError(t('tables', 'Failed to fetch {shareTypeString}', { shareTypeString: this.getShareTypeString().toLowerCase() }))
}
},
debounceGetSuggestions: debounce(function(...args) {
this.getSuggestions(...args)
}, 300),
filterOutCurrentUser(list) {
return list.filter((item) => !(item.isUser && item.user === getCurrentUser().uid))
},
filterOutSelectedUsers(list) {
return list.filter((item) => !(item.isUser && this.localSharees.includes(item.user)))
},
},
}
</script>

View File

@ -0,0 +1,80 @@
<template>
<div class="row space-B">
<NcSelect v-model="value" style="width: 100%;" :loading="loading" :options="options"
:placeholder="getPlaceholder()" :searchable="true" :get-option-key="(option) => option.key"
label="displayName" :aria-label-combobox="getPlaceholder()" :user-select="true" @search="asyncFind"
@input="addTransfer">
<template #noResult>
{{ noResultText }}
</template>
</NcSelect>
</div>
</template>
<script>
import { NcSelect } from '@nextcloud/vue'
import formatting from '../../mixins/formatting.js'
import '@nextcloud/dialogs/style.css'
import searchUserGroup from '../../mixins/searchUserGroup.js'
export default {
components: {
NcSelect,
},
mixins: [formatting, searchUserGroup],
props: {
selectedUserId: {
type: String,
default: '',
},
selectUsers: {
type: Boolean,
default: true,
},
selectGroups: {
type: Boolean,
default: false,
},
},
data() {
return {
value: '',
}
},
computed: {
localValue: {
get() {
return this.selectedUserId
},
set(v) {
this.$emit('update:selectedUserId', v?.id)
},
},
},
methods: {
addTransfer(selectedItem) {
this.localValue = selectedItem
},
filterOutUnwantedItems(items) {
return items.filter((item) => !(item.isUser && item.id === this.currentUserId))
},
formatResult(autocompleteResult) {
return {
id: autocompleteResult.id,
displayName: autocompleteResult.label,
icon: autocompleteResult.icon,
isUser: autocompleteResult.source.startsWith('users'),
key: autocompleteResult.source + '-' + autocompleteResult.id,
}
},
},
}
</script>

View File

@ -1,82 +1,33 @@
<template>
<div class="row space-B">
<NcSelect id="transfer-ownership-select" v-model="value" style="width: 100%;" :loading="loading" :options="options"
:placeholder="getPlaceholder()"
:searchable="true" :get-option-key="(option) => option.key"
label="displayName"
:aria-label-combobox="getPlaceholder()" :user-select="true"
@search="asyncFind" @input="addTransfer">
<template #noResult>
{{ noResultText }}
</template>
</NcSelect>
</div>
</template>
<script>
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import debounce from 'debounce'
import { NcSelect } from '@nextcloud/vue'
import formatting from '../../../shared/mixins/formatting.js'
import ShareTypes from '../../mixins/shareTypesMixin.js'
import { showError } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/style.css'
import ShareTypes from './shareTypesMixin.js'
export default {
components: {
NcSelect,
},
mixins: [ShareTypes, formatting],
props: {
newOwnerUserId: {
type: String,
default: '',
},
selectUsers: {
type: Boolean,
default: true,
},
selectGroups: {
type: Boolean,
default: false,
},
},
mixins: [ShareTypes],
data() {
return {
query: '',
value: '',
loading: false,
minSearchStringLength: 1,
maxAutocompleteResults: 20,
suggestions: [],
recommendations: [],
currentUserId: getCurrentUser().uid,
}
},
computed: {
localValue: {
get() {
return this.newOwnerUserId
},
set(v) {
this.$emit('update:newOwnerUserId', v)
},
},
isValidQuery() {
return this.query?.trim() && this.query.length >= this.minSearchStringLength
},
options() {
if (this.isValidQuery) {
return this.suggestions
}
return []
return this.recommendations
},
noResultText() {
@ -86,20 +37,8 @@ export default {
return t('tables', 'No elements found.')
},
userId() {
return getCurrentUser().uid
},
},
methods: {
addTransfer(selectedItem) {
if (selectedItem) {
this.localValue = selectedItem.user
} else {
this.localValue = ''
}
},
getShareTypes() {
const types = []
if (this.selectUsers) {
@ -110,7 +49,6 @@ export default {
}
return types
},
getShareTypeString() {
if (this.selectUsers && !this.selectGroups) {
return 'User'
@ -120,11 +58,9 @@ export default {
return 'User or group'
}
},
getPlaceholder() {
return t('tables', '{shareTypeString}...', { shareTypeString: this.getShareTypeString() })
},
async asyncFind(query) {
this.query = query.trim()
if (this.isValidQuery) {
@ -132,7 +68,6 @@ export default {
await this.debounceGetSuggestions(query)
}
},
async getSuggestions(search) {
this.loading = true
const shareTypes = this.getShareTypes()
@ -144,17 +79,10 @@ export default {
try {
const res = await axios.get(url)
const rawSuggestions = res.data.ocs.data.map(autocompleteResult => {
return {
user: autocompleteResult.id,
displayName: autocompleteResult.label,
icon: autocompleteResult.icon,
isUser: autocompleteResult.source.startsWith('users'),
key: autocompleteResult.source + '-' + autocompleteResult.id,
}
const rawSuggestions = res.data.ocs.data.map(result => {
return this.formatResult(result)
})
this.suggestions = this.filterOutCurrentUser(rawSuggestions)
this.suggestions = this.filterOutUnwantedItems(rawSuggestions)
this.loading = false
} catch (err) {
console.debug(err)
@ -166,17 +94,5 @@ export default {
this.getSuggestions(...args)
}, 300),
filterOutCurrentUser(list) {
return list.filter((item) => !(item.isUser && item.user === getCurrentUser().uid))
},
},
}
</script>
<style lang="scss" scoped>
.multiselect {
width: 100% !important;
max-width: 100% !important;
}
</style>

View File

@ -348,7 +348,7 @@ export default new Vuex.Store({
try {
for (const receiver of receivers) {
share.receiverType = receiver.isUser ? 'user' : 'group'
share.receiver = receiver.user
share.receiver = receiver.id
// Avoid duplicate shares by checking if share exists first
const existingShare = previousReceivers.find((p) => p.receiver === share.receiver && p.receiver_type === share.receiverType)
if (!existingShare) {
@ -363,7 +363,7 @@ export default new Vuex.Store({
for (const previousReceiver of previousReceivers) {
const currentShare = receivers.find((r) => {
const receiverType = r.isUser ? 'user' : 'group'
return r.user === previousReceiver.receiver && receiverType === previousReceiver.receiver_type
return r.id === previousReceiver.receiver && receiverType === previousReceiver.receiver_type
})
if (!currentShare) {
await axios.delete(generateUrl('/apps/tables/share/' + previousReceiver.share_id))