Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions css/src/style.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@include icon-black-white('circle', 'tasks', 1);

/**
* rules for app-navigation
*/
Expand Down
1 change: 1 addition & 0 deletions img/circle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@vue/test-utils": "^1.0.0-beta.33",
"cdav-library": "github:nextcloud/cdav-library",
"color-convert": "^2.0.1",
"debounce": "^1.2.0",
"ical.js": "~1.4.0",
"jstimezonedetect": "",
"linkify-it": "~2.2.0",
Expand Down
161 changes: 134 additions & 27 deletions src/components/AppNavigation/CalendarShare.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<!--
@copyright Copyright (c) 2018 Team Popcorn <[email protected]>
@copyright Copyright (c) 2019 Georg Ehrke <[email protected]>
@copyright Copyright (c) 2019 Jakob Röhrl <[email protected]>
@copyright Copyright (c) 2020 Raimund Schlüßler <[email protected]>

@author Team Popcorn <[email protected]>
@author Georg Ehrke <[email protected]>
@author Jakob Röhrl <[email protected]>
@author Raimund Schlüßler <[email protected]>

@license GNU AGPL version 3 or any later version
Expand Down Expand Up @@ -39,7 +44,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
track-by="user"
label="user"
@search-change="findSharee"
@input="shareCalendar" />
@change="shareCalendar" />
</li>
<!-- list of user or groups calendar is shared with -->
<CalendarSharee v-for="sharee in calendar.shares"
Expand All @@ -51,11 +56,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>

<script>
import CalendarSharee from './CalendarSharee'
import client from '../../services/cdav'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'

import CalendarSharee from './CalendarSharee'
// import debounce from 'debounce'
import HttpClient from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import debounce from 'debounce'

export default {
name: 'CalendarShare',
Expand Down Expand Up @@ -95,48 +102,148 @@ export default {
* Share calendar
*
* @param {Object} data destructuring object
* @param {string} data.user the userId
* @param {string} data.displayName the displayName
* @param {string} data.uri the sharing principalScheme uri
* @param {boolean} data.isGroup is this a group ?
* @param {String} data.user the userId
* @param {String} data.displayName the displayName
* @param {String} data.uri the sharing principalScheme uri
* @param {Boolean} data.isGroup is this a group ?
* @param {Boolean} data.isCircle is this a circle?
*/
shareCalendar({ user, displayName, uri, isGroup }) {
shareCalendar({ user, displayName, uri, isGroup, isCircle }) {
const calendar = this.calendar
uri = decodeURI(uri)
user = decodeURI(user)
this.$store.dispatch('shareCalendar', { calendar, user, displayName, uri, isGroup })
this.$store.dispatch('shareCalendar', { calendar, user, displayName, uri, isGroup, isCircle })
},

/**
* Use the cdav client call to find matches to the query from the existing Users & Groups
*
* @param {string} query The query string
* @param {String} query
*/
async findSharee(query) {
findSharee: debounce(async function(query) {
const hiddenPrincipalSchemes = []
const hiddenUrls = []
this.calendar.shares.forEach((share) => {
hiddenPrincipalSchemes.push(share.uri)
})
if (this.$store.getters.getCurrentUserPrincipal) {
hiddenUrls.push(this.$store.getters.getCurrentUserPrincipal.url)
}
if (this.calendar.owner) {
hiddenUrls.push(this.calendar.owner)
}

this.isLoading = true
this.usersOrGroups = []

if (query.length > 0) {
const results = await client.principalPropertySearchByDisplayname(query)
this.usersOrGroups = results.reduce((list, result) => {
if (['GROUP', 'INDIVIDUAL'].indexOf(result.calendarUserType) > -1
&& !this.calendar.shares.some((share) => share.uri === result.principalScheme)) {
const isGroup = result.calendarUserType === 'GROUP'
list.push({
user: result[isGroup ? 'groupId' : 'userId'],
displayName: result.displayname,
icon: isGroup ? 'icon-group' : 'icon-user',
uri: result.principalScheme,
isGroup,
})
}
return list
}, [])
const davPromise = this.findShareesFromDav(query, hiddenPrincipalSchemes, hiddenUrls)
const ocsPromise = this.findShareesFromCircles(query, hiddenPrincipalSchemes, hiddenUrls)

const [davResults, ocsResults] = await Promise.all([davPromise, ocsPromise])
this.usersOrGroups = [
...davResults,
...ocsResults,
]

this.isLoading = false
this.inputGiven = true
} else {
this.inputGiven = false
this.isLoading = false
}
}, 500),
/**
*
* @param {String} query The search query
* @param {String[]} hiddenPrincipals A list of principals to exclude from search results
* @param {String[]} hiddenUrls A list of urls to exclude from search results
* @returns {Promise<Object[]>}
*/
async findShareesFromDav(query, hiddenPrincipals, hiddenUrls) {
let results
try {
results = await client.principalPropertySearchByDisplayname(query)
} catch (error) {
return []
}

return results.reduce((list, result) => {
if (hiddenPrincipals.includes(decodeURI(result.principalScheme))) {
return list
}
if (hiddenUrls.includes(result.url)) {
return list
}

// Don't show resources and rooms
if (!['GROUP', 'INDIVIDUAL'].includes(result.calendarUserType)) {
return list
}

const isGroup = result.calendarUserType === 'GROUP'
list.push({
user: result[isGroup ? 'groupId' : 'userId'],
displayName: result.displayname,
icon: isGroup ? 'icon-group' : 'icon-user',
uri: result.principalScheme,
isGroup,
isCircle: false,
isNoUser: isGroup,
search: query,
})
return list
}, [])
},
/**
*
* @param {String} query The search query
* @param {String[]} hiddenPrincipals A list of principals to exclude from search results
* @param {String[]} hiddenUrls A list of urls to exclude from search results
* @returns {Promise<Object[]>}
*/
async findShareesFromCircles(query, hiddenPrincipals, hiddenUrls) {
let results
try {
results = await HttpClient.get(generateOcsUrl('apps/files_sharing/api/v1') + 'sharees', {
params: {
format: 'json',
search: query,
perPage: 200,
itemType: 'principals',
},
})
} catch (error) {
return []
}

if (results.data.ocs.meta.status === 'failure') {
return []
}
let circles = []
if (Array.isArray(results.data.ocs.data.circles)) {
circles = circles.concat(results.data.ocs.data.circles)
}
if (Array.isArray(results.data.ocs.data.exact.circles)) {
circles = circles.concat(results.data.ocs.data.exact.circles)
}

if (circles.length === 0) {
return []
}
Comment on lines +220 to +233
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really need an api for this....

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it's a bit messy and also leads to errors, see nextcloud/calendar#2220.
But that's how it is for now.


return circles.filter((circle) => {
return !hiddenPrincipals.includes('principal:principals/circles/' + circle.value.shareWith)
}).map(circle => ({
user: circle.label,
displayName: circle.label,
icon: 'icon-circle',
uri: 'principal:principals/circles/' + circle.value.shareWith,
isGroup: false,
isCircle: true,
isNoUser: true,
search: query,
}))
},
},
}
Expand Down
19 changes: 14 additions & 5 deletions src/store/calendars.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,20 @@ export function mapDavCollectionToCalendar(calendar, currentUserPrincipal) {
*/
export function mapDavShareeToSharee(sharee) {
const id = sharee.href.split('/').slice(-1)[0]
const name = sharee['common-name']
let name = sharee['common-name']
? sharee['common-name']
: id
: sharee.href

if (sharee.href.startsWith('principal:principals/groups/') && name === sharee.href) {
name = sharee.href.substr(28)
}

return {
displayName: name,
id,
writeable: sharee.access[0].endsWith('read-write'),
isGroup: sharee.href.startsWith('principal:principals/groups/'),
isCircle: sharee.href.startsWith('principal:principals/circles/'),
uri: sharee.href,
}
}
Expand Down Expand Up @@ -391,14 +397,16 @@ const mutations = {
* @param {String} data.displayName The displayName
* @param {String} data.uri The sharing principalScheme uri
* @param {Boolean} data.isGroup Is this a group ?
* @param {Boolean} data.isCircle Is this a circle?
*/
shareCalendar(state, { calendar, user, displayName, uri, isGroup }) {
shareCalendar(state, { calendar, user, displayName, uri, isGroup, isCircle }) {
calendar = state.calendars.find(search => search.id === calendar.id)
const newSharee = {
displayName,
id: user,
writeable: false,
isGroup,
isCircle,
uri,
}
if (!calendar.shares.some((share) => share.uri === uri)) {
Expand Down Expand Up @@ -686,11 +694,12 @@ const actions = {
* @param {String} data.displayName The displayName
* @param {String} data.uri The sharing principalScheme uri
* @param {Boolean} data.isGroup Is this a group ?
* @param {Boolean} data.isCircle Is this a circle?
*/
async shareCalendar(context, { calendar, user, displayName, uri, isGroup }) {
async shareCalendar(context, { calendar, user, displayName, uri, isGroup, isCircle }) {
// Share calendar with entered group or user
await calendar.dav.share(uri)
context.commit('shareCalendar', { calendar, user, displayName, uri, isGroup })
context.commit('shareCalendar', { calendar, user, displayName, uri, isGroup, isCircle })
},
}

Expand Down