mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-02-28 21:19:42 +00:00
Add ability to hide and unhide series
List of hidden series IDs is stored in user.extraData and hidden series are filtered out of the query on the server side.
This commit is contained in:
parent
8498378bca
commit
8fb763d81e
8 changed files with 133 additions and 0 deletions
|
|
@ -131,6 +131,10 @@ export default {
|
|||
{
|
||||
text: this.isSeriesFinished ? this.$strings.MessageMarkAsNotFinished : this.$strings.MessageMarkAsFinished,
|
||||
action: 'mark-series-finished'
|
||||
},
|
||||
{
|
||||
text: !this.isSeriesHidden ? this.$strings.LabelHideSeries : this.$strings.LabelUnhideSeries,
|
||||
action: 'hide-series'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -301,6 +305,10 @@ export default {
|
|||
if (!this.seriesId) return false
|
||||
return this.$store.getters['user/getIsSeriesRemovedFromContinueListening'](this.seriesId)
|
||||
},
|
||||
isSeriesHidden() {
|
||||
if (!this.seriesId) return false
|
||||
return this.$store.getters['user/getIsSeriesHidden'](this.seriesId)
|
||||
},
|
||||
filterBy() {
|
||||
return this.$store.getters['user/getUserSetting']('filterBy')
|
||||
},
|
||||
|
|
@ -439,6 +447,12 @@ export default {
|
|||
return
|
||||
}
|
||||
this.markSeriesFinished()
|
||||
} else if (action === 'hide-series') {
|
||||
if (this.processingSeries) {
|
||||
console.warn('Already processing series')
|
||||
return
|
||||
}
|
||||
this.markSeriesHidden()
|
||||
} else if (this.handleSubtitlesAction(action)) {
|
||||
return
|
||||
} else if (this.handleCollapseSubSeriesAction(action)) {
|
||||
|
|
@ -566,6 +580,33 @@ export default {
|
|||
}
|
||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||
},
|
||||
markSeriesHidden() {
|
||||
const newIsHidden = !this.isSeriesHidden;
|
||||
|
||||
const payload = {
|
||||
message: newIsHidden ? this.$strings.MessageConfirmHideSeries : this.$strings.MessageConfirmUnhideSeries,
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.processingSeries = true
|
||||
const endpoint = newIsHidden ? 'hide' : 'unhide'
|
||||
this.$axios
|
||||
.$get(`/api/me/series/${this.seriesId}/${endpoint}`)
|
||||
.then(() => {
|
||||
this.$toast.success(this.$strings.ToastSeriesUpdateSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toast.error(this.$strings.ToastSeriesUpdateFailed)
|
||||
console.error('Failed to batch update read/not read', error)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processingSeries = false
|
||||
})
|
||||
}
|
||||
},
|
||||
type: 'yesNo'
|
||||
}
|
||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||
},
|
||||
updateOrder() {
|
||||
this.saveSettings()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ export const getters = {
|
|||
if (!state.user || !state.user.seriesHideFromContinueListening || !state.user.seriesHideFromContinueListening.length) return false
|
||||
return state.user.seriesHideFromContinueListening.includes(seriesId)
|
||||
},
|
||||
getIsSeriesHidden: (state) => (seriesId) => {
|
||||
if (!state.user || !state.user.hiddenSeries || !state.user.hiddenSeries.length) return false
|
||||
return state.user.hiddenSeries.includes(seriesId)
|
||||
},
|
||||
getSizeMultiplier: (state) => {
|
||||
return state.settings.bookshelfCoverSize / 120
|
||||
}
|
||||
|
|
|
|||
|
|
@ -394,6 +394,7 @@
|
|||
"LabelHardDeleteFile": "Hard delete file",
|
||||
"LabelHasEbook": "Has ebook",
|
||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
||||
"LabelHideSeries": "Hide Series",
|
||||
"LabelHideSubtitles": "Hide Subtitles",
|
||||
"LabelHighestPriority": "Highest priority",
|
||||
"LabelHost": "Host",
|
||||
|
|
@ -699,6 +700,7 @@
|
|||
"LabelType": "Type",
|
||||
"LabelUnabridged": "Unabridged",
|
||||
"LabelUndo": "Undo",
|
||||
"LabelUnhideSeries": "Unhide Series",
|
||||
"LabelUnknown": "Unknown",
|
||||
"LabelUnknownPublishDate": "Unknown publish date",
|
||||
"LabelUpdateCover": "Update Cover",
|
||||
|
|
@ -774,6 +776,7 @@
|
|||
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
|
||||
"MessageConfirmEmbedMetadataInAudioFiles": "Are you sure you want to embed metadata in {0} audio files?",
|
||||
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
|
||||
"MessageConfirmHideSeries": "Are you sure you want to hide this series?",
|
||||
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||
"MessageConfirmMarkItemFinished": "Are you sure you want to mark \"{0}\" as finished?",
|
||||
|
|
@ -804,6 +807,7 @@
|
|||
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
|
||||
"MessageConfirmResetProgress": "Are you sure you want to reset your progress?",
|
||||
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
||||
"MessageConfirmUnhideSeries": "Are you sure you want to unhide this series?",
|
||||
"MessageConfirmUnlinkOpenId": "Are you sure you want to unlink this user from OpenID?",
|
||||
"MessageDaysListenedInTheLastYear": "{0} days listened in the last year",
|
||||
"MessageDownloadingEpisode": "Downloading episode",
|
||||
|
|
|
|||
|
|
@ -381,6 +381,44 @@ class MeController {
|
|||
res.json(req.user.toOldJSONForBrowser())
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/me/series/:id/hide
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async hideSeries(req, res) {
|
||||
if (!(await Database.seriesModel.checkExistsById(req.params.id))) {
|
||||
Logger.error(`[MeController] hideSeries: Series ${req.params.id} not found`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
const hasUpdated = await req.user.hideSeries(req.params.id)
|
||||
if (hasUpdated) {
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
|
||||
}
|
||||
res.json(req.user.toOldJSONForBrowser())
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/me/series/:id/unhide
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async unhideSeries(req, res) {
|
||||
if (!(await Database.seriesModel.checkExistsById(req.params.id))) {
|
||||
Logger.error(`[MeController] unhideSeries: Series ${req.params.id} not found`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
const hasUpdated = await req.user.unhideSeries(req.params.id)
|
||||
if (hasUpdated) {
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toOldJSONForBrowser())
|
||||
}
|
||||
res.json(req.user.toOldJSONForBrowser())
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: api/me/progress/:id/remove-from-continue-listening
|
||||
*
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ class UserController {
|
|||
permissions,
|
||||
bookmarks: [],
|
||||
extraData: {
|
||||
hiddenSeries: [],
|
||||
seriesHideFromContinueListening: []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -603,6 +603,7 @@ class User extends Model {
|
|||
*/
|
||||
toOldJSONForBrowser(hideRootToken = false, minimal = false) {
|
||||
const seriesHideFromContinueListening = this.extraData?.seriesHideFromContinueListening || []
|
||||
const hiddenSeries = this.extraData?.hiddenSeries || []
|
||||
const librariesAccessible = this.permissions?.librariesAccessible || []
|
||||
const itemTagsSelected = this.permissions?.itemTagsSelected || []
|
||||
const permissions = { ...this.permissions }
|
||||
|
|
@ -621,6 +622,7 @@ class User extends Model {
|
|||
isOldToken: this.isOldToken,
|
||||
mediaProgress: this.mediaProgresses?.map((mp) => mp.getOldMediaProgress()) || [],
|
||||
seriesHideFromContinueListening: [...seriesHideFromContinueListening],
|
||||
hiddenSeries: [...hiddenSeries],
|
||||
bookmarks: this.bookmarks?.map((b) => ({ ...b })) || [],
|
||||
isActive: this.isActive,
|
||||
isLocked: this.isLocked,
|
||||
|
|
@ -938,6 +940,38 @@ class User extends Model {
|
|||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} seriesId
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async hideSeries(seriesId) {
|
||||
if (!this.extraData) this.extraData = {}
|
||||
const hiddenSeries = this.extraData.hiddenSeries || []
|
||||
if (hiddenSeries.includes(seriesId)) return false
|
||||
hiddenSeries.push(seriesId)
|
||||
this.extraData.hiddenSeries = hiddenSeries
|
||||
this.changed('extraData', true)
|
||||
await this.save()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} seriesId
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async unhideSeries(seriesId) {
|
||||
if (!this.extraData) this.extraData = {}
|
||||
let hiddenSeries = this.extraData.hiddenSeries || []
|
||||
if (!hiddenSeries.includes(seriesId)) return false
|
||||
hiddenSeries = hiddenSeries.filter((sid) => sid !== seriesId)
|
||||
this.extraData.hiddenSeries = hiddenSeries
|
||||
this.changed('extraData', true)
|
||||
await this.save()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user permissions from external JSON
|
||||
*
|
||||
|
|
|
|||
|
|
@ -186,6 +186,8 @@ class ApiRouter {
|
|||
this.router.get('/me/items-in-progress', MeController.getAllLibraryItemsInProgress.bind(this))
|
||||
this.router.get('/me/series/:id/remove-from-continue-listening', MeController.removeSeriesFromContinueListening.bind(this))
|
||||
this.router.get('/me/series/:id/readd-to-continue-listening', MeController.readdSeriesFromContinueListening.bind(this))
|
||||
this.router.get('/me/series/:id/hide', MeController.hideSeries.bind(this))
|
||||
this.router.get('/me/series/:id/unhide', MeController.unhideSeries.bind(this))
|
||||
this.router.get('/me/stats/year/:year', MeController.getStatsForYear.bind(this))
|
||||
this.router.post('/me/ereader-devices', MeController.updateUserEReaderDevices.bind(this))
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,15 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
|
||||
// Don't return hidden series
|
||||
if (user.extraData.hiddenSeries) {
|
||||
seriesWhere.push({
|
||||
id: {
|
||||
[Sequelize.Op.notIn]: user.extraData.hiddenSeries
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (attrQuery) {
|
||||
seriesWhere.push(
|
||||
Sequelize.where(Sequelize.literal(`(${attrQuery})`), {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue