-
Notifications
You must be signed in to change notification settings - Fork 314
Post move selection #12660
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Post move selection #12660
Changes from all commits
083bd5e
0a8ca1f
2dbaee6
63679c2
3f725a1
4517f32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,7 +28,8 @@ | |||||||||||||||||||||||||||||||||||||||
| :loading-more="false" | ||||||||||||||||||||||||||||||||||||||||
| :load-more-button="false" | ||||||||||||||||||||||||||||||||||||||||
| :skip-transition="skipListTransition" | ||||||||||||||||||||||||||||||||||||||||
| @delete="onDelete" /> | ||||||||||||||||||||||||||||||||||||||||
| @delete="onDelete" | ||||||||||||||||||||||||||||||||||||||||
| @move="onMove" /> | ||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||
| </template> | ||||||||||||||||||||||||||||||||||||||||
| <EnvelopeList | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -42,6 +43,7 @@ | |||||||||||||||||||||||||||||||||||||||
| :load-more-button="showLoadMore" | ||||||||||||||||||||||||||||||||||||||||
| :skip-transition="skipListTransition" | ||||||||||||||||||||||||||||||||||||||||
| @delete="onDelete" | ||||||||||||||||||||||||||||||||||||||||
| @move="onMove" | ||||||||||||||||||||||||||||||||||||||||
| @load-more="loadMore" /> | ||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||
| </template> | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -62,6 +64,7 @@ | |||||||||||||||||||||||||||||||||||||||
| import { matchError } from '../errors/match.js' | ||||||||||||||||||||||||||||||||||||||||
| import NoTrashMailboxConfiguredError | ||||||||||||||||||||||||||||||||||||||||
| from '../errors/NoTrashMailboxConfiguredError.js' | ||||||||||||||||||||||||||||||||||||||||
| import dragEventBus from '../directives/drag-and-drop/util/dragEventBus.js' | ||||||||||||||||||||||||||||||||||||||||
| import logger from '../logger.js' | ||||||||||||||||||||||||||||||||||||||||
| import useMainStore from '../store/mainStore.js' | ||||||||||||||||||||||||||||||||||||||||
| import { mailboxHasRights } from '../util/acl.js' | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -198,8 +201,10 @@ | |||||||||||||||||||||||||||||||||||||||
| created() { | ||||||||||||||||||||||||||||||||||||||||
| this.bus.on('load-more', this.onScroll) | ||||||||||||||||||||||||||||||||||||||||
| this.bus.on('delete', this.onDelete) | ||||||||||||||||||||||||||||||||||||||||
| this.bus.on('move', this.onMove) | ||||||||||||||||||||||||||||||||||||||||
| this.bus.on('archive', this.onArchive) | ||||||||||||||||||||||||||||||||||||||||
| this.bus.on('shortcut', this.handleShortcut) | ||||||||||||||||||||||||||||||||||||||||
| dragEventBus.on('envelopes-dropped', this.onEnvelopesDropped) | ||||||||||||||||||||||||||||||||||||||||
| this.loadMailboxInterval = setInterval(this.loadMailbox, 60000) | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -220,8 +225,10 @@ | |||||||||||||||||||||||||||||||||||||||
| unmounted() { | ||||||||||||||||||||||||||||||||||||||||
| this.bus.off('load-more', this.onScroll) | ||||||||||||||||||||||||||||||||||||||||
| this.bus.off('delete', this.onDelete) | ||||||||||||||||||||||||||||||||||||||||
| this.bus.off('move', this.onMove) | ||||||||||||||||||||||||||||||||||||||||
| this.bus.off('archive', this.onArchive) | ||||||||||||||||||||||||||||||||||||||||
| this.bus.off('shortcut', this.handleShortcut) | ||||||||||||||||||||||||||||||||||||||||
| dragEventBus.off('envelopes-dropped', this.onEnvelopesDropped) | ||||||||||||||||||||||||||||||||||||||||
| this.stopInterval() | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
|
|
@@ -539,26 +546,38 @@ | |||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // onDelete(id): Load more message and navigate to other message if needed | ||||||||||||||||||||||||||||||||||||||||
| // id: The id of the message being delete | ||||||||||||||||||||||||||||||||||||||||
| onDelete(id) { | ||||||||||||||||||||||||||||||||||||||||
| // Get a new message | ||||||||||||||||||||||||||||||||||||||||
| this.mainStore.fetchNextEnvelopes({ | ||||||||||||||||||||||||||||||||||||||||
| mailboxId: this.mailbox.databaseId, | ||||||||||||||||||||||||||||||||||||||||
| query: this.searchQuery, | ||||||||||||||||||||||||||||||||||||||||
| quantity: 1, | ||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||
| onEnvelopesDropped({ envelopes }) { | ||||||||||||||||||||||||||||||||||||||||
| const currentThreadId = this.$route.params.threadId | ||||||||||||||||||||||||||||||||||||||||
| if (!currentThreadId) { | ||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| const movedIds = envelopes.map((e) => e.databaseId) | ||||||||||||||||||||||||||||||||||||||||
| const wasMoved = movedIds.some((id) => String(id) === String(currentThreadId)) | ||||||||||||||||||||||||||||||||||||||||
| if (wasMoved) { | ||||||||||||||||||||||||||||||||||||||||
| this.onMove(movedIds) | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| navigateToAdjacentEnvelope(id, excludeIds = []) { | ||||||||||||||||||||||||||||||||||||||||
| const idx = findIndex(propEq(id, 'databaseId'), this.envelopes) | ||||||||||||||||||||||||||||||||||||||||
| if (idx === -1) { | ||||||||||||||||||||||||||||||||||||||||
| logger.debug('envelope to delete does not exist in envelope list') | ||||||||||||||||||||||||||||||||||||||||
| logger.debug('envelope does not exist in envelope list') | ||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| if (id !== this.$route.params.threadId) { | ||||||||||||||||||||||||||||||||||||||||
| logger.debug('other message open, not jumping to the next/previous message') | ||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
567
to
569
|
||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const next = this.envelopes[idx === 0 ? 1 : idx - 1] | ||||||||||||||||||||||||||||||||||||||||
| // Find the nearest envelope that is not being moved, preferring | ||||||||||||||||||||||||||||||||||||||||
| // the one above (idx - 1) unless we're at the top of the list | ||||||||||||||||||||||||||||||||||||||||
| let next | ||||||||||||||||||||||||||||||||||||||||
| if (idx === 0) { | ||||||||||||||||||||||||||||||||||||||||
| next = this.envelopes.slice(idx + 1).find((env) => !excludeIds.includes(env.databaseId)) | ||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||
| next = this.envelopes.slice(0, idx).reverse().find((env) => !excludeIds.includes(env.databaseId)) | ||||||||||||||||||||||||||||||||||||||||
| || this.envelopes.slice(idx + 1).find((env) => !excludeIds.includes(env.databaseId)) | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+572
to
+579
|
||||||||||||||||||||||||||||||||||||||||
| // Find the nearest envelope that is not being moved, preferring | |
| // the one above (idx - 1) unless we're at the top of the list | |
| let next | |
| if (idx === 0) { | |
| next = this.envelopes.slice(idx + 1).find((env) => !excludeIds.includes(env.databaseId)) | |
| } else { | |
| next = this.envelopes.slice(0, idx).reverse().find((env) => !excludeIds.includes(env.databaseId)) | |
| || this.envelopes.slice(idx + 1).find((env) => !excludeIds.includes(env.databaseId)) | |
| // Normalize excludeIds to strings and use a Set for O(1), type-robust membership checks | |
| const excludeIdSet = new Set(excludeIds.map(String)) | |
| // Find the nearest envelope that is not being moved, preferring | |
| // the one above (idx - 1) unless we're at the top of the list | |
| let next | |
| if (idx === 0) { | |
| next = this.envelopes.slice(idx + 1).find((env) => !excludeIdSet.has(String(env.databaseId))) | |
| } else { | |
| next = this.envelopes.slice(0, idx).reverse().find((env) => !excludeIdSet.has(String(env.databaseId))) | |
| || this.envelopes.slice(idx + 1).find((env) => !excludeIdSet.has(String(env.databaseId))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly more complexity for practically very low gains. I don't think moving thousands of emails is a common occurrence. I can do it, but should be ok to keep it as is.
Copilot
AI
Mar 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onMove() compares ids to this.$route.params.threadId with strict equality, but ids are frequently numbers while the route param is a string. As a result, move navigation won’t trigger in common cases (e.g., thread view emits numeric ids). Normalize types (String/Number) for the membership check and when passing excludeIds to navigateToAdjacentEnvelope().
| const currentThreadId = this.$route.params.threadId | |
| const movedId = ids.find((id) => id === currentThreadId) | |
| if (movedId !== undefined) { | |
| this.navigateToAdjacentEnvelope(movedId, ids) | |
| const currentThreadIdRaw = this.$route.params.threadId | |
| if (currentThreadIdRaw === undefined || currentThreadIdRaw === null) { | |
| return | |
| } | |
| const idsAreNumbers = ids.some((id) => typeof id === 'number') | |
| const currentThreadId = idsAreNumbers ? Number(currentThreadIdRaw) : String(currentThreadIdRaw) | |
| const movedId = ids.find((id) => id === currentThreadId) | |
| if (movedId !== undefined) { | |
| const normalizedExcludeIds = idsAreNumbers | |
| ? ids.map((id) => Number(id)) | |
| : ids.map((id) => String(id)) | |
| this.navigateToAdjacentEnvelope(movedId, normalizedExcludeIds) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The delete path already used this approach https://github.com/nextcloud/mail/blob/main/src/components/Mailbox.vue#L556
If this is going to be changed maybe it should be in it's own issue? It works in practice as-is, and I don't want to touch more than what I need to fix the specific issue the PR is about.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -190,7 +190,7 @@ | |
| </div> | ||
| </template> | ||
|
|
||
| <Thread v-if="showThread" :current-account-email="account.emailAddress" @delete="deleteMessage" /> | ||
| <Thread v-if="showThread" :current-account-email="account.emailAddress" @delete="deleteMessage" @move="moveMessage" /> | ||
|
Check failure on line 193 in src/components/MailboxThread.vue
|
||
| <NoMessageSelected v-else-if="hasEnvelopes" /> | ||
| </AppContent> | ||
| </template> | ||
|
|
@@ -497,6 +497,10 @@ | |
| this.bus.emit('delete', id) | ||
| }, | ||
|
|
||
| moveMessage(ids) { | ||
| this.bus.emit('move', ids) | ||
| }, | ||
|
|
||
| onScroll(event) { | ||
| logger.debug('scroll', { event }) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -514,13 +514,11 @@ export default { | |
| mounted() { | ||
| dragEventBus.on('drag-start', this.onDragStart) | ||
| dragEventBus.on('drag-end', this.onDragEnd) | ||
| dragEventBus.on('envelopes-moved', this.onEnvelopesMoved) | ||
| }, | ||
|
Comment on lines
514
to
517
|
||
|
|
||
| beforeUnmount() { | ||
| dragEventBus.off('drag-start', this.onDragStart) | ||
| dragEventBus.off('drag-end', this.onDragEnd) | ||
| dragEventBus.off('envelopes-moved', this.onEnvelopesMoved) | ||
| }, | ||
|
|
||
| methods: { | ||
|
|
@@ -763,26 +761,6 @@ export default { | |
| this.showSubMailboxes = false | ||
| }, | ||
|
|
||
| onEnvelopesMoved({ mailboxId, movedEnvelopes }) { | ||
| if (this.mailbox.databaseId !== mailboxId) { | ||
| return | ||
| } | ||
| const openedMessageHasBeenMoved = movedEnvelopes.find((movedEnvelope) => { | ||
| return movedEnvelope.envelopeId === this.$route.params.threadId | ||
| }) | ||
| // navigate to the mailbox root | ||
| // if the currently displayed message has been moved | ||
| if (this.$route.name === 'message' && openedMessageHasBeenMoved) { | ||
| this.$router.push({ | ||
| name: 'mailbox', | ||
| params: { | ||
| mailboxId: this.$route.params.mailboxId, | ||
| filter: this.$route.params?.filter, | ||
| }, | ||
| }) | ||
| } | ||
| }, | ||
|
|
||
| /** | ||
| * Delete all vanished emails that are still cached. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
navigateToAdjacentEnvelope() uses findIndex(propEq(id, 'databaseId'), this.envelopes). propEq is strict-equality, so this will fail if route/thread IDs are numbers but envelope.databaseId values (or the passed id) are strings (or vice versa), preventing any navigation after delete/move in real routes (Thread.vue parses $route.params.threadId as int). Normalize IDs before comparing (e.g., compare String(databaseId) values, or parse both to numbers consistently).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same issue, read the other comments below