Halfway done with redux.

This commit is contained in:
Dessalines 2023-12-06 18:11:12 -05:00
parent 4298c3fb91
commit dc2e636f61
37 changed files with 260 additions and 125 deletions

View file

@ -16,7 +16,7 @@ async function startClient() {
await setupDateFns(); await setupDateFns();
const store = setupRedux(windowData); const store = setupRedux(windowData!);
const wrapper = ( const wrapper = (
<Provider store={store}> <Provider store={store}>

View file

@ -1,4 +1,4 @@
import { initializeSite, isAuthPath } from "@utils/app"; import { initializeSite, isAuthPath, setupRedux } from "@utils/app";
import { getHttpBaseInternal } from "@utils/env"; import { getHttpBaseInternal } from "@utils/env";
import { ErrorPageData } from "@utils/types"; import { ErrorPageData } from "@utils/types";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
@ -21,7 +21,6 @@ import { getErrorPageData } from "../utils/get-error-page-data";
import { setForwardedHeaders } from "../utils/set-forwarded-headers"; import { setForwardedHeaders } from "../utils/set-forwarded-headers";
import { getJwtCookie } from "../utils/has-jwt-cookie"; import { getJwtCookie } from "../utils/has-jwt-cookie";
import { Provider } from "inferno-redux"; import { Provider } from "inferno-redux";
import { configureStore, createSlice } from "@reduxjs/toolkit";
export default async (req: Request, res: Response) => { export default async (req: Request, res: Response) => {
try { try {
@ -110,12 +109,7 @@ export default async (req: Request, res: Response) => {
errorPageData, errorPageData,
}; };
const slice = createSlice({ const store = setupRedux(isoData);
name: "isoData",
initialState: { value: isoData },
reducers: {},
});
const store = configureStore({ reducer: slice.reducer });
const wrapper = ( const wrapper = (
<Provider store={store}> <Provider store={store}>

View file

@ -17,6 +17,9 @@ import AnonymousGuard from "../common/anonymous-guard";
import { CodeTheme } from "./code-theme"; import { CodeTheme } from "./code-theme";
export class App extends Component<any, any> { export class App extends Component<any, any> {
get isoData(): IsoDataOptionalSite {
return this.context.store.getState().value;
}
private readonly mainContentRef: RefObject<HTMLElement>; private readonly mainContentRef: RefObject<HTMLElement>;
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
@ -29,8 +32,7 @@ export class App extends Component<any, any> {
} }
render() { render() {
const reduxState: IsoDataOptionalSite = this.context.store.getState().value; const siteRes = this.isoData.site_res;
const siteRes = reduxState.site_res;
const siteView = siteRes?.site_view; const siteView = siteRes?.site_view;
return ( return (
@ -73,11 +75,16 @@ export class App extends Component<any, any> {
<div tabIndex={-1}> <div tabIndex={-1}>
{RouteComponent && {RouteComponent &&
(isAuthPath(path ?? "") ? ( (isAuthPath(path ?? "") ? (
<AuthGuard {...routeProps}> <AuthGuard
isLoggedIn={!!siteRes?.my_user}
componentProps={routeProps}
>
<RouteComponent {...routeProps} /> <RouteComponent {...routeProps} />
</AuthGuard> </AuthGuard>
) : isAnonymousPath(path ?? "") ? ( ) : isAnonymousPath(path ?? "") ? (
<AnonymousGuard> <AnonymousGuard
isLoggedIn={!!siteRes?.my_user}
>
<RouteComponent {...routeProps} /> <RouteComponent {...routeProps} />
</AnonymousGuard> </AnonymousGuard>
) : ( ) : (

View file

@ -38,7 +38,7 @@ function handleCollapseClick(i: Navbar) {
} }
function handleLogOut(i: Navbar) { function handleLogOut(i: Navbar) {
HttpService._Instance.logout(); HttpService.logout();
handleCollapseClick(i); handleCollapseClick(i);
} }
@ -110,7 +110,11 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
> >
{siteView?.site.icon && {siteView?.site.icon &&
showAvatars(this.props.siteRes?.my_user) && ( showAvatars(this.props.siteRes?.my_user) && (
<PictrsImage src={siteView.site.icon} icon /> <PictrsImage
src={siteView.site.icon}
icon
myUserInfo={this.props.siteRes?.my_user}
/>
)} )}
{siteView?.site.name} {siteView?.site.name}
</NavLink> </NavLink>
@ -379,8 +383,13 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
aria-expanded="false" aria-expanded="false"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
> >
{showAvatars() && person.avatar && ( {showAvatars(this.props.siteRes?.my_user) &&
<PictrsImage src={person.avatar} icon /> person.avatar && (
<PictrsImage
src={person.avatar}
icon
myUserInfo={this.props.siteRes?.my_user}
/>
)} )}
{person.display_name ?? person.name} {person.display_name ?? person.name}
</button> </button>

View file

@ -1,12 +1,17 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { UserService } from "../../services";
import { Spinner } from "./icon"; import { Spinner } from "./icon";
interface AnonymousGuardProps {
isLoggedIn: boolean;
}
interface AnonymousGuardState { interface AnonymousGuardState {
hasRedirected: boolean; hasRedirected: boolean;
} }
class AnonymousGuard extends Component<any, AnonymousGuardState> { class AnonymousGuard extends Component<
AnonymousGuardProps,
AnonymousGuardState
> {
state = { state = {
hasRedirected: false, hasRedirected: false,
} as AnonymousGuardState; } as AnonymousGuardState;
@ -16,7 +21,7 @@ class AnonymousGuard extends Component<any, AnonymousGuardState> {
} }
componentDidMount() { componentDidMount() {
if (UserService.Instance.myUserInfo) { if (this.props.isLoggedIn) {
this.context.router.history.replace(`/`); this.context.router.history.replace(`/`);
} else { } else {
this.setState({ hasRedirected: true }); this.setState({ hasRedirected: true });

View file

@ -1,30 +1,28 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route"; import { RouteComponentProps } from "inferno-router/dist/Route";
import { UserService } from "../../services";
import { Spinner } from "./icon"; import { Spinner } from "./icon";
interface AuthGuardProps {
componentProps: RouteComponentProps<Record<string, string>>;
isLoggedIn: boolean;
}
interface AuthGuardState { interface AuthGuardState {
hasRedirected: boolean; hasRedirected: boolean;
} }
class AuthGuard extends Component< class AuthGuard extends Component<AuthGuardProps, AuthGuardState> {
RouteComponentProps<Record<string, string>>,
AuthGuardState
> {
state = { state = {
hasRedirected: false, hasRedirected: false,
} as AuthGuardState; } as AuthGuardState;
constructor( constructor(props: AuthGuardProps, context: any) {
props: RouteComponentProps<Record<string, string>>,
context: any,
) {
super(props, context); super(props, context);
} }
componentDidMount() { componentDidMount() {
if (!UserService.Instance.myUserInfo) { if (!this.props.isLoggedIn) {
const { pathname, search } = this.props.location; const { pathname, search } = this.props.componentProps.location;
this.context.router.history.replace( this.context.router.history.replace(
`/login?prev=${encodeURIComponent(pathname + search)}`, `/login?prev=${encodeURIComponent(pathname + search)}`,
); );

View file

@ -1,9 +1,11 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { PictrsImage } from "./pictrs-image"; import { PictrsImage } from "./pictrs-image";
import { MyUserInfo } from "lemmy-js-client";
interface BannerIconHeaderProps { interface BannerIconHeaderProps {
banner?: string; banner?: string;
icon?: string; icon?: string;
myUserInfo?: MyUserInfo;
} }
export class BannerIconHeader extends Component<BannerIconHeaderProps, any> { export class BannerIconHeader extends Component<BannerIconHeaderProps, any> {
@ -17,13 +19,21 @@ export class BannerIconHeader extends Component<BannerIconHeaderProps, any> {
return ( return (
(banner || icon) && ( (banner || icon) && (
<div className="banner-icon-header position-relative mb-2"> <div className="banner-icon-header position-relative mb-2">
{banner && <PictrsImage src={banner} banner alt="" />} {banner && (
<PictrsImage
src={banner}
banner
alt=""
myUserInfo={this.props.myUserInfo}
/>
)}
{icon && ( {icon && (
<PictrsImage <PictrsImage
src={icon} src={icon}
iconOverlay iconOverlay
pushup={!!this.props.banner} pushup={!!this.props.banner}
alt="" alt=""
myUserInfo={this.props.myUserInfo}
/> />
)} )}
</div> </div>

View file

@ -1,7 +1,7 @@
import { randomStr } from "@utils/helpers"; import { randomStr } from "@utils/helpers";
import classNames from "classnames"; import classNames from "classnames";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { HttpService, I18NextService, UserService } from "../../services"; import { HttpService, I18NextService } from "../../services";
import { toast } from "../../toast"; import { toast } from "../../toast";
import { Icon } from "./icon"; import { Icon } from "./icon";
@ -11,6 +11,7 @@ interface ImageUploadFormProps {
onUpload(url: string): any; onUpload(url: string): any;
onRemove(): any; onRemove(): any;
rounded?: boolean; rounded?: boolean;
isLoggedIn: boolean;
} }
interface ImageUploadFormState { interface ImageUploadFormState {
@ -63,7 +64,7 @@ export class ImageUploadForm extends Component<
accept="image/*,video/*" accept="image/*,video/*"
className="small form-control" className="small form-control"
name={this.id} name={this.id}
disabled={!UserService.Instance.myUserInfo} disabled={!this.props.isLoggedIn}
onChange={linkEvent(this, this.handleImageUpload)} onChange={linkEvent(this, this.handleImageUpload)}
/> />
</form> </form>

View file

@ -1,14 +1,16 @@
import { randomStr } from "@utils/helpers"; import { randomStr } from "@utils/helpers";
import classNames from "classnames"; import classNames from "classnames";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { ListingType } from "lemmy-js-client"; import { ListingType, MyUserInfo } from "lemmy-js-client";
import { I18NextService, UserService } from "../../services"; import { I18NextService } from "../../services";
import { moderatesSomething } from "@utils/roles";
interface ListingTypeSelectProps { interface ListingTypeSelectProps {
type_: ListingType; type_: ListingType;
showLocal: boolean; showLocal: boolean;
showSubscribed: boolean; showSubscribed: boolean;
onChange(val: ListingType): void; onChange(val: ListingType): void;
myUserInfo?: MyUserInfo;
} }
interface ListingTypeSelectState { interface ListingTypeSelectState {
@ -52,15 +54,15 @@ export class ListingTypeSelect extends Component<
value={"Subscribed"} value={"Subscribed"}
checked={this.state.type_ === "Subscribed"} checked={this.state.type_ === "Subscribed"}
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
disabled={!UserService.Instance.myUserInfo} disabled={!this.props.myUserInfo}
/> />
<label <label
htmlFor={`${this.id}-subscribed`} htmlFor={`${this.id}-subscribed`}
title={I18NextService.i18n.t("subscribed_description")} title={I18NextService.i18n.t("subscribed_description")}
className={classNames("btn btn-outline-secondary", { className={classNames("btn btn-outline-secondary", {
active: this.state.type_ === "Subscribed", active: this.state.type_ === "Subscribed",
disabled: !UserService.Instance.myUserInfo, disabled: !this.props.myUserInfo,
pointer: UserService.Instance.myUserInfo, pointer: !!this.props.myUserInfo,
})} })}
> >
{I18NextService.i18n.t("subscribed")} {I18NextService.i18n.t("subscribed")}
@ -107,7 +109,7 @@ export class ListingTypeSelect extends Component<
> >
{I18NextService.i18n.t("all")} {I18NextService.i18n.t("all")}
</label> </label>
{(UserService.Instance.myUserInfo?.moderates.length ?? 0) > 0 && ( {moderatesSomething(this.props.myUserInfo) && (
<> <>
<input <input
id={`${this.id}-moderator-view`} id={`${this.id}-moderator-view`}

View file

@ -1,7 +1,6 @@
import classNames from "classnames"; import classNames from "classnames";
import { Component } from "inferno"; import { Component } from "inferno";
import { MyUserInfo } from "lemmy-js-client";
import { UserService } from "../../services";
const iconThumbnailSize = 96; const iconThumbnailSize = 96;
const thumbnailSize = 256; const thumbnailSize = 256;
@ -16,6 +15,7 @@ interface PictrsImageProps {
iconOverlay?: boolean; iconOverlay?: boolean;
pushup?: boolean; pushup?: boolean;
cardTop?: boolean; cardTop?: boolean;
myUserInfo?: MyUserInfo;
} }
export class PictrsImage extends Component<PictrsImageProps, any> { export class PictrsImage extends Component<PictrsImageProps, any> {
@ -27,9 +27,9 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
const { src, icon, iconOverlay, banner, thumbnail, nsfw, pushup, cardTop } = const { src, icon, iconOverlay, banner, thumbnail, nsfw, pushup, cardTop } =
this.props; this.props;
let user_blur_nsfw = true; let user_blur_nsfw = true;
if (UserService.Instance.myUserInfo) { if (this.props.myUserInfo) {
user_blur_nsfw = user_blur_nsfw =
UserService.Instance.myUserInfo?.local_user_view.local_user.blur_nsfw; this.props.myUserInfo?.local_user_view.local_user.blur_nsfw;
} }
const blur_image = nsfw && user_blur_nsfw; const blur_image = nsfw && user_blur_nsfw;

View file

@ -3,12 +3,13 @@ import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next"; import { NoOptionI18nKeys } from "i18next";
import { Component, MouseEventHandler, linkEvent } from "inferno"; import { Component, MouseEventHandler, linkEvent } from "inferno";
import { CommunityView } from "lemmy-js-client"; import { CommunityView } from "lemmy-js-client";
import { I18NextService, UserService } from "../../services"; import { I18NextService } 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";
interface SubscribeButtonProps { interface SubscribeButtonProps {
loggedIn: boolean;
communityView: CommunityView; communityView: CommunityView;
onFollow: MouseEventHandler; onFollow: MouseEventHandler;
onUnFollow: MouseEventHandler; onUnFollow: MouseEventHandler;
@ -25,6 +26,7 @@ export function SubscribeButton({
onUnFollow, onUnFollow,
loading = false, loading = false,
isLink = false, isLink = false,
loggedIn,
}: SubscribeButtonProps) { }: SubscribeButtonProps) {
let i18key: NoOptionI18nKeys; let i18key: NoOptionI18nKeys;
@ -51,7 +53,7 @@ export function SubscribeButton({
isLink ? "btn-link d-inline-block" : "d-block mb-2 w-100", isLink ? "btn-link d-inline-block" : "d-block mb-2 w-100",
); );
if (!UserService.Instance.myUserInfo) { if (!loggedIn) {
return ( return (
<> <>
<button <button

View file

@ -186,6 +186,7 @@ export class Communities extends Component<any, CommunitiesState> {
<td className="text-right"> <td className="text-right">
<SubscribeButton <SubscribeButton
communityView={cv} communityView={cv}
loggedIn={!!this.isoData.site_res.my_user}
onFollow={linkEvent( onFollow={linkEvent(
{ {
i: this, i: this,

View file

@ -6,6 +6,7 @@ import {
CreateCommunity, CreateCommunity,
EditCommunity, EditCommunity,
Language, Language,
MyUserInfo,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { I18NextService } from "../../services"; import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
@ -15,6 +16,7 @@ import { MarkdownTextArea } from "../common/markdown-textarea";
interface CommunityFormProps { interface CommunityFormProps {
community_view?: CommunityView; // If a community is given, that means this is an edit community_view?: CommunityView; // If a community is given, that means this is an edit
myUserInfo?: MyUserInfo;
allLanguages: Language[]; allLanguages: Language[];
siteLanguages: number[]; siteLanguages: number[];
communityLanguages?: number[]; communityLanguages?: number[];
@ -166,6 +168,7 @@ export class CommunityForm extends Component<
imageSrc={this.state.form.icon} imageSrc={this.state.form.icon}
onUpload={this.handleIconUpload} onUpload={this.handleIconUpload}
onRemove={this.handleIconRemove} onRemove={this.handleIconRemove}
isLoggedIn={!!this.props.myUserInfo}
rounded rounded
/> />
</div> </div>
@ -180,6 +183,7 @@ export class CommunityForm extends Component<
imageSrc={this.state.form.banner} imageSrc={this.state.form.banner}
onUpload={this.handleBannerUpload} onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove} onRemove={this.handleBannerRemove}
isLoggedIn={!!this.props.myUserInfo}
/> />
</div> </div>
</div> </div>

View file

@ -65,7 +65,14 @@ export class CommunityLink extends Component<CommunityLinkProps, any> {
{!this.props.hideAvatar && {!this.props.hideAvatar &&
!this.props.community.removed && !this.props.community.removed &&
showAvatars(this.props.myUserInfo) && showAvatars(this.props.myUserInfo) &&
icon && <PictrsImage src={icon} icon nsfw={nsfw} />} icon && (
<PictrsImage
src={icon}
icon
nsfw={nsfw}
myUserInfo={this.props.myUserInfo}
/>
)}
<span className="overflow-wrap-anywhere">{displayName}</span> <span className="overflow-wrap-anywhere">{displayName}</span>
</> </>
); );

View file

@ -507,10 +507,15 @@ export class Community extends Component<
return ( return (
community && ( community && (
<div className="mb-2"> <div className="mb-2">
<BannerIconHeader banner={community.banner} icon={community.icon} /> <BannerIconHeader
banner={community.banner}
icon={community.icon}
myUserInfo={this.isoData.site_res.my_user}
/>
<h1 className="h4 mb-0 overflow-wrap-anywhere">{community.title}</h1> <h1 className="h4 mb-0 overflow-wrap-anywhere">{community.title}</h1>
<CommunityLink <CommunityLink
community={community} community={community}
myUserInfo={this.isoData.site_res.my_user}
realLink realLink
useApubName useApubName
muted muted

View file

@ -46,6 +46,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
{I18NextService.i18n.t("create_community")} {I18NextService.i18n.t("create_community")}
</h1> </h1>
<CommunityForm <CommunityForm
myUserInfo={this.isoData.site_res.my_user}
onUpsertCommunity={this.handleCommunityCreate} onUpsertCommunity={this.handleCommunityCreate}
enableNsfw={enableNsfw(this.state.siteRes)} enableNsfw={enableNsfw(this.state.siteRes)}
allLanguages={this.state.siteRes.all_languages} allLanguages={this.state.siteRes.all_languages}

View file

@ -110,6 +110,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
this.sidebar() this.sidebar()
) : ( ) : (
<CommunityForm <CommunityForm
myUserInfo={this.props.myUserInfo}
community_view={this.props.community_view} community_view={this.props.community_view}
allLanguages={this.props.allLanguages} allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages} siteLanguages={this.props.siteLanguages}
@ -136,6 +137,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.communityTitle()} {this.communityTitle()}
{this.props.editable && this.adminButtons()} {this.props.editable && this.adminButtons()}
<SubscribeButton <SubscribeButton
loggedIn={!!this.props.myUserInfo}
communityView={this.props.community_view} communityView={this.props.community_view}
onFollow={linkEvent(this, this.handleFollowCommunity)} onFollow={linkEvent(this, this.handleFollowCommunity)}
onUnFollow={linkEvent(this, this.handleUnfollowCommunity)} onUnFollow={linkEvent(this, this.handleUnfollowCommunity)}
@ -180,7 +182,11 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<div> <div>
<h2 className="h5 mb-0"> <h2 className="h5 mb-0">
{this.props.showIcon && !community.removed && ( {this.props.showIcon && !community.removed && (
<BannerIconHeader icon={community.icon} banner={community.banner} /> <BannerIconHeader
icon={community.icon}
banner={community.banner}
myUserInfo={this.props.myUserInfo}
/>
)} )}
<span className="me-2"> <span className="me-2">
<CommunityLink community={community} hideAvatar /> <CommunityLink community={community} hideAvatar />

View file

@ -1,10 +1,9 @@
import { setIsoData } from "@utils/app";
import { isBrowser, updateDataBsTheme } from "@utils/browser"; import { isBrowser, updateDataBsTheme } from "@utils/browser";
import { getQueryParams } from "@utils/helpers"; import { getQueryParams } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route"; import { RouteComponentProps } from "inferno-router/dist/Route";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { I18NextService, UserService } from "../../services"; import { I18NextService } from "../../services";
import { import {
EMPTY_REQUEST, EMPTY_REQUEST,
HttpService, HttpService,
@ -17,6 +16,9 @@ import { Spinner } from "../common/icon";
import PasswordInput from "../common/password-input"; import PasswordInput from "../common/password-input";
import TotpModal from "../common/totp-modal"; import TotpModal from "../common/totp-modal";
import { UnreadCounterService } from "../../services"; import { UnreadCounterService } from "../../services";
import { IsoData, IsoDataOptionalSite } from "../../interfaces";
import { updateSite } from "@utils/app/setup-redux";
import { EnhancedStore } from "@reduxjs/toolkit";
interface LoginProps { interface LoginProps {
prev?: string; prev?: string;
@ -39,15 +41,20 @@ interface State {
show2faModal: boolean; show2faModal: boolean;
} }
async function handleLoginSuccess(i: Login, loginRes: LoginResponse) { async function handleLoginSuccess(
UserService.Instance.login({ i: Login,
loginRes: LoginResponse,
store: EnhancedStore<IsoDataOptionalSite>,
) {
HttpService.login({
res: loginRes, res: loginRes,
}); });
const site = await HttpService.client.getSite(); const site = await HttpService.client.getSite();
if (site.state === "success") { if (site.state === "success") {
// TODO this is the key, you need to update the redux store here store.dispatch(updateSite(site.data));
UserService.Instance.myUserInfo = site.data.my_user;
// TODO why is this just the theme??
updateDataBsTheme(site.data); updateDataBsTheme(site.data);
} }
@ -86,7 +93,7 @@ async function handleLoginSubmit(i: Login, event: any) {
} }
case "success": { case "success": {
handleLoginSuccess(i, loginRes.data); handleLoginSuccess(i, loginRes.data, this.context.store);
break; break;
} }
} }
@ -111,7 +118,9 @@ export class Login extends Component<
RouteComponentProps<Record<string, never>>, RouteComponentProps<Record<string, never>>,
State State
> { > {
private isoData = setIsoData(this.context); get isoData(): IsoData {
return this.context.store.getState().value;
}
state: State = { state: State = {
loginRes: EMPTY_REQUEST, loginRes: EMPTY_REQUEST,
@ -169,7 +178,7 @@ export class Login extends Component<
const successful = loginRes.state === "success"; const successful = loginRes.state === "success";
if (successful) { if (successful) {
this.setState({ show2faModal: false }); this.setState({ show2faModal: false });
handleLoginSuccess(this, loginRes.data); handleLoginSuccess(this, loginRes.data, this.context.store);
} else { } else {
toast(I18NextService.i18n.t("incorrect_totp_code"), "danger"); toast(I18NextService.i18n.t("incorrect_totp_code"), "danger");
} }

View file

@ -1,4 +1,4 @@
import { fetchThemeList, setIsoData } from "@utils/app"; import { fetchThemeList } from "@utils/app";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet"; import { Helmet } from "inferno-helmet";
import { import {
@ -7,7 +7,7 @@ import {
LoginResponse, LoginResponse,
Register, Register,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { I18NextService, UserService } from "../../services"; import { I18NextService } from "../../services";
import { import {
EMPTY_REQUEST, EMPTY_REQUEST,
HttpService, HttpService,
@ -17,6 +17,7 @@ import {
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import PasswordInput from "../common/password-input"; import PasswordInput from "../common/password-input";
import { SiteForm } from "./site-form"; import { SiteForm } from "./site-form";
import { IsoData } from "../../interfaces";
interface State { interface State {
form: { form: {
@ -37,7 +38,9 @@ interface State {
} }
export class Setup extends Component<any, State> { export class Setup extends Component<any, State> {
private isoData = setIsoData(this.context); get isoData(): IsoData {
return this.context.store.getState().value;
}
state: State = { state: State = {
registerRes: EMPTY_REQUEST, registerRes: EMPTY_REQUEST,
@ -45,7 +48,7 @@ export class Setup extends Component<any, State> {
form: { form: {
show_nsfw: true, show_nsfw: true,
}, },
doneRegisteringUser: !!UserService.Instance.myUserInfo, doneRegisteringUser: !!this.isoData.site_res.my_user,
siteRes: this.isoData.site_res, siteRes: this.isoData.site_res,
}; };
@ -194,7 +197,7 @@ export class Setup extends Component<any, State> {
if (i.state.registerRes.state === "success") { if (i.state.registerRes.state === "success") {
const data = i.state.registerRes.data; const data = i.state.registerRes.data;
UserService.Instance.login({ res: data }); HttpService.login({ res: data });
i.setState({ doneRegisteringUser: true }); i.setState({ doneRegisteringUser: true });
} }
} }

View file

@ -24,6 +24,7 @@ import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
import PasswordInput from "../common/password-input"; import PasswordInput from "../common/password-input";
import { IsoData } from "../../interfaces"; import { IsoData } from "../../interfaces";
import { updateSite } from "@utils/app/setup-redux";
interface State { interface State {
registerRes: RequestState<LoginResponse>; registerRes: RequestState<LoginResponse>;
@ -400,14 +401,15 @@ export class Signup extends Component<any, State> {
// Only log them in if a jwt was set // Only log them in if a jwt was set
if (data.jwt) { if (data.jwt) {
UserService.Instance.login({ HttpService.login({
res: data, res: data,
}); });
const site = await HttpService.client.getSite(); const site = await HttpService.client.getSite();
// TODO test this
if (site.state === "success") { if (site.state === "success") {
UserService.Instance.myUserInfo = site.data.my_user; i.context.store.dispatch(updateSite(site.data));
} }
i.props.history.replace("/communities"); i.props.history.replace("/communities");

View file

@ -167,6 +167,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
imageSrc={this.state.siteForm.icon} imageSrc={this.state.siteForm.icon}
onUpload={this.handleIconUpload} onUpload={this.handleIconUpload}
onRemove={this.handleIconRemove} onRemove={this.handleIconRemove}
isLoggedIn={!!this.props.siteRes.my_user}
rounded rounded
/> />
</div> </div>
@ -181,6 +182,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
imageSrc={this.state.siteForm.banner} imageSrc={this.state.siteForm.banner}
onUpload={this.handleBannerUpload} onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove} onRemove={this.handleBannerRemove}
isLoggedIn={!!this.props.siteRes.my_user}
/> />
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
import classNames from "classnames"; import classNames from "classnames";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { PersonView, Site, SiteAggregates } from "lemmy-js-client"; import { MyUserInfo, PersonView, Site, SiteAggregates } from "lemmy-js-client";
import { mdToHtml } from "../../markdown"; import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services"; import { I18NextService } from "../../services";
import { Badges } from "../common/badges"; import { Badges } from "../common/badges";
@ -14,6 +14,7 @@ interface SiteSidebarProps {
counts?: SiteAggregates; counts?: SiteAggregates;
admins?: PersonView[]; admins?: PersonView[];
isMobile?: boolean; isMobile?: boolean;
myUserInfo?: MyUserInfo;
} }
interface SiteSidebarState { interface SiteSidebarState {
@ -36,7 +37,10 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
<header className="card-header" id="sidebarInfoHeader"> <header className="card-header" id="sidebarInfoHeader">
{this.siteName()} {this.siteName()}
{!this.state.collapsed && ( {!this.state.collapsed && (
<BannerIconHeader banner={this.props.site.banner} /> <BannerIconHeader
banner={this.props.site.banner}
myUserInfo={this.props.myUserInfo}
/>
)} )}
</header> </header>

View file

@ -560,6 +560,7 @@ export class Inbox extends Component<any, InboxState> {
return ( return (
<PrivateMessage <PrivateMessage
key={i.id} key={i.id}
myUserInfo={this.isoData.site_res.my_user}
private_message_view={i.view as PrivateMessageView} private_message_view={i.view as PrivateMessageView}
onDelete={this.handleDeleteMessage} onDelete={this.handleDeleteMessage}
onMarkRead={this.handleMarkMessageAsRead} onMarkRead={this.handleMarkMessageAsRead}
@ -704,6 +705,7 @@ export class Inbox extends Component<any, InboxState> {
<PrivateMessage <PrivateMessage
key={pmv.private_message.id} key={pmv.private_message.id}
private_message_view={pmv} private_message_view={pmv}
myUserInfo={this.isoData.site_res.my_user}
onDelete={this.handleDeleteMessage} onDelete={this.handleDeleteMessage}
onMarkRead={this.handleMarkMessageAsRead} onMarkRead={this.handleMarkMessageAsRead}
onReport={this.handleMessageReport} onReport={this.handleMessageReport}

View file

@ -1,8 +1,7 @@
import { setIsoData } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers"; import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { GetSiteResponse, SuccessResponse } from "lemmy-js-client"; import { GetSiteResponse, SuccessResponse } from "lemmy-js-client";
import { HttpService, I18NextService, UserService } from "../../services"; import { HttpService, I18NextService } from "../../services";
import { import {
EMPTY_REQUEST, EMPTY_REQUEST,
LOADING_REQUEST, LOADING_REQUEST,
@ -12,6 +11,8 @@ import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import PasswordInput from "../common/password-input"; import PasswordInput from "../common/password-input";
import { toast } from "../../toast"; import { toast } from "../../toast";
import { IsoData } from "../../interfaces";
import { updateSite } from "@utils/app/setup-redux";
interface State { interface State {
passwordChangeRes: RequestState<SuccessResponse>; passwordChangeRes: RequestState<SuccessResponse>;
@ -24,7 +25,9 @@ interface State {
} }
export class PasswordChange extends Component<any, State> { export class PasswordChange extends Component<any, State> {
private isoData = setIsoData(this.context); get isoData(): IsoData {
return this.context.store.getState().value;
}
state: State = { state: State = {
passwordChangeRes: EMPTY_REQUEST, passwordChangeRes: EMPTY_REQUEST,
@ -128,9 +131,10 @@ export class PasswordChange extends Component<any, State> {
if (i.state.passwordChangeRes.state === "success") { if (i.state.passwordChangeRes.state === "success") {
toast(I18NextService.i18n.t("password_changed")); toast(I18NextService.i18n.t("password_changed"));
// TODO test this
const site = await HttpService.client.getSite(); const site = await HttpService.client.getSite();
if (site.state === "success") { if (site.state === "success") {
UserService.Instance.myUserInfo = site.data.my_user; i.context.store.dispatch(updateSite(site.data));
} }
i.props.history.replace("/"); i.props.history.replace("/");

View file

@ -92,6 +92,7 @@ export class PersonListing extends Component<PersonListingProps, any> {
<PictrsImage <PictrsImage
src={avatar ?? `${getStaticDir()}/assets/icons/icon-96x96.png`} src={avatar ?? `${getStaticDir()}/assets/icons/icon-96x96.png`}
icon icon
myUserInfo={this.props.myUserInfo}
/> />
)} )}
<span>{displayName}</span> <span>{displayName}</span>

View file

@ -490,6 +490,7 @@ export class Profile extends Component<
<BannerIconHeader <BannerIconHeader
banner={pv.person.banner} banner={pv.person.banner}
icon={pv.person.avatar} icon={pv.person.avatar}
myUserInfo={this.isoData.site_res.my_user}
/> />
)} )}
<div className="mb-3"> <div className="mb-3">

View file

@ -4,7 +4,6 @@ import {
fetchThemeList, fetchThemeList,
fetchUsers, fetchUsers,
instanceToChoice, instanceToChoice,
myAuth,
personToChoice, personToChoice,
setTheme, setTheme,
showLocal, showLocal,
@ -64,6 +63,7 @@ import TotpModal from "../common/totp-modal";
import { LoadingEllipses } from "../common/loading-ellipses"; import { LoadingEllipses } from "../common/loading-ellipses";
import { updateDataBsTheme } from "../../utils/browser"; import { updateDataBsTheme } from "../../utils/browser";
import { getHttpBaseInternal } from "../../utils/env"; import { getHttpBaseInternal } from "../../utils/env";
import { updateSite } from "@utils/app/setup-redux";
type SettingsData = RouteDataResponse<{ type SettingsData = RouteDataResponse<{
instancesRes: GetFederatedInstancesResponse; instancesRes: GetFederatedInstancesResponse;
@ -763,6 +763,7 @@ export class Settings extends Component<any, SettingsState> {
imageSrc={this.state.saveUserSettingsForm.avatar} imageSrc={this.state.saveUserSettingsForm.avatar}
onUpload={this.handleAvatarUpload} onUpload={this.handleAvatarUpload}
onRemove={this.handleAvatarRemove} onRemove={this.handleAvatarRemove}
isLoggedIn={!!this.isoData.site_res.my_user}
rounded rounded
/> />
</div> </div>
@ -777,6 +778,7 @@ export class Settings extends Component<any, SettingsState> {
imageSrc={this.state.saveUserSettingsForm.banner} imageSrc={this.state.saveUserSettingsForm.banner}
onUpload={this.handleBannerUpload} onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove} onRemove={this.handleBannerRemove}
isLoggedIn={!!this.isoData.site_res.my_user}
/> />
</div> </div>
</div> </div>
@ -1298,14 +1300,12 @@ export class Settings extends Component<any, SettingsState> {
} }
async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) { async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
if (myAuth()) {
const res = await HttpService.client.blockCommunity({ const res = await HttpService.client.blockCommunity({
community_id: i.communityId, community_id: i.communityId,
block: false, block: false,
}); });
i.ctx.communityBlock(res); i.ctx.communityBlock(res);
} }
}
async handleBlockInstance({ value }: Choice) { async handleBlockInstance({ value }: Choice) {
if (value !== "0") { if (value !== "0") {
@ -1530,8 +1530,8 @@ export class Settings extends Component<any, SettingsState> {
siteRes: siteRes.data, siteRes: siteRes.data,
}); });
// TODO need to update this // TODO need to test this
UserService.Instance.myUserInfo = siteRes.data.my_user; i.context.store.dispatch(updateSite(siteRes.data));
} }
toast(I18NextService.i18n.t("saved")); toast(I18NextService.i18n.t("saved"));
@ -1631,8 +1631,8 @@ export class Settings extends Component<any, SettingsState> {
}, },
} = siteRes.data.my_user!.local_user_view; } = siteRes.data.my_user!.local_user_view;
// TODO need to update redux // TODO need to test this
UserService.Instance.myUserInfo = siteRes.data.my_user; i.context.store.dispatch(updateSite(siteRes.data));
updateDataBsTheme(siteRes.data); updateDataBsTheme(siteRes.data);
i.setState(prev => ({ i.setState(prev => ({
@ -1695,7 +1695,7 @@ export class Settings extends Component<any, SettingsState> {
delete_content: false, delete_content: false,
}); });
if (deleteAccountRes.state === "success") { if (deleteAccountRes.state === "success") {
UserService.Instance.logout(); HttpService.logout();
this.context.router.history.replace("/"); this.context.router.history.replace("/");
} }

View file

@ -1,4 +1,3 @@
import { myAuth } from "@utils/app";
import { canShare, share } from "@utils/browser"; import { canShare, share } from "@utils/browser";
import { getExternalHost, getHttpBase } from "@utils/env"; import { getExternalHost, getHttpBase } from "@utils/env";
import { import {
@ -255,7 +254,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<> <>
<div className="offset-sm-3 my-2 d-none d-sm-block"> <div className="offset-sm-3 my-2 d-none d-sm-block">
<a href={this.imageSrc} className="d-inline-block"> <a href={this.imageSrc} className="d-inline-block">
<PictrsImage src={this.imageSrc} /> <PictrsImage
src={this.imageSrc}
myUserInfo={this.props.myUserInfo}
/>
</a> </a>
</div> </div>
<div className="my-2 d-block d-sm-none"> <div className="my-2 d-block d-sm-none">
@ -264,7 +266,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="p-0 border-0 bg-transparent d-inline-block" className="p-0 border-0 bg-transparent d-inline-block"
onClick={linkEvent(this, this.handleImageExpandClick)} onClick={linkEvent(this, this.handleImageExpandClick)}
> >
<PictrsImage src={this.imageSrc} /> <PictrsImage
src={this.imageSrc}
myUserInfo={this.props.myUserInfo}
/>
</button> </button>
</div> </div>
</> </>
@ -310,6 +315,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
thumbnail thumbnail
alt="" alt=""
nsfw={pv.post.nsfw || pv.community.nsfw} nsfw={pv.post.nsfw || pv.community.nsfw}
myUserInfo={this.props.myUserInfo}
/> />
); );
} }
@ -630,7 +636,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{mobile && !this.props.viewOnly && ( {mobile && !this.props.viewOnly && (
<VoteButtonsCompact <VoteButtonsCompact
voteContentType={VoteContentType.Post} voteContentType={VoteContentType.Post}
loggedIn={!!this.props.myUserInfo} myUserInfo={this.props.myUserInfo}
id={this.postView.post.id} id={this.postView.post.id}
onVote={this.props.onPostVote} onVote={this.props.onPostVote}
enableDownvotes={this.props.enableDownvotes} enableDownvotes={this.props.enableDownvotes}
@ -1383,7 +1389,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<div className="col flex-grow-0"> <div className="col flex-grow-0">
<VoteButtons <VoteButtons
voteContentType={VoteContentType.Post} voteContentType={VoteContentType.Post}
loggedIn={!!this.props.myUserInfo} myUserInfo={this.props.myUserInfo}
id={this.postView.post.id} id={this.postView.post.id}
onVote={this.props.onPostVote} onVote={this.props.onPostVote}
enableDownvotes={this.props.enableDownvotes} enableDownvotes={this.props.enableDownvotes}
@ -1716,7 +1722,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
i.setState({ imageExpanded: !i.state.imageExpanded }); i.setState({ imageExpanded: !i.state.imageExpanded });
setupTippy(); setupTippy();
if (myAuth() && !i.postView.read) { if (!!this.props.myUserInfo && !i.postView.read) {
i.props.onMarkPostAsRead({ i.props.onMarkPostAsRead({
post_ids: [i.postView.post.id], post_ids: [i.postView.post.id],
read: true, read: true,

View file

@ -5,11 +5,12 @@ import {
DeletePrivateMessage, DeletePrivateMessage,
EditPrivateMessage, EditPrivateMessage,
MarkPrivateMessageAsRead, MarkPrivateMessageAsRead,
MyUserInfo,
Person, Person,
PrivateMessageView, PrivateMessageView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { mdToHtml } from "../../markdown"; import { mdToHtml } from "../../markdown";
import { I18NextService, UserService } from "../../services"; import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time"; import { MomentTime } from "../common/moment-time";
import { PersonListing } from "../person/person-listing"; import { PersonListing } from "../person/person-listing";
@ -28,6 +29,7 @@ interface PrivateMessageState {
interface PrivateMessageProps { interface PrivateMessageProps {
private_message_view: PrivateMessageView; private_message_view: PrivateMessageView;
myUserInfo?: MyUserInfo;
onDelete(form: DeletePrivateMessage): void; onDelete(form: DeletePrivateMessage): void;
onMarkRead(form: MarkPrivateMessageAsRead): void; onMarkRead(form: MarkPrivateMessageAsRead): void;
onReport(form: CreatePrivateMessageReport): void; onReport(form: CreatePrivateMessageReport): void;
@ -57,7 +59,7 @@ export class PrivateMessage extends Component<
get mine(): boolean { get mine(): boolean {
return ( return (
UserService.Instance.myUserInfo?.local_user_view.person.id === this.props.myUserInfo?.local_user_view.person.id ===
this.props.private_message_view.creator.id this.props.private_message_view.creator.id
); );
} }

View file

@ -151,7 +151,11 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
<h1>{I18NextService.i18n.t("community_federated")}</h1> <h1>{I18NextService.i18n.t("community_federated")}</h1>
<div className="card mt-5"> <div className="card mt-5">
{communityView.community.banner && ( {communityView.community.banner && (
<PictrsImage src={communityView.community.banner} cardTop /> <PictrsImage
src={communityView.community.banner}
cardTop
myUserInfo={this.isoData.site_res.my_user}
/>
)} )}
<div className="card-body"> <div className="card-body">
<h2 className="card-title"> <h2 className="card-title">
@ -163,6 +167,7 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
</div> </div>
)} )}
<SubscribeButton <SubscribeButton
loggedIn={!!this.isoData.site_res.my_user}
communityView={communityView} communityView={communityView}
onFollow={linkEvent(this, handleFollow)} onFollow={linkEvent(this, handleFollow)}
onUnFollow={linkEvent(this, handleUnfollow)} onUnFollow={linkEvent(this, handleUnfollow)}

View file

@ -6,7 +6,6 @@ import {
fetchCommunities, fetchCommunities,
fetchUsers, fetchUsers,
getUpdatedSearchId, getUpdatedSearchId,
myAuth,
personToChoice, personToChoice,
showLocal, showLocal,
} from "@utils/app"; } from "@utils/app";
@ -979,7 +978,7 @@ export class Search extends Component<any, SearchState> {
window.scrollTo(0, 0); window.scrollTo(0, 0);
restoreScrollPosition(this.context); restoreScrollPosition(this.context);
if (myAuth()) { if (this.isoData.site_res.my_user) {
this.setState({ resolveObjectRes: LOADING_REQUEST }); this.setState({ resolveObjectRes: LOADING_REQUEST });
this.setState({ this.setState({
resolveObjectRes: await HttpService.silent_client.resolveObject({ resolveObjectRes: await HttpService.silent_client.resolveObject({

View file

@ -104,6 +104,9 @@ export function wrapClient(client: LemmyHttp, silent = false) {
// auth: string; // auth: string;
// } // }
/**
* An HTTP service, only to be used in the browser client
*/
export class HttpService { export class HttpService {
static #_instance: HttpService; static #_instance: HttpService;
#silent_client: WrappedLemmyHttp; #silent_client: WrappedLemmyHttp;
@ -114,13 +117,13 @@ export class HttpService {
const auth = cookie.parse(document.cookie)[authCookieName]; const auth = cookie.parse(document.cookie)[authCookieName];
if (auth) { if (auth) {
HttpService.client.setHeaders({ Authorization: `Bearer ${auth}` }); lemmyHttp.setHeaders({ Authorization: `Bearer ${auth}` });
} }
this.#client = wrapClient(lemmyHttp); this.#client = wrapClient(lemmyHttp);
this.#silent_client = wrapClient(lemmyHttp, true); this.#silent_client = wrapClient(lemmyHttp, true);
} }
public login({ public static login({
res, res,
showToast = true, showToast = true,
}: { }: {
@ -130,15 +133,17 @@ export class HttpService {
if (isBrowser() && res.jwt) { if (isBrowser() && res.jwt) {
showToast && toast(I18NextService.i18n.t("logged_in")); showToast && toast(I18NextService.i18n.t("logged_in"));
setAuthCookie(res.jwt); setAuthCookie(res.jwt);
const headers = { Authorization: `Bearer ${res.jwt}` };
this.#_instance.#client.setHeaders(headers);
this.#_instance.#silent_client.setHeaders(headers);
} }
} }
public logout() { public static logout() {
if (isBrowser()) { if (isBrowser()) {
clearAuthCookie(); clearAuthCookie();
} }
this.#_instance.#client.logout();
this.#client.logout();
if (isAuthPath(location.pathname)) { if (isAuthPath(location.pathname)) {
location.replace("/"); location.replace("/");

View file

@ -1,6 +1,5 @@
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import i18next, { Resource } from "i18next"; import i18next, { Resource } from "i18next";
import { UserService } from "../services";
import { ar } from "../translations/ar"; import { ar } from "../translations/ar";
import { bg } from "../translations/bg"; import { bg } from "../translations/bg";
import { ca } from "../translations/ca"; import { ca } from "../translations/ca";
@ -32,6 +31,7 @@ import { sv } from "../translations/sv";
import { vi } from "../translations/vi"; import { vi } from "../translations/vi";
import { zh } from "../translations/zh"; import { zh } from "../translations/zh";
import { zh_Hant } from "../translations/zh_Hant"; import { zh_Hant } from "../translations/zh_Hant";
import { MyUserInfo } from "lemmy-js-client";
export const languages = [ export const languages = [
{ resource: ar, code: "ar", name: "العربية" }, { resource: ar, code: "ar", name: "العربية" },
@ -74,20 +74,24 @@ function format(value: any, format: any): any {
return format === "uppercase" ? value.toUpperCase() : value; return format === "uppercase" ? value.toUpperCase() : value;
} }
class LanguageDetector { export class LanguageDetector {
static readonly type = "languageDetector"; static readonly type = "languageDetector";
private myLanguages: string[];
// TODO What's going on here? test this.
detect() { detect() {
return this.myLanguages;
}
setupMyLanguages(myUserInfo?: MyUserInfo): string[] {
const langs: string[] = []; const langs: string[] = [];
const myLang = const myLang =
UserService.Instance.myUserInfo?.local_user_view.local_user myUserInfo?.local_user_view.local_user.interface_language ?? "browser";
.interface_language ?? "browser";
if (myLang !== "browser") langs.push(myLang); if (myLang !== "browser") langs.push(myLang);
if (isBrowser()) langs.push(...navigator.languages); if (isBrowser()) langs.push(...navigator.languages);
return langs; return langs;
} }
} }

View file

@ -1,10 +1,10 @@
import { HttpService } from "../services"; import { HttpService } from "../services";
import { updateUnreadCountsInterval } from "../config"; import { updateUnreadCountsInterval } from "../config";
import { poll } from "@utils/helpers"; import { poll } from "@utils/helpers";
import { myAuth } from "@utils/app"; import { amAdmin, moderatesSomething } from "@utils/roles";
import { amAdmin } from "@utils/roles";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { MyUserInfo } from "lemmy-js-client";
/** /**
* Service to poll and keep track of unread messages / notifications. * Service to poll and keep track of unread messages / notifications.
@ -14,6 +14,8 @@ export class UnreadCounterService {
unreadPrivateMessages = 0; unreadPrivateMessages = 0;
unreadReplies = 0; unreadReplies = 0;
unreadMentions = 0; unreadMentions = 0;
myUserInfo?: MyUserInfo = undefined;
public unreadInboxCountSubject: BehaviorSubject<number> = public unreadInboxCountSubject: BehaviorSubject<number> =
new BehaviorSubject<number>(0); new BehaviorSubject<number>(0);
@ -36,15 +38,19 @@ export class UnreadCounterService {
} }
} }
public setMyUserInfo(myUserInfo: MyUserInfo) {
this.myUserInfo = myUserInfo;
}
private get shouldUpdate() { private get shouldUpdate() {
if (window.document.visibilityState === "hidden") { if (window.document.visibilityState === "hidden") {
return false; return false;
} } else if (!this.myUserInfo) {
if (!myAuth()) {
return false; return false;
} } else {
return true; return true;
} }
}
public async updateInboxCounts() { public async updateInboxCounts() {
if (this.shouldUpdate) { if (this.shouldUpdate) {
@ -61,7 +67,7 @@ export class UnreadCounterService {
} }
public async updateReports() { public async updateReports() {
if (this.shouldUpdate && UserService.Instance.moderatesSomething) { if (this.shouldUpdate && moderatesSomething(this.myUserInfo)) {
const reportCountRes = await HttpService.client.getReportCount({}); const reportCountRes = await HttpService.client.getReportCount({});
if (reportCountRes.state === "success") { if (reportCountRes.state === "success") {
this.commentReportCount = reportCountRes.data.comment_reports ?? 0; this.commentReportCount = reportCountRes.data.comment_reports ?? 0;
@ -78,7 +84,7 @@ export class UnreadCounterService {
} }
public async updateApplications() { public async updateApplications() {
if (this.shouldUpdate && amAdmin()) { if (this.shouldUpdate && amAdmin(this.myUserInfo)) {
const unreadApplicationsRes = const unreadApplicationsRes =
await HttpService.client.getUnreadRegistrationApplicationCount(); await HttpService.client.getUnreadRegistrationApplicationCount();
if (unreadApplicationsRes.state === "success") { if (unreadApplicationsRes.state === "success") {

View file

@ -1,12 +1,16 @@
import { GetSiteResponse } from "lemmy-js-client"; import { GetSiteResponse } from "lemmy-js-client";
import { setupEmojiDataModel, setupMarkdown } from "../../markdown"; import { setupEmojiDataModel, setupMarkdown } from "../../markdown";
import { I18NextService, UserService } from "../../services"; import { I18NextService } from "../../services";
import { updateDataBsTheme } from "@utils/browser"; import { updateDataBsTheme } from "@utils/browser";
import { LanguageDetector } from "shared/services/I18NextService";
export default function initializeSite(site?: GetSiteResponse) { export default function initializeSite(site?: GetSiteResponse) {
// TODO Should already be in siteRes
UserService.Instance.myUserInfo = site?.my_user;
updateDataBsTheme(site); updateDataBsTheme(site);
// TODO test this
const ld = new LanguageDetector();
ld.setupMyLanguages(site?.my_user);
I18NextService.i18n.changeLanguage(); I18NextService.i18n.changeLanguage();
if (site) { if (site) {
setupEmojiDataModel(site.custom_emojis ?? []); setupEmojiDataModel(site.custom_emojis ?? []);

View file

@ -1,8 +1,12 @@
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { toast } from "../../../shared/toast"; import { toast } from "../../../shared/toast";
import { I18NextService } from "../../services"; import { I18NextService } from "../../services";
import cookie from "cookie";
import { authCookieName } from "../../config";
// TODO get rid of this
export default function myAuth(throwErr = false): string | undefined { export default function myAuth(throwErr = false): string | undefined {
const auth = cookie.parse(document.cookie)[authCookieName];
if (auth) { if (auth) {
return auth; return auth;
} else { } else {

View file

@ -1,13 +1,33 @@
import { configureStore, createSlice } from "@reduxjs/toolkit"; import { PayloadAction, configureStore, createSlice } from "@reduxjs/toolkit";
import { IsoDataOptionalSite } from "../../../shared/interfaces"; import { IsoDataOptionalSite } from "../../../shared/interfaces";
import { GetSiteResponse } from "lemmy-js-client";
// TODO add reducer function here interface isoDataState {
export default function setupRedux(isoData?: IsoDataOptionalSite) { value?: IsoDataOptionalSite;
const slice = createSlice({ }
const initialState: isoDataState = {
value: undefined,
};
export const isoDataSlice = createSlice({
name: "isoData", name: "isoData",
initialState: { value: isoData }, initialState,
reducers: {}, reducers: {
updateIsoData: (state, action: PayloadAction<IsoDataOptionalSite>) => {
state.value = action.payload;
},
updateSite: (state, action: PayloadAction<GetSiteResponse>) => {
state.value!.site_res = action.payload;
},
},
}); });
const store = configureStore({ reducer: slice.reducer });
export const { updateIsoData, updateSite } = isoDataSlice.actions;
export default function setupRedux(isoData: IsoDataOptionalSite) {
const store = configureStore({ reducer: isoDataSlice.reducer });
store.dispatch(updateIsoData(isoData));
return store; return store;
} }