* Beginning work on websocket -> http client conversion.
* About 30% done.
* half done.
* more done.
* Almost passing lint.
* Passing lint, but untested.
* Add back in event listeners.
* Fixing some community forms.
* Remove webpack cache.
* fixing some more.
* Fixed ISOwrappers.
* A few more fixes.
* Refactor utils
* Fix instance add/remove buttons
* Not catching errors in isoWrapper.
* Wrap Http client
* Fixing up tagline and ratelimit forms.
* Make all http client wrapping be in one place
* Reworking some more forms.
* Upgrading lemmy-js-client.
* Fixing verify email.
* Fix linting errors
* Upgrading woodpecker node.
* Fix comment scrolling rerender bug.
* Fixing a few things, commenting out props for now.
* v0.18.0-beta.1
* Trying to fix woodpecker, 1.
* Trying to fix woodpecker, 2.
* Handroll prompt
* Add navigation prompt to other pages
* Fix prompt navigation bug
* Fix prompt bug introduced from last bug fix
* Fix PWA bug
* Fix isoData not working
* Fix search page update url
* Fix sharp issue.
* v0.18.0-beta.2
* Make create post pre-fetch communities
* Fix bug from last commit
* Fix issue of posts/comments not being switched when changing select options
* Fix unnecessary fetches on home screen
* Make circular icon buttons not look stupid
* Prevent unnecessary fetches
* Make login experience smoother
* Add PWA shortcuts
* Add related application to PWA
* Update translations
* Forgot to add post editing.
* Fixing site setup.
* Deploy script setup.
* v0.18.0-beta.4
* Sanitize again.
* Adding sanitize json function.
* Upping version.
* Another sanitize fix.
* Upping version.
* Prevent search nav item from disappearing when on search page
* Allow admin and mod actions on non-local comments.
* Fix mobile menu collapse bug
* Completely fix prompt component
* Fix undefined value checks in use_http_client_2 (#1230)
* fix: filter out undefined from posts
* fix: emoji initialisation passing undefined
* fix: || => ?? to be more explicit
* linting
---------
Co-authored-by: Alex Maras <alexmaras@gmail.com>
* Re-add accidentally removed state
* Fix dropdown bug
* Use linkEvent where appropriate
* Fix navigation warnings.
---------
Co-authored-by: Dessalines <tyhou13@gmx.com>
Co-authored-by: Alex Maras <dev@alexmaras.com>
Co-authored-by: Alex Maras <alexmaras@gmail.com>
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
+ "@typescript-eslint/no-empty-function": 0,
"arrow-body-style": 0,
"curly": 0,
"eol-last": 0,
pipeline:
fetch_git_submodules:
- image: node:14-alpine
+ image: node:alpine
commands:
- apk add git
- git submodule init
# - git fetch --tags
yarn:
- image: node:14-alpine
+ image: node:alpine
commands:
- yarn
yarn_lint:
- image: node:14-alpine
+ image: node:alpine
commands:
- yarn lint
yarn_build_dev:
- image: node:14-alpine
+ image: node:alpine
commands:
- yarn build:dev
- nightly_build:
- image: plugins/docker
+ publish_release_docker:
+ image: woodpeckerci/plugin-docker-buildx
+ secrets: [docker_username, docker_password]
settings:
- dockerfile: Dockerfile
- repo: dessalines/lemmy-ui
- username:
- from_secret: docker_username
- password:
- from_secret: docker_password
- tags:
- - dev
- when:
- event:
- - cron
-
- publish_release_docker_image_amd:
- image: plugins/docker
- settings:
- dockerfile: Dockerfile
repo: dessalines/lemmy-ui
- auto_tag: true
- auto_tag_suffix: linux-amd64
- username:
- from_secret: docker_username
- password:
- from_secret: docker_password
- when:
- event: tag
- platform: linux/arm64
-
- publish_release_docker_image_arm:
- image: plugins/docker
- settings:
dockerfile: Dockerfile
- repo: dessalines/lemmy-ui
+ platforms: linux/amd64
auto_tag: true
- auto_tag_suffix: linux-arm64
- username:
- from_secret: docker_username
- password:
- from_secret: docker_password
- when:
- event: tag
- platform: linux/amd64
-
- publish_release_docker_manifest:
- image: plugins/manifest
- settings:
- username:
- from_secret: docker_username
- password:
- from_secret: docker_password
- target: "dessalines/lemmy-ui:${CI_COMMIT_TAG}"
- template: "dessalines/lemmy-ui:${CI_COMMIT_TAG}-OS-ARCH"
- platforms:
- - linux/amd64
- - linux/arm64
- ignore_missing: true
- when:
- event: tag
-
- publish_latest_release_docker_manifest:
- image: plugins/manifest
- settings:
- username:
- from_secret: docker_username
- password:
- from_secret: docker_password
- target: "dessalines/lemmy-ui:latest"
- template: "dessalines/lemmy-ui:${CI_COMMIT_TAG}-OS-ARCH"
- platforms:
- - linux/amd64
- - linux/arm64
- ignore_missing: true
when:
event: tag
-# lemmy-ui
+# Lemmy-UI
The official web app for [Lemmy](https://github.com/LemmyNet/lemmy), written in inferno.
| `LEMMY_UI_HOST` | `string` | `0.0.0.0:1234` | The IP / port that the lemmy-ui isomorphic node server is hosted at. |
| `LEMMY_UI_LEMMY_INTERNAL_HOST` | `string` | `0.0.0.0:8536` | The internal IP / port that lemmy is hosted at. Often `lemmy:8536` if using docker. |
| `LEMMY_UI_LEMMY_EXTERNAL_HOST` | `string` | `0.0.0.0:8536` | The external IP / port that lemmy is hosted at. Often `DOMAIN.TLD`. |
-| `LEMMY_UI_LEMMY_WS_HOST` | `string` | `0.0.0.0:8536` | An alternate location for lemmy's websocket address. Not usually necessary. |
| `LEMMY_UI_HTTPS` | `bool` | `false` | Whether to use https. |
| `LEMMY_UI_EXTRA_THEMES_FOLDER` | `string` | `./extra_themes` | A location for additional lemmy css themes. |
| `LEMMY_UI_DEBUG` | `bool` | `false` | Loads the [Eruda](https://github.com/liriliri/eruda) debugging utility. |
new_tag="$1"
# Old deploy
+# sudo docker build . --tag dessalines/lemmy-ui:$new_tag --platform=linux/amd64 --push
# sudo docker build . --tag dessalines/lemmy-ui:$new_tag --platform=linux/amd64
# sudo docker push dessalines/lemmy-ui:$new_tag
-Subproject commit ddf0d3a4dcfba5eddbcdb702db2470b52abb3815
+Subproject commit f45ddff206adb52ab0ac7555bf14978edac5d2f2
{
"name": "lemmy-ui",
- "version": "0.17.1",
+ "version": "0.18.0-beta.6",
"description": "An isomorphic UI for lemmy",
"repository": "https://github.com/LemmyNet/lemmy-ui",
"license": "AGPL-3.0",
"start": "yarn build:dev --watch"
},
"lint-staged": {
- "*.{ts,tsx,js}": [
- "prettier --write",
- "eslint --fix"
- ],
- "*.{css, scss}": [
- "prettier --write"
- ],
- "package.json": [
- "sortpack"
- ]
+ "*.{ts,tsx,js}": ["prettier --write", "eslint --fix"],
+ "*.{css, scss}": ["prettier --write"],
+ "package.json": ["sortpack"]
},
"dependencies": {
"@babel/plugin-proposal-decorators": "^7.21.0",
"emoji-mart": "^5.4.0",
"emoji-short-name": "^2.0.0",
"express": "~4.18.2",
+ "history": "^5.3.0",
"html-to-text": "^9.0.5",
"i18next": "^22.4.15",
"inferno": "^8.1.1",
"inferno-server": "^8.1.1",
"isomorphic-cookie": "^1.2.4",
"jwt-decode": "^3.1.2",
- "lemmy-js-client": "0.17.2-rc.17",
+ "lemmy-js-client": "0.17.2-rc.24",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"markdown-it-container": "^3.0.0",
"moment": "^2.29.4",
"register-service-worker": "^1.7.2",
"run-node-webpack-plugin": "^1.3.0",
- "rxjs": "^7.8.1",
"sanitize-html": "^2.10.0",
"sass": "^1.62.1",
"sass-loader": "^13.2.2",
"tributejs": "^5.1.3",
"webpack": "5.82.1",
"webpack-cli": "^5.1.1",
- "webpack-node-externals": "^3.0.0",
- "websocket-ts": "^1.1.1"
+ "webpack-node-externals": "^3.0.0"
},
"devDependencies": {
"@babel/core": "^7.21.8",
import { hydrate } from "inferno-hydrate";
-import { BrowserRouter } from "inferno-router";
+import { Router } from "inferno-router";
import { App } from "../shared/components/app/app";
import { initializeSite } from "../shared/utils";
import "bootstrap/js/dist/collapse";
import "bootstrap/js/dist/dropdown";
+import { HistoryService } from "../shared/services/HistoryService";
const site = window.isoData.site_res;
initializeSite(site);
const wrapper = (
- <BrowserRouter>
+ <Router history={HistoryService.history}>
<App />
- </BrowserRouter>
+ </Router>
);
const root = document.getElementById("root");
import { matchPath, StaticRouter } from "inferno-router";
import { renderToString } from "inferno-server";
import IsomorphicCookie from "isomorphic-cookie";
-import { GetSite, GetSiteResponse, LemmyHttp, Site } from "lemmy-js-client";
+import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
import path from "path";
import process from "process";
import serialize from "serialize-javascript";
import sharp from "sharp";
import { App } from "../shared/components/app/app";
-import { getHttpBase, getHttpBaseInternal } from "../shared/env";
+import { getHttpBaseExternal, getHttpBaseInternal } from "../shared/env";
import {
ILemmyConfig,
InitialFetchRequest,
IsoDataOptionalSite,
} from "../shared/interfaces";
import { routes } from "../shared/routes";
+import { RequestState, wrapClient } from "../shared/services/HttpService";
import {
ErrorPageData,
favIconPngUrl,
server.get("/service-worker.js", async (_req, res) => {
res.setHeader("Content-Type", "application/javascript");
- res.sendFile(path.resolve("./dist/service-worker.js"));
+ res.sendFile(
+ path.resolve(
+ `./dist/service-worker${
+ process.env.NODE_ENV === "development" ? "-development" : ""
+ }.js`
+ )
+ );
});
server.get("/robots.txt", async (_req, res) => {
const getSiteForm: GetSite = { auth };
const headers = setForwardedHeaders(req.headers);
- const client = new LemmyHttp(getHttpBaseInternal(), headers);
+ const client = wrapClient(new LemmyHttp(getHttpBaseInternal(), headers));
const { path, url, query } = req;
// This bypasses errors, so that the client can hit the error on its own,
// in order to remove the jwt on the browser. Necessary for wrong jwts
let site: GetSiteResponse | undefined = undefined;
- let routeData: any[] = [];
- let errorPageData: ErrorPageData | undefined;
- try {
- let try_site: any = await client.getSite(getSiteForm);
- if (try_site.error == "not_logged_in") {
- console.error(
- "Incorrect JWT token, skipping auth so frontend can remove jwt cookie"
- );
- getSiteForm.auth = undefined;
- auth = undefined;
- try_site = await client.getSite(getSiteForm);
- }
+ const routeData: RequestState<any>[] = [];
+ let errorPageData: ErrorPageData | undefined = undefined;
+ let try_site = await client.getSite(getSiteForm);
+ if (try_site.state === "failed" && try_site.msg == "not_logged_in") {
+ console.error(
+ "Incorrect JWT token, skipping auth so frontend can remove jwt cookie"
+ );
+ getSiteForm.auth = undefined;
+ auth = undefined;
+ try_site = await client.getSite(getSiteForm);
+ }
- if (!auth && isAuthPath(path)) {
- res.redirect("/login");
- return;
- }
+ if (!auth && isAuthPath(path)) {
+ return res.redirect("/login");
+ }
- site = try_site;
+ if (try_site.state === "success") {
+ site = try_site.data;
initializeSite(site);
+ if (path != "/setup" && !site.site_view.local_site.site_setup) {
+ return res.redirect("/setup");
+ }
+
if (site) {
const initialFetchReq: InitialFetchRequest = {
client,
};
if (activeRoute?.fetchInitialData) {
- routeData = await Promise.all([
- ...activeRoute.fetchInitialData(initialFetchReq),
- ]);
+ routeData.push(
+ ...(await Promise.all([
+ ...activeRoute.fetchInitialData(initialFetchReq),
+ ]))
+ );
}
}
- } catch (error) {
- errorPageData = getErrorPageData(error, site);
+ } else if (try_site.state === "failed") {
+ errorPageData = getErrorPageData(new Error(try_site.msg), site);
}
// Redirect to the 404 if there's an API error
- if (routeData[0] && routeData[0].error) {
- const error = routeData[0].error;
+ if (routeData[0] && routeData[0].state === "failed") {
+ const error = routeData[0].msg;
console.error(error);
if (error === "instance_is_private") {
return res.redirect(`/signup`);
} else {
- errorPageData = getErrorPageData(error, site);
+ errorPageData = getErrorPageData(new Error(error), site);
}
}
process.exit(0);
});
-const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512];
+const iconSizes = [72, 96, 144, 192, 512];
const defaultLogoPathDirectory = path.join(
process.cwd(),
"dist",
"icons"
);
-export async function generateManifestBase64(site: Site) {
- const url = (
- process.env.NODE_ENV === "development"
- ? "http://localhost:1236/"
- : getHttpBase()
- ).replace(/\/$/g, "");
+export async function generateManifestBase64({
+ my_user,
+ site_view: {
+ site,
+ local_site: { community_creation_admin_only },
+ },
+}: GetSiteResponse) {
+ const url = getHttpBaseExternal();
+
const icon = site.icon ? await fetchIconPng(site.icon) : null;
const manifest = {
};
})
),
+ shortcuts: [
+ {
+ name: "Search",
+ short_name: "Search",
+ description: "Perform a search.",
+ url: "/search",
+ },
+ {
+ name: "Communities",
+ url: "/communities",
+ short_name: "Communities",
+ description: "Browse communities",
+ },
+ ]
+ .concat(
+ my_user
+ ? [
+ {
+ name: "Create Post",
+ url: "/create_post",
+ short_name: "Create Post",
+ description: "Create a post.",
+ },
+ ]
+ : []
+ )
+ .concat(
+ my_user?.local_user_view.person.admin || !community_creation_admin_only
+ ? [
+ {
+ name: "Create Community",
+ url: "/create_community",
+ short_name: "Create Community",
+ description: "Create a community",
+ },
+ ]
+ : []
+ ),
+ related_applications: [
+ {
+ platform: "f-droid",
+ url: "https://f-droid.org/packages/com.jerboa/",
+ id: "com.jerboa",
+ },
+ ],
};
return Buffer.from(JSON.stringify(manifest)).toString("base64");
}
async function fetchIconPng(iconUrl: string) {
- return await fetch(
- iconUrl.replace(/https?:\/\/[^\/]+/g, getHttpBaseInternal())
- )
+ return await fetch(iconUrl)
.then(res => res.blob())
.then(blob => blob.arrayBuffer());
}
site &&
`<link
rel="manifest"
- href={${`data:application/manifest+json;base64,${await generateManifestBase64(
- site.site_view.site
- )}`}}
+ href=${`data:application/manifest+json;base64,${await generateManifestBase64(
+ site
+ )}`}
/>`
}
<link rel="apple-touch-icon" href=${appleTouchIcon} />
import { Component, createRef, linkEvent } from "inferno";
import { NavLink } from "inferno-router";
import {
- CommentResponse,
- GetReportCount,
GetReportCountResponse,
GetSiteResponse,
- GetUnreadCount,
GetUnreadCountResponse,
- GetUnreadRegistrationApplicationCount,
GetUnreadRegistrationApplicationCountResponse,
- PrivateMessageResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
amAdmin,
canCreateCommunity,
donateLemmyUrl,
isBrowser,
myAuth,
- notifyComment,
- notifyPrivateMessage,
numToSI,
showAvatars,
toast,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { Icon } from "../common/icon";
import { PictrsImage } from "../common/pictrs-image";
}
interface NavbarState {
- unreadInboxCount: number;
- unreadReportCount: number;
- unreadApplicationCount: number;
+ unreadInboxCountRes: RequestState<GetUnreadCountResponse>;
+ unreadReportCountRes: RequestState<GetReportCountResponse>;
+ unreadApplicationCountRes: RequestState<GetUnreadRegistrationApplicationCountResponse>;
onSiteBanner?(url: string): any;
}
function handleCollapseClick(i: Navbar) {
- i.collapseButtonRef.current?.click();
+ if (i.collapseButtonRef.current?.ariaExpanded === "true") {
+ i.collapseButtonRef.current?.click();
+ }
}
function handleLogOut(i: Navbar) {
}
export class Navbar extends Component<NavbarProps, NavbarState> {
- private wsSub: Subscription;
- private userSub: Subscription;
- private unreadInboxCountSub: Subscription;
- private unreadReportCountSub: Subscription;
- private unreadApplicationCountSub: Subscription;
state: NavbarState = {
- unreadInboxCount: 0,
- unreadReportCount: 0,
- unreadApplicationCount: 0,
+ unreadInboxCountRes: { state: "empty" },
+ unreadReportCountRes: { state: "empty" },
+ unreadApplicationCountRes: { state: "empty" },
};
- subscription: any;
collapseButtonRef = createRef<HTMLButtonElement>();
mobileMenuRef = createRef<HTMLDivElement>();
constructor(props: any, context: any) {
super(props, context);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
this.handleOutsideMenuClick = this.handleOutsideMenuClick.bind(this);
}
- componentDidMount() {
+ async componentDidMount() {
// Subscribe to jwt changes
if (isBrowser()) {
// On the first load, check the unreads
- const auth = myAuth(false);
- if (auth && UserService.Instance.myUserInfo) {
- this.requestNotificationPermission();
- WebSocketService.Instance.send(
- wsClient.userJoin({
- auth,
- })
- );
-
- this.fetchUnreads();
- }
-
+ this.requestNotificationPermission();
+ await this.fetchUnreads();
this.requestNotificationPermission();
- // Subscribe to unread count changes
- this.unreadInboxCountSub =
- UserService.Instance.unreadInboxCountSub.subscribe(res => {
- this.setState({ unreadInboxCount: res });
- });
- // Subscribe to unread report count changes
- this.unreadReportCountSub =
- UserService.Instance.unreadReportCountSub.subscribe(res => {
- this.setState({ unreadReportCount: res });
- });
- // Subscribe to unread application count
- this.unreadApplicationCountSub =
- UserService.Instance.unreadApplicationCountSub.subscribe(res => {
- this.setState({ unreadApplicationCount: res });
- });
-
- document.addEventListener("click", this.handleOutsideMenuClick);
+ document.addEventListener("mouseup", this.handleOutsideMenuClick);
}
}
componentWillUnmount() {
- this.wsSub.unsubscribe();
- this.userSub.unsubscribe();
- this.unreadInboxCountSub.unsubscribe();
- this.unreadReportCountSub.unsubscribe();
- this.unreadApplicationCountSub.unsubscribe();
- document.removeEventListener("click", this.handleOutsideMenuClick);
+ document.removeEventListener("mouseup", this.handleOutsideMenuClick);
}
- // TODO class active corresponding to current page
render() {
+ return this.navbar();
+ }
+
+ // TODO class active corresponding to current page
+ navbar() {
const siteView = this.props.siteRes?.site_view;
const person = UserService.Instance.myUserInfo?.local_user_view.person;
return (
to="/inbox"
className="p-1 nav-link border-0"
title={i18n.t("unread_messages", {
- count: Number(this.state.unreadInboxCount),
- formattedCount: numToSI(this.state.unreadInboxCount),
+ count: Number(this.state.unreadApplicationCountRes.state),
+ formattedCount: numToSI(this.unreadInboxCount),
})}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="bell" />
- {this.state.unreadInboxCount > 0 && (
+ {this.unreadInboxCount > 0 && (
<span className="mx-1 badge badge-light">
- {numToSI(this.state.unreadInboxCount)}
+ {numToSI(this.unreadInboxCount)}
</span>
)}
</NavLink>
to="/reports"
className="p-1 nav-link border-0"
title={i18n.t("unread_reports", {
- count: Number(this.state.unreadReportCount),
- formattedCount: numToSI(this.state.unreadReportCount),
+ count: Number(this.unreadReportCount),
+ formattedCount: numToSI(this.unreadReportCount),
})}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="shield" />
- {this.state.unreadReportCount > 0 && (
+ {this.unreadReportCount > 0 && (
<span className="mx-1 badge badge-light">
- {numToSI(this.state.unreadReportCount)}
+ {numToSI(this.unreadReportCount)}
</span>
)}
</NavLink>
to="/registration_applications"
className="p-1 nav-link border-0"
title={i18n.t("unread_registration_applications", {
- count: Number(this.state.unreadApplicationCount),
- formattedCount: numToSI(this.state.unreadApplicationCount),
+ count: Number(this.unreadApplicationCount),
+ formattedCount: numToSI(this.unreadApplicationCount),
})}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="clipboard" />
- {this.state.unreadApplicationCount > 0 && (
+ {this.unreadApplicationCount > 0 && (
<span className="mx-1 badge badge-light">
- {numToSI(this.state.unreadApplicationCount)}
+ {numToSI(this.unreadApplicationCount)}
</span>
)}
</NavLink>
</li>
</ul>
<ul className="navbar-nav">
- {!this.context.router.history.location.pathname.match(
- /^\/search/
- ) && (
- <li className="nav-item">
- <NavLink
- to="/search"
- className="nav-link"
- title={i18n.t("search")}
- onMouseUp={linkEvent(this, handleCollapseClick)}
- >
- <Icon icon="search" />
- </NavLink>
- </li>
- )}
+ <li className="nav-item">
+ <NavLink
+ to="/search"
+ className="nav-link"
+ title={i18n.t("search")}
+ onMouseUp={linkEvent(this, handleCollapseClick)}
+ >
+ <Icon icon="search" />
+ </NavLink>
+ </li>
{amAdmin() && (
<li className="nav-item">
<NavLink
className="nav-link"
to="/inbox"
title={i18n.t("unread_messages", {
- count: Number(this.state.unreadInboxCount),
- formattedCount: numToSI(this.state.unreadInboxCount),
+ count: Number(this.unreadInboxCount),
+ formattedCount: numToSI(this.unreadInboxCount),
})}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="bell" />
- {this.state.unreadInboxCount > 0 && (
- <span className="ml-1 badge badge-light">
- {numToSI(this.state.unreadInboxCount)}
+ {this.unreadInboxCount > 0 && (
+ <span className="mx-1 badge badge-light">
+ {numToSI(this.unreadInboxCount)}
</span>
)}
</NavLink>
className="nav-link"
to="/reports"
title={i18n.t("unread_reports", {
- count: Number(this.state.unreadReportCount),
- formattedCount: numToSI(this.state.unreadReportCount),
+ count: Number(this.unreadReportCount),
+ formattedCount: numToSI(this.unreadReportCount),
})}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="shield" />
- {this.state.unreadReportCount > 0 && (
- <span className="ml-1 badge badge-light">
- {numToSI(this.state.unreadReportCount)}
+ {this.unreadReportCount > 0 && (
+ <span className="mx-1 badge badge-light">
+ {numToSI(this.unreadReportCount)}
</span>
)}
</NavLink>
to="/registration_applications"
className="nav-link"
title={i18n.t("unread_registration_applications", {
- count: Number(this.state.unreadApplicationCount),
- formattedCount: numToSI(
- this.state.unreadApplicationCount
- ),
+ count: Number(this.unreadApplicationCount),
+ formattedCount: numToSI(this.unreadApplicationCount),
})}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="clipboard" />
- {this.state.unreadApplicationCount > 0 && (
+ {this.unreadApplicationCount > 0 && (
<span className="mx-1 badge badge-light">
- {numToSI(this.state.unreadApplicationCount)}
+ {numToSI(this.unreadApplicationCount)}
</span>
)}
</NavLink>
return amAdmin() || moderatesS;
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- if (msg.error == "not_logged_in") {
- UserService.Instance.logout();
- }
- return;
- } else if (msg.reconnect) {
- console.log(i18n.t("websocket_reconnected"));
- const auth = myAuth(false);
- if (UserService.Instance.myUserInfo && auth) {
- WebSocketService.Instance.send(
- wsClient.userJoin({
- auth,
- })
- );
- this.fetchUnreads();
- }
- } else if (op == UserOperation.GetUnreadCount) {
- const data = wsJsonToRes<GetUnreadCountResponse>(msg);
- this.setState({
- unreadInboxCount: data.replies + data.mentions + data.private_messages,
- });
- this.sendUnreadCount();
- } else if (op == UserOperation.GetReportCount) {
- const data = wsJsonToRes<GetReportCountResponse>(msg);
+ async fetchUnreads() {
+ const auth = myAuth();
+ if (auth) {
+ this.setState({ unreadInboxCountRes: { state: "loading" } });
this.setState({
- unreadReportCount:
- data.post_reports +
- data.comment_reports +
- (data.private_message_reports ?? 0),
+ unreadInboxCountRes: await HttpService.client.getUnreadCount({
+ auth,
+ }),
});
- this.sendReportUnread();
- } else if (op == UserOperation.GetUnreadRegistrationApplicationCount) {
- const data =
- wsJsonToRes<GetUnreadRegistrationApplicationCountResponse>(msg);
- this.setState({ unreadApplicationCount: data.registration_applications });
- this.sendApplicationUnread();
- } else if (op == UserOperation.CreateComment) {
- const data = wsJsonToRes<CommentResponse>(msg);
- const mui = UserService.Instance.myUserInfo;
- if (
- mui &&
- data.recipient_ids.includes(mui.local_user_view.local_user.id)
- ) {
+
+ if (this.moderatesSomething) {
+ this.setState({ unreadReportCountRes: { state: "loading" } });
this.setState({
- unreadInboxCount: this.state.unreadInboxCount + 1,
+ unreadReportCountRes: await HttpService.client.getReportCount({
+ auth,
+ }),
});
- this.sendUnreadCount();
- notifyComment(data.comment_view, this.context.router);
}
- } else if (op == UserOperation.CreatePrivateMessage) {
- const data = wsJsonToRes<PrivateMessageResponse>(msg);
- if (
- data.private_message_view.recipient.id ==
- UserService.Instance.myUserInfo?.local_user_view.person.id
- ) {
+ if (amAdmin()) {
+ this.setState({ unreadApplicationCountRes: { state: "loading" } });
this.setState({
- unreadInboxCount: this.state.unreadInboxCount + 1,
+ unreadApplicationCountRes:
+ await HttpService.client.getUnreadRegistrationApplicationCount({
+ auth,
+ }),
});
- this.sendUnreadCount();
- notifyPrivateMessage(data.private_message_view, this.context.router);
}
}
}
- fetchUnreads() {
- console.log("Fetching inbox unreads...");
-
- const auth = myAuth();
- if (auth) {
- const unreadForm: GetUnreadCount = {
- auth,
- };
- WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm));
-
- console.log("Fetching reports...");
-
- const reportCountForm: GetReportCount = {
- auth,
- };
- WebSocketService.Instance.send(wsClient.getReportCount(reportCountForm));
-
- if (amAdmin()) {
- console.log("Fetching applications...");
-
- const applicationCountForm: GetUnreadRegistrationApplicationCount = {
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.getUnreadRegistrationApplicationCount(applicationCountForm)
- );
- }
+ get unreadInboxCount(): number {
+ if (this.state.unreadInboxCountRes.state == "success") {
+ const data = this.state.unreadInboxCountRes.data;
+ return data.replies + data.mentions + data.private_messages;
+ } else {
+ return 0;
}
}
- get currentLocation() {
- return this.context.router.history.location.pathname;
- }
-
- sendUnreadCount() {
- UserService.Instance.unreadInboxCountSub.next(this.state.unreadInboxCount);
+ get unreadReportCount(): number {
+ if (this.state.unreadReportCountRes.state == "success") {
+ const data = this.state.unreadReportCountRes.data;
+ return (
+ data.post_reports +
+ data.comment_reports +
+ (data.private_message_reports ?? 0)
+ );
+ } else {
+ return 0;
+ }
}
- sendReportUnread() {
- UserService.Instance.unreadReportCountSub.next(
- this.state.unreadReportCount
- );
+ get unreadApplicationCount(): number {
+ if (this.state.unreadApplicationCountRes.state == "success") {
+ const data = this.state.unreadApplicationCountRes.data;
+ return data.registration_applications;
+ } else {
+ return 0;
+ }
}
- sendApplicationUnread() {
- UserService.Instance.unreadApplicationCountSub.next(
- this.state.unreadApplicationCount
- );
+ get currentLocation() {
+ return this.context.router.history.location.pathname;
}
requestNotificationPermission() {
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
-import {
- CommentResponse,
- CreateComment,
- EditComment,
- Language,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
-} from "lemmy-js-client";
-import { Subscription } from "rxjs";
+import { CreateComment, EditComment, Language } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { CommentNodeI } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
-import {
- capitalizeFirstLetter,
- myAuth,
- wsClient,
- wsSubscribe,
-} from "../../utils";
+import { UserService } from "../../services";
+import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { Icon } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
* Can either be the parent, or the editable comment. The right side is a postId.
*/
node: CommentNodeI | number;
+ finished?: boolean;
edit?: boolean;
disabled?: boolean;
focus?: boolean;
- onReplyCancel?(): any;
+ onReplyCancel?(): void;
allLanguages: Language[];
siteLanguages: number[];
+ onUpsertComment(form: EditComment | CreateComment): void;
}
-interface CommentFormState {
- buttonTitle: string;
- finished: boolean;
- formId?: string;
-}
-
-export class CommentForm extends Component<CommentFormProps, CommentFormState> {
- private subscription?: Subscription;
- state: CommentFormState = {
- buttonTitle:
- typeof this.props.node === "number"
- ? capitalizeFirstLetter(i18n.t("post"))
- : this.props.edit
- ? capitalizeFirstLetter(i18n.t("save"))
- : capitalizeFirstLetter(i18n.t("reply")),
- finished: false,
- };
-
+export class CommentForm extends Component<CommentFormProps, any> {
constructor(props: any, context: any) {
super(props, context);
this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
- this.handleReplyCancel = this.handleReplyCancel.bind(this);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
- }
-
- componentWillUnmount() {
- this.subscription?.unsubscribe();
}
render() {
<MarkdownTextArea
initialContent={initialContent}
showLanguage
- buttonTitle={this.state.buttonTitle}
- finished={this.state.finished}
+ buttonTitle={this.buttonTitle}
+ finished={this.props.finished}
replyType={typeof this.props.node !== "number"}
focus={this.props.focus}
disabled={this.props.disabled}
onSubmit={this.handleCommentSubmit}
- onReplyCancel={this.handleReplyCancel}
+ onReplyCancel={this.props.onReplyCancel}
placeholder={i18n.t("comment_here")}
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
);
}
- handleCommentSubmit(msg: {
- val: string;
- formId: string;
- languageId?: number;
- }) {
- const content = msg.val;
- const language_id = msg.languageId;
- const node = this.props.node;
-
- this.setState({ formId: msg.formId });
+ get buttonTitle(): string {
+ return typeof this.props.node === "number"
+ ? capitalizeFirstLetter(i18n.t("post"))
+ : this.props.edit
+ ? capitalizeFirstLetter(i18n.t("save"))
+ : capitalizeFirstLetter(i18n.t("reply"));
+ }
- const auth = myAuth();
- if (auth) {
- if (typeof node === "number") {
- const postId = node;
- const form: CreateComment = {
+ handleCommentSubmit(content: string, form_id: string, language_id?: number) {
+ const { node, onUpsertComment, edit } = this.props;
+ if (typeof node === "number") {
+ const post_id = node;
+ onUpsertComment({
+ content,
+ post_id,
+ language_id,
+ form_id,
+ auth: myAuthRequired(),
+ });
+ } else {
+ if (edit) {
+ const comment_id = node.comment_view.comment.id;
+ onUpsertComment({
content,
- form_id: this.state.formId,
- post_id: postId,
+ comment_id,
+ form_id,
language_id,
- auth,
- };
- WebSocketService.Instance.send(wsClient.createComment(form));
+ auth: myAuthRequired(),
+ });
} else {
- if (this.props.edit) {
- const form: EditComment = {
- content,
- form_id: this.state.formId,
- comment_id: node.comment_view.comment.id,
- language_id,
- auth,
- };
- WebSocketService.Instance.send(wsClient.editComment(form));
- } else {
- const form: CreateComment = {
- content,
- form_id: this.state.formId,
- post_id: node.comment_view.post.id,
- parent_id: node.comment_view.comment.id,
- language_id,
- auth,
- };
- WebSocketService.Instance.send(wsClient.createComment(form));
- }
- }
- }
- }
-
- handleReplyCancel() {
- this.props.onReplyCancel?.();
- }
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
-
- // Only do the showing and hiding if logged in
- if (UserService.Instance.myUserInfo) {
- if (
- op == UserOperation.CreateComment ||
- op == UserOperation.EditComment
- ) {
- const data = wsJsonToRes<CommentResponse>(msg);
-
- // This only finishes this form, if the randomly generated form_id matches the one received
- if (this.state.formId && this.state.formId == data.form_id) {
- this.setState({ finished: true });
-
- // Necessary because it broke tribute for some reason
- this.setState({ finished: false });
- }
+ const post_id = node.comment_view.post.id;
+ const parent_id = node.comment_view.comment.id;
+ this.props.onUpsertComment({
+ content,
+ parent_id,
+ post_id,
+ form_id,
+ language_id,
+ auth: myAuthRequired(),
+ });
}
}
}
import classNames from "classnames";
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import { Link } from "inferno-router";
import {
AddAdmin,
BanFromCommunity,
BanPerson,
BlockPerson,
+ CommentId,
CommentReplyView,
CommentView,
CommunityModeratorView,
+ CreateComment,
CreateCommentLike,
CreateCommentReport,
DeleteComment,
DistinguishComment,
+ EditComment,
GetComments,
Language,
MarkCommentReplyAsRead,
CommentNodeI,
CommentViewType,
PurgeType,
+ VoteType,
} from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
import {
amCommunityCreator,
canAdmin,
mdToHtml,
mdToHtmlNoImages,
myAuth,
+ myAuthRequired,
+ newVote,
numToSI,
setupTippy,
showScores,
- wsClient,
} from "../../utils";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
showPurgeDialog: boolean;
purgeReason?: string;
purgeType: PurgeType;
- purgeLoading: boolean;
showConfirmTransferSite: boolean;
showConfirmTransferCommunity: boolean;
showConfirmAppointAsMod: boolean;
showAdvanced: boolean;
showReportDialog: boolean;
reportReason?: string;
- my_vote?: number;
- score: number;
- upvotes: number;
- downvotes: number;
- readLoading: boolean;
+ createOrEditCommentLoading: boolean;
+ upvoteLoading: boolean;
+ downvoteLoading: boolean;
saveLoading: boolean;
+ readLoading: boolean;
+ blockPersonLoading: boolean;
+ deleteLoading: boolean;
+ removeLoading: boolean;
+ distinguishLoading: boolean;
+ banLoading: boolean;
+ addModLoading: boolean;
+ addAdminLoading: boolean;
+ transferCommunityLoading: boolean;
+ fetchChildrenLoading: boolean;
+ reportLoading: boolean;
+ purgeLoading: boolean;
}
interface CommentNodeProps {
allLanguages: Language[];
siteLanguages: number[];
hideImages?: boolean;
+ finished: Map<CommentId, boolean | undefined>;
+ onSaveComment(form: SaveComment): void;
+ onCommentReplyRead(form: MarkCommentReplyAsRead): void;
+ onPersonMentionRead(form: MarkPersonMentionAsRead): void;
+ onCreateComment(form: EditComment | CreateComment): void;
+ onEditComment(form: EditComment | CreateComment): void;
+ onCommentVote(form: CreateCommentLike): void;
+ onBlockPerson(form: BlockPerson): void;
+ onDeleteComment(form: DeleteComment): void;
+ onRemoveComment(form: RemoveComment): void;
+ onDistinguishComment(form: DistinguishComment): void;
+ onAddModToCommunity(form: AddModToCommunity): void;
+ onAddAdmin(form: AddAdmin): void;
+ onBanPersonFromCommunity(form: BanFromCommunity): void;
+ onBanPerson(form: BanPerson): void;
+ onTransferCommunity(form: TransferCommunity): void;
+ onFetchChildren?(form: GetComments): void;
+ onCommentReport(form: CreateCommentReport): void;
+ onPurgePerson(form: PurgePerson): void;
+ onPurgeComment(form: PurgeComment): void;
}
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
removeData: false,
banType: BanType.Community,
showPurgeDialog: false,
- purgeLoading: false,
purgeType: PurgeType.Person,
collapsed: false,
viewSource: false,
showConfirmAppointAsMod: false,
showConfirmAppointAsAdmin: false,
showReportDialog: false,
- my_vote: this.props.node.comment_view.my_vote,
- score: this.props.node.comment_view.counts.score,
- upvotes: this.props.node.comment_view.counts.upvotes,
- downvotes: this.props.node.comment_view.counts.downvotes,
- readLoading: false,
+ createOrEditCommentLoading: false,
+ upvoteLoading: false,
+ downvoteLoading: false,
saveLoading: false,
+ readLoading: false,
+ blockPersonLoading: false,
+ deleteLoading: false,
+ removeLoading: false,
+ distinguishLoading: false,
+ banLoading: false,
+ addModLoading: false,
+ addAdminLoading: false,
+ transferCommunityLoading: false,
+ fetchChildrenLoading: false,
+ reportLoading: false,
+ purgeLoading: false,
};
constructor(props: any, context: any) {
super(props, context);
this.handleReplyCancel = this.handleReplyCancel.bind(this);
- this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
- this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
}
- // TODO see if there's a better way to do this, and all willReceiveProps
- componentWillReceiveProps(nextProps: CommentNodeProps) {
- const cv = nextProps.node.comment_view;
- this.setState({
- my_vote: cv.my_vote,
- upvotes: cv.counts.upvotes,
- downvotes: cv.counts.downvotes,
- score: cv.counts.score,
- readLoading: false,
- saveLoading: false,
- });
+ get commentView(): CommentView {
+ return this.props.node.comment_view;
+ }
+
+ get commentId(): CommentId {
+ return this.commentView.comment.id;
+ }
+
+ componentWillReceiveProps(
+ nextProps: Readonly<{ children?: InfernoNode } & CommentNodeProps>
+ ): void {
+ if (this.props != nextProps) {
+ this.setState({
+ showReply: false,
+ showEdit: false,
+ showRemoveDialog: false,
+ showBanDialog: false,
+ removeData: false,
+ banType: BanType.Community,
+ showPurgeDialog: false,
+ purgeType: PurgeType.Person,
+ collapsed: false,
+ viewSource: false,
+ showAdvanced: false,
+ showConfirmTransferSite: false,
+ showConfirmTransferCommunity: false,
+ showConfirmAppointAsMod: false,
+ showConfirmAppointAsAdmin: false,
+ showReportDialog: false,
+ createOrEditCommentLoading: false,
+ upvoteLoading: false,
+ downvoteLoading: false,
+ saveLoading: false,
+ readLoading: false,
+ blockPersonLoading: false,
+ deleteLoading: false,
+ removeLoading: false,
+ distinguishLoading: false,
+ banLoading: false,
+ addModLoading: false,
+ addAdminLoading: false,
+ transferCommunityLoading: false,
+ fetchChildrenLoading: false,
+ reportLoading: false,
+ purgeLoading: false,
+ });
+ }
}
render() {
const node = this.props.node;
- const cv = this.props.node.comment_view;
+ const cv = this.commentView;
const purgeTypeText =
this.state.purgeType == PurgeType.Comment
? i18n.t("purge_comment")
: `${i18n.t("purge")} ${cv.creator.name}`;
- const canMod_ =
- canMod(cv.creator.id, this.props.moderators, this.props.admins) &&
- cv.community.local;
- const canModOnSelf =
- canMod(
- cv.creator.id,
- this.props.moderators,
- this.props.admins,
- UserService.Instance.myUserInfo,
- true
- ) && cv.community.local;
- const canAdmin_ =
- canAdmin(cv.creator.id, this.props.admins) && cv.community.local;
- const canAdminOnSelf =
- canAdmin(
- cv.creator.id,
- this.props.admins,
- UserService.Instance.myUserInfo,
- true
- ) && cv.community.local;
+ const canMod_ = canMod(
+ cv.creator.id,
+ this.props.moderators,
+ this.props.admins
+ );
+ const canModOnSelf = canMod(
+ cv.creator.id,
+ this.props.moderators,
+ this.props.admins,
+ UserService.Instance.myUserInfo,
+ true
+ );
+ const canAdmin_ = canAdmin(cv.creator.id, this.props.admins);
+ const canAdminOnSelf = canAdmin(
+ cv.creator.id,
+ this.props.admins,
+ UserService.Instance.myUserInfo,
+ true
+ );
const isMod_ = isMod(cv.creator.id, this.props.moderators);
- const isAdmin_ =
- isAdmin(cv.creator.id, this.props.admins) && cv.community.local;
+ const isAdmin_ = isAdmin(cv.creator.id, this.props.admins);
const amCommunityCreator_ = amCommunityCreator(
cv.creator.id,
this.props.moderators
id={`comment-${cv.comment.id}`}
className={classNames(`details comment-node py-2`, {
"border-top border-light": !this.props.noBorder,
- mark:
- this.isCommentNew ||
- this.props.node.comment_view.comment.distinguished,
+ mark: this.isCommentNew || this.commentView.comment.distinguished,
})}
style={
!this.props.noIndent && this.props.node.depth
<>
<a
className={`unselectable pointer ${this.scoreColor}`}
- onClick={this.handleCommentUpvote}
+ onClick={linkEvent(this, this.handleUpvote)}
data-tippy-content={this.pointsTippy}
>
- <span
- className="mr-1 font-weight-bold"
- aria-label={i18n.t("number_of_points", {
- count: Number(this.state.score),
- formattedCount: numToSI(this.state.score),
- })}
- >
- {numToSI(this.state.score)}
- </span>
+ {this.state.upvoteLoading ? (
+ <Spinner />
+ ) : (
+ <span
+ className="mr-1 font-weight-bold"
+ aria-label={i18n.t("number_of_points", {
+ count: Number(this.commentView.counts.score),
+ formattedCount: numToSI(
+ this.commentView.counts.score
+ ),
+ })}
+ >
+ {numToSI(this.commentView.counts.score)}
+ </span>
+ )}
</a>
<span className="mr-1">•</span>
</>
edit
onReplyCancel={this.handleReplyCancel}
disabled={this.props.locked}
+ finished={this.props.finished.get(
+ this.props.node.comment_view.comment.id
+ )}
focus
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
+ onUpsertComment={this.props.onEditComment}
/>
)}
{!this.state.showEdit && !this.state.collapsed && (
{this.props.markable && (
<button
className="btn btn-link btn-animate text-muted"
- onClick={linkEvent(this, this.handleMarkRead)}
+ onClick={linkEvent(this, this.handleMarkAsRead)}
data-tippy-content={
this.commentReplyOrMentionRead
? i18n.t("mark_as_unread")
}
>
{this.state.readLoading ? (
- this.loadingIcon
+ <Spinner />
) : (
<Icon
icon="check"
<>
<button
className={`btn btn-link btn-animate ${
- this.state.my_vote === 1 ? "text-info" : "text-muted"
+ this.commentView.my_vote === 1
+ ? "text-info"
+ : "text-muted"
}`}
- onClick={this.handleCommentUpvote}
+ onClick={linkEvent(this, this.handleUpvote)}
data-tippy-content={i18n.t("upvote")}
aria-label={i18n.t("upvote")}
- aria-pressed={this.state.my_vote === 1}
+ aria-pressed={this.commentView.my_vote === 1}
>
- <Icon icon="arrow-up1" classes="icon-inline" />
- {showScores() &&
- this.state.upvotes !== this.state.score && (
- <span className="ml-1">
- {numToSI(this.state.upvotes)}
- </span>
- )}
+ {this.state.upvoteLoading ? (
+ <Spinner />
+ ) : (
+ <>
+ <Icon icon="arrow-up1" classes="icon-inline" />
+ {showScores() &&
+ this.commentView.counts.upvotes !==
+ this.commentView.counts.score && (
+ <span className="ml-1">
+ {numToSI(this.commentView.counts.upvotes)}
+ </span>
+ )}
+ </>
+ )}
</button>
{this.props.enableDownvotes && (
<button
className={`btn btn-link btn-animate ${
- this.state.my_vote === -1
+ this.commentView.my_vote === -1
? "text-danger"
: "text-muted"
}`}
- onClick={this.handleCommentDownvote}
+ onClick={linkEvent(this, this.handleDownvote)}
data-tippy-content={i18n.t("downvote")}
aria-label={i18n.t("downvote")}
- aria-pressed={this.state.my_vote === -1}
+ aria-pressed={this.commentView.my_vote === -1}
>
- <Icon icon="arrow-down1" classes="icon-inline" />
- {showScores() &&
- this.state.upvotes !== this.state.score && (
- <span className="ml-1">
- {numToSI(this.state.downvotes)}
- </span>
- )}
+ {this.state.downvoteLoading ? (
+ <Spinner />
+ ) : (
+ <>
+ <Icon icon="arrow-down1" classes="icon-inline" />
+ {showScores() &&
+ this.commentView.counts.upvotes !==
+ this.commentView.counts.score && (
+ <span className="ml-1">
+ {numToSI(this.commentView.counts.downvotes)}
+ </span>
+ )}
+ </>
+ )}
</button>
)}
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleBlockUserClick
+ this.handleBlockPerson
)}
data-tippy-content={i18n.t("block_user")}
aria-label={i18n.t("block_user")}
>
- <Icon icon="slash" />
+ {this.state.blockPersonLoading ? (
+ <Spinner />
+ ) : (
+ <Icon icon="slash" />
+ )}
</button>
</>
)}
<button
className="btn btn-link btn-animate text-muted"
- onClick={linkEvent(
- this,
- this.handleSaveCommentClick
- )}
+ onClick={linkEvent(this, this.handleSaveComment)}
data-tippy-content={
cv.saved ? i18n.t("unsave") : i18n.t("save")
}
}
>
{this.state.saveLoading ? (
- this.loadingIcon
+ <Spinner />
) : (
<Icon
icon="star"
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleDeleteClick
+ this.handleDeleteComment
)}
data-tippy-content={
!cv.comment.deleted
: i18n.t("restore")
}
>
- <Icon
- icon="trash"
- classes={`icon-inline ${
- cv.comment.deleted && "text-danger"
- }`}
- />
+ {this.state.deleteLoading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="trash"
+ classes={`icon-inline ${
+ cv.comment.deleted && "text-danger"
+ }`}
+ />
+ )}
</button>
{(canModOnSelf || canAdminOnSelf) && (
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleDistinguishClick
+ this.handleDistinguishComment
)}
data-tippy-content={
!cv.comment.distinguished
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleModRemoveSubmit
+ this.handleRemoveComment
)}
aria-label={i18n.t("restore")}
>
- {i18n.t("restore")}
+ {this.state.removeLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("restore")
+ )}
</button>
)}
</>
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleModBanFromCommunitySubmit
+ this.handleBanPersonFromCommunity
)}
aria-label={i18n.t("unban")}
>
- {i18n.t("unban")}
+ {this.state.banLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("unban")
+ )}
</button>
))}
{!cv.creator_banned_from_community &&
)}
aria-label={i18n.t("yes")}
>
- {i18n.t("yes")}
+ {this.state.addModLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("yes")
+ )}
</button>
<button
className="btn btn-link btn-animate text-muted"
)}
aria-label={i18n.t("yes")}
>
- {i18n.t("yes")}
+ {this.state.transferCommunityLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("yes")
+ )}
</button>
<button
className="btn btn-link btn-animate text-muted"
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleModBanSubmit
+ this.handleBanPerson
)}
aria-label={i18n.t("unban_from_site")}
>
- {i18n.t("unban_from_site")}
+ {this.state.banLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("unban_from_site")
+ )}
</button>
)}
</>
)}
aria-label={i18n.t("yes")}
>
- {i18n.t("yes")}
+ {this.state.addAdminLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("yes")
+ )}
</button>
<button
className="btn btn-link btn-animate text-muted"
className="btn btn-link text-muted"
onClick={linkEvent(this, this.handleFetchChildren)}
>
- {i18n.t("x_more_replies", {
- count: node.comment_view.counts.child_count,
- formattedCount: numToSI(node.comment_view.counts.child_count),
- })}{" "}
- âž”
+ {this.state.fetchChildrenLoading ? (
+ <Spinner />
+ ) : (
+ <>
+ {i18n.t("x_more_replies", {
+ count: node.comment_view.counts.child_count,
+ formattedCount: numToSI(
+ node.comment_view.counts.child_count
+ ),
+ })}{" "}
+ âž”
+ </>
+ )}
</button>
</div>
)}
{this.state.showRemoveDialog && (
<form
className="form-inline"
- onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
+ onSubmit={linkEvent(this, this.handleRemoveComment)}
>
<label
className="sr-only"
{this.state.showReportDialog && (
<form
className="form-inline"
- onSubmit={linkEvent(this, this.handleReportSubmit)}
+ onSubmit={linkEvent(this, this.handleReportComment)}
>
<label
className="sr-only"
className="btn btn-secondary"
aria-label={i18n.t("ban")}
>
- {i18n.t("ban")} {cv.creator.name}
+ {this.state.banLoading ? (
+ <Spinner />
+ ) : (
+ <span>
+ {i18n.t("ban")} {cv.creator.name}
+ </span>
+ )}
</button>
</div>
</form>
)}
{this.state.showPurgeDialog && (
- <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
+ <form onSubmit={linkEvent(this, this.handlePurgeBothSubmit)}>
<PurgeWarning />
<label className="sr-only" htmlFor="purge-reason">
{i18n.t("reason")}
node={node}
onReplyCancel={this.handleReplyCancel}
disabled={this.props.locked}
+ finished={this.props.finished.get(
+ this.props.node.comment_view.comment.id
+ )}
focus
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
+ onUpsertComment={this.props.onCreateComment}
/>
)}
{!this.state.collapsed && node.children.length > 0 && (
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
hideImages={this.props.hideImages}
+ finished={this.props.finished}
+ onCommentReplyRead={this.props.onCommentReplyRead}
+ onPersonMentionRead={this.props.onPersonMentionRead}
+ onCreateComment={this.props.onCreateComment}
+ onEditComment={this.props.onEditComment}
+ onCommentVote={this.props.onCommentVote}
+ onBlockPerson={this.props.onBlockPerson}
+ onSaveComment={this.props.onSaveComment}
+ onDeleteComment={this.props.onDeleteComment}
+ onRemoveComment={this.props.onRemoveComment}
+ onDistinguishComment={this.props.onDistinguishComment}
+ onAddModToCommunity={this.props.onAddModToCommunity}
+ onAddAdmin={this.props.onAddAdmin}
+ onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
+ onBanPerson={this.props.onBanPerson}
+ onTransferCommunity={this.props.onTransferCommunity}
+ onFetchChildren={this.props.onFetchChildren}
+ onCommentReport={this.props.onCommentReport}
+ onPurgePerson={this.props.onPurgePerson}
+ onPurgeComment={this.props.onPurgeComment}
/>
)}
{/* A collapsed clearfix */}
}
get commentReplyOrMentionRead(): boolean {
- const cv = this.props.node.comment_view;
+ const cv = this.commentView;
if (this.isPersonMentionType(cv)) {
return cv.person_mention.read;
}
linkBtn(small = false) {
- const cv = this.props.node.comment_view;
+ const cv = this.commentView;
const classnames = classNames("btn btn-link btn-animate text-muted", {
"btn-sm": small,
});
);
}
- get loadingIcon() {
- return <Spinner />;
- }
-
get myComment(): boolean {
return (
UserService.Instance.myUserInfo?.local_user_view.person.id ==
- this.props.node.comment_view.creator.id
+ this.commentView.creator.id
);
}
get isPostCreator(): boolean {
- return (
- this.props.node.comment_view.creator.id ==
- this.props.node.comment_view.post.creator_id
- );
+ return this.commentView.creator.id == this.commentView.post.creator_id;
+ }
+
+ get scoreColor() {
+ if (this.commentView.my_vote == 1) {
+ return "text-info";
+ } else if (this.commentView.my_vote == -1) {
+ return "text-danger";
+ } else {
+ return "text-muted";
+ }
+ }
+
+ get pointsTippy(): string {
+ const points = i18n.t("number_of_points", {
+ count: Number(this.commentView.counts.score),
+ formattedCount: numToSI(this.commentView.counts.score),
+ });
+
+ const upvotes = i18n.t("number_of_upvotes", {
+ count: Number(this.commentView.counts.upvotes),
+ formattedCount: numToSI(this.commentView.counts.upvotes),
+ });
+
+ const downvotes = i18n.t("number_of_downvotes", {
+ count: Number(this.commentView.counts.downvotes),
+ formattedCount: numToSI(this.commentView.counts.downvotes),
+ });
+
+ return `${points} • ${upvotes} • ${downvotes}`;
+ }
+
+ get expandText(): string {
+ return this.state.collapsed ? i18n.t("expand") : i18n.t("collapse");
}
get commentUnlessRemoved(): string {
- const comment = this.props.node.comment_view.comment;
+ const comment = this.commentView.comment;
return comment.removed
? `*${i18n.t("removed")}*`
: comment.deleted
i.setState({ showEdit: true });
}
- handleBlockUserClick(i: CommentNode) {
- const auth = myAuth();
- if (auth) {
- const blockUserForm: BlockPerson = {
- person_id: i.props.node.comment_view.creator.id,
- block: true,
- auth,
- };
- WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
- }
- }
-
- handleDeleteClick(i: CommentNode) {
- const comment = i.props.node.comment_view.comment;
- const auth = myAuth();
- if (auth) {
- const deleteForm: DeleteComment = {
- comment_id: comment.id,
- deleted: !comment.deleted,
- auth,
- };
- WebSocketService.Instance.send(wsClient.deleteComment(deleteForm));
- }
- }
-
- handleSaveCommentClick(i: CommentNode) {
- const cv = i.props.node.comment_view;
- const save = cv.saved == undefined ? true : !cv.saved;
- const auth = myAuth();
- if (auth) {
- const form: SaveComment = {
- comment_id: cv.comment.id,
- save,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.saveComment(form));
-
- i.setState({ saveLoading: true });
- }
- }
-
handleReplyCancel() {
this.setState({ showReply: false, showEdit: false });
}
- handleCommentUpvote(event: any) {
- event.preventDefault();
- const myVote = this.state.my_vote;
- const newVote = myVote == 1 ? 0 : 1;
-
- if (myVote == 1) {
- this.setState({
- score: this.state.score - 1,
- upvotes: this.state.upvotes - 1,
- });
- } else if (myVote == -1) {
- this.setState({
- downvotes: this.state.downvotes - 1,
- upvotes: this.state.upvotes + 1,
- score: this.state.score + 2,
- });
- } else {
- this.setState({
- score: this.state.score + 1,
- upvotes: this.state.upvotes + 1,
- });
- }
-
- this.setState({ my_vote: newVote });
-
- const auth = myAuth();
- if (auth) {
- const form: CreateCommentLike = {
- comment_id: this.props.node.comment_view.comment.id,
- score: newVote,
- auth,
- };
- WebSocketService.Instance.send(wsClient.likeComment(form));
- setupTippy();
- }
- }
-
- handleCommentDownvote(event: any) {
- event.preventDefault();
- const myVote = this.state.my_vote;
- const newVote = myVote == -1 ? 0 : -1;
-
- if (myVote == 1) {
- this.setState({
- downvotes: this.state.downvotes + 1,
- upvotes: this.state.upvotes - 1,
- score: this.state.score - 2,
- });
- } else if (myVote == -1) {
- this.setState({
- downvotes: this.state.downvotes - 1,
- score: this.state.score + 1,
- });
- } else {
- this.setState({
- downvotes: this.state.downvotes + 1,
- score: this.state.score - 1,
- });
- }
-
- this.setState({ my_vote: newVote });
-
- const auth = myAuth();
- if (auth) {
- const form: CreateCommentLike = {
- comment_id: this.props.node.comment_view.comment.id,
- score: newVote,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.likeComment(form));
- setupTippy();
- }
- }
-
handleShowReportDialog(i: CommentNode) {
i.setState({ showReportDialog: !i.state.showReportDialog });
}
i.setState({ reportReason: event.target.value });
}
- handleReportSubmit(i: CommentNode) {
- const comment = i.props.node.comment_view.comment;
- const reason = i.state.reportReason;
- const auth = myAuth();
- if (reason && auth) {
- const form: CreateCommentReport = {
- comment_id: comment.id,
- reason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.createCommentReport(form));
- i.setState({ showReportDialog: false });
- }
- }
-
handleModRemoveShow(i: CommentNode) {
i.setState({
showRemoveDialog: !i.state.showRemoveDialog,
i.setState({ removeData: event.target.checked });
}
- handleModRemoveSubmit(i: CommentNode) {
- const comment = i.props.node.comment_view.comment;
- const auth = myAuth();
- if (auth) {
- const form: RemoveComment = {
- comment_id: comment.id,
- removed: !comment.removed,
- reason: i.state.removeReason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.removeComment(form));
-
- i.setState({ showRemoveDialog: false });
- }
- }
-
- handleDistinguishClick(i: CommentNode) {
- const comment = i.props.node.comment_view.comment;
- const auth = myAuth();
- if (auth) {
- const form: DistinguishComment = {
- comment_id: comment.id,
- distinguished: !comment.distinguished,
- auth,
- };
- WebSocketService.Instance.send(wsClient.editComment(form));
- i.setState(i.state);
- }
- }
-
isPersonMentionType(
item: CommentView | PersonMentionView | CommentReplyView
): item is PersonMentionView {
return (item as CommentReplyView).comment_reply?.id !== undefined;
}
- handleMarkRead(i: CommentNode) {
- const auth = myAuth();
- if (auth) {
- if (i.isPersonMentionType(i.props.node.comment_view)) {
- const form: MarkPersonMentionAsRead = {
- person_mention_id: i.props.node.comment_view.person_mention.id,
- read: !i.props.node.comment_view.person_mention.read,
- auth,
- };
- WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
- } else if (i.isCommentReplyType(i.props.node.comment_view)) {
- const form: MarkCommentReplyAsRead = {
- comment_reply_id: i.props.node.comment_view.comment_reply.id,
- read: !i.props.node.comment_view.comment_reply.read,
- auth,
- };
- WebSocketService.Instance.send(wsClient.markCommentReplyAsRead(form));
- }
-
- i.setState({ readLoading: true });
- }
- }
-
handleModBanFromCommunityShow(i: CommentNode) {
i.setState({
showBanDialog: true,
i.setState({ banExpireDays: event.target.value });
}
- handleModBanFromCommunitySubmit(i: CommentNode) {
- i.setState({ banType: BanType.Community });
- i.handleModBanBothSubmit(i);
- }
-
- handleModBanSubmit(i: CommentNode) {
- i.setState({ banType: BanType.Site });
- i.handleModBanBothSubmit(i);
- }
-
- handleModBanBothSubmit(i: CommentNode) {
- const cv = i.props.node.comment_view;
- const auth = myAuth();
- if (auth) {
- if (i.state.banType == BanType.Community) {
- // If its an unban, restore all their data
- const ban = !cv.creator_banned_from_community;
- if (ban == false) {
- i.setState({ removeData: false });
- }
- const form: BanFromCommunity = {
- person_id: cv.creator.id,
- community_id: cv.community.id,
- ban,
- remove_data: i.state.removeData,
- reason: i.state.banReason,
- expires: futureDaysToUnixTime(i.state.banExpireDays),
- auth,
- };
- WebSocketService.Instance.send(wsClient.banFromCommunity(form));
- } else {
- // If its an unban, restore all their data
- const ban = !cv.creator.banned;
- if (ban == false) {
- i.setState({ removeData: false });
- }
- const form: BanPerson = {
- person_id: cv.creator.id,
- ban,
- remove_data: i.state.removeData,
- reason: i.state.banReason,
- expires: futureDaysToUnixTime(i.state.banExpireDays),
- auth,
- };
- WebSocketService.Instance.send(wsClient.banPerson(form));
- }
-
- i.setState({ showBanDialog: false });
- }
- }
-
handlePurgePersonShow(i: CommentNode) {
i.setState({
showPurgeDialog: true,
i.setState({ purgeReason: event.target.value });
}
- handlePurgeSubmit(i: CommentNode, event: any) {
- event.preventDefault();
- const auth = myAuth();
- if (auth) {
- if (i.state.purgeType == PurgeType.Person) {
- const form: PurgePerson = {
- person_id: i.props.node.comment_view.creator.id,
- reason: i.state.purgeReason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.purgePerson(form));
- } else if (i.state.purgeType == PurgeType.Comment) {
- const form: PurgeComment = {
- comment_id: i.props.node.comment_view.comment.id,
- reason: i.state.purgeReason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.purgeComment(form));
- }
-
- i.setState({ purgeLoading: true });
- }
- }
-
handleShowConfirmAppointAsMod(i: CommentNode) {
i.setState({ showConfirmAppointAsMod: true });
}
i.setState({ showConfirmAppointAsMod: false });
}
- handleAddModToCommunity(i: CommentNode) {
- const cv = i.props.node.comment_view;
- const auth = myAuth();
- if (auth) {
- const form: AddModToCommunity = {
- person_id: cv.creator.id,
- community_id: cv.community.id,
- added: !isMod(cv.creator.id, i.props.moderators),
- auth,
- };
- WebSocketService.Instance.send(wsClient.addModToCommunity(form));
- i.setState({ showConfirmAppointAsMod: false });
- }
- }
-
handleShowConfirmAppointAsAdmin(i: CommentNode) {
i.setState({ showConfirmAppointAsAdmin: true });
}
i.setState({ showConfirmAppointAsAdmin: false });
}
- handleAddAdmin(i: CommentNode) {
- const auth = myAuth();
- if (auth) {
- const creatorId = i.props.node.comment_view.creator.id;
- const form: AddAdmin = {
- person_id: creatorId,
- added: !isAdmin(creatorId, i.props.admins),
- auth,
- };
- WebSocketService.Instance.send(wsClient.addAdmin(form));
- i.setState({ showConfirmAppointAsAdmin: false });
- }
- }
-
handleShowConfirmTransferCommunity(i: CommentNode) {
i.setState({ showConfirmTransferCommunity: true });
}
i.setState({ showConfirmTransferCommunity: false });
}
- handleTransferCommunity(i: CommentNode) {
- const cv = i.props.node.comment_view;
- const auth = myAuth();
- if (auth) {
- const form: TransferCommunity = {
- community_id: cv.community.id,
- person_id: cv.creator.id,
- auth,
- };
- WebSocketService.Instance.send(wsClient.transferCommunity(form));
- i.setState({ showConfirmTransferCommunity: false });
- }
- }
-
handleShowConfirmTransferSite(i: CommentNode) {
i.setState({ showConfirmTransferSite: true });
}
get isCommentNew(): boolean {
const now = moment.utc().subtract(10, "minutes");
- const then = moment.utc(this.props.node.comment_view.comment.published);
+ const then = moment.utc(this.commentView.comment.published);
return now.isBefore(then);
}
setupTippy();
}
- handleFetchChildren(i: CommentNode) {
- const form: GetComments = {
- post_id: i.props.node.comment_view.post.id,
- parent_id: i.props.node.comment_view.comment.id,
- max_depth: commentTreeMaxDepth,
- limit: 999, // TODO
- type_: "All",
- saved_only: false,
- auth: myAuth(false),
- };
+ handleSaveComment(i: CommentNode) {
+ i.setState({ saveLoading: true });
+
+ i.props.onSaveComment({
+ comment_id: i.commentView.comment.id,
+ save: !i.commentView.saved,
+ auth: myAuthRequired(),
+ });
+ }
- WebSocketService.Instance.send(wsClient.getComments(form));
+ handleUpvote(i: CommentNode) {
+ i.setState({ upvoteLoading: true });
+ i.props.onCommentVote({
+ comment_id: i.commentId,
+ score: newVote(VoteType.Upvote, i.commentView.my_vote),
+ auth: myAuthRequired(),
+ });
}
- get scoreColor() {
- if (this.state.my_vote == 1) {
- return "text-info";
- } else if (this.state.my_vote == -1) {
- return "text-danger";
+ handleDownvote(i: CommentNode) {
+ i.setState({ downvoteLoading: true });
+ i.props.onCommentVote({
+ comment_id: i.commentId,
+ score: newVote(VoteType.Downvote, i.commentView.my_vote),
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleBlockPerson(i: CommentNode) {
+ i.setState({ blockPersonLoading: true });
+ i.props.onBlockPerson({
+ person_id: i.commentView.creator.id,
+ block: true,
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleMarkAsRead(i: CommentNode) {
+ i.setState({ readLoading: true });
+ const cv = i.commentView;
+ if (i.isPersonMentionType(cv)) {
+ i.props.onPersonMentionRead({
+ person_mention_id: cv.person_mention.id,
+ read: !cv.person_mention.read,
+ auth: myAuthRequired(),
+ });
+ } else if (i.isCommentReplyType(cv)) {
+ i.props.onCommentReplyRead({
+ comment_reply_id: cv.comment_reply.id,
+ read: !cv.comment_reply.read,
+ auth: myAuthRequired(),
+ });
+ }
+ }
+
+ handleDeleteComment(i: CommentNode) {
+ i.setState({ deleteLoading: true });
+ i.props.onDeleteComment({
+ comment_id: i.commentId,
+ deleted: !i.commentView.comment.deleted,
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleRemoveComment(i: CommentNode, event: any) {
+ event.preventDefault();
+ i.setState({ removeLoading: true });
+ i.props.onRemoveComment({
+ comment_id: i.commentId,
+ removed: !i.commentView.comment.removed,
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleDistinguishComment(i: CommentNode) {
+ i.setState({ distinguishLoading: true });
+ i.props.onDistinguishComment({
+ comment_id: i.commentId,
+ distinguished: !i.commentView.comment.distinguished,
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleBanPersonFromCommunity(i: CommentNode) {
+ i.setState({ banLoading: true });
+ i.props.onBanPersonFromCommunity({
+ community_id: i.commentView.community.id,
+ person_id: i.commentView.creator.id,
+ ban: !i.commentView.creator_banned_from_community,
+ reason: i.state.banReason,
+ remove_data: i.state.removeData,
+ expires: futureDaysToUnixTime(i.state.banExpireDays),
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleBanPerson(i: CommentNode) {
+ i.setState({ banLoading: true });
+ i.props.onBanPerson({
+ person_id: i.commentView.creator.id,
+ ban: !i.commentView.creator_banned_from_community,
+ reason: i.state.banReason,
+ remove_data: i.state.removeData,
+ expires: futureDaysToUnixTime(i.state.banExpireDays),
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleModBanBothSubmit(i: CommentNode, event: any) {
+ event.preventDefault();
+ if (i.state.banType == BanType.Community) {
+ i.handleBanPersonFromCommunity(i);
} else {
- return "text-muted";
+ i.handleBanPerson(i);
}
}
- get pointsTippy(): string {
- const points = i18n.t("number_of_points", {
- count: Number(this.state.score),
- formattedCount: numToSI(this.state.score),
+ handleAddModToCommunity(i: CommentNode) {
+ i.setState({ addModLoading: true });
+
+ const added = !isMod(i.commentView.comment.creator_id, i.props.moderators);
+ i.props.onAddModToCommunity({
+ community_id: i.commentView.community.id,
+ person_id: i.commentView.creator.id,
+ added,
+ auth: myAuthRequired(),
});
+ }
- const upvotes = i18n.t("number_of_upvotes", {
- count: Number(this.state.upvotes),
- formattedCount: numToSI(this.state.upvotes),
+ handleAddAdmin(i: CommentNode) {
+ i.setState({ addAdminLoading: true });
+
+ const added = !isAdmin(i.commentView.comment.creator_id, i.props.admins);
+ i.props.onAddAdmin({
+ person_id: i.commentView.creator.id,
+ added,
+ auth: myAuthRequired(),
});
+ }
- const downvotes = i18n.t("number_of_downvotes", {
- count: Number(this.state.downvotes),
- formattedCount: numToSI(this.state.downvotes),
+ handleTransferCommunity(i: CommentNode) {
+ i.setState({ transferCommunityLoading: true });
+ i.props.onTransferCommunity({
+ community_id: i.commentView.community.id,
+ person_id: i.commentView.creator.id,
+ auth: myAuthRequired(),
});
+ }
- return `${points} • ${upvotes} • ${downvotes}`;
+ handleReportComment(i: CommentNode, event: any) {
+ event.preventDefault();
+ i.setState({ reportLoading: true });
+ i.props.onCommentReport({
+ comment_id: i.commentId,
+ reason: i.state.reportReason ?? "",
+ auth: myAuthRequired(),
+ });
}
- get expandText(): string {
- return this.state.collapsed ? i18n.t("expand") : i18n.t("collapse");
+ handlePurgeBothSubmit(i: CommentNode, event: any) {
+ event.preventDefault();
+ i.setState({ purgeLoading: true });
+
+ if (i.state.purgeType == PurgeType.Person) {
+ i.props.onPurgePerson({
+ person_id: i.commentView.creator.id,
+ reason: i.state.purgeReason,
+ auth: myAuthRequired(),
+ });
+ } else {
+ i.props.onPurgeComment({
+ comment_id: i.commentId,
+ reason: i.state.purgeReason,
+ auth: myAuthRequired(),
+ });
+ }
+ }
+
+ handleFetchChildren(i: CommentNode) {
+ i.setState({ fetchChildrenLoading: true });
+ i.props.onFetchChildren?.({
+ parent_id: i.commentId,
+ max_depth: commentTreeMaxDepth,
+ limit: 999, // TODO
+ type_: "All",
+ saved_only: false,
+ auth: myAuth(),
+ });
}
}
import { Component } from "inferno";
-import { CommunityModeratorView, Language, PersonView } from "lemmy-js-client";
+import {
+ AddAdmin,
+ AddModToCommunity,
+ BanFromCommunity,
+ BanPerson,
+ BlockPerson,
+ CommentId,
+ CommunityModeratorView,
+ CreateComment,
+ CreateCommentLike,
+ CreateCommentReport,
+ DeleteComment,
+ DistinguishComment,
+ EditComment,
+ GetComments,
+ Language,
+ MarkCommentReplyAsRead,
+ MarkPersonMentionAsRead,
+ PersonView,
+ PurgeComment,
+ PurgePerson,
+ RemoveComment,
+ SaveComment,
+ TransferCommunity,
+} from "lemmy-js-client";
import { CommentNodeI, CommentViewType } from "../../interfaces";
import { CommentNode } from "./comment-node";
allLanguages: Language[];
siteLanguages: number[];
hideImages?: boolean;
+ finished: Map<CommentId, boolean | undefined>;
+ onSaveComment(form: SaveComment): void;
+ onCommentReplyRead(form: MarkCommentReplyAsRead): void;
+ onPersonMentionRead(form: MarkPersonMentionAsRead): void;
+ onCreateComment(form: EditComment | CreateComment): void;
+ onEditComment(form: EditComment | CreateComment): void;
+ onCommentVote(form: CreateCommentLike): void;
+ onBlockPerson(form: BlockPerson): void;
+ onDeleteComment(form: DeleteComment): void;
+ onRemoveComment(form: RemoveComment): void;
+ onDistinguishComment(form: DistinguishComment): void;
+ onAddModToCommunity(form: AddModToCommunity): void;
+ onAddAdmin(form: AddAdmin): void;
+ onBanPersonFromCommunity(form: BanFromCommunity): void;
+ onBanPerson(form: BanPerson): void;
+ onTransferCommunity(form: TransferCommunity): void;
+ onFetchChildren?(form: GetComments): void;
+ onCommentReport(form: CreateCommentReport): void;
+ onPurgePerson(form: PurgePerson): void;
+ onPurgeComment(form: PurgeComment): void;
}
export class CommentNodes extends Component<CommentNodesProps, any> {
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
hideImages={this.props.hideImages}
+ onCommentReplyRead={this.props.onCommentReplyRead}
+ onPersonMentionRead={this.props.onPersonMentionRead}
+ finished={this.props.finished}
+ onCreateComment={this.props.onCreateComment}
+ onEditComment={this.props.onEditComment}
+ onCommentVote={this.props.onCommentVote}
+ onBlockPerson={this.props.onBlockPerson}
+ onSaveComment={this.props.onSaveComment}
+ onDeleteComment={this.props.onDeleteComment}
+ onRemoveComment={this.props.onRemoveComment}
+ onDistinguishComment={this.props.onDistinguishComment}
+ onAddModToCommunity={this.props.onAddModToCommunity}
+ onAddAdmin={this.props.onAddAdmin}
+ onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
+ onBanPerson={this.props.onBanPerson}
+ onTransferCommunity={this.props.onTransferCommunity}
+ onFetchChildren={this.props.onFetchChildren}
+ onCommentReport={this.props.onCommentReport}
+ onPurgePerson={this.props.onPurgePerson}
+ onPurgeComment={this.props.onPurgeComment}
/>
))}
</div>
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
CommentReportView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { CommentNodeI, CommentViewType } from "../../interfaces";
-import { WebSocketService } from "../../services";
-import { myAuth, wsClient } from "../../utils";
-import { Icon } from "../common/icon";
+import { myAuthRequired } from "../../utils";
+import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing";
import { CommentNode } from "./comment-node";
interface CommentReportProps {
report: CommentReportView;
+ onResolveReport(form: ResolveCommentReport): void;
}
-export class CommentReport extends Component<CommentReportProps, any> {
+interface CommentReportState {
+ loading: boolean;
+}
+
+export class CommentReport extends Component<
+ CommentReportProps,
+ CommentReportState
+> {
+ state: CommentReportState = {
+ loading: false,
+ };
constructor(props: any, context: any) {
super(props, context);
}
+ componentWillReceiveProps(
+ nextProps: Readonly<{ children?: InfernoNode } & CommentReportProps>
+ ): void {
+ if (this.props != nextProps) {
+ this.setState({ loading: false });
+ }
+ }
+
render() {
const r = this.props.report;
const comment = r.comment;
allLanguages={[]}
siteLanguages={[]}
hideImages
+ // All of these are unused, since its viewonly
+ finished={new Map()}
+ onSaveComment={() => {}}
+ onBlockPerson={() => {}}
+ onDeleteComment={() => {}}
+ onRemoveComment={() => {}}
+ onCommentVote={() => {}}
+ onCommentReport={() => {}}
+ onDistinguishComment={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
+ onPurgeComment={() => {}}
+ onPurgePerson={() => {}}
+ onCommentReplyRead={() => {}}
+ onPersonMentionRead={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onCreateComment={() => Promise.resolve({ state: "empty" })}
+ onEditComment={() => Promise.resolve({ state: "empty" })}
/>
<div>
{i18n.t("reporter")}: <PersonListing person={r.creator} />
data-tippy-content={tippyContent}
aria-label={tippyContent}
>
- <Icon
- icon="check"
- classes={`icon-inline ${
- r.comment_report.resolved ? "text-success" : "text-danger"
- }`}
- />
+ {this.state.loading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="check"
+ classes={`icon-inline ${
+ r.comment_report.resolved ? "text-success" : "text-danger"
+ }`}
+ />
+ )}
</button>
</div>
);
}
handleResolveReport(i: CommentReport) {
- const auth = myAuth();
- if (auth) {
- const form: ResolveCommentReport = {
- report_id: i.props.report.comment_report.id,
- resolved: !i.props.report.comment_report.resolved,
- auth,
- };
- WebSocketService.Instance.send(wsClient.resolveCommentReport(form));
- }
+ i.setState({ loading: true });
+ i.props.onResolveReport({
+ report_id: i.props.report.comment_report.id,
+ resolved: !i.props.report.comment_report.resolved,
+ auth: myAuthRequired(),
+ });
}
}
import { Component, linkEvent } from "inferno";
import { i18n } from "../../i18next";
-import { UserService } from "../../services";
-import { randomStr, toast, uploadImage } from "../../utils";
+import { HttpService, UserService } from "../../services";
+import { randomStr, toast } from "../../utils";
import { Icon } from "./icon";
interface ImageUploadFormProps {
handleImageUpload(i: ImageUploadForm, event: any) {
event.preventDefault();
- const file = event.target.files[0];
+ const image = event.target.files[0] as File;
i.setState({ loading: true });
- uploadImage(file)
- .then(res => {
- console.log("pictrs upload:");
- console.log(res);
- if (res.msg === "ok") {
- i.setState({ loading: false });
- i.props.onUpload(res.url as string);
+ HttpService.client.uploadImage({ image }).then(res => {
+ console.log("pictrs upload:");
+ console.log(res);
+ if (res.state === "success") {
+ if (res.data.msg === "ok") {
+ i.props.onUpload(res.data.url as string);
} else {
- i.setState({ loading: false });
toast(JSON.stringify(res), "danger");
}
- })
- .catch(error => {
- i.setState({ loading: false });
- console.error(error);
- toast(error, "danger");
- });
+ } else if (res.state === "failed") {
+ console.error(res.msg);
+ toast(res.msg, "danger");
+ }
+
+ i.setState({ loading: false });
+ });
}
handleRemoveImage(i: ImageUploadForm, event: any) {
type_: ListingType;
showLocal: boolean;
showSubscribed: boolean;
- onChange?(val: ListingType): any;
+ onChange(val: ListingType): void;
}
interface ListingTypeSelectState {
super(props, context);
}
- static getDerivedStateFromProps(props: any): ListingTypeSelectProps {
+ static getDerivedStateFromProps(
+ props: ListingTypeSelectProps
+ ): ListingTypeSelectState {
return {
type_: props.type_,
- showLocal: props.showLocal,
- showSubscribed: props.showSubscribed,
};
}
}
handleTypeChange(i: ListingTypeSelect, event: any) {
- i.props.onChange?.(event.target.value);
+ i.props.onChange(event.target.value);
}
}
import { Component, linkEvent } from "inferno";
import { Language } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { UserService } from "../../services";
+import { HttpService, UserService } from "../../services";
import {
concurrentImageUpload,
customEmojisLookup,
setupTippy,
setupTribute,
toast,
- uploadImage,
} from "../../utils";
import { EmojiPicker } from "./emoji-picker";
import { Icon, Spinner } from "./icon";
finished?: boolean;
showLanguage?: boolean;
hideNavigationWarnings?: boolean;
- onContentChange?(val: string): any;
- onReplyCancel?(): any;
- onSubmit?(msg: { val?: string; formId: string; languageId?: number }): any;
+ onContentChange?(val: string): void;
+ onReplyCancel?(): void;
+ onSubmit?(content: string, formId: string, languageId?: number): void;
allLanguages: Language[]; // TODO should probably be nullable
siteLanguages: number[]; // TODO same
}
content?: string;
languageId?: number;
previewMode: boolean;
- loading: boolean;
imageUploadStatus?: ImageUploadStatus;
+ loading: boolean;
+ submitted: boolean;
}
export class MarkdownTextArea extends Component<
languageId: this.props.initialLanguageId,
previewMode: false,
loading: false,
+ submitted: false,
};
constructor(props: any, context: any) {
}
}
- componentDidUpdate() {
- if (!this.props.hideNavigationWarnings && this.state.content) {
- window.onbeforeunload = () => true;
- } else {
- window.onbeforeunload = null;
- }
- }
-
componentWillReceiveProps(nextProps: MarkdownTextAreaProps) {
if (nextProps.finished) {
- this.setState({ previewMode: false, loading: false, content: undefined });
+ this.setState({
+ previewMode: false,
+ imageUploadStatus: undefined,
+ loading: false,
+ content: undefined,
+ });
if (this.props.replyType) {
this.props.onReplyCancel?.();
}
}
}
- componentWillUnmount() {
- window.onbeforeunload = null;
- }
-
render() {
const languageId = this.state.languageId;
+ // TODO add these prompts back in at some point
+ // <Prompt
+ // when={!this.props.hideNavigationWarnings && this.state.content}
+ // message={i18n.t("block_leaving")}
+ // />
return (
<form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}>
- <NavigationPrompt when={!!this.state.content} />
+ <NavigationPrompt
+ when={
+ !this.props.hideNavigationWarnings &&
+ !!this.state.content &&
+ !this.state.submitted
+ }
+ />
<div className="form-group row">
<div className={`col-sm-12`}>
<textarea
}
}
- async uploadSingleImage(i: MarkdownTextArea, file: File) {
- try {
- const res = await uploadImage(file);
- console.log("pictrs upload:");
- console.log(res);
- if (res.msg === "ok") {
- const imageMarkdown = `![](${res.url})`;
+ async uploadSingleImage(i: MarkdownTextArea, image: File) {
+ const res = await HttpService.client.uploadImage({ image });
+ console.log("pictrs upload:");
+ console.log(res);
+ if (res.state === "success") {
+ if (res.data.msg === "ok") {
+ const imageMarkdown = `![](${res.data.url})`;
i.setState(({ content }) => ({
content: content ? `${content}\n${imageMarkdown}` : imageMarkdown,
}));
i.contentChange();
const textarea: any = document.getElementById(i.id);
autosize.update(textarea);
- pictrsDeleteToast(file.name, res.delete_url as string);
+ pictrsDeleteToast(image.name, res.data.delete_url as string);
} else {
- throw JSON.stringify(res);
+ throw JSON.stringify(res.data);
}
- } catch (error) {
+ } else if (res.state === "failed") {
i.setState({ imageUploadStatus: undefined });
- console.error(error);
- toast(error, "danger");
+ console.error(res.msg);
+ toast(res.msg, "danger");
- throw error;
+ throw res.msg;
}
}
handleSubmit(i: MarkdownTextArea, event: any) {
event.preventDefault();
- i.setState({ loading: true });
- const msg = {
- val: i.state.content,
- formId: i.formId,
- languageId: i.state.languageId,
- };
- i.props.onSubmit?.(msg);
+ if (i.state.content) {
+ i.setState({ loading: true, submitted: true });
+ i.props.onSubmit?.(i.state.content, i.formId, i.state.languageId);
+ }
}
handleReplyCancel(i: MarkdownTextArea) {
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
ApproveRegistrationApplication,
RegistrationApplicationView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
-import { mdToHtml, myAuth, wsClient } from "../../utils";
+import { mdToHtml, myAuthRequired } from "../../utils";
import { PersonListing } from "../person/person-listing";
+import { Spinner } from "./icon";
import { MarkdownTextArea } from "./markdown-textarea";
import { MomentTime } from "./moment-time";
interface RegistrationApplicationProps {
application: RegistrationApplicationView;
+ onApproveApplication(form: ApproveRegistrationApplication): void;
}
interface RegistrationApplicationState {
denyReason?: string;
denyExpanded: boolean;
+ approveLoading: boolean;
+ denyLoading: boolean;
}
export class RegistrationApplication extends Component<
state: RegistrationApplicationState = {
denyReason: this.props.application.registration_application.deny_reason,
denyExpanded: false,
+ approveLoading: false,
+ denyLoading: false,
};
constructor(props: any, context: any) {
super(props, context);
this.handleDenyReasonChange = this.handleDenyReasonChange.bind(this);
}
+ componentWillReceiveProps(
+ nextProps: Readonly<
+ { children?: InfernoNode } & RegistrationApplicationProps
+ >
+ ): void {
+ if (this.props != nextProps) {
+ this.setState({
+ denyExpanded: false,
+ approveLoading: false,
+ denyLoading: false,
+ });
+ }
+ }
render() {
const a = this.props.application;
onClick={linkEvent(this, this.handleApprove)}
aria-label={i18n.t("approve")}
>
- {i18n.t("approve")}
+ {this.state.approveLoading ? <Spinner /> : i18n.t("approve")}
</button>
)}
{(!ra.admin_id || (ra.admin_id && accepted)) && (
onClick={linkEvent(this, this.handleDeny)}
aria-label={i18n.t("deny")}
>
- {i18n.t("deny")}
+ {this.state.denyLoading ? <Spinner /> : i18n.t("deny")}
</button>
)}
</div>
}
handleApprove(i: RegistrationApplication) {
- const auth = myAuth();
- if (auth) {
- i.setState({ denyExpanded: false });
- const form: ApproveRegistrationApplication = {
- id: i.props.application.registration_application.id,
- approve: true,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.approveRegistrationApplication(form)
- );
- }
+ i.setState({ denyExpanded: false, approveLoading: true });
+ i.props.onApproveApplication({
+ id: i.props.application.registration_application.id,
+ approve: true,
+ auth: myAuthRequired(),
+ });
}
handleDeny(i: RegistrationApplication) {
if (i.state.denyExpanded) {
- i.setState({ denyExpanded: false });
- const auth = myAuth();
- if (auth) {
- const form: ApproveRegistrationApplication = {
- id: i.props.application.registration_application.id,
- approve: false,
- deny_reason: i.state.denyReason,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.approveRegistrationApplication(form)
- );
- }
+ i.setState({ denyExpanded: false, denyLoading: true });
+ i.props.onApproveApplication({
+ id: i.props.application.registration_application.id,
+ approve: false,
+ deny_reason: i.state.denyReason,
+ auth: myAuthRequired(),
+ });
} else {
i.setState({ denyExpanded: true });
}
});
}
+function focusSearch(i: SearchableSelect) {
+ if (i.toggleButtonRef.current?.ariaExpanded !== "true") {
+ i.searchInputRef.current?.focus();
+
+ if (i.props.onSearch) {
+ i.props.onSearch("");
+ }
+
+ i.setState({
+ searchText: "",
+ });
+ }
+}
+
+function handleChange({ option, i }: { option: Choice; i: SearchableSelect }) {
+ const { onChange, value } = i.props;
+
+ if (option.value !== value?.toString()) {
+ if (onChange) {
+ onChange(option);
+ }
+
+ i.setState({ searchText: "" });
+ }
+}
+
export class SearchableSelect extends Component<
SearchableSelectProps,
SearchableSelectState
> {
- private searchInputRef: RefObject<HTMLInputElement> = createRef();
- private toggleButtonRef: RefObject<HTMLButtonElement> = createRef();
+ searchInputRef: RefObject<HTMLInputElement> = createRef();
+ toggleButtonRef: RefObject<HTMLButtonElement> = createRef();
private loadingEllipsesInterval?: NodeJS.Timer = undefined;
state: SearchableSelectState = {
constructor(props: SearchableSelectProps, context: any) {
super(props, context);
- this.handleChange = this.handleChange.bind(this);
- this.focusSearch = this.focusSearch.bind(this);
-
if (props.value) {
let selectedIndex = props.options.findIndex(
({ value }) => value === props.value?.toString()
className="custom-select text-start"
aria-haspopup="listbox"
data-bs-toggle="dropdown"
- onClick={this.focusSearch}
+ onClick={linkEvent(this, focusSearch)}
+ ref={this.toggleButtonRef}
>
{loading
? `${i18n.t("loading")}${loadingEllipses}`
aria-disabled={option.disabled}
disabled={option.disabled}
aria-selected={selectedIndex === index}
- onClick={() => this.handleChange(option)}
+ onClick={linkEvent({ i: this, option }, handleChange)}
type="button"
>
{option.label}
);
}
- focusSearch() {
- if (this.toggleButtonRef.current?.ariaExpanded !== "true") {
- this.searchInputRef.current?.focus();
-
- if (this.props.onSearch) {
- this.props.onSearch("");
- }
-
- this.setState({
- searchText: "",
- });
- }
- }
-
static getDerivedStateFromProps({
value,
options,
clearInterval(this.loadingEllipsesInterval);
}
}
-
- handleChange(option: Choice) {
- const { onChange, value } = this.props;
-
- if (option.value !== value?.toString()) {
- if (onChange) {
- onChange(option);
- }
-
- this.setState({ searchText: "" });
- }
- }
}
interface SortSelectProps {
sort: SortType;
- onChange?(val: SortType): any;
+ onChange(val: SortType): void;
hideHot?: boolean;
hideMostComments?: boolean;
}
super(props, context);
}
- static getDerivedStateFromProps(props: any): SortSelectState {
+ static getDerivedStateFromProps(props: SortSelectProps): SortSelectState {
return {
sort: props.sort,
};
}
handleSortChange(i: SortSelect, event: any) {
- i.props.onChange?.(event.target.value);
+ i.props.onChange(event.target.value);
}
}
import { Component, linkEvent } from "inferno";
import {
CommunityResponse,
- FollowCommunity,
GetSiteResponse,
ListCommunities,
ListCommunitiesResponse,
ListingType,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
-import { WebSocketService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
+ editCommunity,
getPageFromString,
getQueryParams,
getQueryString,
- isBrowser,
myAuth,
+ myAuthRequired,
numToSI,
setIsoData,
showLocal,
- toast,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
const communityLimit = 50;
interface CommunitiesState {
- listCommunitiesResponse?: ListCommunitiesResponse;
- loading: boolean;
+ listCommunitiesResponse: RequestState<ListCommunitiesResponse>;
siteRes: GetSiteResponse;
searchText: string;
+ isIsomorphic: boolean;
}
interface CommunitiesProps {
page: number;
}
-function getCommunitiesQueryParams() {
- return getQueryParams<CommunitiesProps>({
- listingType: getListingTypeFromQuery,
- page: getPageFromString,
- });
-}
-
function getListingTypeFromQuery(listingType?: string): ListingType {
return listingType ? (listingType as ListingType) : "Local";
}
-function toggleSubscribe(community_id: number, follow: boolean) {
- const auth = myAuth();
- if (auth) {
- const form: FollowCommunity = {
- community_id,
- follow,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.followCommunity(form));
- }
-}
-
-function refetch() {
- const { listingType, page } = getCommunitiesQueryParams();
-
- const listCommunitiesForm: ListCommunities = {
- type_: listingType,
- sort: "TopMonth",
- limit: communityLimit,
- page,
- auth: myAuth(false),
- };
-
- WebSocketService.Instance.send(wsClient.listCommunities(listCommunitiesForm));
-}
-
export class Communities extends Component<any, CommunitiesState> {
- private subscription?: Subscription;
private isoData = setIsoData(this.context);
state: CommunitiesState = {
- loading: true,
+ listCommunitiesResponse: { state: "empty" },
siteRes: this.isoData.site_res,
searchText: "",
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
this.handlePageChange = this.handlePageChange.bind(this);
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
// Only fetch the data if coming from another route
- if (this.isoData.path === this.context.router.route.match.url) {
- const listRes = this.isoData.routeData[0] as ListCommunitiesResponse;
+ if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- listCommunitiesResponse: listRes,
- loading: false,
+ listCommunitiesResponse: this.isoData.routeData[0],
+ isIsomorphic: true,
};
- } else {
- refetch();
}
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.refetch();
}
}
}`;
}
- render() {
- const { listingType, page } = getCommunitiesQueryParams();
-
- return (
- <div className="container-lg">
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- />
- {this.state.loading ? (
+ renderListings() {
+ switch (this.state.listCommunitiesResponse.state) {
+ case "loading":
+ return (
<h5>
<Spinner large />
</h5>
- ) : (
+ );
+ case "success": {
+ const { listingType, page } = this.getCommunitiesQueryParams();
+ return (
<div>
<div className="row">
<div className="col-md-6">
</tr>
</thead>
<tbody>
- {this.state.listCommunitiesResponse?.communities.map(cv => (
- <tr key={cv.community.id}>
- <td>
- <CommunityLink community={cv.community} />
- </td>
- <td className="text-right">
- {numToSI(cv.counts.subscribers)}
- </td>
- <td className="text-right">
- {numToSI(cv.counts.users_active_month)}
- </td>
- <td className="text-right d-none d-lg-table-cell">
- {numToSI(cv.counts.posts)}
- </td>
- <td className="text-right d-none d-lg-table-cell">
- {numToSI(cv.counts.comments)}
- </td>
- <td className="text-right">
- {cv.subscribed == "Subscribed" && (
- <button
- className="btn btn-link d-inline-block"
- onClick={linkEvent(
- cv.community.id,
- this.handleUnsubscribe
- )}
- >
- {i18n.t("unsubscribe")}
- </button>
- )}
- {cv.subscribed === "NotSubscribed" && (
- <button
- className="btn btn-link d-inline-block"
- onClick={linkEvent(
- cv.community.id,
- this.handleSubscribe
- )}
- >
- {i18n.t("subscribe")}
- </button>
- )}
- {cv.subscribed === "Pending" && (
- <div className="text-warning d-inline-block">
- {i18n.t("subscribe_pending")}
- </div>
- )}
- </td>
- </tr>
- ))}
+ {this.state.listCommunitiesResponse.data.communities.map(
+ cv => (
+ <tr key={cv.community.id}>
+ <td>
+ <CommunityLink community={cv.community} />
+ </td>
+ <td className="text-right">
+ {numToSI(cv.counts.subscribers)}
+ </td>
+ <td className="text-right">
+ {numToSI(cv.counts.users_active_month)}
+ </td>
+ <td className="text-right d-none d-lg-table-cell">
+ {numToSI(cv.counts.posts)}
+ </td>
+ <td className="text-right d-none d-lg-table-cell">
+ {numToSI(cv.counts.comments)}
+ </td>
+ <td className="text-right">
+ {cv.subscribed == "Subscribed" && (
+ <button
+ className="btn btn-link d-inline-block"
+ onClick={linkEvent(
+ {
+ i: this,
+ communityId: cv.community.id,
+ follow: false,
+ },
+ this.handleFollow
+ )}
+ >
+ {i18n.t("unsubscribe")}
+ </button>
+ )}
+ {cv.subscribed === "NotSubscribed" && (
+ <button
+ className="btn btn-link d-inline-block"
+ onClick={linkEvent(
+ {
+ i: this,
+ communityId: cv.community.id,
+ follow: true,
+ },
+ this.handleFollow
+ )}
+ >
+ {i18n.t("subscribe")}
+ </button>
+ )}
+ {cv.subscribed === "Pending" && (
+ <div className="text-warning d-inline-block">
+ {i18n.t("subscribe_pending")}
+ </div>
+ )}
+ </td>
+ </tr>
+ )
+ )}
</tbody>
</table>
</div>
<Paginator page={page} onChange={this.handlePageChange} />
</div>
- )}
+ );
+ }
+ }
+ }
+
+ render() {
+ return (
+ <div className="container-lg">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
+ {this.renderListings()}
</div>
);
}
);
}
- updateUrl({ listingType, page }: Partial<CommunitiesProps>) {
+ async updateUrl({ listingType, page }: Partial<CommunitiesProps>) {
const { listingType: urlListingType, page: urlPage } =
- getCommunitiesQueryParams();
+ this.getCommunitiesQueryParams();
const queryParams: QueryParams<CommunitiesProps> = {
listingType: listingType ?? urlListingType,
this.props.history.push(`/communities${getQueryString(queryParams)}`);
- refetch();
+ await this.refetch();
}
handlePageChange(page: number) {
});
}
- handleUnsubscribe(communityId: number) {
- toggleSubscribe(communityId, false);
- }
-
- handleSubscribe(communityId: number) {
- toggleSubscribe(communityId, true);
- }
-
handleSearchChange(i: Communities, event: any) {
i.setState({ searchText: event.target.value });
}
- handleSearchSubmit(i: Communities) {
+ handleSearchSubmit(i: Communities, event: any) {
+ event.preventDefault();
const searchParamEncoded = encodeURIComponent(i.state.searchText);
i.context.router.history.push(`/search?q=${searchParamEncoded}`);
}
query: { listingType, page },
client,
auth,
- }: InitialFetchRequest<QueryParams<CommunitiesProps>>): Promise<any>[] {
+ }: InitialFetchRequest<QueryParams<CommunitiesProps>>): Promise<
+ RequestState<any>
+ >[] {
const listCommunitiesForm: ListCommunities = {
type_: getListingTypeFromQuery(listingType),
sort: "TopMonth",
return [client.listCommunities(listCommunitiesForm)];
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- } else if (op === UserOperation.ListCommunities) {
- const data = wsJsonToRes<ListCommunitiesResponse>(msg);
- this.setState({ listCommunitiesResponse: data, loading: false });
- window.scrollTo(0, 0);
- } else if (op === UserOperation.FollowCommunity) {
- const {
- community_view: {
- community,
- subscribed,
- counts: { subscribers },
- },
- } = wsJsonToRes<CommunityResponse>(msg);
- const res = this.state.listCommunitiesResponse;
- const found = res?.communities.find(
- ({ community: { id } }) => id == community.id
- );
+ getCommunitiesQueryParams() {
+ return getQueryParams<CommunitiesProps>({
+ listingType: getListingTypeFromQuery,
+ page: getPageFromString,
+ });
+ }
+
+ async handleFollow(data: {
+ i: Communities;
+ communityId: number;
+ follow: boolean;
+ }) {
+ const res = await HttpService.client.followCommunity({
+ community_id: data.communityId,
+ follow: data.follow,
+ auth: myAuthRequired(),
+ });
+ data.i.findAndUpdateCommunity(res);
+ }
+
+ async refetch() {
+ this.setState({ listCommunitiesResponse: { state: "loading" } });
+
+ const { listingType, page } = this.getCommunitiesQueryParams();
- if (found) {
- found.subscribed = subscribed;
- found.counts.subscribers = subscribers;
- this.setState(this.state);
+ this.setState({
+ listCommunitiesResponse: await HttpService.client.listCommunities({
+ type_: listingType,
+ sort: "TopMonth",
+ limit: communityLimit,
+ page,
+ auth: myAuth(),
+ }),
+ });
+
+ window.scrollTo(0, 0);
+ }
+
+ findAndUpdateCommunity(res: RequestState<CommunityResponse>) {
+ this.setState(s => {
+ if (
+ s.listCommunitiesResponse.state == "success" &&
+ res.state == "success"
+ ) {
+ s.listCommunitiesResponse.data.communities = editCommunity(
+ res.data.community_view,
+ s.listCommunitiesResponse.data.communities
+ );
}
- }
+ return s;
+ });
}
}
import { Component, linkEvent } from "inferno";
import {
- CommunityResponse,
CommunityView,
CreateCommunity,
EditCommunity,
Language,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
-import {
- capitalizeFirstLetter,
- myAuth,
- randomStr,
- wsClient,
- wsSubscribe,
-} from "../../utils";
+import { capitalizeFirstLetter, myAuthRequired, randomStr } from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
siteLanguages: number[];
communityLanguages?: number[];
onCancel?(): any;
- onCreate?(community: CommunityView): any;
- onEdit?(community: CommunityView): any;
+ onUpsertCommunity(form: CreateCommunity | EditCommunity): void;
enableNsfw?: boolean;
}
discussion_languages?: number[];
};
loading: boolean;
+ submitted: boolean;
}
export class CommunityForm extends Component<
CommunityFormState
> {
private id = `community-form-${randomStr()}`;
- private subscription?: Subscription;
state: CommunityFormState = {
form: {},
loading: false,
+ submitted: false,
};
constructor(props: any, context: any) {
this.handleDiscussionLanguageChange =
this.handleDiscussionLanguageChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
const cv = this.props.community_view;
if (cv) {
this.state = {
+ ...this.state,
form: {
name: cv.community.name,
title: cv.community.title,
}
}
- componentDidUpdate() {
- if (
- !this.state.loading &&
- (this.state.form.name ||
- this.state.form.title ||
- this.state.form.description)
- ) {
- window.onbeforeunload = () => true;
- } else {
- window.onbeforeunload = null;
- }
- }
-
- componentWillUnmount() {
- this.subscription?.unsubscribe();
- window.onbeforeunload = null;
- }
-
render() {
return (
- <>
+ <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
<NavigationPrompt
when={
!this.state.loading &&
this.state.form.name ||
this.state.form.title ||
this.state.form.description
- )
+ ) &&
+ !this.state.submitted
}
/>
- <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
- {!this.props.community_view && (
- <div className="form-group row">
- <label
- className="col-12 col-sm-2 col-form-label"
- htmlFor="community-name"
- >
- {i18n.t("name")}
- <span
- className="position-absolute pointer unselectable ml-2 text-muted"
- data-tippy-content={i18n.t("name_explain")}
- >
- <Icon icon="help-circle" classes="icon-inline" />
- </span>
- </label>
- <div className="col-12 col-sm-10">
- <input
- type="text"
- id="community-name"
- className="form-control"
- value={this.state.form.name}
- onInput={linkEvent(this, this.handleCommunityNameChange)}
- required
- minLength={3}
- pattern="[a-z0-9_]+"
- title={i18n.t("community_reqs")}
- />
- </div>
- </div>
- )}
+ {!this.props.community_view && (
<div className="form-group row">
<label
className="col-12 col-sm-2 col-form-label"
- htmlFor="community-title"
+ htmlFor="community-name"
>
- {i18n.t("display_name")}
+ {i18n.t("name")}
<span
className="position-absolute pointer unselectable ml-2 text-muted"
- data-tippy-content={i18n.t("display_name_explain")}
+ data-tippy-content={i18n.t("name_explain")}
>
<Icon icon="help-circle" classes="icon-inline" />
</span>
<div className="col-12 col-sm-10">
<input
type="text"
- id="community-title"
- value={this.state.form.title}
- onInput={linkEvent(this, this.handleCommunityTitleChange)}
+ id="community-name"
className="form-control"
+ value={this.state.form.name}
+ onInput={linkEvent(this, this.handleCommunityNameChange)}
required
minLength={3}
- maxLength={100}
+ pattern="[a-z0-9_]+"
+ title={i18n.t("community_reqs")}
/>
</div>
</div>
- <div className="form-group row">
- <label className="col-12 col-sm-2">{i18n.t("icon")}</label>
- <div className="col-12 col-sm-10">
- <ImageUploadForm
- uploadTitle={i18n.t("upload_icon")}
- imageSrc={this.state.form.icon}
- onUpload={this.handleIconUpload}
- onRemove={this.handleIconRemove}
- rounded
- />
- </div>
+ )}
+ <div className="form-group row">
+ <label
+ className="col-12 col-sm-2 col-form-label"
+ htmlFor="community-title"
+ >
+ {i18n.t("display_name")}
+ <span
+ className="position-absolute pointer unselectable ml-2 text-muted"
+ data-tippy-content={i18n.t("display_name_explain")}
+ >
+ <Icon icon="help-circle" classes="icon-inline" />
+ </span>
+ </label>
+ <div className="col-12 col-sm-10">
+ <input
+ type="text"
+ id="community-title"
+ value={this.state.form.title}
+ onInput={linkEvent(this, this.handleCommunityTitleChange)}
+ className="form-control"
+ required
+ minLength={3}
+ maxLength={100}
+ />
</div>
- <div className="form-group row">
- <label className="col-12 col-sm-2">{i18n.t("banner")}</label>
- <div className="col-12 col-sm-10">
- <ImageUploadForm
- uploadTitle={i18n.t("upload_banner")}
- imageSrc={this.state.form.banner}
- onUpload={this.handleBannerUpload}
- onRemove={this.handleBannerRemove}
- />
- </div>
+ </div>
+ <div className="form-group row">
+ <label className="col-12 col-sm-2">{i18n.t("icon")}</label>
+ <div className="col-12 col-sm-10">
+ <ImageUploadForm
+ uploadTitle={i18n.t("upload_icon")}
+ imageSrc={this.state.form.icon}
+ onUpload={this.handleIconUpload}
+ onRemove={this.handleIconRemove}
+ rounded
+ />
</div>
- <div className="form-group row">
- <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
- {i18n.t("sidebar")}
- </label>
- <div className="col-12 col-sm-10">
- <MarkdownTextArea
- initialContent={this.state.form.description}
- placeholder={i18n.t("description")}
- onContentChange={this.handleCommunityDescriptionChange}
- allLanguages={[]}
- siteLanguages={[]}
- />
- </div>
+ </div>
+ <div className="form-group row">
+ <label className="col-12 col-sm-2">{i18n.t("banner")}</label>
+ <div className="col-12 col-sm-10">
+ <ImageUploadForm
+ uploadTitle={i18n.t("upload_banner")}
+ imageSrc={this.state.form.banner}
+ onUpload={this.handleBannerUpload}
+ onRemove={this.handleBannerRemove}
+ />
</div>
+ </div>
+ <div className="form-group row">
+ <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
+ {i18n.t("sidebar")}
+ </label>
+ <div className="col-12 col-sm-10">
+ <MarkdownTextArea
+ initialContent={this.state.form.description}
+ placeholder={i18n.t("description")}
+ onContentChange={this.handleCommunityDescriptionChange}
+ hideNavigationWarnings
+ allLanguages={[]}
+ siteLanguages={[]}
+ />
+ </div>
+ </div>
- {this.props.enableNsfw && (
- <div className="form-group row">
- <legend className="col-form-label col-sm-2 pt-0">
- {i18n.t("nsfw")}
- </legend>
- <div className="col-10">
- <div className="form-check">
- <input
- className="form-check-input position-static"
- id="community-nsfw"
- type="checkbox"
- checked={this.state.form.nsfw}
- onChange={linkEvent(this, this.handleCommunityNsfwChange)}
- />
- </div>
- </div>
- </div>
- )}
+ {this.props.enableNsfw && (
<div className="form-group row">
- <legend className="col-form-label col-6 pt-0">
- {i18n.t("only_mods_can_post_in_community")}
+ <legend className="col-form-label col-sm-2 pt-0">
+ {i18n.t("nsfw")}
</legend>
- <div className="col-6">
+ <div className="col-10">
<div className="form-check">
<input
className="form-check-input position-static"
- id="community-only-mods-can-post"
+ id="community-nsfw"
type="checkbox"
- checked={this.state.form.posting_restricted_to_mods}
- onChange={linkEvent(
- this,
- this.handleCommunityPostingRestrictedToMods
- )}
+ checked={this.state.form.nsfw}
+ onChange={linkEvent(this, this.handleCommunityNsfwChange)}
/>
</div>
</div>
</div>
- <LanguageSelect
- allLanguages={this.props.allLanguages}
- siteLanguages={this.props.siteLanguages}
- showSite
- selectedLanguageIds={this.state.form.discussion_languages}
- multiple={true}
- onChange={this.handleDiscussionLanguageChange}
- />
- <div className="form-group row">
- <div className="col-12">
+ )}
+ <div className="form-group row">
+ <legend className="col-form-label col-6 pt-0">
+ {i18n.t("only_mods_can_post_in_community")}
+ </legend>
+ <div className="col-6">
+ <div className="form-check">
+ <input
+ className="form-check-input position-static"
+ id="community-only-mods-can-post"
+ type="checkbox"
+ checked={this.state.form.posting_restricted_to_mods}
+ onChange={linkEvent(
+ this,
+ this.handleCommunityPostingRestrictedToMods
+ )}
+ />
+ </div>
+ </div>
+ </div>
+ <LanguageSelect
+ allLanguages={this.props.allLanguages}
+ siteLanguages={this.props.siteLanguages}
+ showSite
+ selectedLanguageIds={this.state.form.discussion_languages}
+ multiple={true}
+ onChange={this.handleDiscussionLanguageChange}
+ />
+ <div className="form-group row">
+ <div className="col-12">
+ <button
+ type="submit"
+ className="btn btn-secondary mr-2"
+ disabled={this.state.loading}
+ >
+ {this.state.loading ? (
+ <Spinner />
+ ) : this.props.community_view ? (
+ capitalizeFirstLetter(i18n.t("save"))
+ ) : (
+ capitalizeFirstLetter(i18n.t("create"))
+ )}
+ </button>
+ {this.props.community_view && (
<button
- type="submit"
- className="btn btn-secondary mr-2"
- disabled={this.state.loading}
+ type="button"
+ className="btn btn-secondary"
+ onClick={linkEvent(this, this.handleCancel)}
>
- {this.state.loading ? (
- <Spinner />
- ) : this.props.community_view ? (
- capitalizeFirstLetter(i18n.t("save"))
- ) : (
- capitalizeFirstLetter(i18n.t("create"))
- )}
+ {i18n.t("cancel")}
</button>
- {this.props.community_view && (
- <button
- type="button"
- className="btn btn-secondary"
- onClick={linkEvent(this, this.handleCancel)}
- >
- {i18n.t("cancel")}
- </button>
- )}
- </div>
+ )}
</div>
- </form>
- </>
+ </div>
+ </form>
);
}
handleCreateCommunitySubmit(i: CommunityForm, event: any) {
event.preventDefault();
- i.setState({ loading: true });
+ i.setState({ loading: true, submitted: true });
const cForm = i.state.form;
- const auth = myAuth();
+ const auth = myAuthRequired();
const cv = i.props.community_view;
- if (auth) {
- if (cv) {
- const form: EditCommunity = {
- community_id: cv.community.id,
+ if (cv) {
+ i.props.onUpsertCommunity({
+ community_id: cv.community.id,
+ title: cForm.title,
+ description: cForm.description,
+ icon: cForm.icon,
+ banner: cForm.banner,
+ nsfw: cForm.nsfw,
+ posting_restricted_to_mods: cForm.posting_restricted_to_mods,
+ discussion_languages: cForm.discussion_languages,
+ auth,
+ });
+ } else {
+ if (cForm.title && cForm.name) {
+ i.props.onUpsertCommunity({
+ name: cForm.name,
title: cForm.title,
description: cForm.description,
icon: cForm.icon,
posting_restricted_to_mods: cForm.posting_restricted_to_mods,
discussion_languages: cForm.discussion_languages,
auth,
- };
-
- WebSocketService.Instance.send(wsClient.editCommunity(form));
- } else {
- if (cForm.title && cForm.name) {
- const form: CreateCommunity = {
- name: cForm.name,
- title: cForm.title,
- description: cForm.description,
- icon: cForm.icon,
- banner: cForm.banner,
- nsfw: cForm.nsfw,
- posting_restricted_to_mods: cForm.posting_restricted_to_mods,
- discussion_languages: cForm.discussion_languages,
- auth,
- };
- WebSocketService.Instance.send(wsClient.createCommunity(form));
- }
+ });
}
}
- i.setState(i.state);
}
handleCommunityNameChange(i: CommunityForm, event: any) {
- i.state.form.name = event.target.value;
- i.setState(i.state);
+ i.setState(s => ((s.form.name = event.target.value), s));
}
handleCommunityTitleChange(i: CommunityForm, event: any) {
- i.state.form.title = event.target.value;
- i.setState(i.state);
+ i.setState(s => ((s.form.title = event.target.value), s));
}
handleCommunityDescriptionChange(val: string) {
}
handleCommunityNsfwChange(i: CommunityForm, event: any) {
- i.state.form.nsfw = event.target.checked;
- i.setState(i.state);
+ i.setState(s => ((s.form.nsfw = event.target.checked), s));
}
handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
- i.state.form.posting_restricted_to_mods = event.target.checked;
- i.setState(i.state);
+ i.setState(
+ s => ((s.form.posting_restricted_to_mods = event.target.checked), s)
+ );
}
handleCancel(i: CommunityForm) {
handleDiscussionLanguageChange(val: number[]) {
this.setState(s => ((s.form.discussion_languages = val), s));
}
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- // Errors handled by top level pages
- // toast(i18n.t(msg.error), "danger");
- this.setState({ loading: false });
- return;
- } else if (op == UserOperation.CreateCommunity) {
- const data = wsJsonToRes<CommunityResponse>(msg);
- this.props.onCreate?.(data.community_view);
-
- // Update myUserInfo
- const community = data.community_view.community;
-
- const mui = UserService.Instance.myUserInfo;
- if (mui) {
- const person = mui.local_user_view.person;
- mui.follows.push({
- community,
- follower: person,
- });
- mui.moderates.push({
- community,
- moderator: person,
- });
- }
- } else if (op == UserOperation.EditCommunity) {
- const data = wsJsonToRes<CommunityResponse>(msg);
- this.setState({ loading: false });
- this.props.onEdit?.(data.community_view);
- const community = data.community_view.community;
-
- const mui = UserService.Instance.myUserInfo;
- if (mui) {
- const followFound = mui.follows.findIndex(
- f => f.community.id == community.id
- );
- if (followFound) {
- mui.follows[followFound].community = community;
- }
-
- const moderatesFound = mui.moderates.findIndex(
- f => f.community.id == community.id
- );
- if (moderatesFound) {
- mui.moderates[moderatesFound].community = community;
- }
- }
- }
- }
}
import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
+ AddAdmin,
+ AddModToCommunity,
AddModToCommunityResponse,
+ BanFromCommunity,
BanFromCommunityResponse,
- BlockCommunityResponse,
- BlockPersonResponse,
+ BanPerson,
+ BanPersonResponse,
+ BlockCommunity,
+ BlockPerson,
+ CommentId,
+ CommentReplyResponse,
CommentResponse,
- CommentView,
CommunityResponse,
+ CreateComment,
+ CreateCommentLike,
+ CreateCommentReport,
+ CreatePostLike,
+ CreatePostReport,
+ DeleteComment,
+ DeleteCommunity,
+ DeletePost,
+ DistinguishComment,
+ EditComment,
+ EditCommunity,
+ EditPost,
+ FeaturePost,
+ FollowCommunity,
GetComments,
GetCommentsResponse,
GetCommunity,
GetCommunityResponse,
GetPosts,
GetPostsResponse,
- PostReportResponse,
+ GetSiteResponse,
+ LockPost,
+ MarkCommentReplyAsRead,
+ MarkPersonMentionAsRead,
PostResponse,
- PostView,
+ PurgeComment,
+ PurgeCommunity,
PurgeItemResponse,
+ PurgePerson,
+ PurgePost,
+ RemoveComment,
+ RemoveCommunity,
+ RemovePost,
+ SaveComment,
+ SavePost,
SortType,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ TransferCommunity,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import {
CommentViewType,
DataType,
InitialFetchRequest,
} from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
commentsToFlatNodes,
communityRSSUrl,
- createCommentLikeRes,
- createPostLikeFindRes,
- editCommentRes,
- editPostFindRes,
+ editComment,
+ editPost,
+ editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
+ getCommentParentId,
getDataTypeString,
getPageFromString,
getQueryParams,
getQueryString,
- isPostBlocked,
myAuth,
- notifyPost,
- nsfwCheck,
postToCommentSortType,
relTags,
restoreScrollPosition,
- saveCommentRes,
saveScrollPosition,
setIsoData,
setupTippy,
toast,
updateCommunityBlock,
updatePersonBlock,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes";
import { BannerIconHeader } from "../common/banner-icon-header";
import { CommunityLink } from "./community-link";
interface State {
- communityRes?: GetCommunityResponse;
- communityLoading: boolean;
- listingsLoading: boolean;
- posts: PostView[];
- comments: CommentView[];
+ communityRes: RequestState<GetCommunityResponse>;
+ postsRes: RequestState<GetPostsResponse>;
+ commentsRes: RequestState<GetCommentsResponse>;
+ siteRes: GetSiteResponse;
showSidebarMobile: boolean;
+ finished: Map<CommentId, boolean | undefined>;
+ isIsomorphic: boolean;
}
interface CommunityProps {
State
> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: State = {
- communityLoading: true,
- listingsLoading: true,
- posts: [],
- comments: [],
+ communityRes: { state: "empty" },
+ postsRes: { state: "empty" },
+ commentsRes: { state: "empty" },
+ siteRes: this.isoData.site_res,
showSidebarMobile: false,
+ finished: new Map(),
+ isIsomorphic: false,
};
constructor(props: RouteComponentProps<{ name: string }>, context: any) {
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ // All of the action binds
+ this.handleDeleteCommunity = this.handleDeleteCommunity.bind(this);
+ this.handleEditCommunity = this.handleEditCommunity.bind(this);
+ this.handleFollow = this.handleFollow.bind(this);
+ this.handleRemoveCommunity = this.handleRemoveCommunity.bind(this);
+ this.handleCreateComment = this.handleCreateComment.bind(this);
+ this.handleEditComment = this.handleEditComment.bind(this);
+ this.handleSaveComment = this.handleSaveComment.bind(this);
+ this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
+ this.handleBlockPerson = this.handleBlockPerson.bind(this);
+ this.handleDeleteComment = this.handleDeleteComment.bind(this);
+ this.handleRemoveComment = this.handleRemoveComment.bind(this);
+ this.handleCommentVote = this.handleCommentVote.bind(this);
+ this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
+ this.handleAddAdmin = this.handleAddAdmin.bind(this);
+ this.handlePurgePerson = this.handlePurgePerson.bind(this);
+ this.handlePurgeComment = this.handlePurgeComment.bind(this);
+ this.handleCommentReport = this.handleCommentReport.bind(this);
+ this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
+ this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
+ this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
+ this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
+ this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
+ this.handleBanPerson = this.handleBanPerson.bind(this);
+ this.handlePostVote = this.handlePostVote.bind(this);
+ this.handlePostEdit = this.handlePostEdit.bind(this);
+ this.handlePostReport = this.handlePostReport.bind(this);
+ this.handleLockPost = this.handleLockPost.bind(this);
+ this.handleDeletePost = this.handleDeletePost.bind(this);
+ this.handleRemovePost = this.handleRemovePost.bind(this);
+ this.handleSavePost = this.handleSavePost.bind(this);
+ this.handlePurgePost = this.handlePurgePost.bind(this);
+ this.handleFeaturePost = this.handleFeaturePost.bind(this);
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
+ const [communityRes, postsRes, commentsRes] = this.isoData.routeData;
this.state = {
...this.state,
- communityRes: this.isoData.routeData[0] as GetCommunityResponse,
+ communityRes,
+ postsRes,
+ commentsRes,
+ isIsomorphic: true,
};
- const postsRes = this.isoData.routeData[1] as
- | GetPostsResponse
- | undefined;
- const commentsRes = this.isoData.routeData[2] as
- | GetCommentsResponse
- | undefined;
-
- if (postsRes) {
- this.state = { ...this.state, posts: postsRes.posts };
- }
-
- if (commentsRes) {
- this.state = { ...this.state, comments: commentsRes.comments };
- }
-
- this.state = {
- ...this.state,
- communityLoading: false,
- listingsLoading: false,
- };
- } else {
- this.fetchCommunity();
- this.fetchData();
}
}
- fetchCommunity() {
- const form: GetCommunity = {
- name: this.props.match.params.name,
- auth: myAuth(false),
- };
- WebSocketService.Instance.send(wsClient.getCommunity(form));
+ async fetchCommunity() {
+ this.setState({ communityRes: { state: "loading" } });
+ this.setState({
+ communityRes: await HttpService.client.getCommunity({
+ name: this.props.match.params.name,
+ auth: myAuth(),
+ }),
+ });
}
- componentDidMount() {
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await Promise.all([this.fetchCommunity(), this.fetchData()]);
+ }
+
setupTippy();
}
componentWillUnmount() {
saveScrollPosition(this.context);
- this.subscription?.unsubscribe();
}
static fetchInitialData({
path,
query: { dataType: urlDataType, page: urlPage, sort: urlSort },
auth,
- }: InitialFetchRequest<QueryParams<CommunityProps>>): Promise<any>[] {
+ }: InitialFetchRequest<QueryParams<CommunityProps>>): Promise<
+ RequestState<any>
+ >[] {
const pathSplit = path.split("/");
- const promises: Promise<any>[] = [];
+ const promises: Promise<RequestState<any>>[] = [];
const communityName = pathSplit[2];
const communityForm: GetCommunity = {
auth,
};
promises.push(client.getPosts(getPostsForm));
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
} else {
const getCommentsForm: GetComments = {
community_name: communityName,
saved_only: false,
auth,
};
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
promises.push(client.getComments(getCommentsForm));
}
get documentTitle(): string {
const cRes = this.state.communityRes;
- return cRes
- ? `${cRes.community_view.community.title} - ${this.isoData.site_res.site_view.site.name}`
+ return cRes.state == "success"
+ ? `${cRes.data.community_view.community.title} - ${this.isoData.site_res.site_view.site.name}`
: "";
}
- render() {
- const res = this.state.communityRes;
- const { page } = getCommunityQueryParams();
-
- return (
- <div className="container-lg">
- {this.state.communityLoading ? (
+ renderCommunity() {
+ switch (this.state.communityRes.state) {
+ case "loading":
+ return (
<h5>
<Spinner large />
</h5>
- ) : (
- res && (
- <>
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- description={res.community_view.community.description}
- image={res.community_view.community.icon}
- />
-
- <div className="row">
- <div className="col-12 col-md-8">
- {this.communityInfo}
- <div className="d-block d-md-none">
- <button
- className="btn btn-secondary d-inline-block mb-2 mr-3"
- onClick={linkEvent(this, this.handleShowSidebarMobile)}
- >
- {i18n.t("sidebar")}{" "}
- <Icon
- icon={
- this.state.showSidebarMobile
- ? `minus-square`
- : `plus-square`
- }
- classes="icon-inline"
- />
- </button>
- {this.state.showSidebarMobile && this.sidebar(res)}
- </div>
- {this.selects}
- {this.listings}
- <Paginator page={page} onChange={this.handlePageChange} />
- </div>
- <div className="d-none d-md-block col-md-4">
- {this.sidebar(res)}
+ );
+ case "success": {
+ const res = this.state.communityRes.data;
+ const { page } = getCommunityQueryParams();
+
+ return (
+ <>
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ description={res.community_view.community.description}
+ image={res.community_view.community.icon}
+ />
+
+ <div className="row">
+ <div className="col-12 col-md-8">
+ {this.communityInfo(res)}
+ <div className="d-block d-md-none">
+ <button
+ className="btn btn-secondary d-inline-block mb-2 mr-3"
+ onClick={linkEvent(this, this.handleShowSidebarMobile)}
+ >
+ {i18n.t("sidebar")}{" "}
+ <Icon
+ icon={
+ this.state.showSidebarMobile
+ ? `minus-square`
+ : `plus-square`
+ }
+ classes="icon-inline"
+ />
+ </button>
+ {this.state.showSidebarMobile && this.sidebar(res)}
</div>
+ {this.selects(res)}
+ {this.listings(res)}
+ <Paginator page={page} onChange={this.handlePageChange} />
</div>
- </>
- )
- )}
- </div>
- );
+ <div className="d-none d-md-block col-md-4">
+ {this.sidebar(res)}
+ </div>
+ </div>
+ </>
+ );
+ }
+ }
}
- sidebar({
- community_view,
- moderators,
- online,
- discussion_languages,
- site,
- }: GetCommunityResponse) {
+ render() {
+ return <div className="container-lg">{this.renderCommunity()}</div>;
+ }
+
+ sidebar(res: GetCommunityResponse) {
const { site_res } = this.isoData;
// For some reason, this returns an empty vec if it matches the site langs
const communityLangs =
- discussion_languages.length === 0
+ res.discussion_languages.length === 0
? site_res.all_languages.map(({ id }) => id)
- : discussion_languages;
+ : res.discussion_languages;
return (
<>
<Sidebar
- community_view={community_view}
- moderators={moderators}
+ community_view={res.community_view}
+ moderators={res.moderators}
admins={site_res.admins}
- online={online}
+ online={res.online}
enableNsfw={enableNsfw(site_res)}
editable
allLanguages={site_res.all_languages}
siteLanguages={site_res.discussion_languages}
communityLanguages={communityLangs}
+ onDeleteCommunity={this.handleDeleteCommunity}
+ onRemoveCommunity={this.handleRemoveCommunity}
+ onLeaveModTeam={this.handleAddModToCommunity}
+ onFollowCommunity={this.handleFollow}
+ onBlockCommunity={this.handleBlockCommunity}
+ onPurgeCommunity={this.handlePurgeCommunity}
+ onEditCommunity={this.handleEditCommunity}
/>
- {!community_view.community.local && site && (
- <SiteSidebar site={site} showLocal={showLocal(this.isoData)} />
+ {!res.community_view.community.local && res.site && (
+ <SiteSidebar site={res.site} showLocal={showLocal(this.isoData)} />
)}
</>
);
}
- get listings() {
+ listings(communityRes: GetCommunityResponse) {
const { dataType } = getCommunityQueryParams();
const { site_res } = this.isoData;
- const { listingsLoading, posts, comments, communityRes } = this.state;
-
- if (listingsLoading) {
- return (
- <h5>
- <Spinner large />
- </h5>
- );
- } else if (dataType === DataType.Post) {
- return (
- <PostListings
- posts={posts}
- removeDuplicates
- enableDownvotes={enableDownvotes(site_res)}
- enableNsfw={enableNsfw(site_res)}
- allLanguages={site_res.all_languages}
- siteLanguages={site_res.discussion_languages}
- />
- );
+
+ if (dataType === DataType.Post) {
+ switch (this.state.postsRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success":
+ return (
+ <PostListings
+ posts={this.state.postsRes.data.posts}
+ removeDuplicates
+ enableDownvotes={enableDownvotes(site_res)}
+ enableNsfw={enableNsfw(site_res)}
+ allLanguages={site_res.all_languages}
+ siteLanguages={site_res.discussion_languages}
+ onBlockPerson={this.handleBlockPerson}
+ onPostEdit={this.handlePostEdit}
+ onPostVote={this.handlePostVote}
+ onPostReport={this.handlePostReport}
+ onLockPost={this.handleLockPost}
+ onDeletePost={this.handleDeletePost}
+ onRemovePost={this.handleRemovePost}
+ onSavePost={this.handleSavePost}
+ onPurgePerson={this.handlePurgePerson}
+ onPurgePost={this.handlePurgePost}
+ onBanPerson={this.handleBanPerson}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onFeaturePost={this.handleFeaturePost}
+ />
+ );
+ }
} else {
- return (
- <CommentNodes
- nodes={commentsToFlatNodes(comments)}
- viewType={CommentViewType.Flat}
- noIndent
- showContext
- enableDownvotes={enableDownvotes(site_res)}
- moderators={communityRes?.moderators}
- admins={site_res.admins}
- allLanguages={site_res.all_languages}
- siteLanguages={site_res.discussion_languages}
- />
- );
+ switch (this.state.commentsRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success":
+ return (
+ <CommentNodes
+ nodes={commentsToFlatNodes(this.state.commentsRes.data.comments)}
+ viewType={CommentViewType.Flat}
+ finished={this.state.finished}
+ noIndent
+ showContext
+ enableDownvotes={enableDownvotes(site_res)}
+ moderators={communityRes.moderators}
+ admins={site_res.admins}
+ allLanguages={site_res.all_languages}
+ siteLanguages={site_res.discussion_languages}
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPerson}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
+ />
+ );
+ }
}
}
- get communityInfo() {
- const community = this.state.communityRes?.community_view.community;
+ communityInfo(res: GetCommunityResponse) {
+ const community = res.community_view.community;
return (
community && (
);
}
- get selects() {
+ selects(res: GetCommunityResponse) {
// let communityRss = this.state.communityRes.map(r =>
// communityRSSUrl(r.community_view.community.actor_id, this.state.sort)
// );
const { dataType, sort } = getCommunityQueryParams();
- const res = this.state.communityRes;
const communityRss = res
? communityRSSUrl(res.community_view.community.actor_id, sort)
: undefined;
}));
}
- updateUrl({ dataType, page, sort }: Partial<CommunityProps>) {
+ async updateUrl({ dataType, page, sort }: Partial<CommunityProps>) {
const {
dataType: urlDataType,
page: urlPage,
`/c/${this.props.match.params.name}${getQueryString(queryParams)}`
);
- this.setState({
- comments: [],
- posts: [],
- listingsLoading: true,
- });
-
- this.fetchData();
+ await this.fetchData();
}
- fetchData() {
+ async fetchData() {
const { dataType, page, sort } = getCommunityQueryParams();
const { name } = this.props.match.params;
- let req: string;
if (dataType === DataType.Post) {
- const form: GetPosts = {
- page,
- limit: fetchLimit,
- sort,
- type_: "All",
- community_name: name,
- saved_only: false,
- auth: myAuth(false),
- };
- req = wsClient.getPosts(form);
+ this.setState({ postsRes: { state: "loading" } });
+ this.setState({
+ postsRes: await HttpService.client.getPosts({
+ page,
+ limit: fetchLimit,
+ sort,
+ type_: "All",
+ community_name: name,
+ saved_only: false,
+ auth: myAuth(),
+ }),
+ });
} else {
- const form: GetComments = {
- page,
- limit: fetchLimit,
- sort: postToCommentSortType(sort),
- type_: "All",
- community_name: name,
- saved_only: false,
- auth: myAuth(false),
- };
-
- req = wsClient.getComments(form);
+ this.setState({ commentsRes: { state: "loading" } });
+ this.setState({
+ commentsRes: await HttpService.client.getComments({
+ page,
+ limit: fetchLimit,
+ sort: postToCommentSortType(sort),
+ type_: "All",
+ community_name: name,
+ saved_only: false,
+ auth: myAuth(),
+ }),
+ });
}
- WebSocketService.Instance.send(req);
+ restoreScrollPosition(this.context);
+ setupTippy();
}
- parseMessage(msg: any) {
- const { page } = getCommunityQueryParams();
- const op = wsUserOp(msg);
- console.log(msg);
- const res = this.state.communityRes;
-
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.context.router.history.push("/");
- } else if (msg.reconnect) {
- if (res) {
- WebSocketService.Instance.send(
- wsClient.communityJoin({
- community_id: res.community_view.community.id,
- })
- );
- }
-
- this.fetchData();
- } else {
- switch (op) {
- case UserOperation.GetCommunity: {
- const data = wsJsonToRes<GetCommunityResponse>(msg);
-
- this.setState({ communityRes: data, communityLoading: false });
- // TODO why is there no auth in this form?
- WebSocketService.Instance.send(
- wsClient.communityJoin({
- community_id: data.community_view.community.id,
- })
- );
+ async handleDeleteCommunity(form: DeleteCommunity) {
+ const deleteCommunityRes = await HttpService.client.deleteCommunity(form);
+ this.updateCommunity(deleteCommunityRes);
+ }
- break;
- }
+ async handleAddModToCommunity(form: AddModToCommunity) {
+ const addModRes = await HttpService.client.addModToCommunity(form);
+ this.updateModerators(addModRes);
+ }
- case UserOperation.EditCommunity:
- case UserOperation.DeleteCommunity:
- case UserOperation.RemoveCommunity: {
- const { community_view, discussion_languages } =
- wsJsonToRes<CommunityResponse>(msg);
+ async handleFollow(form: FollowCommunity) {
+ const followCommunityRes = await HttpService.client.followCommunity(form);
+ this.updateCommunity(followCommunityRes);
- if (res) {
- res.community_view = community_view;
- res.discussion_languages = discussion_languages;
- this.setState(this.state);
- }
+ // Update myUserInfo
+ if (followCommunityRes.state == "success") {
+ const communityId = followCommunityRes.data.community_view.community.id;
+ const mui = UserService.Instance.myUserInfo;
+ if (mui) {
+ mui.follows = mui.follows.filter(i => i.community.id != communityId);
+ }
+ }
+ }
- break;
- }
+ async handlePurgeCommunity(form: PurgeCommunity) {
+ const purgeCommunityRes = await HttpService.client.purgeCommunity(form);
+ this.purgeItem(purgeCommunityRes);
+ }
- case UserOperation.FollowCommunity: {
- const {
- community_view: {
- subscribed,
- counts: { subscribers },
- },
- } = wsJsonToRes<CommunityResponse>(msg);
-
- if (res) {
- res.community_view.subscribed = subscribed;
- res.community_view.counts.subscribers = subscribers;
- this.setState(this.state);
- }
-
- break;
- }
+ async handlePurgePerson(form: PurgePerson) {
+ const purgePersonRes = await HttpService.client.purgePerson(form);
+ this.purgeItem(purgePersonRes);
+ }
- case UserOperation.GetPosts: {
- const { posts } = wsJsonToRes<GetPostsResponse>(msg);
+ async handlePurgeComment(form: PurgeComment) {
+ const purgeCommentRes = await HttpService.client.purgeComment(form);
+ this.purgeItem(purgeCommentRes);
+ }
- this.setState({ posts, listingsLoading: false });
- restoreScrollPosition(this.context);
- setupTippy();
+ async handlePurgePost(form: PurgePost) {
+ const purgeRes = await HttpService.client.purgePost(form);
+ this.purgeItem(purgeRes);
+ }
- break;
- }
+ async handleBlockCommunity(form: BlockCommunity) {
+ const blockCommunityRes = await HttpService.client.blockCommunity(form);
+ if (blockCommunityRes.state == "success") {
+ updateCommunityBlock(blockCommunityRes.data);
+ }
+ }
- case UserOperation.EditPost:
- case UserOperation.DeletePost:
- case UserOperation.RemovePost:
- case UserOperation.LockPost:
- case UserOperation.FeaturePost:
- case UserOperation.SavePost: {
- const { post_view } = wsJsonToRes<PostResponse>(msg);
+ async handleBlockPerson(form: BlockPerson) {
+ const blockPersonRes = await HttpService.client.blockPerson(form);
+ if (blockPersonRes.state == "success") {
+ updatePersonBlock(blockPersonRes.data);
+ }
+ }
- editPostFindRes(post_view, this.state.posts);
- this.setState(this.state);
+ async handleRemoveCommunity(form: RemoveCommunity) {
+ const removeCommunityRes = await HttpService.client.removeCommunity(form);
+ this.updateCommunity(removeCommunityRes);
+ }
- break;
- }
+ async handleEditCommunity(form: EditCommunity) {
+ const res = await HttpService.client.editCommunity(form);
+ this.updateCommunity(res);
- case UserOperation.CreatePost: {
- const { post_view } = wsJsonToRes<PostResponse>(msg);
+ return res;
+ }
- const showPostNotifs =
- UserService.Instance.myUserInfo?.local_user_view.local_user
- .show_new_post_notifs;
+ async handleCreateComment(form: CreateComment) {
+ const createCommentRes = await HttpService.client.createComment(form);
+ this.createAndUpdateComments(createCommentRes);
- // Only push these if you're on the first page, you pass the nsfw check, and it isn't blocked
- if (page === 1 && nsfwCheck(post_view) && !isPostBlocked(post_view)) {
- this.state.posts.unshift(post_view);
- if (showPostNotifs) {
- notifyPost(post_view, this.context.router);
- }
- this.setState(this.state);
- }
+ return createCommentRes;
+ }
- break;
- }
+ async handleEditComment(form: EditComment) {
+ const editCommentRes = await HttpService.client.editComment(form);
+ this.findAndUpdateComment(editCommentRes);
- case UserOperation.CreatePostLike: {
- const { post_view } = wsJsonToRes<PostResponse>(msg);
+ return editCommentRes;
+ }
- createPostLikeFindRes(post_view, this.state.posts);
- this.setState(this.state);
+ async handleDeleteComment(form: DeleteComment) {
+ const deleteCommentRes = await HttpService.client.deleteComment(form);
+ this.findAndUpdateComment(deleteCommentRes);
+ }
- break;
- }
+ async handleDeletePost(form: DeletePost) {
+ const deleteRes = await HttpService.client.deletePost(form);
+ this.findAndUpdatePost(deleteRes);
+ }
- case UserOperation.AddModToCommunity: {
- const { moderators } = wsJsonToRes<AddModToCommunityResponse>(msg);
+ async handleRemovePost(form: RemovePost) {
+ const removeRes = await HttpService.client.removePost(form);
+ this.findAndUpdatePost(removeRes);
+ }
- if (res) {
- res.moderators = moderators;
- this.setState(this.state);
- }
+ async handleRemoveComment(form: RemoveComment) {
+ const removeCommentRes = await HttpService.client.removeComment(form);
+ this.findAndUpdateComment(removeCommentRes);
+ }
- break;
- }
+ async handleSaveComment(form: SaveComment) {
+ const saveCommentRes = await HttpService.client.saveComment(form);
+ this.findAndUpdateComment(saveCommentRes);
+ }
- case UserOperation.BanFromCommunity: {
- const {
- person_view: {
- person: { id: personId },
- },
- banned,
- } = wsJsonToRes<BanFromCommunityResponse>(msg);
+ async handleSavePost(form: SavePost) {
+ const saveRes = await HttpService.client.savePost(form);
+ this.findAndUpdatePost(saveRes);
+ }
- // TODO this might be incorrect
- this.state.posts
- .filter(p => p.creator.id === personId)
- .forEach(p => (p.creator_banned_from_community = banned));
+ async handleFeaturePost(form: FeaturePost) {
+ const featureRes = await HttpService.client.featurePost(form);
+ this.findAndUpdatePost(featureRes);
+ }
- this.setState(this.state);
+ async handleCommentVote(form: CreateCommentLike) {
+ const voteRes = await HttpService.client.likeComment(form);
+ this.findAndUpdateComment(voteRes);
+ }
- break;
- }
+ async handlePostEdit(form: EditPost) {
+ const res = await HttpService.client.editPost(form);
+ this.findAndUpdatePost(res);
+ }
- case UserOperation.GetComments: {
- const { comments } = wsJsonToRes<GetCommentsResponse>(msg);
- this.setState({ comments, listingsLoading: false });
+ async handlePostVote(form: CreatePostLike) {
+ const voteRes = await HttpService.client.likePost(form);
+ this.findAndUpdatePost(voteRes);
+ }
- break;
- }
+ async handleCommentReport(form: CreateCommentReport) {
+ const reportRes = await HttpService.client.createCommentReport(form);
+ if (reportRes.state == "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
- case UserOperation.EditComment:
- case UserOperation.DeleteComment:
- case UserOperation.RemoveComment: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
- editCommentRes(comment_view, this.state.comments);
- this.setState(this.state);
+ async handlePostReport(form: CreatePostReport) {
+ const reportRes = await HttpService.client.createPostReport(form);
+ if (reportRes.state == "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
- break;
- }
+ async handleLockPost(form: LockPost) {
+ const lockRes = await HttpService.client.lockPost(form);
+ this.findAndUpdatePost(lockRes);
+ }
- case UserOperation.CreateComment: {
- const { form_id, comment_view } = wsJsonToRes<CommentResponse>(msg);
+ async handleDistinguishComment(form: DistinguishComment) {
+ const distinguishRes = await HttpService.client.distinguishComment(form);
+ this.findAndUpdateComment(distinguishRes);
+ }
- // Necessary since it might be a user reply
- if (form_id) {
- this.setState(({ comments }) => ({
- comments: [comment_view].concat(comments),
- }));
- }
+ async handleAddAdmin(form: AddAdmin) {
+ const addAdminRes = await HttpService.client.addAdmin(form);
- break;
- }
+ if (addAdminRes.state == "success") {
+ this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
+ }
+ }
- case UserOperation.SaveComment: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
+ async handleTransferCommunity(form: TransferCommunity) {
+ const transferCommunityRes = await HttpService.client.transferCommunity(
+ form
+ );
+ toast(i18n.t("transfer_community"));
+ this.updateCommunityFull(transferCommunityRes);
+ }
- saveCommentRes(comment_view, this.state.comments);
- this.setState(this.state);
+ async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
+ const readRes = await HttpService.client.markCommentReplyAsRead(form);
+ this.findAndUpdateCommentReply(readRes);
+ }
- break;
- }
+ async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
+ // TODO not sure what to do here. Maybe it is actually optional, because post doesn't need it.
+ await HttpService.client.markPersonMentionAsRead(form);
+ }
- case UserOperation.CreateCommentLike: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
+ async handleBanFromCommunity(form: BanFromCommunity) {
+ const banRes = await HttpService.client.banFromCommunity(form);
+ this.updateBanFromCommunity(banRes);
+ }
- createCommentLikeRes(comment_view, this.state.comments);
- this.setState(this.state);
+ async handleBanPerson(form: BanPerson) {
+ const banRes = await HttpService.client.banPerson(form);
+ this.updateBan(banRes);
+ }
- break;
+ updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (s.postsRes.state == "success") {
+ s.postsRes.data.posts
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
}
+ if (s.commentsRes.state == "success") {
+ s.commentsRes.data.comments
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
+ }
+ return s;
+ });
+ }
+ }
- case UserOperation.BlockPerson: {
- const data = wsJsonToRes<BlockPersonResponse>(msg);
- updatePersonBlock(data);
-
- break;
+ updateBan(banRes: RequestState<BanPersonResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (s.postsRes.state == "success") {
+ s.postsRes.data.posts
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
+ }
+ if (s.commentsRes.state == "success") {
+ s.commentsRes.data.comments
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
}
+ return s;
+ });
+ }
+ }
- case UserOperation.CreatePostReport:
- case UserOperation.CreateCommentReport: {
- const data = wsJsonToRes<PostReportResponse>(msg);
+ updateCommunity(res: RequestState<CommunityResponse>) {
+ this.setState(s => {
+ if (s.communityRes.state == "success" && res.state == "success") {
+ s.communityRes.data.community_view = res.data.community_view;
+ s.communityRes.data.discussion_languages =
+ res.data.discussion_languages;
+ }
+ return s;
+ });
+ }
- if (data) {
- toast(i18n.t("report_created"));
- }
+ updateCommunityFull(res: RequestState<GetCommunityResponse>) {
+ this.setState(s => {
+ if (s.communityRes.state == "success" && res.state == "success") {
+ s.communityRes.data.community_view = res.data.community_view;
+ s.communityRes.data.moderators = res.data.moderators;
+ }
+ return s;
+ });
+ }
- break;
- }
+ purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
+ if (purgeRes.state == "success") {
+ toast(i18n.t("purge_success"));
+ this.context.router.history.push(`/`);
+ }
+ }
- case UserOperation.PurgeCommunity: {
- const { success } = wsJsonToRes<PurgeItemResponse>(msg);
+ findAndUpdateComment(res: RequestState<CommentResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state == "success" && res.state == "success") {
+ s.commentsRes.data.comments = editComment(
+ res.data.comment_view,
+ s.commentsRes.data.comments
+ );
+ s.finished.set(res.data.comment_view.comment.id, true);
+ }
+ return s;
+ });
+ }
- if (success) {
- toast(i18n.t("purge_success"));
- this.context.router.history.push(`/`);
- }
+ createAndUpdateComments(res: RequestState<CommentResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state == "success" && res.state == "success") {
+ s.commentsRes.data.comments.unshift(res.data.comment_view);
- break;
- }
+ // Set finished for the parent
+ s.finished.set(
+ getCommentParentId(res.data.comment_view.comment) ?? 0,
+ true
+ );
+ }
+ return s;
+ });
+ }
+
+ findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state == "success" && res.state == "success") {
+ s.commentsRes.data.comments = editWith(
+ res.data.comment_reply_view,
+ s.commentsRes.data.comments
+ );
+ }
+ return s;
+ });
+ }
- case UserOperation.BlockCommunity: {
- const data = wsJsonToRes<BlockCommunityResponse>(msg);
- if (res) {
- res.community_view.blocked = data.blocked;
- this.setState(this.state);
- }
- updateCommunityBlock(data);
+ findAndUpdatePost(res: RequestState<PostResponse>) {
+ this.setState(s => {
+ if (s.postsRes.state == "success" && res.state == "success") {
+ s.postsRes.data.posts = editPost(
+ res.data.post_view,
+ s.postsRes.data.posts
+ );
+ }
+ return s;
+ });
+ }
- break;
- }
+ updateModerators(res: RequestState<AddModToCommunityResponse>) {
+ // Update the moderators
+ this.setState(s => {
+ if (s.communityRes.state == "success" && res.state == "success") {
+ s.communityRes.data.moderators = res.data.moderators;
}
- }
+ return s;
+ });
}
}
import { Component } from "inferno";
-import { CommunityView, GetSiteResponse } from "lemmy-js-client";
-import { Subscription } from "rxjs";
-import { i18n } from "../../i18next";
import {
- enableNsfw,
- isBrowser,
- setIsoData,
- toast,
- wsSubscribe,
-} from "../../utils";
+ CreateCommunity as CreateCommunityI,
+ GetSiteResponse,
+} from "lemmy-js-client";
+import { i18n } from "../../i18next";
+import { HttpService } from "../../services/HttpService";
+import { enableNsfw, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
-import { Spinner } from "../common/icon";
import { CommunityForm } from "./community-form";
interface CreateCommunityState {
siteRes: GetSiteResponse;
- loading: boolean;
}
export class CreateCommunity extends Component<any, CreateCommunityState> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: CreateCommunityState = {
siteRes: this.isoData.site_res,
- loading: false,
};
constructor(props: any, context: any) {
super(props, context);
this.handleCommunityCreate = this.handleCommunityCreate.bind(this);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
- }
-
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
- }
}
get documentTitle(): string {
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
- {this.state.loading ? (
- <h5>
- <Spinner large />
- </h5>
- ) : (
- <div className="row">
- <div className="col-12 col-lg-6 offset-lg-3 mb-4">
- <h5>{i18n.t("create_community")}</h5>
- <CommunityForm
- onCreate={this.handleCommunityCreate}
- enableNsfw={enableNsfw(this.state.siteRes)}
- allLanguages={this.state.siteRes.all_languages}
- siteLanguages={this.state.siteRes.discussion_languages}
- communityLanguages={this.state.siteRes.discussion_languages}
- />
- </div>
+ <div className="row">
+ <div className="col-12 col-lg-6 offset-lg-3 mb-4">
+ <h5>{i18n.t("create_community")}</h5>
+ <CommunityForm
+ onUpsertCommunity={this.handleCommunityCreate}
+ enableNsfw={enableNsfw(this.state.siteRes)}
+ allLanguages={this.state.siteRes.all_languages}
+ siteLanguages={this.state.siteRes.discussion_languages}
+ communityLanguages={this.state.siteRes.discussion_languages}
+ />
</div>
- )}
+ </div>
</div>
);
}
- handleCommunityCreate(cv: CommunityView) {
- this.props.history.push(`/c/${cv.community.name}`);
- }
-
- parseMessage(msg: any) {
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
+ async handleCommunityCreate(form: CreateCommunityI) {
+ const res = await HttpService.client.createCommunity(form);
+ if (res.state === "success") {
+ const name = res.data.community_view.community.name;
+ this.props.history.replace(`/c/${name}`);
}
}
}
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import { Link } from "inferno-router";
import {
AddModToCommunity,
CommunityModeratorView,
CommunityView,
DeleteCommunity,
+ EditCommunity,
FollowCommunity,
Language,
PersonView,
RemoveCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
import {
amAdmin,
amMod,
getUnixTime,
hostname,
mdToHtml,
- myAuth,
+ myAuthRequired,
numToSI,
- wsClient,
} from "../../utils";
import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
enableNsfw?: boolean;
showIcon?: boolean;
editable?: boolean;
+ onDeleteCommunity(form: DeleteCommunity): void;
+ onRemoveCommunity(form: RemoveCommunity): void;
+ onLeaveModTeam(form: AddModToCommunity): void;
+ onFollowCommunity(form: FollowCommunity): void;
+ onBlockCommunity(form: BlockCommunity): void;
+ onPurgeCommunity(form: PurgeCommunity): void;
+ onEditCommunity(form: EditCommunity): void;
}
interface SidebarState {
showRemoveDialog: boolean;
showPurgeDialog: boolean;
purgeReason?: string;
- purgeLoading: boolean;
showConfirmLeaveModTeam: boolean;
+ deleteCommunityLoading: boolean;
+ removeCommunityLoading: boolean;
+ leaveModTeamLoading: boolean;
+ followCommunityLoading: boolean;
+ blockCommunityLoading: boolean;
+ purgeCommunityLoading: boolean;
}
export class Sidebar extends Component<SidebarProps, SidebarState> {
showEdit: false,
showRemoveDialog: false,
showPurgeDialog: false,
- purgeLoading: false,
showConfirmLeaveModTeam: false,
+ deleteCommunityLoading: false,
+ removeCommunityLoading: false,
+ leaveModTeamLoading: false,
+ followCommunityLoading: false,
+ blockCommunityLoading: false,
+ purgeCommunityLoading: false,
};
constructor(props: any, context: any) {
super(props, context);
- this.handleEditCommunity = this.handleEditCommunity.bind(this);
this.handleEditCancel = this.handleEditCancel.bind(this);
}
+ componentWillReceiveProps(
+ nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>
+ ): void {
+ if (this.props.moderators != nextProps.moderators) {
+ this.setState({
+ showConfirmLeaveModTeam: false,
+ });
+ }
+
+ if (this.props.community_view != nextProps.community_view) {
+ this.setState({
+ showEdit: false,
+ showPurgeDialog: false,
+ showRemoveDialog: false,
+ deleteCommunityLoading: false,
+ removeCommunityLoading: false,
+ leaveModTeamLoading: false,
+ followCommunityLoading: false,
+ blockCommunityLoading: false,
+ purgeCommunityLoading: false,
+ });
+ }
+ }
+
render() {
return (
<div>
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
communityLanguages={this.props.communityLanguages}
- onEdit={this.handleEditCommunity}
+ onUpsertCommunity={this.props.onEditCommunity}
onCancel={this.handleEditCancel}
enableNsfw={this.props.enableNsfw}
/>
{subscribed === "Subscribed" && (
<button
className="btn btn-secondary btn-sm mr-2"
- onClick={linkEvent(this, this.handleUnsubscribe)}
+ onClick={linkEvent(this, this.handleUnfollowCommunity)}
>
- <Icon icon="check" classes="icon-inline text-success mr-1" />
- {i18n.t("joined")}
+ {this.state.followCommunityLoading ? (
+ <Spinner />
+ ) : (
+ <>
+ <Icon icon="check" classes="icon-inline text-success mr-1" />
+ {i18n.t("joined")}
+ </>
+ )}
</button>
)}
{subscribed === "Pending" && (
<button
className="btn btn-warning mr-2"
- onClick={linkEvent(this, this.handleUnsubscribe)}
+ onClick={linkEvent(this, this.handleUnfollowCommunity)}
>
- {i18n.t("subscribe_pending")}
+ {this.state.followCommunityLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("subscribe_pending")
+ )}
</button>
)}
{community.removed && (
{community_view.subscribed == "NotSubscribed" && (
<button
className="btn btn-secondary btn-block"
- onClick={linkEvent(this, this.handleSubscribe)}
+ onClick={linkEvent(this, this.handleFollowCommunity)}
>
- {i18n.t("subscribe")}
+ {this.state.followCommunityLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("subscribe")
+ )}
</button>
)}
</div>
(blocked ? (
<button
className="btn btn-danger btn-block"
- onClick={linkEvent(this, this.handleUnblock)}
+ onClick={linkEvent(this, this.handleBlockCommunity)}
>
- {i18n.t("unblock_community")}
+ {this.state.blockCommunityLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("unblock_community")
+ )}
</button>
) : (
<button
className="btn btn-danger btn-block"
- onClick={linkEvent(this, this.handleBlock)}
+ onClick={linkEvent(this, this.handleBlockCommunity)}
>
- {i18n.t("block_community")}
+ {this.state.blockCommunityLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("block_community")
+ )}
</button>
))}
</div>
<li className="list-inline-item-action">
<button
className="btn btn-link text-muted d-inline-block"
- onClick={linkEvent(this, this.handleLeaveModTeamClick)}
+ onClick={linkEvent(this, this.handleLeaveModTeam)}
>
{i18n.t("yes")}
</button>
<li className="list-inline-item-action">
<button
className="btn btn-link text-muted d-inline-block"
- onClick={linkEvent(this, this.handleDeleteClick)}
+ onClick={linkEvent(this, this.handleDeleteCommunity)}
data-tippy-content={
!community_view.community.deleted
? i18n.t("delete")
: i18n.t("restore")
}
>
- <Icon
- icon="trash"
- classes={`icon-inline ${
- community_view.community.deleted && "text-danger"
- }`}
- />
+ {this.state.deleteCommunityLoading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="trash"
+ classes={`icon-inline ${
+ community_view.community.deleted && "text-danger"
+ }`}
+ />
+ )}{" "}
</button>
</li>
)}
) : (
<button
className="btn btn-link text-muted d-inline-block"
- onClick={linkEvent(this, this.handleModRemoveSubmit)}
+ onClick={linkEvent(this, this.handleRemoveCommunity)}
>
- {i18n.t("restore")}
+ {this.state.removeCommunityLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("restore")
+ )}
</button>
)}
<button
)}
</ul>
{this.state.showRemoveDialog && (
- <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
+ <form onSubmit={linkEvent(this, this.handleRemoveCommunity)}>
<div className="form-group">
<label className="col-form-label" htmlFor="remove-reason">
{i18n.t("reason")}
{/* </div> */}
<div className="form-group">
<button type="submit" className="btn btn-secondary">
- {i18n.t("remove_community")}
+ {this.state.removeCommunityLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("remove_community")
+ )}
</button>
</div>
</form>
)}
{this.state.showPurgeDialog && (
- <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
+ <form onSubmit={linkEvent(this, this.handlePurgeCommunity)}>
<div className="form-group">
<PurgeWarning />
</div>
/>
</div>
<div className="form-group">
- {this.state.purgeLoading ? (
+ {this.state.purgeCommunityLoading ? (
<Spinner />
) : (
<button
i.setState({ showEdit: true });
}
- handleEditCommunity() {
- this.setState({ showEdit: false });
- }
-
handleEditCancel() {
this.setState({ showEdit: false });
}
- handleDeleteClick(i: Sidebar, event: any) {
- event.preventDefault();
- const auth = myAuth();
- if (auth) {
- const deleteForm: DeleteCommunity = {
- community_id: i.props.community_view.community.id,
- deleted: !i.props.community_view.community.deleted,
- auth,
- };
- WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
- }
- }
-
handleShowConfirmLeaveModTeamClick(i: Sidebar) {
i.setState({ showConfirmLeaveModTeam: true });
}
- handleLeaveModTeamClick(i: Sidebar) {
- const mui = UserService.Instance.myUserInfo;
- const auth = myAuth();
- if (auth && mui) {
- const form: AddModToCommunity = {
- person_id: mui.local_user_view.person.id,
- community_id: i.props.community_view.community.id,
- added: false,
- auth,
- };
- WebSocketService.Instance.send(wsClient.addModToCommunity(form));
- i.setState({ showConfirmLeaveModTeam: false });
- }
- }
-
handleCancelLeaveModTeamClick(i: Sidebar) {
i.setState({ showConfirmLeaveModTeam: false });
}
- handleUnsubscribe(i: Sidebar, event: any) {
- event.preventDefault();
- const community_id = i.props.community_view.community.id;
- const auth = myAuth();
- if (auth) {
- const form: FollowCommunity = {
- community_id,
- follow: false,
- auth,
- };
- WebSocketService.Instance.send(wsClient.followCommunity(form));
- }
-
- // Update myUserInfo
- const mui = UserService.Instance.myUserInfo;
- if (mui) {
- mui.follows = mui.follows.filter(i => i.community.id != community_id);
- }
- }
-
- handleSubscribe(i: Sidebar, event: any) {
- event.preventDefault();
- const community_id = i.props.community_view.community.id;
- const auth = myAuth();
- if (auth) {
- const form: FollowCommunity = {
- community_id,
- follow: true,
- auth,
- };
- WebSocketService.Instance.send(wsClient.followCommunity(form));
- }
-
- // Update myUserInfo
- const mui = UserService.Instance.myUserInfo;
- if (mui) {
- mui.follows.push({
- community: i.props.community_view.community,
- follower: mui.local_user_view.person,
- });
- }
- }
-
get canPost(): boolean {
return (
!this.props.community_view.community.posting_restricted_to_mods ||
i.setState({ removeExpires: event.target.value });
}
- handleModRemoveSubmit(i: Sidebar, event: any) {
- event.preventDefault();
- const auth = myAuth();
- if (auth) {
- const removeForm: RemoveCommunity = {
- community_id: i.props.community_view.community.id,
- removed: !i.props.community_view.community.removed,
- reason: i.state.removeReason,
- expires: getUnixTime(i.state.removeExpires),
- auth,
- };
- WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
-
- i.setState({ showRemoveDialog: false });
- }
- }
-
handlePurgeCommunityShow(i: Sidebar) {
i.setState({ showPurgeDialog: true, showRemoveDialog: false });
}
i.setState({ purgeReason: event.target.value });
}
- handlePurgeSubmit(i: Sidebar, event: any) {
- event.preventDefault();
+ // TODO Do we need two of these?
+ handleUnfollowCommunity(i: Sidebar) {
+ i.setState({ followCommunityLoading: true });
+ i.props.onFollowCommunity({
+ community_id: i.props.community_view.community.id,
+ follow: false,
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleFollowCommunity(i: Sidebar) {
+ i.setState({ followCommunityLoading: true });
+ i.props.onFollowCommunity({
+ community_id: i.props.community_view.community.id,
+ follow: true,
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleBlockCommunity(i: Sidebar) {
+ i.setState({ blockCommunityLoading: true });
+ i.props.onBlockCommunity({
+ community_id: 0,
+ block: !i.props.community_view.blocked,
+ auth: myAuthRequired(),
+ });
+ }
- const auth = myAuth();
- if (auth) {
- const form: PurgeCommunity = {
+ handleLeaveModTeam(i: Sidebar) {
+ const myId = UserService.Instance.myUserInfo?.local_user_view.person.id;
+ if (myId) {
+ i.setState({ leaveModTeamLoading: true });
+ i.props.onLeaveModTeam({
community_id: i.props.community_view.community.id,
- reason: i.state.purgeReason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.purgeCommunity(form));
- i.setState({ purgeLoading: true });
+ person_id: 92,
+ added: false,
+ auth: myAuthRequired(),
+ });
}
}
- handleBlock(i: Sidebar, event: any) {
+ handleDeleteCommunity(i: Sidebar) {
+ i.setState({ deleteCommunityLoading: true });
+ i.props.onDeleteCommunity({
+ community_id: i.props.community_view.community.id,
+ deleted: !i.props.community_view.community.deleted,
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleRemoveCommunity(i: Sidebar, event: any) {
event.preventDefault();
- const auth = myAuth();
- if (auth) {
- const blockCommunityForm: BlockCommunity = {
- community_id: i.props.community_view.community.id,
- block: true,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.blockCommunity(blockCommunityForm)
- );
- }
+ i.setState({ removeCommunityLoading: true });
+ i.props.onRemoveCommunity({
+ community_id: i.props.community_view.community.id,
+ removed: !i.props.community_view.community.removed,
+ reason: i.state.removeReason,
+ expires: getUnixTime(i.state.removeExpires), // TODO fix this
+ auth: myAuthRequired(),
+ });
}
- handleUnblock(i: Sidebar, event: any) {
+ handlePurgeCommunity(i: Sidebar, event: any) {
event.preventDefault();
- const auth = myAuth();
- if (auth) {
- const blockCommunityForm: BlockCommunity = {
- community_id: i.props.community_view.community.id,
- block: false,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.blockCommunity(blockCommunityForm)
- );
- }
+ i.setState({ purgeCommunityLoading: true });
+ i.props.onPurgeCommunity({
+ community_id: i.props.community_view.community.id,
+ reason: i.state.purgeReason,
+ auth: myAuthRequired(),
+ });
}
}
-import autosize from "autosize";
import { Component, linkEvent } from "inferno";
import {
BannedPersonsResponse,
- GetBannedPersons,
+ CreateCustomEmoji,
+ DeleteCustomEmoji,
+ EditCustomEmoji,
+ EditSite,
GetFederatedInstancesResponse,
GetSiteResponse,
PersonView,
- SiteResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
-import { WebSocketService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
capitalizeFirstLetter,
- isBrowser,
- myAuth,
- randomStr,
+ fetchThemeList,
+ myAuthRequired,
+ removeFromEmojiDataModel,
setIsoData,
showLocal,
toast,
- wsClient,
- wsSubscribe,
+ updateEmojiDataModel,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
interface AdminSettingsState {
siteRes: GetSiteResponse;
- instancesRes?: GetFederatedInstancesResponse;
banned: PersonView[];
- loading: boolean;
- leaveAdminTeamLoading: boolean;
+ currentTab: string;
+ instancesRes: RequestState<GetFederatedInstancesResponse>;
+ bannedRes: RequestState<BannedPersonsResponse>;
+ leaveAdminTeamRes: RequestState<GetSiteResponse>;
+ themeList: string[];
+ isIsomorphic: boolean;
}
export class AdminSettings extends Component<any, AdminSettingsState> {
- private siteConfigTextAreaId = `site-config-${randomStr()}`;
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: AdminSettingsState = {
siteRes: this.isoData.site_res,
banned: [],
- loading: true,
- leaveAdminTeamLoading: false,
+ currentTab: "site",
+ bannedRes: { state: "empty" },
+ instancesRes: { state: "empty" },
+ leaveAdminTeamRes: { state: "empty" },
+ themeList: [],
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
super(props, context);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleEditSite = this.handleEditSite.bind(this);
+ this.handleEditEmoji = this.handleEditEmoji.bind(this);
+ this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
+ this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
+ const [bannedRes, instancesRes] = this.isoData.routeData;
this.state = {
...this.state,
- banned: (this.isoData.routeData[0] as BannedPersonsResponse).banned,
- instancesRes: this.isoData
- .routeData[1] as GetFederatedInstancesResponse,
- loading: false,
+ bannedRes,
+ instancesRes,
+ isIsomorphic: true,
};
- } else {
- const cAuth = myAuth();
- if (cAuth) {
- WebSocketService.Instance.send(
- wsClient.getBannedPersons({
- auth: cAuth,
- })
- );
- WebSocketService.Instance.send(
- wsClient.getFederatedInstances({ auth: cAuth })
- );
- }
}
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<any>[] = [];
+ async fetchData() {
+ this.setState({
+ bannedRes: { state: "loading" },
+ instancesRes: { state: "loading" },
+ themeList: [],
+ });
- const auth = req.auth;
- if (auth) {
- const bannedPersonsForm: GetBannedPersons = { auth };
- promises.push(req.client.getBannedPersons(bannedPersonsForm));
- promises.push(req.client.getFederatedInstances({ auth }));
- }
+ const auth = myAuthRequired();
- return promises;
+ const [bannedRes, instancesRes, themeList] = await Promise.all([
+ HttpService.client.getBannedPersons({ auth }),
+ HttpService.client.getFederatedInstances({ auth }),
+ fetchThemeList(),
+ ]);
+
+ this.setState({
+ bannedRes,
+ instancesRes,
+ themeList,
+ });
}
- componentDidMount() {
- if (isBrowser()) {
- var textarea: any = document.getElementById(this.siteConfigTextAreaId);
- autosize(textarea);
+ static fetchInitialData({
+ auth,
+ client,
+ }: InitialFetchRequest): Promise<any>[] {
+ const promises: Promise<RequestState<any>>[] = [];
+
+ if (auth) {
+ promises.push(client.getBannedPersons({ auth }));
+ promises.push(client.getFederatedInstances({ auth }));
+ } else {
+ promises.push(
+ Promise.resolve({ state: "empty" }),
+ Promise.resolve({ state: "empty" })
+ );
}
+
+ return promises;
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.fetchData();
}
}
}
render() {
+ const federationData =
+ this.state.instancesRes.state === "success"
+ ? this.state.instancesRes.data.federated_instances
+ : undefined;
+
return (
<div className="container-lg">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
- {this.state.loading ? (
- <h5>
- <Spinner large />
- </h5>
- ) : (
- <Tabs
- tabs={[
- {
- key: "site",
- label: i18n.t("site"),
- getNode: () => (
- <div className="row">
- <div className="col-12 col-md-6">
- <SiteForm
- siteRes={this.state.siteRes}
- instancesRes={this.state.instancesRes}
- showLocal={showLocal(this.isoData)}
- />
- </div>
- <div className="col-12 col-md-6">
- {this.admins()}
- {this.bannedUsers()}
- </div>
+ <Tabs
+ tabs={[
+ {
+ key: "site",
+ label: i18n.t("site"),
+ getNode: () => (
+ <div className="row">
+ <div className="col-12 col-md-6">
+ <SiteForm
+ showLocal={showLocal(this.isoData)}
+ allowedInstances={federationData?.allowed}
+ blockedInstances={federationData?.blocked}
+ onSaveSite={this.handleEditSite}
+ siteRes={this.state.siteRes}
+ themeList={this.state.themeList}
+ />
</div>
- ),
- },
- {
- key: "rate_limiting",
- label: "Rate Limiting",
- getNode: () => (
- <RateLimitForm
- localSiteRateLimit={
- this.state.siteRes.site_view.local_site_rate_limit
- }
- applicationQuestion={
- this.state.siteRes.site_view.local_site
- .application_question
- }
- />
- ),
- },
- {
- key: "taglines",
- label: i18n.t("taglines"),
- getNode: () => (
- <div className="row">
- <TaglineForm siteRes={this.state.siteRes} />
- </div>
- ),
- },
- {
- key: "emojis",
- label: i18n.t("emojis"),
- getNode: () => (
- <div className="row">
- <EmojiForm />
+ <div className="col-12 col-md-6">
+ {this.admins()}
+ {this.bannedUsers()}
</div>
- ),
- },
- ]}
- />
- )}
+ </div>
+ ),
+ },
+ {
+ key: "rate_limiting",
+ label: "Rate Limiting",
+ getNode: () => (
+ <RateLimitForm
+ rateLimits={
+ this.state.siteRes.site_view.local_site_rate_limit
+ }
+ onSaveSite={this.handleEditSite}
+ />
+ ),
+ },
+ {
+ key: "taglines",
+ label: i18n.t("taglines"),
+ getNode: () => (
+ <div className="row">
+ <TaglineForm
+ taglines={this.state.siteRes.taglines}
+ onSaveSite={this.handleEditSite}
+ />
+ </div>
+ ),
+ },
+ {
+ key: "emojis",
+ label: i18n.t("emojis"),
+ getNode: () => (
+ <div className="row">
+ <EmojiForm
+ onCreate={this.handleCreateEmoji}
+ onDelete={this.handleDeleteEmoji}
+ onEdit={this.handleEditEmoji}
+ />
+ </div>
+ ),
+ },
+ ]}
+ />
</div>
);
}
onClick={linkEvent(this, this.handleLeaveAdminTeam)}
className="btn btn-danger mb-2"
>
- {this.state.leaveAdminTeamLoading ? (
+ {this.state.leaveAdminTeamRes.state == "loading" ? (
<Spinner />
) : (
i18n.t("leave_admin_team")
}
bannedUsers() {
- return (
- <>
- <h5>{i18n.t("banned_users")}</h5>
- <ul className="list-unstyled">
- {this.state.banned.map(banned => (
- <li key={banned.person.id} className="list-inline-item">
- <PersonListing person={banned.person} />
- </li>
- ))}
- </ul>
- </>
- );
+ switch (this.state.bannedRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const bans = this.state.bannedRes.data.banned;
+ return (
+ <>
+ <h5>{i18n.t("banned_users")}</h5>
+ <ul className="list-unstyled">
+ {bans.map(banned => (
+ <li key={banned.person.id} className="list-inline-item">
+ <PersonListing person={banned.person} />
+ </li>
+ ))}
+ </ul>
+ </>
+ );
+ }
+ }
}
- handleLeaveAdminTeam(i: AdminSettings) {
- const auth = myAuth();
- if (auth) {
- i.setState({ leaveAdminTeamLoading: true });
- WebSocketService.Instance.send(wsClient.leaveAdmin({ auth }));
+ async handleEditSite(form: EditSite) {
+ const editRes = await HttpService.client.editSite(form);
+
+ if (editRes.state === "success") {
+ this.setState(s => {
+ s.siteRes.site_view = editRes.data.site_view;
+ // TODO: Where to get taglines from?
+ s.siteRes.taglines = editRes.data.taglines;
+ return s;
+ });
+ toast(i18n.t("site_saved"));
}
+
+ return editRes;
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.context.router.history.push("/");
- this.setState({ loading: false });
- return;
- } else if (op == UserOperation.EditSite) {
- const data = wsJsonToRes<SiteResponse>(msg);
- this.setState(s => ((s.siteRes.site_view = data.site_view), s));
- toast(i18n.t("site_saved"));
- } else if (op == UserOperation.GetBannedPersons) {
- const data = wsJsonToRes<BannedPersonsResponse>(msg);
- this.setState({ banned: data.banned, loading: false });
- } else if (op == UserOperation.LeaveAdmin) {
- const data = wsJsonToRes<GetSiteResponse>(msg);
- this.setState(s => ((s.siteRes.site_view = data.site_view), s));
- this.setState({ leaveAdminTeamLoading: false });
+ handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
+ i.ctx.setState({ currentTab: i.tab });
+ }
+
+ async handleLeaveAdminTeam(i: AdminSettings) {
+ i.setState({ leaveAdminTeamRes: { state: "loading" } });
+ this.setState({
+ leaveAdminTeamRes: await HttpService.client.leaveAdmin({
+ auth: myAuthRequired(),
+ }),
+ });
+
+ if (this.state.leaveAdminTeamRes.state === "success") {
toast(i18n.t("left_admin_team"));
- this.context.router.history.push("/");
- } else if (op == UserOperation.GetFederatedInstances) {
- const data = wsJsonToRes<GetFederatedInstancesResponse>(msg);
- this.setState({ instancesRes: data });
+ this.context.router.history.replace("/");
+ }
+ }
+
+ async handleEditEmoji(form: EditCustomEmoji) {
+ const res = await HttpService.client.editCustomEmoji(form);
+ if (res.state === "success") {
+ updateEmojiDataModel(res.data.custom_emoji);
+ }
+ }
+
+ async handleDeleteEmoji(form: DeleteCustomEmoji) {
+ const res = await HttpService.client.deleteCustomEmoji(form);
+ if (res.state === "success") {
+ removeFromEmojiDataModel(res.data.id);
+ }
+ }
+
+ async handleCreateEmoji(form: CreateCustomEmoji) {
+ const res = await HttpService.client.createCustomEmoji(form);
+ if (res.state === "success") {
+ updateEmojiDataModel(res.data.custom_emoji);
}
}
}
import { Component, linkEvent } from "inferno";
import {
CreateCustomEmoji,
- CustomEmojiResponse,
DeleteCustomEmoji,
- DeleteCustomEmojiResponse,
EditCustomEmoji,
GetSiteResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
+import { HttpService } from "../../services/HttpService";
import {
customEmojisLookup,
- isBrowser,
- myAuth,
+ myAuthRequired,
pictrsDeleteToast,
- removeFromEmojiDataModel,
setIsoData,
toast,
- updateEmojiDataModel,
- uploadImage,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { EmojiMart } from "../common/emoji-mart";
import { HtmlTags } from "../common/html-tags";
import { Icon } from "../common/icon";
import { Paginator } from "../common/paginator";
+interface EmojiFormProps {
+ onEdit(form: EditCustomEmoji): void;
+ onCreate(form: CreateCustomEmoji): void;
+ onDelete(form: DeleteCustomEmoji): void;
+}
+
interface EmojiFormState {
siteRes: GetSiteResponse;
customEmojis: CustomEmojiViewForm[];
page: number;
}
-export class EmojiForm extends Component<any, EmojiFormState> {
+export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
private isoData = setIsoData(this.context);
- private subscription: Subscription | undefined;
private itemsPerPage = 15;
private emptyState: EmojiFormState = {
loading: false,
this.state = this.emptyState;
this.handlePageChange = this.handlePageChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
this.handleEmojiClick = this.handleEmojiClick.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
}
get documentTitle(): string {
return i18n.t("custom_emojis");
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
- }
- }
-
render() {
return (
<div className="col-12">
"btn btn-link btn-animate"
}
onClick={linkEvent(
- { form: this, cv: cv },
+ { i: this, cv: cv },
this.handleEditEmojiClick
)}
data-tippy-content={i18n.t("save")}
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
- { form: this, index: index, cv: cv },
+ { i: this, index: index, cv: cv },
this.handleDeleteEmojiClick
)}
data-tippy-content={i18n.t("delete")}
props.form.setState({ customEmojis: custom_emojis });
}
- handleDeleteEmojiClick(props: {
- form: EmojiForm;
+ handleDeleteEmojiClick(d: {
+ i: EmojiForm;
index: number;
cv: CustomEmojiViewForm;
}) {
- const pagedIndex =
- (props.form.state.page - 1) * props.form.itemsPerPage + props.index;
- if (props.cv.id != 0) {
- const deleteForm: DeleteCustomEmoji = {
- id: props.cv.id,
- auth: myAuth() ?? "",
- };
- WebSocketService.Instance.send(wsClient.deleteCustomEmoji(deleteForm));
+ const pagedIndex = (d.i.state.page - 1) * d.i.itemsPerPage + d.index;
+ if (d.cv.id != 0) {
+ d.i.props.onDelete({
+ id: d.cv.id,
+ auth: myAuthRequired(),
+ });
} else {
- const custom_emojis = [...props.form.state.customEmojis];
+ const custom_emojis = [...d.i.state.customEmojis];
custom_emojis.splice(Number(pagedIndex), 1);
- props.form.setState({ customEmojis: custom_emojis });
+ d.i.setState({ customEmojis: custom_emojis });
}
}
- handleEditEmojiClick(props: { form: EmojiForm; cv: CustomEmojiViewForm }) {
- const keywords = props.cv.keywords
+ handleEditEmojiClick(d: { i: EmojiForm; cv: CustomEmojiViewForm }) {
+ const keywords = d.cv.keywords
.split(" ")
.filter(x => x.length > 0) as string[];
const uniqueKeywords = Array.from(new Set(keywords));
- if (props.cv.id != 0) {
- const editForm: EditCustomEmoji = {
- id: props.cv.id,
- category: props.cv.category,
- image_url: props.cv.image_url,
- alt_text: props.cv.alt_text,
+ if (d.cv.id != 0) {
+ d.i.props.onEdit({
+ id: d.cv.id,
+ category: d.cv.category,
+ image_url: d.cv.image_url,
+ alt_text: d.cv.alt_text,
keywords: uniqueKeywords,
- auth: myAuth() ?? "",
- };
- WebSocketService.Instance.send(wsClient.editCustomEmoji(editForm));
+ auth: myAuthRequired(),
+ });
} else {
- const createForm: CreateCustomEmoji = {
- category: props.cv.category,
- shortcode: props.cv.shortcode,
- image_url: props.cv.image_url,
- alt_text: props.cv.alt_text,
+ d.i.props.onCreate({
+ category: d.cv.category,
+ shortcode: d.cv.shortcode,
+ image_url: d.cv.image_url,
+ alt_text: d.cv.alt_text,
keywords: uniqueKeywords,
- auth: myAuth() ?? "",
- };
- WebSocketService.Instance.send(wsClient.createCustomEmoji(createForm));
+ auth: myAuthRequired(),
+ });
}
}
file = event;
}
- uploadImage(file)
- .then(res => {
- console.log("pictrs upload:");
- console.log(res);
- if (res.msg === "ok") {
- pictrsDeleteToast(file.name, res.delete_url as string);
+ HttpService.client.uploadImage({ image: file }).then(res => {
+ console.log("pictrs upload:");
+ console.log(res);
+ if (res.state === "success") {
+ if (res.data.msg === "ok") {
+ pictrsDeleteToast(file.name, res.data.delete_url as string);
} else {
toast(JSON.stringify(res), "danger");
- const hash = res.files?.at(0)?.file;
- const url = `${res.url}/${hash}`;
+ const hash = res.data.files?.at(0)?.file;
+ const url = `${res.data.url}/${hash}`;
props.form.handleEmojiImageUrlChange(
{ form: props.form, index: props.index, overrideValue: url },
event
);
}
- })
- .catch(error => {
- console.error(error);
- toast(error, "danger");
- });
+ } else if (res.state === "failed") {
+ console.error(res.msg);
+ toast(res.msg, "danger");
+ }
+ });
}
configurePicker(): any {
dynamicWidth: true,
};
}
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.context.router.history.push("/");
- this.setState({ loading: false });
- return;
- } else if (op == UserOperation.CreateCustomEmoji) {
- const data = wsJsonToRes<CustomEmojiResponse>(msg);
- const custom_emoji_view = data.custom_emoji;
- updateEmojiDataModel(custom_emoji_view);
- const currentEmojis = this.state.customEmojis;
- const newEmojiIndex = currentEmojis.findIndex(
- x => x.shortcode == custom_emoji_view.custom_emoji.shortcode
- );
- currentEmojis[newEmojiIndex].id = custom_emoji_view.custom_emoji.id;
- currentEmojis[newEmojiIndex].changed = false;
- this.setState({ customEmojis: currentEmojis });
- toast(i18n.t("saved_emoji"));
- this.setState({ loading: false });
- } else if (op == UserOperation.EditCustomEmoji) {
- const data = wsJsonToRes<CustomEmojiResponse>(msg);
- const custom_emoji_view = data.custom_emoji;
- updateEmojiDataModel(data.custom_emoji);
- const currentEmojis = this.state.customEmojis;
- const newEmojiIndex = currentEmojis.findIndex(
- x => x.shortcode == custom_emoji_view.custom_emoji.shortcode
- );
- currentEmojis[newEmojiIndex].changed = false;
- this.setState({ customEmojis: currentEmojis });
- toast(i18n.t("saved_emoji"));
- this.setState({ loading: false });
- } else if (op == UserOperation.DeleteCustomEmoji) {
- const data = wsJsonToRes<DeleteCustomEmojiResponse>(msg);
- if (data.success) {
- removeFromEmojiDataModel(data.id);
- const custom_emojis = [
- ...this.state.customEmojis.filter(x => x.id != data.id),
- ];
- this.setState({ customEmojis: custom_emojis });
- toast(i18n.t("deleted_emoji"));
- }
- this.setState({ loading: false });
- }
- }
}
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
import {
- AddAdminResponse,
+ AddAdmin,
+ AddModToCommunity,
+ BanFromCommunity,
+ BanFromCommunityResponse,
+ BanPerson,
BanPersonResponse,
- BlockPersonResponse,
- CommentReportResponse,
+ BlockPerson,
+ CommentId,
+ CommentReplyResponse,
CommentResponse,
- CommentView,
- CommunityView,
+ CreateComment,
+ CreateCommentLike,
+ CreateCommentReport,
+ CreatePostLike,
+ CreatePostReport,
+ DeleteComment,
+ DeletePost,
+ DistinguishComment,
+ EditComment,
+ EditPost,
+ FeaturePost,
GetComments,
GetCommentsResponse,
GetPosts,
ListCommunities,
ListCommunitiesResponse,
ListingType,
- PostReportResponse,
+ LockPost,
+ MarkCommentReplyAsRead,
+ MarkPersonMentionAsRead,
PostResponse,
- PostView,
+ PurgeComment,
PurgeItemResponse,
- SiteResponse,
+ PurgePerson,
+ PurgePost,
+ RemoveComment,
+ RemovePost,
+ SaveComment,
+ SavePost,
SortType,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ TransferCommunity,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import {
CommentViewType,
DataType,
InitialFetchRequest,
} from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
canCreateCommunity,
commentsToFlatNodes,
- createCommentLikeRes,
- createPostLikeFindRes,
- editCommentRes,
- editPostFindRes,
+ editComment,
+ editPost,
+ editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
+ getCommentParentId,
getDataTypeString,
getPageFromString,
getQueryParams,
getQueryString,
getRandomFromList,
- isBrowser,
- isPostBlocked,
mdToHtml,
myAuth,
- notifyPost,
- nsfwCheck,
postToCommentSortType,
QueryParams,
relTags,
restoreScrollPosition,
- saveCommentRes,
saveScrollPosition,
setIsoData,
setupTippy,
toast,
trendingFetchLimit,
updatePersonBlock,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes";
import { DataTypeSelect } from "../common/data-type-select";
import { SiteSidebar } from "./site-sidebar";
interface HomeState {
- trendingCommunities: CommunityView[];
- siteRes: GetSiteResponse;
- posts: PostView[];
- comments: CommentView[];
+ postsRes: RequestState<GetPostsResponse>;
+ commentsRes: RequestState<GetCommentsResponse>;
+ trendingCommunitiesRes: RequestState<ListCommunitiesResponse>;
showSubscribedMobile: boolean;
showTrendingMobile: boolean;
showSidebarMobile: boolean;
subscribedCollapsed: boolean;
- loading: boolean;
tagline?: string;
+ siteRes: GetSiteResponse;
+ finished: Map<CommentId, boolean | undefined>;
+ isIsomorphic: boolean;
}
interface HomeProps {
UserService.Instance.myUserInfo?.local_user_view?.local_user
?.default_listing_type;
- return type ? (type as ListingType) : myListingType ?? "Local";
+ return (type ? (type as ListingType) : myListingType) ?? "Local";
}
function getSortTypeFromQuery(type?: string): SortType {
UserService.Instance.myUserInfo?.local_user_view?.local_user
?.default_sort_type;
- return type ? (type as SortType) : mySortType ?? "Active";
+ return (type ? (type as SortType) : mySortType) ?? "Active";
}
const getHomeQueryParams = () =>
dataType: getDataTypeFromQuery,
});
-function fetchTrendingCommunities() {
- const listCommunitiesForm: ListCommunities = {
- type_: "Local",
- sort: "Hot",
- limit: trendingFetchLimit,
- auth: myAuth(false),
- };
- WebSocketService.Instance.send(wsClient.listCommunities(listCommunitiesForm));
-}
-
-function fetchData() {
- const auth = myAuth(false);
- const { dataType, page, listingType, sort } = getHomeQueryParams();
- let req: string;
-
- if (dataType === DataType.Post) {
- const getPostsForm: GetPosts = {
- page,
- limit: fetchLimit,
- sort,
- saved_only: false,
- type_: listingType,
- auth,
- };
-
- req = wsClient.getPosts(getPostsForm);
- } else {
- const getCommentsForm: GetComments = {
- page,
- limit: fetchLimit,
- sort: postToCommentSortType(sort),
- saved_only: false,
- type_: listingType,
- auth,
- };
-
- req = wsClient.getComments(getCommentsForm);
- }
-
- WebSocketService.Instance.send(req);
-}
-
const MobileButton = ({
textKey,
show,
</Link>
);
-function getRss(listingType: ListingType) {
- const { sort } = getHomeQueryParams();
- const auth = myAuth(false);
-
- let rss: string | undefined = undefined;
-
- switch (listingType) {
- case "All": {
- rss = `/feeds/all.xml?sort=${sort}`;
- break;
- }
- case "Local": {
- rss = `/feeds/local.xml?sort=${sort}`;
- break;
- }
- case "Subscribed": {
- rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined;
- break;
- }
- }
-
- return (
- rss && (
- <>
- <a href={rss} rel={relTags} title="RSS">
- <Icon icon="rss" classes="text-muted small" />
- </a>
- <link rel="alternate" type="application/atom+xml" href={rss} />
- </>
- )
- );
-}
-
export class Home extends Component<any, HomeState> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: HomeState = {
- trendingCommunities: [],
+ postsRes: { state: "empty" },
+ commentsRes: { state: "empty" },
+ trendingCommunitiesRes: { state: "empty" },
siteRes: this.isoData.site_res,
showSubscribedMobile: false,
showTrendingMobile: false,
showSidebarMobile: false,
subscribedCollapsed: false,
- loading: true,
- posts: [],
- comments: [],
+ finished: new Map(),
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleCreateComment = this.handleCreateComment.bind(this);
+ this.handleEditComment = this.handleEditComment.bind(this);
+ this.handleSaveComment = this.handleSaveComment.bind(this);
+ this.handleBlockPerson = this.handleBlockPerson.bind(this);
+ this.handleDeleteComment = this.handleDeleteComment.bind(this);
+ this.handleRemoveComment = this.handleRemoveComment.bind(this);
+ this.handleCommentVote = this.handleCommentVote.bind(this);
+ this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
+ this.handleAddAdmin = this.handleAddAdmin.bind(this);
+ this.handlePurgePerson = this.handlePurgePerson.bind(this);
+ this.handlePurgeComment = this.handlePurgeComment.bind(this);
+ this.handleCommentReport = this.handleCommentReport.bind(this);
+ this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
+ this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
+ this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
+ this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
+ this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
+ this.handleBanPerson = this.handleBanPerson.bind(this);
+ this.handlePostEdit = this.handlePostEdit.bind(this);
+ this.handlePostVote = this.handlePostVote.bind(this);
+ this.handlePostReport = this.handlePostReport.bind(this);
+ this.handleLockPost = this.handleLockPost.bind(this);
+ this.handleDeletePost = this.handleDeletePost.bind(this);
+ this.handleRemovePost = this.handleRemovePost.bind(this);
+ this.handleSavePost = this.handleSavePost.bind(this);
+ this.handlePurgePost = this.handlePurgePost.bind(this);
+ this.handleFeaturePost = this.handleFeaturePost.bind(this);
// Only fetch the data if coming from another route
- if (this.isoData.path === this.context.router.route.match.url) {
- const postsRes = this.isoData.routeData[0] as
- | GetPostsResponse
- | undefined;
- const commentsRes = this.isoData.routeData[1] as
- | GetCommentsResponse
- | undefined;
- const trendingRes = this.isoData.routeData[2] as
- | ListCommunitiesResponse
- | undefined;
-
- if (postsRes) {
- this.state = { ...this.state, posts: postsRes.posts };
- }
-
- if (commentsRes) {
- this.state = { ...this.state, comments: commentsRes.comments };
- }
+ if (FirstLoadService.isFirstLoad) {
+ const [postsRes, commentsRes, trendingCommunitiesRes] =
+ this.isoData.routeData;
- if (isBrowser()) {
- WebSocketService.Instance.send(
- wsClient.communityJoin({ community_id: 0 })
- );
- }
- const taglines = this.state?.siteRes?.taglines ?? [];
this.state = {
...this.state,
- trendingCommunities: trendingRes?.communities ?? [],
- loading: false,
- tagline: getRandomFromList(taglines)?.content,
+ postsRes,
+ commentsRes,
+ trendingCommunitiesRes,
+ tagline: getRandomFromList(this.state?.siteRes?.taglines ?? [])
+ ?.content,
+ isIsomorphic: true,
};
- } else {
- fetchTrendingCommunities();
- fetchData();
}
}
- componentDidMount() {
- // This means it hasn't been set up yet
- if (!this.state.siteRes.site_view.local_site.site_setup) {
- this.context.router.history.push("/setup");
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]);
}
setupTippy();
}
componentWillUnmount() {
saveScrollPosition(this.context);
- this.subscription?.unsubscribe();
}
static fetchInitialData({
client,
auth,
query: { dataType: urlDataType, listingType, page: urlPage, sort: urlSort },
- }: InitialFetchRequest<QueryParams<HomeProps>>): Promise<any>[] {
+ }: InitialFetchRequest<QueryParams<HomeProps>>): Promise<
+ RequestState<any>
+ >[] {
const dataType = getDataTypeFromQuery(urlDataType);
// TODO figure out auth default_listingType, default_sort_type
const page = urlPage ? Number(urlPage) : 1;
- const promises: Promise<any>[] = [];
+ const promises: Promise<RequestState<any>>[] = [];
if (dataType === DataType.Post) {
const getPostsForm: GetPosts = {
};
promises.push(client.getPosts(getPostsForm));
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
} else {
const getCommentsForm: GetComments = {
page,
saved_only: false,
auth,
};
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
promises.push(client.getComments(getCommentsForm));
}
admins,
online,
},
- loading,
} = this.state;
return (
<div>
- {!loading && (
- <div>
- <div className="card border-secondary mb-3">
- <div className="card-body">
- {this.trendingCommunities()}
- {canCreateCommunity(this.state.siteRes) && (
- <LinkButton
- path="/create_community"
- translationKey="create_a_community"
- />
- )}
+ <div>
+ <div className="card border-secondary mb-3">
+ <div className="card-body">
+ {this.trendingCommunities()}
+ {canCreateCommunity(this.state.siteRes) && (
<LinkButton
- path="/communities"
- translationKey="explore_communities"
+ path="/create_community"
+ translationKey="create_a_community"
/>
- </div>
+ )}
+ <LinkButton
+ path="/communities"
+ translationKey="explore_communities"
+ />
</div>
- <SiteSidebar
- site={site}
- admins={admins}
- counts={counts}
- online={online}
- showLocal={showLocal(this.isoData)}
- />
- {this.hasFollows && (
- <div className="card border-secondary mb-3">
- <div className="card-body">{this.subscribedCommunities}</div>
- </div>
- )}
</div>
- )}
+ <SiteSidebar
+ site={site}
+ admins={admins}
+ counts={counts}
+ online={online}
+ showLocal={showLocal(this.isoData)}
+ />
+ {this.hasFollows && (
+ <div className="card border-secondary mb-3">
+ <div className="card-body">{this.subscribedCommunities}</div>
+ </div>
+ )}
+ </div>
</div>
);
}
trendingCommunities(isMobile = false) {
- return (
- <div className={!isMobile ? "mb-2" : ""}>
- <h5>
- <T i18nKey="trending_communities">
- #
- <Link className="text-body" to="/communities">
- #
- </Link>
- </T>
- </h5>
- <ul className="list-inline mb-0">
- {this.state.trendingCommunities.map(cv => (
- <li
- key={cv.community.id}
- className="list-inline-item d-inline-block"
- >
- <CommunityLink community={cv.community} />
- </li>
- ))}
- </ul>
- </div>
- );
+ switch (this.state.trendingCommunitiesRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const trending = this.state.trendingCommunitiesRes.data.communities;
+ return (
+ <div className={!isMobile ? "mb-2" : ""}>
+ <h5>
+ <T i18nKey="trending_communities">
+ #
+ <Link className="text-body" to="/communities">
+ #
+ </Link>
+ </T>
+ </h5>
+ <ul className="list-inline mb-0">
+ {trending.map(cv => (
+ <li
+ key={cv.community.id}
+ className="list-inline-item d-inline-block"
+ >
+ <CommunityLink community={cv.community} />
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+ }
+ }
}
get subscribedCommunities() {
);
}
- updateUrl({ dataType, listingType, page, sort }: Partial<HomeProps>) {
+ async updateUrl({ dataType, listingType, page, sort }: Partial<HomeProps>) {
const {
dataType: urlDataType,
listingType: urlListingType,
search: getQueryString(queryParams),
});
- this.setState({
- loading: true,
- posts: [],
- comments: [],
- });
-
- fetchData();
+ await this.fetchData();
}
posts() {
return (
<div className="main-content-wrapper">
- {this.state.loading ? (
- <h5>
- <Spinner large />
- </h5>
- ) : (
- <div>
- {this.selects()}
- {this.listings}
- <Paginator page={page} onChange={this.handlePageChange} />
- </div>
- )}
+ <div>
+ {this.selects}
+ {this.listings}
+ <Paginator page={page} onChange={this.handlePageChange} />
+ </div>
</div>
);
}
get listings() {
const { dataType } = getHomeQueryParams();
- const { siteRes, posts, comments } = this.state;
-
- return dataType === DataType.Post ? (
- <PostListings
- posts={posts}
- showCommunity
- removeDuplicates
- enableDownvotes={enableDownvotes(siteRes)}
- enableNsfw={enableNsfw(siteRes)}
- allLanguages={siteRes.all_languages}
- siteLanguages={siteRes.discussion_languages}
- />
- ) : (
- <CommentNodes
- nodes={commentsToFlatNodes(comments)}
- viewType={CommentViewType.Flat}
- noIndent
- showCommunity
- showContext
- enableDownvotes={enableDownvotes(siteRes)}
- allLanguages={siteRes.all_languages}
- siteLanguages={siteRes.discussion_languages}
- />
- );
+ const siteRes = this.state.siteRes;
+
+ if (dataType === DataType.Post) {
+ switch (this.state.postsRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const posts = this.state.postsRes.data.posts;
+ return (
+ <PostListings
+ posts={posts}
+ showCommunity
+ removeDuplicates
+ enableDownvotes={enableDownvotes(siteRes)}
+ enableNsfw={enableNsfw(siteRes)}
+ allLanguages={siteRes.all_languages}
+ siteLanguages={siteRes.discussion_languages}
+ onBlockPerson={this.handleBlockPerson}
+ onPostEdit={this.handlePostEdit}
+ onPostVote={this.handlePostVote}
+ onPostReport={this.handlePostReport}
+ onLockPost={this.handleLockPost}
+ onDeletePost={this.handleDeletePost}
+ onRemovePost={this.handleRemovePost}
+ onSavePost={this.handleSavePost}
+ onPurgePerson={this.handlePurgePerson}
+ onPurgePost={this.handlePurgePost}
+ onBanPerson={this.handleBanPerson}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onFeaturePost={this.handleFeaturePost}
+ />
+ );
+ }
+ }
+ } else {
+ switch (this.state.commentsRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const comments = this.state.commentsRes.data.comments;
+ return (
+ <CommentNodes
+ nodes={commentsToFlatNodes(comments)}
+ viewType={CommentViewType.Flat}
+ finished={this.state.finished}
+ noIndent
+ showCommunity
+ showContext
+ enableDownvotes={enableDownvotes(siteRes)}
+ allLanguages={siteRes.all_languages}
+ siteLanguages={siteRes.discussion_languages}
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPerson}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
+ />
+ );
+ }
+ }
+ }
}
- selects() {
+ get selects() {
const { listingType, dataType, sort } = getHomeQueryParams();
return (
<span className="mr-2">
<SortSelect sort={sort} onChange={this.handleSortChange} />
</span>
- {getRss(listingType)}
+ {this.getRss(listingType)}
</div>
);
}
+ getRss(listingType: ListingType) {
+ const { sort } = getHomeQueryParams();
+ const auth = myAuth();
+
+ let rss: string | undefined = undefined;
+
+ switch (listingType) {
+ case "All": {
+ rss = `/feeds/all.xml?sort=${sort}`;
+ break;
+ }
+ case "Local": {
+ rss = `/feeds/local.xml?sort=${sort}`;
+ break;
+ }
+ case "Subscribed": {
+ rss = auth ? `/feeds/front/${auth}.xml?sort=${sort}` : undefined;
+ break;
+ }
+ }
+
+ return (
+ rss && (
+ <>
+ <a href={rss} rel={relTags} title="RSS">
+ <Icon icon="rss" classes="text-muted small" />
+ </a>
+ <link rel="alternate" type="application/atom+xml" href={rss} />
+ </>
+ )
+ );
+ }
+
+ async fetchTrendingCommunities() {
+ this.setState({ trendingCommunitiesRes: { state: "loading" } });
+ this.setState({
+ trendingCommunitiesRes: await HttpService.client.listCommunities({
+ type_: "Local",
+ sort: "Hot",
+ limit: trendingFetchLimit,
+ auth: myAuth(),
+ }),
+ });
+ }
+
+ async fetchData() {
+ const auth = myAuth();
+ const { dataType, page, listingType, sort } = getHomeQueryParams();
+
+ if (dataType === DataType.Post) {
+ this.setState({ postsRes: { state: "loading" } });
+ this.setState({
+ postsRes: await HttpService.client.getPosts({
+ page,
+ limit: fetchLimit,
+ sort,
+ saved_only: false,
+ type_: listingType,
+ auth,
+ }),
+ });
+ } else {
+ this.setState({ commentsRes: { state: "loading" } });
+ this.setState({
+ commentsRes: await HttpService.client.getComments({
+ page,
+ limit: fetchLimit,
+ sort: postToCommentSortType(sort),
+ saved_only: false,
+ type_: listingType,
+ auth,
+ }),
+ });
+ }
+
+ restoreScrollPosition(this.context);
+ setupTippy();
+ }
+
handleShowSubscribedMobile(i: Home) {
i.setState({ showSubscribedMobile: !i.state.showSubscribedMobile });
}
window.scrollTo(0, 0);
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
+ async handleAddModToCommunity(form: AddModToCommunity) {
+ // TODO not sure what to do here
+ await HttpService.client.addModToCommunity(form);
+ }
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- } else if (msg.reconnect) {
- WebSocketService.Instance.send(
- wsClient.communityJoin({ community_id: 0 })
- );
- fetchData();
- } else {
- switch (op) {
- case UserOperation.ListCommunities: {
- const { communities } = wsJsonToRes<ListCommunitiesResponse>(msg);
- this.setState({ trendingCommunities: communities });
+ async handlePurgePerson(form: PurgePerson) {
+ const purgePersonRes = await HttpService.client.purgePerson(form);
+ this.purgeItem(purgePersonRes);
+ }
- break;
- }
+ async handlePurgeComment(form: PurgeComment) {
+ const purgeCommentRes = await HttpService.client.purgeComment(form);
+ this.purgeItem(purgeCommentRes);
+ }
- case UserOperation.EditSite: {
- const { site_view } = wsJsonToRes<SiteResponse>(msg);
- this.setState(s => ((s.siteRes.site_view = site_view), s));
- toast(i18n.t("site_saved"));
+ async handlePurgePost(form: PurgePost) {
+ const purgeRes = await HttpService.client.purgePost(form);
+ this.purgeItem(purgeRes);
+ }
- break;
- }
+ async handleBlockPerson(form: BlockPerson) {
+ const blockPersonRes = await HttpService.client.blockPerson(form);
+ if (blockPersonRes.state == "success") {
+ updatePersonBlock(blockPersonRes.data);
+ }
+ }
- case UserOperation.GetPosts: {
- const { posts } = wsJsonToRes<GetPostsResponse>(msg);
- this.setState({ posts, loading: false });
- WebSocketService.Instance.send(
- wsClient.communityJoin({ community_id: 0 })
- );
- restoreScrollPosition(this.context);
- setupTippy();
+ async handleCreateComment(form: CreateComment) {
+ const createCommentRes = await HttpService.client.createComment(form);
+ this.createAndUpdateComments(createCommentRes);
- break;
- }
+ return createCommentRes;
+ }
- case UserOperation.CreatePost: {
- const { page, listingType } = getHomeQueryParams();
- const { post_view } = wsJsonToRes<PostResponse>(msg);
-
- // Only push these if you're on the first page, you pass the nsfw check, and it isn't blocked
- if (page === 1 && nsfwCheck(post_view) && !isPostBlocked(post_view)) {
- const mui = UserService.Instance.myUserInfo;
- const showPostNotifs =
- mui?.local_user_view.local_user.show_new_post_notifs;
- let shouldAddPost: boolean;
-
- switch (listingType) {
- case "Subscribed": {
- // If you're on subscribed, only push it if you're subscribed.
- shouldAddPost = !!mui?.follows.some(
- ({ community: { id } }) => id === post_view.community.id
- );
- break;
- }
- case "Local": {
- // If you're on the local view, only push it if its local
- shouldAddPost = post_view.post.local;
- break;
- }
- default: {
- shouldAddPost = true;
- break;
- }
- }
-
- if (shouldAddPost) {
- this.setState(({ posts }) => ({
- posts: [post_view].concat(posts),
- }));
- if (showPostNotifs) {
- notifyPost(post_view, this.context.router);
- }
- }
- }
-
- break;
- }
+ async handleEditComment(form: EditComment) {
+ const editCommentRes = await HttpService.client.editComment(form);
+ this.findAndUpdateComment(editCommentRes);
- case UserOperation.EditPost:
- case UserOperation.DeletePost:
- case UserOperation.RemovePost:
- case UserOperation.LockPost:
- case UserOperation.FeaturePost:
- case UserOperation.SavePost: {
- const { post_view } = wsJsonToRes<PostResponse>(msg);
- editPostFindRes(post_view, this.state.posts);
- this.setState(this.state);
-
- break;
- }
+ return editCommentRes;
+ }
- case UserOperation.CreatePostLike: {
- const { post_view } = wsJsonToRes<PostResponse>(msg);
- createPostLikeFindRes(post_view, this.state.posts);
- this.setState(this.state);
+ async handleDeleteComment(form: DeleteComment) {
+ const deleteCommentRes = await HttpService.client.deleteComment(form);
+ this.findAndUpdateComment(deleteCommentRes);
+ }
- break;
- }
+ async handleDeletePost(form: DeletePost) {
+ const deleteRes = await HttpService.client.deletePost(form);
+ this.findAndUpdatePost(deleteRes);
+ }
- case UserOperation.AddAdmin: {
- const { admins } = wsJsonToRes<AddAdminResponse>(msg);
- this.setState(s => ((s.siteRes.admins = admins), s));
+ async handleRemovePost(form: RemovePost) {
+ const removeRes = await HttpService.client.removePost(form);
+ this.findAndUpdatePost(removeRes);
+ }
- break;
- }
+ async handleRemoveComment(form: RemoveComment) {
+ const removeCommentRes = await HttpService.client.removeComment(form);
+ this.findAndUpdateComment(removeCommentRes);
+ }
- case UserOperation.BanPerson: {
- const {
- banned,
- person_view: {
- person: { id },
- },
- } = wsJsonToRes<BanPersonResponse>(msg);
+ async handleSaveComment(form: SaveComment) {
+ const saveCommentRes = await HttpService.client.saveComment(form);
+ this.findAndUpdateComment(saveCommentRes);
+ }
- this.state.posts
- .filter(p => p.creator.id == id)
- .forEach(p => (p.creator.banned = banned));
- this.setState(this.state);
+ async handleSavePost(form: SavePost) {
+ const saveRes = await HttpService.client.savePost(form);
+ this.findAndUpdatePost(saveRes);
+ }
- break;
- }
+ async handleFeaturePost(form: FeaturePost) {
+ const featureRes = await HttpService.client.featurePost(form);
+ this.findAndUpdatePost(featureRes);
+ }
- case UserOperation.GetComments: {
- const { comments } = wsJsonToRes<GetCommentsResponse>(msg);
- this.setState({ comments, loading: false });
+ async handleCommentVote(form: CreateCommentLike) {
+ const voteRes = await HttpService.client.likeComment(form);
+ this.findAndUpdateComment(voteRes);
+ }
- break;
- }
+ async handlePostEdit(form: EditPost) {
+ const res = await HttpService.client.editPost(form);
+ this.findAndUpdatePost(res);
+ }
- case UserOperation.EditComment:
- case UserOperation.DeleteComment:
- case UserOperation.RemoveComment: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
- editCommentRes(comment_view, this.state.comments);
- this.setState(this.state);
+ async handlePostVote(form: CreatePostLike) {
+ const voteRes = await HttpService.client.likePost(form);
+ this.findAndUpdatePost(voteRes);
+ }
- break;
- }
+ async handleCommentReport(form: CreateCommentReport) {
+ const reportRes = await HttpService.client.createCommentReport(form);
+ if (reportRes.state == "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
- case UserOperation.CreateComment: {
- const { form_id, comment_view } = wsJsonToRes<CommentResponse>(msg);
-
- // Necessary since it might be a user reply
- if (form_id) {
- const { listingType } = getHomeQueryParams();
-
- // If you're on subscribed, only push it if you're subscribed.
- const shouldAddComment =
- listingType === "Subscribed"
- ? UserService.Instance.myUserInfo?.follows.some(
- ({ community: { id } }) => id === comment_view.community.id
- )
- : true;
-
- if (shouldAddComment) {
- this.setState(({ comments }) => ({
- comments: [comment_view].concat(comments),
- }));
- }
- }
-
- break;
- }
+ async handlePostReport(form: CreatePostReport) {
+ const reportRes = await HttpService.client.createPostReport(form);
+ if (reportRes.state == "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
- case UserOperation.SaveComment: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
- saveCommentRes(comment_view, this.state.comments);
- this.setState(this.state);
+ async handleLockPost(form: LockPost) {
+ const lockRes = await HttpService.client.lockPost(form);
+ this.findAndUpdatePost(lockRes);
+ }
- break;
- }
+ async handleDistinguishComment(form: DistinguishComment) {
+ const distinguishRes = await HttpService.client.distinguishComment(form);
+ this.findAndUpdateComment(distinguishRes);
+ }
- case UserOperation.CreateCommentLike: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
- createCommentLikeRes(comment_view, this.state.comments);
- this.setState(this.state);
+ async handleAddAdmin(form: AddAdmin) {
+ const addAdminRes = await HttpService.client.addAdmin(form);
- break;
- }
+ if (addAdminRes.state == "success") {
+ this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
+ }
+ }
- case UserOperation.BlockPerson: {
- const data = wsJsonToRes<BlockPersonResponse>(msg);
- updatePersonBlock(data);
+ async handleTransferCommunity(form: TransferCommunity) {
+ await HttpService.client.transferCommunity(form);
+ toast(i18n.t("transfer_community"));
+ }
- break;
- }
+ async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
+ const readRes = await HttpService.client.markCommentReplyAsRead(form);
+ this.findAndUpdateCommentReply(readRes);
+ }
- case UserOperation.CreatePostReport: {
- const data = wsJsonToRes<PostReportResponse>(msg);
- if (data) {
- toast(i18n.t("report_created"));
- }
+ async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
+ // TODO not sure what to do here. Maybe it is actually optional, because post doesn't need it.
+ await HttpService.client.markPersonMentionAsRead(form);
+ }
- break;
- }
+ async handleBanFromCommunity(form: BanFromCommunity) {
+ const banRes = await HttpService.client.banFromCommunity(form);
+ this.updateBanFromCommunity(banRes);
+ }
- case UserOperation.CreateCommentReport: {
- const data = wsJsonToRes<CommentReportResponse>(msg);
- if (data) {
- toast(i18n.t("report_created"));
- }
+ async handleBanPerson(form: BanPerson) {
+ const banRes = await HttpService.client.banPerson(form);
+ this.updateBan(banRes);
+ }
- break;
+ updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (s.postsRes.state == "success") {
+ s.postsRes.data.posts
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
}
+ if (s.commentsRes.state == "success") {
+ s.commentsRes.data.comments
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
+ }
+ return s;
+ });
+ }
+ }
- case UserOperation.PurgePerson:
- case UserOperation.PurgePost:
- case UserOperation.PurgeComment:
- case UserOperation.PurgeCommunity: {
- const data = wsJsonToRes<PurgeItemResponse>(msg);
- if (data.success) {
- toast(i18n.t("purge_success"));
- this.context.router.history.push(`/`);
- }
-
- break;
+ updateBan(banRes: RequestState<BanPersonResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (s.postsRes.state == "success") {
+ s.postsRes.data.posts
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
}
- }
+ if (s.commentsRes.state == "success") {
+ s.commentsRes.data.comments
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
+ }
+ return s;
+ });
}
}
+
+ purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
+ if (purgeRes.state == "success") {
+ toast(i18n.t("purge_success"));
+ this.context.router.history.push(`/`);
+ }
+ }
+
+ findAndUpdateComment(res: RequestState<CommentResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state == "success" && res.state == "success") {
+ s.commentsRes.data.comments = editComment(
+ res.data.comment_view,
+ s.commentsRes.data.comments
+ );
+ s.finished.set(res.data.comment_view.comment.id, true);
+ }
+ return s;
+ });
+ }
+
+ createAndUpdateComments(res: RequestState<CommentResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state == "success" && res.state == "success") {
+ s.commentsRes.data.comments.unshift(res.data.comment_view);
+
+ // Set finished for the parent
+ s.finished.set(
+ getCommentParentId(res.data.comment_view.comment) ?? 0,
+ true
+ );
+ }
+ return s;
+ });
+ }
+
+ findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state == "success" && res.state == "success") {
+ s.commentsRes.data.comments = editWith(
+ res.data.comment_reply_view,
+ s.commentsRes.data.comments
+ );
+ }
+ return s;
+ });
+ }
+
+ findAndUpdatePost(res: RequestState<PostResponse>) {
+ this.setState(s => {
+ if (s.postsRes.state == "success" && res.state == "success") {
+ s.postsRes.data.posts = editPost(
+ res.data.post_view,
+ s.postsRes.data.posts
+ );
+ }
+ return s;
+ });
+ }
}
GetFederatedInstancesResponse,
GetSiteResponse,
Instance,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
-import { WebSocketService } from "../../services";
-import {
- isBrowser,
- relTags,
- setIsoData,
- toast,
- wsClient,
- wsSubscribe,
-} from "../../utils";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
+import { relTags, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
+import { Spinner } from "../common/icon";
interface InstancesState {
+ instancesRes: RequestState<GetFederatedInstancesResponse>;
siteRes: GetSiteResponse;
- instancesRes?: GetFederatedInstancesResponse;
- loading: boolean;
+ isIsomorphic: boolean;
}
export class Instances extends Component<any, InstancesState> {
private isoData = setIsoData(this.context);
state: InstancesState = {
+ instancesRes: { state: "empty" },
siteRes: this.isoData.site_res,
- loading: true,
+ isIsomorphic: false,
};
- private subscription?: Subscription;
constructor(props: any, context: any) {
super(props, context);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- instancesRes: this.isoData
- .routeData[0] as GetFederatedInstancesResponse,
- loading: false,
+ instancesRes: this.isoData.routeData[0],
+ isIsomorphic: true,
};
- } else {
- WebSocketService.Instance.send(wsClient.getFederatedInstances({}));
}
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<any>[] = [];
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.fetchInstances();
+ }
+ }
- promises.push(req.client.getFederatedInstances({}));
+ async fetchInstances() {
+ this.setState({
+ instancesRes: { state: "loading" },
+ });
- return promises;
+ this.setState({
+ instancesRes: await HttpService.client.getFederatedInstances({}),
+ });
+ }
+
+ static fetchInitialData(
+ req: InitialFetchRequest
+ ): Promise<RequestState<any>>[] {
+ return [req.client.getFederatedInstances({})];
}
get documentTitle(): string {
return `${i18n.t("instances")} - ${this.state.siteRes.site_view.site.name}`;
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ renderInstances() {
+ switch (this.state.instancesRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const instances = this.state.instancesRes.data.federated_instances;
+ return instances ? (
+ <div className="row">
+ <div className="col-md-6">
+ <h5>{i18n.t("linked_instances")}</h5>
+ {this.itemList(instances.linked)}
+ </div>
+ {instances.allowed && instances.allowed.length > 0 && (
+ <div className="col-md-6">
+ <h5>{i18n.t("allowed_instances")}</h5>
+ {this.itemList(instances.allowed)}
+ </div>
+ )}
+ {instances.blocked && instances.blocked.length > 0 && (
+ <div className="col-md-6">
+ <h5>{i18n.t("blocked_instances")}</h5>
+ {this.itemList(instances.blocked)}
+ </div>
+ )}
+ </div>
+ ) : (
+ <></>
+ );
+ }
}
}
render() {
- const federated_instances = this.state.instancesRes?.federated_instances;
- return federated_instances ? (
+ return (
<div className="container-lg">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
- <div className="row">
- <div className="col-md-6">
- <h5>{i18n.t("linked_instances")}</h5>
- {this.itemList(federated_instances.linked)}
- </div>
- {federated_instances.allowed &&
- federated_instances.allowed.length > 0 && (
- <div className="col-md-6">
- <h5>{i18n.t("allowed_instances")}</h5>
- {this.itemList(federated_instances.allowed)}
- </div>
- )}
- {federated_instances.blocked &&
- federated_instances.blocked.length > 0 && (
- <div className="col-md-6">
- <h5>{i18n.t("blocked_instances")}</h5>
- {this.itemList(federated_instances.blocked)}
- </div>
- )}
- </div>
+ {this.renderInstances()}
</div>
- ) : (
- <></>
);
}
<div>{i18n.t("none_found")}</div>
);
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.context.router.history.push("/");
- this.setState({ loading: false });
- return;
- } else if (op == UserOperation.GetFederatedInstances) {
- const data = wsJsonToRes<GetFederatedInstancesResponse>(msg);
- this.setState({ loading: false, instancesRes: data });
- }
- }
}
import { Component, linkEvent } from "inferno";
-import {
- GetSiteResponse,
- Login as LoginI,
- LoginResponse,
- PasswordReset,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
-} from "lemmy-js-client";
-import { Subscription } from "rxjs";
+import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
-import {
- isBrowser,
- setIsoData,
- toast,
- validEmail,
- wsClient,
- wsSubscribe,
-} from "../../utils";
+import { UserService } from "../../services";
+import { HttpService, RequestState } from "../../services/HttpService";
+import { isBrowser, myAuth, setIsoData, toast, validEmail } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
interface State {
+ loginRes: RequestState<LoginResponse>;
form: {
username_or_email?: string;
password?: string;
totp_2fa_token?: string;
};
- loginLoading: boolean;
showTotp: boolean;
siteRes: GetSiteResponse;
}
export class Login extends Component<any, State> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: State = {
+ loginRes: { state: "empty" },
form: {},
- loginLoading: false,
showTotp: false,
siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
super(props, context);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
- if (isBrowser()) {
- WebSocketService.Instance.send(wsClient.getCaptcha({}));
- }
}
componentDidMount() {
}
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
- }
- }
-
get documentTitle(): string {
return `${i18n.t("login")} - ${this.state.siteRes.site_view.site.name}`;
}
<div className="form-group row">
<div className="col-sm-10">
<button type="submit" className="btn btn-secondary">
- {this.state.loginLoading ? <Spinner /> : i18n.t("login")}
+ {this.state.loginRes.state == "loading" ? (
+ <Spinner />
+ ) : (
+ i18n.t("login")
+ )}
</button>
</div>
</div>
);
}
- handleLoginSubmit(i: Login, event: any) {
+ async handleLoginSubmit(i: Login, event: any) {
event.preventDefault();
- i.setState({ loginLoading: true });
- const lForm = i.state.form;
- const username_or_email = lForm.username_or_email;
- const password = lForm.password;
- const totp_2fa_token = lForm.totp_2fa_token;
+ const { password, totp_2fa_token, username_or_email } = i.state.form;
+
if (username_or_email && password) {
- const form: LoginI = {
+ i.setState({ loginRes: { state: "loading" } });
+
+ const loginRes = await HttpService.client.login({
username_or_email,
password,
totp_2fa_token,
- };
- WebSocketService.Instance.send(wsClient.login(form));
+ });
+ switch (loginRes.state) {
+ case "failed": {
+ if (loginRes.msg === "missing_totp_token") {
+ i.setState({ showTotp: true });
+ toast(i18n.t("enter_two_factor_code"), "info");
+ }
+
+ i.setState({ loginRes: { state: "empty" } });
+ break;
+ }
+
+ case "success": {
+ UserService.Instance.login(loginRes.data);
+ const site = await HttpService.client.getSite({
+ auth: myAuth(),
+ });
+
+ if (site.state === "success") {
+ UserService.Instance.myUserInfo = site.data.my_user;
+ }
+
+ i.props.history.replace("/");
+
+ break;
+ }
+ }
}
}
i.setState(i.state);
}
- handlePasswordReset(i: Login, event: any) {
+ async handlePasswordReset(i: Login, event: any) {
event.preventDefault();
const email = i.state.form.username_or_email;
if (email) {
- const resetForm: PasswordReset = { email };
- WebSocketService.Instance.send(wsClient.passwordReset(resetForm));
- }
- }
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- // If the error comes back that the token is missing, show the TOTP field
- if (msg.error == "missing_totp_token") {
- this.setState({ showTotp: true, loginLoading: false });
- toast(i18n.t("enter_two_factor_code"));
- return;
- } else {
- toast(i18n.t(msg.error), "danger");
- this.setState({ form: {}, loginLoading: false });
- return;
- }
- } else {
- if (op == UserOperation.Login) {
- const data = wsJsonToRes<LoginResponse>(msg);
- UserService.Instance.login(data);
- this.props.history.push("/");
- location.reload();
- } else if (op == UserOperation.PasswordReset) {
+ const res = await HttpService.client.passwordReset({ email });
+ if (res.state == "success") {
toast(i18n.t("reset_password_mail_sent"));
- } else if (op == UserOperation.GetSite) {
- const data = wsJsonToRes<GetSiteResponse>(msg);
- this.setState({ siteRes: data });
}
}
}
import { Component, FormEventHandler, linkEvent } from "inferno";
import { EditSite, LocalSiteRateLimit } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
-import { capitalizeFirstLetter, myAuth, wsClient } from "../../utils";
+import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { Spinner } from "../common/icon";
import Tabs from "../common/tabs";
}
interface RateLimitFormProps {
- localSiteRateLimit: LocalSiteRateLimit;
- applicationQuestion?: string;
+ rateLimits: LocalSiteRateLimit;
+ onSaveSite(form: EditSite): void;
}
interface RateLimitFormState {
function submitRateLimitForm(i: RateLimitsForm, event: any) {
event.preventDefault();
- const auth = myAuth() ?? "TODO";
+ const auth = myAuthRequired();
const form: EditSite = Object.entries(i.state.form).reduce(
(acc, [key, val]) => {
acc[`rate_limit_${key}`] = val;
return acc;
},
- { auth, application_question: i.props.applicationQuestion }
+ {
+ auth,
+ }
);
i.setState({ loading: true });
-
- WebSocketService.Instance.send(wsClient.editSite(form));
+ i.props.onSaveSite(form);
}
export default class RateLimitsForm extends Component<
> {
state: RateLimitFormState = {
loading: false,
- form: {},
+ form: this.props.rateLimits,
};
- constructor(props: RateLimitFormProps, context) {
+ constructor(props: RateLimitFormProps, context: any) {
super(props, context);
-
- const {
- comment,
- comment_per_second,
- image,
- image_per_second,
- message,
- message_per_second,
- post,
- post_per_second,
- register,
- register_per_second,
- search,
- search_per_second,
- } = props.localSiteRateLimit;
-
- this.state = {
- ...this.state,
- form: {
- comment,
- comment_per_second,
- image,
- image_per_second,
- message,
- message_per_second,
- post,
- post_per_second,
- register,
- register_per_second,
- search,
- search_per_second,
- },
- };
}
render() {
</form>
);
}
-
- componentDidUpdate({ localSiteRateLimit }: RateLimitFormProps) {
- if (
- this.state.loading &&
- Object.entries(localSiteRateLimit).some(
- ([key, val]) => this.state.form[key] !== val
- )
- ) {
- this.setState({ loading: false });
- }
- }
}
import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet";
import {
+ CreateSite,
GetSiteResponse,
LoginResponse,
Register,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
-import { delay, retryWhen, take } from "rxjs/operators";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
-import { setIsoData, toast, wsClient } from "../../utils";
+import { UserService } from "../../services";
+import { HttpService, RequestState } from "../../services/HttpService";
+import { fetchThemeList, setIsoData } from "../../utils";
import { Spinner } from "../common/icon";
import { SiteForm } from "./site-form";
answer?: string;
};
doneRegisteringUser: boolean;
- userLoading: boolean;
+ registerRes: RequestState<LoginResponse>;
+ themeList: string[];
siteRes: GetSiteResponse;
}
export class Setup extends Component<any, State> {
- private subscription: Subscription;
private isoData = setIsoData(this.context);
state: State = {
+ registerRes: { state: "empty" },
+ themeList: [],
form: {
show_nsfw: true,
},
doneRegisteringUser: !!UserService.Instance.myUserInfo,
- userLoading: false,
siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
super(props, context);
- this.subscription = WebSocketService.Instance.subject
- .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
- .subscribe(
- msg => this.parseMessage(msg),
- err => console.error(err),
- () => console.log("complete")
- );
+ this.handleCreateSite = this.handleCreateSite.bind(this);
}
- componentWillUnmount() {
- this.subscription.unsubscribe();
+ async componentDidMount() {
+ this.setState({ themeList: await fetchThemeList() });
}
get documentTitle(): string {
{!this.state.doneRegisteringUser ? (
this.registerUser()
) : (
- <SiteForm siteRes={this.state.siteRes} showLocal />
+ <SiteForm
+ showLocal
+ onSaveSite={this.handleCreateSite}
+ siteRes={this.state.siteRes}
+ themeList={this.state.themeList}
+ />
)}
</div>
</div>
<div className="form-group row">
<div className="col-sm-10">
<button type="submit" className="btn btn-secondary">
- {this.state.userLoading ? <Spinner /> : i18n.t("sign_up")}
+ {this.state.registerRes.state == "loading" ? (
+ <Spinner />
+ ) : (
+ i18n.t("sign_up")
+ )}
</button>
</div>
</div>
);
}
- handleRegisterSubmit(i: Setup, event: any) {
+ async handleRegisterSubmit(i: Setup, event: any) {
event.preventDefault();
- i.setState({ userLoading: true });
- event.preventDefault();
- const cForm = i.state.form;
- if (cForm.username && cForm.password && cForm.password_verify) {
+ i.setState({ registerRes: { state: "loading" } });
+ const {
+ username,
+ password_verify,
+ password,
+ email,
+ show_nsfw,
+ captcha_uuid,
+ captcha_answer,
+ honeypot,
+ answer,
+ } = i.state.form;
+
+ if (username && password && password_verify) {
const form: Register = {
- username: cForm.username,
- password: cForm.password,
- password_verify: cForm.password_verify,
- email: cForm.email,
- show_nsfw: cForm.show_nsfw,
- captcha_uuid: cForm.captcha_uuid,
- captcha_answer: cForm.captcha_answer,
- honeypot: cForm.honeypot,
- answer: cForm.answer,
+ username,
+ password,
+ password_verify,
+ email,
+ show_nsfw,
+ captcha_uuid,
+ captcha_answer,
+ honeypot,
+ answer,
};
- WebSocketService.Instance.send(wsClient.register(form));
+ i.setState({
+ registerRes: await HttpService.client.register(form),
+ });
+
+ if (i.state.registerRes.state == "success") {
+ const data = i.state.registerRes.data;
+
+ UserService.Instance.login(data);
+ if (UserService.Instance.jwtInfo) {
+ i.setState({ doneRegisteringUser: true });
+ }
+ }
+ }
+ }
+
+ async handleCreateSite(form: CreateSite) {
+ const createRes = await HttpService.client.createSite(form);
+ if (createRes.state === "success") {
+ this.props.history.replace("/");
+ location.reload();
}
}
i.state.form.password_verify = event.target.value;
i.setState(i.state);
}
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.setState({ userLoading: false });
- return;
- } else if (op == UserOperation.Register) {
- const data = wsJsonToRes<LoginResponse>(msg);
- this.setState({ userLoading: false });
- UserService.Instance.login(data);
- if (UserService.Instance.jwtInfo) {
- this.setState({ doneRegisteringUser: true });
- }
- } else if (op == UserOperation.CreateSite) {
- window.location.href = "/";
- }
- }
}
GetCaptchaResponse,
GetSiteResponse,
LoginResponse,
- Register,
SiteView,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
isBrowser,
joinLemmyUrl,
mdToHtml,
+ myAuth,
setIsoData,
toast,
validEmail,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
];
interface State {
+ registerRes: RequestState<LoginResponse>;
+ captchaRes: RequestState<GetCaptchaResponse>;
form: {
username?: string;
email?: string;
honeypot?: string;
answer?: string;
};
- registerLoading: boolean;
- captcha?: GetCaptchaResponse;
captchaPlaying: boolean;
siteRes: GetSiteResponse;
}
export class Signup extends Component<any, State> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
private audio?: HTMLAudioElement;
state: State = {
+ registerRes: { state: "empty" },
+ captchaRes: { state: "empty" },
form: {
show_nsfw: false,
},
- registerLoading: false,
captchaPlaying: false,
siteRes: this.isoData.site_res,
};
super(props, context);
this.handleAnswerChange = this.handleAnswerChange.bind(this);
+ }
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
- if (isBrowser()) {
- WebSocketService.Instance.send(wsClient.getCaptcha({}));
+ async componentDidMount() {
+ if (this.state.siteRes.site_view.local_site.captcha_enabled) {
+ await this.fetchCaptcha();
}
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
- }
+ async fetchCaptcha() {
+ this.setState({ captchaRes: { state: "loading" } });
+ this.setState({
+ captchaRes: await HttpService.client.getCaptcha({}),
+ });
+
+ this.setState(s => {
+ if (s.captchaRes.state == "success") {
+ s.form.captcha_uuid = s.captchaRes.data.ok?.uuid;
+ }
+ return s;
+ });
}
get documentTitle(): string {
</label>
<div className="col-sm-10">
<MarkdownTextArea
+ initialContent=""
onContentChange={this.handleAnswerChange}
hideNavigationWarnings
allLanguages={[]}
</div>
</>
)}
-
- {this.state.captcha && (
- <div className="form-group row">
- <label className="col-sm-2" htmlFor="register-captcha">
- <span className="mr-2">{i18n.t("enter_code")}</span>
- <button
- type="button"
- className="btn btn-secondary"
- onClick={linkEvent(this, this.handleRegenCaptcha)}
- aria-label={i18n.t("captcha")}
- >
- <Icon icon="refresh-cw" classes="icon-refresh-cw" />
- </button>
- </label>
- {this.showCaptcha()}
- <div className="col-sm-6">
- <input
- type="text"
- className="form-control"
- id="register-captcha"
- value={this.state.form.captcha_answer}
- onInput={linkEvent(
- this,
- this.handleRegisterCaptchaAnswerChange
- )}
- required
- />
- </div>
- </div>
- )}
+ {this.renderCaptcha()}
{siteView.local_site.enable_nsfw && (
<div className="form-group row">
<div className="col-sm-10">
<div className="form-group row">
<div className="col-sm-10">
<button type="submit" className="btn btn-secondary">
- {this.state.registerLoading ? (
+ {this.state.registerRes.state == "loading" ? (
<Spinner />
) : (
this.titleName(siteView)
);
}
- showCaptcha() {
- const captchaRes = this.state.captcha?.ok;
+ renderCaptcha() {
+ switch (this.state.captchaRes.state) {
+ case "loading":
+ return <Spinner />;
+ case "success": {
+ const res = this.state.captchaRes.data;
+ return (
+ <div className="form-group row">
+ <label className="col-sm-2" htmlFor="register-captcha">
+ <span className="mr-2">{i18n.t("enter_code")}</span>
+ <button
+ type="button"
+ className="btn btn-secondary"
+ onClick={linkEvent(this, this.handleRegenCaptcha)}
+ aria-label={i18n.t("captcha")}
+ >
+ <Icon icon="refresh-cw" classes="icon-refresh-cw" />
+ </button>
+ </label>
+ {this.showCaptcha(res)}
+ <div className="col-sm-6">
+ <input
+ type="text"
+ className="form-control"
+ id="register-captcha"
+ value={this.state.form.captcha_answer}
+ onInput={linkEvent(
+ this,
+ this.handleRegisterCaptchaAnswerChange
+ )}
+ required
+ />
+ </div>
+ </div>
+ );
+ }
+ }
+ }
+
+ showCaptcha(res: GetCaptchaResponse) {
+ const captchaRes = res?.ok;
return captchaRes ? (
<div className="col-sm-4">
<>
}
}
- handleRegisterSubmit(i: Signup, event: any) {
+ async handleRegisterSubmit(i: Signup, event: any) {
event.preventDefault();
- i.setState({ registerLoading: true });
- const cForm = i.state.form;
- if (cForm.username && cForm.password && cForm.password_verify) {
- const form: Register = {
- username: cForm.username,
- password: cForm.password,
- password_verify: cForm.password_verify,
- email: cForm.email,
- show_nsfw: cForm.show_nsfw,
- captcha_uuid: cForm.captcha_uuid,
- captcha_answer: cForm.captcha_answer,
- honeypot: cForm.honeypot,
- answer: cForm.answer,
- };
- WebSocketService.Instance.send(wsClient.register(form));
+ const {
+ show_nsfw,
+ answer,
+ captcha_answer,
+ captcha_uuid,
+ email,
+ honeypot,
+ password,
+ password_verify,
+ username,
+ } = i.state.form;
+ if (username && password && password_verify) {
+ i.setState({ registerRes: { state: "loading" } });
+
+ const registerRes = await HttpService.client.register({
+ username,
+ password,
+ password_verify,
+ email,
+ show_nsfw,
+ captcha_uuid,
+ captcha_answer,
+ honeypot,
+ answer,
+ });
+ switch (registerRes.state) {
+ case "failed": {
+ toast(registerRes.msg, "danger");
+ i.setState({ registerRes: { state: "empty" } });
+ break;
+ }
+
+ case "success": {
+ const data = registerRes.data;
+
+ // Only log them in if a jwt was set
+ if (data.jwt) {
+ UserService.Instance.login(data);
+
+ const site = await HttpService.client.getSite({ auth: myAuth() });
+
+ if (site.state === "success") {
+ UserService.Instance.myUserInfo = site.data.my_user;
+ }
+
+ i.props.history.replace("/communities");
+ } else {
+ if (data.verify_email_sent) {
+ toast(i18n.t("verify_email_sent"));
+ }
+ if (data.registration_created) {
+ toast(i18n.t("registration_application_sent"));
+ }
+ i.props.history.push("/");
+ }
+ break;
+ }
+ }
}
}
i.setState(i.state);
}
- handleRegenCaptcha(i: Signup) {
+ async handleRegenCaptcha(i: Signup) {
i.audio = undefined;
i.setState({ captchaPlaying: false });
- WebSocketService.Instance.send(wsClient.getCaptcha({}));
+ await i.fetchCaptcha();
}
handleCaptchaPlay(i: Signup) {
// This was a bad bug, it should only build the new audio on a new file.
// Replays would stop prematurely if this was rebuilt every time.
- const captchaRes = i.state.captcha?.ok;
- if (captchaRes) {
+
+ if (i.state.captchaRes.state == "success" && i.state.captchaRes.data.ok) {
+ const captchaRes = i.state.captchaRes.data.ok;
if (!i.audio) {
const base64 = `data:audio/wav;base64,${captchaRes.wav}`;
i.audio = new Audio(base64);
captchaPngSrc(captcha: CaptchaResponse) {
return `data:image/png;base64,${captcha.png}`;
}
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.setState(s => ((s.form.captcha_answer = undefined), s));
- // Refetch another captcha
- // WebSocketService.Instance.send(wsClient.getCaptcha());
- return;
- } else {
- if (op == UserOperation.Register) {
- const data = wsJsonToRes<LoginResponse>(msg);
- // Only log them in if a jwt was set
- if (data.jwt) {
- UserService.Instance.login(data);
- this.props.history.push("/communities");
- location.reload();
- } else {
- if (data.verify_email_sent) {
- toast(i18n.t("verify_email_sent"));
- }
- if (data.registration_created) {
- toast(i18n.t("registration_application_sent"));
- }
- this.props.history.push("/");
- }
- } else if (op == UserOperation.GetCaptcha) {
- const data = wsJsonToRes<GetCaptchaResponse>(msg);
- if (data.ok) {
- this.setState({ captcha: data });
- this.setState(s => ((s.form.captcha_uuid = data.ok?.uuid), s));
- }
- } else if (op == UserOperation.PasswordReset) {
- toast(i18n.t("reset_password_mail_sent"));
- } else if (op == UserOperation.GetSite) {
- const data = wsJsonToRes<GetSiteResponse>(msg);
- this.setState({ siteRes: data });
- }
- }
- }
}
import {
CreateSite,
EditSite,
- GetFederatedInstancesResponse,
GetSiteResponse,
+ Instance,
ListingType,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
-import {
- capitalizeFirstLetter,
- fetchThemeList,
- myAuth,
- wsClient,
-} from "../../utils";
+import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
import NavigationPrompt from "../common/navigation-prompt";
interface SiteFormProps {
- siteRes: GetSiteResponse;
- instancesRes?: GetFederatedInstancesResponse;
+ blockedInstances?: Instance[];
+ allowedInstances?: Instance[];
showLocal?: boolean;
+ themeList?: string[];
+ onSaveSite(form: EditSite): void;
+ siteRes: GetSiteResponse;
}
interface SiteFormState {
siteForm: EditSite;
loading: boolean;
- themeList?: string[];
instance_select: {
allowed_instances: string;
blocked_instances: string;
};
+ submitted: boolean;
}
type InstanceKey = "allowed_instances" | "blocked_instances";
export class SiteForm extends Component<SiteFormProps, SiteFormState> {
state: SiteFormState = {
- siteForm: {
- auth: "TODO",
- },
+ siteForm: this.initSiteForm(),
loading: false,
instance_select: {
allowed_instances: "",
blocked_instances: "",
},
+ submitted: false,
};
+ initSiteForm(): EditSite {
+ const site = this.props.siteRes.site_view.site;
+ const ls = this.props.siteRes.site_view.local_site;
+ return {
+ name: site.name,
+ sidebar: site.sidebar,
+ description: site.description,
+ enable_downvotes: ls.enable_downvotes,
+ registration_mode: ls.registration_mode,
+ enable_nsfw: ls.enable_nsfw,
+ community_creation_admin_only: ls.community_creation_admin_only,
+ icon: site.icon,
+ banner: site.banner,
+ require_email_verification: ls.require_email_verification,
+ application_question: ls.application_question,
+ private_instance: ls.private_instance,
+ default_theme: ls.default_theme,
+ default_post_listing_type: ls.default_post_listing_type,
+ legal_information: ls.legal_information,
+ application_email_admins: ls.application_email_admins,
+ reports_email_admins: ls.reports_email_admins,
+ hide_modlog_mod_names: ls.hide_modlog_mod_names,
+ discussion_languages: this.props.siteRes.discussion_languages,
+ slur_filter_regex: ls.slur_filter_regex,
+ actor_name_max_length: ls.actor_name_max_length,
+ federation_enabled: ls.federation_enabled,
+ federation_debug: ls.federation_debug,
+ federation_worker_count: ls.federation_worker_count,
+ captcha_enabled: ls.captcha_enabled,
+ captcha_difficulty: ls.captcha_difficulty,
+ allowed_instances: this.props.allowedInstances?.map(i => i.domain),
+ blocked_instances: this.props.blockedInstances?.map(i => i.domain),
+ auth: "TODO",
+ };
+ }
+
constructor(props: any, context: any) {
super(props, context);
this.handleDiscussionLanguageChange =
this.handleDiscussionLanguageChange.bind(this);
-
- const site = this.props.siteRes.site_view.site;
- const ls = this.props.siteRes.site_view.local_site;
- this.state = {
- ...this.state,
- siteForm: {
- name: site.name,
- sidebar: site.sidebar,
- description: site.description,
- enable_downvotes: ls.enable_downvotes,
- registration_mode: ls.registration_mode,
- enable_nsfw: ls.enable_nsfw,
- community_creation_admin_only: ls.community_creation_admin_only,
- icon: site.icon,
- banner: site.banner,
- require_email_verification: ls.require_email_verification,
- application_question: ls.application_question,
- private_instance: ls.private_instance,
- default_theme: ls.default_theme,
- default_post_listing_type: ls.default_post_listing_type,
- legal_information: ls.legal_information,
- application_email_admins: ls.application_email_admins,
- reports_email_admins: ls.reports_email_admins,
- hide_modlog_mod_names: ls.hide_modlog_mod_names,
- discussion_languages: this.props.siteRes.discussion_languages,
- slur_filter_regex: ls.slur_filter_regex,
- actor_name_max_length: ls.actor_name_max_length,
- federation_enabled: ls.federation_enabled,
- federation_debug: ls.federation_debug,
- federation_worker_count: ls.federation_worker_count,
- captcha_enabled: ls.captcha_enabled,
- captcha_difficulty: ls.captcha_difficulty,
- allowed_instances:
- this.props.instancesRes?.federated_instances?.allowed.map(
- i => i.domain
- ),
- blocked_instances:
- this.props.instancesRes?.federated_instances?.blocked.map(
- i => i.domain
- ),
- auth: "TODO",
- },
- };
- }
-
- async componentDidMount() {
- this.setState({ themeList: await fetchThemeList() });
+ this.handleAddInstance = this.handleAddInstance.bind(this);
+ this.handleInstanceEnterPress = this.handleInstanceEnterPress.bind(this);
+ this.handleInstanceTextChange = this.handleInstanceTextChange.bind(this);
}
// Necessary to stop the loading
this.setState({ loading: false });
}
- componentDidUpdate() {
- if (
- !this.state.loading &&
- !this.props.siteRes.site_view.local_site.site_setup &&
- (this.state.siteForm.name ||
- this.state.siteForm.sidebar ||
- this.state.siteForm.application_question ||
- this.state.siteForm.description)
- ) {
- window.onbeforeunload = () => true;
- } else {
- window.onbeforeunload = null;
- }
- }
-
- componentWillUnmount() {
- window.onbeforeunload = null;
- }
-
render() {
const siteSetup = this.props.siteRes.site_view.local_site.site_setup;
return (
- <>
+ <form onSubmit={linkEvent(this, this.handleSaveSiteSubmit)}>
<NavigationPrompt
when={
!this.state.loading &&
this.state.siteForm.sidebar ||
this.state.siteForm.application_question ||
this.state.siteForm.description
- )
+ ) &&
+ !this.state.submitted
}
/>
- <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
- <h5>{`${
- siteSetup
- ? capitalizeFirstLetter(i18n.t("save"))
- : capitalizeFirstLetter(i18n.t("name"))
- } ${i18n.t("your_site")}`}</h5>
- <div className="form-group row">
- <label className="col-12 col-form-label" htmlFor="create-site-name">
- {i18n.t("name")}
- </label>
- <div className="col-12">
- <input
- type="text"
- id="create-site-name"
- className="form-control"
- value={this.state.siteForm.name}
- onInput={linkEvent(this, this.handleSiteNameChange)}
- required
- minLength={3}
- maxLength={20}
- />
- </div>
+ <h5>{`${
+ siteSetup
+ ? capitalizeFirstLetter(i18n.t("save"))
+ : capitalizeFirstLetter(i18n.t("name"))
+ } ${i18n.t("your_site")}`}</h5>
+ <div className="form-group row">
+ <label className="col-12 col-form-label" htmlFor="create-site-name">
+ {i18n.t("name")}
+ </label>
+ <div className="col-12">
+ <input
+ type="text"
+ id="create-site-name"
+ className="form-control"
+ value={this.state.siteForm.name}
+ onInput={linkEvent(this, this.handleSiteNameChange)}
+ required
+ minLength={3}
+ maxLength={20}
+ />
</div>
- <div className="form-group">
- <label>{i18n.t("icon")}</label>
- <ImageUploadForm
- uploadTitle={i18n.t("upload_icon")}
- imageSrc={this.state.siteForm.icon}
- onUpload={this.handleIconUpload}
- onRemove={this.handleIconRemove}
- rounded
+ </div>
+ <div className="form-group">
+ <label>{i18n.t("icon")}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t("upload_icon")}
+ imageSrc={this.state.siteForm.icon}
+ onUpload={this.handleIconUpload}
+ onRemove={this.handleIconRemove}
+ rounded
+ />
+ </div>
+ <div className="form-group">
+ <label>{i18n.t("banner")}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t("upload_banner")}
+ imageSrc={this.state.siteForm.banner}
+ onUpload={this.handleBannerUpload}
+ onRemove={this.handleBannerRemove}
+ />
+ </div>
+ <div className="form-group row">
+ <label className="col-12 col-form-label" htmlFor="site-desc">
+ {i18n.t("description")}
+ </label>
+ <div className="col-12">
+ <input
+ type="text"
+ className="form-control"
+ id="site-desc"
+ value={this.state.siteForm.description}
+ onInput={linkEvent(this, this.handleSiteDescChange)}
+ maxLength={150}
/>
</div>
- <div className="form-group">
- <label>{i18n.t("banner")}</label>
- <ImageUploadForm
- uploadTitle={i18n.t("upload_banner")}
- imageSrc={this.state.siteForm.banner}
- onUpload={this.handleBannerUpload}
- onRemove={this.handleBannerRemove}
+ </div>
+ <div className="form-group row">
+ <label className="col-12 col-form-label">{i18n.t("sidebar")}</label>
+ <div className="col-12">
+ <MarkdownTextArea
+ initialContent={this.state.siteForm.sidebar}
+ onContentChange={this.handleSiteSidebarChange}
+ hideNavigationWarnings
+ allLanguages={[]}
+ siteLanguages={[]}
/>
</div>
- <div className="form-group row">
- <label className="col-12 col-form-label" htmlFor="site-desc">
- {i18n.t("description")}
- </label>
- <div className="col-12">
+ </div>
+ <div className="form-group row">
+ <label className="col-12 col-form-label">
+ {i18n.t("legal_information")}
+ </label>
+ <div className="col-12">
+ <MarkdownTextArea
+ initialContent={this.state.siteForm.legal_information}
+ onContentChange={this.handleSiteLegalInfoChange}
+ hideNavigationWarnings
+ allLanguages={[]}
+ siteLanguages={[]}
+ />
+ </div>
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
<input
- type="text"
- className="form-control"
- id="site-desc"
- value={this.state.siteForm.description}
- onInput={linkEvent(this, this.handleSiteDescChange)}
- maxLength={150}
+ className="form-check-input"
+ id="create-site-downvotes"
+ type="checkbox"
+ checked={this.state.siteForm.enable_downvotes}
+ onChange={linkEvent(this, this.handleSiteEnableDownvotesChange)}
/>
+ <label
+ className="form-check-label"
+ htmlFor="create-site-downvotes"
+ >
+ {i18n.t("enable_downvotes")}
+ </label>
</div>
</div>
- <div className="form-group row">
- <label className="col-12 col-form-label">{i18n.t("sidebar")}</label>
- <div className="col-12">
- <MarkdownTextArea
- initialContent={this.state.siteForm.sidebar}
- onContentChange={this.handleSiteSidebarChange}
- hideNavigationWarnings
- allLanguages={[]}
- siteLanguages={[]}
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
+ <input
+ className="form-check-input"
+ id="create-site-enable-nsfw"
+ type="checkbox"
+ checked={this.state.siteForm.enable_nsfw}
+ onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
/>
+ <label
+ className="form-check-label"
+ htmlFor="create-site-enable-nsfw"
+ >
+ {i18n.t("enable_nsfw")}
+ </label>
</div>
</div>
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <label
+ className="form-check-label mr-2"
+ htmlFor="create-site-registration-mode"
+ >
+ {i18n.t("registration_mode")}
+ </label>
+ <select
+ id="create-site-registration-mode"
+ value={this.state.siteForm.registration_mode}
+ onChange={linkEvent(this, this.handleSiteRegistrationModeChange)}
+ className="custom-select w-auto"
+ >
+ <option value={"RequireApplication"}>
+ {i18n.t("require_registration_application")}
+ </option>
+ <option value={"Open"}>{i18n.t("open_registration")}</option>
+ <option value={"Closed"}>{i18n.t("close_registration")}</option>
+ </select>
+ </div>
+ </div>
+ {this.state.siteForm.registration_mode == "RequireApplication" && (
<div className="form-group row">
<label className="col-12 col-form-label">
- {i18n.t("legal_information")}
+ {i18n.t("application_questionnaire")}
</label>
<div className="col-12">
<MarkdownTextArea
- initialContent={this.state.siteForm.legal_information}
- onContentChange={this.handleSiteLegalInfoChange}
+ initialContent={this.state.siteForm.application_question}
+ onContentChange={this.handleSiteApplicationQuestionChange}
hideNavigationWarnings
allLanguages={[]}
siteLanguages={[]}
/>
</div>
</div>
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-downvotes"
- type="checkbox"
- checked={this.state.siteForm.enable_downvotes}
- onChange={linkEvent(
- this,
- this.handleSiteEnableDownvotesChange
- )}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-downvotes"
- >
- {i18n.t("enable_downvotes")}
- </label>
- </div>
- </div>
- </div>
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-enable-nsfw"
- type="checkbox"
- checked={this.state.siteForm.enable_nsfw}
- onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-enable-nsfw"
- >
- {i18n.t("enable_nsfw")}
- </label>
- </div>
- </div>
- </div>
- <div className="form-group row">
- <div className="col-12">
+ )}
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
+ <input
+ className="form-check-input"
+ id="create-site-community-creation-admin-only"
+ type="checkbox"
+ checked={this.state.siteForm.community_creation_admin_only}
+ onChange={linkEvent(
+ this,
+ this.handleSiteCommunityCreationAdminOnly
+ )}
+ />
<label
- className="form-check-label mr-2"
- htmlFor="create-site-registration-mode"
+ className="form-check-label"
+ htmlFor="create-site-community-creation-admin-only"
>
- {i18n.t("registration_mode")}
+ {i18n.t("community_creation_admin_only")}
</label>
- <select
- id="create-site-registration-mode"
- value={this.state.siteForm.registration_mode}
+ </div>
+ </div>
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
+ <input
+ className="form-check-input"
+ id="create-site-require-email-verification"
+ type="checkbox"
+ checked={this.state.siteForm.require_email_verification}
onChange={linkEvent(
this,
- this.handleSiteRegistrationModeChange
+ this.handleSiteRequireEmailVerification
)}
- className="custom-select w-auto"
+ />
+ <label
+ className="form-check-label"
+ htmlFor="create-site-require-email-verification"
>
- <option value={"RequireApplication"}>
- {i18n.t("require_registration_application")}
- </option>
- <option value={"Open"}>{i18n.t("open_registration")}</option>
- <option value={"Closed"}>{i18n.t("close_registration")}</option>
- </select>
- </div>
- </div>
- {this.state.siteForm.registration_mode == "RequireApplication" && (
- <div className="form-group row">
- <label className="col-12 col-form-label">
- {i18n.t("application_questionnaire")}
+ {i18n.t("require_email_verification")}
</label>
- <div className="col-12">
- <MarkdownTextArea
- initialContent={this.state.siteForm.application_question}
- onContentChange={this.handleSiteApplicationQuestionChange}
- hideNavigationWarnings
- allLanguages={[]}
- siteLanguages={[]}
- />
- </div>
- </div>
- )}
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-community-creation-admin-only"
- type="checkbox"
- checked={this.state.siteForm.community_creation_admin_only}
- onChange={linkEvent(
- this,
- this.handleSiteCommunityCreationAdminOnly
- )}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-community-creation-admin-only"
- >
- {i18n.t("community_creation_admin_only")}
- </label>
- </div>
- </div>
- </div>
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-require-email-verification"
- type="checkbox"
- checked={this.state.siteForm.require_email_verification}
- onChange={linkEvent(
- this,
- this.handleSiteRequireEmailVerification
- )}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-require-email-verification"
- >
- {i18n.t("require_email_verification")}
- </label>
- </div>
- </div>
- </div>
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-application-email-admins"
- type="checkbox"
- checked={this.state.siteForm.application_email_admins}
- onChange={linkEvent(
- this,
- this.handleSiteApplicationEmailAdmins
- )}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-email-admins"
- >
- {i18n.t("application_email_admins")}
- </label>
- </div>
- </div>
- </div>
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-reports-email-admins"
- type="checkbox"
- checked={this.state.siteForm.reports_email_admins}
- onChange={linkEvent(this, this.handleSiteReportsEmailAdmins)}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-reports-email-admins"
- >
- {i18n.t("reports_email_admins")}
- </label>
- </div>
</div>
</div>
- <div className="form-group row">
- <div className="col-12">
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
+ <input
+ className="form-check-input"
+ id="create-site-application-email-admins"
+ type="checkbox"
+ checked={this.state.siteForm.application_email_admins}
+ onChange={linkEvent(
+ this,
+ this.handleSiteApplicationEmailAdmins
+ )}
+ />
<label
- className="form-check-label mr-2"
- htmlFor="create-site-default-theme"
+ className="form-check-label"
+ htmlFor="create-site-email-admins"
>
- {i18n.t("theme")}
+ {i18n.t("application_email_admins")}
</label>
- <select
- id="create-site-default-theme"
- value={this.state.siteForm.default_theme}
- onChange={linkEvent(this, this.handleSiteDefaultTheme)}
- className="custom-select w-auto"
- >
- <option value="browser">{i18n.t("browser_default")}</option>
- {this.state.themeList?.map(theme => (
- <option key={theme} value={theme}>
- {theme}
- </option>
- ))}
- </select>
- </div>
- </div>
- {this.props.showLocal && (
- <form className="form-group row">
- <label className="col-sm-3">{i18n.t("listing_type")}</label>
- <div className="col-sm-9">
- <ListingTypeSelect
- type_={
- this.state.siteForm.default_post_listing_type ?? "Local"
- }
- showLocal
- showSubscribed={false}
- onChange={this.handleDefaultPostListingTypeChange}
- />
- </div>
- </form>
- )}
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-private-instance"
- type="checkbox"
- checked={this.state.siteForm.private_instance}
- onChange={linkEvent(this, this.handleSitePrivateInstance)}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-private-instance"
- >
- {i18n.t("private_instance")}
- </label>
- </div>
</div>
</div>
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-hide-modlog-mod-names"
- type="checkbox"
- checked={this.state.siteForm.hide_modlog_mod_names}
- onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-hide-modlog-mod-names"
- >
- {i18n.t("hide_modlog_mod_names")}
- </label>
- </div>
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
+ <input
+ className="form-check-input"
+ id="create-site-reports-email-admins"
+ type="checkbox"
+ checked={this.state.siteForm.reports_email_admins}
+ onChange={linkEvent(this, this.handleSiteReportsEmailAdmins)}
+ />
+ <label
+ className="form-check-label"
+ htmlFor="create-site-reports-email-admins"
+ >
+ {i18n.t("reports_email_admins")}
+ </label>
</div>
</div>
- <div className="form-group row">
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
<label
- className="col-12 col-form-label"
- htmlFor="create-site-slur-filter-regex"
+ className="form-check-label mr-2"
+ htmlFor="create-site-default-theme"
>
- {i18n.t("slur_filter_regex")}
+ {i18n.t("theme")}
</label>
- <div className="col-12">
+ <select
+ id="create-site-default-theme"
+ value={this.state.siteForm.default_theme}
+ onChange={linkEvent(this, this.handleSiteDefaultTheme)}
+ className="custom-select w-auto"
+ >
+ <option value="browser">{i18n.t("browser_default")}</option>
+ {this.props.themeList?.map(theme => (
+ <option key={theme} value={theme}>
+ {theme}
+ </option>
+ ))}
+ </select>
+ </div>
+ </div>
+ {this.props.showLocal && (
+ <form className="form-group row">
+ <label className="col-sm-3">{i18n.t("listing_type")}</label>
+ <div className="col-sm-9">
+ <ListingTypeSelect
+ type_={this.state.siteForm.default_post_listing_type ?? "Local"}
+ showLocal
+ showSubscribed={false}
+ onChange={this.handleDefaultPostListingTypeChange}
+ />
+ </div>
+ </form>
+ )}
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
<input
- type="text"
- id="create-site-slur-filter-regex"
- placeholder="(word1|word2)"
- className="form-control"
- value={this.state.siteForm.slur_filter_regex}
- onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
- minLength={3}
+ className="form-check-input"
+ id="create-site-private-instance"
+ type="checkbox"
+ checked={this.state.siteForm.private_instance}
+ onChange={linkEvent(this, this.handleSitePrivateInstance)}
/>
+ <label
+ className="form-check-label"
+ htmlFor="create-site-private-instance"
+ >
+ {i18n.t("private_instance")}
+ </label>
</div>
</div>
- <LanguageSelect
- allLanguages={this.props.siteRes.all_languages}
- siteLanguages={this.props.siteRes.discussion_languages}
- selectedLanguageIds={this.state.siteForm.discussion_languages}
- multiple={true}
- onChange={this.handleDiscussionLanguageChange}
- showAll
- />
- <div className="form-group row">
- <label
- className="col-12 col-form-label"
- htmlFor="create-site-actor-name"
- >
- {i18n.t("actor_name_max_length")}
- </label>
- <div className="col-12">
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
<input
- type="number"
- id="create-site-actor-name"
- className="form-control"
- min={5}
- value={this.state.siteForm.actor_name_max_length}
- onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
+ className="form-check-input"
+ id="create-site-hide-modlog-mod-names"
+ type="checkbox"
+ checked={this.state.siteForm.hide_modlog_mod_names}
+ onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
/>
+ <label
+ className="form-check-label"
+ htmlFor="create-site-hide-modlog-mod-names"
+ >
+ {i18n.t("hide_modlog_mod_names")}
+ </label>
</div>
</div>
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-federation-enabled"
- type="checkbox"
- checked={this.state.siteForm.federation_enabled}
- onChange={linkEvent(this, this.handleSiteFederationEnabled)}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-federation-enabled"
- >
- {i18n.t("federation_enabled")}
- </label>
- </div>
+ </div>
+ <div className="form-group row">
+ <label
+ className="col-12 col-form-label"
+ htmlFor="create-site-slur-filter-regex"
+ >
+ {i18n.t("slur_filter_regex")}
+ </label>
+ <div className="col-12">
+ <input
+ type="text"
+ id="create-site-slur-filter-regex"
+ placeholder="(word1|word2)"
+ className="form-control"
+ value={this.state.siteForm.slur_filter_regex}
+ onInput={linkEvent(this, this.handleSiteSlurFilterRegex)}
+ minLength={3}
+ />
+ </div>
+ </div>
+ <LanguageSelect
+ allLanguages={this.props.siteRes.all_languages}
+ siteLanguages={this.props.siteRes.discussion_languages}
+ selectedLanguageIds={this.state.siteForm.discussion_languages}
+ multiple={true}
+ onChange={this.handleDiscussionLanguageChange}
+ showAll
+ />
+ <div className="form-group row">
+ <label
+ className="col-12 col-form-label"
+ htmlFor="create-site-actor-name"
+ >
+ {i18n.t("actor_name_max_length")}
+ </label>
+ <div className="col-12">
+ <input
+ type="number"
+ id="create-site-actor-name"
+ className="form-control"
+ min={5}
+ value={this.state.siteForm.actor_name_max_length}
+ onInput={linkEvent(this, this.handleSiteActorNameMaxLength)}
+ />
+ </div>
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
+ <input
+ className="form-check-input"
+ id="create-site-federation-enabled"
+ type="checkbox"
+ checked={this.state.siteForm.federation_enabled}
+ onChange={linkEvent(this, this.handleSiteFederationEnabled)}
+ />
+ <label
+ className="form-check-label"
+ htmlFor="create-site-federation-enabled"
+ >
+ {i18n.t("federation_enabled")}
+ </label>
</div>
</div>
- {this.state.siteForm.federation_enabled && (
- <>
- <div className="form-group row">
- {this.federatedInstanceSelect("allowed_instances")}
- {this.federatedInstanceSelect("blocked_instances")}
- </div>
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-federation-debug"
- type="checkbox"
- checked={this.state.siteForm.federation_debug}
- onChange={linkEvent(this, this.handleSiteFederationDebug)}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-federation-debug"
- >
- {i18n.t("federation_debug")}
- </label>
- </div>
- </div>
- </div>
- <div className="form-group row">
- <label
- className="col-12 col-form-label"
- htmlFor="create-site-federation-worker-count"
- >
- {i18n.t("federation_worker_count")}
- </label>
- <div className="col-12">
+ </div>
+ {this.state.siteForm.federation_enabled && (
+ <>
+ <div className="form-group row">
+ {this.federatedInstanceSelect("allowed_instances")}
+ {this.federatedInstanceSelect("blocked_instances")}
+ </div>
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
<input
- type="number"
- id="create-site-federation-worker-count"
- className="form-control"
- min={0}
- value={this.state.siteForm.federation_worker_count}
- onInput={linkEvent(
- this,
- this.handleSiteFederationWorkerCount
- )}
+ className="form-check-input"
+ id="create-site-federation-debug"
+ type="checkbox"
+ checked={this.state.siteForm.federation_debug}
+ onChange={linkEvent(this, this.handleSiteFederationDebug)}
/>
+ <label
+ className="form-check-label"
+ htmlFor="create-site-federation-debug"
+ >
+ {i18n.t("federation_debug")}
+ </label>
</div>
</div>
- </>
- )}
- <div className="form-group row">
- <div className="col-12">
- <div className="form-check">
- <input
- className="form-check-input"
- id="create-site-captcha-enabled"
- type="checkbox"
- checked={this.state.siteForm.captcha_enabled}
- onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
- />
- <label
- className="form-check-label"
- htmlFor="create-site-captcha-enabled"
- >
- {i18n.t("captcha_enabled")}
- </label>
- </div>
</div>
- </div>
- {this.state.siteForm.captcha_enabled && (
<div className="form-group row">
+ <label
+ className="col-12 col-form-label"
+ htmlFor="create-site-federation-worker-count"
+ >
+ {i18n.t("federation_worker_count")}
+ </label>
<div className="col-12">
- <label
- className="form-check-label mr-2"
- htmlFor="create-site-captcha-difficulty"
- >
- {i18n.t("captcha_difficulty")}
- </label>
- <select
- id="create-site-captcha-difficulty"
- value={this.state.siteForm.captcha_difficulty}
- onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
- className="custom-select w-auto"
- >
- <option value="easy">{i18n.t("easy")}</option>
- <option value="medium">{i18n.t("medium")}</option>
- <option value="hard">{i18n.t("hard")}</option>
- </select>
+ <input
+ type="number"
+ id="create-site-federation-worker-count"
+ className="form-control"
+ min={0}
+ value={this.state.siteForm.federation_worker_count}
+ onInput={linkEvent(
+ this,
+ this.handleSiteFederationWorkerCount
+ )}
+ />
</div>
</div>
- )}
+ </>
+ )}
+ <div className="form-group row">
+ <div className="col-12">
+ <div className="form-check">
+ <input
+ className="form-check-input"
+ id="create-site-captcha-enabled"
+ type="checkbox"
+ checked={this.state.siteForm.captcha_enabled}
+ onChange={linkEvent(this, this.handleSiteCaptchaEnabled)}
+ />
+ <label
+ className="form-check-label"
+ htmlFor="create-site-captcha-enabled"
+ >
+ {i18n.t("captcha_enabled")}
+ </label>
+ </div>
+ </div>
+ </div>
+ {this.state.siteForm.captcha_enabled && (
<div className="form-group row">
<div className="col-12">
- <button
- type="submit"
- className="btn btn-secondary mr-2"
- disabled={this.state.loading}
+ <label
+ className="form-check-label mr-2"
+ htmlFor="create-site-captcha-difficulty"
>
- {this.state.loading ? (
- <Spinner />
- ) : siteSetup ? (
- capitalizeFirstLetter(i18n.t("save"))
- ) : (
- capitalizeFirstLetter(i18n.t("create"))
- )}
- </button>
+ {i18n.t("captcha_difficulty")}
+ </label>
+ <select
+ id="create-site-captcha-difficulty"
+ value={this.state.siteForm.captcha_difficulty}
+ onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
+ className="custom-select w-auto"
+ >
+ <option value="easy">{i18n.t("easy")}</option>
+ <option value="medium">{i18n.t("medium")}</option>
+ <option value="hard">{i18n.t("hard")}</option>
+ </select>
</div>
</div>
- </form>
- </>
+ )}
+ <div className="form-group row">
+ <div className="col-12">
+ <button
+ type="submit"
+ className="btn btn-secondary mr-2"
+ disabled={this.state.loading}
+ >
+ {this.state.loading ? (
+ <Spinner />
+ ) : siteSetup ? (
+ capitalizeFirstLetter(i18n.t("save"))
+ ) : (
+ capitalizeFirstLetter(i18n.t("create"))
+ )}
+ </button>
+ </div>
+ </div>
+ </form>
);
}
type="button"
className="btn btn-sm bg-success ml-2"
onClick={linkEvent(key, this.handleAddInstance)}
+ style={"width: 2rem; height: 2rem;"}
tabIndex={
-1 /* Making this untabble because handling enter key in text input makes keyboard support for this button redundant */
}
>
- <Icon icon="add" classes="icon-inline text-light m-auto" />
+ <Icon
+ icon="add"
+ classes="icon-inline text-light m-auto d-block position-static"
+ />
</button>
</div>
{selectedInstances && selectedInstances.length > 0 && (
<button
id={instance}
type="button"
+ style={"width: 2rem; height: 2rem;"}
className="btn btn-sm bg-danger"
onClick={linkEvent(
{ key, instance },
this.handleRemoveInstance
)}
>
- <Icon icon="x" classes="icon-inline text-light m-auto" />
+ <Icon
+ icon="x"
+ classes="icon-inline text-light m-auto d-block position-static"
+ />
</button>
</li>
))}
}
}
- handleCreateSiteSubmit(i: SiteForm, event: any) {
+ handleSaveSiteSubmit(i: SiteForm, event: any) {
event.preventDefault();
- i.setState({ loading: true });
- const auth = myAuth() ?? "TODO";
+ const auth = myAuthRequired();
i.setState(s => ((s.siteForm.auth = auth), s));
+ i.setState({ loading: true, submitted: true });
+
+ const stateSiteForm = i.state.siteForm;
+
+ let form: EditSite | CreateSite;
+
if (i.props.siteRes.site_view.local_site.site_setup) {
- WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
+ form = stateSiteForm;
} else {
- const sForm = i.state.siteForm;
- const form: CreateSite = {
- name: sForm.name ?? "My site",
- sidebar: sForm.sidebar,
- description: sForm.description,
- icon: sForm.icon,
- banner: sForm.banner,
- community_creation_admin_only: sForm.community_creation_admin_only,
- enable_nsfw: sForm.enable_nsfw,
- enable_downvotes: sForm.enable_downvotes,
- application_question: sForm.application_question,
- registration_mode: sForm.registration_mode,
- require_email_verification: sForm.require_email_verification,
- private_instance: sForm.private_instance,
- default_theme: sForm.default_theme,
- default_post_listing_type: sForm.default_post_listing_type,
- application_email_admins: sForm.application_email_admins,
- hide_modlog_mod_names: sForm.hide_modlog_mod_names,
- legal_information: sForm.legal_information,
- slur_filter_regex: sForm.slur_filter_regex,
- actor_name_max_length: sForm.actor_name_max_length,
- federation_enabled: sForm.federation_enabled,
- federation_debug: sForm.federation_debug,
- federation_worker_count: sForm.federation_worker_count,
- captcha_enabled: sForm.captcha_enabled,
- captcha_difficulty: sForm.captcha_difficulty,
- allowed_instances: sForm.allowed_instances,
- blocked_instances: sForm.blocked_instances,
- discussion_languages: sForm.discussion_languages,
+ form = {
+ name: stateSiteForm.name ?? "My site",
+ sidebar: stateSiteForm.sidebar,
+ description: stateSiteForm.description,
+ icon: stateSiteForm.icon,
+ banner: stateSiteForm.banner,
+ community_creation_admin_only:
+ stateSiteForm.community_creation_admin_only,
+ enable_nsfw: stateSiteForm.enable_nsfw,
+ enable_downvotes: stateSiteForm.enable_downvotes,
+ application_question: stateSiteForm.application_question,
+ registration_mode: stateSiteForm.registration_mode,
+ require_email_verification: stateSiteForm.require_email_verification,
+ private_instance: stateSiteForm.private_instance,
+ default_theme: stateSiteForm.default_theme,
+ default_post_listing_type: stateSiteForm.default_post_listing_type,
+ application_email_admins: stateSiteForm.application_email_admins,
+ hide_modlog_mod_names: stateSiteForm.hide_modlog_mod_names,
+ legal_information: stateSiteForm.legal_information,
+ slur_filter_regex: stateSiteForm.slur_filter_regex,
+ actor_name_max_length: stateSiteForm.actor_name_max_length,
+ rate_limit_message: stateSiteForm.rate_limit_message,
+ rate_limit_message_per_second:
+ stateSiteForm.rate_limit_message_per_second,
+ rate_limit_comment: stateSiteForm.rate_limit_comment,
+ rate_limit_comment_per_second:
+ stateSiteForm.rate_limit_comment_per_second,
+ rate_limit_image: stateSiteForm.rate_limit_image,
+ rate_limit_image_per_second: stateSiteForm.rate_limit_image_per_second,
+ rate_limit_post: stateSiteForm.rate_limit_post,
+ rate_limit_post_per_second: stateSiteForm.rate_limit_post_per_second,
+ rate_limit_register: stateSiteForm.rate_limit_register,
+ rate_limit_register_per_second:
+ stateSiteForm.rate_limit_register_per_second,
+ rate_limit_search: stateSiteForm.rate_limit_search,
+ rate_limit_search_per_second:
+ stateSiteForm.rate_limit_search_per_second,
+ federation_enabled: stateSiteForm.federation_enabled,
+ federation_debug: stateSiteForm.federation_debug,
+ federation_worker_count: stateSiteForm.federation_worker_count,
+ captcha_enabled: stateSiteForm.captcha_enabled,
+ captcha_difficulty: stateSiteForm.captcha_difficulty,
+ allowed_instances: stateSiteForm.allowed_instances,
+ blocked_instances: stateSiteForm.blocked_instances,
+ discussion_languages: stateSiteForm.discussion_languages,
auth,
};
- WebSocketService.Instance.send(wsClient.createSite(form));
}
- i.setState(i.state);
+
+ i.props.onSaveSite(form);
}
handleAddInstance(key: InstanceKey) {
import { Component, InfernoMouseEvent, linkEvent } from "inferno";
-import { EditSite, GetSiteResponse } from "lemmy-js-client";
+import { EditSite, Tagline } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
-import { capitalizeFirstLetter, myAuth, wsClient } from "../../utils";
+import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
interface TaglineFormProps {
- siteRes: GetSiteResponse;
+ taglines: Array<Tagline>;
+ onSaveSite(form: EditSite): void;
}
interface TaglineFormState {
- siteRes: GetSiteResponse;
- siteForm: EditSite;
+ taglines: Array<string>;
loading: boolean;
editingRow?: number;
}
export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
state: TaglineFormState = {
loading: false,
- siteRes: this.props.siteRes,
editingRow: undefined,
- siteForm: {
- taglines: this.props.siteRes.taglines?.map(x => x.content),
- auth: "TODO",
- },
+ taglines: this.props.taglines.map(x => x.content),
};
constructor(props: any, context: any) {
super(props, context);
<th style="width:121px"></th>
</thead>
<tbody>
- {this.state.siteForm.taglines?.map((cv, index) => (
+ {this.state.taglines.map((cv, index) => (
<tr key={index}>
<td>
{this.state.editingRow == index && (
this.handleTaglineChange(this, index, s)
}
hideNavigationWarnings
- allLanguages={this.state.siteRes.all_languages}
- siteLanguages={this.state.siteRes.discussion_languages}
+ allLanguages={[]}
+ siteLanguages={[]}
/>
)}
{this.state.editingRow != index && <div>{cv}</div>}
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
- { form: this, index: index },
+ { i: this, index: index },
this.handleEditTaglineClick
)}
data-tippy-content={i18n.t("edit")}
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(
- { form: this, index: index },
+ { i: this, index: index },
this.handleDeleteTaglineClick
)}
data-tippy-content={i18n.t("delete")}
}
handleTaglineChange(i: TaglineForm, index: number, val: string) {
- const taglines = i.state.siteForm.taglines;
- if (taglines) {
- taglines[index] = val;
- i.setState(i.state);
+ if (i.state.taglines) {
+ i.setState(prev => ({
+ ...prev,
+ taglines: prev.taglines.map((tl, i) => (i === index ? val : tl)),
+ }));
}
}
- handleDeleteTaglineClick(
- props: { form: TaglineForm; index: number },
- event: any
- ) {
+ handleDeleteTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
event.preventDefault();
- const taglines = props.form.state.siteForm.taglines;
- if (taglines) {
- taglines.splice(props.index, 1);
- props.form.state.siteForm.taglines = undefined;
- props.form.setState(props.form.state);
- props.form.state.siteForm.taglines = taglines;
- props.form.setState({ ...props.form.state, editingRow: undefined });
- }
+ d.i.setState(prev => ({
+ ...prev,
+ taglines: prev.taglines.filter((_, i) => i !== d.index),
+ editingRow: undefined,
+ }));
}
- handleEditTaglineClick(
- props: { form: TaglineForm; index: number },
- event: any
- ) {
+ handleEditTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
event.preventDefault();
- if (this.state.editingRow == props.index) {
- props.form.setState({ editingRow: undefined });
+ if (this.state.editingRow == d.index) {
+ d.i.setState({ editingRow: undefined });
} else {
- props.form.setState({ editingRow: props.index });
+ d.i.setState({ editingRow: d.index });
}
}
- handleSaveClick(i: TaglineForm) {
+ async handleSaveClick(i: TaglineForm) {
i.setState({ loading: true });
- const auth = myAuth() ?? "TODO";
- i.setState(s => ((s.siteForm.auth = auth), s));
- WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
- i.setState({ ...i.state, editingRow: undefined });
+ i.props.onSaveSite({
+ taglines: i.state.taglines,
+ auth: myAuthRequired(),
+ });
}
handleAddTaglineClick(
event: InfernoMouseEvent<HTMLButtonElement>
) {
event.preventDefault();
- if (!i.state.siteForm.taglines) {
- i.state.siteForm.taglines = [];
- }
- i.state.siteForm.taglines.push("");
+ const newTaglines = [...i.state.taglines];
+ newTaglines.push("");
+
i.setState({
- ...i.state,
- editingRow: i.state.siteForm.taglines.length - 1,
+ taglines: newTaglines,
+ editingRow: newTaglines.length - 1,
});
}
}
AdminPurgeCommunityView,
AdminPurgePersonView,
AdminPurgePostView,
- CommunityModeratorView,
GetCommunity,
GetCommunityResponse,
GetModlog,
GetModlogResponse,
GetPersonDetails,
- GetPersonDetailsResponse,
ModAddCommunityView,
ModAddView,
ModBanFromCommunityView,
ModTransferCommunityView,
ModlogActionType,
Person,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
import moment from "moment";
-import { Subscription } from "rxjs";
import { i18n } from "../i18next";
import { InitialFetchRequest } from "../interfaces";
-import { WebSocketService } from "../services";
+import { FirstLoadService } from "../services/FirstLoadService";
+import { HttpService, RequestState } from "../services/HttpService";
import {
Choice,
QueryParams,
getQueryParams,
getQueryString,
getUpdatedSearchId,
- isBrowser,
myAuth,
personToChoice,
setIsoData,
- toast,
- wsClient,
- wsSubscribe,
} from "../utils";
import { HtmlTags } from "./common/html-tags";
import { Icon, Spinner } from "./common/icon";
});
interface ModlogState {
- res?: GetModlogResponse;
- communityMods?: CommunityModeratorView[];
- communityName?: string;
- loadingModlog: boolean;
+ res: RequestState<GetModlogResponse>;
+ communityRes: RequestState<GetCommunityResponse>;
loadingModSearch: boolean;
loadingUserSearch: boolean;
modSearchOptions: Choice[];
if (text.length > 0) {
newOptions.push(
- ...(await fetchUsers(text)).users
+ ...(await fetchUsers(text))
.slice(0, Number(fetchLimit))
.map<Choice>(personToChoice)
);
ModlogState
> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: ModlogState = {
- loadingModlog: true,
+ res: { state: "empty" },
+ communityRes: { state: "empty" },
loadingModSearch: false,
loadingUserSearch: false,
userSearchOptions: [],
this.handleUserChange = this.handleUserChange.bind(this);
this.handleModChange = this.handleModChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
// Only fetch the data if coming from another route
- if (this.isoData.path === this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
+ const [res, communityRes, filteredModRes, filteredUserRes] =
+ this.isoData.routeData;
this.state = {
...this.state,
- res: this.isoData.routeData[0] as GetModlogResponse,
+ res,
+ communityRes,
};
- const communityRes: GetCommunityResponse | undefined =
- this.isoData.routeData[1];
-
- // Getting the moderators
- this.state = {
- ...this.state,
- communityMods: communityRes?.moderators,
- };
-
- const filteredModRes: GetPersonDetailsResponse | undefined =
- this.isoData.routeData[2];
- if (filteredModRes) {
+ if (filteredModRes.state === "success") {
this.state = {
...this.state,
- modSearchOptions: [personToChoice(filteredModRes.person_view)],
+ modSearchOptions: [personToChoice(filteredModRes.data.person_view)],
};
}
- const filteredUserRes: GetPersonDetailsResponse | undefined =
- this.isoData.routeData[3];
- if (filteredUserRes) {
+ if (filteredUserRes.state === "success") {
this.state = {
...this.state,
- userSearchOptions: [personToChoice(filteredUserRes.person_view)],
+ userSearchOptions: [personToChoice(filteredUserRes.data.person_view)],
};
}
-
- this.state = { ...this.state, loadingModlog: false };
- } else {
- this.refetch();
- }
- }
-
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
}
}
get combined() {
const res = this.state.res;
- const combined = res ? buildCombined(res) : [];
+ const combined = res.state == "success" ? buildCombined(res.data) : [];
return (
<tbody>
}
get amAdminOrMod(): boolean {
- return amAdmin() || amMod(this.state.communityMods);
+ const amMod_ =
+ this.state.communityRes.state == "success" &&
+ amMod(this.state.communityRes.data.moderators);
+ return amAdmin() || amMod_;
}
modOrAdminText(person?: Person): string {
render() {
const {
- communityName,
- loadingModlog,
loadingModSearch,
loadingUserSearch,
userSearchOptions,
modSearchOptions,
} = this.state;
- const { actionType, page, modId, userId } = getModlogQueryParams();
+ const { actionType, modId, userId } = getModlogQueryParams();
return (
<div className="container-lg">
#<strong>#</strong>#
</T>
</div>
- <h5>
- {communityName && (
- <Link className="text-body" to={`/c/${communityName}`}>
- /c/{communityName}{" "}
+ {this.state.communityRes.state === "success" && (
+ <h5>
+ <Link
+ className="text-body"
+ to={`/c/${this.state.communityRes.data.community_view.community.name}`}
+ >
+ /c/{this.state.communityRes.data.community_view.community.name}{" "}
</Link>
- )}
- <span>{i18n.t("modlog")}</span>
- </h5>
+ <span>{i18n.t("modlog")}</span>
+ </h5>
+ )}
<div className="form-row">
<select
value={actionType}
/>
)}
</div>
- <div className="table-responsive">
- {loadingModlog ? (
- <h5>
- <Spinner large />
- </h5>
- ) : (
- <table id="modlog_table" className="table table-sm table-hover">
- <thead className="pointer">
- <tr>
- <th> {i18n.t("time")}</th>
- <th>{i18n.t("mod")}</th>
- <th>{i18n.t("action")}</th>
- </tr>
- </thead>
- {this.combined}
- </table>
- )}
- <Paginator page={page} onChange={this.handlePageChange} />
- </div>
+ {this.renderModlogTable()}
</div>
</div>
);
}
+ renderModlogTable() {
+ switch (this.state.res.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const page = getModlogQueryParams().page;
+ return (
+ <div className="table-responsive">
+ <table id="modlog_table" className="table table-sm table-hover">
+ <thead className="pointer">
+ <tr>
+ <th> {i18n.t("time")}</th>
+ <th>{i18n.t("mod")}</th>
+ <th>{i18n.t("action")}</th>
+ </tr>
+ </thead>
+ {this.combined}
+ </table>
+ <Paginator page={page} onChange={this.handlePageChange} />
+ </div>
+ );
+ }
+ }
+ }
+
handleFilterActionChange(i: Modlog, event: any) {
i.updateUrl({
actionType: event.target.value as ModlogActionType,
});
});
- updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
+ async updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
const {
page: urlPage,
actionType: urlActionType,
)}`
);
- this.setState({
- loadingModlog: true,
- res: undefined,
- });
-
- this.refetch();
+ await this.refetch();
}
- refetch() {
- const auth = myAuth(false);
+ async refetch() {
+ const auth = myAuth();
const { actionType, page, modId, userId } = getModlogQueryParams();
const { communityId: urlCommunityId } = this.props.match.params;
const communityId = getIdFromString(urlCommunityId);
- const modlogForm: GetModlog = {
- community_id: communityId,
- page,
- limit: fetchLimit,
- type_: actionType,
- other_person_id: userId ?? undefined,
- mod_person_id: !this.isoData.site_res.site_view.local_site
- .hide_modlog_mod_names
- ? modId ?? undefined
- : undefined,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
-
- if (communityId) {
- const communityForm: GetCommunity = {
- id: communityId,
+ this.setState({ res: { state: "loading" } });
+ this.setState({
+ res: await HttpService.client.getModlog({
+ community_id: communityId,
+ page,
+ limit: fetchLimit,
+ type_: actionType,
+ other_person_id: userId ?? undefined,
+ mod_person_id: !this.isoData.site_res.site_view.local_site
+ .hide_modlog_mod_names
+ ? modId ?? undefined
+ : undefined,
auth,
- };
+ }),
+ });
- WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
+ if (communityId) {
+ this.setState({ communityRes: { state: "loading" } });
+ this.setState({
+ communityRes: await HttpService.client.getCommunity({
+ id: communityId,
+ auth,
+ }),
+ });
}
}
query: { modId: urlModId, page, userId: urlUserId, actionType },
auth,
site,
- }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<any>[] {
+ }: InitialFetchRequest<QueryParams<ModlogProps>>): Promise<
+ RequestState<any>
+ >[] {
const pathSplit = path.split("/");
- const promises: Promise<any>[] = [];
+ const promises: Promise<RequestState<any>>[] = [];
const communityId = getIdFromString(pathSplit[2]);
const modId = !site.site_view.local_site.hide_modlog_mod_names
? getIdFromString(urlModId)
};
promises.push(client.getCommunity(communityForm));
} else {
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
}
if (modId) {
promises.push(client.getPersonDetails(getPersonForm));
} else {
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
}
if (userId) {
promises.push(client.getPersonDetails(getPersonForm));
} else {
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
}
return promises;
}
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
-
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- } else {
- switch (op) {
- case UserOperation.GetModlog: {
- const res = wsJsonToRes<GetModlogResponse>(msg);
- window.scrollTo(0, 0);
- this.setState({ res, loadingModlog: false });
-
- break;
- }
-
- case UserOperation.GetCommunity: {
- const {
- moderators,
- community_view: {
- community: { name },
- },
- } = wsJsonToRes<GetCommunityResponse>(msg);
- this.setState({
- communityMods: moderators,
- communityName: name,
- });
-
- break;
- }
- }
- }
- }
}
import { Component, linkEvent } from "inferno";
import {
- BlockPersonResponse,
+ AddAdmin,
+ AddModToCommunity,
+ BanFromCommunity,
+ BanFromCommunityResponse,
+ BanPerson,
+ BanPersonResponse,
+ BlockPerson,
+ CommentId,
CommentReplyResponse,
CommentReplyView,
CommentReportResponse,
CommentResponse,
CommentSortType,
CommentView,
+ CreateComment,
+ CreateCommentLike,
+ CreateCommentReport,
+ CreatePrivateMessage,
+ CreatePrivateMessageReport,
+ DeleteComment,
+ DeletePrivateMessage,
+ DistinguishComment,
+ EditComment,
+ EditPrivateMessage,
GetPersonMentions,
GetPersonMentionsResponse,
GetPrivateMessages,
GetReplies,
GetRepliesResponse,
GetSiteResponse,
+ MarkCommentReplyAsRead,
+ MarkPersonMentionAsRead,
+ MarkPrivateMessageAsRead,
PersonMentionResponse,
PersonMentionView,
- PostReportResponse,
PrivateMessageReportResponse,
PrivateMessageResponse,
PrivateMessageView,
PrivateMessagesResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ PurgeComment,
+ PurgeItemResponse,
+ PurgePerson,
+ PurgePost,
+ RemoveComment,
+ SaveComment,
+ TransferCommunity,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
commentsToFlatNodes,
- createCommentLikeRes,
- editCommentRes,
+ editCommentReply,
+ editMention,
+ editPrivateMessage,
+ editWith,
enableDownvotes,
fetchLimit,
- isBrowser,
+ getCommentParentId,
myAuth,
+ myAuthRequired,
relTags,
- saveCommentRes,
setIsoData,
- setupTippy,
toast,
updatePersonBlock,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes";
import { CommentSortSelect } from "../common/comment-sort-select";
interface InboxState {
unreadOrAll: UnreadOrAll;
messageType: MessageType;
- replies: CommentReplyView[];
- mentions: PersonMentionView[];
- messages: PrivateMessageView[];
- combined: ReplyType[];
+ repliesRes: RequestState<GetRepliesResponse>;
+ mentionsRes: RequestState<GetPersonMentionsResponse>;
+ messagesRes: RequestState<PrivateMessagesResponse>;
+ markAllAsReadRes: RequestState<GetRepliesResponse>;
sort: CommentSortType;
page: number;
siteRes: GetSiteResponse;
- loading: boolean;
+ finished: Map<CommentId, boolean | undefined>;
+ isIsomorphic: boolean;
}
export class Inbox extends Component<any, InboxState> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: InboxState = {
unreadOrAll: UnreadOrAll.Unread,
messageType: MessageType.All,
- replies: [],
- mentions: [],
- messages: [],
- combined: [],
sort: "New",
page: 1,
siteRes: this.isoData.site_res,
- loading: true,
+ repliesRes: { state: "empty" },
+ mentionsRes: { state: "empty" },
+ messagesRes: { state: "empty" },
+ markAllAsReadRes: { state: "empty" },
+ finished: new Map(),
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
this.handleSortChange = this.handleSortChange.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleCreateComment = this.handleCreateComment.bind(this);
+ this.handleEditComment = this.handleEditComment.bind(this);
+ this.handleSaveComment = this.handleSaveComment.bind(this);
+ this.handleBlockPerson = this.handleBlockPerson.bind(this);
+ this.handleDeleteComment = this.handleDeleteComment.bind(this);
+ this.handleRemoveComment = this.handleRemoveComment.bind(this);
+ this.handleCommentVote = this.handleCommentVote.bind(this);
+ this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
+ this.handleAddAdmin = this.handleAddAdmin.bind(this);
+ this.handlePurgePerson = this.handlePurgePerson.bind(this);
+ this.handlePurgeComment = this.handlePurgeComment.bind(this);
+ this.handleCommentReport = this.handleCommentReport.bind(this);
+ this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
+ this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
+ this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
+ this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
+ this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
+ this.handleBanPerson = this.handleBanPerson.bind(this);
+
+ this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
+ this.handleMarkMessageAsRead = this.handleMarkMessageAsRead.bind(this);
+ this.handleMessageReport = this.handleMessageReport.bind(this);
+ this.handleCreateMessage = this.handleCreateMessage.bind(this);
+ this.handleEditMessage = this.handleEditMessage.bind(this);
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
+ const [repliesRes, mentionsRes, messagesRes] = this.isoData.routeData;
+
this.state = {
...this.state,
- replies:
- (this.isoData.routeData[0] as GetRepliesResponse).replies || [],
- mentions:
- (this.isoData.routeData[1] as GetPersonMentionsResponse).mentions ||
- [],
- messages:
- (this.isoData.routeData[2] as PrivateMessagesResponse)
- .private_messages || [],
- loading: false,
+ repliesRes,
+ mentionsRes,
+ messagesRes,
+ isIsomorphic: true,
};
- this.state = { ...this.state, combined: this.buildCombined() };
- } else {
- this.refetch();
}
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.refetch();
}
}
: "";
}
+ get hasUnreads(): boolean {
+ if (this.state.unreadOrAll == UnreadOrAll.Unread) {
+ const { repliesRes, mentionsRes, messagesRes } = this.state;
+ const replyCount =
+ repliesRes.state == "success" ? repliesRes.data.replies.length : 0;
+ const mentionCount =
+ mentionsRes.state == "success" ? mentionsRes.data.mentions.length : 0;
+ const messageCount =
+ messagesRes.state == "success"
+ ? messagesRes.data.private_messages.length
+ : 0;
+
+ return replyCount + mentionCount + messageCount > 0;
+ } else {
+ return false;
+ }
+ }
+
render() {
const auth = myAuth();
const inboxRss = auth ? `/feeds/inbox/${auth}.xml` : undefined;
return (
<div className="container-lg">
- {this.state.loading ? (
- <h5>
- <Spinner large />
- </h5>
- ) : (
- <div className="row">
- <div className="col-12">
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- />
- <h5 className="mb-2">
- {i18n.t("inbox")}
- {inboxRss && (
- <small>
- <a href={inboxRss} title="RSS" rel={relTags}>
- <Icon icon="rss" classes="ml-2 text-muted small" />
- </a>
- <link
- rel="alternate"
- type="application/atom+xml"
- href={inboxRss}
- />
- </small>
- )}
- </h5>
- {this.state.replies.length +
- this.state.mentions.length +
- this.state.messages.length >
- 0 &&
- this.state.unreadOrAll == UnreadOrAll.Unread && (
- <button
- className="btn btn-secondary mb-2"
- onClick={linkEvent(this, this.markAllAsRead)}
- >
- {i18n.t("mark_all_as_read")}
- </button>
+ <div className="row">
+ <div className="col-12">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
+ <h5 className="mb-2">
+ {i18n.t("inbox")}
+ {inboxRss && (
+ <small>
+ <a href={inboxRss} title="RSS" rel={relTags}>
+ <Icon icon="rss" classes="ml-2 text-muted small" />
+ </a>
+ <link
+ rel="alternate"
+ type="application/atom+xml"
+ href={inboxRss}
+ />
+ </small>
+ )}
+ </h5>
+ {this.hasUnreads && (
+ <button
+ className="btn btn-secondary mb-2"
+ onClick={linkEvent(this, this.handleMarkAllAsRead)}
+ >
+ {this.state.markAllAsReadRes.state == "loading" ? (
+ <Spinner />
+ ) : (
+ i18n.t("mark_all_as_read")
)}
- {this.selects()}
- {this.state.messageType == MessageType.All && this.all()}
- {this.state.messageType == MessageType.Replies && this.replies()}
- {this.state.messageType == MessageType.Mentions &&
- this.mentions()}
- {this.state.messageType == MessageType.Messages &&
- this.messages()}
- <Paginator
- page={this.state.page}
- onChange={this.handlePageChange}
- />
- </div>
+ </button>
+ )}
+ {this.selects()}
+ {this.section}
+ <Paginator
+ page={this.state.page}
+ onChange={this.handlePageChange}
+ />
</div>
- )}
+ </div>
</div>
);
}
+ get section() {
+ switch (this.state.messageType) {
+ case MessageType.All: {
+ return this.all();
+ }
+ case MessageType.Replies: {
+ return this.replies();
+ }
+ case MessageType.Mentions: {
+ return this.mentions();
+ }
+ case MessageType.Messages: {
+ return this.messages();
+ }
+ default: {
+ return null;
+ }
+ }
+ }
+
unreadOrAllRadios() {
return (
<div className="btn-group btn-group-toggle flex-wrap mb-2">
}
buildCombined(): ReplyType[] {
- const replies: ReplyType[] = this.state.replies.map(r =>
- this.replyToReplyType(r)
- );
- const mentions: ReplyType[] = this.state.mentions.map(r =>
- this.mentionToReplyType(r)
- );
- const messages: ReplyType[] = this.state.messages.map(r =>
- this.messageToReplyType(r)
- );
+ const replies: ReplyType[] =
+ this.state.repliesRes.state == "success"
+ ? this.state.repliesRes.data.replies.map(this.replyToReplyType)
+ : [];
+ const mentions: ReplyType[] =
+ this.state.mentionsRes.state == "success"
+ ? this.state.mentionsRes.data.mentions.map(this.mentionToReplyType)
+ : [];
+ const messages: ReplyType[] =
+ this.state.messagesRes.state == "success"
+ ? this.state.messagesRes.data.private_messages.map(
+ this.messageToReplyType
+ )
+ : [];
return [...replies, ...mentions, ...messages].sort((a, b) =>
b.published.localeCompare(a.published)
{ comment_view: i.view as CommentView, children: [], depth: 0 },
]}
viewType={CommentViewType.Flat}
+ finished={this.state.finished}
noIndent
markable
showCommunity
enableDownvotes={enableDownvotes(this.state.siteRes)}
allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages}
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPerson}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
/>
);
case ReplyEnum.Mention:
depth: 0,
},
]}
+ finished={this.state.finished}
viewType={CommentViewType.Flat}
noIndent
markable
enableDownvotes={enableDownvotes(this.state.siteRes)}
allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages}
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPerson}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
/>
);
case ReplyEnum.Message:
<PrivateMessage
key={i.id}
private_message_view={i.view as PrivateMessageView}
+ onDelete={this.handleDeleteMessage}
+ onMarkRead={this.handleMarkMessageAsRead}
+ onReport={this.handleMessageReport}
+ onCreate={this.handleCreateMessage}
+ onEdit={this.handleEditMessage}
/>
);
default:
}
all() {
- return <div>{this.state.combined.map(i => this.renderReplyType(i))}</div>;
+ if (
+ this.state.repliesRes.state == "loading" ||
+ this.state.mentionsRes.state == "loading" ||
+ this.state.messagesRes.state == "loading"
+ ) {
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ } else {
+ return (
+ <div>{this.buildCombined().map(r => this.renderReplyType(r))}</div>
+ );
+ }
}
replies() {
- return (
- <div>
- <CommentNodes
- nodes={commentsToFlatNodes(this.state.replies)}
- viewType={CommentViewType.Flat}
- noIndent
- markable
- showCommunity
- showContext
- enableDownvotes={enableDownvotes(this.state.siteRes)}
- allLanguages={this.state.siteRes.all_languages}
- siteLanguages={this.state.siteRes.discussion_languages}
- />
- </div>
- );
+ switch (this.state.repliesRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const replies = this.state.repliesRes.data.replies;
+ return (
+ <div>
+ <CommentNodes
+ nodes={commentsToFlatNodes(replies)}
+ viewType={CommentViewType.Flat}
+ finished={this.state.finished}
+ noIndent
+ markable
+ showCommunity
+ showContext
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ allLanguages={this.state.siteRes.all_languages}
+ siteLanguages={this.state.siteRes.discussion_languages}
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPerson}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
+ />
+ </div>
+ );
+ }
+ }
}
mentions() {
- return (
- <div>
- {this.state.mentions.map(umv => (
- <CommentNodes
- key={umv.person_mention.id}
- nodes={[{ comment_view: umv, children: [], depth: 0 }]}
- viewType={CommentViewType.Flat}
- noIndent
- markable
- showCommunity
- showContext
- enableDownvotes={enableDownvotes(this.state.siteRes)}
- allLanguages={this.state.siteRes.all_languages}
- siteLanguages={this.state.siteRes.discussion_languages}
- />
- ))}
- </div>
- );
+ switch (this.state.mentionsRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const mentions = this.state.mentionsRes.data.mentions;
+ return (
+ <div>
+ {mentions.map(umv => (
+ <CommentNodes
+ key={umv.person_mention.id}
+ nodes={[{ comment_view: umv, children: [], depth: 0 }]}
+ viewType={CommentViewType.Flat}
+ finished={this.state.finished}
+ noIndent
+ markable
+ showCommunity
+ showContext
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ allLanguages={this.state.siteRes.all_languages}
+ siteLanguages={this.state.siteRes.discussion_languages}
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPerson}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
+ />
+ ))}
+ </div>
+ );
+ }
+ }
}
messages() {
- return (
- <div>
- {this.state.messages.map(pmv => (
- <PrivateMessage
- key={pmv.private_message.id}
- private_message_view={pmv}
- />
- ))}
- </div>
- );
+ switch (this.state.messagesRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const messages = this.state.messagesRes.data.private_messages;
+ return (
+ <div>
+ {messages.map(pmv => (
+ <PrivateMessage
+ key={pmv.private_message.id}
+ private_message_view={pmv}
+ onDelete={this.handleDeleteMessage}
+ onMarkRead={this.handleMarkMessageAsRead}
+ onReport={this.handleMessageReport}
+ onCreate={this.handleCreateMessage}
+ onEdit={this.handleEditMessage}
+ />
+ ))}
+ </div>
+ );
+ }
+ }
}
- handlePageChange(page: number) {
+ async handlePageChange(page: number) {
this.setState({ page });
- this.refetch();
+ await this.refetch();
}
- handleUnreadOrAllChange(i: Inbox, event: any) {
+ async handleUnreadOrAllChange(i: Inbox, event: any) {
i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
- i.refetch();
+ await i.refetch();
}
- handleMessageTypeChange(i: Inbox, event: any) {
+ async handleMessageTypeChange(i: Inbox, event: any) {
i.setState({ messageType: Number(event.target.value), page: 1 });
- i.refetch();
+ await i.refetch();
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<any>[] = [];
+ static fetchInitialData({
+ client,
+ auth,
+ }: InitialFetchRequest): Promise<any>[] {
+ const promises: Promise<RequestState<any>>[] = [];
const sort: CommentSortType = "New";
- const auth = req.auth;
if (auth) {
// It can be /u/me, or /username/1
const repliesForm: GetReplies = {
- sort: "New",
+ sort,
unread_only: true,
page: 1,
limit: fetchLimit,
auth,
};
- promises.push(req.client.getReplies(repliesForm));
+ promises.push(client.getReplies(repliesForm));
const personMentionsForm: GetPersonMentions = {
sort,
limit: fetchLimit,
auth,
};
- promises.push(req.client.getPersonMentions(personMentionsForm));
+ promises.push(client.getPersonMentions(personMentionsForm));
const privateMessagesForm: GetPrivateMessages = {
unread_only: true,
limit: fetchLimit,
auth,
};
- promises.push(req.client.getPrivateMessages(privateMessagesForm));
+ promises.push(client.getPrivateMessages(privateMessagesForm));
+ } else {
+ promises.push(
+ Promise.resolve({ state: "empty" }),
+ Promise.resolve({ state: "empty" }),
+ Promise.resolve({ state: "empty" })
+ );
}
return promises;
}
- refetch() {
+ async refetch() {
const sort = this.state.sort;
const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
const page = this.state.page;
const limit = fetchLimit;
- const auth = myAuth();
+ const auth = myAuthRequired();
- if (auth) {
- const repliesForm: GetReplies = {
+ this.setState({ repliesRes: { state: "loading" } });
+ this.setState({
+ repliesRes: await HttpService.client.getReplies({
sort,
unread_only,
page,
limit,
auth,
- };
- WebSocketService.Instance.send(wsClient.getReplies(repliesForm));
+ }),
+ });
- const personMentionsForm: GetPersonMentions = {
+ this.setState({ mentionsRes: { state: "loading" } });
+ this.setState({
+ mentionsRes: await HttpService.client.getPersonMentions({
sort,
unread_only,
page,
limit,
auth,
- };
- WebSocketService.Instance.send(
- wsClient.getPersonMentions(personMentionsForm)
- );
+ }),
+ });
- const privateMessagesForm: GetPrivateMessages = {
+ this.setState({ messagesRes: { state: "loading" } });
+ this.setState({
+ messagesRes: await HttpService.client.getPrivateMessages({
unread_only,
page,
limit,
auth,
- };
- WebSocketService.Instance.send(
- wsClient.getPrivateMessages(privateMessagesForm)
- );
- }
+ }),
+ });
}
- handleSortChange(val: CommentSortType) {
+ async handleSortChange(val: CommentSortType) {
this.setState({ sort: val, page: 1 });
- this.refetch();
+ await this.refetch();
}
- markAllAsRead(i: Inbox) {
- const auth = myAuth();
- if (auth) {
- WebSocketService.Instance.send(
- wsClient.markAllAsRead({
- auth,
- })
- );
- i.setState({ replies: [], mentions: [], messages: [] });
- i.setState({ combined: i.buildCombined() });
- UserService.Instance.unreadInboxCountSub.next(0);
- window.scrollTo(0, 0);
- i.setState(i.state);
+ async handleMarkAllAsRead(i: Inbox) {
+ i.setState({ markAllAsReadRes: { state: "loading" } });
+
+ i.setState({
+ markAllAsReadRes: await HttpService.client.markAllAsRead({
+ auth: myAuthRequired(),
+ }),
+ });
+
+ if (i.state.markAllAsReadRes.state == "success") {
+ i.setState({
+ repliesRes: { state: "empty" },
+ mentionsRes: { state: "empty" },
+ messagesRes: { state: "empty" },
+ });
}
}
- sendUnreadCount(read: boolean) {
- const urcs = UserService.Instance.unreadInboxCountSub;
- if (read) {
- urcs.next(urcs.getValue() - 1);
- } else {
- urcs.next(urcs.getValue() + 1);
+ async handleAddModToCommunity(form: AddModToCommunity) {
+ // TODO not sure what to do here
+ HttpService.client.addModToCommunity(form);
+ }
+
+ async handlePurgePerson(form: PurgePerson) {
+ const purgePersonRes = await HttpService.client.purgePerson(form);
+ this.purgeItem(purgePersonRes);
+ }
+
+ async handlePurgeComment(form: PurgeComment) {
+ const purgeCommentRes = await HttpService.client.purgeComment(form);
+ this.purgeItem(purgeCommentRes);
+ }
+
+ async handlePurgePost(form: PurgePost) {
+ const purgeRes = await HttpService.client.purgePost(form);
+ this.purgeItem(purgeRes);
+ }
+
+ async handleBlockPerson(form: BlockPerson) {
+ const blockPersonRes = await HttpService.client.blockPerson(form);
+ if (blockPersonRes.state == "success") {
+ updatePersonBlock(blockPersonRes.data);
}
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- return;
- } else if (msg.reconnect) {
- this.refetch();
- } else if (op == UserOperation.GetReplies) {
- const data = wsJsonToRes<GetRepliesResponse>(msg);
- this.setState({ replies: data.replies });
- this.setState({ combined: this.buildCombined(), loading: false });
- window.scrollTo(0, 0);
- setupTippy();
- } else if (op == UserOperation.GetPersonMentions) {
- const data = wsJsonToRes<GetPersonMentionsResponse>(msg);
- this.setState({ mentions: data.mentions });
- this.setState({ combined: this.buildCombined() });
- window.scrollTo(0, 0);
- setupTippy();
- } else if (op == UserOperation.GetPrivateMessages) {
- const data = wsJsonToRes<PrivateMessagesResponse>(msg);
- this.setState({ messages: data.private_messages });
- this.setState({ combined: this.buildCombined() });
- window.scrollTo(0, 0);
- setupTippy();
- } else if (op == UserOperation.EditPrivateMessage) {
- const data = wsJsonToRes<PrivateMessageResponse>(msg);
- const found = this.state.messages.find(
- m =>
- m.private_message.id === data.private_message_view.private_message.id
- );
- if (found) {
- const combinedView = this.state.combined.find(
- i => i.id == data.private_message_view.private_message.id
- )?.view as PrivateMessageView | undefined;
- if (combinedView) {
- found.private_message.content = combinedView.private_message.content =
- data.private_message_view.private_message.content;
- found.private_message.updated = combinedView.private_message.updated =
- data.private_message_view.private_message.updated;
- }
+ async handleCreateComment(form: CreateComment) {
+ const res = await HttpService.client.createComment(form);
+
+ if (res.state === "success") {
+ toast(i18n.t("reply_sent"));
+ this.findAndUpdateComment(res);
+ }
+
+ return res;
+ }
+
+ async handleEditComment(form: EditComment) {
+ const res = await HttpService.client.editComment(form);
+
+ if (res.state === "success") {
+ toast(i18n.t("edit"));
+ this.findAndUpdateComment(res);
+ } else if (res.state === "failed") {
+ toast(res.msg, "danger");
+ }
+
+ return res;
+ }
+
+ async handleDeleteComment(form: DeleteComment) {
+ const res = await HttpService.client.deleteComment(form);
+ if (res.state == "success") {
+ toast(i18n.t("deleted"));
+ this.findAndUpdateComment(res);
+ }
+ }
+
+ async handleRemoveComment(form: RemoveComment) {
+ const res = await HttpService.client.removeComment(form);
+ if (res.state == "success") {
+ toast(i18n.t("remove_comment"));
+ this.findAndUpdateComment(res);
+ }
+ }
+
+ async handleSaveComment(form: SaveComment) {
+ const res = await HttpService.client.saveComment(form);
+ this.findAndUpdateComment(res);
+ }
+
+ async handleCommentVote(form: CreateCommentLike) {
+ const res = await HttpService.client.likeComment(form);
+ this.findAndUpdateComment(res);
+ }
+
+ async handleCommentReport(form: CreateCommentReport) {
+ const reportRes = await HttpService.client.createCommentReport(form);
+ this.reportToast(reportRes);
+ }
+
+ async handleDistinguishComment(form: DistinguishComment) {
+ const res = await HttpService.client.distinguishComment(form);
+ this.findAndUpdateComment(res);
+ }
+
+ async handleAddAdmin(form: AddAdmin) {
+ const addAdminRes = await HttpService.client.addAdmin(form);
+
+ if (addAdminRes.state === "success") {
+ this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
+ }
+ }
+
+ async handleTransferCommunity(form: TransferCommunity) {
+ await HttpService.client.transferCommunity(form);
+ toast(i18n.t("transfer_community"));
+ }
+
+ async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
+ const res = await HttpService.client.markCommentReplyAsRead(form);
+ this.findAndUpdateCommentReply(res);
+ }
+
+ async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
+ const res = await HttpService.client.markPersonMentionAsRead(form);
+ this.findAndUpdateMention(res);
+ }
+
+ async handleBanFromCommunity(form: BanFromCommunity) {
+ const banRes = await HttpService.client.banFromCommunity(form);
+ this.updateBanFromCommunity(banRes);
+ }
+
+ async handleBanPerson(form: BanPerson) {
+ const banRes = await HttpService.client.banPerson(form);
+ this.updateBan(banRes);
+ }
+
+ async handleDeleteMessage(form: DeletePrivateMessage) {
+ const res = await HttpService.client.deletePrivateMessage(form);
+ this.findAndUpdateMessage(res);
+ }
+
+ async handleEditMessage(form: EditPrivateMessage) {
+ const res = await HttpService.client.editPrivateMessage(form);
+ this.findAndUpdateMessage(res);
+ }
+
+ async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) {
+ const res = await HttpService.client.markPrivateMessageAsRead(form);
+ this.findAndUpdateMessage(res);
+ }
+
+ async handleMessageReport(form: CreatePrivateMessageReport) {
+ const res = await HttpService.client.createPrivateMessageReport(form);
+ this.reportToast(res);
+ }
+
+ async handleCreateMessage(form: CreatePrivateMessage) {
+ const res = await HttpService.client.createPrivateMessage(form);
+ this.setState(s => {
+ if (s.messagesRes.state == "success" && res.state == "success") {
+ s.messagesRes.data.private_messages.unshift(
+ res.data.private_message_view
+ );
}
- this.setState(this.state);
- } else if (op == UserOperation.DeletePrivateMessage) {
- const data = wsJsonToRes<PrivateMessageResponse>(msg);
- const found = this.state.messages.find(
- m =>
- m.private_message.id === data.private_message_view.private_message.id
- );
- if (found) {
- const combinedView = this.state.combined.find(
- i => i.id == data.private_message_view.private_message.id
- )?.view as PrivateMessageView | undefined;
- if (combinedView) {
- found.private_message.deleted = combinedView.private_message.deleted =
- data.private_message_view.private_message.deleted;
- found.private_message.updated = combinedView.private_message.updated =
- data.private_message_view.private_message.updated;
- }
+
+ return s;
+ });
+ }
+
+ findAndUpdateMessage(res: RequestState<PrivateMessageResponse>) {
+ this.setState(s => {
+ if (s.messagesRes.state === "success" && res.state === "success") {
+ s.messagesRes.data.private_messages = editPrivateMessage(
+ res.data.private_message_view,
+ s.messagesRes.data.private_messages
+ );
}
- this.setState(this.state);
- } else if (op == UserOperation.MarkPrivateMessageAsRead) {
- const data = wsJsonToRes<PrivateMessageResponse>(msg);
- const found = this.state.messages.find(
- m =>
- m.private_message.id === data.private_message_view.private_message.id
- );
+ return s;
+ });
+ }
- if (found) {
- const combinedView = this.state.combined.find(
- i =>
- i.id == data.private_message_view.private_message.id &&
- i.type_ == ReplyEnum.Message
- )?.view as PrivateMessageView | undefined;
- if (combinedView) {
- found.private_message.updated = combinedView.private_message.updated =
- data.private_message_view.private_message.updated;
-
- // If youre in the unread view, just remove it from the list
- if (
- this.state.unreadOrAll == UnreadOrAll.Unread &&
- data.private_message_view.private_message.read
- ) {
- this.setState({
- messages: this.state.messages.filter(
- r =>
- r.private_message.id !==
- data.private_message_view.private_message.id
- ),
- });
- this.setState({
- combined: this.state.combined.filter(
- r => r.id !== data.private_message_view.private_message.id
- ),
- });
- } else {
- found.private_message.read = combinedView.private_message.read =
- data.private_message_view.private_message.read;
- }
+ updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (s.repliesRes.state == "success") {
+ s.repliesRes.data.replies
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
}
- }
- this.sendUnreadCount(data.private_message_view.private_message.read);
- this.setState(this.state);
- } else if (op == UserOperation.MarkAllAsRead) {
- // Moved to be instant
- } else if (
- op == UserOperation.EditComment ||
- op == UserOperation.DeleteComment ||
- op == UserOperation.RemoveComment
- ) {
- const data = wsJsonToRes<CommentResponse>(msg);
- editCommentRes(data.comment_view, this.state.replies);
- this.setState(this.state);
- } else if (op == UserOperation.MarkCommentReplyAsRead) {
- const data = wsJsonToRes<CommentReplyResponse>(msg);
-
- const found = this.state.replies.find(
- c => c.comment_reply.id == data.comment_reply_view.comment_reply.id
- );
+ if (s.mentionsRes.state == "success") {
+ s.mentionsRes.data.mentions
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
+ }
+ return s;
+ });
+ }
+ }
- if (found) {
- const combinedView = this.state.combined.find(
- i =>
- i.id == data.comment_reply_view.comment_reply.id &&
- i.type_ == ReplyEnum.Reply
- )?.view as CommentReplyView | undefined;
- if (combinedView) {
- found.comment.content = combinedView.comment.content =
- data.comment_reply_view.comment.content;
- found.comment.updated = combinedView.comment.updated =
- data.comment_reply_view.comment.updated;
- found.comment.removed = combinedView.comment.removed =
- data.comment_reply_view.comment.removed;
- found.comment.deleted = combinedView.comment.deleted =
- data.comment_reply_view.comment.deleted;
- found.counts.upvotes = combinedView.counts.upvotes =
- data.comment_reply_view.counts.upvotes;
- found.counts.downvotes = combinedView.counts.downvotes =
- data.comment_reply_view.counts.downvotes;
- found.counts.score = combinedView.counts.score =
- data.comment_reply_view.counts.score;
-
- // If youre in the unread view, just remove it from the list
- if (
- this.state.unreadOrAll == UnreadOrAll.Unread &&
- data.comment_reply_view.comment_reply.read
- ) {
- this.setState({
- replies: this.state.replies.filter(
- r =>
- r.comment_reply.id !==
- data.comment_reply_view.comment_reply.id
- ),
- });
- this.setState({
- combined: this.state.combined.filter(
- r => r.id !== data.comment_reply_view.comment_reply.id
- ),
- });
- } else {
- found.comment_reply.read = combinedView.comment_reply.read =
- data.comment_reply_view.comment_reply.read;
- }
+ updateBan(banRes: RequestState<BanPersonResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (s.repliesRes.state == "success") {
+ s.repliesRes.data.replies
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
}
- }
- this.sendUnreadCount(data.comment_reply_view.comment_reply.read);
- this.setState(this.state);
- } else if (op == UserOperation.MarkPersonMentionAsRead) {
- const data = wsJsonToRes<PersonMentionResponse>(msg);
-
- // TODO this might not be correct, it might need to use the comment id
- const found = this.state.mentions.find(
- c => c.person_mention.id == data.person_mention_view.person_mention.id
- );
+ if (s.mentionsRes.state == "success") {
+ s.mentionsRes.data.mentions
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
+ }
+ return s;
+ });
+ }
+ }
+
+ purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
+ if (purgeRes.state == "success") {
+ toast(i18n.t("purge_success"));
+ this.context.router.history.push(`/`);
+ }
+ }
+
+ reportToast(
+ res: RequestState<PrivateMessageReportResponse | CommentReportResponse>
+ ) {
+ if (res.state == "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
- if (found) {
- const combinedView = this.state.combined.find(
- i =>
- i.id == data.person_mention_view.person_mention.id &&
- i.type_ == ReplyEnum.Mention
- )?.view as PersonMentionView | undefined;
- if (combinedView) {
- found.comment.content = combinedView.comment.content =
- data.person_mention_view.comment.content;
- found.comment.updated = combinedView.comment.updated =
- data.person_mention_view.comment.updated;
- found.comment.removed = combinedView.comment.removed =
- data.person_mention_view.comment.removed;
- found.comment.deleted = combinedView.comment.deleted =
- data.person_mention_view.comment.deleted;
- found.counts.upvotes = combinedView.counts.upvotes =
- data.person_mention_view.counts.upvotes;
- found.counts.downvotes = combinedView.counts.downvotes =
- data.person_mention_view.counts.downvotes;
- found.counts.score = combinedView.counts.score =
- data.person_mention_view.counts.score;
-
- // If youre in the unread view, just remove it from the list
- if (
- this.state.unreadOrAll == UnreadOrAll.Unread &&
- data.person_mention_view.person_mention.read
- ) {
- this.setState({
- mentions: this.state.mentions.filter(
- r =>
- r.person_mention.id !==
- data.person_mention_view.person_mention.id
- ),
- });
- this.setState({
- combined: this.state.combined.filter(
- r => r.id !== data.person_mention_view.person_mention.id
- ),
- });
- } else {
- // TODO test to make sure these mentions are getting marked as read
- found.person_mention.read = combinedView.person_mention.read =
- data.person_mention_view.person_mention.read;
- }
+ // A weird case, since you have only replies and mentions, not comment responses
+ findAndUpdateComment(res: RequestState<CommentResponse>) {
+ if (res.state == "success") {
+ this.setState(s => {
+ if (s.repliesRes.state == "success") {
+ s.repliesRes.data.replies = editWith(
+ res.data.comment_view,
+ s.repliesRes.data.replies
+ );
}
- }
- this.sendUnreadCount(data.person_mention_view.person_mention.read);
- this.setState(this.state);
- } else if (op == UserOperation.CreatePrivateMessage) {
- const data = wsJsonToRes<PrivateMessageResponse>(msg);
- const mui = UserService.Instance.myUserInfo;
- if (
- data.private_message_view.recipient.id == mui?.local_user_view.person.id
- ) {
- this.state.messages.unshift(data.private_message_view);
- this.state.combined.unshift(
- this.messageToReplyType(data.private_message_view)
+ if (s.mentionsRes.state == "success") {
+ s.mentionsRes.data.mentions = editWith(
+ res.data.comment_view,
+ s.mentionsRes.data.mentions
+ );
+ }
+ // Set finished for the parent
+ s.finished.set(
+ getCommentParentId(res.data.comment_view.comment) ?? 0,
+ true
);
- this.setState(this.state);
- }
- } else if (op == UserOperation.SaveComment) {
- const data = wsJsonToRes<CommentResponse>(msg);
- saveCommentRes(data.comment_view, this.state.replies);
- this.setState(this.state);
- setupTippy();
- } else if (op == UserOperation.CreateCommentLike) {
- const data = wsJsonToRes<CommentResponse>(msg);
- createCommentLikeRes(data.comment_view, this.state.replies);
- this.setState(this.state);
- } else if (op == UserOperation.BlockPerson) {
- const data = wsJsonToRes<BlockPersonResponse>(msg);
- updatePersonBlock(data);
- } else if (op == UserOperation.CreatePostReport) {
- const data = wsJsonToRes<PostReportResponse>(msg);
- if (data) {
- toast(i18n.t("report_created"));
- }
- } else if (op == UserOperation.CreateCommentReport) {
- const data = wsJsonToRes<CommentReportResponse>(msg);
- if (data) {
- toast(i18n.t("report_created"));
- }
- } else if (op == UserOperation.CreatePrivateMessageReport) {
- const data = wsJsonToRes<PrivateMessageReportResponse>(msg);
- if (data) {
- toast(i18n.t("report_created"));
- }
+ return s;
+ });
}
}
- isMention(view: any): view is PersonMentionView {
- return (view as PersonMentionView).person_mention !== undefined;
+ findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
+ this.setState(s => {
+ if (s.repliesRes.state == "success" && res.state == "success") {
+ s.repliesRes.data.replies = editCommentReply(
+ res.data.comment_reply_view,
+ s.repliesRes.data.replies
+ );
+ }
+ return s;
+ });
}
- isReply(view: any): view is CommentReplyView {
- return (view as CommentReplyView).comment_reply !== undefined;
+ findAndUpdateMention(res: RequestState<PersonMentionResponse>) {
+ this.setState(s => {
+ if (s.mentionsRes.state == "success" && res.state == "success") {
+ s.mentionsRes.data.mentions = editMention(
+ res.data.person_mention_view,
+ s.mentionsRes.data.mentions
+ );
+ }
+ return s;
+ });
}
}
import { Component, linkEvent } from "inferno";
-import {
- GetSiteResponse,
- LoginResponse,
- PasswordChangeAfterReset,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
-} from "lemmy-js-client";
-import { Subscription } from "rxjs";
+import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
-import {
- capitalizeFirstLetter,
- isBrowser,
- setIsoData,
- toast,
- wsClient,
- wsSubscribe,
-} from "../../utils";
+import { HttpService, UserService } from "../../services";
+import { RequestState } from "../../services/HttpService";
+import { capitalizeFirstLetter, myAuth, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
interface State {
+ passwordChangeRes: RequestState<LoginResponse>;
form: {
token: string;
password?: string;
password_verify?: string;
};
- loading: boolean;
siteRes: GetSiteResponse;
}
export class PasswordChange extends Component<any, State> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: State = {
+ passwordChangeRes: { state: "empty" },
+ siteRes: this.isoData.site_res,
form: {
token: this.props.match.params.token,
},
- loading: false,
- siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
super(props, context);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
- }
-
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
- }
}
get documentTitle(): string {
<div className="form-group row">
<div className="col-sm-10">
<button type="submit" className="btn btn-secondary">
- {this.state.loading ? (
+ {this.state.passwordChangeRes.state == "loading" ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("save"))
i.setState(i.state);
}
- handlePasswordChangeSubmit(i: PasswordChange, event: any) {
+ async handlePasswordChangeSubmit(i: PasswordChange, event: any) {
event.preventDefault();
- i.setState({ loading: true });
+ i.setState({ passwordChangeRes: { state: "loading" } });
const password = i.state.form.password;
const password_verify = i.state.form.password_verify;
if (password && password_verify) {
- const form: PasswordChangeAfterReset = {
- token: i.state.form.token,
- password,
- password_verify,
- };
-
- WebSocketService.Instance.send(wsClient.passwordChange(form));
- }
- }
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.setState({ loading: false });
- return;
- } else if (op == UserOperation.PasswordChangeAfterReset) {
- const data = wsJsonToRes<LoginResponse>(msg);
- UserService.Instance.login(data);
- this.props.history.push("/");
- location.reload();
+ i.setState({
+ passwordChangeRes: await HttpService.client.passwordChangeAfterReset({
+ token: i.state.form.token,
+ password,
+ password_verify,
+ }),
+ });
+
+ if (i.state.passwordChangeRes.state === "success") {
+ const data = i.state.passwordChangeRes.data;
+ UserService.Instance.login(data);
+
+ const site = await HttpService.client.getSite({ auth: myAuth() });
+ if (site.state === "success") {
+ UserService.Instance.myUserInfo = site.data.my_user;
+ }
+
+ this.props.history.replace("/");
+ }
}
}
}
import { Component } from "inferno";
import {
+ AddAdmin,
+ AddModToCommunity,
+ BanFromCommunity,
+ BanPerson,
+ BlockPerson,
+ CommentId,
CommentView,
+ CreateComment,
+ CreateCommentLike,
+ CreateCommentReport,
+ CreatePostLike,
+ CreatePostReport,
+ DeleteComment,
+ DeletePost,
+ DistinguishComment,
+ EditComment,
+ EditPost,
+ FeaturePost,
+ GetComments,
GetPersonDetailsResponse,
Language,
+ LockPost,
+ MarkCommentReplyAsRead,
+ MarkPersonMentionAsRead,
PersonView,
PostView,
+ PurgeComment,
+ PurgePerson,
+ PurgePost,
+ RemoveComment,
+ RemovePost,
+ SaveComment,
+ SavePost,
SortType,
+ TransferCommunity,
} from "lemmy-js-client";
import { CommentViewType, PersonDetailsView } from "../../interfaces";
import { commentsToFlatNodes, setupTippy } from "../../utils";
interface PersonDetailsProps {
personRes: GetPersonDetailsResponse;
+ finished: Map<CommentId, boolean | undefined>;
admins: PersonView[];
allLanguages: Language[];
siteLanguages: number[];
enableNsfw: boolean;
view: PersonDetailsView;
onPageChange(page: number): number | any;
+ onSaveComment(form: SaveComment): void;
+ onCommentReplyRead(form: MarkCommentReplyAsRead): void;
+ onPersonMentionRead(form: MarkPersonMentionAsRead): void;
+ onCreateComment(form: CreateComment): void;
+ onEditComment(form: EditComment): void;
+ onCommentVote(form: CreateCommentLike): void;
+ onBlockPerson(form: BlockPerson): void;
+ onDeleteComment(form: DeleteComment): void;
+ onRemoveComment(form: RemoveComment): void;
+ onDistinguishComment(form: DistinguishComment): void;
+ onAddModToCommunity(form: AddModToCommunity): void;
+ onAddAdmin(form: AddAdmin): void;
+ onBanPersonFromCommunity(form: BanFromCommunity): void;
+ onBanPerson(form: BanPerson): void;
+ onTransferCommunity(form: TransferCommunity): void;
+ onFetchChildren?(form: GetComments): void;
+ onCommentReport(form: CreateCommentReport): void;
+ onPurgePerson(form: PurgePerson): void;
+ onPurgeComment(form: PurgeComment): void;
+ onPostEdit(form: EditPost): void;
+ onPostVote(form: CreatePostLike): void;
+ onPostReport(form: CreatePostReport): void;
+ onLockPost(form: LockPost): void;
+ onDeletePost(form: DeletePost): void;
+ onRemovePost(form: RemovePost): void;
+ onSavePost(form: SavePost): void;
+ onFeaturePost(form: FeaturePost): void;
+ onPurgePost(form: PurgePost): void;
}
enum ItemEnum {
key={i.id}
nodes={[{ comment_view: c, children: [], depth: 0 }]}
viewType={CommentViewType.Flat}
+ finished={this.props.finished}
admins={this.props.admins}
noBorder
noIndent
enableDownvotes={this.props.enableDownvotes}
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
+ onCommentReplyRead={this.props.onCommentReplyRead}
+ onPersonMentionRead={this.props.onPersonMentionRead}
+ onCreateComment={this.props.onCreateComment}
+ onEditComment={this.props.onEditComment}
+ onCommentVote={this.props.onCommentVote}
+ onBlockPerson={this.props.onBlockPerson}
+ onSaveComment={this.props.onSaveComment}
+ onDeleteComment={this.props.onDeleteComment}
+ onRemoveComment={this.props.onRemoveComment}
+ onDistinguishComment={this.props.onDistinguishComment}
+ onAddModToCommunity={this.props.onAddModToCommunity}
+ onAddAdmin={this.props.onAddAdmin}
+ onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
+ onBanPerson={this.props.onBanPerson}
+ onTransferCommunity={this.props.onTransferCommunity}
+ onFetchChildren={this.props.onFetchChildren}
+ onCommentReport={this.props.onCommentReport}
+ onPurgePerson={this.props.onPurgePerson}
+ onPurgeComment={this.props.onPurgeComment}
/>
);
}
enableNsfw={this.props.enableNsfw}
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
+ onPostEdit={this.props.onPostEdit}
+ onPostVote={this.props.onPostVote}
+ onPostReport={this.props.onPostReport}
+ onBlockPerson={this.props.onBlockPerson}
+ onLockPost={this.props.onLockPost}
+ onDeletePost={this.props.onDeletePost}
+ onRemovePost={this.props.onRemovePost}
+ onSavePost={this.props.onSavePost}
+ onFeaturePost={this.props.onFeaturePost}
+ onPurgePerson={this.props.onPurgePerson}
+ onPurgePost={this.props.onPurgePost}
+ onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
+ onBanPerson={this.props.onBanPerson}
+ onAddModToCommunity={this.props.onAddModToCommunity}
+ onAddAdmin={this.props.onAddAdmin}
+ onTransferCommunity={this.props.onTransferCommunity}
/>
);
}
nodes={commentsToFlatNodes(this.props.personRes.comments)}
viewType={CommentViewType.Flat}
admins={this.props.admins}
+ finished={this.props.finished}
noIndent
showCommunity
showContext
enableDownvotes={this.props.enableDownvotes}
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
+ onCommentReplyRead={this.props.onCommentReplyRead}
+ onPersonMentionRead={this.props.onPersonMentionRead}
+ onCreateComment={this.props.onCreateComment}
+ onEditComment={this.props.onEditComment}
+ onCommentVote={this.props.onCommentVote}
+ onBlockPerson={this.props.onBlockPerson}
+ onSaveComment={this.props.onSaveComment}
+ onDeleteComment={this.props.onDeleteComment}
+ onRemoveComment={this.props.onRemoveComment}
+ onDistinguishComment={this.props.onDistinguishComment}
+ onAddModToCommunity={this.props.onAddModToCommunity}
+ onAddAdmin={this.props.onAddAdmin}
+ onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
+ onBanPerson={this.props.onBanPerson}
+ onTransferCommunity={this.props.onTransferCommunity}
+ onFetchChildren={this.props.onFetchChildren}
+ onCommentReport={this.props.onCommentReport}
+ onPurgePerson={this.props.onPurgePerson}
+ onPurgeComment={this.props.onPurgeComment}
/>
</div>
);
enableNsfw={this.props.enableNsfw}
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
+ onPostEdit={this.props.onPostEdit}
+ onPostVote={this.props.onPostVote}
+ onPostReport={this.props.onPostReport}
+ onBlockPerson={this.props.onBlockPerson}
+ onLockPost={this.props.onLockPost}
+ onDeletePost={this.props.onDeletePost}
+ onRemovePost={this.props.onRemovePost}
+ onSavePost={this.props.onSavePost}
+ onFeaturePost={this.props.onFeaturePost}
+ onPurgePerson={this.props.onPurgePerson}
+ onPurgePost={this.props.onPurgePost}
+ onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
+ onBanPerson={this.props.onBanPerson}
+ onAddModToCommunity={this.props.onAddModToCommunity}
+ onAddAdmin={this.props.onAddAdmin}
+ onTransferCommunity={this.props.onTransferCommunity}
/>
<hr className="my-3" />
</>
import { Link } from "inferno-router";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
- AddAdminResponse,
+ AddAdmin,
+ AddModToCommunity,
+ BanFromCommunity,
+ BanFromCommunityResponse,
BanPerson,
BanPersonResponse,
BlockPerson,
- BlockPersonResponse,
+ CommentId,
+ CommentReplyResponse,
CommentResponse,
Community,
CommunityModeratorView,
+ CreateComment,
+ CreateCommentLike,
+ CreateCommentReport,
+ CreatePostLike,
+ CreatePostReport,
+ DeleteComment,
+ DeletePost,
+ DistinguishComment,
+ EditComment,
+ EditPost,
+ FeaturePost,
GetPersonDetails,
GetPersonDetailsResponse,
GetSiteResponse,
+ LockPost,
+ MarkCommentReplyAsRead,
+ MarkPersonMentionAsRead,
+ PersonView,
PostResponse,
+ PurgeComment,
PurgeItemResponse,
+ PurgePerson,
+ PurgePost,
+ RemoveComment,
+ RemovePost,
+ SaveComment,
+ SavePost,
SortType,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ TransferCommunity,
} from "lemmy-js-client";
import moment from "moment";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
canMod,
capitalizeFirstLetter,
- createCommentLikeRes,
- createPostLikeFindRes,
- editCommentRes,
- editPostFindRes,
+ editComment,
+ editPost,
+ editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
futureDaysToUnixTime,
+ getCommentParentId,
getPageFromString,
getQueryParams,
getQueryString,
isBanned,
mdToHtml,
myAuth,
+ myAuthRequired,
numToSI,
relTags,
restoreScrollPosition,
- saveCommentRes,
saveScrollPosition,
setIsoData,
setupTippy,
toast,
updatePersonBlock,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { BannerIconHeader } from "../common/banner-icon-header";
import { HtmlTags } from "../common/html-tags";
import { PersonListing } from "./person-listing";
interface ProfileState {
- personRes?: GetPersonDetailsResponse;
- loading: boolean;
+ personRes: RequestState<GetPersonDetailsResponse>;
personBlocked: boolean;
banReason?: string;
banExpireDays?: number;
showBanDialog: boolean;
removeData: boolean;
siteRes: GetSiteResponse;
+ finished: Map<CommentId, boolean | undefined>;
+ isIsomorphic: boolean;
}
interface ProfileProps {
: PersonDetailsView.Overview;
}
-function toggleBlockPerson(recipientId: number, block: boolean) {
- const auth = myAuth();
-
- if (auth) {
- const blockUserForm: BlockPerson = {
- person_id: recipientId,
- block,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
- }
-}
-
-const handleUnblockPerson = (personId: number) =>
- toggleBlockPerson(personId, false);
-
-const handleBlockPerson = (personId: number) =>
- toggleBlockPerson(personId, true);
-
const getCommunitiesListing = (
translationKey: NoOptionI18nKeys,
communityViews?: { community: Community }[]
ProfileState
> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: ProfileState = {
- loading: true,
+ personRes: { state: "empty" },
personBlocked: false,
siteRes: this.isoData.site_res,
showBanDialog: false,
removeData: false,
+ finished: new Map(),
+ isIsomorphic: false,
};
constructor(props: RouteComponentProps<{ username: string }>, context: any) {
this.handleSortChange = this.handleSortChange.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleBlockPerson = this.handleBlockPerson.bind(this);
+ this.handleUnblockPerson = this.handleUnblockPerson.bind(this);
+
+ this.handleCreateComment = this.handleCreateComment.bind(this);
+ this.handleEditComment = this.handleEditComment.bind(this);
+ this.handleSaveComment = this.handleSaveComment.bind(this);
+ this.handleBlockPersonAlt = this.handleBlockPersonAlt.bind(this);
+ this.handleDeleteComment = this.handleDeleteComment.bind(this);
+ this.handleRemoveComment = this.handleRemoveComment.bind(this);
+ this.handleCommentVote = this.handleCommentVote.bind(this);
+ this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
+ this.handleAddAdmin = this.handleAddAdmin.bind(this);
+ this.handlePurgePerson = this.handlePurgePerson.bind(this);
+ this.handlePurgeComment = this.handlePurgeComment.bind(this);
+ this.handleCommentReport = this.handleCommentReport.bind(this);
+ this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
+ this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
+ this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
+ this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
+ this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
+ this.handleBanPerson = this.handleBanPerson.bind(this);
+ this.handlePostVote = this.handlePostVote.bind(this);
+ this.handlePostEdit = this.handlePostEdit.bind(this);
+ this.handlePostReport = this.handlePostReport.bind(this);
+ this.handleLockPost = this.handleLockPost.bind(this);
+ this.handleDeletePost = this.handleDeletePost.bind(this);
+ this.handleRemovePost = this.handleRemovePost.bind(this);
+ this.handleSavePost = this.handleSavePost.bind(this);
+ this.handlePurgePost = this.handlePurgePost.bind(this);
+ this.handleFeaturePost = this.handleFeaturePost.bind(this);
// Only fetch the data if coming from another route
- if (this.isoData.path === this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- personRes: this.isoData.routeData[0] as GetPersonDetailsResponse,
- loading: false,
+ personRes: this.isoData.routeData[0],
+ isIsomorphic: true,
};
- } else {
- this.fetchUserData();
}
}
- fetchUserData() {
- const { page, sort, view } = getProfileQueryParams();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.fetchUserData();
+ }
+ setupTippy();
+ }
- const form: GetPersonDetails = {
- username: this.props.match.params.username,
- sort,
- saved_only: view === PersonDetailsView.Saved,
- page,
- limit: fetchLimit,
- auth: myAuth(false),
- };
+ componentWillUnmount() {
+ saveScrollPosition(this.context);
+ }
+
+ async fetchUserData() {
+ const { page, sort, view } = getProfileQueryParams();
- WebSocketService.Instance.send(wsClient.getPersonDetails(form));
+ this.setState({ personRes: { state: "empty" } });
+ this.setState({
+ personRes: await HttpService.client.getPersonDetails({
+ username: this.props.match.params.username,
+ sort,
+ saved_only: view === PersonDetailsView.Saved,
+ page,
+ limit: fetchLimit,
+ auth: myAuth(),
+ }),
+ });
+ restoreScrollPosition(this.context);
+ this.setPersonBlock();
}
get amCurrentUser() {
- return (
- UserService.Instance.myUserInfo?.local_user_view.person.id ===
- this.state.personRes?.person_view.person.id
- );
+ if (this.state.personRes.state === "success") {
+ return (
+ UserService.Instance.myUserInfo?.local_user_view.person.id ===
+ this.state.personRes.data.person_view.person.id
+ );
+ } else {
+ return false;
+ }
}
setPersonBlock() {
const mui = UserService.Instance.myUserInfo;
const res = this.state.personRes;
- if (mui && res) {
+ if (mui && res.state === "success") {
this.setState({
personBlocked: mui.person_blocks.some(
- ({ target: { id } }) => id === res.person_view.person.id
+ ({ target: { id } }) => id === res.data.person_view.person.id
),
});
}
path,
query: { page, sort, view: urlView },
auth,
- }: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<any>[] {
+ }: InitialFetchRequest<QueryParams<ProfileProps>>): Promise<
+ RequestState<any>
+ >[] {
const pathSplit = path.split("/");
const username = pathSplit[2];
return [client.getPersonDetails(form)];
}
- componentDidMount() {
- this.setPersonBlock();
- setupTippy();
- }
-
- componentWillUnmount() {
- this.subscription?.unsubscribe();
- saveScrollPosition(this.context);
- }
-
get documentTitle(): string {
+ const siteName = this.state.siteRes.site_view.site.name;
const res = this.state.personRes;
- return res
- ? `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`
- : "";
+ return res.state == "success"
+ ? `@${res.data.person_view.person.name} - ${siteName}`
+ : siteName;
}
- render() {
- const { personRes, loading, siteRes } = this.state;
- const { page, sort, view } = getProfileQueryParams();
-
- return (
- <div className="container-lg">
- {loading ? (
+ renderPersonRes() {
+ switch (this.state.personRes.state) {
+ case "loading":
+ return (
<h5>
<Spinner large />
</h5>
- ) : (
- personRes && (
- <div className="row">
- <div className="col-12 col-md-8">
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- description={personRes.person_view.person.bio}
- image={personRes.person_view.person.avatar}
- />
-
- {this.userInfo}
-
- <hr />
-
- {this.selects}
-
- <PersonDetails
- personRes={personRes}
- admins={siteRes.admins}
- sort={sort}
- page={page}
- limit={fetchLimit}
- enableDownvotes={enableDownvotes(siteRes)}
- enableNsfw={enableNsfw(siteRes)}
- view={view}
- onPageChange={this.handlePageChange}
- allLanguages={siteRes.all_languages}
- siteLanguages={siteRes.discussion_languages}
- />
- </div>
+ );
+ case "success": {
+ const siteRes = this.state.siteRes;
+ const personRes = this.state.personRes.data;
+ const { page, sort, view } = getProfileQueryParams();
+
+ return (
+ <div className="row">
+ <div className="col-12 col-md-8">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ description={personRes.person_view.person.bio}
+ image={personRes.person_view.person.avatar}
+ />
+
+ {this.userInfo(personRes.person_view)}
+
+ <hr />
+
+ {this.selects}
+
+ <PersonDetails
+ personRes={personRes}
+ admins={siteRes.admins}
+ sort={sort}
+ page={page}
+ limit={fetchLimit}
+ finished={this.state.finished}
+ enableDownvotes={enableDownvotes(siteRes)}
+ enableNsfw={enableNsfw(siteRes)}
+ view={view}
+ onPageChange={this.handlePageChange}
+ allLanguages={siteRes.all_languages}
+ siteLanguages={siteRes.discussion_languages}
+ // TODO all the forms here
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPersonAlt}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
+ onPostEdit={this.handlePostEdit}
+ onPostVote={this.handlePostVote}
+ onPostReport={this.handlePostReport}
+ onLockPost={this.handleLockPost}
+ onDeletePost={this.handleDeletePost}
+ onRemovePost={this.handleRemovePost}
+ onSavePost={this.handleSavePost}
+ onPurgePost={this.handlePurgePost}
+ onFeaturePost={this.handleFeaturePost}
+ />
+ </div>
- <div className="col-12 col-md-4">
- <Moderates moderates={personRes.moderates} />
- {this.amCurrentUser && <Follows />}
- </div>
+ <div className="col-12 col-md-4">
+ <Moderates moderates={personRes.moderates} />
+ {this.amCurrentUser && <Follows />}
</div>
- )
- )}
- </div>
- );
+ </div>
+ );
+ }
+ }
+ }
+
+ render() {
+ return <div className="container-lg">{this.renderPersonRes()}</div>;
}
get viewRadios() {
);
}
- get userInfo() {
- const pv = this.state.personRes?.person_view;
+ userInfo(pv: PersonView) {
const {
personBlocked,
siteRes: { admins },
)}
</ul>
</div>
- {this.banDialog}
+ {this.banDialog(pv)}
<div className="flex-grow-1 unselectable pointer mx-2"></div>
{!this.amCurrentUser && UserService.Instance.myUserInfo && (
<>
className={
"d-flex align-self-start btn btn-secondary mr-2"
}
- onClick={linkEvent(pv.person.id, handleUnblockPerson)}
+ onClick={linkEvent(
+ pv.person.id,
+ this.handleUnblockPerson
+ )}
>
{i18n.t("unblock_user")}
</button>
className={
"d-flex align-self-start btn btn-secondary mr-2"
}
- onClick={linkEvent(pv.person.id, handleBlockPerson)}
+ onClick={linkEvent(
+ pv.person.id,
+ this.handleBlockPerson
+ )}
>
{i18n.t("block_user")}
</button>
);
}
- get banDialog() {
- const pv = this.state.personRes?.person_view;
+ banDialog(pv: PersonView) {
const { showBanDialog } = this.state;
return (
- pv && (
- <>
- {showBanDialog && (
- <form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
- <div className="form-group row col-12">
- <label className="col-form-label" htmlFor="profile-ban-reason">
- {i18n.t("reason")}
- </label>
- <input
- type="text"
- id="profile-ban-reason"
- className="form-control mr-2"
- placeholder={i18n.t("reason")}
- value={this.state.banReason}
- onInput={linkEvent(this, this.handleModBanReasonChange)}
- />
- <label className="col-form-label" htmlFor={`mod-ban-expires`}>
- {i18n.t("expires")}
- </label>
+ showBanDialog && (
+ <form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
+ <div className="form-group row col-12">
+ <label className="col-form-label" htmlFor="profile-ban-reason">
+ {i18n.t("reason")}
+ </label>
+ <input
+ type="text"
+ id="profile-ban-reason"
+ className="form-control mr-2"
+ placeholder={i18n.t("reason")}
+ value={this.state.banReason}
+ onInput={linkEvent(this, this.handleModBanReasonChange)}
+ />
+ <label className="col-form-label" htmlFor={`mod-ban-expires`}>
+ {i18n.t("expires")}
+ </label>
+ <input
+ type="number"
+ id={`mod-ban-expires`}
+ className="form-control mr-2"
+ placeholder={i18n.t("number_of_days")}
+ value={this.state.banExpireDays}
+ onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
+ />
+ <div className="form-group">
+ <div className="form-check">
<input
- type="number"
- id={`mod-ban-expires`}
- className="form-control mr-2"
- placeholder={i18n.t("number_of_days")}
- value={this.state.banExpireDays}
- onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
+ className="form-check-input"
+ id="mod-ban-remove-data"
+ type="checkbox"
+ checked={this.state.removeData}
+ onChange={linkEvent(this, this.handleModRemoveDataChange)}
/>
- <div className="form-group">
- <div className="form-check">
- <input
- className="form-check-input"
- id="mod-ban-remove-data"
- type="checkbox"
- checked={this.state.removeData}
- onChange={linkEvent(this, this.handleModRemoveDataChange)}
- />
- <label
- className="form-check-label"
- htmlFor="mod-ban-remove-data"
- title={i18n.t("remove_content_more")}
- >
- {i18n.t("remove_content")}
- </label>
- </div>
- </div>
- </div>
- {/* TODO hold off on expires until later */}
- {/* <div class="form-group row"> */}
- {/* <label class="col-form-label">Expires</label> */}
- {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
- {/* </div> */}
- <div className="form-group row">
- <button
- type="reset"
- className="btn btn-secondary mr-2"
- aria-label={i18n.t("cancel")}
- onClick={linkEvent(this, this.handleModBanSubmitCancel)}
- >
- {i18n.t("cancel")}
- </button>
- <button
- type="submit"
- className="btn btn-secondary"
- aria-label={i18n.t("ban")}
+ <label
+ className="form-check-label"
+ htmlFor="mod-ban-remove-data"
+ title={i18n.t("remove_content_more")}
>
- {i18n.t("ban")} {pv.person.name}
- </button>
+ {i18n.t("remove_content")}
+ </label>
</div>
- </form>
- )}
- </>
+ </div>
+ </div>
+ {/* TODO hold off on expires until later */}
+ {/* <div class="form-group row"> */}
+ {/* <label class="col-form-label">Expires</label> */}
+ {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
+ {/* </div> */}
+ <div className="form-group row">
+ <button
+ type="reset"
+ className="btn btn-secondary mr-2"
+ aria-label={i18n.t("cancel")}
+ onClick={linkEvent(this, this.handleModBanSubmitCancel)}
+ >
+ {i18n.t("cancel")}
+ </button>
+ <button
+ type="submit"
+ className="btn btn-secondary"
+ aria-label={i18n.t("ban")}
+ >
+ {i18n.t("ban")} {pv.person.name}
+ </button>
+ </div>
+ </form>
)
);
}
- updateUrl({ page, sort, view }: Partial<ProfileProps>) {
+ async updateUrl({ page, sort, view }: Partial<ProfileProps>) {
const {
page: urlPage,
sort: urlSort,
const { username } = this.props.match.params;
this.props.history.push(`/u/${username}${getQueryString(queryParams)}`);
-
- this.setState({ loading: true });
- this.fetchUserData();
+ await this.fetchUserData();
}
handlePageChange(page: number) {
i.setState({ removeData: event.target.checked });
}
- handleModBanSubmitCancel(i: Profile, event?: any) {
- event.preventDefault();
+ handleModBanSubmitCancel(i: Profile) {
i.setState({ showBanDialog: false });
}
- handleModBanSubmit(i: Profile, event?: any) {
- if (event) event.preventDefault();
- const { personRes, removeData, banReason, banExpireDays } = i.state;
+ async handleModBanSubmit(i: Profile, event: any) {
+ event.preventDefault();
+ const { removeData, banReason, banExpireDays } = i.state;
- const person = personRes?.person_view.person;
- const auth = myAuth();
+ const personRes = i.state.personRes;
- if (person && auth) {
+ if (personRes.state == "success") {
+ const person = personRes.data.person_view.person;
const ban = !person.banned;
// If its an unban, restore all their data
i.setState({ removeData: false });
}
- const form: BanPerson = {
+ const res = await HttpService.client.banPerson({
person_id: person.id,
ban,
remove_data: removeData,
reason: banReason,
expires: futureDaysToUnixTime(banExpireDays),
- auth,
- };
- WebSocketService.Instance.send(wsClient.banPerson(form));
-
+ auth: myAuthRequired(),
+ });
+ // TODO
+ this.updateBan(res);
i.setState({ showBanDialog: false });
}
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
+ async toggleBlockPerson(recipientId: number, block: boolean) {
+ const res = await HttpService.client.blockPerson({
+ person_id: recipientId,
+ block,
+ auth: myAuthRequired(),
+ });
+ if (res.state == "success") {
+ updatePersonBlock(res.data);
+ }
+ }
+
+ handleUnblockPerson(personId: number) {
+ this.toggleBlockPerson(personId, false);
+ }
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
+ handleBlockPerson(personId: number) {
+ this.toggleBlockPerson(personId, true);
+ }
- if (msg.error === "couldnt_find_that_username_or_email") {
- this.context.router.history.push("/");
- }
- } else if (msg.reconnect) {
- this.fetchUserData();
- } else {
- switch (op) {
- case UserOperation.GetPersonDetails: {
- // Since the PersonDetails contains posts/comments as well as some general user info we listen here as well
- // and set the parent state if it is not set or differs
- // TODO this might need to get abstracted
- const data = wsJsonToRes<GetPersonDetailsResponse>(msg);
- this.setState({ personRes: data, loading: false });
- this.setPersonBlock();
- restoreScrollPosition(this.context);
-
- break;
- }
+ async handleAddModToCommunity(form: AddModToCommunity) {
+ // TODO not sure what to do here
+ await HttpService.client.addModToCommunity(form);
+ }
- case UserOperation.AddAdmin: {
- const { admins } = wsJsonToRes<AddAdminResponse>(msg);
- this.setState(s => ((s.siteRes.admins = admins), s));
+ async handlePurgePerson(form: PurgePerson) {
+ const purgePersonRes = await HttpService.client.purgePerson(form);
+ this.purgeItem(purgePersonRes);
+ }
- break;
- }
+ async handlePurgeComment(form: PurgeComment) {
+ const purgeCommentRes = await HttpService.client.purgeComment(form);
+ this.purgeItem(purgeCommentRes);
+ }
- case UserOperation.CreateCommentLike: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
- createCommentLikeRes(comment_view, this.state.personRes?.comments);
- this.setState(this.state);
+ async handlePurgePost(form: PurgePost) {
+ const purgeRes = await HttpService.client.purgePost(form);
+ this.purgeItem(purgeRes);
+ }
- break;
- }
+ async handleBlockPersonAlt(form: BlockPerson) {
+ const blockPersonRes = await HttpService.client.blockPerson(form);
+ if (blockPersonRes.state === "success") {
+ updatePersonBlock(blockPersonRes.data);
+ }
+ }
- case UserOperation.EditComment:
- case UserOperation.DeleteComment:
- case UserOperation.RemoveComment: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
- editCommentRes(comment_view, this.state.personRes?.comments);
- this.setState(this.state);
+ async handleCreateComment(form: CreateComment) {
+ const createCommentRes = await HttpService.client.createComment(form);
+ this.createAndUpdateComments(createCommentRes);
- break;
- }
+ return createCommentRes;
+ }
- case UserOperation.CreateComment: {
- const {
- comment_view: {
- creator: { id },
- },
- } = wsJsonToRes<CommentResponse>(msg);
- const mui = UserService.Instance.myUserInfo;
+ async handleEditComment(form: EditComment) {
+ const editCommentRes = await HttpService.client.editComment(form);
+ this.findAndUpdateComment(editCommentRes);
- if (id === mui?.local_user_view.person.id) {
- toast(i18n.t("reply_sent"));
- }
+ return editCommentRes;
+ }
- break;
- }
+ async handleDeleteComment(form: DeleteComment) {
+ const deleteCommentRes = await HttpService.client.deleteComment(form);
+ this.findAndUpdateComment(deleteCommentRes);
+ }
- case UserOperation.SaveComment: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
- saveCommentRes(comment_view, this.state.personRes?.comments);
- this.setState(this.state);
+ async handleDeletePost(form: DeletePost) {
+ const deleteRes = await HttpService.client.deletePost(form);
+ this.findAndUpdatePost(deleteRes);
+ }
- break;
- }
+ async handleRemovePost(form: RemovePost) {
+ const removeRes = await HttpService.client.removePost(form);
+ this.findAndUpdatePost(removeRes);
+ }
- case UserOperation.EditPost:
- case UserOperation.DeletePost:
- case UserOperation.RemovePost:
- case UserOperation.LockPost:
- case UserOperation.FeaturePost:
- case UserOperation.SavePost: {
- const { post_view } = wsJsonToRes<PostResponse>(msg);
- editPostFindRes(post_view, this.state.personRes?.posts);
- this.setState(this.state);
-
- break;
- }
+ async handleRemoveComment(form: RemoveComment) {
+ const removeCommentRes = await HttpService.client.removeComment(form);
+ this.findAndUpdateComment(removeCommentRes);
+ }
+
+ async handleSaveComment(form: SaveComment) {
+ const saveCommentRes = await HttpService.client.saveComment(form);
+ this.findAndUpdateComment(saveCommentRes);
+ }
+
+ async handleSavePost(form: SavePost) {
+ const saveRes = await HttpService.client.savePost(form);
+ this.findAndUpdatePost(saveRes);
+ }
+
+ async handleFeaturePost(form: FeaturePost) {
+ const featureRes = await HttpService.client.featurePost(form);
+ this.findAndUpdatePost(featureRes);
+ }
+
+ async handleCommentVote(form: CreateCommentLike) {
+ const voteRes = await HttpService.client.likeComment(form);
+ this.findAndUpdateComment(voteRes);
+ }
+
+ async handlePostVote(form: CreatePostLike) {
+ const voteRes = await HttpService.client.likePost(form);
+ this.findAndUpdatePost(voteRes);
+ }
+
+ async handlePostEdit(form: EditPost) {
+ const res = await HttpService.client.editPost(form);
+ this.findAndUpdatePost(res);
+ }
+
+ async handleCommentReport(form: CreateCommentReport) {
+ const reportRes = await HttpService.client.createCommentReport(form);
+ if (reportRes.state === "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
- case UserOperation.CreatePostLike: {
- const { post_view } = wsJsonToRes<PostResponse>(msg);
- createPostLikeFindRes(post_view, this.state.personRes?.posts);
- this.setState(this.state);
+ async handlePostReport(form: CreatePostReport) {
+ const reportRes = await HttpService.client.createPostReport(form);
+ if (reportRes.state === "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
+
+ async handleLockPost(form: LockPost) {
+ const lockRes = await HttpService.client.lockPost(form);
+ this.findAndUpdatePost(lockRes);
+ }
+
+ async handleDistinguishComment(form: DistinguishComment) {
+ const distinguishRes = await HttpService.client.distinguishComment(form);
+ this.findAndUpdateComment(distinguishRes);
+ }
- break;
+ async handleAddAdmin(form: AddAdmin) {
+ const addAdminRes = await HttpService.client.addAdmin(form);
+
+ if (addAdminRes.state == "success") {
+ this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
+ }
+ }
+
+ async handleTransferCommunity(form: TransferCommunity) {
+ await HttpService.client.transferCommunity(form);
+ toast(i18n.t("transfer_community"));
+ }
+
+ async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
+ const readRes = await HttpService.client.markCommentReplyAsRead(form);
+ this.findAndUpdateCommentReply(readRes);
+ }
+
+ async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
+ // TODO not sure what to do here. Maybe it is actually optional, because post doesn't need it.
+ await HttpService.client.markPersonMentionAsRead(form);
+ }
+
+ async handleBanFromCommunity(form: BanFromCommunity) {
+ const banRes = await HttpService.client.banFromCommunity(form);
+ this.updateBanFromCommunity(banRes);
+ }
+
+ async handleBanPerson(form: BanPerson) {
+ const banRes = await HttpService.client.banPerson(form);
+ this.updateBan(banRes);
+ }
+
+ updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
+ // Maybe not necessary
+ if (banRes.state === "success") {
+ this.setState(s => {
+ if (s.personRes.state == "success") {
+ s.personRes.data.posts
+ .filter(c => c.creator.id === banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
+
+ s.personRes.data.comments
+ .filter(c => c.creator.id === banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
}
+ return s;
+ });
+ }
+ }
- case UserOperation.BanPerson: {
- const data = wsJsonToRes<BanPersonResponse>(msg);
- const res = this.state.personRes;
- res?.comments
- .filter(c => c.creator.id === data.person_view.person.id)
- .forEach(c => (c.creator.banned = data.banned));
- res?.posts
- .filter(c => c.creator.id === data.person_view.person.id)
- .forEach(c => (c.creator.banned = data.banned));
- const pv = res?.person_view;
-
- if (pv?.person.id === data.person_view.person.id) {
- pv.person.banned = data.banned;
- }
- this.setState(this.state);
-
- break;
+ updateBan(banRes: RequestState<BanPersonResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (s.personRes.state == "success") {
+ s.personRes.data.posts
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
+ s.personRes.data.comments
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
}
+ return s;
+ });
+ }
+ }
- case UserOperation.BlockPerson: {
- const data = wsJsonToRes<BlockPersonResponse>(msg);
- updatePersonBlock(data);
- this.setPersonBlock();
+ purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
+ if (purgeRes.state == "success") {
+ toast(i18n.t("purge_success"));
+ this.context.router.history.push(`/`);
+ }
+ }
- break;
- }
+ findAndUpdateComment(res: RequestState<CommentResponse>) {
+ this.setState(s => {
+ if (s.personRes.state == "success" && res.state == "success") {
+ s.personRes.data.comments = editComment(
+ res.data.comment_view,
+ s.personRes.data.comments
+ );
+ s.finished.set(res.data.comment_view.comment.id, true);
+ }
+ return s;
+ });
+ }
- case UserOperation.PurgePerson:
- case UserOperation.PurgePost:
- case UserOperation.PurgeComment:
- case UserOperation.PurgeCommunity: {
- const { success } = wsJsonToRes<PurgeItemResponse>(msg);
+ createAndUpdateComments(res: RequestState<CommentResponse>) {
+ this.setState(s => {
+ if (s.personRes.state == "success" && res.state == "success") {
+ s.personRes.data.comments.unshift(res.data.comment_view);
+ // Set finished for the parent
+ s.finished.set(
+ getCommentParentId(res.data.comment_view.comment) ?? 0,
+ true
+ );
+ }
+ return s;
+ });
+ }
- if (success) {
- toast(i18n.t("purge_success"));
- this.context.router.history.push(`/`);
- }
- }
+ findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
+ this.setState(s => {
+ if (s.personRes.state == "success" && res.state == "success") {
+ s.personRes.data.comments = editWith(
+ res.data.comment_reply_view,
+ s.personRes.data.comments
+ );
}
- }
+ return s;
+ });
+ }
+
+ findAndUpdatePost(res: RequestState<PostResponse>) {
+ this.setState(s => {
+ if (s.personRes.state == "success" && res.state == "success") {
+ s.personRes.data.posts = editPost(
+ res.data.post_view,
+ s.personRes.data.posts
+ );
+ }
+ return s;
+ });
}
}
import { Component, linkEvent } from "inferno";
import {
+ ApproveRegistrationApplication,
GetSiteResponse,
ListRegistrationApplications,
ListRegistrationApplicationsResponse,
- RegistrationApplicationResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ RegistrationApplicationView,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
+ editRegistrationApplication,
fetchLimit,
- isBrowser,
- myAuth,
+ myAuthRequired,
setIsoData,
setupTippy,
- toast,
- updateRegistrationApplicationRes,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
}
interface RegistrationApplicationsState {
- listRegistrationApplicationsResponse?: ListRegistrationApplicationsResponse;
+ appsRes: RequestState<ListRegistrationApplicationsResponse>;
siteRes: GetSiteResponse;
unreadOrAll: UnreadOrAll;
page: number;
- loading: boolean;
+ isIsomorphic: boolean;
}
export class RegistrationApplications extends Component<
RegistrationApplicationsState
> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: RegistrationApplicationsState = {
+ appsRes: { state: "empty" },
siteRes: this.isoData.site_res,
unreadOrAll: UnreadOrAll.Unread,
page: 1,
- loading: true,
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
super(props, context);
this.handlePageChange = this.handlePageChange.bind(this);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleApproveApplication = this.handleApproveApplication.bind(this);
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- listRegistrationApplicationsResponse: this.isoData
- .routeData[0] as ListRegistrationApplicationsResponse,
- loading: false,
+ appsRes: this.isoData.routeData[0],
+ isIsomorphic: true,
};
- } else {
- this.refetch();
}
}
- componentDidMount() {
- setupTippy();
- }
-
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.refetch();
}
+ setupTippy();
}
get documentTitle(): string {
: "";
}
- render() {
- return (
- <div className="container-lg">
- {this.state.loading ? (
+ renderApps() {
+ switch (this.state.appsRes.state) {
+ case "loading":
+ return (
<h5>
<Spinner large />
</h5>
- ) : (
+ );
+ case "success": {
+ const apps = this.state.appsRes.data.registration_applications;
+ return (
<div className="row">
<div className="col-12">
<HtmlTags
/>
<h5 className="mb-2">{i18n.t("registration_applications")}</h5>
{this.selects()}
- {this.applicationList()}
+ {this.applicationList(apps)}
<Paginator
page={this.state.page}
onChange={this.handlePageChange}
/>
</div>
</div>
- )}
- </div>
- );
+ );
+ }
+ }
+ }
+
+ render() {
+ return <div className="container-lg">{this.renderApps()}</div>;
}
unreadOrAllRadios() {
);
}
- applicationList() {
- const res = this.state.listRegistrationApplicationsResponse;
+ applicationList(apps: RegistrationApplicationView[]) {
return (
- res && (
- <div>
- {res.registration_applications.map(ra => (
- <>
- <hr />
- <RegistrationApplication
- key={ra.registration_application.id}
- application={ra}
- />
- </>
- ))}
- </div>
- )
+ <div>
+ {apps.map(ra => (
+ <>
+ <hr />
+ <RegistrationApplication
+ key={ra.registration_application.id}
+ application={ra}
+ onApproveApplication={this.handleApproveApplication}
+ />
+ </>
+ ))}
+ </div>
);
}
this.refetch();
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<any>[] = [];
+ static fetchInitialData({
+ auth,
+ client,
+ }: InitialFetchRequest): Promise<any>[] {
+ const promises: Promise<RequestState<any>>[] = [];
- const auth = req.auth;
if (auth) {
const form: ListRegistrationApplications = {
unread_only: true,
limit: fetchLimit,
auth,
};
- promises.push(req.client.listRegistrationApplications(form));
+ promises.push(client.listRegistrationApplications(form));
+ } else {
+ promises.push(Promise.resolve({ state: "empty" }));
}
return promises;
}
- refetch() {
+ async refetch() {
const unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
- const auth = myAuth();
- if (auth) {
- const form: ListRegistrationApplications = {
+ this.setState({
+ appsRes: { state: "loading" },
+ });
+ this.setState({
+ appsRes: await HttpService.client.listRegistrationApplications({
unread_only: unread_only,
page: this.state.page,
limit: fetchLimit,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.listRegistrationApplications(form)
- );
- }
+ auth: myAuthRequired(),
+ }),
+ });
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- return;
- } else if (msg.reconnect) {
- this.refetch();
- } else if (op == UserOperation.ListRegistrationApplications) {
- const data = wsJsonToRes<ListRegistrationApplicationsResponse>(msg);
- this.setState({
- listRegistrationApplicationsResponse: data,
- loading: false,
- });
- window.scrollTo(0, 0);
- } else if (op == UserOperation.ApproveRegistrationApplication) {
- const data = wsJsonToRes<RegistrationApplicationResponse>(msg);
- updateRegistrationApplicationRes(
- data.registration_application,
- this.state.listRegistrationApplicationsResponse
- ?.registration_applications
- );
- const uacs = UserService.Instance.unreadApplicationCountSub;
- // Minor bug, where if the application switches from deny to approve, the count will still go down
- uacs.next(uacs.getValue() - 1);
- this.setState(this.state);
- }
+ async handleApproveApplication(form: ApproveRegistrationApplication) {
+ const approveRes = await HttpService.client.approveRegistrationApplication(
+ form
+ );
+ this.setState(s => {
+ if (s.appsRes.state == "success" && approveRes.state == "success") {
+ s.appsRes.data.registration_applications = editRegistrationApplication(
+ approveRes.data.registration_application,
+ s.appsRes.data.registration_applications
+ );
+ }
+ return s;
+ });
}
}
PostReportView,
PrivateMessageReportResponse,
PrivateMessageReportView,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ ResolveCommentReport,
+ ResolvePostReport,
+ ResolvePrivateMessageReport,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { HttpService, UserService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { RequestState } from "../../services/HttpService";
import {
amAdmin,
+ editCommentReport,
+ editPostReport,
+ editPrivateMessageReport,
fetchLimit,
- isBrowser,
- myAuth,
+ myAuthRequired,
setIsoData,
- setupTippy,
- toast,
- updateCommentReportRes,
- updatePostReportRes,
- updatePrivateMessageReportRes,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { CommentReport } from "../comment/comment-report";
import { HtmlTags } from "../common/html-tags";
};
interface ReportsState {
- listCommentReportsResponse?: ListCommentReportsResponse;
- listPostReportsResponse?: ListPostReportsResponse;
- listPrivateMessageReportsResponse?: ListPrivateMessageReportsResponse;
+ commentReportsRes: RequestState<ListCommentReportsResponse>;
+ postReportsRes: RequestState<ListPostReportsResponse>;
+ messageReportsRes: RequestState<ListPrivateMessageReportsResponse>;
unreadOrAll: UnreadOrAll;
messageType: MessageType;
- combined: ItemType[];
siteRes: GetSiteResponse;
page: number;
- loading: boolean;
+ isIsomorphic: boolean;
}
export class Reports extends Component<any, ReportsState> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: ReportsState = {
+ commentReportsRes: { state: "empty" },
+ postReportsRes: { state: "empty" },
+ messageReportsRes: { state: "empty" },
unreadOrAll: UnreadOrAll.Unread,
messageType: MessageType.All,
- combined: [],
page: 1,
siteRes: this.isoData.site_res,
- loading: true,
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
super(props, context);
this.handlePageChange = this.handlePageChange.bind(this);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleResolveCommentReport =
+ this.handleResolveCommentReport.bind(this);
+ this.handleResolvePostReport = this.handleResolvePostReport.bind(this);
+ this.handleResolvePrivateMessageReport =
+ this.handleResolvePrivateMessageReport.bind(this);
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
+ const [commentReportsRes, postReportsRes, messageReportsRes] =
+ this.isoData.routeData;
this.state = {
...this.state,
- listCommentReportsResponse: this.isoData
- .routeData[0] as ListCommentReportsResponse,
- listPostReportsResponse: this.isoData
- .routeData[1] as ListPostReportsResponse,
+ commentReportsRes,
+ postReportsRes,
+ isIsomorphic: true,
};
+
if (amAdmin()) {
this.state = {
...this.state,
- listPrivateMessageReportsResponse: this.isoData
- .routeData[2] as ListPrivateMessageReportsResponse,
+ messageReportsRes,
};
}
- this.state = {
- ...this.state,
- combined: this.buildCombined(),
- loading: false,
- };
- } else {
- this.refetch();
}
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.refetch();
}
}
render() {
return (
<div className="container-lg">
- {this.state.loading ? (
- <h5>
- <Spinner large />
- </h5>
- ) : (
- <div className="row">
- <div className="col-12">
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- />
- <h5 className="mb-2">{i18n.t("reports")}</h5>
- {this.selects()}
- {this.state.messageType == MessageType.All && this.all()}
- {this.state.messageType == MessageType.CommentReport &&
- this.commentReports()}
- {this.state.messageType == MessageType.PostReport &&
- this.postReports()}
- {this.state.messageType == MessageType.PrivateMessageReport &&
- this.privateMessageReports()}
- <Paginator
- page={this.state.page}
- onChange={this.handlePageChange}
- />
- </div>
+ <div className="row">
+ <div className="col-12">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
+ <h5 className="mb-2">{i18n.t("reports")}</h5>
+ {this.selects()}
+ {this.section}
+ <Paginator
+ page={this.state.page}
+ onChange={this.handlePageChange}
+ />
</div>
- )}
+ </div>
</div>
);
}
+ get section() {
+ switch (this.state.messageType) {
+ case MessageType.All: {
+ return this.all();
+ }
+ case MessageType.CommentReport: {
+ return this.commentReports();
+ }
+ case MessageType.PostReport: {
+ return this.postReports();
+ }
+ case MessageType.PrivateMessageReport: {
+ return this.privateMessageReports();
+ }
+
+ default: {
+ return null;
+ }
+ }
+ }
+
unreadOrAllRadios() {
return (
<div className="btn-group btn-group-toggle flex-wrap mb-2">
};
}
- buildCombined(): ItemType[] {
- // let comments: ItemType[] = this.state.listCommentReportsResponse
- // .map(r => r.comment_reports)
- // .unwrapOr([])
- // .map(r => this.commentReportToItemType(r));
+ get buildCombined(): ItemType[] {
+ const commentRes = this.state.commentReportsRes;
const comments =
- this.state.listCommentReportsResponse?.comment_reports.map(
- this.commentReportToItemType
- ) ?? [];
+ commentRes.state == "success"
+ ? commentRes.data.comment_reports.map(this.commentReportToItemType)
+ : [];
+
+ const postRes = this.state.postReportsRes;
const posts =
- this.state.listPostReportsResponse?.post_reports.map(
- this.postReportToItemType
- ) ?? [];
+ postRes.state == "success"
+ ? postRes.data.post_reports.map(this.postReportToItemType)
+ : [];
+ const pmRes = this.state.messageReportsRes;
const privateMessages =
- this.state.listPrivateMessageReportsResponse?.private_message_reports.map(
- this.privateMessageReportToItemType
- ) ?? [];
+ pmRes.state == "success"
+ ? pmRes.data.private_message_reports.map(
+ this.privateMessageReportToItemType
+ )
+ : [];
return [...comments, ...posts, ...privateMessages].sort((a, b) =>
b.published.localeCompare(a.published)
switch (i.type_) {
case MessageEnum.CommentReport:
return (
- <CommentReport key={i.id} report={i.view as CommentReportView} />
+ <CommentReport
+ key={i.id}
+ report={i.view as CommentReportView}
+ onResolveReport={this.handleResolveCommentReport}
+ />
);
case MessageEnum.PostReport:
- return <PostReport key={i.id} report={i.view as PostReportView} />;
+ return (
+ <PostReport
+ key={i.id}
+ report={i.view as PostReportView}
+ onResolveReport={this.handleResolvePostReport}
+ />
+ );
case MessageEnum.PrivateMessageReport:
return (
<PrivateMessageReport
key={i.id}
report={i.view as PrivateMessageReportView}
+ onResolveReport={this.handleResolvePrivateMessageReport}
/>
);
default:
all() {
return (
<div>
- {this.state.combined.map(i => (
+ {this.buildCombined.map(i => (
<>
<hr />
{this.renderItemType(i)}
}
commentReports() {
- const reports = this.state.listCommentReportsResponse?.comment_reports;
- return (
- reports && (
- <div>
- {reports.map(cr => (
- <>
- <hr />
- <CommentReport key={cr.comment_report.id} report={cr} />
- </>
- ))}
- </div>
- )
- );
+ const res = this.state.commentReportsRes;
+ switch (res.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const reports = res.data.comment_reports;
+ return (
+ <div>
+ {reports.map(cr => (
+ <>
+ <hr />
+ <CommentReport
+ key={cr.comment_report.id}
+ report={cr}
+ onResolveReport={this.handleResolveCommentReport}
+ />
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
}
postReports() {
- const reports = this.state.listPostReportsResponse?.post_reports;
- return (
- reports && (
- <div>
- {reports.map(pr => (
- <>
- <hr />
- <PostReport key={pr.post_report.id} report={pr} />
- </>
- ))}
- </div>
- )
- );
+ const res = this.state.postReportsRes;
+ switch (res.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const reports = res.data.post_reports;
+ return (
+ <div>
+ {reports.map(pr => (
+ <>
+ <hr />
+ <PostReport
+ key={pr.post_report.id}
+ report={pr}
+ onResolveReport={this.handleResolvePostReport}
+ />
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
}
privateMessageReports() {
- const reports =
- this.state.listPrivateMessageReportsResponse?.private_message_reports;
- return (
- reports && (
- <div>
- {reports.map(pmr => (
- <>
- <hr />
- <PrivateMessageReport
- key={pmr.private_message_report.id}
- report={pmr}
- />
- </>
- ))}
- </div>
- )
- );
+ const res = this.state.messageReportsRes;
+ switch (res.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const reports = res.data.private_message_reports;
+ return (
+ <div>
+ {reports.map(pmr => (
+ <>
+ <hr />
+ <PrivateMessageReport
+ key={pmr.private_message_report.id}
+ report={pmr}
+ onResolveReport={this.handleResolvePrivateMessageReport}
+ />
+ </>
+ ))}
+ </div>
+ );
+ }
+ }
}
- handlePageChange(page: number) {
+ async handlePageChange(page: number) {
this.setState({ page });
- this.refetch();
+ await this.refetch();
}
- handleUnreadOrAllChange(i: Reports, event: any) {
+ async handleUnreadOrAllChange(i: Reports, event: any) {
i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
- i.refetch();
+ await i.refetch();
}
- handleMessageTypeChange(i: Reports, event: any) {
+ async handleMessageTypeChange(i: Reports, event: any) {
i.setState({ messageType: Number(event.target.value), page: 1 });
- i.refetch();
+ await i.refetch();
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- const promises: Promise<any>[] = [];
+ static fetchInitialData({
+ auth,
+ client,
+ }: InitialFetchRequest): Promise<any>[] {
+ const promises: Promise<RequestState<any>>[] = [];
const unresolved_only = true;
const page = 1;
const limit = fetchLimit;
- const auth = req.auth;
if (auth) {
const commentReportsForm: ListCommentReports = {
limit,
auth,
};
- promises.push(req.client.listCommentReports(commentReportsForm));
+ promises.push(client.listCommentReports(commentReportsForm));
const postReportsForm: ListPostReports = {
unresolved_only,
limit,
auth,
};
- promises.push(req.client.listPostReports(postReportsForm));
+ promises.push(client.listPostReports(postReportsForm));
if (amAdmin()) {
const privateMessageReportsForm: ListPrivateMessageReports = {
auth,
};
promises.push(
- req.client.listPrivateMessageReports(privateMessageReportsForm)
+ client.listPrivateMessageReports(privateMessageReportsForm)
);
+ } else {
+ promises.push(Promise.resolve({ state: "empty" }));
}
+ } else {
+ promises.push(
+ Promise.resolve({ state: "empty" }),
+ Promise.resolve({ state: "empty" }),
+ Promise.resolve({ state: "empty" })
+ );
}
return promises;
}
- refetch() {
+ async refetch() {
const unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread;
const page = this.state.page;
const limit = fetchLimit;
- const auth = myAuth();
- if (auth) {
- const commentReportsForm: ListCommentReports = {
- unresolved_only,
- page,
- limit,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.listCommentReports(commentReportsForm)
- );
+ const auth = myAuthRequired();
+
+ this.setState({
+ commentReportsRes: { state: "loading" },
+ postReportsRes: { state: "loading" },
+ messageReportsRes: { state: "loading" },
+ });
+
+ const form:
+ | ListCommentReports
+ | ListPostReports
+ | ListPrivateMessageReports = {
+ unresolved_only,
+ page,
+ limit,
+ auth,
+ };
- const postReportsForm: ListPostReports = {
- unresolved_only,
- page,
- limit,
- auth,
- };
- WebSocketService.Instance.send(wsClient.listPostReports(postReportsForm));
+ this.setState({
+ commentReportsRes: await HttpService.client.listCommentReports(form),
+ postReportsRes: await HttpService.client.listPostReports(form),
+ });
+
+ if (amAdmin()) {
+ this.setState({
+ messageReportsRes: await HttpService.client.listPrivateMessageReports(
+ form
+ ),
+ });
+ }
+ }
- if (amAdmin()) {
- const privateMessageReportsForm: ListPrivateMessageReports = {
- unresolved_only,
- page,
- limit,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.listPrivateMessageReports(privateMessageReportsForm)
+ async handleResolveCommentReport(form: ResolveCommentReport) {
+ const res = await HttpService.client.resolveCommentReport(form);
+ this.findAndUpdateCommentReport(res);
+ }
+
+ async handleResolvePostReport(form: ResolvePostReport) {
+ const res = await HttpService.client.resolvePostReport(form);
+ this.findAndUpdatePostReport(res);
+ }
+
+ async handleResolvePrivateMessageReport(form: ResolvePrivateMessageReport) {
+ const res = await HttpService.client.resolvePrivateMessageReport(form);
+ this.findAndUpdatePrivateMessageReport(res);
+ }
+
+ findAndUpdateCommentReport(res: RequestState<CommentReportResponse>) {
+ this.setState(s => {
+ if (s.commentReportsRes.state == "success" && res.state == "success") {
+ s.commentReportsRes.data.comment_reports = editCommentReport(
+ res.data.comment_report_view,
+ s.commentReportsRes.data.comment_reports
);
}
- }
+ return s;
+ });
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- return;
- } else if (msg.reconnect) {
- this.refetch();
- } else if (op == UserOperation.ListCommentReports) {
- const data = wsJsonToRes<ListCommentReportsResponse>(msg);
- this.setState({ listCommentReportsResponse: data });
- this.setState({ combined: this.buildCombined(), loading: false });
- // this.sendUnreadCount();
- window.scrollTo(0, 0);
- setupTippy();
- } else if (op == UserOperation.ListPostReports) {
- const data = wsJsonToRes<ListPostReportsResponse>(msg);
- this.setState({ listPostReportsResponse: data });
- this.setState({ combined: this.buildCombined(), loading: false });
- // this.sendUnreadCount();
- window.scrollTo(0, 0);
- setupTippy();
- } else if (op == UserOperation.ListPrivateMessageReports) {
- const data = wsJsonToRes<ListPrivateMessageReportsResponse>(msg);
- this.setState({ listPrivateMessageReportsResponse: data });
- this.setState({ combined: this.buildCombined(), loading: false });
- // this.sendUnreadCount();
- window.scrollTo(0, 0);
- setupTippy();
- } else if (op == UserOperation.ResolvePostReport) {
- const data = wsJsonToRes<PostReportResponse>(msg);
- updatePostReportRes(
- data.post_report_view,
- this.state.listPostReportsResponse?.post_reports
- );
- const urcs = UserService.Instance.unreadReportCountSub;
- if (data.post_report_view.post_report.resolved) {
- urcs.next(urcs.getValue() - 1);
- } else {
- urcs.next(urcs.getValue() + 1);
- }
- this.setState(this.state);
- } else if (op == UserOperation.ResolveCommentReport) {
- const data = wsJsonToRes<CommentReportResponse>(msg);
- updateCommentReportRes(
- data.comment_report_view,
- this.state.listCommentReportsResponse?.comment_reports
- );
- const urcs = UserService.Instance.unreadReportCountSub;
- if (data.comment_report_view.comment_report.resolved) {
- urcs.next(urcs.getValue() - 1);
- } else {
- urcs.next(urcs.getValue() + 1);
+ findAndUpdatePostReport(res: RequestState<PostReportResponse>) {
+ this.setState(s => {
+ if (s.postReportsRes.state == "success" && res.state == "success") {
+ s.postReportsRes.data.post_reports = editPostReport(
+ res.data.post_report_view,
+ s.postReportsRes.data.post_reports
+ );
}
- this.setState(this.state);
- } else if (op == UserOperation.ResolvePrivateMessageReport) {
- const data = wsJsonToRes<PrivateMessageReportResponse>(msg);
- updatePrivateMessageReportRes(
- data.private_message_report_view,
- this.state.listPrivateMessageReportsResponse?.private_message_reports
- );
- const urcs = UserService.Instance.unreadReportCountSub;
- if (data.private_message_report_view.private_message_report.resolved) {
- urcs.next(urcs.getValue() - 1);
- } else {
- urcs.next(urcs.getValue() + 1);
+ return s;
+ });
+ }
+
+ findAndUpdatePrivateMessageReport(
+ res: RequestState<PrivateMessageReportResponse>
+ ) {
+ this.setState(s => {
+ if (s.messageReportsRes.state == "success" && res.state == "success") {
+ s.messageReportsRes.data.private_message_reports =
+ editPrivateMessageReport(
+ res.data.private_message_report_view,
+ s.messageReportsRes.data.private_message_reports
+ );
}
- this.setState(this.state);
- }
+ return s;
+ });
}
}
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import {
- BlockCommunity,
BlockCommunityResponse,
- BlockPerson,
BlockPersonResponse,
- ChangePassword,
CommunityBlockView,
- DeleteAccount,
+ DeleteAccountResponse,
GetSiteResponse,
ListingType,
LoginResponse,
PersonBlockView,
- SaveUserSettings,
SortType,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n, languages } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
Choice,
capitalizeFirstLetter,
fetchUsers,
getLanguages,
myAuth,
+ myAuthRequired,
personToChoice,
relTags,
setIsoData,
toast,
updateCommunityBlock,
updatePersonBlock,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "./person-listing";
interface SettingsState {
+ saveRes: RequestState<LoginResponse>;
+ changePasswordRes: RequestState<LoginResponse>;
+ deleteAccountRes: RequestState<DeleteAccountResponse>;
// TODO redo these forms
saveUserSettingsForm: {
show_nsfw?: boolean;
communityBlocks: CommunityBlockView[];
currentTab: string;
themeList: string[];
- saveUserSettingsLoading: boolean;
- changePasswordLoading: boolean;
- deleteAccountLoading: boolean;
deleteAccountShowConfirm: boolean;
siteRes: GetSiteResponse;
searchCommunityLoading: boolean;
export class Settings extends Component<any, SettingsState> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: SettingsState = {
+ saveRes: { state: "empty" },
+ deleteAccountRes: { state: "empty" },
+ changePasswordRes: { state: "empty" },
saveUserSettingsForm: {},
changePasswordForm: {},
- saveUserSettingsLoading: false,
- changePasswordLoading: false,
- deleteAccountLoading: false,
deleteAccountShowConfirm: false,
deleteAccountForm: {},
personBlocks: [],
this.userSettings = this.userSettings.bind(this);
this.blockCards = this.blockCards.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleBlockPerson = this.handleBlockPerson.bind(this);
+ this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
const mui = UserService.Instance.myUserInfo;
if (mui) {
this.setState({ themeList: await fetchThemeList() });
}
- componentWillUnmount() {
- this.subscription?.unsubscribe();
- }
-
get documentTitle(): string {
return i18n.t("settings");
}
</div>
<div className="form-group">
<button type="submit" className="btn btn-block btn-secondary mr-4">
- {this.state.changePasswordLoading ? (
+ {this.state.changePasswordRes.state === "loading" ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("save"))
{this.totpSection()}
<div className="form-group">
<button type="submit" className="btn btn-block btn-secondary mr-4">
- {this.state.saveUserSettingsLoading ? (
+ {this.state.saveRes.state === "loading" ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("save"))
disabled={!this.state.deleteAccountForm.password}
onClick={linkEvent(this, this.handleDeleteAccount)}
>
- {this.state.deleteAccountLoading ? (
+ {this.state.deleteAccountRes.state === "loading" ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("delete"))
const searchPersonOptions: Choice[] = [];
if (text.length > 0) {
- searchPersonOptions.push(
- ...(await fetchUsers(text)).users.map(personToChoice)
- );
+ searchPersonOptions.push(...(await fetchUsers(text)).map(personToChoice));
}
this.setState({
if (text.length > 0) {
searchCommunityOptions.push(
- ...(await fetchCommunities(text)).communities.map(communityToChoice)
+ ...(await fetchCommunities(text)).map(communityToChoice)
);
}
});
});
- handleBlockPerson({ value }: Choice) {
- const auth = myAuth();
- if (auth && value !== "0") {
- const blockUserForm: BlockPerson = {
+ async handleBlockPerson({ value }: Choice) {
+ if (value !== "0") {
+ const res = await HttpService.client.blockPerson({
person_id: Number(value),
block: true,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
+ auth: myAuthRequired(),
+ });
+ this.personBlock(res);
}
}
- handleUnblockPerson(i: { ctx: Settings; recipientId: number }) {
- const auth = myAuth();
- if (auth) {
- const blockUserForm: BlockPerson = {
- person_id: i.recipientId,
- block: false,
- auth,
- };
- WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
- }
+ async handleUnblockPerson({
+ ctx,
+ recipientId,
+ }: {
+ ctx: Settings;
+ recipientId: number;
+ }) {
+ const res = await HttpService.client.blockPerson({
+ person_id: recipientId,
+ block: false,
+ auth: myAuthRequired(),
+ });
+ ctx.personBlock(res);
}
- handleBlockCommunity({ value }: Choice) {
- const auth = myAuth();
- if (auth && value !== "0") {
- const blockCommunityForm: BlockCommunity = {
+ async handleBlockCommunity({ value }: Choice) {
+ if (value !== "0") {
+ const res = await HttpService.client.blockCommunity({
community_id: Number(value),
block: true,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.blockCommunity(blockCommunityForm)
- );
+ auth: myAuthRequired(),
+ });
+ this.communityBlock(res);
}
}
- handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
+ async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
const auth = myAuth();
if (auth) {
- const blockCommunityForm: BlockCommunity = {
+ const res = await HttpService.client.blockCommunity({
community_id: i.communityId,
block: false,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.blockCommunity(blockCommunityForm)
- );
+ auth: myAuthRequired(),
+ });
+ i.ctx.communityBlock(res);
}
}
handleShowNsfwChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_nsfw = event.target.checked;
- i.setState(i.state);
+ i.setState(
+ s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s)
+ );
}
handleShowAvatarsChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_avatars = event.target.checked;
const mui = UserService.Instance.myUserInfo;
if (mui) {
mui.local_user_view.local_user.show_avatars = event.target.checked;
}
- i.setState(i.state);
+ i.setState(
+ s => ((s.saveUserSettingsForm.show_avatars = event.target.checked), s)
+ );
}
handleBotAccount(i: Settings, event: any) {
- i.state.saveUserSettingsForm.bot_account = event.target.checked;
- i.setState(i.state);
+ i.setState(
+ s => ((s.saveUserSettingsForm.bot_account = event.target.checked), s)
+ );
}
handleShowBotAccounts(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
- i.setState(i.state);
+ i.setState(
+ s => (
+ (s.saveUserSettingsForm.show_bot_accounts = event.target.checked), s
+ )
+ );
}
handleReadPosts(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
- i.setState(i.state);
+ i.setState(
+ s => ((s.saveUserSettingsForm.show_read_posts = event.target.checked), s)
+ );
}
handleShowNewPostNotifs(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked;
- i.setState(i.state);
+ i.setState(
+ s => (
+ (s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
+ )
+ );
}
handleShowScoresChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_scores = event.target.checked;
const mui = UserService.Instance.myUserInfo;
if (mui) {
mui.local_user_view.local_user.show_scores = event.target.checked;
}
- i.setState(i.state);
+ i.setState(
+ s => ((s.saveUserSettingsForm.show_scores = event.target.checked), s)
+ );
}
handleGenerateTotp(i: Settings, event: any) {
if (checked) {
toast(i18n.t("two_factor_setup_instructions"));
}
- i.state.saveUserSettingsForm.generate_totp_2fa = checked;
- i.setState(i.state);
+ i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
}
handleRemoveTotp(i: Settings, event: any) {
// Coerce true to undefined here, so it won't generate it.
const checked: boolean | undefined = !event.target.checked && undefined;
- i.state.saveUserSettingsForm.generate_totp_2fa = checked;
- i.setState(i.state);
+ i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
}
handleSendNotificationsToEmailChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.send_notifications_to_email =
- event.target.checked;
- i.setState(i.state);
+ i.setState(
+ s => (
+ (s.saveUserSettingsForm.send_notifications_to_email =
+ event.target.checked),
+ s
+ )
+ );
}
handleThemeChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.theme = event.target.value;
+ i.setState(s => ((s.saveUserSettingsForm.theme = event.target.value), s));
setTheme(event.target.value, true);
- i.setState(i.state);
}
handleInterfaceLangChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.interface_language = event.target.value;
+ i.setState(
+ s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
+ );
i18n.changeLanguage(
getLanguages(i.state.saveUserSettingsForm.interface_language).at(0)
);
- i.setState(i.state);
}
handleDiscussionLanguageChange(val: number[]) {
}
handleEmailChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.email = event.target.value;
- i.setState(i.state);
+ i.setState(s => ((s.saveUserSettingsForm.email = event.target.value), s));
}
handleBioChange(val: string) {
}
handleDisplayNameChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.display_name = event.target.value;
- i.setState(i.state);
+ i.setState(
+ s => ((s.saveUserSettingsForm.display_name = event.target.value), s)
+ );
}
handleMatrixUserIdChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.matrix_user_id = event.target.value;
- i.setState(i.state);
+ i.setState(
+ s => ((s.saveUserSettingsForm.matrix_user_id = event.target.value), s)
+ );
}
handleNewPasswordChange(i: Settings, event: any) {
- i.state.changePasswordForm.new_password = event.target.value;
- if (i.state.changePasswordForm.new_password == "") {
- i.state.changePasswordForm.new_password = undefined;
- }
- i.setState(i.state);
+ const newPass: string | undefined =
+ event.target.value == "" ? undefined : event.target.value;
+ i.setState(s => ((s.changePasswordForm.new_password = newPass), s));
}
handleNewPasswordVerifyChange(i: Settings, event: any) {
- i.state.changePasswordForm.new_password_verify = event.target.value;
- if (i.state.changePasswordForm.new_password_verify == "") {
- i.state.changePasswordForm.new_password_verify = undefined;
- }
- i.setState(i.state);
+ const newPassVerify: string | undefined =
+ event.target.value == "" ? undefined : event.target.value;
+ i.setState(
+ s => ((s.changePasswordForm.new_password_verify = newPassVerify), s)
+ );
}
handleOldPasswordChange(i: Settings, event: any) {
- i.state.changePasswordForm.old_password = event.target.value;
- if (i.state.changePasswordForm.old_password == "") {
- i.state.changePasswordForm.old_password = undefined;
- }
- i.setState(i.state);
+ const oldPass: string | undefined =
+ event.target.value == "" ? undefined : event.target.value;
+ i.setState(s => ((s.changePasswordForm.old_password = oldPass), s));
}
- handleSaveSettingsSubmit(i: Settings, event: any) {
+ async handleSaveSettingsSubmit(i: Settings, event: any) {
event.preventDefault();
- i.setState({ saveUserSettingsLoading: true });
- const auth = myAuth();
- if (auth) {
- const form: SaveUserSettings = { ...i.state.saveUserSettingsForm, auth };
- WebSocketService.Instance.send(wsClient.saveUserSettings(form));
+ i.setState({ saveRes: { state: "loading" } });
+
+ const saveRes = await HttpService.client.saveUserSettings({
+ ...i.state.saveUserSettingsForm,
+ auth: myAuthRequired(),
+ });
+ if (saveRes.state === "success") {
+ UserService.Instance.login(saveRes.data);
+ location.reload();
+ toast(i18n.t("saved"));
+ window.scrollTo(0, 0);
}
+
+ i.setState({ saveRes });
}
- handleChangePasswordSubmit(i: Settings, event: any) {
+ async handleChangePasswordSubmit(i: Settings, event: any) {
event.preventDefault();
- i.setState({ changePasswordLoading: true });
- const auth = myAuth();
- const pForm = i.state.changePasswordForm;
- const new_password = pForm.new_password;
- const new_password_verify = pForm.new_password_verify;
- const old_password = pForm.old_password;
- if (auth && new_password && old_password && new_password_verify) {
- const form: ChangePassword = {
+ const { new_password, new_password_verify, old_password } =
+ i.state.changePasswordForm;
+
+ if (new_password && old_password && new_password_verify) {
+ i.setState({ changePasswordRes: { state: "loading" } });
+ const changePasswordRes = await HttpService.client.changePassword({
new_password,
new_password_verify,
old_password,
- auth,
- };
+ auth: myAuthRequired(),
+ });
+ if (changePasswordRes.state === "success") {
+ UserService.Instance.login(changePasswordRes.data);
+ window.scrollTo(0, 0);
+ toast(i18n.t("password_changed"));
+ }
- WebSocketService.Instance.send(wsClient.changePassword(form));
+ i.setState({ changePasswordRes });
}
}
- handleDeleteAccountShowConfirmToggle(i: Settings, event: any) {
- event.preventDefault();
+ handleDeleteAccountShowConfirmToggle(i: Settings) {
i.setState({ deleteAccountShowConfirm: !i.state.deleteAccountShowConfirm });
}
handleDeleteAccountPasswordChange(i: Settings, event: any) {
- i.state.deleteAccountForm.password = event.target.value;
- i.setState(i.state);
+ i.setState(s => ((s.deleteAccountForm.password = event.target.value), s));
}
- handleDeleteAccount(i: Settings, event: any) {
- event.preventDefault();
- i.setState({ deleteAccountLoading: true });
- const auth = myAuth();
+ async handleDeleteAccount(i: Settings) {
const password = i.state.deleteAccountForm.password;
- if (auth && password) {
- const form: DeleteAccount = {
+ if (password) {
+ i.setState({ deleteAccountRes: { state: "loading" } });
+ const deleteAccountRes = await HttpService.client.deleteAccount({
password,
- auth,
- };
- WebSocketService.Instance.send(wsClient.deleteAccount(form));
+ auth: myAuthRequired(),
+ });
+ if (deleteAccountRes.state === "success") {
+ UserService.Instance.logout();
+ this.context.router.history.replace("/");
+ }
+
+ i.setState({ deleteAccountRes });
}
}
i.ctx.setState({ currentTab: i.tab });
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- this.setState({
- saveUserSettingsLoading: false,
- changePasswordLoading: false,
- deleteAccountLoading: false,
- });
- toast(i18n.t(msg.error), "danger");
- return;
- } else if (op == UserOperation.SaveUserSettings) {
- this.setState({ saveUserSettingsLoading: false });
- toast(i18n.t("saved"));
- window.scrollTo(0, 0);
- } else if (op == UserOperation.ChangePassword) {
- const data = wsJsonToRes<LoginResponse>(msg);
- UserService.Instance.login(data);
- this.setState({ changePasswordLoading: false });
- window.scrollTo(0, 0);
- toast(i18n.t("password_changed"));
- } else if (op == UserOperation.DeleteAccount) {
- this.setState({
- deleteAccountLoading: false,
- deleteAccountShowConfirm: false,
- });
- UserService.Instance.logout();
- window.location.href = "/";
- } else if (op == UserOperation.BlockPerson) {
- const data = wsJsonToRes<BlockPersonResponse>(msg);
- updatePersonBlock(data);
+ personBlock(res: RequestState<BlockPersonResponse>) {
+ if (res.state === "success") {
+ updatePersonBlock(res.data);
const mui = UserService.Instance.myUserInfo;
if (mui) {
this.setState({ personBlocks: mui.person_blocks });
}
- } else if (op == UserOperation.BlockCommunity) {
- const data = wsJsonToRes<BlockCommunityResponse>(msg);
- updateCommunityBlock(data);
+ }
+ }
+
+ communityBlock(res: RequestState<BlockCommunityResponse>) {
+ if (res.state === "success") {
+ updateCommunityBlock(res.data);
const mui = UserService.Instance.myUserInfo;
if (mui) {
this.setState({ communityBlocks: mui.community_blocks });
import { Component } from "inferno";
-import {
- GetSiteResponse,
- UserOperation,
- VerifyEmail as VerifyEmailForm,
- wsJsonToRes,
- wsUserOp,
-} from "lemmy-js-client";
-import { Subscription } from "rxjs";
+import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
-import {
- isBrowser,
- setIsoData,
- toast,
- wsClient,
- wsSubscribe,
-} from "../../utils";
+import { HttpService, RequestState } from "../../services/HttpService";
+import { setIsoData, toast } from "../../utils";
import { HtmlTags } from "../common/html-tags";
+import { Spinner } from "../common/icon";
interface State {
- verifyEmailForm: VerifyEmailForm;
+ verifyRes: RequestState<VerifyEmailResponse>;
siteRes: GetSiteResponse;
}
export class VerifyEmail extends Component<any, State> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: State = {
- verifyEmailForm: {
- token: this.props.match.params.token,
- },
+ verifyRes: { state: "empty" },
siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
super(props, context);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
}
- componentDidMount() {
- WebSocketService.Instance.send(
- wsClient.verifyEmail(this.state.verifyEmailForm)
- );
- }
+ async verify() {
+ this.setState({
+ verifyRes: { state: "loading" },
+ });
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ this.setState({
+ verifyRes: await HttpService.client.verifyEmail({
+ token: this.props.match.params.token,
+ }),
+ });
+
+ if (this.state.verifyRes.state == "success") {
+ toast(i18n.t("email_verified"));
+ this.props.history.push("/login");
}
}
+ async componentDidMount() {
+ await this.verify();
+ }
+
get documentTitle(): string {
return `${i18n.t("verify_email")} - ${
this.state.siteRes.site_view.site.name
<div className="row">
<div className="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t("verify_email")}</h5>
+ {this.state.verifyRes.state == "loading" && (
+ <h5>
+ <Spinner large />
+ </h5>
+ )}
</div>
</div>
</div>
);
}
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.setState(this.state);
- this.props.history.push("/");
- return;
- } else if (op == UserOperation.VerifyEmail) {
- const data = wsJsonToRes(msg);
- if (data) {
- toast(i18n.t("email_verified"));
- this.props.history.push("/login");
- }
- }
- }
}
import { Component } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
+ CreatePost as CreatePostI,
GetCommunity,
- GetCommunityResponse,
GetSiteResponse,
- PostView,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ ListCommunitiesResponse,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest, PostFormParams } from "../../interfaces";
-import { WebSocketService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import {
+ HttpService,
+ RequestState,
+ WrappedLemmyHttp,
+} from "../../services/HttpService";
import {
Choice,
QueryParams,
enableNsfw,
getIdFromString,
getQueryParams,
- isBrowser,
myAuth,
setIsoData,
- toast,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
});
}
+function fetchCommunitiesForOptions(client: WrappedLemmyHttp) {
+ return client.listCommunities({ limit: 30, sort: "TopMonth", type_: "All" });
+}
+
interface CreatePostState {
siteRes: GetSiteResponse;
loading: boolean;
selectedCommunityChoice?: Choice;
+ initialCommunitiesRes: RequestState<ListCommunitiesResponse>;
+ isIsomorphic: boolean;
}
export class CreatePost extends Component<
CreatePostState
> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: CreatePostState = {
siteRes: this.isoData.site_res,
loading: true,
+ initialCommunitiesRes: { state: "empty" },
+ isIsomorphic: false,
};
constructor(props: RouteComponentProps<Record<string, never>>, context: any) {
this.handleSelectedCommunityChange =
this.handleSelectedCommunityChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
// Only fetch the data if coming from another route
- if (this.isoData.path === this.context.router.route.match.url) {
- const communityRes = this.isoData.routeData[0] as
- | GetCommunityResponse
- | undefined;
+ if (FirstLoadService.isFirstLoad) {
+ const [communityRes, listCommunitiesRes] = this.isoData.routeData;
- if (communityRes) {
+ if (communityRes?.state === "success") {
const communityChoice: Choice = {
- label: communityRes.community_view.community.title,
- value: communityRes.community_view.community.id.toString(),
+ label: communityRes.data.community_view.community.title,
+ value: communityRes.data.community_view.community.id.toString(),
};
this.state = {
this.state = {
...this.state,
loading: false,
+ initialCommunitiesRes: listCommunitiesRes,
+ isIsomorphic: true,
};
- } else {
- this.fetchCommunity();
}
}
- fetchCommunity() {
+ async fetchCommunity() {
const { communityId } = getCreatePostQueryParams();
- const auth = myAuth(false);
+ const auth = myAuth();
if (communityId) {
- const form: GetCommunity = {
+ const res = await HttpService.client.getCommunity({
id: communityId,
auth,
- };
-
- WebSocketService.Instance.send(wsClient.getCommunity(form));
+ });
+ if (res.state === "success") {
+ this.setState({
+ selectedCommunityChoice: {
+ label: res.data.community_view.community.name,
+ value: res.data.community_view.community.id.toString(),
+ },
+ loading: false,
+ });
+ }
}
}
- componentDidMount(): void {
- const { communityId } = getCreatePostQueryParams();
+ async componentDidMount() {
+ // TODO test this
+ if (!this.state.isIsomorphic) {
+ const { communityId } = getCreatePostQueryParams();
+
+ const initialCommunitiesRes = await fetchCommunitiesForOptions(
+ HttpService.client
+ );
- if (communityId?.toString() !== this.state.selectedCommunityChoice?.value) {
- this.fetchCommunity();
- } else if (!communityId) {
this.setState({
- selectedCommunityChoice: undefined,
- loading: false,
+ initialCommunitiesRes,
});
- }
- }
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ if (
+ communityId?.toString() !== this.state.selectedCommunityChoice?.value
+ ) {
+ await this.fetchCommunity();
+ } else if (!communityId) {
+ this.setState({
+ selectedCommunityChoice: undefined,
+ loading: false,
+ });
+ }
}
}
siteLanguages={this.state.siteRes.discussion_languages}
selectedCommunityChoice={selectedCommunityChoice}
onSelectCommunity={this.handleSelectedCommunityChange}
+ initialCommunities={
+ this.state.initialCommunitiesRes.state === "success"
+ ? this.state.initialCommunitiesRes.data.communities
+ : []
+ }
/>
</div>
</div>
);
}
- updateUrl({ communityId }: Partial<CreatePostProps>) {
+ async updateUrl({ communityId }: Partial<CreatePostProps>) {
const { communityId: urlCommunityId } = getCreatePostQueryParams();
const locationState = this.props.history.location.state as
history.replaceState(locationState, "", url);
- this.fetchCommunity();
+ await this.fetchCommunity();
}
handleSelectedCommunityChange(choice: Choice) {
});
}
- handlePostCreate(post_view: PostView) {
- this.props.history.replace(`/post/${post_view.post.id}`);
+ async handlePostCreate(form: CreatePostI) {
+ const res = await HttpService.client.createPost(form);
+
+ if (res.state === "success") {
+ const postId = res.data.post_view.post.id;
+ this.props.history.replace(`/post/${postId}`);
+ }
}
static fetchInitialData({
client,
query: { communityId },
auth,
- }: InitialFetchRequest<QueryParams<CreatePostProps>>): Promise<any>[] {
- const promises: Promise<any>[] = [];
+ }: InitialFetchRequest<QueryParams<CreatePostProps>>): Promise<
+ RequestState<any>
+ >[] {
+ const promises: Promise<RequestState<any>>[] = [];
if (communityId) {
const form: GetCommunity = {
promises.push(client.getCommunity(form));
} else {
- promises.push(Promise.resolve());
- }
-
- return promises;
- }
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- return;
+ promises.push(Promise.resolve({ state: "empty" }));
}
- if (op === UserOperation.GetCommunity) {
- const {
- community_view: {
- community: { title, id },
- },
- } = wsJsonToRes<GetCommunityResponse>(msg);
+ promises.push(fetchCommunitiesForOptions(client));
- this.setState({
- selectedCommunityChoice: { label: title, value: id.toString() },
- loading: false,
- });
- }
+ return promises;
}
}
import autosize from "autosize";
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import {
+ CommunityView,
CreatePost,
EditPost,
+ GetSiteMetadataResponse,
Language,
- PostResponse,
PostView,
- Search,
SearchResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { PostFormParams } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
Choice,
archiveTodayUrl,
debounce,
fetchCommunities,
getIdFromString,
- getSiteMetadata,
ghostArchiveUrl,
isImage,
myAuth,
+ myAuthRequired,
pictrsDeleteToast,
relTags,
setupTippy,
toast,
trendingFetchLimit,
- uploadImage,
validTitle,
validURL,
webArchiveUrl,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { LanguageSelect } from "../common/language-select";
interface PostFormProps {
post_view?: PostView; // If a post is given, that means this is an edit
+ crossPosts?: PostView[];
allLanguages: Language[];
siteLanguages: number[];
params?: PostFormParams;
- onCancel?(): any;
- onCreate?(post: PostView): any;
- onEdit?(post: PostView): any;
+ onCancel?(): void;
+ onCreate?(form: CreatePost): void;
+ onEdit?(form: EditPost): void;
enableNsfw?: boolean;
enableDownvotes?: boolean;
selectedCommunityChoice?: Choice;
onSelectCommunity?: (choice: Choice) => void;
+ initialCommunities?: CommunityView[];
}
interface PostFormState {
community_id?: number;
honeypot?: string;
};
- suggestedTitle?: string;
- suggestedPosts?: PostView[];
- crossPosts?: PostView[];
loading: boolean;
+ suggestedPostsRes: RequestState<SearchResponse>;
+ metadataRes: RequestState<GetSiteMetadataResponse>;
imageLoading: boolean;
communitySearchLoading: boolean;
communitySearchOptions: Choice[];
previewMode: boolean;
+ submitted: boolean;
}
export class PostForm extends Component<PostFormProps, PostFormState> {
- private subscription?: Subscription;
state: PostFormState = {
+ suggestedPostsRes: { state: "empty" },
+ metadataRes: { state: "empty" },
form: {},
loading: false,
imageLoading: false,
communitySearchLoading: false,
previewMode: false,
communitySearchOptions: [],
+ submitted: false,
};
constructor(props: PostFormProps, context: any) {
this.handleLanguageChange = this.handleLanguageChange.bind(this);
this.handleCommunitySelect = this.handleCommunitySelect.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ const { post_view, selectedCommunityChoice, params } = this.props;
// Means its an edit
- const pv = this.props.post_view;
- if (pv) {
+ if (post_view) {
this.state = {
...this.state,
form: {
- body: pv.post.body,
- name: pv.post.name,
- community_id: pv.community.id,
- url: pv.post.url,
- nsfw: pv.post.nsfw,
- language_id: pv.post.language_id,
+ body: post_view.post.body,
+ name: post_view.post.name,
+ community_id: post_view.community.id,
+ url: post_view.post.url,
+ nsfw: post_view.post.nsfw,
+ language_id: post_view.post.language_id,
},
};
- }
-
- const selectedCommunityChoice = this.props.selectedCommunityChoice;
-
- if (selectedCommunityChoice) {
+ } else if (selectedCommunityChoice) {
this.state = {
...this.state,
form: {
...this.state.form,
community_id: getIdFromString(selectedCommunityChoice.value),
},
- communitySearchOptions: [selectedCommunityChoice],
+ communitySearchOptions: [selectedCommunityChoice]
+ .concat(
+ this.props.initialCommunities?.map(
+ ({ community: { id, title } }) => ({
+ label: title,
+ value: id.toString(),
+ })
+ ) ?? []
+ )
+ .filter(option => option.value !== selectedCommunityChoice.value),
+ };
+ } else {
+ this.state = {
+ ...this.state,
+ communitySearchOptions:
+ this.props.initialCommunities?.map(
+ ({ community: { id, title } }) => ({
+ label: title,
+ value: id.toString(),
+ })
+ ) ?? [],
};
}
- const params = this.props.params;
if (params) {
this.state = {
...this.state,
}
}
- componentDidUpdate() {
- if (
- !this.state.loading &&
- (this.state.form.name || this.state.form.url || this.state.form.body)
- ) {
- window.onbeforeunload = () => true;
- } else {
- window.onbeforeunload = null;
+ componentWillReceiveProps(
+ nextProps: Readonly<{ children?: InfernoNode } & PostFormProps>
+ ): void {
+ if (this.props != nextProps) {
+ this.setState(
+ s => (
+ (s.form.community_id = getIdFromString(
+ nextProps.selectedCommunityChoice?.value
+ )),
+ s
+ )
+ );
}
}
- componentWillUnmount() {
- this.subscription?.unsubscribe();
- /* this.choices && this.choices.destroy(); */
- window.onbeforeunload = null;
- }
-
- static getDerivedStateFromProps(
- { selectedCommunityChoice }: PostFormProps,
- { form, ...restState }: PostFormState
- ) {
- return {
- ...restState,
- form: {
- ...form,
- community_id: getIdFromString(selectedCommunityChoice?.value),
- },
- };
- }
-
render() {
const firstLang = this.state.form.language_id;
const selectedLangs = firstLang ? Array.of(firstLang) : undefined;
const url = this.state.form.url;
+
+ // TODO
+ // const promptCheck =
+ // !!this.state.form.name || !!this.state.form.url || !!this.state.form.body;
+ // <Prompt when={promptCheck} message={i18n.t("block_leaving")} />
return (
- <div>
+ <form onSubmit={linkEvent(this, this.handlePostSubmit)}>
<NavigationPrompt
when={
- !this.state.loading &&
!!(
this.state.form.name ||
this.state.form.url ||
this.state.form.body
- )
+ ) && !this.state.submitted
}
/>
- <form onSubmit={linkEvent(this, this.handlePostSubmit)}>
- <div className="form-group row">
- <label className="col-sm-2 col-form-label" htmlFor="post-url">
- {i18n.t("url")}
- </label>
- <div className="col-sm-10">
+ <div className="form-group row">
+ <label className="col-sm-2 col-form-label" htmlFor="post-url">
+ {i18n.t("url")}
+ </label>
+ <div className="col-sm-10">
+ <input
+ type="url"
+ id="post-url"
+ className="form-control"
+ value={this.state.form.url}
+ onInput={linkEvent(this, this.handlePostUrlChange)}
+ onPaste={linkEvent(this, this.handleImageUploadPaste)}
+ />
+ {this.renderSuggestedTitleCopy()}
+ <form>
+ <label
+ htmlFor="file-upload"
+ className={`${
+ UserService.Instance.myUserInfo && "pointer"
+ } d-inline-block float-right text-muted font-weight-bold`}
+ data-tippy-content={i18n.t("upload_image")}
+ >
+ <Icon icon="image" classes="icon-inline" />
+ </label>
<input
- type="url"
- id="post-url"
- className="form-control"
- value={this.state.form.url}
- onInput={linkEvent(this, this.handlePostUrlChange)}
- onPaste={linkEvent(this, this.handleImageUploadPaste)}
+ id="file-upload"
+ type="file"
+ accept="image/*,video/*"
+ name="file"
+ className="d-none"
+ disabled={!UserService.Instance.myUserInfo}
+ onChange={linkEvent(this, this.handleImageUpload)}
/>
- {this.state.suggestedTitle && (
- <div
- className="mt-1 text-muted small font-weight-bold pointer"
- role="button"
- onClick={linkEvent(this, this.copySuggestedTitle)}
+ </form>
+ {url && validURL(url) && (
+ <div>
+ <a
+ href={`${webArchiveUrl}/save/${encodeURIComponent(url)}`}
+ className="mr-2 d-inline-block float-right text-muted small font-weight-bold"
+ rel={relTags}
>
- {i18n.t("copy_suggested_title", { title: "" })}{" "}
- {this.state.suggestedTitle}
- </div>
- )}
- <form>
- <label
- htmlFor="file-upload"
- className={`${
- UserService.Instance.myUserInfo && "pointer"
- } d-inline-block float-right text-muted font-weight-bold`}
- data-tippy-content={i18n.t("upload_image")}
+ archive.org {i18n.t("archive_link")}
+ </a>
+ <a
+ href={`${ghostArchiveUrl}/search?term=${encodeURIComponent(
+ url
+ )}`}
+ className="mr-2 d-inline-block float-right text-muted small font-weight-bold"
+ rel={relTags}
>
- <Icon icon="image" classes="icon-inline" />
- </label>
- <input
- id="file-upload"
- type="file"
- accept="image/*,video/*"
- name="file"
- className="d-none"
- disabled={!UserService.Instance.myUserInfo}
- onChange={linkEvent(this, this.handleImageUpload)}
- />
- </form>
- {url && validURL(url) && (
- <div>
- <a
- href={`${webArchiveUrl}/save/${encodeURIComponent(url)}`}
- className="mr-2 d-inline-block float-right text-muted small font-weight-bold"
- rel={relTags}
- >
- archive.org {i18n.t("archive_link")}
- </a>
- <a
- href={`${ghostArchiveUrl}/search?term=${encodeURIComponent(
- url
- )}`}
- className="mr-2 d-inline-block float-right text-muted small font-weight-bold"
- rel={relTags}
- >
- ghostarchive.org {i18n.t("archive_link")}
- </a>
- <a
- href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent(
- url
- )}`}
- className="mr-2 d-inline-block float-right text-muted small font-weight-bold"
- rel={relTags}
- >
- archive.today {i18n.t("archive_link")}
- </a>
+ ghostarchive.org {i18n.t("archive_link")}
+ </a>
+ <a
+ href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent(
+ url
+ )}`}
+ className="mr-2 d-inline-block float-right text-muted small font-weight-bold"
+ rel={relTags}
+ >
+ archive.today {i18n.t("archive_link")}
+ </a>
+ </div>
+ )}
+ {this.state.imageLoading && <Spinner />}
+ {url && isImage(url) && (
+ <img src={url} className="img-fluid" alt="" />
+ )}
+ {this.props.crossPosts && this.props.crossPosts.length > 0 && (
+ <>
+ <div className="my-1 text-muted small font-weight-bold">
+ {i18n.t("cross_posts")}
</div>
- )}
- {this.state.imageLoading && <Spinner />}
- {url && isImage(url) && (
- <img src={url} className="img-fluid" alt="" />
- )}
- {this.state.crossPosts && this.state.crossPosts.length > 0 && (
- <>
- <div className="my-1 text-muted small font-weight-bold">
- {i18n.t("cross_posts")}
- </div>
- <PostListings
- showCommunity
- posts={this.state.crossPosts}
- enableDownvotes={this.props.enableDownvotes}
- enableNsfw={this.props.enableNsfw}
- allLanguages={this.props.allLanguages}
- siteLanguages={this.props.siteLanguages}
- />
- </>
- )}
- </div>
+ <PostListings
+ showCommunity
+ posts={this.props.crossPosts}
+ enableDownvotes={this.props.enableDownvotes}
+ enableNsfw={this.props.enableNsfw}
+ allLanguages={this.props.allLanguages}
+ siteLanguages={this.props.siteLanguages}
+ viewOnly
+ // All of these are unused, since its view only
+ onPostEdit={() => {}}
+ onPostVote={() => {}}
+ onPostReport={() => {}}
+ onBlockPerson={() => {}}
+ onLockPost={() => {}}
+ onDeletePost={() => {}}
+ onRemovePost={() => {}}
+ onSavePost={() => {}}
+ onFeaturePost={() => {}}
+ onPurgePerson={() => {}}
+ onPurgePost={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
+ />
+ </>
+ )}
+ </div>
+ </div>
+ <div className="form-group row">
+ <label className="col-sm-2 col-form-label" htmlFor="post-title">
+ {i18n.t("title")}
+ </label>
+ <div className="col-sm-10">
+ <textarea
+ value={this.state.form.name}
+ id="post-title"
+ onInput={linkEvent(this, this.handlePostNameChange)}
+ className={`form-control ${
+ !validTitle(this.state.form.name) && "is-invalid"
+ }`}
+ required
+ rows={1}
+ minLength={3}
+ maxLength={MAX_POST_TITLE_LENGTH}
+ />
+ {!validTitle(this.state.form.name) && (
+ <div className="invalid-feedback">
+ {i18n.t("invalid_post_title")}
+ </div>
+ )}
+ {this.renderSuggestedPosts()}
</div>
+ </div>
+
+ <div className="form-group row">
+ <label className="col-sm-2 col-form-label">{i18n.t("body")}</label>
+ <div className="col-sm-10">
+ <MarkdownTextArea
+ initialContent={this.state.form.body}
+ onContentChange={this.handlePostBodyChange}
+ allLanguages={this.props.allLanguages}
+ siteLanguages={this.props.siteLanguages}
+ hideNavigationWarnings
+ />
+ </div>
+ </div>
+ {!this.props.post_view && (
<div className="form-group row">
- <label className="col-sm-2 col-form-label" htmlFor="post-title">
- {i18n.t("title")}
+ <label className="col-sm-2 col-form-label" htmlFor="post-community">
+ {i18n.t("community")}
</label>
<div className="col-sm-10">
- <textarea
- value={this.state.form.name}
- id="post-title"
- onInput={linkEvent(this, this.handlePostNameChange)}
- className={`form-control ${
- !validTitle(this.state.form.name) && "is-invalid"
- }`}
- required
- rows={1}
- minLength={3}
- maxLength={MAX_POST_TITLE_LENGTH}
+ <SearchableSelect
+ id="post-community"
+ value={this.state.form.community_id}
+ options={[
+ {
+ label: i18n.t("select_a_community"),
+ value: "",
+ disabled: true,
+ } as Choice,
+ ].concat(this.state.communitySearchOptions)}
+ loading={this.state.communitySearchLoading}
+ onChange={this.handleCommunitySelect}
+ onSearch={this.handleCommunitySearch}
/>
- {!validTitle(this.state.form.name) && (
- <div className="invalid-feedback">
- {i18n.t("invalid_post_title")}
- </div>
- )}
- {this.state.suggestedPosts &&
- this.state.suggestedPosts.length > 0 && (
- <>
- <div className="my-1 text-muted small font-weight-bold">
- {i18n.t("related_posts")}
- </div>
- <PostListings
- showCommunity
- posts={this.state.suggestedPosts}
- enableDownvotes={this.props.enableDownvotes}
- enableNsfw={this.props.enableNsfw}
- allLanguages={this.props.allLanguages}
- siteLanguages={this.props.siteLanguages}
- />
- </>
- )}
</div>
</div>
-
+ )}
+ {this.props.enableNsfw && (
<div className="form-group row">
- <label className="col-sm-2 col-form-label">{i18n.t("body")}</label>
+ <legend className="col-form-label col-sm-2 pt-0">
+ {i18n.t("nsfw")}
+ </legend>
<div className="col-sm-10">
- <MarkdownTextArea
- initialContent={this.state.form.body}
- onContentChange={this.handlePostBodyChange}
- allLanguages={this.props.allLanguages}
- siteLanguages={this.props.siteLanguages}
- />
- </div>
- </div>
- {!this.props.post_view && (
- <div className="form-group row">
- <label
- className="col-sm-2 col-form-label"
- htmlFor="post-community"
- >
- {i18n.t("community")}
- </label>
- <div className="col-sm-10">
- <SearchableSelect
- id="post-community"
- value={this.state.form.community_id}
- options={[
- {
- label: i18n.t("select_a_community"),
- value: "",
- disabled: true,
- } as Choice,
- ].concat(this.state.communitySearchOptions)}
- loading={this.state.communitySearchLoading}
- onChange={this.handleCommunitySelect}
- onSearch={this.handleCommunitySearch}
+ <div className="form-check">
+ <input
+ className="form-check-input position-static"
+ id="post-nsfw"
+ type="checkbox"
+ checked={this.state.form.nsfw}
+ onChange={linkEvent(this, this.handlePostNsfwChange)}
/>
</div>
</div>
- )}
- {this.props.enableNsfw && (
- <div className="form-group row">
- <legend className="col-form-label col-sm-2 pt-0">
- {i18n.t("nsfw")}
- </legend>
- <div className="col-sm-10">
- <div className="form-check">
- <input
- className="form-check-input position-static"
- id="post-nsfw"
- type="checkbox"
- checked={this.state.form.nsfw}
- onChange={linkEvent(this, this.handlePostNsfwChange)}
- />
- </div>
- </div>
- </div>
- )}
- <LanguageSelect
- allLanguages={this.props.allLanguages}
- siteLanguages={this.props.siteLanguages}
- selectedLanguageIds={selectedLangs}
- multiple={false}
- onChange={this.handleLanguageChange}
- />
- <input
- tabIndex={-1}
- autoComplete="false"
- name="a_password"
- type="text"
- className="form-control honeypot"
- id="register-honey"
- value={this.state.form.honeypot}
- onInput={linkEvent(this, this.handleHoneyPotChange)}
- />
- <div className="form-group row">
- <div className="col-sm-10">
+ </div>
+ )}
+ <LanguageSelect
+ allLanguages={this.props.allLanguages}
+ siteLanguages={this.props.siteLanguages}
+ selectedLanguageIds={selectedLangs}
+ multiple={false}
+ onChange={this.handleLanguageChange}
+ />
+ <input
+ tabIndex={-1}
+ autoComplete="false"
+ name="a_password"
+ type="text"
+ className="form-control honeypot"
+ id="register-honey"
+ value={this.state.form.honeypot}
+ onInput={linkEvent(this, this.handleHoneyPotChange)}
+ />
+ <div className="form-group row">
+ <div className="col-sm-10">
+ <button
+ disabled={!this.state.form.community_id || this.state.loading}
+ type="submit"
+ className="btn btn-secondary mr-2"
+ >
+ {this.state.loading ? (
+ <Spinner />
+ ) : this.props.post_view ? (
+ capitalizeFirstLetter(i18n.t("save"))
+ ) : (
+ capitalizeFirstLetter(i18n.t("create"))
+ )}
+ </button>
+ {this.props.post_view && (
<button
- disabled={!this.state.form.community_id || this.state.loading}
- type="submit"
- className="btn btn-secondary mr-2"
+ type="button"
+ className="btn btn-secondary"
+ onClick={linkEvent(this, this.handleCancel)}
>
- {this.state.loading ? (
- <Spinner />
- ) : this.props.post_view ? (
- capitalizeFirstLetter(i18n.t("save"))
- ) : (
- capitalizeFirstLetter(i18n.t("create"))
- )}
+ {i18n.t("cancel")}
</button>
- {this.props.post_view && (
- <button
- type="button"
- className="btn btn-secondary"
- onClick={linkEvent(this, this.handleCancel)}
- >
- {i18n.t("cancel")}
- </button>
- )}
- </div>
+ )}
</div>
- </form>
- </div>
+ </div>
+ </form>
);
}
- handlePostSubmit(i: PostForm, event: any) {
- event.preventDefault();
+ renderSuggestedTitleCopy() {
+ switch (this.state.metadataRes.state) {
+ case "loading":
+ return <Spinner />;
+ case "success": {
+ const suggestedTitle = this.state.metadataRes.data.metadata.title;
+
+ return (
+ suggestedTitle && (
+ <div
+ className="mt-1 text-muted small font-weight-bold pointer"
+ role="button"
+ onClick={linkEvent(
+ { i: this, suggestedTitle },
+ this.copySuggestedTitle
+ )}
+ >
+ {i18n.t("copy_suggested_title", { title: "" })} {suggestedTitle}
+ </div>
+ )
+ );
+ }
+ }
+ }
- i.setState({ loading: true });
+ renderSuggestedPosts() {
+ switch (this.state.suggestedPostsRes.state) {
+ case "loading":
+ return <Spinner />;
+ case "success": {
+ const suggestedPosts = this.state.suggestedPostsRes.data.posts;
+
+ return (
+ suggestedPosts &&
+ suggestedPosts.length > 0 && (
+ <>
+ <div className="my-1 text-muted small font-weight-bold">
+ {i18n.t("related_posts")}
+ </div>
+ <PostListings
+ showCommunity
+ posts={suggestedPosts}
+ enableDownvotes={this.props.enableDownvotes}
+ enableNsfw={this.props.enableNsfw}
+ allLanguages={this.props.allLanguages}
+ siteLanguages={this.props.siteLanguages}
+ viewOnly
+ // All of these are unused, since its view only
+ onPostEdit={() => {}}
+ onPostVote={() => {}}
+ onPostReport={() => {}}
+ onBlockPerson={() => {}}
+ onLockPost={() => {}}
+ onDeletePost={() => {}}
+ onRemovePost={() => {}}
+ onSavePost={() => {}}
+ onFeaturePost={() => {}}
+ onPurgePerson={() => {}}
+ onPurgePost={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
+ />
+ </>
+ )
+ );
+ }
+ }
+ }
+ handlePostSubmit(i: PostForm, event: any) {
+ event.preventDefault();
// Coerce empty url string to undefined
- if ((i.state.form.url ?? "blank") === "") {
+ if ((i.state.form.url ?? "") === "") {
i.setState(s => ((s.form.url = undefined), s));
}
+ i.setState({ loading: true, submitted: true });
+ const auth = myAuthRequired();
const pForm = i.state.form;
const pv = i.props.post_view;
- const auth = myAuth();
- if (auth) {
- if (pv) {
- const form: EditPost = {
- name: pForm.name,
- url: pForm.url,
- body: pForm.body,
- nsfw: pForm.nsfw,
- post_id: pv.post.id,
- language_id: pForm.language_id,
- auth,
- };
- WebSocketService.Instance.send(wsClient.editPost(form));
- } else {
- if (pForm.name && pForm.community_id) {
- const form: CreatePost = {
- name: pForm.name,
- community_id: pForm.community_id,
- url: pForm.url,
- body: pForm.body,
- nsfw: pForm.nsfw,
- language_id: pForm.language_id,
- honeypot: pForm.honeypot,
- auth,
- };
- WebSocketService.Instance.send(wsClient.createPost(form));
- }
- }
+
+ if (pv) {
+ i.props.onEdit?.({
+ name: pForm.name,
+ url: pForm.url,
+ body: pForm.body,
+ nsfw: pForm.nsfw,
+ post_id: pv.post.id,
+ language_id: pForm.language_id,
+ auth,
+ });
+ } else if (pForm.name && pForm.community_id) {
+ i.props.onCreate?.({
+ name: pForm.name,
+ community_id: pForm.community_id,
+ url: pForm.url,
+ body: pForm.body,
+ nsfw: pForm.nsfw,
+ language_id: pForm.language_id,
+ honeypot: pForm.honeypot,
+ auth,
+ });
}
}
- copySuggestedTitle(i: PostForm) {
- const sTitle = i.state.suggestedTitle;
+ copySuggestedTitle(d: { i: PostForm; suggestedTitle?: string }) {
+ const sTitle = d.suggestedTitle;
if (sTitle) {
- i.setState(
+ d.i.setState(
s => ((s.form.name = sTitle?.substring(0, MAX_POST_TITLE_LENGTH)), s)
);
- i.setState({ suggestedTitle: undefined });
+ d.i.setState({ suggestedPostsRes: { state: "empty" } });
setTimeout(() => {
const textarea: any = document.getElementById("post-title");
autosize.update(textarea);
i.fetchPageTitle();
}
- fetchPageTitle() {
+ async fetchPageTitle() {
const url = this.state.form.url;
if (url && validURL(url)) {
- const form: Search = {
- q: url,
- type_: "Url",
- sort: "TopAll",
- listing_type: "All",
- page: 1,
- limit: trendingFetchLimit,
- auth: myAuth(false),
- };
-
- WebSocketService.Instance.send(wsClient.search(form));
-
- // Fetch the page title
- getSiteMetadata(url).then(d => {
- this.setState({ suggestedTitle: d.metadata.title });
+ this.setState({ metadataRes: { state: "loading" } });
+ this.setState({
+ metadataRes: await HttpService.client.getSiteMetadata({ url }),
});
- } else {
- this.setState({ suggestedTitle: undefined, crossPosts: undefined });
}
}
i.fetchSimilarPosts();
}
- fetchSimilarPosts() {
+ async fetchSimilarPosts() {
const q = this.state.form.name;
if (q && q !== "") {
- const form: Search = {
- q,
- type_: "Posts",
- sort: "TopAll",
- listing_type: "All",
- community_id: this.state.form.community_id,
- page: 1,
- limit: trendingFetchLimit,
- auth: myAuth(false),
- };
-
- WebSocketService.Instance.send(wsClient.search(form));
- } else {
- this.setState({ suggestedPosts: undefined });
+ this.setState({ suggestedPostsRes: { state: "loading" } });
+ this.setState({
+ suggestedPostsRes: await HttpService.client.search({
+ q,
+ type_: "Posts",
+ sort: "TopAll",
+ listing_type: "All",
+ community_id: this.state.form.community_id,
+ page: 1,
+ limit: trendingFetchLimit,
+ auth: myAuth(),
+ }),
+ });
}
}
i.setState({ imageLoading: true });
- uploadImage(file)
- .then(res => {
- console.log("pictrs upload:");
- console.log(res);
- if (res.msg === "ok") {
- i.state.form.url = res.url;
+ HttpService.client.uploadImage({ image: file }).then(res => {
+ console.log("pictrs upload:");
+ console.log(res);
+ if (res.state === "success") {
+ if (res.data.msg === "ok") {
+ i.state.form.url = res.data.url;
+ pictrsDeleteToast(file.name, res.data.delete_url as string);
i.setState({ imageLoading: false });
- pictrsDeleteToast(file.name, res.delete_url as string);
} else {
- i.setState({ imageLoading: false });
toast(JSON.stringify(res), "danger");
}
- })
- .catch(error => {
- i.setState({ imageLoading: false });
- console.error(error);
- toast(error, "danger");
- });
+ } else if (res.state === "failed") {
+ console.error(res.msg);
+ toast(res.msg, "danger");
+ }
+ });
}
handleCommunitySearch = debounce(async (text: string) => {
}
if (text.length > 0) {
- newOptions.push(
- ...(await fetchCommunities(text)).communities.map(communityToChoice)
- );
+ newOptions.push(...(await fetchCommunities(text)).map(communityToChoice));
this.setState({
communitySearchOptions: newOptions,
this.props.onSelectCommunity(choice);
}
}
-
- parseMessage(msg: any) {
- const mui = UserService.Instance.myUserInfo;
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- // Errors handled by top level pages
- // toast(i18n.t(msg.error), "danger");
- this.setState({ loading: false });
- return;
- } else if (op == UserOperation.CreatePost) {
- const data = wsJsonToRes<PostResponse>(msg);
- if (data.post_view.creator.id == mui?.local_user_view.person.id) {
- this.props.onCreate?.(data.post_view);
- }
- } else if (op == UserOperation.EditPost) {
- const data = wsJsonToRes<PostResponse>(msg);
- if (data.post_view.creator.id == mui?.local_user_view.person.id) {
- this.setState({ loading: false });
- this.props.onEdit?.(data.post_view);
- }
- } else if (op == UserOperation.Search) {
- const data = wsJsonToRes<SearchResponse>(msg);
-
- if (data.type_ == "Posts") {
- this.setState({ suggestedPosts: data.posts });
- } else if (data.type_ == "Url") {
- this.setState({ crossPosts: data.posts });
- }
- }
- }
}
CreatePostLike,
CreatePostReport,
DeletePost,
+ EditPost,
FeaturePost,
Language,
LockPost,
} from "lemmy-js-client";
import { getExternalHost, getHttpBase } from "../../env";
import { i18n } from "../../i18next";
-import { BanType, PostFormParams, PurgeType } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
+import { UserService } from "../../services";
import {
amAdmin,
amCommunityCreator,
mdNoImages,
mdToHtml,
mdToHtmlInline,
- myAuth,
+ myAuthRequired,
+ newVote,
numToSI,
relTags,
setupTippy,
share,
showScores,
- wsClient,
} from "../../utils";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
showBody: boolean;
showReportDialog: boolean;
reportReason?: string;
- my_vote?: number;
- score: number;
- upvotes: number;
- downvotes: number;
+ upvoteLoading: boolean;
+ downvoteLoading: boolean;
+ reportLoading: boolean;
+ blockLoading: boolean;
+ lockLoading: boolean;
+ deleteLoading: boolean;
+ removeLoading: boolean;
+ saveLoading: boolean;
+ featureCommunityLoading: boolean;
+ featureLocalLoading: boolean;
+ banLoading: boolean;
+ addModLoading: boolean;
+ addAdminLoading: boolean;
+ transferLoading: boolean;
}
interface PostListingProps {
post_view: PostView;
- duplicates?: PostView[];
+ crossPosts?: PostView[];
moderators?: CommunityModeratorView[];
admins?: PersonView[];
allLanguages: Language[];
enableDownvotes?: boolean;
enableNsfw?: boolean;
viewOnly?: boolean;
+ onPostEdit(form: EditPost): void;
+ onPostVote(form: CreatePostLike): void;
+ onPostReport(form: CreatePostReport): void;
+ onBlockPerson(form: BlockPerson): void;
+ onLockPost(form: LockPost): void;
+ onDeletePost(form: DeletePost): void;
+ onRemovePost(form: RemovePost): void;
+ onSavePost(form: SavePost): void;
+ onFeaturePost(form: FeaturePost): void;
+ onPurgePerson(form: PurgePerson): void;
+ onPurgePost(form: PurgePost): void;
+ onBanPersonFromCommunity(form: BanFromCommunity): void;
+ onBanPerson(form: BanPerson): void;
+ onAddModToCommunity(form: AddModToCommunity): void;
+ onAddAdmin(form: AddAdmin): void;
+ onTransferCommunity(form: TransferCommunity): void;
}
export class PostListing extends Component<PostListingProps, PostListingState> {
showRemoveDialog: false,
showPurgeDialog: false,
purgeType: PurgeType.Person,
- purgeLoading: false,
showBanDialog: false,
banType: BanType.Community,
removeData: false,
showMoreMobile: false,
showBody: false,
showReportDialog: false,
- my_vote: this.props.post_view.my_vote,
- score: this.props.post_view.counts.score,
- upvotes: this.props.post_view.counts.upvotes,
- downvotes: this.props.post_view.counts.downvotes,
+ upvoteLoading: false,
+ downvoteLoading: false,
+ purgeLoading: false,
+ reportLoading: false,
+ blockLoading: false,
+ lockLoading: false,
+ deleteLoading: false,
+ removeLoading: false,
+ saveLoading: false,
+ featureCommunityLoading: false,
+ featureLocalLoading: false,
+ banLoading: false,
+ addModLoading: false,
+ addAdminLoading: false,
+ transferLoading: false,
};
constructor(props: any, context: any) {
super(props, context);
- this.handlePostLike = this.handlePostLike.bind(this);
- this.handlePostDisLike = this.handlePostDisLike.bind(this);
this.handleEditPost = this.handleEditPost.bind(this);
this.handleEditCancel = this.handleEditCancel.bind(this);
}
componentWillReceiveProps(nextProps: PostListingProps) {
- this.setState({
- my_vote: nextProps.post_view.my_vote,
- upvotes: nextProps.post_view.counts.upvotes,
- downvotes: nextProps.post_view.counts.downvotes,
- score: nextProps.post_view.counts.score,
- });
- if (this.props.post_view.post.id !== nextProps.post_view.post.id) {
- this.setState({ imageExpanded: false });
+ if (this.props !== nextProps) {
+ this.setState({
+ upvoteLoading: false,
+ downvoteLoading: false,
+ purgeLoading: false,
+ reportLoading: false,
+ blockLoading: false,
+ lockLoading: false,
+ deleteLoading: false,
+ removeLoading: false,
+ saveLoading: false,
+ featureCommunityLoading: false,
+ featureLocalLoading: false,
+ banLoading: false,
+ addModLoading: false,
+ addAdminLoading: false,
+ transferLoading: false,
+ imageExpanded: false,
+ });
}
}
+ get postView(): PostView {
+ return this.props.post_view;
+ }
+
render() {
- const post = this.props.post_view.post;
+ const post = this.postView.post;
return (
<div className="post-listing">
<>
{this.listing()}
{this.state.imageExpanded && !this.props.hideImage && this.img}
- {post.url && this.showBody && post.embed_title && (
+ {post.url && this.state.showBody && post.embed_title && (
<MetadataCard post={post} />
)}
{this.showBody && this.body()}
</>
) : (
- <div className="col-12">
- <PostForm
- post_view={this.props.post_view}
- onEdit={this.handleEditPost}
- onCancel={this.handleEditCancel}
- enableNsfw={this.props.enableNsfw}
- enableDownvotes={this.props.enableDownvotes}
- allLanguages={this.props.allLanguages}
- siteLanguages={this.props.siteLanguages}
- />
- </div>
+ <PostForm
+ post_view={this.postView}
+ crossPosts={this.props.crossPosts}
+ onEdit={this.handleEditPost}
+ onCancel={this.handleEditCancel}
+ enableNsfw={this.props.enableNsfw}
+ enableDownvotes={this.props.enableDownvotes}
+ allLanguages={this.props.allLanguages}
+ siteLanguages={this.props.siteLanguages}
+ />
)}
</div>
);
}
body() {
- const body = this.props.post_view.post.body;
+ const body = this.postView.post.body;
return body ? (
<div className="col-12 card my-2 p-2">
{this.state.viewSource ? (
}
get img() {
- const src = this.imageSrc;
- return src ? (
+ return this.imageSrc ? (
<>
<div className="offset-sm-3 my-2 d-none d-sm-block">
- <a href={src} className="d-inline-block">
- <PictrsImage src={src} />
+ <a href={this.imageSrc} className="d-inline-block">
+ <PictrsImage src={this.imageSrc} />
</a>
</div>
<div className="my-2 d-block d-sm-none">
className="d-inline-block"
onClick={linkEvent(this, this.handleImageExpandClick)}
>
- <PictrsImage src={src} />
+ <PictrsImage src={this.imageSrc} />
</a>
</div>
</>
}
imgThumb(src: string) {
- const post_view = this.props.post_view;
+ const post_view = this.postView;
return (
<PictrsImage
src={src}
}
get imageSrc(): string | undefined {
- const post = this.props.post_view.post;
+ const post = this.postView.post;
const url = post.url;
const thumbnail = post.thumbnail_url;
}
thumbnail() {
- const post = this.props.post_view.post;
+ const post = this.postView.post;
const url = post.url;
const thumbnail = post.thumbnail_url;
}
createdLine() {
- const post_view = this.props.post_view;
+ const post_view = this.postView;
const url = post_view.post.url;
const body = post_view.post.body;
return (
<div className={`vote-bar col-1 pr-0 small text-center`}>
<button
className={`btn-animate btn btn-link p-0 ${
- this.state.my_vote === 1 ? "text-info" : "text-muted"
+ this.postView.my_vote == 1 ? "text-info" : "text-muted"
}`}
- onClick={this.handlePostLike}
+ onClick={linkEvent(this, this.handleUpvote)}
data-tippy-content={i18n.t("upvote")}
aria-label={i18n.t("upvote")}
- aria-pressed={this.state.my_vote === 1}
+ aria-pressed={this.postView.my_vote === 1}
>
- <Icon icon="arrow-up1" classes="upvote" />
+ {this.state.upvoteLoading ? (
+ <Spinner />
+ ) : (
+ <Icon icon="arrow-up1" classes="upvote" />
+ )}
</button>
{showScores() ? (
<div
className={`unselectable pointer font-weight-bold text-muted px-1`}
data-tippy-content={this.pointsTippy}
>
- {numToSI(this.state.score)}
+ {numToSI(this.postView.counts.score)}
</div>
) : (
<div className="p-1"></div>
{this.props.enableDownvotes && (
<button
className={`btn-animate btn btn-link p-0 ${
- this.state.my_vote === -1 ? "text-danger" : "text-muted"
+ this.postView.my_vote == -1 ? "text-danger" : "text-muted"
}`}
- onClick={this.handlePostDisLike}
+ onClick={linkEvent(this, this.handleDownvote)}
data-tippy-content={i18n.t("downvote")}
aria-label={i18n.t("downvote")}
- aria-pressed={this.state.my_vote === -1}
+ aria-pressed={this.postView.my_vote === -1}
>
- <Icon icon="arrow-down1" classes="downvote" />
+ {this.state.downvoteLoading ? (
+ <Spinner />
+ ) : (
+ <Icon icon="arrow-down1" classes="downvote" />
+ )}
</button>
)}
</div>
}
get postLink() {
- const post = this.props.post_view.post;
+ const post = this.postView.post;
return (
<Link
className={`d-inline-block ${
}
postTitleLine() {
- const post = this.props.post_view.post;
+ const post = this.postView.post;
const url = post.url;
return (
}
duplicatesLine() {
- const dupes = this.props.duplicates;
+ const dupes = this.props.crossPosts;
return dupes && dupes.length > 0 ? (
<ul className="list-inline mb-1 small text-muted">
<>
}
commentsLine(mobile = false) {
- const post = this.props.post_view.post;
+ const post = this.postView.post;
return (
<div className="d-flex justify-content-start flex-wrap text-muted font-weight-bold mb-1">
postActions(mobile = false) {
// Possible enhancement: Priority+ pattern instead of just hard coding which get hidden behind the show more button.
// Possible enhancement: Make each button a component.
- const post_view = this.props.post_view;
+ const post_view = this.postView;
return (
<>
{this.saveButton}
}
get commentsButton() {
- const post_view = this.props.post_view;
+ const post_view = this.postView;
return (
<Link
className="btn btn-link text-muted py-0 pl-0 text-muted"
}
get unreadCount(): number | undefined {
- const pv = this.props.post_view;
+ const pv = this.postView;
return pv.unread_comments == pv.counts.comments || pv.unread_comments == 0
? undefined
: pv.unread_comments;
<div>
<button
className={`btn-animate btn py-0 px-1 ${
- this.state.my_vote === 1 ? "text-info" : "text-muted"
+ this.postView.my_vote === 1 ? "text-info" : "text-muted"
}`}
{...tippy}
- onClick={this.handlePostLike}
+ onClick={linkEvent(this, this.handleUpvote)}
aria-label={i18n.t("upvote")}
- aria-pressed={this.state.my_vote === 1}
+ aria-pressed={this.postView.my_vote === 1}
>
- <Icon icon="arrow-up1" classes="icon-inline small" />
- {showScores() && (
- <span className="ml-2">{numToSI(this.state.upvotes)}</span>
+ {this.state.upvoteLoading ? (
+ <Spinner />
+ ) : (
+ <>
+ <Icon icon="arrow-up1" classes="icon-inline small" />
+ {showScores() && (
+ <span className="ml-2">
+ {numToSI(this.postView.counts.upvotes)}
+ </span>
+ )}
+ </>
)}
</button>
{this.props.enableDownvotes && (
<button
className={`ml-2 btn-animate btn py-0 px-1 ${
- this.state.my_vote === -1 ? "text-danger" : "text-muted"
+ this.postView.my_vote === -1 ? "text-danger" : "text-muted"
}`}
- onClick={this.handlePostDisLike}
+ onClick={linkEvent(this, this.handleDownvote)}
{...tippy}
aria-label={i18n.t("downvote")}
- aria-pressed={this.state.my_vote === -1}
+ aria-pressed={this.postView.my_vote === -1}
>
- <Icon icon="arrow-down1" classes="icon-inline small" />
- {showScores() && (
- <span
- className={classNames("ml-2", {
- invisible: this.state.downvotes === 0,
- })}
- >
- {numToSI(this.state.downvotes)}
- </span>
+ {this.state.downvoteLoading ? (
+ <Spinner />
+ ) : (
+ <>
+ <Icon icon="arrow-down1" classes="icon-inline small" />
+ {showScores() && (
+ <span
+ className={classNames("ml-2", {
+ invisible: this.postView.counts.downvotes === 0,
+ })}
+ >
+ {numToSI(this.postView.counts.downvotes)}
+ </span>
+ )}
+ </>
)}
</button>
)}
}
get saveButton() {
- const saved = this.props.post_view.saved;
+ const saved = this.postView.saved;
const label = saved ? i18n.t("unsave") : i18n.t("save");
return (
<button
data-tippy-content={label}
aria-label={label}
>
- <Icon
- icon="star"
- classes={classNames({ "text-warning": saved })}
- inline
- />
+ {this.state.saveLoading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="star"
+ classes={classNames({ "text-warning": saved })}
+ inline
+ />
+ )}
</button>
);
}
return (
<button
className="btn btn-link btn-animate text-muted py-0"
- onClick={linkEvent(this, this.handleBlockUserClick)}
+ onClick={linkEvent(this, this.handleBlockPersonClick)}
data-tippy-content={i18n.t("block_user")}
aria-label={i18n.t("block_user")}
>
- <Icon icon="slash" inline />
+ {this.state.blockLoading ? <Spinner /> : <Icon icon="slash" inline />}
</button>
);
}
}
get deleteButton() {
- const deleted = this.props.post_view.post.deleted;
+ const deleted = this.postView.post.deleted;
const label = !deleted ? i18n.t("delete") : i18n.t("restore");
return (
<button
data-tippy-content={label}
aria-label={label}
>
- <Icon
- icon="trash"
- classes={classNames({ "text-danger": deleted })}
- inline
- />
+ {this.state.deleteLoading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="trash"
+ classes={classNames({ "text-danger": deleted })}
+ inline
+ />
+ )}
</button>
);
}
}
get lockButton() {
- const locked = this.props.post_view.post.locked;
+ const locked = this.postView.post.locked;
const label = locked ? i18n.t("unlock") : i18n.t("lock");
return (
<button
data-tippy-content={label}
aria-label={label}
>
- <Icon
- icon="lock"
- classes={classNames({ "text-danger": locked })}
- inline
- />
+ {this.state.lockLoading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="lock"
+ classes={classNames({ "text-danger": locked })}
+ inline
+ />
+ )}
</button>
);
}
get featureButton() {
- const featuredCommunity = this.props.post_view.post.featured_community;
+ const featuredCommunity = this.postView.post.featured_community;
const labelCommunity = featuredCommunity
? i18n.t("unfeature_from_community")
: i18n.t("feature_in_community");
- const featuredLocal = this.props.post_view.post.featured_local;
+ const featuredLocal = this.postView.post.featured_local;
const labelLocal = featuredLocal
? i18n.t("unfeature_from_local")
: i18n.t("feature_in_local");
data-tippy-content={labelCommunity}
aria-label={labelCommunity}
>
- <Icon
- icon="pin"
- classes={classNames({ "text-success": featuredCommunity })}
- inline
- />{" "}
- Community
+ {this.state.featureCommunityLoading ? (
+ <Spinner />
+ ) : (
+ <span>
+ <Icon
+ icon="pin"
+ classes={classNames({ "text-success": featuredCommunity })}
+ inline
+ />
+ {i18n.t("community")}
+ </span>
+ )}
</button>
{amAdmin() && (
<button
data-tippy-content={labelLocal}
aria-label={labelLocal}
>
- <Icon
- icon="pin"
- classes={classNames({ "text-success": featuredLocal })}
- inline
- />{" "}
- Local
+ {this.state.featureLocalLoading ? (
+ <Spinner />
+ ) : (
+ <span>
+ <Icon
+ icon="pin"
+ classes={classNames({ "text-success": featuredLocal })}
+ inline
+ />
+ {i18n.t("local")}
+ </span>
+ )}
</button>
)}
</span>
}
get modRemoveButton() {
- const removed = this.props.post_view.post.removed;
+ const removed = this.postView.post.removed;
return (
<button
className="btn btn-link btn-animate text-muted py-0"
)}
>
{/* TODO: Find an icon for this. */}
- {!removed ? i18n.t("remove") : i18n.t("restore")}
+ {this.state.removeLoading ? (
+ <Spinner />
+ ) : !removed ? (
+ i18n.t("remove")
+ ) : (
+ i18n.t("restore")
+ )}
</button>
);
}
*/
userActionsLine() {
// TODO: make nicer
- const post_view = this.props.post_view;
+ const post_view = this.postView;
return (
this.state.showAdvanced && (
<>
)}
aria-label={i18n.t("unban")}
>
- {i18n.t("unban")}
+ {this.state.banLoading ? <Spinner /> : i18n.t("unban")}
</button>
))}
{!post_view.creator_banned_from_community && (
: i18n.t("appoint_as_mod")
}
>
- {this.creatorIsMod_
- ? i18n.t("remove_as_mod")
- : i18n.t("appoint_as_mod")}
+ {this.state.addModLoading ? (
+ <Spinner />
+ ) : this.creatorIsMod_ ? (
+ i18n.t("remove_as_mod")
+ ) : (
+ i18n.t("appoint_as_mod")
+ )}
</button>
)}
</>
aria-label={i18n.t("yes")}
onClick={linkEvent(this, this.handleTransferCommunity)}
>
- {i18n.t("yes")}
+ {this.state.transferLoading ? <Spinner /> : i18n.t("yes")}
</button>
<button
className="btn btn-link btn-animate text-muted py-0 d-inline-block"
onClick={linkEvent(this, this.handleModBanSubmit)}
aria-label={i18n.t("unban_from_site")}
>
- {i18n.t("unban_from_site")}
+ {this.state.banLoading ? (
+ <Spinner />
+ ) : (
+ i18n.t("unban_from_site")
+ )}
</button>
)}
<button
: i18n.t("appoint_as_admin")
}
>
- {this.creatorIsAdmin_
- ? i18n.t("remove_as_admin")
- : i18n.t("appoint_as_admin")}
+ {this.state.addAdminLoading ? (
+ <Spinner />
+ ) : this.creatorIsAdmin_ ? (
+ i18n.t("remove_as_admin")
+ ) : (
+ i18n.t("appoint_as_admin")
+ )}
</button>
)}
</>
}
removeAndBanDialogs() {
- const post = this.props.post_view;
+ const post = this.postView;
const purgeTypeText =
this.state.purgeType == PurgeType.Post
? i18n.t("purge_post")
className="btn btn-secondary"
aria-label={i18n.t("remove_post")}
>
- {i18n.t("remove_post")}
+ {this.state.removeLoading ? <Spinner /> : i18n.t("remove_post")}
</button>
</form>
)}
className="btn btn-secondary"
aria-label={i18n.t("ban")}
>
- {i18n.t("ban")} {post.creator.name}
+ {this.state.banLoading ? (
+ <Spinner />
+ ) : (
+ <span>
+ {i18n.t("ban")} {post.creator.name}
+ </span>
+ )}
</button>
</div>
</form>
className="btn btn-secondary"
aria-label={i18n.t("create_report")}
>
- {i18n.t("create_report")}
+ {this.state.reportLoading ? <Spinner /> : i18n.t("create_report")}
</button>
</form>
)}
className="btn btn-secondary"
aria-label={purgeTypeText}
>
- {purgeTypeText}
+ {this.state.purgeLoading ? <Spinner /> : { purgeTypeText }}
</button>
)}
</form>
}
mobileThumbnail() {
- const post = this.props.post_view.post;
+ const post = this.postView.post;
return post.thumbnail_url || (post.url && isImage(post.url)) ? (
<div className="row">
<div className={`${this.state.imageExpanded ? "col-12" : "col-8"}`}>
}
showMobilePreview() {
- const body = this.props.post_view.post.body;
+ const body = this.postView.post.body;
return !this.showBody && body ? (
<div className="md-div mb-1 preview-lines">{body}</div>
) : (
private get myPost(): boolean {
return (
- this.props.post_view.creator.id ==
+ this.postView.creator.id ==
UserService.Instance.myUserInfo?.local_user_view.person.id
);
}
-
- handlePostLike(event: any) {
- event.preventDefault();
- if (!UserService.Instance.myUserInfo) {
- this.context.router.history.push(`/login`);
- }
-
- const myVote = this.state.my_vote;
- const newVote = myVote == 1 ? 0 : 1;
-
- if (myVote == 1) {
- this.setState({
- score: this.state.score - 1,
- upvotes: this.state.upvotes - 1,
- });
- } else if (myVote == -1) {
- this.setState({
- score: this.state.score + 2,
- upvotes: this.state.upvotes + 1,
- downvotes: this.state.downvotes - 1,
- });
- } else {
- this.setState({
- score: this.state.score + 1,
- upvotes: this.state.upvotes + 1,
- });
- }
-
- this.setState({ my_vote: newVote });
-
- const auth = myAuth();
- if (auth) {
- const form: CreatePostLike = {
- post_id: this.props.post_view.post.id,
- score: newVote,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.likePost(form));
- this.setState(this.state);
- }
- setupTippy();
- }
-
- handlePostDisLike(event: any) {
- event.preventDefault();
- if (!UserService.Instance.myUserInfo) {
- this.context.router.history.push(`/login`);
- }
-
- const myVote = this.state.my_vote;
- const newVote = myVote == -1 ? 0 : -1;
-
- if (myVote == 1) {
- this.setState({
- score: this.state.score - 2,
- upvotes: this.state.upvotes - 1,
- downvotes: this.state.downvotes + 1,
- });
- } else if (myVote == -1) {
- this.setState({
- score: this.state.score + 1,
- downvotes: this.state.downvotes - 1,
- });
- } else {
- this.setState({
- score: this.state.score - 1,
- downvotes: this.state.downvotes + 1,
- });
- }
-
- this.setState({ my_vote: newVote });
-
- const auth = myAuth();
- if (auth) {
- const form: CreatePostLike = {
- post_id: this.props.post_view.post.id,
- score: newVote,
- auth,
- };
-
- WebSocketService.Instance.send(wsClient.likePost(form));
- this.setState(this.state);
- }
- setupTippy();
- }
-
handleEditClick(i: PostListing) {
i.setState({ showEdit: true });
}
}
// The actual editing is done in the receive for post
- handleEditPost() {
+ handleEditPost(form: EditPost) {
this.setState({ showEdit: false });
+ this.props.onPostEdit(form);
}
handleShare(i: PostListing) {
handleReportSubmit(i: PostListing, event: any) {
event.preventDefault();
- const auth = myAuth();
- const reason = i.state.reportReason;
- if (auth && reason) {
- const form: CreatePostReport = {
- post_id: i.props.post_view.post.id,
- reason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.createPostReport(form));
-
- i.setState({ showReportDialog: false });
- }
+ i.setState({ reportLoading: true });
+ i.props.onPostReport({
+ post_id: i.postView.post.id,
+ reason: i.state.reportReason ?? "",
+ auth: myAuthRequired(),
+ });
}
- handleBlockUserClick(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const blockUserForm: BlockPerson = {
- person_id: i.props.post_view.creator.id,
- block: true,
- auth,
- };
- WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
- }
+ handleBlockPersonClick(i: PostListing) {
+ i.setState({ blockLoading: true });
+ i.props.onBlockPerson({
+ person_id: i.postView.creator.id,
+ block: true,
+ auth: myAuthRequired(),
+ });
}
handleDeleteClick(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const deleteForm: DeletePost = {
- post_id: i.props.post_view.post.id,
- deleted: !i.props.post_view.post.deleted,
- auth,
- };
- WebSocketService.Instance.send(wsClient.deletePost(deleteForm));
- }
+ i.setState({ deleteLoading: true });
+ i.props.onDeletePost({
+ post_id: i.postView.post.id,
+ deleted: !i.postView.post.deleted,
+ auth: myAuthRequired(),
+ });
}
handleSavePostClick(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const saved =
- i.props.post_view.saved == undefined ? true : !i.props.post_view.saved;
- const form: SavePost = {
- post_id: i.props.post_view.post.id,
- save: saved,
- auth,
- };
- WebSocketService.Instance.send(wsClient.savePost(form));
- }
+ i.setState({ saveLoading: true });
+ i.props.onSavePost({
+ post_id: i.postView.post.id,
+ save: !i.postView.saved,
+ auth: myAuthRequired(),
+ });
}
get crossPostParams(): PostFormParams {
const queryParams: PostFormParams = {};
- const { name, url } = this.props.post_view.post;
+ const { name, url } = this.postView.post;
queryParams.name = name;
}
crossPostBody(): string | undefined {
- const post = this.props.post_view.post;
+ const post = this.postView.post;
const body = post.body;
return body
handleModRemoveSubmit(i: PostListing, event: any) {
event.preventDefault();
-
- const auth = myAuth();
- if (auth) {
- const form: RemovePost = {
- post_id: i.props.post_view.post.id,
- removed: !i.props.post_view.post.removed,
- reason: i.state.removeReason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.removePost(form));
- i.setState({ showRemoveDialog: false });
- }
+ i.setState({ removeLoading: true });
+ i.props.onRemovePost({
+ post_id: i.postView.post.id,
+ removed: !i.postView.post.removed,
+ auth: myAuthRequired(),
+ });
}
handleModLock(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const form: LockPost = {
- post_id: i.props.post_view.post.id,
- locked: !i.props.post_view.post.locked,
- auth,
- };
- WebSocketService.Instance.send(wsClient.lockPost(form));
- }
+ i.setState({ lockLoading: true });
+ i.props.onLockPost({
+ post_id: i.postView.post.id,
+ locked: !i.postView.post.locked,
+ auth: myAuthRequired(),
+ });
}
handleModFeaturePostLocal(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const form: FeaturePost = {
- post_id: i.props.post_view.post.id,
- feature_type: "Local",
- featured: !i.props.post_view.post.featured_local,
- auth,
- };
- WebSocketService.Instance.send(wsClient.featurePost(form));
- }
+ i.setState({ featureLocalLoading: true });
+ i.props.onFeaturePost({
+ post_id: i.postView.post.id,
+ featured: !i.postView.post.featured_local,
+ feature_type: "Local",
+ auth: myAuthRequired(),
+ });
}
handleModFeaturePostCommunity(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const form: FeaturePost = {
- post_id: i.props.post_view.post.id,
- feature_type: "Community",
- featured: !i.props.post_view.post.featured_community,
- auth,
- };
- WebSocketService.Instance.send(wsClient.featurePost(form));
- }
+ i.setState({ featureCommunityLoading: true });
+ i.props.onFeaturePost({
+ post_id: i.postView.post.id,
+ featured: !i.postView.post.featured_community,
+ feature_type: "Community",
+ auth: myAuthRequired(),
+ });
}
handleModBanFromCommunityShow(i: PostListing) {
handlePurgeSubmit(i: PostListing, event: any) {
event.preventDefault();
-
- const auth = myAuth();
- if (auth) {
- if (i.state.purgeType == PurgeType.Person) {
- const form: PurgePerson = {
- person_id: i.props.post_view.creator.id,
- reason: i.state.purgeReason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.purgePerson(form));
- } else if (i.state.purgeType == PurgeType.Post) {
- const form: PurgePost = {
- post_id: i.props.post_view.post.id,
- reason: i.state.purgeReason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.purgePost(form));
- }
-
- i.setState({ purgeLoading: true });
+ i.setState({ purgeLoading: true });
+ if (i.state.purgeType == PurgeType.Person) {
+ i.props.onPurgePerson({
+ person_id: i.postView.creator.id,
+ reason: i.state.purgeReason,
+ auth: myAuthRequired(),
+ });
+ } else if (i.state.purgeType == PurgeType.Post) {
+ i.props.onPurgePost({
+ post_id: i.postView.post.id,
+ reason: i.state.purgeReason,
+ auth: myAuthRequired(),
+ });
}
}
i.setState({ banExpireDays: event.target.value });
}
- handleModBanFromCommunitySubmit(i: PostListing) {
+ handleModBanFromCommunitySubmit(i: PostListing, event: any) {
i.setState({ banType: BanType.Community });
- i.handleModBanBothSubmit(i);
+ i.handleModBanBothSubmit(i, event);
}
- handleModBanSubmit(i: PostListing) {
+ handleModBanSubmit(i: PostListing, event: any) {
i.setState({ banType: BanType.Site });
- i.handleModBanBothSubmit(i);
- }
-
- handleModBanBothSubmit(i: PostListing, event?: any) {
- if (event) event.preventDefault();
- const auth = myAuth();
- if (auth) {
- const ban = !i.props.post_view.creator_banned_from_community;
- const person_id = i.props.post_view.creator.id;
- const remove_data = i.state.removeData;
- const reason = i.state.banReason;
- const expires = futureDaysToUnixTime(i.state.banExpireDays);
-
- if (i.state.banType == BanType.Community) {
- // If its an unban, restore all their data
- if (ban == false) {
- i.setState({ removeData: false });
- }
-
- const form: BanFromCommunity = {
- person_id,
- community_id: i.props.post_view.community.id,
- ban,
- remove_data,
- reason,
- expires,
- auth,
- };
- WebSocketService.Instance.send(wsClient.banFromCommunity(form));
- } else {
- // If its an unban, restore all their data
- const ban = !i.props.post_view.creator.banned;
- if (ban == false) {
- i.setState({ removeData: false });
- }
- const form: BanPerson = {
- person_id,
- ban,
- remove_data,
- reason,
- expires,
- auth,
- };
- WebSocketService.Instance.send(wsClient.banPerson(form));
- }
+ i.handleModBanBothSubmit(i, event);
+ }
+
+ handleModBanBothSubmit(i: PostListing, event: any) {
+ event.preventDefault();
+ i.setState({ banLoading: true });
- i.setState({ showBanDialog: false });
+ const ban = !i.props.post_view.creator_banned_from_community;
+ // If its an unban, restore all their data
+ if (ban == false) {
+ i.setState({ removeData: false });
+ }
+ const person_id = i.props.post_view.creator.id;
+ const remove_data = i.state.removeData;
+ const reason = i.state.banReason;
+ const expires = futureDaysToUnixTime(i.state.banExpireDays);
+
+ if (i.state.banType == BanType.Community) {
+ const community_id = i.postView.community.id;
+ i.props.onBanPersonFromCommunity({
+ community_id,
+ person_id,
+ ban,
+ remove_data,
+ reason,
+ expires,
+ auth: myAuthRequired(),
+ });
+ } else {
+ i.props.onBanPerson({
+ person_id,
+ ban,
+ remove_data,
+ reason,
+ expires,
+ auth: myAuthRequired(),
+ });
}
}
handleAddModToCommunity(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const form: AddModToCommunity = {
- person_id: i.props.post_view.creator.id,
- community_id: i.props.post_view.community.id,
- added: !i.creatorIsMod_,
- auth,
- };
- WebSocketService.Instance.send(wsClient.addModToCommunity(form));
- i.setState(i.state);
- }
+ i.setState({ addModLoading: true });
+ i.props.onAddModToCommunity({
+ community_id: i.postView.community.id,
+ person_id: i.postView.creator.id,
+ added: !i.creatorIsMod_,
+ auth: myAuthRequired(),
+ });
}
handleAddAdmin(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const form: AddAdmin = {
- person_id: i.props.post_view.creator.id,
- added: !i.creatorIsAdmin_,
- auth,
- };
- WebSocketService.Instance.send(wsClient.addAdmin(form));
- i.setState(i.state);
- }
+ i.setState({ addAdminLoading: true });
+ i.props.onAddAdmin({
+ person_id: i.postView.creator.id,
+ added: !i.creatorIsAdmin_,
+ auth: myAuthRequired(),
+ });
}
handleShowConfirmTransferCommunity(i: PostListing) {
}
handleTransferCommunity(i: PostListing) {
- const auth = myAuth();
- if (auth) {
- const form: TransferCommunity = {
- community_id: i.props.post_view.community.id,
- person_id: i.props.post_view.creator.id,
- auth,
- };
- WebSocketService.Instance.send(wsClient.transferCommunity(form));
- i.setState({ showConfirmTransferCommunity: false });
- }
+ i.setState({ transferLoading: true });
+ i.props.onTransferCommunity({
+ community_id: i.postView.community.id,
+ person_id: i.postView.creator.id,
+ auth: myAuthRequired(),
+ });
}
handleShowConfirmTransferSite(i: PostListing) {
setupTippy();
}
+ handleUpvote(i: PostListing) {
+ i.setState({ upvoteLoading: true });
+ i.props.onPostVote({
+ post_id: i.postView.post.id,
+ score: newVote(VoteType.Upvote, i.props.post_view.my_vote),
+ auth: myAuthRequired(),
+ });
+ }
+
+ handleDownvote(i: PostListing) {
+ i.setState({ downvoteLoading: true });
+ i.props.onPostVote({
+ post_id: i.postView.post.id,
+ score: newVote(VoteType.Downvote, i.props.post_view.my_vote),
+ auth: myAuthRequired(),
+ });
+ }
+
get pointsTippy(): string {
const points = i18n.t("number_of_points", {
- count: Number(this.state.score),
- formattedCount: Number(this.state.score),
+ count: Number(this.postView.counts.score),
+ formattedCount: Number(this.postView.counts.score),
});
const upvotes = i18n.t("number_of_upvotes", {
- count: Number(this.state.upvotes),
- formattedCount: Number(this.state.upvotes),
+ count: Number(this.postView.counts.upvotes),
+ formattedCount: Number(this.postView.counts.upvotes),
});
const downvotes = i18n.t("number_of_downvotes", {
- count: Number(this.state.downvotes),
- formattedCount: Number(this.state.downvotes),
+ count: Number(this.postView.counts.downvotes),
+ formattedCount: Number(this.postView.counts.downvotes),
});
return `${points} • ${upvotes} • ${downvotes}`;
get canModOnSelf_(): boolean {
return canMod(
- this.props.post_view.creator.id,
+ this.postView.creator.id,
this.props.moderators,
this.props.admins,
undefined,
get canMod_(): boolean {
return canMod(
- this.props.post_view.creator.id,
+ this.postView.creator.id,
this.props.moderators,
this.props.admins
);
}
get canAdmin_(): boolean {
- return canAdmin(this.props.post_view.creator.id, this.props.admins);
+ return canAdmin(this.postView.creator.id, this.props.admins);
}
get creatorIsMod_(): boolean {
- return isMod(this.props.post_view.creator.id, this.props.moderators);
+ return isMod(this.postView.creator.id, this.props.moderators);
}
get creatorIsAdmin_(): boolean {
- return isAdmin(this.props.post_view.creator.id, this.props.admins);
+ return isAdmin(this.postView.creator.id, this.props.admins);
}
}
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
-import { Language, PostView } from "lemmy-js-client";
+import {
+ AddAdmin,
+ AddModToCommunity,
+ BanFromCommunity,
+ BanPerson,
+ BlockPerson,
+ CreatePostLike,
+ CreatePostReport,
+ DeletePost,
+ EditPost,
+ FeaturePost,
+ Language,
+ LockPost,
+ PostView,
+ PurgePerson,
+ PurgePost,
+ RemovePost,
+ SavePost,
+ TransferCommunity,
+} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { PostListing } from "./post-listing";
removeDuplicates?: boolean;
enableDownvotes?: boolean;
enableNsfw?: boolean;
+ viewOnly?: boolean;
+ onPostEdit(form: EditPost): void;
+ onPostVote(form: CreatePostLike): void;
+ onPostReport(form: CreatePostReport): void;
+ onBlockPerson(form: BlockPerson): void;
+ onLockPost(form: LockPost): void;
+ onDeletePost(form: DeletePost): void;
+ onRemovePost(form: RemovePost): void;
+ onSavePost(form: SavePost): void;
+ onFeaturePost(form: FeaturePost): void;
+ onPurgePerson(form: PurgePerson): void;
+ onPurgePost(form: PurgePost): void;
+ onBanPersonFromCommunity(form: BanFromCommunity): void;
+ onBanPerson(form: BanPerson): void;
+ onAddModToCommunity(form: AddModToCommunity): void;
+ onAddAdmin(form: AddAdmin): void;
+ onTransferCommunity(form: TransferCommunity): void;
}
export class PostListings extends Component<PostListingsProps, any> {
<>
<PostListing
post_view={post_view}
- duplicates={this.duplicatesMap.get(post_view.post.id)}
+ crossPosts={this.duplicatesMap.get(post_view.post.id)}
showCommunity={this.props.showCommunity}
enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw}
+ viewOnly={this.props.viewOnly}
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
+ onPostEdit={this.props.onPostEdit}
+ onPostVote={this.props.onPostVote}
+ onPostReport={this.props.onPostReport}
+ onBlockPerson={this.props.onBlockPerson}
+ onLockPost={this.props.onLockPost}
+ onDeletePost={this.props.onDeletePost}
+ onRemovePost={this.props.onRemovePost}
+ onSavePost={this.props.onSavePost}
+ onFeaturePost={this.props.onFeaturePost}
+ onPurgePerson={this.props.onPurgePerson}
+ onPurgePost={this.props.onPurgePost}
+ onBanPersonFromCommunity={this.props.onBanPersonFromCommunity}
+ onBanPerson={this.props.onBanPerson}
+ onAddModToCommunity={this.props.onAddModToCommunity}
+ onAddAdmin={this.props.onAddAdmin}
+ onTransferCommunity={this.props.onTransferCommunity}
/>
<hr className="my-3" />
</>
removeDuplicates(): PostView[] {
// Must use a spread to clone the props, because splice will fail below otherwise.
- const posts = [...this.props.posts];
+ const posts = [...this.props.posts].filter(empty => empty);
// A map from post url to list of posts (dupes)
const urlMap = new Map<string, PostView[]>();
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import { PostReportView, PostView, ResolvePostReport } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
-import { myAuth, wsClient } from "../../utils";
-import { Icon } from "../common/icon";
+import { myAuthRequired } from "../../utils";
+import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing";
import { PostListing } from "./post-listing";
interface PostReportProps {
report: PostReportView;
+ onResolveReport(form: ResolvePostReport): void;
}
-export class PostReport extends Component<PostReportProps, any> {
+interface PostReportState {
+ loading: boolean;
+}
+
+export class PostReport extends Component<PostReportProps, PostReportState> {
+ state: PostReportState = {
+ loading: false,
+ };
+
constructor(props: any, context: any) {
super(props, context);
}
+ componentWillReceiveProps(
+ nextProps: Readonly<{ children?: InfernoNode } & PostReportProps>
+ ): void {
+ if (this.props != nextProps) {
+ this.setState({ loading: false });
+ }
+ }
+
render() {
const r = this.props.report;
const resolver = r.resolver;
allLanguages={[]}
siteLanguages={[]}
hideImage
+ // All of these are unused, since its view only
+ onPostEdit={() => {}}
+ onPostVote={() => {}}
+ onPostReport={() => {}}
+ onBlockPerson={() => {}}
+ onLockPost={() => {}}
+ onDeletePost={() => {}}
+ onRemovePost={() => {}}
+ onSavePost={() => {}}
+ onFeaturePost={() => {}}
+ onPurgePerson={() => {}}
+ onPurgePost={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
/>
<div>
{i18n.t("reporter")}: <PersonListing person={r.creator} />
data-tippy-content={tippyContent}
aria-label={tippyContent}
>
- <Icon
- icon="check"
- classes={`icon-inline ${
- r.post_report.resolved ? "text-success" : "text-danger"
- }`}
- />
+ {this.state.loading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="check"
+ classes={`icon-inline ${
+ r.post_report.resolved ? "text-success" : "text-danger"
+ }`}
+ />
+ )}
</button>
</div>
);
}
handleResolveReport(i: PostReport) {
- const auth = myAuth();
- if (auth) {
- const form: ResolvePostReport = {
- report_id: i.props.report.post_report.id,
- resolved: !i.props.report.post_report.resolved,
- auth,
- };
- WebSocketService.Instance.send(wsClient.resolvePostReport(form));
- }
+ i.setState({ loading: true });
+ i.props.onResolveReport({
+ report_id: i.props.report.post_report.id,
+ resolved: !i.props.report.post_report.resolved,
+ auth: myAuthRequired(),
+ });
}
}
import autosize from "autosize";
import { Component, createRef, linkEvent, RefObject } from "inferno";
import {
- AddAdminResponse,
+ AddAdmin,
+ AddModToCommunity,
AddModToCommunityResponse,
+ BanFromCommunity,
BanFromCommunityResponse,
+ BanPerson,
BanPersonResponse,
- BlockPersonResponse,
- CommentReportResponse,
+ BlockCommunity,
+ BlockPerson,
+ CommentId,
+ CommentReplyResponse,
CommentResponse,
CommentSortType,
CommunityResponse,
+ CreateComment,
+ CreateCommentLike,
+ CreateCommentReport,
+ CreatePostLike,
+ CreatePostReport,
+ DeleteComment,
+ DeleteCommunity,
+ DeletePost,
+ DistinguishComment,
+ EditComment,
+ EditCommunity,
+ EditPost,
+ FeaturePost,
+ FollowCommunity,
GetComments,
GetCommentsResponse,
GetCommunityResponse,
GetPost,
GetPostResponse,
GetSiteResponse,
- PostReportResponse,
+ LockPost,
+ MarkCommentReplyAsRead,
+ MarkPersonMentionAsRead,
PostResponse,
- PostView,
+ PurgeComment,
+ PurgeCommunity,
PurgeItemResponse,
- Search,
- SearchResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ PurgePerson,
+ PurgePost,
+ RemoveComment,
+ RemoveCommunity,
+ RemovePost,
+ SaveComment,
+ SavePost,
+ TransferCommunity,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import {
CommentNodeI,
CommentViewType,
InitialFetchRequest,
} from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
+import { UserService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
buildCommentsTree,
commentsToFlatNodes,
commentTreeMaxDepth,
- createCommentLikeRes,
- createPostLikeRes,
debounce,
- editCommentRes,
+ editComment,
+ editWith,
enableDownvotes,
enableNsfw,
getCommentIdFromProps,
getCommentParentId,
getDepthFromComment,
getIdFromProps,
- insertCommentIntoTree,
isBrowser,
isImage,
myAuth,
restoreScrollPosition,
- saveCommentRes,
saveScrollPosition,
setIsoData,
setupTippy,
toast,
- trendingFetchLimit,
+ updateCommunityBlock,
updatePersonBlock,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { CommentForm } from "../comment/comment-form";
import { CommentNodes } from "../comment/comment-nodes";
interface PostState {
postId?: number;
commentId?: number;
- postRes?: GetPostResponse;
- commentsRes?: GetCommentsResponse;
- commentTree: CommentNodeI[];
+ postRes: RequestState<GetPostResponse>;
+ commentsRes: RequestState<GetCommentsResponse>;
commentSort: CommentSortType;
commentViewType: CommentViewType;
scrolled?: boolean;
- loading: boolean;
- crossPosts?: PostView[];
siteRes: GetSiteResponse;
commentSectionRef?: RefObject<HTMLDivElement>;
showSidebarMobile: boolean;
maxCommentsShown: number;
+ finished: Map<CommentId, boolean | undefined>;
+ isIsomorphic: boolean;
}
export class Post extends Component<any, PostState> {
- private subscription?: Subscription;
private isoData = setIsoData(this.context);
private commentScrollDebounced: () => void;
state: PostState = {
+ postRes: { state: "empty" },
+ commentsRes: { state: "empty" },
postId: getIdFromProps(this.props),
commentId: getCommentIdFromProps(this.props),
- commentTree: [],
commentSort: "Hot",
commentViewType: CommentViewType.Tree,
scrolled: false,
- loading: true,
siteRes: this.isoData.site_res,
showSidebarMobile: false,
maxCommentsShown: commentsShownInterval,
+ finished: new Map(),
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
super(props, context);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleDeleteCommunityClick =
+ this.handleDeleteCommunityClick.bind(this);
+ this.handleEditCommunity = this.handleEditCommunity.bind(this);
+ this.handleFollow = this.handleFollow.bind(this);
+ this.handleModRemoveCommunity = this.handleModRemoveCommunity.bind(this);
+ this.handleCreateComment = this.handleCreateComment.bind(this);
+ this.handleEditComment = this.handleEditComment.bind(this);
+ this.handleSaveComment = this.handleSaveComment.bind(this);
+ this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
+ this.handleBlockPerson = this.handleBlockPerson.bind(this);
+ this.handleDeleteComment = this.handleDeleteComment.bind(this);
+ this.handleRemoveComment = this.handleRemoveComment.bind(this);
+ this.handleCommentVote = this.handleCommentVote.bind(this);
+ this.handleAddModToCommunity = this.handleAddModToCommunity.bind(this);
+ this.handleAddAdmin = this.handleAddAdmin.bind(this);
+ this.handlePurgePerson = this.handlePurgePerson.bind(this);
+ this.handlePurgeComment = this.handlePurgeComment.bind(this);
+ this.handleCommentReport = this.handleCommentReport.bind(this);
+ this.handleDistinguishComment = this.handleDistinguishComment.bind(this);
+ this.handleTransferCommunity = this.handleTransferCommunity.bind(this);
+ this.handleFetchChildren = this.handleFetchChildren.bind(this);
+ this.handleCommentReplyRead = this.handleCommentReplyRead.bind(this);
+ this.handlePersonMentionRead = this.handlePersonMentionRead.bind(this);
+ this.handleBanFromCommunity = this.handleBanFromCommunity.bind(this);
+ this.handleBanPerson = this.handleBanPerson.bind(this);
+ this.handlePostEdit = this.handlePostEdit.bind(this);
+ this.handlePostVote = this.handlePostVote.bind(this);
+ this.handlePostReport = this.handlePostReport.bind(this);
+ this.handleLockPost = this.handleLockPost.bind(this);
+ this.handleDeletePost = this.handleDeletePost.bind(this);
+ this.handleRemovePost = this.handleRemovePost.bind(this);
+ this.handleSavePost = this.handleSavePost.bind(this);
+ this.handlePurgePost = this.handlePurgePost.bind(this);
+ this.handleFeaturePost = this.handleFeaturePost.bind(this);
this.state = { ...this.state, commentSectionRef: createRef() };
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
+ const [postRes, commentsRes] = this.isoData.routeData;
+
this.state = {
...this.state,
- postRes: this.isoData.routeData[0] as GetPostResponse,
- commentsRes: this.isoData.routeData[1] as GetCommentsResponse,
+ postRes,
+ commentsRes,
+ isIsomorphic: true,
};
- if (this.state.commentsRes) {
- this.state = {
- ...this.state,
- commentTree: buildCommentsTree(
- this.state.commentsRes.comments,
- !!this.state.commentId
- ),
- };
- }
-
- this.state = { ...this.state, loading: false };
-
if (isBrowser()) {
- if (this.state.postRes) {
- WebSocketService.Instance.send(
- wsClient.communityJoin({
- community_id: this.state.postRes.community_view.community.id,
- })
- );
- }
-
- if (this.state.postId) {
- WebSocketService.Instance.send(
- wsClient.postJoin({ post_id: this.state.postId })
- );
- }
-
- this.fetchCrossPosts();
-
if (this.checkScrollIntoCommentsParam) {
this.scrollIntoCommentSection();
}
}
- } else {
- this.fetchPost();
}
}
- fetchPost() {
- const auth = myAuth(false);
- const postForm: GetPost = {
- id: this.state.postId,
- comment_id: this.state.commentId,
- auth,
- };
- WebSocketService.Instance.send(wsClient.getPost(postForm));
+ async fetchPost() {
+ this.setState({
+ postRes: { state: "loading" },
+ commentsRes: { state: "loading" },
+ });
- const commentsForm: GetComments = {
- post_id: this.state.postId,
- parent_id: this.state.commentId,
- max_depth: commentTreeMaxDepth,
- sort: this.state.commentSort,
- type_: "All",
- saved_only: false,
- auth,
- };
- WebSocketService.Instance.send(wsClient.getComments(commentsForm));
- }
-
- fetchCrossPosts() {
- const q = this.state.postRes?.post_view.post.url;
- if (q) {
- const form: Search = {
- q,
- type_: "Url",
- sort: "TopAll",
- listing_type: "All",
- page: 1,
- limit: trendingFetchLimit,
- auth: myAuth(false),
- };
- WebSocketService.Instance.send(wsClient.search(form));
+ const auth = myAuth();
+
+ this.setState({
+ postRes: await HttpService.client.getPost({
+ id: this.state.postId,
+ comment_id: this.state.commentId,
+ auth,
+ }),
+ commentsRes: await HttpService.client.getComments({
+ post_id: this.state.postId,
+ parent_id: this.state.commentId,
+ max_depth: commentTreeMaxDepth,
+ sort: this.state.commentSort,
+ type_: "All",
+ saved_only: false,
+ auth,
+ }),
+ });
+
+ setupTippy();
+
+ if (!this.state.commentId) restoreScrollPosition(this.context);
+
+ if (this.checkScrollIntoCommentsParam) {
+ this.scrollIntoCommentSection();
}
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- const pathSplit = req.path.split("/");
- const promises: Promise<any>[] = [];
+ static fetchInitialData({
+ auth,
+ client,
+ path,
+ }: InitialFetchRequest): Promise<any>[] {
+ const pathSplit = path.split("/");
+ const promises: Promise<RequestState<any>>[] = [];
const pathType = pathSplit.at(1);
const id = pathSplit.at(2) ? Number(pathSplit.at(2)) : undefined;
- const auth = req.auth;
const postForm: GetPost = {
auth,
};
// Set the correct id based on the path type
- if (pathType == "post") {
+ if (pathType === "post") {
postForm.id = id;
commentsForm.post_id = id;
} else {
commentsForm.parent_id = id;
}
- promises.push(req.client.getPost(postForm));
- promises.push(req.client.getComments(commentsForm));
+ promises.push(client.getPost(postForm));
+ promises.push(client.getComments(commentsForm));
return promises;
}
componentWillUnmount() {
- this.subscription?.unsubscribe();
document.removeEventListener("scroll", this.commentScrollDebounced);
saveScrollPosition(this.context);
}
- componentDidMount() {
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.fetchPost();
+ }
+
autosize(document.querySelectorAll("textarea"));
this.commentScrollDebounced = debounce(this.trackCommentsBoxScrolling, 100);
document.addEventListener("scroll", this.commentScrollDebounced);
}
- componentDidUpdate(_lastProps: any) {
+ async componentDidUpdate(_lastProps: any) {
// Necessary if you are on a post and you click another post (same route)
if (_lastProps.location.pathname !== _lastProps.history.location.pathname) {
- // TODO Couldnt get a refresh working. This does for now.
- location.reload();
-
- // let currentId = this.props.match.params.id;
- // WebSocketService.Instance.getPost(currentId);
- // this.context.refresh();
- // this.context.router.history.push(_lastProps.location.pathname);
+ await this.fetchPost();
}
}
trackCommentsBoxScrolling = () => {
const wrappedElement = document.getElementsByClassName("comments")[0];
if (wrappedElement && this.isBottom(wrappedElement)) {
- this.setState({
- maxCommentsShown: this.state.maxCommentsShown + commentsShownInterval,
- });
+ const commentCount =
+ this.state.commentsRes.state == "success"
+ ? this.state.commentsRes.data.comments.length
+ : 0;
+
+ if (this.state.maxCommentsShown < commentCount) {
+ this.setState({
+ maxCommentsShown: this.state.maxCommentsShown + commentsShownInterval,
+ });
+ }
}
};
get documentTitle(): string {
- const name_ = this.state.postRes?.post_view.post.name;
const siteName = this.state.siteRes.site_view.site.name;
- return name_ ? `${name_} - ${siteName}` : "";
+ return this.state.postRes.state == "success"
+ ? `${this.state.postRes.data.post_view.post.name} - ${siteName}`
+ : siteName;
}
get imageTag(): string | undefined {
- const post = this.state.postRes?.post_view.post;
- const thumbnail = post?.thumbnail_url;
- const url = post?.url;
- return thumbnail || (url && isImage(url) ? url : undefined);
+ if (this.state.postRes.state == "success") {
+ const post = this.state.postRes.data.post_view.post;
+ const thumbnail = post.thumbnail_url;
+ const url = post.url;
+ return thumbnail || (url && isImage(url) ? url : undefined);
+ } else return undefined;
}
- render() {
- const res = this.state.postRes;
- const description = res?.post_view.post.body;
- return (
- <div className="container-lg">
- {this.state.loading ? (
+ renderPostRes() {
+ switch (this.state.postRes.state) {
+ case "loading":
+ return (
<h5>
<Spinner large />
</h5>
- ) : (
- res && (
- <div className="row">
- <div className="col-12 col-md-8 mb-3">
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- image={this.imageTag}
- description={description}
- />
- <PostListing
- post_view={res.post_view}
- duplicates={this.state.crossPosts}
- showBody
- showCommunity
- moderators={res.moderators}
- admins={this.state.siteRes.admins}
- enableDownvotes={enableDownvotes(this.state.siteRes)}
- enableNsfw={enableNsfw(this.state.siteRes)}
- allLanguages={this.state.siteRes.all_languages}
- siteLanguages={this.state.siteRes.discussion_languages}
- />
- <div ref={this.state.commentSectionRef} className="mb-2" />
- <CommentForm
- node={res.post_view.post.id}
- disabled={res.post_view.post.locked}
- allLanguages={this.state.siteRes.all_languages}
- siteLanguages={this.state.siteRes.discussion_languages}
- />
- <div className="d-block d-md-none">
- <button
- className="btn btn-secondary d-inline-block mb-2 mr-3"
- onClick={linkEvent(this, this.handleShowSidebarMobile)}
- >
- {i18n.t("sidebar")}{" "}
- <Icon
- icon={
- this.state.showSidebarMobile
- ? `minus-square`
- : `plus-square`
- }
- classes="icon-inline"
- />
- </button>
- {this.state.showSidebarMobile && this.sidebar()}
- </div>
- {this.sortRadios()}
- {this.state.commentViewType == CommentViewType.Tree &&
- this.commentsTree()}
- {this.state.commentViewType == CommentViewType.Flat &&
- this.commentsFlat()}
+ );
+ case "success": {
+ const res = this.state.postRes.data;
+ return (
+ <div className="row">
+ <div className="col-12 col-md-8 mb-3">
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ image={this.imageTag}
+ description={res.post_view.post.body}
+ />
+ <PostListing
+ post_view={res.post_view}
+ crossPosts={res.cross_posts}
+ showBody
+ showCommunity
+ moderators={res.moderators}
+ admins={this.state.siteRes.admins}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ enableNsfw={enableNsfw(this.state.siteRes)}
+ allLanguages={this.state.siteRes.all_languages}
+ siteLanguages={this.state.siteRes.discussion_languages}
+ onBlockPerson={this.handleBlockPerson}
+ onPostEdit={this.handlePostEdit}
+ onPostVote={this.handlePostVote}
+ onPostReport={this.handlePostReport}
+ onLockPost={this.handleLockPost}
+ onDeletePost={this.handleDeletePost}
+ onRemovePost={this.handleRemovePost}
+ onSavePost={this.handleSavePost}
+ onPurgePerson={this.handlePurgePerson}
+ onPurgePost={this.handlePurgePost}
+ onBanPerson={this.handleBanPerson}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onFeaturePost={this.handleFeaturePost}
+ />
+ <div ref={this.state.commentSectionRef} className="mb-2" />
+ <CommentForm
+ node={res.post_view.post.id}
+ disabled={res.post_view.post.locked}
+ allLanguages={this.state.siteRes.all_languages}
+ siteLanguages={this.state.siteRes.discussion_languages}
+ onUpsertComment={this.handleCreateComment}
+ finished={this.state.finished.get(0)}
+ />
+ <div className="d-block d-md-none">
+ <button
+ className="btn btn-secondary d-inline-block mb-2 mr-3"
+ onClick={linkEvent(this, this.handleShowSidebarMobile)}
+ >
+ {i18n.t("sidebar")}{" "}
+ <Icon
+ icon={
+ this.state.showSidebarMobile
+ ? `minus-square`
+ : `plus-square`
+ }
+ classes="icon-inline"
+ />
+ </button>
+ {this.state.showSidebarMobile && this.sidebar()}
</div>
- <div className="d-none d-md-block col-md-4">{this.sidebar()}</div>
+ {this.sortRadios()}
+ {this.state.commentViewType == CommentViewType.Tree &&
+ this.commentsTree()}
+ {this.state.commentViewType == CommentViewType.Flat &&
+ this.commentsFlat()}
</div>
- )
- )}
- </div>
- );
+ <div className="d-none d-md-block col-md-4">{this.sidebar()}</div>
+ </div>
+ );
+ }
+ }
+ }
+
+ render() {
+ return <div className="container-lg">{this.renderPostRes()}</div>;
}
sortRadios() {
// These are already sorted by new
const commentsRes = this.state.commentsRes;
const postRes = this.state.postRes;
- return (
- commentsRes &&
- postRes && (
+
+ if (commentsRes.state == "success" && postRes.state == "success") {
+ return (
<div>
<CommentNodes
- nodes={commentsToFlatNodes(commentsRes.comments)}
+ nodes={commentsToFlatNodes(commentsRes.data.comments)}
viewType={this.state.commentViewType}
maxCommentsShown={this.state.maxCommentsShown}
noIndent
- locked={postRes.post_view.post.locked}
- moderators={postRes.moderators}
+ locked={postRes.data.post_view.post.locked}
+ moderators={postRes.data.moderators}
admins={this.state.siteRes.admins}
enableDownvotes={enableDownvotes(this.state.siteRes)}
showContext
+ finished={this.state.finished}
allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages}
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPerson}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onFetchChildren={this.handleFetchChildren}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
/>
</div>
- )
- );
+ );
+ }
}
sidebar() {
const res = this.state.postRes;
- return (
- res && (
+ if (res.state === "success") {
+ return (
<div className="mb-3">
<Sidebar
- community_view={res.community_view}
- moderators={res.moderators}
+ community_view={res.data.community_view}
+ moderators={res.data.moderators}
admins={this.state.siteRes.admins}
- online={res.online}
+ online={res.data.online}
enableNsfw={enableNsfw(this.state.siteRes)}
showIcon
allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages}
+ onDeleteCommunity={this.handleDeleteCommunityClick}
+ onLeaveModTeam={this.handleAddModToCommunity}
+ onFollowCommunity={this.handleFollow}
+ onRemoveCommunity={this.handleModRemoveCommunity}
+ onPurgeCommunity={this.handlePurgeCommunity}
+ onBlockCommunity={this.handleBlockCommunity}
+ onEditCommunity={this.handleEditCommunity}
/>
</div>
- )
- );
- }
-
- handleCommentSortChange(i: Post, event: any) {
- i.setState({
- commentSort: event.target.value as CommentSortType,
- commentViewType: CommentViewType.Tree,
- commentsRes: undefined,
- postRes: undefined,
- });
- i.fetchPost();
- }
-
- handleCommentViewTypeChange(i: Post, event: any) {
- const comments = i.state.commentsRes?.comments;
- if (comments) {
- i.setState({
- commentViewType: Number(event.target.value),
- commentSort: "New",
- commentTree: buildCommentsTree(comments, !!i.state.commentId),
- });
- }
- }
-
- handleShowSidebarMobile(i: Post) {
- i.setState({ showSidebarMobile: !i.state.showSidebarMobile });
- }
-
- handleViewPost(i: Post) {
- const id = i.state.postRes?.post_view.post.id;
- if (id) {
- i.context.router.history.push(`/post/${id}`);
- }
- }
-
- handleViewContext(i: Post) {
- const parentId = getCommentParentId(
- i.state.commentsRes?.comments?.at(0)?.comment
- );
- if (parentId) {
- i.context.router.history.push(`/comment/${parentId}`);
+ );
}
}
commentsTree() {
const res = this.state.postRes;
- const firstComment = this.state.commentTree.at(0)?.comment_view.comment;
+ const firstComment = this.commentTree().at(0)?.comment_view.comment;
const depth = getDepthFromComment(firstComment);
const showContextButton = depth ? depth > 0 : false;
return (
- res && (
+ res.state == "success" && (
<div>
{!!this.state.commentId && (
<>
</>
)}
<CommentNodes
- nodes={this.state.commentTree}
+ nodes={this.commentTree()}
viewType={this.state.commentViewType}
maxCommentsShown={this.state.maxCommentsShown}
- locked={res.post_view.post.locked}
- moderators={res.moderators}
+ locked={res.data.post_view.post.locked}
+ moderators={res.data.moderators}
admins={this.state.siteRes.admins}
enableDownvotes={enableDownvotes(this.state.siteRes)}
+ finished={this.state.finished}
allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages}
+ onSaveComment={this.handleSaveComment}
+ onBlockPerson={this.handleBlockPerson}
+ onDeleteComment={this.handleDeleteComment}
+ onRemoveComment={this.handleRemoveComment}
+ onCommentVote={this.handleCommentVote}
+ onCommentReport={this.handleCommentReport}
+ onDistinguishComment={this.handleDistinguishComment}
+ onAddModToCommunity={this.handleAddModToCommunity}
+ onAddAdmin={this.handleAddAdmin}
+ onTransferCommunity={this.handleTransferCommunity}
+ onFetchChildren={this.handleFetchChildren}
+ onPurgeComment={this.handlePurgeComment}
+ onPurgePerson={this.handlePurgePerson}
+ onCommentReplyRead={this.handleCommentReplyRead}
+ onPersonMentionRead={this.handlePersonMentionRead}
+ onBanPersonFromCommunity={this.handleBanFromCommunity}
+ onBanPerson={this.handleBanPerson}
+ onCreateComment={this.handleCreateComment}
+ onEditComment={this.handleEditComment}
/>
</div>
)
);
}
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- return;
- } else if (msg.reconnect) {
- const post_id = this.state.postRes?.post_view.post.id;
- if (post_id) {
- WebSocketService.Instance.send(wsClient.postJoin({ post_id }));
- WebSocketService.Instance.send(
- wsClient.getPost({
- id: post_id,
- auth: myAuth(false),
- })
- );
- }
- } else if (op == UserOperation.GetPost) {
- const data = wsJsonToRes<GetPostResponse>(msg);
- this.setState({ postRes: data });
-
- // join the rooms
- WebSocketService.Instance.send(
- wsClient.postJoin({ post_id: data.post_view.post.id })
- );
- WebSocketService.Instance.send(
- wsClient.communityJoin({
- community_id: data.community_view.community.id,
- })
+ commentTree(): CommentNodeI[] {
+ if (this.state.commentsRes.state == "success") {
+ return buildCommentsTree(
+ this.state.commentsRes.data.comments,
+ !!this.state.commentId
);
+ } else {
+ return [];
+ }
+ }
- // Get cross-posts
- // TODO move this into initial fetch and refetch
- this.fetchCrossPosts();
- setupTippy();
- if (!this.state.commentId) restoreScrollPosition(this.context);
+ async handleCommentSortChange(i: Post, event: any) {
+ i.setState({
+ commentSort: event.target.value as CommentSortType,
+ commentViewType: CommentViewType.Tree,
+ commentsRes: { state: "loading" },
+ postRes: { state: "loading" },
+ });
+ await i.fetchPost();
+ }
+
+ handleCommentViewTypeChange(i: Post, event: any) {
+ i.setState({
+ commentViewType: Number(event.target.value),
+ commentSort: "New",
+ });
+ }
- if (this.checkScrollIntoCommentsParam) {
- this.scrollIntoCommentSection();
+ handleShowSidebarMobile(i: Post) {
+ i.setState({ showSidebarMobile: !i.state.showSidebarMobile });
+ }
+
+ handleViewPost(i: Post) {
+ if (i.state.postRes.state == "success") {
+ const id = i.state.postRes.data.post_view.post.id;
+ i.context.router.history.push(`/post/${id}`);
+ }
+ }
+
+ handleViewContext(i: Post) {
+ if (i.state.commentsRes.state == "success") {
+ const parentId = getCommentParentId(
+ i.state.commentsRes.data.comments.at(0)?.comment
+ );
+ if (parentId) {
+ i.context.router.history.push(`/comment/${parentId}`);
}
- } else if (op == UserOperation.GetComments) {
- const data = wsJsonToRes<GetCommentsResponse>(msg);
- // This section sets the comments res
- const comments = this.state.commentsRes?.comments;
- if (comments) {
- // You might need to append here, since this could be building more comments from a tree fetch
- // Remove the first comment, since it is the parent
- const newComments = data.comments;
- newComments.shift();
- comments.push(...newComments);
- } else {
- this.setState({ commentsRes: data });
+ }
+ }
+
+ async handleDeleteCommunityClick(form: DeleteCommunity) {
+ const deleteCommunityRes = await HttpService.client.deleteCommunity(form);
+ this.updateCommunity(deleteCommunityRes);
+ }
+
+ async handleAddModToCommunity(form: AddModToCommunity) {
+ const addModRes = await HttpService.client.addModToCommunity(form);
+ this.updateModerators(addModRes);
+ }
+
+ async handleFollow(form: FollowCommunity) {
+ const followCommunityRes = await HttpService.client.followCommunity(form);
+ this.updateCommunity(followCommunityRes);
+
+ // Update myUserInfo
+ if (followCommunityRes.state === "success") {
+ const communityId = followCommunityRes.data.community_view.community.id;
+ const mui = UserService.Instance.myUserInfo;
+ if (mui) {
+ mui.follows = mui.follows.filter(i => i.community.id != communityId);
}
+ }
+ }
- const cComments = this.state.commentsRes?.comments ?? [];
- this.setState({
- commentTree: buildCommentsTree(cComments, !!this.state.commentId),
- loading: false,
- });
- } else if (op == UserOperation.CreateComment) {
- const data = wsJsonToRes<CommentResponse>(msg);
+ async handlePurgeCommunity(form: PurgeCommunity) {
+ const purgeCommunityRes = await HttpService.client.purgeCommunity(form);
+ this.purgeItem(purgeCommunityRes);
+ }
+
+ async handlePurgePerson(form: PurgePerson) {
+ const purgePersonRes = await HttpService.client.purgePerson(form);
+ this.purgeItem(purgePersonRes);
+ }
+
+ async handlePurgeComment(form: PurgeComment) {
+ const purgeCommentRes = await HttpService.client.purgeComment(form);
+ this.purgeItem(purgeCommentRes);
+ }
- // Don't get comments from the post room, if the creator is blocked
- const creatorBlocked = UserService.Instance.myUserInfo?.person_blocks
- .map(pb => pb.target.id)
- .includes(data.comment_view.creator.id);
+ async handlePurgePost(form: PurgePost) {
+ const purgeRes = await HttpService.client.purgePost(form);
+ this.purgeItem(purgeRes);
+ }
- // Necessary since it might be a user reply, which has the recipients, to avoid double
- const postRes = this.state.postRes;
- const commentsRes = this.state.commentsRes;
+ async handleBlockCommunity(form: BlockCommunity) {
+ const blockCommunityRes = await HttpService.client.blockCommunity(form);
+ // TODO Probably isn't necessary
+ this.setState(s => {
if (
- data.recipient_ids.length == 0 &&
- !creatorBlocked &&
- postRes &&
- data.comment_view.post.id == postRes.post_view.post.id &&
- commentsRes
+ s.postRes.state == "success" &&
+ blockCommunityRes.state == "success"
) {
- commentsRes.comments.unshift(data.comment_view);
- insertCommentIntoTree(
- this.state.commentTree,
- data.comment_view,
- !!this.state.commentId
- );
- postRes.post_view.counts.comments++;
-
- this.setState(this.state);
- setupTippy();
+ s.postRes.data.community_view = blockCommunityRes.data.community_view;
}
- } else if (
- op == UserOperation.EditComment ||
- op == UserOperation.DeleteComment ||
- op == UserOperation.RemoveComment
- ) {
- const data = wsJsonToRes<CommentResponse>(msg);
- editCommentRes(data.comment_view, this.state.commentsRes?.comments);
- this.setState(this.state);
- setupTippy();
- } else if (op == UserOperation.SaveComment) {
- const data = wsJsonToRes<CommentResponse>(msg);
- saveCommentRes(data.comment_view, this.state.commentsRes?.comments);
- this.setState(this.state);
- setupTippy();
- } else if (op == UserOperation.CreateCommentLike) {
- const data = wsJsonToRes<CommentResponse>(msg);
- createCommentLikeRes(data.comment_view, this.state.commentsRes?.comments);
- this.setState(this.state);
- } else if (op == UserOperation.CreatePostLike) {
- const data = wsJsonToRes<PostResponse>(msg);
- createPostLikeRes(data.post_view, this.state.postRes?.post_view);
- this.setState(this.state);
- } else if (
- op == UserOperation.EditPost ||
- op == UserOperation.DeletePost ||
- op == UserOperation.RemovePost ||
- op == UserOperation.LockPost ||
- op == UserOperation.FeaturePost ||
- op == UserOperation.SavePost
- ) {
- const data = wsJsonToRes<PostResponse>(msg);
- const res = this.state.postRes;
- if (res) {
- res.post_view = data.post_view;
- this.setState(this.state);
- setupTippy();
- }
- } else if (
- op == UserOperation.EditCommunity ||
- op == UserOperation.DeleteCommunity ||
- op == UserOperation.RemoveCommunity ||
- op == UserOperation.FollowCommunity
+ return s;
+ });
+
+ if (blockCommunityRes.state == "success") {
+ updateCommunityBlock(blockCommunityRes.data);
+ }
+ }
+
+ async handleBlockPerson(form: BlockPerson) {
+ const blockPersonRes = await HttpService.client.blockPerson(form);
+ if (blockPersonRes.state == "success") {
+ updatePersonBlock(blockPersonRes.data);
+ }
+ }
+
+ async handleModRemoveCommunity(form: RemoveCommunity) {
+ const removeCommunityRes = await HttpService.client.removeCommunity(form);
+ this.updateCommunity(removeCommunityRes);
+ }
+
+ async handleEditCommunity(form: EditCommunity) {
+ const res = await HttpService.client.editCommunity(form);
+ this.updateCommunity(res);
+
+ return res;
+ }
+
+ async handleCreateComment(form: CreateComment) {
+ const createCommentRes = await HttpService.client.createComment(form);
+ this.createAndUpdateComments(createCommentRes);
+
+ return createCommentRes;
+ }
+
+ async handleEditComment(form: EditComment) {
+ const editCommentRes = await HttpService.client.editComment(form);
+ this.findAndUpdateComment(editCommentRes);
+
+ return editCommentRes;
+ }
+
+ async handleDeleteComment(form: DeleteComment) {
+ const deleteCommentRes = await HttpService.client.deleteComment(form);
+ this.findAndUpdateComment(deleteCommentRes);
+ }
+
+ async handleDeletePost(form: DeletePost) {
+ const deleteRes = await HttpService.client.deletePost(form);
+ this.updatePost(deleteRes);
+ }
+
+ async handleRemovePost(form: RemovePost) {
+ const removeRes = await HttpService.client.removePost(form);
+ this.updatePost(removeRes);
+ }
+
+ async handleRemoveComment(form: RemoveComment) {
+ const removeCommentRes = await HttpService.client.removeComment(form);
+ this.findAndUpdateComment(removeCommentRes);
+ }
+
+ async handleSaveComment(form: SaveComment) {
+ const saveCommentRes = await HttpService.client.saveComment(form);
+ this.findAndUpdateComment(saveCommentRes);
+ }
+
+ async handleSavePost(form: SavePost) {
+ const saveRes = await HttpService.client.savePost(form);
+ this.updatePost(saveRes);
+ }
+
+ async handleFeaturePost(form: FeaturePost) {
+ const featureRes = await HttpService.client.featurePost(form);
+ this.updatePost(featureRes);
+ }
+
+ async handleCommentVote(form: CreateCommentLike) {
+ const voteRes = await HttpService.client.likeComment(form);
+ this.findAndUpdateComment(voteRes);
+ }
+
+ async handlePostVote(form: CreatePostLike) {
+ const voteRes = await HttpService.client.likePost(form);
+ this.updatePost(voteRes);
+ }
+
+ async handlePostEdit(form: EditPost) {
+ const res = await HttpService.client.editPost(form);
+ this.updatePost(res);
+ }
+
+ async handleCommentReport(form: CreateCommentReport) {
+ const reportRes = await HttpService.client.createCommentReport(form);
+ if (reportRes.state == "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
+
+ async handlePostReport(form: CreatePostReport) {
+ const reportRes = await HttpService.client.createPostReport(form);
+ if (reportRes.state == "success") {
+ toast(i18n.t("report_created"));
+ }
+ }
+
+ async handleLockPost(form: LockPost) {
+ const lockRes = await HttpService.client.lockPost(form);
+ this.updatePost(lockRes);
+ }
+
+ async handleDistinguishComment(form: DistinguishComment) {
+ const distinguishRes = await HttpService.client.distinguishComment(form);
+ this.findAndUpdateComment(distinguishRes);
+ }
+
+ async handleAddAdmin(form: AddAdmin) {
+ const addAdminRes = await HttpService.client.addAdmin(form);
+
+ if (addAdminRes.state === "success") {
+ this.setState(s => ((s.siteRes.admins = addAdminRes.data.admins), s));
+ }
+ }
+
+ async handleTransferCommunity(form: TransferCommunity) {
+ const transferCommunityRes = await HttpService.client.transferCommunity(
+ form
+ );
+ this.updateCommunityFull(transferCommunityRes);
+ }
+
+ async handleFetchChildren(form: GetComments) {
+ const moreCommentsRes = await HttpService.client.getComments(form);
+ if (
+ this.state.commentsRes.state == "success" &&
+ moreCommentsRes.state == "success"
) {
- const data = wsJsonToRes<CommunityResponse>(msg);
- const res = this.state.postRes;
- if (res) {
- res.community_view = data.community_view;
- res.post_view.community = data.community_view.community;
- this.setState(this.state);
- }
- } else if (op == UserOperation.BanFromCommunity) {
- const data = wsJsonToRes<BanFromCommunityResponse>(msg);
+ const newComments = moreCommentsRes.data.comments;
+ // Remove the first comment, since it is the parent
+ newComments.shift();
+ const newRes = this.state.commentsRes;
+ newRes.data.comments.push(...newComments);
+ this.setState({ commentsRes: newRes });
+ }
+ }
+
+ async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
+ const readRes = await HttpService.client.markCommentReplyAsRead(form);
+ this.findAndUpdateCommentReply(readRes);
+ }
+
+ async handlePersonMentionRead(form: MarkPersonMentionAsRead) {
+ // TODO not sure what to do here. Maybe it is actually optional, because post doesn't need it.
+ await HttpService.client.markPersonMentionAsRead(form);
+ }
- const res = this.state.postRes;
- if (res) {
- if (res.post_view.creator.id == data.person_view.person.id) {
- res.post_view.creator_banned_from_community = data.banned;
+ async handleBanFromCommunity(form: BanFromCommunity) {
+ const banRes = await HttpService.client.banFromCommunity(form);
+ this.updateBan(banRes);
+ }
+
+ async handleBanPerson(form: BanPerson) {
+ const banRes = await HttpService.client.banPerson(form);
+ this.updateBan(banRes);
+ }
+
+ updateBanFromCommunity(banRes: RequestState<BanFromCommunityResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (
+ s.postRes.state == "success" &&
+ s.postRes.data.post_view.creator.id ==
+ banRes.data.person_view.person.id
+ ) {
+ s.postRes.data.post_view.creator_banned_from_community =
+ banRes.data.banned;
}
+ if (s.commentsRes.state == "success") {
+ s.commentsRes.data.comments
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(
+ c => (c.creator_banned_from_community = banRes.data.banned)
+ );
+ }
+ return s;
+ });
+ }
+ }
+
+ updateBan(banRes: RequestState<BanPersonResponse>) {
+ // Maybe not necessary
+ if (banRes.state == "success") {
+ this.setState(s => {
+ if (
+ s.postRes.state == "success" &&
+ s.postRes.data.post_view.creator.id ==
+ banRes.data.person_view.person.id
+ ) {
+ s.postRes.data.post_view.creator.banned = banRes.data.banned;
+ }
+ if (s.commentsRes.state == "success") {
+ s.commentsRes.data.comments
+ .filter(c => c.creator.id == banRes.data.person_view.person.id)
+ .forEach(c => (c.creator.banned = banRes.data.banned));
+ }
+ return s;
+ });
+ }
+ }
+
+ updateCommunity(communityRes: RequestState<CommunityResponse>) {
+ this.setState(s => {
+ if (s.postRes.state == "success" && communityRes.state == "success") {
+ s.postRes.data.community_view = communityRes.data.community_view;
}
+ return s;
+ });
+ }
- this.state.commentsRes?.comments
- .filter(c => c.creator.id == data.person_view.person.id)
- .forEach(c => (c.creator_banned_from_community = data.banned));
- this.setState(this.state);
- } else if (op == UserOperation.AddModToCommunity) {
- const data = wsJsonToRes<AddModToCommunityResponse>(msg);
- const res = this.state.postRes;
- if (res) {
- res.moderators = data.moderators;
- this.setState(this.state);
+ updateCommunityFull(res: RequestState<GetCommunityResponse>) {
+ this.setState(s => {
+ if (s.postRes.state == "success" && res.state == "success") {
+ s.postRes.data.community_view = res.data.community_view;
+ s.postRes.data.moderators = res.data.moderators;
}
- } else if (op == UserOperation.BanPerson) {
- const data = wsJsonToRes<BanPersonResponse>(msg);
- this.state.commentsRes?.comments
- .filter(c => c.creator.id == data.person_view.person.id)
- .forEach(c => (c.creator.banned = data.banned));
-
- const res = this.state.postRes;
- if (res) {
- if (res.post_view.creator.id == data.person_view.person.id) {
- res.post_view.creator.banned = data.banned;
- }
+ return s;
+ });
+ }
+
+ updatePost(post: RequestState<PostResponse>) {
+ this.setState(s => {
+ if (s.postRes.state == "success" && post.state == "success") {
+ s.postRes.data.post_view = post.data.post_view;
}
- this.setState(this.state);
- } else if (op == UserOperation.AddAdmin) {
- const data = wsJsonToRes<AddAdminResponse>(msg);
- this.setState(s => ((s.siteRes.admins = data.admins), s));
- } else if (op == UserOperation.Search) {
- const data = wsJsonToRes<SearchResponse>(msg);
- const xPosts = data.posts.filter(
- p => p.post.ap_id != this.state.postRes?.post_view.post.ap_id
- );
- this.setState({ crossPosts: xPosts.length > 0 ? xPosts : undefined });
- } else if (op == UserOperation.LeaveAdmin) {
- const data = wsJsonToRes<GetSiteResponse>(msg);
- this.setState({ siteRes: data });
- } else if (op == UserOperation.TransferCommunity) {
- const data = wsJsonToRes<GetCommunityResponse>(msg);
- const res = this.state.postRes;
- if (res) {
- res.community_view = data.community_view;
- res.post_view.community = data.community_view.community;
- res.moderators = data.moderators;
- this.setState(this.state);
+ return s;
+ });
+ }
+
+ purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
+ if (purgeRes.state == "success") {
+ toast(i18n.t("purge_success"));
+ this.context.router.history.push(`/`);
+ }
+ }
+
+ createAndUpdateComments(res: RequestState<CommentResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state === "success" && res.state === "success") {
+ s.commentsRes.data.comments.unshift(res.data.comment_view);
+
+ // Set finished for the parent
+ s.finished.set(
+ getCommentParentId(res.data.comment_view.comment) ?? 0,
+ true
+ );
}
- } else if (op == UserOperation.BlockPerson) {
- const data = wsJsonToRes<BlockPersonResponse>(msg);
- updatePersonBlock(data);
- } else if (op == UserOperation.CreatePostReport) {
- const data = wsJsonToRes<PostReportResponse>(msg);
- if (data) {
- toast(i18n.t("report_created"));
+ return s;
+ });
+ }
+
+ findAndUpdateComment(res: RequestState<CommentResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state == "success" && res.state == "success") {
+ s.commentsRes.data.comments = editComment(
+ res.data.comment_view,
+ s.commentsRes.data.comments
+ );
+ s.finished.set(res.data.comment_view.comment.id, true);
}
- } else if (op == UserOperation.CreateCommentReport) {
- const data = wsJsonToRes<CommentReportResponse>(msg);
- if (data) {
- toast(i18n.t("report_created"));
+ return s;
+ });
+ }
+
+ findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
+ this.setState(s => {
+ if (s.commentsRes.state == "success" && res.state == "success") {
+ s.commentsRes.data.comments = editWith(
+ res.data.comment_reply_view,
+ s.commentsRes.data.comments
+ );
}
- } else if (
- op == UserOperation.PurgePerson ||
- op == UserOperation.PurgePost ||
- op == UserOperation.PurgeComment ||
- op == UserOperation.PurgeCommunity
- ) {
- const data = wsJsonToRes<PurgeItemResponse>(msg);
- if (data.success) {
- toast(i18n.t("purge_success"));
- this.context.router.history.push(`/`);
+ return s;
+ });
+ }
+
+ updateModerators(res: RequestState<AddModToCommunityResponse>) {
+ // Update the moderators
+ this.setState(s => {
+ if (s.postRes.state == "success" && res.state == "success") {
+ s.postRes.data.moderators = res.data.moderators;
}
- }
+ return s;
+ });
}
}
import { Component } from "inferno";
import {
+ CreatePrivateMessage as CreatePrivateMessageI,
GetPersonDetails,
GetPersonDetailsResponse,
GetSiteResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
-import { WebSocketService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
getRecipientIdFromProps,
- isBrowser,
myAuth,
setIsoData,
toast,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
interface CreatePrivateMessageState {
siteRes: GetSiteResponse;
- recipientDetailsRes?: GetPersonDetailsResponse;
- recipient_id: number;
- loading: boolean;
+ recipientRes: RequestState<GetPersonDetailsResponse>;
+ recipientId: number;
+ isIsomorphic: boolean;
}
export class CreatePrivateMessage extends Component<
CreatePrivateMessageState
> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: CreatePrivateMessageState = {
siteRes: this.isoData.site_res,
- recipient_id: getRecipientIdFromProps(this.props),
- loading: true,
+ recipientRes: { state: "empty" },
+ recipientId: getRecipientIdFromProps(this.props),
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
this.handlePrivateMessageCreate =
this.handlePrivateMessageCreate.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
this.state = {
...this.state,
- recipientDetailsRes: this.isoData
- .routeData[0] as GetPersonDetailsResponse,
- loading: false,
+ recipientRes: this.isoData.routeData[0],
+ isIsomorphic: true,
};
- } else {
- this.fetchPersonDetails();
}
}
- fetchPersonDetails() {
- const form: GetPersonDetails = {
- person_id: this.state.recipient_id,
- sort: "New",
- saved_only: false,
- auth: myAuth(false),
- };
- WebSocketService.Instance.send(wsClient.getPersonDetails(form));
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.fetchPersonDetails();
+ }
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
+ async fetchPersonDetails() {
+ this.setState({
+ recipientRes: { state: "loading" },
+ });
+
+ this.setState({
+ recipientRes: await HttpService.client.getPersonDetails({
+ person_id: this.state.recipientId,
+ sort: "New",
+ saved_only: false,
+ auth: myAuth(),
+ }),
+ });
+ }
+
+ static fetchInitialData(
+ req: InitialFetchRequest
+ ): Promise<RequestState<any>>[] {
const person_id = Number(req.path.split("/").pop());
const form: GetPersonDetails = {
person_id,
}
get documentTitle(): string {
- const name_ = this.state.recipientDetailsRes?.person_view.person.name;
- return name_ ? `${i18n.t("create_private_message")} - ${name_}` : "";
+ if (this.state.recipientRes.state == "success") {
+ const name_ = this.state.recipientRes.data.person_view.person.name;
+ return `${i18n.t("create_private_message")} - ${name_}`;
+ } else {
+ return "";
+ }
}
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ renderRecipientRes() {
+ switch (this.state.recipientRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const res = this.state.recipientRes.data;
+ return (
+ <div className="row">
+ <div className="col-12 col-lg-6 offset-lg-3 mb-4">
+ <h5>{i18n.t("create_private_message")}</h5>
+ <PrivateMessageForm
+ onCreate={this.handlePrivateMessageCreate}
+ recipient={res.person_view.person}
+ />
+ </div>
+ </div>
+ );
+ }
}
}
render() {
- const res = this.state.recipientDetailsRes;
return (
<div className="container-lg">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
- {this.state.loading ? (
- <h5>
- <Spinner large />
- </h5>
- ) : (
- res && (
- <div className="row">
- <div className="col-12 col-lg-6 offset-lg-3 mb-4">
- <h5>{i18n.t("create_private_message")}</h5>
- <PrivateMessageForm
- onCreate={this.handlePrivateMessageCreate}
- recipient={res.person_view.person}
- />
- </div>
- </div>
- )
- )}
+ {this.renderRecipientRes()}
</div>
);
}
- handlePrivateMessageCreate() {
- toast(i18n.t("message_sent"));
+ async handlePrivateMessageCreate(form: CreatePrivateMessageI) {
+ const res = await HttpService.client.createPrivateMessage(form);
- // Navigate to the front
- this.context.router.history.push("/");
- }
+ if (res.state == "success") {
+ toast(i18n.t("message_sent"));
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.setState({ loading: false });
- return;
- } else if (op == UserOperation.GetPersonDetails) {
- const data = wsJsonToRes<GetPersonDetailsResponse>(msg);
- this.setState({ recipientDetailsRes: data, loading: false });
+ // Navigate to the front
+ this.context.router.history.push("/");
}
}
}
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
CreatePrivateMessage,
EditPrivateMessage,
Person,
- PrivateMessageResponse,
PrivateMessageView,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
import {
capitalizeFirstLetter,
- isBrowser,
- myAuth,
+ myAuthRequired,
relTags,
setupTippy,
- toast,
- wsClient,
- wsSubscribe,
} from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
recipient: Person;
privateMessageView?: PrivateMessageView; // If a pm is given, that means this is an edit
onCancel?(): any;
- onCreate?(message: PrivateMessageView): any;
- onEdit?(message: PrivateMessageView): any;
+ onCreate?(form: CreatePrivateMessage): void;
+ onEdit?(form: EditPrivateMessage): void;
}
interface PrivateMessageFormState {
loading: boolean;
previewMode: boolean;
showDisclaimer: boolean;
+ submitted: boolean;
}
export class PrivateMessageForm extends Component<
PrivateMessageFormProps,
PrivateMessageFormState
> {
- private subscription?: Subscription;
state: PrivateMessageFormState = {
loading: false,
previewMode: false,
showDisclaimer: false,
+ content: this.props.privateMessageView
+ ? this.props.privateMessageView.private_message.content
+ : undefined,
+ submitted: false,
};
constructor(props: any, context: any) {
super(props, context);
this.handleContentChange = this.handleContentChange.bind(this);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
- // Its an edit
- if (this.props.privateMessageView) {
- this.state.content =
- this.props.privateMessageView.private_message.content;
- }
}
componentDidMount() {
setupTippy();
}
- componentDidUpdate() {
- if (!this.state.loading && this.state.content) {
- window.onbeforeunload = () => true;
- } else {
- window.onbeforeunload = null;
- }
- }
-
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
- window.onbeforeunload = null;
+ componentWillReceiveProps(
+ nextProps: Readonly<{ children?: InfernoNode } & PrivateMessageFormProps>
+ ): void {
+ if (this.props != nextProps) {
+ this.setState({ loading: false, content: undefined, previewMode: false });
}
}
+ // TODO
+ // <Prompt
+ // when={!this.state.loading && this.state.content}
+ // message={i18n.t("block_leaving")}
+ // />
render() {
return (
- <div>
- <NavigationPrompt when={!this.state.loading && !!this.state.content} />
- <form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}>
- {!this.props.privateMessageView && (
- <div className="form-group row">
- <label className="col-sm-2 col-form-label">
- {capitalizeFirstLetter(i18n.t("to"))}
- </label>
-
- <div className="col-sm-10 form-control-plaintext">
- <PersonListing person={this.props.recipient} />
- </div>
- </div>
- )}
+ <form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}>
+ <NavigationPrompt
+ when={
+ !this.state.loading && !!this.state.content && !this.state.submitted
+ }
+ />
+ {!this.props.privateMessageView && (
<div className="form-group row">
<label className="col-sm-2 col-form-label">
- {i18n.t("message")}
- <button
- className="btn btn-link text-warning d-inline-block"
- onClick={linkEvent(this, this.handleShowDisclaimer)}
- data-tippy-content={i18n.t("private_message_disclaimer")}
- aria-label={i18n.t("private_message_disclaimer")}
- >
- <Icon icon="alert-triangle" classes="icon-inline" />
- </button>
+ {capitalizeFirstLetter(i18n.t("to"))}
</label>
- <div className="col-sm-10">
- <MarkdownTextArea
- initialContent={this.state.content}
- onContentChange={this.handleContentChange}
- allLanguages={[]}
- siteLanguages={[]}
- />
+
+ <div className="col-sm-10 form-control-plaintext">
+ <PersonListing person={this.props.recipient} />
</div>
</div>
+ )}
+ <div className="form-group row">
+ <label className="col-sm-2 col-form-label">
+ {i18n.t("message")}
+ <button
+ className="btn btn-link text-warning d-inline-block"
+ onClick={linkEvent(this, this.handleShowDisclaimer)}
+ data-tippy-content={i18n.t("private_message_disclaimer")}
+ aria-label={i18n.t("private_message_disclaimer")}
+ >
+ <Icon icon="alert-triangle" classes="icon-inline" />
+ </button>
+ </label>
+ <div className="col-sm-10">
+ <MarkdownTextArea
+ initialContent={this.state.content}
+ onContentChange={this.handleContentChange}
+ allLanguages={[]}
+ siteLanguages={[]}
+ hideNavigationWarnings
+ />
+ </div>
+ </div>
- {this.state.showDisclaimer && (
- <div className="form-group row">
- <div className="offset-sm-2 col-sm-10">
- <div className="alert alert-danger" role="alert">
- <T i18nKey="private_message_disclaimer">
+ {this.state.showDisclaimer && (
+ <div className="form-group row">
+ <div className="offset-sm-2 col-sm-10">
+ <div className="alert alert-danger" role="alert">
+ <T i18nKey="private_message_disclaimer">
+ #
+ <a
+ className="alert-link"
+ rel={relTags}
+ href="https://element.io/get-started"
+ >
#
- <a
- className="alert-link"
- rel={relTags}
- href="https://element.io/get-started"
- >
- #
- </a>
- </T>
- </div>
+ </a>
+ </T>
</div>
</div>
- )}
- <div className="form-group row">
- <div className="offset-sm-2 col-sm-10">
+ </div>
+ )}
+ <div className="form-group row">
+ <div className="offset-sm-2 col-sm-10">
+ <button
+ type="submit"
+ className="btn btn-secondary mr-2"
+ disabled={this.state.loading}
+ >
+ {this.state.loading ? (
+ <Spinner />
+ ) : this.props.privateMessageView ? (
+ capitalizeFirstLetter(i18n.t("save"))
+ ) : (
+ capitalizeFirstLetter(i18n.t("send_message"))
+ )}
+ </button>
+ {this.props.privateMessageView && (
<button
- type="submit"
- className="btn btn-secondary mr-2"
- disabled={this.state.loading}
+ type="button"
+ className="btn btn-secondary"
+ onClick={linkEvent(this, this.handleCancel)}
>
- {this.state.loading ? (
- <Spinner />
- ) : this.props.privateMessageView ? (
- capitalizeFirstLetter(i18n.t("save"))
- ) : (
- capitalizeFirstLetter(i18n.t("send_message"))
- )}
+ {i18n.t("cancel")}
</button>
- {this.props.privateMessageView && (
- <button
- type="button"
- className="btn btn-secondary"
- onClick={linkEvent(this, this.handleCancel)}
- >
- {i18n.t("cancel")}
- </button>
- )}
- <ul className="d-inline-block float-right list-inline mb-1 text-muted font-weight-bold">
- <li className="list-inline-item"></li>
- </ul>
- </div>
+ )}
+ <ul className="d-inline-block float-right list-inline mb-1 text-muted font-weight-bold">
+ <li className="list-inline-item"></li>
+ </ul>
</div>
- </form>
- </div>
+ </div>
+ </form>
);
}
handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) {
event.preventDefault();
+ i.setState({ loading: true, submitted: true });
const pm = i.props.privateMessageView;
- const auth = myAuth();
- const content = i.state.content;
- if (auth && content) {
- if (pm) {
- const form: EditPrivateMessage = {
- private_message_id: pm.private_message.id,
- content,
- auth,
- };
- WebSocketService.Instance.send(wsClient.editPrivateMessage(form));
- } else {
- const form: CreatePrivateMessage = {
- content,
- recipient_id: i.props.recipient.id,
- auth,
- };
- WebSocketService.Instance.send(wsClient.createPrivateMessage(form));
- }
- i.setState({ loading: true });
+ const auth = myAuthRequired();
+ const content = i.state.content ?? "";
+ if (pm) {
+ i.props.onEdit?.({
+ private_message_id: pm.private_message.id,
+ content,
+ auth,
+ });
+ } else {
+ i.props.onCreate?.({
+ content,
+ recipient_id: i.props.recipient.id,
+ auth,
+ });
}
}
handleShowDisclaimer(i: PrivateMessageForm) {
i.setState({ showDisclaimer: !i.state.showDisclaimer });
}
-
- parseMessage(msg: any) {
- const op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.setState({ loading: false });
- return;
- } else if (
- op == UserOperation.EditPrivateMessage ||
- op == UserOperation.DeletePrivateMessage ||
- op == UserOperation.MarkPrivateMessageAsRead
- ) {
- const data = wsJsonToRes<PrivateMessageResponse>(msg);
- this.setState({ loading: false });
- this.props.onEdit?.(data.private_message_view);
- } else if (op == UserOperation.CreatePrivateMessage) {
- const data = wsJsonToRes<PrivateMessageResponse>(msg);
- this.props.onCreate?.(data.private_message_view);
- }
- }
}
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
PrivateMessageReportView,
ResolvePrivateMessageReport,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
-import { mdToHtml, myAuth, wsClient } from "../../utils";
-import { Icon } from "../common/icon";
+import { mdToHtml, myAuthRequired } from "../../utils";
+import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing";
interface Props {
report: PrivateMessageReportView;
+ onResolveReport(form: ResolvePrivateMessageReport): void;
}
-export class PrivateMessageReport extends Component<Props, any> {
+interface State {
+ loading: boolean;
+}
+
+export class PrivateMessageReport extends Component<Props, State> {
+ state: State = {
+ loading: false,
+ };
+
constructor(props: any, context: any) {
super(props, context);
}
+ componentWillReceiveProps(
+ nextProps: Readonly<{ children?: InfernoNode } & Props>
+ ): void {
+ if (this.props != nextProps) {
+ this.setState({ loading: false });
+ }
+ }
+
render() {
const r = this.props.report;
const pmr = r.private_message_report;
data-tippy-content={tippyContent}
aria-label={tippyContent}
>
- <Icon
- icon="check"
- classes={`icon-inline ${
- pmr.resolved ? "text-success" : "text-danger"
- }`}
- />
+ {this.state.loading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="check"
+ classes={`icon-inline ${
+ pmr.resolved ? "text-success" : "text-danger"
+ }`}
+ />
+ )}
</button>
</div>
);
}
handleResolveReport(i: PrivateMessageReport) {
+ i.setState({ loading: true });
const pmr = i.props.report.private_message_report;
- const auth = myAuth();
- if (auth) {
- const form: ResolvePrivateMessageReport = {
- report_id: pmr.id,
- resolved: !pmr.resolved,
- auth,
- };
- WebSocketService.Instance.send(
- wsClient.resolvePrivateMessageReport(form)
- );
- }
+ i.props.onResolveReport({
+ report_id: pmr.id,
+ resolved: !pmr.resolved,
+ auth: myAuthRequired(),
+ });
}
}
-import { Component, linkEvent } from "inferno";
+import { Component, InfernoNode, linkEvent } from "inferno";
import {
+ CreatePrivateMessage,
CreatePrivateMessageReport,
DeletePrivateMessage,
+ EditPrivateMessage,
MarkPrivateMessageAsRead,
Person,
PrivateMessageView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { UserService, WebSocketService } from "../../services";
-import { mdToHtml, myAuth, toast, wsClient } from "../../utils";
-import { Icon } from "../common/icon";
+import { UserService } from "../../services";
+import { mdToHtml, myAuthRequired } from "../../utils";
+import { Icon, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { PersonListing } from "../person/person-listing";
import { PrivateMessageForm } from "./private-message-form";
viewSource: boolean;
showReportDialog: boolean;
reportReason?: string;
+ deleteLoading: boolean;
+ readLoading: boolean;
+ reportLoading: boolean;
}
interface PrivateMessageProps {
private_message_view: PrivateMessageView;
+ onDelete(form: DeletePrivateMessage): void;
+ onMarkRead(form: MarkPrivateMessageAsRead): void;
+ onReport(form: CreatePrivateMessageReport): void;
+ onCreate(form: CreatePrivateMessage): void;
+ onEdit(form: EditPrivateMessage): void;
}
export class PrivateMessage extends Component<
collapsed: false,
viewSource: false,
showReportDialog: false,
+ deleteLoading: false,
+ readLoading: false,
+ reportLoading: false,
};
constructor(props: any, context: any) {
super(props, context);
-
this.handleReplyCancel = this.handleReplyCancel.bind(this);
- this.handlePrivateMessageCreate =
- this.handlePrivateMessageCreate.bind(this);
- this.handlePrivateMessageEdit = this.handlePrivateMessageEdit.bind(this);
}
get mine(): boolean {
);
}
+ componentWillReceiveProps(
+ nextProps: Readonly<{ children?: InfernoNode } & PrivateMessageProps>
+ ): void {
+ if (this.props != nextProps) {
+ this.setState({
+ showReply: false,
+ showEdit: false,
+ collapsed: false,
+ viewSource: false,
+ showReportDialog: false,
+ deleteLoading: false,
+ readLoading: false,
+ reportLoading: false,
+ });
+ }
+ }
+
render() {
const message_view = this.props.private_message_view;
const otherPerson: Person = this.mine
<PrivateMessageForm
recipient={otherPerson}
privateMessageView={message_view}
- onEdit={this.handlePrivateMessageEdit}
- onCreate={this.handlePrivateMessageCreate}
+ onEdit={this.props.onEdit}
onCancel={this.handleReplyCancel}
/>
)}
: i18n.t("mark_as_read")
}
>
- <Icon
- icon="check"
- classes={`icon-inline ${
- message_view.private_message.read && "text-success"
- }`}
- />
+ {this.state.readLoading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="check"
+ classes={`icon-inline ${
+ message_view.private_message.read &&
+ "text-success"
+ }`}
+ />
+ )}
</button>
</li>
<li className="list-inline-item">{this.reportButton}</li>
: i18n.t("restore")
}
>
- <Icon
- icon="trash"
- classes={`icon-inline ${
- message_view.private_message.deleted &&
- "text-danger"
- }`}
- />
+ {this.state.deleteLoading ? (
+ <Spinner />
+ ) : (
+ <Icon
+ icon="trash"
+ classes={`icon-inline ${
+ message_view.private_message.deleted &&
+ "text-danger"
+ }`}
+ />
+ )}
</button>
</li>
</>
className="btn btn-secondary"
aria-label={i18n.t("create_report")}
>
- {i18n.t("create_report")}
+ {this.state.reportLoading ? <Spinner /> : i18n.t("create_report")}
</button>
</form>
)}
{this.state.showReply && (
<PrivateMessageForm
recipient={otherPerson}
- onCreate={this.handlePrivateMessageCreate}
+ onCreate={this.props.onCreate}
/>
)}
{/* A collapsed clearfix */}
}
handleDeleteClick(i: PrivateMessage) {
- const auth = myAuth();
- if (auth) {
- const form: DeletePrivateMessage = {
- private_message_id: i.props.private_message_view.private_message.id,
- deleted: !i.props.private_message_view.private_message.deleted,
- auth,
- };
- WebSocketService.Instance.send(wsClient.deletePrivateMessage(form));
- }
+ i.setState({ deleteLoading: true });
+ i.props.onDelete({
+ private_message_id: i.props.private_message_view.private_message.id,
+ deleted: !i.props.private_message_view.private_message.deleted,
+ auth: myAuthRequired(),
+ });
}
handleReplyCancel() {
}
handleMarkRead(i: PrivateMessage) {
- const auth = myAuth();
- if (auth) {
- const form: MarkPrivateMessageAsRead = {
- private_message_id: i.props.private_message_view.private_message.id,
- read: !i.props.private_message_view.private_message.read,
- auth,
- };
- WebSocketService.Instance.send(wsClient.markPrivateMessageAsRead(form));
- }
+ i.setState({ readLoading: true });
+ i.props.onMarkRead({
+ private_message_id: i.props.private_message_view.private_message.id,
+ read: !i.props.private_message_view.private_message.read,
+ auth: myAuthRequired(),
+ });
}
handleMessageCollapse(i: PrivateMessage) {
handleReportSubmit(i: PrivateMessage, event: any) {
event.preventDefault();
- const auth = myAuth();
- const reason = i.state.reportReason;
- if (auth && reason) {
- const form: CreatePrivateMessageReport = {
- private_message_id: i.props.private_message_view.private_message.id,
- reason,
- auth,
- };
- WebSocketService.Instance.send(wsClient.createPrivateMessageReport(form));
-
- i.setState({ showReportDialog: false });
- }
- }
-
- handlePrivateMessageEdit() {
- this.setState({ showEdit: false });
- }
-
- handlePrivateMessageCreate(message: PrivateMessageView) {
- if (
- message.creator.id ==
- UserService.Instance.myUserInfo?.local_user_view.person.id
- ) {
- this.setState({ showReply: false });
- toast(i18n.t("message_sent"));
- }
+ i.setState({ reportLoading: true });
+ i.props.onReport({
+ private_message_id: i.props.private_message_view.private_message.id,
+ reason: i.state.reportReason ?? "",
+ auth: myAuthRequired(),
+ });
}
}
import type { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import {
- CommentResponse,
CommentView,
CommunityView,
GetCommunity,
ListCommunitiesResponse,
ListingType,
PersonView,
- PostResponse,
PostView,
ResolveObject,
ResolveObjectResponse,
SearchResponse,
SearchType,
SortType,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../i18next";
import { CommentViewType, InitialFetchRequest } from "../interfaces";
-import { WebSocketService } from "../services";
+import { FirstLoadService } from "../services/FirstLoadService";
+import { HttpService, RequestState } from "../services/HttpService";
import {
Choice,
QueryParams,
capitalizeFirstLetter,
commentsToFlatNodes,
communityToChoice,
- createCommentLikeRes,
- createPostLikeFindRes,
debounce,
enableDownvotes,
enableNsfw,
saveScrollPosition,
setIsoData,
showLocal,
- toast,
- wsClient,
- wsSubscribe,
} from "../utils";
import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags";
type FilterType = "creator" | "community";
interface SearchState {
- searchResponse?: SearchResponse;
- communities: CommunityView[];
- creatorDetails?: GetPersonDetailsResponse;
- searchLoading: boolean;
- searchCommunitiesLoading: boolean;
- searchCreatorLoading: boolean;
+ searchRes: RequestState<SearchResponse>;
+ resolveObjectRes: RequestState<ResolveObjectResponse>;
+ creatorDetailsRes: RequestState<GetPersonDetailsResponse>;
+ communitiesRes: RequestState<ListCommunitiesResponse>;
+ communityRes: RequestState<GetCommunityResponse>;
siteRes: GetSiteResponse;
searchText?: string;
- resolveObjectResponse?: ResolveObjectResponse;
communitySearchOptions: Choice[];
creatorSearchOptions: Choice[];
+ searchCreatorLoading: boolean;
+ searchCommunitiesLoading: boolean;
+ isIsomorphic: boolean;
}
interface Combined {
export class Search extends Component<any, SearchState> {
private isoData = setIsoData(this.context);
- private subscription?: Subscription;
state: SearchState = {
- searchLoading: false,
+ resolveObjectRes: { state: "empty" },
+ creatorDetailsRes: { state: "empty" },
+ communitiesRes: { state: "empty" },
+ communityRes: { state: "empty" },
siteRes: this.isoData.site_res,
- communities: [],
- searchCommunitiesLoading: false,
- searchCreatorLoading: false,
creatorSearchOptions: [],
communitySearchOptions: [],
+ searchRes: { state: "empty" },
+ searchCreatorLoading: false,
+ searchCommunitiesLoading: false,
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
this.handleCommunityFilterChange.bind(this);
this.handleCreatorFilterChange = this.handleCreatorFilterChange.bind(this);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
const { q } = getSearchQueryParams();
this.state = {
};
// Only fetch the data if coming from another route
- if (this.isoData.path === this.context.router.route.match.url) {
- const communityRes = this.isoData.routeData[0] as
- | GetCommunityResponse
- | undefined;
- const communitiesRes = this.isoData.routeData[1] as
- | ListCommunitiesResponse
- | undefined;
- // This can be single or multiple communities given
- if (communitiesRes) {
- this.state = {
- ...this.state,
- communities: communitiesRes.communities,
- };
- }
- if (communityRes) {
+ if (FirstLoadService.isFirstLoad) {
+ const [
+ communityRes,
+ communitiesRes,
+ creatorDetailsRes,
+ searchRes,
+ resolveObjectRes,
+ ] = this.isoData.routeData;
+
+ this.state = {
+ ...this.state,
+ communitiesRes,
+ communityRes,
+ creatorDetailsRes,
+ creatorSearchOptions:
+ creatorDetailsRes.state == "success"
+ ? [personToChoice(creatorDetailsRes.data.person_view)]
+ : [],
+ isIsomorphic: true,
+ };
+
+ if (communityRes.state === "success") {
this.state = {
...this.state,
- communities: [communityRes.community_view],
communitySearchOptions: [
- communityToChoice(communityRes.community_view),
+ communityToChoice(communityRes.data.community_view),
],
};
}
- const creatorRes = this.isoData.routeData[2] as GetPersonDetailsResponse;
-
- this.state = {
- ...this.state,
- creatorDetails: creatorRes,
- creatorSearchOptions: creatorRes
- ? [personToChoice(creatorRes.person_view)]
- : [],
- };
-
- if (q !== "") {
+ if (q) {
this.state = {
...this.state,
- searchResponse: this.isoData.routeData[3] as SearchResponse,
- resolveObjectResponse: this.isoData
- .routeData[4] as ResolveObjectResponse,
- searchLoading: false,
+ searchRes,
+ resolveObjectRes,
};
- } else {
- this.search();
}
- } else {
- const listCommunitiesForm: ListCommunities = {
- type_: defaultListingType,
- sort: defaultSortType,
- limit: fetchLimit,
- auth: myAuth(false),
- };
-
- WebSocketService.Instance.send(
- wsClient.listCommunities(listCommunitiesForm)
- );
+ }
+ }
- if (q) {
- this.search();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ const promises = [this.fetchCommunities()];
+ if (this.state.searchText) {
+ promises.push(this.search());
}
+
+ await Promise.all(promises);
}
}
+ async fetchCommunities() {
+ this.setState({ communitiesRes: { state: "loading" } });
+ this.setState({
+ communitiesRes: await HttpService.client.listCommunities({
+ type_: defaultListingType,
+ sort: defaultSortType,
+ limit: fetchLimit,
+ auth: myAuth(),
+ }),
+ });
+ }
+
componentWillUnmount() {
- this.subscription?.unsubscribe();
saveScrollPosition(this.context);
}
client,
auth,
query: { communityId, creatorId, q, type, sort, listingType, page },
- }: InitialFetchRequest<QueryParams<SearchProps>>): Promise<any>[] {
- const promises: Promise<any>[] = [];
+ }: InitialFetchRequest<QueryParams<SearchProps>>): Promise<
+ RequestState<any>
+ >[] {
+ const promises: Promise<RequestState<any>>[] = [];
const community_id = getIdFromString(communityId);
if (community_id) {
auth,
};
promises.push(client.getCommunity(getCommunityForm));
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
} else {
const listCommunitiesForm: ListCommunities = {
type_: defaultListingType,
limit: fetchLimit,
auth,
};
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
promises.push(client.listCommunities(listCommunitiesForm));
}
};
promises.push(client.getPersonDetails(getCreatorForm));
} else {
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
}
const query = getSearchQueryFromQuery(q);
promises.push(client.resolveObject(resolveObjectForm));
}
} else {
- promises.push(Promise.resolve());
- promises.push(Promise.resolve());
+ promises.push(Promise.resolve({ state: "empty" }));
+ promises.push(Promise.resolve({ state: "empty" }));
}
}
{this.selects}
{this.searchForm}
{this.displayResults(type)}
- {this.resultsCount === 0 && !this.state.searchLoading && (
- <span>{i18n.t("no_results")}</span>
- )}
+ {this.resultsCount === 0 &&
+ this.state.searchRes.state === "success" && (
+ <span>{i18n.t("no_results")}</span>
+ )}
<Paginator page={page} onChange={this.handlePageChange} />
</div>
);
minLength={1}
/>
<button type="submit" className="btn btn-secondary mr-2 mb-2">
- {this.state.searchLoading ? (
+ {this.state.searchRes.state == "loading" ? (
<Spinner />
) : (
<span>{i18n.t("search")}</span>
creatorSearchOptions,
searchCommunitiesLoading,
searchCreatorLoading,
+ communitiesRes,
} = this.state;
+ const hasCommunities =
+ communitiesRes.state == "success" &&
+ communitiesRes.data.communities.length > 0;
+
return (
<div className="mb-2">
<select
/>
</span>
<div className="form-row">
- {this.state.communities.length > 0 && (
+ {hasCommunities && (
<Filter
filterType="community"
onChange={this.handleCommunityFilterChange}
onSearch={this.handleCommunitySearch}
options={communitySearchOptions}
- loading={searchCommunitiesLoading}
value={communityId}
+ loading={searchCommunitiesLoading}
/>
)}
<Filter
onChange={this.handleCreatorFilterChange}
onSearch={this.handleCreatorSearch}
options={creatorSearchOptions}
- loading={searchCreatorLoading}
value={creatorId}
+ loading={searchCreatorLoading}
/>
</div>
</div>
buildCombined(): Combined[] {
const combined: Combined[] = [];
- const { resolveObjectResponse, searchResponse } = this.state;
+ const {
+ resolveObjectRes: resolveObjectResponse,
+ searchRes: searchResponse,
+ } = this.state;
// Push the possible resolve / federated objects first
- if (resolveObjectResponse) {
- const { comment, post, community, person } = resolveObjectResponse;
+ if (resolveObjectResponse.state == "success") {
+ const { comment, post, community, person } = resolveObjectResponse.data;
if (comment) {
combined.push(commentViewToCombined(comment));
}
// Push the search results
- if (searchResponse) {
- const { comments, posts, communities, users } = searchResponse;
+ if (searchResponse.state === "success") {
+ const { comments, posts, communities, users } = searchResponse.data;
combined.push(
...[
allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages}
viewOnly
+ // All of these are unused, since its view only
+ onPostEdit={() => {}}
+ onPostVote={() => {}}
+ onPostReport={() => {}}
+ onBlockPerson={() => {}}
+ onLockPost={() => {}}
+ onDeletePost={() => {}}
+ onRemovePost={() => {}}
+ onSavePost={() => {}}
+ onFeaturePost={() => {}}
+ onPurgePerson={() => {}}
+ onPurgePost={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
/>
)}
{i.type_ === "comments" && (
enableDownvotes={enableDownvotes(this.state.siteRes)}
allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages}
+ // All of these are unused, since its viewonly
+ finished={new Map()}
+ onSaveComment={() => {}}
+ onBlockPerson={() => {}}
+ onDeleteComment={() => {}}
+ onRemoveComment={() => {}}
+ onCommentVote={() => {}}
+ onCommentReport={() => {}}
+ onDistinguishComment={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
+ onPurgeComment={() => {}}
+ onPurgePerson={() => {}}
+ onCommentReplyRead={() => {}}
+ onPersonMentionRead={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onCreateComment={() => Promise.resolve({ state: "empty" })}
+ onEditComment={() => Promise.resolve({ state: "empty" })}
/>
)}
{i.type_ === "communities" && (
}
get comments() {
- const { searchResponse, resolveObjectResponse, siteRes } = this.state;
- const comments = searchResponse?.comments ?? [];
-
- if (resolveObjectResponse?.comment) {
- comments.unshift(resolveObjectResponse?.comment);
+ const {
+ searchRes: searchResponse,
+ resolveObjectRes: resolveObjectResponse,
+ siteRes,
+ } = this.state;
+ const comments =
+ searchResponse.state === "success" ? searchResponse.data.comments : [];
+
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.comment
+ ) {
+ comments.unshift(resolveObjectResponse.data.comment);
}
return (
enableDownvotes={enableDownvotes(siteRes)}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
+ // All of these are unused, since its viewonly
+ finished={new Map()}
+ onSaveComment={() => {}}
+ onBlockPerson={() => {}}
+ onDeleteComment={() => {}}
+ onRemoveComment={() => {}}
+ onCommentVote={() => {}}
+ onCommentReport={() => {}}
+ onDistinguishComment={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
+ onPurgeComment={() => {}}
+ onPurgePerson={() => {}}
+ onCommentReplyRead={() => {}}
+ onPersonMentionRead={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onCreateComment={() => Promise.resolve({ state: "empty" })}
+ onEditComment={() => Promise.resolve({ state: "empty" })}
/>
);
}
get posts() {
- const { searchResponse, resolveObjectResponse, siteRes } = this.state;
- const posts = searchResponse?.posts ?? [];
-
- if (resolveObjectResponse?.post) {
- posts.unshift(resolveObjectResponse.post);
+ const {
+ searchRes: searchResponse,
+ resolveObjectRes: resolveObjectResponse,
+ siteRes,
+ } = this.state;
+ const posts =
+ searchResponse.state === "success" ? searchResponse.data.posts : [];
+
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.post
+ ) {
+ posts.unshift(resolveObjectResponse.data.post);
}
return (
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
viewOnly
+ // All of these are unused, since its view only
+ onPostEdit={() => {}}
+ onPostVote={() => {}}
+ onPostReport={() => {}}
+ onBlockPerson={() => {}}
+ onLockPost={() => {}}
+ onDeletePost={() => {}}
+ onRemovePost={() => {}}
+ onSavePost={() => {}}
+ onFeaturePost={() => {}}
+ onPurgePerson={() => {}}
+ onPurgePost={() => {}}
+ onBanPersonFromCommunity={() => {}}
+ onBanPerson={() => {}}
+ onAddModToCommunity={() => {}}
+ onAddAdmin={() => {}}
+ onTransferCommunity={() => {}}
/>
</div>
</div>
}
get communities() {
- const { searchResponse, resolveObjectResponse } = this.state;
- const communities = searchResponse?.communities ?? [];
-
- if (resolveObjectResponse?.community) {
- communities.unshift(resolveObjectResponse.community);
+ const {
+ searchRes: searchResponse,
+ resolveObjectRes: resolveObjectResponse,
+ } = this.state;
+ const communities =
+ searchResponse.state === "success" ? searchResponse.data.communities : [];
+
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.community
+ ) {
+ communities.unshift(resolveObjectResponse.data.community);
}
return (
}
get users() {
- const { searchResponse, resolveObjectResponse } = this.state;
- const users = searchResponse?.users ?? [];
-
- if (resolveObjectResponse?.person) {
- users.unshift(resolveObjectResponse.person);
+ const {
+ searchRes: searchResponse,
+ resolveObjectRes: resolveObjectResponse,
+ } = this.state;
+ const users =
+ searchResponse.state === "success" ? searchResponse.data.users : [];
+
+ if (
+ resolveObjectResponse.state === "success" &&
+ resolveObjectResponse.data.person
+ ) {
+ users.unshift(resolveObjectResponse.data.person);
}
return (
}
get resultsCount(): number {
- const { searchResponse: r, resolveObjectResponse: resolveRes } = this.state;
-
- const searchCount = r
- ? r.posts.length +
- r.comments.length +
- r.communities.length +
- r.users.length
- : 0;
-
- const resObjCount = resolveRes
- ? resolveRes.post ||
- resolveRes.person ||
- resolveRes.community ||
- resolveRes.comment
- ? 1
- : 0
- : 0;
+ const { searchRes: r, resolveObjectRes: resolveRes } = this.state;
+
+ const searchCount =
+ r.state === "success"
+ ? r.data.posts.length +
+ r.data.comments.length +
+ r.data.communities.length +
+ r.data.users.length
+ : 0;
+
+ const resObjCount =
+ resolveRes.state === "success"
+ ? resolveRes.data.post ||
+ resolveRes.data.person ||
+ resolveRes.data.community ||
+ resolveRes.data.comment
+ ? 1
+ : 0
+ : 0;
return resObjCount + searchCount;
}
- search() {
- const auth = myAuth(false);
+ async search() {
+ const auth = myAuth();
const { searchText: q } = this.state;
const { communityId, creatorId, type, sort, listingType, page } =
getSearchQueryParams();
- if (q && q !== "") {
- const form: SearchForm = {
- q,
- community_id: communityId ?? undefined,
- creator_id: creatorId ?? undefined,
- type_: type,
- sort,
- listing_type: listingType,
- page,
- limit: fetchLimit,
- auth,
- };
-
- if (auth) {
- const resolveObjectForm: ResolveObject = {
+ if (q) {
+ this.setState({ searchRes: { state: "loading" } });
+ this.setState({
+ searchRes: await HttpService.client.search({
q,
+ community_id: communityId ?? undefined,
+ creator_id: creatorId ?? undefined,
+ type_: type,
+ sort,
+ listing_type: listingType,
+ page,
+ limit: fetchLimit,
auth,
- };
- WebSocketService.Instance.send(
- wsClient.resolveObject(resolveObjectForm)
- );
- }
-
- this.setState({
- searchResponse: undefined,
- resolveObjectResponse: undefined,
- searchLoading: true,
+ }),
});
+ window.scrollTo(0, 0);
+ restoreScrollPosition(this.context);
- WebSocketService.Instance.send(wsClient.search(form));
+ if (auth) {
+ this.setState({ resolveObjectRes: { state: "loading" } });
+ this.setState({
+ resolveObjectRes: await HttpService.client.resolveObject({
+ q,
+ auth,
+ }),
+ });
+ }
}
}
handleCreatorSearch = debounce(async (text: string) => {
const { creatorId } = getSearchQueryParams();
const { creatorSearchOptions } = this.state;
- this.setState({
- searchCreatorLoading: true,
- });
-
const newOptions: Choice[] = [];
+ this.setState({ searchCreatorLoading: true });
+
const selectedChoice = creatorSearchOptions.find(
choice => getIdFromString(choice.value) === creatorId
);
}
if (text.length > 0) {
- newOptions.push(...(await fetchUsers(text)).users.map(personToChoice));
+ newOptions.push(...(await fetchUsers(text)).map(personToChoice));
}
this.setState({
}
if (text.length > 0) {
- newOptions.push(
- ...(await fetchCommunities(text)).communities.map(communityToChoice)
- );
+ newOptions.push(...(await fetchCommunities(text)).map(communityToChoice));
}
this.setState({
i.setState({ searchText: event.target.value });
}
- updateUrl({
+ async updateUrl({
q,
type,
listingType,
this.props.history.push(`/search${getQueryString(queryParams)}`);
- this.search();
- }
-
- parseMessage(msg: any) {
- console.log(msg);
- const op = wsUserOp(msg);
- if (msg.error) {
- if (msg.error === "couldnt_find_object") {
- this.setState({
- resolveObjectResponse: {},
- });
- this.checkFinishedLoading();
- } else {
- toast(i18n.t(msg.error), "danger");
- }
- } else {
- switch (op) {
- case UserOperation.Search: {
- const searchResponse = wsJsonToRes<SearchResponse>(msg);
- this.setState({ searchResponse });
- window.scrollTo(0, 0);
- this.checkFinishedLoading();
- restoreScrollPosition(this.context);
-
- break;
- }
-
- case UserOperation.CreateCommentLike: {
- const { comment_view } = wsJsonToRes<CommentResponse>(msg);
- createCommentLikeRes(
- comment_view,
- this.state.searchResponse?.comments
- );
-
- break;
- }
-
- case UserOperation.CreatePostLike: {
- const { post_view } = wsJsonToRes<PostResponse>(msg);
- createPostLikeFindRes(post_view, this.state.searchResponse?.posts);
-
- break;
- }
-
- case UserOperation.ListCommunities: {
- const { communities } = wsJsonToRes<ListCommunitiesResponse>(msg);
- this.setState({ communities });
-
- break;
- }
-
- case UserOperation.ResolveObject: {
- const resolveObjectResponse = wsJsonToRes<ResolveObjectResponse>(msg);
- this.setState({ resolveObjectResponse });
- this.checkFinishedLoading();
-
- break;
- }
- }
- }
- }
-
- checkFinishedLoading() {
- if (this.state.searchResponse || this.state.resolveObjectResponse) {
- this.setState({ searchLoading: false });
- }
+ await this.search();
}
}
return isBrowser() ? getExternalHost() : getInternalHost();
}
-function getWsHost() {
- return isBrowser()
- ? window.lemmyConfig?.wsHost ?? getHost()
- : process.env.LEMMY_UI_LEMMY_WS_HOST ?? getExternalHost();
-}
-
function getBaseLocal(s = "") {
return `http${s}://${getHost()}`;
}
export function getHttpBaseInternal() {
return getBaseLocal(); // Don't use secure here
}
+
+export function getHttpBaseExternal() {
+ return `http${getSecure()}://${getExternalHost()}`;
+}
+
export function getHttpBase() {
return getBaseLocal(getSecure());
}
-export function getWsUri() {
- return `ws${getSecure()}://${getWsHost()}/api/v3/ws`;
-}
+
export function isHttps() {
return getSecure() === "s";
}
console.log(`httpbase: ${getHttpBase()}`);
-console.log(`wsUri: ${getWsUri()}`);
console.log(`isHttps: ${isHttps()}`);
// This is for html tags, don't include port
-import { CommentView, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
+import { CommentView, GetSiteResponse } from "lemmy-js-client";
import type { ParsedQs } from "qs";
+import { RequestState, WrappedLemmyHttp } from "./services/HttpService";
import { ErrorPageData } from "./utils";
/**
*/
export interface IsoData {
path: string;
- routeData: any[];
+ routeData: RequestState<any>[];
site_res: GetSiteResponse;
errorPageData?: ErrorPageData;
}
export interface InitialFetchRequest<T extends ParsedQs = ParsedQs> {
auth?: string;
- client: LemmyHttp;
+ client: WrappedLemmyHttp;
path: string;
query: T;
site: GetSiteResponse;
Comment,
}
+export enum VoteType {
+ Upvote,
+ Downvote,
+}
+
export interface CommentNodeI {
comment_view: CommentView;
children: Array<CommentNodeI>;
import { CreatePrivateMessage } from "./components/private_message/create-private-message";
import { Search } from "./components/search";
import { InitialFetchRequest } from "./interfaces";
+import { RequestState } from "./services/HttpService";
interface IRoutePropsWithFetch extends IRouteProps {
// TODO Make sure this one is good.
- fetchInitialData?(req: InitialFetchRequest): Promise<any>[];
+ fetchInitialData?(req: InitialFetchRequest): Promise<RequestState<any>>[];
}
export const routes: IRoutePropsWithFetch[] = [
--- /dev/null
+export class FirstLoadService {
+ #isFirstLoad: boolean;
+ static #instance: FirstLoadService;
+
+ private constructor() {
+ this.#isFirstLoad = true;
+ }
+
+ get isFirstLoad() {
+ const isFirst = this.#isFirstLoad;
+ if (isFirst) {
+ this.#isFirstLoad = false;
+ }
+
+ return isFirst;
+ }
+
+ static get #Instance() {
+ return this.#instance ?? (this.#instance = new this());
+ }
+
+ static get isFirstLoad() {
+ return this.#Instance.isFirstLoad;
+ }
+}
--- /dev/null
+import { History, createBrowserHistory } from "history";
+
+export class HistoryService {
+ static #_instance: HistoryService;
+ #history: History;
+
+ private constructor() {
+ this.#history = createBrowserHistory();
+ }
+
+ static get #Instance() {
+ return this.#_instance ?? (this.#_instance = new this());
+ }
+
+ public static get history() {
+ return this.#Instance.#history;
+ }
+}
--- /dev/null
+import { LemmyHttp } from "lemmy-js-client";
+import { getHttpBase } from "../../shared/env";
+import { i18n } from "../../shared/i18next";
+import { toast } from "../../shared/utils";
+
+type EmptyRequestState = {
+ state: "empty";
+};
+
+type LoadingRequestState = {
+ state: "loading";
+};
+
+type FailedRequestState = {
+ state: "failed";
+ msg: string;
+};
+
+type SuccessRequestState<T> = {
+ state: "success";
+ data: T;
+};
+
+/**
+ * Shows the state of an API request.
+ *
+ * Can be empty, loading, failed, or success
+ */
+export type RequestState<T> =
+ | EmptyRequestState
+ | LoadingRequestState
+ | FailedRequestState
+ | SuccessRequestState<T>;
+
+export type WrappedLemmyHttp = {
+ [K in keyof LemmyHttp]: LemmyHttp[K] extends (...args: any[]) => any
+ ? ReturnType<LemmyHttp[K]> extends Promise<infer U>
+ ? (...args: Parameters<LemmyHttp[K]>) => Promise<RequestState<U>>
+ : (
+ ...args: Parameters<LemmyHttp[K]>
+ ) => Promise<RequestState<LemmyHttp[K]>>
+ : LemmyHttp[K];
+};
+
+class WrappedLemmyHttpClient {
+ #client: LemmyHttp;
+
+ constructor(client: LemmyHttp) {
+ this.#client = client;
+
+ for (const key of Object.getOwnPropertyNames(
+ Object.getPrototypeOf(this.#client)
+ )) {
+ if (key !== "constructor") {
+ WrappedLemmyHttpClient.prototype[key] = async (...args) => {
+ try {
+ const res = await this.#client[key](...args);
+
+ return {
+ data: res,
+ state: "success",
+ };
+ } catch (error) {
+ console.error(`API error: ${error}`);
+ toast(i18n.t(error), "danger");
+ return {
+ state: "failed",
+ msg: error,
+ };
+ }
+ };
+ }
+ }
+ }
+}
+
+export function wrapClient(client: LemmyHttp) {
+ return new WrappedLemmyHttpClient(client) as unknown as WrappedLemmyHttp; // unfortunately, this verbose cast is necessary
+}
+
+export class HttpService {
+ static #_instance: HttpService;
+ #client: WrappedLemmyHttp;
+
+ private constructor() {
+ this.#client = wrapClient(new LemmyHttp(getHttpBase()));
+ }
+
+ static get #Instance() {
+ return this.#_instance ?? (this.#_instance = new this());
+ }
+
+ public static get client() {
+ return this.#Instance.#client;
+ }
+}
import IsomorphicCookie from "isomorphic-cookie";
import jwt_decode from "jwt-decode";
import { LoginResponse, MyUserInfo } from "lemmy-js-client";
-import { BehaviorSubject } from "rxjs";
import { isHttps } from "../env";
import { i18n } from "../i18next";
import { isAuthPath, isBrowser, toast } from "../utils";
}
export class UserService {
- private static _instance: UserService;
+ static #instance: UserService;
public myUserInfo?: MyUserInfo;
public jwtInfo?: JwtInfo;
- public unreadInboxCountSub: BehaviorSubject<number> =
- new BehaviorSubject<number>(0);
- public unreadReportCountSub: BehaviorSubject<number> =
- new BehaviorSubject<number>(0);
- public unreadApplicationCountSub: BehaviorSubject<number> =
- new BehaviorSubject<number>(0);
private constructor() {
- this.setJwtInfo();
+ this.#setJwtInfo();
}
public login(res: LoginResponse) {
if (res.jwt) {
toast(i18n.t("logged_in"));
IsomorphicCookie.save("jwt", res.jwt, { expires, secure: isHttps() });
- this.setJwtInfo();
+ this.#setJwtInfo();
}
}
}
}
- public auth(throwErr = true): string | undefined {
+ public auth(throwErr = false): string | undefined {
const jwt = this.jwtInfo?.jwt;
if (jwt) {
return jwt;
}
}
- private setJwtInfo() {
+ #setJwtInfo() {
const jwt: string | undefined = IsomorphicCookie.load("jwt");
if (jwt) {
}
public static get Instance() {
- return this._instance || (this._instance = new this());
+ return this.#instance || (this.#instance = new this());
}
}
+++ /dev/null
-import { Observable } from "rxjs";
-import { share } from "rxjs/operators";
-import {
- ExponentialBackoff,
- LRUBuffer,
- Websocket as WS,
- WebsocketBuilder,
-} from "websocket-ts";
-import { getWsUri } from "../env";
-import { isBrowser } from "../utils";
-
-export class WebSocketService {
- private static _instance: WebSocketService;
- private ws: WS;
- public subject: Observable<any>;
-
- private constructor() {
- let firstConnect = true;
-
- this.subject = new Observable((obs: any) => {
- this.ws = new WebsocketBuilder(getWsUri())
- .onMessage((_i, e) => {
- try {
- obs.next(JSON.parse(e.data.toString()));
- } catch (err) {
- console.error(err);
- }
- })
- .onOpen(() => {
- console.log(`Connected to ${getWsUri()}`);
-
- if (!firstConnect) {
- const res = {
- reconnect: true,
- };
- obs.next(res);
- }
- firstConnect = false;
- })
- .onRetry(() => {
- console.log("Retrying websocket connection...");
- })
- .onClose(() => {
- console.error("Websocket closed.");
- })
- .withBackoff(new ExponentialBackoff(100, 7))
- .withBuffer(new LRUBuffer(1000))
- .build();
- }).pipe(share());
-
- if (isBrowser()) {
- window.onunload = () => {
- this.ws.close();
-
- // Clears out scroll positions.
- sessionStorage.clear();
- };
- }
- }
-
- public send(data: string) {
- this.ws.send(data);
- }
-
- public static get Instance() {
- return this._instance || (this._instance = new this());
- }
-}
+export { HttpService } from "./HttpService";
export { UserService } from "./UserService";
-export { WebSocketService } from "./WebSocketService";
import {
BlockCommunityResponse,
BlockPersonResponse,
+ CommentAggregates,
Comment as CommentI,
+ CommentReplyView,
CommentReportView,
CommentSortType,
CommentView,
GetSiteResponse,
Language,
LemmyHttp,
- LemmyWebsocket,
MyUserInfo,
Person,
+ PersonMentionView,
PersonView,
PostReportView,
PostView,
PrivateMessageView,
RegistrationApplicationView,
Search,
+ SearchType,
SortType,
- UploadImageResponse,
} from "lemmy-js-client";
import { default as MarkdownIt } from "markdown-it";
import markdown_it_container from "markdown-it-container";
import Renderer from "markdown-it/lib/renderer";
import Token from "markdown-it/lib/token";
import moment from "moment";
-import { Subscription } from "rxjs";
-import { delay, retryWhen, take } from "rxjs/operators";
import tippy from "tippy.js";
import Toastify from "toastify-js";
import { getHttpBase } from "./env";
import { i18n, languages } from "./i18next";
-import { CommentNodeI, DataType, IsoData } from "./interfaces";
-import { UserService, WebSocketService } from "./services";
+import { CommentNodeI, DataType, IsoData, VoteType } from "./interfaces";
+import { HttpService, UserService } from "./services";
let Tribute: any;
if (isBrowser()) {
Tribute = require("tributejs");
}
-export const wsClient = new LemmyWebsocket();
-
export const favIconUrl = "/static/assets/icons/favicon.svg";
export const favIconPngUrl = "/static/assets/icons/apple-touch-icon.png";
// TODO
}
export function mdToHtml(text: string) {
- // restore '>' character to fix quotes
return { __html: md.render(text) };
}
export function mdToHtmlNoImages(text: string) {
- // restore '>' character to fix quotes
return { __html: mdNoImages.render(text) };
}
}
}
-interface NotifyInfo {
- name: string;
- icon?: string;
- link: string;
- body?: string;
-}
-
-export function messageToastify(info: NotifyInfo, router: any) {
- if (isBrowser()) {
- const htmlBody = info.body ? md.render(info.body) : "";
- const backgroundColor = `var(--light)`;
-
- const toast = Toastify({
- text: `${htmlBody}<br />${info.name}`,
- avatar: info.icon,
- backgroundColor: backgroundColor,
- className: "text-dark",
- close: true,
- gravity: "top",
- position: "right",
- duration: 5000,
- escapeMarkup: false,
- onClick: () => {
- if (toast) {
- toast.hideToast();
- router.history.push(info.link);
- }
- },
- });
- toast.showToast();
- }
-}
-
-export function notifyPost(post_view: PostView, router: any) {
- const info: NotifyInfo = {
- name: post_view.community.name,
- icon: post_view.community.icon,
- link: `/post/${post_view.post.id}`,
- body: post_view.post.name,
- };
- notify(info, router);
-}
-
-export function notifyComment(comment_view: CommentView, router: any) {
- const info: NotifyInfo = {
- name: comment_view.creator.name,
- icon: comment_view.creator.avatar,
- link: `/comment/${comment_view.comment.id}`,
- body: comment_view.comment.content,
- };
- notify(info, router);
-}
-
-export function notifyPrivateMessage(pmv: PrivateMessageView, router: any) {
- const info: NotifyInfo = {
- name: pmv.creator.name,
- icon: pmv.creator.avatar,
- link: `/inbox`,
- body: pmv.private_message.content,
- };
- notify(info, router);
-}
-
-function notify(info: NotifyInfo, router: any) {
- messageToastify(info, router);
-
- if (Notification.permission !== "granted") Notification.requestPermission();
- else {
- var notification = new Notification(info.name, {
- ...{ body: info.body },
- ...(info.icon && { icon: info.icon }),
- });
-
- notification.onclick = (ev: Event): any => {
- ev.preventDefault();
- router.history.push(info.link);
- };
- }
-}
-
export function setupTribute() {
return new Tribute({
noMatchTemplate: function () {
}
async function personSearch(text: string): Promise<PersonTribute[]> {
- const users = (await fetchUsers(text)).users;
- const persons: PersonTribute[] = users.map(pv => {
- const tribute: PersonTribute = {
- key: `@${pv.person.name}@${hostname(pv.person.actor_id)}`,
- view: pv,
- };
- return tribute;
- });
- return persons;
+ const usersResponse = await fetchUsers(text);
+
+ return usersResponse.map(pv => ({
+ key: `@${pv.person.name}@${hostname(pv.person.actor_id)}`,
+ view: pv,
+ }));
}
interface CommunityTribute {
}
async function communitySearch(text: string): Promise<CommunityTribute[]> {
- const comms = (await fetchCommunities(text)).communities;
- const communities: CommunityTribute[] = comms.map(cv => {
- const tribute: CommunityTribute = {
- key: `!${cv.community.name}@${hostname(cv.community.actor_id)}`,
- view: cv,
- };
- return tribute;
- });
- return communities;
+ const communitiesResponse = await fetchCommunities(text);
+
+ return communitiesResponse.map(cv => ({
+ key: `!${cv.community.name}@${hostname(cv.community.actor_id)}`,
+ view: cv,
+ }));
}
export function getRecipientIdFromProps(props: any): number {
return id ? Number(id) : undefined;
}
-export function editCommentRes(data: CommentView, comments?: CommentView[]) {
- const found = comments?.find(c => c.comment.id == data.comment.id);
- if (found) {
- found.comment.content = data.comment.content;
- found.comment.distinguished = data.comment.distinguished;
- found.comment.updated = data.comment.updated;
- found.comment.removed = data.comment.removed;
- found.comment.deleted = data.comment.deleted;
- found.counts.upvotes = data.counts.upvotes;
- found.counts.downvotes = data.counts.downvotes;
- found.counts.score = data.counts.score;
- }
+type ImmutableListKey =
+ | "comment"
+ | "comment_reply"
+ | "person_mention"
+ | "community"
+ | "private_message"
+ | "post"
+ | "post_report"
+ | "comment_report"
+ | "private_message_report"
+ | "registration_application";
+
+function editListImmutable<
+ T extends { [key in F]: { id: number } },
+ F extends ImmutableListKey
+>(fieldName: F, data: T, list: T[]): T[] {
+ return [
+ ...list.map(c => (c[fieldName].id === data[fieldName].id ? data : c)),
+ ];
+}
+
+export function editComment(
+ data: CommentView,
+ comments: CommentView[]
+): CommentView[] {
+ return editListImmutable("comment", data, comments);
}
-export function saveCommentRes(data: CommentView, comments?: CommentView[]) {
- const found = comments?.find(c => c.comment.id == data.comment.id);
- if (found) {
- found.saved = data.saved;
- }
+export function editCommentReply(
+ data: CommentReplyView,
+ replies: CommentReplyView[]
+): CommentReplyView[] {
+ return editListImmutable("comment_reply", data, replies);
+}
+
+interface WithComment {
+ comment: CommentI;
+ counts: CommentAggregates;
+ my_vote?: number;
+ saved: boolean;
+}
+
+export function editMention(
+ data: PersonMentionView,
+ comments: PersonMentionView[]
+): PersonMentionView[] {
+ return editListImmutable("person_mention", data, comments);
+}
+
+export function editCommunity(
+ data: CommunityView,
+ communities: CommunityView[]
+): CommunityView[] {
+ return editListImmutable("community", data, communities);
+}
+
+export function editPrivateMessage(
+ data: PrivateMessageView,
+ messages: PrivateMessageView[]
+): PrivateMessageView[] {
+ return editListImmutable("private_message", data, messages);
+}
+
+export function editPost(data: PostView, posts: PostView[]): PostView[] {
+ return editListImmutable("post", data, posts);
+}
+
+export function editPostReport(
+ data: PostReportView,
+ reports: PostReportView[]
+) {
+ return editListImmutable("post_report", data, reports);
+}
+
+export function editCommentReport(
+ data: CommentReportView,
+ reports: CommentReportView[]
+): CommentReportView[] {
+ return editListImmutable("comment_report", data, reports);
+}
+
+export function editPrivateMessageReport(
+ data: PrivateMessageReportView,
+ reports: PrivateMessageReportView[]
+): PrivateMessageReportView[] {
+ return editListImmutable("private_message_report", data, reports);
+}
+
+export function editRegistrationApplication(
+ data: RegistrationApplicationView,
+ apps: RegistrationApplicationView[]
+): RegistrationApplicationView[] {
+ return editListImmutable("registration_application", data, apps);
+}
+
+export function editWith<D extends WithComment, L extends WithComment>(
+ { comment, counts, saved, my_vote }: D,
+ list: L[]
+) {
+ return [
+ ...list.map(c =>
+ c.comment.id === comment.id
+ ? { ...c, comment, counts, saved, my_vote }
+ : c
+ ),
+ ];
}
export function updatePersonBlock(
data: BlockPersonResponse,
myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
) {
- const mui = myUserInfo;
- if (mui) {
+ if (myUserInfo) {
if (data.blocked) {
- mui.person_blocks.push({
- person: mui.local_user_view.person,
+ myUserInfo.person_blocks.push({
+ person: myUserInfo.local_user_view.person,
target: data.person_view.person,
});
toast(`${i18n.t("blocked")} ${data.person_view.person.name}`);
} else {
- mui.person_blocks = mui.person_blocks.filter(
- i => i.target.id != data.person_view.person.id
+ myUserInfo.person_blocks = myUserInfo.person_blocks.filter(
+ i => i.target.id !== data.person_view.person.id
);
toast(`${i18n.t("unblocked")} ${data.person_view.person.name}`);
}
data: BlockCommunityResponse,
myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
) {
- const mui = myUserInfo;
- if (mui) {
+ if (myUserInfo) {
if (data.blocked) {
- mui.community_blocks.push({
- person: mui.local_user_view.person,
+ myUserInfo.community_blocks.push({
+ person: myUserInfo.local_user_view.person,
community: data.community_view.community,
});
toast(`${i18n.t("blocked")} ${data.community_view.community.name}`);
} else {
- mui.community_blocks = mui.community_blocks.filter(
- i => i.community.id != data.community_view.community.id
+ myUserInfo.community_blocks = myUserInfo.community_blocks.filter(
+ i => i.community.id !== data.community_view.community.id
);
toast(`${i18n.t("unblocked")} ${data.community_view.community.name}`);
}
}
}
-export function createCommentLikeRes(
- data: CommentView,
- comments?: CommentView[]
-) {
- const found = comments?.find(c => c.comment.id === data.comment.id);
- if (found) {
- found.counts.score = data.counts.score;
- found.counts.upvotes = data.counts.upvotes;
- found.counts.downvotes = data.counts.downvotes;
- if (data.my_vote !== null) {
- found.my_vote = data.my_vote;
- }
- }
-}
-
-export function createPostLikeFindRes(data: PostView, posts?: PostView[]) {
- const found = posts?.find(p => p.post.id == data.post.id);
- if (found) {
- createPostLikeRes(data, found);
- }
-}
-
-export function createPostLikeRes(data: PostView, post_view?: PostView) {
- if (post_view) {
- post_view.counts.score = data.counts.score;
- post_view.counts.upvotes = data.counts.upvotes;
- post_view.counts.downvotes = data.counts.downvotes;
- if (data.my_vote !== null) {
- post_view.my_vote = data.my_vote;
- }
- }
-}
-
-export function editPostFindRes(data: PostView, posts?: PostView[]) {
- const found = posts?.find(p => p.post.id == data.post.id);
- if (found) {
- editPostRes(data, found);
- }
-}
-
-export function editPostRes(data: PostView, post: PostView) {
- if (post) {
- post.post.url = data.post.url;
- post.post.name = data.post.name;
- post.post.nsfw = data.post.nsfw;
- post.post.deleted = data.post.deleted;
- post.post.removed = data.post.removed;
- post.post.featured_community = data.post.featured_community;
- post.post.featured_local = data.post.featured_local;
- post.post.body = data.post.body;
- post.post.locked = data.post.locked;
- post.saved = data.saved;
- }
-}
-
-// TODO possible to make these generic?
-export function updatePostReportRes(
- data: PostReportView,
- reports?: PostReportView[]
-) {
- const found = reports?.find(p => p.post_report.id == data.post_report.id);
- if (found) {
- found.post_report = data.post_report;
- }
-}
-
-export function updateCommentReportRes(
- data: CommentReportView,
- reports?: CommentReportView[]
-) {
- const found = reports?.find(
- c => c.comment_report.id == data.comment_report.id
- );
- if (found) {
- found.comment_report = data.comment_report;
- }
-}
-
-export function updatePrivateMessageReportRes(
- data: PrivateMessageReportView,
- reports?: PrivateMessageReportView[]
-) {
- const found = reports?.find(
- c => c.private_message_report.id == data.private_message_report.id
- );
- if (found) {
- found.private_message_report = data.private_message_report;
- }
-}
-
-export function updateRegistrationApplicationRes(
- data: RegistrationApplicationView,
- applications?: RegistrationApplicationView[]
-) {
- const found = applications?.find(
- ra => ra.registration_application.id == data.registration_application.id
- );
- if (found) {
- found.registration_application = data.registration_application;
- found.admin = data.admin;
- found.creator_local_user = data.creator_local_user;
- }
-}
-
export function commentsToFlatNodes(comments: CommentView[]): CommentNodeI[] {
const nodes: CommentNodeI[] = [];
for (const comment of comments) {
return len ? len - 2 : undefined;
}
+// TODO make immutable
export function insertCommentIntoTree(
tree: CommentNodeI[],
cv: CommentView,
} else return context.router.staticContext;
}
-export function wsSubscribe(parseMessage: any): Subscription | undefined {
- if (isBrowser()) {
- return WebSocketService.Instance.subject
- .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
- .subscribe(
- msg => parseMessage(msg),
- err => console.error(err),
- () => console.log("complete")
- );
- } else {
- return undefined;
- }
-}
-
moment.updateLocale("en", {
relativeTime: {
future: "in %s",
};
}
-export async function fetchCommunities(q: string) {
+function fetchSearchResults(q: string, type_: SearchType) {
const form: Search = {
q,
- type_: "Communities",
+ type_,
sort: "TopAll",
listing_type: "All",
page: 1,
limit: fetchLimit,
- auth: myAuth(false),
+ auth: myAuth(),
};
- const client = new LemmyHttp(getHttpBase());
- return client.search(form);
+
+ return HttpService.client.search(form);
+}
+
+export async function fetchCommunities(q: string) {
+ const res = await fetchSearchResults(q, "Communities");
+
+ return res.state === "success" ? res.data.communities : [];
}
export async function fetchUsers(q: string) {
- const form: Search = {
- q,
- type_: "Users",
- sort: "TopAll",
- listing_type: "All",
- page: 1,
- limit: fetchLimit,
- auth: myAuth(false),
- };
- const client = new LemmyHttp(getHttpBase());
- return client.search(form);
+ const res = await fetchSearchResults(q, "Users");
+
+ return res.state === "success" ? res.data.users : [];
}
export function communitySelectName(cv: CommunityView): string {
UserService.Instance.myUserInfo = site?.my_user;
i18n.changeLanguage(getLanguages()[0]);
if (site) {
- setupEmojiDataModel(site.custom_emojis);
+ setupEmojiDataModel(site.custom_emojis ?? []);
}
setupMarkdown();
}
}
}
-export function myAuth(throwErr = true): string | undefined {
- return UserService.Instance.auth(throwErr);
+export function myAuth(): string | undefined {
+ return UserService.Instance.auth();
+}
+
+export function myAuthRequired(): string {
+ return UserService.Instance.auth(true) ?? "";
}
export function enableDownvotes(siteRes: GetSiteResponse): boolean {
}
}
-export function uploadImage(image: File): Promise<UploadImageResponse> {
- const client = new LemmyHttp(getHttpBase());
-
- return client.uploadImage({ image });
-}
-
interface EmojiMartCategory {
id: string;
name: string;
}
export function isAuthPath(pathname: string) {
- return /create_.*|inbox|settings|setup|admin|reports|registration_applications/g.test(
+ return /create_.*|inbox|settings|admin|reports|registration_applications/g.test(
pathname
);
}
navigator.share(shareData);
}
}
+
+export function newVote(voteType: VoteType, myVote?: number): number {
+ if (voteType == VoteType.Upvote) {
+ return myVote == 1 ? 0 : 1;
+ } else {
+ return myVote == -1 ? 0 : -1;
+ }
+}
});
if (mode === "development") {
- config.cache = {
- type: "filesystem",
- name: "server",
- };
+ // config.cache = {
+ // type: "filesystem",
+ // name: "server",
+ // };
config.plugins.push(
new RunNodeWebpackPlugin({
plugins: [
...base.plugins,
new ServiceWorkerPlugin({
- enableInDevelopment: true,
+ enableInDevelopment: mode !== "development", // this may seem counterintuitive, but it is correct
workbox: {
modifyURLPrefix: {
"/": "/static/",
});
if (mode === "development") {
- config.cache = {
- type: "filesystem",
- name: "client",
- };
+ // config.cache = {
+ // type: "filesystem",
+ // name: "client",
+ // };
}
return config;
jsonpointer "^5.0.0"
leven "^3.1.0"
-"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4":
+"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39"
integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
dependencies:
"@babel/highlight" "^7.18.6"
-"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5":
- version "7.21.7"
- resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc"
- integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==
+"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5", "@babel/compat-data@^7.22.0", "@babel/compat-data@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.3.tgz#cd502a6a0b6e37d7ad72ce7e71a7160a3ae36f7e"
+ integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==
"@babel/core@^7.11.1", "@babel/core@^7.2.2", "@babel/core@^7.21.8":
- version "7.21.8"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4"
- integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.1.tgz#5de51c5206f4c6f5533562838337a603c1033cfd"
+ integrity sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.21.4"
- "@babel/generator" "^7.21.5"
- "@babel/helper-compilation-targets" "^7.21.5"
- "@babel/helper-module-transforms" "^7.21.5"
- "@babel/helpers" "^7.21.5"
- "@babel/parser" "^7.21.8"
- "@babel/template" "^7.20.7"
- "@babel/traverse" "^7.21.5"
- "@babel/types" "^7.21.5"
+ "@babel/generator" "^7.22.0"
+ "@babel/helper-compilation-targets" "^7.22.1"
+ "@babel/helper-module-transforms" "^7.22.1"
+ "@babel/helpers" "^7.22.0"
+ "@babel/parser" "^7.22.0"
+ "@babel/template" "^7.21.9"
+ "@babel/traverse" "^7.22.1"
+ "@babel/types" "^7.22.0"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.2"
semver "^6.3.0"
-"@babel/generator@^7.21.5":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f"
- integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==
+"@babel/generator@^7.22.0", "@babel/generator@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.3.tgz#0ff675d2edb93d7596c5f6728b52615cfc0df01e"
+ integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==
dependencies:
- "@babel/types" "^7.21.5"
+ "@babel/types" "^7.22.3"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/types" "^7.18.6"
"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz#817f73b6c59726ab39f6ba18c234268a519e5abb"
- integrity sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g==
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.3.tgz#c9b83d1ba74e163e023f008a3d3204588a7ceb60"
+ integrity sha512-ahEoxgqNoYXm0k22TvOke48i1PkavGu0qGCmcq9ugi6gnmvKNaMjKBSrZTnWUi1CFEeNAUiVba0Wtzm03aSkJg==
dependencies:
- "@babel/types" "^7.21.5"
+ "@babel/types" "^7.22.3"
-"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366"
- integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==
+"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5", "@babel/helper-compilation-targets@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58"
+ integrity sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==
dependencies:
- "@babel/compat-data" "^7.21.5"
+ "@babel/compat-data" "^7.22.0"
"@babel/helper-validator-option" "^7.21.0"
browserslist "^4.21.3"
lru-cache "^5.1.1"
semver "^6.3.0"
-"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0":
- version "7.21.8"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz#205b26330258625ef8869672ebca1e0dee5a0f02"
- integrity sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==
+"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.1.tgz#ae3de70586cc757082ae3eba57240d42f468c41b"
+ integrity sha512-SowrZ9BWzYFgzUMwUmowbPSGu6CXL5MSuuCkG3bejahSpSymioPmuLdhPxNOc9MjuNGjy7M/HaXvJ8G82Lywlw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
- "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-environment-visitor" "^7.22.1"
"@babel/helper-function-name" "^7.21.0"
- "@babel/helper-member-expression-to-functions" "^7.21.5"
+ "@babel/helper-member-expression-to-functions" "^7.22.0"
"@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/helper-replace-supers" "^7.21.5"
+ "@babel/helper-replace-supers" "^7.22.1"
"@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
"@babel/helper-split-export-declaration" "^7.18.6"
semver "^6.3.0"
-"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5":
- version "7.21.8"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz#a7886f61c2e29e21fd4aaeaf1e473deba6b571dc"
- integrity sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g==
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz#a7ed9a8488b45b467fca353cd1a44dc5f0cf5c70"
+ integrity sha512-WWjdnfR3LPIe+0EY8td7WmjhytxXtjKAEpnAxun/hkNiyOaPlvGK+NZaBFIdi9ndYV3Gav7BpFvtUwnaJlwi1w==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
regexpu-core "^5.3.1"
resolve "^1.14.2"
semver "^6.1.2"
-"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.21.5":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba"
- integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==
+"@babel/helper-define-polyfill-provider@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8"
+ integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.17.7"
+ "@babel/helper-plugin-utils" "^7.16.7"
+ debug "^4.1.1"
+ lodash.debounce "^4.0.8"
+ resolve "^1.14.2"
+ semver "^6.1.2"
+
+"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8"
+ integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==
"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0":
version "7.21.0"
dependencies:
"@babel/types" "^7.18.6"
-"@babel/helper-member-expression-to-functions@^7.21.5":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz#3b1a009af932e586af77c1030fba9ee0bde396c0"
- integrity sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==
+"@babel/helper-member-expression-to-functions@^7.22.0":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz#4b77a12c1b4b8e9e28736ed47d8b91f00976911f"
+ integrity sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA==
dependencies:
- "@babel/types" "^7.21.5"
+ "@babel/types" "^7.22.3"
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4":
version "7.21.4"
dependencies:
"@babel/types" "^7.21.4"
-"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420"
- integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==
+"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5", "@babel/helper-module-transforms@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz#e0cad47fedcf3cae83c11021696376e2d5a50c63"
+ integrity sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==
dependencies:
- "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-environment-visitor" "^7.22.1"
"@babel/helper-module-imports" "^7.21.4"
"@babel/helper-simple-access" "^7.21.5"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/helper-validator-identifier" "^7.19.1"
- "@babel/template" "^7.20.7"
- "@babel/traverse" "^7.21.5"
- "@babel/types" "^7.21.5"
+ "@babel/template" "^7.21.9"
+ "@babel/traverse" "^7.22.1"
+ "@babel/types" "^7.22.0"
"@babel/helper-optimise-call-expression@^7.18.6":
version "7.18.6"
"@babel/helper-wrap-function" "^7.18.9"
"@babel/types" "^7.18.9"
-"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.21.5":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz#a6ad005ba1c7d9bc2973dfde05a1bba7065dde3c"
- integrity sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==
+"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.1.tgz#38cf6e56f7dc614af63a21b45565dd623f0fdc95"
+ integrity sha512-ut4qrkE4AuSfrwHSps51ekR1ZY/ygrP1tp0WFm8oVq6nzc/hvfV/22JylndIbsf2U2M9LOMwiSddr6y+78j+OQ==
dependencies:
- "@babel/helper-environment-visitor" "^7.21.5"
- "@babel/helper-member-expression-to-functions" "^7.21.5"
+ "@babel/helper-environment-visitor" "^7.22.1"
+ "@babel/helper-member-expression-to-functions" "^7.22.0"
"@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/template" "^7.20.7"
- "@babel/traverse" "^7.21.5"
- "@babel/types" "^7.21.5"
+ "@babel/template" "^7.21.9"
+ "@babel/traverse" "^7.22.1"
+ "@babel/types" "^7.22.0"
"@babel/helper-simple-access@^7.21.5":
version "7.21.5"
"@babel/traverse" "^7.20.5"
"@babel/types" "^7.20.5"
-"@babel/helpers@^7.21.5":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08"
- integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==
+"@babel/helpers@^7.22.0":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e"
+ integrity sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==
dependencies:
- "@babel/template" "^7.20.7"
- "@babel/traverse" "^7.21.5"
- "@babel/types" "^7.21.5"
+ "@babel/template" "^7.21.9"
+ "@babel/traverse" "^7.22.1"
+ "@babel/types" "^7.22.3"
"@babel/highlight@^7.18.6":
version "7.18.6"
chalk "^2.0.0"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0-beta.54", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8":
- version "7.21.8"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
- integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
+"@babel/parser@^7.0.0-beta.54", "@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4":
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32"
+ integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.20.7":
- version "7.20.7"
- resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1"
- integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.20.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.3.tgz#a75be1365c0c3188c51399a662168c1c98108659"
+ integrity sha512-6r4yRwEnorYByILoDRnEqxtojYKuiIv9FojW2E8GUKo9eWBwbKcd9IiZOZpdyXc64RmyGGyPu3/uAcrz/dq2kQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-plugin-utils" "^7.21.5"
"@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
- "@babel/plugin-proposal-optional-chaining" "^7.20.7"
+ "@babel/plugin-transform-optional-chaining" "^7.22.3"
"@babel/plugin-proposal-async-generator-functions@^7.20.7":
version "7.20.7"
"@babel/plugin-syntax-class-static-block" "^7.14.5"
"@babel/plugin-proposal-decorators@^7.21.0":
- version "7.21.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.21.0.tgz#70e0c89fdcd7465c97593edb8f628ba6e4199d63"
- integrity sha512-MfgX49uRrFUTL/HvWtmx3zmpyzMMr4MTj3d527MLlr/4RTT9G/ytFFP7qet2uM2Ve03b+BkpWUpK+lRXnQ+v9w==
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.22.3.tgz#3502c0f8cfe0cdb79b62102c9c9b111309d942b7"
+ integrity sha512-XjTKH3sHr6pPqG+hR1NCdVupwiosfdKM2oSMyKQVQ5Bym9l/p7BuLAqT5U32zZzRCfPq/TPRPzMiiTE9bOXU4w==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.21.0"
- "@babel/helper-plugin-utils" "^7.20.2"
- "@babel/helper-replace-supers" "^7.20.7"
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-replace-supers" "^7.22.1"
"@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/plugin-syntax-decorators" "^7.21.0"
+ "@babel/plugin-syntax-decorators" "^7.22.3"
"@babel/plugin-proposal-dynamic-import@^7.18.6":
version "7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
-"@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0":
+"@babel/plugin-proposal-optional-chaining@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea"
integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-proposal-private-property-in-object@^7.21.0":
- version "7.21.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc"
- integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==
+ version "7.21.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c"
+ integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
"@babel/helper-create-class-features-plugin" "^7.21.0"
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
-"@babel/plugin-syntax-decorators@^7.21.0":
- version "7.21.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.21.0.tgz#d2b3f31c3e86fa86e16bb540b7660c55bd7d0e78"
- integrity sha512-tIoPpGBR8UuM4++ccWN3gifhVvQu7ZizuR1fklhRJrd5ewgbkUS+0KVFeWWxELtn18NTLoW32XV7zyOgIAiz+w==
+"@babel/plugin-syntax-decorators@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.22.3.tgz#760f2d812d56c1d05970d01cdcd3c05e3d87d6ca"
+ integrity sha512-R16Zuge73+8/nLcDjkIpyhi5wIbN7i7fiuLJR8yQX7vPAa/ltUKtd3iLbb4AgP5nrLi91HnNUNosELIGUGH1bg==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-plugin-utils" "^7.21.5"
"@babel/plugin-syntax-dynamic-import@^7.8.3":
version "7.8.3"
dependencies:
"@babel/helper-plugin-utils" "^7.19.0"
+"@babel/plugin-syntax-import-attributes@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.3.tgz#d7168f22b9b49a6cc1792cec78e06a18ad2e7b4b"
+ integrity sha512-i35jZJv6aO7hxEbIWQ41adVfOzjm9dcYDNeWlBMd8p0ZQRtNUCBrmGwZt+H5lb+oOC9a3svp956KP0oWGA1YsA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
"@babel/plugin-syntax-import-meta@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
-"@babel/plugin-syntax-typescript@^7.20.0":
+"@babel/plugin-syntax-typescript@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz#2751948e9b7c6d771a8efa59340c15d4a2891ff8"
integrity sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==
dependencies:
"@babel/helper-plugin-utils" "^7.20.2"
+"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
+ integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@babel/plugin-transform-arrow-functions@^7.21.5":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929"
dependencies:
"@babel/helper-plugin-utils" "^7.21.5"
+"@babel/plugin-transform-async-generator-functions@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.3.tgz#3ed99924c354fb9e80dabb2cc8d002c702e94527"
+ integrity sha512-36A4Aq48t66btydbZd5Fk0/xJqbpg/v4QWI4AH4cYHBXy9Mu42UOupZpebKFiCFNT9S9rJFcsld0gsv0ayLjtA==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-remap-async-to-generator" "^7.18.9"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+
"@babel/plugin-transform-async-to-generator@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354"
dependencies:
"@babel/helper-plugin-utils" "^7.20.2"
+"@babel/plugin-transform-class-properties@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.3.tgz#3407145e513830df77f0cef828b8b231c166fe4c"
+ integrity sha512-mASLsd6rhOrLZ5F3WbCxkzl67mmOnqik0zrg5W6D/X0QMW7HtvnoL1dRARLKIbMP3vXwkwziuLesPqWVGIl6Bw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-class-static-block@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.3.tgz#e352cf33567385c731a8f21192efeba760358773"
+ integrity sha512-5BirgNWNOx7cwbTJCOmKFJ1pZjwk5MUfMIwiBBvsirCJMZeQgs5pk6i1OlkVg+1Vef5LfBahFOrdCnAWvkVKMw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+
"@babel/plugin-transform-classes@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665"
dependencies:
"@babel/helper-plugin-utils" "^7.18.9"
+"@babel/plugin-transform-dynamic-import@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.1.tgz#6c56afaf896a07026330cf39714532abed8d9ed1"
+ integrity sha512-rlhWtONnVBPdmt+jeewS0qSnMz/3yLFrqAP8hHC6EDcrYRSyuz9f9yQhHvVn2Ad6+yO9fHXac5piudeYrInxwQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+
"@babel/plugin-transform-exponentiation-operator@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd"
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
+"@babel/plugin-transform-export-namespace-from@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.3.tgz#9b8700aa495007d3bebac8358d1c562434b680b9"
+ integrity sha512-5Ti1cHLTDnt3vX61P9KZ5IG09bFXp4cDVFJIAeCZuxu9OXXJJZp5iP0n/rzM2+iAutJY+KWEyyHcRaHlpQ/P5g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
"@babel/plugin-transform-for-of@^7.21.5":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc"
"@babel/helper-function-name" "^7.18.9"
"@babel/helper-plugin-utils" "^7.18.9"
+"@babel/plugin-transform-json-strings@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.3.tgz#a181b8679cf7c93e9d0e3baa5b1776d65be601a9"
+ integrity sha512-IuvOMdeOOY2X4hRNAT6kwbePtK21BUyrAEgLKviL8pL6AEEVUVcqtRdN/HJXBLGIbt9T3ETmXRnFedRRmQNTYw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+
"@babel/plugin-transform-literals@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc"
dependencies:
"@babel/helper-plugin-utils" "^7.18.9"
+"@babel/plugin-transform-logical-assignment-operators@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.3.tgz#9e021455810f33b0baccb82fb759b194f5dc36f0"
+ integrity sha512-CbayIfOw4av2v/HYZEsH+Klks3NC2/MFIR3QR8gnpGNNPEaq2fdlVCRYG/paKs7/5hvBLQ+H70pGWOHtlNEWNA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
"@babel/plugin-transform-member-expression-literals@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e"
"@babel/helper-plugin-utils" "^7.21.5"
"@babel/helper-simple-access" "^7.21.5"
-"@babel/plugin-transform-modules-systemjs@^7.20.11":
- version "7.20.11"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e"
- integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==
+"@babel/plugin-transform-modules-systemjs@^7.20.11", "@babel/plugin-transform-modules-systemjs@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.3.tgz#cc507e03e88d87b016feaeb5dae941e6ef50d91e"
+ integrity sha512-V21W3bKLxO3ZjcBJZ8biSvo5gQ85uIXW2vJfh7JSWf/4SLUSr1tOoHX3ruN4+Oqa2m+BKfsxTR1I+PsvkIWvNw==
dependencies:
"@babel/helper-hoist-variables" "^7.18.6"
- "@babel/helper-module-transforms" "^7.20.11"
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-module-transforms" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
"@babel/helper-validator-identifier" "^7.19.1"
"@babel/plugin-transform-modules-umd@^7.18.6":
"@babel/helper-module-transforms" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-named-capturing-groups-regex@^7.20.5":
- version "7.20.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8"
- integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.20.5", "@babel/plugin-transform-named-capturing-groups-regex@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.3.tgz#db6fb77e6b3b53ec3b8d370246f0b7cf67d35ab4"
+ integrity sha512-c6HrD/LpUdNNJsISQZpds3TXvfYIAbo+efE9aWmY/PmSRD0agrJ9cPMt4BmArwUQ7ZymEWTFjTyp+yReLJZh0Q==
dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.20.5"
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-create-regexp-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
-"@babel/plugin-transform-new-target@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8"
- integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==
+"@babel/plugin-transform-new-target@^7.18.6", "@babel/plugin-transform-new-target@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.3.tgz#deb0377d741cbee2f45305868b9026dcd6dd96e2"
+ integrity sha512-5RuJdSo89wKdkRTqtM9RVVJzHum9c2s0te9rB7vZC1zKKxcioWIy+xcu4OoIAjyFZhb/bp5KkunuLin1q7Ct+w==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-nullish-coalescing-operator@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.3.tgz#8c519f8bf5af94a9ca6f65cf422a9d3396e542b9"
+ integrity sha512-CpaoNp16nX7ROtLONNuCyenYdY/l7ZsR6aoVa7rW7nMWisoNoQNIH5Iay/4LDyRjKMuElMqXiBoOQCDLTMGZiw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+
+"@babel/plugin-transform-numeric-separator@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.3.tgz#02493070ca6685884b0eee705363ee4da2132ab0"
+ integrity sha512-+AF88fPDJrnseMh5vD9+SH6wq4ZMvpiTMHh58uLs+giMEyASFVhcT3NkoyO+NebFCNnpHJEq5AXO2txV4AGPDQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
+"@babel/plugin-transform-object-rest-spread@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.3.tgz#da6fba693effb8c203d8c3bdf7bf4e2567e802e9"
+ integrity sha512-38bzTsqMMCI46/TQnJwPPpy33EjLCc1Gsm2hRTF6zTMWnKsN61vdrpuzIEGQyKEhDSYDKyZHrrd5FMj4gcUHhw==
+ dependencies:
+ "@babel/compat-data" "^7.22.3"
+ "@babel/helper-compilation-targets" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-transform-parameters" "^7.22.3"
"@babel/plugin-transform-object-super@^7.18.6":
version "7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/helper-replace-supers" "^7.18.6"
-"@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.21.3":
- version "7.21.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db"
- integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==
+"@babel/plugin-transform-optional-catch-binding@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.3.tgz#e971a083fc7d209d9cd18253853af1db6d8dc42f"
+ integrity sha512-bnDFWXFzWY0BsOyqaoSXvMQ2F35zutQipugog/rqotL2S4ciFOKlRYUu9djt4iq09oh2/34hqfRR2k1dIvuu4g==
dependencies:
- "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+
+"@babel/plugin-transform-optional-chaining@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.3.tgz#5fd24a4a7843b76da6aeec23c7f551da5d365290"
+ integrity sha512-63v3/UFFxhPKT8j8u1jTTGVyITxl7/7AfOqK8C5gz1rHURPUGe3y5mvIf68eYKGoBNahtJnTxBKug4BQOnzeJg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+"@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.21.3", "@babel/plugin-transform-parameters@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.3.tgz#24477acfd2fd2bc901df906c9bf17fbcfeee900d"
+ integrity sha512-x7QHQJHPuD9VmfpzboyGJ5aHEr9r7DsAsdxdhJiTB3J3j8dyl+NFZ+rX5Q2RWFDCs61c06qBfS4ys2QYn8UkMw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-private-methods@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.3.tgz#adac38020bab5047482d3297107c1f58e9c574f6"
+ integrity sha512-fC7jtjBPFqhqpPAE+O4LKwnLq7gGkD3ZmC2E3i4qWH34mH3gOg2Xrq5YMHUq6DM30xhqM1DNftiRaSqVjEG+ug==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-private-property-in-object@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz#031621b02c7b7d95389de1a3dba2fe9e8c548e56"
+ integrity sha512-C7MMl4qWLpgVCbXfj3UW8rR1xeCnisQ0cU7YJHV//8oNBS0aCIVg1vFnZXxOckHhEpQyqNNkWmvSEWnMLlc+Vw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
"@babel/plugin-transform-property-literals@^7.18.6":
version "7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-runtime@^7.21.4":
- version "7.21.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz#2e1da21ca597a7d01fc96b699b21d8d2023191aa"
- integrity sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA==
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.4.tgz#f8353f313f18c3ce1315688631ec48657b97af42"
+ integrity sha512-Urkiz1m4zqiRo17klj+l3nXgiRTFQng91Bc1eiLF7BMQu1e7wE5Gcq9xSv062IF068NHjcutSbIMev60gXxAvA==
dependencies:
"@babel/helper-module-imports" "^7.21.4"
- "@babel/helper-plugin-utils" "^7.20.2"
- babel-plugin-polyfill-corejs2 "^0.3.3"
- babel-plugin-polyfill-corejs3 "^0.6.0"
- babel-plugin-polyfill-regenerator "^0.4.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ babel-plugin-polyfill-corejs2 "^0.4.3"
+ babel-plugin-polyfill-corejs3 "^0.8.1"
+ babel-plugin-polyfill-regenerator "^0.5.0"
semver "^6.3.0"
"@babel/plugin-transform-shorthand-properties@^7.18.6":
"@babel/helper-plugin-utils" "^7.18.9"
"@babel/plugin-transform-typescript@^7.21.3":
- version "7.21.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz#316c5be579856ea890a57ebc5116c5d064658f2b"
- integrity sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.3.tgz#8f662cec8ba88c873f1c7663c0c94e3f68592f09"
+ integrity sha512-pyjnCIniO5PNaEuGxT28h0HbMru3qCVrMqVgVOz/krComdIrY9W6FCLBq9NWHY8HDGaUlan+UhmZElDENIfCcw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
- "@babel/helper-create-class-features-plugin" "^7.21.0"
- "@babel/helper-plugin-utils" "^7.20.2"
- "@babel/plugin-syntax-typescript" "^7.20.0"
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-typescript" "^7.21.4"
"@babel/plugin-transform-unicode-escapes@^7.21.5":
version "7.21.5"
dependencies:
"@babel/helper-plugin-utils" "^7.21.5"
+"@babel/plugin-transform-unicode-property-regex@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.3.tgz#597b6a614dc93eaae605ee293e674d79d32eb380"
+ integrity sha512-5ScJ+OmdX+O6HRuMGW4kv7RL9vIKdtdAj9wuWUKy1wbHY3jaM/UlyIiC1G7J6UJiiyMukjjK0QwL3P0vBd0yYg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
"@babel/plugin-transform-unicode-regex@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca"
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/preset-env@7.21.5", "@babel/preset-env@^7.11.0":
+"@babel/plugin-transform-unicode-sets-regex@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.3.tgz#7c14ee33fa69782b0101d0f7143d3fc73ce00700"
+ integrity sha512-hNufLdkF8vqywRp+P55j4FHXqAX2LRUccoZHH7AFn1pq5ZOO2ISKW9w13bFZVjBoTqeve2HOgoJCcaziJVhGNw==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/preset-env@7.21.5":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb"
integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg==
core-js-compat "^3.25.1"
semver "^6.3.0"
+"@babel/preset-env@^7.11.0":
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.4.tgz#c86a82630f0e8c61d9bb9327b7b896732028cbed"
+ integrity sha512-c3lHOjbwBv0TkhYCr+XCR6wKcSZ1QbQTVdSkZUaVpLv8CVWotBMArWUi5UAJrcrQaEnleVkkvaV8F/pmc/STZQ==
+ dependencies:
+ "@babel/compat-data" "^7.22.3"
+ "@babel/helper-compilation-targets" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-validator-option" "^7.21.0"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.3"
+ "@babel/plugin-proposal-private-property-in-object" "^7.21.0"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+ "@babel/plugin-syntax-class-properties" "^7.12.13"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+ "@babel/plugin-syntax-import-assertions" "^7.20.0"
+ "@babel/plugin-syntax-import-attributes" "^7.22.3"
+ "@babel/plugin-syntax-import-meta" "^7.10.4"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+ "@babel/plugin-syntax-top-level-await" "^7.14.5"
+ "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
+ "@babel/plugin-transform-arrow-functions" "^7.21.5"
+ "@babel/plugin-transform-async-generator-functions" "^7.22.3"
+ "@babel/plugin-transform-async-to-generator" "^7.20.7"
+ "@babel/plugin-transform-block-scoped-functions" "^7.18.6"
+ "@babel/plugin-transform-block-scoping" "^7.21.0"
+ "@babel/plugin-transform-class-properties" "^7.22.3"
+ "@babel/plugin-transform-class-static-block" "^7.22.3"
+ "@babel/plugin-transform-classes" "^7.21.0"
+ "@babel/plugin-transform-computed-properties" "^7.21.5"
+ "@babel/plugin-transform-destructuring" "^7.21.3"
+ "@babel/plugin-transform-dotall-regex" "^7.18.6"
+ "@babel/plugin-transform-duplicate-keys" "^7.18.9"
+ "@babel/plugin-transform-dynamic-import" "^7.22.1"
+ "@babel/plugin-transform-exponentiation-operator" "^7.18.6"
+ "@babel/plugin-transform-export-namespace-from" "^7.22.3"
+ "@babel/plugin-transform-for-of" "^7.21.5"
+ "@babel/plugin-transform-function-name" "^7.18.9"
+ "@babel/plugin-transform-json-strings" "^7.22.3"
+ "@babel/plugin-transform-literals" "^7.18.9"
+ "@babel/plugin-transform-logical-assignment-operators" "^7.22.3"
+ "@babel/plugin-transform-member-expression-literals" "^7.18.6"
+ "@babel/plugin-transform-modules-amd" "^7.20.11"
+ "@babel/plugin-transform-modules-commonjs" "^7.21.5"
+ "@babel/plugin-transform-modules-systemjs" "^7.22.3"
+ "@babel/plugin-transform-modules-umd" "^7.18.6"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.3"
+ "@babel/plugin-transform-new-target" "^7.22.3"
+ "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.3"
+ "@babel/plugin-transform-numeric-separator" "^7.22.3"
+ "@babel/plugin-transform-object-rest-spread" "^7.22.3"
+ "@babel/plugin-transform-object-super" "^7.18.6"
+ "@babel/plugin-transform-optional-catch-binding" "^7.22.3"
+ "@babel/plugin-transform-optional-chaining" "^7.22.3"
+ "@babel/plugin-transform-parameters" "^7.22.3"
+ "@babel/plugin-transform-private-methods" "^7.22.3"
+ "@babel/plugin-transform-private-property-in-object" "^7.22.3"
+ "@babel/plugin-transform-property-literals" "^7.18.6"
+ "@babel/plugin-transform-regenerator" "^7.21.5"
+ "@babel/plugin-transform-reserved-words" "^7.18.6"
+ "@babel/plugin-transform-shorthand-properties" "^7.18.6"
+ "@babel/plugin-transform-spread" "^7.20.7"
+ "@babel/plugin-transform-sticky-regex" "^7.18.6"
+ "@babel/plugin-transform-template-literals" "^7.18.9"
+ "@babel/plugin-transform-typeof-symbol" "^7.18.9"
+ "@babel/plugin-transform-unicode-escapes" "^7.21.5"
+ "@babel/plugin-transform-unicode-property-regex" "^7.22.3"
+ "@babel/plugin-transform-unicode-regex" "^7.18.6"
+ "@babel/plugin-transform-unicode-sets-regex" "^7.22.3"
+ "@babel/preset-modules" "^0.1.5"
+ "@babel/types" "^7.22.4"
+ babel-plugin-polyfill-corejs2 "^0.4.3"
+ babel-plugin-polyfill-corejs3 "^0.8.1"
+ babel-plugin-polyfill-regenerator "^0.5.0"
+ core-js-compat "^3.30.2"
+ semver "^6.3.0"
+
"@babel/preset-modules@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.11.2", "@babel/runtime@^7.20.6", "@babel/runtime@^7.21.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
- integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb"
+ integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==
dependencies:
regenerator-runtime "^0.13.11"
-"@babel/template@^7.18.10", "@babel/template@^7.20.7":
- version "7.20.7"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
- integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==
+"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9":
+ version "7.21.9"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb"
+ integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==
dependencies:
- "@babel/code-frame" "^7.18.6"
- "@babel/parser" "^7.20.7"
- "@babel/types" "^7.20.7"
+ "@babel/code-frame" "^7.21.4"
+ "@babel/parser" "^7.21.9"
+ "@babel/types" "^7.21.5"
-"@babel/traverse@^7.0.0-beta.54", "@babel/traverse@^7.20.5", "@babel/traverse@^7.21.5":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133"
- integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==
+"@babel/traverse@^7.0.0-beta.54", "@babel/traverse@^7.20.5", "@babel/traverse@^7.22.1":
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.4.tgz#c3cf96c5c290bd13b55e29d025274057727664c0"
+ integrity sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==
dependencies:
"@babel/code-frame" "^7.21.4"
- "@babel/generator" "^7.21.5"
- "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/generator" "^7.22.3"
+ "@babel/helper-environment-visitor" "^7.22.1"
"@babel/helper-function-name" "^7.21.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/parser" "^7.21.5"
- "@babel/types" "^7.21.5"
+ "@babel/parser" "^7.22.4"
+ "@babel/types" "^7.22.4"
debug "^4.1.0"
globals "^11.1.0"
-"@babel/types@^7", "@babel/types@^7.0.0-beta.54", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.4.4":
- version "7.21.5"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
- integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
+"@babel/types@^7", "@babel/types@^7.0.0-beta.54", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.4.4":
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.4.tgz#56a2653ae7e7591365dabf20b76295410684c071"
+ integrity sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==
dependencies:
"@babel/helper-string-parser" "^7.21.5"
"@babel/helper-validator-identifier" "^7.19.1"
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
-"@eslint/js@8.40.0":
- version "8.40.0"
- resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec"
- integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==
+"@eslint/js@8.42.0":
+ version "8.42.0"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6"
+ integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==
-"@humanwhocodes/config-array@^0.11.8":
- version "0.11.8"
- resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
- integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==
+"@humanwhocodes/config-array@^0.11.10":
+ version "0.11.10"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2"
+ integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==
dependencies:
"@humanwhocodes/object-schema" "^1.2.1"
debug "^4.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
-"@jridgewell/source-map@^0.3.2":
+"@jridgewell/source-map@^0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda"
integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@pkgr/utils@^2.3.1":
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.0.tgz#b6373d2504aedaf2fc7cdf2d13ab1f48fa5f12d5"
- integrity sha512-2OCURAmRtdlL8iUDTypMrrxfwe8frXTeXaxGsVOaYtc/wrUyk8Z/0OBetM7cdlsy7ZFWlMX72VogKeh+A4Xcjw==
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.1.tgz#adf291d0357834c410ce80af16e711b56c7b1cd3"
+ integrity sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==
dependencies:
cross-spawn "^7.0.3"
fast-glob "^3.2.12"
tslib "^2.5.0"
"@popperjs/core@^2.9.0", "@popperjs/core@^2.9.2":
- version "2.11.7"
- resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7"
- integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==
+ version "2.11.8"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
+ integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
"@types/estree" "*"
"@types/eslint@*":
- version "8.37.0"
- resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.37.0.tgz#29cebc6c2a3ac7fea7113207bf5a828fdf4d7ef1"
- integrity sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==
+ version "8.40.0"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.40.0.tgz#ae73dc9ec5237f2794c4f79efd6a4c73b13daf23"
+ integrity sha512-nbq2mvc/tBrK9zQQuItvjJl++GTN5j06DaPtp3hZCpngmG6Q3xoyEmd0TwZI0gAy/G1X0zhGBbr2imsGFdFV0g==
dependencies:
"@types/estree" "*"
"@types/json-schema" "*"
"@types/node" "*"
"@types/html-to-text@^9.0.0":
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-9.0.0.tgz#28a676984c281a67478773519da6b5c2bdfb22ed"
- integrity sha512-FnF3p2FJZ1kJT/0C/lmBzw7HSlH3RhtACVYyrwUsJoCmFNuiLpusWT2FWWB7P9A48CaYpvD6Q2fprn7sZeffpw==
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-9.0.1.tgz#d4f8b1844464df3a13ca14134f5970c847de6751"
+ integrity sha512-sHu702QGb0SP2F0Zt+CxdCmGZIZ0gEaaCjqOh/V4iba1wTxPVntEPOM/vHm5bel08TILhB3+OxUTkDJWnr/zHQ==
"@types/http-proxy@^1.17.8":
version "1.17.11"
"@types/node" "*"
"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
- version "7.0.11"
- resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
- integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
+ version "7.0.12"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
+ integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
"@types/linkify-it@*":
version "3.0.2"
integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
"@types/node@*", "@types/node@^20.1.2":
- version "20.1.4"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.4.tgz#83f148d2d1f5fe6add4c53358ba00d97fc4cdb71"
- integrity sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q==
+ version "20.2.5"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb"
+ integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==
"@types/qs@*":
version "6.9.7"
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^5.59.5":
- version "5.59.5"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz#f156827610a3f8cefc56baeaa93cd4a5f32966b4"
- integrity sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==
+ version "5.59.9"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz#2604cfaf2b306e120044f901e20c8ed926debf15"
+ integrity sha512-4uQIBq1ffXd2YvF7MAvehWKW3zVv/w+mSfRAu+8cKbfj3nwzyqJLNcZJpQ/WZ1HLbJDiowwmQ6NO+63nCA+fqA==
dependencies:
"@eslint-community/regexpp" "^4.4.0"
- "@typescript-eslint/scope-manager" "5.59.5"
- "@typescript-eslint/type-utils" "5.59.5"
- "@typescript-eslint/utils" "5.59.5"
+ "@typescript-eslint/scope-manager" "5.59.9"
+ "@typescript-eslint/type-utils" "5.59.9"
+ "@typescript-eslint/utils" "5.59.9"
debug "^4.3.4"
grapheme-splitter "^1.0.4"
ignore "^5.2.0"
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.59.5":
- version "5.59.5"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.5.tgz#63064f5eafbdbfb5f9dfbf5c4503cdf949852981"
- integrity sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==
+ version "5.59.9"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.9.tgz#a85c47ccdd7e285697463da15200f9a8561dd5fa"
+ integrity sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ==
dependencies:
- "@typescript-eslint/scope-manager" "5.59.5"
- "@typescript-eslint/types" "5.59.5"
- "@typescript-eslint/typescript-estree" "5.59.5"
+ "@typescript-eslint/scope-manager" "5.59.9"
+ "@typescript-eslint/types" "5.59.9"
+ "@typescript-eslint/typescript-estree" "5.59.9"
debug "^4.3.4"
-"@typescript-eslint/scope-manager@5.59.5":
- version "5.59.5"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz#33ffc7e8663f42cfaac873de65ebf65d2bce674d"
- integrity sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==
+"@typescript-eslint/scope-manager@5.59.9":
+ version "5.59.9"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz#eadce1f2733389cdb58c49770192c0f95470d2f4"
+ integrity sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ==
dependencies:
- "@typescript-eslint/types" "5.59.5"
- "@typescript-eslint/visitor-keys" "5.59.5"
+ "@typescript-eslint/types" "5.59.9"
+ "@typescript-eslint/visitor-keys" "5.59.9"
-"@typescript-eslint/type-utils@5.59.5":
- version "5.59.5"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz#485b0e2c5b923460bc2ea6b338c595343f06fc9b"
- integrity sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==
+"@typescript-eslint/type-utils@5.59.9":
+ version "5.59.9"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz#53bfaae2e901e6ac637ab0536d1754dfef4dafc2"
+ integrity sha512-ksEsT0/mEHg9e3qZu98AlSrONAQtrSTljL3ow9CGej8eRo7pe+yaC/mvTjptp23Xo/xIf2mLZKC6KPv4Sji26Q==
dependencies:
- "@typescript-eslint/typescript-estree" "5.59.5"
- "@typescript-eslint/utils" "5.59.5"
+ "@typescript-eslint/typescript-estree" "5.59.9"
+ "@typescript-eslint/utils" "5.59.9"
debug "^4.3.4"
tsutils "^3.21.0"
-"@typescript-eslint/types@5.59.5":
- version "5.59.5"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.5.tgz#e63c5952532306d97c6ea432cee0981f6d2258c7"
- integrity sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==
+"@typescript-eslint/types@5.59.9":
+ version "5.59.9"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.9.tgz#3b4e7ae63718ce1b966e0ae620adc4099a6dcc52"
+ integrity sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw==
-"@typescript-eslint/typescript-estree@5.59.5":
- version "5.59.5"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz#9b252ce55dd765e972a7a2f99233c439c5101e42"
- integrity sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==
+"@typescript-eslint/typescript-estree@5.59.9":
+ version "5.59.9"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz#6bfea844e468427b5e72034d33c9fffc9557392b"
+ integrity sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA==
dependencies:
- "@typescript-eslint/types" "5.59.5"
- "@typescript-eslint/visitor-keys" "5.59.5"
+ "@typescript-eslint/types" "5.59.9"
+ "@typescript-eslint/visitor-keys" "5.59.9"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
-"@typescript-eslint/utils@5.59.5":
- version "5.59.5"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.5.tgz#15b3eb619bb223302e60413adb0accd29c32bcae"
- integrity sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==
+"@typescript-eslint/utils@5.59.9":
+ version "5.59.9"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.9.tgz#adee890107b5ffe02cd46fdaa6c2125fb3c6c7c4"
+ integrity sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@types/json-schema" "^7.0.9"
"@types/semver" "^7.3.12"
- "@typescript-eslint/scope-manager" "5.59.5"
- "@typescript-eslint/types" "5.59.5"
- "@typescript-eslint/typescript-estree" "5.59.5"
+ "@typescript-eslint/scope-manager" "5.59.9"
+ "@typescript-eslint/types" "5.59.9"
+ "@typescript-eslint/typescript-estree" "5.59.9"
eslint-scope "^5.1.1"
semver "^7.3.7"
-"@typescript-eslint/visitor-keys@5.59.5":
- version "5.59.5"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz#ba5b8d6791a13cf9fea6716af1e7626434b29b9b"
- integrity sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==
+"@typescript-eslint/visitor-keys@5.59.9":
+ version "5.59.9"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz#9f86ef8e95aca30fb5a705bb7430f95fc58b146d"
+ integrity sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q==
dependencies:
- "@typescript-eslint/types" "5.59.5"
+ "@typescript-eslint/types" "5.59.9"
eslint-visitor-keys "^3.3.0"
"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
"@webassemblyjs/ast" "1.11.6"
"@xtuc/long" "4.2.2"
-"@webpack-cli/configtest@^2.1.0":
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.0.tgz#b59b33377b1b896a9a7357cfc643b39c1524b1e6"
- integrity sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==
+"@webpack-cli/configtest@^2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646"
+ integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==
-"@webpack-cli/info@^2.0.1":
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.1.tgz#eed745799c910d20081e06e5177c2b2569f166c0"
- integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==
+"@webpack-cli/info@^2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd"
+ integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==
-"@webpack-cli/serve@^2.0.4":
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.4.tgz#3982ee6f8b42845437fc4d391e93ac5d9da52f0f"
- integrity sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==
+"@webpack-cli/serve@^2.0.5":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e"
+ integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0:
+acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.2:
version "8.8.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
"@babel/helper-define-polyfill-provider" "^0.3.3"
semver "^6.1.1"
+babel-plugin-polyfill-corejs2@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd"
+ integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==
+ dependencies:
+ "@babel/compat-data" "^7.17.7"
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+ semver "^6.1.1"
+
babel-plugin-polyfill-corejs3@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a"
"@babel/helper-define-polyfill-provider" "^0.3.3"
core-js-compat "^3.25.1"
+babel-plugin-polyfill-corejs3@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a"
+ integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+ core-js-compat "^3.30.1"
+
babel-plugin-polyfill-regenerator@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747"
dependencies:
"@babel/helper-define-polyfill-provider" "^0.3.3"
+babel-plugin-polyfill-regenerator@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380"
+ integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
multicast-dns "^7.2.5"
bootstrap@^5.2.3:
- version "5.2.3"
- resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.3.tgz#54739f4414de121b9785c5da3c87b37ff008322b"
- integrity sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.0.tgz#0718a7cc29040ee8dbf1bd652b896f3436a87c29"
+ integrity sha512-UnBV3E3v4STVNQdms6jSGO2CvOkjUMdDAVR2V5N4uCMdaIkaQjbcEAMqRimDHIs4uqBYzDAKCQwCB+97tJgHQw==
bootswatch@^5.2.3:
- version "5.2.3"
- resolved "https://registry.yarnpkg.com/bootswatch/-/bootswatch-5.2.3.tgz#a12bef6ea1a54f1b5b55b472c11a846d1cb77239"
- integrity sha512-tvnW15WoOY2sEp1uT1ITDQiJy2TekQa+K+Q28WDXibleIxsY0nAoC9IylbnUPD7Q5vkCIclOuBHLVBblJYYPSA==
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/bootswatch/-/bootswatch-5.3.0.tgz#7c7dd50bbe8519b0c6dbe01f4f9c3100b60228bd"
+ integrity sha512-ga2hHognDrh5h3+CaBBug6ktx3MTlnDzH57s+Mvjt9ZcNxqwpK+m3sE3YIUSr8zf2iG05elOb1mnqqcdbce2ow==
boxen@^1.2.1:
version "1.3.0"
fill-range "^7.0.1"
browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.5:
- version "4.21.5"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7"
- integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==
+ version "4.21.7"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.7.tgz#e2b420947e5fb0a58e8f4668ae6e23488127e551"
+ integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==
dependencies:
- caniuse-lite "^1.0.30001449"
- electron-to-chromium "^1.4.284"
- node-releases "^2.0.8"
- update-browserslist-db "^1.0.10"
+ caniuse-lite "^1.0.30001489"
+ electron-to-chromium "^1.4.411"
+ node-releases "^2.0.12"
+ update-browserslist-db "^1.0.11"
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-caniuse-lite@^1.0.30001449:
- version "1.0.30001487"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz#d882d1a34d89c11aea53b8cdc791931bdab5fe1b"
- integrity sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==
+caniuse-lite@^1.0.30001489:
+ version "1.0.30001495"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz#64a0ccef1911a9dcff647115b4430f8eff1ef2d9"
+ integrity sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg==
capture-stack-trace@^1.0.0:
version "1.0.2"
schema-utils "^4.0.0"
serialize-javascript "^6.0.0"
-core-js-compat@^3.25.1:
+core-js-compat@^3.25.1, core-js-compat@^3.30.1, core-js-compat@^3.30.2:
version "3.30.2"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.2.tgz#83f136e375babdb8c80ad3c22d67c69098c1dd8b"
integrity sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==
capture-stack-trace "^1.0.0"
cross-fetch@^3.1.5:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
- integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.6.tgz#bae05aa31a4da760969756318feeee6e70f15d6c"
+ integrity sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==
dependencies:
- node-fetch "2.6.7"
+ node-fetch "^2.6.11"
cross-spawn@^5.0.1:
version "5.1.0"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
css-loader@^6.7.3:
- version "6.7.3"
- resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.3.tgz#1e8799f3ccc5874fdd55461af51137fcc5befbcd"
- integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==
+ version "6.8.1"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88"
+ integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==
dependencies:
icss-utils "^5.1.0"
- postcss "^8.4.19"
+ postcss "^8.4.21"
postcss-modules-extract-imports "^3.0.0"
- postcss-modules-local-by-default "^4.0.0"
+ postcss-modules-local-by-default "^4.0.3"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.2.0"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
cyclist@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
- integrity sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.2.tgz#673b5f233bf34d8e602b949429f8171d9121bea3"
+ integrity sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==
dashdash@^1.12.0:
version "1.14.1"
dependencies:
jake "^10.8.5"
-electron-to-chromium@^1.4.284:
- version "1.4.394"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.394.tgz#989abe104a40366755648876cde2cdeda9f31133"
- integrity sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw==
+electron-to-chromium@^1.4.411:
+ version "1.4.421"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.421.tgz#2b8c0ef98ba00d4aef4c664933d570922da52161"
+ integrity sha512-wZOyn3s/aQOtLGAwXMZfteQPN68kgls2wDAnYOA8kCjBvKVrW5RwmWVspxJYTqrcN7Y263XJVsC66VCIGzDO3g==
emoji-mart@^5.4.0:
version "5.5.2"
once "^1.4.0"
enhanced-resolve@^5.14.0:
- version "5.14.0"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz#0b6c676c8a3266c99fa281e4433a706f5c0c61c4"
- integrity sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==
+ version "5.14.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz#de684b6803724477a4af5d74ccae5de52c25f6b3"
+ integrity sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
eslint@^8.40.0:
- version "8.40.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4"
- integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==
+ version "8.42.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.42.0.tgz#7bebdc3a55f9ed7167251fe7259f75219cade291"
+ integrity sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.4.0"
"@eslint/eslintrc" "^2.0.3"
- "@eslint/js" "8.40.0"
- "@humanwhocodes/config-array" "^0.11.8"
+ "@eslint/js" "8.42.0"
+ "@humanwhocodes/config-array" "^0.11.10"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
ajv "^6.10.0"
find-up "^5.0.0"
glob-parent "^6.0.2"
globals "^13.19.0"
- grapheme-splitter "^1.0.4"
+ graphemer "^1.4.0"
ignore "^5.2.0"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
is-path-inside "^3.0.3"
- js-sdsl "^4.1.4"
js-yaml "^4.1.0"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-diff@^1.1.2:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
- integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
+ integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9:
version "3.2.12"
dependencies:
flat-cache "^3.0.4"
-filelist@^1.0.1:
+filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
minipass "^2.6.0"
fs-monkey@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3"
- integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.4.tgz#ee8c1b53d3fe8bb7e5d2c5c5dfc0168afdd2f747"
+ integrity sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==
fs-vacuum@^1.2.10, fs-vacuum@~1.2.10:
version "1.2.10"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
- integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
+ integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
+ has-proto "^1.0.1"
has-symbols "^1.0.3"
get-own-enumerable-property-symbols@^3.0.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-glob@^10.0.0:
- version "10.2.3"
- resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.3.tgz#aa6765963fe6c5936d5c2e00943e7af06302a1a7"
- integrity sha512-Kb4rfmBVE3eQTAimgmeqc2LwSnN0wIOkkUL6HmxEFxNJ4fHghYHVbFba/HcGcRjE6s9KoMNK3rSOwkL4PioZjg==
+glob@^10.2.5:
+ version "10.2.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.6.tgz#1e27edbb3bbac055cb97113e27a066c100a4e5e1"
+ integrity sha512-U/rnDpXJGF414QQQZv5uVsabTVxMSwzS5CH0p3DRCIV6ownl4f7PzGnkGmvlum2wB+9RlJWJZ6ACU1INnBqiPA==
dependencies:
foreground-child "^3.1.0"
jackspeak "^2.0.3"
- minimatch "^9.0.0"
- minipass "^5.0.0"
+ minimatch "^9.0.1"
+ minipass "^5.0.0 || ^6.0.2"
path-scurry "^1.7.0"
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
handle-thing@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
wbuf "^1.1.0"
html-entities@^2.3.2:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
- integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.5.tgz#9f117bf6a5962efc31e094f6c6dad3cf3b95e33e"
+ integrity sha512-72TJlcMkYsEJASa/3HnX7VT59htM7iSHbH59NSZbtc+22Ap0Txnlx91sfeB+/A7wNZg7UxtZdhAW4y+/jimrdg==
html-parse-stringify2@^2.0.1:
version "2.0.1"
integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==
i18next@^22.4.15:
- version "22.4.15"
- resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.15.tgz#951882b751872994f8502b5a6ef6f796e6a7d7f8"
- integrity sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==
+ version "22.5.1"
+ resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.5.1.tgz#99df0b318741a506000c243429a7352e5f44d424"
+ integrity sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==
dependencies:
"@babel/runtime" "^7.20.6"
integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
inferno-clone-vnode@^8.0.3:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/inferno-clone-vnode/-/inferno-clone-vnode-8.1.1.tgz#433dea414f94e99f823cdba1adec4904bc9e880f"
- integrity sha512-AssRHxyrNaw17lUzVqqp7ATySMAaCRAGZAwJNmmUdmfqnpEvcjeKGmagbjiWAhkCGRiWOTr1wAbQuyInxr3jNg==
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/inferno-clone-vnode/-/inferno-clone-vnode-8.2.1.tgz#6206083abb16647f177cb36db219593da43d5955"
+ integrity sha512-TEkNH+1vzMz11ohw/Tjn+ULdlxSMxUOC/9XF7BWl2T+p0BXwXf8oO0WALrwIUMmZLROoBKnEnyiDVg5U0H/LqA==
dependencies:
- inferno "8.1.1"
+ inferno "8.2.1"
inferno-create-element@^8.0.3, inferno-create-element@^8.1.1:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-8.1.1.tgz#f135ea6fb784d2024148845eeb4f752da6252f0d"
- integrity sha512-RzoRK/gl6kADO9hzviChjCLvD5whsUJE5k71ordYuq/6aixdLaiizebthNkE+18ZKoN6VUXH1PlczaMPu/YIpQ==
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/inferno-create-element/-/inferno-create-element-8.2.1.tgz#b1883363d5603226e0209fb17a06be4610669eea"
+ integrity sha512-UhRjt0r6PA4e3OaloMIWoR1xmC6FZA9yWN7ZCI2v3xcchukHfmLOHnZjEhr0MnYDvXuH7yR8ut1evsHa1UFm4w==
dependencies:
- inferno "8.1.1"
+ inferno "8.2.1"
inferno-helmet@^5.2.1:
version "5.2.1"
object-assign "^4.1.1"
inferno-hydrate@^8.1.1:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/inferno-hydrate/-/inferno-hydrate-8.1.1.tgz#b3e20b123df31c0787f2d16fe915f30045c984a4"
- integrity sha512-u4hXW+mgdmVV3EkEU+0DzrtEBkCtpCE5w8N7mEkxnH5o4A1GfAaiWN1iJKETcS5yEJEg1j1RipLfSteSbKo7jA==
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/inferno-hydrate/-/inferno-hydrate-8.2.1.tgz#15c26e4b046220f40b04f8e27b539654c73b030d"
+ integrity sha512-gQ4q/qklV69tiaCLLH1IjVYJnvvUuSdh2RNapqynPu7HFcV6QzIRRS97xjX967Fzw86wOnU+/IVyQFmOofiXXg==
dependencies:
- inferno "8.1.1"
+ inferno "8.2.1"
inferno-i18next-dess@0.0.2:
version "0.0.2"
inferno-vnode-flags "^8.0.3"
inferno-router@^8.1.1:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/inferno-router/-/inferno-router-8.1.1.tgz#254b5a123a7c91965cf2c55ef801605c2dea5f7a"
- integrity sha512-ntjwvVWbRFIpD8NqU+9KXuL4rc4BuhlXZbzigP4o4KgQlF6D78eTYznWyztqYA39Qz2Sg0gAKf7GJoDJUc2+ig==
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/inferno-router/-/inferno-router-8.2.1.tgz#4bcecc7f7f665c54b152cb94ffc4491e1d7aef1f"
+ integrity sha512-vFm04mEXoOVcvHOHP5PWfydDM4kzlkettwFK5/apY0g4XXPjIvleHclrAaEzOjG7KIw05zTbt/Do8tacxZ3ELA==
dependencies:
history "^5.3.0"
hoist-non-inferno-statics "^1.1.3"
- inferno "8.1.1"
+ inferno "8.2.1"
path-to-regexp-es6 "1.7.0"
inferno-server@^8.1.1:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/inferno-server/-/inferno-server-8.1.1.tgz#e5e547761cfa85606e74f3bb4253ee5d09c6443e"
- integrity sha512-qgQc23csgHllF46i0hEJUq1mO+ObG5vis8B7vR732gjcm+K1p9jQ17dtbuLiLlQHciMItVdqnG3zLdoNCWtc2Q==
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/inferno-server/-/inferno-server-8.2.1.tgz#ea4b04b3afcd2c18810cf521b1a0fb7291c82eac"
+ integrity sha512-M+c7FH3pv8/VIh8lEqs/1Tt89GHIUNlu9gkAbPXXO7YpJgrFY6BXhShZBP8cshJamee756XnTYY0v5hpo/jDMQ==
dependencies:
- inferno "8.1.1"
+ inferno "8.2.1"
inferno-shared@^8.0.3:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-8.1.1.tgz#1c6d60fd503eb403efc080762b9ec874dfeedcc6"
- integrity sha512-5qcPBnNdIi/4/ICGOWG7rrV6E/r0RgJfgQckDaNbj8gBGdlzlhs/tR+c6VrwBWSb/IR0Pho2m2Mf2eUPiVAUmQ==
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/inferno-shared/-/inferno-shared-8.2.1.tgz#48b64a7fd8f7ca0e8358fc5c35ebc05402cc3d46"
+ integrity sha512-94+DR84MFfdSepwCYfxRTpqWa6xkoiOZxy8kBowkyDlZCnAt9VofjVMknwebgVP4XXxk3D5nlrV1quwkrSQNDw==
inferno-side-effect@^1.1.5:
version "1.1.5"
npm "^5.8.0"
shallowequal "^1.0.1"
-inferno-vnode-flags@8.1.1, inferno-vnode-flags@^8.0.3:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-8.1.1.tgz#b405edf33bf12c850b253b6c26700b8368f338d7"
- integrity sha512-Az2vuT5FRF6zGW8e13ye5Mg0YoKvk/XL2LVJr4i+LrZWn9fb9AqyCDuCSvMe9ZBWKw2pnsbsUaDlQYhNh0tjkA==
+inferno-vnode-flags@8.2.1, inferno-vnode-flags@^8.0.3:
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/inferno-vnode-flags/-/inferno-vnode-flags-8.2.1.tgz#cd8dc52096f6c0c7ca30f65fb30c620c4bfe1030"
+ integrity sha512-gdxF0zya4kYAoY5BUQ9LPWfEZ2nlLsaREyQn0ohzPihnsw91L7CW2ShLtBlgvGtAkt3khVczG9OAGPZTeQEDdg==
-inferno@8.1.1, inferno@^8.0.3, inferno@^8.1.1:
- version "8.1.1"
- resolved "https://registry.yarnpkg.com/inferno/-/inferno-8.1.1.tgz#f2e7b02eb32e7fb3da0f8b4a5a38640979546c0b"
- integrity sha512-PjpQkS1uYLeK8FHpMBgJi0qE5traFZs/jpgk+Ddx1Y7HGp5kneyus02eajgWzPM4imGYf6S3h+qbjwSXGutveA==
+inferno@8.2.1, inferno@^8.0.3, inferno@^8.1.1:
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/inferno/-/inferno-8.2.1.tgz#b374b7578166f45239edc72d3e2b76761325f9df"
+ integrity sha512-cg4CYcIhBQoMOWpazLBGvJV3J7TCHCG2RTfQDVDFVtn6BIwDFp7xqpI0XSCVuo4GAUfMG9G8/cA78pBoe4PuKQ==
dependencies:
csstype "^3.1.1"
- inferno-vnode-flags "8.1.1"
+ inferno-vnode-flags "8.2.1"
opencollective-postinstall "^2.0.3"
inflight@^1.0.4, inflight@~1.0.6:
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
ipaddr.js@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
- integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f"
+ integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==
is-arguments@^1.0.4:
version "1.1.1"
cidr-regex "1.0.6"
is-core-module@^2.11.0, is-core-module@^2.9.0:
- version "2.12.0"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4"
- integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd"
+ integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==
dependencies:
has "^1.0.3"
integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
jackspeak@^2.0.3:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.0.tgz#497cbaedc902ec3f31d5d61be804d2364ff9ddad"
- integrity sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ==
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.1.tgz#655e8cf025d872c9c03d3eb63e8f0c024fef16a6"
+ integrity sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==
dependencies:
"@isaacs/cliui" "^8.0.2"
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
jake@^10.8.5:
- version "10.8.5"
- resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
- integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
+ version "10.8.7"
+ resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f"
+ integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==
dependencies:
async "^3.2.3"
chalk "^4.0.2"
- filelist "^1.0.1"
- minimatch "^3.0.4"
+ filelist "^1.0.4"
+ minimatch "^3.1.2"
jest-worker@^26.2.1:
version "26.6.2"
merge-stream "^2.0.0"
supports-color "^8.0.0"
-js-sdsl@^4.1.4:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430"
- integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==
-
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^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/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912"
integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
-lemmy-js-client@0.17.2-rc.17:
- version "0.17.2-rc.17"
- resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.17.tgz#91a167c3b61db39fab2e977685a42a77aeae519a"
- integrity sha512-DBzQjVRo89co7Wppl72/xlNdJfAnXrUE0UgWZxO3v2I8axK9JUD4XmodpRe33thpfPmsURQ1W7dOUX60rcQPQg==
+lemmy-js-client@0.17.2-rc.24:
+ version "0.17.2-rc.24"
+ resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.24.tgz#3b09233a6d89286e559be2e840d81c0c549562ad"
+ integrity sha512-aSHz7UTcwnwnNd9poY8tEXP7RA9ieZm9MAfSljcbCNU5ds9CASXYNodmraUVJiqCmT4HWnj7IeVmBC9r7nTHnw==
dependencies:
cross-fetch "^3.1.5"
form-data "^4.0.0"
yallist "^4.0.0"
lru-cache@^9.1.1:
- version "9.1.1"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1"
- integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==
+ version "9.1.2"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.2.tgz#255fdbc14b75589d6d0e73644ca167a8db506835"
+ integrity sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==
magic-string@^0.25.0, magic-string@^0.25.7:
version "0.25.9"
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
memfs@^3.4.3:
- version "3.5.1"
- resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.5.1.tgz#f0cd1e2bfaef58f6fe09bfb9c2288f07fea099ec"
- integrity sha512-UWbFJKvj5k+nETdteFndTpYxdeTMox/ULeqX5k/dpaQJCCFmj5EeKv3dBcyO2xmkRAx2vppRu5dVG7SOtsGOzA==
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.5.2.tgz#3367cb58940e45224a7e377015b37f55a831b3ac"
+ integrity sha512-4kbWXbVZ+LU4XFDS2CuA7frnwz2HxCMB/0yOXc86q7aCQrfWKkL11t6al1e2CsVC7uhnBNTQ1TfUsAxVauO9IQ==
dependencies:
fs-monkey "^1.0.3"
mime-db "^1.6.0"
mini-css-extract-plugin@^2.7.5:
- version "2.7.5"
- resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.5.tgz#afbb344977659ec0f1f6e050c7aea456b121cfc5"
- integrity sha512-9HaR++0mlgom81s95vvNjxkg52n2b5s//3ZTI1EtzFb98awsLSivs2LMsVqnQ3ay0PVhqWcGNyDaTE961FOcjQ==
+ version "2.7.6"
+ resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz#282a3d38863fddcd2e0c220aaed5b90bc156564d"
+ integrity sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==
dependencies:
schema-utils "^4.0.0"
dependencies:
brace-expansion "^2.0.1"
-minimatch@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.0.tgz#bfc8e88a1c40ffd40c172ddac3decb8451503b56"
- integrity sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==
+minimatch@^9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253"
+ integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==
dependencies:
brace-expansion "^2.0.1"
safe-buffer "^5.1.2"
yallist "^3.0.0"
-minipass@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
- integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
+"minipass@^5.0.0 || ^6.0.2":
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.2.tgz#542844b6c4ce95b202c0995b0a471f1229de4c81"
+ integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==
minizlib@^1.3.3:
version "1.3.3"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
node-abi@^3.3.0:
- version "3.40.0"
- resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.40.0.tgz#51d8ed44534f70ff1357dfbc3a89717b1ceac1b4"
- integrity sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA==
+ version "3.43.0"
+ resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.43.0.tgz#468dc09af3c262ef2fb3a0d2ff34cf8fba61952a"
+ integrity sha512-QB0MMv+tn9Ur2DtJrc8y09n0n6sw88CyDniWSX2cHW10goQXYPK9ZpFJOktDS4ron501edPX6h9i7Pg+RnH5nQ==
dependencies:
semver "^7.3.5"
json-parse-better-errors "^1.0.0"
safe-buffer "^5.1.1"
-node-fetch@2.6.7:
- version "2.6.7"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
- integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
+node-fetch@^2.6.11:
+ version "2.6.11"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25"
+ integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==
dependencies:
whatwg-url "^5.0.0"
tar "^4.4.8"
which "1"
-node-releases@^2.0.8:
- version "2.0.10"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f"
- integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
+node-releases@^2.0.12:
+ version "2.0.12"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039"
+ integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==
"nopt@2 || 3":
version "3.0.6"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-scurry@^1.7.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.8.0.tgz#809e09690c63817c76d0183f19a5b21b530ff7d2"
- integrity sha512-IjTrKseM404/UAWA8bBbL3Qp6O2wXkanuIE3seCxBH7ctRuvH1QRawy1N3nVDHGkdeZsjOsSe/8AQBL/VQCy2g==
+ version "1.9.2"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.9.2.tgz#90f9d296ac5e37e608028e28a447b11d385b3f63"
+ integrity sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==
dependencies:
lru-cache "^9.1.1"
- minipass "^5.0.0"
+ minipass "^5.0.0 || ^6.0.2"
path-to-regexp-es6@1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
-postcss-modules-local-by-default@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
- integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
+postcss-modules-local-by-default@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524"
+ integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser "^6.0.2"
icss-utils "^5.0.0"
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
- version "6.0.12"
- resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz#2efae5ffab3c8bfb2b7fbf0c426e3bca616c4abb"
- integrity sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==
+ version "6.0.13"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
+ integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
-postcss@^8.3.11, postcss@^8.4.19:
- version "8.4.23"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab"
- integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==
+postcss@^8.3.11, postcss@^8.4.21:
+ version "8.4.24"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df"
+ integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
glob "^7.1.3"
rimraf@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.0.tgz#5bda14e410d7e4dd522154891395802ce032c2cb"
- integrity sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g==
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.1.tgz#0881323ab94ad45fec7c0221f27ea1a142f3f0d0"
+ integrity sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==
dependencies:
- glob "^10.0.0"
+ glob "^10.2.5"
rimraf@~2.6.2:
version "2.6.3"
dependencies:
aproba "^1.1.1"
-rxjs@^7.8.0, rxjs@^7.8.1:
+rxjs@^7.8.0:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
postcss "^8.3.11"
sass-loader@^13.2.2:
- version "13.2.2"
- resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.2.2.tgz#f97e803993b24012c10d7ba9676548bf7a6b18b9"
- integrity sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==
+ version "13.3.1"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.3.1.tgz#32ee5791434b9b4dbd1adcce76fcb4cea49cc12c"
+ integrity sha512-cBTxmgyVA1nXPvIK4brjJMXOMJ2v2YrQEuHqLw3LylGb3gsR6jAvdjHMcy/+JGTmmIF9SauTrLLR7bsWDMWqgg==
dependencies:
klona "^2.0.6"
neo-async "^2.6.2"
ansi-regex "^4.1.0"
strip-ansi@^7.0.1:
- version "7.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
- integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
+ integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
dependencies:
ansi-regex "^6.0.1"
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
style-loader@^3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.2.tgz#eaebca714d9e462c19aa1e3599057bc363924899"
- integrity sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw==
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff"
+ integrity sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==
supports-color@^5.3.0:
version "5.5.0"
execa "^0.7.0"
terser-webpack-plugin@^5.3.7:
- version "5.3.8"
- resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz#415e03d2508f7de63d59eca85c5d102838f06610"
- integrity sha512-WiHL3ElchZMsK27P8uIUh4604IgJyAW47LVXGbEoB21DbQcZ+OuMpGjVYnEUaqcWM6dO8uS2qUbA7LSCWqvsbg==
+ version "5.3.9"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"
+ integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==
dependencies:
"@jridgewell/trace-mapping" "^0.3.17"
jest-worker "^27.4.5"
terser "^5.16.8"
terser@^5.0.0, terser@^5.16.8, terser@^5.17.3:
- version "5.17.3"
- resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.3.tgz#7f908f16b3cdf3f6c0f8338e6c1c674837f90d25"
- integrity sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==
+ version "5.17.7"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.7.tgz#2a8b134826fe179b711969fd9d9a0c2479b2a8c3"
+ integrity sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==
dependencies:
- "@jridgewell/source-map" "^0.3.2"
- acorn "^8.5.0"
+ "@jridgewell/source-map" "^0.3.3"
+ acorn "^8.8.2"
commander "^2.20.0"
source-map-support "~0.5.20"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.1.0, tslib@^2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
- integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
+ version "2.5.3"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913"
+ integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==
tsutils@^3.21.0:
version "3.21.0"
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
typescript@^5.0.4:
- version "5.0.4"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
- integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826"
+ integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
-update-browserslist-db@^1.0.10:
+update-browserslist-db@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
webpack-cli@^5.1.1:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.1.tgz#c211ac6d911e77c512978f7132f0d735d4a97ace"
- integrity sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.3.tgz#6b6186270efec62394f6fefeebed0872a779f345"
+ integrity sha512-MTuk7NUMvEHQUSXCpvUrF1q2p0FJS40dPFfqQvG3jTWcgv/8plBNz2Kv2HXZiLGPnfmSAA5uCtCILO1JBmmkfw==
dependencies:
"@discoveryjs/json-ext" "^0.5.0"
- "@webpack-cli/configtest" "^2.1.0"
- "@webpack-cli/info" "^2.0.1"
- "@webpack-cli/serve" "^2.0.4"
+ "@webpack-cli/configtest" "^2.1.1"
+ "@webpack-cli/info" "^2.0.2"
+ "@webpack-cli/serve" "^2.0.5"
colorette "^2.0.14"
commander "^10.0.1"
cross-spawn "^7.0.3"
schema-utils "^4.0.0"
webpack-merge@^5.7.3:
- version "5.8.0"
- resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61"
- integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==
+ version "5.9.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826"
+ integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==
dependencies:
clone-deep "^4.0.1"
wildcard "^2.0.0"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
-websocket-ts@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/websocket-ts/-/websocket-ts-1.1.1.tgz#de482da5e0c714ebc58a43fe94157e5a855f2828"
- integrity sha512-rm+S60J74Ckw5iizzgID12ju+OfaHAa6dhXhULIOrXkl0e05RzxfY42/vMStpz5jWL3iz9mkyjPcFUY1IgI0fw==
-
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
-workbox-background-sync@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9"
- integrity sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==
+workbox-background-sync@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.6.1.tgz#08d603a33717ce663e718c30cc336f74909aff2f"
+ integrity sha512-trJd3ovpWCvzu4sW0E8rV3FUyIcC0W8G+AZ+VcqzzA890AsWZlUGOTSxIMmIHVusUw/FDq1HFWfy/kC/WTRqSg==
dependencies:
idb "^7.0.1"
- workbox-core "6.5.4"
+ workbox-core "6.6.1"
-workbox-broadcast-update@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz#8441cff5417cd41f384ba7633ca960a7ffe40f66"
- integrity sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==
+workbox-broadcast-update@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.6.1.tgz#0fad9454cf8e4ace0c293e5617c64c75d8a8c61e"
+ integrity sha512-fBhffRdaANdeQ1V8s692R9l/gzvjjRtydBOvR6WCSB0BNE2BacA29Z4r9/RHd9KaXCPl6JTdI9q0bR25YKP8TQ==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "6.6.1"
-workbox-build@6.5.4, workbox-build@^6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.5.4.tgz#7d06d31eb28a878817e1c991c05c5b93409f0389"
- integrity sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==
+workbox-build@6.6.1, workbox-build@^6.5.4:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.6.1.tgz#6010e9ce550910156761448f2dbea8cfcf759cb0"
+ integrity sha512-INPgDx6aRycAugUixbKgiEQBWD0MPZqU5r0jyr24CehvNuLPSXp/wGOpdRJmts656lNiXwqV7dC2nzyrzWEDnw==
dependencies:
"@apideck/better-ajv-errors" "^0.3.1"
"@babel/core" "^7.11.1"
strip-comments "^2.0.1"
tempy "^0.6.0"
upath "^1.2.0"
- workbox-background-sync "6.5.4"
- workbox-broadcast-update "6.5.4"
- workbox-cacheable-response "6.5.4"
- workbox-core "6.5.4"
- workbox-expiration "6.5.4"
- workbox-google-analytics "6.5.4"
- workbox-navigation-preload "6.5.4"
- workbox-precaching "6.5.4"
- workbox-range-requests "6.5.4"
- workbox-recipes "6.5.4"
- workbox-routing "6.5.4"
- workbox-strategies "6.5.4"
- workbox-streams "6.5.4"
- workbox-sw "6.5.4"
- workbox-window "6.5.4"
-
-workbox-cacheable-response@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz#a5c6ec0c6e2b6f037379198d4ef07d098f7cf137"
- integrity sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==
- dependencies:
- workbox-core "6.5.4"
-
-workbox-core@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.5.4.tgz#df48bf44cd58bb1d1726c49b883fb1dffa24c9ba"
- integrity sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==
-
-workbox-expiration@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.5.4.tgz#501056f81e87e1d296c76570bb483ce5e29b4539"
- integrity sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==
+ workbox-background-sync "6.6.1"
+ workbox-broadcast-update "6.6.1"
+ workbox-cacheable-response "6.6.1"
+ workbox-core "6.6.1"
+ workbox-expiration "6.6.1"
+ workbox-google-analytics "6.6.1"
+ workbox-navigation-preload "6.6.1"
+ workbox-precaching "6.6.1"
+ workbox-range-requests "6.6.1"
+ workbox-recipes "6.6.1"
+ workbox-routing "6.6.1"
+ workbox-strategies "6.6.1"
+ workbox-streams "6.6.1"
+ workbox-sw "6.6.1"
+ workbox-window "6.6.1"
+
+workbox-cacheable-response@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.6.1.tgz#284c2b86be3f4fd191970ace8c8e99797bcf58e9"
+ integrity sha512-85LY4veT2CnTCDxaVG7ft3NKaFbH6i4urZXgLiU4AiwvKqS2ChL6/eILiGRYXfZ6gAwDnh5RkuDbr/GMS4KSag==
+ dependencies:
+ workbox-core "6.6.1"
+
+workbox-core@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.6.1.tgz#7184776d4134c5ed2f086878c882728fc9084265"
+ integrity sha512-ZrGBXjjaJLqzVothoE12qTbVnOAjFrHDXpZe7coCb6q65qI/59rDLwuFMO4PcZ7jcbxY+0+NhUVztzR/CbjEFw==
+
+workbox-expiration@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.6.1.tgz#a841fa36676104426dbfb9da1ef6a630b4f93739"
+ integrity sha512-qFiNeeINndiOxaCrd2DeL1Xh1RFug3JonzjxUHc5WkvkD2u5abY3gZL1xSUNt3vZKsFFGGORItSjVTVnWAZO4A==
dependencies:
idb "^7.0.1"
- workbox-core "6.5.4"
+ workbox-core "6.6.1"
-workbox-google-analytics@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz#c74327f80dfa4c1954cbba93cd7ea640fe7ece7d"
- integrity sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==
+workbox-google-analytics@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.6.1.tgz#a07a6655ab33d89d1b0b0a935ffa5dea88618c5d"
+ integrity sha512-1TjSvbFSLmkpqLcBsF7FuGqqeDsf+uAXO/pjiINQKg3b1GN0nBngnxLcXDYo1n/XxK4N7RaRrpRlkwjY/3ocuA==
dependencies:
- workbox-background-sync "6.5.4"
- workbox-core "6.5.4"
- workbox-routing "6.5.4"
- workbox-strategies "6.5.4"
+ workbox-background-sync "6.6.1"
+ workbox-core "6.6.1"
+ workbox-routing "6.6.1"
+ workbox-strategies "6.6.1"
-workbox-navigation-preload@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz#ede56dd5f6fc9e860a7e45b2c1a8f87c1c793212"
- integrity sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==
+workbox-navigation-preload@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.6.1.tgz#61a34fe125558dd88cf09237f11bd966504ea059"
+ integrity sha512-DQCZowCecO+wRoIxJI2V6bXWK6/53ff+hEXLGlQL4Rp9ZaPDLrgV/32nxwWIP7QpWDkVEtllTAK5h6cnhxNxDA==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "6.6.1"
-workbox-precaching@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.5.4.tgz#740e3561df92c6726ab5f7471e6aac89582cab72"
- integrity sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==
+workbox-precaching@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.6.1.tgz#dedeeba10a2d163d990bf99f1c2066ac0d1a19e2"
+ integrity sha512-K4znSJ7IKxCnCYEdhNkMr7X1kNh8cz+mFgx9v5jFdz1MfI84pq8C2zG+oAoeE5kFrUf7YkT5x4uLWBNg0DVZ5A==
dependencies:
- workbox-core "6.5.4"
- workbox-routing "6.5.4"
- workbox-strategies "6.5.4"
+ workbox-core "6.6.1"
+ workbox-routing "6.6.1"
+ workbox-strategies "6.6.1"
-workbox-range-requests@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz#86b3d482e090433dab38d36ae031b2bb0bd74399"
- integrity sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==
+workbox-range-requests@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.6.1.tgz#ddaf7e73af11d362fbb2f136a9063a4c7f507a39"
+ integrity sha512-4BDzk28govqzg2ZpX0IFkthdRmCKgAKreontYRC5YsAPB2jDtPNxqx3WtTXgHw1NZalXpcH/E4LqUa9+2xbv1g==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "6.6.1"
-workbox-recipes@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.5.4.tgz#cca809ee63b98b158b2702dcfb741b5cc3e24acb"
- integrity sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==
+workbox-recipes@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.6.1.tgz#ea70d2b2b0b0bce8de0a9d94f274d4a688e69fae"
+ integrity sha512-/oy8vCSzromXokDA+X+VgpeZJvtuf8SkQ8KL0xmRivMgJZrjwM3c2tpKTJn6PZA6TsbxGs3Sc7KwMoZVamcV2g==
dependencies:
- workbox-cacheable-response "6.5.4"
- workbox-core "6.5.4"
- workbox-expiration "6.5.4"
- workbox-precaching "6.5.4"
- workbox-routing "6.5.4"
- workbox-strategies "6.5.4"
+ workbox-cacheable-response "6.6.1"
+ workbox-core "6.6.1"
+ workbox-expiration "6.6.1"
+ workbox-precaching "6.6.1"
+ workbox-routing "6.6.1"
+ workbox-strategies "6.6.1"
-workbox-routing@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.5.4.tgz#6a7fbbd23f4ac801038d9a0298bc907ee26fe3da"
- integrity sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==
+workbox-routing@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.6.1.tgz#cba9a1c7e0d1ea11e24b6f8c518840efdc94f581"
+ integrity sha512-j4ohlQvfpVdoR8vDYxTY9rA9VvxTHogkIDwGdJ+rb2VRZQ5vt1CWwUUZBeD/WGFAni12jD1HlMXvJ8JS7aBWTg==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "6.6.1"
-workbox-strategies@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.5.4.tgz#4edda035b3c010fc7f6152918370699334cd204d"
- integrity sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==
+workbox-strategies@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.6.1.tgz#38d0f0fbdddba97bd92e0c6418d0b1a2ccd5b8bf"
+ integrity sha512-WQLXkRnsk4L81fVPkkgon1rZNxnpdO5LsO+ws7tYBC6QQQFJVI6v98klrJEjFtZwzw/mB/HT5yVp7CcX0O+mrw==
dependencies:
- workbox-core "6.5.4"
+ workbox-core "6.6.1"
-workbox-streams@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.5.4.tgz#1cb3c168a6101df7b5269d0353c19e36668d7d69"
- integrity sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==
+workbox-streams@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.6.1.tgz#b2f7ba7b315c27a6e3a96a476593f99c5d227d26"
+ integrity sha512-maKG65FUq9e4BLotSKWSTzeF0sgctQdYyTMq529piEN24Dlu9b6WhrAfRpHdCncRS89Zi2QVpW5V33NX8PgH3Q==
dependencies:
- workbox-core "6.5.4"
- workbox-routing "6.5.4"
+ workbox-core "6.6.1"
+ workbox-routing "6.6.1"
-workbox-sw@6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.5.4.tgz#d93e9c67924dd153a61367a4656ff4d2ae2ed736"
- integrity sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==
+workbox-sw@6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.6.1.tgz#d4c4ca3125088e8b9fd7a748ed537fa0247bd72c"
+ integrity sha512-R7whwjvU2abHH/lR6kQTTXLHDFU2izht9kJOvBRYK65FbwutT4VvnUAJIgHvfWZ/fokrOPhfoWYoPCMpSgUKHQ==
workbox-webpack-plugin@^6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz#baf2d3f4b8f435f3469887cf4fba2b7fac3d0fd7"
- integrity sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.1.tgz#4f81cc1ad4e5d2cd7477a86ba83c84ee2d187531"
+ integrity sha512-zpZ+ExFj9NmiI66cFEApyjk7hGsfJ1YMOaLXGXBoZf0v7Iu6hL0ZBe+83mnDq3YYWAfA3fnyFejritjOHkFcrA==
dependencies:
fast-json-stable-stringify "^2.1.0"
pretty-bytes "^5.4.1"
upath "^1.2.0"
webpack-sources "^1.4.3"
- workbox-build "6.5.4"
+ workbox-build "6.6.1"
-workbox-window@6.5.4, workbox-window@^6.5.4:
- version "6.5.4"
- resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.5.4.tgz#d991bc0a94dff3c2dbb6b84558cff155ca878e91"
- integrity sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==
+workbox-window@6.6.1, workbox-window@^6.5.4:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.6.1.tgz#f22a394cbac36240d0dadcbdebc35f711bb7b89e"
+ integrity sha512-wil4nwOY58nTdCvif/KEZjQ2NP8uk3gGeRNy2jPBbzypU4BT4D9L8xiwbmDBpZlSgJd2xsT9FvSNU0gsxV51JQ==
dependencies:
"@types/trusted-types" "^2.0.2"
- workbox-core "6.5.4"
+ workbox-core "6.6.1"
worker-farm@^1.6.0:
version "1.7.0"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^2.2.2:
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073"
- integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
+ integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
yargs-parser@^15.0.1:
version "15.0.3"