feat(all-feed): add NSFW/SFW filter

This commit is contained in:
plebeius
2025-12-29 16:18:59 +01:00
parent 790c341976
commit 652385561b
5 changed files with 103 additions and 18 deletions
+36 -12
View File
@@ -10,6 +10,7 @@ import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useSortingStore from '../../stores/use-sorting-store';
import useAllFeedFilterStore from '../../stores/use-all-feed-filter-store';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useIsMobile from '../../hooks/use-is-mobile';
import useTimeFilter from '../../hooks/use-time-filter';
@@ -247,14 +248,14 @@ export const TimeFilter = ({ isInAllView, isInCatalogView, isInSubscriptionsView
? `/all/catalog/${timeFilterName}`
: `/all/${timeFilterName}`
: isInSubscriptionsView
? isInCatalogView
? `/subs/catalog/${timeFilterName}`
: `/subs/${timeFilterName}`
: isInModView
? isInCatalogView
? `/mod/catalog/${timeFilterName}`
: `/mod/${timeFilterName}`
: null;
? isInCatalogView
? `/subs/catalog/${timeFilterName}`
: `/subs/${timeFilterName}`
: isInModView
? isInCatalogView
? `/mod/catalog/${timeFilterName}`
: `/mod/${timeFilterName}`
: null;
link && navigate(link);
};
@@ -282,6 +283,22 @@ export const TimeFilter = ({ isInAllView, isInCatalogView, isInSubscriptionsView
);
};
const AllFeedFilter = () => {
const { t } = useTranslation();
const { filter, setFilter } = useAllFeedFilterStore();
return (
<>
<span>{t('show')}</span>:&nbsp;
<select className='capitalize' value={filter} onChange={(e) => setFilter(e.target.value as 'all' | 'nsfw' | 'sfw')}>
<option value='all'>{t('show_all_boards')}</option>
<option value='nsfw'>{t('show_nsfw_boards_only')}</option>
<option value='sfw'>{t('show_worksafe_boards_only')}</option>
</select>
</>
);
};
export const MobileBoardButtons = () => {
const { t } = useTranslation();
const params = useParams();
@@ -341,6 +358,14 @@ export const MobileBoardButtons = () => {
</span>
)
)}
{isInAllView && (
<>
<hr />
<div className={styles.options}>
<AllFeedFilter />
</div>
</>
)}
{isInCatalogView && (
<>
<hr />
@@ -409,11 +434,9 @@ export const DesktopBoardButtons = () => {
{isInPostView || isInPendingPostPage ? (
<>
[
<ReturnButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
] [
<ReturnButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />] [
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />] [
<UpdateButton />
] [
<UpdateButton />] [
<AutoButton />]
<span className={styles.rightSideButtons}>
<PostPageStats />
@@ -462,6 +485,7 @@ export const DesktopBoardButtons = () => {
<ShowOPCommentOption />
</>
)}
{isInAllView && <AllFeedFilter />}
{(isInAllView || isInSubscriptionsView || isInModView) && (
<TimeFilter isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
)}
@@ -0,0 +1,19 @@
import { useMemo } from 'react';
import { useDefaultSubplebbits } from './use-default-subplebbits';
import useAllFeedFilterStore from '../stores/use-all-feed-filter-store';
export const useFilteredDefaultSubplebbitAddresses = () => {
const defaultSubplebbits = useDefaultSubplebbits();
const { filter } = useAllFeedFilterStore();
return useMemo(() => {
if (filter === 'all') {
return defaultSubplebbits.map((subplebbit) => subplebbit.address);
}
if (filter === 'nsfw') {
return defaultSubplebbits.filter((subplebbit) => subplebbit.nsfw === true).map((subplebbit) => subplebbit.address);
}
// filter === 'sfw'
return defaultSubplebbits.filter((subplebbit) => subplebbit.nsfw !== true).map((subplebbit) => subplebbit.address);
}, [defaultSubplebbits, filter]);
};
+31
View File
@@ -0,0 +1,31 @@
import { create } from 'zustand';
export type AllFeedFilter = 'all' | 'nsfw' | 'sfw';
interface AllFeedFilterStore {
filter: AllFeedFilter;
setFilter: (value: AllFeedFilter) => void;
}
const getStoredFilter = (): AllFeedFilter => {
try {
const stored = localStorage.getItem('5chan-all-feed-filter') as AllFeedFilter;
return stored === 'nsfw' || stored === 'sfw' ? stored : 'all';
} catch {
return 'all';
}
};
const useAllFeedFilterStore = create<AllFeedFilterStore>((set) => ({
filter: getStoredFilter(),
setFilter: (value: AllFeedFilter) => {
set({ filter: value });
try {
localStorage.setItem('5chan-all-feed-filter', value);
} catch (error) {
console.warn('Failed to save allFeedFilter to localStorage:', error);
}
},
}));
export default useAllFeedFilterStore;
+13 -2
View File
@@ -7,6 +7,7 @@ import styles from './board.module.css';
import { shouldShowSnow } from '../../lib/snow';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { useDefaultSubplebbitAddresses, useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { useFilteredDefaultSubplebbitAddresses } from '../../hooks/use-filtered-default-subplebbit-addresses';
import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string';
import useTimeFilter, { timeFilterNameToSeconds } from '../../hooks/use-time-filter';
@@ -60,6 +61,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
const boardPath = useBoardPath(subplebbitAddress);
const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses();
const filteredDefaultSubplebbitAddresses = useFilteredDefaultSubplebbitAddresses();
const account = useAccount();
const subscriptions = account?.subscriptions;
@@ -69,7 +71,7 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
const subplebbitAddresses = useMemo(() => {
if (isInAllView) {
return defaultSubplebbitAddresses;
return filteredDefaultSubplebbitAddresses;
}
if (isInSubscriptionsView) {
return subscriptions || [];
@@ -78,7 +80,16 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
return accountSubplebbitAddresses;
}
return [subplebbitAddress];
}, [isInAllView, isInSubscriptionsView, isInModView, subplebbitAddress, defaultSubplebbitAddresses, subscriptions, accountSubplebbitAddresses]);
}, [
isInAllView,
isInSubscriptionsView,
isInModView,
subplebbitAddress,
defaultSubplebbitAddresses,
filteredDefaultSubplebbitAddresses,
subscriptions,
accountSubplebbitAddresses,
]);
const { sortType } = useSortingStore();
const { timeFilterSeconds: timeFilterSecondsFromHook, timeFilterName: timeFilterNameFromHook } = useTimeFilter();
+4 -4
View File
@@ -6,6 +6,7 @@ import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
import { useFilteredDefaultSubplebbitAddresses } from '../../hooks/use-filtered-default-subplebbit-addresses';
import { useResolvedSubplebbitAddress, useBoardPath } from '../../hooks/use-resolved-subplebbit-address';
import { useFeedStateString } from '../../hooks/use-state-string';
import useTimeFilter, { timeFilterNameToSeconds } from '../../hooks/use-time-filter';
@@ -152,19 +153,18 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
const account = useAccount();
const subscriptions = account?.subscriptions;
const filteredDefaultSubplebbitAddresses = useFilteredDefaultSubplebbitAddresses();
const subplebbitAddresses = useMemo(() => {
const filteredDefaultSubplebbits = defaultSubplebbits.map((subplebbit) => subplebbit.address).filter(Boolean); // Filter out any undefined/null values
if (isInAllView) {
return filteredDefaultSubplebbits;
return filteredDefaultSubplebbitAddresses;
}
if (isInSubscriptionsView) {
return (subscriptions || []).filter(Boolean); // Filter out any undefined/null values
}
// Only include subplebbitAddress if it's defined
return subplebbitAddress ? [subplebbitAddress] : [];
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbits, subscriptions]);
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, filteredDefaultSubplebbitAddresses, subscriptions]);
const { imageSize } = useCatalogStyleStore();
const columnWidth = imageSize === 'Large' ? 270 : 180;