mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 12:21:13 +00:00
Rework query parsing (#2396)
* Pass parsed query params as props to components * Pass parsed query params to fetchInitialData * Pass router Match to fetchInitialData * Cast individual routes to their concrete types Adds an IRoutePropsWithFetch definition for routes with getQueryParams or fetchInitialData to cause compiler errors when the types no longer match. * Don't double decode query parameters. Problem: A search for "%ab" produces a url with "%25ab". Refreshing the page results in URLSearchParams turning "%25ab" back into "%ab". decodeURIComponent() then complains about "%ab" being malformed. This removes decodeURIComponent() calls for query parameters and composes all query strings with getQueryString(), which now uses URLSearchParams. Query parsing already goes through getQueryParams() which also uses URLSearchParams. * Fix for PictrsImage when src also has query params * Small getQueryParams cleanup
This commit is contained in:
parent
579aea40d0
commit
70e382b3d9
32 changed files with 695 additions and 368 deletions
|
@ -3,6 +3,7 @@ import { getHttpBaseInternal } from "@utils/env";
|
|||
import { ErrorPageData } from "@utils/types";
|
||||
import type { Request, Response } from "express";
|
||||
import { StaticRouter, matchPath } from "inferno-router";
|
||||
import { Match } from "inferno-router/dist/Route";
|
||||
import { renderToString } from "inferno-server";
|
||||
import { GetSiteResponse, LemmyHttp } from "lemmy-js-client";
|
||||
import { App } from "../../shared/components/app/app";
|
||||
|
@ -25,6 +26,8 @@ import {
|
|||
LanguageService,
|
||||
UserService,
|
||||
} from "../../shared/services/";
|
||||
import { parsePath } from "history";
|
||||
import { getQueryString } from "@utils/helpers";
|
||||
|
||||
export default async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
@ -40,7 +43,10 @@ export default async (req: Request, res: Response) => {
|
|||
.sort((a, b) => b.q - a.q)
|
||||
.map(x => (x.lang === "*" ? "en" : x.lang)) ?? [];
|
||||
|
||||
const activeRoute = routes.find(route => matchPath(req.path, route));
|
||||
let match: Match<any> | null | undefined;
|
||||
const activeRoute = routes.find(
|
||||
route => (match = matchPath(req.path, route)),
|
||||
);
|
||||
|
||||
const headers = setForwardedHeaders(req.headers);
|
||||
const auth = getJwtCookie(req.headers);
|
||||
|
@ -49,7 +55,7 @@ export default async (req: Request, res: Response) => {
|
|||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
|
||||
const { path, url, query } = req;
|
||||
const { path, url } = req;
|
||||
|
||||
// Get site data first
|
||||
// This bypasses errors, so that the client can hit the error on its own,
|
||||
|
@ -71,7 +77,7 @@ export default async (req: Request, res: Response) => {
|
|||
}
|
||||
|
||||
if (!auth && isAuthPath(path)) {
|
||||
return res.redirect(`/login?prev=${encodeURIComponent(url)}`);
|
||||
return res.redirect(`/login${getQueryString({ prev: url })}`);
|
||||
}
|
||||
|
||||
if (try_site.state === "success") {
|
||||
|
@ -83,10 +89,12 @@ export default async (req: Request, res: Response) => {
|
|||
return res.redirect("/setup");
|
||||
}
|
||||
|
||||
if (site && activeRoute?.fetchInitialData) {
|
||||
const initialFetchReq: InitialFetchRequest = {
|
||||
if (site && activeRoute?.fetchInitialData && match) {
|
||||
const { search } = parsePath(url);
|
||||
const initialFetchReq: InitialFetchRequest<Record<string, any>> = {
|
||||
path,
|
||||
query,
|
||||
query: activeRoute.getQueryParams?.(search, site) ?? {},
|
||||
match,
|
||||
site,
|
||||
headers,
|
||||
};
|
||||
|
|
|
@ -49,7 +49,12 @@ export class App extends Component<any, any> {
|
|||
<div className="mt-4 p-0 fl-1">
|
||||
<Switch>
|
||||
{routes.map(
|
||||
({ path, component: RouteComponent, fetchInitialData }) => (
|
||||
({
|
||||
path,
|
||||
component: RouteComponent,
|
||||
fetchInitialData,
|
||||
getQueryParams,
|
||||
}) => (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
|
@ -59,20 +64,34 @@ export class App extends Component<any, any> {
|
|||
FirstLoadService.falsify();
|
||||
}
|
||||
|
||||
let queryProps = routeProps;
|
||||
if (getQueryParams && this.isoData.site_res) {
|
||||
// ErrorGuard will not render its children when
|
||||
// site_res is missing, this guarantees that props
|
||||
// will always contain the query params.
|
||||
queryProps = {
|
||||
...routeProps,
|
||||
...getQueryParams(
|
||||
routeProps.location.search,
|
||||
this.isoData.site_res,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorGuard>
|
||||
<div tabIndex={-1}>
|
||||
{RouteComponent &&
|
||||
(isAuthPath(path ?? "") ? (
|
||||
<AuthGuard {...routeProps}>
|
||||
<RouteComponent {...routeProps} />
|
||||
<RouteComponent {...queryProps} />
|
||||
</AuthGuard>
|
||||
) : isAnonymousPath(path ?? "") ? (
|
||||
<AnonymousGuard>
|
||||
<RouteComponent {...routeProps} />
|
||||
<RouteComponent {...queryProps} />
|
||||
</AnonymousGuard>
|
||||
) : (
|
||||
<RouteComponent {...routeProps} />
|
||||
<RouteComponent {...queryProps} />
|
||||
))}
|
||||
</div>
|
||||
</ErrorGuard>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component } from "inferno";
|
|||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { UserService } from "../../services";
|
||||
import { Spinner } from "./icon";
|
||||
import { getQueryString } from "@utils/helpers";
|
||||
|
||||
interface AuthGuardState {
|
||||
hasRedirected: boolean;
|
||||
|
@ -26,7 +27,7 @@ class AuthGuard extends Component<
|
|||
if (!UserService.Instance.myUserInfo) {
|
||||
const { pathname, search } = this.props.location;
|
||||
this.context.router.history.replace(
|
||||
`/login?prev=${encodeURIComponent(pathname + search)}`,
|
||||
`/login${getQueryString({ prev: pathname + search })}`,
|
||||
);
|
||||
} else {
|
||||
this.setState({ hasRedirected: true });
|
||||
|
|
|
@ -68,28 +68,31 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
|
|||
// sample url:
|
||||
// http://localhost:8535/pictrs/image/file.png?thumbnail=256&format=jpg
|
||||
|
||||
const split = this.props.src.split("/pictrs/image/");
|
||||
|
||||
// If theres not multiple, then its not a pictrs image
|
||||
if (split.length === 1) {
|
||||
let url: URL | undefined;
|
||||
try {
|
||||
url = new URL(this.props.src);
|
||||
} catch {
|
||||
return this.props.src;
|
||||
}
|
||||
|
||||
const host = split[0];
|
||||
const path = split[1];
|
||||
|
||||
const params = { format };
|
||||
|
||||
if (this.props.thumbnail) {
|
||||
params["thumbnail"] = thumbnailSize;
|
||||
} else if (this.props.icon) {
|
||||
params["thumbnail"] = iconThumbnailSize;
|
||||
// If theres no match, then its not a pictrs image
|
||||
if (!url.pathname.includes("/pictrs/image/")) {
|
||||
return this.props.src;
|
||||
}
|
||||
|
||||
const paramsStr = new URLSearchParams(params).toString();
|
||||
const out = `${host}/pictrs/image/${path}?${paramsStr}`;
|
||||
// Keeps original search params. Could probably do `url.search = ""` here.
|
||||
|
||||
return out;
|
||||
url.searchParams.set("format", format);
|
||||
|
||||
if (this.props.thumbnail) {
|
||||
url.searchParams.set("thumbnail", thumbnailSize.toString());
|
||||
} else if (this.props.icon) {
|
||||
url.searchParams.set("thumbnail", iconThumbnailSize.toString());
|
||||
} else {
|
||||
url.searchParams.delete("thumbnail");
|
||||
}
|
||||
|
||||
return url.href;
|
||||
}
|
||||
|
||||
alt(): string {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { validInstanceTLD } from "@utils/helpers";
|
||||
import { getQueryString, validInstanceTLD } from "@utils/helpers";
|
||||
import classNames from "classnames";
|
||||
import { NoOptionI18nKeys } from "i18next";
|
||||
import { Component, MouseEventHandler, linkEvent } from "inferno";
|
||||
|
@ -134,8 +134,8 @@ function submitRemoteFollow(
|
|||
instanceText = `http${VERSION !== "dev" ? "s" : ""}://${instanceText}`;
|
||||
}
|
||||
|
||||
window.location.href = `${instanceText}/activitypub/externalInteraction?uri=${encodeURIComponent(
|
||||
communityActorId,
|
||||
window.location.href = `${instanceText}/activitypub/externalInteraction${getQueryString(
|
||||
{ uri: communityActorId },
|
||||
)}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ import { CommunityLink } from "./community-link";
|
|||
import { communityLimit } from "../../config";
|
||||
import { SubscribeButton } from "../common/subscribe-button";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
type CommunitiesData = RouteDataResponse<{
|
||||
listCommunitiesResponse: ListCommunitiesResponse;
|
||||
|
@ -62,15 +64,30 @@ function getSortTypeFromQuery(type?: string): SortType {
|
|||
return type ? (type as SortType) : "TopMonth";
|
||||
}
|
||||
|
||||
function getCommunitiesQueryParams() {
|
||||
return getQueryParams<CommunitiesProps>({
|
||||
listingType: getListingTypeFromQuery,
|
||||
sort: getSortTypeFromQuery,
|
||||
page: getPageFromString,
|
||||
});
|
||||
export function getCommunitiesQueryParams(source?: string): CommunitiesProps {
|
||||
return getQueryParams<CommunitiesProps>(
|
||||
{
|
||||
listingType: getListingTypeFromQuery,
|
||||
sort: getSortTypeFromQuery,
|
||||
page: getPageFromString,
|
||||
},
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
export class Communities extends Component<any, CommunitiesState> {
|
||||
type CommunitiesPathProps = Record<string, never>;
|
||||
type CommunitiesRouteProps = RouteComponentProps<CommunitiesPathProps> &
|
||||
CommunitiesProps;
|
||||
export type CommunitiesFetchConfig = IRoutePropsWithFetch<
|
||||
CommunitiesData,
|
||||
CommunitiesPathProps,
|
||||
CommunitiesProps
|
||||
>;
|
||||
|
||||
export class Communities extends Component<
|
||||
CommunitiesRouteProps,
|
||||
CommunitiesState
|
||||
> {
|
||||
private isoData = setIsoData<CommunitiesData>(this.context);
|
||||
state: CommunitiesState = {
|
||||
listCommunitiesResponse: EMPTY_REQUEST,
|
||||
|
@ -79,7 +96,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
isIsomorphic: false,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
constructor(props: CommunitiesRouteProps, context: any) {
|
||||
super(props, context);
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
|
@ -118,7 +135,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
</h5>
|
||||
);
|
||||
case "success": {
|
||||
const { listingType, sort, page } = getCommunitiesQueryParams();
|
||||
const { listingType, sort, page } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<h1 className="h4 mb-4">
|
||||
|
@ -268,7 +285,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
listingType: urlListingType,
|
||||
sort: urlSort,
|
||||
page: urlPage,
|
||||
} = getCommunitiesQueryParams();
|
||||
} = this.props;
|
||||
|
||||
const queryParams: QueryParams<CommunitiesProps> = {
|
||||
listingType: listingType ?? urlListingType,
|
||||
|
@ -302,10 +319,10 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
|
||||
handleSearchSubmit(i: Communities, event: any) {
|
||||
event.preventDefault();
|
||||
const searchParamEncoded = encodeURIComponent(i.state.searchText);
|
||||
const { listingType } = getCommunitiesQueryParams();
|
||||
const searchParamEncoded = i.state.searchText;
|
||||
const { listingType } = i.props;
|
||||
i.context.router.history.push(
|
||||
`/search?q=${searchParamEncoded}&type=Communities&listingType=${listingType}`,
|
||||
`/search${getQueryString({ q: searchParamEncoded, type: "Communities", listingType })}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -313,16 +330,17 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
headers,
|
||||
query: { listingType, sort, page },
|
||||
}: InitialFetchRequest<
|
||||
QueryParams<CommunitiesProps>
|
||||
CommunitiesPathProps,
|
||||
CommunitiesProps
|
||||
>): Promise<CommunitiesData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
const listCommunitiesForm: ListCommunities = {
|
||||
type_: getListingTypeFromQuery(listingType),
|
||||
sort: getSortTypeFromQuery(sort),
|
||||
type_: listingType,
|
||||
sort,
|
||||
limit: communityLimit,
|
||||
page: getPageFromString(page),
|
||||
page,
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -346,7 +364,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
|||
async refetch() {
|
||||
this.setState({ listCommunitiesResponse: LOADING_REQUEST });
|
||||
|
||||
const { listingType, sort, page } = getCommunitiesQueryParams();
|
||||
const { listingType, sort, page } = this.props;
|
||||
|
||||
this.setState({
|
||||
listCommunitiesResponse: await HttpService.client.listCommunities({
|
||||
|
|
|
@ -101,6 +101,7 @@ import { CommunityLink } from "./community-link";
|
|||
import { PaginatorCursor } from "../common/paginator-cursor";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { Sidebar } from "./sidebar";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
type CommunityData = RouteDataResponse<{
|
||||
communityRes: GetCommunityResponse;
|
||||
|
@ -124,12 +125,26 @@ interface CommunityProps {
|
|||
pageCursor?: PaginationCursor;
|
||||
}
|
||||
|
||||
function getCommunityQueryParams() {
|
||||
return getQueryParams<CommunityProps>({
|
||||
dataType: getDataTypeFromQuery,
|
||||
pageCursor: cursor => cursor,
|
||||
sort: getSortTypeFromQuery,
|
||||
});
|
||||
type Fallbacks = { sort: SortType };
|
||||
|
||||
export function getCommunityQueryParams(
|
||||
source: string | undefined,
|
||||
siteRes: GetSiteResponse,
|
||||
) {
|
||||
const myUserInfo = siteRes.my_user ?? UserService.Instance.myUserInfo;
|
||||
const local_user = myUserInfo?.local_user_view.local_user;
|
||||
const local_site = siteRes.site_view.local_site;
|
||||
return getQueryParams<CommunityProps, Fallbacks>(
|
||||
{
|
||||
dataType: getDataTypeFromQuery,
|
||||
pageCursor: (cursor?: string) => cursor,
|
||||
sort: getSortTypeFromQuery,
|
||||
},
|
||||
source,
|
||||
{
|
||||
sort: local_user?.default_sort_type ?? local_site.default_sort_type,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function getDataTypeFromQuery(type?: string): DataType {
|
||||
|
@ -144,10 +159,16 @@ function getSortTypeFromQuery(type?: string): SortType {
|
|||
return type ? (type as SortType) : mySortType ?? "Active";
|
||||
}
|
||||
|
||||
export class Community extends Component<
|
||||
RouteComponentProps<{ name: string }>,
|
||||
State
|
||||
> {
|
||||
type CommunityPathProps = { name: string };
|
||||
type CommunityRouteProps = RouteComponentProps<CommunityPathProps> &
|
||||
CommunityProps;
|
||||
export type CommunityFetchConfig = IRoutePropsWithFetch<
|
||||
CommunityData,
|
||||
CommunityPathProps,
|
||||
CommunityProps
|
||||
>;
|
||||
|
||||
export class Community extends Component<CommunityRouteProps, State> {
|
||||
private isoData = setIsoData<CommunityData>(this.context);
|
||||
state: State = {
|
||||
communityRes: EMPTY_REQUEST,
|
||||
|
@ -159,7 +180,7 @@ export class Community extends Component<
|
|||
isIsomorphic: false,
|
||||
};
|
||||
private readonly mainContentRef: RefObject<HTMLElement>;
|
||||
constructor(props: RouteComponentProps<{ name: string }>, context: any) {
|
||||
constructor(props: CommunityRouteProps, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
|
@ -234,25 +255,22 @@ export class Community extends Component<
|
|||
|
||||
static async fetchInitialData({
|
||||
headers,
|
||||
path,
|
||||
query: { dataType: urlDataType, pageCursor, sort: urlSort },
|
||||
}: InitialFetchRequest<QueryParams<CommunityProps>>): Promise<
|
||||
Promise<CommunityData>
|
||||
> {
|
||||
query: { dataType, pageCursor, sort },
|
||||
match: {
|
||||
params: { name: communityName },
|
||||
},
|
||||
}: InitialFetchRequest<
|
||||
CommunityPathProps,
|
||||
CommunityProps
|
||||
>): Promise<CommunityData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
const pathSplit = path.split("/");
|
||||
|
||||
const communityName = pathSplit[2];
|
||||
const communityForm: GetCommunity = {
|
||||
name: communityName,
|
||||
};
|
||||
|
||||
const dataType = getDataTypeFromQuery(urlDataType);
|
||||
|
||||
const sort = getSortTypeFromQuery(urlSort);
|
||||
|
||||
let postsFetch: Promise<RequestState<GetPostsResponse>> =
|
||||
Promise.resolve(EMPTY_REQUEST);
|
||||
let commentsFetch: Promise<RequestState<GetCommentsResponse>> =
|
||||
|
@ -411,7 +429,7 @@ export class Community extends Component<
|
|||
}
|
||||
|
||||
listings(communityRes: GetCommunityResponse) {
|
||||
const { dataType } = getCommunityQueryParams();
|
||||
const { dataType } = this.props;
|
||||
const { site_res } = this.isoData;
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
|
@ -534,7 +552,7 @@ export class Community extends Component<
|
|||
// let communityRss = this.state.communityRes.map(r =>
|
||||
// communityRSSUrl(r.community_view.community.actor_id, this.state.sort)
|
||||
// );
|
||||
const { dataType, sort } = getCommunityQueryParams();
|
||||
const { dataType, sort } = this.props;
|
||||
const communityRss = res
|
||||
? communityRSSUrl(res.community_view.community.actor_id, sort)
|
||||
: undefined;
|
||||
|
@ -592,7 +610,7 @@ export class Community extends Component<
|
|||
}
|
||||
|
||||
async updateUrl({ dataType, pageCursor, sort }: Partial<CommunityProps>) {
|
||||
const { dataType: urlDataType, sort: urlSort } = getCommunityQueryParams();
|
||||
const { dataType: urlDataType, sort: urlSort } = this.props;
|
||||
|
||||
const queryParams: QueryParams<CommunityProps> = {
|
||||
dataType: getDataTypeString(dataType ?? urlDataType),
|
||||
|
@ -608,7 +626,7 @@ export class Community extends Component<
|
|||
}
|
||||
|
||||
async fetchData() {
|
||||
const { dataType, pageCursor, sort } = getCommunityQueryParams();
|
||||
const { dataType, pageCursor, sort } = this.props;
|
||||
const { name } = this.props.match.params;
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { hostname } from "@utils/helpers";
|
||||
import { getQueryString, hostname } from "@utils/helpers";
|
||||
import { amAdmin, amMod, amTopMod } from "@utils/roles";
|
||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
|
@ -287,7 +287,10 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
|||
className={`btn btn-secondary d-block mb-2 w-100 ${
|
||||
cv.community.deleted || cv.community.removed ? "no-click" : ""
|
||||
}`}
|
||||
to={`/create_post?communityId=${cv.community.id}`}
|
||||
to={
|
||||
"/create_post" +
|
||||
getQueryString({ communityId: cv.community.id.toString() })
|
||||
}
|
||||
>
|
||||
{I18NextService.i18n.t("create_a_post")}
|
||||
</Link>
|
||||
|
|
|
@ -34,6 +34,8 @@ import RateLimitForm from "./rate-limit-form";
|
|||
import { SiteForm } from "./site-form";
|
||||
import { TaglineForm } from "./tagline-form";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
type AdminSettingsData = RouteDataResponse<{
|
||||
bannedRes: BannedPersonsResponse;
|
||||
|
@ -52,7 +54,18 @@ interface AdminSettingsState {
|
|||
isIsomorphic: boolean;
|
||||
}
|
||||
|
||||
export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||
type AdminSettingsRouteProps = RouteComponentProps<Record<string, never>> &
|
||||
Record<string, never>;
|
||||
export type AdminSettingsFetchConfig = IRoutePropsWithFetch<
|
||||
AdminSettingsData,
|
||||
Record<string, never>,
|
||||
Record<string, never>
|
||||
>;
|
||||
|
||||
export class AdminSettings extends Component<
|
||||
AdminSettingsRouteProps,
|
||||
AdminSettingsState
|
||||
> {
|
||||
private isoData = setIsoData<AdminSettingsData>(this.context);
|
||||
state: AdminSettingsState = {
|
||||
siteRes: this.isoData.site_res,
|
||||
|
|
|
@ -100,6 +100,8 @@ import { PostListings } from "../post/post-listings";
|
|||
import { SiteSidebar } from "./site-sidebar";
|
||||
import { PaginatorCursor } from "../common/paginator-cursor";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
interface HomeState {
|
||||
postsRes: RequestState<GetPostsResponse>;
|
||||
|
@ -129,23 +131,22 @@ type HomeData = RouteDataResponse<{
|
|||
trendingCommunitiesRes: ListCommunitiesResponse;
|
||||
}>;
|
||||
|
||||
function getRss(listingType: ListingType) {
|
||||
const { sort } = getHomeQueryParams();
|
||||
|
||||
function getRss(listingType: ListingType, sort: SortType) {
|
||||
let rss: string | undefined = undefined;
|
||||
|
||||
const queryString = getQueryString({ sort });
|
||||
switch (listingType) {
|
||||
case "All": {
|
||||
rss = `/feeds/all.xml?sort=${sort}`;
|
||||
rss = "/feeds/all.xml" + queryString;
|
||||
break;
|
||||
}
|
||||
case "Local": {
|
||||
rss = `/feeds/local.xml?sort=${sort}`;
|
||||
rss = "/feeds/local.xml" + queryString;
|
||||
break;
|
||||
}
|
||||
case "Subscribed": {
|
||||
const auth = myAuth();
|
||||
rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined;
|
||||
rss = auth ? `/feeds/front/${auth}.xml${queryString}` : undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -167,31 +168,46 @@ function getDataTypeFromQuery(type?: string): DataType {
|
|||
}
|
||||
|
||||
function getListingTypeFromQuery(
|
||||
type?: string,
|
||||
myUserInfo = UserService.Instance.myUserInfo,
|
||||
): ListingType | undefined {
|
||||
const myListingType =
|
||||
myUserInfo?.local_user_view?.local_user?.default_listing_type;
|
||||
|
||||
return type ? (type as ListingType) : myListingType;
|
||||
type: string | undefined,
|
||||
fallback: ListingType,
|
||||
): ListingType {
|
||||
return type ? (type as ListingType) : fallback;
|
||||
}
|
||||
|
||||
function getSortTypeFromQuery(
|
||||
type?: string,
|
||||
myUserInfo = UserService.Instance.myUserInfo,
|
||||
type: string | undefined,
|
||||
fallback: SortType,
|
||||
): SortType {
|
||||
const mySortType = myUserInfo?.local_user_view?.local_user?.default_sort_type;
|
||||
|
||||
return (type ? (type as SortType) : mySortType) ?? "Active";
|
||||
return type ? (type as SortType) : fallback;
|
||||
}
|
||||
|
||||
function getHomeQueryParams() {
|
||||
return getQueryParams<HomeProps>({
|
||||
sort: getSortTypeFromQuery,
|
||||
listingType: getListingTypeFromQuery,
|
||||
pageCursor: cursor => cursor,
|
||||
dataType: getDataTypeFromQuery,
|
||||
});
|
||||
type Fallbacks = {
|
||||
sort: SortType;
|
||||
listingType: ListingType;
|
||||
};
|
||||
|
||||
export function getHomeQueryParams(
|
||||
source: string | undefined,
|
||||
siteRes: GetSiteResponse,
|
||||
): HomeProps {
|
||||
const myUserInfo = siteRes.my_user ?? UserService.Instance.myUserInfo;
|
||||
const local_user = myUserInfo?.local_user_view.local_user;
|
||||
const local_site = siteRes.site_view.local_site;
|
||||
return getQueryParams<HomeProps, Fallbacks>(
|
||||
{
|
||||
sort: getSortTypeFromQuery,
|
||||
listingType: getListingTypeFromQuery,
|
||||
pageCursor: (cursor?: string) => cursor,
|
||||
dataType: getDataTypeFromQuery,
|
||||
},
|
||||
source,
|
||||
{
|
||||
sort: local_user?.default_sort_type ?? local_site.default_sort_type,
|
||||
listingType:
|
||||
local_user?.default_listing_type ??
|
||||
local_site.default_post_listing_type,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const MobileButton = ({
|
||||
|
@ -224,7 +240,15 @@ const LinkButton = ({
|
|||
</Link>
|
||||
);
|
||||
|
||||
export class Home extends Component<any, HomeState> {
|
||||
type HomePathProps = Record<string, never>;
|
||||
type HomeRouteProps = RouteComponentProps<HomePathProps> & HomeProps;
|
||||
export type HomeFetchConfig = IRoutePropsWithFetch<
|
||||
HomeData,
|
||||
HomePathProps,
|
||||
HomeProps
|
||||
>;
|
||||
|
||||
export class Home extends Component<HomeRouteProps, HomeState> {
|
||||
private isoData = setIsoData<HomeData>(this.context);
|
||||
state: HomeState = {
|
||||
postsRes: EMPTY_REQUEST,
|
||||
|
@ -310,20 +334,13 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
static async fetchInitialData({
|
||||
query: { dataType: urlDataType, listingType, pageCursor, sort: urlSort },
|
||||
site,
|
||||
query: { listingType, dataType, sort, pageCursor },
|
||||
headers,
|
||||
}: InitialFetchRequest<QueryParams<HomeProps>>): Promise<HomeData> {
|
||||
}: InitialFetchRequest<HomePathProps, HomeProps>): Promise<HomeData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
|
||||
const dataType = getDataTypeFromQuery(urlDataType);
|
||||
const type_ =
|
||||
getListingTypeFromQuery(listingType, site.my_user) ??
|
||||
site.site_view.local_site.default_post_listing_type;
|
||||
const sort = getSortTypeFromQuery(urlSort, site.my_user);
|
||||
|
||||
let postsFetch: Promise<RequestState<GetPostsResponse>> =
|
||||
Promise.resolve(EMPTY_REQUEST);
|
||||
let commentsFetch: Promise<RequestState<GetCommentsResponse>> =
|
||||
|
@ -331,7 +348,7 @@ export class Home extends Component<any, HomeState> {
|
|||
|
||||
if (dataType === DataType.Post) {
|
||||
const getPostsForm: GetPosts = {
|
||||
type_,
|
||||
type_: listingType,
|
||||
page_cursor: pageCursor,
|
||||
limit: fetchLimit,
|
||||
sort,
|
||||
|
@ -343,7 +360,7 @@ export class Home extends Component<any, HomeState> {
|
|||
const getCommentsForm: GetComments = {
|
||||
limit: fetchLimit,
|
||||
sort: postToCommentSortType(sort),
|
||||
type_,
|
||||
type_: listingType,
|
||||
saved_only: false,
|
||||
};
|
||||
|
||||
|
@ -635,7 +652,7 @@ export class Home extends Component<any, HomeState> {
|
|||
dataType: urlDataType,
|
||||
listingType: urlListingType,
|
||||
sort: urlSort,
|
||||
} = getHomeQueryParams();
|
||||
} = this.props;
|
||||
|
||||
const queryParams: QueryParams<HomeProps> = {
|
||||
dataType: getDataTypeString(dataType ?? urlDataType),
|
||||
|
@ -679,7 +696,7 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
get listings() {
|
||||
const { dataType } = getHomeQueryParams();
|
||||
const { dataType } = this.props;
|
||||
const siteRes = this.state.siteRes;
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
|
@ -771,7 +788,7 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
get selects() {
|
||||
const { listingType, dataType, sort } = getHomeQueryParams();
|
||||
const { listingType, dataType, sort } = this.props;
|
||||
|
||||
return (
|
||||
<div className="row align-items-center mb-3 g-3">
|
||||
|
@ -799,6 +816,7 @@ export class Home extends Component<any, HomeState> {
|
|||
{getRss(
|
||||
listingType ??
|
||||
this.state.siteRes.site_view.local_site.default_post_listing_type,
|
||||
sort,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -817,7 +835,7 @@ export class Home extends Component<any, HomeState> {
|
|||
}
|
||||
|
||||
async fetchData() {
|
||||
const { dataType, pageCursor, listingType, sort } = getHomeQueryParams();
|
||||
const { dataType, pageCursor, listingType, sort } = this.props;
|
||||
|
||||
if (dataType === DataType.Post) {
|
||||
this.setState({ postsRes: LOADING_REQUEST });
|
||||
|
|
|
@ -22,6 +22,8 @@ import { HtmlTags } from "../common/html-tags";
|
|||
import { Spinner } from "../common/icon";
|
||||
import Tabs from "../common/tabs";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
type InstancesData = RouteDataResponse<{
|
||||
federatedInstancesResponse: GetFederatedInstancesResponse;
|
||||
|
@ -33,7 +35,15 @@ interface InstancesState {
|
|||
isIsomorphic: boolean;
|
||||
}
|
||||
|
||||
export class Instances extends Component<any, InstancesState> {
|
||||
type InstancesRouteProps = RouteComponentProps<Record<string, never>> &
|
||||
Record<string, never>;
|
||||
export type InstancesFetchConfig = IRoutePropsWithFetch<
|
||||
InstancesData,
|
||||
Record<string, never>,
|
||||
Record<string, never>
|
||||
>;
|
||||
|
||||
export class Instances extends Component<InstancesRouteProps, InstancesState> {
|
||||
private isoData = setIsoData<InstancesData>(this.context);
|
||||
state: InstancesState = {
|
||||
instancesRes: EMPTY_REQUEST,
|
||||
|
|
|
@ -17,17 +17,21 @@ import { Spinner } from "../common/icon";
|
|||
import PasswordInput from "../common/password-input";
|
||||
import TotpModal from "../common/totp-modal";
|
||||
import { UnreadCounterService } from "../../services";
|
||||
import { RouteData } from "../../interfaces";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
interface LoginProps {
|
||||
prev?: string;
|
||||
}
|
||||
|
||||
const getLoginQueryParams = () =>
|
||||
getQueryParams<LoginProps>({
|
||||
prev(param) {
|
||||
return param ? decodeURIComponent(param) : undefined;
|
||||
export function getLoginQueryParams(source?: string): LoginProps {
|
||||
return getQueryParams<LoginProps>(
|
||||
{
|
||||
prev: (param?: string) => param,
|
||||
},
|
||||
});
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
interface State {
|
||||
loginRes: RequestState<LoginResponse>;
|
||||
|
@ -50,7 +54,7 @@ async function handleLoginSuccess(i: Login, loginRes: LoginResponse) {
|
|||
refreshTheme();
|
||||
}
|
||||
|
||||
const { prev } = getLoginQueryParams();
|
||||
const { prev } = i.props;
|
||||
|
||||
prev
|
||||
? i.props.history.replace(prev)
|
||||
|
@ -114,10 +118,14 @@ function handleClose2faModal(i: Login) {
|
|||
i.setState({ show2faModal: false });
|
||||
}
|
||||
|
||||
export class Login extends Component<
|
||||
RouteComponentProps<Record<string, never>>,
|
||||
State
|
||||
> {
|
||||
type LoginRouteProps = RouteComponentProps<Record<string, never>> & LoginProps;
|
||||
export type LoginFetchConfig = IRoutePropsWithFetch<
|
||||
RouteData,
|
||||
Record<string, never>,
|
||||
LoginProps
|
||||
>;
|
||||
|
||||
export class Login extends Component<LoginRouteProps, State> {
|
||||
private isoData = setIsoData(this.context);
|
||||
|
||||
state: State = {
|
||||
|
|
|
@ -63,6 +63,7 @@ import { SearchableSelect } from "./common/searchable-select";
|
|||
import { CommunityLink } from "./community/community-link";
|
||||
import { PersonListing } from "./person/person-listing";
|
||||
import { getHttpBaseInternal } from "../utils/env";
|
||||
import { IRoutePropsWithFetch } from "../routes";
|
||||
|
||||
type FilterType = "mod" | "user";
|
||||
|
||||
|
@ -97,13 +98,17 @@ interface ModlogType {
|
|||
when_: string;
|
||||
}
|
||||
|
||||
const getModlogQueryParams = () =>
|
||||
getQueryParams<ModlogProps>({
|
||||
actionType: getActionFromString,
|
||||
modId: getIdFromString,
|
||||
userId: getIdFromString,
|
||||
page: getPageFromString,
|
||||
});
|
||||
export function getModlogQueryParams(source?: string): ModlogProps {
|
||||
return getQueryParams<ModlogProps>(
|
||||
{
|
||||
actionType: getActionFromString,
|
||||
modId: getIdFromString,
|
||||
userId: getIdFromString,
|
||||
page: getPageFromString,
|
||||
},
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
interface ModlogState {
|
||||
res: RequestState<GetModlogResponse>;
|
||||
|
@ -117,8 +122,8 @@ interface ModlogState {
|
|||
|
||||
interface ModlogProps {
|
||||
page: number;
|
||||
userId?: number | null;
|
||||
modId?: number | null;
|
||||
userId?: number;
|
||||
modId?: number;
|
||||
actionType: ModlogActionType;
|
||||
}
|
||||
|
||||
|
@ -632,10 +637,15 @@ async function createNewOptions({
|
|||
}
|
||||
}
|
||||
|
||||
export class Modlog extends Component<
|
||||
RouteComponentProps<{ communityId?: string }>,
|
||||
ModlogState
|
||||
> {
|
||||
type ModlogPathProps = { communityId?: string };
|
||||
type ModlogRouteProps = RouteComponentProps<ModlogPathProps> & ModlogProps;
|
||||
export type ModlogFetchConfig = IRoutePropsWithFetch<
|
||||
ModlogData,
|
||||
ModlogPathProps,
|
||||
ModlogProps
|
||||
>;
|
||||
|
||||
export class Modlog extends Component<ModlogRouteProps, ModlogState> {
|
||||
private isoData = setIsoData<ModlogData>(this.context);
|
||||
|
||||
state: ModlogState = {
|
||||
|
@ -648,10 +658,7 @@ export class Modlog extends Component<
|
|||
isIsomorphic: false,
|
||||
};
|
||||
|
||||
constructor(
|
||||
props: RouteComponentProps<{ communityId?: string }>,
|
||||
context: any,
|
||||
) {
|
||||
constructor(props: ModlogRouteProps, context: any) {
|
||||
super(props, context);
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
this.handleUserChange = this.handleUserChange.bind(this);
|
||||
|
@ -687,7 +694,7 @@ export class Modlog extends Component<
|
|||
|
||||
async componentDidMount() {
|
||||
if (!this.state.isIsomorphic) {
|
||||
const { modId, userId } = getModlogQueryParams();
|
||||
const { modId, userId } = this.props;
|
||||
const promises = [this.refetch()];
|
||||
|
||||
if (userId) {
|
||||
|
@ -774,7 +781,7 @@ export class Modlog extends Component<
|
|||
userSearchOptions,
|
||||
modSearchOptions,
|
||||
} = this.state;
|
||||
const { actionType, modId, userId } = getModlogQueryParams();
|
||||
const { actionType, modId, userId } = this.props;
|
||||
|
||||
return (
|
||||
<div className="modlog container-lg">
|
||||
|
@ -873,7 +880,7 @@ export class Modlog extends Component<
|
|||
</h5>
|
||||
);
|
||||
case "success": {
|
||||
const page = getModlogQueryParams().page;
|
||||
const page = this.props.page;
|
||||
return (
|
||||
<div className="table-responsive">
|
||||
<table id="modlog_table" className="table table-sm table-hover">
|
||||
|
@ -909,15 +916,15 @@ export class Modlog extends Component<
|
|||
}
|
||||
|
||||
handleUserChange(option: Choice) {
|
||||
this.updateUrl({ userId: getIdFromString(option.value) ?? null, page: 1 });
|
||||
this.updateUrl({ userId: getIdFromString(option.value), page: 1 });
|
||||
}
|
||||
|
||||
handleModChange(option: Choice) {
|
||||
this.updateUrl({ modId: getIdFromString(option.value) ?? null, page: 1 });
|
||||
this.updateUrl({ modId: getIdFromString(option.value), page: 1 });
|
||||
}
|
||||
|
||||
handleSearchUsers = debounce(async (text: string) => {
|
||||
const { userId } = getModlogQueryParams();
|
||||
const { userId } = this.props;
|
||||
const { userSearchOptions } = this.state;
|
||||
this.setState({ loadingUserSearch: true });
|
||||
|
||||
|
@ -934,7 +941,7 @@ export class Modlog extends Component<
|
|||
});
|
||||
|
||||
handleSearchMods = debounce(async (text: string) => {
|
||||
const { modId } = getModlogQueryParams();
|
||||
const { modId } = this.props;
|
||||
const { modSearchOptions } = this.state;
|
||||
this.setState({ loadingModSearch: true });
|
||||
|
||||
|
@ -956,7 +963,7 @@ export class Modlog extends Component<
|
|||
actionType: urlActionType,
|
||||
modId: urlModId,
|
||||
userId: urlUserId,
|
||||
} = getModlogQueryParams();
|
||||
} = this.props;
|
||||
|
||||
const queryParams: QueryParams<ModlogProps> = {
|
||||
page: (page ?? urlPage).toString(),
|
||||
|
@ -977,7 +984,7 @@ export class Modlog extends Component<
|
|||
}
|
||||
|
||||
async refetch() {
|
||||
const { actionType, page, modId, userId } = getModlogQueryParams();
|
||||
const { actionType, page, modId, userId } = this.props;
|
||||
const { communityId: urlCommunityId } = this.props.match.params;
|
||||
const communityId = getIdFromString(urlCommunityId);
|
||||
|
||||
|
@ -988,10 +995,10 @@ export class Modlog extends Component<
|
|||
page,
|
||||
limit: fetchLimit,
|
||||
type_: actionType,
|
||||
other_person_id: userId ?? undefined,
|
||||
other_person_id: userId,
|
||||
mod_person_id: !this.isoData.site_res.site_view.local_site
|
||||
.hide_modlog_mod_names
|
||||
? modId ?? undefined
|
||||
? modId
|
||||
: undefined,
|
||||
}),
|
||||
});
|
||||
|
@ -1008,25 +1015,25 @@ export class Modlog extends Component<
|
|||
|
||||
static async fetchInitialData({
|
||||
headers,
|
||||
path,
|
||||
query: { modId: urlModId, page, userId: urlUserId, actionType },
|
||||
query: { page, userId, modId: modId_, actionType },
|
||||
match: {
|
||||
params: { communityId: urlCommunityId },
|
||||
},
|
||||
site,
|
||||
}: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
|
||||
}: InitialFetchRequest<ModlogPathProps, ModlogProps>): Promise<ModlogData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
const pathSplit = path.split("/");
|
||||
const communityId = getIdFromString(pathSplit[2]);
|
||||
const communityId = getIdFromString(urlCommunityId);
|
||||
const modId = !site.site_view.local_site.hide_modlog_mod_names
|
||||
? getIdFromString(urlModId)
|
||||
? modId_
|
||||
: undefined;
|
||||
const userId = getIdFromString(urlUserId);
|
||||
|
||||
const modlogForm: GetModlog = {
|
||||
page: getPageFromString(page),
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
community_id: communityId,
|
||||
type_: getActionFromString(actionType),
|
||||
type_: actionType,
|
||||
mod_person_id: modId,
|
||||
other_person_id: userId,
|
||||
};
|
||||
|
|
|
@ -80,6 +80,8 @@ import { Icon, Spinner } from "../common/icon";
|
|||
import { Paginator } from "../common/paginator";
|
||||
import { PrivateMessage } from "../private_message/private-message";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
enum UnreadOrAll {
|
||||
Unread,
|
||||
|
@ -126,7 +128,15 @@ interface InboxState {
|
|||
isIsomorphic: boolean;
|
||||
}
|
||||
|
||||
export class Inbox extends Component<any, InboxState> {
|
||||
type InboxRouteProps = RouteComponentProps<Record<string, never>> &
|
||||
Record<string, never>;
|
||||
export type InboxFetchConfig = IRoutePropsWithFetch<
|
||||
InboxData,
|
||||
Record<string, never>,
|
||||
Record<string, never>
|
||||
>;
|
||||
|
||||
export class Inbox extends Component<InboxRouteProps, InboxState> {
|
||||
private isoData = setIsoData<InboxData>(this.context);
|
||||
state: InboxState = {
|
||||
unreadOrAll: UnreadOrAll.Unread,
|
||||
|
|
|
@ -94,6 +94,7 @@ import { CommunityLink } from "../community/community-link";
|
|||
import { PersonDetails } from "./person-details";
|
||||
import { PersonListing } from "./person-listing";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
type ProfileData = RouteDataResponse<{
|
||||
personResponse: GetPersonDetailsResponse;
|
||||
|
@ -117,12 +118,15 @@ interface ProfileProps {
|
|||
page: number;
|
||||
}
|
||||
|
||||
function getProfileQueryParams() {
|
||||
return getQueryParams<ProfileProps>({
|
||||
view: getViewFromProps,
|
||||
page: getPageFromString,
|
||||
sort: getSortTypeFromQuery,
|
||||
});
|
||||
export function getProfileQueryParams(source?: string): ProfileProps {
|
||||
return getQueryParams<ProfileProps>(
|
||||
{
|
||||
view: getViewFromProps,
|
||||
page: getPageFromString,
|
||||
sort: getSortTypeFromQuery,
|
||||
},
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
function getSortTypeFromQuery(sort?: string): SortType {
|
||||
|
@ -171,10 +175,15 @@ function isPersonBlocked(personRes: RequestState<GetPersonDetailsResponse>) {
|
|||
);
|
||||
}
|
||||
|
||||
export class Profile extends Component<
|
||||
RouteComponentProps<{ username: string }>,
|
||||
ProfileState
|
||||
> {
|
||||
type ProfilePathProps = { username: string };
|
||||
type ProfileRouteProps = RouteComponentProps<ProfilePathProps> & ProfileProps;
|
||||
export type ProfileFetchConfig = IRoutePropsWithFetch<
|
||||
ProfileData,
|
||||
ProfilePathProps,
|
||||
ProfileProps
|
||||
>;
|
||||
|
||||
export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||
private isoData = setIsoData<ProfileData>(this.context);
|
||||
state: ProfileState = {
|
||||
personRes: EMPTY_REQUEST,
|
||||
|
@ -186,7 +195,7 @@ export class Profile extends Component<
|
|||
isIsomorphic: false,
|
||||
};
|
||||
|
||||
constructor(props: RouteComponentProps<{ username: string }>, context: any) {
|
||||
constructor(props: ProfileRouteProps, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
|
@ -248,7 +257,7 @@ export class Profile extends Component<
|
|||
}
|
||||
|
||||
async fetchUserData() {
|
||||
const { page, sort, view } = getProfileQueryParams();
|
||||
const { page, sort, view } = this.props;
|
||||
|
||||
this.setState({ personRes: LOADING_REQUEST });
|
||||
const personRes = await HttpService.client.getPersonDetails({
|
||||
|
@ -278,22 +287,23 @@ export class Profile extends Component<
|
|||
|
||||
static async fetchInitialData({
|
||||
headers,
|
||||
path,
|
||||
query: { page, sort, view: urlView },
|
||||
}: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<ProfileData> {
|
||||
query: { view, sort, page },
|
||||
match: {
|
||||
params: { username },
|
||||
},
|
||||
}: InitialFetchRequest<
|
||||
ProfilePathProps,
|
||||
ProfileProps
|
||||
>): Promise<ProfileData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
const pathSplit = path.split("/");
|
||||
|
||||
const username = pathSplit[2];
|
||||
const view = getViewFromProps(urlView);
|
||||
|
||||
const form: GetPersonDetails = {
|
||||
username: username,
|
||||
sort: getSortTypeFromQuery(sort),
|
||||
sort,
|
||||
saved_only: view === PersonDetailsView.Saved,
|
||||
page: getPageFromString(page),
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
};
|
||||
|
||||
|
@ -321,7 +331,7 @@ export class Profile extends Component<
|
|||
case "success": {
|
||||
const siteRes = this.state.siteRes;
|
||||
const personRes = this.state.personRes.data;
|
||||
const { page, sort, view } = getProfileQueryParams();
|
||||
const { page, sort, view } = this.props;
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
|
@ -415,7 +425,7 @@ export class Profile extends Component<
|
|||
}
|
||||
|
||||
getRadio(view: PersonDetailsView) {
|
||||
const { view: urlView } = getProfileQueryParams();
|
||||
const { view: urlView } = this.props;
|
||||
const active = view === urlView;
|
||||
const radioId = randomStr();
|
||||
|
||||
|
@ -442,10 +452,10 @@ export class Profile extends Component<
|
|||
}
|
||||
|
||||
get selects() {
|
||||
const { sort } = getProfileQueryParams();
|
||||
const { sort } = this.props;
|
||||
const { username } = this.props.match.params;
|
||||
|
||||
const profileRss = `/feeds/u/${username}.xml?sort=${sort}`;
|
||||
const profileRss = `/feeds/u/${username}.xml${getQueryString({ sort })}`;
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
|
@ -713,11 +723,7 @@ export class Profile extends Component<
|
|||
}
|
||||
|
||||
async updateUrl({ page, sort, view }: Partial<ProfileProps>) {
|
||||
const {
|
||||
page: urlPage,
|
||||
sort: urlSort,
|
||||
view: urlView,
|
||||
} = getProfileQueryParams();
|
||||
const { page: urlPage, sort: urlSort, view: urlView } = this.props;
|
||||
|
||||
const queryParams: QueryParams<ProfileProps> = {
|
||||
page: (page ?? urlPage).toString(),
|
||||
|
|
|
@ -27,6 +27,8 @@ import { Paginator } from "../common/paginator";
|
|||
import { RegistrationApplication } from "../common/registration-application";
|
||||
import { UnreadCounterService } from "../../services";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
enum RegistrationState {
|
||||
Unread,
|
||||
|
@ -46,8 +48,18 @@ interface RegistrationApplicationsState {
|
|||
isIsomorphic: boolean;
|
||||
}
|
||||
|
||||
type RegistrationApplicationsRouteProps = RouteComponentProps<
|
||||
Record<string, never>
|
||||
> &
|
||||
Record<string, never>;
|
||||
export type RegistrationApplicationsFetchConfig = IRoutePropsWithFetch<
|
||||
RegistrationApplicationsData,
|
||||
Record<string, never>,
|
||||
Record<string, never>
|
||||
>;
|
||||
|
||||
export class RegistrationApplications extends Component<
|
||||
any,
|
||||
RegistrationApplicationsRouteProps,
|
||||
RegistrationApplicationsState
|
||||
> {
|
||||
private isoData = setIsoData<RegistrationApplicationsData>(this.context);
|
||||
|
|
|
@ -50,6 +50,8 @@ import { PostReport } from "../post/post-report";
|
|||
import { PrivateMessageReport } from "../private_message/private-message-report";
|
||||
import { UnreadCounterService } from "../../services";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
enum UnreadOrAll {
|
||||
Unread,
|
||||
|
@ -93,7 +95,15 @@ interface ReportsState {
|
|||
isIsomorphic: boolean;
|
||||
}
|
||||
|
||||
export class Reports extends Component<any, ReportsState> {
|
||||
type ReportsRouteProps = RouteComponentProps<Record<string, never>> &
|
||||
Record<string, never>;
|
||||
export type ReportsFetchConfig = IRoutePropsWithFetch<
|
||||
ReportsData,
|
||||
Record<string, never>,
|
||||
Record<string, never>
|
||||
>;
|
||||
|
||||
export class Reports extends Component<ReportsRouteProps, ReportsState> {
|
||||
private isoData = setIsoData<ReportsData>(this.context);
|
||||
state: ReportsState = {
|
||||
commentReportsRes: EMPTY_REQUEST,
|
||||
|
|
|
@ -68,6 +68,8 @@ import TotpModal from "../common/totp-modal";
|
|||
import { LoadingEllipses } from "../common/loading-ellipses";
|
||||
import { refreshTheme, setThemeOverride } from "../../utils/browser";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
|
||||
type SettingsData = RouteDataResponse<{
|
||||
instancesRes: GetFederatedInstancesResponse;
|
||||
|
@ -193,7 +195,15 @@ function handleClose2faModal(i: Settings) {
|
|||
i.setState({ show2faModal: false });
|
||||
}
|
||||
|
||||
export class Settings extends Component<any, SettingsState> {
|
||||
type SettingsRouteProps = RouteComponentProps<Record<string, never>> &
|
||||
Record<string, never>;
|
||||
export type SettingsFetchConfig = IRoutePropsWithFetch<
|
||||
SettingsData,
|
||||
Record<string, never>,
|
||||
Record<string, never>
|
||||
>;
|
||||
|
||||
export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
||||
private isoData = setIsoData<SettingsData>(this.context);
|
||||
exportSettingsLink = createRef<HTMLAnchorElement>();
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
setIsoData,
|
||||
} from "@utils/app";
|
||||
import { getIdFromString, getQueryParams } from "@utils/helpers";
|
||||
import type { QueryParams } from "@utils/types";
|
||||
import { Choice, RouteDataResponse } from "@utils/types";
|
||||
import { Component } from "inferno";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
|
@ -30,6 +29,7 @@ import { HtmlTags } from "../common/html-tags";
|
|||
import { Spinner } from "../common/icon";
|
||||
import { PostForm } from "./post-form";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
export interface CreatePostProps {
|
||||
communityId?: number;
|
||||
|
@ -40,10 +40,13 @@ type CreatePostData = RouteDataResponse<{
|
|||
initialCommunitiesRes: ListCommunitiesResponse;
|
||||
}>;
|
||||
|
||||
function getCreatePostQueryParams() {
|
||||
return getQueryParams<CreatePostProps>({
|
||||
communityId: getIdFromString,
|
||||
});
|
||||
export function getCreatePostQueryParams(source?: string): CreatePostProps {
|
||||
return getQueryParams<CreatePostProps>(
|
||||
{
|
||||
communityId: getIdFromString,
|
||||
},
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
function fetchCommunitiesForOptions(client: WrappedLemmyHttp) {
|
||||
|
@ -58,8 +61,17 @@ interface CreatePostState {
|
|||
isIsomorphic: boolean;
|
||||
}
|
||||
|
||||
type CreatePostPathProps = Record<string, never>;
|
||||
type CreatePostRouteProps = RouteComponentProps<CreatePostPathProps> &
|
||||
CreatePostProps;
|
||||
export type CreatePostFetchConfig = IRoutePropsWithFetch<
|
||||
CreatePostData,
|
||||
CreatePostPathProps,
|
||||
CreatePostProps
|
||||
>;
|
||||
|
||||
export class CreatePost extends Component<
|
||||
RouteComponentProps<Record<string, never>>,
|
||||
CreatePostRouteProps,
|
||||
CreatePostState
|
||||
> {
|
||||
private isoData = setIsoData<CreatePostData>(this.context);
|
||||
|
@ -70,7 +82,7 @@ export class CreatePost extends Component<
|
|||
isIsomorphic: false,
|
||||
};
|
||||
|
||||
constructor(props: RouteComponentProps<Record<string, never>>, context: any) {
|
||||
constructor(props: CreatePostRouteProps, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.handlePostCreate = this.handlePostCreate.bind(this);
|
||||
|
@ -102,9 +114,7 @@ export class CreatePost extends Component<
|
|||
}
|
||||
}
|
||||
|
||||
async fetchCommunity() {
|
||||
const { communityId } = getCreatePostQueryParams();
|
||||
|
||||
async fetchCommunity({ communityId }: CreatePostProps) {
|
||||
if (communityId) {
|
||||
const res = await HttpService.client.getCommunity({
|
||||
id: communityId,
|
||||
|
@ -121,7 +131,7 @@ export class CreatePost extends Component<
|
|||
async componentDidMount() {
|
||||
// TODO test this
|
||||
if (!this.state.isIsomorphic) {
|
||||
const { communityId } = getCreatePostQueryParams();
|
||||
const { communityId } = this.props;
|
||||
|
||||
const initialCommunitiesRes = await fetchCommunitiesForOptions(
|
||||
HttpService.client,
|
||||
|
@ -134,7 +144,7 @@ export class CreatePost extends Component<
|
|||
if (
|
||||
communityId?.toString() !== this.state.selectedCommunityChoice?.value
|
||||
) {
|
||||
await this.fetchCommunity();
|
||||
await this.fetchCommunity({ communityId });
|
||||
} else if (!communityId) {
|
||||
this.setState({
|
||||
selectedCommunityChoice: undefined,
|
||||
|
@ -199,15 +209,13 @@ export class CreatePost extends Component<
|
|||
}
|
||||
|
||||
async updateUrl({ communityId }: Partial<CreatePostProps>) {
|
||||
const { communityId: urlCommunityId } = getCreatePostQueryParams();
|
||||
|
||||
const locationState = this.props.history.location.state as
|
||||
| PostFormParams
|
||||
| undefined;
|
||||
|
||||
const url = new URL(location.href);
|
||||
|
||||
const newId = (communityId ?? urlCommunityId)?.toString();
|
||||
const newId = communityId?.toString();
|
||||
|
||||
if (newId !== undefined) {
|
||||
url.searchParams.set("communityId", newId);
|
||||
|
@ -215,9 +223,10 @@ export class CreatePost extends Component<
|
|||
url.searchParams.delete("communityId");
|
||||
}
|
||||
|
||||
history.replaceState(locationState, "", url);
|
||||
// This bypasses the router and doesn't update the query props.
|
||||
window.history.replaceState(locationState, "", url);
|
||||
|
||||
await this.fetchCommunity();
|
||||
await this.fetchCommunity({ communityId });
|
||||
}
|
||||
|
||||
handleSelectedCommunityChange(choice: Choice) {
|
||||
|
@ -243,7 +252,8 @@ export class CreatePost extends Component<
|
|||
headers,
|
||||
query: { communityId },
|
||||
}: InitialFetchRequest<
|
||||
QueryParams<CreatePostProps>
|
||||
CreatePostPathProps,
|
||||
CreatePostProps
|
||||
>): Promise<CreatePostData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
|
@ -255,7 +265,7 @@ export class CreatePost extends Component<
|
|||
|
||||
if (communityId) {
|
||||
const form: GetCommunity = {
|
||||
id: getIdFromString(communityId),
|
||||
id: communityId,
|
||||
};
|
||||
|
||||
data.communityResponse = await client.getCommunity(form);
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
capitalizeFirstLetter,
|
||||
debounce,
|
||||
getIdFromString,
|
||||
getQueryString,
|
||||
validTitle,
|
||||
validURL,
|
||||
} from "@utils/helpers";
|
||||
|
@ -380,18 +381,14 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
|||
archive.org {I18NextService.i18n.t("archive_link")}
|
||||
</a>
|
||||
<a
|
||||
href={`${ghostArchiveUrl}/search?term=${encodeURIComponent(
|
||||
url,
|
||||
)}`}
|
||||
href={`${ghostArchiveUrl}/search${getQueryString({ term: url })}`}
|
||||
className="me-2 d-inline-block float-right text-muted small fw-bold"
|
||||
rel={relTags}
|
||||
>
|
||||
ghostarchive.org {I18NextService.i18n.t("archive_link")}
|
||||
</a>
|
||||
<a
|
||||
href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent(
|
||||
url,
|
||||
)}`}
|
||||
href={`${archiveTodayUrl}/${getQueryString({ run: "1", url })}`}
|
||||
className="me-2 d-inline-block float-right text-muted small fw-bold"
|
||||
rel={relTags}
|
||||
>
|
||||
|
|
|
@ -98,6 +98,8 @@ import { Icon, Spinner } from "../common/icon";
|
|||
import { Sidebar } from "../community/sidebar";
|
||||
import { PostListing } from "./post-listing";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
const commentsShownInterval = 15;
|
||||
|
||||
|
@ -122,7 +124,18 @@ interface PostState {
|
|||
isIsomorphic: boolean;
|
||||
}
|
||||
|
||||
export class Post extends Component<any, PostState> {
|
||||
type PostPathProps =
|
||||
| { post_id: string; comment_id: never }
|
||||
| { post_id: never; comment_id: string };
|
||||
type PostRouteProps = RouteComponentProps<PostPathProps> &
|
||||
Record<string, never>;
|
||||
export type PostFetchConfig = IRoutePropsWithFetch<
|
||||
PostData,
|
||||
PostPathProps,
|
||||
Record<string, never>
|
||||
>;
|
||||
|
||||
export class Post extends Component<PostRouteProps, PostState> {
|
||||
private isoData = setIsoData<PostData>(this.context);
|
||||
private commentScrollDebounced: () => void;
|
||||
state: PostState = {
|
||||
|
@ -235,15 +248,13 @@ export class Post extends Component<any, PostState> {
|
|||
|
||||
static async fetchInitialData({
|
||||
headers,
|
||||
path,
|
||||
}: InitialFetchRequest): Promise<PostData> {
|
||||
match,
|
||||
}: InitialFetchRequest<PostPathProps>): Promise<PostData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
const pathSplit = path.split("/");
|
||||
|
||||
const pathType = pathSplit.at(1);
|
||||
const id = pathSplit.at(2) ? Number(pathSplit.at(2)) : undefined;
|
||||
const postId = getIdFromProps({ match });
|
||||
const commentId = getCommentIdFromProps({ match });
|
||||
|
||||
const postForm: GetPost = {};
|
||||
|
||||
|
@ -254,14 +265,11 @@ export class Post extends Component<any, PostState> {
|
|||
saved_only: false,
|
||||
};
|
||||
|
||||
// Set the correct id based on the path type
|
||||
if (pathType === "post") {
|
||||
postForm.id = id;
|
||||
commentsForm.post_id = id;
|
||||
} else {
|
||||
postForm.comment_id = id;
|
||||
commentsForm.parent_id = id;
|
||||
}
|
||||
postForm.id = postId;
|
||||
postForm.comment_id = commentId;
|
||||
|
||||
commentsForm.post_id = postId;
|
||||
commentsForm.parent_id = commentId;
|
||||
|
||||
const [postRes, commentsRes] = await Promise.all([
|
||||
client.getPost(postForm),
|
||||
|
|
|
@ -22,6 +22,8 @@ import { HtmlTags } from "../common/html-tags";
|
|||
import { Spinner } from "../common/icon";
|
||||
import { PrivateMessageForm } from "./private-message-form";
|
||||
import { getHttpBaseInternal } from "../../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../../routes";
|
||||
|
||||
type CreatePrivateMessageData = RouteDataResponse<{
|
||||
recipientDetailsResponse: GetPersonDetailsResponse;
|
||||
|
@ -34,8 +36,17 @@ interface CreatePrivateMessageState {
|
|||
isIsomorphic: boolean;
|
||||
}
|
||||
|
||||
type CreatePrivateMessagePathProps = { recipient_id: string };
|
||||
type CreatePrivateMessageRouteProps =
|
||||
RouteComponentProps<CreatePrivateMessagePathProps> & Record<string, never>;
|
||||
export type CreatePrivateMessageFetchConfig = IRoutePropsWithFetch<
|
||||
CreatePrivateMessageData,
|
||||
CreatePrivateMessagePathProps,
|
||||
Record<string, never>
|
||||
>;
|
||||
|
||||
export class CreatePrivateMessage extends Component<
|
||||
any,
|
||||
CreatePrivateMessageRouteProps,
|
||||
CreatePrivateMessageState
|
||||
> {
|
||||
private isoData = setIsoData<CreatePrivateMessageData>(this.context);
|
||||
|
@ -69,12 +80,12 @@ export class CreatePrivateMessage extends Component<
|
|||
|
||||
static async fetchInitialData({
|
||||
headers,
|
||||
path,
|
||||
}: InitialFetchRequest): Promise<CreatePrivateMessageData> {
|
||||
match,
|
||||
}: InitialFetchRequest<CreatePrivateMessagePathProps>): Promise<CreatePrivateMessageData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
const person_id = Number(path.split("/").pop());
|
||||
const person_id = getRecipientIdFromProps({ match });
|
||||
|
||||
const form: GetPersonDetails = {
|
||||
person_id,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { setIsoData } from "@utils/app";
|
||||
import { getQueryParams } from "@utils/helpers";
|
||||
import { QueryParams, RouteDataResponse } from "@utils/types";
|
||||
import { RouteDataResponse } from "@utils/types";
|
||||
import { Component, linkEvent } from "inferno";
|
||||
import {
|
||||
CommunityView,
|
||||
|
@ -22,6 +22,8 @@ import { PictrsImage } from "./common/pictrs-image";
|
|||
import { SubscribeButton } from "./common/subscribe-button";
|
||||
import { CommunityLink } from "./community/community-link";
|
||||
import { getHttpBaseInternal } from "../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../routes";
|
||||
|
||||
interface RemoteFetchProps {
|
||||
uri?: string;
|
||||
|
@ -37,16 +39,19 @@ interface RemoteFetchState {
|
|||
followCommunityLoading: boolean;
|
||||
}
|
||||
|
||||
const getUriFromQuery = (uri?: string): string | undefined =>
|
||||
uri ? decodeURIComponent(uri) : undefined;
|
||||
const getUriFromQuery = (uri?: string): string | undefined => uri;
|
||||
|
||||
const getRemoteFetchQueryParams = () =>
|
||||
getQueryParams<RemoteFetchProps>({
|
||||
uri: getUriFromQuery,
|
||||
});
|
||||
export function getRemoteFetchQueryParams(source?: string): RemoteFetchProps {
|
||||
return getQueryParams<RemoteFetchProps>(
|
||||
{
|
||||
uri: getUriFromQuery,
|
||||
},
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
function uriToQuery(uri: string) {
|
||||
const match = decodeURIComponent(uri).match(/https?:\/\/(.+)\/c\/(.+)/);
|
||||
const match = uri.match(/https?:\/\/(.+)\/c\/(.+)/);
|
||||
|
||||
return match ? `!${match[2]}@${match[1]}` : "";
|
||||
}
|
||||
|
@ -83,7 +88,19 @@ async function handleToggleFollow(i: RemoteFetch, follow: boolean) {
|
|||
const handleFollow = (i: RemoteFetch) => handleToggleFollow(i, true);
|
||||
const handleUnfollow = (i: RemoteFetch) => handleToggleFollow(i, false);
|
||||
|
||||
export class RemoteFetch extends Component<any, RemoteFetchState> {
|
||||
type RemoteFetchPathProps = Record<string, never>;
|
||||
type RemoteFetchRouteProps = RouteComponentProps<RemoteFetchPathProps> &
|
||||
RemoteFetchProps;
|
||||
export type RemoteFetchFetchConfig = IRoutePropsWithFetch<
|
||||
RemoteFetchData,
|
||||
RemoteFetchPathProps,
|
||||
RemoteFetchProps
|
||||
>;
|
||||
|
||||
export class RemoteFetch extends Component<
|
||||
RemoteFetchRouteProps,
|
||||
RemoteFetchState
|
||||
> {
|
||||
private isoData = setIsoData<RemoteFetchData>(this.context);
|
||||
state: RemoteFetchState = {
|
||||
resolveObjectRes: EMPTY_REQUEST,
|
||||
|
@ -91,7 +108,7 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
|||
followCommunityLoading: false,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
constructor(props: RemoteFetchRouteProps, context: any) {
|
||||
super(props, context);
|
||||
|
||||
if (FirstLoadService.isFirstLoad) {
|
||||
|
@ -107,7 +124,7 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
|||
|
||||
async componentDidMount() {
|
||||
if (!this.state.isIsomorphic) {
|
||||
const { uri } = getRemoteFetchQueryParams();
|
||||
const { uri } = this.props;
|
||||
|
||||
if (uri) {
|
||||
this.setState({ resolveObjectRes: LOADING_REQUEST });
|
||||
|
@ -139,7 +156,7 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
|||
get content() {
|
||||
const res = this.state.resolveObjectRes;
|
||||
|
||||
const { uri } = getRemoteFetchQueryParams();
|
||||
const { uri } = this.props;
|
||||
const remoteCommunityName = uri ? uriToQuery(uri) : "remote community";
|
||||
|
||||
switch (res.state) {
|
||||
|
@ -204,7 +221,7 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
|||
}
|
||||
|
||||
get documentTitle(): string {
|
||||
const { uri } = getRemoteFetchQueryParams();
|
||||
const { uri } = this.props;
|
||||
const name = this.isoData.site_res.site_view.site.name;
|
||||
return `${I18NextService.i18n.t("remote_follow")} - ${
|
||||
uri ? `${uri} - ` : ""
|
||||
|
@ -215,7 +232,8 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
|||
headers,
|
||||
query: { uri },
|
||||
}: InitialFetchRequest<
|
||||
QueryParams<RemoteFetchProps>
|
||||
RemoteFetchPathProps,
|
||||
RemoteFetchProps
|
||||
>): Promise<RemoteFetchData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
|
|
|
@ -67,14 +67,16 @@ import { CommunityLink } from "./community/community-link";
|
|||
import { PersonListing } from "./person/person-listing";
|
||||
import { PostListing } from "./post/post-listing";
|
||||
import { getHttpBaseInternal } from "../utils/env";
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import { IRoutePropsWithFetch } from "../routes";
|
||||
|
||||
interface SearchProps {
|
||||
q?: string;
|
||||
type: SearchType;
|
||||
sort: SortType;
|
||||
listingType: ListingType;
|
||||
communityId?: number | null;
|
||||
creatorId?: number | null;
|
||||
communityId?: number;
|
||||
creatorId?: number;
|
||||
page: number;
|
||||
}
|
||||
|
||||
|
@ -112,19 +114,22 @@ const defaultListingType = "All";
|
|||
|
||||
const searchTypes = ["All", "Comments", "Posts", "Communities", "Users", "Url"];
|
||||
|
||||
const getSearchQueryParams = () =>
|
||||
getQueryParams<SearchProps>({
|
||||
q: getSearchQueryFromQuery,
|
||||
type: getSearchTypeFromQuery,
|
||||
sort: getSortTypeFromQuery,
|
||||
listingType: getListingTypeFromQuery,
|
||||
communityId: getIdFromString,
|
||||
creatorId: getIdFromString,
|
||||
page: getPageFromString,
|
||||
});
|
||||
export function getSearchQueryParams(source?: string): SearchProps {
|
||||
return getQueryParams<SearchProps>(
|
||||
{
|
||||
q: getSearchQueryFromQuery,
|
||||
type: getSearchTypeFromQuery,
|
||||
sort: getSortTypeFromQuery,
|
||||
listingType: getListingTypeFromQuery,
|
||||
communityId: getIdFromString,
|
||||
creatorId: getIdFromString,
|
||||
page: getPageFromString,
|
||||
},
|
||||
source,
|
||||
);
|
||||
}
|
||||
|
||||
const getSearchQueryFromQuery = (q?: string): string | undefined =>
|
||||
q ? decodeURIComponent(q) : undefined;
|
||||
const getSearchQueryFromQuery = (q?: string): string | undefined => q;
|
||||
|
||||
function getSearchTypeFromQuery(type_?: string): SearchType {
|
||||
return type_ ? (type_ as SearchType) : defaultSearchType;
|
||||
|
@ -240,7 +245,15 @@ function getListing(
|
|||
);
|
||||
}
|
||||
|
||||
export class Search extends Component<any, SearchState> {
|
||||
type SearchPathProps = Record<string, never>;
|
||||
type SearchRouteProps = RouteComponentProps<SearchPathProps> & SearchProps;
|
||||
export type SearchFetchConfig = IRoutePropsWithFetch<
|
||||
SearchData,
|
||||
SearchPathProps,
|
||||
SearchProps
|
||||
>;
|
||||
|
||||
export class Search extends Component<SearchRouteProps, SearchState> {
|
||||
private isoData = setIsoData<SearchData>(this.context);
|
||||
searchInput = createRef<HTMLInputElement>();
|
||||
|
||||
|
@ -255,7 +268,7 @@ export class Search extends Component<any, SearchState> {
|
|||
isIsomorphic: false,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
constructor(props: SearchRouteProps, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.handleSortChange = this.handleSortChange.bind(this);
|
||||
|
@ -265,7 +278,7 @@ export class Search extends Component<any, SearchState> {
|
|||
this.handleCommunityFilterChange.bind(this);
|
||||
this.handleCreatorFilterChange = this.handleCreatorFilterChange.bind(this);
|
||||
|
||||
const { q } = getSearchQueryParams();
|
||||
const { q } = this.props;
|
||||
|
||||
this.state.searchText = q;
|
||||
|
||||
|
@ -335,7 +348,7 @@ export class Search extends Component<any, SearchState> {
|
|||
}),
|
||||
];
|
||||
|
||||
const { communityId, creatorId } = getSearchQueryParams();
|
||||
const { communityId, creatorId } = this.props;
|
||||
|
||||
if (communityId) {
|
||||
promises.push(
|
||||
|
@ -390,12 +403,19 @@ export class Search extends Component<any, SearchState> {
|
|||
|
||||
static async fetchInitialData({
|
||||
headers,
|
||||
query: { communityId, creatorId, q, type, sort, listingType, page },
|
||||
}: InitialFetchRequest<QueryParams<SearchProps>>): Promise<SearchData> {
|
||||
query: {
|
||||
q: query,
|
||||
type: searchType,
|
||||
sort,
|
||||
listingType: listing_type,
|
||||
communityId: community_id,
|
||||
creatorId: creator_id,
|
||||
page,
|
||||
},
|
||||
}: InitialFetchRequest<SearchPathProps, SearchProps>): Promise<SearchData> {
|
||||
const client = wrapClient(
|
||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||
);
|
||||
const community_id = getIdFromString(communityId);
|
||||
let communityResponse: RequestState<GetCommunityResponse> = EMPTY_REQUEST;
|
||||
if (community_id) {
|
||||
const getCommunityForm: GetCommunity = {
|
||||
|
@ -411,7 +431,6 @@ export class Search extends Component<any, SearchState> {
|
|||
limit: fetchLimit,
|
||||
});
|
||||
|
||||
const creator_id = getIdFromString(creatorId);
|
||||
let creatorDetailsResponse: RequestState<GetPersonDetailsResponse> =
|
||||
EMPTY_REQUEST;
|
||||
if (creator_id) {
|
||||
|
@ -422,8 +441,6 @@ export class Search extends Component<any, SearchState> {
|
|||
creatorDetailsResponse = await client.getPersonDetails(getCreatorForm);
|
||||
}
|
||||
|
||||
const query = getSearchQueryFromQuery(q);
|
||||
|
||||
let searchResponse: RequestState<SearchResponse> = EMPTY_REQUEST;
|
||||
let resolveObjectResponse: RequestState<ResolveObjectResponse> =
|
||||
EMPTY_REQUEST;
|
||||
|
@ -433,10 +450,10 @@ export class Search extends Component<any, SearchState> {
|
|||
q: query,
|
||||
community_id,
|
||||
creator_id,
|
||||
type_: getSearchTypeFromQuery(type),
|
||||
sort: getSortTypeFromQuery(sort),
|
||||
listing_type: getListingTypeFromQuery(listingType),
|
||||
page: getPageFromString(page),
|
||||
type_: searchType,
|
||||
sort,
|
||||
listing_type,
|
||||
page,
|
||||
limit: fetchLimit,
|
||||
};
|
||||
|
||||
|
@ -466,13 +483,13 @@ export class Search extends Component<any, SearchState> {
|
|||
}
|
||||
|
||||
get documentTitle(): string {
|
||||
const { q } = getSearchQueryParams();
|
||||
const { q } = this.props;
|
||||
const name = this.state.siteRes.site_view.site.name;
|
||||
return `${I18NextService.i18n.t("search")} - ${q ? `${q} - ` : ""}${name}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { type, page } = getSearchQueryParams();
|
||||
const { type, page } = this.props;
|
||||
|
||||
return (
|
||||
<div className="search container-lg">
|
||||
|
@ -555,8 +572,7 @@ export class Search extends Component<any, SearchState> {
|
|||
}
|
||||
|
||||
get selects() {
|
||||
const { type, listingType, sort, communityId, creatorId } =
|
||||
getSearchQueryParams();
|
||||
const { type, listingType, sort, communityId, creatorId } = this.props;
|
||||
const {
|
||||
communitySearchOptions,
|
||||
creatorSearchOptions,
|
||||
|
@ -664,7 +680,7 @@ export class Search extends Component<any, SearchState> {
|
|||
);
|
||||
}
|
||||
|
||||
const { sort } = getSearchQueryParams();
|
||||
const { sort } = this.props;
|
||||
|
||||
// Sort it
|
||||
if (sort === "New") {
|
||||
|
@ -959,7 +975,7 @@ export class Search extends Component<any, SearchState> {
|
|||
async search() {
|
||||
const { searchText: q } = this.state;
|
||||
const { communityId, creatorId, type, sort, listingType, page } =
|
||||
getSearchQueryParams();
|
||||
this.props;
|
||||
|
||||
if (q) {
|
||||
this.setState({ searchRes: LOADING_REQUEST });
|
||||
|
@ -991,7 +1007,7 @@ export class Search extends Component<any, SearchState> {
|
|||
|
||||
handleCreatorSearch = debounce(async (text: string) => {
|
||||
if (text.length > 0) {
|
||||
const { creatorId } = getSearchQueryParams();
|
||||
const { creatorId } = this.props;
|
||||
const { creatorSearchOptions } = this.state;
|
||||
|
||||
this.setState({ searchCreatorLoading: true });
|
||||
|
@ -1009,7 +1025,7 @@ export class Search extends Component<any, SearchState> {
|
|||
|
||||
handleCommunitySearch = debounce(async (text: string) => {
|
||||
if (text.length > 0) {
|
||||
const { communityId } = getSearchQueryParams();
|
||||
const { communityId } = this.props;
|
||||
const { communitySearchOptions } = this.state;
|
||||
|
||||
this.setState({
|
||||
|
@ -1053,14 +1069,14 @@ export class Search extends Component<any, SearchState> {
|
|||
|
||||
handleCommunityFilterChange({ value }: Choice) {
|
||||
this.updateUrl({
|
||||
communityId: getIdFromString(value) ?? null,
|
||||
communityId: getIdFromString(value),
|
||||
page: 1,
|
||||
});
|
||||
}
|
||||
|
||||
handleCreatorFilterChange({ value }: Choice) {
|
||||
this.updateUrl({
|
||||
creatorId: getIdFromString(value) ?? null,
|
||||
creatorId: getIdFromString(value),
|
||||
page: 1,
|
||||
});
|
||||
}
|
||||
|
@ -1095,13 +1111,9 @@ export class Search extends Component<any, SearchState> {
|
|||
sort: urlSort,
|
||||
creatorId: urlCreatorId,
|
||||
page: urlPage,
|
||||
} = getSearchQueryParams();
|
||||
} = this.props;
|
||||
|
||||
let query = q ?? this.state.searchText ?? urlQ;
|
||||
|
||||
if (query && query.length > 0) {
|
||||
query = encodeURIComponent(query);
|
||||
}
|
||||
const query = q ?? this.state.searchText ?? urlQ;
|
||||
|
||||
const queryParams: QueryParams<SearchProps> = {
|
||||
q: query,
|
||||
|
|
|
@ -5,8 +5,8 @@ import {
|
|||
GetSiteResponse,
|
||||
PersonMention,
|
||||
} from "lemmy-js-client";
|
||||
import type { ParsedQs } from "qs";
|
||||
import { RequestState } from "./services/HttpService";
|
||||
import { Match } from "inferno-router/dist/Route";
|
||||
|
||||
/**
|
||||
* This contains serialized data, it needs to be deserialized before use.
|
||||
|
@ -30,9 +30,13 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
export interface InitialFetchRequest<T extends ParsedQs = ParsedQs> {
|
||||
export interface InitialFetchRequest<
|
||||
P extends Record<string, string> = Record<string, never>,
|
||||
T extends Record<string, any> = Record<string, never>,
|
||||
> {
|
||||
path: string;
|
||||
query: T;
|
||||
match: Match<P>;
|
||||
site: GetSiteResponse;
|
||||
headers: { [key: string]: string };
|
||||
}
|
||||
|
|
|
@ -1,45 +1,107 @@
|
|||
import { IRouteProps } from "inferno-router/dist/Route";
|
||||
import { Communities } from "./components/community/communities";
|
||||
import { Community } from "./components/community/community";
|
||||
import { IRouteProps, RouteComponentProps } from "inferno-router/dist/Route";
|
||||
import {
|
||||
Communities,
|
||||
CommunitiesFetchConfig,
|
||||
getCommunitiesQueryParams,
|
||||
} from "./components/community/communities";
|
||||
import {
|
||||
Community,
|
||||
CommunityFetchConfig,
|
||||
getCommunityQueryParams,
|
||||
} from "./components/community/community";
|
||||
import { CreateCommunity } from "./components/community/create-community";
|
||||
import { AdminSettings } from "./components/home/admin-settings";
|
||||
import { Home } from "./components/home/home";
|
||||
import { Instances } from "./components/home/instances";
|
||||
import {
|
||||
AdminSettings,
|
||||
AdminSettingsFetchConfig,
|
||||
} from "./components/home/admin-settings";
|
||||
import {
|
||||
Home,
|
||||
HomeFetchConfig,
|
||||
getHomeQueryParams,
|
||||
} from "./components/home/home";
|
||||
import { Instances, InstancesFetchConfig } from "./components/home/instances";
|
||||
import { Legal } from "./components/home/legal";
|
||||
import { Login } from "./components/home/login";
|
||||
import {
|
||||
Login,
|
||||
LoginFetchConfig,
|
||||
getLoginQueryParams,
|
||||
} from "./components/home/login";
|
||||
import { LoginReset } from "./components/home/login-reset";
|
||||
import { Setup } from "./components/home/setup";
|
||||
import { Signup } from "./components/home/signup";
|
||||
import { Modlog } from "./components/modlog";
|
||||
import { Inbox } from "./components/person/inbox";
|
||||
import {
|
||||
Modlog,
|
||||
ModlogFetchConfig,
|
||||
getModlogQueryParams,
|
||||
} from "./components/modlog";
|
||||
import { Inbox, InboxFetchConfig } from "./components/person/inbox";
|
||||
import { PasswordChange } from "./components/person/password-change";
|
||||
import { Profile } from "./components/person/profile";
|
||||
import { RegistrationApplications } from "./components/person/registration-applications";
|
||||
import { Reports } from "./components/person/reports";
|
||||
import { Settings } from "./components/person/settings";
|
||||
import {
|
||||
Profile,
|
||||
ProfileFetchConfig,
|
||||
getProfileQueryParams,
|
||||
} from "./components/person/profile";
|
||||
import {
|
||||
RegistrationApplications,
|
||||
RegistrationApplicationsFetchConfig,
|
||||
} from "./components/person/registration-applications";
|
||||
import { Reports, ReportsFetchConfig } from "./components/person/reports";
|
||||
import { Settings, SettingsFetchConfig } from "./components/person/settings";
|
||||
import { VerifyEmail } from "./components/person/verify-email";
|
||||
import { CreatePost } from "./components/post/create-post";
|
||||
import { Post } from "./components/post/post";
|
||||
import { CreatePrivateMessage } from "./components/private_message/create-private-message";
|
||||
import { RemoteFetch } from "./components/remote-fetch";
|
||||
import { Search } from "./components/search";
|
||||
import {
|
||||
CreatePostFetchConfig,
|
||||
CreatePost,
|
||||
getCreatePostQueryParams,
|
||||
} from "./components/post/create-post";
|
||||
import { Post, PostFetchConfig } from "./components/post/post";
|
||||
import {
|
||||
CreatePrivateMessage,
|
||||
CreatePrivateMessageFetchConfig,
|
||||
} from "./components/private_message/create-private-message";
|
||||
import {
|
||||
RemoteFetch,
|
||||
RemoteFetchFetchConfig,
|
||||
getRemoteFetchQueryParams,
|
||||
} from "./components/remote-fetch";
|
||||
import {
|
||||
Search,
|
||||
SearchFetchConfig,
|
||||
getSearchQueryParams,
|
||||
} from "./components/search";
|
||||
import { InitialFetchRequest, RouteData } from "./interfaces";
|
||||
import { GetSiteResponse } from "lemmy-js-client";
|
||||
import { Inferno } from "inferno";
|
||||
|
||||
interface IRoutePropsWithFetch<T extends RouteData> extends IRouteProps {
|
||||
fetchInitialData?(req: InitialFetchRequest): Promise<T>;
|
||||
export interface IRoutePropsWithFetch<
|
||||
DataT extends RouteData,
|
||||
PathPropsT extends Record<string, string>,
|
||||
QueryPropsT extends Record<string, any>,
|
||||
> extends IRouteProps {
|
||||
fetchInitialData?(
|
||||
req: InitialFetchRequest<PathPropsT, QueryPropsT>,
|
||||
): Promise<DataT>;
|
||||
getQueryParams?(
|
||||
source: string | undefined,
|
||||
siteRes: GetSiteResponse,
|
||||
): QueryPropsT;
|
||||
component: Inferno.ComponentClass<
|
||||
RouteComponentProps<PathPropsT> & QueryPropsT
|
||||
>;
|
||||
}
|
||||
|
||||
export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [
|
||||
export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
|
||||
{
|
||||
path: `/`,
|
||||
component: Home,
|
||||
fetchInitialData: Home.fetchInitialData,
|
||||
exact: true,
|
||||
},
|
||||
getQueryParams: getHomeQueryParams,
|
||||
} as HomeFetchConfig,
|
||||
{
|
||||
path: `/login`,
|
||||
component: Login,
|
||||
},
|
||||
getQueryParams: getLoginQueryParams,
|
||||
} as LoginFetchConfig,
|
||||
{
|
||||
path: `/login_reset`,
|
||||
component: LoginReset,
|
||||
|
@ -52,7 +114,8 @@ export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [
|
|||
path: `/create_post`,
|
||||
component: CreatePost,
|
||||
fetchInitialData: CreatePost.fetchInitialData,
|
||||
},
|
||||
getQueryParams: getCreatePostQueryParams,
|
||||
} as CreatePostFetchConfig,
|
||||
{
|
||||
path: `/create_community`,
|
||||
component: CreateCommunity,
|
||||
|
@ -61,73 +124,79 @@ export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [
|
|||
path: `/create_private_message/:recipient_id`,
|
||||
component: CreatePrivateMessage,
|
||||
fetchInitialData: CreatePrivateMessage.fetchInitialData,
|
||||
},
|
||||
} as CreatePrivateMessageFetchConfig,
|
||||
{
|
||||
path: `/communities`,
|
||||
component: Communities,
|
||||
fetchInitialData: Communities.fetchInitialData,
|
||||
},
|
||||
getQueryParams: getCommunitiesQueryParams,
|
||||
} as CommunitiesFetchConfig,
|
||||
{
|
||||
path: `/post/:post_id`,
|
||||
component: Post,
|
||||
fetchInitialData: Post.fetchInitialData,
|
||||
},
|
||||
} as PostFetchConfig,
|
||||
{
|
||||
path: `/comment/:comment_id`,
|
||||
component: Post,
|
||||
fetchInitialData: Post.fetchInitialData,
|
||||
},
|
||||
} as PostFetchConfig,
|
||||
{
|
||||
path: `/c/:name`,
|
||||
component: Community,
|
||||
fetchInitialData: Community.fetchInitialData,
|
||||
},
|
||||
getQueryParams: getCommunityQueryParams,
|
||||
} as CommunityFetchConfig,
|
||||
{
|
||||
path: `/u/:username`,
|
||||
component: Profile,
|
||||
fetchInitialData: Profile.fetchInitialData,
|
||||
},
|
||||
getQueryParams: getProfileQueryParams,
|
||||
} as ProfileFetchConfig,
|
||||
{
|
||||
path: `/inbox`,
|
||||
component: Inbox,
|
||||
fetchInitialData: Inbox.fetchInitialData,
|
||||
},
|
||||
} as InboxFetchConfig,
|
||||
{
|
||||
path: `/settings`,
|
||||
component: Settings,
|
||||
fetchInitialData: Settings.fetchInitialData,
|
||||
},
|
||||
} as SettingsFetchConfig,
|
||||
{
|
||||
path: `/modlog/:communityId`,
|
||||
component: Modlog,
|
||||
fetchInitialData: Modlog.fetchInitialData,
|
||||
},
|
||||
getQueryParams: getModlogQueryParams,
|
||||
} as ModlogFetchConfig,
|
||||
{
|
||||
path: `/modlog`,
|
||||
component: Modlog,
|
||||
fetchInitialData: Modlog.fetchInitialData,
|
||||
},
|
||||
getQueryParams: getModlogQueryParams,
|
||||
} as ModlogFetchConfig,
|
||||
{ path: `/setup`, component: Setup },
|
||||
{
|
||||
path: `/admin`,
|
||||
component: AdminSettings,
|
||||
fetchInitialData: AdminSettings.fetchInitialData,
|
||||
},
|
||||
} as AdminSettingsFetchConfig,
|
||||
{
|
||||
path: `/reports`,
|
||||
component: Reports,
|
||||
fetchInitialData: Reports.fetchInitialData,
|
||||
},
|
||||
} as ReportsFetchConfig,
|
||||
{
|
||||
path: `/registration_applications`,
|
||||
component: RegistrationApplications,
|
||||
fetchInitialData: RegistrationApplications.fetchInitialData,
|
||||
},
|
||||
} as RegistrationApplicationsFetchConfig,
|
||||
{
|
||||
path: `/search`,
|
||||
component: Search,
|
||||
fetchInitialData: Search.fetchInitialData,
|
||||
},
|
||||
getQueryParams: getSearchQueryParams,
|
||||
} as SearchFetchConfig,
|
||||
{
|
||||
path: `/password_change/:token`,
|
||||
component: PasswordChange,
|
||||
|
@ -140,11 +209,12 @@ export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [
|
|||
path: `/instances`,
|
||||
component: Instances,
|
||||
fetchInitialData: Instances.fetchInitialData,
|
||||
},
|
||||
} as InstancesFetchConfig,
|
||||
{ path: `/legal`, component: Legal },
|
||||
{
|
||||
path: "/activitypub/externalInteraction",
|
||||
component: RemoteFetch,
|
||||
fetchInitialData: RemoteFetch.fetchInitialData,
|
||||
},
|
||||
getQueryParams: getRemoteFetchQueryParams,
|
||||
} as RemoteFetchFetchConfig,
|
||||
];
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { getQueryString } from "@utils/helpers";
|
||||
|
||||
export default function communityRSSUrl(actorId: string, sort: string): string {
|
||||
const url = new URL(actorId);
|
||||
return `${url.origin}/feeds${url.pathname}.xml?sort=${sort}`;
|
||||
return `${url.origin}/feeds${url.pathname}.xml${getQueryString({ sort })}`;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
export default function getCommentIdFromProps(props: any): number | undefined {
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
|
||||
export default function getCommentIdFromProps(
|
||||
props: Pick<RouteComponentProps<{ comment_id?: string }>, "match">,
|
||||
): number | undefined {
|
||||
const id = props.match.params.comment_id;
|
||||
return id ? Number(id) : undefined;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
export default function getIdFromProps(props: any): number | undefined {
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
|
||||
export default function getIdFromProps(
|
||||
props: Pick<RouteComponentProps<{ post_id?: string }>, "match">,
|
||||
): number | undefined {
|
||||
const id = props.match.params.post_id;
|
||||
return id ? Number(id) : undefined;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
export default function getRecipientIdFromProps(props: any): number {
|
||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||
|
||||
export default function getRecipientIdFromProps(
|
||||
props: Pick<RouteComponentProps<{ recipient_id: string }>, "match">,
|
||||
): number {
|
||||
return props.match.params.recipient_id
|
||||
? Number(props.match.params.recipient_id)
|
||||
: 1;
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
import { isBrowser } from "@utils/browser";
|
||||
type Empty = NonNullable<unknown>;
|
||||
|
||||
type QueryMapping<PropsT, FallbacksT extends Empty> = {
|
||||
[K in keyof PropsT]-?: (
|
||||
input: string | undefined,
|
||||
fallback: K extends keyof FallbacksT ? FallbacksT[K] : undefined,
|
||||
) => PropsT[K];
|
||||
};
|
||||
|
||||
export default function getQueryParams<
|
||||
T extends Record<string, any>,
|
||||
>(processors: {
|
||||
[K in keyof T]: (param: string) => T[K];
|
||||
}): T {
|
||||
if (isBrowser()) {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
PropsT,
|
||||
FallbacksT extends Empty = Empty,
|
||||
>(
|
||||
processors: QueryMapping<PropsT, FallbacksT>,
|
||||
source?: string,
|
||||
fallbacks: FallbacksT = {} as FallbacksT,
|
||||
): PropsT {
|
||||
const searchParams = new URLSearchParams(source);
|
||||
|
||||
return Array.from(Object.entries(processors)).reduce(
|
||||
(acc, [key, process]) => ({
|
||||
...acc,
|
||||
[key]: process(searchParams.get(key)),
|
||||
}),
|
||||
{} as T,
|
||||
const ret: Partial<PropsT> = {};
|
||||
for (const key in processors) {
|
||||
ret[key as string] = processors[key](
|
||||
searchParams.get(key) ?? undefined,
|
||||
fallbacks[key as string],
|
||||
);
|
||||
}
|
||||
|
||||
return {} as T;
|
||||
return ret as PropsT;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
export default function getQueryString<
|
||||
T extends Record<string, string | undefined>,
|
||||
>(obj: T) {
|
||||
return Object.entries(obj)
|
||||
const searchParams = new URLSearchParams();
|
||||
Object.entries(obj)
|
||||
.filter(([, val]) => val !== undefined && val !== null)
|
||||
.reduce(
|
||||
(acc, [key, val], index) => `${acc}${index > 0 ? "&" : ""}${key}=${val}`,
|
||||
"?",
|
||||
);
|
||||
.forEach(([key, val]) => searchParams.set(key, val ?? ""));
|
||||
if (searchParams.size) {
|
||||
return "?" + searchParams.toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue