mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(catalog): add filters modal for text-only posts, nsfw boards in p/all
This commit is contained in:
@@ -2,9 +2,10 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
|
||||||
import { isAllView, isCatalogView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isCatalogView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
|
import useSortingStore from '../../stores/use-sorting-store';
|
||||||
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
||||||
import useTimeFilter from '../../hooks/use-time-filter';
|
import useTimeFilter from '../../hooks/use-time-filter';
|
||||||
|
import CatalogFilters from '../../views/catalog/catalog-filters/';
|
||||||
import styles from './board-buttons.module.css';
|
import styles from './board-buttons.module.css';
|
||||||
|
|
||||||
interface BoardButtonsProps {
|
interface BoardButtonsProps {
|
||||||
@@ -82,7 +83,7 @@ const RefreshButton = () => {
|
|||||||
|
|
||||||
const SortOptions = () => {
|
const SortOptions = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { sortType, setSortType } = useCatalogFiltersStore();
|
const { sortType, setSortType } = useSortingStore();
|
||||||
|
|
||||||
const handleSortChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleSortChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const type = event.target.value as 'active' | 'new';
|
const type = event.target.value as 'active' | 'new';
|
||||||
@@ -183,20 +184,6 @@ export const MobileBoardButtons = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TextOnlyPostsFilter = () => {
|
|
||||||
const { showTextOnlyThreads, setShowTextOnlyThreads } = useCatalogFiltersStore();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
Text-Only Posts:{' '}
|
|
||||||
<select value={showTextOnlyThreads ? 'Show' : 'Hide'} onChange={(e) => setShowTextOnlyThreads(e.target.value === 'Show')}>
|
|
||||||
<option value='Hide'>Hide</option>
|
|
||||||
<option value='Show'>Show</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DesktopBoardButtons = () => {
|
export const DesktopBoardButtons = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -244,7 +231,7 @@ export const DesktopBoardButtons = () => {
|
|||||||
{(isInAllView || isInSubscriptionsView) && (
|
{(isInAllView || isInSubscriptionsView) && (
|
||||||
<TimeFilter isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />
|
<TimeFilter isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />
|
||||||
)}
|
)}
|
||||||
{isInCatalogView && <TextOnlyPostsFilter />}
|
{(isInCatalogView || isInAllView || isInSubscriptionsView) && <CatalogFilters />}{' '}
|
||||||
{!(isInAllView || isInSubscriptionsView) && (
|
{!(isInAllView || isInSubscriptionsView) && (
|
||||||
<>
|
<>
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -24,12 +24,12 @@
|
|||||||
|
|
||||||
.settingsModal {
|
.settingsModal {
|
||||||
top: 25px;
|
top: 25px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
width: 394px;
|
width: 394px;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
left: calc(50% - 22px);
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin-left: -178px;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const SettingsModal = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.overlay} onClick={closeModal}></div>
|
<div className={styles.overlay} onClick={closeModal} />
|
||||||
<div className={styles.settingsModal}>
|
<div className={styles.settingsModal}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<span className={styles.title}>{t('settings')}</span>
|
<span className={styles.title}>{t('settings')}</span>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
interface CatalogFiltersStore {
|
interface CatalogFiltersStore {
|
||||||
|
showAdultBoards: boolean;
|
||||||
|
setShowAdultBoards: (value: boolean) => void;
|
||||||
|
showGoreBoards: boolean;
|
||||||
|
setShowGoreBoards: (value: boolean) => void;
|
||||||
showTextOnlyThreads: boolean;
|
showTextOnlyThreads: boolean;
|
||||||
setShowTextOnlyThreads: (value: boolean) => void;
|
setShowTextOnlyThreads: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
@@ -11,6 +15,16 @@ const useCatalogFiltersStore = create<CatalogFiltersStore>((set) => ({
|
|||||||
set({ showTextOnlyThreads: value });
|
set({ showTextOnlyThreads: value });
|
||||||
localStorage.setItem('showTextOnlyThreads', value.toString());
|
localStorage.setItem('showTextOnlyThreads', value.toString());
|
||||||
},
|
},
|
||||||
|
showAdultBoards: localStorage.getItem('showAdultBoards') === 'true' ? true : false,
|
||||||
|
setShowAdultBoards: (value: boolean) => {
|
||||||
|
set({ showAdultBoards: value });
|
||||||
|
localStorage.setItem('showAdultBoards', value.toString());
|
||||||
|
},
|
||||||
|
showGoreBoards: localStorage.getItem('showGoreBoards') === 'true' ? true : false,
|
||||||
|
setShowGoreBoards: (value: boolean) => {
|
||||||
|
set({ showGoreBoards: value });
|
||||||
|
localStorage.setItem('showGoreBoards', value.toString());
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useCatalogFiltersStore;
|
export default useCatalogFiltersStore;
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
text-transform: capitalize;
|
||||||
|
color: var(--button-desktop-text-color);
|
||||||
|
text-decoration: var(--button-text-decoration);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
color: var(--button-desktop-text-color-hover);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal label, .modal button, .modal select {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal button {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
top: 60px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 340px;
|
||||||
|
height: auto;
|
||||||
|
max-height: 80%;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 5;
|
||||||
|
padding: 2px;
|
||||||
|
font-size: var(--settings-modal-font-size);
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.25);
|
||||||
|
background-color: var(--settings-modal-background-color);
|
||||||
|
border-top: var(--settings-modal-border-top);
|
||||||
|
border-right: var(--settings-modal-border-right);
|
||||||
|
border-bottom: var(--settings-modal-border-bottom);
|
||||||
|
border-left: var(--settings-modal-border-left);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 14px;
|
||||||
|
border-bottom: var(--settings-modal-header-border-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
right: 5px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: block;
|
||||||
|
background-size: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
background-image: var(--settings-modal-close-button-background-image);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters label input[type='checkbox'] {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryTitle {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paddingBottom {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { isAllView, isCatalogView } from '../../../lib/utils/view-utils';
|
||||||
|
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
|
||||||
|
import styles from './catalog-filters.module.css';
|
||||||
|
|
||||||
|
const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { showAdultBoards, setShowAdultBoards, showGoreBoards, setShowGoreBoards, showTextOnlyThreads, setShowTextOnlyThreads } = useCatalogFiltersStore();
|
||||||
|
const location = useLocation();
|
||||||
|
const isInCatalogView = isCatalogView(location.pathname, useParams());
|
||||||
|
const isInAllView = isAllView(location.pathname);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.overlay} onClick={closeModal} />
|
||||||
|
<div className={styles.modal}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<span className={styles.title}>{t('filters')}</span>
|
||||||
|
<span className={styles.closeButton} title='close' onClick={closeModal} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.filters}>
|
||||||
|
{isInCatalogView && (
|
||||||
|
<label className={styles.paddingBottom}>
|
||||||
|
<input type='checkbox' checked={!showTextOnlyThreads} onChange={(e) => setShowTextOnlyThreads(!e.target.checked)} />
|
||||||
|
Hide Text-Only Posts
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
{isInAllView && (
|
||||||
|
<div className={styles.nsfwLabels}>
|
||||||
|
<div className={styles.categoryTitle}>NSFW Boards</div>
|
||||||
|
<label>
|
||||||
|
<input type='checkbox' checked={!showGoreBoards} onChange={(e) => setShowGoreBoards(!e.target.checked)} />
|
||||||
|
Hide Gore Boards
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type='checkbox' checked={!showAdultBoards} onChange={(e) => setShowAdultBoards(!e.target.checked)} />
|
||||||
|
Hide Adult Boards
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CatalogFilters = () => {
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setShowModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
[
|
||||||
|
<span className={styles.button} onClick={() => setShowModal(true)}>
|
||||||
|
Filters
|
||||||
|
</span>
|
||||||
|
]{showModal && <FiltersModal closeModal={closeModal} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CatalogFilters;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './catalog-filters';
|
||||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useAccount, Subplebbit, useFeed, useSubplebbit, Comment } from '@plebbit/plebbit-react-hooks';
|
import { useAccount, Subplebbit, useFeed, useSubplebbit, Comment } from '@plebbit/plebbit-react-hooks';
|
||||||
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
import useDefaultSubplebbits from '../../hooks/use-default-subplebbits';
|
||||||
import useFeedStateString from '../../hooks/use-feed-state-string';
|
import useFeedStateString from '../../hooks/use-feed-state-string';
|
||||||
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
|
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
|
||||||
import useTimeFilter from '../../hooks/use-time-filter';
|
import useTimeFilter from '../../hooks/use-time-filter';
|
||||||
@@ -91,21 +91,39 @@ const Catalog = () => {
|
|||||||
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
|
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
|
||||||
|
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses();
|
const defaultSubplebbits = useDefaultSubplebbits();
|
||||||
|
const { showAdultBoards, showGoreBoards } = useCatalogFiltersStore();
|
||||||
|
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const subscriptions = account?.subscriptions;
|
const subscriptions = account?.subscriptions;
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname);
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname);
|
||||||
|
|
||||||
const subplebbitAddresses = useMemo(() => {
|
const subplebbitAddresses = useMemo(() => {
|
||||||
|
const filteredDefaultSubplebbits = defaultSubplebbits
|
||||||
|
.filter((subplebbit) => {
|
||||||
|
const { tags } = subplebbit;
|
||||||
|
const hasGoreTag = tags?.includes('gore');
|
||||||
|
const hasAdultTag = tags?.includes('adult');
|
||||||
|
|
||||||
|
if ((hasGoreTag && !showGoreBoards) || (hasAdultTag && !showAdultBoards)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((subplebbit) => subplebbit.address);
|
||||||
|
|
||||||
if (isInAllView) {
|
if (isInAllView) {
|
||||||
return defaultSubplebbitAddresses;
|
return filteredDefaultSubplebbits;
|
||||||
}
|
}
|
||||||
if (isInSubscriptionsView) {
|
if (isInSubscriptionsView) {
|
||||||
return subscriptions || [];
|
return subscriptions || [];
|
||||||
}
|
}
|
||||||
return [subplebbitAddress];
|
return [subplebbitAddress];
|
||||||
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbitAddresses, subscriptions]);
|
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbits, subscriptions, showAdultBoards, showGoreBoards]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(showAdultBoards, showGoreBoards);
|
||||||
|
}, [showAdultBoards, showGoreBoards]);
|
||||||
|
|
||||||
const columnCount = Math.floor(useWindowWidth() / columnWidth);
|
const columnCount = Math.floor(useWindowWidth() / columnWidth);
|
||||||
// postPerPage based on columnCount for optimized feed, dont change value after first render
|
// postPerPage based on columnCount for optimized feed, dont change value after first render
|
||||||
|
|||||||
Reference in New Issue
Block a user