feat(catalog): add text pattern filters

This commit is contained in:
Tom (plebeius.eth)
2024-09-05 14:46:44 +02:00
parent 1122eb4f71
commit da33358ca4
4 changed files with 170 additions and 43 deletions
+20 -17
View File
@@ -1,4 +1,5 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CatalogFiltersStore {
showAdultBoards: boolean;
@@ -7,24 +8,26 @@ interface CatalogFiltersStore {
setShowGoreBoards: (value: boolean) => void;
showTextOnlyThreads: boolean;
setShowTextOnlyThreads: (value: boolean) => void;
filterText: string;
setFilterText: (value: string) => void;
}
const useCatalogFiltersStore = create<CatalogFiltersStore>((set) => ({
showTextOnlyThreads: localStorage.getItem('showTextOnlyThreads') === 'true' ? true : false,
setShowTextOnlyThreads: (value: boolean) => {
set({ showTextOnlyThreads: value });
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());
},
}));
const useCatalogFiltersStore = create(
persist<CatalogFiltersStore>(
(set) => ({
showTextOnlyThreads: false,
setShowTextOnlyThreads: (value: boolean) => set({ showTextOnlyThreads: value }),
showAdultBoards: false,
setShowAdultBoards: (value: boolean) => set({ showAdultBoards: value }),
showGoreBoards: false,
setShowGoreBoards: (value: boolean) => set({ showGoreBoards: value }),
filterText: '',
setFilterText: (value: string) => set({ filterText: value }),
}),
{
name: 'catalog-filters-storage',
},
),
);
export default useCatalogFiltersStore;
@@ -84,7 +84,7 @@
}
.filters label {
display: block;
display: inline-block;
margin-bottom: 7px;
}
@@ -100,4 +100,63 @@
.paddingBottom {
padding-bottom: 5px;
}
}
.saveButton {
margin-left: 5px;
}
.filtersTable {
width: 100%;
border-spacing: 1px;
margin-left: auto;
margin-right: auto;
padding-top: 10px;
}
.filtersTable th {
text-align: center;
font-weight: 700;
font-size: 11px;
min-width: 20px;
}
.filtersTable tbody td {
padding: 8px 0 0;
}
.filtersTable td {
text-align: center;
margin: 0;
font-size: 10pt;
}
.filtersTable input[type='checkbox'] {
display: inline-block;
margin: auto;
}
.filtersTable input[type='text'] {
margin: 0 2px;
padding: 1px;
outline: none;
border: 1px solid #aaa;
font-size: 11px;
width: 180px;
}
.filtersTable span {
cursor: pointer;
}
.filtersTable tfoot td {
padding-top: 20px;
}
.addButton {
float: left;
}
.saveButton {
float: right;
}
@@ -5,6 +5,49 @@ import { isAllView, isCatalogView } from '../../../lib/utils/view-utils';
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
import styles from './catalog-filters.module.css';
const FiltersTable = () => {
const { t } = useTranslation();
const { filterText, setFilterText } = useCatalogFiltersStore();
const [localFilterText, setLocalFilterText] = useState(filterText);
return (
<table className={styles.filtersTable}>
<thead>
<tr>
<th>Order</th>
<th>On</th>
<th>Pattern</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span className={styles.orderButton}></span>
</td>
<td>
<input type='checkbox' className={styles.onCheckbox} />
</td>
<td>
<input type='text' onChange={(e) => setLocalFilterText(e.target.value)} value={localFilterText} />
</td>
<td>
<span className={styles.deleteButton}>×</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colSpan={9}>
<button className={styles.addButton}>Add</button>
<button className={styles.saveButton}>Save</button>
</td>
</tr>
</tfoot>
</table>
);
};
const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
const { t } = useTranslation();
const { showAdultBoards, setShowAdultBoards, showGoreBoards, setShowGoreBoards, showTextOnlyThreads, setShowTextOnlyThreads } = useCatalogFiltersStore();
@@ -23,24 +66,30 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
</div>
<div className={styles.filters}>
{isInCatalogView && (
<label className={`${styles.paddingBottom} capitalize`}>
<input type='checkbox' checked={!showTextOnlyThreads} onChange={(e) => setShowTextOnlyThreads(!e.target.checked)} />
{t('hide_threads_without_images')}
</label>
)}
{isInAllView && (
<div className={styles.nsfwLabels}>
<div className={styles.categoryTitle}>{t('nsfw_boards')}</div>
<label>
<input type='checkbox' checked={!showGoreBoards} onChange={(e) => setShowGoreBoards(!e.target.checked)} />
{t('hide_gore_boards')}
</label>
<label>
<input type='checkbox' checked={!showAdultBoards} onChange={(e) => setShowAdultBoards(!e.target.checked)} />
{t('hide_adult_boards')}
<div>
<label className='capitalize'>
<input type='checkbox' checked={!showTextOnlyThreads} onChange={(e) => setShowTextOnlyThreads(!e.target.checked)} />
{t('hide_threads_without_images')}
</label>
</div>
)}
{isInAllView && (
<div className={styles.nsfwLabels}>
<div>
<label>
<input type='checkbox' checked={!showGoreBoards} onChange={(e) => setShowGoreBoards(!e.target.checked)} />
hide gore content
</label>
</div>
<div>
<label>
<input type='checkbox' checked={!showAdultBoards} onChange={(e) => setShowAdultBoards(!e.target.checked)} />
hide adult content
</label>
</div>
</div>
)}
<FiltersTable />
</div>
</div>
</>
+25 -9
View File
@@ -21,12 +21,21 @@ import styles from './catalog.module.css';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const catalogFilter = (comment: Comment) => {
const threadsWithImagesFilter = (comment: Comment) => {
const commentMediaInfo = getCommentMediaInfo(comment);
const hasThumbnail = getHasThumbnail(commentMediaInfo, comment?.link);
return hasThumbnail;
};
const textFilter = (comment: Comment, filterText: string) => {
if (!filterText) return true;
const filterWords = filterText.toLowerCase().split(/\s+/);
const titleWords = comment?.title?.toLowerCase().split(/\s+/) || [];
const contentWords = comment?.content?.toLowerCase().split(/\s+/) || [];
return !filterWords.every((filterWord) => titleWords.includes(filterWord) || contentWords.includes(filterWord));
};
const Catalog = () => {
const { t } = useTranslation();
const location = useLocation();
@@ -34,7 +43,7 @@ const Catalog = () => {
const isInAllView = isAllView(location.pathname, useParams());
const defaultSubplebbits = useDefaultSubplebbits();
const { showAdultBoards, showGoreBoards } = useCatalogFiltersStore();
const { showAdultBoards, showGoreBoards, showTextOnlyThreads, filterText } = useCatalogFiltersStore();
const account = useAccount();
const subscriptions = account?.subscriptions;
@@ -72,15 +81,22 @@ const Catalog = () => {
const postsPerPage = useMemo(() => (columnCount <= 2 ? 10 : columnCount === 3 ? 15 : columnCount === 4 ? 20 : 25), []);
const { timeFilterSeconds } = useTimeFilter();
const { showTextOnlyThreads } = useCatalogFiltersStore();
const { sortType } = useSortingStore();
const feedOptions: any = {
subplebbitAddresses,
sortType,
postsPerPage: isInAllView || isInSubscriptionsView ? 10 : postsPerPage,
filter: !showTextOnlyThreads && catalogFilter,
};
const feedOptions: any = useMemo(
() => ({
subplebbitAddresses,
sortType,
postsPerPage: isInAllView || isInSubscriptionsView ? 10 : postsPerPage,
filter: (comment: Comment) => {
if (!showTextOnlyThreads && !threadsWithImagesFilter(comment)) {
return false;
}
return textFilter(comment, filterText);
},
}),
[subplebbitAddresses, sortType, isInAllView, isInSubscriptionsView, postsPerPage, showTextOnlyThreads, filterText],
);
if (isInAllView || isInSubscriptionsView) {
feedOptions.newerThan = timeFilterSeconds;