mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Adds genres to gloabl search
This commit is contained in:
		
							parent
							
								
									9cd0ac80b1
								
							
						
					
					
						commit
						bff56220c2
					
				
							
								
								
									
										34
									
								
								client/components/cards/GenreSearchCard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								client/components/cards/GenreSearchCard.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="flex h-full px-1 overflow-hidden"> | ||||||
|  |     <div class="w-10 h-10 flex items-center justify-center"> | ||||||
|  |       <span class="material-symbols text-2xl text-gray-200">category</span> | ||||||
|  |     </div> | ||||||
|  |     <div class="flex-grow px-2 tagSearchCardContent h-full"> | ||||||
|  |       <p class="truncate text-sm">{{ genre }}</p> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | export default { | ||||||
|  |   props: { | ||||||
|  |     genre: String | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return {} | ||||||
|  |   }, | ||||||
|  |   computed: {}, | ||||||
|  |   methods: {}, | ||||||
|  |   mounted() {} | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style> | ||||||
|  | .tagSearchCardContent { | ||||||
|  |   width: calc(100% - 40px); | ||||||
|  |   height: 40px; | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   justify-content: center; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @ -59,13 +59,22 @@ | |||||||
| 
 | 
 | ||||||
|           <p v-if="tagResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelTags }}</p> |           <p v-if="tagResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelTags }}</p> | ||||||
|           <template v-for="item in tagResults"> |           <template v-for="item in tagResults"> | ||||||
|             <li :key="item.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> |             <li :key="`tag.${item.name}`" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> | ||||||
|               <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(item.name)}`"> |               <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=tags.${$encode(item.name)}`"> | ||||||
|                 <cards-tag-search-card :tag="item.name" /> |                 <cards-tag-search-card :tag="item.name" /> | ||||||
|               </nuxt-link> |               </nuxt-link> | ||||||
|             </li> |             </li> | ||||||
|           </template> |           </template> | ||||||
| 
 | 
 | ||||||
|  |           <p v-if="genreResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelGenres }}</p> | ||||||
|  |           <template v-for="item in genreResults"> | ||||||
|  |             <li :key="`genre.${item.name}`" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> | ||||||
|  |               <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=genres.${$encode(item.name)}`"> | ||||||
|  |                 <cards-genre-search-card :genre="item.name" /> | ||||||
|  |               </nuxt-link> | ||||||
|  |             </li> | ||||||
|  |           </template> | ||||||
|  | 
 | ||||||
|           <p v-if="narratorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelNarrators }}</p> |           <p v-if="narratorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelNarrators }}</p> | ||||||
|           <template v-for="narrator in narratorResults"> |           <template v-for="narrator in narratorResults"> | ||||||
|             <li :key="narrator.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> |             <li :key="narrator.name" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption"> | ||||||
| @ -95,6 +104,7 @@ export default { | |||||||
|       authorResults: [], |       authorResults: [], | ||||||
|       seriesResults: [], |       seriesResults: [], | ||||||
|       tagResults: [], |       tagResults: [], | ||||||
|  |       genreResults: [], | ||||||
|       narratorResults: [], |       narratorResults: [], | ||||||
|       searchTimeout: null, |       searchTimeout: null, | ||||||
|       lastSearch: null |       lastSearch: null | ||||||
| @ -105,7 +115,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.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 | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
| @ -126,6 +136,7 @@ export default { | |||||||
|       this.authorResults = [] |       this.authorResults = [] | ||||||
|       this.seriesResults = [] |       this.seriesResults = [] | ||||||
|       this.tagResults = [] |       this.tagResults = [] | ||||||
|  |       this.genreResults = [] | ||||||
|       this.narratorResults = [] |       this.narratorResults = [] | ||||||
|       this.showMenu = false |       this.showMenu = false | ||||||
|       this.isFetching = false |       this.isFetching = false | ||||||
| @ -168,6 +179,7 @@ export default { | |||||||
|       this.authorResults = searchResults.authors || [] |       this.authorResults = searchResults.authors || [] | ||||||
|       this.seriesResults = searchResults.series || [] |       this.seriesResults = searchResults.series || [] | ||||||
|       this.tagResults = searchResults.tags || [] |       this.tagResults = searchResults.tags || [] | ||||||
|  |       this.genreResults = searchResults.genres || [] | ||||||
|       this.narratorResults = searchResults.narrators || [] |       this.narratorResults = searchResults.narrators || [] | ||||||
| 
 | 
 | ||||||
|       this.isFetching = false |       this.isFetching = false | ||||||
|  | |||||||
| @ -1079,7 +1079,7 @@ module.exports = { | |||||||
| 
 | 
 | ||||||
|     // Search tags
 |     // Search tags
 | ||||||
|     const tagMatches = [] |     const tagMatches = [] | ||||||
|     const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.tags) WHERE json_valid(b.tags) AND json_each.value LIKE :query AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value LIMIT :limit OFFSET :offset;`, { |     const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.tags) WHERE json_valid(b.tags) AND json_each.value LIKE :query AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, { | ||||||
|       replacements: { |       replacements: { | ||||||
|         query: `%${query}%`, |         query: `%${query}%`, | ||||||
|         libraryId: oldLibrary.id, |         libraryId: oldLibrary.id, | ||||||
| @ -1095,6 +1095,24 @@ module.exports = { | |||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Search genres
 | ||||||
|  |     const genreMatches = [] | ||||||
|  |     const [genreResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.genres) WHERE json_valid(b.genres) AND json_each.value LIKE :query AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, { | ||||||
|  |       replacements: { | ||||||
|  |         query: `%${query}%`, | ||||||
|  |         libraryId: oldLibrary.id, | ||||||
|  |         limit, | ||||||
|  |         offset | ||||||
|  |       }, | ||||||
|  |       raw: true | ||||||
|  |     }) | ||||||
|  |     for (const row of genreResults) { | ||||||
|  |       genreMatches.push({ | ||||||
|  |         name: row.value, | ||||||
|  |         numItems: row.numItems | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // Search series
 |     // Search series
 | ||||||
|     const allSeries = await Database.seriesModel.findAll({ |     const allSeries = await Database.seriesModel.findAll({ | ||||||
|       where: { |       where: { | ||||||
| @ -1140,6 +1158,7 @@ module.exports = { | |||||||
|       book: itemMatches, |       book: itemMatches, | ||||||
|       narrators: narratorMatches, |       narrators: narratorMatches, | ||||||
|       tags: tagMatches, |       tags: tagMatches, | ||||||
|  |       genres: genreMatches, | ||||||
|       series: seriesMatches, |       series: seriesMatches, | ||||||
|       authors: authorMatches |       authors: authorMatches | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| 
 |  | ||||||
| const Sequelize = require('sequelize') | const Sequelize = require('sequelize') | ||||||
| const Database = require('../../Database') | const Database = require('../../Database') | ||||||
| const Logger = require('../../Logger') | const Logger = require('../../Logger') | ||||||
| @ -23,9 +22,11 @@ module.exports = { | |||||||
|       if (user.permissions.selectedTagsNotAccessible) { |       if (user.permissions.selectedTagsNotAccessible) { | ||||||
|         podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0)) |         podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0)) | ||||||
|       } else { |       } else { | ||||||
|         podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), { |         podcastWhere.push( | ||||||
|  |           Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), { | ||||||
|             [Sequelize.Op.gte]: 1 |             [Sequelize.Op.gte]: 1 | ||||||
|         })) |           }) | ||||||
|  |         ) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return { |     return { | ||||||
| @ -130,7 +131,7 @@ module.exports = { | |||||||
|       ] |       ] | ||||||
|     } else if (filterGroup === 'recent') { |     } else if (filterGroup === 'recent') { | ||||||
|       libraryItemWhere['createdAt'] = { |       libraryItemWhere['createdAt'] = { | ||||||
|         [Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago
 |         [Sequelize.Op.gte]: new Date(new Date() - 60 * 24 * 60 * 60 * 1000) // 60 days ago
 | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -154,10 +155,7 @@ module.exports = { | |||||||
|       replacements, |       replacements, | ||||||
|       distinct: true, |       distinct: true, | ||||||
|       attributes: { |       attributes: { | ||||||
|         include: [ |         include: [[Sequelize.literal(`(SELECT count(*) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'numEpisodes'], ...podcastIncludes] | ||||||
|           [Sequelize.literal(`(SELECT count(*) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'numEpisodes'], |  | ||||||
|           ...podcastIncludes |  | ||||||
|         ] |  | ||||||
|       }, |       }, | ||||||
|       include: [ |       include: [ | ||||||
|         { |         { | ||||||
| @ -251,7 +249,7 @@ module.exports = { | |||||||
|       } |       } | ||||||
|     } else if (filterGroup === 'recent') { |     } else if (filterGroup === 'recent') { | ||||||
|       podcastEpisodeWhere['createdAt'] = { |       podcastEpisodeWhere['createdAt'] = { | ||||||
|         [Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago
 |         [Sequelize.Op.gte]: new Date(new Date() - 60 * 24 * 60 * 60 * 1000) // 60 days ago
 | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -386,7 +384,7 @@ module.exports = { | |||||||
| 
 | 
 | ||||||
|     // Search tags
 |     // Search tags
 | ||||||
|     const tagMatches = [] |     const tagMatches = [] | ||||||
|     const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.tags) WHERE json_valid(p.tags) AND json_each.value LIKE :query AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value LIMIT :limit OFFSET :offset;`, { |     const [tagResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.tags) WHERE json_valid(p.tags) AND json_each.value LIKE :query AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, { | ||||||
|       replacements: { |       replacements: { | ||||||
|         query: `%${query}%`, |         query: `%${query}%`, | ||||||
|         libraryId: oldLibrary.id, |         libraryId: oldLibrary.id, | ||||||
| @ -402,9 +400,28 @@ module.exports = { | |||||||
|       }) |       }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Search genres
 | ||||||
|  |     const genreMatches = [] | ||||||
|  |     const [genreResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.genres) WHERE json_valid(p.genres) AND json_each.value LIKE :query AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC LIMIT :limit OFFSET :offset;`, { | ||||||
|  |       replacements: { | ||||||
|  |         query: `%${query}%`, | ||||||
|  |         libraryId: oldLibrary.id, | ||||||
|  |         limit, | ||||||
|  |         offset | ||||||
|  |       }, | ||||||
|  |       raw: true | ||||||
|  |     }) | ||||||
|  |     for (const row of genreResults) { | ||||||
|  |       genreMatches.push({ | ||||||
|  |         name: row.value, | ||||||
|  |         numItems: row.numItems | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return { |     return { | ||||||
|       podcast: itemMatches, |       podcast: itemMatches, | ||||||
|       tags: tagMatches |       tags: tagMatches, | ||||||
|  |       genres: genreMatches | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
| @ -446,9 +463,7 @@ module.exports = { | |||||||
|           required: false |           required: false | ||||||
|         } |         } | ||||||
|       ], |       ], | ||||||
|       order: [ |       order: [['publishedAt', 'DESC']], | ||||||
|         ['publishedAt', 'DESC'] |  | ||||||
|       ], |  | ||||||
|       subQuery: false, |       subQuery: false, | ||||||
|       limit, |       limit, | ||||||
|       offset |       offset | ||||||
| @ -519,11 +534,7 @@ module.exports = { | |||||||
|    */ |    */ | ||||||
|   async getLongestPodcasts(libraryId, limit) { |   async getLongestPodcasts(libraryId, limit) { | ||||||
|     const podcasts = await Database.podcastModel.findAll({ |     const podcasts = await Database.podcastModel.findAll({ | ||||||
|       attributes: [ |       attributes: ['id', 'title', [Sequelize.literal(`(SELECT SUM(json_extract(pe.audioFile, '$.duration')) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'duration']], | ||||||
|         'id', |  | ||||||
|         'title', |  | ||||||
|         [Sequelize.literal(`(SELECT SUM(json_extract(pe.audioFile, '$.duration')) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'duration'] |  | ||||||
|       ], |  | ||||||
|       include: { |       include: { | ||||||
|         model: Database.libraryItemModel, |         model: Database.libraryItemModel, | ||||||
|         attributes: ['id', 'libraryId'], |         attributes: ['id', 'libraryId'], | ||||||
| @ -531,12 +542,10 @@ module.exports = { | |||||||
|           libraryId |           libraryId | ||||||
|         } |         } | ||||||
|       }, |       }, | ||||||
|       order: [ |       order: [['duration', 'DESC']], | ||||||
|         ['duration', 'DESC'] |  | ||||||
|       ], |  | ||||||
|       limit |       limit | ||||||
|     }) |     }) | ||||||
|     return podcasts.map(podcast => { |     return podcasts.map((podcast) => { | ||||||
|       return { |       return { | ||||||
|         id: podcast.libraryItem.id, |         id: podcast.libraryItem.id, | ||||||
|         title: podcast.title, |         title: podcast.title, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user