mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 12:21:13 +00:00
Render more while reloading only some resources (#2480)
* AdminSettings remove unused currentTab state * Fix amAdmin check in reports fetchInitialData * Make CreatePost render earlier * Include children of auth and anonymous guard in first render. * Convert DidMount to WillMount where things don't depend on the DOM `componentDidMount` is called after the first render. A lot of components used it to call `setState`, which causes a second render. * Keep route components mounted during same route navigation Not sure why this wasn't the case without this change. The only difference here is that the same array is reused in every render. * Disable mounted same route navigation by default * Enable mounted same route navigation for some routes * Render more while loading * Prettier markup * Make Post use query params and reload comments independently * Fix issue with <Prompt /> for forms that remain mounted after "leaving". * Make Search not rerender the results on every keystroke * Discard old requests These used to (mostly) arrive at the old already unmounted components. Now they would render briefly until the latest response is received. * Move non breaking space to modlog * Make show optional for modals
This commit is contained in:
parent
937fd3eb4e
commit
b7fe70d8c1
38 changed files with 1529 additions and 934 deletions
|
@ -32,6 +32,64 @@ export default class App extends Component<any, any> {
|
||||||
destroyTippy();
|
destroyTippy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
routes = routes.map(
|
||||||
|
({
|
||||||
|
path,
|
||||||
|
component: RouteComponent,
|
||||||
|
fetchInitialData,
|
||||||
|
getQueryParams,
|
||||||
|
mountedSameRouteNavKey,
|
||||||
|
}) => (
|
||||||
|
<Route
|
||||||
|
key={path}
|
||||||
|
path={path}
|
||||||
|
exact
|
||||||
|
component={routeProps => {
|
||||||
|
if (!fetchInitialData) {
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// When key is location.key the component will be recreated when
|
||||||
|
// navigating to itself. This is usesful to e.g. reset forms.
|
||||||
|
const key = mountedSameRouteNavKey ?? routeProps.location.key;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorGuard>
|
||||||
|
<div tabIndex={-1}>
|
||||||
|
{RouteComponent &&
|
||||||
|
(isAuthPath(path ?? "") ? (
|
||||||
|
<AuthGuard {...routeProps}>
|
||||||
|
<RouteComponent key={key} {...queryProps} />
|
||||||
|
</AuthGuard>
|
||||||
|
) : isAnonymousPath(path ?? "") ? (
|
||||||
|
<AnonymousGuard>
|
||||||
|
<RouteComponent key={key} {...queryProps} />
|
||||||
|
</AnonymousGuard>
|
||||||
|
) : (
|
||||||
|
<RouteComponent key={key} {...queryProps} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ErrorGuard>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const siteRes = this.isoData.site_res;
|
const siteRes = this.isoData.site_res;
|
||||||
const siteView = siteRes?.site_view;
|
const siteView = siteRes?.site_view;
|
||||||
|
@ -64,58 +122,7 @@ export default class App extends Component<any, any> {
|
||||||
<Navbar siteRes={siteRes} />
|
<Navbar siteRes={siteRes} />
|
||||||
<div className="mt-4 p-0 fl-1">
|
<div className="mt-4 p-0 fl-1">
|
||||||
<Switch>
|
<Switch>
|
||||||
{routes.map(
|
{this.routes}
|
||||||
({
|
|
||||||
path,
|
|
||||||
component: RouteComponent,
|
|
||||||
fetchInitialData,
|
|
||||||
getQueryParams,
|
|
||||||
}) => (
|
|
||||||
<Route
|
|
||||||
key={path}
|
|
||||||
path={path}
|
|
||||||
exact
|
|
||||||
component={routeProps => {
|
|
||||||
if (!fetchInitialData) {
|
|
||||||
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 {...queryProps} />
|
|
||||||
</AuthGuard>
|
|
||||||
) : isAnonymousPath(path ?? "") ? (
|
|
||||||
<AnonymousGuard>
|
|
||||||
<RouteComponent {...queryProps} />
|
|
||||||
</AnonymousGuard>
|
|
||||||
) : (
|
|
||||||
<RouteComponent {...queryProps} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ErrorGuard>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
<Route component={ErrorPage} />
|
<Route component={ErrorPage} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,7 +63,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
||||||
this.handleOutsideMenuClick = this.handleOutsideMenuClick.bind(this);
|
this.handleOutsideMenuClick = this.handleOutsideMenuClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
// Subscribe to jwt changes
|
// Subscribe to jwt changes
|
||||||
if (isBrowser()) {
|
if (isBrowser()) {
|
||||||
// On the first load, check the unreads
|
// On the first load, check the unreads
|
||||||
|
|
|
@ -502,7 +502,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
className={classnames}
|
className={classnames}
|
||||||
to={`/comment/${
|
to={`/post/${cv.post.id}/${
|
||||||
(this.props.showContext && getCommentParentId(cv.comment)) ||
|
(this.props.showContext && getCommentParentId(cv.comment)) ||
|
||||||
cv.comment.id
|
cv.comment.id
|
||||||
}`}
|
}`}
|
||||||
|
|
|
@ -1,30 +1,25 @@
|
||||||
import { Component } from "inferno";
|
import { Component } from "inferno";
|
||||||
import { UserService } from "../../services";
|
import { UserService } from "../../services";
|
||||||
import { Spinner } from "./icon";
|
import { Spinner } from "./icon";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
interface AnonymousGuardState {
|
class AnonymousGuard extends Component<any, any> {
|
||||||
hasRedirected: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AnonymousGuard extends Component<any, AnonymousGuardState> {
|
|
||||||
state = {
|
|
||||||
hasRedirected: false,
|
|
||||||
} as AnonymousGuardState;
|
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
hasAuth() {
|
||||||
if (UserService.Instance.myUserInfo) {
|
return UserService.Instance.myUserInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (this.hasAuth() && isBrowser()) {
|
||||||
this.context.router.history.replace(`/`);
|
this.context.router.history.replace(`/`);
|
||||||
} else {
|
|
||||||
this.setState({ hasRedirected: true });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.state.hasRedirected ? this.props.children : <Spinner />;
|
return !this.hasAuth() ? this.props.children : <Spinner />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,12 @@ import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { UserService } from "../../services";
|
import { UserService } from "../../services";
|
||||||
import { Spinner } from "./icon";
|
import { Spinner } from "./icon";
|
||||||
import { getQueryString } from "@utils/helpers";
|
import { getQueryString } from "@utils/helpers";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
interface AuthGuardState {
|
|
||||||
hasRedirected: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthGuard extends Component<
|
class AuthGuard extends Component<
|
||||||
RouteComponentProps<Record<string, string>>,
|
RouteComponentProps<Record<string, string>>,
|
||||||
AuthGuardState
|
any
|
||||||
> {
|
> {
|
||||||
state = {
|
|
||||||
hasRedirected: false,
|
|
||||||
} as AuthGuardState;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
props: RouteComponentProps<Record<string, string>>,
|
props: RouteComponentProps<Record<string, string>>,
|
||||||
context: any,
|
context: any,
|
||||||
|
@ -23,19 +16,21 @@ class AuthGuard extends Component<
|
||||||
super(props, context);
|
super(props, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
hasAuth() {
|
||||||
if (!UserService.Instance.myUserInfo) {
|
return UserService.Instance.myUserInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (!this.hasAuth() && isBrowser()) {
|
||||||
const { pathname, search } = this.props.location;
|
const { pathname, search } = this.props.location;
|
||||||
this.context.router.history.replace(
|
this.context.router.history.replace(
|
||||||
`/login${getQueryString({ prev: pathname + search })}`,
|
`/login${getQueryString({ prev: pathname + search })}`,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
this.setState({ hasRedirected: true });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return this.state.hasRedirected ? this.props.children : <Spinner />;
|
return this.hasAuth() ? this.props.children : <Spinner />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -643,7 +643,6 @@ export default class ContentActionDropdown extends Component<
|
||||||
type,
|
type,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// Wait until componentDidMount runs (which only happens on the browser) to prevent sending over a gratuitous amount of markup
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderRemoveDialog && (
|
{renderRemoveDialog && (
|
||||||
|
|
|
@ -39,7 +39,7 @@ function handleSearch(i: SearchableSelect, e: ChangeEvent<HTMLInputElement>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusSearch(i: SearchableSelect) {
|
function focusSearch(i: SearchableSelect) {
|
||||||
if (i.toggleButtonRef.current?.ariaExpanded !== "true") {
|
if (i.toggleButtonRef.current?.ariaExpanded === "true") {
|
||||||
i.searchInputRef.current?.focus();
|
i.searchInputRef.current?.focus();
|
||||||
|
|
||||||
if (i.props.onSearch) {
|
if (i.props.onSearch) {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { getQueryString, validInstanceTLD } from "@utils/helpers";
|
import { getQueryString, validInstanceTLD } from "@utils/helpers";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { NoOptionI18nKeys } from "i18next";
|
import { NoOptionI18nKeys } from "i18next";
|
||||||
import { Component, MouseEventHandler, linkEvent } from "inferno";
|
import { Component, MouseEventHandler, createRef, linkEvent } from "inferno";
|
||||||
import { CommunityView } from "lemmy-js-client";
|
import { CommunityView } from "lemmy-js-client";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { VERSION } from "../../version";
|
import { VERSION } from "../../version";
|
||||||
import { Icon, Spinner } from "./icon";
|
import { Icon, Spinner } from "./icon";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
|
import { modalMixin } from "../mixins/modal-mixin";
|
||||||
|
|
||||||
interface SubscribeButtonProps {
|
interface SubscribeButtonProps {
|
||||||
communityView: CommunityView;
|
communityView: CommunityView;
|
||||||
|
@ -93,6 +94,7 @@ export function SubscribeButton({
|
||||||
|
|
||||||
interface RemoteFetchModalProps {
|
interface RemoteFetchModalProps {
|
||||||
communityActorId: string;
|
communityActorId: string;
|
||||||
|
show?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RemoteFetchModalState {
|
interface RemoteFetchModalState {
|
||||||
|
@ -103,10 +105,6 @@ function handleInput(i: RemoteFetchModal, event: any) {
|
||||||
i.setState({ instanceText: event.target.value });
|
i.setState({ instanceText: event.target.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusInput() {
|
|
||||||
document.getElementById("remoteFetchInstance")?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitRemoteFollow(
|
function submitRemoteFollow(
|
||||||
{ state: { instanceText }, props: { communityActorId } }: RemoteFetchModal,
|
{ state: { instanceText }, props: { communityActorId } }: RemoteFetchModal,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
@ -139,6 +137,7 @@ function submitRemoteFollow(
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@modalMixin
|
||||||
class RemoteFetchModal extends Component<
|
class RemoteFetchModal extends Component<
|
||||||
RemoteFetchModalProps,
|
RemoteFetchModalProps,
|
||||||
RemoteFetchModalState
|
RemoteFetchModalState
|
||||||
|
@ -147,20 +146,15 @@ class RemoteFetchModal extends Component<
|
||||||
instanceText: "",
|
instanceText: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
modalDivRef = createRef<HTMLDivElement>();
|
||||||
|
inputRef = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
handleShow() {
|
||||||
document
|
this.inputRef.current?.focus();
|
||||||
.getElementById("remoteFetchModal")
|
|
||||||
?.addEventListener("shown.bs.modal", focusInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
|
||||||
document
|
|
||||||
.getElementById("remoteFetchModal")
|
|
||||||
?.removeEventListener("shown.bs.modal", focusInput);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -171,6 +165,7 @@ class RemoteFetchModal extends Component<
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
aria-labelledby="#remoteFetchModalTitle"
|
aria-labelledby="#remoteFetchModalTitle"
|
||||||
|
ref={this.modalDivRef}
|
||||||
>
|
>
|
||||||
<div className="modal-dialog modal-dialog-centered modal-fullscreen-sm-down">
|
<div className="modal-dialog modal-dialog-centered modal-fullscreen-sm-down">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
|
@ -203,6 +198,7 @@ class RemoteFetchModal extends Component<
|
||||||
required
|
required
|
||||||
enterKeyHint="go"
|
enterKeyHint="go"
|
||||||
inputMode="url"
|
inputMode="url"
|
||||||
|
ref={this.inputRef}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
<footer className="modal-footer">
|
<footer className="modal-footer">
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { fetchLimit } from "../../config";
|
||||||
import { PersonListing } from "../person/person-listing";
|
import { PersonListing } from "../person/person-listing";
|
||||||
import { modalMixin } from "../mixins/modal-mixin";
|
import { modalMixin } from "../mixins/modal-mixin";
|
||||||
import { UserBadges } from "./user-badges";
|
import { UserBadges } from "./user-badges";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
interface ViewVotesModalProps {
|
interface ViewVotesModalProps {
|
||||||
children?: InfernoNode;
|
children?: InfernoNode;
|
||||||
|
@ -96,8 +97,8 @@ export default class ViewVotesModal extends Component<
|
||||||
this.handlePageChange = this.handlePageChange.bind(this);
|
this.handlePageChange = this.handlePageChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (this.props.show) {
|
if (this.props.show && isBrowser()) {
|
||||||
await this.refetch();
|
await this.refetch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
import { scrollMixin } from "../mixins/scroll-mixin";
|
import { scrollMixin } from "../mixins/scroll-mixin";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
type CommunitiesData = RouteDataResponse<{
|
type CommunitiesData = RouteDataResponse<{
|
||||||
listCommunitiesResponse: ListCommunitiesResponse;
|
listCommunitiesResponse: ListCommunitiesResponse;
|
||||||
|
@ -121,19 +122,23 @@ export class Communities extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
await this.refetch();
|
await this.refetch(this.props);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps: CommunitiesRouteProps) {
|
||||||
|
this.refetch(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
get documentTitle(): string {
|
get documentTitle(): string {
|
||||||
return `${I18NextService.i18n.t("communities")} - ${
|
return `${I18NextService.i18n.t("communities")} - ${
|
||||||
this.state.siteRes.site_view.site.name
|
this.state.siteRes.site_view.site.name
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderListings() {
|
renderListingsTable() {
|
||||||
switch (this.state.listCommunitiesResponse.state) {
|
switch (this.state.listCommunitiesResponse.state) {
|
||||||
case "loading":
|
case "loading":
|
||||||
return (
|
return (
|
||||||
|
@ -142,120 +147,114 @@ export class Communities extends Component<
|
||||||
</h5>
|
</h5>
|
||||||
);
|
);
|
||||||
case "success": {
|
case "success": {
|
||||||
const { listingType, sort, page } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<table id="community_table" className="table table-sm table-hover">
|
||||||
<h1 className="h4 mb-4">
|
<thead className="pointer">
|
||||||
{I18NextService.i18n.t("list_of_communities")}
|
<tr>
|
||||||
</h1>
|
<th>{I18NextService.i18n.t("name")}</th>
|
||||||
<div className="row g-3 align-items-center mb-2">
|
<th className="text-right">
|
||||||
<div className="col-auto">
|
{I18NextService.i18n.t("subscribers")}
|
||||||
<ListingTypeSelect
|
</th>
|
||||||
type_={listingType}
|
<th className="text-right">
|
||||||
showLocal={showLocal(this.isoData)}
|
{I18NextService.i18n.t("users")} /{" "}
|
||||||
showSubscribed
|
{I18NextService.i18n.t("month")}
|
||||||
onChange={this.handleListingTypeChange}
|
</th>
|
||||||
/>
|
<th className="text-right d-none d-lg-table-cell">
|
||||||
</div>
|
{I18NextService.i18n.t("posts")}
|
||||||
<div className="col-auto me-auto">
|
</th>
|
||||||
<SortSelect sort={sort} onChange={this.handleSortChange} />
|
<th className="text-right d-none d-lg-table-cell">
|
||||||
</div>
|
{I18NextService.i18n.t("comments")}
|
||||||
<div className="col-auto">{this.searchForm()}</div>
|
</th>
|
||||||
</div>
|
<th></th>
|
||||||
|
</tr>
|
||||||
<div className="table-responsive">
|
</thead>
|
||||||
<table
|
<tbody>
|
||||||
id="community_table"
|
{this.state.listCommunitiesResponse.data.communities.map(cv => (
|
||||||
className="table table-sm table-hover"
|
<tr key={cv.community.id}>
|
||||||
>
|
<td>
|
||||||
<thead className="pointer">
|
<CommunityLink community={cv.community} />
|
||||||
<tr>
|
</td>
|
||||||
<th>{I18NextService.i18n.t("name")}</th>
|
<td className="text-right">
|
||||||
<th className="text-right">
|
{numToSI(cv.counts.subscribers)}
|
||||||
{I18NextService.i18n.t("subscribers")}
|
</td>
|
||||||
</th>
|
<td className="text-right">
|
||||||
<th className="text-right">
|
{numToSI(cv.counts.users_active_month)}
|
||||||
{I18NextService.i18n.t("users")} /{" "}
|
</td>
|
||||||
{I18NextService.i18n.t("month")}
|
<td className="text-right d-none d-lg-table-cell">
|
||||||
</th>
|
{numToSI(cv.counts.posts)}
|
||||||
<th className="text-right d-none d-lg-table-cell">
|
</td>
|
||||||
{I18NextService.i18n.t("posts")}
|
<td className="text-right d-none d-lg-table-cell">
|
||||||
</th>
|
{numToSI(cv.counts.comments)}
|
||||||
<th className="text-right d-none d-lg-table-cell">
|
</td>
|
||||||
{I18NextService.i18n.t("comments")}
|
<td className="text-right">
|
||||||
</th>
|
<SubscribeButton
|
||||||
<th></th>
|
communityView={cv}
|
||||||
</tr>
|
onFollow={linkEvent(
|
||||||
</thead>
|
{
|
||||||
<tbody>
|
i: this,
|
||||||
{this.state.listCommunitiesResponse.data.communities.map(
|
communityId: cv.community.id,
|
||||||
cv => (
|
follow: true,
|
||||||
<tr key={cv.community.id}>
|
},
|
||||||
<td>
|
this.handleFollow,
|
||||||
<CommunityLink community={cv.community} />
|
)}
|
||||||
</td>
|
onUnFollow={linkEvent(
|
||||||
<td className="text-right">
|
{
|
||||||
{numToSI(cv.counts.subscribers)}
|
i: this,
|
||||||
</td>
|
communityId: cv.community.id,
|
||||||
<td className="text-right">
|
follow: false,
|
||||||
{numToSI(cv.counts.users_active_month)}
|
},
|
||||||
</td>
|
this.handleFollow,
|
||||||
<td className="text-right d-none d-lg-table-cell">
|
)}
|
||||||
{numToSI(cv.counts.posts)}
|
isLink
|
||||||
</td>
|
/>
|
||||||
<td className="text-right d-none d-lg-table-cell">
|
</td>
|
||||||
{numToSI(cv.counts.comments)}
|
</tr>
|
||||||
</td>
|
))}
|
||||||
<td className="text-right">
|
</tbody>
|
||||||
<SubscribeButton
|
</table>
|
||||||
communityView={cv}
|
|
||||||
onFollow={linkEvent(
|
|
||||||
{
|
|
||||||
i: this,
|
|
||||||
communityId: cv.community.id,
|
|
||||||
follow: true,
|
|
||||||
},
|
|
||||||
this.handleFollow,
|
|
||||||
)}
|
|
||||||
onUnFollow={linkEvent(
|
|
||||||
{
|
|
||||||
i: this,
|
|
||||||
communityId: cv.community.id,
|
|
||||||
follow: false,
|
|
||||||
},
|
|
||||||
this.handleFollow,
|
|
||||||
)}
|
|
||||||
isLink
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<Paginator
|
|
||||||
page={page}
|
|
||||||
onChange={this.handlePageChange}
|
|
||||||
nextDisabled={
|
|
||||||
communityLimit >
|
|
||||||
this.state.listCommunitiesResponse.data.communities.length
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { listingType, sort, page } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="communities container-lg">
|
<div className="communities container-lg">
|
||||||
<HtmlTags
|
<HtmlTags
|
||||||
title={this.documentTitle}
|
title={this.documentTitle}
|
||||||
path={this.context.router.route.match.url}
|
path={this.context.router.route.match.url}
|
||||||
/>
|
/>
|
||||||
{this.renderListings()}
|
<div>
|
||||||
|
<h1 className="h4 mb-4">
|
||||||
|
{I18NextService.i18n.t("list_of_communities")}
|
||||||
|
</h1>
|
||||||
|
<div className="row g-3 align-items-center mb-2">
|
||||||
|
<div className="col-auto">
|
||||||
|
<ListingTypeSelect
|
||||||
|
type_={listingType}
|
||||||
|
showLocal={showLocal(this.isoData)}
|
||||||
|
showSubscribed
|
||||||
|
onChange={this.handleListingTypeChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-auto me-auto">
|
||||||
|
<SortSelect sort={sort} onChange={this.handleSortChange} />
|
||||||
|
</div>
|
||||||
|
<div className="col-auto">{this.searchForm()}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="table-responsive">{this.renderListingsTable()}</div>
|
||||||
|
<Paginator
|
||||||
|
page={page}
|
||||||
|
onChange={this.handlePageChange}
|
||||||
|
nextDisabled={
|
||||||
|
this.state.listCommunitiesResponse.state !== "success" ||
|
||||||
|
communityLimit >
|
||||||
|
this.state.listCommunitiesResponse.data.communities.length
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -287,22 +286,16 @@ export class Communities extends Component<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUrl({ listingType, sort, page }: Partial<CommunitiesProps>) {
|
async updateUrl(props: Partial<CommunitiesProps>) {
|
||||||
const {
|
const { listingType, sort, page } = { ...this.props, ...props };
|
||||||
listingType: urlListingType,
|
|
||||||
sort: urlSort,
|
|
||||||
page: urlPage,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const queryParams: QueryParams<CommunitiesProps> = {
|
const queryParams: QueryParams<CommunitiesProps> = {
|
||||||
listingType: listingType ?? urlListingType,
|
listingType: listingType,
|
||||||
sort: sort ?? urlSort,
|
sort: sort,
|
||||||
page: (page ?? urlPage)?.toString(),
|
page: page?.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.history.push(`/communities${getQueryString(queryParams)}`);
|
this.props.history.push(`/communities${getQueryString(queryParams)}`);
|
||||||
|
|
||||||
await this.refetch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePageChange(page: number) {
|
handlePageChange(page: number) {
|
||||||
|
@ -368,19 +361,19 @@ export class Communities extends Component<
|
||||||
data.i.findAndUpdateCommunity(res);
|
data.i.findAndUpdateCommunity(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
fetchToken?: symbol;
|
||||||
|
async refetch({ listingType, sort, page }: CommunitiesProps) {
|
||||||
|
const token = (this.fetchToken = Symbol());
|
||||||
this.setState({ listCommunitiesResponse: LOADING_REQUEST });
|
this.setState({ listCommunitiesResponse: LOADING_REQUEST });
|
||||||
|
const listCommunitiesResponse = await HttpService.client.listCommunities({
|
||||||
const { listingType, sort, page } = this.props;
|
type_: listingType,
|
||||||
|
sort: sort,
|
||||||
this.setState({
|
limit: communityLimit,
|
||||||
listCommunitiesResponse: await HttpService.client.listCommunities({
|
page,
|
||||||
type_: listingType,
|
|
||||||
sort: sort,
|
|
||||||
limit: communityLimit,
|
|
||||||
page,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchToken) {
|
||||||
|
this.setState({ listCommunitiesResponse });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndUpdateCommunity(res: RequestState<CommunityResponse>) {
|
findAndUpdateCommunity(res: RequestState<CommunityResponse>) {
|
||||||
|
|
|
@ -19,11 +19,18 @@ import {
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
getQueryString,
|
getQueryString,
|
||||||
resourcesSettled,
|
resourcesSettled,
|
||||||
|
bareRoutePush,
|
||||||
} from "@utils/helpers";
|
} from "@utils/helpers";
|
||||||
import { scrollMixin } from "../mixins/scroll-mixin";
|
import { scrollMixin } from "../mixins/scroll-mixin";
|
||||||
import type { QueryParams, StringBoolean } from "@utils/types";
|
import type { QueryParams, StringBoolean } from "@utils/types";
|
||||||
import { RouteDataResponse } from "@utils/types";
|
import { RouteDataResponse } from "@utils/types";
|
||||||
import { Component, RefObject, createRef, linkEvent } from "inferno";
|
import {
|
||||||
|
Component,
|
||||||
|
InfernoNode,
|
||||||
|
RefObject,
|
||||||
|
createRef,
|
||||||
|
linkEvent,
|
||||||
|
} from "inferno";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import {
|
import {
|
||||||
AddAdmin,
|
AddAdmin,
|
||||||
|
@ -100,7 +107,7 @@ import { CommentNodes } from "../comment/comment-nodes";
|
||||||
import { BannerIconHeader } from "../common/banner-icon-header";
|
import { BannerIconHeader } from "../common/banner-icon-header";
|
||||||
import { DataTypeSelect } from "../common/data-type-select";
|
import { DataTypeSelect } from "../common/data-type-select";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon } from "../common/icon";
|
||||||
import { SortSelect } from "../common/sort-select";
|
import { SortSelect } from "../common/sort-select";
|
||||||
import { SiteSidebar } from "../home/site-sidebar";
|
import { SiteSidebar } from "../home/site-sidebar";
|
||||||
import { PostListings } from "../post/post-listings";
|
import { PostListings } from "../post/post-listings";
|
||||||
|
@ -114,6 +121,8 @@ import {
|
||||||
import { Sidebar } from "./sidebar";
|
import { Sidebar } from "./sidebar";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
import PostHiddenSelect from "../common/post-hidden-select";
|
import PostHiddenSelect from "../common/post-hidden-select";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
import { LoadingEllipses } from "../common/loading-ellipses";
|
||||||
|
|
||||||
type CommunityData = RouteDataResponse<{
|
type CommunityData = RouteDataResponse<{
|
||||||
communityRes: GetCommunityResponse;
|
communityRes: GetCommunityResponse;
|
||||||
|
@ -265,21 +274,39 @@ export class Community extends Component<CommunityRouteProps, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchCommunity() {
|
fetchCommunityToken?: symbol;
|
||||||
|
async fetchCommunity(props: CommunityRouteProps) {
|
||||||
|
const token = (this.fetchCommunityToken = Symbol());
|
||||||
this.setState({ communityRes: LOADING_REQUEST });
|
this.setState({ communityRes: LOADING_REQUEST });
|
||||||
this.setState({
|
const communityRes = await HttpService.client.getCommunity({
|
||||||
communityRes: await HttpService.client.getCommunity({
|
name: props.match.params.name,
|
||||||
name: this.props.match.params.name,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchCommunityToken) {
|
||||||
|
this.setState({ communityRes });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
await Promise.all([this.fetchCommunity(), this.fetchData()]);
|
await Promise.all([
|
||||||
|
this.fetchCommunity(this.props),
|
||||||
|
this.fetchData(this.props),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(
|
||||||
|
nextProps: CommunityRouteProps & { children?: InfernoNode },
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
bareRoutePush(this.props, nextProps) ||
|
||||||
|
this.props.match.params.name !== nextProps.match.params.name
|
||||||
|
) {
|
||||||
|
this.fetchCommunity(nextProps);
|
||||||
|
}
|
||||||
|
this.fetchData(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
headers,
|
headers,
|
||||||
query: { dataType, pageCursor, sort, showHidden },
|
query: { dataType, pageCursor, sort, showHidden },
|
||||||
|
@ -356,73 +383,67 @@ export class Community extends Component<CommunityRouteProps, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCommunity() {
|
renderCommunity() {
|
||||||
switch (this.state.communityRes.state) {
|
const res =
|
||||||
case "loading":
|
this.state.communityRes.state === "success" &&
|
||||||
return (
|
this.state.communityRes.data;
|
||||||
<h5>
|
return (
|
||||||
<Spinner large />
|
<>
|
||||||
</h5>
|
{res && (
|
||||||
);
|
<HtmlTags
|
||||||
case "success": {
|
title={this.documentTitle}
|
||||||
const res = this.state.communityRes.data;
|
path={this.context.router.route.match.url}
|
||||||
|
canonicalPath={res.community_view.community.actor_id}
|
||||||
|
description={res.community_view.community.description}
|
||||||
|
image={res.community_view.community.icon}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
return (
|
{this.communityInfo()}
|
||||||
<>
|
<div className="d-block d-md-none">
|
||||||
<HtmlTags
|
<button
|
||||||
title={this.documentTitle}
|
className="btn btn-secondary d-inline-block mb-2 me-3"
|
||||||
path={this.context.router.route.match.url}
|
onClick={linkEvent(this, this.handleShowSidebarMobile)}
|
||||||
canonicalPath={res.community_view.community.actor_id}
|
>
|
||||||
description={res.community_view.community.description}
|
{I18NextService.i18n.t("sidebar")}{" "}
|
||||||
image={res.community_view.community.icon}
|
<Icon
|
||||||
|
icon={
|
||||||
|
this.state.showSidebarMobile ? `minus-square` : `plus-square`
|
||||||
|
}
|
||||||
|
classes="icon-inline"
|
||||||
/>
|
/>
|
||||||
|
</button>
|
||||||
<div className="row">
|
{this.state.showSidebarMobile && this.sidebar()}
|
||||||
<main
|
</div>
|
||||||
className="col-12 col-md-8 col-lg-9"
|
</>
|
||||||
ref={this.mainContentRef}
|
);
|
||||||
>
|
|
||||||
{this.communityInfo(res)}
|
|
||||||
<div className="d-block d-md-none">
|
|
||||||
<button
|
|
||||||
className="btn btn-secondary d-inline-block mb-2 me-3"
|
|
||||||
onClick={linkEvent(this, this.handleShowSidebarMobile)}
|
|
||||||
>
|
|
||||||
{I18NextService.i18n.t("sidebar")}{" "}
|
|
||||||
<Icon
|
|
||||||
icon={
|
|
||||||
this.state.showSidebarMobile
|
|
||||||
? `minus-square`
|
|
||||||
: `plus-square`
|
|
||||||
}
|
|
||||||
classes="icon-inline"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{this.state.showSidebarMobile && this.sidebar(res)}
|
|
||||||
</div>
|
|
||||||
{this.selects(res)}
|
|
||||||
{this.listings(res)}
|
|
||||||
<PaginatorCursor
|
|
||||||
nextPage={this.getNextPage}
|
|
||||||
onNext={this.handlePageNext}
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
<aside className="d-none d-md-block col-md-4 col-lg-3">
|
|
||||||
{this.sidebar(res)}
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="community container-lg">{this.renderCommunity()}</div>
|
<div className="community container-lg">
|
||||||
|
<div className="row">
|
||||||
|
<main className="col-12 col-md-8 col-lg-9" ref={this.mainContentRef}>
|
||||||
|
{this.renderCommunity()}
|
||||||
|
{this.selects()}
|
||||||
|
{this.listings()}
|
||||||
|
<PaginatorCursor
|
||||||
|
nextPage={this.getNextPage}
|
||||||
|
onNext={this.handlePageNext}
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
<aside className="d-none d-md-block col-md-4 col-lg-3">
|
||||||
|
{this.sidebar()}
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebar(res: GetCommunityResponse) {
|
sidebar() {
|
||||||
|
if (this.state.communityRes.state !== "success") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const res = this.state.communityRes.data;
|
||||||
const siteRes = this.isoData.site_res;
|
const siteRes = this.isoData.site_res;
|
||||||
// For some reason, this returns an empty vec if it matches the site langs
|
// For some reason, this returns an empty vec if it matches the site langs
|
||||||
const communityLangs =
|
const communityLangs =
|
||||||
|
@ -456,7 +477,7 @@ export class Community extends Component<CommunityRouteProps, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
listings(communityRes: GetCommunityResponse) {
|
listings() {
|
||||||
const { dataType } = this.props;
|
const { dataType } = this.props;
|
||||||
const siteRes = this.isoData.site_res;
|
const siteRes = this.isoData.site_res;
|
||||||
|
|
||||||
|
@ -496,6 +517,9 @@ export class Community extends Component<CommunityRouteProps, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (this.state.communityRes.state !== "success") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (this.state.commentsRes.state) {
|
switch (this.state.commentsRes.state) {
|
||||||
case "loading":
|
case "loading":
|
||||||
return <CommentsLoadingSkeleton />;
|
return <CommentsLoadingSkeleton />;
|
||||||
|
@ -509,7 +533,7 @@ export class Community extends Component<CommunityRouteProps, State> {
|
||||||
showContext
|
showContext
|
||||||
enableDownvotes={enableDownvotes(siteRes)}
|
enableDownvotes={enableDownvotes(siteRes)}
|
||||||
voteDisplayMode={voteDisplayMode(siteRes)}
|
voteDisplayMode={voteDisplayMode(siteRes)}
|
||||||
moderators={communityRes.moderators}
|
moderators={this.state.communityRes.data.moderators}
|
||||||
admins={siteRes.admins}
|
admins={siteRes.admins}
|
||||||
allLanguages={siteRes.all_languages}
|
allLanguages={siteRes.all_languages}
|
||||||
siteLanguages={siteRes.discussion_languages}
|
siteLanguages={siteRes.discussion_languages}
|
||||||
|
@ -537,28 +561,40 @@ export class Community extends Component<CommunityRouteProps, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
communityInfo(res: GetCommunityResponse) {
|
communityInfo() {
|
||||||
const community = res.community_view.community;
|
const res =
|
||||||
|
(this.state.communityRes.state === "success" &&
|
||||||
|
this.state.communityRes.data) ||
|
||||||
|
undefined;
|
||||||
|
const community = res && res.community_view.community;
|
||||||
|
const urlCommunityName = this.props.match.params.name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
community && (
|
<div className="mb-2">
|
||||||
<div className="mb-2">
|
{community && (
|
||||||
<BannerIconHeader banner={community.banner} icon={community.icon} />
|
<BannerIconHeader banner={community.banner} icon={community.icon} />
|
||||||
<div>
|
)}
|
||||||
<h1
|
<div>
|
||||||
className="h4 mb-0 overflow-wrap-anywhere d-inline"
|
<h1
|
||||||
data-tippy-content={
|
className="h4 mb-0 overflow-wrap-anywhere d-inline"
|
||||||
community.posting_restricted_to_mods
|
data-tippy-content={
|
||||||
? I18NextService.i18n.t("community_locked")
|
community?.posting_restricted_to_mods
|
||||||
: ""
|
? I18NextService.i18n.t("community_locked")
|
||||||
}
|
: ""
|
||||||
>
|
}
|
||||||
{community.title}
|
>
|
||||||
</h1>
|
{community?.title ?? (
|
||||||
{community.posting_restricted_to_mods && (
|
<>
|
||||||
<Icon icon="lock" inline classes="text-danger fs-4 ms-2" />
|
{urlCommunityName}
|
||||||
|
<LoadingEllipses />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</h1>
|
||||||
|
{community?.posting_restricted_to_mods && (
|
||||||
|
<Icon icon="lock" inline classes="text-danger fs-4 ms-2" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{(community && (
|
||||||
<CommunityLink
|
<CommunityLink
|
||||||
community={community}
|
community={community}
|
||||||
realLink
|
realLink
|
||||||
|
@ -566,12 +602,16 @@ export class Community extends Component<CommunityRouteProps, State> {
|
||||||
muted
|
muted
|
||||||
hideAvatar
|
hideAvatar
|
||||||
/>
|
/>
|
||||||
</div>
|
)) ??
|
||||||
)
|
urlCommunityName}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
selects(res: GetCommunityResponse) {
|
selects() {
|
||||||
|
const res =
|
||||||
|
this.state.communityRes.state === "success" &&
|
||||||
|
this.state.communityRes.data;
|
||||||
const { dataType, sort, showHidden } = this.props;
|
const { dataType, sort, showHidden } = this.props;
|
||||||
const communityRss = res
|
const communityRss = res
|
||||||
? communityRSSUrl(res.community_view.community.actor_id, sort)
|
? communityRSSUrl(res.community_view.community.actor_id, sort)
|
||||||
|
@ -641,60 +681,62 @@ export class Community extends Component<CommunityRouteProps, State> {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUrl({
|
async updateUrl(props: Partial<CommunityProps>) {
|
||||||
dataType,
|
|
||||||
pageCursor,
|
|
||||||
sort,
|
|
||||||
showHidden,
|
|
||||||
}: Partial<CommunityProps>) {
|
|
||||||
const {
|
const {
|
||||||
dataType: urlDataType,
|
dataType,
|
||||||
sort: urlSort,
|
pageCursor,
|
||||||
showHidden: urlShowHidden,
|
sort,
|
||||||
} = this.props;
|
showHidden,
|
||||||
|
match: {
|
||||||
const queryParams: QueryParams<CommunityProps> = {
|
params: { name },
|
||||||
dataType: getDataTypeString(dataType ?? urlDataType),
|
},
|
||||||
pageCursor: pageCursor,
|
} = {
|
||||||
sort: sort ?? urlSort,
|
...this.props,
|
||||||
showHidden: showHidden ?? urlShowHidden,
|
...props,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.history.push(
|
const queryParams: QueryParams<CommunityProps> = {
|
||||||
`/c/${this.props.match.params.name}${getQueryString(queryParams)}`,
|
dataType: getDataTypeString(dataType ?? DataType.Post),
|
||||||
);
|
pageCursor: pageCursor,
|
||||||
|
sort: sort,
|
||||||
|
showHidden: showHidden,
|
||||||
|
};
|
||||||
|
|
||||||
await this.fetchData();
|
this.props.history.push(`/c/${name}${getQueryString(queryParams)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchData() {
|
fetchDataToken?: symbol;
|
||||||
const { dataType, pageCursor, sort, showHidden } = this.props;
|
async fetchData(props: CommunityRouteProps) {
|
||||||
const { name } = this.props.match.params;
|
const token = (this.fetchDataToken = Symbol());
|
||||||
|
const { dataType, pageCursor, sort, showHidden } = props;
|
||||||
|
const { name } = props.match.params;
|
||||||
|
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
this.setState({ postsRes: LOADING_REQUEST });
|
this.setState({ postsRes: LOADING_REQUEST, commentsRes: EMPTY_REQUEST });
|
||||||
this.setState({
|
const postsRes = await HttpService.client.getPosts({
|
||||||
postsRes: await HttpService.client.getPosts({
|
page_cursor: pageCursor,
|
||||||
page_cursor: pageCursor,
|
limit: fetchLimit,
|
||||||
limit: fetchLimit,
|
sort,
|
||||||
sort,
|
type_: "All",
|
||||||
type_: "All",
|
community_name: name,
|
||||||
community_name: name,
|
saved_only: false,
|
||||||
saved_only: false,
|
show_hidden: showHidden === "true",
|
||||||
show_hidden: showHidden === "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchDataToken) {
|
||||||
|
this.setState({ postsRes });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setState({ commentsRes: LOADING_REQUEST });
|
this.setState({ commentsRes: LOADING_REQUEST, postsRes: EMPTY_REQUEST });
|
||||||
this.setState({
|
const commentsRes = await HttpService.client.getComments({
|
||||||
commentsRes: await HttpService.client.getComments({
|
limit: fetchLimit,
|
||||||
limit: fetchLimit,
|
sort: postToCommentSortType(sort),
|
||||||
sort: postToCommentSortType(sort),
|
type_: "All",
|
||||||
type_: "All",
|
community_name: name,
|
||||||
community_name: name,
|
saved_only: false,
|
||||||
saved_only: false,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchDataToken) {
|
||||||
|
this.setState({ commentsRes });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,21 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
this.handleEditCancel = this.handleEditCancel.bind(this);
|
this.handleEditCancel = this.handleEditCancel.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unlisten = () => {};
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
// Leave edit mode on navigation
|
||||||
|
this.unlisten = this.context.router.history.listen(() => {
|
||||||
|
if (this.state.showEdit) {
|
||||||
|
this.setState({ showEdit: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.unlisten();
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(
|
componentWillReceiveProps(
|
||||||
nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>,
|
nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>,
|
||||||
): void {
|
): void {
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { IRoutePropsWithFetch } from "../../routes";
|
||||||
import { MediaUploads } from "../common/media-uploads";
|
import { MediaUploads } from "../common/media-uploads";
|
||||||
import { Paginator } from "../common/paginator";
|
import { Paginator } from "../common/paginator";
|
||||||
import { snapToTop } from "@utils/browser";
|
import { snapToTop } from "@utils/browser";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
type AdminSettingsData = RouteDataResponse<{
|
type AdminSettingsData = RouteDataResponse<{
|
||||||
bannedRes: BannedPersonsResponse;
|
bannedRes: BannedPersonsResponse;
|
||||||
|
@ -51,7 +52,6 @@ type AdminSettingsData = RouteDataResponse<{
|
||||||
interface AdminSettingsState {
|
interface AdminSettingsState {
|
||||||
siteRes: GetSiteResponse;
|
siteRes: GetSiteResponse;
|
||||||
banned: PersonView[];
|
banned: PersonView[];
|
||||||
currentTab: string;
|
|
||||||
instancesRes: RequestState<GetFederatedInstancesResponse>;
|
instancesRes: RequestState<GetFederatedInstancesResponse>;
|
||||||
bannedRes: RequestState<BannedPersonsResponse>;
|
bannedRes: RequestState<BannedPersonsResponse>;
|
||||||
leaveAdminTeamRes: RequestState<GetSiteResponse>;
|
leaveAdminTeamRes: RequestState<GetSiteResponse>;
|
||||||
|
@ -79,7 +79,6 @@ export class AdminSettings extends Component<
|
||||||
state: AdminSettingsState = {
|
state: AdminSettingsState = {
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
banned: [],
|
banned: [],
|
||||||
currentTab: "site",
|
|
||||||
bannedRes: EMPTY_REQUEST,
|
bannedRes: EMPTY_REQUEST,
|
||||||
instancesRes: EMPTY_REQUEST,
|
instancesRes: EMPTY_REQUEST,
|
||||||
leaveAdminTeamRes: EMPTY_REQUEST,
|
leaveAdminTeamRes: EMPTY_REQUEST,
|
||||||
|
@ -134,12 +133,14 @@ export class AdminSettings extends Component<
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (isBrowser()) {
|
||||||
await this.fetchData();
|
if (!this.state.isIsomorphic) {
|
||||||
} else {
|
await this.fetchData();
|
||||||
const themeList = await fetchThemeList();
|
} else {
|
||||||
this.setState({ themeList });
|
const themeList = await fetchThemeList();
|
||||||
|
this.setState({ themeList });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,10 +432,6 @@ export class AdminSettings extends Component<
|
||||||
return editRes;
|
return editRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
|
|
||||||
i.ctx.setState({ currentTab: i.tab });
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleLeaveAdminTeam(i: AdminSettings) {
|
async handleLeaveAdminTeam(i: AdminSettings) {
|
||||||
i.setState({ leaveAdminTeamRes: LOADING_REQUEST });
|
i.setState({ leaveAdminTeamRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -19,13 +19,14 @@ import {
|
||||||
getQueryString,
|
getQueryString,
|
||||||
getRandomFromList,
|
getRandomFromList,
|
||||||
resourcesSettled,
|
resourcesSettled,
|
||||||
|
bareRoutePush,
|
||||||
} from "@utils/helpers";
|
} from "@utils/helpers";
|
||||||
import { scrollMixin } from "../mixins/scroll-mixin";
|
import { scrollMixin } from "../mixins/scroll-mixin";
|
||||||
import { canCreateCommunity } from "@utils/roles";
|
import { canCreateCommunity } from "@utils/roles";
|
||||||
import type { QueryParams, StringBoolean } from "@utils/types";
|
import type { QueryParams, StringBoolean } from "@utils/types";
|
||||||
import { RouteDataResponse } from "@utils/types";
|
import { RouteDataResponse } from "@utils/types";
|
||||||
import { NoOptionI18nKeys } from "i18next";
|
import { NoOptionI18nKeys } from "i18next";
|
||||||
import { Component, MouseEventHandler, linkEvent } from "inferno";
|
import { Component, InfernoNode, MouseEventHandler, linkEvent } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
import { Link } from "inferno-router";
|
import { Link } from "inferno-router";
|
||||||
import {
|
import {
|
||||||
|
@ -112,7 +113,7 @@ import {
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
import PostHiddenSelect from "../common/post-hidden-select";
|
import PostHiddenSelect from "../common/post-hidden-select";
|
||||||
import { snapToTop } from "@utils/browser";
|
import { isBrowser, snapToTop } from "@utils/browser";
|
||||||
|
|
||||||
interface HomeState {
|
interface HomeState {
|
||||||
postsRes: RequestState<GetPostsResponse>;
|
postsRes: RequestState<GetPostsResponse>;
|
||||||
|
@ -344,14 +345,28 @@ export class Home extends Component<HomeRouteProps, HomeState> {
|
||||||
)?.content;
|
)?.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (
|
if (
|
||||||
!this.state.isIsomorphic ||
|
(!this.state.isIsomorphic ||
|
||||||
!Object.values(this.isoData.routeData).some(
|
!Object.values(this.isoData.routeData).some(
|
||||||
res => res.state === "success" || res.state === "failed",
|
res => res.state === "success" || res.state === "failed",
|
||||||
)
|
)) &&
|
||||||
|
isBrowser()
|
||||||
) {
|
) {
|
||||||
await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]);
|
await Promise.all([
|
||||||
|
this.fetchTrendingCommunities(),
|
||||||
|
this.fetchData(this.props),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(
|
||||||
|
nextProps: HomeRouteProps & { children?: InfernoNode },
|
||||||
|
) {
|
||||||
|
this.fetchData(nextProps);
|
||||||
|
|
||||||
|
if (bareRoutePush(this.props, nextProps)) {
|
||||||
|
this.fetchTrendingCommunities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -661,34 +676,23 @@ export class Home extends Component<HomeRouteProps, HomeState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUrl({
|
async updateUrl(props: Partial<HomeProps>) {
|
||||||
dataType,
|
const { dataType, listingType, pageCursor, sort, showHidden } = {
|
||||||
listingType,
|
...this.props,
|
||||||
pageCursor,
|
...props,
|
||||||
sort,
|
};
|
||||||
showHidden,
|
|
||||||
}: Partial<HomeProps>) {
|
|
||||||
const {
|
|
||||||
dataType: urlDataType,
|
|
||||||
listingType: urlListingType,
|
|
||||||
sort: urlSort,
|
|
||||||
showHidden: urlShowHidden,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const queryParams: QueryParams<HomeProps> = {
|
const queryParams: QueryParams<HomeProps> = {
|
||||||
dataType: getDataTypeString(dataType ?? urlDataType),
|
dataType: getDataTypeString(dataType ?? DataType.Post),
|
||||||
listingType: listingType ?? urlListingType,
|
listingType: listingType,
|
||||||
pageCursor: pageCursor,
|
pageCursor: pageCursor,
|
||||||
sort: sort ?? urlSort,
|
sort: sort,
|
||||||
showHidden: showHidden ?? urlShowHidden,
|
showHidden: showHidden,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.history.push({
|
this.props.history.push({
|
||||||
pathname: "/",
|
pathname: "/",
|
||||||
search: getQueryString(queryParams),
|
search: getQueryString(queryParams),
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.fetchData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get posts() {
|
get posts() {
|
||||||
|
@ -854,31 +858,39 @@ export class Home extends Component<HomeRouteProps, HomeState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchData() {
|
fetchDataToken?: symbol;
|
||||||
const { dataType, pageCursor, listingType, sort, showHidden } = this.props;
|
async fetchData({
|
||||||
|
dataType,
|
||||||
|
pageCursor,
|
||||||
|
listingType,
|
||||||
|
sort,
|
||||||
|
showHidden,
|
||||||
|
}: HomeProps) {
|
||||||
|
const token = (this.fetchDataToken = Symbol());
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
this.setState({ postsRes: LOADING_REQUEST });
|
this.setState({ postsRes: LOADING_REQUEST, commentsRes: EMPTY_REQUEST });
|
||||||
this.setState({
|
const postsRes = await HttpService.client.getPosts({
|
||||||
postsRes: await HttpService.client.getPosts({
|
page_cursor: pageCursor,
|
||||||
page_cursor: pageCursor,
|
limit: fetchLimit,
|
||||||
limit: fetchLimit,
|
sort,
|
||||||
sort,
|
saved_only: false,
|
||||||
saved_only: false,
|
type_: listingType,
|
||||||
type_: listingType,
|
show_hidden: showHidden === "true",
|
||||||
show_hidden: showHidden === "true",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchDataToken) {
|
||||||
|
this.setState({ postsRes });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setState({ commentsRes: LOADING_REQUEST });
|
this.setState({ commentsRes: LOADING_REQUEST, postsRes: EMPTY_REQUEST });
|
||||||
this.setState({
|
const commentsRes = await HttpService.client.getComments({
|
||||||
commentsRes: await HttpService.client.getComments({
|
limit: fetchLimit,
|
||||||
limit: fetchLimit,
|
sort: postToCommentSortType(sort),
|
||||||
sort: postToCommentSortType(sort),
|
saved_only: false,
|
||||||
saved_only: false,
|
type_: listingType,
|
||||||
type_: listingType,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchDataToken) {
|
||||||
|
this.setState({ commentsRes });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
import { resourcesSettled } from "@utils/helpers";
|
import { resourcesSettled } from "@utils/helpers";
|
||||||
import { scrollMixin } from "../mixins/scroll-mixin";
|
import { scrollMixin } from "../mixins/scroll-mixin";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
type InstancesData = RouteDataResponse<{
|
type InstancesData = RouteDataResponse<{
|
||||||
federatedInstancesResponse: GetFederatedInstancesResponse;
|
federatedInstancesResponse: GetFederatedInstancesResponse;
|
||||||
|
@ -71,8 +72,8 @@ export class Instances extends Component<InstancesRouteProps, InstancesState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
await this.fetchInstances();
|
await this.fetchInstances();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import PasswordInput from "../common/password-input";
|
||||||
import { SiteForm } from "./site-form";
|
import { SiteForm } from "./site-form";
|
||||||
import { simpleScrollMixin } from "../mixins/scroll-mixin";
|
import { simpleScrollMixin } from "../mixins/scroll-mixin";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
form: {
|
form: {
|
||||||
|
@ -61,8 +62,10 @@ export class Setup extends Component<
|
||||||
this.handleCreateSite = this.handleCreateSite.bind(this);
|
this.handleCreateSite = this.handleCreateSite.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
this.setState({ themeList: await fetchThemeList() });
|
if (isBrowser()) {
|
||||||
|
this.setState({ themeList: await fetchThemeList() });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get documentTitle(): string {
|
get documentTitle(): string {
|
||||||
|
|
|
@ -76,8 +76,11 @@ export class Signup extends Component<
|
||||||
this.handleAnswerChange = this.handleAnswerChange.bind(this);
|
this.handleAnswerChange = this.handleAnswerChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (this.state.siteRes.site_view.local_site.captcha_enabled) {
|
if (
|
||||||
|
this.state.siteRes.site_view.local_site.captcha_enabled &&
|
||||||
|
isBrowser()
|
||||||
|
) {
|
||||||
await this.fetchCaptcha();
|
await this.fetchCaptcha();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Modal } from "bootstrap";
|
||||||
import { Component, InfernoNode, RefObject } from "inferno";
|
import { Component, InfernoNode, RefObject } from "inferno";
|
||||||
|
|
||||||
export function modalMixin<
|
export function modalMixin<
|
||||||
P extends { show: boolean },
|
P extends { show?: boolean },
|
||||||
S,
|
S,
|
||||||
Base extends new (...args: any[]) => Component<P, S> & {
|
Base extends new (...args: any[]) => Component<P, S> & {
|
||||||
readonly modalDivRef: RefObject<HTMLDivElement>;
|
readonly modalDivRef: RefObject<HTMLDivElement>;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { isBrowser, nextUserAction, snapToTop } from "../../utils/browser";
|
import { isBrowser, nextUserAction, snapToTop } from "../../utils/browser";
|
||||||
import { Component, InfernoNode } from "inferno";
|
import { Component, InfernoNode } from "inferno";
|
||||||
import { Location } from "history";
|
import { Location, History, Action } from "history";
|
||||||
|
|
||||||
function restoreScrollPosition(props: { location: Location }) {
|
function restoreScrollPosition(props: { location: Location }) {
|
||||||
const key: string = props.location.key;
|
const key: string = props.location.key;
|
||||||
|
@ -25,7 +25,7 @@ function dropScrollPosition(props: { location: Location }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scrollMixin<
|
export function scrollMixin<
|
||||||
P extends { location: Location },
|
P extends { location: Location; history: History },
|
||||||
S,
|
S,
|
||||||
Base extends new (
|
Base extends new (
|
||||||
...args: any
|
...args: any
|
||||||
|
@ -68,10 +68,11 @@ export function scrollMixin<
|
||||||
nextProps: Readonly<{ children?: InfernoNode } & P>,
|
nextProps: Readonly<{ children?: InfernoNode } & P>,
|
||||||
nextContext: any,
|
nextContext: any,
|
||||||
) {
|
) {
|
||||||
// Currently this is hypothetical. Components unmount before route changes.
|
|
||||||
if (this.props.location.key !== nextProps.location.key) {
|
if (this.props.location.key !== nextProps.location.key) {
|
||||||
this.saveFinalPosition();
|
if (nextProps.history.action !== Action.Replace) {
|
||||||
this.reset();
|
this.saveFinalPosition();
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return super.componentWillReceiveProps?.(nextProps, nextContext);
|
return super.componentWillReceiveProps?.(nextProps, nextContext);
|
||||||
}
|
}
|
||||||
|
@ -131,7 +132,7 @@ export function scrollMixin<
|
||||||
}
|
}
|
||||||
|
|
||||||
export function simpleScrollMixin<
|
export function simpleScrollMixin<
|
||||||
P extends { location: Location },
|
P extends { location: Location; history: History },
|
||||||
S,
|
S,
|
||||||
Base extends new (...args: any) => Component<P, S>,
|
Base extends new (...args: any) => Component<P, S>,
|
||||||
>(base: Base, _context?: ClassDecoratorContext<Base>) {
|
>(base: Base, _context?: ClassDecoratorContext<Base>) {
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { fetchUsers, personToChoice, setIsoData } from "@utils/app";
|
||||||
fetchUsers,
|
|
||||||
getUpdatedSearchId,
|
|
||||||
personToChoice,
|
|
||||||
setIsoData,
|
|
||||||
} from "@utils/app";
|
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
formatPastDate,
|
formatPastDate,
|
||||||
|
@ -12,6 +7,7 @@ import {
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
getQueryString,
|
getQueryString,
|
||||||
resourcesSettled,
|
resourcesSettled,
|
||||||
|
bareRoutePush,
|
||||||
} from "@utils/helpers";
|
} from "@utils/helpers";
|
||||||
import { scrollMixin } from "./mixins/scroll-mixin";
|
import { scrollMixin } from "./mixins/scroll-mixin";
|
||||||
import { amAdmin, amMod } from "@utils/roles";
|
import { amAdmin, amMod } from "@utils/roles";
|
||||||
|
@ -66,6 +62,8 @@ import { CommunityLink } from "./community/community-link";
|
||||||
import { PersonListing } from "./person/person-listing";
|
import { PersonListing } from "./person/person-listing";
|
||||||
import { getHttpBaseInternal } from "../utils/env";
|
import { getHttpBaseInternal } from "../utils/env";
|
||||||
import { IRoutePropsWithFetch } from "../routes";
|
import { IRoutePropsWithFetch } from "../routes";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
import { LoadingEllipses } from "./common/loading-ellipses";
|
||||||
|
|
||||||
type FilterType = "mod" | "user";
|
type FilterType = "mod" | "user";
|
||||||
|
|
||||||
|
@ -703,40 +701,68 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
const { modId, userId } = this.props;
|
await Promise.all([
|
||||||
const promises = [this.refetch()];
|
this.fetchModlog(this.props),
|
||||||
|
this.fetchCommunity(this.props),
|
||||||
|
this.fetchUser(this.props),
|
||||||
|
this.fetchMod(this.props),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (userId) {
|
componentWillReceiveProps(nextProps: ModlogRouteProps) {
|
||||||
promises.push(
|
this.fetchModlog(nextProps);
|
||||||
HttpService.client
|
|
||||||
.getPersonDetails({ person_id: userId })
|
const reload = bareRoutePush(this.props, nextProps);
|
||||||
.then(res => {
|
|
||||||
if (res.state === "success") {
|
if (nextProps.modId !== this.props.modId || reload) {
|
||||||
this.setState({
|
this.fetchMod(nextProps);
|
||||||
userSearchOptions: [personToChoice(res.data.person_view)],
|
}
|
||||||
});
|
if (nextProps.userId !== this.props.userId || reload) {
|
||||||
}
|
this.fetchUser(nextProps);
|
||||||
}),
|
}
|
||||||
);
|
if (
|
||||||
|
nextProps.match.params.communityId !==
|
||||||
|
this.props.match.params.communityId ||
|
||||||
|
reload
|
||||||
|
) {
|
||||||
|
this.fetchCommunity(nextProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUserToken?: symbol;
|
||||||
|
async fetchUser(props: ModlogRouteProps) {
|
||||||
|
const token = (this.fetchUserToken = Symbol());
|
||||||
|
const { userId } = props;
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
const res = await HttpService.client.getPersonDetails({
|
||||||
|
person_id: userId,
|
||||||
|
});
|
||||||
|
if (res.state === "success" && token === this.fetchUserToken) {
|
||||||
|
this.setState({
|
||||||
|
userSearchOptions: [personToChoice(res.data.person_view)],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (modId) {
|
fetchModToken?: symbol;
|
||||||
promises.push(
|
async fetchMod(props: ModlogRouteProps) {
|
||||||
HttpService.client
|
const token = (this.fetchModToken = Symbol());
|
||||||
.getPersonDetails({ person_id: modId })
|
const { modId } = props;
|
||||||
.then(res => {
|
|
||||||
if (res.state === "success") {
|
if (modId) {
|
||||||
this.setState({
|
const res = await HttpService.client.getPersonDetails({
|
||||||
modSearchOptions: [personToChoice(res.data.person_view)],
|
person_id: modId,
|
||||||
});
|
});
|
||||||
}
|
if (res.state === "success" && token === this.fetchModToken) {
|
||||||
}),
|
this.setState({
|
||||||
);
|
modSearchOptions: [personToChoice(res.data.person_view)],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -793,6 +819,11 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
|
||||||
modSearchOptions,
|
modSearchOptions,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { actionType, modId, userId } = this.props;
|
const { actionType, modId, userId } = this.props;
|
||||||
|
const { communityId } = this.props.match.params;
|
||||||
|
|
||||||
|
const communityState = this.state.communityRes.state;
|
||||||
|
const communityResp =
|
||||||
|
communityState === "success" && this.state.communityRes.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modlog container-lg">
|
<div className="modlog container-lg">
|
||||||
|
@ -816,15 +847,26 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
|
||||||
#<strong>#</strong>#
|
#<strong>#</strong>#
|
||||||
</T>
|
</T>
|
||||||
</div>
|
</div>
|
||||||
{this.state.communityRes.state === "success" && (
|
{communityId && (
|
||||||
<h5>
|
<h5>
|
||||||
<Link
|
{communityResp ? (
|
||||||
className="text-body"
|
<>
|
||||||
to={`/c/${this.state.communityRes.data.community_view.community.name}`}
|
<Link
|
||||||
>
|
className="text-body"
|
||||||
/c/{this.state.communityRes.data.community_view.community.name}{" "}
|
to={`/c/${communityResp.community_view.community.name}`}
|
||||||
</Link>
|
>
|
||||||
<span>{I18NextService.i18n.t("modlog")}</span>
|
/c/{communityResp.community_view.community.name}
|
||||||
|
</Link>{" "}
|
||||||
|
<span>{I18NextService.i18n.t("modlog")}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
communityState === "loading" && (
|
||||||
|
<>
|
||||||
|
<LoadingEllipses />
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</h5>
|
</h5>
|
||||||
)}
|
)}
|
||||||
<div className="row mb-2">
|
<div className="row mb-2">
|
||||||
|
@ -935,6 +977,10 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchUsers = debounce(async (text: string) => {
|
handleSearchUsers = debounce(async (text: string) => {
|
||||||
|
if (!text.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { userId } = this.props;
|
const { userId } = this.props;
|
||||||
const { userSearchOptions } = this.state;
|
const { userSearchOptions } = this.state;
|
||||||
this.setState({ loadingUserSearch: true });
|
this.setState({ loadingUserSearch: true });
|
||||||
|
@ -952,6 +998,10 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
|
||||||
});
|
});
|
||||||
|
|
||||||
handleSearchMods = debounce(async (text: string) => {
|
handleSearchMods = debounce(async (text: string) => {
|
||||||
|
if (!text.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { modId } = this.props;
|
const { modId } = this.props;
|
||||||
const { modSearchOptions } = this.state;
|
const { modSearchOptions } = this.state;
|
||||||
this.setState({ loadingModSearch: true });
|
this.setState({ loadingModSearch: true });
|
||||||
|
@ -968,61 +1018,73 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
|
async updateUrl(props: Partial<ModlogProps>) {
|
||||||
const {
|
const {
|
||||||
page: urlPage,
|
actionType,
|
||||||
actionType: urlActionType,
|
modId,
|
||||||
modId: urlModId,
|
page,
|
||||||
userId: urlUserId,
|
userId,
|
||||||
} = this.props;
|
match: {
|
||||||
|
params: { communityId },
|
||||||
|
},
|
||||||
|
} = { ...this.props, ...props };
|
||||||
|
|
||||||
const queryParams: QueryParams<ModlogProps> = {
|
const queryParams: QueryParams<ModlogProps> = {
|
||||||
page: (page ?? urlPage).toString(),
|
page: page.toString(),
|
||||||
actionType: actionType ?? urlActionType,
|
actionType: actionType,
|
||||||
modId: getUpdatedSearchId(modId, urlModId),
|
modId: modId?.toString(),
|
||||||
userId: getUpdatedSearchId(userId, urlUserId),
|
userId: userId?.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const communityId = this.props.match.params.communityId;
|
|
||||||
|
|
||||||
this.props.history.push(
|
this.props.history.push(
|
||||||
`/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
|
`/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
|
||||||
queryParams,
|
queryParams,
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.refetch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
fetchModlogToken?: symbol;
|
||||||
const { actionType, page, modId, userId, postId, commentId } = this.props;
|
async fetchModlog(props: ModlogRouteProps) {
|
||||||
const { communityId: urlCommunityId } = this.props.match.params;
|
const token = (this.fetchModlogToken = Symbol());
|
||||||
|
const { actionType, page, modId, userId, postId, commentId } = props;
|
||||||
|
const { communityId: urlCommunityId } = props.match.params;
|
||||||
const communityId = getIdFromString(urlCommunityId);
|
const communityId = getIdFromString(urlCommunityId);
|
||||||
|
|
||||||
this.setState({ res: LOADING_REQUEST });
|
this.setState({ res: LOADING_REQUEST });
|
||||||
this.setState({
|
const res = await HttpService.client.getModlog({
|
||||||
res: await HttpService.client.getModlog({
|
community_id: communityId,
|
||||||
community_id: communityId,
|
page,
|
||||||
page,
|
limit: fetchLimit,
|
||||||
limit: fetchLimit,
|
type_: actionType,
|
||||||
type_: actionType,
|
other_person_id: userId,
|
||||||
other_person_id: userId,
|
mod_person_id: !this.isoData.site_res.site_view.local_site
|
||||||
mod_person_id: !this.isoData.site_res.site_view.local_site
|
.hide_modlog_mod_names
|
||||||
.hide_modlog_mod_names
|
? modId
|
||||||
? modId
|
: undefined,
|
||||||
: undefined,
|
comment_id: commentId,
|
||||||
comment_id: commentId,
|
post_id: postId,
|
||||||
post_id: postId,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchModlogToken) {
|
||||||
|
this.setState({ res });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCommunityToken?: symbol;
|
||||||
|
async fetchCommunity(props: ModlogRouteProps) {
|
||||||
|
const token = (this.fetchCommunityToken = Symbol());
|
||||||
|
const { communityId: urlCommunityId } = props.match.params;
|
||||||
|
const communityId = getIdFromString(urlCommunityId);
|
||||||
|
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
this.setState({ communityRes: LOADING_REQUEST });
|
this.setState({ communityRes: LOADING_REQUEST });
|
||||||
this.setState({
|
const communityRes = await HttpService.client.getCommunity({
|
||||||
communityRes: await HttpService.client.getCommunity({
|
id: communityId,
|
||||||
id: communityId,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchCommunityToken) {
|
||||||
|
this.setState({ communityRes });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({ communityRes: EMPTY_REQUEST });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,7 @@ import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { CommentsLoadingSkeleton } from "../common/loading-skeleton";
|
import { CommentsLoadingSkeleton } from "../common/loading-skeleton";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
enum UnreadOrAll {
|
enum UnreadOrAll {
|
||||||
Unread,
|
Unread,
|
||||||
|
@ -213,8 +214,8 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
await this.refetch();
|
await this.refetch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -784,40 +785,60 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
|
||||||
return inboxData;
|
return inboxData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refetchToken?: symbol;
|
||||||
async refetch() {
|
async refetch() {
|
||||||
|
const token = (this.refetchToken = Symbol());
|
||||||
const sort = this.state.sort;
|
const sort = this.state.sort;
|
||||||
const unread_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
const unread_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
||||||
const page = this.state.page;
|
const page = this.state.page;
|
||||||
const limit = fetchLimit;
|
const limit = fetchLimit;
|
||||||
|
|
||||||
this.setState({ repliesRes: LOADING_REQUEST });
|
|
||||||
this.setState({
|
this.setState({
|
||||||
repliesRes: await HttpService.client.getReplies({
|
repliesRes: LOADING_REQUEST,
|
||||||
|
mentionsRes: LOADING_REQUEST,
|
||||||
|
messagesRes: LOADING_REQUEST,
|
||||||
|
});
|
||||||
|
const repliesPromise = HttpService.client
|
||||||
|
.getReplies({
|
||||||
sort,
|
sort,
|
||||||
unread_only,
|
unread_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
}),
|
})
|
||||||
});
|
.then(repliesRes => {
|
||||||
|
if (token === this.refetchToken) {
|
||||||
|
this.setState({
|
||||||
|
repliesRes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.setState({ mentionsRes: LOADING_REQUEST });
|
const mentionsPromise = HttpService.client
|
||||||
this.setState({
|
.getPersonMentions({
|
||||||
mentionsRes: await HttpService.client.getPersonMentions({
|
|
||||||
sort,
|
sort,
|
||||||
unread_only,
|
unread_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
}),
|
})
|
||||||
});
|
.then(mentionsRes => {
|
||||||
|
if (token === this.refetchToken) {
|
||||||
|
this.setState({ mentionsRes });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.setState({ messagesRes: LOADING_REQUEST });
|
const messagesPromise = HttpService.client
|
||||||
this.setState({
|
.getPrivateMessages({
|
||||||
messagesRes: await HttpService.client.getPrivateMessages({
|
|
||||||
unread_only,
|
unread_only,
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
}),
|
})
|
||||||
});
|
.then(messagesRes => {
|
||||||
|
if (token === this.refetchToken) {
|
||||||
|
this.setState({ messagesRes });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([repliesPromise, mentionsPromise, messagesPromise]);
|
||||||
UnreadCounterService.Instance.updateInboxCounts();
|
UnreadCounterService.Instance.updateInboxCounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
numToSI,
|
numToSI,
|
||||||
randomStr,
|
randomStr,
|
||||||
resourcesSettled,
|
resourcesSettled,
|
||||||
|
bareRoutePush,
|
||||||
} from "@utils/helpers";
|
} from "@utils/helpers";
|
||||||
import { canMod } from "@utils/roles";
|
import { canMod } from "@utils/roles";
|
||||||
import type { QueryParams } from "@utils/types";
|
import type { QueryParams } from "@utils/types";
|
||||||
|
@ -100,6 +101,7 @@ import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
import { MediaUploads } from "../common/media-uploads";
|
import { MediaUploads } from "../common/media-uploads";
|
||||||
import { cakeDate } from "@utils/helpers";
|
import { cakeDate } from "@utils/helpers";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
type ProfileData = RouteDataResponse<{
|
type ProfileData = RouteDataResponse<{
|
||||||
personRes: GetPersonDetailsResponse;
|
personRes: GetPersonDetailsResponse;
|
||||||
|
@ -108,6 +110,9 @@ type ProfileData = RouteDataResponse<{
|
||||||
|
|
||||||
interface ProfileState {
|
interface ProfileState {
|
||||||
personRes: RequestState<GetPersonDetailsResponse>;
|
personRes: RequestState<GetPersonDetailsResponse>;
|
||||||
|
// personRes and personDetailsRes point to `===` identical data. This allows
|
||||||
|
// to render the start of the profile while the new details are loading.
|
||||||
|
personDetailsRes: RequestState<GetPersonDetailsResponse>;
|
||||||
uploadsRes: RequestState<ListMediaResponse>;
|
uploadsRes: RequestState<ListMediaResponse>;
|
||||||
personBlocked: boolean;
|
personBlocked: boolean;
|
||||||
banReason?: string;
|
banReason?: string;
|
||||||
|
@ -195,6 +200,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
private isoData = setIsoData<ProfileData>(this.context);
|
private isoData = setIsoData<ProfileData>(this.context);
|
||||||
state: ProfileState = {
|
state: ProfileState = {
|
||||||
personRes: EMPTY_REQUEST,
|
personRes: EMPTY_REQUEST,
|
||||||
|
personDetailsRes: EMPTY_REQUEST,
|
||||||
uploadsRes: EMPTY_REQUEST,
|
uploadsRes: EMPTY_REQUEST,
|
||||||
personBlocked: false,
|
personBlocked: false,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
|
@ -205,7 +211,12 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
loadingSettled() {
|
loadingSettled() {
|
||||||
return resourcesSettled([this.state.personRes]);
|
return resourcesSettled([
|
||||||
|
this.state.personRes,
|
||||||
|
this.props.view === PersonDetailsView.Uploads
|
||||||
|
? this.state.uploadsRes
|
||||||
|
: this.state.personDetailsRes,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: ProfileRouteProps, context: any) {
|
constructor(props: ProfileRouteProps, context: any) {
|
||||||
|
@ -253,6 +264,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
personRes,
|
personRes,
|
||||||
|
personDetailsRes: personRes,
|
||||||
uploadsRes,
|
uploadsRes,
|
||||||
isIsomorphic: true,
|
isIsomorphic: true,
|
||||||
personBlocked: isPersonBlocked(personRes),
|
personBlocked: isPersonBlocked(personRes),
|
||||||
|
@ -260,37 +272,96 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
await this.fetchUserData();
|
await this.fetchUserData(this.props, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchUserData() {
|
componentWillReceiveProps(nextProps: ProfileRouteProps) {
|
||||||
const { page, sort, view } = this.props;
|
// Overview, Posts and Comments views can use the same data.
|
||||||
|
const sharedViewTypes = [nextProps.view, this.props.view].every(
|
||||||
|
v =>
|
||||||
|
v === PersonDetailsView.Overview ||
|
||||||
|
v === PersonDetailsView.Posts ||
|
||||||
|
v === PersonDetailsView.Comments,
|
||||||
|
);
|
||||||
|
|
||||||
|
const reload = bareRoutePush(this.props, nextProps);
|
||||||
|
|
||||||
|
const newUsername =
|
||||||
|
nextProps.match.params.username !== this.props.match.params.username;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(nextProps.view !== this.props.view && !sharedViewTypes) ||
|
||||||
|
nextProps.sort !== this.props.sort ||
|
||||||
|
nextProps.page !== this.props.page ||
|
||||||
|
newUsername ||
|
||||||
|
reload
|
||||||
|
) {
|
||||||
|
this.fetchUserData(nextProps, reload || newUsername);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUploadsToken?: symbol;
|
||||||
|
async fetchUploads(props: ProfileRouteProps) {
|
||||||
|
const token = (this.fetchUploadsToken = Symbol());
|
||||||
|
const { page } = props;
|
||||||
|
this.setState({ uploadsRes: LOADING_REQUEST });
|
||||||
|
const form: ListMedia = {
|
||||||
|
// userId?
|
||||||
|
page,
|
||||||
|
limit: fetchLimit,
|
||||||
|
};
|
||||||
|
const uploadsRes = await HttpService.client.listMedia(form);
|
||||||
|
if (token === this.fetchUploadsToken) {
|
||||||
|
this.setState({ uploadsRes });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUserDataToken?: symbol;
|
||||||
|
async fetchUserData(props: ProfileRouteProps, showBothLoading = false) {
|
||||||
|
const token = (this.fetchUploadsToken = this.fetchUserDataToken = Symbol());
|
||||||
|
const { page, sort, view } = props;
|
||||||
|
|
||||||
|
if (view === PersonDetailsView.Uploads) {
|
||||||
|
this.fetchUploads(props);
|
||||||
|
if (!showBothLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
personRes: LOADING_REQUEST,
|
||||||
|
personDetailsRes: LOADING_REQUEST,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (showBothLoading) {
|
||||||
|
this.setState({
|
||||||
|
personRes: LOADING_REQUEST,
|
||||||
|
personDetailsRes: LOADING_REQUEST,
|
||||||
|
uploadsRes: EMPTY_REQUEST,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
personDetailsRes: LOADING_REQUEST,
|
||||||
|
uploadsRes: EMPTY_REQUEST,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ personRes: LOADING_REQUEST });
|
|
||||||
const personRes = await HttpService.client.getPersonDetails({
|
const personRes = await HttpService.client.getPersonDetails({
|
||||||
username: this.props.match.params.username,
|
username: props.match.params.username,
|
||||||
sort,
|
sort,
|
||||||
saved_only: view === PersonDetailsView.Saved,
|
saved_only: view === PersonDetailsView.Saved,
|
||||||
page,
|
page,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
if (token === this.fetchUserDataToken) {
|
||||||
personRes,
|
this.setState({
|
||||||
personBlocked: isPersonBlocked(personRes),
|
personRes,
|
||||||
});
|
personDetailsRes: personRes,
|
||||||
|
personBlocked: isPersonBlocked(personRes),
|
||||||
if (view === PersonDetailsView.Uploads) {
|
});
|
||||||
this.setState({ uploadsRes: LOADING_REQUEST });
|
|
||||||
const form: ListMedia = {
|
|
||||||
page,
|
|
||||||
limit: fetchLimit,
|
|
||||||
};
|
|
||||||
const uploadsRes = await HttpService.client.listMedia(form);
|
|
||||||
this.setState({ uploadsRes });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,6 +455,10 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
const personRes = this.state.personRes.data;
|
const personRes = this.state.personRes.data;
|
||||||
const { page, sort, view } = this.props;
|
const { page, sort, view } = this.props;
|
||||||
|
|
||||||
|
const personDetailsState = this.state.personDetailsRes.state;
|
||||||
|
const personDetailsRes =
|
||||||
|
personDetailsState === "success" && this.state.personDetailsRes.data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 col-md-8">
|
<div className="col-12 col-md-8">
|
||||||
|
@ -403,50 +478,59 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
|
|
||||||
{this.renderUploadsRes()}
|
{this.renderUploadsRes()}
|
||||||
|
|
||||||
<PersonDetails
|
{personDetailsState === "loading" &&
|
||||||
personRes={personRes}
|
this.props.view !== PersonDetailsView.Uploads ? (
|
||||||
admins={siteRes.admins}
|
<h5>
|
||||||
sort={sort}
|
<Spinner large />
|
||||||
page={page}
|
</h5>
|
||||||
limit={fetchLimit}
|
) : (
|
||||||
finished={this.state.finished}
|
personDetailsRes && (
|
||||||
enableDownvotes={enableDownvotes(siteRes)}
|
<PersonDetails
|
||||||
voteDisplayMode={voteDisplayMode(siteRes)}
|
personRes={personDetailsRes}
|
||||||
enableNsfw={enableNsfw(siteRes)}
|
admins={siteRes.admins}
|
||||||
view={view}
|
sort={sort}
|
||||||
onPageChange={this.handlePageChange}
|
page={page}
|
||||||
allLanguages={siteRes.all_languages}
|
limit={fetchLimit}
|
||||||
siteLanguages={siteRes.discussion_languages}
|
finished={this.state.finished}
|
||||||
// TODO all the forms here
|
enableDownvotes={enableDownvotes(siteRes)}
|
||||||
onSaveComment={this.handleSaveComment}
|
voteDisplayMode={voteDisplayMode(siteRes)}
|
||||||
onBlockPerson={this.handleBlockPersonAlt}
|
enableNsfw={enableNsfw(siteRes)}
|
||||||
onDeleteComment={this.handleDeleteComment}
|
view={view}
|
||||||
onRemoveComment={this.handleRemoveComment}
|
onPageChange={this.handlePageChange}
|
||||||
onCommentVote={this.handleCommentVote}
|
allLanguages={siteRes.all_languages}
|
||||||
onCommentReport={this.handleCommentReport}
|
siteLanguages={siteRes.discussion_languages}
|
||||||
onDistinguishComment={this.handleDistinguishComment}
|
// TODO all the forms here
|
||||||
onAddModToCommunity={this.handleAddModToCommunity}
|
onSaveComment={this.handleSaveComment}
|
||||||
onAddAdmin={this.handleAddAdmin}
|
onBlockPerson={this.handleBlockPersonAlt}
|
||||||
onTransferCommunity={this.handleTransferCommunity}
|
onDeleteComment={this.handleDeleteComment}
|
||||||
onPurgeComment={this.handlePurgeComment}
|
onRemoveComment={this.handleRemoveComment}
|
||||||
onPurgePerson={this.handlePurgePerson}
|
onCommentVote={this.handleCommentVote}
|
||||||
onCommentReplyRead={this.handleCommentReplyRead}
|
onCommentReport={this.handleCommentReport}
|
||||||
onPersonMentionRead={this.handlePersonMentionRead}
|
onDistinguishComment={this.handleDistinguishComment}
|
||||||
onBanPersonFromCommunity={this.handleBanFromCommunity}
|
onAddModToCommunity={this.handleAddModToCommunity}
|
||||||
onBanPerson={this.handleBanPerson}
|
onAddAdmin={this.handleAddAdmin}
|
||||||
onCreateComment={this.handleCreateComment}
|
onTransferCommunity={this.handleTransferCommunity}
|
||||||
onEditComment={this.handleEditComment}
|
onPurgeComment={this.handlePurgeComment}
|
||||||
onPostEdit={this.handlePostEdit}
|
onPurgePerson={this.handlePurgePerson}
|
||||||
onPostVote={this.handlePostVote}
|
onCommentReplyRead={this.handleCommentReplyRead}
|
||||||
onPostReport={this.handlePostReport}
|
onPersonMentionRead={this.handlePersonMentionRead}
|
||||||
onLockPost={this.handleLockPost}
|
onBanPersonFromCommunity={this.handleBanFromCommunity}
|
||||||
onDeletePost={this.handleDeletePost}
|
onBanPerson={this.handleBanPerson}
|
||||||
onRemovePost={this.handleRemovePost}
|
onCreateComment={this.handleCreateComment}
|
||||||
onSavePost={this.handleSavePost}
|
onEditComment={this.handleEditComment}
|
||||||
onPurgePost={this.handlePurgePost}
|
onPostEdit={this.handlePostEdit}
|
||||||
onFeaturePost={this.handleFeaturePost}
|
onPostVote={this.handlePostVote}
|
||||||
onMarkPostAsRead={() => {}}
|
onPostReport={this.handlePostReport}
|
||||||
/>
|
onLockPost={this.handleLockPost}
|
||||||
|
onDeletePost={this.handleDeletePost}
|
||||||
|
onRemovePost={this.handleRemovePost}
|
||||||
|
onSavePost={this.handleSavePost}
|
||||||
|
onPurgePost={this.handlePurgePost}
|
||||||
|
onFeaturePost={this.handleFeaturePost}
|
||||||
|
onMarkPostAsRead={() => {}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 col-md-4">
|
<div className="col-12 col-md-4">
|
||||||
|
@ -788,19 +872,23 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUrl({ page, sort, view }: Partial<ProfileProps>) {
|
async updateUrl(props: Partial<ProfileRouteProps>) {
|
||||||
const { page: urlPage, sort: urlSort, view: urlView } = this.props;
|
const {
|
||||||
|
page,
|
||||||
|
sort,
|
||||||
|
view,
|
||||||
|
match: {
|
||||||
|
params: { username },
|
||||||
|
},
|
||||||
|
} = { ...this.props, ...props };
|
||||||
|
|
||||||
const queryParams: QueryParams<ProfileProps> = {
|
const queryParams: QueryParams<ProfileProps> = {
|
||||||
page: (page ?? urlPage).toString(),
|
page: page?.toString(),
|
||||||
sort: sort ?? urlSort,
|
sort,
|
||||||
view: view ?? urlView,
|
view,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { username } = this.props.match.params;
|
|
||||||
|
|
||||||
this.props.history.push(`/u/${username}${getQueryString(queryParams)}`);
|
this.props.history.push(`/u/${username}${getQueryString(queryParams)}`);
|
||||||
await this.fetchUserData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePageChange(page: number) {
|
handlePageChange(page: number) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { UnreadCounterService } from "../../services";
|
||||||
import { getHttpBaseInternal } from "../../utils/env";
|
import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
enum RegistrationState {
|
enum RegistrationState {
|
||||||
Unread,
|
Unread,
|
||||||
|
@ -92,8 +93,8 @@ export class RegistrationApplications extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
await this.refetch();
|
await this.refetch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,37 +109,41 @@ export class RegistrationApplications extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
renderApps() {
|
renderApps() {
|
||||||
switch (this.state.appsRes.state) {
|
const appsState = this.state.appsRes.state;
|
||||||
case "loading":
|
const apps =
|
||||||
return (
|
appsState === "success" &&
|
||||||
<h5>
|
this.state.appsRes.data.registration_applications;
|
||||||
<Spinner large />
|
|
||||||
</h5>
|
return (
|
||||||
);
|
<div className="row">
|
||||||
case "success": {
|
<div className="col-12">
|
||||||
const apps = this.state.appsRes.data.registration_applications;
|
<HtmlTags
|
||||||
return (
|
title={this.documentTitle}
|
||||||
<div className="row">
|
path={this.context.router.route.match.url}
|
||||||
<div className="col-12">
|
/>
|
||||||
<HtmlTags
|
<h1 className="h4 mb-4">
|
||||||
title={this.documentTitle}
|
{I18NextService.i18n.t("registration_applications")}
|
||||||
path={this.context.router.route.match.url}
|
</h1>
|
||||||
/>
|
{this.selects()}
|
||||||
<h1 className="h4 mb-4">
|
{apps ? (
|
||||||
{I18NextService.i18n.t("registration_applications")}
|
<>
|
||||||
</h1>
|
|
||||||
{this.selects()}
|
|
||||||
{this.applicationList(apps)}
|
{this.applicationList(apps)}
|
||||||
<Paginator
|
<Paginator
|
||||||
page={this.state.page}
|
page={this.state.page}
|
||||||
onChange={this.handlePageChange}
|
onChange={this.handlePageChange}
|
||||||
nextDisabled={fetchLimit > apps.length}
|
nextDisabled={fetchLimit > apps.length}
|
||||||
/>
|
/>
|
||||||
</div>
|
</>
|
||||||
</div>
|
) : (
|
||||||
);
|
appsState === "loading" && (
|
||||||
}
|
<div className="text-center">
|
||||||
}
|
<Spinner large />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -263,19 +268,22 @@ export class RegistrationApplications extends Component<
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refetchToken?: symbol;
|
||||||
async refetch() {
|
async refetch() {
|
||||||
|
const token = (this.refetchToken = Symbol());
|
||||||
const unread_only =
|
const unread_only =
|
||||||
this.state.registrationState === RegistrationState.Unread;
|
this.state.registrationState === RegistrationState.Unread;
|
||||||
this.setState({
|
this.setState({
|
||||||
appsRes: LOADING_REQUEST,
|
appsRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
this.setState({
|
const appsRes = await HttpService.client.listRegistrationApplications({
|
||||||
appsRes: await HttpService.client.listRegistrationApplications({
|
unread_only: unread_only,
|
||||||
unread_only: unread_only,
|
page: this.state.page,
|
||||||
page: this.state.page,
|
limit: fetchLimit,
|
||||||
limit: fetchLimit,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.refetchToken) {
|
||||||
|
this.setState({ appsRes });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleApproveApplication(form: ApproveRegistrationApplication) {
|
async handleApproveApplication(form: ApproveRegistrationApplication) {
|
||||||
|
|
|
@ -56,6 +56,7 @@ import { UnreadCounterService } from "../../services";
|
||||||
import { getHttpBaseInternal } from "../../utils/env";
|
import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
enum UnreadOrAll {
|
enum UnreadOrAll {
|
||||||
Unread,
|
Unread,
|
||||||
|
@ -160,8 +161,8 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
await this.refetch();
|
await this.refetch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -452,9 +453,22 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
all() {
|
all() {
|
||||||
|
const combined = this.buildCombined;
|
||||||
|
if (
|
||||||
|
combined.length === 0 &&
|
||||||
|
(this.state.commentReportsRes.state === "loading" ||
|
||||||
|
this.state.postReportsRes.state === "loading" ||
|
||||||
|
this.state.messageReportsRes.state === "loading")
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<h5>
|
||||||
|
<Spinner large />
|
||||||
|
</h5>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.buildCombined.map(i => (
|
{combined.map(i => (
|
||||||
<>
|
<>
|
||||||
<hr />
|
<hr />
|
||||||
{this.renderItemType(i)}
|
{this.renderItemType(i)}
|
||||||
|
@ -575,6 +589,7 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
headers,
|
headers,
|
||||||
|
site,
|
||||||
}: InitialFetchRequest): Promise<ReportsData> {
|
}: InitialFetchRequest): Promise<ReportsData> {
|
||||||
const client = wrapClient(
|
const client = wrapClient(
|
||||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||||
|
@ -601,7 +616,7 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
|
||||||
messageReportsRes: EMPTY_REQUEST,
|
messageReportsRes: EMPTY_REQUEST,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (amAdmin()) {
|
if (amAdmin(site.my_user)) {
|
||||||
const privateMessageReportsForm: ListPrivateMessageReports = {
|
const privateMessageReportsForm: ListPrivateMessageReports = {
|
||||||
unresolved_only,
|
unresolved_only,
|
||||||
page,
|
page,
|
||||||
|
@ -616,7 +631,9 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refetchToken?: symbol;
|
||||||
async refetch() {
|
async refetch() {
|
||||||
|
const token = (this.refetchToken = Symbol());
|
||||||
const unresolved_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
const unresolved_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
||||||
const page = this.state.page;
|
const page = this.state.page;
|
||||||
const limit = fetchLimit;
|
const limit = fetchLimit;
|
||||||
|
@ -636,17 +653,30 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
|
||||||
limit,
|
limit,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({
|
const commentReportPromise = HttpService.client
|
||||||
commentReportsRes: await HttpService.client.listCommentReports(form),
|
.listCommentReports(form)
|
||||||
postReportsRes: await HttpService.client.listPostReports(form),
|
.then(commentReportsRes => {
|
||||||
});
|
if (token === this.refetchToken) {
|
||||||
|
this.setState({ commentReportsRes });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const postReportPromise = HttpService.client
|
||||||
|
.listPostReports(form)
|
||||||
|
.then(postReportsRes => {
|
||||||
|
if (token === this.refetchToken) {
|
||||||
|
this.setState({ postReportsRes });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (amAdmin()) {
|
if (amAdmin()) {
|
||||||
this.setState({
|
const messageReportsRes =
|
||||||
messageReportsRes:
|
await HttpService.client.listPrivateMessageReports(form);
|
||||||
await HttpService.client.listPrivateMessageReports(form),
|
if (token === this.refetchToken) {
|
||||||
});
|
this.setState({ messageReportsRes });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Promise.all([commentReportPromise, postReportPromise]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleResolveCommentReport(form: ResolveCommentReport) {
|
async handleResolveCommentReport(form: ResolveCommentReport) {
|
||||||
|
|
|
@ -348,7 +348,7 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
this.setState({ themeList: await fetchThemeList() });
|
this.setState({ themeList: await fetchThemeList() });
|
||||||
|
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
import { simpleScrollMixin } from "../mixins/scroll-mixin";
|
import { simpleScrollMixin } from "../mixins/scroll-mixin";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
verifyRes: RequestState<SuccessResponse>;
|
verifyRes: RequestState<SuccessResponse>;
|
||||||
|
@ -52,8 +53,10 @@ export class VerifyEmail extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
await this.verify();
|
if (isBrowser()) {
|
||||||
|
await this.verify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get documentTitle(): string {
|
get documentTitle(): string {
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
import { simpleScrollMixin } from "../mixins/scroll-mixin";
|
import { simpleScrollMixin } from "../mixins/scroll-mixin";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
export interface CreatePostProps {
|
export interface CreatePostProps {
|
||||||
communityId?: number;
|
communityId?: number;
|
||||||
|
@ -81,7 +82,7 @@ export class CreatePost extends Component<
|
||||||
private isoData = setIsoData<CreatePostData>(this.context);
|
private isoData = setIsoData<CreatePostData>(this.context);
|
||||||
state: CreatePostState = {
|
state: CreatePostState = {
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
loading: true,
|
loading: false,
|
||||||
initialCommunitiesRes: EMPTY_REQUEST,
|
initialCommunitiesRes: EMPTY_REQUEST,
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
@ -132,9 +133,9 @@ export class CreatePost extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
// TODO test this
|
// TODO test this
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
const { communityId } = this.props;
|
const { communityId } = this.props;
|
||||||
|
|
||||||
const initialCommunitiesRes = await fetchCommunitiesForOptions(
|
const initialCommunitiesRes = await fetchCommunitiesForOptions(
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import { isImage } from "@utils/media";
|
import { isImage } from "@utils/media";
|
||||||
import { Choice } from "@utils/types";
|
import { Choice } from "@utils/types";
|
||||||
import autosize from "autosize";
|
import autosize from "autosize";
|
||||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
import { Component, InfernoNode, createRef, linkEvent } from "inferno";
|
||||||
import { Prompt } from "inferno-router";
|
import { Prompt } from "inferno-router";
|
||||||
import {
|
import {
|
||||||
CommunityView,
|
CommunityView,
|
||||||
|
@ -132,8 +132,9 @@ function copySuggestedTitle(d: { i: PostForm; suggestedTitle?: string }) {
|
||||||
);
|
);
|
||||||
d.i.setState({ suggestedPostsRes: EMPTY_REQUEST });
|
d.i.setState({ suggestedPostsRes: EMPTY_REQUEST });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const textarea: any = document.getElementById("post-title");
|
if (d.i.postTitleRef.current) {
|
||||||
autosize.update(textarea);
|
autosize.update(d.i.postTitleRef.current);
|
||||||
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,6 +249,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
submitted: false,
|
submitted: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
postTitleRef = createRef<HTMLTextAreaElement>();
|
||||||
|
|
||||||
constructor(props: PostFormProps, context: any) {
|
constructor(props: PostFormProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.fetchSimilarPosts = debounce(this.fetchSimilarPosts.bind(this));
|
this.fetchSimilarPosts = debounce(this.fetchSimilarPosts.bind(this));
|
||||||
|
@ -306,17 +309,19 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const textarea: any = document.getElementById("post-title");
|
if (this.postTitleRef.current) {
|
||||||
|
autosize(this.postTitleRef.current);
|
||||||
if (textarea) {
|
|
||||||
autosize(textarea);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(
|
componentWillReceiveProps(
|
||||||
nextProps: Readonly<{ children?: InfernoNode } & PostFormProps>,
|
nextProps: Readonly<{ children?: InfernoNode } & PostFormProps>,
|
||||||
): void {
|
): void {
|
||||||
if (this.props !== nextProps) {
|
if (
|
||||||
|
this.props.selectedCommunityChoice?.value !==
|
||||||
|
nextProps.selectedCommunityChoice?.value &&
|
||||||
|
nextProps.selectedCommunityChoice
|
||||||
|
) {
|
||||||
this.setState(
|
this.setState(
|
||||||
s => (
|
s => (
|
||||||
(s.form.community_id = getIdFromString(
|
(s.form.community_id = getIdFromString(
|
||||||
|
@ -325,6 +330,22 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
s
|
s
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
this.setState({
|
||||||
|
communitySearchOptions: [nextProps.selectedCommunityChoice].concat(
|
||||||
|
(nextProps.initialCommunities?.map(communityToChoice) ?? []).filter(
|
||||||
|
option => option.value !== nextProps.selectedCommunityChoice?.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!this.props.initialCommunities?.length &&
|
||||||
|
nextProps.initialCommunities?.length
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
communitySearchOptions:
|
||||||
|
nextProps.initialCommunities?.map(communityToChoice) ?? [],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +383,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
rows={1}
|
rows={1}
|
||||||
minLength={3}
|
minLength={3}
|
||||||
maxLength={MAX_POST_TITLE_LENGTH}
|
maxLength={MAX_POST_TITLE_LENGTH}
|
||||||
|
ref={this.postTitleRef}
|
||||||
/>
|
/>
|
||||||
{!validTitle(this.state.form.name) && (
|
{!validTitle(this.state.form.name) && (
|
||||||
<div className="invalid-feedback">
|
<div className="invalid-feedback">
|
||||||
|
|
|
@ -137,7 +137,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
this.handleHidePost = this.handleHidePost.bind(this);
|
this.handleHidePost = this.handleHidePost.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
unlisten = () => {};
|
||||||
|
|
||||||
|
componentWillMount(): void {
|
||||||
if (
|
if (
|
||||||
UserService.Instance.myUserInfo &&
|
UserService.Instance.myUserInfo &&
|
||||||
!this.isoData.showAdultConsentModal
|
!this.isoData.showAdultConsentModal
|
||||||
|
@ -148,6 +150,17 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
||||||
imageExpanded: auto_expand && !(blur_nsfw && this.postView.post.nsfw),
|
imageExpanded: auto_expand && !(blur_nsfw && this.postView.post.nsfw),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave edit mode on navigation
|
||||||
|
this.unlisten = this.context.router.history.listen(() => {
|
||||||
|
if (this.state.showEdit) {
|
||||||
|
this.setState({ showEdit: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount(): void {
|
||||||
|
this.unlisten();
|
||||||
}
|
}
|
||||||
|
|
||||||
get postView(): PostView {
|
get postView(): PostView {
|
||||||
|
|
|
@ -18,15 +18,17 @@ import { isBrowser } from "@utils/browser";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
getApubName,
|
getApubName,
|
||||||
|
getQueryParams,
|
||||||
|
getQueryString,
|
||||||
randomStr,
|
randomStr,
|
||||||
resourcesSettled,
|
resourcesSettled,
|
||||||
|
bareRoutePush,
|
||||||
} from "@utils/helpers";
|
} from "@utils/helpers";
|
||||||
import { scrollMixin } from "../mixins/scroll-mixin";
|
import { scrollMixin } from "../mixins/scroll-mixin";
|
||||||
import { isImage } from "@utils/media";
|
import { isImage } from "@utils/media";
|
||||||
import { RouteDataResponse } from "@utils/types";
|
import { QueryParams, RouteDataResponse } from "@utils/types";
|
||||||
import autosize from "autosize";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Component, RefObject, createRef, linkEvent } from "inferno";
|
import { Component, createRef, linkEvent } from "inferno";
|
||||||
import {
|
import {
|
||||||
AddAdmin,
|
AddAdmin,
|
||||||
AddModToCommunity,
|
AddModToCommunity,
|
||||||
|
@ -103,6 +105,7 @@ import { PostListing } from "./post-listing";
|
||||||
import { getHttpBaseInternal } from "../../utils/env";
|
import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
|
import { compareAsc, compareDesc } from "date-fns";
|
||||||
|
|
||||||
const commentsShownInterval = 15;
|
const commentsShownInterval = 15;
|
||||||
|
|
||||||
|
@ -112,44 +115,107 @@ type PostData = RouteDataResponse<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
interface PostState {
|
interface PostState {
|
||||||
postId?: number;
|
|
||||||
commentId?: number;
|
|
||||||
postRes: RequestState<GetPostResponse>;
|
postRes: RequestState<GetPostResponse>;
|
||||||
commentsRes: RequestState<GetCommentsResponse>;
|
commentsRes: RequestState<GetCommentsResponse>;
|
||||||
commentSort: CommentSortType;
|
|
||||||
commentViewType: CommentViewType;
|
|
||||||
scrolled?: boolean;
|
|
||||||
siteRes: GetSiteResponse;
|
siteRes: GetSiteResponse;
|
||||||
commentSectionRef?: RefObject<HTMLDivElement>;
|
|
||||||
showSidebarMobile: boolean;
|
showSidebarMobile: boolean;
|
||||||
maxCommentsShown: number;
|
maxCommentsShown: number;
|
||||||
finished: Map<CommentId, boolean | undefined>;
|
finished: Map<CommentId, boolean | undefined>;
|
||||||
isIsomorphic: boolean;
|
isIsomorphic: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostPathProps =
|
const defaultCommentSort: CommentSortType = "Hot";
|
||||||
| { post_id: string; comment_id: never }
|
|
||||||
| { post_id: never; comment_id: string };
|
function getCommentSortTypeFromQuery(source?: string): CommentSortType {
|
||||||
type PostRouteProps = RouteComponentProps<PostPathProps> &
|
if (!source) {
|
||||||
Record<string, never>;
|
return defaultCommentSort;
|
||||||
|
}
|
||||||
|
switch (source) {
|
||||||
|
case "Hot":
|
||||||
|
case "Top":
|
||||||
|
case "New":
|
||||||
|
case "Old":
|
||||||
|
case "Controversial":
|
||||||
|
return source;
|
||||||
|
default:
|
||||||
|
return defaultCommentSort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryStringFromCommentSortType(
|
||||||
|
sort: CommentSortType,
|
||||||
|
): undefined | string {
|
||||||
|
if (sort === defaultCommentSort) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultCommentView: CommentViewType = CommentViewType.Tree;
|
||||||
|
|
||||||
|
function getCommentViewTypeFromQuery(source?: string): CommentViewType {
|
||||||
|
switch (source) {
|
||||||
|
case "Tree":
|
||||||
|
return CommentViewType.Tree;
|
||||||
|
case "Flat":
|
||||||
|
return CommentViewType.Flat;
|
||||||
|
default:
|
||||||
|
return defaultCommentView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryStringFromCommentView(
|
||||||
|
view: CommentViewType,
|
||||||
|
): string | undefined {
|
||||||
|
if (view === defaultCommentView) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
switch (view) {
|
||||||
|
case CommentViewType.Tree:
|
||||||
|
return "Tree";
|
||||||
|
case CommentViewType.Flat:
|
||||||
|
return "Flat";
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PostProps {
|
||||||
|
sort: CommentSortType;
|
||||||
|
view: CommentViewType;
|
||||||
|
scrollToComments: boolean;
|
||||||
|
}
|
||||||
|
export function getPostQueryParams(source: string | undefined): PostProps {
|
||||||
|
return getQueryParams<PostProps>(
|
||||||
|
{
|
||||||
|
scrollToComments: (s?: string) => !!s,
|
||||||
|
sort: getCommentSortTypeFromQuery,
|
||||||
|
view: getCommentViewTypeFromQuery,
|
||||||
|
},
|
||||||
|
source,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostPathProps = { post_id?: string; comment_id?: string };
|
||||||
|
type PostRouteProps = RouteComponentProps<PostPathProps> & PostProps;
|
||||||
|
type PartialPostRouteProps = Partial<
|
||||||
|
PostProps & { match: { params: PostPathProps } }
|
||||||
|
>;
|
||||||
export type PostFetchConfig = IRoutePropsWithFetch<
|
export type PostFetchConfig = IRoutePropsWithFetch<
|
||||||
PostData,
|
PostData,
|
||||||
PostPathProps,
|
PostPathProps,
|
||||||
Record<string, never>
|
PostProps
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@scrollMixin
|
@scrollMixin
|
||||||
export class Post extends Component<PostRouteProps, PostState> {
|
export class Post extends Component<PostRouteProps, PostState> {
|
||||||
private isoData = setIsoData<PostData>(this.context);
|
private isoData = setIsoData<PostData>(this.context);
|
||||||
private commentScrollDebounced: () => void;
|
private commentScrollDebounced: () => void;
|
||||||
|
private shouldScrollToComments: boolean = false;
|
||||||
|
private commentSectionRef = createRef<HTMLDivElement>();
|
||||||
state: PostState = {
|
state: PostState = {
|
||||||
postRes: EMPTY_REQUEST,
|
postRes: EMPTY_REQUEST,
|
||||||
commentsRes: EMPTY_REQUEST,
|
commentsRes: EMPTY_REQUEST,
|
||||||
postId: getIdFromProps(this.props),
|
|
||||||
commentId: getCommentIdFromProps(this.props),
|
|
||||||
commentSort: "Hot",
|
|
||||||
commentViewType: CommentViewType.Tree,
|
|
||||||
scrolled: false,
|
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
showSidebarMobile: false,
|
showSidebarMobile: false,
|
||||||
maxCommentsShown: commentsShownInterval,
|
maxCommentsShown: commentsShownInterval,
|
||||||
|
@ -201,8 +267,6 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
this.handleScrollIntoCommentsClick =
|
this.handleScrollIntoCommentsClick =
|
||||||
this.handleScrollIntoCommentsClick.bind(this);
|
this.handleScrollIntoCommentsClick.bind(this);
|
||||||
|
|
||||||
this.state = { ...this.state, commentSectionRef: createRef() };
|
|
||||||
|
|
||||||
// Only fetch the data if coming from another route
|
// Only fetch the data if coming from another route
|
||||||
if (FirstLoadService.isFirstLoad) {
|
if (FirstLoadService.isFirstLoad) {
|
||||||
const { commentsRes, postRes } = this.isoData.routeData;
|
const { commentsRes, postRes } = this.isoData.routeData;
|
||||||
|
@ -213,71 +277,104 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
commentsRes,
|
commentsRes,
|
||||||
isIsomorphic: true,
|
isIsomorphic: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isBrowser()) {
|
|
||||||
if (this.checkScrollIntoCommentsParam) {
|
|
||||||
this.scrollIntoCommentSection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchPost() {
|
fetchPostToken?: symbol;
|
||||||
this.setState({
|
async fetchPost(props: PostRouteProps) {
|
||||||
postRes: LOADING_REQUEST,
|
const token = (this.fetchPostToken = Symbol());
|
||||||
commentsRes: LOADING_REQUEST,
|
this.setState({ postRes: LOADING_REQUEST });
|
||||||
|
const postRes = await HttpService.client.getPost({
|
||||||
|
id: getIdFromProps(props),
|
||||||
|
comment_id: getCommentIdFromProps(props),
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchPostToken) {
|
||||||
|
this.setState({ postRes });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [postRes, commentsRes] = await Promise.all([
|
fetchCommentsToken?: symbol;
|
||||||
await HttpService.client.getPost({
|
async fetchComments(props: PostRouteProps) {
|
||||||
id: this.state.postId,
|
const token = (this.fetchCommentsToken = Symbol());
|
||||||
comment_id: this.state.commentId,
|
const { sort } = props;
|
||||||
}),
|
this.setState({ commentsRes: LOADING_REQUEST });
|
||||||
HttpService.client.getComments({
|
const commentsRes = await HttpService.client.getComments({
|
||||||
post_id: this.state.postId,
|
post_id: getIdFromProps(props),
|
||||||
parent_id: this.state.commentId,
|
parent_id: getCommentIdFromProps(props),
|
||||||
max_depth: commentTreeMaxDepth,
|
max_depth: commentTreeMaxDepth,
|
||||||
sort: this.state.commentSort,
|
sort,
|
||||||
type_: "All",
|
type_: "All",
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
postRes,
|
|
||||||
commentsRes,
|
|
||||||
});
|
});
|
||||||
|
if (token === this.fetchCommentsToken) {
|
||||||
|
this.setState({ commentsRes });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.checkScrollIntoCommentsParam) {
|
updateUrl(props: PartialPostRouteProps, replace = false) {
|
||||||
this.scrollIntoCommentSection();
|
const {
|
||||||
|
view,
|
||||||
|
sort,
|
||||||
|
match: {
|
||||||
|
params: { comment_id, post_id },
|
||||||
|
},
|
||||||
|
} = {
|
||||||
|
...this.props,
|
||||||
|
...props,
|
||||||
|
};
|
||||||
|
|
||||||
|
const query: QueryParams<PostProps> = {
|
||||||
|
sort: getQueryStringFromCommentSortType(sort),
|
||||||
|
view: getQueryStringFromCommentView(view),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Not inheriting old scrollToComments
|
||||||
|
if (props.scrollToComments) {
|
||||||
|
query.scrollToComments = true.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
let pathname: string | undefined;
|
||||||
|
if (comment_id && post_id) {
|
||||||
|
pathname = `/post/${post_id}/${comment_id}`;
|
||||||
|
} else if (comment_id) {
|
||||||
|
pathname = `/comment/${comment_id}`;
|
||||||
|
} else {
|
||||||
|
pathname = `/post/${post_id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const location = { pathname, search: getQueryString(query) };
|
||||||
|
if (replace || this.props.location.pathname === pathname) {
|
||||||
|
this.props.history.replace(location);
|
||||||
|
} else {
|
||||||
|
this.props.history.push(location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
headers,
|
headers,
|
||||||
match,
|
match,
|
||||||
}: InitialFetchRequest<PostPathProps>): Promise<PostData> {
|
query: { sort },
|
||||||
|
}: InitialFetchRequest<PostPathProps, PostProps>): Promise<PostData> {
|
||||||
const client = wrapClient(
|
const client = wrapClient(
|
||||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||||
);
|
);
|
||||||
const postId = getIdFromProps({ match });
|
const postId = getIdFromProps({ match });
|
||||||
const commentId = getCommentIdFromProps({ match });
|
const commentId = getCommentIdFromProps({ match });
|
||||||
|
|
||||||
const postForm: GetPost = {};
|
const postForm: GetPost = {
|
||||||
|
id: postId,
|
||||||
|
comment_id: commentId,
|
||||||
|
};
|
||||||
|
|
||||||
const commentsForm: GetComments = {
|
const commentsForm: GetComments = {
|
||||||
|
post_id: postId,
|
||||||
|
parent_id: commentId,
|
||||||
max_depth: commentTreeMaxDepth,
|
max_depth: commentTreeMaxDepth,
|
||||||
sort: "Hot",
|
sort,
|
||||||
type_: "All",
|
type_: "All",
|
||||||
saved_only: false,
|
saved_only: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
postForm.id = postId;
|
|
||||||
postForm.comment_id = commentId;
|
|
||||||
|
|
||||||
commentsForm.post_id = postId;
|
|
||||||
commentsForm.parent_id = commentId;
|
|
||||||
|
|
||||||
const [postRes, commentsRes] = await Promise.all([
|
const [postRes, commentsRes] = await Promise.all([
|
||||||
client.getPost(postForm),
|
client.getPost(postForm),
|
||||||
client.getComments(commentsForm),
|
client.getComments(commentsForm),
|
||||||
|
@ -293,15 +390,79 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
document.removeEventListener("scroll", this.commentScrollDebounced);
|
document.removeEventListener("scroll", this.commentScrollDebounced);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (isBrowser()) {
|
||||||
await this.fetchPost();
|
this.shouldScrollToComments = this.props.scrollToComments;
|
||||||
|
if (!this.state.isIsomorphic) {
|
||||||
|
await Promise.all([
|
||||||
|
this.fetchPost(this.props),
|
||||||
|
this.fetchComments(this.props),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
autosize(document.querySelectorAll("textarea"));
|
componentDidMount() {
|
||||||
|
|
||||||
this.commentScrollDebounced = debounce(this.trackCommentsBoxScrolling, 100);
|
this.commentScrollDebounced = debounce(this.trackCommentsBoxScrolling, 100);
|
||||||
document.addEventListener("scroll", this.commentScrollDebounced);
|
document.addEventListener("scroll", this.commentScrollDebounced);
|
||||||
|
|
||||||
|
if (this.state.isIsomorphic) {
|
||||||
|
this.maybeScrollToComments();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps: PostRouteProps): void {
|
||||||
|
const { post_id: nextPost, comment_id: nextComment } =
|
||||||
|
nextProps.match.params;
|
||||||
|
const { post_id: prevPost, comment_id: prevComment } =
|
||||||
|
this.props.match.params;
|
||||||
|
|
||||||
|
const newOrder =
|
||||||
|
this.props.sort !== nextProps.sort || this.props.view !== nextProps.view;
|
||||||
|
|
||||||
|
// For comment links restore sort type from current props.
|
||||||
|
if (
|
||||||
|
nextPost === prevPost &&
|
||||||
|
nextComment &&
|
||||||
|
newOrder &&
|
||||||
|
!nextProps.location.search &&
|
||||||
|
nextProps.history.action === "PUSH"
|
||||||
|
) {
|
||||||
|
this.updateUrl({ match: nextProps.match }, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const needPost =
|
||||||
|
prevPost !== nextPost ||
|
||||||
|
(bareRoutePush(this.props, nextProps) && !nextComment);
|
||||||
|
const needComments =
|
||||||
|
needPost ||
|
||||||
|
prevComment !== nextComment ||
|
||||||
|
nextProps.sort !== this.props.sort;
|
||||||
|
|
||||||
|
if (needPost) {
|
||||||
|
this.fetchPost(nextProps);
|
||||||
|
}
|
||||||
|
if (needComments) {
|
||||||
|
this.fetchComments(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
nextProps.scrollToComments &&
|
||||||
|
this.props.scrollToComments !== nextProps.scrollToComments
|
||||||
|
) {
|
||||||
|
this.shouldScrollToComments = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(): void {
|
||||||
|
if (
|
||||||
|
this.commentSectionRef.current &&
|
||||||
|
this.state.postRes.state === "success" &&
|
||||||
|
this.state.commentsRes.state === "success"
|
||||||
|
) {
|
||||||
|
this.maybeScrollToComments();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScrollIntoCommentsClick(e: MouseEvent) {
|
handleScrollIntoCommentsClick(e: MouseEvent) {
|
||||||
|
@ -309,16 +470,18 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
get checkScrollIntoCommentsParam() {
|
maybeScrollToComments() {
|
||||||
return (
|
if (this.shouldScrollToComments) {
|
||||||
Boolean(
|
this.shouldScrollToComments = false;
|
||||||
new URLSearchParams(this.props.location.search).get("scrollToComments"),
|
if (this.props.history.action !== "POP" || this.state.isIsomorphic) {
|
||||||
) && this.props.history.action !== "POP"
|
this.scrollIntoCommentSection();
|
||||||
);
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollIntoCommentSection() {
|
scrollIntoCommentSection() {
|
||||||
this.state.commentSectionRef?.current?.scrollIntoView();
|
// This doesn't work when in a background tab in firefox.
|
||||||
|
this.commentSectionRef.current?.scrollIntoView();
|
||||||
}
|
}
|
||||||
|
|
||||||
isBottom(el: Element): boolean {
|
isBottom(el: Element): boolean {
|
||||||
|
@ -413,13 +576,18 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
onHidePost={this.handleHidePost}
|
onHidePost={this.handleHidePost}
|
||||||
onScrollIntoCommentsClick={this.handleScrollIntoCommentsClick}
|
onScrollIntoCommentsClick={this.handleScrollIntoCommentsClick}
|
||||||
/>
|
/>
|
||||||
<div ref={this.state.commentSectionRef} className="mb-2" />
|
<div ref={this.commentSectionRef} className="mb-2" />
|
||||||
|
|
||||||
{/* Only show the top level comment form if its not a context view */}
|
{/* Only show the top level comment form if its not a context view */}
|
||||||
{!(
|
{!(
|
||||||
this.state.commentId || res.post_view.banned_from_community
|
getCommentIdFromProps(this.props) ||
|
||||||
|
res.post_view.banned_from_community
|
||||||
) && (
|
) && (
|
||||||
<CommentForm
|
<CommentForm
|
||||||
|
key={
|
||||||
|
this.context.router.history.location.key
|
||||||
|
// reset on new location, otherwise <Prompt /> stops working
|
||||||
|
}
|
||||||
node={res.post_view.post.id}
|
node={res.post_view.post.id}
|
||||||
disabled={res.post_view.post.locked}
|
disabled={res.post_view.post.locked}
|
||||||
allLanguages={siteRes.all_languages}
|
allLanguages={siteRes.all_languages}
|
||||||
|
@ -447,10 +615,8 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
{this.state.showSidebarMobile && this.sidebar()}
|
{this.state.showSidebarMobile && this.sidebar()}
|
||||||
</div>
|
</div>
|
||||||
{this.sortRadios()}
|
{this.sortRadios()}
|
||||||
{this.state.commentViewType === CommentViewType.Tree &&
|
{this.props.view === CommentViewType.Tree && this.commentsTree()}
|
||||||
this.commentsTree()}
|
{this.props.view === CommentViewType.Flat && this.commentsFlat()}
|
||||||
{this.state.commentViewType === CommentViewType.Flat &&
|
|
||||||
this.commentsFlat()}
|
|
||||||
</main>
|
</main>
|
||||||
<aside className="d-none d-md-block col-md-4 col-lg-3">
|
<aside className="d-none d-md-block col-md-4 col-lg-3">
|
||||||
{this.sidebar()}
|
{this.sidebar()}
|
||||||
|
@ -482,13 +648,13 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
type="radio"
|
type="radio"
|
||||||
className="btn-check"
|
className="btn-check"
|
||||||
value={"Hot"}
|
value={"Hot"}
|
||||||
checked={this.state.commentSort === "Hot"}
|
checked={this.props.sort === "Hot"}
|
||||||
onChange={linkEvent(this, this.handleCommentSortChange)}
|
onChange={linkEvent(this, this.handleCommentSortChange)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={`${radioId}-hot`}
|
htmlFor={`${radioId}-hot`}
|
||||||
className={classNames("btn btn-outline-secondary pointer", {
|
className={classNames("btn btn-outline-secondary pointer", {
|
||||||
active: this.state.commentSort === "Hot",
|
active: this.props.sort === "Hot",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("hot")}
|
{I18NextService.i18n.t("hot")}
|
||||||
|
@ -498,13 +664,13 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
type="radio"
|
type="radio"
|
||||||
className="btn-check"
|
className="btn-check"
|
||||||
value={"Top"}
|
value={"Top"}
|
||||||
checked={this.state.commentSort === "Top"}
|
checked={this.props.sort === "Top"}
|
||||||
onChange={linkEvent(this, this.handleCommentSortChange)}
|
onChange={linkEvent(this, this.handleCommentSortChange)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={`${radioId}-top`}
|
htmlFor={`${radioId}-top`}
|
||||||
className={classNames("btn btn-outline-secondary pointer", {
|
className={classNames("btn btn-outline-secondary pointer", {
|
||||||
active: this.state.commentSort === "Top",
|
active: this.props.sort === "Top",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("top")}
|
{I18NextService.i18n.t("top")}
|
||||||
|
@ -514,13 +680,13 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
type="radio"
|
type="radio"
|
||||||
className="btn-check"
|
className="btn-check"
|
||||||
value={"Controversial"}
|
value={"Controversial"}
|
||||||
checked={this.state.commentSort === "Controversial"}
|
checked={this.props.sort === "Controversial"}
|
||||||
onChange={linkEvent(this, this.handleCommentSortChange)}
|
onChange={linkEvent(this, this.handleCommentSortChange)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={`${radioId}-controversial`}
|
htmlFor={`${radioId}-controversial`}
|
||||||
className={classNames("btn btn-outline-secondary pointer", {
|
className={classNames("btn btn-outline-secondary pointer", {
|
||||||
active: this.state.commentSort === "Controversial",
|
active: this.props.sort === "Controversial",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("controversial")}
|
{I18NextService.i18n.t("controversial")}
|
||||||
|
@ -530,13 +696,13 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
type="radio"
|
type="radio"
|
||||||
className="btn-check"
|
className="btn-check"
|
||||||
value={"New"}
|
value={"New"}
|
||||||
checked={this.state.commentSort === "New"}
|
checked={this.props.sort === "New"}
|
||||||
onChange={linkEvent(this, this.handleCommentSortChange)}
|
onChange={linkEvent(this, this.handleCommentSortChange)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={`${radioId}-new`}
|
htmlFor={`${radioId}-new`}
|
||||||
className={classNames("btn btn-outline-secondary pointer", {
|
className={classNames("btn btn-outline-secondary pointer", {
|
||||||
active: this.state.commentSort === "New",
|
active: this.props.sort === "New",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("new")}
|
{I18NextService.i18n.t("new")}
|
||||||
|
@ -546,13 +712,13 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
type="radio"
|
type="radio"
|
||||||
className="btn-check"
|
className="btn-check"
|
||||||
value={"Old"}
|
value={"Old"}
|
||||||
checked={this.state.commentSort === "Old"}
|
checked={this.props.sort === "Old"}
|
||||||
onChange={linkEvent(this, this.handleCommentSortChange)}
|
onChange={linkEvent(this, this.handleCommentSortChange)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={`${radioId}-old`}
|
htmlFor={`${radioId}-old`}
|
||||||
className={classNames("btn btn-outline-secondary pointer", {
|
className={classNames("btn btn-outline-secondary pointer", {
|
||||||
active: this.state.commentSort === "Old",
|
active: this.props.sort === "Old",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("old")}
|
{I18NextService.i18n.t("old")}
|
||||||
|
@ -564,13 +730,13 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
type="radio"
|
type="radio"
|
||||||
className="btn-check"
|
className="btn-check"
|
||||||
value={CommentViewType.Flat}
|
value={CommentViewType.Flat}
|
||||||
checked={this.state.commentViewType === CommentViewType.Flat}
|
checked={this.props.view === CommentViewType.Flat}
|
||||||
onChange={linkEvent(this, this.handleCommentViewTypeChange)}
|
onChange={linkEvent(this, this.handleCommentViewTypeChange)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={`${radioId}-chat`}
|
htmlFor={`${radioId}-chat`}
|
||||||
className={classNames("btn btn-outline-secondary pointer", {
|
className={classNames("btn btn-outline-secondary pointer", {
|
||||||
active: this.state.commentViewType === CommentViewType.Flat,
|
active: this.props.view === CommentViewType.Flat,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("chat")}
|
{I18NextService.i18n.t("chat")}
|
||||||
|
@ -581,6 +747,14 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
commentsFlat() {
|
commentsFlat() {
|
||||||
|
if (this.state.commentsRes.state === "loading") {
|
||||||
|
return (
|
||||||
|
<div className="text-center">
|
||||||
|
<Spinner large />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// These are already sorted by new
|
// These are already sorted by new
|
||||||
const commentsRes = this.state.commentsRes;
|
const commentsRes = this.state.commentsRes;
|
||||||
const postRes = this.state.postRes;
|
const postRes = this.state.postRes;
|
||||||
|
@ -590,8 +764,8 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<CommentNodes
|
<CommentNodes
|
||||||
nodes={commentsToFlatNodes(commentsRes.data.comments)}
|
nodes={this.sortedFlatNodes()}
|
||||||
viewType={this.state.commentViewType}
|
viewType={this.props.view}
|
||||||
maxCommentsShown={this.state.maxCommentsShown}
|
maxCommentsShown={this.state.maxCommentsShown}
|
||||||
isTopLevel
|
isTopLevel
|
||||||
locked={postRes.data.post_view.post.locked}
|
locked={postRes.data.post_view.post.locked}
|
||||||
|
@ -652,7 +826,29 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortedFlatNodes(): CommentNodeI[] {
|
||||||
|
if (this.state.commentsRes.state !== "success") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const nodeToDate = (node: CommentNodeI) =>
|
||||||
|
node.comment_view.comment.published;
|
||||||
|
const nodes = commentsToFlatNodes(this.state.commentsRes.data.comments);
|
||||||
|
if (this.props.sort === "New") {
|
||||||
|
return nodes.sort((a, b) => compareDesc(nodeToDate(a), nodeToDate(b)));
|
||||||
|
} else {
|
||||||
|
return nodes.sort((a, b) => compareAsc(nodeToDate(a), nodeToDate(b)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
commentsTree() {
|
commentsTree() {
|
||||||
|
if (this.state.commentsRes.state === "loading") {
|
||||||
|
return (
|
||||||
|
<div className="text-center">
|
||||||
|
<Spinner large />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const res = this.state.postRes;
|
const res = this.state.postRes;
|
||||||
const firstComment = this.commentTree().at(0)?.comment_view.comment;
|
const firstComment = this.commentTree().at(0)?.comment_view.comment;
|
||||||
const depth = getDepthFromComment(firstComment);
|
const depth = getDepthFromComment(firstComment);
|
||||||
|
@ -662,11 +858,11 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
return (
|
return (
|
||||||
res.state === "success" && (
|
res.state === "success" && (
|
||||||
<div>
|
<div>
|
||||||
{!!this.state.commentId && (
|
{!!getCommentIdFromProps(this.props) && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="ps-0 d-block btn btn-link text-muted"
|
className="ps-0 d-block btn btn-link text-muted"
|
||||||
onClick={linkEvent(this, this.handleViewPost)}
|
onClick={linkEvent(this, this.handleViewAllComments)}
|
||||||
>
|
>
|
||||||
{I18NextService.i18n.t("view_all_comments")} ➔
|
{I18NextService.i18n.t("view_all_comments")} ➔
|
||||||
</button>
|
</button>
|
||||||
|
@ -682,7 +878,7 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
)}
|
)}
|
||||||
<CommentNodes
|
<CommentNodes
|
||||||
nodes={this.commentTree()}
|
nodes={this.commentTree()}
|
||||||
viewType={this.state.commentViewType}
|
viewType={this.props.view}
|
||||||
maxCommentsShown={this.state.maxCommentsShown}
|
maxCommentsShown={this.state.maxCommentsShown}
|
||||||
locked={res.data.post_view.post.locked}
|
locked={res.data.post_view.post.locked}
|
||||||
moderators={res.data.moderators}
|
moderators={res.data.moderators}
|
||||||
|
@ -719,50 +915,69 @@ export class Post extends Component<PostRouteProps, PostState> {
|
||||||
|
|
||||||
commentTree(): CommentNodeI[] {
|
commentTree(): CommentNodeI[] {
|
||||||
if (this.state.commentsRes.state === "success") {
|
if (this.state.commentsRes.state === "success") {
|
||||||
return buildCommentsTree(
|
const comments = this.state.commentsRes.data.comments;
|
||||||
this.state.commentsRes.data.comments,
|
if (comments.length) {
|
||||||
!!this.state.commentId,
|
return buildCommentsTree(comments, !!getCommentIdFromProps(this.props));
|
||||||
);
|
}
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCommentSortChange(i: Post, event: any) {
|
async handleCommentSortChange(i: Post, event: any) {
|
||||||
i.setState({
|
const sort = event.target.value as CommentSortType;
|
||||||
commentSort: event.target.value as CommentSortType,
|
const flattenable = sort === "New" || sort === "Old";
|
||||||
commentViewType: CommentViewType.Tree,
|
if (flattenable || i.props.view !== CommentViewType.Flat) {
|
||||||
commentsRes: LOADING_REQUEST,
|
i.updateUrl({ sort });
|
||||||
postRes: LOADING_REQUEST,
|
} else {
|
||||||
});
|
i.updateUrl({ sort, view: CommentViewType.Tree });
|
||||||
await i.fetchPost();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommentViewTypeChange(i: Post, event: any) {
|
handleCommentViewTypeChange(i: Post, event: any) {
|
||||||
i.setState({
|
const flattenable = i.props.sort === "New" || i.props.sort === "Old";
|
||||||
commentViewType: Number(event.target.value),
|
const view: CommentViewType = Number(event.target.value);
|
||||||
commentSort: "New",
|
if (flattenable || view !== CommentViewType.Flat) {
|
||||||
});
|
i.updateUrl({ view });
|
||||||
|
} else {
|
||||||
|
i.updateUrl({ view, sort: "New" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShowSidebarMobile(i: Post) {
|
handleShowSidebarMobile(i: Post) {
|
||||||
i.setState({ showSidebarMobile: !i.state.showSidebarMobile });
|
i.setState({ showSidebarMobile: !i.state.showSidebarMobile });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleViewPost(i: Post) {
|
handleViewAllComments(i: Post) {
|
||||||
if (i.state.postRes.state === "success") {
|
const id =
|
||||||
const id = i.state.postRes.data.post_view.post.id;
|
getIdFromProps(i.props) ||
|
||||||
i.context.router.history.push(`/post/${id}`);
|
(i.state.postRes.state === "success" &&
|
||||||
|
i.state.postRes.data.post_view.post.id);
|
||||||
|
if (id) {
|
||||||
|
i.updateUrl({
|
||||||
|
match: { params: { post_id: id.toString() } },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleViewContext(i: Post) {
|
handleViewContext(i: Post) {
|
||||||
if (i.state.commentsRes.state === "success") {
|
if (i.state.commentsRes.state === "success") {
|
||||||
const parentId = getCommentParentId(
|
const commentId = getCommentIdFromProps(i.props);
|
||||||
i.state.commentsRes.data.comments.at(0)?.comment,
|
const commentView = i.state.commentsRes.data.comments.find(
|
||||||
|
c => c.comment.id === commentId,
|
||||||
);
|
);
|
||||||
if (parentId) {
|
|
||||||
i.context.router.history.push(`/comment/${parentId}`);
|
const parentId = getCommentParentId(commentView?.comment);
|
||||||
|
const postId = commentView?.post.id;
|
||||||
|
|
||||||
|
if (parentId && postId) {
|
||||||
|
i.updateUrl({
|
||||||
|
match: {
|
||||||
|
params: {
|
||||||
|
post_id: postId.toString(),
|
||||||
|
comment_id: parentId.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
import { resourcesSettled } from "@utils/helpers";
|
import { resourcesSettled } from "@utils/helpers";
|
||||||
import { scrollMixin } from "../mixins/scroll-mixin";
|
import { scrollMixin } from "../mixins/scroll-mixin";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
type CreatePrivateMessageData = RouteDataResponse<{
|
type CreatePrivateMessageData = RouteDataResponse<{
|
||||||
recipientDetailsResponse: GetPersonDetailsResponse;
|
recipientDetailsResponse: GetPersonDetailsResponse;
|
||||||
|
@ -79,8 +80,8 @@ export class CreatePrivateMessage extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
await this.fetchPersonDetails();
|
await this.fetchPersonDetails();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { CommunityLink } from "./community/community-link";
|
||||||
import { getHttpBaseInternal } from "../utils/env";
|
import { getHttpBaseInternal } from "../utils/env";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../routes";
|
import { IRoutePropsWithFetch } from "../routes";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
interface RemoteFetchProps {
|
interface RemoteFetchProps {
|
||||||
uri?: string;
|
uri?: string;
|
||||||
|
@ -128,8 +129,8 @@ export class RemoteFetch extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentWillMount() {
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
const { uri } = this.props;
|
const { uri } = this.props;
|
||||||
|
|
||||||
if (uri) {
|
if (uri) {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
enableNsfw,
|
enableNsfw,
|
||||||
fetchCommunities,
|
fetchCommunities,
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
getUpdatedSearchId,
|
|
||||||
myAuth,
|
myAuth,
|
||||||
personToChoice,
|
personToChoice,
|
||||||
setIsoData,
|
setIsoData,
|
||||||
|
@ -71,6 +70,7 @@ import { PostListing } from "./post/post-listing";
|
||||||
import { getHttpBaseInternal } from "../utils/env";
|
import { getHttpBaseInternal } from "../utils/env";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../routes";
|
import { IRoutePropsWithFetch } from "../routes";
|
||||||
|
import { isBrowser } from "@utils/browser";
|
||||||
|
|
||||||
interface SearchProps {
|
interface SearchProps {
|
||||||
q?: string;
|
q?: string;
|
||||||
|
@ -96,7 +96,6 @@ interface SearchState {
|
||||||
searchRes: RequestState<SearchResponse>;
|
searchRes: RequestState<SearchResponse>;
|
||||||
resolveObjectRes: RequestState<ResolveObjectResponse>;
|
resolveObjectRes: RequestState<ResolveObjectResponse>;
|
||||||
siteRes: GetSiteResponse;
|
siteRes: GetSiteResponse;
|
||||||
searchText?: string;
|
|
||||||
communitySearchOptions: Choice[];
|
communitySearchOptions: Choice[];
|
||||||
creatorSearchOptions: Choice[];
|
creatorSearchOptions: Choice[];
|
||||||
searchCreatorLoading: boolean;
|
searchCreatorLoading: boolean;
|
||||||
|
@ -285,10 +284,6 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
||||||
this.handleCommunityFilterChange.bind(this);
|
this.handleCommunityFilterChange.bind(this);
|
||||||
this.handleCreatorFilterChange = this.handleCreatorFilterChange.bind(this);
|
this.handleCreatorFilterChange = this.handleCreatorFilterChange.bind(this);
|
||||||
|
|
||||||
const { q } = this.props;
|
|
||||||
|
|
||||||
this.state.searchText = q;
|
|
||||||
|
|
||||||
// Only fetch the data if coming from another route
|
// Only fetch the data if coming from another route
|
||||||
if (FirstLoadService.isFirstLoad) {
|
if (FirstLoadService.isFirstLoad) {
|
||||||
const {
|
const {
|
||||||
|
@ -329,81 +324,142 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
componentWillMount() {
|
||||||
if (this.props.history.action !== "POP") {
|
if (!this.state.isIsomorphic && isBrowser()) {
|
||||||
|
this.fetchAll(this.props);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.history.action !== "POP" || this.state.isIsomorphic) {
|
||||||
this.searchInput.current?.select();
|
this.searchInput.current?.select();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.state.isIsomorphic) {
|
componentWillReceiveProps(nextProps: SearchRouteProps) {
|
||||||
this.setState({
|
if (nextProps.communityId !== this.props.communityId) {
|
||||||
searchCommunitiesLoading: true,
|
this.fetchSelectedCommunity(nextProps);
|
||||||
searchCreatorLoading: true,
|
}
|
||||||
});
|
if (nextProps.creatorId !== this.props.creatorId) {
|
||||||
|
this.fetchSelectedCreator(nextProps);
|
||||||
|
}
|
||||||
|
this.search(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
const promises = [
|
componentDidUpdate(prevProps: SearchRouteProps) {
|
||||||
HttpService.client
|
if (this.props.location.key !== prevProps.location.key) {
|
||||||
.listCommunities({
|
if (this.props.history.action !== "POP") {
|
||||||
type_: defaultListingType,
|
this.searchInput.current?.select();
|
||||||
sort: defaultSortType,
|
}
|
||||||
limit: fetchLimit,
|
}
|
||||||
})
|
}
|
||||||
.then(res => {
|
|
||||||
if (res.state === "success") {
|
|
||||||
this.setState({
|
|
||||||
communitySearchOptions:
|
|
||||||
res.data.communities.map(communityToChoice),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
const { communityId, creatorId } = this.props;
|
fetchDefaultCommunitiesToken?: symbol;
|
||||||
|
async fetchDefaultCommunities({
|
||||||
|
communityId,
|
||||||
|
}: Pick<SearchRouteProps, "communityId">) {
|
||||||
|
const token = (this.fetchDefaultCommunitiesToken = Symbol());
|
||||||
|
this.setState({
|
||||||
|
searchCommunitiesLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (communityId) {
|
const res = await HttpService.client.listCommunities({
|
||||||
promises.push(
|
type_: defaultListingType,
|
||||||
HttpService.client.getCommunity({ id: communityId }).then(res => {
|
sort: defaultSortType,
|
||||||
if (res.state === "success") {
|
limit: fetchLimit,
|
||||||
this.setState(prev => {
|
});
|
||||||
prev.communitySearchOptions.unshift(
|
|
||||||
communityToChoice(res.data.community_view),
|
|
||||||
);
|
|
||||||
|
|
||||||
return prev;
|
if (token !== this.fetchDefaultCommunitiesToken) {
|
||||||
});
|
return;
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
|
if (res.state === "success") {
|
||||||
|
const retainSelected: false | undefined | Choice =
|
||||||
|
!res.data.communities.some(cv => cv.community.id === communityId) &&
|
||||||
|
this.state.communitySearchOptions.find(
|
||||||
|
choice => choice.value === communityId?.toString(),
|
||||||
);
|
);
|
||||||
}
|
const choices = res.data.communities.map(communityToChoice);
|
||||||
|
|
||||||
if (creatorId) {
|
|
||||||
promises.push(
|
|
||||||
HttpService.client
|
|
||||||
.getPersonDetails({
|
|
||||||
person_id: creatorId,
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (res.state === "success") {
|
|
||||||
this.setState(prev => {
|
|
||||||
prev.creatorSearchOptions.push(
|
|
||||||
personToChoice(res.data.person_view),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.searchText) {
|
|
||||||
promises.push(this.search());
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
searchCommunitiesLoading: false,
|
communitySearchOptions: retainSelected
|
||||||
searchCreatorLoading: false,
|
? [retainSelected, ...choices]
|
||||||
|
: choices,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
searchCommunitiesLoading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchSelectedCommunityToken?: symbol;
|
||||||
|
async fetchSelectedCommunity({
|
||||||
|
communityId,
|
||||||
|
}: Pick<SearchRouteProps, "communityId">) {
|
||||||
|
const token = (this.fetchSelectedCommunityToken = Symbol());
|
||||||
|
const needsSelectedCommunity = () => {
|
||||||
|
return !this.state.communitySearchOptions.some(
|
||||||
|
choice => choice.value === communityId?.toString(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if (communityId && needsSelectedCommunity()) {
|
||||||
|
const res = await HttpService.client.getCommunity({ id: communityId });
|
||||||
|
if (
|
||||||
|
res.state === "success" &&
|
||||||
|
needsSelectedCommunity() &&
|
||||||
|
token === this.fetchSelectedCommunityToken
|
||||||
|
) {
|
||||||
|
this.setState(prev => {
|
||||||
|
prev.communitySearchOptions.unshift(
|
||||||
|
communityToChoice(res.data.community_view),
|
||||||
|
);
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchSelectedCreatorToken?: symbol;
|
||||||
|
async fetchSelectedCreator({
|
||||||
|
creatorId,
|
||||||
|
}: Pick<SearchRouteProps, "creatorId">) {
|
||||||
|
const token = (this.fetchSelectedCreatorToken = Symbol());
|
||||||
|
const needsSelectedCreator = () => {
|
||||||
|
return !this.state.creatorSearchOptions.some(
|
||||||
|
choice => choice.value === creatorId?.toString(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!creatorId || !needsSelectedCreator()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ searchCreatorLoading: true });
|
||||||
|
|
||||||
|
const res = await HttpService.client.getPersonDetails({
|
||||||
|
person_id: creatorId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (token !== this.fetchSelectedCreatorToken) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.state === "success" && needsSelectedCreator()) {
|
||||||
|
this.setState(prev => {
|
||||||
|
prev.creatorSearchOptions.push(personToChoice(res.data.person_view));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ searchCreatorLoading: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchAll(props: SearchRouteProps) {
|
||||||
|
await Promise.all([
|
||||||
|
this.fetchDefaultCommunities(props),
|
||||||
|
this.fetchSelectedCommunity(props),
|
||||||
|
this.fetchSelectedCreator(props),
|
||||||
|
this.search(props),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchInitialData({
|
static async fetchInitialData({
|
||||||
|
@ -551,13 +607,15 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
||||||
onSubmit={linkEvent(this, this.handleSearchSubmit)}
|
onSubmit={linkEvent(this, this.handleSearchSubmit)}
|
||||||
>
|
>
|
||||||
<div className="col-auto flex-grow-1 flex-sm-grow-0">
|
<div className="col-auto flex-grow-1 flex-sm-grow-0">
|
||||||
|
{/* key is necessary for defaultValue to update when props.q changes,
|
||||||
|
e.g. back button. */}
|
||||||
<input
|
<input
|
||||||
|
key={this.context.router.history.location.key}
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control me-2 mb-2 col-sm-8"
|
className="form-control me-2 mb-2 col-sm-8"
|
||||||
value={this.state.searchText}
|
defaultValue={this.props.q ?? ""}
|
||||||
placeholder={`${I18NextService.i18n.t("search")}...`}
|
placeholder={`${I18NextService.i18n.t("search")}...`}
|
||||||
aria-label={I18NextService.i18n.t("search")}
|
aria-label={I18NextService.i18n.t("search")}
|
||||||
onInput={linkEvent(this, this.handleQChange)}
|
|
||||||
required
|
required
|
||||||
minLength={1}
|
minLength={1}
|
||||||
ref={this.searchInput}
|
ref={this.searchInput}
|
||||||
|
@ -984,34 +1042,39 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
||||||
return resObjCount + searchCount;
|
return resObjCount + searchCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
async search() {
|
searchToken?: symbol;
|
||||||
const { searchText: q } = this.state;
|
async search(props: SearchRouteProps) {
|
||||||
const { communityId, creatorId, type, sort, listingType, page } =
|
const token = (this.searchToken = Symbol());
|
||||||
this.props;
|
const { q, communityId, creatorId, type, sort, listingType, page } = props;
|
||||||
|
|
||||||
if (q) {
|
if (q) {
|
||||||
this.setState({ searchRes: LOADING_REQUEST });
|
this.setState({ searchRes: LOADING_REQUEST });
|
||||||
this.setState({
|
const searchRes = await HttpService.client.search({
|
||||||
searchRes: await HttpService.client.search({
|
q,
|
||||||
q,
|
community_id: communityId ?? undefined,
|
||||||
community_id: communityId ?? undefined,
|
creator_id: creatorId ?? undefined,
|
||||||
creator_id: creatorId ?? undefined,
|
type_: type,
|
||||||
type_: type,
|
sort,
|
||||||
sort,
|
listing_type: listingType,
|
||||||
listing_type: listingType,
|
page,
|
||||||
page,
|
limit: fetchLimit,
|
||||||
limit: fetchLimit,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token !== this.searchToken) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({ searchRes });
|
||||||
|
|
||||||
if (myAuth()) {
|
if (myAuth()) {
|
||||||
this.setState({ resolveObjectRes: LOADING_REQUEST });
|
this.setState({ resolveObjectRes: LOADING_REQUEST });
|
||||||
this.setState({
|
const resolveObjectRes = await HttpService.client.resolveObject({
|
||||||
resolveObjectRes: await HttpService.client.resolveObject({
|
q,
|
||||||
q,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (token === this.searchToken) {
|
||||||
|
this.setState({ resolveObjectRes });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({ searchRes: EMPTY_REQUEST });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1053,8 +1116,12 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getQ(): string | undefined {
|
||||||
|
return this.searchInput.current?.value ?? this.props.q;
|
||||||
|
}
|
||||||
|
|
||||||
handleSortChange(sort: SortType) {
|
handleSortChange(sort: SortType) {
|
||||||
this.updateUrl({ sort, page: 1 });
|
this.updateUrl({ sort, page: 1, q: this.getQ() });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTypeChange(i: Search, event: any) {
|
handleTypeChange(i: Search, event: any) {
|
||||||
|
@ -1063,6 +1130,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
||||||
i.updateUrl({
|
i.updateUrl({
|
||||||
type,
|
type,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
q: i.getQ(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1074,20 +1142,23 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
||||||
this.updateUrl({
|
this.updateUrl({
|
||||||
listingType,
|
listingType,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
q: this.getQ(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommunityFilterChange({ value }: Choice) {
|
handleCommunityFilterChange({ value }: Choice) {
|
||||||
this.updateUrl({
|
this.updateUrl({
|
||||||
communityId: getIdFromString(value) ?? 0,
|
communityId: getIdFromString(value),
|
||||||
page: 1,
|
page: 1,
|
||||||
|
q: this.getQ(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCreatorFilterChange({ value }: Choice) {
|
handleCreatorFilterChange({ value }: Choice) {
|
||||||
this.updateUrl({
|
this.updateUrl({
|
||||||
creatorId: getIdFromString(value) ?? 0,
|
creatorId: getIdFromString(value),
|
||||||
page: 1,
|
page: 1,
|
||||||
|
q: this.getQ(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1095,44 +1166,25 @@ export class Search extends Component<SearchRouteProps, SearchState> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
i.updateUrl({
|
i.updateUrl({
|
||||||
q: i.state.searchText,
|
q: i.getQ(),
|
||||||
page: 1,
|
page: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQChange(i: Search, event: any) {
|
async updateUrl(props: Partial<SearchProps>) {
|
||||||
i.setState({ searchText: event.target.value });
|
const { q, type, listingType, sort, communityId, creatorId, page } = {
|
||||||
}
|
...this.props,
|
||||||
|
...props,
|
||||||
async updateUrl({
|
};
|
||||||
q,
|
|
||||||
type,
|
|
||||||
listingType,
|
|
||||||
sort,
|
|
||||||
communityId,
|
|
||||||
creatorId,
|
|
||||||
page,
|
|
||||||
}: Partial<SearchProps>) {
|
|
||||||
const {
|
|
||||||
q: urlQ,
|
|
||||||
type: urlType,
|
|
||||||
listingType: urlListingType,
|
|
||||||
communityId: urlCommunityId,
|
|
||||||
sort: urlSort,
|
|
||||||
creatorId: urlCreatorId,
|
|
||||||
page: urlPage,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const query = q ?? this.state.searchText ?? urlQ;
|
|
||||||
|
|
||||||
const queryParams: QueryParams<SearchProps> = {
|
const queryParams: QueryParams<SearchProps> = {
|
||||||
q: query,
|
q,
|
||||||
type: type ?? urlType,
|
type: type,
|
||||||
listingType: listingType ?? urlListingType,
|
listingType: listingType,
|
||||||
communityId: getUpdatedSearchId(communityId, urlCommunityId),
|
communityId: communityId?.toString(),
|
||||||
creatorId: getUpdatedSearchId(creatorId, urlCreatorId),
|
creatorId: creatorId?.toString(),
|
||||||
page: (page ?? urlPage).toString(),
|
page: page?.toString(),
|
||||||
sort: sort ?? urlSort,
|
sort: sort,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.history.push(`/search${getQueryString(queryParams)}`);
|
this.props.history.push(`/search${getQueryString(queryParams)}`);
|
||||||
|
|
|
@ -53,7 +53,11 @@ import {
|
||||||
CreatePost,
|
CreatePost,
|
||||||
getCreatePostQueryParams,
|
getCreatePostQueryParams,
|
||||||
} from "./components/post/create-post";
|
} from "./components/post/create-post";
|
||||||
import { Post, PostFetchConfig } from "./components/post/post";
|
import {
|
||||||
|
Post,
|
||||||
|
PostFetchConfig,
|
||||||
|
getPostQueryParams,
|
||||||
|
} from "./components/post/post";
|
||||||
import {
|
import {
|
||||||
CreatePrivateMessage,
|
CreatePrivateMessage,
|
||||||
CreatePrivateMessageFetchConfig,
|
CreatePrivateMessageFetchConfig,
|
||||||
|
@ -87,6 +91,7 @@ export interface IRoutePropsWithFetch<
|
||||||
component: Inferno.ComponentClass<
|
component: Inferno.ComponentClass<
|
||||||
RouteComponentProps<PathPropsT> & QueryPropsT
|
RouteComponentProps<PathPropsT> & QueryPropsT
|
||||||
>;
|
>;
|
||||||
|
mountedSameRouteNavKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
|
export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
|
||||||
|
@ -96,6 +101,7 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
|
||||||
fetchInitialData: Home.fetchInitialData,
|
fetchInitialData: Home.fetchInitialData,
|
||||||
exact: true,
|
exact: true,
|
||||||
getQueryParams: getHomeQueryParams,
|
getQueryParams: getHomeQueryParams,
|
||||||
|
mountedSameRouteNavKey: "home",
|
||||||
} as HomeFetchConfig,
|
} as HomeFetchConfig,
|
||||||
{
|
{
|
||||||
path: `/login`,
|
path: `/login`,
|
||||||
|
@ -130,28 +136,38 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
|
||||||
component: Communities,
|
component: Communities,
|
||||||
fetchInitialData: Communities.fetchInitialData,
|
fetchInitialData: Communities.fetchInitialData,
|
||||||
getQueryParams: getCommunitiesQueryParams,
|
getQueryParams: getCommunitiesQueryParams,
|
||||||
|
mountedSameRouteNavKey: "communities",
|
||||||
} as CommunitiesFetchConfig,
|
} as CommunitiesFetchConfig,
|
||||||
{
|
{
|
||||||
path: `/post/:post_id`,
|
// "/comment/:post_id?/:comment_id" would be preferable as direct comment
|
||||||
|
// link, but it looks like a Route can't match multiple paths and a
|
||||||
|
// component can't stay mounted across routes.
|
||||||
|
path: `/post/:post_id/:comment_id?`,
|
||||||
component: Post,
|
component: Post,
|
||||||
fetchInitialData: Post.fetchInitialData,
|
fetchInitialData: Post.fetchInitialData,
|
||||||
|
getQueryParams: getPostQueryParams,
|
||||||
|
mountedSameRouteNavKey: "post",
|
||||||
} as PostFetchConfig,
|
} as PostFetchConfig,
|
||||||
{
|
{
|
||||||
path: `/comment/:comment_id`,
|
path: `/comment/:comment_id`,
|
||||||
component: Post,
|
component: Post,
|
||||||
fetchInitialData: Post.fetchInitialData,
|
fetchInitialData: Post.fetchInitialData,
|
||||||
|
getQueryParams: getPostQueryParams,
|
||||||
|
mountedSameRouteNavKey: "post",
|
||||||
} as PostFetchConfig,
|
} as PostFetchConfig,
|
||||||
{
|
{
|
||||||
path: `/c/:name`,
|
path: `/c/:name`,
|
||||||
component: Community,
|
component: Community,
|
||||||
fetchInitialData: Community.fetchInitialData,
|
fetchInitialData: Community.fetchInitialData,
|
||||||
getQueryParams: getCommunityQueryParams,
|
getQueryParams: getCommunityQueryParams,
|
||||||
|
mountedSameRouteNavKey: "community",
|
||||||
} as CommunityFetchConfig,
|
} as CommunityFetchConfig,
|
||||||
{
|
{
|
||||||
path: `/u/:username`,
|
path: `/u/:username`,
|
||||||
component: Profile,
|
component: Profile,
|
||||||
fetchInitialData: Profile.fetchInitialData,
|
fetchInitialData: Profile.fetchInitialData,
|
||||||
getQueryParams: getProfileQueryParams,
|
getQueryParams: getProfileQueryParams,
|
||||||
|
mountedSameRouteNavKey: "profile",
|
||||||
} as ProfileFetchConfig,
|
} as ProfileFetchConfig,
|
||||||
{
|
{
|
||||||
path: `/inbox`,
|
path: `/inbox`,
|
||||||
|
@ -164,16 +180,11 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
|
||||||
fetchInitialData: Settings.fetchInitialData,
|
fetchInitialData: Settings.fetchInitialData,
|
||||||
} as SettingsFetchConfig,
|
} as SettingsFetchConfig,
|
||||||
{
|
{
|
||||||
path: `/modlog/:communityId`,
|
path: `/modlog/:communityId?`,
|
||||||
component: Modlog,
|
|
||||||
fetchInitialData: Modlog.fetchInitialData,
|
|
||||||
getQueryParams: getModlogQueryParams,
|
|
||||||
} as ModlogFetchConfig,
|
|
||||||
{
|
|
||||||
path: `/modlog`,
|
|
||||||
component: Modlog,
|
component: Modlog,
|
||||||
fetchInitialData: Modlog.fetchInitialData,
|
fetchInitialData: Modlog.fetchInitialData,
|
||||||
getQueryParams: getModlogQueryParams,
|
getQueryParams: getModlogQueryParams,
|
||||||
|
mountedSameRouteNavKey: "modlog",
|
||||||
} as ModlogFetchConfig,
|
} as ModlogFetchConfig,
|
||||||
{ path: `/setup`, component: Setup },
|
{ path: `/setup`, component: Setup },
|
||||||
{
|
{
|
||||||
|
@ -196,6 +207,7 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
|
||||||
component: Search,
|
component: Search,
|
||||||
fetchInitialData: Search.fetchInitialData,
|
fetchInitialData: Search.fetchInitialData,
|
||||||
getQueryParams: getSearchQueryParams,
|
getQueryParams: getSearchQueryParams,
|
||||||
|
mountedSameRouteNavKey: "search",
|
||||||
} as SearchFetchConfig,
|
} as SearchFetchConfig,
|
||||||
{
|
{
|
||||||
path: `/password_change/:token`,
|
path: `/password_change/:token`,
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
export default function getUpdatedSearchId(
|
|
||||||
id?: number | null,
|
|
||||||
urlId?: number | null,
|
|
||||||
) {
|
|
||||||
return id === null
|
|
||||||
? undefined
|
|
||||||
: ((id ?? urlId) === 0 ? undefined : id ?? urlId)?.toString();
|
|
||||||
}
|
|
|
@ -31,7 +31,6 @@ import getDataTypeString from "./get-data-type-string";
|
||||||
import getDepthFromComment from "./get-depth-from-comment";
|
import getDepthFromComment from "./get-depth-from-comment";
|
||||||
import getIdFromProps from "./get-id-from-props";
|
import getIdFromProps from "./get-id-from-props";
|
||||||
import getRecipientIdFromProps from "./get-recipient-id-from-props";
|
import getRecipientIdFromProps from "./get-recipient-id-from-props";
|
||||||
import getUpdatedSearchId from "./get-updated-search-id";
|
|
||||||
import initializeSite from "./initialize-site";
|
import initializeSite from "./initialize-site";
|
||||||
import insertCommentIntoTree from "./insert-comment-into-tree";
|
import insertCommentIntoTree from "./insert-comment-into-tree";
|
||||||
import isAuthPath from "./is-auth-path";
|
import isAuthPath from "./is-auth-path";
|
||||||
|
@ -89,7 +88,6 @@ export {
|
||||||
getDepthFromComment,
|
getDepthFromComment,
|
||||||
getIdFromProps,
|
getIdFromProps,
|
||||||
getRecipientIdFromProps,
|
getRecipientIdFromProps,
|
||||||
getUpdatedSearchId,
|
|
||||||
initializeSite,
|
initializeSite,
|
||||||
insertCommentIntoTree,
|
insertCommentIntoTree,
|
||||||
isAuthPath,
|
isAuthPath,
|
||||||
|
|
14
src/shared/utils/helpers/bare-route-push.ts
Normal file
14
src/shared/utils/helpers/bare-route-push.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
|
|
||||||
|
// Intended to allow reloading all the data of the current page by clicking the
|
||||||
|
// navigation link of the current page.
|
||||||
|
export default function bareRoutePush<P extends RouteComponentProps<any>>(
|
||||||
|
prevProps: P,
|
||||||
|
nextProps: P,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
prevProps.location.pathname === nextProps.location.pathname &&
|
||||||
|
!nextProps.location.search &&
|
||||||
|
nextProps.history.action === "PUSH"
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import bareRoutePush from "./bare-route-push";
|
||||||
import capitalizeFirstLetter from "./capitalize-first-letter";
|
import capitalizeFirstLetter from "./capitalize-first-letter";
|
||||||
import debounce from "./debounce";
|
import debounce from "./debounce";
|
||||||
import editListImmutable from "./edit-list-immutable";
|
import editListImmutable from "./edit-list-immutable";
|
||||||
|
@ -27,6 +28,7 @@ import dedupByProperty from "./dedup-by-property";
|
||||||
import getApubName from "./apub-name";
|
import getApubName from "./apub-name";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
bareRoutePush,
|
||||||
cakeDate,
|
cakeDate,
|
||||||
capitalizeFirstLetter,
|
capitalizeFirstLetter,
|
||||||
debounce,
|
debounce,
|
||||||
|
|
Loading…
Reference in a new issue