From 93472015affa47820281852451ed085e2edba88f Mon Sep 17 00:00:00 2001 From: Alexander Harding <2166114+aeharding@users.noreply.github.com> Date: Wed, 10 Jul 2024 22:50:39 -0500 Subject: [PATCH] Add explore communities page Add filter to commmunity search results page --- .../list/ResolvedCommunitiesList.tsx | 4 +- src/features/feed/ListingType.tsx | 88 +++++++++++++++ src/features/search/EmptySearch.tsx | 4 +- ...domCommunity.tsx => SpecialSearchMenu.tsx} | 8 +- .../pages/search/CommunitiesResultsPage.tsx | 104 ++++++++++++++++++ .../search/results/SearchCommunitiesPage.tsx | 84 +------------- src/routes/tabs/search.tsx | 4 + 7 files changed, 208 insertions(+), 88 deletions(-) create mode 100644 src/features/feed/ListingType.tsx rename src/features/search/{RandomCommunity.tsx => SpecialSearchMenu.tsx} (70%) create mode 100644 src/routes/pages/search/CommunitiesResultsPage.tsx diff --git a/src/features/community/list/ResolvedCommunitiesList.tsx b/src/features/community/list/ResolvedCommunitiesList.tsx index f97be69a3c..fb5d9e3c30 100644 --- a/src/features/community/list/ResolvedCommunitiesList.tsx +++ b/src/features/community/list/ResolvedCommunitiesList.tsx @@ -13,7 +13,7 @@ import { memo, useMemo, useRef } from "react"; import { sortBy } from "lodash"; import { getHandle } from "../../../helpers/lemmy"; import { Community } from "lemmy-js-client"; -import { home, library, people, shieldCheckmark } from "ionicons/icons"; +import { earth, home, people, shieldCheckmark } from "ionicons/icons"; import CommunityListItem from "./CommunityListItem"; import { VList, VListHandle } from "virtua"; import { maxWidthCss } from "../../shared/AppContent"; @@ -143,7 +143,7 @@ function ResolvedCommunitiesList({ {...attributedPreventOnClickNavigationBug} > - +
All
diff --git a/src/features/feed/ListingType.tsx b/src/features/feed/ListingType.tsx new file mode 100644 index 0000000000..5f831567ad --- /dev/null +++ b/src/features/feed/ListingType.tsx @@ -0,0 +1,88 @@ +import type { IonActionSheetCustomEvent } from "@ionic/core"; +import { + ActionSheetButton, + IonActionSheet, + IonButton, + IonIcon, +} from "@ionic/react"; +import { OverlayEventDetail } from "@ionic/react/dist/types/components/react-component-lib/interfaces"; +import { + earthOutline, + homeOutline, + peopleOutline, + shieldCheckmarkOutline, +} from "ionicons/icons"; +import { useContext, useState } from "react"; +import { startCase } from "lodash"; +import { ListingType } from "lemmy-js-client"; +import { scrollUpIfNeeded } from "../../helpers/scrollUpIfNeeded"; +import { AppContext } from "../auth/AppContext"; + +export const LISTING_TYPES = [ + "All", + "Local", + "Subscribed", + "ModeratorView", +] as const; + +const BUTTONS: ActionSheetButton[] = LISTING_TYPES.map( + (listingType) => ({ + text: startCase(listingType), + data: listingType, + icon: getListingTypeIcon(listingType), + }), +); + +interface CommentSortProps { + listingType: ListingType | undefined; + setListingType: (listingType: ListingType) => void; +} + +export default function ListingTypeFilter({ + listingType, + setListingType, +}: CommentSortProps) { + const [open, setOpen] = useState(false); + const { activePageRef } = useContext(AppContext); + + if (!listingType) return; + + return ( + <> + setOpen(true)}> + + + setOpen(false)} + onWillDismiss={( + e: IonActionSheetCustomEvent>, + ) => { + if (!e.detail.data) return; + + setListingType(e.detail.data); + scrollUpIfNeeded(activePageRef?.current, 1, "auto"); + }} + header="Filter by..." + buttons={BUTTONS.map((b) => ({ + ...b, + role: listingType === b.data ? "selected" : undefined, + }))} + /> + + ); +} + +export function getListingTypeIcon(listingType: ListingType): string { + switch (listingType) { + case "All": + return earthOutline; + case "Local": + return peopleOutline; + case "Subscribed": + return homeOutline; + case "ModeratorView": + return shieldCheckmarkOutline; + } +} diff --git a/src/features/search/EmptySearch.tsx b/src/features/search/EmptySearch.tsx index 86a231fd59..4ed8dc83d8 100644 --- a/src/features/search/EmptySearch.tsx +++ b/src/features/search/EmptySearch.tsx @@ -1,11 +1,11 @@ -import RandomCommunity from "./RandomCommunity"; +import SpecialSearchMenu from "./SpecialSearchMenu"; import TrendingCommunities from "./TrendingCommunities"; export default function EmptySearch() { return ( <> - + ); } diff --git a/src/features/search/RandomCommunity.tsx b/src/features/search/SpecialSearchMenu.tsx similarity index 70% rename from src/features/search/RandomCommunity.tsx rename to src/features/search/SpecialSearchMenu.tsx index d749d88d18..37119fb188 100644 --- a/src/features/search/RandomCommunity.tsx +++ b/src/features/search/SpecialSearchMenu.tsx @@ -1,8 +1,8 @@ import { IonIcon, IonItem, IonLabel, IonList } from "@ionic/react"; -import { shuffle } from "ionicons/icons"; +import { planetOutline, shuffle } from "ionicons/icons"; import { useAppSelector } from "../../store"; -export default function RandomCommunity() { +export default function SpecialSearchMenu() { const trendingCommunities = useAppSelector( (state) => state.community.trendingCommunities, ); @@ -21,6 +21,10 @@ export default function RandomCommunity() { Random Community + + + Explore + ); } diff --git a/src/routes/pages/search/CommunitiesResultsPage.tsx b/src/routes/pages/search/CommunitiesResultsPage.tsx new file mode 100644 index 0000000000..2b98a79114 --- /dev/null +++ b/src/routes/pages/search/CommunitiesResultsPage.tsx @@ -0,0 +1,104 @@ +import { + IonBackButton, + IonButtons, + IonContent, + IonPage, + IonTitle, + IonToolbar, +} from "@ionic/react"; +import { useBuildGeneralBrowseLink } from "../../../helpers/routes"; +import { useCallback, useState } from "react"; +import { FetchFn, isFirstPage } from "../../../features/feed/Feed"; +import useClient from "../../../helpers/useClient"; +import { LIMIT } from "../../../services/lemmy"; +import PostSort from "../../../features/feed/PostSort"; +import { CommunityView, LemmyHttp, ListingType } from "lemmy-js-client"; +import CommunityFeed from "../../../features/feed/CommunityFeed"; +import { isLemmyError } from "../../../helpers/lemmyErrors"; +import useFeedSort from "../../../features/feed/sort/useFeedSort"; +import { compact } from "lodash"; +import AppHeader from "../../../features/shared/AppHeader"; +import ListingTypeFilter from "../../../features/feed/ListingType"; + +interface CommunitiesResultsPageProps { + search?: string; +} + +export default function CommunitiesResultsPage({ + search, +}: CommunitiesResultsPageProps) { + const buildGeneralBrowseLink = useBuildGeneralBrowseLink(); + const client = useClient(); + const [sort, setSort] = useFeedSort("posts"); + const [listingType, setListingType] = useState("All"); + + const fetchFn: FetchFn = useCallback( + async (pageData) => { + if (isFirstPage(pageData) && search?.includes("@")) { + return compact([await findExactCommunity(search, client)]); + } + + const response = await (search + ? client.search({ + limit: LIMIT, + q: search, + type_: "Communities", + listing_type: listingType, + ...pageData, + sort, + }) + : client.listCommunities({ + limit: LIMIT, + type_: listingType, + ...pageData, + sort, + })); + + return response.communities; + }, + [client, search, sort, listingType], + ); + + return ( + + + + + + + + {search ? <>“{search}” : "Communities"} + + + + + + + + + + + + ); +} + +async function findExactCommunity( + name: string, + client: LemmyHttp, +): Promise { + const sanitizedName = name.startsWith("!") ? name.slice(1) : name; + + try { + return (await client.getCommunity({ name: sanitizedName })).community_view; + } catch (error) { + if (isLemmyError(error, "couldnt_find_community")) return; + + throw error; + } +} diff --git a/src/routes/pages/search/results/SearchCommunitiesPage.tsx b/src/routes/pages/search/results/SearchCommunitiesPage.tsx index 6c17376974..8ddfdff014 100644 --- a/src/routes/pages/search/results/SearchCommunitiesPage.tsx +++ b/src/routes/pages/search/results/SearchCommunitiesPage.tsx @@ -1,89 +1,9 @@ -import { - IonBackButton, - IonButtons, - IonContent, - IonPage, - IonTitle, - IonToolbar, -} from "@ionic/react"; -import { useBuildGeneralBrowseLink } from "../../../../helpers/routes"; -import { useCallback } from "react"; -import { FetchFn, isFirstPage } from "../../../../features/feed/Feed"; -import useClient from "../../../../helpers/useClient"; -import { LIMIT } from "../../../../services/lemmy"; import { useParams } from "react-router"; -import PostSort from "../../../../features/feed/PostSort"; -import { CommunityView, LemmyHttp } from "lemmy-js-client"; -import CommunityFeed from "../../../../features/feed/CommunityFeed"; -import { isLemmyError } from "../../../../helpers/lemmyErrors"; -import useFeedSort from "../../../../features/feed/sort/useFeedSort"; -import { compact } from "lodash"; -import AppHeader from "../../../../features/shared/AppHeader"; +import CommunitiesResultsPage from "../CommunitiesResultsPage"; export default function SearchCommunitiesPage() { const { search: _encodedSearch } = useParams<{ search: string }>(); - const buildGeneralBrowseLink = useBuildGeneralBrowseLink(); - const client = useClient(); - const [sort, setSort] = useFeedSort("posts"); - const search = decodeURIComponent(_encodedSearch); - const fetchFn: FetchFn = useCallback( - async (pageData) => { - if (isFirstPage(pageData) && search.includes("@")) { - return compact([await findExactCommunity(search, client)]); - } - - const response = await client.search({ - limit: LIMIT, - q: search, - type_: "Communities", - listing_type: "All", - ...pageData, - sort, - }); - - return response.communities; - }, - [client, search, sort], - ); - - return ( - - - - - - - - “{search}” - - - - - - - - - - - ); -} - -async function findExactCommunity( - name: string, - client: LemmyHttp, -): Promise { - const sanitizedName = name.startsWith("!") ? name.slice(1) : name; - - try { - return (await client.getCommunity({ name: sanitizedName })).community_view; - } catch (error) { - if (isLemmyError(error, "couldnt_find_community")) return; - - throw error; - } + return ; } diff --git a/src/routes/tabs/search.tsx b/src/routes/tabs/search.tsx index 64e21aefdd..25b77c3e80 100644 --- a/src/routes/tabs/search.tsx +++ b/src/routes/tabs/search.tsx @@ -5,6 +5,7 @@ import SearchPage from "../pages/search/SearchPage"; import SearchPostsResultsPage from "../pages/search/results/SearchFeedResultsPage"; import SearchCommunitiesPage from "../pages/search/results/SearchCommunitiesPage"; import RandomCommunityPage from "../pages/search/RandomCommunityPage"; +import CommunitiesResultsPage from "../pages/search/CommunitiesResultsPage"; export default [ @@ -22,4 +23,7 @@ export default [ , + + + , ];