Skip to content
Open
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
7 changes: 3 additions & 4 deletions src/components/Member/CurrentMembers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@
<script>
import { getCurrentUser } from '@nextcloud/auth'
import { t } from '@nextcloud/l10n'
import { mapState } from 'pinia'
import NcAppNavigationCaption from '@nextcloud/vue/components/NcAppNavigationCaption'
import MemberItem from './MemberItem.vue'
import MembersHint from './MembersHint.vue'
import { useCirclesStore } from '../../stores/circles.js'
import { circleMemberType } from '../../util/circles.ts'

export default {
name: 'CurrentMembers',
Expand Down Expand Up @@ -66,8 +65,6 @@ export default {
},

computed: {
...mapState(useCirclesStore, ['circleMemberType']),

isSearching() {
return this.searchQuery !== ''
},
Expand Down Expand Up @@ -102,6 +99,8 @@ export default {
methods: {
t,

circleMemberType,

/**
* @param {object} m1 First member
* @param {string} m1.userId First member user ID
Expand Down
7 changes: 2 additions & 5 deletions src/components/Member/MemberPicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import { emit } from '@nextcloud/event-bus'
import { t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import debounce from 'debounce'
import { mapState } from 'pinia'
import NcAppNavigationCaption from '@nextcloud/vue/components/NcAppNavigationCaption'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import MagnifyIcon from 'vue-material-design-icons/Magnify.vue'
Expand All @@ -70,7 +69,7 @@ import MemberSearchResults from './MemberSearchResults.vue'
import MembersHint from './MembersHint.vue'
import SelectedMembers from './SelectedMembers.vue'
import { autocompleteSourcesToCircleMemberTypes, circlesMemberTypes, shareTypes } from '../../constants.js'
import { useCirclesStore } from '../../stores/circles.js'
import { circleMemberType } from '../../util/circles.ts'

export default {
name: 'MemberPicker',
Expand Down Expand Up @@ -156,8 +155,6 @@ export default {
},

computed: {
...mapState(useCirclesStore, ['circleMemberType']),

hasSearchQuery() {
return this.searchQuery !== ''
},
Expand Down Expand Up @@ -224,7 +221,7 @@ export default {
filterSearchResults(item) {
return !this.currentMembers.find((m) => {
return (item.source === 'circlesx' && item.id === this.circleId)
|| (this.circleMemberType(m) === circlesMemberTypes[autocompleteSourcesToCircleMemberTypes[item.source]]
|| (circleMemberType(m) === circlesMemberTypes[autocompleteSourcesToCircleMemberTypes[item.source]]
&& m.displayName === item.label)
})
},
Expand Down
9 changes: 2 additions & 7 deletions src/components/Page/LandingPageWidgets/MembersWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { circlesMemberTypes } from '../../../constants.js'
import { useCirclesStore } from '../../../stores/circles.js'
import { useCollectivesStore } from '../../../stores/collectives.js'
import { usePagesStore } from '../../../stores/pages.js'
import { circleMemberType } from '../../../util/circles.ts'

export default {
name: 'MembersWidget',
Expand Down Expand Up @@ -97,7 +98,6 @@ export default {
...mapState(useCirclesStore, [
'circleMembers',
'circleMembersSorted',
'circleMemberType',
]),

...mapState(useCollectivesStore, [
Expand Down Expand Up @@ -134,7 +134,7 @@ export default {

isNoUser() {
return (member) => {
return this.circleMemberType(member) !== circlesMemberTypes.TYPE_USER
return circleMemberType(member) !== circlesMemberTypes.TYPE_USER
}
},

Expand Down Expand Up @@ -185,10 +185,6 @@ export default {
},
},

beforeMount() {
this.getCircleMembers(this.currentCollective.circleId)
},

mounted() {
window.addEventListener('resize', this.updateShowMembersCountDebounced)
},
Expand All @@ -200,7 +196,6 @@ export default {
methods: {
t,

...mapActions(useCirclesStore, ['getCircleMembers']),
...mapActions(useCollectivesStore, [
'setCollectiveUserSettingShowMembers',
'setMembersCollectiveId',
Expand Down
8 changes: 8 additions & 0 deletions src/composables/useEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import debounce from 'debounce'
import { computed, markRaw, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { useCirclesStore } from '../stores/circles.js'
import { useCollectivesStore } from '../stores/collectives.js'
import { usePagesStore } from '../stores/pages.js'
import { useRootStore } from '../stores/root.js'
Expand All @@ -23,6 +24,7 @@ export function useEditor(davContent) {
let editorPromise = null
const updateCounter = ref(0)
const rootStore = useRootStore()
const circlesStore = useCirclesStore()
const searchStore = useSearchStore()
const collectivesStore = useCollectivesStore()
const pagesStore = usePagesStore()
Expand Down Expand Up @@ -102,6 +104,12 @@ export function useEditor(davContent) {
onAttachmentsUpdated({ attachmentSrcs }) {
pagesStore.setEditorEmbeddedAttachmentSrcs(attachmentSrcs)
},
onMentionSearch(query) {
const users = circlesStore.currentCircleUserMembersSorted
const lowerQuery = query.toLowerCase().trim()
console.debug('members for mention search', users)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can this be removed?

return Object.fromEntries(Object.entries(users).filter(([key, value]) => key.toLowerCase().includes(lowerQuery) || value.toLowerCase().includes(lowerQuery)))
},
onOutlineToggle: pagesStore.setOutlineForCurrentPage,
})

Expand Down
2 changes: 1 addition & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const TEMPLATE_PAGE = 'Template'
export const PAGE_SUFFIX = '.md'
export const TEMPLATE_PATH = '.templates'

// Circle lember levels
// Circle member levels
export const memberLevels = {
LEVEL_MEMBER: 1,
LEVEL_MODERATOR: 4,
Expand Down
52 changes: 18 additions & 34 deletions src/stores/circles.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { generateOcsUrl } from '@nextcloud/router'
import { useLocalStorage } from '@vueuse/core'
import { defineStore } from 'pinia'
import { circlesMemberTypes } from '../constants.js'
import { sortMembersByLevelAndType } from '../util/circles.ts'
import { useCollectivesStore } from './collectives.js'
import { useRootStore } from './root.js'

Expand All @@ -34,48 +35,31 @@ export const useCirclesStore = defineStore('circles', {
})
},

circleMembers: (state) => (circleId) => state.circlesMembers[circleId] || [],

circleMemberType: () => (member) => {
// If the user type is a circle, this could originate from multiple sources
// Copied from Contacts app src/models/member.ts get userType()
return member.userType !== circlesMemberTypes.TYPE_CIRCLE
? member.userType
: member.basedOn.source
currentCircleMembers: (state) => {
const collectivesStore = useCollectivesStore()
const currentCircleId = collectivesStore.currentCollective?.circleId
return state.circlesMembers[currentCircleId]
},

circleMembersSorted: (state) => (circleId) => {
/**
* @param {object} m1 First member
* @param {string} m1.userId First member user ID
* @param {string} m1.displayName First member display name
* @param {number} m1.level First member level
* @param {number} m1.userType First member user type
* @param {object} m2 Second member
* @param {string} m2.userId Second member user ID
* @param {string} m2.displayName Second member display name
* @param {number} m2.level Second member level
* @param {number} m2.userType Second member user type
*/
function sortMembersByLevelAndType(m1, m2) {
// Sort by level (admin > moderator > member)
if (m1.level !== m2.level) {
return m1.level < m2.level
}

// Sort by user type (user > group > circle)
if (state.circleMemberType(m1) !== state.circleMemberType(m2)) {
return state.circleMemberType(m1) > state.circleMemberType(m2)
}
circleMembers: (state) => (circleId) => state.circlesMembers[circleId] || [],

// Sort by display name
return m1.displayName.localeCompare(m2.displayName)
}
currentCircleMembersSorted: (state) => state.currentCircleMembers.slice().sort(sortMembersByLevelAndType),

circleMembersSorted: (state) => (circleId) => {
return state.circleMembers(circleId)
.slice()
.sort(sortMembersByLevelAndType)
},

currentCircleUserMembersSorted: (state) => {
const users = {}
for (const member of state.currentCircleMembersSorted) {
if (member.userType === circlesMemberTypes.TYPE_USER) {
users[member.userId] = member.displayName
}
}
return users
},
},

actions: {
Expand Down
62 changes: 62 additions & 0 deletions src/util/circles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { circlesMemberTypes } from '../constants.js'

type Circle = {
id: string
name: string
displayName: string
source: number
}

type CircleMember = {
id: string
circleId: string
singleId: string
userId: string
userType: number
displayName: string
level: number
basedOn?: Circle
}

/**
* @param member Member
*/
export function circleMemberType(member: CircleMember): number {
// If the user type is a circle, this could originate from multiple sources
// Copied from Contacts app src/models/member.ts get userType()
return member.userType !== circlesMemberTypes.TYPE_CIRCLE
? member.userType
: member.basedOn.source
}

/**
* @param m1 First member
* @param m1.userId First member user ID
* @param m1.displayName First member display name
* @param m1.level First member level
* @param m1.userType First member user type
* @param m2 Second member
* @param m2.userId Second member user ID
* @param m2.displayName Second member display name
* @param m2.level Second member level
* @param m2.userType Second member user type
*/
export function sortMembersByLevelAndType(m1: CircleMember, m2: CircleMember): number {
// Sort by level (admin > moderator > member)
if (m1.level !== m2.level) {
return m2.level - m1.level
}

// Sort by user type (user > group > circle)
if (circleMemberType(m1) !== circleMemberType(m2)) {
return circleMemberType(m1) - circleMemberType(m2)
}

// Sort by display name
return m1.displayName.localeCompare(m2.displayName)
}
3 changes: 3 additions & 0 deletions src/views/CollectiveView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import CollectiveNotFound from '../components/CollectiveNotFound.vue'
import PageList from '../components/PageList.vue'
import { useNetworkState } from '../composables/useNetworkState.ts'
import { sessionUpdateInterval } from '../constants.js'
import { useCirclesStore } from '../stores/circles.js'
import { useCollectivesStore } from '../stores/collectives.js'
import { usePagesStore } from '../stores/pages.js'
import { useRootStore } from '../stores/root.js'
Expand Down Expand Up @@ -123,6 +124,7 @@ export default {

methods: {
...mapActions(useRootStore, ['hide']),
...mapActions(useCirclesStore, ['getCircleMembers']),
...mapActions(useSessionsStore, ['createSession', 'updateSession', 'closeSession']),
...mapActions(useTagsStore, ['getTags']),
...mapActions(useTemplatesStore, ['getTemplates']),
Expand Down Expand Up @@ -218,6 +220,7 @@ export default {
if (!this.currentCollectiveIsPageShare) {
promises.push(this.getTemplates(setLoading))
}
promises.push(this.getCircleMembers(this.currentCollective.circleId))
}

try {
Expand Down
Loading