Skip to content

Commit

Permalink
Add explore communities page
Browse files Browse the repository at this point in the history
Add filter to commmunity search results page
  • Loading branch information
aeharding committed Jul 11, 2024
1 parent c1ba60e commit 9347201
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 88 deletions.
4 changes: 2 additions & 2 deletions src/features/community/list/ResolvedCommunitiesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -143,7 +143,7 @@ function ResolvedCommunitiesList({
{...attributedPreventOnClickNavigationBug}
>
<Content>
<SubIcon icon={library} color="#009dff" />
<SubIcon icon={earth} color="#009dff" />
<div>
All<aside>Posts across all federated communities</aside>
</div>
Expand Down
88 changes: 88 additions & 0 deletions src/features/feed/ListingType.tsx
Original file line number Diff line number Diff line change
@@ -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<ListingType>[] = 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 (
<>
<IonButton onClick={() => setOpen(true)}>
<IonIcon icon={getListingTypeIcon(listingType)} slot="icon-only" />
</IonButton>
<IonActionSheet
cssClass="left-align-buttons"
isOpen={open}
onDidDismiss={() => setOpen(false)}
onWillDismiss={(
e: IonActionSheetCustomEvent<OverlayEventDetail<ListingType>>,
) => {
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;
}
}
4 changes: 2 additions & 2 deletions src/features/search/EmptySearch.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import RandomCommunity from "./RandomCommunity";
import SpecialSearchMenu from "./SpecialSearchMenu";
import TrendingCommunities from "./TrendingCommunities";

export default function EmptySearch() {
return (
<>
<TrendingCommunities />
<RandomCommunity />
<SpecialSearchMenu />
</>
);
}
Original file line number Diff line number Diff line change
@@ -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,
);
Expand All @@ -21,6 +21,10 @@ export default function RandomCommunity() {
<IonIcon icon={shuffle} color="primary" slot="start" />
<IonLabel className="ion-text-nowrap">Random Community</IonLabel>
</IonItem>
<IonItem routerLink="/search/explore">
<IonIcon icon={planetOutline} color="primary" slot="start" />
<IonLabel className="ion-text-nowrap">Explore</IonLabel>
</IonItem>
</IonList>
);
}
104 changes: 104 additions & 0 deletions src/routes/pages/search/CommunitiesResultsPage.tsx
Original file line number Diff line number Diff line change
@@ -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<ListingType>("All");

const fetchFn: FetchFn<CommunityView> = 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 (
<IonPage>
<AppHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton
text="Search"
defaultHref={buildGeneralBrowseLink("")}
/>
</IonButtons>

<IonTitle>{search ? <>{search}</> : "Communities"}</IonTitle>

<IonButtons slot="end">
<ListingTypeFilter
listingType={listingType}
setListingType={setListingType}
/>
<PostSort sort={sort} setSort={setSort} />
</IonButtons>
</IonToolbar>
</AppHeader>
<IonContent>
<CommunityFeed fetchFn={fetchFn} />
</IonContent>
</IonPage>
);
}

async function findExactCommunity(
name: string,
client: LemmyHttp,
): Promise<CommunityView | undefined> {
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;
}
}
84 changes: 2 additions & 82 deletions src/routes/pages/search/results/SearchCommunitiesPage.tsx
Original file line number Diff line number Diff line change
@@ -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<CommunityView> = 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 (
<IonPage>
<AppHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton
text="Search"
defaultHref={buildGeneralBrowseLink("")}
/>
</IonButtons>

<IonTitle>{search}</IonTitle>

<IonButtons slot="end">
<PostSort sort={sort} setSort={setSort} />
</IonButtons>
</IonToolbar>
</AppHeader>
<IonContent>
<CommunityFeed fetchFn={fetchFn} />
</IonContent>
</IonPage>
);
}

async function findExactCommunity(
name: string,
client: LemmyHttp,
): Promise<CommunityView | undefined> {
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 <CommunitiesResultsPage search={search} />;
}
4 changes: 4 additions & 0 deletions src/routes/tabs/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
<Route exact path="/search">
Expand All @@ -22,4 +23,7 @@ export default [
<Route exact path="/search/communities/:search">
<SearchCommunitiesPage />
</Route>,
<Route exact path="/search/explore">
<CommunitiesResultsPage />
</Route>,
];

0 comments on commit 9347201

Please sign in to comment.