diff --git a/.eslintrc.js b/.eslintrc.js
index 307f668a..a532df9e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,8 +1,20 @@
module.exports = {
globals: {
- appVersion: true
+ appVersion: true,
+ },
+
+ plugins: ['import'],
+ extends: ['@nextcloud'],
+
+ settings: {
+ 'import/parsers': {
+ '@typescript-eslint/parser': ['.ts', '.tsx'],
+ },
+ 'import/resolver': {
+ typescript: {
+ alwaysTryTypes: true,
+ paths: './tsconfig.json',
+ },
+ },
},
- extends: [
- '@nextcloud'
- ]
}
diff --git a/package.json b/package.json
index db5467c3..d96d2dae 100644
--- a/package.json
+++ b/package.json
@@ -36,13 +36,14 @@
"@mattkrick/sanitize-svg": "^0.3.1",
"@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.6.0",
+ "@nextcloud/capabilities": "^1.0.4",
"@nextcloud/dialogs": "^3.1.2",
"@nextcloud/event-bus": "^1.2.0",
"@nextcloud/initial-state": "^1.2.0",
"@nextcloud/l10n": "^1.4.1",
"@nextcloud/moment": "^1.1.1",
"@nextcloud/paths": "^1.1.2",
- "@nextcloud/router": "^1.2.0",
+ "@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^3.9.0",
"axios": "^0.21.1",
"b64-to-blob": "^1.2.19",
diff --git a/src/components/AppContent/CircleContent.vue b/src/components/AppContent/CircleContent.vue
index d382ae16..164aaf5b 100644
--- a/src/components/AppContent/CircleContent.vue
+++ b/src/components/AppContent/CircleContent.vue
@@ -50,7 +50,7 @@
- {{ t('contacts', 'Joining circle') }}
+ {{ t('contacts', 'Your request to join this circle is pending approval') }}
@@ -76,7 +76,8 @@ import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import CircleDetails from '../CircleDetails'
import MemberList from '../MemberList'
import RouterMixin from '../../mixins/RouterMixin'
-import { MEMBER_LEVEL_NONE } from '../../models/constants'
+import { joinCircle } from '../../services/circles.ts'
+import { showError } from '@nextcloud/dialogs'
export default {
name: 'CircleContent',
@@ -123,14 +124,6 @@ export default {
isEmptyCircle() {
return this.members.length === 0
},
-
- /**
- * Is the current user member of this circle?
- * @returns {boolean}
- */
- isMemberOfCircle() {
- return this.circle.initiator?.level > MEMBER_LEVEL_NONE
- },
},
watch: {
@@ -150,8 +143,17 @@ export default {
/**
* Request to join this circle
*/
- requestJoin() {
+ async requestJoin() {
this.loadingJoin = true
+
+ try {
+ await joinCircle(this.circle.id)
+ } catch (error) {
+ showError(t('contacts', 'Unable to join the circle'))
+ } finally {
+ this.loadingJoin = false
+ }
+
},
},
}
diff --git a/src/components/AppNavigation/CircleNavigationItem.vue b/src/components/AppNavigation/CircleNavigationItem.vue
index 32846d5f..26838678 100644
--- a/src/components/AppNavigation/CircleNavigationItem.vue
+++ b/src/components/AppNavigation/CircleNavigationItem.vue
@@ -41,7 +41,7 @@
{{ copyButtonText }}
@@ -49,7 +49,7 @@
{{ t('contacts', 'Leave circle') }}
{{ joinButtonTitle }}
diff --git a/src/components/MemberList/MemberListItem.vue b/src/components/MembersList/MembersListItem.vue
similarity index 67%
rename from src/components/MemberList/MemberListItem.vue
rename to src/components/MembersList/MembersListItem.vue
index f47495f6..19e67906 100644
--- a/src/components/MemberList/MemberListItem.vue
+++ b/src/components/MembersList/MembersListItem.vue
@@ -22,13 +22,14 @@
+ :user="source.userId"
+ class="members-list__item">
@@ -36,31 +37,26 @@
-
-
-
- {{ t('contacts', 'Back to the menu') }}
-
-
-
- {{ CIRCLES_MEMBER_LEVELS[level] }}
-
-
-
-
- {{ t('contacts', 'Change level') }}
-
-
+
+
+
+ {{ t('contacts', 'Manage level') }}
+
+
+
+ {{ levelChangeLabel(level) }}
+
+
+
+
@@ -78,30 +74,30 @@
diff --git a/src/models/circle.d.ts b/src/models/circle.d.ts
new file mode 100644
index 00000000..6c058f34
--- /dev/null
+++ b/src/models/circle.d.ts
@@ -0,0 +1,142 @@
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ
+ *
+ * @author John Molakvoæ
+ *
+ * @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 .
+ *
+ */
+import Member from './member';
+declare type MemberList = Record;
+export default class Circle {
+ _data: any;
+ _members: MemberList;
+ _owner: Member;
+ _initiator: Member;
+ /**
+ * Creates an instance of Circle
+ */
+ constructor(data: Object);
+ /**
+ * Update inner circle data, owner and initiator
+ */
+ updateData(data: any): void;
+ /**
+ * Circle id
+ */
+ get id(): string;
+ /**
+ * Formatted display name
+ */
+ get displayName(): string;
+ /**
+ * Circle creation date
+ */
+ get creation(): number;
+ /**
+ * Circle description
+ */
+ get description(): string;
+ /**
+ * Circle description
+ */
+ set description(text: string);
+ /**
+ * Circle ini_initiator the current
+ * user info for this circle
+ */
+ get initiator(): Member;
+ /**
+ * Circle ownership
+ */
+ get owner(): Member;
+ /**
+ * Set new circle owner
+ */
+ set owner(owner: Member);
+ /**
+ * Circle members
+ */
+ get members(): MemberList;
+ /**
+ * Define members circle
+ */
+ set members(members: MemberList);
+ /**
+ * Add a member to this circle
+ */
+ addMember(member: Member): void;
+ /**
+ * Remove a member from this circle
+ */
+ deleteMember(member: Member): void;
+ get settings(): any;
+ /**
+ * Circle config
+ */
+ get config(): any;
+ /**
+ * Circle requires invite to be confirmed by moderator or above
+ */
+ get requireJoinAccept(): boolean;
+ /**
+ * Circle can be requested to join
+ */
+ get canJoin(): boolean;
+ /**
+ * Circle is visible to others
+ */
+ get isVisible(): boolean;
+ /**
+ * Circle requires invite to be accepted by the member
+ */
+ get requireInviteAccept(): boolean;
+ /**
+ * Can the initiator add members to this circle?
+ */
+ get isOwner(): boolean;
+ /**
+ * Is the initiator a member of this circle?
+ */
+ get isMember(): boolean;
+ /**
+ * Can the initiator delete this circle?
+ */
+ get canDelete(): boolean;
+ /**
+ * Can the initiator leave this circle?
+ */
+ get canLeave(): boolean;
+ /**
+ * Can the initiator add/remove members to this circle?
+ */
+ get canManageMembers(): boolean;
+ /**
+ * Vue router param
+ */
+ get router(): {
+ name: string;
+ params: {
+ selectedCircle: string;
+ };
+ };
+ /**
+ * Default javascript fallback
+ * Used for sorting as well
+ */
+ toString(): string;
+}
+export {};
diff --git a/src/models/circle.js b/src/models/circle.ts
similarity index 55%
rename from src/models/circle.js
rename to src/models/circle.ts
index 2fdd22ae..b9caf9ff 100644
--- a/src/models/circle.js
+++ b/src/models/circle.ts
@@ -20,38 +20,31 @@
*
*/
-/** @typedef { import('./member') } Member */
-
-import {
- MEMBER_LEVEL_MODERATOR, MEMBER_LEVEL_NONE, MEMBER_LEVEL_OWNER,
- CIRCLE_CONFIG_REQUEST, CIRCLE_CONFIG_INVITE, CIRCLE_CONFIG_OPEN, CIRCLE_CONFIG_VISIBLE,
-} from './constants'
-
import Vue from 'vue'
import Member from './member'
+import { CircleConfigs, MemberLevels } from './constants'
+
+type MemberList = Record
+
export default class Circle {
- _data = {}
- _members = {}
+ _data: any = {}
+ _members: MemberList = {}
+ _owner: Member
+ _initiator: Member
/**
* Creates an instance of Circle
- *
- * @param {Object} data the vcard data as string with proper new lines
- * @param {object} circle the addressbook which the contat belongs to
- * @memberof Circle
*/
- constructor(data) {
+ constructor(data: Object) {
this.updateData(data)
}
/**
* Update inner circle data, owner and initiator
- * @param {Object} data the vcard data as string with proper new lines
- * @memberof Circle
*/
- updateData(data) {
+ updateData(data: any) {
if (typeof data !== 'object') {
throw new Error('Invalid circle')
}
@@ -62,145 +55,119 @@ export default class Circle {
}
this._data = data
- this._data.initiator = new Member(data.initiator, this)
- this._data.owner = new Member(data.owner)
+ this._owner = new Member(data.owner, this)
+
+ if (data.initiator) {
+ this._initiator = new Member(data.initiator, this)
+ }
}
// METADATA -----------------------------------------
/**
* Circle id
- * @readonly
- * @memberof Circle
- * @returns {string}
*/
- get id() {
+ get id(): string {
return this._data.id
}
/**
* Formatted display name
- * @readonly
- * @memberof Circle
- * @returns {string}
*/
- get displayName() {
+ get displayName(): string {
return this._data.displayName
}
/**
* Circle creation date
- * @readonly
- * @memberof Circle
- * @returns {number}
*/
- get creation() {
+ get creation(): number {
return this._data.creation
}
/**
* Circle description
- * @readonly
- * @memberof Circle
- * @returns {string}
*/
- get description() {
+ get description(): string {
return this._data.description
}
/**
* Circle description
- * @param {string} text circle description
- * @memberof Circle
*/
- set description(text) {
+ set description(text: string) {
this._data.description = text
}
// MEMBERSHIP -----------------------------------------
/**
- * Circle initiator. This is the current
+ * Circle ini_initiator the current
* user info for this circle
- * @readonly
- * @memberof Circle
- * @returns {Member}
*/
- get initiator() {
- return this._data.initiator
+ get initiator(): Member {
+ return this._initiator
}
/**
* Circle ownership
- * @readonly
- * @memberof Circle
- * @returns {Member}
*/
- get owner() {
- return this._data.owner
+ get owner(): Member {
+ return this._owner
}
/**
* Set new circle owner
- * @param {Member} owner circle owner
- * @memberof Circle
*/
- set owner(owner) {
+ set owner(owner: Member) {
if (owner.constructor.name !== Member.name) {
throw new Error('Owner must be a Member type')
}
- this._data.owner = owner
+ this._owner = owner
}
/**
* Circle members
- * @readonly
- * @memberof Circle
- * @returns {Member[]}
*/
- get members() {
+ get members(): MemberList {
return this._members
}
/**
* Define members circle
- * @param {Member[]} members the members list
- * @memberof Circle
*/
- set members(members) {
+ set members(members: MemberList) {
this._members = members
}
/**
* Add a member to this circle
- * @param {Member} member the member to add
*/
- addMember(member) {
+ addMember(member: Member) {
if (member.constructor.name !== Member.name) {
throw new Error('Member must be a Member type')
}
- const uid = member.id
- if (this._members[uid]) {
- console.warn('Duplicate member overrided', this._members[uid], member)
+ const singleId = member.singleId
+ if (this._members[singleId]) {
+ console.warn('Ignoring duplicate member', member)
}
- Vue.set(this._members, uid, member)
+ Vue.set(this._members, singleId, member)
}
/**
* Remove a member from this circle
- * @param {Member} member the member to delete
*/
- deleteMember(member) {
+ deleteMember(member: Member) {
if (member.constructor.name !== Member.name) {
throw new Error('Member must be a Member type')
}
- const uid = member.id
- if (!this._members[uid]) {
+ const singleId = member.singleId
+ if (!this._members[singleId]) {
console.warn('The member was not in this circle. Nothing was done.', member)
}
// Delete and clear memory
- Vue.delete(this._members, uid)
+ Vue.delete(this._members, singleId)
}
// CONFIGS --------------------------------------------
@@ -210,9 +177,6 @@ export default class Circle {
/**
* Circle config
- * @readonly
- * @memberof Circle
- * @returns {number}
*/
get config() {
return this._data.config
@@ -220,91 +184,71 @@ export default class Circle {
/**
* Circle requires invite to be confirmed by moderator or above
- * @readonly
- * @memberof Circle
- * @returns {boolean}
*/
get requireJoinAccept() {
- return (this._data.config & CIRCLE_CONFIG_REQUEST) !== 0
+ return (this._data.config & CircleConfigs.VISIBLE) !== 0
}
/**
* Circle can be requested to join
- * @readonly
- * @memberof Circle
- * @returns {boolean}
*/
get canJoin() {
- return (this._data.config & CIRCLE_CONFIG_OPEN) !== 0
+ return (this._data.config & CircleConfigs.OPEN) !== 0
}
/**
* Circle is visible to others
- * @readonly
- * @memberof Circle
- * @returns {boolean}
*/
get isVisible() {
- return (this._data.config & CIRCLE_CONFIG_VISIBLE) !== 0
+ return (this._data.config & CircleConfigs.VISIBLE) !== 0
}
/**
* Circle requires invite to be accepted by the member
- * @readonly
- * @memberof Circle
- * @returns {boolean}
*/
get requireInviteAccept() {
- return (this._data.config & CIRCLE_CONFIG_INVITE) !== 0
+ return (this._data.config & CircleConfigs.INVITE) !== 0
}
// PERMISSIONS SHORTCUTS ------------------------------
/**
* Can the initiator add members to this circle?
- * @readonly
- * @memberof Circle
- * @returns {boolean}
*/
get isOwner() {
- return this.initiator.level === MEMBER_LEVEL_OWNER
+ return this.initiator?.level === MemberLevels.OWNER
}
/**
* Is the initiator a member of this circle?
- * @readonly
- * @memberof Circle
- * @returns {boolean}
*/
get isMember() {
- return this.initiator.level > MEMBER_LEVEL_NONE
+ return this.initiator?.level > MemberLevels.NONE
}
/**
* Can the initiator delete this circle?
- * @readonly
- * @memberof Circle
- * @returns {boolean}
*/
get canDelete() {
return this.isOwner
}
+ /**
+ * Can the initiator leave this circle?
+ */
+ get canLeave() {
+ return this.isMember && !this.isOwner
+ }
+
/**
* Can the initiator add/remove members to this circle?
- * @readonly
- * @memberof Circle
- * @returns {boolean}
*/
get canManageMembers() {
- return this.initiator.level >= MEMBER_LEVEL_MODERATOR
+ return this.initiator?.level >= MemberLevels.MODERATOR
}
// PARAMS ---------------------------------------------
/**
* Vue router param
- * @readonly
- * @memberof Circle
- * @returns {Object}
*/
get router() {
return {
@@ -316,8 +260,6 @@ export default class Circle {
/**
* Default javascript fallback
* Used for sorting as well
- * @memberof Circle
- * @returns {string}
*/
toString() {
return this.displayName
diff --git a/src/models/constants.d.ts b/src/models/constants.d.ts
new file mode 100644
index 00000000..f56e03eb
--- /dev/null
+++ b/src/models/constants.d.ts
@@ -0,0 +1,68 @@
+/**
+ * @copyright Copyright (c) 2021 John Molakvoæ
+ *
+ * @author John Molakvoæ
+ *
+ * @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 .
+ *
+ */
+export declare type CircleConfig = number;
+export declare type MemberLevel = number;
+export declare type MemberType = number;
+export declare const LIST_SIZE = 60;
+export declare const GROUP_ALL_CONTACTS: string;
+export declare const GROUP_NO_GROUP_CONTACTS: string;
+export declare const GROUP_RECENTLY_CONTACTED: string;
+export declare const ROUTE_CIRCLE = "circle";
+export declare const ELLIPSIS_COUNT = 5;
+export declare const CIRCLES_MEMBER_TYPES: {
+ [x: number]: string;
+};
+export declare const CIRCLES_MEMBER_LEVELS: {
+ [x: number]: string;
+};
+export declare const SHARES_TYPES_MEMBER_MAP: {
+ [x: number]: number;
+};
+export declare enum MemberLevels {
+ NONE,
+ MEMBER,
+ MODERATOR,
+ ADMIN,
+ OWNER
+}
+export declare enum MemberTypes {
+ CIRCLE,
+ USER,
+ GROUP,
+ MAIL,
+ CONTACT
+}
+export declare enum CircleConfigs {
+ SYSTEM,
+ VISIBLE,
+ OPEN,
+ INVITE,
+ REQUEST,
+ FRIEND,
+ PROTECTED,
+ NO_OWNER,
+ HIDDEN,
+ BACKEND,
+ ROOT,
+ CIRCLE_INVITE,
+ FEDERATED
+}
diff --git a/src/models/constants.js b/src/models/constants.js
deleted file mode 100644
index 2ec1fd21..00000000
--- a/src/models/constants.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @copyright Copyright (c) 2021 John Molakvoæ
- *
- * @author John Molakvoæ
- *
- * @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 .
- *
- */
-/* eslint-disable no-tabs */
-
-// Dynamic groups
-export const GROUP_ALL_CONTACTS = t('contacts', 'All contacts')
-export const GROUP_NO_GROUP_CONTACTS = t('contacts', 'Not grouped')
-export const GROUP_RECENTLY_CONTACTED = t('contactsinteraction', 'Recently contacted')
-
-// Default max number of items to show in the navigation
-export const ELLIPSIS_COUNT = 5
-
-// Circles member levels
-export const MEMBER_LEVEL_NONE = 0
-export const MEMBER_LEVEL_MEMBER = 1
-export const MEMBER_LEVEL_MODERATOR = 4
-export const MEMBER_LEVEL_ADMIN = 8
-export const MEMBER_LEVEL_OWNER = 9
-
-// Circles member types
-export const MEMBER_TYPE_CIRCLE = 16
-export const MEMBER_TYPE_USER = 1
-export const MEMBER_TYPE_GROUP = 2
-export const MEMBER_TYPE_MAIL = 3
-export const MEMBER_TYPE_CONTACT = 4
-
-// Circles config flags
-export const CIRCLE_CONFIG_SYSTEM = 4 // System Circle (not managed by the official front-end). Meaning some config are limited
-export const CIRCLE_CONFIG_VISIBLE = 8 // Visible to everyone, if not visible, people have to know its name to be able to find it
-export const CIRCLE_CONFIG_OPEN = 16 // Circle is open, people can join
-export const CIRCLE_CONFIG_INVITE = 32 // Adding a member generate an invitation that needs to be accepted
-export const CIRCLE_CONFIG_REQUEST = 64 // Request to join Circles needs to be confirmed by a moderator
-export const CIRCLE_CONFIG_FRIEND = 128 // Members of the circle can invite their friends
-export const CIRCLE_CONFIG_PROTECTED = 256 // Password protected to join/request
-export const CIRCLE_CONFIG_NO_OWNER = 512 // no owner, only members
-export const CIRCLE_CONFIG_HIDDEN = 1024 // hidden from listing, but available as a share entity
-export const CIRCLE_CONFIG_BACKEND = 2048 // Fully hidden, only backend Circles
-export const CIRCLE_CONFIG_ROOT = 4096 // Circle cannot be inside another Circle
-export const CIRCLE_CONFIG_CIRCLE_INVITE = 8192 // Circle must confirm when invited in another circle
-export const CIRCLE_CONFIG_FEDERATED = 16384 // Federated
-
-export const CIRCLES_MEMBER_TYPES = {
- [MEMBER_TYPE_CIRCLE]: t('circles', 'Circle'),
- [MEMBER_TYPE_USER]: t('circles', 'User'),
- [MEMBER_TYPE_GROUP]: t('circles', 'Group'),
- [MEMBER_TYPE_MAIL]: t('circles', 'Mail'),
- [MEMBER_TYPE_CONTACT]: t('circles', 'Contact'),
-}
-
-export const CIRCLES_MEMBER_LEVELS = {
- // [MEMBER_LEVEL_NONE]: t('circles', 'None'),
- [MEMBER_LEVEL_MEMBER]: t('circles', 'Member'),
- [MEMBER_LEVEL_MODERATOR]: t('circles', 'Moderator'),
- [MEMBER_LEVEL_ADMIN]: t('circles', 'Admin'),
- [MEMBER_LEVEL_OWNER]: t('circles', 'Owner'),
-}
diff --git a/src/models/constants.ts b/src/models/constants.ts
new file mode 100644
index 00000000..8d736319
--- /dev/null
+++ b/src/models/constants.ts
@@ -0,0 +1,133 @@
+/**
+ * @copyright Copyright (c) 2021 John Molakvoæ
+ *
+ * @author John Molakvoæ
+ *
+ * @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 .
+ *
+ */
+///
+
+import { translate as t } from '@nextcloud/l10n'
+
+interface OC extends Nextcloud.Common.OC {
+ Share: any
+}
+declare const OC: OC
+
+export type CircleConfig = number
+export type MemberLevel = number
+export type MemberType = number
+
+// Global sizes
+export const LIST_SIZE = 60
+
+// Dynamic groups
+export const GROUP_ALL_CONTACTS = t('contacts', 'All contacts')
+export const GROUP_NO_GROUP_CONTACTS = t('contacts', 'Not grouped')
+export const GROUP_RECENTLY_CONTACTED = t('contactsinteraction', 'Recently contacted')
+
+// Circle route, see vue-router conf
+export const ROUTE_CIRCLE = 'circle'
+
+// Default max number of items to show in the navigation
+export const ELLIPSIS_COUNT = 5
+
+// Circles member levels
+const MEMBER_LEVEL_NONE: MemberLevel = 0
+const MEMBER_LEVEL_MEMBER: MemberLevel = 1
+const MEMBER_LEVEL_MODERATOR: MemberLevel = 4
+const MEMBER_LEVEL_ADMIN: MemberLevel = 8
+const MEMBER_LEVEL_OWNER: MemberLevel = 9
+
+// Circles member types
+const MEMBER_TYPE_SINGLEID: MemberType = 0
+const MEMBER_TYPE_USER: MemberType = 1
+const MEMBER_TYPE_GROUP : MemberType= 2
+const MEMBER_TYPE_MAIL: MemberType = 4
+const MEMBER_TYPE_CONTACT: MemberType = 8
+const MEMBER_TYPE_CIRCLE: MemberType = 16
+
+// Circles config flags
+const CIRCLE_CONFIG_SYSTEM: CircleConfig = 4 // System Circle (not managed by the official front-end). Meaning some config are limited
+const CIRCLE_CONFIG_VISIBLE: CircleConfig = 8 // Visible to everyone, if not visible, people have to know its name to be able to find it
+const CIRCLE_CONFIG_OPEN: CircleConfig = 16 // Circle is open, people can join
+const CIRCLE_CONFIG_INVITE: CircleConfig = 32 // Adding a member generate an invitation that needs to be accepted
+const CIRCLE_CONFIG_REQUEST: CircleConfig = 64 // Request to join Circles needs to be confirmed by a moderator
+const CIRCLE_CONFIG_FRIEND: CircleConfig = 128 // Members of the circle can invite their friends
+const CIRCLE_CONFIG_PROTECTED: CircleConfig = 256 // Password protected to join/request
+const CIRCLE_CONFIG_NO_OWNER: CircleConfig = 512 // no owner, only members
+const CIRCLE_CONFIG_HIDDEN: CircleConfig = 1024 // hidden from listing, but available as a share entity
+const CIRCLE_CONFIG_BACKEND: CircleConfig = 2048 // Fully hidden, only backend Circles
+const CIRCLE_CONFIG_ROOT: CircleConfig = 4096 // Circle cannot be inside another Circle
+const CIRCLE_CONFIG_CIRCLE_INVITE: CircleConfig = 8192 // Circle must confirm when invited in another circle
+const CIRCLE_CONFIG_FEDERATED: CircleConfig = 16384 // Federated
+
+export const CIRCLES_MEMBER_TYPES = {
+ [MEMBER_TYPE_CIRCLE]: t('circles', 'Circle'),
+ [MEMBER_TYPE_USER]: t('circles', 'User'),
+ [MEMBER_TYPE_GROUP]: t('circles', 'Group'),
+ [MEMBER_TYPE_MAIL]: t('circles', 'Mail'),
+ [MEMBER_TYPE_CONTACT]: t('circles', 'Contact'),
+}
+
+export const CIRCLES_MEMBER_LEVELS = {
+ // [MEMBER_LEVEL_NONE]: t('circles', 'None'),
+ [MEMBER_LEVEL_MEMBER]: t('circles', 'Member'),
+ [MEMBER_LEVEL_MODERATOR]: t('circles', 'Moderator'),
+ [MEMBER_LEVEL_ADMIN]: t('circles', 'Admin'),
+ [MEMBER_LEVEL_OWNER]: t('circles', 'Owner'),
+}
+
+export const SHARES_TYPES_MEMBER_MAP = {
+ [OC.Share.SHARE_TYPE_CIRCLE]: MEMBER_TYPE_SINGLEID,
+ [OC.Share.SHARE_TYPE_USER]: MEMBER_TYPE_USER,
+ [OC.Share.SHARE_TYPE_GROUP]: MEMBER_TYPE_GROUP,
+ [OC.Share.SHARE_TYPE_EMAIL]: MEMBER_TYPE_MAIL,
+ // []: MEMBER_TYPE_CONTACT,
+}
+
+export enum MemberLevels {
+ NONE = MEMBER_LEVEL_NONE,
+ MEMBER = MEMBER_LEVEL_MEMBER,
+ MODERATOR = MEMBER_LEVEL_MODERATOR,
+ ADMIN = MEMBER_LEVEL_ADMIN,
+ OWNER = MEMBER_LEVEL_OWNER,
+}
+
+export enum MemberTypes {
+ CIRCLE = MEMBER_TYPE_CIRCLE,
+ USER = MEMBER_TYPE_USER,
+ GROUP = MEMBER_TYPE_GROUP,
+ MAIL = MEMBER_TYPE_MAIL,
+ CONTACT = MEMBER_TYPE_CONTACT,
+}
+
+export enum CircleConfigs {
+ SYSTEM = CIRCLE_CONFIG_SYSTEM,
+ VISIBLE = CIRCLE_CONFIG_VISIBLE,
+ OPEN = CIRCLE_CONFIG_OPEN,
+ INVITE = CIRCLE_CONFIG_INVITE,
+ REQUEST = CIRCLE_CONFIG_REQUEST,
+ FRIEND = CIRCLE_CONFIG_FRIEND,
+ PROTECTED = CIRCLE_CONFIG_PROTECTED,
+ NO_OWNER = CIRCLE_CONFIG_NO_OWNER,
+ HIDDEN = CIRCLE_CONFIG_HIDDEN,
+ BACKEND = CIRCLE_CONFIG_BACKEND,
+ ROOT = CIRCLE_CONFIG_ROOT,
+ CIRCLE_INVITE = CIRCLE_CONFIG_CIRCLE_INVITE,
+ FEDERATED = CIRCLE_CONFIG_FEDERATED,
+}
diff --git a/src/models/member.d.ts b/src/models/member.d.ts
new file mode 100644
index 00000000..995458b1
--- /dev/null
+++ b/src/models/member.d.ts
@@ -0,0 +1,76 @@
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ
+ *
+ * @author John Molakvoæ
+ *
+ * @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 .
+ *
+ */
+import Circle from './circle';
+import { MemberLevel } from './constants';
+export default class Member {
+ _data: any;
+ _circle: Circle;
+ /**
+ * Creates an instance of Member
+ */
+ constructor(data: any, circle: Circle);
+ /**
+ * Get the circle of this member
+ */
+ get circle(): Circle;
+ /**
+ * Set the circle of this member
+ */
+ set circle(circle: Circle);
+ /**
+ * Member id
+ */
+ get id(): string;
+ /**
+ * Single uid
+ */
+ get singleId(): string;
+ /**
+ * Formatted display name
+ */
+ get displayName(): string;
+ /**
+ * Member userId
+ */
+ get userId(): string;
+ /**
+ * Member level
+ *
+ */
+ get level(): MemberLevel;
+ /**
+ * Set member level
+ */
+ set level(level: MemberLevel);
+ /**
+ * Is the current member a user?
+ */
+ get isUser(): boolean;
+ /**
+ * Is the current member without a circle?
+ */
+ get isOrphan(): boolean;
+ /**
+ * Delete this member and any reference from its circle
+ */
+ delete(): void;
+}
diff --git a/src/models/member.js b/src/models/member.ts
similarity index 71%
rename from src/models/member.js
rename to src/models/member.ts
index 47bb8072..b5b89e8c 100644
--- a/src/models/member.js
+++ b/src/models/member.ts
@@ -20,25 +20,18 @@
*
*/
-/** @typedef { import('./circle') } Circle */
-
-import { MEMBER_TYPE_USER } from './constants'
-
import Circle from './circle'
+import { MemberLevel, MemberLevels } from './constants'
+
export default class Member {
- /** @typedef Circle */
- _circle
- _data = {}
+ _data: any = {}
+ _circle: Circle
/**
- * Creates an instance of Contact
- *
- * @param {Object} data the vcard data as string with proper new lines
- * @param {Circle} circle the addressbook which the contat belongs to
- * @memberof Member
+ * Creates an instance of Member
*/
- constructor(data, circle) {
+ constructor(data: any, circle: Circle) {
if (typeof data !== 'object') {
throw new Error('Invalid member')
}
@@ -55,19 +48,15 @@ export default class Member {
/**
* Get the circle of this member
- * @readonly
- * @memberof Member
*/
- get circle() {
+ get circle(): Circle {
return this._circle
}
/**
* Set the circle of this member
- * @param {Circle} circle the circle
- * @memberof Member
*/
- set circle(circle) {
+ set circle(circle: Circle) {
if (circle.constructor.name !== Circle.name) {
throw new Error('circle must be a Circle type')
}
@@ -76,54 +65,59 @@ export default class Member {
/**
* Member id
- * @readonly
- * @memberof Member
*/
- get id() {
+ get id(): string {
return this._data.id
}
/**
- * Formatted display name
- * @readonly
- * @memberof Member
+ * Single uid
*/
- get displayName() {
+ get singleId(): string {
+ return this._data.singleId
+ }
+
+ /**
+ * Formatted display name
+ */
+ get displayName(): string {
return this._data.displayName
}
/**
* Member userId
- * @readonly
- * @memberof Member
*/
- get userId() {
+ get userId(): string {
return this._data.userId
}
/**
* Member level
- * @see file src/models/constants.js
- * @readonly
- * @memberof Member
+ *
*/
- get level() {
+ get level(): MemberLevel {
return this._data.level
}
+ /**
+ * Set member level
+ */
+ set level(level: MemberLevel) {
+ if (!(level in MemberLevels)) {
+ throw new Error('Invalid level')
+ }
+ this._data.level = level
+ }
+
/**
* Is the current member a user?
- * @readonly
- * @memberof Member
*/
get isUser() {
- return this._data.userType === MEMBER_TYPE_USER
+ return this._data.userType === MemberLevels.MEMBER
}
/**
* Is the current member without a circle?
- * @readonly
- * @memberof Member
*/
get isOrphan() {
return this._circle?.constructor?.name !== 'Circle'
@@ -137,7 +131,6 @@ export default class Member {
throw new Error('Cannot delete this member as it doesn\'t belong to any circle')
}
this.circle.deleteMember(this)
- this._circle = undefined
this._data = undefined
}
diff --git a/src/router/index.js b/src/router/index.js
index 1c2d5d0b..985fda48 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -23,6 +23,8 @@
import Vue from 'vue'
import Router from 'vue-router'
import { generateUrl } from '@nextcloud/router'
+
+import { ROUTE_CIRCLE } from '../models/constants.ts'
import Contacts from '../views/Contacts'
Vue.use(Router)
@@ -51,13 +53,13 @@ export default new Router({
component: Contacts,
},
{
- path: ':selectedGroup',
- name: 'group',
+ path: `${ROUTE_CIRCLE}/:selectedCircle`,
+ name: 'circle',
component: Contacts,
},
{
- path: 'circle/:selectedCircle',
- name: 'circle',
+ path: ':selectedGroup',
+ name: 'group',
component: Contacts,
},
{
diff --git a/src/services/circles.d.ts b/src/services/circles.d.ts
new file mode 100644
index 00000000..3f8de3ee
--- /dev/null
+++ b/src/services/circles.d.ts
@@ -0,0 +1,101 @@
+/**
+ * @copyright Copyright (c) 2021 John Molakvoæ
+ *
+ * @author John Molakvoæ
+ *
+ * @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 .
+ *
+ */
+import { MemberLevel, MemberType } from '../models/constants';
+interface MemberPairs {
+ id: string;
+ type: MemberType;
+}
+/**
+ * Get the circles list without the members
+ *
+ * @returns {Array}
+ */
+export declare const getCircles: () => Promise;
+/**
+ * Create a new circle
+ *
+ * @param {string} name the circle name
+ * @returns {Object}
+ */
+export declare const createCircle: (name: string) => Promise;
+/**
+ * Delete an existing circle
+ *
+ * @param {string} circleId the circle name
+ * @returns {Object}
+ */
+export declare const deleteCircle: (circleId: string) => Promise;
+/**
+ * Join a circle
+ *
+ * @param {string} circleId the circle name
+ * @returns {Array}
+ */
+export declare const joinCircle: (circleId: string) => Promise;
+/**
+ * Leave a circle
+ *
+ * @param {string} circleId the circle name
+ * @returns {Array}
+ */
+export declare const leaveCircle: (circleId: string) => Promise;
+/**
+ * Get the circle members without the members
+ *
+ * @param {string} circleId the circle id
+ * @returns {Array}
+ */
+export declare const getCircleMembers: (circleId: string) => Promise;
+/**
+ * Search a potential circle member
+ *
+ * @param {string} term the search query
+ * @returns {Array}
+ */
+export declare const searchMember: (term: string) => Promise;
+/**
+ * Add a circle member
+ *
+ * @param {string} circleId the circle id
+ * @param {string} members the member id
+ * @returns {Array}
+ */
+export declare const addMembers: (circleId: string, members: Array) => Promise;
+/**
+ * Delete a circle member
+ *
+ * @param {string} circleId the circle id
+ * @param {string} memberId the member id
+ * @returns {Array}
+ */
+export declare const deleteMember: (circleId: string, memberId: string) => Promise;
+/**
+ * change a member level
+ * @see levels file src/models/constants.js
+ *
+ * @param {string} circleId the circle id
+ * @param {string} memberId the member id
+ * @param {number} level the new member level
+ * @returns {Array}
+ */
+export declare const changeMemberLevel: (circleId: string, memberId: string, level: MemberLevel) => Promise;
+export {};
diff --git a/src/services/circles.js b/src/services/circles.ts
similarity index 52%
rename from src/services/circles.js
rename to src/services/circles.ts
index 544a944b..5d30c42e 100644
--- a/src/services/circles.js
+++ b/src/services/circles.ts
@@ -22,9 +22,12 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
-import { CIRCLES_MEMBER_LEVELS } from '../models/constants'
+import { MemberLevel, MemberLevels, MemberType } from '../models/constants'
-const baseApi = generateOcsUrl('apps/circles', 2)
+interface MemberPairs {
+ id: string,
+ type: MemberType
+}
/**
* Get the circles list without the members
@@ -32,7 +35,7 @@ const baseApi = generateOcsUrl('apps/circles', 2)
* @returns {Array}
*/
export const getCircles = async function() {
- const response = await axios.get(baseApi + 'circles')
+ const response = await axios.get(generateOcsUrl('apps/circles/circles'))
return response.data.ocs.data
}
@@ -42,8 +45,8 @@ export const getCircles = async function() {
* @param {string} name the circle name
* @returns {Object}
*/
-export const createCircle = async function(name) {
- const response = await axios.post(baseApi + 'circles', {
+export const createCircle = async function(name: string) {
+ const response = await axios.post(generateOcsUrl('apps/circles/circles'), {
name,
})
return response.data.ocs.data
@@ -55,8 +58,8 @@ export const createCircle = async function(name) {
* @param {string} circleId the circle name
* @returns {Object}
*/
-export const deleteCircle = async function(circleId) {
- const response = await axios.delete(baseApi + `circles/${circleId}`)
+export const deleteCircle = async function(circleId: string) {
+ const response = await axios.delete(generateOcsUrl('apps/circles/circles/{circleId}', { circleId }))
return response.data.ocs.data
}
@@ -66,8 +69,8 @@ export const deleteCircle = async function(circleId) {
* @param {string} circleId the circle name
* @returns {Array}
*/
-export const joinCircle = async function(circleId) {
- const response = await axios.put(baseApi + `circles/${circleId}/join`)
+export const joinCircle = async function(circleId: string) {
+ const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/join', { circleId }))
return response.data.ocs.data
}
@@ -77,8 +80,8 @@ export const joinCircle = async function(circleId) {
* @param {string} circleId the circle name
* @returns {Array}
*/
-export const leaveCircle = async function(circleId) {
- const response = await axios.put(baseApi + `circles/${circleId}/leave`)
+export const leaveCircle = async function(circleId: string) {
+ const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/leave', { circleId }))
return response.data.ocs.data
}
@@ -88,21 +91,32 @@ export const leaveCircle = async function(circleId) {
* @param {string} circleId the circle id
* @returns {Array}
*/
-export const getCircleMembers = async function(circleId) {
- const response = await axios.get(baseApi + `circles/${circleId}/members`)
- return Object.values(response.data.ocs.data)
+export const getCircleMembers = async function(circleId: string) {
+ const response = await axios.get(generateOcsUrl('apps/circles/circles/{circleId}/members', { circleId }))
+ return response.data.ocs.data
+}
+
+/**
+ * Search a potential circle member
+ *
+ * @param {string} term the search query
+ * @returns {Array}
+ */
+export const searchMember = async function(term: string) {
+ const response = await axios.get(generateOcsUrl('apps/circles/search?term={term}', { term }))
+ return response.data.ocs.data
}
/**
* Add a circle member
*
* @param {string} circleId the circle id
- * @param {string} memberId the member id
+ * @param {string} members the member id
* @returns {Array}
*/
-export const addMember = async function(circleId, memberId) {
- const response = await axios.delete(baseApi + `circles/${circleId}/members/${memberId}`)
- return Object.values(response.data.ocs.data)
+export const addMembers = async function(circleId: string, members: Array) {
+ const response = await axios.post(generateOcsUrl('apps/circles/circles/{circleId}/members/multi', { circleId }), { members })
+ return response.data.ocs.data
}
/**
@@ -112,8 +126,8 @@ export const addMember = async function(circleId, memberId) {
* @param {string} memberId the member id
* @returns {Array}
*/
-export const deleteMember = async function(circleId, memberId) {
- const response = await axios.delete(baseApi + `circles/${circleId}/members/${memberId}`)
+export const deleteMember = async function(circleId: string, memberId: string) {
+ const response = await axios.delete(generateOcsUrl('apps/circles/circles/{circleId}/members/{memberId}', { circleId, memberId }))
return Object.values(response.data.ocs.data)
}
@@ -126,12 +140,12 @@ export const deleteMember = async function(circleId, memberId) {
* @param {number} level the new member level
* @returns {Array}
*/
-export const changeMemberLevel = async function(circleId, memberId, level) {
- if (!(level in CIRCLES_MEMBER_LEVELS)) {
- throw new Error('Invalid level. Valid levels are', CIRCLES_MEMBER_LEVELS)
+export const changeMemberLevel = async function(circleId: string, memberId: string, level: MemberLevel) {
+ if (!(level in MemberLevels)) {
+ throw new Error('Invalid level.')
}
- const response = await axios.put(baseApi + `circles/${circleId}/members/${memberId}}/level`, {
+ const response = await axios.put(generateOcsUrl('apps/circles/circles/{circleId}/members/{memberId}/level', { circleId, memberId }), {
level,
})
return Object.values(response.data.ocs.data)
diff --git a/src/services/collaborationAutocompletion.js b/src/services/collaborationAutocompletion.js
new file mode 100644
index 00000000..f7200387
--- /dev/null
+++ b/src/services/collaborationAutocompletion.js
@@ -0,0 +1,141 @@
+
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ
+ *
+ * @author John Molakvoæ
+ *
+ * @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 .
+ *
+ */
+
+import axios from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+const maxAutocompleteResults = parseInt(OC.config['sharing.maxAutocompleteResults'], 10) || 25
+
+export const shareType = [
+ OC.Share.SHARE_TYPE_USER,
+ OC.Share.SHARE_TYPE_GROUP,
+ // OC.Share.SHARE_TYPE_REMOTE,
+ // OC.Share.SHARE_TYPE_REMOTE_GROUP,
+ OC.Share.SHARE_TYPE_CIRCLE,
+ // OC.Share.SHARE_TYPE_ROOM,
+ // OC.Share.SHARE_TYPE_GUEST,
+ // OC.Share.SHARE_TYPE_DECK,
+ OC.Share.SHARE_TYPE_EMAIL,
+]
+
+/**
+ * Get suggestions
+ *
+ * @param {string} search the search query
+ */
+export const getSuggestions = async function(search) {
+ const request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees'), {
+ params: {
+ format: 'json',
+ itemType: 'file',
+ search,
+ perPage: maxAutocompleteResults,
+ shareType,
+ },
+ })
+
+ const data = request.data.ocs.data
+ const exact = request.data.ocs.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 = rawExactSuggestions
+ .filter(result => typeof result === 'object')
+ .map(share => formatResults(share))
+ // sort by type so we can get user&groups first...
+ .sort((a, b) => a.shareType - b.shareType)
+ const suggestions = rawSuggestions
+ .filter(result => typeof result === 'object')
+ .map(share => formatResults(share))
+ // sort by type so we can get user&groups first...
+ .sort((a, b) => a.shareType - b.shareType)
+
+ const allSuggestions = exactSuggestions.concat(suggestions)
+
+ // Count occurances of display names in order to provide a distinguishable description if needed
+ const nameCounts = allSuggestions.reduce((nameCounts, result) => {
+ if (!result.displayName) {
+ return nameCounts
+ }
+ if (!nameCounts[result.displayName]) {
+ nameCounts[result.displayName] = 0
+ }
+ nameCounts[result.displayName]++
+ return nameCounts
+ }, {})
+
+ const finalResults = allSuggestions.map(item => {
+ // Make sure that items with duplicate displayName get the shareWith applied as a description
+ if (nameCounts[item.displayName] > 1 && !item.desc) {
+ return { ...item, desc: item.shareWithDisplayNameUnique }
+ }
+ return item
+ })
+
+ console.info('suggestions', finalResults)
+
+ return finalResults
+}
+
+/**
+ * Get the sharing recommendations
+ */
+export const getRecommendations = async function() {
+ const request = await axios.get(generateOcsUrl('apps/files_sharing/api/v1/sharees_recommended'), {
+ params: {
+ format: 'json',
+ itemType: 'file',
+ shareType,
+ },
+ })
+
+ // flatten array of arrays
+ const exact = request.data.ocs.data.exact
+ const recommendations = Object.values(exact).reduce((arr, elem) => arr.concat(elem), [])
+
+ // remove invalid data and format to user-select layout
+ const finalResults = recommendations
+ .map(share => formatResults(share))
+
+ console.info('recommendations', finalResults)
+
+ return finalResults
+}
+
+const formatResults = function(result) {
+ const type = `picker-${result.value.shareType}`
+ return {
+ label: result.label,
+ id: `${type}-${result.value.shareWith}`,
+ // If this is a user, set as user for avatar display by UserBubble
+ user: result.value.shareType === OC.Share.SHARE_TYPE_USER
+ ? result.value.shareWith
+ : null,
+ type,
+ ...result.value,
+ }
+}
diff --git a/src/store/circles.js b/src/store/circles.js
index 900d6356..f171650f 100644
--- a/src/store/circles.js
+++ b/src/store/circles.js
@@ -23,9 +23,9 @@
import { showError } from '@nextcloud/dialogs'
import Vue from 'vue'
-import { createCircle, deleteCircle, deleteMember, getCircleMembers, getCircles, leaveCircle } from '../services/circles'
-import Member from '../models/member'
-import Circle from '../models/circle'
+import { createCircle, deleteCircle, deleteMember, getCircleMembers, getCircles, leaveCircle, addMembers } from '../services/circles.ts'
+import Member from '../models/member.ts'
+import Circle from '../models/circle.ts'
const state = {
/** @type {Object.} Circle */
@@ -113,10 +113,20 @@ const actions = {
const circles = await getCircles()
console.debug(`Retrieved ${circles.length} circle(s)`, circles)
- circles.map(circle => new Circle(circle))
- .forEach(circle => {
- context.commit('addCircle', circle)
- })
+ let failure = false
+ circles.forEach(circle => {
+ try {
+ const newCircle = new Circle(circle)
+ context.commit('addCircle', newCircle)
+ } catch (error) {
+ failure = true
+ console.error('This circle failed to be processed', circle, error)
+ }
+ })
+
+ if (failure) {
+ showError(t('contacts', 'Some circle(s) errored, check the console for more details'))
+ }
return circles
},
@@ -140,12 +150,15 @@ const actions = {
*
* @param {Object} context the store mutations Current context
* @param {string} circleName the circle name
+ * @returns {Circle} the new circle
*/
async createCircle(context, circleName) {
try {
const response = await createCircle(circleName)
const circle = new Circle(response)
+ context.commit('addCircle', circle)
console.debug('Created circle', circleName, circle)
+ return circle
} catch (error) {
console.error(error)
showError(t('contacts', 'Unable to create circle {circleName}', { circleName }))
@@ -169,16 +182,23 @@ const actions = {
},
/**
- * Add a member to a circle
+ * Add members to a circle
*
* @param {Object} context the store mutations Current context
* @param {Object} data destructuring object
* @param {string} data.circleId the circle to manage
- * @param {string} data.memberId the member to add
+ * @param {Array} data.selection the members to add, see addMembers service
+ * @returns {Member[]}
*/
- async addMemberToCircle(context, { circleId, memberId }) {
- await this.addMember(circleId, memberId)
- console.debug('Added member', circleId, memberId)
+ async addMembersToCircle(context, { circleId, selection }) {
+ const circle = context.getters.getCircle(circleId)
+ const results = await addMembers(circleId, selection)
+ const members = results.map(member => new Member(member, circle))
+
+ console.debug('Added members to circle', circle, members)
+ context.commit('appendMembersToCircle', members)
+
+ return members
},
/**
diff --git a/src/views/Contacts.vue b/src/views/Contacts.vue
index 5b9dfa53..c336115f 100644
--- a/src/views/Contacts.vue
+++ b/src/views/Contacts.vue
@@ -66,7 +66,7 @@