diff --git a/client/components/cards/GenreSearchCard.vue b/client/components/cards/GenreSearchCard.vue
new file mode 100644
index 00000000..05be7614
--- /dev/null
+++ b/client/components/cards/GenreSearchCard.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
diff --git a/client/components/controls/GlobalSearch.vue b/client/components/controls/GlobalSearch.vue
index e20732d5..d9cc6787 100644
--- a/client/components/controls/GlobalSearch.vue
+++ b/client/components/controls/GlobalSearch.vue
@@ -59,13 +59,22 @@
{{ $strings.LabelTags }}
-
+
+ {{ $strings.LabelGenres }}
+
+
+
+
+
+
+
+
{{ $strings.LabelNarrators }}
@@ -95,6 +104,7 @@ export default {
authorResults: [],
seriesResults: [],
tagResults: [],
+ genreResults: [],
narratorResults: [],
searchTimeout: null,
lastSearch: null
@@ -105,7 +115,7 @@ export default {
return this.$store.state.libraries.currentLibraryId
},
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: {
@@ -126,6 +136,7 @@ export default {
this.authorResults = []
this.seriesResults = []
this.tagResults = []
+ this.genreResults = []
this.narratorResults = []
this.showMenu = false
this.isFetching = false
@@ -168,6 +179,7 @@ export default {
this.authorResults = searchResults.authors || []
this.seriesResults = searchResults.series || []
this.tagResults = searchResults.tags || []
+ this.genreResults = searchResults.genres || []
this.narratorResults = searchResults.narrators || []
this.isFetching = false
@@ -203,4 +215,4 @@ export default {
.globalSearchMenu {
max-height: calc(100vh - 75px);
}
-
\ No newline at end of file
+
diff --git a/server/utils/queries/libraryItemsBookFilters.js b/server/utils/queries/libraryItemsBookFilters.js
index c2b2a9df..f81aef5b 100644
--- a/server/utils/queries/libraryItemsBookFilters.js
+++ b/server/utils/queries/libraryItemsBookFilters.js
@@ -1079,7 +1079,7 @@ module.exports = {
// Search tags
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: {
query: `%${query}%`,
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
const allSeries = await Database.seriesModel.findAll({
where: {
@@ -1140,6 +1158,7 @@ module.exports = {
book: itemMatches,
narrators: narratorMatches,
tags: tagMatches,
+ genres: genreMatches,
series: seriesMatches,
authors: authorMatches
}
diff --git a/server/utils/queries/libraryItemsPodcastFilters.js b/server/utils/queries/libraryItemsPodcastFilters.js
index 3e6e337c..3fb29761 100644
--- a/server/utils/queries/libraryItemsPodcastFilters.js
+++ b/server/utils/queries/libraryItemsPodcastFilters.js
@@ -1,4 +1,3 @@
-
const Sequelize = require('sequelize')
const Database = require('../../Database')
const Logger = require('../../Logger')
@@ -7,7 +6,7 @@ const { asciiOnlyToLowerCase } = require('../index')
module.exports = {
/**
* User permissions to restrict podcasts for explicit content & tags
- * @param {import('../../objects/user/User')} user
+ * @param {import('../../objects/user/User')} user
* @returns {{ podcastWhere:Sequelize.WhereOptions, replacements:object }}
*/
getUserPermissionPodcastWhereQuery(user) {
@@ -23,9 +22,11 @@ module.exports = {
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))
} else {
- 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
- }))
+ 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
+ })
+ )
}
}
return {
@@ -36,8 +37,8 @@ module.exports = {
/**
* Get where options for Podcast model
- * @param {string} group
- * @param {[string]} value
+ * @param {string} group
+ * @param {[string]} value
* @returns {object} { Sequelize.WhereOptions, string[] }
*/
getMediaGroupQuery(group, value) {
@@ -63,8 +64,8 @@ module.exports = {
/**
* Get sequelize order
- * @param {string} sortBy
- * @param {boolean} sortDesc
+ * @param {string} sortBy
+ * @param {boolean} sortDesc
* @returns {Sequelize.order}
*/
getOrder(sortBy, sortDesc) {
@@ -94,15 +95,15 @@ module.exports = {
/**
* Get library items for podcast media type using filter and sort
- * @param {string} libraryId
+ * @param {string} libraryId
* @param {oldUser} user
- * @param {[string]} filterGroup
- * @param {[string]} filterValue
- * @param {string} sortBy
- * @param {string} sortDesc
+ * @param {[string]} filterGroup
+ * @param {[string]} filterValue
+ * @param {string} sortBy
+ * @param {string} sortDesc
* @param {string[]} include
- * @param {number} limit
- * @param {number} offset
+ * @param {number} limit
+ * @param {number} offset
* @returns {object} { libraryItems:LibraryItem[], count:number }
*/
async getFilteredLibraryItems(libraryId, user, filterGroup, filterValue, sortBy, sortDesc, include, limit, offset) {
@@ -130,7 +131,7 @@ module.exports = {
]
} else if (filterGroup === 'recent') {
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,
distinct: true,
attributes: {
- include: [
- [Sequelize.literal(`(SELECT count(*) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'numEpisodes'],
- ...podcastIncludes
- ]
+ include: [[Sequelize.literal(`(SELECT count(*) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'numEpisodes'], ...podcastIncludes]
},
include: [
{
@@ -199,14 +197,14 @@ module.exports = {
/**
* Get podcast episodes filtered and sorted
- * @param {string} libraryId
- * @param {oldUser} user
- * @param {[string]} filterGroup
- * @param {[string]} filterValue
- * @param {string} sortBy
- * @param {string} sortDesc
- * @param {number} limit
- * @param {number} offset
+ * @param {string} libraryId
+ * @param {oldUser} user
+ * @param {[string]} filterGroup
+ * @param {[string]} filterValue
+ * @param {string} sortBy
+ * @param {string} sortDesc
+ * @param {number} limit
+ * @param {number} offset
* @param {boolean} isHomePage for home page shelves
* @returns {object} {libraryItems:LibraryItem[], count:number}
*/
@@ -251,7 +249,7 @@ module.exports = {
}
} else if (filterGroup === 'recent') {
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
}
}
@@ -305,10 +303,10 @@ module.exports = {
/**
* Search podcasts
* @param {import('../../objects/user/User')} oldUser
- * @param {import('../../objects/Library')} oldLibrary
- * @param {string} query
- * @param {number} limit
- * @param {number} offset
+ * @param {import('../../objects/Library')} oldLibrary
+ * @param {string} query
+ * @param {number} limit
+ * @param {number} offset
* @returns {{podcast:object[], tags:object[]}}
*/
async search(oldUser, oldLibrary, query, limit, offset) {
@@ -386,7 +384,7 @@ module.exports = {
// Search tags
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: {
query: `%${query}%`,
libraryId: oldLibrary.id,
@@ -402,18 +400,37 @@ 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 {
podcast: itemMatches,
- tags: tagMatches
+ tags: tagMatches,
+ genres: genreMatches
}
},
/**
* Most recent podcast episodes not finished
- * @param {import('../../objects/user/User')} oldUser
- * @param {import('../../objects/Library')} oldLibrary
- * @param {number} limit
- * @param {number} offset
+ * @param {import('../../objects/user/User')} oldUser
+ * @param {import('../../objects/Library')} oldLibrary
+ * @param {number} limit
+ * @param {number} offset
* @returns {Promise