feat(blacklist): hide blacklisted items from discover pages for admins (#1601)

* feat(blacklist): add 'Hide Blacklisted Items' setting to general settings and UI

* feat(migration): add HideBlacklistedItems migration for PostgreSQL and SQLite

* feat(settings): add hideBlacklisted option to application settings

* feat(settings): add tooltips for hideAvailable and hideBlacklisted options in settings

* chore(migration): remove HideBlacklistedItems migration files for PostgreSQL and SQLite

* docs(settings): clarify description of 'Hide Blacklisted Items' setting to specify it affects all users

* docs(settings): update tooltip and description for 'Hide Blacklisted Items' to clarify it applies to all users, including administrators

* docs(settings): clarify 'Hide Blacklisted Items' functionality to specify it applies only to administrators with the "Manage Blacklist" permission

* fix(hooks): update permission check for 'Hide Blacklisted Items' to include 'Manage Blacklist'

* fix(settings): update tooltip for 'Hide Blacklisted Items' to clarify it applies to all users with the "Manage Blacklist" permission

* feat(settings): add experimental badge to settings tooltip for 'Hide Available' option
This commit is contained in:
Jam
2025-05-06 19:01:23 +01:00
committed by GitHub
parent 2e4d14698f
commit 185167a0a7
9 changed files with 65 additions and 2 deletions

View File

@@ -78,6 +78,12 @@ Available media will still appear in search results, however, so it is possible
This setting is **disabled** by default.
## Hide Blacklisted Items
When enabled, media that has been blacklisted will not appear on the "Discover" home page, for all administrators. This can be useful to hide content that you don't want to see, such as content with specific tags or content that has been manually blacklisted when you have the "Manage Blacklist" permission.
This setting is **disabled** by default.
## Allow Partial Series Requests
When enabled, users will be able to submit requests for specific seasons of TV series. If disabled, users will only be able to submit requests for all unavailable seasons.

View File

@@ -29,6 +29,7 @@ export interface PublicSettingsResponse {
applicationTitle: string;
applicationUrl: string;
hideAvailable: boolean;
hideBlacklisted: boolean;
localLogin: boolean;
mediaServerLogin: boolean;
movie4kEnabled: boolean;

View File

@@ -122,6 +122,7 @@ export interface MainSettings {
tv: Quota;
};
hideAvailable: boolean;
hideBlacklisted: boolean;
localLogin: boolean;
mediaServerLogin: boolean;
newPlexLogin: boolean;
@@ -150,6 +151,7 @@ interface FullPublicSettings extends PublicSettings {
applicationTitle: string;
applicationUrl: string;
hideAvailable: boolean;
hideBlacklisted: boolean;
localLogin: boolean;
mediaServerLogin: boolean;
movie4kEnabled: boolean;
@@ -360,6 +362,7 @@ class Settings {
tv: {},
},
hideAvailable: false,
hideBlacklisted: false,
localLogin: true,
mediaServerLogin: true,
newPlexLogin: true,
@@ -618,6 +621,7 @@ class Settings {
applicationTitle: this.data.main.applicationTitle,
applicationUrl: this.data.main.applicationUrl,
hideAvailable: this.data.main.hideAvailable,
hideBlacklisted: this.data.main.hideBlacklisted,
localLogin: this.data.main.localLogin,
mediaServerLogin: this.data.main.mediaServerLogin,
jellyfinExternalHost: this.data.jellyfin.externalHostname,

View File

@@ -34,7 +34,7 @@ const Search = () => {
{
query: router.query.query,
},
{ hideAvailable: false }
{ hideAvailable: false, hideBlacklisted: false }
);
if (error) {

View File

@@ -45,11 +45,16 @@ const messages = defineMessages('components.Settings.SettingsMain', {
'The "Process Blacklisted Tags" job will blacklist this many pages into each sort. Larger numbers will create a more accurate blacklist, but use more space.',
streamingRegion: 'Streaming Region',
streamingRegionTip: 'Show streaming sites by regional availability',
hideBlacklisted: 'Hide Blacklisted Items',
hideBlacklistedTip:
'Hide blacklisted items from discover pages for all users with the "Manage Blacklist" permission',
toastApiKeySuccess: 'New API key generated successfully!',
toastApiKeyFailure: 'Something went wrong while generating a new API key.',
toastSettingsSuccess: 'Settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
hideAvailable: 'Hide Available Media',
hideAvailableTip:
'Hide available media from the discover pages but not search results',
cacheImages: 'Enable Image Caching',
cacheImagesTip:
'Cache externally sourced images (requires a significant amount of disk space)',
@@ -145,6 +150,7 @@ const SettingsMain = () => {
applicationTitle: data?.applicationTitle,
applicationUrl: data?.applicationUrl,
hideAvailable: data?.hideAvailable,
hideBlacklisted: data?.hideBlacklisted,
locale: data?.locale ?? 'en',
discoverRegion: data?.discoverRegion,
originalLanguage: data?.originalLanguage,
@@ -163,6 +169,7 @@ const SettingsMain = () => {
applicationTitle: values.applicationTitle,
applicationUrl: values.applicationUrl,
hideAvailable: values.hideAvailable,
hideBlacklisted: values.hideBlacklisted,
locale: values.locale,
discoverRegion: values.discoverRegion,
streamingRegion: values.streamingRegion,
@@ -428,6 +435,9 @@ const SettingsMain = () => {
{intl.formatMessage(messages.hideAvailable)}
</span>
<SettingsBadge badgeType="experimental" />
<span className="label-tip">
{intl.formatMessage(messages.hideAvailableTip)}
</span>
</label>
<div className="form-input-area">
<Field
@@ -440,6 +450,29 @@ const SettingsMain = () => {
/>
</div>
</div>
<div className="form-row">
<label htmlFor="hideBlacklisted" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.hideBlacklisted)}
</span>
<span className="label-tip">
{intl.formatMessage(messages.hideBlacklistedTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="hideBlacklisted"
name="hideBlacklisted"
onChange={() => {
setFieldValue(
'hideBlacklisted',
!values.hideBlacklisted
);
}}
/>
</div>
</div>
<div className="form-row">
<label
htmlFor="partialRequestsEnabled"

View File

@@ -13,6 +13,7 @@ const defaultSettings = {
applicationTitle: 'Jellyseerr',
applicationUrl: '',
hideAvailable: false,
hideBlacklisted: false,
localLogin: true,
mediaServerLogin: true,
movie4kEnabled: false,

View File

@@ -1,6 +1,7 @@
import { MediaStatus } from '@server/constants/media';
import useSWRInfinite from 'swr/infinite';
import useSettings from './useSettings';
import { Permission, useUser } from './useUser';
export interface BaseSearchResult<T> {
page: number;
@@ -53,9 +54,10 @@ const useDiscover = <
>(
endpoint: string,
options?: O,
{ hideAvailable = true } = {}
{ hideAvailable = true, hideBlacklisted = true } = {}
): DiscoverResult<T, S> => {
const settings = useSettings();
const { hasPermission } = useUser();
const { data, error, size, setSize, isValidating, mutate } = useSWRInfinite<
BaseSearchResult<T> & S
>(
@@ -120,6 +122,18 @@ const useDiscover = <
);
}
if (
settings.currentSettings.hideBlacklisted &&
hideBlacklisted &&
hasPermission(Permission.MANAGE_BLACKLIST)
) {
titles = titles.filter(
(i) =>
(i.mediaType === 'movie' || i.mediaType === 'tv') &&
i.mediaInfo?.status !== MediaStatus.BLACKLISTED
);
}
const isEmpty = !isLoadingInitialData && titles?.length === 0;
const isReachingEnd =
isEmpty ||

View File

@@ -959,6 +959,9 @@
"components.Settings.SettingsMain.generalsettings": "General Settings",
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",
"components.Settings.SettingsMain.hideAvailable": "Hide Available Media",
"components.Settings.SettingsMain.hideAvailableTip": "Hide available media from the discover pages but not search results",
"components.Settings.SettingsMain.hideBlacklisted": "Hide Blacklisted Items",
"components.Settings.SettingsMain.hideBlacklistedTip": "Hide blacklisted items from discover pages for all users with the \"Manage Blacklist\" permission",
"components.Settings.SettingsMain.locale": "Display Language",
"components.Settings.SettingsMain.originallanguage": "Discover Language",
"components.Settings.SettingsMain.originallanguageTip": "Filter content by original language",

View File

@@ -231,6 +231,7 @@ CoreApp.getInitialProps = async (initialProps) => {
applicationTitle: '',
applicationUrl: '',
hideAvailable: false,
hideBlacklisted: false,
movie4kEnabled: false,
series4kEnabled: false,
localLogin: true,