mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Merge pull request #3487 from mikiher/lazy-bookshelf-authors
Move authors to LazyBookshelf
This commit is contained in:
commit
a7ac82b023
@ -24,7 +24,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="shelf.type === 'authors'" class="flex items-center">
|
<div v-if="shelf.type === 'authors'" class="flex items-center">
|
||||||
<template v-for="entity in shelf.entities">
|
<template v-for="entity in shelf.entities">
|
||||||
<cards-author-card :key="entity.id" :author="entity" @hook:updated="updatedBookCard" class="mx-2e" @edit="editAuthor" />
|
<cards-author-card :key="entity.id" :authorMount="entity" @hook:updated="updatedBookCard" class="mx-2e" @edit="editAuthor" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shelf.type === 'narrators'" class="flex items-center">
|
<div v-if="shelf.type === 'narrators'" class="flex items-center">
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<p v-if="isCollectionsPage" class="text-sm">{{ $strings.ButtonCollections }}</p>
|
<p v-if="isCollectionsPage" class="text-sm">{{ $strings.ButtonCollections }}</p>
|
||||||
<span v-else class="material-symbols text-lg"></span>
|
<span v-else class="material-symbols text-lg"></span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/authors`" class="flex-grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
|
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="flex-grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-primary bg-opacity-40'">
|
||||||
<p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p>
|
<p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p>
|
||||||
<svg v-else class="w-5 h-5" viewBox="0 0 24 24">
|
<svg v-else class="w-5 h-5" viewBox="0 0 24 24">
|
||||||
<path
|
<path
|
||||||
@ -62,7 +62,7 @@
|
|||||||
<ui-context-menu-dropdown v-if="!isBatchSelecting && seriesContextMenuItems.length" :items="seriesContextMenuItems" class="mx-px" @action="seriesContextMenuAction" />
|
<ui-context-menu-dropdown v-if="!isBatchSelecting && seriesContextMenuItems.length" :items="seriesContextMenuItems" class="mx-px" @action="seriesContextMenuAction" />
|
||||||
</template>
|
</template>
|
||||||
<!-- library & collections page -->
|
<!-- library & collections page -->
|
||||||
<template v-else-if="page !== 'search' && page !== 'podcast-search' && page !== 'recent-episodes' && !isHome">
|
<template v-else-if="page !== 'search' && page !== 'podcast-search' && page !== 'recent-episodes' && !isHome && !isAuthorsPage">
|
||||||
<p class="hidden md:block">{{ $formatNumber(numShowing) }} {{ entityName }}</p>
|
<p class="hidden md:block">{{ $formatNumber(numShowing) }} {{ entityName }}</p>
|
||||||
|
|
||||||
<div class="flex-grow hidden sm:inline-block" />
|
<div class="flex-grow hidden sm:inline-block" />
|
||||||
@ -92,12 +92,14 @@
|
|||||||
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
|
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
|
||||||
</template>
|
</template>
|
||||||
<!-- authors page -->
|
<!-- authors page -->
|
||||||
<template v-else-if="page === 'authors'">
|
<template v-else-if="isAuthorsPage">
|
||||||
<div class="flex-grow" />
|
<p class="hidden md:block">{{ $formatNumber(numShowing) }} {{ entityName }}</p>
|
||||||
<ui-btn v-if="userCanUpdate && authors?.length && !isBatchSelecting" :loading="processingAuthors" color="primary" small @click="matchAllAuthors">{{ $strings.ButtonMatchAllAuthors }}</ui-btn>
|
|
||||||
|
<div class="flex-grow hidden sm:inline-block" />
|
||||||
|
<ui-btn v-if="userCanUpdate && !isBatchSelecting" :loading="processingAuthors" color="primary" small @click="matchAllAuthors">{{ $strings.ButtonMatchAllAuthors }}</ui-btn>
|
||||||
|
|
||||||
<!-- author sort select -->
|
<!-- author sort select -->
|
||||||
<controls-sort-select v-if="authors?.length" v-model="settings.authorSortBy" :descending.sync="settings.authorSortDesc" :items="authorSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateAuthorSort" />
|
<controls-sort-select v-model="settings.authorSortBy" :descending.sync="settings.authorSortDesc" :items="authorSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateAuthorSort" />
|
||||||
</template>
|
</template>
|
||||||
<!-- home page -->
|
<!-- home page -->
|
||||||
<template v-else-if="isHome">
|
<template v-else-if="isHome">
|
||||||
@ -117,11 +119,7 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => null
|
default: () => null
|
||||||
},
|
},
|
||||||
searchQuery: String,
|
searchQuery: String
|
||||||
authors: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -268,7 +266,7 @@ export default {
|
|||||||
return this.$route.name === 'library-library-podcast-latest'
|
return this.$route.name === 'library-library-podcast-latest'
|
||||||
},
|
},
|
||||||
isAuthorsPage() {
|
isAuthorsPage() {
|
||||||
return this.$route.name === 'library-library-authors'
|
return this.page === 'authors'
|
||||||
},
|
},
|
||||||
isAlbumsPage() {
|
isAlbumsPage() {
|
||||||
return this.page === 'albums'
|
return this.page === 'albums'
|
||||||
@ -284,6 +282,7 @@ export default {
|
|||||||
if (this.isSeriesPage) return this.$strings.LabelSeries
|
if (this.isSeriesPage) return this.$strings.LabelSeries
|
||||||
if (this.isCollectionsPage) return this.$strings.LabelCollections
|
if (this.isCollectionsPage) return this.$strings.LabelCollections
|
||||||
if (this.isPlaylistsPage) return this.$strings.LabelPlaylists
|
if (this.isPlaylistsPage) return this.$strings.LabelPlaylists
|
||||||
|
if (this.isAuthorsPage) return this.$strings.LabelAuthors
|
||||||
return ''
|
return ''
|
||||||
},
|
},
|
||||||
seriesId() {
|
seriesId() {
|
||||||
@ -479,10 +478,18 @@ export default {
|
|||||||
this.processingSeries = false
|
this.processingSeries = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
async fetchAllAuthors() {
|
||||||
|
// fetch all authors from the server, in the order that they are currently displayed
|
||||||
|
const response = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/authors?sort=${this.settings.authorSortBy}&desc=${this.settings.authorSortDesc}`)
|
||||||
|
return response.authors
|
||||||
|
},
|
||||||
async matchAllAuthors() {
|
async matchAllAuthors() {
|
||||||
this.processingAuthors = true
|
this.processingAuthors = true
|
||||||
|
|
||||||
for (const author of this.authors) {
|
try {
|
||||||
|
const authors = await this.fetchAllAuthors()
|
||||||
|
|
||||||
|
for (const author of authors) {
|
||||||
const payload = {}
|
const payload = {}
|
||||||
if (author.asin) payload.asin = author.asin
|
if (author.asin) payload.asin = author.asin
|
||||||
else payload.q = author.name
|
else payload.q = author.name
|
||||||
@ -510,6 +517,10 @@ export default {
|
|||||||
|
|
||||||
this.$eventBus.$emit(`searching-author-${author.id}`, false)
|
this.$eventBus.$emit(`searching-author-${author.id}`, false)
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to match all authors', error)
|
||||||
|
this.$toast.error(this.$strings.ToastMatchAllAuthorsFailed)
|
||||||
|
}
|
||||||
this.processingAuthors = false
|
this.processingAuthors = false
|
||||||
},
|
},
|
||||||
removeAllIssues() {
|
removeAllIssues() {
|
||||||
|
@ -91,6 +91,7 @@ export default {
|
|||||||
if (this.page === 'series') return this.$strings.MessageBookshelfNoSeries
|
if (this.page === 'series') return this.$strings.MessageBookshelfNoSeries
|
||||||
if (this.page === 'collections') return this.$strings.MessageBookshelfNoCollections
|
if (this.page === 'collections') return this.$strings.MessageBookshelfNoCollections
|
||||||
if (this.page === 'playlists') return this.$strings.MessageNoUserPlaylists
|
if (this.page === 'playlists') return this.$strings.MessageNoUserPlaylists
|
||||||
|
if (this.page === 'authors') return this.$strings.MessageNoAuthors
|
||||||
if (this.hasFilter) {
|
if (this.hasFilter) {
|
||||||
if (this.filterName === 'Issues') return this.$strings.MessageNoIssues
|
if (this.filterName === 'Issues') return this.$strings.MessageNoIssues
|
||||||
else if (this.filterName === 'Feed-open') return this.$strings.MessageBookshelfNoRSSFeeds
|
else if (this.filterName === 'Feed-open') return this.$strings.MessageBookshelfNoRSSFeeds
|
||||||
@ -111,6 +112,12 @@ export default {
|
|||||||
seriesFilterBy() {
|
seriesFilterBy() {
|
||||||
return this.$store.getters['user/getUserSetting']('seriesFilterBy')
|
return this.$store.getters['user/getUserSetting']('seriesFilterBy')
|
||||||
},
|
},
|
||||||
|
authorSortBy() {
|
||||||
|
return this.$store.getters['user/getUserSetting']('authorSortBy')
|
||||||
|
},
|
||||||
|
authorSortDesc() {
|
||||||
|
return !!this.$store.getters['user/getUserSetting']('authorSortDesc')
|
||||||
|
},
|
||||||
orderBy() {
|
orderBy() {
|
||||||
return this.$store.getters['user/getUserSetting']('orderBy')
|
return this.$store.getters['user/getUserSetting']('orderBy')
|
||||||
},
|
},
|
||||||
@ -217,6 +224,8 @@ export default {
|
|||||||
this.$store.commit('globals/setEditCollection', entity)
|
this.$store.commit('globals/setEditCollection', entity)
|
||||||
} else if (this.entityName === 'playlists') {
|
} else if (this.entityName === 'playlists') {
|
||||||
this.$store.commit('globals/setEditPlaylist', entity)
|
this.$store.commit('globals/setEditPlaylist', entity)
|
||||||
|
} else if (this.entityName === 'authors') {
|
||||||
|
this.$store.commit('globals/showEditAuthorModal', entity)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearSelectedEntities() {
|
clearSelectedEntities() {
|
||||||
@ -457,6 +466,9 @@ export default {
|
|||||||
if (this.collapseBookSeries) {
|
if (this.collapseBookSeries) {
|
||||||
searchParams.set('collapseseries', 1)
|
searchParams.set('collapseseries', 1)
|
||||||
}
|
}
|
||||||
|
} else if (this.page === 'authors') {
|
||||||
|
searchParams.set('sort', this.authorSortBy)
|
||||||
|
searchParams.set('desc', this.authorSortDesc ? 1 : 0)
|
||||||
} else {
|
} else {
|
||||||
if (this.filterBy && this.filterBy !== 'all') {
|
if (this.filterBy && this.filterBy !== 'all') {
|
||||||
searchParams.set('filter', this.filterBy)
|
searchParams.set('filter', this.filterBy)
|
||||||
@ -601,6 +613,34 @@ export default {
|
|||||||
this.executeRebuild()
|
this.executeRebuild()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
authorAdded(author) {
|
||||||
|
if (this.entityName !== 'authors') return
|
||||||
|
console.log(`[LazyBookshelf] authorAdded ${author.id}`, author)
|
||||||
|
this.resetEntities()
|
||||||
|
},
|
||||||
|
authorUpdated(author) {
|
||||||
|
if (this.entityName !== 'authors') return
|
||||||
|
console.log(`[LazyBookshelf] authorUpdated ${author.id}`, author)
|
||||||
|
const indexOf = this.entities.findIndex((ent) => ent && ent.id === author.id)
|
||||||
|
if (indexOf >= 0) {
|
||||||
|
this.entities[indexOf] = author
|
||||||
|
if (this.entityComponentRefs[indexOf]) {
|
||||||
|
this.entityComponentRefs[indexOf].setEntity(author)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authorRemoved(author) {
|
||||||
|
if (this.entityName !== 'authors') return
|
||||||
|
console.log(`[LazyBookshelf] authorRemoved ${author.id}`, author)
|
||||||
|
const indexOf = this.entities.findIndex((ent) => ent && ent.id === author.id)
|
||||||
|
if (indexOf >= 0) {
|
||||||
|
this.entities = this.entities.filter((ent) => ent.id !== author.id)
|
||||||
|
this.totalEntities--
|
||||||
|
this.$eventBus.$emit('bookshelf-total-entities', this.totalEntities)
|
||||||
|
this.executeRebuild()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
shareOpen(mediaItemShare) {
|
shareOpen(mediaItemShare) {
|
||||||
if (this.entityName === 'items' || this.entityName === 'series-books') {
|
if (this.entityName === 'items' || this.entityName === 'series-books') {
|
||||||
var indexOf = this.entities.findIndex((ent) => ent?.media?.id === mediaItemShare.mediaItemId)
|
var indexOf = this.entities.findIndex((ent) => ent?.media?.id === mediaItemShare.mediaItemId)
|
||||||
@ -727,6 +767,9 @@ export default {
|
|||||||
this.$root.socket.on('playlist_added', this.playlistAdded)
|
this.$root.socket.on('playlist_added', this.playlistAdded)
|
||||||
this.$root.socket.on('playlist_updated', this.playlistUpdated)
|
this.$root.socket.on('playlist_updated', this.playlistUpdated)
|
||||||
this.$root.socket.on('playlist_removed', this.playlistRemoved)
|
this.$root.socket.on('playlist_removed', this.playlistRemoved)
|
||||||
|
this.$root.socket.on('author_added', this.authorAdded)
|
||||||
|
this.$root.socket.on('author_updated', this.authorUpdated)
|
||||||
|
this.$root.socket.on('author_removed', this.authorRemoved)
|
||||||
this.$root.socket.on('share_open', this.shareOpen)
|
this.$root.socket.on('share_open', this.shareOpen)
|
||||||
this.$root.socket.on('share_closed', this.shareClosed)
|
this.$root.socket.on('share_closed', this.shareClosed)
|
||||||
} else {
|
} else {
|
||||||
@ -756,6 +799,9 @@ export default {
|
|||||||
this.$root.socket.off('playlist_added', this.playlistAdded)
|
this.$root.socket.off('playlist_added', this.playlistAdded)
|
||||||
this.$root.socket.off('playlist_updated', this.playlistUpdated)
|
this.$root.socket.off('playlist_updated', this.playlistUpdated)
|
||||||
this.$root.socket.off('playlist_removed', this.playlistRemoved)
|
this.$root.socket.off('playlist_removed', this.playlistRemoved)
|
||||||
|
this.$root.socket.off('author_added', this.authorAdded)
|
||||||
|
this.$root.socket.off('author_updated', this.authorUpdated)
|
||||||
|
this.$root.socket.off('author_removed', this.authorRemoved)
|
||||||
this.$root.socket.off('share_open', this.shareOpen)
|
this.$root.socket.off('share_open', this.shareOpen)
|
||||||
this.$root.socket.off('share_closed', this.shareClosed)
|
this.$root.socket.off('share_closed', this.shareClosed)
|
||||||
} else {
|
} else {
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
<div v-show="isPlaylistsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
<div v-show="isPlaylistsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/authors`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isAuthorsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
<svg class="w-6 h-6" viewBox="0 0 24 24">
|
<svg class="w-6 h-6" viewBox="0 0 24 24">
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@ -180,7 +180,7 @@ export default {
|
|||||||
return this.$route.name === 'library-library-series-id' || this.paramId === 'series'
|
return this.$route.name === 'library-library-series-id' || this.paramId === 'series'
|
||||||
},
|
},
|
||||||
isAuthorsPage() {
|
isAuthorsPage() {
|
||||||
return this.$route.name === 'library-library-authors'
|
return this.libraryBookshelfPage && this.paramId === 'authors'
|
||||||
},
|
},
|
||||||
isNarratorsPage() {
|
isNarratorsPage() {
|
||||||
return this.$route.name === 'library-library-narrators'
|
return this.$route.name === 'library-library-narrators'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :style="{ minWidth: cardWidth + 'px', maxWidth: cardWidth + 'px' }">
|
<div class="pb-3e" :style="{ minWidth: cardWidth + 'px', maxWidth: cardWidth + 'px' }">
|
||||||
<nuxt-link :to="`/author/${author.id}`">
|
<nuxt-link :to="`/author/${author?.id}`">
|
||||||
<div cy-id="card" @mouseover="mouseover" @mouseleave="mouseleave">
|
<div cy-id="card" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||||
<div cy-id="imageArea" :style="{ height: cardHeight + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
<div cy-id="imageArea" :style="{ height: cardHeight + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||||
<!-- Image or placeholder -->
|
<!-- Image or placeholder -->
|
||||||
@ -40,7 +40,7 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
author: {
|
authorMount: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
@ -57,7 +57,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
searching: false,
|
searching: false,
|
||||||
isHovering: false
|
isHovering: false,
|
||||||
|
author: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -68,34 +69,37 @@ export default {
|
|||||||
return this.height * this.sizeMultiplier
|
return this.height * this.sizeMultiplier
|
||||||
},
|
},
|
||||||
userToken() {
|
userToken() {
|
||||||
return this.$store.getters['user/getToken']
|
return this.store.getters['user/getToken']
|
||||||
},
|
},
|
||||||
_author() {
|
_author() {
|
||||||
return this.author || {}
|
return this.author || {}
|
||||||
},
|
},
|
||||||
authorId() {
|
authorId() {
|
||||||
return this._author.id
|
return this._author?.id || ''
|
||||||
},
|
},
|
||||||
name() {
|
name() {
|
||||||
return this._author.name || ''
|
return this._author?.name || ''
|
||||||
},
|
},
|
||||||
asin() {
|
asin() {
|
||||||
return this._author.asin || ''
|
return this._author?.asin || ''
|
||||||
},
|
},
|
||||||
numBooks() {
|
numBooks() {
|
||||||
return this._author.numBooks || 0
|
return this._author?.numBooks || 0
|
||||||
|
},
|
||||||
|
store() {
|
||||||
|
return this.$store || this.$nuxt.$store
|
||||||
},
|
},
|
||||||
userCanUpdate() {
|
userCanUpdate() {
|
||||||
return this.$store.getters['user/getUserCanUpdate']
|
return this.store.getters['user/getUserCanUpdate']
|
||||||
},
|
},
|
||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
libraryProvider() {
|
libraryProvider() {
|
||||||
return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google'
|
return this.store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google'
|
||||||
},
|
},
|
||||||
sizeMultiplier() {
|
sizeMultiplier() {
|
||||||
return this.$store.getters['user/getSizeMultiplier']
|
return this.store.getters['user/getSizeMultiplier']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -132,13 +136,40 @@ export default {
|
|||||||
},
|
},
|
||||||
setSearching(isSearching) {
|
setSearching(isSearching) {
|
||||||
this.searching = isSearching
|
this.searching = isSearching
|
||||||
|
},
|
||||||
|
setEntity(author) {
|
||||||
|
this.removeListeners()
|
||||||
|
this.author = author
|
||||||
|
this.addListeners()
|
||||||
|
},
|
||||||
|
addListeners() {
|
||||||
|
if (this.author) {
|
||||||
|
this.$eventBus.$on(`searching-author-${this.authorId}`, this.setSearching)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
removeListeners() {
|
||||||
|
if (this.author) {
|
||||||
|
this.$eventBus.$off(`searching-author-${this.authorId}`, this.setSearching)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
// destroy the vue listeners, etc
|
||||||
|
this.$destroy()
|
||||||
|
|
||||||
|
// remove the element from the DOM
|
||||||
|
if (this.$el && this.$el.parentNode) {
|
||||||
|
this.$el.parentNode.removeChild(this.$el)
|
||||||
|
} else if (this.$el && this.$el.remove) {
|
||||||
|
this.$el.remove()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSelectionMode(val) {}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$eventBus.$on(`searching-author-${this.authorId}`, this.setSearching)
|
if (this.authorMount) this.setEntity(this.authorMount)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.$eventBus.$off(`searching-author-${this.authorId}`, this.setSearching)
|
this.removeListeners()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -65,7 +65,7 @@ export default {
|
|||||||
},
|
},
|
||||||
authors: {
|
authors: {
|
||||||
component: 'cards-author-card',
|
component: 'cards-author-card',
|
||||||
itemPropName: 'author',
|
itemPropName: 'author-mount',
|
||||||
itemIdFunc: (item) => item.id
|
itemIdFunc: (item) => item.id
|
||||||
},
|
},
|
||||||
narrators: {
|
narrators: {
|
||||||
|
@ -5,14 +5,14 @@ import Tooltip from '@/components/ui/Tooltip.vue'
|
|||||||
import LoadingSpinner from '@/components/widgets/LoadingSpinner.vue'
|
import LoadingSpinner from '@/components/widgets/LoadingSpinner.vue'
|
||||||
|
|
||||||
describe('AuthorCard', () => {
|
describe('AuthorCard', () => {
|
||||||
const author = {
|
const authorMount = {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'John Doe',
|
name: 'John Doe',
|
||||||
numBooks: 5
|
numBooks: 5
|
||||||
}
|
}
|
||||||
|
|
||||||
const propsData = {
|
const propsData = {
|
||||||
author,
|
authorMount,
|
||||||
nameBelow: false
|
nameBelow: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import LazySeriesCard from '@/components/cards/LazySeriesCard'
|
|||||||
import LazyCollectionCard from '@/components/cards/LazyCollectionCard'
|
import LazyCollectionCard from '@/components/cards/LazyCollectionCard'
|
||||||
import LazyPlaylistCard from '@/components/cards/LazyPlaylistCard'
|
import LazyPlaylistCard from '@/components/cards/LazyPlaylistCard'
|
||||||
import LazyAlbumCard from '@/components/cards/LazyAlbumCard'
|
import LazyAlbumCard from '@/components/cards/LazyAlbumCard'
|
||||||
|
import AuthorCard from '@/components/cards/AuthorCard'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@ -20,6 +21,7 @@ export default {
|
|||||||
if (this.entityName === 'collections') return Vue.extend(LazyCollectionCard)
|
if (this.entityName === 'collections') return Vue.extend(LazyCollectionCard)
|
||||||
if (this.entityName === 'playlists') return Vue.extend(LazyPlaylistCard)
|
if (this.entityName === 'playlists') return Vue.extend(LazyPlaylistCard)
|
||||||
if (this.entityName === 'albums') return Vue.extend(LazyAlbumCard)
|
if (this.entityName === 'albums') return Vue.extend(LazyAlbumCard)
|
||||||
|
if (this.entityName === 'authors') return Vue.extend(AuthorCard)
|
||||||
return Vue.extend(LazyBookCard)
|
return Vue.extend(LazyBookCard)
|
||||||
},
|
},
|
||||||
getComponentName() {
|
getComponentName() {
|
||||||
@ -27,6 +29,7 @@ export default {
|
|||||||
if (this.entityName === 'collections') return 'cards-lazy-collection-card'
|
if (this.entityName === 'collections') return 'cards-lazy-collection-card'
|
||||||
if (this.entityName === 'playlists') return 'cards-lazy-playlist-card'
|
if (this.entityName === 'playlists') return 'cards-lazy-playlist-card'
|
||||||
if (this.entityName === 'albums') return 'cards-lazy-album-card'
|
if (this.entityName === 'albums') return 'cards-lazy-album-card'
|
||||||
|
if (this.entityName === 'authors') return 'cards-author-card'
|
||||||
return 'cards-lazy-book-card'
|
return 'cards-lazy-book-card'
|
||||||
},
|
},
|
||||||
async setCardSize() {
|
async setCardSize() {
|
||||||
@ -46,13 +49,14 @@ export default {
|
|||||||
props.orderBy = this.seriesSortBy
|
props.orderBy = this.seriesSortBy
|
||||||
}
|
}
|
||||||
const instance = new ComponentClass({
|
const instance = new ComponentClass({
|
||||||
propsData: props
|
propsData: props,
|
||||||
|
parent: this
|
||||||
})
|
})
|
||||||
instance.$mount()
|
instance.$mount()
|
||||||
this.resizeObserver = new ResizeObserver((entries) => {
|
this.resizeObserver = new ResizeObserver((entries) => {
|
||||||
for (let entry of entries) {
|
for (let entry of entries) {
|
||||||
this.cardWidth = entry.contentRect.width
|
this.cardWidth = entry.borderBoxSize[0].inlineSize
|
||||||
this.cardHeight = entry.contentRect.height
|
this.cardHeight = entry.borderBoxSize[0].blockSize
|
||||||
this.resizeObserver.disconnect()
|
this.resizeObserver.disconnect()
|
||||||
this.$refs.bookshelf.removeChild(instance.$el)
|
this.$refs.bookshelf.removeChild(instance.$el)
|
||||||
}
|
}
|
||||||
@ -72,7 +76,7 @@ export default {
|
|||||||
})
|
})
|
||||||
const timeAfter = performance.now()
|
const timeAfter = performance.now()
|
||||||
},
|
},
|
||||||
async mountEntityCard(index) {
|
mountEntityCard(index) {
|
||||||
var shelf = Math.floor(index / this.entitiesPerShelf)
|
var shelf = Math.floor(index / this.entitiesPerShelf)
|
||||||
var shelfEl = document.getElementById(`shelf-${shelf}`)
|
var shelfEl = document.getElementById(`shelf-${shelf}`)
|
||||||
if (!shelfEl) {
|
if (!shelfEl) {
|
||||||
@ -114,6 +118,7 @@ export default {
|
|||||||
const _this = this
|
const _this = this
|
||||||
const instance = new ComponentClass({
|
const instance = new ComponentClass({
|
||||||
propsData: props,
|
propsData: props,
|
||||||
|
parent: this,
|
||||||
created() {
|
created() {
|
||||||
this.$on('edit', (entity) => {
|
this.$on('edit', (entity) => {
|
||||||
if (_this.editEntity) _this.editEntity(entity)
|
if (_this.editEntity) _this.editEntity(entity)
|
||||||
|
@ -53,7 +53,7 @@ export default {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!author) {
|
if (!author) {
|
||||||
return redirect(`/library/${store.state.libraries.currentLibraryId}/authors`)
|
return redirect(`/library/${store.state.libraries.currentLibraryId}/bookshelf/authors`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.state.libraries.currentLibraryId !== author.libraryId || !store.state.libraries.filterData) {
|
if (store.state.libraries.currentLibraryId !== author.libraryId || !store.state.libraries.filterData) {
|
||||||
@ -109,7 +109,7 @@ export default {
|
|||||||
authorRemoved(author) {
|
authorRemoved(author) {
|
||||||
if (author.id === this.author.id) {
|
if (author.id === this.author.id) {
|
||||||
console.warn('Author was removed')
|
console.warn('Author was removed')
|
||||||
this.$router.replace(`/library/${this.currentLibraryId}/authors`)
|
this.$router.replace(`/library/${this.currentLibraryId}/bookshelf/authors`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
|
|
||||||
<app-book-shelf-toolbar page="authors" is-home :authors="authors" />
|
|
||||||
<div id="bookshelf" class="w-full h-full p-8e overflow-y-auto" :style="{ fontSize: sizeMultiplier + 'rem' }">
|
|
||||||
<!-- Cover size widget -->
|
|
||||||
<widgets-cover-size-widget class="fixed right-4 z-50" :style="{ bottom: streamLibraryItem ? '181px' : '16px' }" />
|
|
||||||
<div class="flex flex-wrap justify-center">
|
|
||||||
<template v-for="author in authorsSorted">
|
|
||||||
<cards-author-card :key="author.id" :author="author" class="p-3e" @edit="editAuthor" />
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
async asyncData({ store, params, redirect, query, app }) {
|
|
||||||
var libraryId = params.library
|
|
||||||
var libraryData = await store.dispatch('libraries/fetch', libraryId)
|
|
||||||
if (!libraryData) {
|
|
||||||
return redirect('/oops?message=Library not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
const library = libraryData.library
|
|
||||||
if (library.mediaType === 'podcast') {
|
|
||||||
return redirect(`/library/${libraryId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
libraryId
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: true,
|
|
||||||
authors: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sizeMultiplier() {
|
|
||||||
return this.$store.getters['user/getSizeMultiplier']
|
|
||||||
},
|
|
||||||
streamLibraryItem() {
|
|
||||||
return this.$store.state.streamLibraryItem
|
|
||||||
},
|
|
||||||
currentLibraryId() {
|
|
||||||
return this.$store.state.libraries.currentLibraryId
|
|
||||||
},
|
|
||||||
selectedAuthor() {
|
|
||||||
return this.$store.state.globals.selectedAuthor
|
|
||||||
},
|
|
||||||
authorSortBy() {
|
|
||||||
return this.$store.getters['user/getUserSetting']('authorSortBy') || 'name'
|
|
||||||
},
|
|
||||||
authorSortDesc() {
|
|
||||||
return !!this.$store.getters['user/getUserSetting']('authorSortDesc')
|
|
||||||
},
|
|
||||||
authorsSorted() {
|
|
||||||
const sortProp = this.authorSortBy
|
|
||||||
const bDesc = this.authorSortDesc ? -1 : 1
|
|
||||||
return this.authors.sort((a, b) => {
|
|
||||||
if (typeof a[sortProp] === 'number' && typeof b[sortProp] === 'number') {
|
|
||||||
// Fallback to name sort if equal
|
|
||||||
if (a[sortProp] === b[sortProp]) return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) * bDesc
|
|
||||||
return a[sortProp] > b[sortProp] ? bDesc : -bDesc
|
|
||||||
}
|
|
||||||
return a[sortProp]?.localeCompare(b[sortProp], undefined, { sensitivity: 'base' }) * bDesc
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async init() {
|
|
||||||
this.authors = await this.$axios
|
|
||||||
.$get(`/api/libraries/${this.currentLibraryId}/authors`)
|
|
||||||
.then((response) => response.authors)
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to load authors', error)
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
this.loading = false
|
|
||||||
},
|
|
||||||
authorAdded(author) {
|
|
||||||
if (!this.authors.some((au) => au.id === author.id)) {
|
|
||||||
this.authors.push(author)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authorUpdated(author) {
|
|
||||||
this.authors = this.authors.map((au) => {
|
|
||||||
if (au.id === author.id) {
|
|
||||||
return author
|
|
||||||
}
|
|
||||||
return au
|
|
||||||
})
|
|
||||||
},
|
|
||||||
authorRemoved(author) {
|
|
||||||
this.authors = this.authors.filter((au) => au.id !== author.id)
|
|
||||||
},
|
|
||||||
editAuthor(author) {
|
|
||||||
this.$store.commit('globals/showEditAuthorModal', author)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.init()
|
|
||||||
this.$root.socket.on('author_added', this.authorAdded)
|
|
||||||
this.$root.socket.on('author_updated', this.authorUpdated)
|
|
||||||
this.$root.socket.on('author_removed', this.authorRemoved)
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.$root.socket.off('author_added', this.authorAdded)
|
|
||||||
this.$root.socket.off('author_updated', this.authorUpdated)
|
|
||||||
this.$root.socket.off('author_removed', this.authorRemoved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -27,7 +27,7 @@ export default {
|
|||||||
|
|
||||||
// Redirect podcast libraries
|
// Redirect podcast libraries
|
||||||
const library = libraryData.library
|
const library = libraryData.library
|
||||||
if (library.mediaType === 'podcast' && (params.id === 'collections' || params.id === 'series')) {
|
if (library.mediaType === 'podcast' && (params.id === 'collections' || params.id === 'series' || params.id === 'authors')) {
|
||||||
return redirect(`/library/${libraryId}`)
|
return redirect(`/library/${libraryId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -920,6 +920,7 @@
|
|||||||
"ToastLibraryScanFailedToStart": "Failed to start scan",
|
"ToastLibraryScanFailedToStart": "Failed to start scan",
|
||||||
"ToastLibraryScanStarted": "Library scan started",
|
"ToastLibraryScanStarted": "Library scan started",
|
||||||
"ToastLibraryUpdateSuccess": "Library \"{0}\" updated",
|
"ToastLibraryUpdateSuccess": "Library \"{0}\" updated",
|
||||||
|
"ToastMatchAllAuthorsFailed": "Failed to match all authors",
|
||||||
"ToastNameEmailRequired": "Name and email are required",
|
"ToastNameEmailRequired": "Name and email are required",
|
||||||
"ToastNameRequired": "Name is required",
|
"ToastNameRequired": "Name is required",
|
||||||
"ToastNewUserCreatedFailed": "Failed to create account: \"{0}\"",
|
"ToastNewUserCreatedFailed": "Failed to create account: \"{0}\"",
|
||||||
|
@ -493,8 +493,8 @@ class LibraryController {
|
|||||||
const payload = {
|
const payload = {
|
||||||
results: [],
|
results: [],
|
||||||
total: undefined,
|
total: undefined,
|
||||||
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
|
limit: req.query.limit,
|
||||||
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
|
page: req.query.page,
|
||||||
sortBy: req.query.sort,
|
sortBy: req.query.sort,
|
||||||
sortDesc: req.query.desc === '1',
|
sortDesc: req.query.desc === '1',
|
||||||
filterBy: req.query.filter,
|
filterBy: req.query.filter,
|
||||||
@ -504,13 +504,6 @@ class LibraryController {
|
|||||||
include: include.join(',')
|
include: include.join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Number.isInteger(payload.limit) || payload.limit < 0) {
|
|
||||||
return res.status(400).send('Invalid request. Limit must be a positive integer')
|
|
||||||
}
|
|
||||||
if (!Number.isInteger(payload.page) || payload.page < 0) {
|
|
||||||
return res.status(400).send('Invalid request. Page must be a positive integer')
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.offset = payload.page * payload.limit
|
payload.offset = payload.page * payload.limit
|
||||||
|
|
||||||
// TODO: Temporary way of handling collapse sub-series. Either remove feature or handle through sql queries
|
// TODO: Temporary way of handling collapse sub-series. Either remove feature or handle through sql queries
|
||||||
@ -602,8 +595,8 @@ class LibraryController {
|
|||||||
const payload = {
|
const payload = {
|
||||||
results: [],
|
results: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
|
limit: req.query.limit,
|
||||||
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
|
page: req.query.page,
|
||||||
sortBy: req.query.sort,
|
sortBy: req.query.sort,
|
||||||
sortDesc: req.query.desc === '1',
|
sortDesc: req.query.desc === '1',
|
||||||
filterBy: req.query.filter,
|
filterBy: req.query.filter,
|
||||||
@ -674,8 +667,8 @@ class LibraryController {
|
|||||||
const payload = {
|
const payload = {
|
||||||
results: [],
|
results: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
|
limit: req.query.limit,
|
||||||
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
|
page: req.query.page,
|
||||||
sortBy: req.query.sort,
|
sortBy: req.query.sort,
|
||||||
sortDesc: req.query.desc === '1',
|
sortDesc: req.query.desc === '1',
|
||||||
filterBy: req.query.filter,
|
filterBy: req.query.filter,
|
||||||
@ -710,8 +703,8 @@ class LibraryController {
|
|||||||
const payload = {
|
const payload = {
|
||||||
results: [],
|
results: [],
|
||||||
total: playlistsForUser.length,
|
total: playlistsForUser.length,
|
||||||
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
|
limit: req.query.limit,
|
||||||
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0
|
page: req.query.page
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.limit) {
|
if (payload.limit) {
|
||||||
@ -742,7 +735,7 @@ class LibraryController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async getUserPersonalizedShelves(req, res) {
|
async getUserPersonalizedShelves(req, res) {
|
||||||
const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
|
const limitPerShelf = req.query.limit || 10
|
||||||
const include = (req.query.include || '')
|
const include = (req.query.include || '')
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((v) => v.trim().toLowerCase())
|
.map((v) => v.trim().toLowerCase())
|
||||||
@ -815,7 +808,7 @@ class LibraryController {
|
|||||||
return res.status(400).send('Invalid request. Query param "q" must be a string')
|
return res.status(400).send('Invalid request. Query param "q" must be a string')
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
|
const limit = req.query.limit || 12
|
||||||
const query = asciiOnlyToLowerCase(req.query.q.trim())
|
const query = asciiOnlyToLowerCase(req.query.q.trim())
|
||||||
|
|
||||||
const matches = await libraryItemFilters.search(req.user, req.library, query, limit)
|
const matches = await libraryItemFilters.search(req.user, req.library, query, limit)
|
||||||
@ -873,8 +866,40 @@ class LibraryController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async getAuthors(req, res) {
|
async getAuthors(req, res) {
|
||||||
|
const isPaginated = req.query.limit && !isNaN(req.query.limit) && !isNaN(req.query.page)
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
results: [],
|
||||||
|
total: 0,
|
||||||
|
limit: isPaginated ? Number(req.query.limit) : 0,
|
||||||
|
page: isPaginated ? Number(req.query.page) : 0,
|
||||||
|
sortBy: req.query.sort,
|
||||||
|
sortDesc: req.query.desc === '1',
|
||||||
|
filterBy: req.query.filter,
|
||||||
|
minified: req.query.minified === '1',
|
||||||
|
include: req.query.include
|
||||||
|
}
|
||||||
|
|
||||||
|
// create order, limit and offset for pagination
|
||||||
|
let offset = isPaginated ? payload.page * payload.limit : undefined
|
||||||
|
let limit = isPaginated ? payload.limit : undefined
|
||||||
|
let order = undefined
|
||||||
|
const direction = payload.sortDesc ? 'DESC' : 'ASC'
|
||||||
|
if (payload.sortBy === 'name') {
|
||||||
|
order = [[Sequelize.literal('name COLLATE NOCASE'), direction]]
|
||||||
|
} else if (payload.sortBy === 'lastFirst') {
|
||||||
|
order = [[Sequelize.literal('lastFirst COLLATE NOCASE'), direction]]
|
||||||
|
} else if (payload.sortBy === 'addedAt') {
|
||||||
|
order = [['createdAt', direction]]
|
||||||
|
} else if (payload.sortBy === 'updatedAt') {
|
||||||
|
order = [['updatedAt', direction]]
|
||||||
|
} else if (payload.sortBy === 'numBooks') {
|
||||||
|
offset = undefined
|
||||||
|
limit = undefined
|
||||||
|
}
|
||||||
|
|
||||||
const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user)
|
const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user)
|
||||||
const authors = await Database.authorModel.findAll({
|
const { rows: authors, count } = await Database.authorModel.findAndCountAll({
|
||||||
where: {
|
where: {
|
||||||
libraryId: req.library.id
|
libraryId: req.library.id
|
||||||
},
|
},
|
||||||
@ -888,10 +913,13 @@ class LibraryController {
|
|||||||
attributes: []
|
attributes: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
order: [[Sequelize.literal('name COLLATE NOCASE'), 'ASC']]
|
order: order,
|
||||||
|
limit: limit,
|
||||||
|
offset: offset,
|
||||||
|
distinct: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const oldAuthors = []
|
let oldAuthors = []
|
||||||
|
|
||||||
for (const author of authors) {
|
for (const author of authors) {
|
||||||
const oldAuthor = author.toOldJSONExpanded(author.books.length)
|
const oldAuthor = author.toOldJSONExpanded(author.books.length)
|
||||||
@ -899,10 +927,26 @@ class LibraryController {
|
|||||||
oldAuthors.push(oldAuthor)
|
oldAuthors.push(oldAuthor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// numBooks sort is handled post-query
|
||||||
|
if (payload.sortBy === 'numBooks') {
|
||||||
|
oldAuthors.sort((a, b) => (payload.sortDesc ? b.numBooks - a.numBooks : a.numBooks - b.numBooks))
|
||||||
|
if (isPaginated) {
|
||||||
|
const startIndex = payload.page * payload.limit
|
||||||
|
const endIndex = startIndex + payload.limit
|
||||||
|
oldAuthors = oldAuthors.slice(startIndex, endIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.results = oldAuthors
|
||||||
|
if (isPaginated) {
|
||||||
|
payload.total = count
|
||||||
|
res.json(payload)
|
||||||
|
} else {
|
||||||
res.json({
|
res.json({
|
||||||
authors: oldAuthors
|
authors: payload.results
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET: /api/libraries/:id/narrators
|
* GET: /api/libraries/:id/narrators
|
||||||
@ -1096,8 +1140,8 @@ class LibraryController {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
episodes: [],
|
episodes: [],
|
||||||
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
|
limit: req.query.limit,
|
||||||
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0
|
page: req.query.page
|
||||||
}
|
}
|
||||||
|
|
||||||
const offset = payload.page * payload.limit
|
const offset = payload.page * payload.limit
|
||||||
@ -1200,6 +1244,17 @@ class LibraryController {
|
|||||||
return res.status(404).send('Library not found')
|
return res.status(404).send('Library not found')
|
||||||
}
|
}
|
||||||
req.library = library
|
req.library = library
|
||||||
|
|
||||||
|
// Ensure pagination query params are positive integers
|
||||||
|
for (const queryKey of ['limit', 'page']) {
|
||||||
|
if (req.query[queryKey] !== undefined) {
|
||||||
|
req.query[queryKey] = !isNaN(req.query[queryKey]) ? Number(req.query[queryKey]) : 0
|
||||||
|
if (!Number.isInteger(req.query[queryKey]) || req.query[queryKey] < 0) {
|
||||||
|
return res.status(400).send(`Invalid request. ${queryKey} must be a positive integer`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user