Update explorer with some of recent comments

merge-requests/3337/head
danidfra 2025-03-02 23:21:24 -03:00
rodzic 3955977b1f
commit f8f1292c39
6 zmienionych plików z 166 dodań i 81 usunięć

Wyświetl plik

@ -27,7 +27,16 @@ const SidebarNavigationLink = forwardRef((props: ISidebarNavigationLink, ref: Re
const { icon, activeIcon, text, to = '', count, countMax, onClick } = props;
const { pathname } = useLocation();
const isActive = pathname === to;
const isDefault = to === '' || to === '/';
let isActive;
if (isDefault) {
isActive = pathname === to;
} else {
isActive = pathname.includes(to);
}
const handleClick: React.EventHandler<React.MouseEvent> = (e) => {
if (onClick) {

Wyświetl plik

@ -1,18 +1,12 @@
import globeIcon from '@tabler/icons/outline/globe.svg';
import trendIcon from '@tabler/icons/outline/trending-up.svg';
import userIcon from '@tabler/icons/outline/user.svg';
import xIcon from '@tabler/icons/outline/x.svg';
import clsx from 'clsx';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import {
FormattedMessage,
defineMessages,
useIntl,
} from 'react-intl';
import {
expandSearch,
setFilter,
setSearchAccount,
} from 'soapbox/actions/search.ts';
import { expandTrendingStatuses, fetchTrendingStatuses } from 'soapbox/actions/trending-statuses.ts';
@ -20,7 +14,6 @@ import { useAccount } from 'soapbox/api/hooks/index.ts';
import Hashtag from 'soapbox/components/hashtag.tsx';
import IconButton from 'soapbox/components/icon-button.tsx';
import ScrollableList from 'soapbox/components/scrollable-list.tsx';
import ExplorerTabs from 'soapbox/components/ui/explorer-tabs.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Spinner from 'soapbox/components/ui/spinner.tsx';
import Text from 'soapbox/components/ui/text.tsx';
@ -29,33 +22,19 @@ import StatusContainer from 'soapbox/containers/status-container.tsx';
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account.tsx';
import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder-hashtag.tsx';
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status.tsx';
import PublicTimeline from 'soapbox/features/public-timeline/index.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useSuggestions } from 'soapbox/queries/suggestions.ts';
import { initialState as filterInitialState } from 'soapbox/reducers/search-filter.ts';
import { SearchFilter } from 'soapbox/reducers/search.ts';
import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
import type { VirtuosoHandle } from 'react-virtuoso';
const messages = defineMessages({
accounts: { id: 'search_results.accounts', defaultMessage: 'Accounts' },
statuses: { id: 'search_results.posts', defaultMessage: 'Posts' },
trends: { id: 'search_results.trends', defaultMessage: 'Trends' },
search: { id: 'common.search', defaultMessage: 'Search' },
});
const SearchResults = () => {
const node = useRef<VirtuosoHandle>(null);
const filters = useAppSelector((state) => state.search_filter);
const intl = useIntl();
const dispatch = useAppDispatch();
const { data: suggestions } = useSuggestions();
const [withFilter, setWithFilter] = useState(false);
const [tab, setTab] = useState('global');
const value = useAppSelector((state) => state.search.submittedValue);
const results = useAppSelector((state) => state.search.results);
@ -76,39 +55,6 @@ const SearchResults = () => {
};
const handleUnsetAccount = () => dispatch(setSearchAccount(null));
const handleAction = (filter: SearchFilter, tab: string) =>{
selectFilter(filter);
setTab(tab);
};
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
const renderFilterBar = () => {
const items = [];
items.push(
{
label: intl.formatMessage(messages.statuses),
action: () => handleAction('statuses', 'global'),
name: 'global',
icon: globeIcon,
},
{
label: intl.formatMessage(messages.trends),
action: () => handleAction('statuses', 'statuses'),
name: 'statuses',
icon: trendIcon,
},
// TODO : limit search accounts to only be able use include
{
label: intl.formatMessage(messages.accounts),
action: () => handleAction('accounts', 'accounts'),
name: 'accounts',
icon: userIcon,
},
);
return <ExplorerTabs items={items} activeItem={tab} />;
};
const getCurrentIndex = (id: string): number => {
return resultsIds?.keySeq().findIndex(key => key === id);
@ -143,16 +89,6 @@ const SearchResults = () => {
dispatch(fetchTrendingStatuses());
}, []);
useEffect(() => {
setWithFilter(filters.length !== filterInitialState.length ||
!filters.every((filter, index) =>
filter.name === filterInitialState[index].name &&
filter.status === filterInitialState[index].status &&
filter.value === filterInitialState[index].value,
),
);
}, [filters]);
let searchResults;
let hasMore = false;
let loaded;
@ -247,7 +183,7 @@ const SearchResults = () => {
return (
<>
{filterByAccount ? (
{filterByAccount && (
<HStack className='mb-4 border-b border-solid border-gray-200 px-2 pb-4 dark:border-gray-800' space={2}>
<IconButton iconClassName='h-5 w-5' src={xIcon} onClick={handleUnsetAccount} />
<Text truncate>
@ -258,13 +194,9 @@ const SearchResults = () => {
/>
</Text>
</HStack>
) : (
<div className='relative px-4'>
{renderFilterBar()}
</div>
)}
{tab === 'global' && !withFilter ? <PublicTimeline /> : (noResultsMessage || (
{noResultsMessage || (
<ScrollableList
id='search-results'
ref={node}
@ -287,7 +219,7 @@ const SearchResults = () => {
>
{searchResults || []}
</ScrollableList>
))}
)}
</>
);
};

Wyświetl plik

@ -1,23 +1,58 @@
import globeIcon from '@tabler/icons/outline/globe.svg';
import trendIcon from '@tabler/icons/outline/trending-up.svg';
import userIcon from '@tabler/icons/outline/user.svg';
import { useEffect, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Route, Switch, useLocation } from 'react-router-dom';
import { useNavigate } from 'react-router-dom-v5-compat';
import { clearSearch, setFilter } from 'soapbox/actions/search.ts';
import { Column } from 'soapbox/components/ui/column.tsx';
import Divider from 'soapbox/components/ui/divider.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import Tabs from 'soapbox/components/ui/tabs.tsx';
import SearchResults from 'soapbox/features/compose/components/search-results.tsx';
import Search from 'soapbox/features/compose/components/search.tsx';
import ExplorerCards from 'soapbox/features/explorer/components/explorer-cards.tsx';
import ExplorerFilter from 'soapbox/features/explorer/components/explorerFilter.tsx';
import AccountsCarousel from 'soapbox/features/explorer/components/popular-accounts.tsx';
import { PublicTimeline } from 'soapbox/features/ui/util/async-components.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { IFilters, initialState as filterInitialState } from 'soapbox/reducers/search-filter.ts';
import { SearchFilter } from 'soapbox/reducers/search.ts';
const messages = defineMessages({
heading: { id: 'column.explorer', defaultMessage: 'Explorer' },
accounts: { id: 'search_results.accounts', defaultMessage: 'Accounts' },
statuses: { id: 'search_results.posts', defaultMessage: 'Posts' },
trends: { id: 'search_results.trends', defaultMessage: 'Trends' },
});
const SearchPage = () => {
const intl = useIntl();
const checkFilters = (filters: IFilters[]) => {
return filters.length !== filterInitialState.length ||
!filters.every((filter, index) =>
filter.name === filterInitialState[index].name &&
filter.status === filterInitialState[index].status &&
filter.value === filterInitialState[index].value,
);
};
const PostsTab = () => {
const path = useLocation().pathname;
const inPosts = path === '/explorer';
const filters = useAppSelector((state) => state.search_filter);
const [withFilter, setWithFilter] = useState(checkFilters(filters));
useEffect(() => {
setWithFilter(checkFilters(filters));
}, [filters]);
return (
<Column label={intl.formatMessage(messages.heading)} withHeader={false} slim>
<Stack space={4}>
{inPosts && <>
<Stack space={4}>
<ExplorerCards />
<Divider text='Filters' />
@ -26,9 +61,107 @@ const SearchPage = () => {
<Divider />
{!withFilter ? <PublicTimeline /> : <SearchResults /> }
</>
}
</Stack>
);
};
const TrendsTab = () => {
return (
<Stack>
<SearchResults />
</Stack>
);
};
const AccountsTab = () => {
return (
<Stack space={4} className='pt-1'>
<AccountsCarousel />
<Divider />
<Stack space={3}>
<div className='px-4'>
<Search autoSubmit />
</div>
<SearchResults />
</Stack>
</Stack>
);
};
const SearchPage = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const navigate = useNavigate();
const [selectedFilter, setSelectedFilter] = useState('global');
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
const renderFilterBar = () => {
const items = [];
const handleTabs = (path: string, filter?: SearchFilter) => {
if (filter) {
selectFilter(filter);
dispatch(clearSearch());
dispatch(setFilter(filter));
} else {
dispatch(setFilter('statuses'));
}
setSelectedFilter(filter ?? 'global');
navigate(`/explorer${path}`);
};
items.push(
{
text: intl.formatMessage(messages.statuses),
action: () => handleTabs(''),
name: 'global',
icon: globeIcon,
},
{
text: intl.formatMessage(messages.trends),
action: () => handleTabs('/trends', 'statuses'),
name: 'statuses',
icon: trendIcon,
},
{
text: intl.formatMessage(messages.accounts),
action: () => handleTabs('/accounts', 'accounts'),
name: 'accounts',
icon: userIcon,
},
);
return <Tabs items={items} activeItem={selectedFilter} />;
};
return (
<Column label={intl.formatMessage(messages.heading)} withHeader={false} slim>
<Stack space={2}>
<div className='relative px-4'>
{renderFilterBar()}
</div>
<Switch>
<Route exact path={'/'} component={PostsTab} />
<Route exact path={'/explorer'} component={PostsTab} />
<Route path={'/explorer/trends'} component={TrendsTab} />
<Route path={'/explorer/accounts'} component={AccountsTab} />
</Switch>
</Stack>
</Column>
);
};

Wyświetl plik

@ -48,6 +48,7 @@ import Navbar from './components/navbar.tsx';
import {
Status,
RemoteTimeline,
PublicTimeline,
AccountTimeline,
AccountGallery,
HomeTimeline,

Wyświetl plik

@ -1,3 +1,5 @@
import { useLocation } from 'react-router-dom';
import Layout from 'soapbox/components/ui/layout.tsx';
import LinkFooter from 'soapbox/features/ui/components/link-footer.tsx';
import {
@ -5,6 +7,7 @@ import {
TrendsPanel,
SignUpPanel,
CtaBanner,
LatestAccountsPanel,
SuggestedGroupsPanel,
} from 'soapbox/features/ui/util/async-components.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
@ -17,6 +20,7 @@ interface ISearchPage {
const SearchPage: React.FC<ISearchPage> = ({ children }) => {
const me = useAppSelector(state => state.me);
const features = useFeatures();
const accountsPath = useLocation().pathname === '/explorer/accounts';
return (
<>
@ -37,8 +41,9 @@ const SearchPage: React.FC<ISearchPage> = ({ children }) => {
<TrendsPanel limit={5} />
)}
{features.suggestions && (
<WhoToFollowPanel limit={3} />
{features.suggestions && (accountsPath
? <LatestAccountsPanel limit={3} />
: <WhoToFollowPanel limit={3} />
)}
{features.groups && (

Wyświetl plik

@ -7,8 +7,10 @@ import appReducer from './reducers/index.ts';
import type { AnyAction } from 'redux';
const loadState = () => {
const loadState = (pathname: string) => {
try {
if (pathname !== '/explorer') return undefined;
const savedState = localStorage.getItem('soapbox:explorer:filters');
return savedState ? JSON.parse(savedState) : undefined;
} catch (error) {
@ -17,7 +19,10 @@ const loadState = () => {
}
};
const preloadedState = { ...loadState() ? { search_filter: loadState() } : {} };
const preloadedState = (() => {
const storedFilter = loadState(window.location.pathname);
return storedFilter ? { search_filter: storedFilter } : {};
})();
export const store = configureStore({
reducer: appReducer,