feat: sort favorite contact first in list#5251
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This comment was marked as outdated.
This comment was marked as outdated.
658fb4b to
693cb61
Compare
|
Cleaned-up the PR description so it's clear that we can not merge this before the cdav merge, release and update here |
GVodyanov
left a comment
There was a problem hiding this comment.
Tested it and works great!
Just a few styling comments, will approve to not block.
I also noted that there is the age old issue representing itself (at least to me in firefox)
Right after favouriting u get the star stuck on the side of the component until you hover over it, but that's almost more of a vue or browser bug probably, I had this on other PRs too and wasn't able to fix it.
DerDreschner
left a comment
There was a problem hiding this comment.
For me, the sorting is... strange?
simplescreenrecorder-2026-05-06_15.14.52.mp4
And when I open a contact, the favorite status is being lost as it's only included in the PROPFIND for the address book by the cdav-library right now, not the contact itself. Could you have a look?
If necessary, I can modify the cdav-library to retrieve the favorite property as well. But as there is no star indicator on the contact itself, only the contacts list, I see no reason for retrieving it there as well.
DerDreschner
left a comment
There was a problem hiding this comment.
Retested and the sorting is now correct! 😄 Only two things that can be fixed in follow-ups:
- When selecting a contact marked as favorite, the contact list jumps unexpectedly so the selected contact is on the top for some reason:
simplescreenrecorder-2026-05-07_14.55.00.mp4
- Hiding the star for address books that don't have the
{DAV:}write-propertiesright (see nextcloud/server#60183 (comment))
|
Read-only is fine. We want the feature to be usable for the SAB and for read-only shared ABs. It should only not be usable for the recently contacted, because that data is volatile. |
So it's supposed to work for SAB cards ? |
| async toggleFavorite() { | ||
| const contact = this.$store.getters.getContact(this.source.key) | ||
| if (!contact) { | ||
| console.error('Could not find contact in store', this.source.key) |
There was a problem hiding this comment.
all console.error should be logger.error instead
Works fine btw 👏🏼 |
There was a problem hiding this comment.
Pull request overview
This PR adds “favorite contact” support to the Contacts frontend so favorites can be toggled from the list UI and are sorted to the top of contact lists.
Changes:
- Add favorite-aware sorting and a
toggleFavoriteVuex action/mutation flow in the contacts store. - Add a
favoritegetter/setter on theContactmodel backed bycontact.dav.favorite. - Add a favorites toggle control (star) to each contact list item, plus an action menu entry.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
src/store/contacts.js |
Introduces favorite-aware sorting, store mutation/action to toggle favorites, and preserves favorite metadata across fetch/update flows. |
src/models/contact.js |
Adds favorite getter/setter that proxies to the DAV layer. |
src/components/ContactsList/ContactsListItem.vue |
Adds UI controls (star + action button) to toggle favorite state from the contact list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (let i = 0, len = state.sortedContacts.length; i < len; i++) { | ||
| if (sortData(state.sortedContacts[i], sortedContact) >= 0) { | ||
| state.sortedContacts.splice(i, 0, sortedContact) | ||
| break | ||
| } else if (i + 1 === len) { | ||
| // we reached the end insert it now | ||
| const other = state.sortedContacts[i] | ||
|
|
||
| // favorite comes before non-favorite | ||
| const differentFavStatus = other.favorite !== sortedContact.favorite | ||
| const otherShouldComeFirst = differentFavStatus && other.favorite | ||
| const sameFavAndSortedFirst = !differentFavStatus && sortData(other, sortedContact) >= 0 | ||
|
|
||
| if (otherShouldComeFirst || sameFavAndSortedFirst) { | ||
| continue | ||
| } | ||
|
|
||
| if (i + 1 === len) { | ||
| state.sortedContacts.push(sortedContact) | ||
| } else { | ||
| state.sortedContacts.splice(i, 0, sortedContact) | ||
| } | ||
| break | ||
| } |
| // alphabetical within each group | ||
| if (!a.value && !b.value) { | ||
| return 0 | ||
| } | ||
| if (!a.value) { | ||
| return 1 | ||
| } | ||
| if (!b.value) { | ||
| return -1 | ||
| } | ||
| return a.value.localeCompare(b.value) |
| .sort(sortData) | ||
| .map((contact) => ({ | ||
| key: contact.key, | ||
| value: (contact[state.orderKey] || '').toString().toLowerCase(), |
| <div | ||
| class="favorite-star" | ||
| :class="{ favorite: isFavorite }" | ||
| @click.stop="toggleFavorite"> | ||
| <StarIcon | ||
| v-if="isFavorite" | ||
| :size="16" | ||
| class="favorite-icon" /> | ||
| </div> |
Thanks for the feedback! Makes my blood pressure going down a lot 😄 |
|
requires #5310 |
Showtime |
b4000c1 to
eaca005
Compare
2ca5373 to
d874b8d
Compare
Signed-off-by: greta <gretadoci@gmail.com>
d874b8d to
bf76118
Compare
jancborchardt
left a comment
There was a problem hiding this comment.
Very nice, thank you! :)
|
/backport to stable8.5 please |

Fixes #105
favoriteproperty for each vCard cdav-library#1029 merged, and releasedHow to test
You need to link cdav-library with contacts while in this pr: nextcloud/cdav-library#1029