kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Update explorer with some of recent comments
rodzic
3955977b1f
commit
f8f1292c39
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
))}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -48,6 +48,7 @@ import Navbar from './components/navbar.tsx';
|
|||
import {
|
||||
Status,
|
||||
RemoteTimeline,
|
||||
PublicTimeline,
|
||||
AccountTimeline,
|
||||
AccountGallery,
|
||||
HomeTimeline,
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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,
|
||||
|
|
Ładowanie…
Reference in New Issue