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
+2 -1
View File
@@ -55,8 +55,9 @@ const BoardLayout = () => {
{isMobile
? (subplebbitAddress || isInAllView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && (
<>
<MobileBoardButtons />
<PostForm key={key} />
<hr />
<MobileBoardButtons />
</>
)
: (subplebbitAddress || isInAllView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && (
@@ -51,7 +51,6 @@
}
.desktopBoardButtons {
padding-right: 5px;
text-transform: capitalize;
}
+13 -13
View File
@@ -4,17 +4,18 @@ import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
import { isAllView, isCatalogView, isDescriptionView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
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 useTimeFilter from '../../hooks/use-time-filter';
import CatalogFilters from '../../views/catalog/catalog-filters/';
import styles from './board-buttons.module.css';
import Tooltip from '../tooltip';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import _ from 'lodash';
import useIsMobile from '../../hooks/use-is-mobile';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import useTimeFilter from '../../hooks/use-time-filter';
import CatalogFilters from '../catalog-filters';
import CatalogSearch from '../catalog-search';
import Tooltip from '../tooltip';
import styles from './board-buttons.module.css';
import _ from 'lodash';
interface BoardButtonsProps {
address?: string | undefined;
@@ -282,7 +283,7 @@ export const MobileBoardButtons = () => {
<SortOptions /> <ImageSizeOptions />
</div>
<div className={styles.mobileCatalogOptionsPadding}>
<ShowOPCommentOption /> <CatalogFilters />
<ShowOPCommentOption /> <CatalogFilters /> <CatalogSearch />
</div>
</div>
</>
@@ -380,17 +381,16 @@ export const DesktopBoardButtons = () => {
{(isInAllView || isInSubscriptionsView) && (
<TimeFilter isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />
)}
{isInCatalogView && (
<>
[
<CatalogFilters />]
</>
)}{' '}
{!(isInAllView || isInSubscriptionsView) && (
<>
[
<SubscribeButton address={subplebbitAddress} />]
</>
)}{' '}
{isInCatalogView && (
<>
[<CatalogFilters />] <CatalogSearch />
</>
)}
</span>
</>
@@ -1,6 +1,6 @@
import { useState, useCallback, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import styles from './catalog-filters.module.css';
const FiltersTable = ({ onSave }: { onSave: () => void }) => {
@@ -41,6 +41,8 @@ const FiltersTable = ({ onSave }: { onSave: () => void }) => {
enabled: true,
count: 0,
filteredCids: new Set<string>(),
subplebbitCounts: new Map<string, number>(),
subplebbitFilteredCids: new Map<string, Set<string>>(),
hide: true,
top: false,
},
@@ -0,0 +1,49 @@
.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;
}
.searchContainer {
display: inline;
}
.searchContainer input {
width: 75px;
margin: 0px 5px;
padding: 1px;
outline: none;
border: 1px solid #AAA;
font-size: 11px;
}
.searchContainer input:focus {
border: var(--post-form-field-input-focus-border);
}
.closeSearch {
cursor: pointer;
border: none;
white-space: nowrap;
color: var(--button-desktop-text-color);
}
.closeSearch:hover {
color: var(--button-desktop-text-color-hover);
}
@media (max-width: 640px) {
.searchContainer input {
width: 75px;
font-size: 16px;
}
}
@@ -0,0 +1,45 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styles from './catalog-search.module.css';
import useIsMobile from '../../hooks/use-is-mobile';
const CatalogSearch = () => {
const { t } = useTranslation();
const [openSearch, setOpenSearch] = useState(false);
useEffect(() => {
if (openSearch) {
const input = document.querySelector('input');
input?.focus();
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setOpenSearch(false);
}
};
window.addEventListener('keydown', handleKeyDown);
}
}, [openSearch]);
const isMobile = useIsMobile();
return (
<>
{!isMobile && '['}
<span className={`${styles.filtersButton} button`} onClick={() => setOpenSearch(!openSearch)}>
{t('search')}
</span>
{!isMobile && ']'}
{openSearch && (
<div className={styles.searchContainer}>
<input type='text' />
<span className={styles.closeSearch} onClick={() => setOpenSearch(false)}>
</span>
</div>
)}
</>
);
};
export default CatalogSearch;
+1
View File
@@ -0,0 +1 @@
export { default } from './catalog-search';
@@ -15,8 +15,7 @@
.postFormMobile {
text-align: center;
margin-top: -2px;
padding-bottom: 7px;
margin-top: -3px;
}
.closed {
+66 -8
View File
@@ -7,6 +7,8 @@ interface FilterItem {
enabled: boolean;
count: number;
filteredCids: Set<string>;
subplebbitCounts: Map<string, number>;
subplebbitFilteredCids: Map<string, Set<string>>;
hide: boolean;
top: boolean;
}
@@ -24,8 +26,11 @@ interface CatalogFiltersStore {
initializeFilter: () => void;
filteredCount: number;
filteredCids: Set<string>;
incrementFilterCount: (filterIndex: number, cid: string) => void;
incrementFilterCount: (filterIndex: number, cid: string, subplebbitAddress: string) => void;
recalcFilteredCount: () => void;
currentSubplebbitAddress: string | null;
setCurrentSubplebbitAddress: (address: string | null) => void;
getFilteredCountForCurrentSubplebbit: () => number;
}
const useCatalogFiltersStore = create(
@@ -41,6 +46,8 @@ const useCatalogFiltersStore = create(
filterItems: [],
filteredCount: 0,
filteredCids: new Set<string>(),
currentSubplebbitAddress: null,
setCurrentSubplebbitAddress: (address: string | null) => set({ currentSubplebbitAddress: address }),
setFilterItems: (items: FilterItem[]) => {
const nonEmptyItems = items
.filter((item) => item.text.trim() !== '')
@@ -48,6 +55,8 @@ const useCatalogFiltersStore = create(
...item,
count: item.count || 0,
filteredCids: item.filteredCids || new Set(),
subplebbitCounts: item.subplebbitCounts || new Map(),
subplebbitFilteredCids: item.subplebbitFilteredCids || new Map(),
hide: item.hide ?? true,
top: item.top ?? false,
}));
@@ -61,6 +70,8 @@ const useCatalogFiltersStore = create(
...item,
count: item.count || 0,
filteredCids: item.filteredCids || new Set(),
subplebbitCounts: item.subplebbitCounts || new Map(),
subplebbitFilteredCids: item.subplebbitFilteredCids || new Map(),
hide: item.hide ?? true,
top: item.top ?? false,
}));
@@ -98,30 +109,75 @@ const useCatalogFiltersStore = create(
initializeFilter: () => {
get().updateFilter();
},
incrementFilterCount: (filterIndex: number, cid: string) => {
incrementFilterCount: (filterIndex: number, cid: string, subplebbitAddress: string) => {
set((state) => {
const newFilterItems = [...state.filterItems];
if (newFilterItems[filterIndex]) {
const item = newFilterItems[filterIndex];
if (!item.filteredCids.has(cid)) {
const subplebbitFilteredCids = new Map(item.subplebbitFilteredCids);
if (!subplebbitFilteredCids.has(subplebbitAddress)) {
subplebbitFilteredCids.set(subplebbitAddress, new Set());
}
const cidSet = subplebbitFilteredCids.get(subplebbitAddress)!;
if (!cidSet.has(cid)) {
const newItemFilteredCids = new Set(item.filteredCids);
newItemFilteredCids.add(cid);
cidSet.add(cid);
subplebbitFilteredCids.set(subplebbitAddress, cidSet);
const subplebbitCounts = new Map(item.subplebbitCounts);
const currentCount = subplebbitCounts.get(subplebbitAddress) || 0;
subplebbitCounts.set(subplebbitAddress, currentCount + 1);
newFilterItems[filterIndex] = {
...item,
count: item.count + 1,
filteredCids: newItemFilteredCids,
subplebbitCounts,
subplebbitFilteredCids,
};
const newFilteredCount = newFilterItems.reduce((sum, filt) => (filt.hide ? sum + filt.count : sum), 0);
return { filterItems: newFilterItems, filteredCount: newFilteredCount };
return { filterItems: newFilterItems };
}
}
return state;
});
get().recalcFilteredCount();
},
recalcFilteredCount: () => {
set((state) => ({
filteredCount: state.filterItems.reduce((sum, item) => (item.hide ? sum + item.count : sum), 0),
}));
set((state) => {
const currentSubplebbit = state.currentSubplebbitAddress;
if (!currentSubplebbit) return { filteredCount: 0 };
let filteredCount = 0;
for (const item of state.filterItems) {
if (item.enabled && item.hide) {
const subCount = item.subplebbitCounts?.get(currentSubplebbit) || 0;
filteredCount += subCount;
}
}
return { filteredCount };
});
},
getFilteredCountForCurrentSubplebbit: () => {
const state = get();
const currentSubplebbit = state.currentSubplebbitAddress;
if (!currentSubplebbit) return 0;
let filteredCount = 0;
for (const item of state.filterItems) {
if (item.enabled && item.hide) {
const subCount = item.subplebbitCounts?.get(currentSubplebbit) || 0;
filteredCount += subCount;
}
}
return filteredCount;
},
}),
{
@@ -134,6 +190,8 @@ const useCatalogFiltersStore = create(
enabled: item.enabled,
hide: item.hide,
top: item.top,
subplebbitCounts: Array.from(item.subplebbitCounts || new Map()),
subplebbitFilteredCids: Array.from((item.subplebbitFilteredCids || new Map()).entries()).map(([key, value]) => [key, Array.from(value)]),
})),
} as any;
},
+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);