Skip to content

Commit d642f9b

Browse files
authored
fix(piefed): don't fail on unknown sort, hide unsupported features (#2064)
1 parent f1ee555 commit d642f9b

File tree

19 files changed

+195
-145
lines changed

19 files changed

+195
-145
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
"rehype-remark": "^10.0.1",
116116
"remark-parse": "^11.0.0",
117117
"remark-stringify": "^11.0.0",
118-
"threadiverse": "^0.3.4",
118+
"threadiverse": "^0.4.1",
119119
"ua-parser-js": "^2.0.3",
120120
"unified": "^11.0.5",
121121
"unist-util-visit": "^5.0.0",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/features/auth/siteSlice.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createSelector, createSlice, PayloadAction } from "@reduxjs/toolkit";
22
import {
33
GetSiteResponse,
44
ProviderInfo,
5+
ThreadiverseClient,
56
UnsupportedSoftwareError,
67
} from "threadiverse";
78

@@ -192,3 +193,12 @@ function getSiteReqId(instance: string, handle: string | undefined) {
192193

193194
return `${instance}-${handle}`;
194195
}
196+
197+
export const modeSelector = createSelector(
198+
[(state: RootState) => state.site.software],
199+
(software) => {
200+
return software
201+
? ThreadiverseClient.resolveClient(software)?.mode
202+
: undefined;
203+
},
204+
);

src/features/comment/inTree/Comments.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default function Comments({
8888

8989
const sortParams = useFeedSortParams("comments", sort);
9090

91-
const ready = !!sortParams;
91+
const ready = sortParams !== undefined;
9292

9393
const preservePositionFromBottomInScrollView =
9494
usePreservePositionFromBottomInScrollView(

src/features/feed/endItems/EndPost.tsx

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { VgerTopSort } from "#/features/feed/sort/topSorts";
2-
31
import styles from "./EndPost.module.css";
42

53
export interface EndPostProps {
@@ -30,7 +28,7 @@ export default function EndPost({
3028
if (sortDuration)
3129
return (
3230
<div className={styles.container}>
33-
No posts in {feedName} for last {sortDuration}.
31+
No posts in {feedName} for last {sortDuration.toLowerCase()}.
3432
</div>
3533
);
3634

@@ -48,32 +46,3 @@ export default function EndPost({
4846

4947
return renderError();
5048
}
51-
52-
export function getSortDuration(
53-
sort: VgerTopSort | string | undefined,
54-
): string | undefined {
55-
switch (sort as VgerTopSort) {
56-
case "TopDay":
57-
return "day";
58-
case "TopHour":
59-
return "hour";
60-
case "TopMonth":
61-
return "month";
62-
case "TopNineMonths":
63-
return "9 months";
64-
case "TopSixHour":
65-
return "6 hours";
66-
case "TopSixMonths":
67-
return "6 months";
68-
case "TopThreeMonths":
69-
return "3 months";
70-
case "TopTwelveHour":
71-
return "12 hours";
72-
case "TopWeek":
73-
return "week";
74-
case "TopYear":
75-
return "year";
76-
case "TopAll":
77-
return "all time";
78-
}
79-
}

src/features/feed/sort/useFeedSort.tsx

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,20 @@ import { VgerPostSortType, VgerPostSortTypeByMode } from "./PostSort";
3838
import { VgerSearchSortType, VgerSearchSortTypeByMode } from "./SearchSort";
3939
import { isTopSort, topSortToDuration, VgerTopSort } from "./topSorts";
4040

41-
interface VgerSorts {
41+
interface VgerSortsByContext {
4242
posts: VgerPostSortType;
4343
comments: VgerCommentSortType;
4444
search: VgerSearchSortType;
4545
communities: VgerCommunitySortType;
4646
}
4747

48+
interface VgerSortsByContextByMode {
49+
posts: VgerPostSortTypeByMode;
50+
comments: VgerCommentSortTypeByMode;
51+
search: VgerSearchSortTypeByMode;
52+
communities: VgerCommunitySortTypeByMode;
53+
}
54+
4855
interface Sorts {
4956
posts: PostSortType;
5057
comments: CommentSortType;
@@ -57,13 +64,21 @@ export type FeedSortContext = "posts" | "comments" | "search" | "communities";
5764
export default function useFeedSort<Context extends FeedSortContext>(
5865
context: Context,
5966
feed?: AnyFeed | undefined,
60-
overrideSort?: VgerSorts[Context],
67+
overrideSort?:
68+
| VgerSortsByContextByMode[Context]
69+
| VgerSortsByContext[Context],
6170
) {
62-
type Sort = VgerSorts[Context];
71+
type Sort = VgerSortsByContext[Context];
6372

6473
const dispatch = useAppDispatch();
6574
const mode = useMode();
6675

76+
function getOverrideSort(mode: ThreadiverseMode): Sort | null | undefined {
77+
if (typeof overrideSort === "string") return overrideSort;
78+
if (!mode) return undefined;
79+
return (overrideSort?.[mode] as Sort) ?? null;
80+
}
81+
6782
const feedSort = useAppSelector(getFeedSortSelectorBuilder(feed, context)) as
6883
| Sort
6984
| null
@@ -77,11 +92,12 @@ export default function useFeedSort<Context extends FeedSortContext>(
7792
.rememberCommunitySort,
7893
);
7994

80-
const [sort, _setSort] = useState<Sort | undefined>(
81-
!rememberCommunitySort
82-
? (overrideSort ?? defaultSort)
83-
: (feedSort ?? overrideSort),
84-
);
95+
const [sort, _setSort] = useState<Sort | null | undefined>(() => {
96+
if (!mode) return undefined;
97+
if (!rememberCommunitySort) return getOverrideSort(mode) ?? defaultSort;
98+
if (feedSort) return feedSort;
99+
return getOverrideSort(mode);
100+
});
85101

86102
useEffect(() => {
87103
(async () => {
@@ -151,31 +167,46 @@ function findFeedContext(
151167
}
152168
}
153169

170+
/**
171+
* @param context What kind of feed is this?
172+
* @param sort The Voyager sort to convert to threadiverse sort params
173+
* @returns The sort, null if loaded but no result. Null if still loading async.
174+
*/
154175
export function useFeedSortParams<Context extends FeedSortContext>(
155176
context: Context,
156-
sort: VgerSorts[Context] | undefined,
157-
): Sorts[Context] | undefined {
177+
sort: VgerSortsByContext[Context] | null | undefined,
178+
): Sorts[Context] | null | undefined {
158179
const mode = useMode();
159180

160-
if (!sort || !mode) return;
181+
if (!mode) return undefined; // not loaded
182+
if (!sort) return null; // loaded, but not found
161183

162-
return convertSortToLemmyParams(context, sort, mode);
184+
return convertSortToLemmyParams(context, sort, mode) ?? null;
163185
}
164186

165187
function convertSortToLemmyParams<Context extends FeedSortContext>(
166188
context: Context,
167-
sort: VgerSorts[Context],
189+
sort: VgerSortsByContext[Context],
168190
mode: ThreadiverseMode,
169191
) {
170192
switch (context) {
171193
case "posts":
172-
return convertPostSortToParams(sort as VgerSorts["posts"], mode);
194+
return convertPostSortToParams(sort as VgerSortsByContext["posts"], mode);
173195
case "comments":
174-
return convertCommentSortToParams(sort as VgerSorts["comments"], mode);
196+
return convertCommentSortToParams(
197+
sort as VgerSortsByContext["comments"],
198+
mode,
199+
);
175200
case "search":
176-
return convertSearchSortToParams(sort as VgerSearchSortType, mode);
201+
return convertSearchSortToParams(
202+
sort as VgerSortsByContext["search"],
203+
mode,
204+
);
177205
case "communities":
178-
return convertCommunitySortToParams(sort as VgerCommunitySortType, mode);
206+
return convertCommunitySortToParams(
207+
sort as VgerSortsByContext["communities"],
208+
mode,
209+
);
179210
}
180211
}
181212

src/features/instances/instancesSlice.tsx

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createSelector, createSlice, PayloadAction } from "@reduxjs/toolkit";
22
import { FederatedInstances } from "threadiverse";
33

44
import { clientSelector, urlSelector } from "#/features/auth/authSelectors";
5+
import { KNOWN_SOFTWARE } from "#/helpers/threadiverse";
56
import { db } from "#/services/db";
67
import { customBackOff } from "#/services/lemmy";
78
import { AppDispatch, RootState } from "#/store";
@@ -45,32 +46,22 @@ export const {
4546

4647
export default instancesSlice.reducer;
4748

48-
export const knownInstancesSelector = createSelector(
49+
export const knownInstancesSelectorBySoftware = createSelector(
4950
[
5051
(state: RootState) => state.instances.knownInstances,
5152
(state: RootState) => state.auth.connectedInstance,
53+
(state: RootState) => state.site.software,
5254
],
53-
(knownInstances, connectedInstance) => {
54-
if (!knownInstances || knownInstances === "pending")
55-
return [connectedInstance];
56-
57-
return [
58-
connectedInstance,
59-
...knownInstances.linked
60-
.filter((instance) => instance.software === "lemmy")
61-
.map((instance) => instance.domain),
62-
];
63-
},
64-
);
55+
(knownInstances, connectedInstance, software) => {
56+
const initialResult = { [software?.name ?? "lemmy"]: [connectedInstance] };
6557

66-
export const knownPiefedInstancesSelector = createSelector(
67-
[(state: RootState) => state.instances.knownInstances],
68-
(knownInstances) => {
69-
if (!knownInstances || knownInstances === "pending") return [];
58+
if (!knownInstances || knownInstances === "pending") return initialResult;
7059

71-
return knownInstances.linked
72-
.filter((instance) => instance.software === "piefed")
73-
.map((instance) => instance.domain);
60+
return groupKnownInstancesBySoftware(
61+
knownInstances,
62+
KNOWN_SOFTWARE,
63+
initialResult,
64+
);
7465
},
7566
);
7667

@@ -122,3 +113,28 @@ export const getInstances =
122113

123114
dispatch(receivedInstances(federated_instances));
124115
};
116+
117+
export type InstancesBySoftware = Record<string, string[]>;
118+
119+
function groupKnownInstancesBySoftware(
120+
knownInstances: FederatedInstances,
121+
knownSoftware: string[],
122+
initialResult?: InstancesBySoftware,
123+
): InstancesBySoftware {
124+
const result: InstancesBySoftware = {
125+
...Object.fromEntries(knownSoftware.map((software) => [software, []])),
126+
...initialResult,
127+
};
128+
129+
for (const instance of knownInstances.linked) {
130+
if (!instance.software) continue;
131+
132+
const potentialInstanceSoftwareArr = result[instance.software];
133+
134+
if (!potentialInstanceSoftwareArr) continue;
135+
136+
potentialInstanceSoftwareArr.push(instance.domain);
137+
}
138+
139+
return result;
140+
}

src/features/search/SearchOptions.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
buildUserLinkFromHandle,
1717
} from "#/helpers/appLinkBuilder";
1818
import { useBuildGeneralBrowseLink } from "#/helpers/routes";
19+
import { useMode } from "#/helpers/threadiverse";
1920

2021
import AutoResolvePostComment from "./AutoResolvePostComment";
2122

@@ -25,6 +26,7 @@ interface SearchOptionsProps {
2526

2627
export default function SearchOptions({ search }: SearchOptionsProps) {
2728
const buildGeneralBrowseLink = useBuildGeneralBrowseLink();
29+
const mode = useMode();
2830
const { determineObjectTypeFromUrl, redirectToLemmyObjectIfNeeded } =
2931
useLemmyUrlHandler();
3032

@@ -56,12 +58,14 @@ export default function SearchOptions({ search }: SearchOptionsProps) {
5658
<IonIcon icon={albumsOutline} color="primary" slot="start" />
5759
<IonLabel className="ion-text-nowrap">Posts with “{search}</IonLabel>
5860
</IonItem>
59-
<IonItem routerLink={buildSearchCommentsLink(search)}>
60-
<IonIcon icon={chatbubbleOutline} color="primary" slot="start" />
61-
<IonLabel className="ion-text-nowrap">
62-
Comments with “{search}
63-
</IonLabel>
64-
</IonItem>
61+
{mode !== "piefed" && (
62+
<IonItem routerLink={buildSearchCommentsLink(search)}>
63+
<IonIcon icon={chatbubbleOutline} color="primary" slot="start" />
64+
<IonLabel className="ion-text-nowrap">
65+
Comments with “{search}
66+
</IonLabel>
67+
</IonItem>
68+
)}
6569
<IonItem routerLink={buildSearchCommunitiesLink(search)}>
6670
<IonIcon icon={searchOutline} color="primary" slot="start" />
6771
<IonLabel className="ion-text-nowrap">

0 commit comments

Comments
 (0)