mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge pull request #4293 from advplyr/search_episodes
Add support for searching podcast episode titles #3301
This commit is contained in:
		
						commit
						8d0434143c
					
				@ -217,6 +217,16 @@ export default {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.results.episodes?.length) {
 | 
				
			||||||
 | 
					        shelves.push({
 | 
				
			||||||
 | 
					          id: 'episodes',
 | 
				
			||||||
 | 
					          label: 'Episodes',
 | 
				
			||||||
 | 
					          labelStringKey: 'LabelEpisodes',
 | 
				
			||||||
 | 
					          type: 'episode',
 | 
				
			||||||
 | 
					          entities: this.results.episodes.map((res) => res.libraryItem)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.results.series?.length) {
 | 
					      if (this.results.series?.length) {
 | 
				
			||||||
        shelves.push({
 | 
					        shelves.push({
 | 
				
			||||||
          id: 'series',
 | 
					          id: 'series',
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="flex items-center h-full px-1 overflow-hidden">
 | 
				
			||||||
 | 
					    <covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
 | 
				
			||||||
 | 
					    <div class="grow px-2 episodeSearchCardContent">
 | 
				
			||||||
 | 
					      <p class="truncate text-sm">{{ episodeTitle }}</p>
 | 
				
			||||||
 | 
					      <p class="text-xs text-gray-200 truncate">{{ podcastTitle }}</p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    libraryItem: {
 | 
				
			||||||
 | 
					      type: Object,
 | 
				
			||||||
 | 
					      default: () => {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    episode: {
 | 
				
			||||||
 | 
					      type: Object,
 | 
				
			||||||
 | 
					      default: () => {}
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {}
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    bookCoverAspectRatio() {
 | 
				
			||||||
 | 
					      return this.$store.getters['libraries/getBookCoverAspectRatio']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    coverWidth() {
 | 
				
			||||||
 | 
					      if (this.bookCoverAspectRatio === 1) return 50 * 1.2
 | 
				
			||||||
 | 
					      return 50
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    media() {
 | 
				
			||||||
 | 
					      return this.libraryItem?.media || {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    mediaMetadata() {
 | 
				
			||||||
 | 
					      return this.media.metadata || {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    episodeTitle() {
 | 
				
			||||||
 | 
					      return this.episode.title || 'No Title'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    podcastTitle() {
 | 
				
			||||||
 | 
					      return this.mediaMetadata.title || 'No Title'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {},
 | 
				
			||||||
 | 
					  mounted() {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					.episodeSearchCardContent {
 | 
				
			||||||
 | 
					  width: calc(100% - 80px);
 | 
				
			||||||
 | 
					  height: 75px;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@ -39,6 +39,15 @@
 | 
				
			|||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <p v-if="episodeResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">{{ $strings.LabelEpisodes }}</p>
 | 
				
			||||||
 | 
					          <template v-for="item in episodeResults">
 | 
				
			||||||
 | 
					            <li :key="item.libraryItem.recentEpisode.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
 | 
				
			||||||
 | 
					              <nuxt-link :to="`/item/${item.libraryItem.id}`">
 | 
				
			||||||
 | 
					                <cards-episode-search-card :episode="item.libraryItem.recentEpisode" :library-item="item.libraryItem" />
 | 
				
			||||||
 | 
					              </nuxt-link>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					          </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <p v-if="authorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelAuthors }}</p>
 | 
					          <p v-if="authorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelAuthors }}</p>
 | 
				
			||||||
          <template v-for="item in authorResults">
 | 
					          <template v-for="item in authorResults">
 | 
				
			||||||
            <li :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
 | 
					            <li :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
 | 
				
			||||||
@ -100,6 +109,7 @@ export default {
 | 
				
			|||||||
      isFetching: false,
 | 
					      isFetching: false,
 | 
				
			||||||
      search: null,
 | 
					      search: null,
 | 
				
			||||||
      podcastResults: [],
 | 
					      podcastResults: [],
 | 
				
			||||||
 | 
					      episodeResults: [],
 | 
				
			||||||
      bookResults: [],
 | 
					      bookResults: [],
 | 
				
			||||||
      authorResults: [],
 | 
					      authorResults: [],
 | 
				
			||||||
      seriesResults: [],
 | 
					      seriesResults: [],
 | 
				
			||||||
@ -115,7 +125,7 @@ export default {
 | 
				
			|||||||
      return this.$store.state.libraries.currentLibraryId
 | 
					      return this.$store.state.libraries.currentLibraryId
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    totalResults() {
 | 
					    totalResults() {
 | 
				
			||||||
      return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length
 | 
					      return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length + this.episodeResults.length
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
@ -132,6 +142,7 @@ export default {
 | 
				
			|||||||
      this.search = null
 | 
					      this.search = null
 | 
				
			||||||
      this.lastSearch = null
 | 
					      this.lastSearch = null
 | 
				
			||||||
      this.podcastResults = []
 | 
					      this.podcastResults = []
 | 
				
			||||||
 | 
					      this.episodeResults = []
 | 
				
			||||||
      this.bookResults = []
 | 
					      this.bookResults = []
 | 
				
			||||||
      this.authorResults = []
 | 
					      this.authorResults = []
 | 
				
			||||||
      this.seriesResults = []
 | 
					      this.seriesResults = []
 | 
				
			||||||
@ -175,6 +186,7 @@ export default {
 | 
				
			|||||||
      if (!this.isFetching) return
 | 
					      if (!this.isFetching) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.podcastResults = searchResults.podcast || []
 | 
					      this.podcastResults = searchResults.podcast || []
 | 
				
			||||||
 | 
					      this.episodeResults = searchResults.episodes || []
 | 
				
			||||||
      this.bookResults = searchResults.book || []
 | 
					      this.bookResults = searchResults.book || []
 | 
				
			||||||
      this.authorResults = searchResults.authors || []
 | 
					      this.authorResults = searchResults.authors || []
 | 
				
			||||||
      this.seriesResults = searchResults.series || []
 | 
					      this.seriesResults = searchResults.series || []
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,7 @@ export default {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
    results = {
 | 
					    results = {
 | 
				
			||||||
      podcasts: results?.podcast || [],
 | 
					      podcasts: results?.podcast || [],
 | 
				
			||||||
 | 
					      episodes: results?.episodes || [],
 | 
				
			||||||
      books: results?.book || [],
 | 
					      books: results?.book || [],
 | 
				
			||||||
      authors: results?.authors || [],
 | 
					      authors: results?.authors || [],
 | 
				
			||||||
      series: results?.series || [],
 | 
					      series: results?.series || [],
 | 
				
			||||||
@ -61,6 +62,7 @@ export default {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
      this.results = {
 | 
					      this.results = {
 | 
				
			||||||
        podcasts: results?.podcast || [],
 | 
					        podcasts: results?.podcast || [],
 | 
				
			||||||
 | 
					        episodes: results?.episodes || [],
 | 
				
			||||||
        books: results?.book || [],
 | 
					        books: results?.book || [],
 | 
				
			||||||
        authors: results?.authors || [],
 | 
					        authors: results?.authors || [],
 | 
				
			||||||
        series: results?.series || [],
 | 
					        series: results?.series || [],
 | 
				
			||||||
 | 
				
			|||||||
@ -411,6 +411,43 @@ module.exports = {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Search podcast episode title
 | 
				
			||||||
 | 
					    const podcastEpisodes = await Database.podcastEpisodeModel.findAll({
 | 
				
			||||||
 | 
					      where: [
 | 
				
			||||||
 | 
					        Sequelize.literal(textSearchQuery.matchExpression('podcastEpisode.title')),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          '$podcast.libraryItem.libraryId$': library.id
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      replacements: userPermissionPodcastWhere.replacements,
 | 
				
			||||||
 | 
					      include: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          model: Database.podcastModel,
 | 
				
			||||||
 | 
					          where: [...userPermissionPodcastWhere.podcastWhere],
 | 
				
			||||||
 | 
					          include: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              model: Database.libraryItemModel
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      distinct: true,
 | 
				
			||||||
 | 
					      offset,
 | 
				
			||||||
 | 
					      limit
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    const episodeMatches = []
 | 
				
			||||||
 | 
					    for (const episode of podcastEpisodes) {
 | 
				
			||||||
 | 
					      const libraryItem = episode.podcast.libraryItem
 | 
				
			||||||
 | 
					      libraryItem.media = episode.podcast
 | 
				
			||||||
 | 
					      libraryItem.media.podcastEpisodes = []
 | 
				
			||||||
 | 
					      const oldPodcastEpisodeJson = episode.toOldJSONExpanded(libraryItem.id)
 | 
				
			||||||
 | 
					      const libraryItemJson = libraryItem.toOldJSONExpanded()
 | 
				
			||||||
 | 
					      libraryItemJson.recentEpisode = oldPodcastEpisodeJson
 | 
				
			||||||
 | 
					      episodeMatches.push({
 | 
				
			||||||
 | 
					        libraryItem: libraryItemJson
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const matchJsonValue = textSearchQuery.matchExpression('json_each.value')
 | 
					    const matchJsonValue = textSearchQuery.matchExpression('json_each.value')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Search tags
 | 
					    // Search tags
 | 
				
			||||||
@ -450,7 +487,8 @@ module.exports = {
 | 
				
			|||||||
    return {
 | 
					    return {
 | 
				
			||||||
      podcast: itemMatches,
 | 
					      podcast: itemMatches,
 | 
				
			||||||
      tags: tagMatches,
 | 
					      tags: tagMatches,
 | 
				
			||||||
      genres: genreMatches
 | 
					      genres: genreMatches,
 | 
				
			||||||
 | 
					      episodes: episodeMatches
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user