Allow books to be merged

This commit is contained in:
Tiberiu Ichim 2026-02-12 19:57:04 +02:00
parent fc97b10f58
commit 56eca37304
9 changed files with 615 additions and 25 deletions

View file

@ -36,6 +36,12 @@
</ui-tooltip>
</nuxt-link>
<nuxt-link v-if="userIsAdminOrUp && currentLibrary" :to="`/config/libraries?edit=${currentLibrary.id}`" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.HeaderUpdateLibrary" direction="bottom" class="flex items-center">
<span class="material-symbols text-2xl" aria-label="Edit Library" role="button">&#xe3c9;</span>
</ui-tooltip>
</nuxt-link>
<nuxt-link v-if="userIsAdminOrUp" to="/config" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1">
<ui-tooltip :text="$strings.HeaderSettings" direction="bottom" class="flex items-center">
<span class="material-symbols text-2xl" aria-label="System Settings" role="button">&#xe8b8;</span>
@ -195,6 +201,14 @@ export default {
text: this.$strings.LabelMoveToLibrary,
action: 'move-to-library'
})
// Merge option - only for books and if multiple selected
if (this.isBookLibrary && this.selectedMediaItems.length > 1) {
options.push({
text: this.$strings.LabelMerge,
action: 'merge'
})
}
}
return options
@ -236,8 +250,41 @@ export default {
this.batchDownload()
} else if (action === 'move-to-library') {
this.batchMoveToLibrary()
} else if (action === 'merge') {
this.batchMerge()
}
},
batchMerge() {
const payload = {
message: this.$strings.MessageConfirmBatchMerge,
callback: (confirmed) => {
if (confirmed) {
const libraryItemIds = this.selectedMediaItems.map((i) => i.id)
this.$store.commit('setProcessingBatch', true)
this.$axios
.$post('/api/items/batch/merge', { libraryItemIds })
.then((data) => {
if (data.success) {
this.$toast.success(this.$strings.ToastBatchMergeSuccess)
} else {
this.$toast.warning(this.$strings.ToastBatchMergePartiallySuccess)
}
this.cancelSelectionMode()
})
.catch((error) => {
console.error('Batch merge failed', error)
const errorMsg = error.response.data || this.$strings.ToastBatchMergeFailed
this.$toast.error(errorMsg)
})
.finally(() => {
this.$store.commit('setProcessingBatch', false)
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
batchMoveToLibrary() {
// Clear any single library item that might be lingering
this.$store.commit('setSelectedLibraryItem', null)

View file

@ -32,12 +32,32 @@ export default {
}
},
computed: {},
watch: {
'$route.query.edit': {
handler(val) {
if (val) {
const library = this.$store.state.libraries.libraries.find((lib) => lib.id === val)
if (library) {
this.setShowLibraryModal(library)
}
}
}
}
},
methods: {
setShowLibraryModal(selectedLibrary) {
this.selectedLibrary = selectedLibrary
this.showLibraryModal = true
}
},
mounted() {}
mounted() {
const editLibraryId = this.$route.query.edit
if (editLibraryId) {
const library = this.$store.state.libraries.libraries.find((lib) => lib.id === editLibraryId)
if (library) {
this.setShowLibraryModal(library)
}
}
}
}
</script>

View file

@ -460,6 +460,7 @@
"LabelMaxEpisodesToKeepHelp": "Value of 0 sets no max limit. After a new episode is auto-downloaded this will delete the oldest episode if you have more than X episodes. This will only delete 1 episode per new download.",
"LabelMediaPlayer": "Media Player",
"LabelMediaType": "Media Type",
"LabelMerge": "Merge",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
@ -772,6 +773,7 @@
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
"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.",
"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}?",
@ -1018,6 +1020,9 @@
"ToastBatchApplyDetailsToItemsSuccess": "Details applied to items",
"ToastBatchDeleteFailed": "Batch delete failed",
"ToastBatchDeleteSuccess": "Batch delete success",
"ToastBatchMergeFailed": "Failed to merge books",
"ToastBatchMergePartiallySuccess": "Books merged with some errors",
"ToastBatchMergeSuccess": "Books merged successfully",
"ToastBatchQuickMatchFailed": "Batch Quick Match failed!",
"ToastBatchQuickMatchStarted": "Batch Quick Match of {0} books started!",
"ToastBatchUpdateFailed": "Batch update failed",