mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 20:31:13 +00:00
feat: Accomodate 2FA changes in UI
* feat: Add modal for totp settings * Make inputs show up on totp modal * Make modal work when enabling and disabling 2FA * Give user better feedback when en/disabling totp * Use new 2FA flow for login * Refactor 2fa modal to prevent implementation details from leaking * chore: Use constant objects where appropriate * Incorporate translations
This commit is contained in:
parent
0c18224cf2
commit
c6d6107ddc
29 changed files with 773 additions and 278 deletions
|
@ -42,6 +42,7 @@
|
||||||
"@babel/preset-typescript": "^7.21.5",
|
"@babel/preset-typescript": "^7.21.5",
|
||||||
"@babel/runtime": "^7.21.5",
|
"@babel/runtime": "^7.21.5",
|
||||||
"@emoji-mart/data": "^1.1.0",
|
"@emoji-mart/data": "^1.1.0",
|
||||||
|
"@shortcm/qr-image": "^9.0.2",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"babel-loader": "^9.1.2",
|
"babel-loader": "^9.1.2",
|
||||||
"babel-plugin-inferno": "^6.6.0",
|
"babel-plugin-inferno": "^6.6.0",
|
||||||
|
@ -69,7 +70,7 @@
|
||||||
"inferno-router": "^8.2.2",
|
"inferno-router": "^8.2.2",
|
||||||
"inferno-server": "^8.2.2",
|
"inferno-server": "^8.2.2",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lemmy-js-client": "^0.19.0-rc.12",
|
"lemmy-js-client": "^0.19.0-rc.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"markdown-it-container": "^3.0.0",
|
"markdown-it-container": "^3.0.0",
|
||||||
|
|
|
@ -448,3 +448,7 @@ br.big {
|
||||||
.skip-link:focus {
|
.skip-link:focus {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.totp-link {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { I18NextService } from "../../services";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
import { PersonListing } from "../person/person-listing";
|
import { PersonListing } from "../person/person-listing";
|
||||||
import { CommentNode } from "./comment-node";
|
import { CommentNode } from "./comment-node";
|
||||||
|
import { EMPTY_REQUEST } from "../../services/HttpService";
|
||||||
|
|
||||||
interface CommentReportProps {
|
interface CommentReportProps {
|
||||||
report: CommentReportView;
|
report: CommentReportView;
|
||||||
|
@ -97,8 +98,8 @@ export class CommentReport extends Component<
|
||||||
onPersonMentionRead={() => {}}
|
onPersonMentionRead={() => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={() => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={() => {}}
|
||||||
onCreateComment={() => Promise.resolve({ state: "empty" })}
|
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
onEditComment={() => Promise.resolve({ state: "empty" })}
|
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
{I18NextService.i18n.t("reporter")}:{" "}
|
{I18NextService.i18n.t("reporter")}:{" "}
|
||||||
|
|
268
src/shared/components/common/totp-modal.tsx
Normal file
268
src/shared/components/common/totp-modal.tsx
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
MouseEventHandler,
|
||||||
|
RefObject,
|
||||||
|
createRef,
|
||||||
|
linkEvent,
|
||||||
|
} from "inferno";
|
||||||
|
import { I18NextService } from "../../services";
|
||||||
|
import { toast } from "../../toast";
|
||||||
|
import type { Modal } from "bootstrap";
|
||||||
|
|
||||||
|
interface TotpModalProps {
|
||||||
|
/**Takes totp as param, returns whether submit was successful*/
|
||||||
|
onSubmit: (totp: string) => Promise<boolean>;
|
||||||
|
onClose: MouseEventHandler;
|
||||||
|
type: "login" | "remove" | "generate";
|
||||||
|
secretUrl?: string;
|
||||||
|
show?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TotpModalState {
|
||||||
|
totp: string;
|
||||||
|
qrCode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOTP_LENGTH = 6;
|
||||||
|
|
||||||
|
async function handleSubmit(modal: TotpModal, totp: string) {
|
||||||
|
const successful = await modal.props.onSubmit(totp);
|
||||||
|
|
||||||
|
if (!successful) {
|
||||||
|
modal.setState({ totp: "" });
|
||||||
|
modal.inputRefs[0]?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInput(
|
||||||
|
{ modal, i }: { modal: TotpModal; i: number },
|
||||||
|
event: any,
|
||||||
|
) {
|
||||||
|
if (isNaN(event.target.value)) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.setState(prev => ({ ...prev, totp: prev.totp + event.target.value }));
|
||||||
|
modal.inputRefs[i + 1]?.focus();
|
||||||
|
|
||||||
|
const { totp } = modal.state;
|
||||||
|
if (totp.length >= TOTP_LENGTH) {
|
||||||
|
handleSubmit(modal, totp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyUp(
|
||||||
|
{ modal, i }: { modal: TotpModal; i: number },
|
||||||
|
event: any,
|
||||||
|
) {
|
||||||
|
if (event.key === "Backspace" && i > 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
modal.setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
totp: prev.totp.slice(0, prev.totp.length - 1),
|
||||||
|
}));
|
||||||
|
modal.inputRefs[i - 1]?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePaste(modal: TotpModal, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
const text: string = event.clipboardData.getData("text");
|
||||||
|
|
||||||
|
if (text.length > TOTP_LENGTH || isNaN(Number(text))) {
|
||||||
|
toast(I18NextService.i18n.t("invalid_totp_code"), "danger");
|
||||||
|
modal.setState({ totp: "" });
|
||||||
|
} else {
|
||||||
|
modal.setState({ totp: text });
|
||||||
|
handleSubmit(modal, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TotpModal extends Component<
|
||||||
|
TotpModalProps,
|
||||||
|
TotpModalState
|
||||||
|
> {
|
||||||
|
private readonly modalDivRef: RefObject<HTMLDivElement>;
|
||||||
|
inputRefs: (HTMLInputElement | null)[] = [];
|
||||||
|
modal: Modal;
|
||||||
|
state: TotpModalState = {
|
||||||
|
totp: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: TotpModalProps, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.modalDivRef = createRef();
|
||||||
|
|
||||||
|
this.clearTotp = this.clearTotp.bind(this);
|
||||||
|
this.handleShow = this.handleShow.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
this.modalDivRef.current?.addEventListener(
|
||||||
|
"shown.bs.modal",
|
||||||
|
this.handleShow,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.modalDivRef.current?.addEventListener(
|
||||||
|
"hidden.bs.modal",
|
||||||
|
this.clearTotp,
|
||||||
|
);
|
||||||
|
|
||||||
|
const Modal = (await import("bootstrap/js/dist/modal")).default;
|
||||||
|
this.modal = new Modal(this.modalDivRef.current!);
|
||||||
|
|
||||||
|
if (this.props.show) {
|
||||||
|
this.modal.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.modalDivRef.current?.removeEventListener(
|
||||||
|
"shown.bs.modal",
|
||||||
|
this.handleShow,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.modalDivRef.current?.removeEventListener(
|
||||||
|
"hidden.bs.modal",
|
||||||
|
this.clearTotp,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.modal.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate({ show: prevShow }: TotpModalProps) {
|
||||||
|
if (!!prevShow !== !!this.props.show) {
|
||||||
|
if (this.props.show) {
|
||||||
|
this.modal.show();
|
||||||
|
} else {
|
||||||
|
this.modal.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { type, secretUrl, onClose } = this.props;
|
||||||
|
const { totp } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="modal fade"
|
||||||
|
id="totpModal"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-hidden
|
||||||
|
aria-labelledby="#totpModalTitle"
|
||||||
|
data-bs-backdrop="static"
|
||||||
|
ref={this.modalDivRef}
|
||||||
|
>
|
||||||
|
<div className="modal-dialog modal-fullscreen-sm-down">
|
||||||
|
<div className="modal-content">
|
||||||
|
<header className="modal-header">
|
||||||
|
<h3 className="modal-title" id="totpModalTitle">
|
||||||
|
{I18NextService.i18n.t(
|
||||||
|
type === "generate"
|
||||||
|
? "enable_totp"
|
||||||
|
: type === "remove"
|
||||||
|
? "disable_totp"
|
||||||
|
: "enter_totp_code",
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-close"
|
||||||
|
aria-label="Close"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<div className="modal-body d-flex flex-column align-items-center justify-content-center">
|
||||||
|
{type === "generate" && (
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
className="btn btn-secondary mx-auto d-block totp-link"
|
||||||
|
href={secretUrl}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("totp_link")}
|
||||||
|
</a>
|
||||||
|
<div className="mx-auto mt-3 w-50 h-50 text-center">
|
||||||
|
<strong className="fw-semibold">
|
||||||
|
{I18NextService.i18n.t("totp_qr_segue")}
|
||||||
|
</strong>
|
||||||
|
<img
|
||||||
|
src={this.state.qrCode}
|
||||||
|
className="d-block mt-1 mx-auto"
|
||||||
|
alt={I18NextService.i18n.t("totp_qr")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<form id="totp-form">
|
||||||
|
<label
|
||||||
|
className="form-label ms-2 mt-4 fw-bold"
|
||||||
|
id="totp-input-label"
|
||||||
|
htmlFor="totp-input-0"
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("enter_totp_code")}
|
||||||
|
</label>
|
||||||
|
<div className="d-flex justify-content-between align-items-center p-2">
|
||||||
|
{Array.from(Array(TOTP_LENGTH).keys()).map(i => (
|
||||||
|
<input
|
||||||
|
key={
|
||||||
|
i /*While using indices as keys is usually bad practice, in this case we don't have to worry about the order of the list items changing.*/
|
||||||
|
}
|
||||||
|
type="text"
|
||||||
|
inputMode="numeric"
|
||||||
|
autoComplete="one-time-code"
|
||||||
|
maxLength={1}
|
||||||
|
value={totp[i] ?? ""}
|
||||||
|
disabled={totp.length !== i}
|
||||||
|
aria-labelledby="totp-input-label"
|
||||||
|
id={`totp-input-${i}`}
|
||||||
|
className="form-control form-control-lg mx-2 p-1 p-md-2 text-center"
|
||||||
|
onInput={linkEvent({ modal: this, i }, handleInput)}
|
||||||
|
onKeyUp={linkEvent({ modal: this, i }, handleKeyUp)}
|
||||||
|
onPaste={linkEvent(this, handlePaste)}
|
||||||
|
ref={element => {
|
||||||
|
this.inputRefs[i] = element;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<footer className="modal-footer">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("cancel")}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTotp() {
|
||||||
|
this.setState({ totp: "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleShow() {
|
||||||
|
this.inputRefs[0]?.focus();
|
||||||
|
|
||||||
|
if (this.props.type === "generate") {
|
||||||
|
const { getSVG } = await import("@shortcm/qr-image/lib/svg");
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
qrCode: URL.createObjectURL(
|
||||||
|
new Blob([(await getSVG(this.props.secretUrl!)).buffer], {
|
||||||
|
type: "image/svg+xml",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,12 @@ import {
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
import { ListingTypeSelect } from "../common/listing-type-select";
|
import { ListingTypeSelect } from "../common/listing-type-select";
|
||||||
|
@ -64,7 +69,7 @@ function getCommunitiesQueryParams() {
|
||||||
export class Communities extends Component<any, CommunitiesState> {
|
export class Communities extends Component<any, CommunitiesState> {
|
||||||
private isoData = setIsoData<CommunitiesData>(this.context);
|
private isoData = setIsoData<CommunitiesData>(this.context);
|
||||||
state: CommunitiesState = {
|
state: CommunitiesState = {
|
||||||
listCommunitiesResponse: { state: "empty" },
|
listCommunitiesResponse: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
searchText: "",
|
searchText: "",
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
|
@ -333,7 +338,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
async refetch() {
|
||||||
this.setState({ listCommunitiesResponse: { state: "loading" } });
|
this.setState({ listCommunitiesResponse: LOADING_REQUEST });
|
||||||
|
|
||||||
const { listingType, sort, page } = getCommunitiesQueryParams();
|
const { listingType, sort, page } = getCommunitiesQueryParams();
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,12 @@ import {
|
||||||
InitialFetchRequest,
|
InitialFetchRequest,
|
||||||
} from "../../interfaces";
|
} from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { CommentNodes } from "../comment/comment-nodes";
|
import { CommentNodes } from "../comment/comment-nodes";
|
||||||
|
@ -146,9 +151,9 @@ export class Community extends Component<
|
||||||
> {
|
> {
|
||||||
private isoData = setIsoData<CommunityData>(this.context);
|
private isoData = setIsoData<CommunityData>(this.context);
|
||||||
state: State = {
|
state: State = {
|
||||||
communityRes: { state: "empty" },
|
communityRes: EMPTY_REQUEST,
|
||||||
postsRes: { state: "empty" },
|
postsRes: EMPTY_REQUEST,
|
||||||
commentsRes: { state: "empty" },
|
commentsRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
showSidebarMobile: false,
|
showSidebarMobile: false,
|
||||||
finished: new Map(),
|
finished: new Map(),
|
||||||
|
@ -212,7 +217,7 @@ export class Community extends Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchCommunity() {
|
async fetchCommunity() {
|
||||||
this.setState({ communityRes: { state: "loading" } });
|
this.setState({ communityRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
communityRes: await HttpService.client.getCommunity({
|
communityRes: await HttpService.client.getCommunity({
|
||||||
name: this.props.match.params.name,
|
name: this.props.match.params.name,
|
||||||
|
@ -248,10 +253,8 @@ export class Community extends Component<
|
||||||
|
|
||||||
const page = getPageFromString(urlPage);
|
const page = getPageFromString(urlPage);
|
||||||
|
|
||||||
let postsResponse: RequestState<GetPostsResponse> = { state: "empty" };
|
let postsResponse: RequestState<GetPostsResponse> = EMPTY_REQUEST;
|
||||||
let commentsResponse: RequestState<GetCommentsResponse> = {
|
let commentsResponse: RequestState<GetCommentsResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
const getPostsForm: GetPosts = {
|
const getPostsForm: GetPosts = {
|
||||||
|
@ -585,7 +588,7 @@ export class Community extends Component<
|
||||||
const { name } = this.props.match.params;
|
const { name } = this.props.match.params;
|
||||||
|
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
this.setState({ postsRes: { state: "loading" } });
|
this.setState({ postsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
postsRes: await HttpService.client.getPosts({
|
postsRes: await HttpService.client.getPosts({
|
||||||
page,
|
page,
|
||||||
|
@ -597,7 +600,7 @@ export class Community extends Component<
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ commentsRes: { state: "loading" } });
|
this.setState({ commentsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
commentsRes: await HttpService.client.getComments({
|
commentsRes: await HttpService.client.getComments({
|
||||||
page,
|
page,
|
||||||
|
|
|
@ -16,7 +16,12 @@ import {
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
|
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
@ -50,9 +55,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
banned: [],
|
banned: [],
|
||||||
currentTab: "site",
|
currentTab: "site",
|
||||||
bannedRes: { state: "empty" },
|
bannedRes: EMPTY_REQUEST,
|
||||||
instancesRes: { state: "empty" },
|
instancesRes: EMPTY_REQUEST,
|
||||||
leaveAdminTeamRes: { state: "empty" },
|
leaveAdminTeamRes: EMPTY_REQUEST,
|
||||||
loading: false,
|
loading: false,
|
||||||
themeList: [],
|
themeList: [],
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
|
@ -231,8 +236,8 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
|
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
this.setState({
|
this.setState({
|
||||||
bannedRes: { state: "loading" },
|
bannedRes: LOADING_REQUEST,
|
||||||
instancesRes: { state: "loading" },
|
instancesRes: LOADING_REQUEST,
|
||||||
themeList: [],
|
themeList: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -333,7 +338,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleLeaveAdminTeam(i: AdminSettings) {
|
async handleLeaveAdminTeam(i: AdminSettings) {
|
||||||
i.setState({ leaveAdminTeamRes: { state: "loading" } });
|
i.setState({ leaveAdminTeamRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
leaveAdminTeamRes: await HttpService.client.leaveAdmin(),
|
leaveAdminTeamRes: await HttpService.client.leaveAdmin(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -85,7 +85,12 @@ import {
|
||||||
I18NextService,
|
I18NextService,
|
||||||
UserService,
|
UserService,
|
||||||
} from "../../services";
|
} from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { CommentNodes } from "../comment/comment-nodes";
|
import { CommentNodes } from "../comment/comment-nodes";
|
||||||
|
@ -221,9 +226,9 @@ const LinkButton = ({
|
||||||
export class Home extends Component<any, HomeState> {
|
export class Home extends Component<any, HomeState> {
|
||||||
private isoData = setIsoData<HomeData>(this.context);
|
private isoData = setIsoData<HomeData>(this.context);
|
||||||
state: HomeState = {
|
state: HomeState = {
|
||||||
postsRes: { state: "empty" },
|
postsRes: EMPTY_REQUEST,
|
||||||
commentsRes: { state: "empty" },
|
commentsRes: EMPTY_REQUEST,
|
||||||
trendingCommunitiesRes: { state: "empty" },
|
trendingCommunitiesRes: EMPTY_REQUEST,
|
||||||
scrolled: true,
|
scrolled: true,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
showSubscribedMobile: false,
|
showSubscribedMobile: false,
|
||||||
|
@ -321,10 +326,8 @@ export class Home extends Component<any, HomeState> {
|
||||||
|
|
||||||
const page = urlPage ? Number(urlPage) : 1;
|
const page = urlPage ? Number(urlPage) : 1;
|
||||||
|
|
||||||
let postsRes: RequestState<GetPostsResponse> = { state: "empty" };
|
let postsRes: RequestState<GetPostsResponse> = EMPTY_REQUEST;
|
||||||
let commentsRes: RequestState<GetCommentsResponse> = {
|
let commentsRes: RequestState<GetCommentsResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dataType === DataType.Post) {
|
if (dataType === DataType.Post) {
|
||||||
const getPostsForm: GetPosts = {
|
const getPostsForm: GetPosts = {
|
||||||
|
@ -790,7 +793,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchTrendingCommunities() {
|
async fetchTrendingCommunities() {
|
||||||
this.setState({ trendingCommunitiesRes: { state: "loading" } });
|
this.setState({ trendingCommunitiesRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
trendingCommunitiesRes: await HttpService.client.listCommunities({
|
trendingCommunitiesRes: await HttpService.client.listCommunities({
|
||||||
type_: "Local",
|
type_: "Local",
|
||||||
|
@ -814,7 +817,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
behavior: "instant",
|
behavior: "instant",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ postsRes: { state: "loading" } });
|
this.setState({ postsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
postsRes: await HttpService.client.getPosts({
|
postsRes: await HttpService.client.getPosts({
|
||||||
page,
|
page,
|
||||||
|
@ -828,7 +831,7 @@ export class Home extends Component<any, HomeState> {
|
||||||
HomeCacheService.postsRes = this.state.postsRes;
|
HomeCacheService.postsRes = this.state.postsRes;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setState({ commentsRes: { state: "loading" } });
|
this.setState({ commentsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
commentsRes: await HttpService.client.getComments({
|
commentsRes: await HttpService.client.getComments({
|
||||||
page,
|
page,
|
||||||
|
|
|
@ -10,7 +10,12 @@ import classNames from "classnames";
|
||||||
import { relTags } from "../../config";
|
import { relTags } from "../../config";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
import Tabs from "../common/tabs";
|
import Tabs from "../common/tabs";
|
||||||
|
@ -28,7 +33,7 @@ interface InstancesState {
|
||||||
export class Instances extends Component<any, InstancesState> {
|
export class Instances extends Component<any, InstancesState> {
|
||||||
private isoData = setIsoData<InstancesData>(this.context);
|
private isoData = setIsoData<InstancesData>(this.context);
|
||||||
state: InstancesState = {
|
state: InstancesState = {
|
||||||
instancesRes: { state: "empty" },
|
instancesRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
@ -54,7 +59,7 @@ export class Instances extends Component<any, InstancesState> {
|
||||||
|
|
||||||
async fetchInstances() {
|
async fetchInstances() {
|
||||||
this.setState({
|
this.setState({
|
||||||
instancesRes: { state: "loading" },
|
instancesRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -5,11 +5,17 @@ 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, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
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 TotpModal from "../common/totp-modal";
|
||||||
|
|
||||||
interface LoginProps {
|
interface LoginProps {
|
||||||
prev?: string;
|
prev?: string;
|
||||||
|
@ -25,42 +31,16 @@ const getLoginQueryParams = () =>
|
||||||
interface State {
|
interface State {
|
||||||
loginRes: RequestState<LoginResponse>;
|
loginRes: RequestState<LoginResponse>;
|
||||||
form: {
|
form: {
|
||||||
username_or_email?: string;
|
username_or_email: string;
|
||||||
password?: string;
|
password: string;
|
||||||
totp_2fa_token?: string;
|
|
||||||
};
|
};
|
||||||
showTotp: boolean;
|
|
||||||
siteRes: GetSiteResponse;
|
siteRes: GetSiteResponse;
|
||||||
|
show2faModal: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleLoginSubmit(i: Login, event: any) {
|
async function handleLoginSuccess(i: Login, loginRes: LoginResponse) {
|
||||||
event.preventDefault();
|
|
||||||
const { password, totp_2fa_token, username_or_email } = i.state.form;
|
|
||||||
|
|
||||||
if (username_or_email && password) {
|
|
||||||
i.setState({ loginRes: { state: "loading" } });
|
|
||||||
|
|
||||||
const loginRes = await HttpService.client.login({
|
|
||||||
username_or_email,
|
|
||||||
password,
|
|
||||||
totp_2fa_token,
|
|
||||||
});
|
|
||||||
switch (loginRes.state) {
|
|
||||||
case "failed": {
|
|
||||||
if (loginRes.msg === "missing_totp_token") {
|
|
||||||
i.setState({ showTotp: true });
|
|
||||||
toast(I18NextService.i18n.t("enter_two_factor_code"), "info");
|
|
||||||
} else {
|
|
||||||
toast(I18NextService.i18n.t(loginRes.msg), "danger");
|
|
||||||
}
|
|
||||||
|
|
||||||
i.setState({ loginRes: { state: "failed", msg: loginRes.msg } });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "success": {
|
|
||||||
UserService.Instance.login({
|
UserService.Instance.login({
|
||||||
res: loginRes.data,
|
res: loginRes,
|
||||||
});
|
});
|
||||||
const site = await HttpService.client.getSite();
|
const site = await HttpService.client.getSite();
|
||||||
|
|
||||||
|
@ -75,7 +55,33 @@ async function handleLoginSubmit(i: Login, event: any) {
|
||||||
: i.props.history.action === "PUSH"
|
: i.props.history.action === "PUSH"
|
||||||
? i.props.history.back()
|
? i.props.history.back()
|
||||||
: i.props.history.replace("/");
|
: i.props.history.replace("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLoginSubmit(i: Login, event: any) {
|
||||||
|
event.preventDefault();
|
||||||
|
const { password, username_or_email } = i.state.form;
|
||||||
|
|
||||||
|
if (username_or_email && password) {
|
||||||
|
i.setState({ loginRes: LOADING_REQUEST });
|
||||||
|
|
||||||
|
const loginRes = await HttpService.client.login({
|
||||||
|
username_or_email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
switch (loginRes.state) {
|
||||||
|
case "failed": {
|
||||||
|
if (loginRes.msg === "missing_totp_token") {
|
||||||
|
i.setState({ show2faModal: true });
|
||||||
|
} else {
|
||||||
|
toast(I18NextService.i18n.t(loginRes.msg), "danger");
|
||||||
|
}
|
||||||
|
|
||||||
|
i.setState({ loginRes });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "success": {
|
||||||
|
handleLoginSuccess(i, loginRes.data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,14 +94,14 @@ function handleLoginUsernameChange(i: Login, event: any) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLoginTotpChange(i: Login, event: any) {
|
|
||||||
i.setState(prevState => (prevState.form.totp_2fa_token = event.target.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLoginPasswordChange(i: Login, event: any) {
|
function handleLoginPasswordChange(i: Login, event: any) {
|
||||||
i.setState(prevState => (prevState.form.password = event.target.value));
|
i.setState(prevState => (prevState.form.password = event.target.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleClose2faModal(i: Login) {
|
||||||
|
i.setState({ show2faModal: false });
|
||||||
|
}
|
||||||
|
|
||||||
export class Login extends Component<
|
export class Login extends Component<
|
||||||
RouteComponentProps<Record<string, never>>,
|
RouteComponentProps<Record<string, never>>,
|
||||||
State
|
State
|
||||||
|
@ -103,14 +109,19 @@ export class Login extends Component<
|
||||||
private isoData = setIsoData(this.context);
|
private isoData = setIsoData(this.context);
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
loginRes: { state: "empty" },
|
loginRes: EMPTY_REQUEST,
|
||||||
form: {},
|
form: {
|
||||||
showTotp: false,
|
username_or_email: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
|
show2faModal: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this.handleSubmitTotp = this.handleSubmitTotp.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -137,6 +148,12 @@ export class Login extends Component<
|
||||||
title={this.documentTitle}
|
title={this.documentTitle}
|
||||||
path={this.context.router.route.match.url}
|
path={this.context.router.route.match.url}
|
||||||
/>
|
/>
|
||||||
|
<TotpModal
|
||||||
|
type="login"
|
||||||
|
onSubmit={this.handleSubmitTotp}
|
||||||
|
show={this.state.show2faModal}
|
||||||
|
onClose={linkEvent(this, handleClose2faModal)}
|
||||||
|
/>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</div>
|
<div className="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -144,6 +161,24 @@ export class Login extends Component<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleSubmitTotp(totp: string) {
|
||||||
|
const loginRes = await HttpService.client.login({
|
||||||
|
password: this.state.form.password,
|
||||||
|
username_or_email: this.state.form.username_or_email,
|
||||||
|
totp_2fa_token: totp,
|
||||||
|
});
|
||||||
|
|
||||||
|
const successful = loginRes.state === "success";
|
||||||
|
if (successful) {
|
||||||
|
this.setState({ show2faModal: false });
|
||||||
|
handleLoginSuccess(this, loginRes.data);
|
||||||
|
} else {
|
||||||
|
toast(I18NextService.i18n.t("incorrect_totp_code"), "danger");
|
||||||
|
}
|
||||||
|
|
||||||
|
return successful;
|
||||||
|
}
|
||||||
|
|
||||||
loginForm() {
|
loginForm() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -178,28 +213,6 @@ export class Login extends Component<
|
||||||
showForgotLink
|
showForgotLink
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.state.showTotp && (
|
|
||||||
<div className="mb-3 row">
|
|
||||||
<label
|
|
||||||
className="col-sm-6 col-form-label"
|
|
||||||
htmlFor="login-totp-token"
|
|
||||||
>
|
|
||||||
{I18NextService.i18n.t("two_factor_token")}
|
|
||||||
</label>
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
inputMode="numeric"
|
|
||||||
className="form-control"
|
|
||||||
id="login-totp-token"
|
|
||||||
pattern="[0-9]*"
|
|
||||||
autoComplete="one-time-code"
|
|
||||||
value={this.state.form.totp_2fa_token}
|
|
||||||
onInput={linkEvent(this, handleLoginTotpChange)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="mb-3 row">
|
<div className="mb-3 row">
|
||||||
<div className="col-sm-10">
|
<div className="col-sm-10">
|
||||||
<button type="submit" className="btn btn-secondary">
|
<button type="submit" className="btn btn-secondary">
|
||||||
|
|
|
@ -8,7 +8,12 @@ import {
|
||||||
Register,
|
Register,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
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";
|
||||||
|
@ -35,7 +40,7 @@ export class Setup extends Component<any, State> {
|
||||||
private isoData = setIsoData(this.context);
|
private isoData = setIsoData(this.context);
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
registerRes: { state: "empty" },
|
registerRes: EMPTY_REQUEST,
|
||||||
themeList: [],
|
themeList: [],
|
||||||
form: {
|
form: {
|
||||||
show_nsfw: true,
|
show_nsfw: true,
|
||||||
|
@ -157,7 +162,7 @@ export class Setup extends Component<any, State> {
|
||||||
|
|
||||||
async handleRegisterSubmit(i: Setup, event: any) {
|
async handleRegisterSubmit(i: Setup, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.setState({ registerRes: { state: "loading" } });
|
i.setState({ registerRes: LOADING_REQUEST });
|
||||||
const {
|
const {
|
||||||
username,
|
username,
|
||||||
password_verify,
|
password_verify,
|
||||||
|
|
|
@ -13,7 +13,12 @@ import {
|
||||||
import { joinLemmyUrl } from "../../config";
|
import { joinLemmyUrl } from "../../config";
|
||||||
import { mdToHtml } from "../../markdown";
|
import { mdToHtml } from "../../markdown";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
|
@ -43,8 +48,8 @@ export class Signup extends Component<any, State> {
|
||||||
private audio?: HTMLAudioElement;
|
private audio?: HTMLAudioElement;
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
registerRes: { state: "empty" },
|
registerRes: EMPTY_REQUEST,
|
||||||
captchaRes: { state: "empty" },
|
captchaRes: EMPTY_REQUEST,
|
||||||
form: {
|
form: {
|
||||||
show_nsfw: false,
|
show_nsfw: false,
|
||||||
},
|
},
|
||||||
|
@ -65,7 +70,7 @@ export class Signup extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchCaptcha() {
|
async fetchCaptcha() {
|
||||||
this.setState({ captchaRes: { state: "loading" } });
|
this.setState({ captchaRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
captchaRes: await HttpService.client.getCaptcha(),
|
captchaRes: await HttpService.client.getCaptcha(),
|
||||||
});
|
});
|
||||||
|
@ -368,7 +373,7 @@ export class Signup extends Component<any, State> {
|
||||||
username,
|
username,
|
||||||
} = i.state.form;
|
} = i.state.form;
|
||||||
if (username && password && password_verify) {
|
if (username && password && password_verify) {
|
||||||
i.setState({ registerRes: { state: "loading" } });
|
i.setState({ registerRes: LOADING_REQUEST });
|
||||||
|
|
||||||
const registerRes = await HttpService.client.register({
|
const registerRes = await HttpService.client.register({
|
||||||
username,
|
username,
|
||||||
|
@ -384,7 +389,7 @@ export class Signup extends Component<any, State> {
|
||||||
switch (registerRes.state) {
|
switch (registerRes.state) {
|
||||||
case "failed": {
|
case "failed": {
|
||||||
toast(registerRes.msg, "danger");
|
toast(registerRes.msg, "danger");
|
||||||
i.setState({ registerRes: { state: "empty" } });
|
i.setState({ registerRes: EMPTY_REQUEST });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,12 @@ import {
|
||||||
import { fetchLimit } from "../config";
|
import { fetchLimit } from "../config";
|
||||||
import { InitialFetchRequest } from "../interfaces";
|
import { InitialFetchRequest } from "../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../services";
|
import { FirstLoadService, I18NextService } from "../services";
|
||||||
import { HttpService, RequestState } from "../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../services/HttpService";
|
||||||
import { HtmlTags } from "./common/html-tags";
|
import { HtmlTags } from "./common/html-tags";
|
||||||
import { Icon, Spinner } from "./common/icon";
|
import { Icon, Spinner } from "./common/icon";
|
||||||
import { MomentTime } from "./common/moment-time";
|
import { MomentTime } from "./common/moment-time";
|
||||||
|
@ -647,8 +652,8 @@ export class Modlog extends Component<
|
||||||
private isoData = setIsoData<ModlogData>(this.context);
|
private isoData = setIsoData<ModlogData>(this.context);
|
||||||
|
|
||||||
state: ModlogState = {
|
state: ModlogState = {
|
||||||
res: { state: "empty" },
|
res: EMPTY_REQUEST,
|
||||||
communityRes: { state: "empty" },
|
communityRes: EMPTY_REQUEST,
|
||||||
loadingModSearch: false,
|
loadingModSearch: false,
|
||||||
loadingUserSearch: false,
|
loadingUserSearch: false,
|
||||||
userSearchOptions: [],
|
userSearchOptions: [],
|
||||||
|
@ -954,7 +959,7 @@ export class Modlog extends Component<
|
||||||
const { communityId: urlCommunityId } = this.props.match.params;
|
const { communityId: urlCommunityId } = this.props.match.params;
|
||||||
const communityId = getIdFromString(urlCommunityId);
|
const communityId = getIdFromString(urlCommunityId);
|
||||||
|
|
||||||
this.setState({ res: { state: "loading" } });
|
this.setState({ res: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
res: await HttpService.client.getModlog({
|
res: await HttpService.client.getModlog({
|
||||||
community_id: communityId,
|
community_id: communityId,
|
||||||
|
@ -970,7 +975,7 @@ export class Modlog extends Component<
|
||||||
});
|
});
|
||||||
|
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
this.setState({ communityRes: { state: "loading" } });
|
this.setState({ communityRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
communityRes: await HttpService.client.getCommunity({
|
communityRes: await HttpService.client.getCommunity({
|
||||||
id: communityId,
|
id: communityId,
|
||||||
|
@ -1001,9 +1006,7 @@ export class Modlog extends Component<
|
||||||
other_person_id: userId,
|
other_person_id: userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
let communityResponse: RequestState<GetCommunityResponse> = {
|
let communityResponse: RequestState<GetCommunityResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
const communityForm: GetCommunity = {
|
const communityForm: GetCommunity = {
|
||||||
|
@ -1013,9 +1016,7 @@ export class Modlog extends Component<
|
||||||
communityResponse = await client.getCommunity(communityForm);
|
communityResponse = await client.getCommunity(communityForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
let modUserResponse: RequestState<GetPersonDetailsResponse> = {
|
let modUserResponse: RequestState<GetPersonDetailsResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (modId) {
|
if (modId) {
|
||||||
const getPersonForm: GetPersonDetails = {
|
const getPersonForm: GetPersonDetails = {
|
||||||
|
@ -1025,9 +1026,7 @@ export class Modlog extends Component<
|
||||||
modUserResponse = await client.getPersonDetails(getPersonForm);
|
modUserResponse = await client.getPersonDetails(getPersonForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
let userResponse: RequestState<GetPersonDetailsResponse> = {
|
let userResponse: RequestState<GetPersonDetailsResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const getPersonForm: GetPersonDetails = {
|
const getPersonForm: GetPersonDetails = {
|
||||||
|
|
|
@ -64,8 +64,10 @@ import { CommentViewType, InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { UnreadCounterService } from "../../services";
|
import { UnreadCounterService } from "../../services";
|
||||||
import {
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
EmptyRequestState,
|
EmptyRequestState,
|
||||||
HttpService,
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
RequestState,
|
RequestState,
|
||||||
} from "../../services/HttpService";
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
|
@ -129,10 +131,10 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
sort: "New",
|
sort: "New",
|
||||||
page: 1,
|
page: 1,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
repliesRes: { state: "empty" },
|
repliesRes: EMPTY_REQUEST,
|
||||||
mentionsRes: { state: "empty" },
|
mentionsRes: EMPTY_REQUEST,
|
||||||
messagesRes: { state: "empty" },
|
messagesRes: EMPTY_REQUEST,
|
||||||
markAllAsReadRes: { state: "empty" },
|
markAllAsReadRes: EMPTY_REQUEST,
|
||||||
finished: new Map(),
|
finished: new Map(),
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
@ -725,7 +727,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
client,
|
client,
|
||||||
}: InitialFetchRequest): Promise<InboxData> {
|
}: InitialFetchRequest): Promise<InboxData> {
|
||||||
const sort: CommentSortType = "New";
|
const sort: CommentSortType = "New";
|
||||||
const empty: EmptyRequestState = { state: "empty" };
|
const empty: EmptyRequestState = EMPTY_REQUEST;
|
||||||
let inboxData: InboxData = {
|
let inboxData: InboxData = {
|
||||||
mentionsRes: empty,
|
mentionsRes: empty,
|
||||||
messagesRes: empty,
|
messagesRes: empty,
|
||||||
|
@ -765,7 +767,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
const page = this.state.page;
|
const page = this.state.page;
|
||||||
const limit = fetchLimit;
|
const limit = fetchLimit;
|
||||||
|
|
||||||
this.setState({ repliesRes: { state: "loading" } });
|
this.setState({ repliesRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
repliesRes: await HttpService.client.getReplies({
|
repliesRes: await HttpService.client.getReplies({
|
||||||
sort,
|
sort,
|
||||||
|
@ -775,7 +777,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ mentionsRes: { state: "loading" } });
|
this.setState({ mentionsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
mentionsRes: await HttpService.client.getPersonMentions({
|
mentionsRes: await HttpService.client.getPersonMentions({
|
||||||
sort,
|
sort,
|
||||||
|
@ -785,7 +787,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ messagesRes: { state: "loading" } });
|
this.setState({ messagesRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
messagesRes: await HttpService.client.getPrivateMessages({
|
messagesRes: await HttpService.client.getPrivateMessages({
|
||||||
unread_only,
|
unread_only,
|
||||||
|
@ -802,7 +804,7 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleMarkAllAsRead(i: Inbox) {
|
async handleMarkAllAsRead(i: Inbox) {
|
||||||
i.setState({ markAllAsReadRes: { state: "loading" } });
|
i.setState({ markAllAsReadRes: LOADING_REQUEST });
|
||||||
|
|
||||||
i.setState({
|
i.setState({
|
||||||
markAllAsReadRes: await HttpService.client.markAllAsRead(),
|
markAllAsReadRes: await HttpService.client.markAllAsRead(),
|
||||||
|
@ -810,9 +812,9 @@ export class Inbox extends Component<any, InboxState> {
|
||||||
|
|
||||||
if (i.state.markAllAsReadRes.state === "success") {
|
if (i.state.markAllAsReadRes.state === "success") {
|
||||||
i.setState({
|
i.setState({
|
||||||
repliesRes: { state: "empty" },
|
repliesRes: EMPTY_REQUEST,
|
||||||
mentionsRes: { state: "empty" },
|
mentionsRes: EMPTY_REQUEST,
|
||||||
messagesRes: { state: "empty" },
|
messagesRes: EMPTY_REQUEST,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,11 @@ import { capitalizeFirstLetter } from "@utils/helpers";
|
||||||
import { Component, linkEvent } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
|
||||||
import { HttpService, I18NextService, UserService } from "../../services";
|
import { HttpService, I18NextService, UserService } from "../../services";
|
||||||
import { RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
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";
|
||||||
|
@ -22,7 +26,7 @@ export class PasswordChange extends Component<any, State> {
|
||||||
private isoData = setIsoData(this.context);
|
private isoData = setIsoData(this.context);
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
passwordChangeRes: { state: "empty" },
|
passwordChangeRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
form: {
|
form: {
|
||||||
token: this.props.match.params.token,
|
token: this.props.match.params.token,
|
||||||
|
@ -106,7 +110,7 @@ export class PasswordChange extends Component<any, State> {
|
||||||
|
|
||||||
async handlePasswordChangeSubmit(i: PasswordChange, event: any) {
|
async handlePasswordChangeSubmit(i: PasswordChange, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.setState({ passwordChangeRes: { state: "loading" } });
|
i.setState({ passwordChangeRes: LOADING_REQUEST });
|
||||||
|
|
||||||
const password = i.state.form.password;
|
const password = i.state.form.password;
|
||||||
const password_verify = i.state.form.password_verify;
|
const password_verify = i.state.form.password_verify;
|
||||||
|
|
|
@ -76,7 +76,12 @@ import { fetchLimit, relTags } from "../../config";
|
||||||
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
|
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
|
||||||
import { mdToHtml } from "../../markdown";
|
import { mdToHtml } from "../../markdown";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { BannerIconHeader } from "../common/banner-icon-header";
|
import { BannerIconHeader } from "../common/banner-icon-header";
|
||||||
|
@ -171,7 +176,7 @@ export class Profile extends Component<
|
||||||
> {
|
> {
|
||||||
private isoData = setIsoData<ProfileData>(this.context);
|
private isoData = setIsoData<ProfileData>(this.context);
|
||||||
state: ProfileState = {
|
state: ProfileState = {
|
||||||
personRes: { state: "empty" },
|
personRes: EMPTY_REQUEST,
|
||||||
personBlocked: false,
|
personBlocked: false,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
showBanDialog: false,
|
showBanDialog: false,
|
||||||
|
@ -245,7 +250,7 @@ export class Profile extends Component<
|
||||||
async fetchUserData() {
|
async fetchUserData() {
|
||||||
const { page, sort, view } = getProfileQueryParams();
|
const { page, sort, view } = getProfileQueryParams();
|
||||||
|
|
||||||
this.setState({ personRes: { state: "loading" } });
|
this.setState({ personRes: LOADING_REQUEST });
|
||||||
const personRes = await HttpService.client.getPersonDetails({
|
const personRes = await HttpService.client.getPersonDetails({
|
||||||
username: this.props.match.params.username,
|
username: this.props.match.params.username,
|
||||||
sort,
|
sort,
|
||||||
|
|
|
@ -12,7 +12,12 @@ import {
|
||||||
import { fetchLimit } from "../../config";
|
import { fetchLimit } from "../../config";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
@ -42,7 +47,7 @@ export class RegistrationApplications extends Component<
|
||||||
> {
|
> {
|
||||||
private isoData = setIsoData<RegistrationApplicationsData>(this.context);
|
private isoData = setIsoData<RegistrationApplicationsData>(this.context);
|
||||||
state: RegistrationApplicationsState = {
|
state: RegistrationApplicationsState = {
|
||||||
appsRes: { state: "empty" },
|
appsRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
unreadOrAll: UnreadOrAll.Unread,
|
unreadOrAll: UnreadOrAll.Unread,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -210,14 +215,14 @@ export class RegistrationApplications extends Component<
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
})
|
})
|
||||||
: { state: "empty" },
|
: EMPTY_REQUEST,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async refetch() {
|
async refetch() {
|
||||||
const unread_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
const unread_only = this.state.unreadOrAll === UnreadOrAll.Unread;
|
||||||
this.setState({
|
this.setState({
|
||||||
appsRes: { state: "loading" },
|
appsRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
appsRes: await HttpService.client.listRegistrationApplications({
|
appsRes: await HttpService.client.listRegistrationApplications({
|
||||||
|
|
|
@ -35,7 +35,11 @@ import {
|
||||||
I18NextService,
|
I18NextService,
|
||||||
UserService,
|
UserService,
|
||||||
} from "../../services";
|
} from "../../services";
|
||||||
import { RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { CommentReport } from "../comment/comment-report";
|
import { CommentReport } from "../comment/comment-report";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
@ -88,9 +92,9 @@ interface ReportsState {
|
||||||
export class Reports extends Component<any, ReportsState> {
|
export class Reports extends Component<any, ReportsState> {
|
||||||
private isoData = setIsoData<ReportsData>(this.context);
|
private isoData = setIsoData<ReportsData>(this.context);
|
||||||
state: ReportsState = {
|
state: ReportsState = {
|
||||||
commentReportsRes: { state: "empty" },
|
commentReportsRes: EMPTY_REQUEST,
|
||||||
postReportsRes: { state: "empty" },
|
postReportsRes: EMPTY_REQUEST,
|
||||||
messageReportsRes: { state: "empty" },
|
messageReportsRes: EMPTY_REQUEST,
|
||||||
unreadOrAll: UnreadOrAll.Unread,
|
unreadOrAll: UnreadOrAll.Unread,
|
||||||
messageType: MessageType.All,
|
messageType: MessageType.All,
|
||||||
page: 1,
|
page: 1,
|
||||||
|
@ -551,7 +555,7 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
const data: ReportsData = {
|
const data: ReportsData = {
|
||||||
commentReportsRes: await client.listCommentReports(commentReportsForm),
|
commentReportsRes: await client.listCommentReports(commentReportsForm),
|
||||||
postReportsRes: await client.listPostReports(postReportsForm),
|
postReportsRes: await client.listPostReports(postReportsForm),
|
||||||
messageReportsRes: { state: "empty" },
|
messageReportsRes: EMPTY_REQUEST,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (amAdmin()) {
|
if (amAdmin()) {
|
||||||
|
@ -575,9 +579,9 @@ export class Reports extends Component<any, ReportsState> {
|
||||||
const limit = fetchLimit;
|
const limit = fetchLimit;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
commentReportsRes: { state: "loading" },
|
commentReportsRes: LOADING_REQUEST,
|
||||||
postReportsRes: { state: "loading" },
|
postReportsRes: LOADING_REQUEST,
|
||||||
messageReportsRes: { state: "loading" },
|
messageReportsRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
const form:
|
const form:
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
BlockPersonResponse,
|
BlockPersonResponse,
|
||||||
CommunityBlockView,
|
CommunityBlockView,
|
||||||
DeleteAccountResponse,
|
DeleteAccountResponse,
|
||||||
|
GenerateTotpSecretResponse,
|
||||||
GetFederatedInstancesResponse,
|
GetFederatedInstancesResponse,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
Instance,
|
Instance,
|
||||||
|
@ -32,10 +33,16 @@ import {
|
||||||
LoginResponse,
|
LoginResponse,
|
||||||
PersonBlockView,
|
PersonBlockView,
|
||||||
SortType,
|
SortType,
|
||||||
|
UpdateTotpResponse,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { elementUrl, emDash, relTags } from "../../config";
|
import { elementUrl, emDash, relTags } from "../../config";
|
||||||
import { FirstLoadService, UserService } from "../../services";
|
import { FirstLoadService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { I18NextService, languages } from "../../services/I18NextService";
|
import { I18NextService, languages } from "../../services/I18NextService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
|
@ -52,6 +59,7 @@ import Tabs from "../common/tabs";
|
||||||
import { CommunityLink } from "../community/community-link";
|
import { CommunityLink } from "../community/community-link";
|
||||||
import { PersonListing } from "./person-listing";
|
import { PersonListing } from "./person-listing";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
|
import TotpModal from "../common/totp-modal";
|
||||||
|
|
||||||
type SettingsData = RouteDataResponse<{
|
type SettingsData = RouteDataResponse<{
|
||||||
instancesRes: GetFederatedInstancesResponse;
|
instancesRes: GetFederatedInstancesResponse;
|
||||||
|
@ -62,6 +70,8 @@ interface SettingsState {
|
||||||
changePasswordRes: RequestState<LoginResponse>;
|
changePasswordRes: RequestState<LoginResponse>;
|
||||||
deleteAccountRes: RequestState<DeleteAccountResponse>;
|
deleteAccountRes: RequestState<DeleteAccountResponse>;
|
||||||
instancesRes: RequestState<GetFederatedInstancesResponse>;
|
instancesRes: RequestState<GetFederatedInstancesResponse>;
|
||||||
|
generateTotpRes: RequestState<GenerateTotpSecretResponse>;
|
||||||
|
updateTotpRes: RequestState<UpdateTotpResponse>;
|
||||||
// TODO redo these forms
|
// TODO redo these forms
|
||||||
saveUserSettingsForm: {
|
saveUserSettingsForm: {
|
||||||
show_nsfw?: boolean;
|
show_nsfw?: boolean;
|
||||||
|
@ -85,7 +95,6 @@ interface SettingsState {
|
||||||
show_read_posts?: boolean;
|
show_read_posts?: boolean;
|
||||||
show_new_post_notifs?: boolean;
|
show_new_post_notifs?: boolean;
|
||||||
discussion_languages?: number[];
|
discussion_languages?: number[];
|
||||||
generate_totp_2fa?: boolean;
|
|
||||||
open_links_in_new_tab?: boolean;
|
open_links_in_new_tab?: boolean;
|
||||||
};
|
};
|
||||||
changePasswordForm: {
|
changePasswordForm: {
|
||||||
|
@ -109,6 +118,7 @@ interface SettingsState {
|
||||||
searchPersonOptions: Choice[];
|
searchPersonOptions: Choice[];
|
||||||
searchInstanceOptions: Choice[];
|
searchInstanceOptions: Choice[];
|
||||||
isIsomorphic: boolean;
|
isIsomorphic: boolean;
|
||||||
|
show2faModal: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterType = "user" | "community" | "instance";
|
type FilterType = "user" | "community" | "instance";
|
||||||
|
@ -147,13 +157,37 @@ const Filter = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
async function handleGenerateTotp(i: Settings) {
|
||||||
|
i.setState({ generateTotpRes: LOADING_REQUEST });
|
||||||
|
|
||||||
|
const generateTotpRes = await HttpService.client.generateTotpSecret();
|
||||||
|
|
||||||
|
if (generateTotpRes.state === "failed") {
|
||||||
|
toast(generateTotpRes.msg, "danger");
|
||||||
|
} else {
|
||||||
|
i.setState({ show2faModal: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
i.setState({
|
||||||
|
generateTotpRes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShowTotpModal(i: Settings) {
|
||||||
|
i.setState({ show2faModal: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose2faModal(i: Settings) {
|
||||||
|
i.setState({ show2faModal: false });
|
||||||
|
}
|
||||||
|
|
||||||
export class Settings extends Component<any, SettingsState> {
|
export class Settings extends Component<any, SettingsState> {
|
||||||
private isoData = setIsoData<SettingsData>(this.context);
|
private isoData = setIsoData<SettingsData>(this.context);
|
||||||
state: SettingsState = {
|
state: SettingsState = {
|
||||||
saveRes: { state: "empty" },
|
saveRes: EMPTY_REQUEST,
|
||||||
deleteAccountRes: { state: "empty" },
|
deleteAccountRes: EMPTY_REQUEST,
|
||||||
changePasswordRes: { state: "empty" },
|
changePasswordRes: EMPTY_REQUEST,
|
||||||
instancesRes: { state: "empty" },
|
instancesRes: EMPTY_REQUEST,
|
||||||
saveUserSettingsForm: {},
|
saveUserSettingsForm: {},
|
||||||
changePasswordForm: {},
|
changePasswordForm: {},
|
||||||
deleteAccountShowConfirm: false,
|
deleteAccountShowConfirm: false,
|
||||||
|
@ -170,6 +204,9 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
searchPersonOptions: [],
|
searchPersonOptions: [],
|
||||||
searchInstanceOptions: [],
|
searchInstanceOptions: [],
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
|
generateTotpRes: EMPTY_REQUEST,
|
||||||
|
updateTotpRes: EMPTY_REQUEST,
|
||||||
|
show2faModal: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -193,6 +230,10 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
|
this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
|
||||||
this.handleBlockInstance = this.handleBlockInstance.bind(this);
|
this.handleBlockInstance = this.handleBlockInstance.bind(this);
|
||||||
|
|
||||||
|
this.handleToggle2fa = this.handleToggle2fa.bind(this);
|
||||||
|
this.handleEnable2fa = this.handleEnable2fa.bind(this);
|
||||||
|
this.handleDisable2fa = this.handleDisable2fa.bind(this);
|
||||||
|
|
||||||
const mui = UserService.Instance.myUserInfo;
|
const mui = UserService.Instance.myUserInfo;
|
||||||
if (mui) {
|
if (mui) {
|
||||||
const {
|
const {
|
||||||
|
@ -271,7 +312,7 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
|
|
||||||
if (!this.state.isIsomorphic) {
|
if (!this.state.isIsomorphic) {
|
||||||
this.setState({
|
this.setState({
|
||||||
instancesRes: { state: "loading" },
|
instancesRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1001,57 +1042,89 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
totpSection() {
|
totpSection() {
|
||||||
const totpUrl =
|
const totpEnabled =
|
||||||
UserService.Instance.myUserInfo?.local_user_view.local_user.totp_2fa_url;
|
!!UserService.Instance.myUserInfo?.local_user_view.local_user
|
||||||
|
.totp_2fa_enabled;
|
||||||
|
const { generateTotpRes } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!totpUrl && (
|
<button
|
||||||
<div className="input-group mb-3">
|
type="button"
|
||||||
<div className="form-check">
|
className="btn btn-secondary my-2"
|
||||||
<input
|
onClick={linkEvent(
|
||||||
className="form-check-input"
|
this,
|
||||||
id="user-generate-totp"
|
totpEnabled ? handleShowTotpModal : handleGenerateTotp,
|
||||||
type="checkbox"
|
|
||||||
checked={this.state.saveUserSettingsForm.generate_totp_2fa}
|
|
||||||
onChange={linkEvent(this, this.handleGenerateTotp)}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label" htmlFor="user-generate-totp">
|
|
||||||
{I18NextService.i18n.t("set_up_two_factor")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
{totpUrl && (
|
{I18NextService.i18n.t(totpEnabled ? "disable_totp" : "enable_totp")}
|
||||||
<>
|
</button>
|
||||||
<div>
|
{totpEnabled ? (
|
||||||
<a className="btn btn-secondary mb-2" href={totpUrl}>
|
<TotpModal
|
||||||
{I18NextService.i18n.t("two_factor_link")}
|
type="remove"
|
||||||
</a>
|
onSubmit={this.handleDisable2fa}
|
||||||
</div>
|
show={this.state.show2faModal}
|
||||||
<div className="input-group mb-3">
|
onClose={linkEvent(this, handleClose2faModal)}
|
||||||
<div className="form-check">
|
/>
|
||||||
<input
|
) : (
|
||||||
className="form-check-input"
|
<TotpModal
|
||||||
id="user-remove-totp"
|
type="generate"
|
||||||
type="checkbox"
|
onSubmit={this.handleEnable2fa}
|
||||||
checked={
|
secretUrl={
|
||||||
this.state.saveUserSettingsForm.generate_totp_2fa === false
|
generateTotpRes.state === "success"
|
||||||
}
|
? generateTotpRes.data.totp_secret_url
|
||||||
onChange={linkEvent(this, this.handleRemoveTotp)}
|
: undefined
|
||||||
|
}
|
||||||
|
show={this.state.show2faModal}
|
||||||
|
onClose={linkEvent(this, handleClose2faModal)}
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label" htmlFor="user-remove-totp">
|
|
||||||
{I18NextService.i18n.t("remove_two_factor")}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleToggle2fa(totp: string, enabled: boolean) {
|
||||||
|
this.setState({ updateTotpRes: LOADING_REQUEST });
|
||||||
|
|
||||||
|
const updateTotpRes = await HttpService.client.updateTotp({
|
||||||
|
enabled,
|
||||||
|
totp_token: totp,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({ updateTotpRes });
|
||||||
|
|
||||||
|
const successful = updateTotpRes.state === "success";
|
||||||
|
if (successful) {
|
||||||
|
this.setState({ show2faModal: false });
|
||||||
|
|
||||||
|
const siteRes = await HttpService.client.getSite();
|
||||||
|
UserService.Instance.myUserInfo!.local_user_view.local_user.totp_2fa_enabled =
|
||||||
|
enabled;
|
||||||
|
|
||||||
|
if (siteRes.state === "success") {
|
||||||
|
this.setState({ siteRes: siteRes.data });
|
||||||
|
}
|
||||||
|
|
||||||
|
toast(
|
||||||
|
I18NextService.i18n.t(
|
||||||
|
enabled ? "enable_totp_success" : "disable_totp_success",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast(I18NextService.i18n.t("incorrect_totp_code"), "danger");
|
||||||
|
}
|
||||||
|
|
||||||
|
return successful;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEnable2fa(totp: string) {
|
||||||
|
return this.handleToggle2fa(totp, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDisable2fa(totp: string) {
|
||||||
|
return this.handleToggle2fa(totp, false);
|
||||||
|
}
|
||||||
|
|
||||||
handlePersonSearch = debounce(async (text: string) => {
|
handlePersonSearch = debounce(async (text: string) => {
|
||||||
this.setState({ searchPersonLoading: true });
|
this.setState({ searchPersonLoading: true });
|
||||||
|
|
||||||
|
@ -1248,19 +1321,12 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGenerateTotp(i: Settings, event: any) {
|
async handleGenerateTotp(i: Settings) {
|
||||||
// Coerce false to undefined here, so it won't generate it.
|
i.setState({ generateTotpRes: LOADING_REQUEST });
|
||||||
const checked: boolean | undefined = event.target.checked || undefined;
|
|
||||||
if (checked) {
|
|
||||||
toast(I18NextService.i18n.t("two_factor_setup_instructions"));
|
|
||||||
}
|
|
||||||
i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRemoveTotp(i: Settings, event: any) {
|
i.setState({
|
||||||
// Coerce true to undefined here, so it won't generate it.
|
generateTotpRes: await HttpService.client.generateTotpSecret(),
|
||||||
const checked: boolean | undefined = !event.target.checked && undefined;
|
});
|
||||||
i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSendNotificationsToEmailChange(i: Settings, event: any) {
|
handleSendNotificationsToEmailChange(i: Settings, event: any) {
|
||||||
|
@ -1365,7 +1431,7 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
|
|
||||||
async handleSaveSettingsSubmit(i: Settings, event: any) {
|
async handleSaveSettingsSubmit(i: Settings, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
i.setState({ saveRes: { state: "loading" } });
|
i.setState({ saveRes: LOADING_REQUEST });
|
||||||
|
|
||||||
const saveRes = await HttpService.client.saveUserSettings({
|
const saveRes = await HttpService.client.saveUserSettings({
|
||||||
...i.state.saveUserSettingsForm,
|
...i.state.saveUserSettingsForm,
|
||||||
|
@ -1400,7 +1466,7 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
i.state.changePasswordForm;
|
i.state.changePasswordForm;
|
||||||
|
|
||||||
if (new_password && old_password && new_password_verify) {
|
if (new_password && old_password && new_password_verify) {
|
||||||
i.setState({ changePasswordRes: { state: "loading" } });
|
i.setState({ changePasswordRes: LOADING_REQUEST });
|
||||||
const changePasswordRes = await HttpService.client.changePassword({
|
const changePasswordRes = await HttpService.client.changePassword({
|
||||||
new_password,
|
new_password,
|
||||||
new_password_verify,
|
new_password_verify,
|
||||||
|
@ -1431,7 +1497,7 @@ export class Settings extends Component<any, SettingsState> {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const password = i.state.deleteAccountForm.password;
|
const password = i.state.deleteAccountForm.password;
|
||||||
if (password) {
|
if (password) {
|
||||||
i.setState({ deleteAccountRes: { state: "loading" } });
|
i.setState({ deleteAccountRes: LOADING_REQUEST });
|
||||||
const deleteAccountRes = await HttpService.client.deleteAccount({
|
const deleteAccountRes = await HttpService.client.deleteAccount({
|
||||||
password,
|
password,
|
||||||
// TODO: promt user weather he wants the content to be deleted
|
// TODO: promt user weather he wants the content to be deleted
|
||||||
|
|
|
@ -2,7 +2,12 @@ import { setIsoData } from "@utils/app";
|
||||||
import { Component } from "inferno";
|
import { Component } from "inferno";
|
||||||
import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
|
import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
|
||||||
import { I18NextService } from "../../services";
|
import { I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
@ -16,7 +21,7 @@ export class VerifyEmail extends Component<any, State> {
|
||||||
private isoData = setIsoData(this.context);
|
private isoData = setIsoData(this.context);
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
verifyRes: { state: "empty" },
|
verifyRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,7 +31,7 @@ export class VerifyEmail extends Component<any, State> {
|
||||||
|
|
||||||
async verify() {
|
async verify() {
|
||||||
this.setState({
|
this.setState({
|
||||||
verifyRes: { state: "loading" },
|
verifyRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
import { InitialFetchRequest, PostFormParams } from "../../interfaces";
|
import { InitialFetchRequest, PostFormParams } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import {
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
HttpService,
|
HttpService,
|
||||||
RequestState,
|
RequestState,
|
||||||
WrappedLemmyHttp,
|
WrappedLemmyHttp,
|
||||||
|
@ -57,7 +58,7 @@ export class CreatePost extends Component<
|
||||||
state: CreatePostState = {
|
state: CreatePostState = {
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
loading: true,
|
loading: true,
|
||||||
initialCommunitiesRes: { state: "empty" },
|
initialCommunitiesRes: EMPTY_REQUEST,
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -242,7 +243,7 @@ export class CreatePost extends Component<
|
||||||
>): Promise<CreatePostData> {
|
>): Promise<CreatePostData> {
|
||||||
const data: CreatePostData = {
|
const data: CreatePostData = {
|
||||||
initialCommunitiesRes: await fetchCommunitiesForOptions(client),
|
initialCommunitiesRes: await fetchCommunitiesForOptions(client),
|
||||||
communityResponse: { state: "empty" },
|
communityResponse: EMPTY_REQUEST,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (communityId) {
|
if (communityId) {
|
||||||
|
|
|
@ -29,7 +29,12 @@ import {
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { PostFormParams } from "../../interfaces";
|
import { PostFormParams } from "../../interfaces";
|
||||||
import { I18NextService, UserService } from "../../services";
|
import { I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { Icon, Spinner } from "../common/icon";
|
import { Icon, Spinner } from "../common/icon";
|
||||||
|
@ -116,7 +121,7 @@ function copySuggestedTitle(d: { i: PostForm; suggestedTitle?: string }) {
|
||||||
d.i.setState(
|
d.i.setState(
|
||||||
s => ((s.form.name = sTitle?.substring(0, MAX_POST_TITLE_LENGTH)), s),
|
s => ((s.form.name = sTitle?.substring(0, MAX_POST_TITLE_LENGTH)), s),
|
||||||
);
|
);
|
||||||
d.i.setState({ suggestedPostsRes: { state: "empty" } });
|
d.i.setState({ suggestedPostsRes: EMPTY_REQUEST });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const textarea: any = document.getElementById("post-title");
|
const textarea: any = document.getElementById("post-title");
|
||||||
autosize.update(textarea);
|
autosize.update(textarea);
|
||||||
|
@ -215,8 +220,8 @@ function handleImageDelete(i: PostForm) {
|
||||||
|
|
||||||
export class PostForm extends Component<PostFormProps, PostFormState> {
|
export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
state: PostFormState = {
|
state: PostFormState = {
|
||||||
suggestedPostsRes: { state: "empty" },
|
suggestedPostsRes: EMPTY_REQUEST,
|
||||||
metadataRes: { state: "empty" },
|
metadataRes: EMPTY_REQUEST,
|
||||||
form: {},
|
form: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
imageLoading: false,
|
imageLoading: false,
|
||||||
|
@ -648,7 +653,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
async fetchPageTitle() {
|
async fetchPageTitle() {
|
||||||
const url = this.state.form.url;
|
const url = this.state.form.url;
|
||||||
if (url && validURL(url)) {
|
if (url && validURL(url)) {
|
||||||
this.setState({ metadataRes: { state: "loading" } });
|
this.setState({ metadataRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
metadataRes: await HttpService.client.getSiteMetadata({ url }),
|
metadataRes: await HttpService.client.getSiteMetadata({ url }),
|
||||||
});
|
});
|
||||||
|
@ -658,7 +663,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
async fetchSimilarPosts() {
|
async fetchSimilarPosts() {
|
||||||
const q = this.state.form.name;
|
const q = this.state.form.name;
|
||||||
if (q && q !== "") {
|
if (q && q !== "") {
|
||||||
this.setState({ suggestedPostsRes: { state: "loading" } });
|
this.setState({ suggestedPostsRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
suggestedPostsRes: await HttpService.client.search({
|
suggestedPostsRes: await HttpService.client.search({
|
||||||
q,
|
q,
|
||||||
|
|
|
@ -83,7 +83,12 @@ import {
|
||||||
InitialFetchRequest,
|
InitialFetchRequest,
|
||||||
} from "../../interfaces";
|
} from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
import { FirstLoadService, I18NextService, UserService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { setupTippy } from "../../tippy";
|
import { setupTippy } from "../../tippy";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { CommentForm } from "../comment/comment-form";
|
import { CommentForm } from "../comment/comment-form";
|
||||||
|
@ -120,8 +125,8 @@ export class Post extends Component<any, PostState> {
|
||||||
private isoData = setIsoData<PostData>(this.context);
|
private isoData = setIsoData<PostData>(this.context);
|
||||||
private commentScrollDebounced: () => void;
|
private commentScrollDebounced: () => void;
|
||||||
state: PostState = {
|
state: PostState = {
|
||||||
postRes: { state: "empty" },
|
postRes: EMPTY_REQUEST,
|
||||||
commentsRes: { state: "empty" },
|
commentsRes: EMPTY_REQUEST,
|
||||||
postId: getIdFromProps(this.props),
|
postId: getIdFromProps(this.props),
|
||||||
commentId: getCommentIdFromProps(this.props),
|
commentId: getCommentIdFromProps(this.props),
|
||||||
commentSort: "Hot",
|
commentSort: "Hot",
|
||||||
|
@ -196,8 +201,8 @@ export class Post extends Component<any, PostState> {
|
||||||
|
|
||||||
async fetchPost() {
|
async fetchPost() {
|
||||||
this.setState({
|
this.setState({
|
||||||
postRes: { state: "loading" },
|
postRes: LOADING_REQUEST,
|
||||||
commentsRes: { state: "loading" },
|
commentsRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [postRes, commentsRes] = await Promise.all([
|
const [postRes, commentsRes] = await Promise.all([
|
||||||
|
@ -697,8 +702,8 @@ export class Post extends Component<any, PostState> {
|
||||||
i.setState({
|
i.setState({
|
||||||
commentSort: event.target.value as CommentSortType,
|
commentSort: event.target.value as CommentSortType,
|
||||||
commentViewType: CommentViewType.Tree,
|
commentViewType: CommentViewType.Tree,
|
||||||
commentsRes: { state: "loading" },
|
commentsRes: LOADING_REQUEST,
|
||||||
postRes: { state: "loading" },
|
postRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
await i.fetchPost();
|
await i.fetchPost();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,12 @@ import {
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../../services";
|
import { FirstLoadService, I18NextService } from "../../services";
|
||||||
import { HttpService, RequestState } from "../../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../../services/HttpService";
|
||||||
import { toast } from "../../toast";
|
import { toast } from "../../toast";
|
||||||
import { HtmlTags } from "../common/html-tags";
|
import { HtmlTags } from "../common/html-tags";
|
||||||
import { Spinner } from "../common/icon";
|
import { Spinner } from "../common/icon";
|
||||||
|
@ -33,7 +38,7 @@ export class CreatePrivateMessage extends Component<
|
||||||
private isoData = setIsoData<CreatePrivateMessageData>(this.context);
|
private isoData = setIsoData<CreatePrivateMessageData>(this.context);
|
||||||
state: CreatePrivateMessageState = {
|
state: CreatePrivateMessageState = {
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
recipientRes: { state: "empty" },
|
recipientRes: EMPTY_REQUEST,
|
||||||
recipientId: getRecipientIdFromProps(this.props),
|
recipientId: getRecipientIdFromProps(this.props),
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
@ -78,7 +83,7 @@ export class CreatePrivateMessage extends Component<
|
||||||
|
|
||||||
async fetchPersonDetails() {
|
async fetchPersonDetails() {
|
||||||
this.setState({
|
this.setState({
|
||||||
recipientRes: { state: "loading" },
|
recipientRes: LOADING_REQUEST,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -5,7 +5,11 @@ import { Component, linkEvent } from "inferno";
|
||||||
import { CommunityView, ResolveObjectResponse } from "lemmy-js-client";
|
import { CommunityView, ResolveObjectResponse } from "lemmy-js-client";
|
||||||
import { InitialFetchRequest } from "../interfaces";
|
import { InitialFetchRequest } from "../interfaces";
|
||||||
import { FirstLoadService, HttpService, I18NextService } from "../services";
|
import { FirstLoadService, HttpService, I18NextService } from "../services";
|
||||||
import { RequestState } from "../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../services/HttpService";
|
||||||
import { HtmlTags } from "./common/html-tags";
|
import { HtmlTags } from "./common/html-tags";
|
||||||
import { Spinner } from "./common/icon";
|
import { Spinner } from "./common/icon";
|
||||||
import { LoadingEllipses } from "./common/loading-ellipses";
|
import { LoadingEllipses } from "./common/loading-ellipses";
|
||||||
|
@ -76,7 +80,7 @@ const handleUnfollow = (i: RemoteFetch) => handleToggleFollow(i, false);
|
||||||
export class RemoteFetch extends Component<any, RemoteFetchState> {
|
export class RemoteFetch extends Component<any, RemoteFetchState> {
|
||||||
private isoData = setIsoData<RemoteFetchData>(this.context);
|
private isoData = setIsoData<RemoteFetchData>(this.context);
|
||||||
state: RemoteFetchState = {
|
state: RemoteFetchState = {
|
||||||
resolveObjectRes: { state: "empty" },
|
resolveObjectRes: EMPTY_REQUEST,
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
followCommunityLoading: false,
|
followCommunityLoading: false,
|
||||||
};
|
};
|
||||||
|
@ -100,7 +104,7 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
||||||
const { uri } = getRemoteFetchQueryParams();
|
const { uri } = getRemoteFetchQueryParams();
|
||||||
|
|
||||||
if (uri) {
|
if (uri) {
|
||||||
this.setState({ resolveObjectRes: { state: "loading" } });
|
this.setState({ resolveObjectRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
resolveObjectRes: await HttpService.client.resolveObject({
|
resolveObjectRes: await HttpService.client.resolveObject({
|
||||||
q: uriToQuery(uri),
|
q: uriToQuery(uri),
|
||||||
|
@ -208,7 +212,7 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
||||||
}: InitialFetchRequest<
|
}: InitialFetchRequest<
|
||||||
QueryParams<RemoteFetchProps>
|
QueryParams<RemoteFetchProps>
|
||||||
>): Promise<RemoteFetchData> {
|
>): Promise<RemoteFetchData> {
|
||||||
const data: RemoteFetchData = { resolveObjectRes: { state: "empty" } };
|
const data: RemoteFetchData = { resolveObjectRes: EMPTY_REQUEST };
|
||||||
|
|
||||||
if (uri && auth) {
|
if (uri && auth) {
|
||||||
data.resolveObjectRes = await client.resolveObject({
|
data.resolveObjectRes = await client.resolveObject({
|
||||||
|
|
|
@ -48,7 +48,12 @@ import {
|
||||||
import { fetchLimit } from "../config";
|
import { fetchLimit } from "../config";
|
||||||
import { CommentViewType, InitialFetchRequest } from "../interfaces";
|
import { CommentViewType, InitialFetchRequest } from "../interfaces";
|
||||||
import { FirstLoadService, I18NextService } from "../services";
|
import { FirstLoadService, I18NextService } from "../services";
|
||||||
import { HttpService, RequestState } from "../services/HttpService";
|
import {
|
||||||
|
EMPTY_REQUEST,
|
||||||
|
HttpService,
|
||||||
|
LOADING_REQUEST,
|
||||||
|
RequestState,
|
||||||
|
} from "../services/HttpService";
|
||||||
import { CommentNodes } from "./comment/comment-nodes";
|
import { CommentNodes } from "./comment/comment-nodes";
|
||||||
import { HtmlTags } from "./common/html-tags";
|
import { HtmlTags } from "./common/html-tags";
|
||||||
import { Spinner } from "./common/icon";
|
import { Spinner } from "./common/icon";
|
||||||
|
@ -239,14 +244,14 @@ export class Search extends Component<any, SearchState> {
|
||||||
private isoData = setIsoData<SearchData>(this.context);
|
private isoData = setIsoData<SearchData>(this.context);
|
||||||
|
|
||||||
state: SearchState = {
|
state: SearchState = {
|
||||||
resolveObjectRes: { state: "empty" },
|
resolveObjectRes: EMPTY_REQUEST,
|
||||||
creatorDetailsRes: { state: "empty" },
|
creatorDetailsRes: EMPTY_REQUEST,
|
||||||
communitiesRes: { state: "empty" },
|
communitiesRes: EMPTY_REQUEST,
|
||||||
communityRes: { state: "empty" },
|
communityRes: EMPTY_REQUEST,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
creatorSearchOptions: [],
|
creatorSearchOptions: [],
|
||||||
communitySearchOptions: [],
|
communitySearchOptions: [],
|
||||||
searchRes: { state: "empty" },
|
searchRes: EMPTY_REQUEST,
|
||||||
searchCreatorLoading: false,
|
searchCreatorLoading: false,
|
||||||
searchCommunitiesLoading: false,
|
searchCommunitiesLoading: false,
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
|
@ -343,7 +348,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchCommunities() {
|
async fetchCommunities() {
|
||||||
this.setState({ communitiesRes: { state: "loading" } });
|
this.setState({ communitiesRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
communitiesRes: await HttpService.client.listCommunities({
|
communitiesRes: await HttpService.client.listCommunities({
|
||||||
type_: defaultListingType,
|
type_: defaultListingType,
|
||||||
|
@ -362,12 +367,9 @@ export class Search extends Component<any, SearchState> {
|
||||||
query: { communityId, creatorId, q, type, sort, listingType, page },
|
query: { communityId, creatorId, q, type, sort, listingType, page },
|
||||||
}: InitialFetchRequest<QueryParams<SearchProps>>): Promise<SearchData> {
|
}: InitialFetchRequest<QueryParams<SearchProps>>): Promise<SearchData> {
|
||||||
const community_id = getIdFromString(communityId);
|
const community_id = getIdFromString(communityId);
|
||||||
let communityResponse: RequestState<GetCommunityResponse> = {
|
let communityResponse: RequestState<GetCommunityResponse> = EMPTY_REQUEST;
|
||||||
state: "empty",
|
let listCommunitiesResponse: RequestState<ListCommunitiesResponse> =
|
||||||
};
|
EMPTY_REQUEST;
|
||||||
let listCommunitiesResponse: RequestState<ListCommunitiesResponse> = {
|
|
||||||
state: "empty",
|
|
||||||
};
|
|
||||||
if (community_id) {
|
if (community_id) {
|
||||||
const getCommunityForm: GetCommunity = {
|
const getCommunityForm: GetCommunity = {
|
||||||
id: community_id,
|
id: community_id,
|
||||||
|
@ -387,9 +389,8 @@ export class Search extends Component<any, SearchState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const creator_id = getIdFromString(creatorId);
|
const creator_id = getIdFromString(creatorId);
|
||||||
let creatorDetailsResponse: RequestState<GetPersonDetailsResponse> = {
|
let creatorDetailsResponse: RequestState<GetPersonDetailsResponse> =
|
||||||
state: "empty",
|
EMPTY_REQUEST;
|
||||||
};
|
|
||||||
if (creator_id) {
|
if (creator_id) {
|
||||||
const getCreatorForm: GetPersonDetails = {
|
const getCreatorForm: GetPersonDetails = {
|
||||||
person_id: creator_id,
|
person_id: creator_id,
|
||||||
|
@ -400,10 +401,9 @@ export class Search extends Component<any, SearchState> {
|
||||||
|
|
||||||
const query = getSearchQueryFromQuery(q);
|
const query = getSearchQueryFromQuery(q);
|
||||||
|
|
||||||
let searchResponse: RequestState<SearchResponse> = { state: "empty" };
|
let searchResponse: RequestState<SearchResponse> = EMPTY_REQUEST;
|
||||||
let resolveObjectResponse: RequestState<ResolveObjectResponse> = {
|
let resolveObjectResponse: RequestState<ResolveObjectResponse> =
|
||||||
state: "empty",
|
EMPTY_REQUEST;
|
||||||
};
|
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
const form: SearchForm = {
|
const form: SearchForm = {
|
||||||
|
@ -430,7 +430,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
// If we return this object with a state of failed, the catch-all-handler will redirect
|
// If we return this object with a state of failed, the catch-all-handler will redirect
|
||||||
// to an error page, so we ignore it by covering up the error with the empty state.
|
// to an error page, so we ignore it by covering up the error with the empty state.
|
||||||
if (resolveObjectResponse.state === "failed") {
|
if (resolveObjectResponse.state === "failed") {
|
||||||
resolveObjectResponse = { state: "empty" };
|
resolveObjectResponse = EMPTY_REQUEST;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -744,8 +744,8 @@ export class Search extends Component<any, SearchState> {
|
||||||
onPersonMentionRead={() => {}}
|
onPersonMentionRead={() => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={() => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={() => {}}
|
||||||
onCreateComment={() => Promise.resolve({ state: "empty" })}
|
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
onEditComment={() => Promise.resolve({ state: "empty" })}
|
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{i.type_ === "communities" && (
|
{i.type_ === "communities" && (
|
||||||
|
@ -805,8 +805,8 @@ export class Search extends Component<any, SearchState> {
|
||||||
onPersonMentionRead={() => {}}
|
onPersonMentionRead={() => {}}
|
||||||
onBanPersonFromCommunity={() => {}}
|
onBanPersonFromCommunity={() => {}}
|
||||||
onBanPerson={() => {}}
|
onBanPerson={() => {}}
|
||||||
onCreateComment={() => Promise.resolve({ state: "empty" })}
|
onCreateComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
onEditComment={() => Promise.resolve({ state: "empty" })}
|
onEditComment={() => Promise.resolve(EMPTY_REQUEST)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -948,7 +948,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
getSearchQueryParams();
|
getSearchQueryParams();
|
||||||
|
|
||||||
if (q) {
|
if (q) {
|
||||||
this.setState({ searchRes: { state: "loading" } });
|
this.setState({ searchRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
searchRes: await HttpService.client.search({
|
searchRes: await HttpService.client.search({
|
||||||
q,
|
q,
|
||||||
|
@ -965,7 +965,7 @@ export class Search extends Component<any, SearchState> {
|
||||||
restoreScrollPosition(this.context);
|
restoreScrollPosition(this.context);
|
||||||
|
|
||||||
if (myAuth()) {
|
if (myAuth()) {
|
||||||
this.setState({ resolveObjectRes: { state: "loading" } });
|
this.setState({ resolveObjectRes: LOADING_REQUEST });
|
||||||
this.setState({
|
this.setState({
|
||||||
resolveObjectRes: await HttpService.silent_client.resolveObject({
|
resolveObjectRes: await HttpService.silent_client.resolveObject({
|
||||||
q,
|
q,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { GetPostsResponse } from "lemmy-js-client";
|
import { GetPostsResponse } from "lemmy-js-client";
|
||||||
import { RequestState } from "./HttpService.js";
|
import { EMPTY_REQUEST, RequestState } from "./HttpService";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to cache home post listings and restore home state when user uses the browser back buttons.
|
* Service to cache home post listings and restore home state when user uses the browser back buttons.
|
||||||
|
@ -8,7 +8,7 @@ export class HomeCacheService {
|
||||||
static #_instance: HomeCacheService;
|
static #_instance: HomeCacheService;
|
||||||
historyIdx = 0;
|
historyIdx = 0;
|
||||||
scrollY = 0;
|
scrollY = 0;
|
||||||
posts: RequestState<GetPostsResponse> = { state: "empty" };
|
posts: RequestState<GetPostsResponse> = EMPTY_REQUEST;
|
||||||
|
|
||||||
get active() {
|
get active() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -3,13 +3,17 @@ import { LemmyHttp } from "lemmy-js-client";
|
||||||
import { toast } from "../toast";
|
import { toast } from "../toast";
|
||||||
import { I18NextService } from "./I18NextService";
|
import { I18NextService } from "./I18NextService";
|
||||||
|
|
||||||
export type EmptyRequestState = {
|
export const EMPTY_REQUEST = {
|
||||||
state: "empty";
|
state: "empty",
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
type LoadingRequestState = {
|
export type EmptyRequestState = typeof EMPTY_REQUEST;
|
||||||
state: "loading";
|
|
||||||
};
|
export const LOADING_REQUEST = {
|
||||||
|
state: "loading",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type LoadingRequestState = typeof LOADING_REQUEST;
|
||||||
|
|
||||||
export type FailedRequestState = {
|
export type FailedRequestState = {
|
||||||
state: "failed";
|
state: "failed";
|
||||||
|
|
73
yarn.lock
73
yarn.lock
|
@ -1613,6 +1613,20 @@
|
||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@pdf-lib/standard-fonts@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz#8ba691c4421f71662ed07c9a0294b44528af2d7f"
|
||||||
|
integrity sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==
|
||||||
|
dependencies:
|
||||||
|
pako "^1.0.6"
|
||||||
|
|
||||||
|
"@pdf-lib/upng@^1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@pdf-lib/upng/-/upng-1.0.1.tgz#7dc9c636271aca007a9df4deaf2dd7e7960280cb"
|
||||||
|
integrity sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==
|
||||||
|
dependencies:
|
||||||
|
pako "^1.0.10"
|
||||||
|
|
||||||
"@pkgjs/parseargs@^0.11.0":
|
"@pkgjs/parseargs@^0.11.0":
|
||||||
version "0.11.0"
|
version "0.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||||
|
@ -1685,6 +1699,16 @@
|
||||||
domhandler "^5.0.3"
|
domhandler "^5.0.3"
|
||||||
selderee "^0.11.0"
|
selderee "^0.11.0"
|
||||||
|
|
||||||
|
"@shortcm/qr-image@^9.0.2":
|
||||||
|
version "9.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@shortcm/qr-image/-/qr-image-9.0.2.tgz#a24ed06026466974badb7fc7fc863d704d496bbe"
|
||||||
|
integrity sha512-/hz2NqFlT0Xmd5FDiYSsb/lDucZbByWeFUiEz1ekFnz6MHtdpv03mSMSsLm+LF8n/LgumjBcKci3gG2TMirIJA==
|
||||||
|
dependencies:
|
||||||
|
color-string "^1.9.1"
|
||||||
|
js-base64 "^3.7.5"
|
||||||
|
pdf-lib "^1.17.1"
|
||||||
|
sharp "^0.32.5"
|
||||||
|
|
||||||
"@surma/rollup-plugin-off-main-thread@^2.2.3":
|
"@surma/rollup-plugin-off-main-thread@^2.2.3":
|
||||||
version "2.2.3"
|
version "2.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
|
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
|
||||||
|
@ -3210,7 +3234,7 @@ color-name@^1.0.0, color-name@~1.1.4:
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
color-string@^1.9.0:
|
color-string@^1.9.0, color-string@^1.9.1:
|
||||||
version "1.9.1"
|
version "1.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
|
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
|
||||||
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
|
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
|
||||||
|
@ -5874,6 +5898,11 @@ jest-worker@^27.4.5:
|
||||||
merge-stream "^2.0.0"
|
merge-stream "^2.0.0"
|
||||||
supports-color "^8.0.0"
|
supports-color "^8.0.0"
|
||||||
|
|
||||||
|
js-base64@^3.7.5:
|
||||||
|
version "3.7.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
|
||||||
|
integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
|
@ -6035,10 +6064,10 @@ leac@^0.6.0:
|
||||||
resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
|
resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
|
||||||
integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
|
integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
|
||||||
|
|
||||||
lemmy-js-client@^0.19.0-rc.12:
|
lemmy-js-client@^0.19.0-rc.13:
|
||||||
version "0.19.0-rc.12"
|
version "0.19.0-rc.13"
|
||||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.12.tgz#e3bd4e21b1966d583ab790ef70ece8394b012b48"
|
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.19.0-rc.13.tgz#e0e15ba6fe3a08cb85130eea7eec4bd2773999f9"
|
||||||
integrity sha512-1iu2fW9vlb3TrI+QR/ODP3+5pWZB0rUqL1wH09IzomDXohCqoQvfmXpwArmgF4Eq8GZgjkcfeMDC2gMrfw/i7Q==
|
integrity sha512-JP9oEh1+Wfttqx5O5EMAVIR/hFVS66iVKmEo8/Uxw8fJfyUeQo7BhKvG8LTYegBE39Womgyu3KxXb7Jy9DRI5A==
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-fetch "^3.1.5"
|
cross-fetch "^3.1.5"
|
||||||
form-data "^4.0.0"
|
form-data "^4.0.0"
|
||||||
|
@ -7451,6 +7480,11 @@ pacote@^8.1.6:
|
||||||
unique-filename "^1.1.0"
|
unique-filename "^1.1.0"
|
||||||
which "^1.3.0"
|
which "^1.3.0"
|
||||||
|
|
||||||
|
pako@^1.0.10, pako@^1.0.11, pako@^1.0.6:
|
||||||
|
version "1.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||||
|
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||||
|
|
||||||
parallel-transform@^1.1.0:
|
parallel-transform@^1.1.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc"
|
resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc"
|
||||||
|
@ -7570,6 +7604,16 @@ path-type@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||||
|
|
||||||
|
pdf-lib@^1.17.1:
|
||||||
|
version "1.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pdf-lib/-/pdf-lib-1.17.1.tgz#9e7dd21261a0c1fb17992580885b39e7d08f451f"
|
||||||
|
integrity sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==
|
||||||
|
dependencies:
|
||||||
|
"@pdf-lib/standard-fonts" "^1.0.0"
|
||||||
|
"@pdf-lib/upng" "^1.0.1"
|
||||||
|
pako "^1.0.11"
|
||||||
|
tslib "^1.11.1"
|
||||||
|
|
||||||
peberminta@^0.9.0:
|
peberminta@^0.9.0:
|
||||||
version "0.9.0"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.9.0.tgz#8ec9bc0eb84b7d368126e71ce9033501dca2a352"
|
resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.9.0.tgz#8ec9bc0eb84b7d368126e71ce9033501dca2a352"
|
||||||
|
@ -8570,6 +8614,20 @@ sharp@^0.32.4:
|
||||||
tar-fs "^3.0.4"
|
tar-fs "^3.0.4"
|
||||||
tunnel-agent "^0.6.0"
|
tunnel-agent "^0.6.0"
|
||||||
|
|
||||||
|
sharp@^0.32.5:
|
||||||
|
version "0.32.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.6.tgz#6ad30c0b7cd910df65d5f355f774aa4fce45732a"
|
||||||
|
integrity sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==
|
||||||
|
dependencies:
|
||||||
|
color "^4.2.3"
|
||||||
|
detect-libc "^2.0.2"
|
||||||
|
node-addon-api "^6.1.0"
|
||||||
|
prebuild-install "^7.1.1"
|
||||||
|
semver "^7.5.4"
|
||||||
|
simple-get "^4.0.1"
|
||||||
|
tar-fs "^3.0.4"
|
||||||
|
tunnel-agent "^0.6.0"
|
||||||
|
|
||||||
shebang-command@^1.2.0:
|
shebang-command@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||||
|
@ -9386,6 +9444,11 @@ ts-api-utils@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d"
|
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d"
|
||||||
integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==
|
integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==
|
||||||
|
|
||||||
|
tslib@^1.11.1:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.0:
|
tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.0:
|
||||||
version "2.6.0"
|
version "2.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3"
|
||||||
|
|
Loading…
Reference in a new issue