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) {
 | 
			
		||||
        shelves.push({
 | 
			
		||||
          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>
 | 
			
		||||
          </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>
 | 
			
		||||
          <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">
 | 
			
		||||
@ -100,6 +109,7 @@ export default {
 | 
			
		||||
      isFetching: false,
 | 
			
		||||
      search: null,
 | 
			
		||||
      podcastResults: [],
 | 
			
		||||
      episodeResults: [],
 | 
			
		||||
      bookResults: [],
 | 
			
		||||
      authorResults: [],
 | 
			
		||||
      seriesResults: [],
 | 
			
		||||
@ -115,7 +125,7 @@ export default {
 | 
			
		||||
      return this.$store.state.libraries.currentLibraryId
 | 
			
		||||
    },
 | 
			
		||||
    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: {
 | 
			
		||||
@ -132,6 +142,7 @@ export default {
 | 
			
		||||
      this.search = null
 | 
			
		||||
      this.lastSearch = null
 | 
			
		||||
      this.podcastResults = []
 | 
			
		||||
      this.episodeResults = []
 | 
			
		||||
      this.bookResults = []
 | 
			
		||||
      this.authorResults = []
 | 
			
		||||
      this.seriesResults = []
 | 
			
		||||
@ -175,6 +186,7 @@ export default {
 | 
			
		||||
      if (!this.isFetching) return
 | 
			
		||||
 | 
			
		||||
      this.podcastResults = searchResults.podcast || []
 | 
			
		||||
      this.episodeResults = searchResults.episodes || []
 | 
			
		||||
      this.bookResults = searchResults.book || []
 | 
			
		||||
      this.authorResults = searchResults.authors || []
 | 
			
		||||
      this.seriesResults = searchResults.series || []
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ export default {
 | 
			
		||||
    })
 | 
			
		||||
    results = {
 | 
			
		||||
      podcasts: results?.podcast || [],
 | 
			
		||||
      episodes: results?.episodes || [],
 | 
			
		||||
      books: results?.book || [],
 | 
			
		||||
      authors: results?.authors || [],
 | 
			
		||||
      series: results?.series || [],
 | 
			
		||||
@ -61,6 +62,7 @@ export default {
 | 
			
		||||
      })
 | 
			
		||||
      this.results = {
 | 
			
		||||
        podcasts: results?.podcast || [],
 | 
			
		||||
        episodes: results?.episodes || [],
 | 
			
		||||
        books: results?.book || [],
 | 
			
		||||
        authors: results?.authors || [],
 | 
			
		||||
        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')
 | 
			
		||||
 | 
			
		||||
    // Search tags
 | 
			
		||||
@ -450,7 +487,8 @@ module.exports = {
 | 
			
		||||
    return {
 | 
			
		||||
      podcast: itemMatches,
 | 
			
		||||
      tags: tagMatches,
 | 
			
		||||
      genres: genreMatches
 | 
			
		||||
      genres: genreMatches,
 | 
			
		||||
      episodes: episodeMatches
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user