"start": "yarn build:dev --watch"
},
"lint-staged": {
- "*.{ts,tsx,js}": ["prettier --write", "eslint --fix"],
- "*.{css, scss}": ["prettier --write"],
- "package.json": ["sortpack"]
+ "*.{ts,tsx,js}": [
+ "prettier --write",
+ "eslint --fix"
+ ],
+ "*.{css, scss}": [
+ "prettier --write"
+ ],
+ "package.json": [
+ "sortpack"
+ ]
},
"dependencies": {
"@babel/plugin-proposal-decorators": "^7.21.0",
ILemmyConfig,
InitialFetchRequest,
IsoDataOptionalSite,
+ RouteData,
} from "../shared/interfaces";
import { routes } from "../shared/routes";
-import { RequestState, wrapClient } from "../shared/services/HttpService";
+import { FailedRequestState, wrapClient } from "../shared/services/HttpService";
import {
ErrorPageData,
favIconPngUrl,
// This bypasses errors, so that the client can hit the error on its own,
// in order to remove the jwt on the browser. Necessary for wrong jwts
let site: GetSiteResponse | undefined = undefined;
- const routeData: RequestState<any>[] = [];
+ let routeData: RouteData = {};
let errorPageData: ErrorPageData | undefined = undefined;
let try_site = await client.getSite(getSiteForm);
if (try_site.state === "failed" && try_site.msg == "not_logged_in") {
return res.redirect("/setup");
}
- if (site) {
+ if (site && activeRoute?.fetchInitialData) {
const initialFetchReq: InitialFetchRequest = {
client,
auth,
site,
};
- if (activeRoute?.fetchInitialData) {
- routeData.push(
- ...(await Promise.all([
- ...activeRoute.fetchInitialData(initialFetchReq),
- ]))
- );
- }
+ routeData = await activeRoute.fetchInitialData(initialFetchReq);
}
} else if (try_site.state === "failed") {
errorPageData = getErrorPageData(new Error(try_site.msg), site);
}
+ const error = Object.values(routeData).find(
+ res => res.state === "failed"
+ ) as FailedRequestState | undefined;
+
// Redirect to the 404 if there's an API error
- if (routeData[0] && routeData[0].state === "failed") {
- const error = routeData[0].msg;
- console.error(error);
- if (error === "instance_is_private") {
+ if (error) {
+ console.error(error.msg);
+ if (error.msg === "instance_is_private") {
return res.redirect(`/signup`);
} else {
- errorPageData = getErrorPageData(new Error(error), site);
+ errorPageData = getErrorPageData(new Error(error.msg), site);
}
}
import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
+ RouteDataResponse,
editCommunity,
getPageFromString,
getQueryParams,
const communityLimit = 50;
+type CommunitiesData = RouteDataResponse<{
+ listCommunitiesResponse: ListCommunitiesResponse;
+}>;
+
interface CommunitiesState {
listCommunitiesResponse: RequestState<ListCommunitiesResponse>;
siteRes: GetSiteResponse;
}
export class Communities extends Component<any, CommunitiesState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<CommunitiesData>(this.context);
state: CommunitiesState = {
listCommunitiesResponse: { state: "empty" },
siteRes: this.isoData.site_res,
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
+ const { listCommunitiesResponse } = this.isoData.routeData;
+
this.state = {
...this.state,
- listCommunitiesResponse: this.isoData.routeData[0],
+ listCommunitiesResponse,
isIsomorphic: true,
};
}
i.context.router.history.push(`/search?q=${searchParamEncoded}`);
}
- static fetchInitialData({
+ static async fetchInitialData({
query: { listingType, page },
client,
auth,
- }: InitialFetchRequest<QueryParams<CommunitiesProps>>): Promise<
- RequestState<any>
- >[] {
+ }: InitialFetchRequest<
+ QueryParams<CommunitiesProps>
+ >): Promise<CommunitiesData> {
const listCommunitiesForm: ListCommunities = {
type_: getListingTypeFromQuery(listingType),
sort: "TopMonth",
auth: auth,
};
- return [client.listCommunities(listCommunitiesForm)];
+ return {
+ listCommunitiesResponse: await client.listCommunities(
+ listCommunitiesForm
+ ),
+ };
}
getCommunitiesQueryParams() {
import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
+ RouteDataResponse,
commentsToFlatNodes,
communityRSSUrl,
editComment,
import { PostListings } from "../post/post-listings";
import { CommunityLink } from "./community-link";
+type CommunityData = RouteDataResponse<{
+ communityRes: GetCommunityResponse;
+ postsRes: GetPostsResponse;
+ commentsRes: GetCommentsResponse;
+}>;
+
interface State {
communityRes: RequestState<GetCommunityResponse>;
postsRes: RequestState<GetPostsResponse>;
RouteComponentProps<{ name: string }>,
State
> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<CommunityData>(this.context);
state: State = {
communityRes: { state: "empty" },
postsRes: { state: "empty" },
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [communityRes, postsRes, commentsRes] = this.isoData.routeData;
+ const { communityRes, commentsRes, postsRes } = this.isoData.routeData;
+
this.state = {
...this.state,
+ isIsomorphic: true,
+ commentsRes,
communityRes,
postsRes,
- commentsRes,
- isIsomorphic: true,
};
}
}
saveScrollPosition(this.context);
}
- static fetchInitialData({
+ static async fetchInitialData({
client,
path,
query: { dataType: urlDataType, page: urlPage, sort: urlSort },
auth,
}: InitialFetchRequest<QueryParams<CommunityProps>>): Promise<
- RequestState<any>
- >[] {
+ Promise<CommunityData>
+ > {
const pathSplit = path.split("/");
- const promises: Promise<RequestState<any>>[] = [];
const communityName = pathSplit[2];
const communityForm: GetCommunity = {
name: communityName,
auth,
};
- promises.push(client.getCommunity(communityForm));
const dataType = getDataTypeFromQuery(urlDataType);
const page = getPageFromString(urlPage);
+ let postsResponse: RequestState<GetPostsResponse> = { state: "empty" };
+ let commentsResponse: RequestState<GetCommentsResponse> = {
+ state: "empty",
+ };
+
if (dataType === DataType.Post) {
const getPostsForm: GetPosts = {
community_name: communityName,
saved_only: false,
auth,
};
- promises.push(client.getPosts(getPostsForm));
- promises.push(Promise.resolve({ state: "empty" }));
+
+ postsResponse = await client.getPosts(getPostsForm);
} else {
const getCommentsForm: GetComments = {
community_name: communityName,
saved_only: false,
auth,
};
- promises.push(Promise.resolve({ state: "empty" }));
- promises.push(client.getComments(getCommentsForm));
+
+ commentsResponse = await client.getComments(getCommentsForm);
}
- return promises;
+ return {
+ communityRes: await client.getCommunity(communityForm),
+ commentsRes: commentsResponse,
+ postsRes: postsResponse,
+ };
}
get documentTitle(): string {
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
import {
+ RouteDataResponse,
capitalizeFirstLetter,
fetchThemeList,
myAuthRequired,
import { SiteForm } from "./site-form";
import { TaglineForm } from "./tagline-form";
+type AdminSettingsData = RouteDataResponse<{
+ bannedRes: BannedPersonsResponse;
+ instancesRes: GetFederatedInstancesResponse;
+}>;
+
interface AdminSettingsState {
siteRes: GetSiteResponse;
banned: PersonView[];
}
export class AdminSettings extends Component<any, AdminSettingsState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<AdminSettingsData>(this.context);
state: AdminSettingsState = {
siteRes: this.isoData.site_res,
banned: [],
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [bannedRes, instancesRes] = this.isoData.routeData;
+ const { bannedRes, instancesRes } = this.isoData.routeData;
+
this.state = {
...this.state,
bannedRes,
}
}
- async fetchData() {
- this.setState({
- bannedRes: { state: "loading" },
- instancesRes: { state: "loading" },
- themeList: [],
- loading: true,
- });
-
- const auth = myAuthRequired();
-
- const [bannedRes, instancesRes, themeList] = await Promise.all([
- HttpService.client.getBannedPersons({ auth }),
- HttpService.client.getFederatedInstances({ auth }),
- fetchThemeList(),
- ]);
-
- this.setState({
- bannedRes,
- instancesRes,
- themeList,
- loading: false,
- });
- }
-
- static fetchInitialData({
+ static async fetchInitialData({
auth,
client,
- }: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<RequestState<any>>[] = [];
-
- if (auth) {
- promises.push(client.getBannedPersons({ auth }));
- promises.push(client.getFederatedInstances({ auth }));
- } else {
- promises.push(
- Promise.resolve({ state: "empty" }),
- Promise.resolve({ state: "empty" })
- );
- }
-
- return promises;
+ }: InitialFetchRequest): Promise<AdminSettingsData> {
+ return {
+ bannedRes: await client.getBannedPersons({
+ auth: auth as string,
+ }),
+ instancesRes: await client.getFederatedInstances({
+ auth: auth as string,
+ }),
+ };
}
async componentDidMount() {
);
}
+ async fetchData() {
+ this.setState({
+ bannedRes: { state: "loading" },
+ instancesRes: { state: "loading" },
+ themeList: [],
+ });
+
+ const auth = myAuthRequired();
+
+ const [bannedRes, instancesRes, themeList] = await Promise.all([
+ HttpService.client.getBannedPersons({ auth }),
+ HttpService.client.getFederatedInstances({ auth }),
+ fetchThemeList(),
+ ]);
+
+ this.setState({
+ bannedRes,
+ instancesRes,
+ themeList,
+ });
+ }
+
admins() {
return (
<>
QueryParams,
relTags,
restoreScrollPosition,
+ RouteDataResponse,
saveScrollPosition,
setIsoData,
setupTippy,
page: number;
}
+type HomeData = RouteDataResponse<{
+ postsRes: GetPostsResponse;
+ commentsRes: GetCommentsResponse;
+ trendingCommunitiesRes: ListCommunitiesResponse;
+}>;
+
+function getRss(listingType: ListingType) {
+ const { sort } = getHomeQueryParams();
+ const auth = myAuth();
+
+ let rss: string | undefined = undefined;
+
+ switch (listingType) {
+ case "All": {
+ rss = `/feeds/all.xml?sort=${sort}`;
+ break;
+ }
+ case "Local": {
+ rss = `/feeds/local.xml?sort=${sort}`;
+ break;
+ }
+ case "Subscribed": {
+ rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined;
+ break;
+ }
+ }
+
+ return (
+ rss && (
+ <>
+ <a href={rss} rel={relTags} title="RSS">
+ <Icon icon="rss" classes="text-muted small" />
+ </a>
+ <link rel="alternate" type="application/atom+xml" href={rss} />
+ </>
+ )
+ );
+}
+
function getDataTypeFromQuery(type?: string): DataType {
return type ? DataType[type] : DataType.Post;
}
);
export class Home extends Component<any, HomeState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<HomeData>(this.context);
state: HomeState = {
postsRes: { state: "empty" },
commentsRes: { state: "empty" },
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [postsRes, commentsRes, trendingCommunitiesRes] =
+ const { trendingCommunitiesRes, commentsRes, postsRes } =
this.isoData.routeData;
this.state = {
...this.state,
- postsRes,
- commentsRes,
trendingCommunitiesRes,
+ commentsRes,
+ postsRes,
tagline: getRandomFromList(this.state?.siteRes?.taglines ?? [])
?.content,
isIsomorphic: true,
}
async componentDidMount() {
- if (!this.state.isIsomorphic || !this.isoData.routeData.length) {
+ if (
+ !this.state.isIsomorphic ||
+ !Object.values(this.isoData.routeData).some(
+ res => res.state === "success" || res.state === "failed"
+ )
+ ) {
await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]);
}
saveScrollPosition(this.context);
}
- static fetchInitialData({
+ static async fetchInitialData({
client,
auth,
query: { dataType: urlDataType, listingType, page: urlPage, sort: urlSort },
- }: InitialFetchRequest<QueryParams<HomeProps>>): Promise<
- RequestState<any>
- >[] {
+ }: InitialFetchRequest<QueryParams<HomeProps>>): Promise<HomeData> {
const dataType = getDataTypeFromQuery(urlDataType);
// TODO figure out auth default_listingType, default_sort_type
const page = urlPage ? Number(urlPage) : 1;
- const promises: Promise<RequestState<any>>[] = [];
+ let postsRes: RequestState<GetPostsResponse> = { state: "empty" };
+ let commentsRes: RequestState<GetCommentsResponse> = {
+ state: "empty",
+ };
if (dataType === DataType.Post) {
const getPostsForm: GetPosts = {
auth,
};
- promises.push(client.getPosts(getPostsForm));
- promises.push(Promise.resolve({ state: "empty" }));
+ postsRes = await client.getPosts(getPostsForm);
} else {
const getCommentsForm: GetComments = {
page,
saved_only: false,
auth,
};
- promises.push(Promise.resolve({ state: "empty" }));
- promises.push(client.getComments(getCommentsForm));
+
+ commentsRes = await client.getComments(getCommentsForm);
}
const trendingCommunitiesForm: ListCommunities = {
limit: trendingFetchLimit,
auth,
};
- promises.push(client.listCommunities(trendingCommunitiesForm));
- return promises;
+ return {
+ trendingCommunitiesRes: await client.listCommunities(
+ trendingCommunitiesForm
+ ),
+ commentsRes,
+ postsRes,
+ };
}
get documentTitle(): string {
></div>
)}
<div className="d-block d-md-none">{this.mobileView}</div>
- {this.posts()}
+ {this.posts}
</main>
<aside className="d-none d-md-block col-md-4">
{this.mySidebar}
await this.fetchData();
}
- posts() {
+ get posts() {
const { page } = getHomeQueryParams();
return (
const siteRes = this.state.siteRes;
if (dataType === DataType.Post) {
- switch (this.state.postsRes?.state) {
+ switch (this.state.postsRes.state) {
case "loading":
return (
<h5>
<span className="mr-2">
<SortSelect sort={sort} onChange={this.handleSortChange} />
</span>
- {this.getRss(listingType)}
+ {getRss(listingType)}
</div>
);
}
- getRss(listingType: ListingType) {
- const { sort } = getHomeQueryParams();
- const auth = myAuth();
-
- let rss: string | undefined = undefined;
-
- switch (listingType) {
- case "All": {
- rss = `/feeds/all.xml?sort=${sort}`;
- break;
- }
- case "Local": {
- rss = `/feeds/local.xml?sort=${sort}`;
- break;
- }
- case "Subscribed": {
- rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined;
- break;
- }
- }
-
- return (
- rss && (
- <>
- <a href={rss} rel={relTags} title="RSS">
- <Icon icon="rss" classes="text-muted small" />
- </a>
- <link rel="alternate" type="application/atom+xml" href={rss} />
- </>
- )
- );
- }
-
async fetchTrendingCommunities() {
this.setState({ trendingCommunitiesRes: { state: "loading" } });
this.setState({
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
-import { relTags, setIsoData } from "../../utils";
+import { RouteDataResponse, relTags, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
+type InstancesData = RouteDataResponse<{
+ federatedInstancesResponse: GetFederatedInstancesResponse;
+}>;
+
interface InstancesState {
instancesRes: RequestState<GetFederatedInstancesResponse>;
siteRes: GetSiteResponse;
}
export class Instances extends Component<any, InstancesState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<InstancesData>(this.context);
state: InstancesState = {
instancesRes: { state: "empty" },
siteRes: this.isoData.site_res,
if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- instancesRes: this.isoData.routeData[0],
+ instancesRes: this.isoData.routeData.federatedInstancesResponse,
isIsomorphic: true,
};
}
});
}
- static fetchInitialData(
- req: InitialFetchRequest
- ): Promise<RequestState<any>>[] {
- return [req.client.getFederatedInstances({})];
+ static async fetchInitialData({
+ client,
+ }: InitialFetchRequest): Promise<InstancesData> {
+ return {
+ federatedInstancesResponse: await client.getFederatedInstances({}),
+ };
}
get documentTitle(): string {
GetModlog,
GetModlogResponse,
GetPersonDetails,
+ GetPersonDetailsResponse,
ModAddCommunityView,
ModAddView,
ModBanFromCommunityView,
import {
Choice,
QueryParams,
+ RouteDataResponse,
amAdmin,
amMod,
debounce,
| AdminPurgePostView
| AdminPurgeCommentView;
+type ModlogData = RouteDataResponse<{
+ res: GetModlogResponse;
+ communityRes: GetCommunityResponse;
+ modUserResponse: GetPersonDetailsResponse;
+ userResponse: GetPersonDetailsResponse;
+}>;
+
interface ModlogType {
id: number;
type_: ModlogActionType;
RouteComponentProps<{ communityId?: string }>,
ModlogState
> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<ModlogData>(this.context);
state: ModlogState = {
res: { state: "empty" },
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [res, communityRes, filteredModRes, filteredUserRes] =
+ const { res, communityRes, modUserResponse, userResponse } =
this.isoData.routeData;
+
this.state = {
...this.state,
res,
communityRes,
};
- if (filteredModRes.state === "success") {
+ if (modUserResponse.state === "success") {
this.state = {
...this.state,
- modSearchOptions: [personToChoice(filteredModRes.data.person_view)],
+ modSearchOptions: [personToChoice(modUserResponse.data.person_view)],
};
}
- if (filteredUserRes.state === "success") {
+ if (userResponse.state === "success") {
this.state = {
...this.state,
- userSearchOptions: [personToChoice(filteredUserRes.data.person_view)],
+ userSearchOptions: [personToChoice(userResponse.data.person_view)],
};
}
}
}
}
- static fetchInitialData({
+ static async fetchInitialData({
client,
path,
query: { modId: urlModId, page, userId: urlUserId, actionType },
auth,
site,
- }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<
- RequestState<any>
- >[] {
+ }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<ModlogData> {
const pathSplit = path.split("/");
- const promises: Promise<RequestState<any>>[] = [];
const communityId = getIdFromString(pathSplit[2]);
const modId = !site.site_view.local_site.hide_modlog_mod_names
? getIdFromString(urlModId)
auth,
};
- promises.push(client.getModlog(modlogForm));
+ let communityResponse: RequestState<GetCommunityResponse> = {
+ state: "empty",
+ };
if (communityId) {
const communityForm: GetCommunity = {
id: communityId,
auth,
};
- promises.push(client.getCommunity(communityForm));
- } else {
- promises.push(Promise.resolve({ state: "empty" }));
+
+ communityResponse = await client.getCommunity(communityForm);
}
+ let modUserResponse: RequestState<GetPersonDetailsResponse> = {
+ state: "empty",
+ };
+
if (modId) {
const getPersonForm: GetPersonDetails = {
person_id: modId,
auth,
};
- promises.push(client.getPersonDetails(getPersonForm));
- } else {
- promises.push(Promise.resolve({ state: "empty" }));
+ modUserResponse = await client.getPersonDetails(getPersonForm);
}
+ let userResponse: RequestState<GetPersonDetailsResponse> = {
+ state: "empty",
+ };
+
if (userId) {
const getPersonForm: GetPersonDetails = {
person_id: userId,
auth,
};
- promises.push(client.getPersonDetails(getPersonForm));
- } else {
- promises.push(Promise.resolve({ state: "empty" }));
+ userResponse = await client.getPersonDetails(getPersonForm);
}
- return promises;
+ return {
+ res: await client.getModlog(modlogForm),
+ communityRes: communityResponse,
+ modUserResponse,
+ userResponse,
+ };
}
}
DistinguishComment,
EditComment,
EditPrivateMessage,
- GetPersonMentions,
GetPersonMentionsResponse,
- GetPrivateMessages,
- GetReplies,
GetRepliesResponse,
GetSiteResponse,
MarkCommentReplyAsRead,
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
import {
+ RouteDataResponse,
commentsToFlatNodes,
editCommentReply,
editMention,
Mention,
Message,
}
+
+type InboxData = RouteDataResponse<{
+ repliesRes: GetRepliesResponse;
+ mentionsRes: GetPersonMentionsResponse;
+ messagesRes: PrivateMessagesResponse;
+}>;
+
type ReplyType = {
id: number;
type_: ReplyEnum;
}
export class Inbox extends Component<any, InboxState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<InboxData>(this.context);
state: InboxState = {
unreadOrAll: UnreadOrAll.Unread,
messageType: MessageType.All,
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [repliesRes, mentionsRes, messagesRes] = this.isoData.routeData;
+ const { mentionsRes, messagesRes, repliesRes } = this.isoData.routeData;
this.state = {
...this.state,
await i.refetch();
}
- static fetchInitialData({
+ static async fetchInitialData({
client,
auth,
- }: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<RequestState<any>>[] = [];
-
+ }: InitialFetchRequest): Promise<InboxData> {
const sort: CommentSortType = "New";
- if (auth) {
- // It can be /u/me, or /username/1
- const repliesForm: GetReplies = {
- sort,
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- auth,
- };
- promises.push(client.getReplies(repliesForm));
-
- const personMentionsForm: GetPersonMentions = {
- sort,
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- auth,
- };
- promises.push(client.getPersonMentions(personMentionsForm));
-
- const privateMessagesForm: GetPrivateMessages = {
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- auth,
- };
- promises.push(client.getPrivateMessages(privateMessagesForm));
- } else {
- promises.push(
- Promise.resolve({ state: "empty" }),
- Promise.resolve({ state: "empty" }),
- Promise.resolve({ state: "empty" })
- );
- }
-
- return promises;
+ return {
+ mentionsRes: auth
+ ? await client.getPersonMentions({
+ sort,
+ unread_only: true,
+ page: 1,
+ limit: fetchLimit,
+ auth,
+ })
+ : { state: "empty" },
+ messagesRes: auth
+ ? await client.getPrivateMessages({
+ unread_only: true,
+ page: 1,
+ limit: fetchLimit,
+ auth,
+ })
+ : { state: "empty" },
+ repliesRes: auth
+ ? await client.getReplies({
+ sort,
+ unread_only: true,
+ page: 1,
+ limit: fetchLimit,
+ auth,
+ })
+ : { state: "empty" },
+ };
}
async refetch() {
import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
+ RouteDataResponse,
canMod,
capitalizeFirstLetter,
editComment,
import { PersonDetails } from "./person-details";
import { PersonListing } from "./person-listing";
+type ProfileData = RouteDataResponse<{
+ personResponse: GetPersonDetailsResponse;
+}>;
+
interface ProfileState {
personRes: RequestState<GetPersonDetailsResponse>;
personBlocked: boolean;
RouteComponentProps<{ username: string }>,
ProfileState
> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<ProfileData>(this.context);
state: ProfileState = {
personRes: { state: "empty" },
personBlocked: false,
if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- personRes: this.isoData.routeData[0],
+ personRes: this.isoData.routeData.personResponse,
isIsomorphic: true,
};
}
}
}
- static fetchInitialData({
+ static async fetchInitialData({
client,
path,
query: { page, sort, view: urlView },
auth,
- }: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<
- RequestState<any>
- >[] {
+ }: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<ProfileData> {
const pathSplit = path.split("/");
const username = pathSplit[2];
auth,
};
- return [client.getPersonDetails(form)];
+ return {
+ personResponse: await client.getPersonDetails(form),
+ };
}
get documentTitle(): string {
import {
ApproveRegistrationApplication,
GetSiteResponse,
- ListRegistrationApplications,
ListRegistrationApplicationsResponse,
RegistrationApplicationView,
} from "lemmy-js-client";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
import {
+ RouteDataResponse,
editRegistrationApplication,
fetchLimit,
myAuthRequired,
All,
}
+type RegistrationApplicationsData = RouteDataResponse<{
+ listRegistrationApplicationsResponse: ListRegistrationApplicationsResponse;
+}>;
+
interface RegistrationApplicationsState {
appsRes: RequestState<ListRegistrationApplicationsResponse>;
siteRes: GetSiteResponse;
any,
RegistrationApplicationsState
> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<RegistrationApplicationsData>(this.context);
state: RegistrationApplicationsState = {
appsRes: { state: "empty" },
siteRes: this.isoData.site_res,
if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- appsRes: this.isoData.routeData[0],
+ appsRes: this.isoData.routeData.listRegistrationApplicationsResponse,
isIsomorphic: true,
};
}
this.refetch();
}
- static fetchInitialData({
+ static async fetchInitialData({
auth,
client,
- }: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<RequestState<any>>[] = [];
-
- if (auth) {
- const form: ListRegistrationApplications = {
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- auth,
- };
- promises.push(client.listRegistrationApplications(form));
- } else {
- promises.push(Promise.resolve({ state: "empty" }));
- }
-
- return promises;
+ }: InitialFetchRequest): Promise<RegistrationApplicationsData> {
+ return {
+ listRegistrationApplicationsResponse: auth
+ ? await client.listRegistrationApplications({
+ unread_only: true,
+ page: 1,
+ limit: fetchLimit,
+ auth: auth as string,
+ })
+ : { state: "empty" },
+ };
}
async refetch() {
import { FirstLoadService } from "../../services/FirstLoadService";
import { RequestState } from "../../services/HttpService";
import {
+ RouteDataResponse,
amAdmin,
editCommentReport,
editPostReport,
PrivateMessageReport,
}
+type ReportsData = RouteDataResponse<{
+ commentReportsRes: ListCommentReportsResponse;
+ postReportsRes: ListPostReportsResponse;
+ messageReportsRes: ListPrivateMessageReportsResponse;
+}>;
+
type ItemType = {
id: number;
type_: MessageEnum;
}
export class Reports extends Component<any, ReportsState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<ReportsData>(this.context);
state: ReportsState = {
commentReportsRes: { state: "empty" },
postReportsRes: { state: "empty" },
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [commentReportsRes, postReportsRes, messageReportsRes] =
+ const { commentReportsRes, postReportsRes, messageReportsRes } =
this.isoData.routeData;
+
this.state = {
...this.state,
commentReportsRes,
if (amAdmin()) {
this.state = {
...this.state,
- messageReportsRes,
+ messageReportsRes: messageReportsRes,
};
}
}
await i.refetch();
}
- static fetchInitialData({
+ static async fetchInitialData({
auth,
client,
- }: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<RequestState<any>>[] = [];
-
+ }: InitialFetchRequest): Promise<ReportsData> {
const unresolved_only = true;
const page = 1;
const limit = fetchLimit;
- if (auth) {
- const commentReportsForm: ListCommentReports = {
- unresolved_only,
- page,
- limit,
- auth,
- };
- promises.push(client.listCommentReports(commentReportsForm));
+ const commentReportsForm: ListCommentReports = {
+ unresolved_only,
+ page,
+ limit,
+ auth: auth as string,
+ };
- const postReportsForm: ListPostReports = {
+ const postReportsForm: ListPostReports = {
+ unresolved_only,
+ page,
+ limit,
+ auth: auth as string,
+ };
+
+ const data: ReportsData = {
+ commentReportsRes: await client.listCommentReports(commentReportsForm),
+ postReportsRes: await client.listPostReports(postReportsForm),
+ messageReportsRes: { state: "empty" },
+ };
+
+ if (amAdmin()) {
+ const privateMessageReportsForm: ListPrivateMessageReports = {
unresolved_only,
page,
limit,
- auth,
+ auth: auth as string,
};
- promises.push(client.listPostReports(postReportsForm));
- if (amAdmin()) {
- const privateMessageReportsForm: ListPrivateMessageReports = {
- unresolved_only,
- page,
- limit,
- auth,
- };
- promises.push(
- client.listPrivateMessageReports(privateMessageReportsForm)
- );
- } else {
- promises.push(Promise.resolve({ state: "empty" }));
- }
- } else {
- promises.push(
- Promise.resolve({ state: "empty" }),
- Promise.resolve({ state: "empty" }),
- Promise.resolve({ state: "empty" })
+ data.messageReportsRes = await client.listPrivateMessageReports(
+ privateMessageReportsForm
);
}
- return promises;
+ return data;
}
async refetch() {
import {
CreatePost as CreatePostI,
GetCommunity,
+ GetCommunityResponse,
GetSiteResponse,
ListCommunitiesResponse,
} from "lemmy-js-client";
import {
Choice,
QueryParams,
+ RouteDataResponse,
enableDownvotes,
enableNsfw,
getIdFromString,
communityId?: number;
}
+type CreatePostData = RouteDataResponse<{
+ communityResponse: GetCommunityResponse;
+ initialCommunitiesRes: ListCommunitiesResponse;
+}>;
+
function getCreatePostQueryParams() {
return getQueryParams<CreatePostProps>({
communityId: getIdFromString,
RouteComponentProps<Record<string, never>>,
CreatePostState
> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<CreatePostData>(this.context);
state: CreatePostState = {
siteRes: this.isoData.site_res,
loading: true,
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [communityRes, listCommunitiesRes] = this.isoData.routeData;
+ const { communityResponse: communityRes, initialCommunitiesRes } =
+ this.isoData.routeData;
+
+ this.state = {
+ ...this.state,
+ loading: false,
+ initialCommunitiesRes,
+ isIsomorphic: true,
+ };
if (communityRes?.state === "success") {
const communityChoice: Choice = {
selectedCommunityChoice: communityChoice,
};
}
-
- this.state = {
- ...this.state,
- loading: false,
- initialCommunitiesRes: listCommunitiesRes,
- isIsomorphic: true,
- };
}
}
}
}
- static fetchInitialData({
+ static async fetchInitialData({
client,
query: { communityId },
auth,
- }: InitialFetchRequest<QueryParams<CreatePostProps>>): Promise<
- RequestState<any>
- >[] {
- const promises: Promise<RequestState<any>>[] = [];
+ }: InitialFetchRequest<
+ QueryParams<CreatePostProps>
+ >): Promise<CreatePostData> {
+ const data: CreatePostData = {
+ initialCommunitiesRes: await fetchCommunitiesForOptions(client),
+ communityResponse: { state: "empty" },
+ };
if (communityId) {
const form: GetCommunity = {
id: getIdFromString(communityId),
};
- promises.push(client.getCommunity(form));
- } else {
- promises.push(Promise.resolve({ state: "empty" }));
+ data.communityResponse = await client.getCommunity(form);
}
- promises.push(fetchCommunitiesForOptions(client));
-
- return promises;
+ return data;
}
}
isImage,
myAuth,
restoreScrollPosition,
+ RouteDataResponse,
saveScrollPosition,
setIsoData,
setupTippy,
const commentsShownInterval = 15;
+type PostData = RouteDataResponse<{
+ postRes: GetPostResponse;
+ commentsRes: GetCommentsResponse;
+}>;
+
interface PostState {
postId?: number;
commentId?: number;
}
export class Post extends Component<any, PostState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<PostData>(this.context);
private commentScrollDebounced: () => void;
state: PostState = {
postRes: { state: "empty" },
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [postRes, commentsRes] = this.isoData.routeData;
+ const { commentsRes, postRes } = this.isoData.routeData;
this.state = {
...this.state,
}
}
- static fetchInitialData({
- auth,
+ static async fetchInitialData({
client,
path,
- }: InitialFetchRequest): Promise<any>[] {
+ auth,
+ }: InitialFetchRequest): Promise<PostData> {
const pathSplit = path.split("/");
- const promises: Promise<RequestState<any>>[] = [];
const pathType = pathSplit.at(1);
const id = pathSplit.at(2) ? Number(pathSplit.at(2)) : undefined;
commentsForm.parent_id = id;
}
- promises.push(client.getPost(postForm));
- promises.push(client.getComments(commentsForm));
-
- return promises;
+ return {
+ postRes: await client.getPost(postForm),
+ commentsRes: await client.getComments(commentsForm),
+ };
}
componentWillUnmount() {
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
import {
+ RouteDataResponse,
getRecipientIdFromProps,
myAuth,
setIsoData,
import { Spinner } from "../common/icon";
import { PrivateMessageForm } from "./private-message-form";
+type CreatePrivateMessageData = RouteDataResponse<{
+ recipientDetailsResponse: GetPersonDetailsResponse;
+}>;
+
interface CreatePrivateMessageState {
siteRes: GetSiteResponse;
recipientRes: RequestState<GetPersonDetailsResponse>;
any,
CreatePrivateMessageState
> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<CreatePrivateMessageData>(this.context);
state: CreatePrivateMessageState = {
siteRes: this.isoData.site_res,
recipientRes: { state: "empty" },
if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- recipientRes: this.isoData.routeData[0],
+ recipientRes: this.isoData.routeData.recipientDetailsResponse,
isIsomorphic: true,
};
}
}
}
+ static async fetchInitialData({
+ client,
+ path,
+ auth,
+ }: InitialFetchRequest): Promise<CreatePrivateMessageData> {
+ const person_id = Number(path.split("/").pop());
+
+ const form: GetPersonDetails = {
+ person_id,
+ sort: "New",
+ saved_only: false,
+ auth,
+ };
+
+ return {
+ recipientDetailsResponse: await client.getPersonDetails(form),
+ };
+ }
+
async fetchPersonDetails() {
this.setState({
recipientRes: { state: "loading" },
});
}
- static fetchInitialData(
- req: InitialFetchRequest
- ): Promise<RequestState<any>>[] {
- const person_id = Number(req.path.split("/").pop());
- const form: GetPersonDetails = {
- person_id,
- sort: "New",
- saved_only: false,
- auth: req.auth,
- };
- return [req.client.getPersonDetails(form)];
- }
-
get documentTitle(): string {
if (this.state.recipientRes.state == "success") {
const name_ = this.state.recipientRes.data.person_view.person.name;
import {
Choice,
QueryParams,
+ RouteDataResponse,
capitalizeFirstLetter,
commentsToFlatNodes,
communityToChoice,
page: number;
}
+type SearchData = RouteDataResponse<{
+ communityResponse: GetCommunityResponse;
+ listCommunitiesResponse: ListCommunitiesResponse;
+ creatorDetailsResponse: GetPersonDetailsResponse;
+ searchResponse: SearchResponse;
+ resolveObjectResponse: ResolveObjectResponse;
+}>;
+
type FilterType = "creator" | "community";
interface SearchState {
}
export class Search extends Component<any, SearchState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData<SearchData>(this.context);
+
state: SearchState = {
resolveObjectRes: { state: "empty" },
creatorDetailsRes: { state: "empty" },
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
- const [
- communityRes,
- communitiesRes,
- creatorDetailsRes,
- searchRes,
- resolveObjectRes,
- ] = this.isoData.routeData;
+ const {
+ communityResponse: communityRes,
+ creatorDetailsResponse: creatorDetailsRes,
+ listCommunitiesResponse: communitiesRes,
+ resolveObjectResponse: resolveObjectRes,
+ searchResponse: searchRes,
+ } = this.isoData.routeData;
this.state = {
...this.state,
- communitiesRes,
- communityRes,
- creatorDetailsRes,
- creatorSearchOptions:
- creatorDetailsRes.state == "success"
- ? [personToChoice(creatorDetailsRes.data.person_view)]
- : [],
isIsomorphic: true,
};
- if (communityRes.state === "success") {
+ if (creatorDetailsRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ creatorSearchOptions:
+ creatorDetailsRes?.state === "success"
+ ? [personToChoice(creatorDetailsRes.data.person_view)]
+ : [],
+ creatorDetailsRes,
+ };
+ }
+
+ if (communitiesRes?.state === "success") {
this.state = {
...this.state,
- communitySearchOptions: [
- communityToChoice(communityRes.data.community_view),
- ],
+ communitiesRes,
};
}
- if (q) {
+ if (communityRes?.state === "success") {
this.state = {
...this.state,
- searchRes,
- resolveObjectRes,
+ communityRes,
};
}
+
+ if (q !== "") {
+ this.state = {
+ ...this.state,
+ };
+
+ if (searchRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ searchRes,
+ };
+ }
+
+ if (resolveObjectRes?.state === "success") {
+ this.state = {
+ ...this.state,
+ resolveObjectRes,
+ };
+ }
+ }
}
}
saveScrollPosition(this.context);
}
- static fetchInitialData({
+ static async fetchInitialData({
client,
auth,
query: { communityId, creatorId, q, type, sort, listingType, page },
- }: InitialFetchRequest<QueryParams<SearchProps>>): Promise<
- RequestState<any>
- >[] {
- const promises: Promise<RequestState<any>>[] = [];
-
+ }: InitialFetchRequest<QueryParams<SearchProps>>): Promise<SearchData> {
const community_id = getIdFromString(communityId);
+ let communityResponse: RequestState<GetCommunityResponse> = {
+ state: "empty",
+ };
+ let listCommunitiesResponse: RequestState<ListCommunitiesResponse> = {
+ state: "empty",
+ };
if (community_id) {
const getCommunityForm: GetCommunity = {
id: community_id,
auth,
};
- promises.push(client.getCommunity(getCommunityForm));
- promises.push(Promise.resolve({ state: "empty" }));
+
+ communityResponse = await client.getCommunity(getCommunityForm);
} else {
const listCommunitiesForm: ListCommunities = {
type_: defaultListingType,
limit: fetchLimit,
auth,
};
- promises.push(Promise.resolve({ state: "empty" }));
- promises.push(client.listCommunities(listCommunitiesForm));
+
+ listCommunitiesResponse = await client.listCommunities(
+ listCommunitiesForm
+ );
}
const creator_id = getIdFromString(creatorId);
+ let creatorDetailsResponse: RequestState<GetPersonDetailsResponse> = {
+ state: "empty",
+ };
if (creator_id) {
const getCreatorForm: GetPersonDetails = {
person_id: creator_id,
auth,
};
- promises.push(client.getPersonDetails(getCreatorForm));
- } else {
- promises.push(Promise.resolve({ state: "empty" }));
+
+ creatorDetailsResponse = await client.getPersonDetails(getCreatorForm);
}
const query = getSearchQueryFromQuery(q);
+ let searchResponse: RequestState<SearchResponse> = { state: "empty" };
+ let resolveObjectResponse: RequestState<ResolveObjectResponse> = {
+ state: "empty",
+ };
+
if (query) {
const form: SearchForm = {
q: query,
};
if (query !== "") {
- promises.push(client.search(form));
+ searchResponse = await client.search(form);
if (auth) {
const resolveObjectForm: ResolveObject = {
q: query,
auth,
};
- promises.push(client.resolveObject(resolveObjectForm));
+ resolveObjectResponse = await client.resolveObject(resolveObjectForm);
}
- } else {
- promises.push(Promise.resolve({ state: "empty" }));
- promises.push(Promise.resolve({ state: "empty" }));
}
}
- return promises;
+ return {
+ communityResponse,
+ creatorDetailsResponse,
+ listCommunitiesResponse,
+ resolveObjectResponse,
+ searchResponse,
+ };
}
get documentTitle(): string {
minLength={1}
/>
<button type="submit" className="btn btn-secondary mr-2 mb-2">
- {this.state.searchRes.state == "loading" ? (
+ {this.state.searchRes.state === "loading" ? (
<Spinner />
) : (
<span>{i18n.t("search")}</span>
/**
* This contains serialized data, it needs to be deserialized before use.
*/
-export interface IsoData {
+export interface IsoData<T extends RouteData = any> {
path: string;
- routeData: RequestState<any>[];
+ routeData: T;
site_res: GetSiteResponse;
errorPageData?: ErrorPageData;
}
-export type IsoDataOptionalSite = Partial<IsoData> &
- Pick<IsoData, Exclude<keyof IsoData, "site_res">>;
+export type IsoDataOptionalSite<T extends RouteData = any> = Partial<
+ IsoData<T>
+> &
+ Pick<IsoData<T>, Exclude<keyof IsoData<T>, "site_res">>;
export interface ILemmyConfig {
wsHost?: string;
children: Array<CommentNodeI>;
depth: number;
}
+
+export type RouteData = Record<string, RequestState<any>>;
import { Post } from "./components/post/post";
import { CreatePrivateMessage } from "./components/private_message/create-private-message";
import { Search } from "./components/search";
-import { InitialFetchRequest } from "./interfaces";
-import { RequestState } from "./services/HttpService";
+import { InitialFetchRequest, RouteData } from "./interfaces";
-interface IRoutePropsWithFetch extends IRouteProps {
- // TODO Make sure this one is good.
- fetchInitialData?(req: InitialFetchRequest): Promise<RequestState<any>>[];
+interface IRoutePropsWithFetch<T extends RouteData> extends IRouteProps {
+ fetchInitialData?(req: InitialFetchRequest): Promise<T>;
}
-export const routes: IRoutePropsWithFetch[] = [
+export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [
{
path: `/`,
component: Home,
state: "loading";
};
-type FailedRequestState = {
+export type FailedRequestState = {
state: "failed";
msg: string;
};
return {
data: res,
- state: "success",
+ state: !(res === undefined || res === null) ? "success" : "empty",
};
} catch (error) {
console.error(`API error: ${error}`);
import Toastify from "toastify-js";
import { getHttpBase } from "./env";
import { i18n } from "./i18next";
-import { CommentNodeI, DataType, IsoData, VoteType } from "./interfaces";
+import {
+ CommentNodeI,
+ DataType,
+ IsoData,
+ RouteData,
+ VoteType,
+} from "./interfaces";
import { HttpService, UserService } from "./services";
+import { RequestState } from "./services/HttpService";
let Tribute: any;
if (isBrowser()) {
return typeof window !== "undefined";
}
-export function setIsoData(context: any): IsoData {
+export function setIsoData<T extends RouteData>(context: any): IsoData<T> {
// If its the browser, you need to deserialize the data from the window
if (isBrowser()) {
return window.isoData;
}
}
+export type RouteDataResponse<T extends Record<string, any>> = {
+ [K in keyof T]: RequestState<T[K]>;
+};
+
function sleep(millis: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, millis));
}