This commit is contained in:
Dyson 2025-11-22 18:04:17 +00:00 committed by GitHub
commit 7d0c7312d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 109 additions and 0 deletions

View File

@ -131,6 +131,10 @@ export default {
{ {
text: this.isSeriesFinished ? this.$strings.MessageMarkAsNotFinished : this.$strings.MessageMarkAsFinished, text: this.isSeriesFinished ? this.$strings.MessageMarkAsNotFinished : this.$strings.MessageMarkAsFinished,
action: 'mark-series-finished' 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 if (!this.seriesId) return false
return this.$store.getters['user/getIsSeriesRemovedFromContinueListening'](this.seriesId) return this.$store.getters['user/getIsSeriesRemovedFromContinueListening'](this.seriesId)
}, },
isSeriesHidden() {
if (!this.seriesId) return false
return this.$store.getters['user/getIsSeriesHidden'](this.seriesId)
},
filterBy() { filterBy() {
return this.$store.getters['user/getUserSetting']('filterBy') return this.$store.getters['user/getUserSetting']('filterBy')
}, },
@ -439,6 +447,12 @@ export default {
return return
} }
this.markSeriesFinished() this.markSeriesFinished()
} else if (action === 'hide-series') {
if (this.processingSeries) {
console.warn('Already processing series')
return
}
this.markSeriesHidden()
} else if (this.handleSubtitlesAction(action)) { } else if (this.handleSubtitlesAction(action)) {
return return
} else if (this.handleCollapseSubSeriesAction(action)) { } else if (this.handleCollapseSubSeriesAction(action)) {
@ -566,6 +580,35 @@ export default {
} }
this.$store.commit('globals/setConfirmPrompt', payload) 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 payload = {
makeHidden: newIsHidden
}
this.$axios
.$post(`/api/me/series/${this.seriesId}/hide`, payload)
.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() { updateOrder() {
this.saveSettings() this.saveSettings()
}, },

View File

@ -76,6 +76,10 @@ export const getters = {
if (!state.user || !state.user.seriesHideFromContinueListening || !state.user.seriesHideFromContinueListening.length) return false if (!state.user || !state.user.seriesHideFromContinueListening || !state.user.seriesHideFromContinueListening.length) return false
return state.user.seriesHideFromContinueListening.includes(seriesId) 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) => { getSizeMultiplier: (state) => {
return state.settings.bookshelfCoverSize / 120 return state.settings.bookshelfCoverSize / 120
} }

View File

@ -394,6 +394,7 @@
"LabelHardDeleteFile": "Hard delete file", "LabelHardDeleteFile": "Hard delete file",
"LabelHasEbook": "Has ebook", "LabelHasEbook": "Has ebook",
"LabelHasSupplementaryEbook": "Has supplementary ebook", "LabelHasSupplementaryEbook": "Has supplementary ebook",
"LabelHideSeries": "Hide Series",
"LabelHideSubtitles": "Hide Subtitles", "LabelHideSubtitles": "Hide Subtitles",
"LabelHighestPriority": "Highest priority", "LabelHighestPriority": "Highest priority",
"LabelHost": "Host", "LabelHost": "Host",
@ -699,6 +700,7 @@
"LabelType": "Type", "LabelType": "Type",
"LabelUnabridged": "Unabridged", "LabelUnabridged": "Unabridged",
"LabelUndo": "Undo", "LabelUndo": "Undo",
"LabelUnhideSeries": "Unhide Series",
"LabelUnknown": "Unknown", "LabelUnknown": "Unknown",
"LabelUnknownPublishDate": "Unknown publish date", "LabelUnknownPublishDate": "Unknown publish date",
"LabelUpdateCover": "Update Cover", "LabelUpdateCover": "Update Cover",
@ -774,6 +776,7 @@
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?", "MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
"MessageConfirmEmbedMetadataInAudioFiles": "Are you sure you want to embed metadata in {0} audio files?", "MessageConfirmEmbedMetadataInAudioFiles": "Are you sure you want to embed metadata in {0} audio files?",
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?", "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?", "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?", "MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
"MessageConfirmMarkItemFinished": "Are you sure you want to mark \"{0}\" as 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}\".", "MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
"MessageConfirmResetProgress": "Are you sure you want to reset your progress?", "MessageConfirmResetProgress": "Are you sure you want to reset your progress?",
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?", "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?", "MessageConfirmUnlinkOpenId": "Are you sure you want to unlink this user from OpenID?",
"MessageDaysListenedInTheLastYear": "{0} days listened in the last year", "MessageDaysListenedInTheLastYear": "{0} days listened in the last year",
"MessageDownloadingEpisode": "Downloading episode", "MessageDownloadingEpisode": "Downloading episode",

View File

@ -381,6 +381,27 @@ class MeController {
res.json(req.user.toOldJSONForBrowser()) res.json(req.user.toOldJSONForBrowser())
} }
/**
* POST: /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 { makeHidden } = req.body
const hasUpdated = await req.user.hideSeries(req.params.id, makeHidden)
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 * GET: api/me/progress/:id/remove-from-continue-listening
* *

View File

@ -178,6 +178,7 @@ class UserController {
permissions, permissions,
bookmarks: [], bookmarks: [],
extraData: { extraData: {
hiddenSeries: [],
seriesHideFromContinueListening: [] seriesHideFromContinueListening: []
} }
} }

View File

@ -603,6 +603,7 @@ class User extends Model {
*/ */
toOldJSONForBrowser(hideRootToken = false, minimal = false) { toOldJSONForBrowser(hideRootToken = false, minimal = false) {
const seriesHideFromContinueListening = this.extraData?.seriesHideFromContinueListening || [] const seriesHideFromContinueListening = this.extraData?.seriesHideFromContinueListening || []
const hiddenSeries = this.extraData?.hiddenSeries || []
const librariesAccessible = this.permissions?.librariesAccessible || [] const librariesAccessible = this.permissions?.librariesAccessible || []
const itemTagsSelected = this.permissions?.itemTagsSelected || [] const itemTagsSelected = this.permissions?.itemTagsSelected || []
const permissions = { ...this.permissions } const permissions = { ...this.permissions }
@ -621,6 +622,7 @@ class User extends Model {
isOldToken: this.isOldToken, isOldToken: this.isOldToken,
mediaProgress: this.mediaProgresses?.map((mp) => mp.getOldMediaProgress()) || [], mediaProgress: this.mediaProgresses?.map((mp) => mp.getOldMediaProgress()) || [],
seriesHideFromContinueListening: [...seriesHideFromContinueListening], seriesHideFromContinueListening: [...seriesHideFromContinueListening],
hiddenSeries: [...hiddenSeries],
bookmarks: this.bookmarks?.map((b) => ({ ...b })) || [], bookmarks: this.bookmarks?.map((b) => ({ ...b })) || [],
isActive: this.isActive, isActive: this.isActive,
isLocked: this.isLocked, isLocked: this.isLocked,
@ -938,6 +940,30 @@ class User extends Model {
return true return true
} }
/**
*
* @param {string} seriesId
* @param {boolean} makeHidden
* @returns {Promise<boolean>}
*/
async hideSeries(seriesId, makeHidden) {
if (!this.extraData) this.extraData = {}
let hiddenSeries = this.extraData.hiddenSeries || []
if (makeHidden) {
if (hiddenSeries.includes(seriesId)) return false
hiddenSeries.push(seriesId)
} else {
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 * Update user permissions from external JSON
* *

View File

@ -186,6 +186,7 @@ class ApiRouter {
this.router.get('/me/items-in-progress', MeController.getAllLibraryItemsInProgress.bind(this)) 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/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/readd-to-continue-listening', MeController.readdSeriesFromContinueListening.bind(this))
this.router.post('/me/series/:id/hide', MeController.hideSeries.bind(this))
this.router.get('/me/stats/year/:year', MeController.getStatsForYear.bind(this)) this.router.get('/me/stats/year/:year', MeController.getStatsForYear.bind(this))
this.router.post('/me/ereader-devices', MeController.updateUserEReaderDevices.bind(this)) this.router.post('/me/ereader-devices', MeController.updateUserEReaderDevices.bind(this))

View File

@ -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) { if (attrQuery) {
seriesWhere.push( seriesWhere.push(
Sequelize.where(Sequelize.literal(`(${attrQuery})`), { Sequelize.where(Sequelize.literal(`(${attrQuery})`), {