update board buttons UI, add catalog search UI

This commit is contained in:
Tom (plebeius.eth)
2025-03-06 15:44:26 +01:00
parent 23f70c96b8
commit cf183393a4
12 changed files with 213 additions and 38 deletions
@@ -1,191 +0,0 @@
.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;
}
.filtersButton {
text-transform: capitalize;
}
.modal label, .modal button, .modal select {
cursor: pointer;
}
.modal button {
text-transform: capitalize;
}
.modal {
top: 60px;
left: 50%;
transform: translateX(-50%);
width: 380px;
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);
}
@media (max-width: 420px) {
.modal {
width: 90vw;
}
}
.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;
text-align: left;
}
.filters label {
display: inline-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;
}
.saveButton {
margin-left: 5px;
}
.filtersTable {
width: 100%;
border-spacing: 1px;
margin-left: auto;
margin-right: auto;
padding: 10px 2px 2px 2px;
}
.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: var(--reply-modal-field-input-border, revert);
font-size: 11px;
width: 150px;
}
.filtersTable input[type='text']:focus {
border: var(--reply-modal-field-input-border-focus, revert);
}
.filtersTable span {
cursor: pointer;
}
.filtersTable tfoot td {
padding-top: 20px;
}
.addButton {
float: left;
}
.saveButton {
float: right;
}
.filterHits {
font-size: 12px !important;
text-transform: none;
}
.clickbox {
width: 16px;
height: 16px;
line-height: 16px;
font-size: 10px;
display: block;
text-align: center;
background-color: #fff;
border: 1px solid #aaa;
text-decoration: none;
color: #000;
margin: auto;
}
@@ -1,193 +0,0 @@
import { useState, useCallback, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
import styles from './catalog-filters.module.css';
const FiltersTable = ({ onSave }: { onSave: () => void }) => {
const { t } = useTranslation();
const { filterItems, saveAndApplyFilters } = useCatalogFiltersStore();
const [localFilterItems, setLocalFilterItems] = useState(
filterItems.map((item) => ({
...item,
hide: item.hide ?? true,
top: item.top ?? false,
})),
);
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
// Update local items when store changes
useEffect(() => {
setLocalFilterItems(
filterItems.map((item) => ({
...item,
hide: item.hide ?? true,
top: item.top ?? false,
})),
);
}, [filterItems]);
const handleAddFilter = useCallback(() => {
setLocalFilterItems((prev) => {
const newIndex = prev.length;
setTimeout(() => {
inputRefs.current[newIndex]?.focus();
}, 0);
return [
...prev,
{
text: '',
enabled: true,
count: 0,
filteredCids: new Set<string>(),
hide: true,
top: false,
},
];
});
}, []);
const handleSave = useCallback(() => {
const nonEmptyFilters = localFilterItems.filter((item) => item.text.trim() !== '');
saveAndApplyFilters(nonEmptyFilters);
onSave();
}, [saveAndApplyFilters, localFilterItems, onSave]);
const updateLocalFilterItem = useCallback((index: number, item: any) => {
setLocalFilterItems((prev) => prev.map((f, i) => (i === index ? item : f)));
}, []);
const removeLocalFilterItem = useCallback((index: number) => {
setLocalFilterItems((prev) => prev.filter((_, i) => i !== index));
}, []);
const moveLocalFilterItemUp = useCallback((index: number) => {
if (index === 0) return;
setLocalFilterItems((prev) => {
const newItems = [...prev];
[newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
return newItems;
});
}, []);
return (
<table className={styles.filtersTable}>
<thead>
<tr>
<th>order</th>
<th>on</th>
<th>pattern</th>
<th>color</th>
<th>hide</th>
<th>top</th>
<th>del</th>
</tr>
</thead>
<tbody>
{localFilterItems.map((item, index) => (
<tr key={index}>
<td>
<span className={styles.orderButton} onClick={() => moveLocalFilterItemUp(index)}>
</span>
</td>
<td>
<input
type='checkbox'
className={styles.onCheckbox}
checked={item.enabled}
onChange={(e) => updateLocalFilterItem(index, { ...item, enabled: e.target.checked })}
/>
</td>
<td>
<input
type='text'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
value={item.text}
onChange={(e) => updateLocalFilterItem(index, { ...item, text: e.target.value })}
ref={(el) => (inputRefs.current[index] = el)}
/>
</td>
<td>
<span className={styles.clickbox} />
</td>
<td>
<input type='checkbox' checked={item.hide} onChange={(e) => updateLocalFilterItem(index, { ...item, hide: e.target.checked })} />
</td>
<td>
<input type='checkbox' checked={item.top} onChange={(e) => updateLocalFilterItem(index, { ...item, top: e.target.checked })} />
</td>
<td>
<span className={styles.deleteButton} onClick={() => removeLocalFilterItem(index)}>
×
</span>
</td>
<td className={styles.filterHits}>{item.count > 0 && `x${item.count}`}</td>
</tr>
))}
</tbody>
<tfoot>
<tr>
<td colSpan={7}>
<button className={styles.addButton} onClick={handleAddFilter}>
{t('add')}
</button>
<button className={styles.saveButton} onClick={handleSave}>
{t('save')}
</button>
</td>
</tr>
</tfoot>
</table>
);
};
const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
const { t } = useTranslation();
return (
<>
<div className={styles.overlay} onClick={closeModal} />
<div className={styles.modal}>
<div className={styles.header}>
<span className={styles.title}>{t('filters_and_highlights')}</span>
<span className={styles.closeButton} title='close' onClick={closeModal} />
</div>
<FiltersTable onSave={closeModal} />
</div>
</>
);
};
const CatalogFilters = () => {
const { t } = useTranslation();
const [showModal, setShowModal] = useState(false);
const closeModal = useCallback(() => {
setShowModal(false);
}, []);
useEffect(() => {
const onEscapeKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
closeModal();
}
};
document.addEventListener('keydown', onEscapeKey);
return () => document.removeEventListener('keydown', onEscapeKey);
}, [closeModal]);
return (
<>
<span className={`${styles.filtersButton} button`} onClick={() => setShowModal(true)}>
{t('filters')}
</span>
{showModal && <FiltersModal closeModal={closeModal} />}
</>
);
};
export default CatalogFilters;
@@ -1 +0,0 @@
export { default } from './catalog-filters';
+33 -12
View File
@@ -23,7 +23,8 @@ const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const createContentFilter = (
filterItems: { text: string; enabled: boolean; count: number; filteredCids: Set<string>; hide: boolean; top: boolean }[],
onFilterMatch?: (filterIndex: number, cid: string) => void,
subplebbitAddress: string,
onFilterMatch?: (filterIndex: number, cid: string, subplebbitAddress: string) => void,
) => {
// Create a unique key based on the enabled filter items
const enabledFilters = filterItems.filter((item) => item.enabled && item.text.trim() !== '');
@@ -51,10 +52,10 @@ const createContentFilter = (
const filterIndex = filterItems.findIndex((f) => f.text === item.text && f.enabled);
if (filterIndex !== -1) {
if (onFilterMatch) {
onFilterMatch(filterIndex, comment.cid);
onFilterMatch(filterIndex, comment.cid, subplebbitAddress);
} else {
// Fallback to the store method if no callback provided
useCatalogFiltersStore.getState().incrementFilterCount(filterIndex, comment.cid);
useCatalogFiltersStore.getState().incrementFilterCount(filterIndex, comment.cid, subplebbitAddress);
}
}
@@ -91,10 +92,11 @@ const createImageFilter = (showTextOnlyThreads: boolean) => {
const createCombinedFilter = (
showTextOnlyThreads: boolean,
filterItems: { text: string; enabled: boolean; count: number; filteredCids: Set<string>; hide: boolean; top: boolean }[],
onFilterMatch?: (filterIndex: number, cid: string) => void,
subplebbitAddress: string,
onFilterMatch?: (filterIndex: number, cid: string, subplebbitAddress: string) => void,
) => {
const imageFilter = createImageFilter(showTextOnlyThreads);
const contentFilter = createContentFilter(filterItems, onFilterMatch);
const contentFilter = createContentFilter(filterItems, subplebbitAddress, onFilterMatch);
return {
filter: (comment: Comment) => {
@@ -152,17 +154,25 @@ const Catalog = () => {
const { timeFilterSeconds, timeFilterName } = useTimeFilter();
const { sortType } = useSortingStore();
// stable callback for filter matching
const handleFilterMatch = useCallback((filterIndex: number, cid: string) => {
useCatalogFiltersStore.getState().incrementFilterCount(filterIndex, cid);
// Create a stable callback for filter matching
const handleFilterMatch = useCallback((filterIndex: number, cid: string, subplebbitAddress: string) => {
useCatalogFiltersStore.getState().incrementFilterCount(filterIndex, cid, subplebbitAddress);
}, []);
// Set the current subplebbit address
useEffect(() => {
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress(subplebbitAddress || null);
return () => {
useCatalogFiltersStore.getState().setCurrentSubplebbitAddress(null);
};
}, [subplebbitAddress]);
const feedOptions = useMemo(() => {
const options: any = {
subplebbitAddresses,
sortType,
postsPerPage: isInAllView || isInSubscriptionsView ? 10 : postsPerPage,
filter: createCombinedFilter(showTextOnlyThreads, filterItems, handleFilterMatch),
filter: createCombinedFilter(showTextOnlyThreads, filterItems, subplebbitAddress || 'all', handleFilterMatch),
};
if (isInAllView || isInSubscriptionsView) {
@@ -170,7 +180,18 @@ const Catalog = () => {
}
return options;
}, [subplebbitAddresses, sortType, isInAllView, isInSubscriptionsView, postsPerPage, timeFilterSeconds, showTextOnlyThreads, filterItems, handleFilterMatch]);
}, [
subplebbitAddresses,
sortType,
isInAllView,
isInSubscriptionsView,
postsPerPage,
timeFilterSeconds,
showTextOnlyThreads,
filterItems,
subplebbitAddress,
handleFilterMatch,
]);
const { feed, hasMore, loadMore, reset, subplebbitAddressesWithNewerPosts } = useFeed(feedOptions);
const { accountComments } = useAccountComments();
@@ -226,14 +247,14 @@ const Catalog = () => {
subplebbitAddresses,
sortType,
newerThan: 60 * 60 * 24 * 7,
filter: createCombinedFilter(showTextOnlyThreads, filterItems, handleFilterMatch),
filter: createCombinedFilter(showTextOnlyThreads, filterItems, subplebbitAddress || 'all', handleFilterMatch),
});
const { feed: monthlyFeed } = useFeed({
subplebbitAddresses,
sortType,
newerThan: 60 * 60 * 24 * 30,
filter: createCombinedFilter(showTextOnlyThreads, filterItems, handleFilterMatch),
filter: createCombinedFilter(showTextOnlyThreads, filterItems, subplebbitAddress || 'all', handleFilterMatch),
});
const [showMorePostsSuggestion, setShowMorePostsSuggestion] = useState(false);