Add consolidate feature

This commit is contained in:
Tiberiu Ichim 2026-02-13 14:15:18 +02:00
parent 56eca37304
commit 96707200b8
8 changed files with 294 additions and 13 deletions

View file

@ -209,6 +209,13 @@ export default {
action: 'merge'
})
}
if (this.isBookLibrary) {
options.push({
text: 'Consolidate',
action: 'consolidate'
})
}
}
return options
@ -252,8 +259,38 @@ export default {
this.batchMoveToLibrary()
} else if (action === 'merge') {
this.batchMerge()
} else if (action === 'consolidate') {
this.batchConsolidate()
}
},
batchConsolidate() {
const payload = {
message: this.$getString('MessageConfirmConsolidate', [this.$getString('MessageItemsSelected', [this.numMediaItemsSelected]), 'Author - Title']),
callback: (confirmed) => {
if (confirmed) {
this.$store.commit('setProcessingBatch', true)
this.$axios
.$post('/api/items/batch/consolidate', {
libraryItemIds: this.selectedMediaItems.map((i) => i.id)
})
.then((data) => {
this.$toast.success(this.$strings.ToastBatchConsolidateSuccess)
this.cancelSelectionMode()
})
.catch((error) => {
console.error('Batch consolidation failed', error)
const errorMsg = error.response?.data || this.$strings.ToastBatchConsolidateFailed
this.$toast.error(errorMsg)
})
.finally(() => {
this.$store.commit('setProcessingBatch', false)
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
batchMerge() {
const payload = {
message: this.$strings.MessageConfirmBatchMerge,
@ -266,10 +303,14 @@ export default {
.then((data) => {
if (data.success) {
this.$toast.success(this.$strings.ToastBatchMergeSuccess)
if (data.mergedItemId) {
this.$router.push(`/item/${data.mergedItemId}`)
}
} else {
this.$toast.warning(this.$strings.ToastBatchMergePartiallySuccess)
}
this.cancelSelectionMode()
this.$store.commit('globals/resetSelectedMediaItems', [])
this.$eventBus.$emit('bookshelf_clear_selection')
})
.catch((error) => {
console.error('Batch merge failed', error)

View file

@ -565,6 +565,12 @@ export default {
func: 'showEditModalMatch',
text: this.$strings.HeaderMatch
})
if (!this.isFile && !this.isPodcast) {
items.push({
func: 'consolidate',
text: 'Consolidate'
})
}
}
if ((this.userIsAdminOrUp || this.userCanDelete) && !this.isFile) {
items.push({
@ -799,6 +805,31 @@ export default {
// More menu func
this.$emit('edit', this.libraryItem, 'match')
},
consolidate() {
const payload = {
message: this.$getString('MessageConfirmConsolidate', [this.title, `${this.author} - ${this.title}`]),
callback: (confirmed) => {
if (confirmed) {
this.processing = true
const axios = this.$axios || this.$nuxt.$axios
axios
.$post(`/api/items/${this.libraryItemId}/consolidate`)
.then(() => {
this.$toast.success(this.$strings.ToastConsolidateSuccess || 'Consolidate successful')
})
.catch((error) => {
console.error('Failed to consolidate', error)
this.$toast.error(error.response?.data || this.$strings.ToastConsolidateFailed || 'Consolidate failed')
})
.finally(() => {
this.processing = false
})
}
},
type: 'yesNo'
}
this.store.commit('globals/setConfirmPrompt', payload)
},
sendToDevice(deviceName) {
// More menu func
const payload = {

View file

@ -428,6 +428,12 @@ export default {
text: this.$strings.ButtonReScan,
action: 'rescan'
})
if (!this.isFile && !this.isPodcast) {
items.push({
text: 'Consolidate',
action: 'consolidate'
})
}
items.push({
text: this.$strings.ButtonMoveToLibrary,
action: 'move'
@ -786,6 +792,31 @@ export default {
this.processing = false
})
},
consolidate() {
const author = this.authors?.[0]?.name || 'Unknown Author'
const payload = {
message: this.$getString('MessageConfirmConsolidate', [this.title, `${author} - ${this.title}`]),
callback: (confirmed) => {
if (confirmed) {
this.processing = true
this.$axios
.$post(`/api/items/${this.libraryItemId}/consolidate`)
.then(() => {
this.$toast.success(this.$strings.ToastConsolidateSuccess)
})
.catch((error) => {
console.error('Failed to consolidate', error)
this.$toast.error(error.response?.data || this.$strings.ToastConsolidateFailed)
})
.finally(() => {
this.processing = false
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
contextMenuAction({ action, data }) {
if (action === 'collections') {
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
@ -806,6 +837,8 @@ export default {
} else if (action === 'move') {
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
this.$store.commit('globals/setShowMoveToLibraryModal', true)
} else if (action === 'consolidate') {
this.consolidate()
} else if (action === 'sendToDevice') {
this.sendToDevice(data)
} else if (action === 'share') {

View file

@ -774,6 +774,7 @@
"MessageChaptersNotFound": "Chapters not found",
"MessageCheckingCron": "Checking cron...",
"MessageConfirmBatchMerge": "Are you sure you want to merge the selected books into a single book? The files will be moved to the first selected book's folder, and other books will be deleted.",
"MessageConfirmConsolidate": "Are you sure you want to consolidate \"{0}\"? This will rename the folder to \"{1}\" and move it to the library root.",
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
"MessageConfirmDeleteApiKey": "Are you sure you want to delete API key \"{0}\"?",
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
@ -1020,6 +1021,10 @@
"ToastBatchApplyDetailsToItemsSuccess": "Details applied to items",
"ToastBatchDeleteFailed": "Batch delete failed",
"ToastBatchDeleteSuccess": "Batch delete success",
"ToastConsolidateFailed": "Consolidate failed",
"ToastConsolidateSuccess": "Consolidate successful",
"ToastBatchConsolidateFailed": "Batch consolidate failed",
"ToastBatchConsolidateSuccess": "Batch consolidate successful",
"ToastBatchMergeFailed": "Failed to merge books",
"ToastBatchMergePartiallySuccess": "Books merged with some errors",
"ToastBatchMergeSuccess": "Books merged successfully",