From b64f47cfe9084c04a9ec6b8e62ac448f302e2600 Mon Sep 17 00:00:00 2001 From: Dessalines <dessalines@users.noreply.github.com> Date: Wed, 4 Jan 2023 11:56:24 -0500 Subject: [PATCH] Removing monads. Fixes #884 (#886) * Removing monads. Fixes #884 * Fixing post fetching. * Dont show not_logged_in error for navbar. * Adding the lemmy-js-client RC. * Fixing registration application mode --- lemmy-translations | 2 +- package.json | 5 +- src/client/index.tsx | 10 +- src/server/index.tsx | 35 +- src/shared/components/app/app.tsx | 44 +- src/shared/components/app/footer.tsx | 2 +- src/shared/components/app/navbar.tsx | 299 ++--- src/shared/components/app/theme.tsx | 10 +- .../components/comment/comment-form.tsx | 131 +- .../components/comment/comment-node.tsx | 1125 +++++++++-------- .../components/comment/comment-nodes.tsx | 11 +- .../components/comment/comment-report.tsx | 53 +- .../components/common/banner-icon-header.tsx | 31 +- .../components/common/comment-sort-select.tsx | 5 +- .../components/common/data-type-select.tsx | 5 +- src/shared/components/common/html-tags.tsx | 35 +- .../components/common/image-upload-form.tsx | 50 +- .../components/common/language-select.tsx | 27 +- .../components/common/listing-type-select.tsx | 9 +- .../components/common/markdown-textarea.tsx | 193 ++- src/shared/components/common/moment-time.tsx | 20 +- .../common/registration-application.tsx | 119 +- src/shared/components/common/sort-select.tsx | 5 +- .../components/community/communities.tsx | 207 ++- .../components/community/community-form.tsx | 233 ++-- .../components/community/community-link.tsx | 11 +- src/shared/components/community/community.tsx | 516 ++++---- .../components/community/create-community.tsx | 17 +- src/shared/components/community/sidebar.tsx | 218 ++-- src/shared/components/home/admin-settings.tsx | 52 +- src/shared/components/home/home.tsx | 323 ++--- src/shared/components/home/instances.tsx | 70 +- src/shared/components/home/legal.tsx | 16 +- src/shared/components/home/login.tsx | 65 +- src/shared/components/home/setup.tsx | 69 +- src/shared/components/home/signup.tsx | 262 ++-- src/shared/components/home/site-form.tsx | 570 ++++----- src/shared/components/home/site-sidebar.tsx | 34 +- src/shared/components/modlog.tsx | 358 +++--- src/shared/components/person/inbox.tsx | 534 ++++---- .../components/person/password-change.tsx | 51 +- .../components/person/person-details.tsx | 17 +- .../components/person/person-listing.tsx | 14 +- src/shared/components/person/profile.tsx | 895 ++++++------- .../person/registration-applications.tsx | 99 +- src/shared/components/person/reports.tsx | 284 ++--- src/shared/components/person/settings.tsx | 387 +++--- src/shared/components/person/verify-email.tsx | 18 +- src/shared/components/post/create-post.tsx | 188 ++- src/shared/components/post/metadata-card.tsx | 116 +- src/shared/components/post/post-form.tsx | 564 ++++----- src/shared/components/post/post-listing.tsx | 796 ++++++------ src/shared/components/post/post-listings.tsx | 59 +- src/shared/components/post/post-report.tsx | 55 +- src/shared/components/post/post.tsx | 701 +++++----- .../create-private-message.tsx | 93 +- .../private_message/private-message-form.tsx | 93 +- .../private-message-report.tsx | 52 +- .../private_message/private-message.tsx | 92 +- src/shared/components/search.tsx | 420 +++--- src/shared/interfaces.ts | 11 +- src/shared/routes.ts | 3 + src/shared/services/UserService.ts | 41 +- src/shared/utils.ts | 548 +++----- tsconfig.json | 2 +- yarn.lock | 26 +- 66 files changed, 5171 insertions(+), 6235 deletions(-) diff --git a/lemmy-translations b/lemmy-translations index c970056..975c922 160000 --- a/lemmy-translations +++ b/lemmy-translations @@ -1 +1 @@ -Subproject commit c97005696d132acf2cad3506df4f3c2256142349 +Subproject commit 975c922271f27920aef51a2c3ae9f24714c44004 diff --git a/package.json b/package.json index 54732be..ff20ab6 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,11 @@ "@babel/preset-env": "7.19.3", "@babel/preset-typescript": "^7.18.6", "@babel/runtime": "^7.18.9", - "@sniptt/monads": "^0.5.10", "autosize": "^5.0.1", "babel-loader": "^8.2.5", "babel-plugin-inferno": "^6.5.0", "check-password-strength": "^2.0.7", "choices.js": "^10.1.0", - "class-transformer": "^0.5.1", "classnames": "^2.3.1", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^11.0.0", @@ -47,7 +45,7 @@ "inferno-server": "^8.0.5", "isomorphic-cookie": "^1.2.4", "jwt-decode": "^3.1.2", - "lemmy-js-client": "0.17.0-rc.57", + "lemmy-js-client": "0.17.0-rc.61", "markdown-it": "^13.0.1", "markdown-it-container": "^3.0.0", "markdown-it-footnote": "^3.0.3", @@ -57,7 +55,6 @@ "mini-css-extract-plugin": "^2.6.1", "moment": "^2.29.4", "node-fetch": "^2.6.1", - "reflect-metadata": "^0.1.13", "register-service-worker": "^1.7.2", "run-node-webpack-plugin": "^1.3.0", "rxjs": "^7.5.6", diff --git a/src/client/index.tsx b/src/client/index.tsx index 3838dca..a96e6bd 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -1,10 +1,9 @@ import { hydrate } from "inferno-hydrate"; import { BrowserRouter } from "inferno-router"; -import { GetSiteResponse } from "lemmy-js-client"; import { App } from "../shared/components/app/app"; -import { convertWindowJson, initializeSite } from "../shared/utils"; +import { initializeSite } from "../shared/utils"; -const site = convertWindowJson(GetSiteResponse, window.isoData.site_res); +const site = window.isoData.site_res; initializeSite(site); const wrapper = ( @@ -13,4 +12,7 @@ const wrapper = ( </BrowserRouter> ); -hydrate(wrapper, document.getElementById("root")); +let root = document.getElementById("root"); +if (root) { + hydrate(wrapper, root); +} diff --git a/src/server/index.tsx b/src/server/index.tsx index 7d6200e..048e702 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -1,5 +1,3 @@ -import { None, Option } from "@sniptt/monads"; -import { serialize as serializeO } from "class-transformer"; import express from "express"; import fs from "fs"; import { IncomingHttpHeaders } from "http"; @@ -7,7 +5,7 @@ import { Helmet } from "inferno-helmet"; import { matchPath, StaticRouter } from "inferno-router"; import { renderToString } from "inferno-server"; import IsomorphicCookie from "isomorphic-cookie"; -import { GetSite, GetSiteResponse, LemmyHttp, toOption } 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"; @@ -114,11 +112,11 @@ server.get("/css/themelist", async (_req, res) => { // server.use(cookieParser()); server.get("/*", async (req, res) => { try { - const activeRoute = routes.find(route => matchPath(req.path, route)) || {}; + const activeRoute = routes.find(route => matchPath(req.path, route)); const context = {} as any; - let auth: Option<string> = toOption(IsomorphicCookie.load("jwt", req)); + let auth: string | undefined = IsomorphicCookie.load("jwt", req); - let getSiteForm = new GetSite({ auth }); + let getSiteForm: GetSite = { auth }; let promises: Promise<any>[] = []; @@ -138,14 +136,14 @@ server.get("/*", async (req, res) => { console.error( "Incorrect JWT token, skipping auth so frontend can remove jwt cookie" ); - getSiteForm.auth = None; - initialFetchReq.auth = None; + getSiteForm.auth = undefined; + initialFetchReq.auth = undefined; try_site = await initialFetchReq.client.getSite(getSiteForm); } let site: GetSiteResponse = try_site; initializeSite(site); - if (activeRoute.fetchInitialData) { + if (activeRoute?.fetchInitialData) { promises.push(...activeRoute.fetchInitialData(initialFetchReq)); } @@ -193,7 +191,7 @@ server.get("/*", async (req, res) => { <!DOCTYPE html> <html ${helmet.htmlAttributes.toString()} lang="en"> <head> - <script>window.isoData = ${serializeO(isoData)}</script> + <script>window.isoData = ${JSON.stringify(isoData)}</script> <script>window.lemmyConfig = ${serialize(config)}</script> <!-- A remote debugging utility for mobile --> @@ -246,14 +244,17 @@ server.listen(Number(port), hostname, () => { function setForwardedHeaders(headers: IncomingHttpHeaders): { [key: string]: string; } { - let out = { - host: headers.host, - }; - if (headers["x-real-ip"]) { - out["x-real-ip"] = headers["x-real-ip"]; + let out: { [key: string]: string } = {}; + if (headers.host) { + out.host = headers.host; } - if (headers["x-forwarded-for"]) { - out["x-forwarded-for"] = headers["x-forwarded-for"]; + let realIp = headers["x-real-ip"]; + if (realIp) { + out["x-real-ip"] = realIp as string; + } + let forwardedFor = headers["x-forwarded-for"]; + if (forwardedFor) { + out["x-forwarded-for"] = forwardedFor as string; } return out; diff --git a/src/shared/components/app/app.tsx b/src/shared/components/app/app.tsx index a788636..45c67b3 100644 --- a/src/shared/components/app/app.tsx +++ b/src/shared/components/app/app.tsx @@ -19,37 +19,37 @@ export class App extends Component<any, any> { render() { let siteRes = this.isoData.site_res; let siteView = siteRes.site_view; + let icon = siteView.site.icon; return ( <> <Provider i18next={i18n}> <div id="app"> <Theme defaultTheme={siteView.local_site.default_theme} /> - {siteView.site.icon.match({ - some: icon => ( - <Helmet> - <link - id="favicon" - rel="shortcut icon" - type="image/x-icon" - href={icon || favIconUrl} - /> - <link rel="apple-touch-icon" href={icon || favIconPngUrl} /> - </Helmet> - ), - none: <></>, - })} + {icon && ( + <Helmet> + <link + id="favicon" + rel="shortcut icon" + type="image/x-icon" + href={icon || favIconUrl} + /> + <link rel="apple-touch-icon" href={icon || favIconPngUrl} /> + </Helmet> + )} <Navbar siteRes={siteRes} /> <div className="mt-4 p-0 fl-1"> <Switch> - {routes.map(({ path, exact, component: C, ...rest }) => ( - <Route - key={path} - path={path} - exact={exact} - render={props => <C {...props} {...rest} />} - /> - ))} + {routes.map( + ({ path, exact, component: Component, ...rest }) => ( + <Route + key={path} + path={path} + exact={exact} + render={props => <Component {...props} {...rest} />} + /> + ) + )} <Route render={props => <NoMatch {...props} />} /> </Switch> </div> diff --git a/src/shared/components/app/footer.tsx b/src/shared/components/app/footer.tsx index 8a7f3bd..a62a8ac 100644 --- a/src/shared/components/app/footer.tsx +++ b/src/shared/components/app/footer.tsx @@ -32,7 +32,7 @@ export class Footer extends Component<FooterProps, any> { {i18n.t("modlog")} </NavLink> </li> - {this.props.site.site_view.local_site.legal_information.isSome() && ( + {this.props.site.site_view.local_site.legal_information && ( <li className="nav-item"> <NavLink className="nav-link" to="/legal"> {i18n.t("legal_information")} diff --git a/src/shared/components/app/navbar.tsx b/src/shared/components/app/navbar.tsx index 06da94d..d758af6 100644 --- a/src/shared/components/app/navbar.tsx +++ b/src/shared/components/app/navbar.tsx @@ -1,4 +1,3 @@ -import { None } from "@sniptt/monads"; import { Component, createRef, linkEvent, RefObject } from "inferno"; import { NavLink } from "inferno-router"; import { @@ -20,10 +19,10 @@ import { i18n } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; import { amAdmin, - auth, canCreateCommunity, donateLemmyUrl, isBrowser, + myAuth, notifyComment, notifyPrivateMessage, numToSI, @@ -57,7 +56,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { private unreadReportCountSub: Subscription; private unreadApplicationCountSub: Subscription; private searchTextField: RefObject<HTMLInputElement>; - emptyState: NavbarState = { + state: NavbarState = { unreadInboxCount: 0, unreadReportCount: 0, unreadApplicationCount: 0, @@ -70,7 +69,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); @@ -82,11 +80,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> { this.searchTextField = createRef(); // On the first load, check the unreads - if (UserService.Instance.myUserInfo.isSome()) { + let auth = myAuth(false); + if (auth && UserService.Instance.myUserInfo) { this.requestNotificationPermission(); WebSocketService.Instance.send( wsClient.userJoin({ - auth: auth().unwrap(), + auth, }) ); @@ -143,22 +142,22 @@ export class Navbar extends Component<NavbarProps, NavbarState> { // TODO class active corresponding to current page navbar() { let siteView = this.props.siteRes.site_view; + let person = UserService.Instance.myUserInfo?.local_user_view.person; return ( <nav className="navbar navbar-expand-md navbar-light shadow-sm p-0 px-3"> <div className="container-lg"> <NavLink to="/" onMouseUp={linkEvent(this, this.handleHideExpandNavbar)} - title={siteView.site.description.unwrapOr(siteView.site.name)} + title={siteView.site.description ?? siteView.site.name} className="d-flex align-items-center navbar-brand mr-md-3" > - {siteView.site.icon.match({ - some: icon => showAvatars() && <PictrsImage src={icon} icon />, - none: <></>, - })} + {siteView.site.icon && showAvatars() && ( + <PictrsImage src={siteView.site.icon} icon /> + )} {siteView.site.name} </NavLink> - {UserService.Instance.myUserInfo.isSome() && ( + {UserService.Instance.myUserInfo && ( <> <ul className="navbar-nav ml-auto"> <li className="nav-item"> @@ -341,7 +340,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { </li> </ul> )} - {UserService.Instance.myUserInfo.isSome() ? ( + {UserService.Instance.myUserInfo ? ( <> <ul className="navbar-nav my-2"> <li className="nav-item"> @@ -409,81 +408,69 @@ export class Navbar extends Component<NavbarProps, NavbarState> { </li> </ul> )} - {UserService.Instance.myUserInfo - .map(m => m.local_user_view.person) - .match({ - some: person => ( - <ul className="navbar-nav"> - <li className="nav-item dropdown"> - <button - className="nav-link btn btn-link dropdown-toggle" - onClick={linkEvent(this, this.handleToggleDropdown)} - id="navbarDropdown" - role="button" - aria-expanded="false" - > - <span> - {showAvatars() && - person.avatar.match({ - some: avatar => ( - <PictrsImage src={avatar} icon /> - ), - none: <></>, - })} - {person.display_name.unwrapOr(person.name)} - </span> - </button> - {this.state.showDropdown && ( - <div - className="dropdown-content" - onMouseLeave={linkEvent( - this, - this.handleToggleDropdown - )} - > - <li className="nav-item"> - <NavLink - to={`/u/${person.name}`} - className="nav-link" - title={i18n.t("profile")} - > - <Icon icon="user" classes="mr-1" /> - {i18n.t("profile")} - </NavLink> - </li> - <li className="nav-item"> - <NavLink - to="/settings" - className="nav-link" - title={i18n.t("settings")} - > - <Icon icon="settings" classes="mr-1" /> - {i18n.t("settings")} - </NavLink> - </li> - <li> - <hr className="dropdown-divider" /> - </li> - <li className="nav-item"> - <button - className="nav-link btn btn-link" - onClick={linkEvent( - this, - this.handleLogoutClick - )} - title="test" - > - <Icon icon="log-out" classes="mr-1" /> - {i18n.t("logout")} - </button> - </li> - </div> + {person && ( + <ul className="navbar-nav"> + <li className="nav-item dropdown"> + <button + className="nav-link btn btn-link dropdown-toggle" + onClick={linkEvent(this, this.handleToggleDropdown)} + id="navbarDropdown" + role="button" + aria-expanded="false" + > + <span> + {showAvatars() && person.avatar && ( + <PictrsImage src={person.avatar} icon /> + )} + {person.display_name ?? person.name} + </span> + </button> + {this.state.showDropdown && ( + <div + className="dropdown-content" + onMouseLeave={linkEvent( + this, + this.handleToggleDropdown )} - </li> - </ul> - ), - none: <></>, - })} + > + <li className="nav-item"> + <NavLink + to={`/u/${person.name}`} + className="nav-link" + title={i18n.t("profile")} + > + <Icon icon="user" classes="mr-1" /> + {i18n.t("profile")} + </NavLink> + </li> + <li className="nav-item"> + <NavLink + to="/settings" + className="nav-link" + title={i18n.t("settings")} + > + <Icon icon="settings" classes="mr-1" /> + {i18n.t("settings")} + </NavLink> + </li> + <li> + <hr className="dropdown-divider" /> + </li> + <li className="nav-item"> + <button + className="nav-link btn btn-link" + onClick={linkEvent(this, this.handleLogoutClick)} + title="test" + > + <Icon icon="log-out" classes="mr-1" /> + {i18n.t("logout")} + </button> + </li> + </div> + )} + </li> + </ul> + )} </> ) : ( <ul className="navbar-nav my-2"> @@ -516,11 +503,9 @@ export class Navbar extends Component<NavbarProps, NavbarState> { } get moderatesSomething(): boolean { - return ( - amAdmin() || - UserService.Instance.myUserInfo.map(m => m.moderates).unwrapOr([]) - .length > 0 - ); + let mods = UserService.Instance.myUserInfo?.moderates; + let moderatesS = (mods && mods.length > 0) || false; + return amAdmin() || moderatesS; } handleToggleExpandNavbar(i: Navbar) { @@ -544,9 +529,9 @@ export class Navbar extends Component<NavbarProps, NavbarState> { event.preventDefault(); i.setState({ toggleSearch: true }); - i.searchTextField.current.focus(); - const offsetWidth = i.searchTextField.current.offsetWidth; - if (i.state.searchParam && offsetWidth > 100) { + i.searchTextField.current?.focus(); + const offsetWidth = i.searchTextField.current?.offsetWidth; + if (i.state.searchParam && offsetWidth && offsetWidth > 100) { i.updateUrl(); } } @@ -576,109 +561,91 @@ export class Navbar extends Component<NavbarProps, NavbarState> { return; } else if (msg.reconnect) { console.log(i18n.t("websocket_reconnected")); - if (UserService.Instance.myUserInfo.isSome()) { + let auth = myAuth(false); + if (UserService.Instance.myUserInfo && auth) { WebSocketService.Instance.send( wsClient.userJoin({ - auth: auth().unwrap(), + auth, }) ); this.fetchUnreads(); } } else if (op == UserOperation.GetUnreadCount) { - let data = wsJsonToRes<GetUnreadCountResponse>( - msg, - GetUnreadCountResponse - ); + let data = wsJsonToRes<GetUnreadCountResponse>(msg); this.setState({ unreadInboxCount: data.replies + data.mentions + data.private_messages, }); this.sendUnreadCount(); } else if (op == UserOperation.GetReportCount) { - let data = wsJsonToRes<GetReportCountResponse>( - msg, - GetReportCountResponse - ); + let data = wsJsonToRes<GetReportCountResponse>(msg); this.setState({ unreadReportCount: data.post_reports + data.comment_reports + - data.private_message_reports.unwrapOr(0), + (data.private_message_reports ?? 0), }); this.sendReportUnread(); } else if (op == UserOperation.GetUnreadRegistrationApplicationCount) { - let data = wsJsonToRes<GetUnreadRegistrationApplicationCountResponse>( - msg, - GetUnreadRegistrationApplicationCountResponse - ); + let data = + wsJsonToRes<GetUnreadRegistrationApplicationCountResponse>(msg); this.setState({ unreadApplicationCount: data.registration_applications }); this.sendApplicationUnread(); } else if (op == UserOperation.CreateComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); - - UserService.Instance.myUserInfo.match({ - some: mui => { - if (data.recipient_ids.includes(mui.local_user_view.local_user.id)) { - this.setState({ - unreadInboxCount: this.state.unreadInboxCount + 1, - }); - this.sendUnreadCount(); - notifyComment(data.comment_view, this.context.router); - } - }, - none: void 0, - }); + let data = wsJsonToRes<CommentResponse>(msg); + let mui = UserService.Instance.myUserInfo; + if ( + mui && + data.recipient_ids.includes(mui.local_user_view.local_user.id) + ) { + this.setState({ + unreadInboxCount: this.state.unreadInboxCount + 1, + }); + this.sendUnreadCount(); + notifyComment(data.comment_view, this.context.router); + } } else if (op == UserOperation.CreatePrivateMessage) { - let data = wsJsonToRes<PrivateMessageResponse>( - msg, - PrivateMessageResponse - ); - - UserService.Instance.myUserInfo.match({ - some: mui => { - if ( - data.private_message_view.recipient.id == - mui.local_user_view.person.id - ) { - this.setState({ - unreadInboxCount: this.state.unreadInboxCount + 1, - }); - this.sendUnreadCount(); - notifyPrivateMessage( - data.private_message_view, - this.context.router - ); - } - }, - none: void 0, - }); + let data = wsJsonToRes<PrivateMessageResponse>(msg); + + if ( + data.private_message_view.recipient.id == + UserService.Instance.myUserInfo?.local_user_view.person.id + ) { + this.setState({ + unreadInboxCount: this.state.unreadInboxCount + 1, + }); + this.sendUnreadCount(); + notifyPrivateMessage(data.private_message_view, this.context.router); + } } } fetchUnreads() { console.log("Fetching inbox unreads..."); - let unreadForm = new GetUnreadCount({ - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm)); + let auth = myAuth(); + if (auth) { + let unreadForm: GetUnreadCount = { + auth, + }; + WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm)); - console.log("Fetching reports..."); + console.log("Fetching reports..."); - let reportCountForm = new GetReportCount({ - community_id: None, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.getReportCount(reportCountForm)); + let reportCountForm: GetReportCount = { + auth, + }; + WebSocketService.Instance.send(wsClient.getReportCount(reportCountForm)); - if (amAdmin()) { - console.log("Fetching applications..."); + if (amAdmin()) { + console.log("Fetching applications..."); - let applicationCountForm = new GetUnreadRegistrationApplicationCount({ - auth: auth().unwrap(), - }); - WebSocketService.Instance.send( - wsClient.getUnreadRegistrationApplicationCount(applicationCountForm) - ); + let applicationCountForm: GetUnreadRegistrationApplicationCount = { + auth, + }; + WebSocketService.Instance.send( + wsClient.getUnreadRegistrationApplicationCount(applicationCountForm) + ); + } } } @@ -703,7 +670,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> { } requestNotificationPermission() { - if (UserService.Instance.myUserInfo.isSome()) { + if (UserService.Instance.myUserInfo) { document.addEventListener("DOMContentLoaded", function () { if (!Notification) { toast(i18n.t("notifications_error"), "danger"); diff --git a/src/shared/components/app/theme.tsx b/src/shared/components/app/theme.tsx index 0f2a083..4510fe2 100644 --- a/src/shared/components/app/theme.tsx +++ b/src/shared/components/app/theme.tsx @@ -9,19 +9,15 @@ interface Props { export class Theme extends Component<Props> { render() { let user = UserService.Instance.myUserInfo; - let hasTheme = user - .map(m => m.local_user_view.local_user.theme !== "browser") - .unwrapOr(false); + let hasTheme = user?.local_user_view.local_user.theme !== "browser"; - if (hasTheme) { + if (user && hasTheme) { return ( <Helmet> <link rel="stylesheet" type="text/css" - href={`/css/themes/${ - user.unwrap().local_user_view.local_user.theme - }.css`} + href={`/css/themes/${user.local_user_view.local_user.theme}.css`} /> </Helmet> ); diff --git a/src/shared/components/comment/comment-form.tsx b/src/shared/components/comment/comment-form.tsx index a74cc8b..40650db 100644 --- a/src/shared/components/comment/comment-form.tsx +++ b/src/shared/components/comment/comment-form.tsx @@ -1,4 +1,3 @@ -import { Either, None, Option, Some } from "@sniptt/monads"; import { Component } from "inferno"; import { T } from "inferno-i18next-dess"; import { Link } from "inferno-router"; @@ -16,8 +15,8 @@ import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; import { - auth, capitalizeFirstLetter, + myAuth, myFirstDiscussionLanguageId, wsClient, wsSubscribe, @@ -29,7 +28,7 @@ interface CommentFormProps { /** * Can either be the parent, or the editable comment. The right side is a postId. */ - node: Either<CommentNodeI, number>; + node: CommentNodeI | number; edit?: boolean; disabled?: boolean; focus?: boolean; @@ -41,19 +40,19 @@ interface CommentFormProps { interface CommentFormState { buttonTitle: string; finished: boolean; - formId: Option<string>; + formId?: string; } export class CommentForm extends Component<CommentFormProps, CommentFormState> { - private subscription: Subscription; - private emptyState: CommentFormState = { - buttonTitle: this.props.node.isRight() - ? capitalizeFirstLetter(i18n.t("post")) - : this.props.edit - ? capitalizeFirstLetter(i18n.t("save")) - : capitalizeFirstLetter(i18n.t("reply")), + 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, - formId: None, }; constructor(props: any, context: any) { @@ -62,50 +61,46 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { this.handleCommentSubmit = this.handleCommentSubmit.bind(this); this.handleReplyCancel = this.handleReplyCancel.bind(this); - this.state = this.emptyState; - this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); } componentWillUnmount() { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } render() { - let initialContent = this.props.node.match({ - left: node => - this.props.edit ? Some(node.comment_view.comment.content) : None, - right: () => None, - }); - - let selectedLang = this.props.node - .left() - .map(n => n.comment_view.comment.language_id) - .or( - myFirstDiscussionLanguageId( - this.props.allLanguages, - this.props.siteLanguages, - UserService.Instance.myUserInfo - ) - ); + let initialContent = + typeof this.props.node !== "number" + ? this.props.edit + ? this.props.node.comment_view.comment.content + : undefined + : undefined; + + let selectedLang = + typeof this.props.node !== "number" + ? this.props.node.comment_view.comment.language_id + : myFirstDiscussionLanguageId( + this.props.allLanguages, + this.props.siteLanguages, + UserService.Instance.myUserInfo + ); return ( <div className="mb-3"> - {UserService.Instance.myUserInfo.isSome() ? ( + {UserService.Instance.myUserInfo ? ( <MarkdownTextArea initialContent={initialContent} initialLanguageId={selectedLang} showLanguage - buttonTitle={Some(this.state.buttonTitle)} - maxLength={None} + buttonTitle={this.state.buttonTitle} finished={this.state.finished} - replyType={this.props.node.isLeft()} + replyType={typeof this.props.node !== "number"} focus={this.props.focus} disabled={this.props.disabled} onSubmit={this.handleCommentSubmit} onReplyCancel={this.handleReplyCancel} - placeholder={Some(i18n.t("comment_here"))} + placeholder={i18n.t("comment_here")} allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} /> @@ -125,55 +120,55 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { } handleCommentSubmit(msg: { - val: Option<string>; + val: string; formId: string; - languageId: Option<number>; + languageId?: number; }) { let content = msg.val; let language_id = msg.languageId; - this.setState({ formId: Some(msg.formId) }); + let node = this.props.node; + + this.setState({ formId: msg.formId }); - this.props.node.match({ - left: node => { + let auth = myAuth(); + if (auth) { + if (typeof node === "number") { + let postId = node; + let form: CreateComment = { + content, + form_id: this.state.formId, + post_id: postId, + language_id, + auth, + }; + WebSocketService.Instance.send(wsClient.createComment(form)); + } else { if (this.props.edit) { - let form = new EditComment({ + let form: EditComment = { content, - distinguished: None, form_id: this.state.formId, comment_id: node.comment_view.comment.id, language_id, - auth: auth().unwrap(), - }); + auth, + }; WebSocketService.Instance.send(wsClient.editComment(form)); } else { - let form = new CreateComment({ - content: content.unwrap(), + let form: CreateComment = { + content, form_id: this.state.formId, post_id: node.comment_view.post.id, - parent_id: Some(node.comment_view.comment.id), + parent_id: node.comment_view.comment.id, language_id, - auth: auth().unwrap(), - }); + auth, + }; WebSocketService.Instance.send(wsClient.createComment(form)); } - }, - right: postId => { - let form = new CreateComment({ - content: content.unwrap(), - form_id: this.state.formId, - post_id: postId, - parent_id: None, - language_id, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.createComment(form)); - }, - }); - this.setState(this.state); + } + } } handleReplyCancel() { - this.props.onReplyCancel(); + this.props.onReplyCancel?.(); } parseMessage(msg: any) { @@ -181,15 +176,15 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { console.log(msg); // Only do the showing and hiding if logged in - if (UserService.Instance.myUserInfo.isSome()) { + if (UserService.Instance.myUserInfo) { if ( op == UserOperation.CreateComment || op == UserOperation.EditComment ) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); // This only finishes this form, if the randomly generated form_id matches the one received - if (this.state.formId.unwrapOr("") == data.form_id.unwrapOr("")) { + if (this.state.formId == data.form_id) { this.setState({ finished: true }); // Necessary because it broke tribute for some reason diff --git a/src/shared/components/comment/comment-node.tsx b/src/shared/components/comment/comment-node.tsx index e37c68e..50da456 100644 --- a/src/shared/components/comment/comment-node.tsx +++ b/src/shared/components/comment/comment-node.tsx @@ -1,4 +1,3 @@ -import { Left, None, Option, Some } from "@sniptt/monads"; import classNames from "classnames"; import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; @@ -27,7 +26,6 @@ import { PurgePerson, RemoveComment, SaveComment, - toUndefined, TransferCommunity, } from "lemmy-js-client"; import moment from "moment"; @@ -36,7 +34,6 @@ import { BanType, CommentViewType, PurgeType } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { amCommunityCreator, - auth, canAdmin, canMod, colorList, @@ -47,6 +44,7 @@ import { isMod, mdToHtml, mdToHtmlNoImages, + myAuth, numToSI, setupTippy, showScores, @@ -63,14 +61,14 @@ interface CommentNodeState { showReply: boolean; showEdit: boolean; showRemoveDialog: boolean; - removeReason: Option<string>; + removeReason?: string; showBanDialog: boolean; removeData: boolean; - banReason: Option<string>; - banExpireDays: Option<number>; + banReason?: string; + banExpireDays?: number; banType: BanType; showPurgeDialog: boolean; - purgeReason: Option<string>; + purgeReason?: string; purgeType: PurgeType; purgeLoading: boolean; showConfirmTransferSite: boolean; @@ -81,8 +79,8 @@ interface CommentNodeState { viewSource: boolean; showAdvanced: boolean; showReportDialog: boolean; - reportReason: string; - my_vote: Option<number>; + reportReason?: string; + my_vote?: number; score: number; upvotes: number; downvotes: number; @@ -92,8 +90,8 @@ interface CommentNodeState { interface CommentNodeProps { node: CommentNodeI; - moderators: Option<CommunityModeratorView[]>; - admins: Option<PersonViewSafe[]>; + moderators?: CommunityModeratorView[]; + admins?: PersonViewSafe[]; noBorder?: boolean; noIndent?: boolean; viewOnly?: boolean; @@ -101,7 +99,7 @@ interface CommentNodeProps { markable?: boolean; showContext?: boolean; showCommunity?: boolean; - enableDownvotes: boolean; + enableDownvotes?: boolean; viewType: CommentViewType; allLanguages: Language[]; siteLanguages: number[]; @@ -109,19 +107,15 @@ interface CommentNodeProps { } export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { - private emptyState: CommentNodeState = { + state: CommentNodeState = { showReply: false, showEdit: false, showRemoveDialog: false, - removeReason: None, showBanDialog: false, removeData: false, - banReason: None, - banExpireDays: None, banType: BanType.Community, showPurgeDialog: false, purgeLoading: false, - purgeReason: None, purgeType: PurgeType.Person, collapsed: false, viewSource: false, @@ -131,7 +125,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { showConfirmAppointAsMod: false, showConfirmAppointAsAdmin: false, showReportDialog: false, - reportReason: null, 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, @@ -143,7 +136,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleReplyCancel = this.handleReplyCancel.bind(this); this.handleCommentUpvote = this.handleCommentUpvote.bind(this); this.handleCommentDownvote = this.handleCommentDownvote.bind(this); @@ -166,37 +158,35 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { let node = this.props.node; let cv = this.props.node.comment_view; - let purgeTypeText: string; - if (this.state.purgeType == PurgeType.Comment) { - purgeTypeText = i18n.t("purge_comment"); - } else if (this.state.purgeType == PurgeType.Person) { - purgeTypeText = `${i18n.t("purge")} ${cv.creator.name}`; - } + let purgeTypeText = + this.state.purgeType == PurgeType.Comment + ? i18n.t("purge_comment") + : `${i18n.t("purge")} ${cv.creator.name}`; let canMod_ = canMod( + cv.creator.id, this.props.moderators, - this.props.admins, - cv.creator.id + this.props.admins ); let canModOnSelf = canMod( + cv.creator.id, this.props.moderators, this.props.admins, - cv.creator.id, UserService.Instance.myUserInfo, true ); - let canAdmin_ = canAdmin(this.props.admins, cv.creator.id); + let canAdmin_ = canAdmin(cv.creator.id, this.props.admins); let canAdminOnSelf = canAdmin( - this.props.admins, cv.creator.id, + this.props.admins, UserService.Instance.myUserInfo, true ); - let isMod_ = isMod(this.props.moderators, cv.creator.id); - let isAdmin_ = isAdmin(this.props.admins, cv.creator.id); + let isMod_ = isMod(cv.creator.id, this.props.moderators); + let isAdmin_ = isAdmin(cv.creator.id, this.props.admins); let amCommunityCreator_ = amCommunityCreator( - this.props.moderators, - cv.creator.id + cv.creator.id, + this.props.moderators ); let borderColor = this.props.node.depth @@ -227,15 +217,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { this.props.node.comment_view.comment.distinguished, })} style={ - !this.props.noIndent && - this.props.node.depth && - `border-left: 2px ${borderColor} solid !important` + !this.props.noIndent && this.props.node.depth + ? `border-left: 2px ${borderColor} solid !important` + : "" } > <div - className={`${ - !this.props.noIndent && this.props.node.depth && "ml-2" - }`} + className={classNames({ + "ml-2": !this.props.noIndent && this.props.node.depth, + })} > <div className="d-flex flex-wrap align-items-center text-muted small"> <span className="mr-2"> @@ -324,7 +314,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { {/* end of user row */} {this.state.showEdit && ( <CommentForm - node={Left(node)} + node={node} edit onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} @@ -376,461 +366,454 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { )} </button> )} - {UserService.Instance.myUserInfo.isSome() && - !this.props.viewOnly && ( - <> + {UserService.Instance.myUserInfo && !this.props.viewOnly && ( + <> + <button + className={`btn btn-link btn-animate ${ + this.state.my_vote == 1 ? "text-info" : "text-muted" + }`} + onClick={this.handleCommentUpvote} + data-tippy-content={i18n.t("upvote")} + aria-label={i18n.t("upvote")} + > + <Icon icon="arrow-up1" classes="icon-inline" /> + {showScores() && + this.state.upvotes !== this.state.score && ( + <span className="ml-1"> + {numToSI(this.state.upvotes)} + </span> + )} + </button> + {this.props.enableDownvotes && ( <button className={`btn btn-link btn-animate ${ - this.state.my_vote.unwrapOr(0) == 1 - ? "text-info" + this.state.my_vote == -1 + ? "text-danger" : "text-muted" }`} - onClick={this.handleCommentUpvote} - data-tippy-content={i18n.t("upvote")} - aria-label={i18n.t("upvote")} + onClick={this.handleCommentDownvote} + data-tippy-content={i18n.t("downvote")} + aria-label={i18n.t("downvote")} > - <Icon icon="arrow-up1" classes="icon-inline" /> + <Icon icon="arrow-down1" classes="icon-inline" /> {showScores() && this.state.upvotes !== this.state.score && ( <span className="ml-1"> - {numToSI(this.state.upvotes)} + {numToSI(this.state.downvotes)} </span> )} </button> - {this.props.enableDownvotes && ( - <button - className={`btn btn-link btn-animate ${ - this.state.my_vote.unwrapOr(0) == -1 - ? "text-danger" - : "text-muted" - }`} - onClick={this.handleCommentDownvote} - data-tippy-content={i18n.t("downvote")} - aria-label={i18n.t("downvote")} - > - <Icon icon="arrow-down1" classes="icon-inline" /> - {showScores() && - this.state.upvotes !== this.state.score && ( - <span className="ml-1"> - {numToSI(this.state.downvotes)} - </span> - )} - </button> - )} + )} + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent(this, this.handleReplyClick)} + data-tippy-content={i18n.t("reply")} + aria-label={i18n.t("reply")} + > + <Icon icon="reply1" classes="icon-inline" /> + </button> + {!this.state.showAdvanced ? ( <button className="btn btn-link btn-animate text-muted" - onClick={linkEvent(this, this.handleReplyClick)} - data-tippy-content={i18n.t("reply")} - aria-label={i18n.t("reply")} + onClick={linkEvent(this, this.handleShowAdvanced)} + data-tippy-content={i18n.t("more")} + aria-label={i18n.t("more")} > - <Icon icon="reply1" classes="icon-inline" /> + <Icon icon="more-vertical" classes="icon-inline" /> </button> - {!this.state.showAdvanced ? ( + ) : ( + <> + {!this.myComment && ( + <> + <button className="btn btn-link btn-animate"> + <Link + className="text-muted" + to={`/create_private_message/recipient/${cv.creator.id}`} + title={i18n.t("message").toLowerCase()} + > + <Icon icon="mail" /> + </Link> + </button> + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent( + this, + this.handleShowReportDialog + )} + data-tippy-content={i18n.t( + "show_report_dialog" + )} + aria-label={i18n.t("show_report_dialog")} + > + <Icon icon="flag" /> + </button> + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent( + this, + this.handleBlockUserClick + )} + data-tippy-content={i18n.t("block_user")} + aria-label={i18n.t("block_user")} + > + <Icon icon="slash" /> + </button> + </> + )} <button className="btn btn-link btn-animate text-muted" - onClick={linkEvent(this, this.handleShowAdvanced)} - data-tippy-content={i18n.t("more")} - aria-label={i18n.t("more")} + onClick={linkEvent( + this, + this.handleSaveCommentClick + )} + data-tippy-content={ + cv.saved ? i18n.t("unsave") : i18n.t("save") + } + aria-label={ + cv.saved ? i18n.t("unsave") : i18n.t("save") + } > - <Icon icon="more-vertical" classes="icon-inline" /> + {this.state.saveLoading ? ( + this.loadingIcon + ) : ( + <Icon + icon="star" + classes={`icon-inline ${ + cv.saved && "text-warning" + }`} + /> + )} </button> - ) : ( - <> - {!this.myComment && ( - <> - <button className="btn btn-link btn-animate"> - <Link - className="text-muted" - to={`/create_private_message/recipient/${cv.creator.id}`} - title={i18n.t("message").toLowerCase()} - > - <Icon icon="mail" /> - </Link> - </button> - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleShowReportDialog - )} - data-tippy-content={i18n.t( - "show_report_dialog" - )} - aria-label={i18n.t("show_report_dialog")} - > - <Icon icon="flag" /> - </button> + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent(this, this.handleViewSource)} + data-tippy-content={i18n.t("view_source")} + aria-label={i18n.t("view_source")} + > + <Icon + icon="file-text" + classes={`icon-inline ${ + this.state.viewSource && "text-success" + }`} + /> + </button> + {this.myComment && ( + <> + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent(this, this.handleEditClick)} + data-tippy-content={i18n.t("edit")} + aria-label={i18n.t("edit")} + > + <Icon icon="edit" classes="icon-inline" /> + </button> + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent( + this, + this.handleDeleteClick + )} + data-tippy-content={ + !cv.comment.deleted + ? i18n.t("delete") + : i18n.t("restore") + } + aria-label={ + !cv.comment.deleted + ? i18n.t("delete") + : i18n.t("restore") + } + > + <Icon + icon="trash" + classes={`icon-inline ${ + cv.comment.deleted && "text-danger" + }`} + /> + </button> + + {(canModOnSelf || canAdminOnSelf) && ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleBlockUserClick + this.handleDistinguishClick )} - data-tippy-content={i18n.t("block_user")} - aria-label={i18n.t("block_user")} + data-tippy-content={ + !cv.comment.distinguished + ? i18n.t("distinguish") + : i18n.t("undistinguish") + } + aria-label={ + !cv.comment.distinguished + ? i18n.t("distinguish") + : i18n.t("undistinguish") + } > - <Icon icon="slash" /> + <Icon + icon="shield" + classes={`icon-inline ${ + cv.comment.distinguished && "text-danger" + }`} + /> </button> - </> - )} - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleSaveCommentClick - )} - data-tippy-content={ - cv.saved ? i18n.t("unsave") : i18n.t("save") - } - aria-label={ - cv.saved ? i18n.t("unsave") : i18n.t("save") - } - > - {this.state.saveLoading ? ( - this.loadingIcon - ) : ( - <Icon - icon="star" - classes={`icon-inline ${ - cv.saved && "text-warning" - }`} - /> )} - </button> - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent(this, this.handleViewSource)} - data-tippy-content={i18n.t("view_source")} - aria-label={i18n.t("view_source")} - > - <Icon - icon="file-text" - classes={`icon-inline ${ - this.state.viewSource && "text-success" - }`} - /> - </button> - {this.myComment && ( - <> + </> + )} + {/* Admins and mods can remove comments */} + {(canMod_ || canAdmin_) && ( + <> + {!cv.comment.removed ? ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleEditClick + this.handleModRemoveShow )} - data-tippy-content={i18n.t("edit")} - aria-label={i18n.t("edit")} + aria-label={i18n.t("remove")} > - <Icon icon="edit" classes="icon-inline" /> + {i18n.t("remove")} </button> + ) : ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleDeleteClick + this.handleModRemoveSubmit )} - data-tippy-content={ - !cv.comment.deleted - ? i18n.t("delete") - : i18n.t("restore") - } - aria-label={ - !cv.comment.deleted - ? i18n.t("delete") - : i18n.t("restore") - } + aria-label={i18n.t("restore")} > - <Icon - icon="trash" - classes={`icon-inline ${ - cv.comment.deleted && "text-danger" - }`} - /> + {i18n.t("restore")} </button> - - {(canModOnSelf || canAdminOnSelf) && ( + )} + </> + )} + {/* Mods can ban from community, and appoint as mods to community */} + {canMod_ && ( + <> + {!isMod_ && + (!cv.creator_banned_from_community ? ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleDistinguishClick + this.handleModBanFromCommunityShow )} - data-tippy-content={ - !cv.comment.distinguished - ? i18n.t("distinguish") - : i18n.t("undistinguish") - } - aria-label={ - !cv.comment.distinguished - ? i18n.t("distinguish") - : i18n.t("undistinguish") - } + aria-label={i18n.t("ban")} > - <Icon - icon="shield" - classes={`icon-inline ${ - cv.comment.distinguished && - "text-danger" - }`} - /> + {i18n.t("ban")} </button> - )} - </> - )} - {/* Admins and mods can remove comments */} - {(canMod_ || canAdmin_) && ( - <> - {!cv.comment.removed ? ( + ) : ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleModRemoveShow + this.handleModBanFromCommunitySubmit )} - aria-label={i18n.t("remove")} + aria-label={i18n.t("unban")} > - {i18n.t("remove")} + {i18n.t("unban")} </button> - ) : ( + ))} + {!cv.creator_banned_from_community && + (!this.state.showConfirmAppointAsMod ? ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleModRemoveSubmit + this.handleShowConfirmAppointAsMod )} - aria-label={i18n.t("restore")} + aria-label={ + isMod_ + ? i18n.t("remove_as_mod") + : i18n.t("appoint_as_mod") + } > - {i18n.t("restore")} + {isMod_ + ? i18n.t("remove_as_mod") + : i18n.t("appoint_as_mod")} </button> - )} - </> - )} - {/* Mods can ban from community, and appoint as mods to community */} - {canMod_ && ( - <> - {!isMod_ && - (!cv.creator_banned_from_community ? ( + ) : ( + <> <button className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleModBanFromCommunityShow - )} - aria-label={i18n.t("ban")} + aria-label={i18n.t("are_you_sure")} > - {i18n.t("ban")} + {i18n.t("are_you_sure")} </button> - ) : ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleModBanFromCommunitySubmit + this.handleAddModToCommunity )} - aria-label={i18n.t("unban")} + aria-label={i18n.t("yes")} > - {i18n.t("unban")} + {i18n.t("yes")} </button> - ))} - {!cv.creator_banned_from_community && - (!this.state.showConfirmAppointAsMod ? ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleShowConfirmAppointAsMod + this.handleCancelConfirmAppointAsMod )} - aria-label={ - isMod_ - ? i18n.t("remove_as_mod") - : i18n.t("appoint_as_mod") - } + aria-label={i18n.t("no")} > - {isMod_ - ? i18n.t("remove_as_mod") - : i18n.t("appoint_as_mod")} + {i18n.t("no")} </button> - ) : ( - <> - <button - className="btn btn-link btn-animate text-muted" - aria-label={i18n.t("are_you_sure")} - > - {i18n.t("are_you_sure")} - </button> - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleAddModToCommunity - )} - aria-label={i18n.t("yes")} - > - {i18n.t("yes")} - </button> - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleCancelConfirmAppointAsMod - )} - aria-label={i18n.t("no")} - > - {i18n.t("no")} - </button> - </> - ))} - </> - )} - {/* Community creators and admins can transfer community to another mod */} - {(amCommunityCreator_ || canAdmin_) && - isMod_ && - cv.creator.local && - (!this.state.showConfirmTransferCommunity ? ( + </> + ))} + </> + )} + {/* Community creators and admins can transfer community to another mod */} + {(amCommunityCreator_ || canAdmin_) && + isMod_ && + cv.creator.local && + (!this.state.showConfirmTransferCommunity ? ( + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent( + this, + this.handleShowConfirmTransferCommunity + )} + aria-label={i18n.t("transfer_community")} + > + {i18n.t("transfer_community")} + </button> + ) : ( + <> + <button + className="btn btn-link btn-animate text-muted" + aria-label={i18n.t("are_you_sure")} + > + {i18n.t("are_you_sure")} + </button> <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleShowConfirmTransferCommunity + this.handleTransferCommunity )} - aria-label={i18n.t("transfer_community")} + aria-label={i18n.t("yes")} > - {i18n.t("transfer_community")} + {i18n.t("yes")} </button> - ) : ( + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent( + this, + this + .handleCancelShowConfirmTransferCommunity + )} + aria-label={i18n.t("no")} + > + {i18n.t("no")} + </button> + </> + ))} + {/* Admins can ban from all, and appoint other admins */} + {canAdmin_ && ( + <> + {!isAdmin_ && ( <> - <button - className="btn btn-link btn-animate text-muted" - aria-label={i18n.t("are_you_sure")} - > - {i18n.t("are_you_sure")} - </button> <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleTransferCommunity + this.handlePurgePersonShow )} - aria-label={i18n.t("yes")} + aria-label={i18n.t("purge_user")} > - {i18n.t("yes")} + {i18n.t("purge_user")} </button> <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this - .handleCancelShowConfirmTransferCommunity + this.handlePurgeCommentShow )} - aria-label={i18n.t("no")} + aria-label={i18n.t("purge_comment")} > - {i18n.t("no")} + {i18n.t("purge_comment")} </button> - </> - ))} - {/* Admins can ban from all, and appoint other admins */} - {canAdmin_ && ( - <> - {!isAdmin_ && ( - <> + + {!isBanned(cv.creator) ? ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handlePurgePersonShow + this.handleModBanShow )} - aria-label={i18n.t("purge_user")} + aria-label={i18n.t("ban_from_site")} > - {i18n.t("purge_user")} + {i18n.t("ban_from_site")} </button> + ) : ( <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handlePurgeCommentShow + this.handleModBanSubmit )} - aria-label={i18n.t("purge_comment")} + aria-label={i18n.t("unban_from_site")} > - {i18n.t("purge_comment")} + {i18n.t("unban_from_site")} </button> - - {!isBanned(cv.creator) ? ( - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleModBanShow - )} - aria-label={i18n.t("ban_from_site")} - > - {i18n.t("ban_from_site")} - </button> - ) : ( - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleModBanSubmit - )} - aria-label={i18n.t("unban_from_site")} - > - {i18n.t("unban_from_site")} - </button> + )} + </> + )} + {!isBanned(cv.creator) && + cv.creator.local && + (!this.state.showConfirmAppointAsAdmin ? ( + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent( + this, + this.handleShowConfirmAppointAsAdmin )} - </> - )} - {!isBanned(cv.creator) && - cv.creator.local && - (!this.state.showConfirmAppointAsAdmin ? ( + aria-label={ + isAdmin_ + ? i18n.t("remove_as_admin") + : i18n.t("appoint_as_admin") + } + > + {isAdmin_ + ? i18n.t("remove_as_admin") + : i18n.t("appoint_as_admin")} + </button> + ) : ( + <> + <button className="btn btn-link btn-animate text-muted"> + {i18n.t("are_you_sure")} + </button> <button className="btn btn-link btn-animate text-muted" onClick={linkEvent( this, - this.handleShowConfirmAppointAsAdmin + this.handleAddAdmin )} - aria-label={ - isAdmin_ - ? i18n.t("remove_as_admin") - : i18n.t("appoint_as_admin") - } + aria-label={i18n.t("yes")} > - {isAdmin_ - ? i18n.t("remove_as_admin") - : i18n.t("appoint_as_admin")} + {i18n.t("yes")} </button> - ) : ( - <> - <button className="btn btn-link btn-animate text-muted"> - {i18n.t("are_you_sure")} - </button> - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleAddAdmin - )} - aria-label={i18n.t("yes")} - > - {i18n.t("yes")} - </button> - <button - className="btn btn-link btn-animate text-muted" - onClick={linkEvent( - this, - this.handleCancelConfirmAppointAsAdmin - )} - aria-label={i18n.t("no")} - > - {i18n.t("no")} - </button> - </> - ))} - </> - )} - </> - )} - </> - )} + <button + className="btn btn-link btn-animate text-muted" + onClick={linkEvent( + this, + this.handleCancelConfirmAppointAsAdmin + )} + aria-label={i18n.t("no")} + > + {i18n.t("no")} + </button> + </> + ))} + </> + )} + </> + )} + </> + )} </div> {/* end of button group */} </div> @@ -873,7 +856,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { id={`mod-remove-reason-${cv.comment.id}`} className="form-control mr-2" placeholder={i18n.t("reason")} - value={toUndefined(this.state.removeReason)} + value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> <button @@ -928,7 +911,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { id={`mod-ban-reason-${cv.comment.id}`} className="form-control mr-2" placeholder={i18n.t("reason")} - value={toUndefined(this.state.banReason)} + value={this.state.banReason} onInput={linkEvent(this, this.handleModBanReasonChange)} /> <label @@ -942,7 +925,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { id={`mod-ban-expires-${cv.comment.id}`} className="form-control mr-2" placeholder={i18n.t("number_of_days")} - value={toUndefined(this.state.banExpireDays)} + value={this.state.banExpireDays} onInput={linkEvent(this, this.handleModBanExpireDaysChange)} /> <div className="form-group"> @@ -992,7 +975,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { id="purge-reason" className="form-control my-3" placeholder={i18n.t("reason")} - value={toUndefined(this.state.purgeReason)} + value={this.state.purgeReason} onInput={linkEvent(this, this.handlePurgeReasonChange)} /> <div className="form-group row col-12"> @@ -1012,7 +995,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { )} {this.state.showReply && ( <CommentForm - node={Left(node)} + node={node} onReplyCancel={this.handleReplyCancel} disabled={this.props.locked} focus @@ -1026,7 +1009,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { locked={this.props.locked} moderators={this.props.moderators} admins={this.props.admins} - maxCommentsShown={None} enableDownvotes={this.props.enableDownvotes} viewType={this.props.viewType} allLanguages={this.props.allLanguages} @@ -1085,12 +1067,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } get myComment(): boolean { - return UserService.Instance.myUserInfo - .map( - m => - m.local_user_view.person.id == this.props.node.comment_view.creator.id - ) - .unwrapOr(false); + return ( + UserService.Instance.myUserInfo?.local_user_view.person.id == + this.props.node.comment_view.creator.id + ); } get isPostCreator(): boolean { @@ -1118,36 +1098,45 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } handleBlockUserClick(i: CommentNode) { - let blockUserForm = new BlockPerson({ - person_id: i.props.node.comment_view.creator.id, - block: true, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + let auth = myAuth(); + if (auth) { + let blockUserForm: BlockPerson = { + person_id: i.props.node.comment_view.creator.id, + block: true, + auth, + }; + WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + } } handleDeleteClick(i: CommentNode) { let comment = i.props.node.comment_view.comment; - let deleteForm = new DeleteComment({ - comment_id: comment.id, - deleted: !comment.deleted, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.deleteComment(deleteForm)); + let auth = myAuth(); + if (auth) { + let deleteForm: DeleteComment = { + comment_id: comment.id, + deleted: !comment.deleted, + auth, + }; + WebSocketService.Instance.send(wsClient.deleteComment(deleteForm)); + } } handleSaveCommentClick(i: CommentNode) { let cv = i.props.node.comment_view; let save = cv.saved == undefined ? true : !cv.saved; - let form = new SaveComment({ - comment_id: cv.comment.id, - save, - auth: auth().unwrap(), - }); + let auth = myAuth(); + if (auth) { + let form: SaveComment = { + comment_id: cv.comment.id, + save, + auth, + }; - WebSocketService.Instance.send(wsClient.saveComment(form)); + WebSocketService.Instance.send(wsClient.saveComment(form)); - i.setState({ saveLoading: true }); + i.setState({ saveLoading: true }); + } } handleReplyCancel() { @@ -1156,7 +1145,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { handleCommentUpvote(event: any) { event.preventDefault(); - let myVote = this.state.my_vote.unwrapOr(0); + let myVote = this.state.my_vote; let newVote = myVote == 1 ? 0 : 1; if (myVote == 1) { @@ -1177,20 +1166,23 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { }); } - this.setState({ my_vote: Some(newVote) }); - - let form = new CreateCommentLike({ - comment_id: this.props.node.comment_view.comment.id, - score: newVote, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.likeComment(form)); - setupTippy(); + this.setState({ my_vote: newVote }); + + let auth = myAuth(); + if (auth) { + let 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(); - let myVote = this.state.my_vote.unwrapOr(0); + let myVote = this.state.my_vote; let newVote = myVote == -1 ? 0 : -1; if (myVote == 1) { @@ -1211,16 +1203,19 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { }); } - this.setState({ my_vote: Some(newVote) }); + this.setState({ my_vote: newVote }); - let form = new CreateCommentLike({ - comment_id: this.props.node.comment_view.comment.id, - score: newVote, - auth: auth().unwrap(), - }); + let auth = myAuth(); + if (auth) { + let form: CreateCommentLike = { + comment_id: this.props.node.comment_view.comment.id, + score: newVote, + auth, + }; - WebSocketService.Instance.send(wsClient.likeComment(form)); - setupTippy(); + WebSocketService.Instance.send(wsClient.likeComment(form)); + setupTippy(); + } } handleShowReportDialog(i: CommentNode) { @@ -1233,14 +1228,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { handleReportSubmit(i: CommentNode) { let comment = i.props.node.comment_view.comment; - let form = new CreateCommentReport({ - comment_id: comment.id, - reason: i.state.reportReason, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.createCommentReport(form)); - - i.setState({ showReportDialog: false }); + let reason = i.state.reportReason; + let auth = myAuth(); + if (reason && auth) { + let form: CreateCommentReport = { + comment_id: comment.id, + reason, + auth, + }; + WebSocketService.Instance.send(wsClient.createCommentReport(form)); + i.setState({ showReportDialog: false }); + } } handleModRemoveShow(i: CommentNode) { @@ -1251,7 +1249,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } handleModRemoveReasonChange(i: CommentNode, event: any) { - i.setState({ removeReason: Some(event.target.value) }); + i.setState({ removeReason: event.target.value }); } handleModRemoveDataChange(i: CommentNode, event: any) { @@ -1260,29 +1258,32 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { handleModRemoveSubmit(i: CommentNode) { let comment = i.props.node.comment_view.comment; - let form = new RemoveComment({ - comment_id: comment.id, - removed: !comment.removed, - reason: i.state.removeReason, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.removeComment(form)); - - i.setState({ showRemoveDialog: false }); + let auth = myAuth(); + if (auth) { + let 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) { let comment = i.props.node.comment_view.comment; - let form = new EditComment({ - comment_id: comment.id, - form_id: None, // TODO not sure about this - content: None, - distinguished: Some(!comment.distinguished), - language_id: Some(comment.language_id), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.editComment(form)); - i.setState(i.state); + let auth = myAuth(); + if (auth) { + let form: EditComment = { + comment_id: comment.id, + distinguished: !comment.distinguished, + auth, + }; + WebSocketService.Instance.send(wsClient.editComment(form)); + i.setState(i.state); + } } isPersonMentionType( @@ -1298,23 +1299,26 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } handleMarkRead(i: CommentNode) { - if (i.isPersonMentionType(i.props.node.comment_view)) { - let form = new MarkPersonMentionAsRead({ - person_mention_id: i.props.node.comment_view.person_mention.id, - read: !i.props.node.comment_view.person_mention.read, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form)); - } else if (i.isCommentReplyType(i.props.node.comment_view)) { - let form = new MarkCommentReplyAsRead({ - comment_reply_id: i.props.node.comment_view.comment_reply.id, - read: !i.props.node.comment_view.comment_reply.read, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.markCommentReplyAsRead(form)); - } + let auth = myAuth(); + if (auth) { + if (i.isPersonMentionType(i.props.node.comment_view)) { + let 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)) { + let 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 }); + i.setState({ readLoading: true }); + } } handleModBanFromCommunityShow(i: CommentNode) { @@ -1334,11 +1338,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } handleModBanReasonChange(i: CommentNode, event: any) { - i.setState({ banReason: Some(event.target.value) }); + i.setState({ banReason: event.target.value }); } handleModBanExpireDaysChange(i: CommentNode, event: any) { - i.setState({ banExpireDays: Some(event.target.value) }); + i.setState({ banExpireDays: event.target.value }); } handleModBanFromCommunitySubmit(i: CommentNode) { @@ -1353,41 +1357,43 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { handleModBanBothSubmit(i: CommentNode) { let cv = i.props.node.comment_view; - - if (i.state.banType == BanType.Community) { - // If its an unban, restore all their data - let ban = !cv.creator_banned_from_community; - if (ban == false) { - i.setState({ removeData: false }); - } - let form = new BanFromCommunity({ - person_id: cv.creator.id, - community_id: cv.community.id, - ban, - remove_data: Some(i.state.removeData), - reason: i.state.banReason, - expires: i.state.banExpireDays.map(futureDaysToUnixTime), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.banFromCommunity(form)); - } else { - // If its an unban, restore all their data - let ban = !cv.creator.banned; - if (ban == false) { - i.setState({ removeData: false }); + let auth = myAuth(); + if (auth) { + if (i.state.banType == BanType.Community) { + // If its an unban, restore all their data + let ban = !cv.creator_banned_from_community; + if (ban == false) { + i.setState({ removeData: false }); + } + let 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 + let ban = !cv.creator.banned; + if (ban == false) { + i.setState({ removeData: false }); + } + let 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)); } - let form = new BanPerson({ - person_id: cv.creator.id, - ban, - remove_data: Some(i.state.removeData), - reason: i.state.banReason, - expires: i.state.banExpireDays.map(futureDaysToUnixTime), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.banPerson(form)); - } - i.setState({ showBanDialog: false }); + i.setState({ showBanDialog: false }); + } } handlePurgePersonShow(i: CommentNode) { @@ -1407,29 +1413,31 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } handlePurgeReasonChange(i: CommentNode, event: any) { - i.setState({ purgeReason: Some(event.target.value) }); + i.setState({ purgeReason: event.target.value }); } handlePurgeSubmit(i: CommentNode, event: any) { event.preventDefault(); + let auth = myAuth(); + if (auth) { + if (i.state.purgeType == PurgeType.Person) { + let 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) { + let form: PurgeComment = { + comment_id: i.props.node.comment_view.comment.id, + reason: i.state.purgeReason, + auth, + }; + WebSocketService.Instance.send(wsClient.purgeComment(form)); + } - if (i.state.purgeType == PurgeType.Person) { - let form = new PurgePerson({ - person_id: i.props.node.comment_view.creator.id, - reason: i.state.purgeReason, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.purgePerson(form)); - } else if (i.state.purgeType == PurgeType.Comment) { - let form = new PurgeComment({ - comment_id: i.props.node.comment_view.comment.id, - reason: i.state.purgeReason, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.purgeComment(form)); + i.setState({ purgeLoading: true }); } - - i.setState({ purgeLoading: true }); } handleShowConfirmAppointAsMod(i: CommentNode) { @@ -1442,14 +1450,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { handleAddModToCommunity(i: CommentNode) { let cv = i.props.node.comment_view; - let form = new AddModToCommunity({ - person_id: cv.creator.id, - community_id: cv.community.id, - added: !isMod(i.props.moderators, cv.creator.id), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.addModToCommunity(form)); - i.setState({ showConfirmAppointAsMod: false }); + let auth = myAuth(); + if (auth) { + let 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) { @@ -1461,14 +1472,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } handleAddAdmin(i: CommentNode) { - let creatorId = i.props.node.comment_view.creator.id; - let form = new AddAdmin({ - person_id: creatorId, - added: !isAdmin(i.props.admins, creatorId), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.addAdmin(form)); - i.setState({ showConfirmAppointAsAdmin: false }); + let auth = myAuth(); + if (auth) { + let creatorId = i.props.node.comment_view.creator.id; + let 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) { @@ -1481,13 +1495,16 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { handleTransferCommunity(i: CommentNode) { let cv = i.props.node.comment_view; - let form = new TransferCommunity({ - community_id: cv.community.id, - person_id: cv.creator.id, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.transferCommunity(form)); - i.setState({ showConfirmTransferCommunity: false }); + let auth = myAuth(); + if (auth) { + let 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) { @@ -1519,27 +1536,23 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> { } handleFetchChildren(i: CommentNode) { - let form = new GetComments({ - post_id: Some(i.props.node.comment_view.post.id), - parent_id: Some(i.props.node.comment_view.comment.id), - max_depth: Some(commentTreeMaxDepth), - page: None, - sort: None, - limit: Some(999), - type_: Some(ListingType.All), - community_name: None, - community_id: None, - saved_only: Some(false), - auth: auth(false).ok(), - }); + let 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_: ListingType.All, + saved_only: false, + auth: myAuth(false), + }; WebSocketService.Instance.send(wsClient.getComments(form)); } get scoreColor() { - if (this.state.my_vote.unwrapOr(0) == 1) { + if (this.state.my_vote == 1) { return "text-info"; - } else if (this.state.my_vote.unwrapOr(0) == -1) { + } else if (this.state.my_vote == -1) { return "text-danger"; } else { return "text-muted"; diff --git a/src/shared/components/comment/comment-nodes.tsx b/src/shared/components/comment/comment-nodes.tsx index 9072e72..a053f99 100644 --- a/src/shared/components/comment/comment-nodes.tsx +++ b/src/shared/components/comment/comment-nodes.tsx @@ -1,4 +1,3 @@ -import { Option } from "@sniptt/monads"; import { Component } from "inferno"; import { CommentNode as CommentNodeI, @@ -11,9 +10,9 @@ import { CommentNode } from "./comment-node"; interface CommentNodesProps { nodes: CommentNodeI[]; - moderators: Option<CommunityModeratorView[]>; - admins: Option<PersonViewSafe[]>; - maxCommentsShown: Option<number>; + moderators?: CommunityModeratorView[]; + admins?: PersonViewSafe[]; + maxCommentsShown?: number; noBorder?: boolean; noIndent?: boolean; viewOnly?: boolean; @@ -34,9 +33,7 @@ export class CommentNodes extends Component<CommentNodesProps, any> { } render() { - let maxComments = this.props.maxCommentsShown.unwrapOr( - this.props.nodes.length - ); + let maxComments = this.props.maxCommentsShown ?? this.props.nodes.length; return ( <div className="comments"> diff --git a/src/shared/components/comment/comment-report.tsx b/src/shared/components/comment/comment-report.tsx index b90eae4..5f7c24c 100644 --- a/src/shared/components/comment/comment-report.tsx +++ b/src/shared/components/comment/comment-report.tsx @@ -1,4 +1,3 @@ -import { None } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { @@ -11,7 +10,7 @@ import { import { i18n } from "../../i18next"; import { CommentViewType } from "../../interfaces"; import { WebSocketService } from "../../services"; -import { auth, wsClient } from "../../utils"; +import { myAuth, wsClient } from "../../utils"; import { Icon } from "../common/icon"; import { PersonListing } from "../person/person-listing"; import { CommentNode } from "./comment-node"; @@ -59,8 +58,6 @@ export class CommentReport extends Component<CommentReportProps, any> { <CommentNode node={node} viewType={CommentViewType.Flat} - moderators={None} - admins={None} enableDownvotes={true} viewOnly={true} showCommunity={true} @@ -74,24 +71,21 @@ export class CommentReport extends Component<CommentReportProps, any> { <div> {i18n.t("reason")}: {r.comment_report.reason} </div> - {r.resolver.match({ - some: resolver => ( - <div> - {r.comment_report.resolved ? ( - <T i18nKey="resolved_by"> - # - <PersonListing person={resolver} /> - </T> - ) : ( - <T i18nKey="unresolved_by"> - # - <PersonListing person={resolver} /> - </T> - )} - </div> - ), - none: <></>, - })} + {r.resolver && ( + <div> + {r.comment_report.resolved ? ( + <T i18nKey="resolved_by"> + # + <PersonListing person={r.resolver} /> + </T> + ) : ( + <T i18nKey="unresolved_by"> + # + <PersonListing person={r.resolver} /> + </T> + )} + </div> + )} <button className="btn btn-link btn-animate text-muted py-0" onClick={linkEvent(this, this.handleResolveReport)} @@ -110,11 +104,14 @@ export class CommentReport extends Component<CommentReportProps, any> { } handleResolveReport(i: CommentReport) { - let form = new ResolveCommentReport({ - report_id: i.props.report.comment_report.id, - resolved: !i.props.report.comment_report.resolved, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.resolveCommentReport(form)); + let auth = myAuth(); + if (auth) { + let form: ResolveCommentReport = { + report_id: i.props.report.comment_report.id, + resolved: !i.props.report.comment_report.resolved, + auth, + }; + WebSocketService.Instance.send(wsClient.resolveCommentReport(form)); + } } } diff --git a/src/shared/components/common/banner-icon-header.tsx b/src/shared/components/common/banner-icon-header.tsx index a72400a..e290145 100644 --- a/src/shared/components/common/banner-icon-header.tsx +++ b/src/shared/components/common/banner-icon-header.tsx @@ -1,10 +1,9 @@ -import { Option } from "@sniptt/monads"; import { Component } from "inferno"; import { PictrsImage } from "./pictrs-image"; interface BannerIconHeaderProps { - banner: Option<string>; - icon: Option<string>; + banner?: string; + icon?: string; } export class BannerIconHeader extends Component<BannerIconHeaderProps, any> { @@ -13,23 +12,19 @@ export class BannerIconHeader extends Component<BannerIconHeaderProps, any> { } render() { + let banner = this.props.banner; + let icon = this.props.icon; return ( <div className="position-relative mb-2"> - {this.props.banner.match({ - some: banner => <PictrsImage src={banner} banner alt="" />, - none: <></>, - })} - {this.props.icon.match({ - some: icon => ( - <PictrsImage - src={icon} - iconOverlay - pushup={this.props.banner.isSome()} - alt="" - /> - ), - none: <></>, - })} + {banner && <PictrsImage src={banner} banner alt="" />} + {icon && ( + <PictrsImage + src={icon} + iconOverlay + pushup={!!this.props.banner} + alt="" + /> + )} </div> ); } diff --git a/src/shared/components/common/comment-sort-select.tsx b/src/shared/components/common/comment-sort-select.tsx index 0e49d56..1874776 100644 --- a/src/shared/components/common/comment-sort-select.tsx +++ b/src/shared/components/common/comment-sort-select.tsx @@ -18,13 +18,12 @@ export class CommentSortSelect extends Component< CommentSortSelectState > { private id = `sort-select-${randomStr()}`; - private emptyState: CommentSortSelectState = { + state: CommentSortSelectState = { sort: this.props.sort, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; } static getDerivedStateFromProps(props: any): CommentSortSelectState { @@ -65,6 +64,6 @@ export class CommentSortSelect extends Component< } handleSortChange(i: CommentSortSelect, event: any) { - i.props.onChange(event.target.value); + i.props.onChange?.(event.target.value); } } diff --git a/src/shared/components/common/data-type-select.tsx b/src/shared/components/common/data-type-select.tsx index 8a5613e..59c07d7 100644 --- a/src/shared/components/common/data-type-select.tsx +++ b/src/shared/components/common/data-type-select.tsx @@ -15,13 +15,12 @@ export class DataTypeSelect extends Component< DataTypeSelectProps, DataTypeSelectState > { - private emptyState: DataTypeSelectState = { + state: DataTypeSelectState = { type_: this.props.type_, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; } static getDerivedStateFromProps(props: any): DataTypeSelectProps { @@ -64,6 +63,6 @@ export class DataTypeSelect extends Component< } handleTypeChange(i: DataTypeSelect, event: any) { - i.props.onChange(Number(event.target.value)); + i.props.onChange?.(Number(event.target.value)); } } diff --git a/src/shared/components/common/html-tags.tsx b/src/shared/components/common/html-tags.tsx index 8f35b12..67abe3a 100644 --- a/src/shared/components/common/html-tags.tsx +++ b/src/shared/components/common/html-tags.tsx @@ -1,4 +1,3 @@ -import { Option } from "@sniptt/monads"; import { htmlToText } from "html-to-text"; import { Component } from "inferno"; import { Helmet } from "inferno-helmet"; @@ -8,14 +7,16 @@ import { md } from "../../utils"; interface HtmlTagsProps { title: string; path: string; - description: Option<string>; - image: Option<string>; + description?: string; + image?: string; } /// Taken from https://metatags.io/ export class HtmlTags extends Component<HtmlTagsProps, any> { render() { let url = httpExternalPath(this.props.path); + let desc = this.props.description; + let image = this.props.image; return ( <Helmet title={this.props.title}> @@ -33,21 +34,19 @@ export class HtmlTags extends Component<HtmlTagsProps, any> { <meta property="twitter:card" content="summary_large_image" /> {/* Optional desc and images */} - {this.props.description.isSome() && - ["description", "og:description", "twitter:description"].map(n => ( - <meta - key={n} - name={n} - content={htmlToText( - md.renderInline(this.props.description.unwrap()) - )} - /> - ))} - - {this.props.image.isSome() && - ["og:image", "twitter:image"].map(p => ( - <meta key={p} property={p} content={this.props.image.unwrap()} /> - ))} + {["description", "og:description", "twitter:description"].map( + n => + desc && ( + <meta + key={n} + name={n} + content={htmlToText(md.renderInline(desc))} + /> + ) + )} + {["og:image", "twitter:image"].map( + p => image && <meta key={p} property={p} content={image} /> + )} </Helmet> ); } diff --git a/src/shared/components/common/image-upload-form.tsx b/src/shared/components/common/image-upload-form.tsx index 432521e..6cf4e14 100644 --- a/src/shared/components/common/image-upload-form.tsx +++ b/src/shared/components/common/image-upload-form.tsx @@ -1,4 +1,3 @@ -import { Option } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { pictrsUri } from "../../env"; import { i18n } from "../../i18next"; @@ -8,7 +7,7 @@ import { Icon } from "./icon"; interface ImageUploadFormProps { uploadTitle: string; - imageSrc: Option<string>; + imageSrc?: string; onUpload(url: string): any; onRemove(): any; rounded?: boolean; @@ -39,31 +38,26 @@ export class ImageUploadForm extends Component< htmlFor={this.id} className="pointer text-muted small font-weight-bold" > - {this.props.imageSrc.match({ - some: imageSrc => ( - <span className="d-inline-block position-relative"> - <img - src={imageSrc} - height={this.props.rounded ? 60 : ""} - width={this.props.rounded ? 60 : ""} - className={`img-fluid ${ - this.props.rounded ? "rounded-circle" : "" - }`} - /> - <a - onClick={linkEvent(this, this.handleRemoveImage)} - aria-label={i18n.t("remove")} - > - <Icon icon="x" classes="mini-overlay" /> - </a> - </span> - ), - none: ( - <span className="btn btn-secondary"> - {this.props.uploadTitle} - </span> - ), - })} + {this.props.imageSrc ? ( + <span className="d-inline-block position-relative"> + <img + src={this.props.imageSrc} + height={this.props.rounded ? 60 : ""} + width={this.props.rounded ? 60 : ""} + className={`img-fluid ${ + this.props.rounded ? "rounded-circle" : "" + }`} + /> + <a + onClick={linkEvent(this, this.handleRemoveImage)} + aria-label={i18n.t("remove")} + > + <Icon icon="x" classes="mini-overlay" /> + </a> + </span> + ) : ( + <span className="btn btn-secondary">{this.props.uploadTitle}</span> + )} </label> <input id={this.id} @@ -71,7 +65,7 @@ export class ImageUploadForm extends Component< accept="image/*,video/*" name={this.id} className="d-none" - disabled={UserService.Instance.myUserInfo.isNone()} + disabled={!UserService.Instance.myUserInfo} onChange={linkEvent(this, this.handleImageUpload)} /> </form> diff --git a/src/shared/components/common/language-select.tsx b/src/shared/components/common/language-select.tsx index c7da2f0..1182531 100644 --- a/src/shared/components/common/language-select.tsx +++ b/src/shared/components/common/language-select.tsx @@ -1,4 +1,3 @@ -import { Option } from "@sniptt/monads"; import classNames from "classnames"; import { Component, linkEvent } from "inferno"; import { Language } from "lemmy-js-client"; @@ -10,7 +9,7 @@ import { Icon } from "./icon"; interface LanguageSelectProps { allLanguages: Language[]; siteLanguages: number[]; - selectedLanguageIds: Option<number[]>; + selectedLanguageIds?: number[]; multiple: boolean; onChange(val: number[]): any; showAll?: boolean; @@ -31,19 +30,17 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> { // Necessary because there is no HTML way to set selected for multiple in value= setSelectedValues() { - this.props.selectedLanguageIds.map(toString).match({ - some: ids => { - var select = (document.getElementById(this.id) as HTMLSelectElement) - .options; - for (let i = 0; i < select.length; i++) { - let o = select[i]; - if (ids.includes(o.value)) { - o.selected = true; - } + let ids = this.props.selectedLanguageIds?.map(toString); + if (ids) { + let select = (document.getElementById(this.id) as HTMLSelectElement) + .options; + for (let i = 0; i < select.length; i++) { + let o = select[i]; + if (ids.includes(o.value)) { + o.selected = true; } - }, - none: void 0, - }); + } + } } render() { @@ -107,7 +104,7 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> { <option key={l.id} value={l.id} - selected={selectedLangs.unwrapOr([]).includes(l.id)} + selected={selectedLangs?.includes(l.id)} > {l.name} </option> diff --git a/src/shared/components/common/listing-type-select.tsx b/src/shared/components/common/listing-type-select.tsx index 38e6acd..c214d44 100644 --- a/src/shared/components/common/listing-type-select.tsx +++ b/src/shared/components/common/listing-type-select.tsx @@ -21,13 +21,12 @@ export class ListingTypeSelect extends Component< > { private id = `listing-type-input-${randomStr()}`; - private emptyState: ListingTypeSelectState = { + state: ListingTypeSelectState = { type_: this.props.type_, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; } static getDerivedStateFromProps(props: any): ListingTypeSelectProps { @@ -46,7 +45,7 @@ export class ListingTypeSelect extends Component< title={i18n.t("subscribed_description")} className={`btn btn-outline-secondary ${this.state.type_ == ListingType.Subscribed && "active"} - ${UserService.Instance.myUserInfo.isNone() ? "disabled" : "pointer"} + ${!UserService.Instance.myUserInfo ? "disabled" : "pointer"} `} > <input @@ -55,7 +54,7 @@ export class ListingTypeSelect extends Component< value={ListingType.Subscribed} checked={this.state.type_ == ListingType.Subscribed} onChange={linkEvent(this, this.handleTypeChange)} - disabled={UserService.Instance.myUserInfo.isNone()} + disabled={!UserService.Instance.myUserInfo} /> {i18n.t("subscribed")} </label> @@ -100,6 +99,6 @@ export class ListingTypeSelect extends Component< } handleTypeChange(i: ListingTypeSelect, event: any) { - i.props.onChange(event.target.value); + i.props.onChange?.(event.target.value); } } diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index 6b517d7..9b51a7e 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -1,8 +1,7 @@ -import { None, Option, Some } from "@sniptt/monads"; import autosize from "autosize"; import { Component, linkEvent } from "inferno"; import { Prompt } from "inferno-router"; -import { Language, toUndefined } from "lemmy-js-client"; +import { Language } from "lemmy-js-client"; import { pictrsUri } from "../../env"; import { i18n } from "../../i18next"; import { UserService } from "../../services"; @@ -22,11 +21,11 @@ import { Icon, Spinner } from "./icon"; import { LanguageSelect } from "./language-select"; interface MarkdownTextAreaProps { - initialContent: Option<string>; - initialLanguageId: Option<number>; - placeholder: Option<string>; - buttonTitle: Option<string>; - maxLength: Option<number>; + initialContent?: string; + initialLanguageId?: number; + placeholder?: string; + buttonTitle?: string; + maxLength?: number; replyType?: boolean; focus?: boolean; disabled?: boolean; @@ -35,18 +34,14 @@ interface MarkdownTextAreaProps { hideNavigationWarnings?: boolean; onContentChange?(val: string): any; onReplyCancel?(): any; - onSubmit?(msg: { - val: Option<string>; - formId: string; - languageId: Option<number>; - }): any; - allLanguages: Language[]; - siteLanguages: number[]; + onSubmit?(msg: { val?: string; formId: string; languageId?: number }): any; + allLanguages: Language[]; // TODO should probably be nullable + siteLanguages: number[]; // TODO same } interface MarkdownTextAreaState { - content: Option<string>; - languageId: Option<number>; + content?: string; + languageId?: number; previewMode: boolean; loading: boolean; imageLoading: boolean; @@ -59,7 +54,7 @@ export class MarkdownTextArea extends Component< private id = `comment-textarea-${randomStr()}`; private formId = `comment-form-${randomStr()}`; private tribute: any; - private emptyState: MarkdownTextAreaState = { + state: MarkdownTextAreaState = { content: this.props.initialContent, languageId: this.props.initialLanguageId, previewMode: false, @@ -75,7 +70,6 @@ export class MarkdownTextArea extends Component< if (isBrowser()) { this.tribute = setupTribute(); } - this.state = this.emptyState; } componentDidMount() { @@ -84,7 +78,7 @@ export class MarkdownTextArea extends Component< autosize(textarea); this.tribute.attach(textarea); textarea.addEventListener("tribute-replaced", () => { - this.setState({ content: Some(textarea.value) }); + this.setState({ content: textarea.value }); autosize.update(textarea); }); @@ -100,18 +94,18 @@ export class MarkdownTextArea extends Component< } componentDidUpdate() { - if (!this.props.hideNavigationWarnings && this.state.content.isSome()) { + if (!this.props.hideNavigationWarnings && this.state.content) { window.onbeforeunload = () => true; } else { - window.onbeforeunload = undefined; + window.onbeforeunload = null; } } componentWillReceiveProps(nextProps: MarkdownTextAreaProps) { if (nextProps.finished) { - this.setState({ previewMode: false, loading: false, content: None }); + this.setState({ previewMode: false, loading: false, content: undefined }); if (this.props.replyType) { - this.props.onReplyCancel(); + this.props.onReplyCancel?.(); } let textarea: any = document.getElementById(this.id); @@ -126,12 +120,12 @@ export class MarkdownTextArea extends Component< } render() { + let languageId = this.state.languageId; + return ( <form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}> <Prompt - when={ - !this.props.hideNavigationWarnings && this.state.content.isSome() - } + when={!this.props.hideNavigationWarnings && this.state.content} message={i18n.t("block_leaving")} /> <div className="form-group row"> @@ -139,27 +133,21 @@ export class MarkdownTextArea extends Component< <textarea id={this.id} className={`form-control ${this.state.previewMode && "d-none"}`} - value={toUndefined(this.state.content)} + value={this.state.content} onInput={linkEvent(this, this.handleContentChange)} onPaste={linkEvent(this, this.handleImageUploadPaste)} required disabled={this.props.disabled} rows={2} - maxLength={this.props.maxLength.unwrapOr( - markdownFieldCharacterLimit - )} - placeholder={toUndefined(this.props.placeholder)} + maxLength={this.props.maxLength ?? markdownFieldCharacterLimit} + placeholder={this.props.placeholder} /> - {this.state.previewMode && - this.state.content.match({ - some: content => ( - <div - className="card border-secondary card-body md-div" - dangerouslySetInnerHTML={mdToHtml(content)} - /> - ), - none: <></>, - })} + {this.state.previewMode && this.state.content && ( + <div + className="card border-secondary card-body md-div" + dangerouslySetInnerHTML={mdToHtml(this.state.content)} + /> + )} </div> <label className="sr-only" htmlFor={this.id}> {i18n.t("body")} @@ -167,22 +155,19 @@ export class MarkdownTextArea extends Component< </div> <div className="row"> <div className="col-sm-12 d-flex flex-wrap"> - {this.props.buttonTitle.match({ - some: buttonTitle => ( - <button - type="submit" - className="btn btn-sm btn-secondary mr-2" - disabled={this.props.disabled || this.state.loading} - > - {this.state.loading ? ( - <Spinner /> - ) : ( - <span>{buttonTitle}</span> - )} - </button> - ), - none: <></>, - })} + {this.props.buttonTitle && ( + <button + type="submit" + className="btn btn-sm btn-secondary mr-2" + disabled={this.props.disabled || this.state.loading} + > + {this.state.loading ? ( + <Spinner /> + ) : ( + <span>{this.props.buttonTitle}</span> + )} + </button> + )} {this.props.replyType && ( <button type="button" @@ -192,7 +177,7 @@ export class MarkdownTextArea extends Component< {i18n.t("cancel")} </button> )} - {this.state.content.isSome() && ( + {this.state.content && ( <button className={`btn btn-sm btn-secondary mr-2 ${ this.state.previewMode && "active" @@ -209,7 +194,9 @@ export class MarkdownTextArea extends Component< <LanguageSelect iconVersion allLanguages={this.props.allLanguages} - selectedLanguageIds={this.state.languageId.map(Array.of)} + selectedLanguageIds={ + languageId ? Array.of(languageId) : undefined + } siteLanguages={this.props.siteLanguages} multiple={false} onChange={this.handleLanguageChange} @@ -243,7 +230,7 @@ export class MarkdownTextArea extends Component< <label htmlFor={`file-upload-${this.id}`} className={`mb-0 ${ - UserService.Instance.myUserInfo.isSome() && "pointer" + UserService.Instance.myUserInfo && "pointer" }`} data-tippy-content={i18n.t("upload_image")} > @@ -259,7 +246,7 @@ export class MarkdownTextArea extends Component< accept="image/*,video/*" name="file" className="d-none" - disabled={UserService.Instance.myUserInfo.isNone()} + disabled={!UserService.Instance.myUserInfo} onChange={linkEvent(this, this.handleImageUpload)} /> </form> @@ -376,13 +363,9 @@ export class MarkdownTextArea extends Component< let deleteToken = res.files[0].delete_token; let deleteUrl = `${pictrsUri}/delete/${deleteToken}/${hash}`; let imageMarkdown = `![](${url})`; + let content = i.state.content; i.setState({ - content: Some( - i.state.content.match({ - some: content => `${content}\n${imageMarkdown}`, - none: imageMarkdown, - }) - ), + content: content ? `${content}\n${imageMarkdown}` : imageMarkdown, imageLoading: false, }); i.contentChange(); @@ -407,13 +390,13 @@ export class MarkdownTextArea extends Component< } contentChange() { - if (this.props.onContentChange) { - this.props.onContentChange(toUndefined(this.state.content)); + if (this.state.content) { + this.props.onContentChange?.(this.state.content); } } handleContentChange(i: MarkdownTextArea, event: any) { - i.setState({ content: Some(event.target.value) }); + i.setState({ content: event.target.value }); i.contentChange(); } @@ -423,7 +406,7 @@ export class MarkdownTextArea extends Component< } handleLanguageChange(val: number[]) { - this.setState({ languageId: Some(val[0]) }); + this.setState({ languageId: val[0] }); } handleSubmit(i: MarkdownTextArea, event: any) { @@ -434,11 +417,11 @@ export class MarkdownTextArea extends Component< formId: i.formId, languageId: i.state.languageId, }; - i.props.onSubmit(msg); + i.props.onSubmit?.(msg); } handleReplyCancel(i: MarkdownTextArea) { - i.props.onReplyCancel(); + i.props.onReplyCancel?.(); } handleInsertLink(i: MarkdownTextArea, event: any) { @@ -448,25 +431,24 @@ export class MarkdownTextArea extends Component< let start: number = textarea.selectionStart; let end: number = textarea.selectionEnd; - if (i.state.content.isNone()) { - i.setState({ content: Some("") }); - } + let content = i.state.content; - let content = i.state.content.unwrap(); + if (!i.state.content) { + i.setState({ content: "" }); + } if (start !== end) { - let selectedText = content.substring(start, end); + let selectedText = content?.substring(start, end); i.setState({ - content: Some( - `${content.substring(0, start)}[${selectedText}]()${content.substring( - end - )}` - ), + content: `${content?.substring( + 0, + start + )}[${selectedText}]()${content?.substring(end)}`, }); textarea.focus(); setTimeout(() => (textarea.selectionEnd = end + 3), 10); } else { - i.setState({ content: Some(`${content} []()`) }); + i.setState({ content: `${content} []()` }); textarea.focus(); setTimeout(() => (textarea.selectionEnd -= 1), 10); } @@ -486,28 +468,25 @@ export class MarkdownTextArea extends Component< afterChars: string, emptyChars = "___" ) { - if (this.state.content.isNone()) { - this.setState({ content: Some("") }); + let content = this.state.content; + if (!this.state.content) { + this.setState({ content: "" }); } let textarea: any = document.getElementById(this.id); let start: number = textarea.selectionStart; let end: number = textarea.selectionEnd; - let content = this.state.content.unwrap(); - if (start !== end) { - let selectedText = content.substring(start, end); + let selectedText = content?.substring(start, end); this.setState({ - content: Some( - `${content.substring( - 0, - start - )}${beforeChars}${selectedText}${afterChars}${content.substring(end)}` - ), + content: `${content?.substring( + 0, + start + )}${beforeChars}${selectedText}${afterChars}${content?.substring(end)}`, }); } else { this.setState({ - content: Some(`${content}${beforeChars}${emptyChars}${afterChars}`), + content: `${content}${beforeChars}${emptyChars}${afterChars}`, }); } this.contentChange(); @@ -581,11 +560,12 @@ export class MarkdownTextArea extends Component< } simpleInsert(chars: string) { - if (this.state.content.isNone()) { - this.setState({ content: Some(`${chars} `) }); + let content = this.state.content; + if (!content) { + this.setState({ content: `${chars} ` }); } else { this.setState({ - content: Some(`${this.state.content.unwrap()}\n${chars} `), + content: `${content}\n${chars} `, }); } @@ -606,20 +586,21 @@ export class MarkdownTextArea extends Component< quoteInsert() { let textarea: any = document.getElementById(this.id); - let selectedText = window.getSelection().toString(); + let selectedText = window.getSelection()?.toString(); + let content = this.state.content; if (selectedText) { let quotedText = selectedText .split("\n") .map(t => `> ${t}`) .join("\n") + "\n\n"; - if (this.state.content.isNone()) { - this.setState({ content: Some("") }); + if (!content) { + this.setState({ content: "" }); } else { - this.setState({ content: Some(`${this.state.content.unwrap()}\n`) }); + this.setState({ content: `${content}\n` }); } this.setState({ - content: Some(`${this.state.content.unwrap()}${quotedText}`), + content: `${content}${quotedText}`, }); this.contentChange(); // Not sure why this needs a delay @@ -631,8 +612,6 @@ export class MarkdownTextArea extends Component< let textarea: any = document.getElementById(this.id); let start: number = textarea.selectionStart; let end: number = textarea.selectionEnd; - return start !== end - ? this.state.content.unwrap().substring(start, end) - : ""; + return start !== end ? this.state.content?.substring(start, end) ?? "" : ""; } } diff --git a/src/shared/components/common/moment-time.tsx b/src/shared/components/common/moment-time.tsx index 3d3c548..81aedd1 100644 --- a/src/shared/components/common/moment-time.tsx +++ b/src/shared/components/common/moment-time.tsx @@ -1,4 +1,3 @@ -import { Option } from "@sniptt/monads"; import { Component } from "inferno"; import moment from "moment"; import { i18n } from "../../i18next"; @@ -7,7 +6,7 @@ import { Icon } from "./icon"; interface MomentTimeProps { published: string; - updated: Option<string>; + updated?: string; showAgo?: boolean; ignoreUpdated?: boolean; } @@ -22,22 +21,27 @@ export class MomentTime extends Component<MomentTimeProps, any> { } createdAndModifiedTimes() { - return `${capitalizeFirstLetter(i18n.t("created"))}: ${this.format( + let updated = this.props.updated; + let line = `${capitalizeFirstLetter(i18n.t("created"))}: ${this.format( this.props.published - )}\n\n\n${ - this.props.updated.isSome() && capitalizeFirstLetter(i18n.t("modified")) - } ${this.format(this.props.updated.unwrap())}`; + )}`; + if (updated) { + line += `\n\n\n${capitalizeFirstLetter(i18n.t("modified"))} ${this.format( + updated + )}`; + } + return line; } render() { - if (!this.props.ignoreUpdated && this.props.updated.isSome()) { + if (!this.props.ignoreUpdated && this.props.updated) { return ( <span data-tippy-content={this.createdAndModifiedTimes()} className="font-italics pointer unselectable" > <Icon icon="edit-2" classes="icon-inline mr-1" /> - {moment.utc(this.props.updated.unwrap()).fromNow(!this.props.showAgo)} + {moment.utc(this.props.updated).fromNow(!this.props.showAgo)} </span> ); } else { diff --git a/src/shared/components/common/registration-application.tsx b/src/shared/components/common/registration-application.tsx index 050fe96..cbd8ffa 100644 --- a/src/shared/components/common/registration-application.tsx +++ b/src/shared/components/common/registration-application.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { @@ -7,7 +6,7 @@ import { } from "lemmy-js-client"; import { i18n } from "../../i18next"; import { WebSocketService } from "../../services"; -import { auth, mdToHtml, wsClient } from "../../utils"; +import { mdToHtml, myAuth, wsClient } from "../../utils"; import { PersonListing } from "../person/person-listing"; import { MarkdownTextArea } from "./markdown-textarea"; import { MomentTime } from "./moment-time"; @@ -17,7 +16,7 @@ interface RegistrationApplicationProps { } interface RegistrationApplicationState { - denyReason: Option<string>; + denyReason?: string; denyExpanded: boolean; } @@ -25,15 +24,13 @@ export class RegistrationApplication extends Component< RegistrationApplicationProps, RegistrationApplicationState > { - private emptyState: RegistrationApplicationState = { + state: RegistrationApplicationState = { denyReason: this.props.application.registration_application.deny_reason, denyExpanded: false, }; constructor(props: any, context: any) { super(props, context); - - this.state = this.emptyState; this.handleDenyReasonChange = this.handleDenyReasonChange.bind(this); } @@ -48,44 +45,37 @@ export class RegistrationApplication extends Component< {i18n.t("applicant")}: <PersonListing person={a.creator} /> </div> <div> - {i18n.t("created")}:{" "} - <MomentTime showAgo published={ra.published} updated={None} /> + {i18n.t("created")}: <MomentTime showAgo published={ra.published} /> </div> <div>{i18n.t("answer")}:</div> <div className="md-div" dangerouslySetInnerHTML={mdToHtml(ra.answer)} /> - {a.admin.match({ - some: admin => ( - <div> - {accepted ? ( - <T i18nKey="approved_by"> + {a.admin && ( + <div> + {accepted ? ( + <T i18nKey="approved_by"> + # + <PersonListing person={a.admin} /> + </T> + ) : ( + <div> + <T i18nKey="denied_by"> # - <PersonListing person={admin} /> + <PersonListing person={a.admin} /> </T> - ) : ( - <div> - <T i18nKey="denied_by"> - # - <PersonListing person={admin} /> - </T> - {ra.deny_reason.match({ - some: deny_reason => ( - <div> - {i18n.t("deny_reason")}:{" "} - <div - className="md-div d-inline-flex" - dangerouslySetInnerHTML={mdToHtml(deny_reason)} - /> - </div> - ), - none: <></>, - })} - </div> - )} - </div> - ), - none: <></>, - })} + {ra.deny_reason && ( + <div> + {i18n.t("deny_reason")}:{" "} + <div + className="md-div d-inline-flex" + dangerouslySetInnerHTML={mdToHtml(ra.deny_reason)} + /> + </div> + )} + </div> + )} + </div> + )} {this.state.denyExpanded && ( <div className="form-group row"> @@ -95,11 +85,7 @@ export class RegistrationApplication extends Component< <div className="col-sm-10"> <MarkdownTextArea initialContent={this.state.denyReason} - initialLanguageId={None} onContentChange={this.handleDenyReasonChange} - placeholder={None} - buttonTitle={None} - maxLength={None} hideNavigationWarnings allLanguages={[]} siteLanguages={[]} @@ -107,7 +93,7 @@ export class RegistrationApplication extends Component< </div> </div> )} - {(ra.admin_id.isNone() || (ra.admin_id.isSome() && !accepted)) && ( + {(!ra.admin_id || (ra.admin_id && !accepted)) && ( <button className="btn btn-secondary mr-2 my-2" onClick={linkEvent(this, this.handleApprove)} @@ -116,7 +102,7 @@ export class RegistrationApplication extends Component< {i18n.t("approve")} </button> )} - {(ra.admin_id.isNone() || (ra.admin_id.isSome() && accepted)) && ( + {(!ra.admin_id || (ra.admin_id && accepted)) && ( <button className="btn btn-secondary mr-2" onClick={linkEvent(this, this.handleDeny)} @@ -130,36 +116,41 @@ export class RegistrationApplication extends Component< } handleApprove(i: RegistrationApplication) { - i.setState({ denyExpanded: false }); - let form = new ApproveRegistrationApplication({ - id: i.props.application.registration_application.id, - deny_reason: None, - approve: true, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send( - wsClient.approveRegistrationApplication(form) - ); - } - - handleDeny(i: RegistrationApplication) { - if (i.state.denyExpanded) { + let auth = myAuth(); + if (auth) { i.setState({ denyExpanded: false }); - let form = new ApproveRegistrationApplication({ + let form: ApproveRegistrationApplication = { id: i.props.application.registration_application.id, - approve: false, - deny_reason: i.state.denyReason, - auth: auth().unwrap(), - }); + approve: true, + auth, + }; WebSocketService.Instance.send( wsClient.approveRegistrationApplication(form) ); + } + } + + handleDeny(i: RegistrationApplication) { + if (i.state.denyExpanded) { + i.setState({ denyExpanded: false }); + let auth = myAuth(); + if (auth) { + let form: ApproveRegistrationApplication = { + id: i.props.application.registration_application.id, + approve: false, + deny_reason: i.state.denyReason, + auth, + }; + WebSocketService.Instance.send( + wsClient.approveRegistrationApplication(form) + ); + } } else { i.setState({ denyExpanded: true }); } } handleDenyReasonChange(val: string) { - this.setState({ denyReason: Some(val) }); + this.setState({ denyReason: val }); } } diff --git a/src/shared/components/common/sort-select.tsx b/src/shared/components/common/sort-select.tsx index facc16f..c0d277f 100644 --- a/src/shared/components/common/sort-select.tsx +++ b/src/shared/components/common/sort-select.tsx @@ -17,13 +17,12 @@ interface SortSelectState { export class SortSelect extends Component<SortSelectProps, SortSelectState> { private id = `sort-select-${randomStr()}`; - private emptyState: SortSelectState = { + state: SortSelectState = { sort: this.props.sort, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; } static getDerivedStateFromProps(props: any): SortSelectState { @@ -86,6 +85,6 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> { } handleSortChange(i: SortSelect, event: any) { - i.props.onChange(event.target.value); + i.props.onChange?.(event.target.value); } } diff --git a/src/shared/components/community/communities.tsx b/src/shared/components/community/communities.tsx index c33963a..e6cdc45 100644 --- a/src/shared/components/community/communities.tsx +++ b/src/shared/components/community/communities.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { CommunityResponse, @@ -18,10 +17,10 @@ import { InitialFetchRequest } from "shared/interfaces"; import { i18n } from "../../i18next"; import { WebSocketService } from "../../services"; import { - auth, getListingTypeFromPropsNoDefault, getPageFromProps, isBrowser, + myAuth, numToSI, setIsoData, showLocal, @@ -38,7 +37,7 @@ import { CommunityLink } from "./community-link"; const communityLimit = 50; interface CommunitiesState { - listCommunitiesResponse: Option<ListCommunitiesResponse>; + listCommunitiesResponse?: ListCommunitiesResponse; page: number; loading: boolean; siteRes: GetSiteResponse; @@ -52,10 +51,9 @@ interface CommunitiesProps { } export class Communities extends Component<any, CommunitiesState> { - private subscription: Subscription; - private isoData = setIsoData(this.context, ListCommunitiesResponse); - private emptyState: CommunitiesState = { - listCommunitiesResponse: None, + private subscription?: Subscription; + private isoData = setIsoData(this.context); + state: CommunitiesState = { loading: true, page: getPageFromProps(this.props), listingType: getListingTypeFromPropsNoDefault(this.props), @@ -65,7 +63,6 @@ export class Communities extends Component<any, CommunitiesState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handlePageChange = this.handlePageChange.bind(this); this.handleListingTypeChange = this.handleListingTypeChange.bind(this); @@ -74,7 +71,7 @@ export class Communities extends Component<any, CommunitiesState> { // Only fetch the data if coming from another route if (this.isoData.path == this.context.router.route.match.url) { - let listRes = Some(this.isoData.routeData[0] as ListCommunitiesResponse); + let listRes = this.isoData.routeData[0] as ListCommunitiesResponse; this.state = { ...this.state, listCommunitiesResponse: listRes, @@ -87,7 +84,7 @@ export class Communities extends Component<any, CommunitiesState> { componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -120,8 +117,6 @@ export class Communities extends Component<any, CommunitiesState> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> {this.state.loading ? ( <h5> @@ -168,57 +163,54 @@ export class Communities extends Component<any, CommunitiesState> { </tr> </thead> <tbody> - {this.state.listCommunitiesResponse - .map(l => l.communities) - .unwrapOr([]) - .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 == SubscribedType.Subscribed && ( - <button - className="btn btn-link d-inline-block" - onClick={linkEvent( - cv.community.id, - this.handleUnsubscribe - )} - > - {i18n.t("unsubscribe")} - </button> - )} - {cv.subscribed == SubscribedType.NotSubscribed && ( - <button - className="btn btn-link d-inline-block" - onClick={linkEvent( - cv.community.id, - this.handleSubscribe - )} - > - {i18n.t("subscribe")} - </button> - )} - {cv.subscribed == SubscribedType.Pending && ( - <div className="text-warning d-inline-block"> - {i18n.t("subscribe_pending")} - </div> - )} - </td> - </tr> - ))} + {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 == SubscribedType.Subscribed && ( + <button + className="btn btn-link d-inline-block" + onClick={linkEvent( + cv.community.id, + this.handleUnsubscribe + )} + > + {i18n.t("unsubscribe")} + </button> + )} + {cv.subscribed == SubscribedType.NotSubscribed && ( + <button + className="btn btn-link d-inline-block" + onClick={linkEvent( + cv.community.id, + this.handleSubscribe + )} + > + {i18n.t("subscribe")} + </button> + )} + {cv.subscribed == SubscribedType.Pending && ( + <div className="text-warning d-inline-block"> + {i18n.t("subscribe_pending")} + </div> + )} + </td> + </tr> + ))} </tbody> </table> </div> @@ -278,21 +270,27 @@ export class Communities extends Component<any, CommunitiesState> { } handleUnsubscribe(communityId: number) { - let form = new FollowCommunity({ - community_id: communityId, - follow: false, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.followCommunity(form)); + let auth = myAuth(); + if (auth) { + let form: FollowCommunity = { + community_id: communityId, + follow: false, + auth, + }; + WebSocketService.Instance.send(wsClient.followCommunity(form)); + } } handleSubscribe(communityId: number) { - let form = new FollowCommunity({ - community_id: communityId, - follow: true, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.followCommunity(form)); + let auth = myAuth(); + if (auth) { + let form: FollowCommunity = { + community_id: communityId, + follow: true, + auth, + }; + WebSocketService.Instance.send(wsClient.followCommunity(form)); + } } handleSearchChange(i: Communities, event: any) { @@ -307,13 +305,13 @@ export class Communities extends Component<any, CommunitiesState> { } refetch() { - let listCommunitiesForm = new ListCommunities({ - type_: Some(this.state.listingType), - sort: Some(SortType.TopMonth), - limit: Some(communityLimit), - page: Some(this.state.page), - auth: auth(false).ok(), - }); + let listCommunitiesForm: ListCommunities = { + type_: this.state.listingType, + sort: SortType.TopMonth, + limit: communityLimit, + page: this.state.page, + auth: myAuth(false), + }; WebSocketService.Instance.send( wsClient.listCommunities(listCommunitiesForm) @@ -322,17 +320,17 @@ export class Communities extends Component<any, CommunitiesState> { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { let pathSplit = req.path.split("/"); - let type_: Option<ListingType> = Some( - pathSplit[3] ? ListingType[pathSplit[3]] : ListingType.Local - ); - let page = Some(pathSplit[5] ? Number(pathSplit[5]) : 1); - let listCommunitiesForm = new ListCommunities({ + let type_: ListingType = pathSplit[3] + ? ListingType[pathSplit[3]] + : ListingType.Local; + let page = pathSplit[5] ? Number(pathSplit[5]) : 1; + let listCommunitiesForm: ListCommunities = { type_, - sort: Some(SortType.TopMonth), - limit: Some(communityLimit), + sort: SortType.TopMonth, + limit: communityLimit, page, auth: req.auth, - }); + }; return [req.client.listCommunities(listCommunitiesForm)]; } @@ -344,25 +342,20 @@ export class Communities extends Component<any, CommunitiesState> { toast(i18n.t(msg.error), "danger"); return; } else if (op == UserOperation.ListCommunities) { - let data = wsJsonToRes<ListCommunitiesResponse>( - msg, - ListCommunitiesResponse - ); - this.setState({ listCommunitiesResponse: Some(data), loading: false }); + let data = wsJsonToRes<ListCommunitiesResponse>(msg); + this.setState({ listCommunitiesResponse: data, loading: false }); window.scrollTo(0, 0); } else if (op == UserOperation.FollowCommunity) { - let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse); - this.state.listCommunitiesResponse.match({ - some: res => { - let found = res.communities.find( - c => c.community.id == data.community_view.community.id - ); - found.subscribed = data.community_view.subscribed; - found.counts.subscribers = data.community_view.counts.subscribers; - }, - none: void 0, - }); - this.setState(this.state); + let data = wsJsonToRes<CommunityResponse>(msg); + let res = this.state.listCommunitiesResponse; + let found = res?.communities.find( + c => c.community.id == data.community_view.community.id + ); + if (found) { + found.subscribed = data.community_view.subscribed; + found.counts.subscribers = data.community_view.counts.subscribers; + this.setState(this.state); + } } } } diff --git a/src/shared/components/community/community-form.tsx b/src/shared/components/community/community-form.tsx index 805e496..c6ba5d8 100644 --- a/src/shared/components/community/community-form.tsx +++ b/src/shared/components/community/community-form.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { Prompt } from "inferno-router"; import { @@ -7,7 +6,6 @@ import { CreateCommunity, EditCommunity, Language, - toUndefined, UserOperation, wsJsonToRes, wsUserOp, @@ -16,8 +14,8 @@ import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; import { - auth, capitalizeFirstLetter, + myAuth, randomStr, wsClient, wsSubscribe, @@ -28,10 +26,10 @@ import { LanguageSelect } from "../common/language-select"; import { MarkdownTextArea } from "../common/markdown-textarea"; interface CommunityFormProps { - community_view: Option<CommunityView>; // If a community is given, that means this is an edit + community_view?: CommunityView; // If a community is given, that means this is an edit allLanguages: Language[]; siteLanguages: number[]; - communityLanguages: Option<number[]>; + communityLanguages?: number[]; onCancel?(): any; onCreate?(community: CommunityView): any; onEdit?(community: CommunityView): any; @@ -39,7 +37,16 @@ interface CommunityFormProps { } interface CommunityFormState { - communityForm: CreateCommunity; + form: { + name?: string; + title?: string; + description?: string; + icon?: string; + banner?: string; + nsfw?: boolean; + posting_restricted_to_mods?: boolean; + discussion_languages?: number[]; + }; loading: boolean; } @@ -48,28 +55,16 @@ export class CommunityForm extends Component< CommunityFormState > { private id = `community-form-${randomStr()}`; - private subscription: Subscription; - - private emptyState: CommunityFormState = { - communityForm: new CreateCommunity({ - name: undefined, - title: undefined, - description: None, - discussion_languages: this.props.communityLanguages, - nsfw: None, - icon: None, - banner: None, - posting_restricted_to_mods: None, - auth: undefined, - }), + private subscription?: Subscription; + + state: CommunityFormState = { + form: {}, loading: false, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; - this.handleCommunityDescriptionChange = this.handleCommunityDescriptionChange.bind(this); @@ -84,24 +79,21 @@ export class CommunityForm extends Component< this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); + let cv = this.props.community_view; - if (this.props.community_view.isSome()) { - let cv = this.props.community_view.unwrap(); + if (cv) { this.state = { - ...this.state, - communityForm: new CreateCommunity({ + form: { name: cv.community.name, title: cv.community.title, description: cv.community.description, - nsfw: Some(cv.community.nsfw), + nsfw: cv.community.nsfw, icon: cv.community.icon, banner: cv.community.banner, - posting_restricted_to_mods: Some( - cv.community.posting_restricted_to_mods - ), + posting_restricted_to_mods: cv.community.posting_restricted_to_mods, discussion_languages: this.props.communityLanguages, - auth: undefined, - }), + }, + loading: false, }; } } @@ -109,18 +101,18 @@ export class CommunityForm extends Component< componentDidUpdate() { if ( !this.state.loading && - (this.state.communityForm.name || - this.state.communityForm.title || - this.state.communityForm.description.isSome()) + (this.state.form.name || + this.state.form.title || + this.state.form.description) ) { window.onbeforeunload = () => true; } else { - window.onbeforeunload = undefined; + window.onbeforeunload = null; } } componentWillUnmount() { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); window.onbeforeunload = null; } @@ -130,14 +122,14 @@ export class CommunityForm extends Component< <Prompt when={ !this.state.loading && - (this.state.communityForm.name || - this.state.communityForm.title || - this.state.communityForm.description.isSome()) + (this.state.form.name || + this.state.form.title || + this.state.form.description) } message={i18n.t("block_leaving")} /> <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}> - {this.props.community_view.isNone() && ( + {!this.props.community_view && ( <div className="form-group row"> <label className="col-12 col-sm-2 col-form-label" @@ -156,7 +148,7 @@ export class CommunityForm extends Component< type="text" id="community-name" className="form-control" - value={this.state.communityForm.name} + value={this.state.form.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} @@ -183,7 +175,7 @@ export class CommunityForm extends Component< <input type="text" id="community-title" - value={this.state.communityForm.title} + value={this.state.form.title} onInput={linkEvent(this, this.handleCommunityTitleChange)} className="form-control" required @@ -197,7 +189,7 @@ export class CommunityForm extends Component< <div className="col-12 col-sm-10"> <ImageUploadForm uploadTitle={i18n.t("upload_icon")} - imageSrc={this.state.communityForm.icon} + imageSrc={this.state.form.icon} onUpload={this.handleIconUpload} onRemove={this.handleIconRemove} rounded @@ -209,7 +201,7 @@ export class CommunityForm extends Component< <div className="col-12 col-sm-10"> <ImageUploadForm uploadTitle={i18n.t("upload_banner")} - imageSrc={this.state.communityForm.banner} + imageSrc={this.state.form.banner} onUpload={this.handleBannerUpload} onRemove={this.handleBannerRemove} /> @@ -221,11 +213,8 @@ export class CommunityForm extends Component< </label> <div className="col-12 col-sm-10"> <MarkdownTextArea - initialContent={this.state.communityForm.description} - initialLanguageId={None} - placeholder={Some("description")} - buttonTitle={None} - maxLength={None} + initialContent={this.state.form.description} + placeholder={i18n.t("description")} onContentChange={this.handleCommunityDescriptionChange} allLanguages={[]} siteLanguages={[]} @@ -244,7 +233,7 @@ export class CommunityForm extends Component< className="form-check-input position-static" id="community-nsfw" type="checkbox" - checked={toUndefined(this.state.communityForm.nsfw)} + checked={this.state.form.nsfw} onChange={linkEvent(this, this.handleCommunityNsfwChange)} /> </div> @@ -261,9 +250,7 @@ export class CommunityForm extends Component< className="form-check-input position-static" id="community-only-mods-can-post" type="checkbox" - checked={toUndefined( - this.state.communityForm.posting_restricted_to_mods - )} + checked={this.state.form.posting_restricted_to_mods} onChange={linkEvent( this, this.handleCommunityPostingRestrictedToMods @@ -276,7 +263,7 @@ export class CommunityForm extends Component< allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} showSite - selectedLanguageIds={this.state.communityForm.discussion_languages} + selectedLanguageIds={this.state.form.discussion_languages} multiple={true} onChange={this.handleDiscussionLanguageChange} /> @@ -289,13 +276,13 @@ export class CommunityForm extends Component< > {this.state.loading ? ( <Spinner /> - ) : this.props.community_view.isSome() ? ( + ) : this.props.community_view ? ( capitalizeFirstLetter(i18n.t("save")) ) : ( capitalizeFirstLetter(i18n.t("create")) )} </button> - {this.props.community_view.isSome() && ( + {this.props.community_view && ( <button type="button" className="btn btn-secondary" @@ -314,82 +301,92 @@ export class CommunityForm extends Component< handleCreateCommunitySubmit(i: CommunityForm, event: any) { event.preventDefault(); i.setState({ loading: true }); - let cForm = i.state.communityForm; - cForm.auth = auth().unwrap(); + let cForm = i.state.form; + let auth = myAuth(); - i.props.community_view.match({ - some: cv => { - let form = new EditCommunity({ + let cv = i.props.community_view; + + if (auth) { + if (cv) { + let form: EditCommunity = { community_id: cv.community.id, - title: Some(cForm.title), + 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: cForm.auth, - }); + auth, + }; WebSocketService.Instance.send(wsClient.editCommunity(form)); - }, - none: () => { - WebSocketService.Instance.send( - wsClient.createCommunity(i.state.communityForm) - ); - }, - }); + } else { + if (cForm.title && cForm.name) { + let 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.communityForm.name = event.target.value; + i.state.form.name = event.target.value; i.setState(i.state); } handleCommunityTitleChange(i: CommunityForm, event: any) { - i.state.communityForm.title = event.target.value; + i.state.form.title = event.target.value; i.setState(i.state); } handleCommunityDescriptionChange(val: string) { - this.setState(s => ((s.communityForm.description = Some(val)), s)); + this.setState(s => ((s.form.description = val), s)); } handleCommunityNsfwChange(i: CommunityForm, event: any) { - i.state.communityForm.nsfw = Some(event.target.checked); + i.state.form.nsfw = event.target.checked; i.setState(i.state); } handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) { - i.state.communityForm.posting_restricted_to_mods = Some( - event.target.checked - ); + i.state.form.posting_restricted_to_mods = event.target.checked; i.setState(i.state); } handleCancel(i: CommunityForm) { - i.props.onCancel(); + i.props.onCancel?.(); } handleIconUpload(url: string) { - this.setState(s => ((s.communityForm.icon = Some(url)), s)); + this.setState(s => ((s.form.icon = url), s)); } handleIconRemove() { - this.setState(s => ((s.communityForm.icon = Some("")), s)); + this.setState(s => ((s.form.icon = ""), s)); } handleBannerUpload(url: string) { - this.setState(s => ((s.communityForm.banner = Some(url)), s)); + this.setState(s => ((s.form.banner = url), s)); } handleBannerRemove() { - this.setState(s => ((s.communityForm.banner = Some("")), s)); + this.setState(s => ((s.form.banner = ""), s)); } handleDiscussionLanguageChange(val: number[]) { - this.setState(s => ((s.communityForm.discussion_languages = Some(val)), s)); + this.setState(s => ((s.form.discussion_languages = val), s)); } parseMessage(msg: any) { @@ -401,50 +398,46 @@ export class CommunityForm extends Component< this.setState({ loading: false }); return; } else if (op == UserOperation.CreateCommunity) { - let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse); - this.props.onCreate(data.community_view); + let data = wsJsonToRes<CommunityResponse>(msg); + this.props.onCreate?.(data.community_view); // Update myUserInfo let community = data.community_view.community; - UserService.Instance.myUserInfo.match({ - some: mui => { - let person = mui.local_user_view.person; - mui.follows.push({ - community, - follower: person, - }); - mui.moderates.push({ - community, - moderator: person, - }); - }, - none: void 0, - }); + let mui = UserService.Instance.myUserInfo; + if (mui) { + let person = mui.local_user_view.person; + mui.follows.push({ + community, + follower: person, + }); + mui.moderates.push({ + community, + moderator: person, + }); + } } else if (op == UserOperation.EditCommunity) { - let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse); + let data = wsJsonToRes<CommunityResponse>(msg); this.setState({ loading: false }); - this.props.onEdit(data.community_view); + this.props.onEdit?.(data.community_view); let community = data.community_view.community; - UserService.Instance.myUserInfo.match({ - some: mui => { - let followFound = mui.follows.findIndex( - f => f.community.id == community.id - ); - if (followFound) { - mui.follows[followFound].community = community; - } + let mui = UserService.Instance.myUserInfo; + if (mui) { + let followFound = mui.follows.findIndex( + f => f.community.id == community.id + ); + if (followFound) { + mui.follows[followFound].community = community; + } - let moderatesFound = mui.moderates.findIndex( - f => f.community.id == community.id - ); - if (moderatesFound) { - mui.moderates[moderatesFound].community = community; - } - }, - none: void 0, - }); + let moderatesFound = mui.moderates.findIndex( + f => f.community.id == community.id + ); + if (moderatesFound) { + mui.moderates[moderatesFound].community = community; + } + } } } } diff --git a/src/shared/components/community/community-link.tsx b/src/shared/components/community/community-link.tsx index b051ed8..b65e3d1 100644 --- a/src/shared/components/community/community-link.tsx +++ b/src/shared/components/community/community-link.tsx @@ -5,7 +5,6 @@ import { hostname, relTags, showAvatars } from "../../utils"; import { PictrsImage } from "../common/pictrs-image"; interface CommunityLinkProps { - // TODO figure this out better community: CommunitySafe; realLink?: boolean; useApubName?: boolean; @@ -56,14 +55,12 @@ export class CommunityLink extends Component<CommunityLinkProps, any> { } avatarAndName(displayName: string) { + let icon = this.props.community.icon; return ( <> - {!this.props.hideAvatar && - showAvatars() && - this.props.community.icon.match({ - some: icon => <PictrsImage src={icon} icon />, - none: <></>, - })} + {!this.props.hideAvatar && showAvatars() && icon && ( + <PictrsImage src={icon} icon /> + )} <span className="overflow-wrap-anywhere">{displayName}</span> </> ); diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index 7ce6099..5880ee7 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { AddModToCommunityResponse, @@ -22,7 +21,6 @@ import { PostView, PurgeItemResponse, SortType, - toOption, UserOperation, wsJsonToRes, wsUserOp, @@ -36,7 +34,6 @@ import { } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { - auth, commentsToFlatNodes, communityRSSUrl, createCommentLikeRes, @@ -50,6 +47,7 @@ import { getPageFromProps, getSortTypeFromProps, isPostBlocked, + myAuth, notifyPost, nsfwCheck, postToCommentSortType, @@ -79,7 +77,7 @@ import { PostListings } from "../post/post-listings"; import { CommunityLink } from "./community-link"; interface State { - communityRes: Option<GetCommunityResponse>; + communityRes?: GetCommunityResponse; siteRes: GetSiteResponse; communityName: string; communityLoading: boolean; @@ -106,15 +104,9 @@ interface UrlParams { } export class Community extends Component<any, State> { - private isoData = setIsoData( - this.context, - GetCommunityResponse, - GetPostsResponse, - GetCommentsResponse - ); - private subscription: Subscription; - private emptyState: State = { - communityRes: None, + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: State = { communityName: this.props.match.params.name, communityLoading: true, postsLoading: true, @@ -131,7 +123,6 @@ export class Community extends Component<any, State> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleSortChange = this.handleSortChange.bind(this); this.handleDataTypeChange = this.handleDataTypeChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); @@ -143,17 +134,19 @@ export class Community extends Component<any, State> { if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - communityRes: Some(this.isoData.routeData[0] as GetCommunityResponse), + communityRes: this.isoData.routeData[0] as GetCommunityResponse, }; - let postsRes = Some(this.isoData.routeData[1] as GetPostsResponse); - let commentsRes = Some(this.isoData.routeData[2] as GetCommentsResponse); + let postsRes = this.isoData.routeData[1] as GetPostsResponse | undefined; + let commentsRes = this.isoData.routeData[2] as + | GetCommentsResponse + | undefined; - if (postsRes.isSome()) { - this.state = { ...this.state, posts: postsRes.unwrap().posts }; + if (postsRes) { + this.state = { ...this.state, posts: postsRes.posts }; } - if (commentsRes.isSome()) { - this.state = { ...this.state, comments: commentsRes.unwrap().comments }; + if (commentsRes) { + this.state = { ...this.state, comments: commentsRes.comments }; } this.state = { @@ -169,11 +162,10 @@ export class Community extends Component<any, State> { } fetchCommunity() { - let form = new GetCommunity({ - name: Some(this.state.communityName), - id: None, - auth: auth(false).ok(), - }); + let form: GetCommunity = { + name: this.state.communityName, + auth: myAuth(false), + }; WebSocketService.Instance.send(wsClient.getCommunity(form)); } @@ -183,7 +175,7 @@ export class Community extends Component<any, State> { componentWillUnmount() { saveScrollPosition(this.context); - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } static getDerivedStateFromProps(props: any): CommunityProps { @@ -199,58 +191,50 @@ export class Community extends Component<any, State> { let promises: Promise<any>[] = []; let communityName = pathSplit[2]; - let communityForm = new GetCommunity({ - name: Some(communityName), - id: None, + let communityForm: GetCommunity = { + name: communityName, auth: req.auth, - }); + }; promises.push(req.client.getCommunity(communityForm)); let dataType: DataType = pathSplit[4] ? DataType[pathSplit[4]] : DataType.Post; - let sort: Option<SortType> = toOption( - pathSplit[6] - ? SortType[pathSplit[6]] - : UserService.Instance.myUserInfo.match({ - some: mui => - Object.values(SortType)[ - mui.local_user_view.local_user.default_sort_type - ], - none: SortType.Active, - }) - ); + let mui = UserService.Instance.myUserInfo; + + let sort: SortType = pathSplit[6] + ? SortType[pathSplit[6]] + : mui + ? Object.values(SortType)[ + mui.local_user_view.local_user.default_sort_type + ] + : SortType.Active; - let page = toOption(pathSplit[8] ? Number(pathSplit[8]) : 1); + let page = pathSplit[8] ? Number(pathSplit[8]) : 1; if (dataType == DataType.Post) { - let getPostsForm = new GetPosts({ - community_name: Some(communityName), - community_id: None, + let getPostsForm: GetPosts = { + community_name: communityName, page, - limit: Some(fetchLimit), + limit: fetchLimit, sort, - type_: Some(ListingType.All), - saved_only: Some(false), + type_: ListingType.All, + saved_only: false, auth: req.auth, - }); + }; promises.push(req.client.getPosts(getPostsForm)); promises.push(Promise.resolve()); } else { - let getCommentsForm = new GetComments({ - community_name: Some(communityName), - community_id: None, + let getCommentsForm: GetComments = { + community_name: communityName, page, - limit: Some(fetchLimit), - max_depth: None, - sort: sort.map(postToCommentSortType), - type_: Some(ListingType.All), - saved_only: Some(false), - post_id: None, - parent_id: None, + limit: fetchLimit, + sort: postToCommentSortType(sort), + type_: ListingType.All, + saved_only: false, auth: req.auth, - }); + }; promises.push(Promise.resolve()); promises.push(req.client.getComments(getCommentsForm)); } @@ -270,23 +254,19 @@ export class Community extends Component<any, State> { } get documentTitle(): string { - return this.state.communityRes.match({ - some: res => - `${res.community_view.community.title} - ${this.state.siteRes.site_view.site.name}`, - none: "", - }); + let cRes = this.state.communityRes; + return cRes + ? `${cRes.community_view.community.title} - ${this.state.siteRes.site_view.site.name}` + : ""; } render() { // For some reason, this returns an empty vec if it matches the site langs - let communityLangs = this.state.communityRes.map(r => { - let langs = r.discussion_languages; - if (langs.length == 0) { - return this.state.siteRes.all_languages.map(l => l.id); - } else { - return langs; - } - }); + let res = this.state.communityRes; + let communityLangs = + res?.discussion_languages.length == 0 + ? this.state.siteRes.all_languages.map(l => l.id) + : res?.discussion_languages; return ( <div className="container-lg"> @@ -295,103 +275,86 @@ export class Community extends Component<any, State> { <Spinner large /> </h5> ) : ( - this.state.communityRes.match({ - some: 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` + 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 && ( + <> + <Sidebar + community_view={res.community_view} + moderators={res.moderators} + admins={this.state.siteRes.admins} + online={res.online} + enableNsfw={enableNsfw(this.state.siteRes)} + editable + allLanguages={this.state.siteRes.all_languages} + siteLanguages={ + this.state.siteRes.discussion_languages } - classes="icon-inline" + communityLanguages={communityLangs} /> - </button> - {this.state.showSidebarMobile && ( - <> - <Sidebar - community_view={res.community_view} - moderators={res.moderators} - admins={this.state.siteRes.admins} - online={res.online} - enableNsfw={enableNsfw(this.state.siteRes)} - editable - allLanguages={this.state.siteRes.all_languages} - siteLanguages={ - this.state.siteRes.discussion_languages - } - communityLanguages={communityLangs} - /> - {!res.community_view.community.local && - res.site.match({ - some: site => ( - <SiteSidebar - site={site} - showLocal={showLocal(this.isoData)} - admins={None} - counts={None} - online={None} - /> - ), - none: <></>, - })} - </> - )} - </div> - {this.selects()} - {this.listings()} - <Paginator - page={this.state.page} - onChange={this.handlePageChange} - /> - </div> - <div className="d-none d-md-block col-md-4"> - <Sidebar - community_view={res.community_view} - moderators={res.moderators} - admins={this.state.siteRes.admins} - online={res.online} - enableNsfw={enableNsfw(this.state.siteRes)} - editable - allLanguages={this.state.siteRes.all_languages} - siteLanguages={this.state.siteRes.discussion_languages} - communityLanguages={communityLangs} - /> - {!res.community_view.community.local && - res.site.match({ - some: site => ( + {!res.community_view.community.local && res.site && ( <SiteSidebar - site={site} + site={res.site} showLocal={showLocal(this.isoData)} - admins={None} - counts={None} - online={None} /> - ), - none: <></>, - })} + )} + </> + )} </div> + {this.selects()} + {this.listings()} + <Paginator + page={this.state.page} + onChange={this.handlePageChange} + /> </div> - </> - ), - none: <></>, - }) + <div className="d-none d-md-block col-md-4"> + <Sidebar + community_view={res.community_view} + moderators={res.moderators} + admins={this.state.siteRes.admins} + online={res.online} + enableNsfw={enableNsfw(this.state.siteRes)} + editable + allLanguages={this.state.siteRes.all_languages} + siteLanguages={this.state.siteRes.discussion_languages} + communityLanguages={communityLangs} + /> + {!res.community_view.community.local && res.site && ( + <SiteSidebar + site={res.site} + showLocal={showLocal(this.isoData)} + /> + )} + </div> + </div> + </> + ) )} </div> ); @@ -424,9 +387,8 @@ export class Community extends Component<any, State> { noIndent showContext enableDownvotes={enableDownvotes(this.state.siteRes)} - moderators={this.state.communityRes.map(r => r.moderators)} - admins={Some(this.state.siteRes.admins)} - maxCommentsShown={None} + moderators={this.state.communityRes?.moderators} + admins={this.state.siteRes.admins} allLanguages={this.state.siteRes.all_languages} siteLanguages={this.state.siteRes.discussion_languages} /> @@ -434,30 +396,33 @@ export class Community extends Component<any, State> { } communityInfo() { - return this.state.communityRes - .map(r => r.community_view.community) - .match({ - some: community => ( - <div className="mb-2"> - <BannerIconHeader banner={community.banner} icon={community.icon} /> - <h5 className="mb-0 overflow-wrap-anywhere">{community.title}</h5> - <CommunityLink - community={community} - realLink - useApubName - muted - hideAvatar - /> - </div> - ), - none: <></>, - }); + let community = this.state.communityRes?.community_view.community; + return ( + community && ( + <div className="mb-2"> + <BannerIconHeader banner={community.banner} icon={community.icon} /> + <h5 className="mb-0 overflow-wrap-anywhere">{community.title}</h5> + <CommunityLink + community={community} + realLink + useApubName + muted + hideAvatar + /> + </div> + ) + ); } selects() { - let communityRss = this.state.communityRes.map(r => - communityRSSUrl(r.community_view.community.actor_id, this.state.sort) - ); + // let communityRss = this.state.communityRes.map(r => + // communityRSSUrl(r.community_view.community.actor_id, this.state.sort) + // ); + let res = this.state.communityRes; + let communityRss = res + ? communityRSSUrl(res.community_view.community.actor_id, this.state.sort) + : undefined; + return ( <div className="mb-3"> <span className="mr-3"> @@ -469,17 +434,18 @@ export class Community extends Component<any, State> { <span className="mr-2"> <SortSelect sort={this.state.sort} onChange={this.handleSortChange} /> </span> - {communityRss.match({ - some: rss => ( - <> - <a href={rss} title="RSS" rel={relTags}> - <Icon icon="rss" classes="text-muted small" /> - </a> - <link rel="alternate" type="application/atom+xml" href={rss} /> - </> - ), - none: <></>, - })} + {communityRss && ( + <> + <a href={communityRss} title="RSS" rel={relTags}> + <Icon icon="rss" classes="text-muted small" /> + </a> + <link + rel="alternate" + type="application/atom+xml" + href={communityRss} + /> + </> + )} </div> ); } @@ -517,31 +483,26 @@ export class Community extends Component<any, State> { fetchData() { if (this.state.dataType == DataType.Post) { - let form = new GetPosts({ - page: Some(this.state.page), - limit: Some(fetchLimit), - sort: Some(this.state.sort), - type_: Some(ListingType.All), - community_name: Some(this.state.communityName), - community_id: None, - saved_only: Some(false), - auth: auth(false).ok(), - }); + let form: GetPosts = { + page: this.state.page, + limit: fetchLimit, + sort: this.state.sort, + type_: ListingType.All, + community_name: this.state.communityName, + saved_only: false, + auth: myAuth(false), + }; WebSocketService.Instance.send(wsClient.getPosts(form)); } else { - let form = new GetComments({ - page: Some(this.state.page), - limit: Some(fetchLimit), - max_depth: None, - sort: Some(postToCommentSortType(this.state.sort)), - type_: Some(ListingType.All), - community_name: Some(this.state.communityName), - community_id: None, - saved_only: Some(false), - post_id: None, - parent_id: None, - auth: auth(false).ok(), - }); + let form: GetComments = { + page: this.state.page, + limit: fetchLimit, + sort: postToCommentSortType(this.state.sort), + type_: ListingType.All, + community_name: this.state.communityName, + saved_only: false, + auth: myAuth(false), + }; WebSocketService.Instance.send(wsClient.getComments(form)); } } @@ -549,25 +510,23 @@ export class Community extends Component<any, State> { parseMessage(msg: any) { let op = wsUserOp(msg); console.log(msg); + let res = this.state.communityRes; if (msg.error) { toast(i18n.t(msg.error), "danger"); this.context.router.history.push("/"); return; } else if (msg.reconnect) { - this.state.communityRes.match({ - some: res => { - WebSocketService.Instance.send( - wsClient.communityJoin({ - community_id: res.community_view.community.id, - }) - ); - }, - none: void 0, - }); + if (res) { + WebSocketService.Instance.send( + wsClient.communityJoin({ + community_id: res.community_view.community.id, + }) + ); + } this.fetchData(); } else if (op == UserOperation.GetCommunity) { - let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse); - this.setState({ communityRes: Some(data), communityLoading: false }); + let data = wsJsonToRes<GetCommunityResponse>(msg); + this.setState({ communityRes: data, communityLoading: false }); // TODO why is there no auth in this form? WebSocketService.Instance.send( wsClient.communityJoin({ @@ -579,28 +538,22 @@ export class Community extends Component<any, State> { op == UserOperation.DeleteCommunity || op == UserOperation.RemoveCommunity ) { - let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse); - this.state.communityRes.match({ - some: res => { - res.community_view = data.community_view; - res.discussion_languages = data.discussion_languages; - }, - none: void 0, - }); + let data = wsJsonToRes<CommunityResponse>(msg); + if (res) { + res.community_view = data.community_view; + res.discussion_languages = data.discussion_languages; + } this.setState(this.state); } else if (op == UserOperation.FollowCommunity) { - let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse); - this.state.communityRes.match({ - some: res => { - res.community_view.subscribed = data.community_view.subscribed; - res.community_view.counts.subscribers = - data.community_view.counts.subscribers; - }, - none: void 0, - }); + let data = wsJsonToRes<CommunityResponse>(msg); + if (res) { + res.community_view.subscribed = data.community_view.subscribed; + res.community_view.counts.subscribers = + data.community_view.counts.subscribers; + } this.setState(this.state); } else if (op == UserOperation.GetPosts) { - let data = wsJsonToRes<GetPostsResponse>(msg, GetPostsResponse); + let data = wsJsonToRes<GetPostsResponse>(msg); this.setState({ posts: data.posts, postsLoading: false }); restoreScrollPosition(this.context); setupTippy(); @@ -612,15 +565,15 @@ export class Community extends Component<any, State> { op == UserOperation.FeaturePost || op == UserOperation.SavePost ) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); + let data = wsJsonToRes<PostResponse>(msg); editPostFindRes(data.post_view, this.state.posts); this.setState(this.state); } else if (op == UserOperation.CreatePost) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); + let data = wsJsonToRes<PostResponse>(msg); - let showPostNotifs = UserService.Instance.myUserInfo - .map(m => m.local_user_view.local_user.show_new_post_notifs) - .unwrapOr(false); + let showPostNotifs = + UserService.Instance.myUserInfo?.local_user_view.local_user + .show_new_post_notifs; // Only push these if you're on the first page, you pass the nsfw check, and it isn't blocked // @@ -636,24 +589,17 @@ export class Community extends Component<any, State> { this.setState(this.state); } } else if (op == UserOperation.CreatePostLike) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); + let data = wsJsonToRes<PostResponse>(msg); createPostLikeFindRes(data.post_view, this.state.posts); this.setState(this.state); } else if (op == UserOperation.AddModToCommunity) { - let data = wsJsonToRes<AddModToCommunityResponse>( - msg, - AddModToCommunityResponse - ); - this.state.communityRes.match({ - some: res => (res.moderators = data.moderators), - none: void 0, - }); + let data = wsJsonToRes<AddModToCommunityResponse>(msg); + if (res) { + res.moderators = data.moderators; + } this.setState(this.state); } else if (op == UserOperation.BanFromCommunity) { - let data = wsJsonToRes<BanFromCommunityResponse>( - msg, - BanFromCommunityResponse - ); + let data = wsJsonToRes<BanFromCommunityResponse>(msg); // TODO this might be incorrect this.state.posts @@ -662,18 +608,18 @@ export class Community extends Component<any, State> { this.setState(this.state); } else if (op == UserOperation.GetComments) { - let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse); + let data = wsJsonToRes<GetCommentsResponse>(msg); this.setState({ comments: data.comments, commentsLoading: false }); } else if ( op == UserOperation.EditComment || op == UserOperation.DeleteComment || op == UserOperation.RemoveComment ) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); editCommentRes(data.comment_view, this.state.comments); this.setState(this.state); } else if (op == UserOperation.CreateComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); // Necessary since it might be a user reply if (data.form_id) { @@ -681,41 +627,37 @@ export class Community extends Component<any, State> { this.setState(this.state); } } else if (op == UserOperation.SaveComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); saveCommentRes(data.comment_view, this.state.comments); this.setState(this.state); } else if (op == UserOperation.CreateCommentLike) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); createCommentLikeRes(data.comment_view, this.state.comments); this.setState(this.state); } else if (op == UserOperation.BlockPerson) { - let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse); + let data = wsJsonToRes<BlockPersonResponse>(msg); updatePersonBlock(data); } else if (op == UserOperation.CreatePostReport) { - let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse); + let data = wsJsonToRes<PostReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } } else if (op == UserOperation.CreateCommentReport) { - let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse); + let data = wsJsonToRes<CommentReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } } else if (op == UserOperation.PurgeCommunity) { - let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse); + let data = wsJsonToRes<PurgeItemResponse>(msg); if (data.success) { toast(i18n.t("purge_success")); this.context.router.history.push(`/`); } } else if (op == UserOperation.BlockCommunity) { - let data = wsJsonToRes<BlockCommunityResponse>( - msg, - BlockCommunityResponse - ); - this.state.communityRes.match({ - some: res => (res.community_view.blocked = data.blocked), - none: void 0, - }); + let data = wsJsonToRes<BlockCommunityResponse>(msg); + if (res) { + res.community_view.blocked = data.blocked; + } updateCommunityBlock(data); this.setState(this.state); } diff --git a/src/shared/components/community/create-community.tsx b/src/shared/components/community/create-community.tsx index 5981a9f..da8ef09 100644 --- a/src/shared/components/community/create-community.tsx +++ b/src/shared/components/community/create-community.tsx @@ -1,4 +1,3 @@ -import { None, Some } from "@sniptt/monads"; import { Component } from "inferno"; import { CommunityView, GetSiteResponse } from "lemmy-js-client"; import { Subscription } from "rxjs"; @@ -22,20 +21,19 @@ interface CreateCommunityState { export class CreateCommunity extends Component<any, CreateCommunityState> { private isoData = setIsoData(this.context); - private subscription: Subscription; - private emptyState: CreateCommunityState = { + 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.state = this.emptyState; this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); - if (UserService.Instance.myUserInfo.isNone() && isBrowser()) { + if (!UserService.Instance.myUserInfo && isBrowser()) { toast(i18n.t("not_logged_in"), "danger"); this.context.router.history.push(`/login`); } @@ -43,7 +41,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> { componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -59,8 +57,6 @@ export class CreateCommunity extends Component<any, CreateCommunityState> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> {this.state.loading ? ( <h5> @@ -71,14 +67,11 @@ export class CreateCommunity extends Component<any, CreateCommunityState> { <div className="col-12 col-lg-6 offset-lg-3 mb-4"> <h5>{i18n.t("create_community")}</h5> <CommunityForm - community_view={None} onCreate={this.handleCommunityCreate} enableNsfw={enableNsfw(this.state.siteRes)} allLanguages={this.state.siteRes.all_languages} siteLanguages={this.state.siteRes.discussion_languages} - communityLanguages={Some( - this.state.siteRes.discussion_languages - )} + communityLanguages={this.state.siteRes.discussion_languages} /> </div> </div> diff --git a/src/shared/components/community/sidebar.tsx b/src/shared/components/community/sidebar.tsx index 845ef9e..144075b 100644 --- a/src/shared/components/community/sidebar.tsx +++ b/src/shared/components/community/sidebar.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { @@ -13,7 +12,6 @@ import { PurgeCommunity, RemoveCommunity, SubscribedType, - toUndefined, } from "lemmy-js-client"; import { i18n } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; @@ -21,9 +19,9 @@ import { amAdmin, amMod, amTopMod, - auth, getUnixTime, mdToHtml, + myAuth, numToSI, wsClient, } from "../../utils"; @@ -39,7 +37,7 @@ interface SidebarProps { admins: PersonViewSafe[]; allLanguages: Language[]; siteLanguages: number[]; - communityLanguages: Option<number[]>; + communityLanguages?: number[]; online: number; enableNsfw?: boolean; showIcon?: boolean; @@ -47,31 +45,27 @@ interface SidebarProps { } interface SidebarState { - removeReason: Option<string>; - removeExpires: Option<string>; + removeReason?: string; + removeExpires?: string; showEdit: boolean; showRemoveDialog: boolean; showPurgeDialog: boolean; - purgeReason: Option<string>; + purgeReason?: string; purgeLoading: boolean; showConfirmLeaveModTeam: boolean; } export class Sidebar extends Component<SidebarProps, SidebarState> { - private emptyState: SidebarState = { + state: SidebarState = { showEdit: false, showRemoveDialog: false, - removeReason: None, - removeExpires: None, showPurgeDialog: false, - purgeReason: None, purgeLoading: false, showConfirmLeaveModTeam: false, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleEditCommunity = this.handleEditCommunity.bind(this); this.handleEditCancel = this.handleEditCancel.bind(this); } @@ -83,7 +77,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { this.sidebar() ) : ( <CommunityForm - community_view={Some(this.props.community_view)} + community_view={this.props.community_view} allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} communityLanguages={this.props.communityLanguages} @@ -336,13 +330,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { } description() { - let description = this.props.community_view.community.description; - return description.match({ - some: desc => ( + let desc = this.props.community_view.community.description; + return ( + desc && ( <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} /> - ), - none: <></>, - }); + ) + ); } adminButtons() { @@ -350,7 +343,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { return ( <> <ul className="list-inline mb-1 text-muted font-weight-bold"> - {amMod(Some(this.props.moderators)) && ( + {amMod(this.props.moderators) && ( <> <li className="list-inline-item-action"> <button @@ -362,7 +355,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { <Icon icon="edit" classes="icon-inline" /> </button> </li> - {!amTopMod(Some(this.props.moderators)) && + {!amTopMod(this.props.moderators) && (!this.state.showConfirmLeaveModTeam ? ( <li className="list-inline-item-action"> <button @@ -401,7 +394,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { </li> </> ))} - {amTopMod(Some(this.props.moderators)) && ( + {amTopMod(this.props.moderators) && ( <li className="list-inline-item-action"> <button className="btn btn-link text-muted d-inline-block" @@ -466,7 +459,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { id="remove-reason" className="form-control mr-2" placeholder={i18n.t("optional")} - value={toUndefined(this.state.removeReason)} + value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> </div> @@ -496,7 +489,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { id="purge-reason" className="form-control mr-2" placeholder={i18n.t("reason")} - value={toUndefined(this.state.purgeReason)} + value={this.state.purgeReason} onInput={linkEvent(this, this.handlePurgeReasonChange)} /> </div> @@ -533,12 +526,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { handleDeleteClick(i: Sidebar, event: any) { event.preventDefault(); - let deleteForm = new DeleteCommunity({ - community_id: i.props.community_view.community.id, - deleted: !i.props.community_view.community.deleted, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm)); + let auth = myAuth(); + if (auth) { + let 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) { @@ -546,19 +542,18 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { } handleLeaveModTeamClick(i: Sidebar) { - UserService.Instance.myUserInfo.match({ - some: mui => { - let form = new AddModToCommunity({ - person_id: mui.local_user_view.person.id, - community_id: i.props.community_view.community.id, - added: false, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.addModToCommunity(form)); - i.setState({ showConfirmLeaveModTeam: false }); - }, - none: void 0, - }); + let mui = UserService.Instance.myUserInfo; + let auth = myAuth(); + if (auth && mui) { + let 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) { @@ -568,46 +563,50 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { handleUnsubscribe(i: Sidebar, event: any) { event.preventDefault(); let community_id = i.props.community_view.community.id; - let form = new FollowCommunity({ - community_id, - follow: false, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.followCommunity(form)); + let auth = myAuth(); + if (auth) { + let form: FollowCommunity = { + community_id, + follow: false, + auth, + }; + WebSocketService.Instance.send(wsClient.followCommunity(form)); + } // Update myUserInfo - UserService.Instance.myUserInfo.match({ - some: mui => - (mui.follows = mui.follows.filter(i => i.community.id != community_id)), - none: void 0, - }); + let mui = UserService.Instance.myUserInfo; + if (mui) { + mui.follows = mui.follows.filter(i => i.community.id != community_id); + } } handleSubscribe(i: Sidebar, event: any) { event.preventDefault(); let community_id = i.props.community_view.community.id; - let form = new FollowCommunity({ - community_id, - follow: true, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.followCommunity(form)); + let auth = myAuth(); + if (auth) { + let form: FollowCommunity = { + community_id, + follow: true, + auth, + }; + WebSocketService.Instance.send(wsClient.followCommunity(form)); + } // Update myUserInfo - UserService.Instance.myUserInfo.match({ - some: mui => - mui.follows.push({ - community: i.props.community_view.community, - follower: mui.local_user_view.person, - }), - none: void 0, - }); + let 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 || - amMod(Some(this.props.moderators)) || + amMod(this.props.moderators) || amAdmin() ); } @@ -617,25 +616,28 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { } handleModRemoveReasonChange(i: Sidebar, event: any) { - i.setState({ removeReason: Some(event.target.value) }); + i.setState({ removeReason: event.target.value }); } handleModRemoveExpiresChange(i: Sidebar, event: any) { - i.setState({ removeExpires: Some(event.target.value) }); + i.setState({ removeExpires: event.target.value }); } handleModRemoveSubmit(i: Sidebar, event: any) { event.preventDefault(); - let removeForm = new RemoveCommunity({ - community_id: i.props.community_view.community.id, - removed: !i.props.community_view.community.removed, - reason: i.state.removeReason, - expires: i.state.removeExpires.map(getUnixTime), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.removeCommunity(removeForm)); - - i.setState({ showRemoveDialog: false }); + let auth = myAuth(); + if (auth) { + let 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) { @@ -643,39 +645,51 @@ export class Sidebar extends Component<SidebarProps, SidebarState> { } handlePurgeReasonChange(i: Sidebar, event: any) { - i.setState({ purgeReason: Some(event.target.value) }); + i.setState({ purgeReason: event.target.value }); } handlePurgeSubmit(i: Sidebar, event: any) { event.preventDefault(); - let form = new PurgeCommunity({ - community_id: i.props.community_view.community.id, - reason: i.state.purgeReason, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.purgeCommunity(form)); - - i.setState({ purgeLoading: true }); + let auth = myAuth(); + if (auth) { + let form: PurgeCommunity = { + community_id: i.props.community_view.community.id, + reason: i.state.purgeReason, + auth, + }; + WebSocketService.Instance.send(wsClient.purgeCommunity(form)); + i.setState({ purgeLoading: true }); + } } handleBlock(i: Sidebar, event: any) { event.preventDefault(); - let blockCommunityForm = new BlockCommunity({ - community_id: i.props.community_view.community.id, - block: true, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm)); + let auth = myAuth(); + if (auth) { + let blockCommunityForm: BlockCommunity = { + community_id: i.props.community_view.community.id, + block: true, + auth, + }; + WebSocketService.Instance.send( + wsClient.blockCommunity(blockCommunityForm) + ); + } } handleUnblock(i: Sidebar, event: any) { event.preventDefault(); - let blockCommunityForm = new BlockCommunity({ - community_id: i.props.community_view.community.id, - block: false, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm)); + let auth = myAuth(); + if (auth) { + let blockCommunityForm: BlockCommunity = { + community_id: i.props.community_view.community.id, + block: false, + auth, + }; + WebSocketService.Instance.send( + wsClient.blockCommunity(blockCommunityForm) + ); + } } } diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx index b077e8e..b590988 100644 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@ -1,4 +1,3 @@ -import { None } from "@sniptt/monads"; import autosize from "autosize"; import { Component, linkEvent } from "inferno"; import { @@ -16,9 +15,9 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { WebSocketService } from "../../services"; import { - auth, capitalizeFirstLetter, isBrowser, + myAuth, randomStr, setIsoData, showLocal, @@ -40,20 +39,18 @@ interface AdminSettingsState { export class AdminSettings extends Component<any, AdminSettingsState> { private siteConfigTextAreaId = `site-config-${randomStr()}`; - private isoData = setIsoData(this.context, BannedPersonsResponse); - private subscription: Subscription; - private emptyState: AdminSettingsState = { + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: AdminSettingsState = { siteRes: this.isoData.site_res, banned: [], loading: true, - leaveAdminTeamLoading: null, + leaveAdminTeamLoading: false, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; - this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); @@ -65,19 +62,25 @@ export class AdminSettings extends Component<any, AdminSettingsState> { loading: false, }; } else { - WebSocketService.Instance.send( - wsClient.getBannedPersons({ - auth: auth().unwrap(), - }) - ); + let cAuth = myAuth(); + if (cAuth) { + WebSocketService.Instance.send( + wsClient.getBannedPersons({ + auth: cAuth, + }) + ); + } } } static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { let promises: Promise<any>[] = []; - let bannedPersonsForm = new GetBannedPersons({ auth: req.auth.unwrap() }); - promises.push(req.client.getBannedPersons(bannedPersonsForm)); + let auth = req.auth; + if (auth) { + let bannedPersonsForm: GetBannedPersons = { auth }; + promises.push(req.client.getBannedPersons(bannedPersonsForm)); + } return promises; } @@ -91,7 +94,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> { componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -114,8 +117,6 @@ export class AdminSettings extends Component<any, AdminSettingsState> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <SiteForm siteRes={this.state.siteRes} @@ -179,10 +180,11 @@ export class AdminSettings extends Component<any, AdminSettingsState> { } handleLeaveAdminTeam(i: AdminSettings) { - i.setState({ leaveAdminTeamLoading: true }); - WebSocketService.Instance.send( - wsClient.leaveAdmin({ auth: auth().unwrap() }) - ); + let auth = myAuth(); + if (auth) { + i.setState({ leaveAdminTeamLoading: true }); + WebSocketService.Instance.send(wsClient.leaveAdmin({ auth })); + } } parseMessage(msg: any) { @@ -194,14 +196,14 @@ export class AdminSettings extends Component<any, AdminSettingsState> { this.setState({ loading: false }); return; } else if (op == UserOperation.EditSite) { - let data = wsJsonToRes<SiteResponse>(msg, SiteResponse); + let 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) { - let data = wsJsonToRes<BannedPersonsResponse>(msg, BannedPersonsResponse); + let data = wsJsonToRes<BannedPersonsResponse>(msg); this.setState({ banned: data.banned, loading: false }); } else if (op == UserOperation.LeaveAdmin) { - let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse); + let data = wsJsonToRes<GetSiteResponse>(msg); this.setState(s => ((s.siteRes.site_view = data.site_view), s)); this.setState({ leaveAdminTeamLoading: false }); diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index 2b51e8e..b0187c9 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { Link } from "inferno-router"; @@ -37,7 +36,6 @@ import { } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { - auth, canCreateCommunity, commentsToFlatNodes, createCommentLikeRes, @@ -55,6 +53,7 @@ import { isBrowser, isPostBlocked, mdToHtml, + myAuth, notifyPost, nsfwCheck, postToCommentSortType, @@ -96,7 +95,7 @@ interface HomeState { showSidebarMobile: boolean; subscribedCollapsed: boolean; loading: boolean; - tagline: Option<string>; + tagline?: string; } interface HomeProps { @@ -114,14 +113,9 @@ interface UrlParams { } export class Home extends Component<any, HomeState> { - private isoData = setIsoData( - this.context, - GetPostsResponse, - GetCommentsResponse, - ListCommunitiesResponse - ); - private subscription: Subscription; - private emptyState: HomeState = { + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: HomeState = { trendingCommunities: [], siteRes: this.isoData.site_res, showSubscribedMobile: false, @@ -140,13 +134,11 @@ export class Home extends Component<any, HomeState> { dataType: getDataTypeFromProps(this.props), sort: getSortTypeFromProps(this.props), page: getPageFromProps(this.props), - tagline: None, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleSortChange = this.handleSortChange.bind(this); this.handleListingTypeChange = this.handleListingTypeChange.bind(this); this.handleDataTypeChange = this.handleDataTypeChange.bind(this); @@ -157,16 +149,20 @@ export class Home extends Component<any, HomeState> { // Only fetch the data if coming from another route if (this.isoData.path == this.context.router.route.match.url) { - let postsRes = Some(this.isoData.routeData[0] as GetPostsResponse); - let commentsRes = Some(this.isoData.routeData[1] as GetCommentsResponse); - let trendingRes = this.isoData.routeData[2] as ListCommunitiesResponse; - - if (postsRes.isSome()) { - this.state = { ...this.state, posts: postsRes.unwrap().posts }; + let postsRes = this.isoData.routeData[0] as GetPostsResponse | undefined; + let commentsRes = this.isoData.routeData[1] as + | GetCommentsResponse + | undefined; + let trendingRes = this.isoData.routeData[2] as + | ListCommunitiesResponse + | undefined; + + if (postsRes) { + this.state = { ...this.state, posts: postsRes.posts }; } - if (commentsRes.isSome()) { - this.state = { ...this.state, comments: commentsRes.unwrap().comments }; + if (commentsRes) { + this.state = { ...this.state, comments: commentsRes.comments }; } if (isBrowser()) { @@ -177,9 +173,9 @@ export class Home extends Component<any, HomeState> { const taglines = this.state.siteRes.taglines; this.state = { ...this.state, - trendingCommunities: trendingRes.communities, + trendingCommunities: trendingRes?.communities ?? [], loading: false, - tagline: taglines.map(tls => getRandomFromList(tls).content), + tagline: getRandomFromList(taglines)?.content, }; } else { this.fetchTrendingCommunities(); @@ -188,13 +184,12 @@ export class Home extends Component<any, HomeState> { } fetchTrendingCommunities() { - let listCommunitiesForm = new ListCommunities({ - type_: Some(ListingType.Local), - sort: Some(SortType.Hot), - limit: Some(trendingFetchLimit), - page: None, - auth: auth(false).ok(), - }); + let listCommunitiesForm: ListCommunities = { + type_: ListingType.Local, + sort: SortType.Hot, + limit: trendingFetchLimit, + auth: myAuth(false), + }; WebSocketService.Instance.send( wsClient.listCommunities(listCommunitiesForm) ); @@ -210,7 +205,7 @@ export class Home extends Component<any, HomeState> { componentWillUnmount() { saveScrollPosition(this.context); - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } static getDerivedStateFromProps( @@ -230,74 +225,60 @@ export class Home extends Component<any, HomeState> { let dataType: DataType = pathSplit[3] ? DataType[pathSplit[3]] : DataType.Post; + let mui = UserService.Instance.myUserInfo; + let auth = req.auth; // TODO figure out auth default_listingType, default_sort_type - let type_: Option<ListingType> = Some( - pathSplit[5] - ? ListingType[pathSplit[5]] - : UserService.Instance.myUserInfo.match({ - some: mui => - Object.values(ListingType)[ - mui.local_user_view.local_user.default_listing_type - ], - none: ListingType.Local, - }) - ); - let sort: Option<SortType> = Some( - pathSplit[7] - ? SortType[pathSplit[7]] - : UserService.Instance.myUserInfo.match({ - some: mui => - Object.values(SortType)[ - mui.local_user_view.local_user.default_sort_type - ], - none: SortType.Active, - }) - ); - - let page = Some(pathSplit[9] ? Number(pathSplit[9]) : 1); + let type_: ListingType = pathSplit[5] + ? ListingType[pathSplit[5]] + : mui + ? Object.values(ListingType)[ + mui.local_user_view.local_user.default_listing_type + ] + : ListingType.Local; + let sort: SortType = pathSplit[7] + ? SortType[pathSplit[7]] + : mui + ? (Object.values(SortType)[ + mui.local_user_view.local_user.default_sort_type + ] as SortType) + : SortType.Active; + + let page = pathSplit[9] ? Number(pathSplit[9]) : 1; let promises: Promise<any>[] = []; if (dataType == DataType.Post) { - let getPostsForm = new GetPosts({ - community_id: None, - community_name: None, + let getPostsForm: GetPosts = { type_, page, - limit: Some(fetchLimit), + limit: fetchLimit, sort, - saved_only: Some(false), - auth: req.auth, - }); + saved_only: false, + auth, + }; promises.push(req.client.getPosts(getPostsForm)); promises.push(Promise.resolve()); } else { - let getCommentsForm = new GetComments({ - community_id: None, - community_name: None, + let getCommentsForm: GetComments = { page, - limit: Some(fetchLimit), - max_depth: None, - sort: sort.map(postToCommentSortType), + limit: fetchLimit, + sort: postToCommentSortType(sort), type_, - saved_only: Some(false), - post_id: None, - parent_id: None, - auth: req.auth, - }); + saved_only: false, + auth, + }; promises.push(Promise.resolve()); promises.push(req.client.getComments(getCommentsForm)); } - let trendingCommunitiesForm = new ListCommunities({ - type_: Some(ListingType.Local), - sort: Some(SortType.Hot), - limit: Some(trendingFetchLimit), - page: None, - auth: req.auth, - }); + let trendingCommunitiesForm: ListCommunities = { + type_: ListingType.Local, + sort: SortType.Hot, + limit: trendingFetchLimit, + auth, + }; promises.push(req.client.listCommunities(trendingCommunitiesForm)); return promises; @@ -317,33 +298,28 @@ export class Home extends Component<any, HomeState> { get documentTitle(): string { let siteView = this.state.siteRes.site_view; - return this.state.siteRes.site_view.site.description.match({ - some: desc => `${siteView.site.name} - ${desc}`, - none: siteView.site.name, - }); + let desc = this.state.siteRes.site_view.site.description; + return desc ? `${siteView.site.name} - ${desc}` : siteView.site.name; } render() { + let tagline = this.state.tagline; + return ( <div className="container-lg"> <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> {this.state.siteRes.site_view.local_site.site_setup && ( <div className="row"> <main role="main" className="col-12 col-md-8"> - {this.state.tagline.match({ - some: tagline => ( - <div - id="tagline" - dangerouslySetInnerHTML={mdToHtml(tagline)} - ></div> - ), - none: <></>, - })} + {tagline && ( + <div + id="tagline" + dangerouslySetInnerHTML={mdToHtml(tagline)} + ></div> + )} <div className="d-block d-md-none">{this.mobileView()}</div> {this.posts()} </main> @@ -357,10 +333,8 @@ export class Home extends Component<any, HomeState> { } get hasFollows(): boolean { - return UserService.Instance.myUserInfo.match({ - some: mui => mui.follows.length > 0, - none: false, - }); + let mui = UserService.Instance.myUserInfo; + return !!mui && mui.follows.length > 0; } mobileView() { @@ -412,9 +386,9 @@ export class Home extends Component<any, HomeState> { {this.state.showSidebarMobile && ( <SiteSidebar site={siteView.site} - admins={Some(siteRes.admins)} - counts={Some(siteView.counts)} - online={Some(siteRes.online)} + admins={siteRes.admins} + counts={siteView.counts} + online={siteRes.online} showLocal={showLocal(this.isoData)} /> )} @@ -450,9 +424,9 @@ export class Home extends Component<any, HomeState> { </div> <SiteSidebar site={siteView.site} - admins={Some(siteRes.admins)} - counts={Some(siteView.counts)} - online={Some(siteRes.online)} + admins={siteRes.admins} + counts={siteView.counts} + online={siteRes.online} showLocal={showLocal(this.isoData)} /> {this.hasFollows && ( @@ -532,17 +506,14 @@ export class Home extends Component<any, HomeState> { </h5> {!this.state.subscribedCollapsed && ( <ul className="list-inline mb-0"> - {UserService.Instance.myUserInfo - .map(m => m.follows) - .unwrapOr([]) - .map(cfv => ( - <li - key={cfv.community.id} - className="list-inline-item d-inline-block" - > - <CommunityLink community={cfv.community} /> - </li> - ))} + {UserService.Instance.myUserInfo?.follows.map(cfv => ( + <li + key={cfv.community.id} + className="list-inline-item d-inline-block" + > + <CommunityLink community={cfv.community} /> + </li> + ))} </ul> )} </div> @@ -595,9 +566,6 @@ export class Home extends Component<any, HomeState> { <CommentNodes nodes={commentsToFlatNodes(this.state.comments)} viewType={CommentViewType.Flat} - moderators={None} - admins={None} - maxCommentsShown={None} noIndent showCommunity showContext @@ -611,9 +579,10 @@ export class Home extends Component<any, HomeState> { selects() { let allRss = `/feeds/all.xml?sort=${this.state.sort}`; let localRss = `/feeds/local.xml?sort=${this.state.sort}`; - let frontRss = auth(false) - .ok() - .map(auth => `/feeds/front/${auth}.xml?sort=${this.state.sort}`); + let auth = myAuth(false); + let frontRss = auth + ? `/feeds/front/${auth}.xml?sort=${this.state.sort}` + : undefined; return ( <div className="mb-3"> @@ -650,18 +619,14 @@ export class Home extends Component<any, HomeState> { <link rel="alternate" type="application/atom+xml" href={localRss} /> </> )} - {this.state.listingType == ListingType.Subscribed && - frontRss.match({ - some: rss => ( - <> - <a href={rss} title="RSS" rel={relTags}> - <Icon icon="rss" classes="text-muted small" /> - </a> - <link rel="alternate" type="application/atom+xml" href={rss} /> - </> - ), - none: <></>, - })} + {this.state.listingType == ListingType.Subscribed && frontRss && ( + <> + <a href={frontRss} title="RSS" rel={relTags}> + <Icon icon="rss" classes="text-muted small" /> + </a> + <link rel="alternate" type="application/atom+xml" href={frontRss} /> + </> + )} </div> ); } @@ -703,33 +668,27 @@ export class Home extends Component<any, HomeState> { } fetchData() { + let auth = myAuth(false); if (this.state.dataType == DataType.Post) { - let getPostsForm = new GetPosts({ - community_id: None, - community_name: None, - page: Some(this.state.page), - limit: Some(fetchLimit), - sort: Some(this.state.sort), - saved_only: Some(false), - auth: auth(false).ok(), - type_: Some(this.state.listingType), - }); + let getPostsForm: GetPosts = { + page: this.state.page, + limit: fetchLimit, + sort: this.state.sort, + saved_only: false, + type_: this.state.listingType, + auth, + }; WebSocketService.Instance.send(wsClient.getPosts(getPostsForm)); } else { - let getCommentsForm = new GetComments({ - community_id: None, - community_name: None, - page: Some(this.state.page), - limit: Some(fetchLimit), - max_depth: None, - sort: Some(postToCommentSortType(this.state.sort)), - saved_only: Some(false), - post_id: None, - parent_id: None, - auth: auth(false).ok(), - type_: Some(this.state.listingType), - }); + let getCommentsForm: GetComments = { + page: this.state.page, + limit: fetchLimit, + sort: postToCommentSortType(this.state.sort), + saved_only: false, + type_: this.state.listingType, + auth, + }; WebSocketService.Instance.send(wsClient.getComments(getCommentsForm)); } } @@ -746,17 +705,14 @@ export class Home extends Component<any, HomeState> { ); this.fetchData(); } else if (op == UserOperation.ListCommunities) { - let data = wsJsonToRes<ListCommunitiesResponse>( - msg, - ListCommunitiesResponse - ); + let data = wsJsonToRes<ListCommunitiesResponse>(msg); this.setState({ trendingCommunities: data.communities }); } else if (op == UserOperation.EditSite) { - let data = wsJsonToRes<SiteResponse>(msg, SiteResponse); + let data = wsJsonToRes<SiteResponse>(msg); this.setState(s => ((s.siteRes.site_view = data.site_view), s)); toast(i18n.t("site_saved")); } else if (op == UserOperation.GetPosts) { - let data = wsJsonToRes<GetPostsResponse>(msg, GetPostsResponse); + let data = wsJsonToRes<GetPostsResponse>(msg); this.setState({ posts: data.posts, loading: false }); WebSocketService.Instance.send( wsClient.communityJoin({ community_id: 0 }) @@ -764,11 +720,10 @@ export class Home extends Component<any, HomeState> { restoreScrollPosition(this.context); setupTippy(); } else if (op == UserOperation.CreatePost) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); + let data = wsJsonToRes<PostResponse>(msg); + let mui = UserService.Instance.myUserInfo; - let showPostNotifs = UserService.Instance.myUserInfo - .map(m => m.local_user_view.local_user.show_new_post_notifs) - .unwrapOr(false); + let showPostNotifs = mui?.local_user_view.local_user.show_new_post_notifs; // Only push these if you're on the first page, you pass the nsfw check, and it isn't blocked if ( @@ -779,9 +734,7 @@ export class Home extends Component<any, HomeState> { // If you're on subscribed, only push it if you're subscribed. if (this.state.listingType == ListingType.Subscribed) { if ( - UserService.Instance.myUserInfo - .map(m => m.follows) - .unwrapOr([]) + mui?.follows .map(c => c.community.id) .includes(data.post_view.community.id) ) { @@ -814,45 +767,43 @@ export class Home extends Component<any, HomeState> { op == UserOperation.FeaturePost || op == UserOperation.SavePost ) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); + let data = wsJsonToRes<PostResponse>(msg); editPostFindRes(data.post_view, this.state.posts); this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); + let data = wsJsonToRes<PostResponse>(msg); createPostLikeFindRes(data.post_view, this.state.posts); this.setState(this.state); } else if (op == UserOperation.AddAdmin) { - let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse); + let data = wsJsonToRes<AddAdminResponse>(msg); this.setState(s => ((s.siteRes.admins = data.admins), s)); } else if (op == UserOperation.BanPerson) { - let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse); + let data = wsJsonToRes<BanPersonResponse>(msg); this.state.posts .filter(p => p.creator.id == data.person_view.person.id) .forEach(p => (p.creator.banned = data.banned)); this.setState(this.state); } else if (op == UserOperation.GetComments) { - let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse); + let data = wsJsonToRes<GetCommentsResponse>(msg); this.setState({ comments: data.comments, loading: false }); } else if ( op == UserOperation.EditComment || op == UserOperation.DeleteComment || op == UserOperation.RemoveComment ) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); editCommentRes(data.comment_view, this.state.comments); this.setState(this.state); } else if (op == UserOperation.CreateComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); // Necessary since it might be a user reply if (data.form_id) { // If you're on subscribed, only push it if you're subscribed. if (this.state.listingType == ListingType.Subscribed) { if ( - UserService.Instance.myUserInfo - .map(m => m.follows) - .unwrapOr([]) + UserService.Instance.myUserInfo?.follows .map(c => c.community.id) .includes(data.comment_view.community.id) ) { @@ -864,23 +815,23 @@ export class Home extends Component<any, HomeState> { this.setState(this.state); } } else if (op == UserOperation.SaveComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); saveCommentRes(data.comment_view, this.state.comments); this.setState(this.state); } else if (op == UserOperation.CreateCommentLike) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); createCommentLikeRes(data.comment_view, this.state.comments); this.setState(this.state); } else if (op == UserOperation.BlockPerson) { - let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse); + let data = wsJsonToRes<BlockPersonResponse>(msg); updatePersonBlock(data); } else if (op == UserOperation.CreatePostReport) { - let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse); + let data = wsJsonToRes<PostReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } } else if (op == UserOperation.CreateCommentReport) { - let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse); + let data = wsJsonToRes<CommentReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } @@ -890,7 +841,7 @@ export class Home extends Component<any, HomeState> { op == UserOperation.PurgeComment || op == UserOperation.PurgeCommunity ) { - let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse); + let data = wsJsonToRes<PurgeItemResponse>(msg); if (data.success) { toast(i18n.t("purge_success")); this.context.router.history.push(`/`); diff --git a/src/shared/components/home/instances.tsx b/src/shared/components/home/instances.tsx index 434fae9..0870f65 100644 --- a/src/shared/components/home/instances.tsx +++ b/src/shared/components/home/instances.tsx @@ -1,4 +1,3 @@ -import { None } from "@sniptt/monads"; import { Component } from "inferno"; import { GetSiteResponse } from "lemmy-js-client"; import { i18n } from "../../i18next"; @@ -11,13 +10,12 @@ interface InstancesState { export class Instances extends Component<any, InstancesState> { private isoData = setIsoData(this.context); - private emptyState: InstancesState = { + state: InstancesState = { siteRes: this.isoData.site_res, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; } get documentTitle(): string { @@ -25,45 +23,37 @@ export class Instances extends Component<any, InstancesState> { } render() { - return this.state.siteRes.federated_instances.match({ - some: federated_instances => ( - <div className="container-lg"> - <HtmlTags - title={this.documentTitle} - path={this.context.router.route.match.url} - description={None} - image={None} - /> - <div className="row"> - <div className="col-md-6"> - <h5>{i18n.t("linked_instances")}</h5> - {this.itemList(federated_instances.linked)} - </div> - {federated_instances.allowed.match({ - some: allowed => - allowed.length > 0 && ( - <div className="col-md-6"> - <h5>{i18n.t("allowed_instances")}</h5> - {this.itemList(allowed)} - </div> - ), - none: <></>, - })} - {federated_instances.blocked.match({ - some: blocked => - blocked.length > 0 && ( - <div className="col-md-6"> - <h5>{i18n.t("blocked_instances")}</h5> - {this.itemList(blocked)} - </div> - ), - none: <></>, - })} + let federated_instances = this.state.siteRes.federated_instances; + return federated_instances ? ( + <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> - ), - none: <></>, - }); + </div> + ) : ( + <></> + ); } itemList(items: string[]) { diff --git a/src/shared/components/home/legal.tsx b/src/shared/components/home/legal.tsx index 3930db2..d2c9953 100644 --- a/src/shared/components/home/legal.tsx +++ b/src/shared/components/home/legal.tsx @@ -1,4 +1,3 @@ -import { None } from "@sniptt/monads"; import { Component } from "inferno"; import { GetSiteResponse } from "lemmy-js-client"; import { i18n } from "../../i18next"; @@ -11,13 +10,12 @@ interface LegalState { export class Legal extends Component<any, LegalState> { private isoData = setIsoData(this.context); - private emptyState: LegalState = { + state: LegalState = { siteRes: this.isoData.site_res, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; } get documentTitle(): string { @@ -25,20 +23,16 @@ export class Legal extends Component<any, LegalState> { } render() { + let legal = this.state.siteRes.site_view.local_site.legal_information; return ( <div className="container-lg"> <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> - {this.state.siteRes.site_view.local_site.legal_information.match({ - some: legal => ( - <div className="md-div" dangerouslySetInnerHTML={mdToHtml(legal)} /> - ), - none: <></>, - })} + {legal && ( + <div className="md-div" dangerouslySetInnerHTML={mdToHtml(legal)} /> + )} </div> ); } diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx index 650b317..7cdde92 100644 --- a/src/shared/components/home/login.tsx +++ b/src/shared/components/home/login.tsx @@ -1,8 +1,7 @@ -import { None } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { GetSiteResponse, - Login as LoginForm, + Login as LoginI, LoginResponse, PasswordReset, UserOperation, @@ -24,20 +23,20 @@ import { HtmlTags } from "../common/html-tags"; import { Spinner } from "../common/icon"; interface State { - loginForm: LoginForm; + form: { + username_or_email?: string; + password?: string; + }; loginLoading: boolean; siteRes: GetSiteResponse; } export class Login extends Component<any, State> { private isoData = setIsoData(this.context); - private subscription: Subscription; + private subscription?: Subscription; - emptyState: State = { - loginForm: new LoginForm({ - username_or_email: undefined, - password: undefined, - }), + state: State = { + form: {}, loginLoading: false, siteRes: this.isoData.site_res, }; @@ -45,8 +44,6 @@ export class Login extends Component<any, State> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; - this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); @@ -57,14 +54,14 @@ export class Login extends Component<any, State> { componentDidMount() { // Navigate to home if already logged in - if (UserService.Instance.myUserInfo.isSome()) { + if (UserService.Instance.myUserInfo) { this.context.router.history.push("/"); } } componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -82,8 +79,6 @@ export class Login extends Component<any, State> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <div className="row"> <div className="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</div> @@ -109,7 +104,7 @@ export class Login extends Component<any, State> { type="text" className="form-control" id="login-email-or-username" - value={this.state.loginForm.username_or_email} + value={this.state.form.username_or_email} onInput={linkEvent(this, this.handleLoginUsernameChange)} autoComplete="email" required @@ -125,7 +120,7 @@ export class Login extends Component<any, State> { <input type="password" id="login-password" - value={this.state.loginForm.password} + value={this.state.form.password} onInput={linkEvent(this, this.handleLoginPasswordChange)} className="form-control" autoComplete="current-password" @@ -136,7 +131,10 @@ export class Login extends Component<any, State> { type="button" onClick={linkEvent(this, this.handlePasswordReset)} className="btn p-0 btn-link d-inline-block float-right text-muted small font-weight-bold pointer-events not-allowed" - disabled={!validEmail(this.state.loginForm.username_or_email)} + disabled={ + !!this.state.form.username_or_email && + !validEmail(this.state.form.username_or_email) + } title={i18n.t("no_password_reset")} > {i18n.t("forgot_password")} @@ -158,25 +156,35 @@ export class Login extends Component<any, State> { handleLoginSubmit(i: Login, event: any) { event.preventDefault(); i.setState({ loginLoading: true }); - WebSocketService.Instance.send(wsClient.login(i.state.loginForm)); + let lForm = i.state.form; + let username_or_email = lForm.username_or_email; + let password = lForm.password; + if (username_or_email && password) { + let form: LoginI = { + username_or_email, + password, + }; + WebSocketService.Instance.send(wsClient.login(form)); + } } handleLoginUsernameChange(i: Login, event: any) { - i.state.loginForm.username_or_email = event.target.value; + i.state.form.username_or_email = event.target.value; i.setState(i.state); } handleLoginPasswordChange(i: Login, event: any) { - i.state.loginForm.password = event.target.value; + i.state.form.password = event.target.value; i.setState(i.state); } handlePasswordReset(i: Login, event: any) { event.preventDefault(); - let resetForm = new PasswordReset({ - email: i.state.loginForm.username_or_email, - }); - WebSocketService.Instance.send(wsClient.passwordReset(resetForm)); + let email = i.state.form.username_or_email; + if (email) { + let resetForm: PasswordReset = { email }; + WebSocketService.Instance.send(wsClient.passwordReset(resetForm)); + } } parseMessage(msg: any) { @@ -184,19 +192,18 @@ export class Login extends Component<any, State> { console.log(msg); if (msg.error) { toast(i18n.t(msg.error), "danger"); - this.setState(this.emptyState); + this.setState({ form: {} }); return; } else { if (op == UserOperation.Login) { - let data = wsJsonToRes<LoginResponse>(msg, LoginResponse); - this.setState(this.emptyState); + let data = wsJsonToRes<LoginResponse>(msg); UserService.Instance.login(data); this.props.history.push("/"); location.reload(); } else if (op == UserOperation.PasswordReset) { toast(i18n.t("reset_password_mail_sent")); } else if (op == UserOperation.GetSite) { - let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse); + let data = wsJsonToRes<GetSiteResponse>(msg); this.setState({ siteRes: data }); } } diff --git a/src/shared/components/home/setup.tsx b/src/shared/components/home/setup.tsx index 943e74a..726edc3 100644 --- a/src/shared/components/home/setup.tsx +++ b/src/shared/components/home/setup.tsx @@ -1,11 +1,9 @@ -import { None, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { Helmet } from "inferno-helmet"; import { GetSiteResponse, LoginResponse, Register, - toUndefined, UserOperation, wsJsonToRes, wsUserOp, @@ -19,7 +17,17 @@ import { Spinner } from "../common/icon"; import { SiteForm } from "./site-form"; interface State { - userForm: Register; + form: { + username?: string; + email?: string; + password?: string; + password_verify?: string; + show_nsfw: boolean; + captcha_uuid?: string; + captcha_answer?: string; + honeypot?: string; + answer?: string; + }; doneRegisteringUser: boolean; userLoading: boolean; siteRes: GetSiteResponse; @@ -29,20 +37,11 @@ export class Setup extends Component<any, State> { private subscription: Subscription; private isoData = setIsoData(this.context); - private emptyState: State = { - userForm: new Register({ - username: undefined, - password: undefined, - password_verify: undefined, + state: State = { + form: { show_nsfw: true, - // The first admin signup doesn't need a captcha - captcha_uuid: None, - captcha_answer: None, - email: None, - honeypot: None, - answer: None, - }), - doneRegisteringUser: UserService.Instance.myUserInfo.isSome(), + }, + doneRegisteringUser: !!UserService.Instance.myUserInfo, userLoading: false, siteRes: this.isoData.site_res, }; @@ -50,8 +49,6 @@ export class Setup extends Component<any, State> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; - this.subscription = WebSocketService.Instance.subject .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) .subscribe( @@ -100,7 +97,7 @@ export class Setup extends Component<any, State> { type="text" className="form-control" id="username" - value={this.state.userForm.username} + value={this.state.form.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} @@ -119,7 +116,7 @@ export class Setup extends Component<any, State> { id="email" className="form-control" placeholder={i18n.t("optional")} - value={toUndefined(this.state.userForm.email)} + value={this.state.form.email} onInput={linkEvent(this, this.handleRegisterEmailChange)} minLength={3} /> @@ -133,7 +130,7 @@ export class Setup extends Component<any, State> { <input type="password" id="password" - value={this.state.userForm.password} + value={this.state.form.password} onInput={linkEvent(this, this.handleRegisterPasswordChange)} className="form-control" required @@ -151,7 +148,7 @@ export class Setup extends Component<any, State> { <input type="password" id="verify-password" - value={this.state.userForm.password_verify} + value={this.state.form.password_verify} onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} className="form-control" required @@ -176,26 +173,40 @@ export class Setup extends Component<any, State> { event.preventDefault(); i.setState({ userLoading: true }); event.preventDefault(); - WebSocketService.Instance.send(wsClient.register(i.state.userForm)); + let cForm = i.state.form; + if (cForm.username && cForm.password && cForm.password_verify) { + let 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)); + } } handleRegisterUsernameChange(i: Setup, event: any) { - i.state.userForm.username = event.target.value; + i.state.form.username = event.target.value; i.setState(i.state); } handleRegisterEmailChange(i: Setup, event: any) { - i.state.userForm.email = Some(event.target.value); + i.state.form.email = event.target.value; i.setState(i.state); } handleRegisterPasswordChange(i: Setup, event: any) { - i.state.userForm.password = event.target.value; + i.state.form.password = event.target.value; i.setState(i.state); } handleRegisterPasswordVerifyChange(i: Setup, event: any) { - i.state.userForm.password_verify = event.target.value; + i.state.form.password_verify = event.target.value; i.setState(i.state); } @@ -206,10 +217,10 @@ export class Setup extends Component<any, State> { this.setState({ userLoading: false }); return; } else if (op == UserOperation.Register) { - let data = wsJsonToRes<LoginResponse>(msg, LoginResponse); + let data = wsJsonToRes<LoginResponse>(msg); this.setState({ userLoading: false }); UserService.Instance.login(data); - if (UserService.Instance.jwtInfo.isSome()) { + if (UserService.Instance.jwtInfo) { this.setState({ doneRegisteringUser: true }); } } else if (op == UserOperation.CreateSite) { diff --git a/src/shared/components/home/signup.tsx b/src/shared/components/home/signup.tsx index 7e35ecd..5deb802 100644 --- a/src/shared/components/home/signup.tsx +++ b/src/shared/components/home/signup.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Options, passwordStrength } from "check-password-strength"; import { I18nKeys } from "i18next"; import { Component, linkEvent } from "inferno"; @@ -9,8 +8,8 @@ import { GetSiteResponse, LoginResponse, Register, + RegistrationMode, SiteView, - toUndefined, UserOperation, wsJsonToRes, wsUserOp, @@ -60,32 +59,33 @@ const passwordStrengthOptions: Options<string> = [ ]; interface State { - registerForm: Register; + form: { + username?: string; + email?: string; + password?: string; + password_verify?: string; + show_nsfw: boolean; + captcha_uuid?: string; + captcha_answer?: string; + honeypot?: string; + answer?: string; + }; registerLoading: boolean; - captcha: Option<GetCaptchaResponse>; + captcha?: GetCaptchaResponse; captchaPlaying: boolean; siteRes: GetSiteResponse; } export class Signup extends Component<any, State> { private isoData = setIsoData(this.context); - private subscription: Subscription; - private audio: HTMLAudioElement; - - emptyState: State = { - registerForm: new Register({ - username: undefined, - password: undefined, - password_verify: undefined, + private subscription?: Subscription; + private audio?: HTMLAudioElement; + + state: State = { + form: { show_nsfw: false, - captcha_uuid: None, - captcha_answer: None, - honeypot: None, - answer: None, - email: None, - }), + }, registerLoading: false, - captcha: None, captchaPlaying: false, siteRes: this.isoData.site_res, }; @@ -93,7 +93,6 @@ export class Signup extends Component<any, State> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleAnswerChange = this.handleAnswerChange.bind(this); this.parseMessage = this.parseMessage.bind(this); @@ -106,7 +105,7 @@ export class Signup extends Component<any, State> { componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -131,8 +130,6 @@ export class Signup extends Component<any, State> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <div className="row"> <div className="col-12 col-lg-6 offset-lg-3"> @@ -172,7 +169,7 @@ export class Signup extends Component<any, State> { type="text" id="register-username" className="form-control" - value={this.state.registerForm.username} + value={this.state.form.username} onInput={linkEvent(this, this.handleRegisterUsernameChange)} required minLength={3} @@ -196,14 +193,15 @@ export class Signup extends Component<any, State> { ? i18n.t("required") : i18n.t("optional") } - value={toUndefined(this.state.registerForm.email)} + value={this.state.form.email} autoComplete="email" onInput={linkEvent(this, this.handleRegisterEmailChange)} required={siteView.local_site.require_email_verification} minLength={3} /> {!siteView.local_site.require_email_verification && - !this.state.registerForm.email.map(validEmail).unwrapOr(true) && ( + this.state.form.email && + !validEmail(this.state.form.email) && ( <div className="mt-2 mb-0 alert alert-warning" role="alert"> <Icon icon="alert-triangle" classes="icon-inline mr-2" /> {i18n.t("no_password_reset")} @@ -223,7 +221,7 @@ export class Signup extends Component<any, State> { <input type="password" id="register-password" - value={this.state.registerForm.password} + value={this.state.form.password} autoComplete="new-password" onInput={linkEvent(this, this.handleRegisterPasswordChange)} minLength={10} @@ -231,7 +229,7 @@ export class Signup extends Component<any, State> { className="form-control" required /> - {this.state.registerForm.password && ( + {this.state.form.password && ( <div className={this.passwordColorClass}> {i18n.t(this.passwordStrength as I18nKeys)} </div> @@ -250,7 +248,7 @@ export class Signup extends Component<any, State> { <input type="password" id="register-verify-password" - value={this.state.registerForm.password_verify} + value={this.state.form.password_verify} autoComplete="new-password" onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} maxLength={60} @@ -260,7 +258,8 @@ export class Signup extends Component<any, State> { </div> </div> - {siteView.local_site.require_application && ( + {siteView.local_site.registration_mode == + RegistrationMode.RequireApplication && ( <> <div className="form-group row"> <div className="offset-sm-2 col-sm-10"> @@ -268,15 +267,14 @@ export class Signup extends Component<any, State> { <Icon icon="alert-triangle" classes="icon-inline mr-2" /> {i18n.t("fill_out_application")} </div> - {siteView.local_site.application_question.match({ - some: question => ( - <div - className="md-div" - dangerouslySetInnerHTML={mdToHtml(question)} - /> - ), - none: <></>, - })} + {siteView.local_site.application_question && ( + <div + className="md-div" + dangerouslySetInnerHTML={mdToHtml( + siteView.local_site.application_question + )} + /> + )} </div> </div> @@ -289,11 +287,6 @@ export class Signup extends Component<any, State> { </label> <div className="col-sm-10"> <MarkdownTextArea - initialContent={None} - initialLanguageId={None} - placeholder={None} - buttonTitle={None} - maxLength={None} onContentChange={this.handleAnswerChange} hideNavigationWarnings allLanguages={[]} @@ -304,7 +297,7 @@ export class Signup extends Component<any, State> { </> )} - {this.state.captcha.isSome() && ( + {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> @@ -323,7 +316,7 @@ export class Signup extends Component<any, State> { type="text" className="form-control" id="register-captcha" - value={toUndefined(this.state.registerForm.captcha_answer)} + value={this.state.form.captcha_answer} onInput={linkEvent( this, this.handleRegisterCaptchaAnswerChange @@ -341,7 +334,7 @@ export class Signup extends Component<any, State> { className="form-check-input" id="register-show-nsfw" type="checkbox" - checked={this.state.registerForm.show_nsfw} + checked={this.state.form.show_nsfw} onChange={linkEvent(this, this.handleRegisterShowNsfwChange)} /> <label @@ -361,7 +354,7 @@ export class Signup extends Component<any, State> { type="text" className="form-control honeypot" id="register-honey" - value={toUndefined(this.state.registerForm.honeypot)} + value={this.state.form.honeypot} onInput={linkEvent(this, this.handleHoneyPotChange)} /> <div className="form-group row"> @@ -380,51 +373,46 @@ export class Signup extends Component<any, State> { } showCaptcha() { - return this.state.captcha.match({ - some: captcha => ( - <div className="col-sm-4"> - {captcha.ok.match({ - some: res => ( - <> - <img - className="rounded-top img-fluid" - src={this.captchaPngSrc(res)} - style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;" - alt={i18n.t("captcha")} - /> - {res.wav.isSome() && ( - <button - className="rounded-bottom btn btn-sm btn-secondary btn-block" - style="border-top-right-radius: 0; border-top-left-radius: 0;" - title={i18n.t("play_captcha_audio")} - onClick={linkEvent(this, this.handleCaptchaPlay)} - type="button" - disabled={this.state.captchaPlaying} - > - <Icon icon="play" classes="icon-play" /> - </button> - )} - </> - ), - none: <></>, - })} - </div> - ), - none: <></>, - }); + let captchaRes = this.state.captcha?.ok; + return captchaRes ? ( + <div className="col-sm-4"> + <> + <img + className="rounded-top img-fluid" + src={this.captchaPngSrc(captchaRes)} + style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;" + alt={i18n.t("captcha")} + /> + {captchaRes.wav && ( + <button + className="rounded-bottom btn btn-sm btn-secondary btn-block" + style="border-top-right-radius: 0; border-top-left-radius: 0;" + title={i18n.t("play_captcha_audio")} + onClick={linkEvent(this, this.handleCaptchaPlay)} + type="button" + disabled={this.state.captchaPlaying} + > + <Icon icon="play" classes="icon-play" /> + </button> + )} + </> + </div> + ) : ( + <></> + ); } - get passwordStrength() { - return passwordStrength( - this.state.registerForm.password, - passwordStrengthOptions - ).value; + get passwordStrength(): string | undefined { + let password = this.state.form.password; + return password + ? passwordStrength(password, passwordStrengthOptions).value + : undefined; } get passwordColorClass(): string { let strength = this.passwordStrength; - if (["weak", "medium"].includes(strength)) { + if (strength && ["weak", "medium"].includes(strength)) { return "text-warning"; } else if (strength == "strong") { return "text-success"; @@ -436,53 +424,67 @@ export class Signup extends Component<any, State> { handleRegisterSubmit(i: Signup, event: any) { event.preventDefault(); i.setState({ registerLoading: true }); - WebSocketService.Instance.send(wsClient.register(i.state.registerForm)); + let cForm = i.state.form; + if (cForm.username && cForm.password && cForm.password_verify) { + let 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)); + } } handleRegisterUsernameChange(i: Signup, event: any) { - i.state.registerForm.username = event.target.value; + i.state.form.username = event.target.value; i.setState(i.state); } handleRegisterEmailChange(i: Signup, event: any) { - i.state.registerForm.email = Some(event.target.value); - if (i.state.registerForm.email.unwrap() == "") { - i.state.registerForm.email = None; + i.state.form.email = event.target.value; + if (i.state.form.email == "") { + i.state.form.email = undefined; } i.setState(i.state); } handleRegisterPasswordChange(i: Signup, event: any) { - i.state.registerForm.password = event.target.value; + i.state.form.password = event.target.value; i.setState(i.state); } handleRegisterPasswordVerifyChange(i: Signup, event: any) { - i.state.registerForm.password_verify = event.target.value; + i.state.form.password_verify = event.target.value; i.setState(i.state); } handleRegisterShowNsfwChange(i: Signup, event: any) { - i.state.registerForm.show_nsfw = event.target.checked; + i.state.form.show_nsfw = event.target.checked; i.setState(i.state); } handleRegisterCaptchaAnswerChange(i: Signup, event: any) { - i.state.registerForm.captcha_answer = Some(event.target.value); + i.state.form.captcha_answer = event.target.value; i.setState(i.state); } handleAnswerChange(val: string) { - this.setState(s => ((s.registerForm.answer = Some(val)), s)); + this.setState(s => ((s.form.answer = val), s)); } handleHoneyPotChange(i: Signup, event: any) { - i.state.registerForm.honeypot = Some(event.target.value); + i.state.form.honeypot = event.target.value; i.setState(i.state); } handleRegenCaptcha(i: Signup) { - i.audio = null; + i.audio = undefined; i.setState({ captchaPlaying: false }); WebSocketService.Instance.send(wsClient.getCaptcha()); } @@ -490,28 +492,23 @@ export class Signup extends Component<any, State> { 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. - i.state.captcha.match({ - some: captcha => - captcha.ok.match({ - some: res => { - if (i.audio == null) { - let base64 = `data:audio/wav;base64,${res.wav}`; - i.audio = new Audio(base64); - } - - i.audio.play(); - - i.setState({ captchaPlaying: true }); - - i.audio.addEventListener("ended", () => { - i.audio.currentTime = 0; - i.setState({ captchaPlaying: false }); - }); - }, - none: void 0, - }), - none: void 0, - }); + let captchaRes = i.state.captcha?.ok; + if (captchaRes) { + if (!i.audio) { + let base64 = `data:audio/wav;base64,${captchaRes.wav}`; + i.audio = new Audio(base64); + i.audio.play(); + + i.setState({ captchaPlaying: true }); + + i.audio.addEventListener("ended", () => { + if (i.audio) { + i.audio.currentTime = 0; + i.setState({ captchaPlaying: false }); + } + }); + } + } } captchaPngSrc(captcha: CaptchaResponse) { @@ -523,17 +520,15 @@ export class Signup extends Component<any, State> { console.log(msg); if (msg.error) { toast(i18n.t(msg.error), "danger"); - this.setState(this.emptyState); - this.setState(s => ((s.registerForm.captcha_answer = undefined), s)); + this.setState(s => ((s.form.captcha_answer = undefined), s)); // Refetch another captcha // WebSocketService.Instance.send(wsClient.getCaptcha()); return; } else { if (op == UserOperation.Register) { - let data = wsJsonToRes<LoginResponse>(msg, LoginResponse); - this.setState(this.emptyState); + let data = wsJsonToRes<LoginResponse>(msg); // Only log them in if a jwt was set - if (data.jwt.isSome()) { + if (data.jwt) { UserService.Instance.login(data); this.props.history.push("/communities"); location.reload(); @@ -547,20 +542,15 @@ export class Signup extends Component<any, State> { this.props.history.push("/"); } } else if (op == UserOperation.GetCaptcha) { - let data = wsJsonToRes<GetCaptchaResponse>(msg, GetCaptchaResponse); - data.ok.match({ - some: res => { - this.setState({ captcha: Some(data) }); - this.setState( - s => ((s.registerForm.captcha_uuid = Some(res.uuid)), s) - ); - }, - none: void 0, - }); + let 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) { - let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse); + let data = wsJsonToRes<GetSiteResponse>(msg); this.setState({ siteRes: data }); } } diff --git a/src/shared/components/home/site-form.tsx b/src/shared/components/home/site-form.tsx index 58532b6..668f00d 100644 --- a/src/shared/components/home/site-form.tsx +++ b/src/shared/components/home/site-form.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, InfernoMouseEvent, linkEvent } from "inferno"; import { Prompt } from "inferno-router"; import { @@ -6,14 +5,14 @@ import { EditSite, GetSiteResponse, ListingType, - toUndefined, + RegistrationMode, } from "lemmy-js-client"; import { i18n } from "../../i18next"; import { WebSocketService } from "../../services"; import { - auth, capitalizeFirstLetter, fetchThemeList, + myAuth, wsClient, } from "../../utils"; import { Icon, Spinner } from "../common/icon"; @@ -30,63 +29,20 @@ interface SiteFormProps { interface SiteFormState { siteForm: EditSite; loading: boolean; - themeList: Option<string[]>; + themeList?: string[]; } export class SiteForm extends Component<SiteFormProps, SiteFormState> { - private emptyState: SiteFormState = { - siteForm: new EditSite({ - enable_downvotes: None, - open_registration: None, - enable_nsfw: None, - name: None, - icon: None, - banner: None, - require_email_verification: None, - require_application: None, - application_question: None, - private_instance: None, - default_theme: None, - sidebar: None, - default_post_listing_type: None, - legal_information: None, - description: None, - community_creation_admin_only: None, - application_email_admins: None, - hide_modlog_mod_names: None, - discussion_languages: None, - slur_filter_regex: None, - actor_name_max_length: None, - rate_limit_message: None, - rate_limit_message_per_second: None, - rate_limit_comment: None, - rate_limit_comment_per_second: None, - rate_limit_image: None, - rate_limit_image_per_second: None, - rate_limit_post: None, - rate_limit_post_per_second: None, - rate_limit_register: None, - rate_limit_register_per_second: None, - rate_limit_search: None, - rate_limit_search_per_second: None, - federation_enabled: None, - federation_debug: None, - federation_worker_count: None, - captcha_enabled: None, - captcha_difficulty: None, - allowed_instances: None, - blocked_instances: None, - taglines: None, - auth: undefined, - }), + state: SiteFormState = { + siteForm: { + auth: "TODO", + }, loading: false, - themeList: None, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this); this.handleSiteLegalInfoChange = this.handleSiteLegalInfoChange.bind(this); this.handleSiteApplicationQuestionChange = @@ -109,59 +65,54 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { let lsrl = this.props.siteRes.site_view.local_site_rate_limit; this.state = { ...this.state, - siteForm: new EditSite({ - name: Some(site.name), + siteForm: { + name: site.name, sidebar: site.sidebar, description: site.description, - enable_downvotes: Some(ls.enable_downvotes), - open_registration: Some(ls.open_registration), - enable_nsfw: Some(ls.enable_nsfw), - community_creation_admin_only: Some(ls.community_creation_admin_only), + 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: Some(ls.require_email_verification), - require_application: Some(ls.require_application), + require_email_verification: ls.require_email_verification, application_question: ls.application_question, - private_instance: Some(ls.private_instance), - default_theme: Some(ls.default_theme), - default_post_listing_type: Some(ls.default_post_listing_type), + 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: Some(ls.application_email_admins), - hide_modlog_mod_names: Some(ls.hide_modlog_mod_names), - discussion_languages: Some(this.props.siteRes.discussion_languages), + application_email_admins: ls.application_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: Some(ls.actor_name_max_length), - rate_limit_message: Some(lsrl.message), - rate_limit_message_per_second: Some(lsrl.message_per_second), - rate_limit_comment: Some(lsrl.comment), - rate_limit_comment_per_second: Some(lsrl.comment_per_second), - rate_limit_image: Some(lsrl.image), - rate_limit_image_per_second: Some(lsrl.image_per_second), - rate_limit_post: Some(lsrl.post), - rate_limit_post_per_second: Some(lsrl.post_per_second), - rate_limit_register: Some(lsrl.register), - rate_limit_register_per_second: Some(lsrl.register_per_second), - rate_limit_search: Some(lsrl.search), - rate_limit_search_per_second: Some(lsrl.search_per_second), - federation_enabled: Some(ls.federation_enabled), - federation_debug: Some(ls.federation_debug), - federation_worker_count: Some(ls.federation_worker_count), - captcha_enabled: Some(ls.captcha_enabled), - captcha_difficulty: Some(ls.captcha_difficulty), - allowed_instances: this.props.siteRes.federated_instances.andThen( - f => f.allowed - ), - blocked_instances: this.props.siteRes.federated_instances.andThen( - f => f.blocked - ), - taglines: this.props.siteRes.taglines.map(x => x.map(y => y.content)), - auth: undefined, - }), + actor_name_max_length: ls.actor_name_max_length, + rate_limit_message: lsrl.message, + rate_limit_message_per_second: lsrl.message_per_second, + rate_limit_comment: lsrl.comment, + rate_limit_comment_per_second: lsrl.comment_per_second, + rate_limit_image: lsrl.image, + rate_limit_image_per_second: lsrl.image_per_second, + rate_limit_post: lsrl.post, + rate_limit_post_per_second: lsrl.post_per_second, + rate_limit_register: lsrl.register, + rate_limit_register_per_second: lsrl.register_per_second, + rate_limit_search: lsrl.search, + rate_limit_search_per_second: lsrl.search_per_second, + 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.siteRes.federated_instances?.allowed, + blocked_instances: this.props.siteRes.federated_instances?.blocked, + taglines: this.props.siteRes.taglines?.map(x => x.content), + auth: "TODO", + }, }; } async componentDidMount() { - this.setState({ themeList: Some(await fetchThemeList()) }); + this.setState({ themeList: await fetchThemeList() }); } // Necessary to stop the loading @@ -180,7 +131,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { ) { window.onbeforeunload = () => true; } else { - window.onbeforeunload = undefined; + window.onbeforeunload = null; } } @@ -218,7 +169,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { type="text" id="create-site-name" className="form-control" - value={toUndefined(this.state.siteForm.name)} + value={this.state.siteForm.name} onInput={linkEvent(this, this.handleSiteNameChange)} required minLength={3} @@ -254,7 +205,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { type="text" className="form-control" id="site-desc" - value={toUndefined(this.state.siteForm.description)} + value={this.state.siteForm.description} onInput={linkEvent(this, this.handleSiteDescChange)} maxLength={150} /> @@ -265,10 +216,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { <div className="col-12"> <MarkdownTextArea initialContent={this.state.siteForm.sidebar} - initialLanguageId={None} - placeholder={None} - buttonTitle={None} - maxLength={None} onContentChange={this.handleSiteSidebarChange} hideNavigationWarnings allLanguages={[]} @@ -283,10 +230,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { <div className="col-12"> <MarkdownTextArea initialContent={this.state.siteForm.legal_information} - initialLanguageId={None} - placeholder={None} - buttonTitle={None} - maxLength={None} onContentChange={this.handleSiteLegalInfoChange} hideNavigationWarnings allLanguages={[]} @@ -294,26 +237,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { /> </div> </div> - {this.state.siteForm.require_application.unwrapOr(false) && ( - <div className="form-group row"> - <label className="col-12 col-form-label"> - {i18n.t("application_questionnaire")} - </label> - <div className="col-12"> - <MarkdownTextArea - initialContent={this.state.siteForm.application_question} - initialLanguageId={None} - placeholder={None} - buttonTitle={None} - maxLength={None} - onContentChange={this.handleSiteApplicationQuestionChange} - hideNavigationWarnings - allLanguages={[]} - siteLanguages={[]} - /> - </div> - </div> - )} <div className="form-group row"> <div className="col-12"> <div className="form-check"> @@ -321,7 +244,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-downvotes" type="checkbox" - checked={toUndefined(this.state.siteForm.enable_downvotes)} + checked={this.state.siteForm.enable_downvotes} onChange={linkEvent( this, this.handleSiteEnableDownvotesChange @@ -343,7 +266,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-enable-nsfw" type="checkbox" - checked={toUndefined(this.state.siteForm.enable_nsfw)} + checked={this.state.siteForm.enable_nsfw} onChange={linkEvent(this, this.handleSiteEnableNsfwChange)} /> <label @@ -357,26 +280,50 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { </div> <div className="form-group row"> <div className="col-12"> - <div className="form-check"> - <input - className="form-check-input" - id="create-site-open-registration" - type="checkbox" - checked={toUndefined(this.state.siteForm.open_registration)} - onChange={linkEvent( - this, - this.handleSiteOpenRegistrationChange - )} - /> - <label - className="form-check-label" - htmlFor="create-site-open-registration" - > + <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={RegistrationMode.RequireApplication}> + {i18n.t("require_registration_application")} + </option> + <option value={RegistrationMode.Open}> {i18n.t("open_registration")} - </label> - </div> + </option> + <option value={RegistrationMode.Closed}> + {i18n.t("close_registration")} + </option> + </select> </div> </div> + {this.state.siteForm.registration_mode == + RegistrationMode.RequireApplication && ( + <div className="form-group row"> + <label className="col-12 col-form-label"> + {i18n.t("application_questionnaire")} + </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"> @@ -384,9 +331,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-community-creation-admin-only" type="checkbox" - checked={toUndefined( - this.state.siteForm.community_creation_admin_only - )} + checked={this.state.siteForm.community_creation_admin_only} onChange={linkEvent( this, this.handleSiteCommunityCreationAdminOnly @@ -408,9 +353,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-require-email-verification" type="checkbox" - checked={toUndefined( - this.state.siteForm.require_email_verification - )} + checked={this.state.siteForm.require_email_verification} onChange={linkEvent( this, this.handleSiteRequireEmailVerification @@ -425,25 +368,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { </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-application" - type="checkbox" - checked={toUndefined(this.state.siteForm.require_application)} - onChange={linkEvent(this, this.handleSiteRequireApplication)} - /> - <label - className="form-check-label" - htmlFor="create-site-require-application" - > - {i18n.t("require_registration_application")} - </label> - </div> - </div> - </div> <div className="form-group row"> <div className="col-12"> <div className="form-check"> @@ -451,9 +375,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-application-email-admins" type="checkbox" - checked={toUndefined( - this.state.siteForm.application_email_admins - )} + checked={this.state.siteForm.application_email_admins} onChange={linkEvent( this, this.handleSiteApplicationEmailAdmins @@ -478,12 +400,12 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { </label> <select id="create-site-default-theme" - value={toUndefined(this.state.siteForm.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.unwrapOr([]).map(theme => ( + {this.state.themeList?.map(theme => ( <option key={theme} value={theme}> {theme} </option> @@ -498,9 +420,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { <ListingTypeSelect type_={ ListingType[ - this.state.siteForm.default_post_listing_type.unwrapOr( - "Local" - ) + this.state.siteForm.default_post_listing_type ?? "Local" ] } showLocal @@ -517,7 +437,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-private-instance" type="checkbox" - checked={toUndefined(this.state.siteForm.private_instance)} + checked={this.state.siteForm.private_instance} onChange={linkEvent(this, this.handleSitePrivateInstance)} /> <label @@ -536,9 +456,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-hide-modlog-mod-names" type="checkbox" - checked={toUndefined( - this.state.siteForm.hide_modlog_mod_names - )} + checked={this.state.siteForm.hide_modlog_mod_names} onChange={linkEvent(this, this.handleSiteHideModlogModNames)} /> <label @@ -563,7 +481,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-slur-filter-regex" placeholder="(word1|word2)" className="form-control" - value={toUndefined(this.state.siteForm.slur_filter_regex)} + value={this.state.siteForm.slur_filter_regex} onInput={linkEvent(this, this.handleSiteSlurFilterRegex)} minLength={3} /> @@ -590,7 +508,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-actor-name" className="form-control" min={5} - value={toUndefined(this.state.siteForm.actor_name_max_length)} + value={this.state.siteForm.actor_name_max_length} onInput={linkEvent(this, this.handleSiteActorNameMaxLength)} /> </div> @@ -602,7 +520,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-federation-enabled" type="checkbox" - checked={toUndefined(this.state.siteForm.federation_enabled)} + checked={this.state.siteForm.federation_enabled} onChange={linkEvent(this, this.handleSiteFederationEnabled)} /> <label @@ -614,7 +532,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { </div> </div> </div> - {this.state.siteForm.federation_enabled.unwrapOr(false) && ( + {this.state.siteForm.federation_enabled && ( <> <div className="form-group row"> <label @@ -663,9 +581,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-federation-debug" type="checkbox" - checked={toUndefined( - this.state.siteForm.federation_debug - )} + checked={this.state.siteForm.federation_debug} onChange={linkEvent(this, this.handleSiteFederationDebug)} /> <label @@ -690,9 +606,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-federation-worker-count" className="form-control" min={0} - value={toUndefined( - this.state.siteForm.federation_worker_count - )} + value={this.state.siteForm.federation_worker_count} onInput={linkEvent( this, this.handleSiteFederationWorkerCount @@ -709,7 +623,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { className="form-check-input" id="create-site-captcha-enabled" type="checkbox" - checked={toUndefined(this.state.siteForm.captcha_enabled)} + checked={this.state.siteForm.captcha_enabled} onChange={linkEvent(this, this.handleSiteCaptchaEnabled)} /> <label @@ -721,7 +635,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { </div> </div> </div> - {this.state.siteForm.captcha_enabled.unwrapOr(false) && ( + {this.state.siteForm.captcha_enabled && ( <div className="form-group row"> <div className="col-12"> <label @@ -732,7 +646,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { </label> <select id="create-site-captcha-difficulty" - value={toUndefined(this.state.siteForm.captcha_difficulty)} + value={this.state.siteForm.captcha_difficulty} onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)} className="custom-select w-auto" > @@ -756,7 +670,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-message" className="form-control" min={0} - value={toUndefined(this.state.siteForm.rate_limit_message)} + value={this.state.siteForm.rate_limit_message} onInput={linkEvent(this, this.handleSiteRateLimitMessage)} /> </div> @@ -774,9 +688,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-message-per-second" className="form-control" min={0} - value={toUndefined( - this.state.siteForm.rate_limit_message_per_second - )} + value={this.state.siteForm.rate_limit_message_per_second} onInput={linkEvent( this, this.handleSiteRateLimitMessagePerSecond @@ -797,7 +709,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-post" className="form-control" min={0} - value={toUndefined(this.state.siteForm.rate_limit_post)} + value={this.state.siteForm.rate_limit_post} onInput={linkEvent(this, this.handleSiteRateLimitPost)} /> </div> @@ -815,9 +727,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-post-per-second" className="form-control" min={0} - value={toUndefined( - this.state.siteForm.rate_limit_post_per_second - )} + value={this.state.siteForm.rate_limit_post_per_second} onInput={linkEvent(this, this.handleSiteRateLimitPostPerSecond)} /> </div> @@ -835,7 +745,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-register" className="form-control" min={0} - value={toUndefined(this.state.siteForm.rate_limit_register)} + value={this.state.siteForm.rate_limit_register} onInput={linkEvent(this, this.handleSiteRateLimitRegister)} /> </div> @@ -853,9 +763,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-register-per-second" className="form-control" min={0} - value={toUndefined( - this.state.siteForm.rate_limit_register_per_second - )} + value={this.state.siteForm.rate_limit_register_per_second} onInput={linkEvent( this, this.handleSiteRateLimitRegisterPerSecond @@ -876,7 +784,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-image" className="form-control" min={0} - value={toUndefined(this.state.siteForm.rate_limit_image)} + value={this.state.siteForm.rate_limit_image} onInput={linkEvent(this, this.handleSiteRateLimitImage)} /> </div> @@ -894,9 +802,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-image-per-second" className="form-control" min={0} - value={toUndefined( - this.state.siteForm.rate_limit_image_per_second - )} + value={this.state.siteForm.rate_limit_image_per_second} onInput={linkEvent( this, this.handleSiteRateLimitImagePerSecond @@ -917,7 +823,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-comment" className="form-control" min={0} - value={toUndefined(this.state.siteForm.rate_limit_comment)} + value={this.state.siteForm.rate_limit_comment} onInput={linkEvent(this, this.handleSiteRateLimitComment)} /> </div> @@ -935,9 +841,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-comment-per-second" className="form-control" min={0} - value={toUndefined( - this.state.siteForm.rate_limit_comment_per_second - )} + value={this.state.siteForm.rate_limit_comment_per_second} onInput={linkEvent( this, this.handleSiteRateLimitCommentPerSecond @@ -958,7 +862,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-search" className="form-control" min={0} - value={toUndefined(this.state.siteForm.rate_limit_search)} + value={this.state.siteForm.rate_limit_search} onInput={linkEvent(this, this.handleSiteRateLimitSearch)} /> </div> @@ -976,9 +880,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { id="create-site-rate-limit-search-per-second" className="form-control" min={0} - value={toUndefined( - this.state.siteForm.rate_limit_search_per_second - )} + value={this.state.siteForm.rate_limit_search_per_second} onInput={linkEvent( this, this.handleSiteRateLimitSearchPerSecond @@ -992,44 +894,38 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { <table id="taglines_table" className="table table-sm table-hover"> <thead className="pointer"></thead> <tbody> - {this.state.siteForm.taglines - .unwrapOr([]) - .map((cv, index) => ( - <tr key={index}> - <td> - <MarkdownTextArea - initialContent={Some(cv)} - initialLanguageId={None} - placeholder={None} - buttonTitle={None} - maxLength={None} - onContentChange={s => - this.handleTaglineChange(this, index, s) - } - hideNavigationWarnings - allLanguages={this.props.siteRes.all_languages} - siteLanguages={ - this.props.siteRes.discussion_languages - } + {this.state.siteForm.taglines?.map((cv, index) => ( + <tr key={index}> + <td> + <MarkdownTextArea + initialContent={cv} + onContentChange={s => + this.handleTaglineChange(this, index, s) + } + hideNavigationWarnings + allLanguages={this.props.siteRes.all_languages} + siteLanguages={ + this.props.siteRes.discussion_languages + } + /> + </td> + <td className="text-right"> + <button + className="btn btn-link btn-animate text-muted" + onClick={e => + this.handleDeleteTaglineClick(this, index, e) + } + data-tippy-content={i18n.t("delete")} + aria-label={i18n.t("delete")} + > + <Icon + icon="trash" + classes={`icon-inline text-danger`} /> - </td> - <td className="text-right"> - <button - className="btn btn-link btn-animate text-muted" - onClick={e => - this.handleDeleteTaglineClick(this, index, e) - } - data-tippy-content={i18n.t("delete")} - aria-label={i18n.t("delete")} - > - <Icon - icon="trash" - classes={`icon-inline text-danger`} - /> - </button> - </td> - </tr> - ))} + </button> + </td> + </tr> + ))} </tbody> </table> <button @@ -1065,13 +961,14 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { handleCreateSiteSubmit(i: SiteForm, event: any) { event.preventDefault(); i.setState({ loading: true }); - i.setState(s => ((s.siteForm.auth = auth().unwrap()), s)); + let auth = myAuth() ?? "TODO"; + i.setState(s => ((s.siteForm.auth = auth), s)); if (i.props.siteRes.site_view.local_site.site_setup) { WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm)); } else { let sForm = i.state.siteForm; - let form = new CreateSite({ - name: sForm.name.unwrapOr("My site"), + let form: CreateSite = { + name: sForm.name ?? "My site", sidebar: sForm.sidebar, description: sForm.description, icon: sForm.icon, @@ -1079,15 +976,13 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { community_creation_admin_only: sForm.community_creation_admin_only, enable_nsfw: sForm.enable_nsfw, enable_downvotes: sForm.enable_downvotes, - require_application: sForm.require_application, application_question: sForm.application_question, - open_registration: sForm.open_registration, + 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, - auth: auth().unwrap(), hide_modlog_mod_names: sForm.hide_modlog_mod_names, legal_information: sForm.legal_information, slur_filter_regex: sForm.slur_filter_regex, @@ -1113,14 +1008,15 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { blocked_instances: sForm.blocked_instances, discussion_languages: sForm.discussion_languages, taglines: sForm.taglines, - }); + auth, + }; WebSocketService.Instance.send(wsClient.createSite(form)); } i.setState(i.state); } - instancesToString(opt: Option<string[]>): string { - return opt.map(list => list.join(",")).unwrapOr(""); + instancesToString(opt?: string[]): string { + return opt ? opt.join(",") : ""; } handleSiteAllowedInstances(i: SiteForm, event: any) { @@ -1134,26 +1030,24 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { } handleSiteNameChange(i: SiteForm, event: any) { - i.state.siteForm.name = Some(event.target.value); + i.state.siteForm.name = event.target.value; i.setState(i.state); } handleSiteSidebarChange(val: string) { - this.setState(s => ((s.siteForm.sidebar = Some(val)), s)); + this.setState(s => ((s.siteForm.sidebar = val), s)); } handleSiteLegalInfoChange(val: string) { - this.setState(s => ((s.siteForm.legal_information = Some(val)), s)); + this.setState(s => ((s.siteForm.legal_information = val), s)); } handleTaglineChange(i: SiteForm, index: number, val: string) { - i.state.siteForm.taglines.match({ - some: tls => { - tls[index] = val; - }, - none: void 0, - }); - i.setState(i.state); + let taglines = i.state.siteForm.taglines; + if (taglines) { + taglines[index] = val; + i.setState(i.state); + } } handleDeleteTaglineClick( @@ -1162,12 +1056,12 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { event: InfernoMouseEvent<HTMLButtonElement> ) { event.preventDefault(); - if (i.state.siteForm.taglines.isSome()) { - let taglines = i.state.siteForm.taglines.unwrap(); + let taglines = i.state.siteForm.taglines; + if (taglines) { taglines.splice(index, 1); - i.state.siteForm.taglines = None; // force rerender of table rows + i.state.siteForm.taglines = undefined; i.setState(i.state); - i.state.siteForm.taglines = Some(taglines); + i.state.siteForm.taglines = taglines; i.setState(i.state); } } @@ -1177,116 +1071,103 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { event: InfernoMouseEvent<HTMLButtonElement> ) { event.preventDefault(); - if (i.state.siteForm.taglines.isNone()) { - i.state.siteForm.taglines = Some([]); + if (!i.state.siteForm.taglines) { + i.state.siteForm.taglines = []; } - i.state.siteForm.taglines.unwrap().push(""); + i.state.siteForm.taglines.push(""); i.setState(i.state); } handleSiteApplicationQuestionChange(val: string) { - this.setState(s => ((s.siteForm.application_question = Some(val)), s)); + this.setState(s => ((s.siteForm.application_question = val), s)); } handleSiteDescChange(i: SiteForm, event: any) { - i.state.siteForm.description = Some(event.target.value); + i.state.siteForm.description = event.target.value; i.setState(i.state); } handleSiteEnableNsfwChange(i: SiteForm, event: any) { - i.state.siteForm.enable_nsfw = Some(event.target.checked); + i.state.siteForm.enable_nsfw = event.target.checked; i.setState(i.state); } - handleSiteOpenRegistrationChange(i: SiteForm, event: any) { - i.state.siteForm.open_registration = Some(event.target.checked); + handleSiteRegistrationModeChange(i: SiteForm, event: any) { + i.state.siteForm.registration_mode = event.target.value; i.setState(i.state); } handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) { - i.state.siteForm.community_creation_admin_only = Some(event.target.checked); + i.state.siteForm.community_creation_admin_only = event.target.checked; i.setState(i.state); } handleSiteEnableDownvotesChange(i: SiteForm, event: any) { - i.state.siteForm.enable_downvotes = Some(event.target.checked); - i.setState(i.state); - } - - handleSiteRequireApplication(i: SiteForm, event: any) { - i.state.siteForm.require_application = Some(event.target.checked); + i.state.siteForm.enable_downvotes = event.target.checked; i.setState(i.state); } handleSiteRequireEmailVerification(i: SiteForm, event: any) { - i.state.siteForm.require_email_verification = Some(event.target.checked); + i.state.siteForm.require_email_verification = event.target.checked; i.setState(i.state); } handleSiteApplicationEmailAdmins(i: SiteForm, event: any) { - i.state.siteForm.application_email_admins = Some(event.target.checked); + i.state.siteForm.application_email_admins = event.target.checked; i.setState(i.state); } handleSitePrivateInstance(i: SiteForm, event: any) { - i.state.siteForm.private_instance = Some(event.target.checked); + i.state.siteForm.private_instance = event.target.checked; i.setState(i.state); } handleSiteHideModlogModNames(i: SiteForm, event: any) { - i.state.siteForm.hide_modlog_mod_names = Some(event.target.checked); + i.state.siteForm.hide_modlog_mod_names = event.target.checked; i.setState(i.state); } handleSiteDefaultTheme(i: SiteForm, event: any) { - i.state.siteForm.default_theme = Some(event.target.value); + i.state.siteForm.default_theme = event.target.value; i.setState(i.state); } handleIconUpload(url: string) { - this.setState(s => ((s.siteForm.icon = Some(url)), s)); + this.setState(s => ((s.siteForm.icon = url), s)); } handleIconRemove() { - this.setState(s => ((s.siteForm.icon = Some("")), s)); + this.setState(s => ((s.siteForm.icon = ""), s)); } handleBannerUpload(url: string) { - this.setState(s => ((s.siteForm.banner = Some(url)), s)); + this.setState(s => ((s.siteForm.banner = url), s)); } handleBannerRemove() { - this.setState(s => ((s.siteForm.banner = Some("")), s)); + this.setState(s => ((s.siteForm.banner = ""), s)); } handleSiteSlurFilterRegex(i: SiteForm, event: any) { - i.setState( - s => ((s.siteForm.slur_filter_regex = Some(event.target.value)), s) - ); + i.setState(s => ((s.siteForm.slur_filter_regex = event.target.value), s)); } handleSiteActorNameMaxLength(i: SiteForm, event: any) { i.setState( - s => ( - (s.siteForm.actor_name_max_length = Some(Number(event.target.value))), s - ) + s => ((s.siteForm.actor_name_max_length = Number(event.target.value)), s) ); } handleSiteRateLimitMessage(i: SiteForm, event: any) { i.setState( - s => ( - (s.siteForm.rate_limit_message = Some(Number(event.target.value))), s - ) + s => ((s.siteForm.rate_limit_message = Number(event.target.value)), s) ); } handleSiteRateLimitMessagePerSecond(i: SiteForm, event: any) { i.setState( s => ( - (s.siteForm.rate_limit_message_per_second = Some( - Number(event.target.value) - )), + (s.siteForm.rate_limit_message_per_second = Number(event.target.value)), s ) ); @@ -1294,52 +1175,42 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { handleSiteRateLimitPost(i: SiteForm, event: any) { i.setState( - s => ((s.siteForm.rate_limit_post = Some(Number(event.target.value))), s) + s => ((s.siteForm.rate_limit_post = Number(event.target.value)), s) ); } handleSiteRateLimitPostPerSecond(i: SiteForm, event: any) { i.setState( s => ( - (s.siteForm.rate_limit_post_per_second = Some( - Number(event.target.value) - )), - s + (s.siteForm.rate_limit_post_per_second = Number(event.target.value)), s ) ); } handleSiteRateLimitImage(i: SiteForm, event: any) { i.setState( - s => ((s.siteForm.rate_limit_image = Some(Number(event.target.value))), s) + s => ((s.siteForm.rate_limit_image = Number(event.target.value)), s) ); } handleSiteRateLimitImagePerSecond(i: SiteForm, event: any) { i.setState( s => ( - (s.siteForm.rate_limit_image_per_second = Some( - Number(event.target.value) - )), - s + (s.siteForm.rate_limit_image_per_second = Number(event.target.value)), s ) ); } handleSiteRateLimitComment(i: SiteForm, event: any) { i.setState( - s => ( - (s.siteForm.rate_limit_comment = Some(Number(event.target.value))), s - ) + s => ((s.siteForm.rate_limit_comment = Number(event.target.value)), s) ); } handleSiteRateLimitCommentPerSecond(i: SiteForm, event: any) { i.setState( s => ( - (s.siteForm.rate_limit_comment_per_second = Some( - Number(event.target.value) - )), + (s.siteForm.rate_limit_comment_per_second = Number(event.target.value)), s ) ); @@ -1347,18 +1218,14 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { handleSiteRateLimitSearch(i: SiteForm, event: any) { i.setState( - s => ( - (s.siteForm.rate_limit_search = Some(Number(event.target.value))), s - ) + s => ((s.siteForm.rate_limit_search = Number(event.target.value)), s) ); } handleSiteRateLimitSearchPerSecond(i: SiteForm, event: any) { i.setState( s => ( - (s.siteForm.rate_limit_search_per_second = Some( - Number(event.target.value) - )), + (s.siteForm.rate_limit_search_per_second = Number(event.target.value)), s ) ); @@ -1366,17 +1233,15 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { handleSiteRateLimitRegister(i: SiteForm, event: any) { i.setState( - s => ( - (s.siteForm.rate_limit_register = Some(Number(event.target.value))), s - ) + s => ((s.siteForm.rate_limit_register = Number(event.target.value)), s) ); } handleSiteRateLimitRegisterPerSecond(i: SiteForm, event: any) { i.setState( s => ( - (s.siteForm.rate_limit_register_per_second = Some( - Number(event.target.value) + (s.siteForm.rate_limit_register_per_second = Number( + event.target.value )), s ) @@ -1384,56 +1249,51 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { } handleSiteFederationEnabled(i: SiteForm, event: any) { - i.state.siteForm.federation_enabled = Some(event.target.checked); + i.state.siteForm.federation_enabled = event.target.checked; i.setState(i.state); } handleSiteFederationDebug(i: SiteForm, event: any) { - i.state.siteForm.federation_debug = Some(event.target.checked); + i.state.siteForm.federation_debug = event.target.checked; i.setState(i.state); } handleSiteFederationWorkerCount(i: SiteForm, event: any) { i.setState( s => ( - (s.siteForm.federation_worker_count = Some(Number(event.target.value))), - s + (s.siteForm.federation_worker_count = Number(event.target.value)), s ) ); } handleSiteCaptchaEnabled(i: SiteForm, event: any) { - i.state.siteForm.captcha_enabled = Some(event.target.checked); + i.state.siteForm.captcha_enabled = event.target.checked; i.setState(i.state); } handleSiteCaptchaDifficulty(i: SiteForm, event: any) { - i.setState( - s => ((s.siteForm.captcha_difficulty = Some(event.target.value)), s) - ); + i.setState(s => ((s.siteForm.captcha_difficulty = event.target.value), s)); } handleDiscussionLanguageChange(val: number[]) { - this.setState(s => ((s.siteForm.discussion_languages = Some(val)), s)); + this.setState(s => ((s.siteForm.discussion_languages = val), s)); } handleDefaultPostListingTypeChange(val: ListingType) { this.setState( s => ( - (s.siteForm.default_post_listing_type = Some( - ListingType[ListingType[val]] - )), + (s.siteForm.default_post_listing_type = ListingType[ListingType[val]]), s ) ); } } -function splitToList(commaList: string): Option<string[]> { +function splitToList(commaList: string): string[] { if (commaList !== "") { let list = commaList.trim().split(","); - return Some(list); + return list; } else { - return Some([]); + return []; } } diff --git a/src/shared/components/home/site-sidebar.tsx b/src/shared/components/home/site-sidebar.tsx index e321ae9..fbe3e2a 100644 --- a/src/shared/components/home/site-sidebar.tsx +++ b/src/shared/components/home/site-sidebar.tsx @@ -1,4 +1,3 @@ -import { None, Option } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { PersonViewSafe, Site, SiteAggregates } from "lemmy-js-client"; @@ -11,9 +10,9 @@ import { PersonListing } from "../person/person-listing"; interface SiteSidebarProps { site: Site; showLocal: boolean; - counts: Option<SiteAggregates>; - admins: Option<PersonViewSafe[]>; - online: Option<number>; + counts?: SiteAggregates; + admins?: PersonViewSafe[]; + online?: number; } interface SiteSidebarState { @@ -21,13 +20,12 @@ interface SiteSidebarState { } export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> { - private emptyState: SiteSidebarState = { + state: SiteSidebarState = { collapsed: false, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; } render() { @@ -38,7 +36,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> { <div className="mb-2">{this.siteName()}</div> {!this.state.collapsed && ( <> - <BannerIconHeader banner={this.props.site.banner} icon={None} /> + <BannerIconHeader banner={this.props.site.banner} /> {this.siteInfo()} </> )} @@ -72,22 +70,10 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> { let site = this.props.site; return ( <div> - {site.description.match({ - some: description => <h6>{description}</h6>, - none: <></>, - })} - {site.sidebar.match({ - some: sidebar => this.siteSidebar(sidebar), - none: <></>, - })} - {this.props.counts.match({ - some: counts => this.badges(counts), - none: <></>, - })} - {this.props.admins.match({ - some: admins => this.admins(admins), - none: <></>, - })} + {site.description && <h6>{site.description}</h6>} + {site.sidebar && this.siteSidebar(site.sidebar)} + {this.props.counts && this.badges(this.props.counts)} + {this.props.admins && this.admins(this.props.admins)} </div> ); } @@ -113,7 +99,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> { badges(siteAggregates: SiteAggregates) { let counts = siteAggregates; - let online = this.props.online.unwrapOr(1); + let online = this.props.online ?? 1; return ( <ul className="my-2 list-inline"> <li className="list-inline-item badge badge-secondary"> diff --git a/src/shared/components/modlog.tsx b/src/shared/components/modlog.tsx index 95e0cc8..140d56a 100644 --- a/src/shared/components/modlog.tsx +++ b/src/shared/components/modlog.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { @@ -24,7 +23,6 @@ import { ModRemovePostView, ModTransferCommunityView, PersonSafe, - toUndefined, UserOperation, wsJsonToRes, wsUserOp, @@ -37,12 +35,12 @@ import { WebSocketService } from "../services"; import { amAdmin, amMod, - auth, choicesConfig, debounce, fetchLimit, fetchUsers, isBrowser, + myAuth, setIsoData, toast, wsClient, @@ -57,7 +55,7 @@ import { PersonListing } from "./person/person-listing"; type ModlogType = { id: number; type_: ModlogActionType; - moderator: Option<PersonSafe>; + moderator?: PersonSafe; view: | ModRemovePostView | ModLockPostView @@ -81,43 +79,32 @@ if (isBrowser()) { } interface ModlogState { - res: Option<GetModlogResponse>; - communityId: Option<number>; - communityMods: Option<CommunityModeratorView[]>; - communityName: Option<string>; + res?: GetModlogResponse; + communityId?: number; + communityMods?: CommunityModeratorView[]; + communityName?: string; page: number; siteRes: GetSiteResponse; loading: boolean; filter_action: ModlogActionType; - filter_user: Option<number>; - filter_mod: Option<number>; + filter_user?: number; + filter_mod?: number; } export class Modlog extends Component<any, ModlogState> { - private isoData = setIsoData( - this.context, - GetModlogResponse, - GetCommunityResponse - ); - private subscription: Subscription; + private isoData = setIsoData(this.context); + private subscription?: Subscription; private userChoices: any; private modChoices: any; - private emptyState: ModlogState = { - res: None, - communityId: None, - communityMods: None, - communityName: None, + state: ModlogState = { page: 1, loading: true, siteRes: this.isoData.site_res, filter_action: ModlogActionType.All, - filter_user: None, - filter_mod: None, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handlePageChange = this.handlePageChange.bind(this); this.parseMessage = this.parseMessage.bind(this); @@ -126,27 +113,25 @@ export class Modlog extends Component<any, ModlogState> { this.state = { ...this.state, communityId: this.props.match.params.community_id - ? Some(Number(this.props.match.params.community_id)) - : None, + ? Number(this.props.match.params.community_id) + : undefined, }; // Only fetch the data if coming from another route if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - res: Some(this.isoData.routeData[0] as GetModlogResponse), + res: this.isoData.routeData[0] as GetModlogResponse, }; - if (this.isoData.routeData[1]) { - // Getting the moderators - let communityRes = Some( - this.isoData.routeData[1] as GetCommunityResponse - ); - this.state = { - ...this.state, - communityMods: communityRes.map(c => c.moderators), - }; - } + let communityRes: GetCommunityResponse | undefined = + this.isoData.routeData[1]; + + // Getting the moderators + this.state = { + ...this.state, + communityMods: communityRes?.moderators, + }; this.state = { ...this.state, loading: false }; } else { @@ -161,7 +146,7 @@ export class Modlog extends Component<any, ModlogState> { componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -310,22 +295,20 @@ export class Modlog extends Component<any, ModlogState> { switch (i.type_) { case ModlogActionType.ModRemovePost: { let mrpv = i.view as ModRemovePostView; + let reason = mrpv.mod_remove_post.reason; return ( <> <span> - {mrpv.mod_remove_post.removed.unwrapOr(false) - ? "Removed " - : "Restored "} + {mrpv.mod_remove_post.removed ? "Removed " : "Restored "} </span> <span> Post <Link to={`/post/${mrpv.post.id}`}>{mrpv.post.name}</Link> </span> - <span> - {mrpv.mod_remove_post.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} </> ); } @@ -333,11 +316,7 @@ export class Modlog extends Component<any, ModlogState> { let mlpv = i.view as ModLockPostView; return ( <> - <span> - {mlpv.mod_lock_post.locked.unwrapOr(false) - ? "Locked " - : "Unlocked "} - </span> + <span>{mlpv.mod_lock_post.locked ? "Locked " : "Unlocked "}</span> <span> Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link> </span> @@ -364,12 +343,11 @@ export class Modlog extends Component<any, ModlogState> { } case ModlogActionType.ModRemoveComment: { let mrc = i.view as ModRemoveCommentView; + let reason = mrc.mod_remove_comment.reason; return ( <> <span> - {mrc.mod_remove_comment.removed.unwrapOr(false) - ? "Removed " - : "Restored "} + {mrc.mod_remove_comment.removed ? "Removed " : "Restored "} </span> <span> Comment{" "} @@ -381,52 +359,47 @@ export class Modlog extends Component<any, ModlogState> { {" "} by <PersonListing person={mrc.commenter} /> </span> - <span> - {mrc.mod_remove_comment.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} </> ); } case ModlogActionType.ModRemoveCommunity: { let mrco = i.view as ModRemoveCommunityView; + let reason = mrco.mod_remove_community.reason; + let expires = mrco.mod_remove_community.expires; return ( <> <span> - {mrco.mod_remove_community.removed.unwrapOr(false) - ? "Removed " - : "Restored "} + {mrco.mod_remove_community.removed ? "Removed " : "Restored "} </span> <span> Community <CommunityLink community={mrco.community} /> </span> - <span> - {mrco.mod_remove_community.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> - <span> - {mrco.mod_remove_community.expires.match({ - some: expires => ( - <div>expires: {moment.utc(expires).fromNow()}</div> - ), - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} + {expires && ( + <span> + <div>expires: {moment.utc(expires).fromNow()}</div> + </span> + )} </> ); } case ModlogActionType.ModBanFromCommunity: { let mbfc = i.view as ModBanFromCommunityView; + let reason = mbfc.mod_ban_from_community.reason; + let expires = mbfc.mod_ban_from_community.expires; return ( <> <span> - {mbfc.mod_ban_from_community.banned.unwrapOr(false) - ? "Banned " - : "Unbanned "}{" "} + {mbfc.mod_ban_from_community.banned ? "Banned " : "Unbanned "}{" "} </span> <span> <PersonListing person={mbfc.banned_person} /> @@ -435,20 +408,16 @@ export class Modlog extends Component<any, ModlogState> { <span> <CommunityLink community={mbfc.community} /> </span> - <span> - {mbfc.mod_ban_from_community.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> - <span> - {mbfc.mod_ban_from_community.expires.match({ - some: expires => ( - <div>expires: {moment.utc(expires).fromNow()}</div> - ), - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} + {expires && ( + <span> + <div>expires: {moment.utc(expires).fromNow()}</div> + </span> + )} </> ); } @@ -457,9 +426,7 @@ export class Modlog extends Component<any, ModlogState> { return ( <> <span> - {mac.mod_add_community.removed.unwrapOr(false) - ? "Removed " - : "Appointed "}{" "} + {mac.mod_add_community.removed ? "Removed " : "Appointed "}{" "} </span> <span> <PersonListing person={mac.modded_person} /> @@ -476,9 +443,7 @@ export class Modlog extends Component<any, ModlogState> { return ( <> <span> - {mtc.mod_transfer_community.removed.unwrapOr(false) - ? "Removed " - : "Transferred "}{" "} + {mtc.mod_transfer_community.removed ? "Removed " : "Transferred "}{" "} </span> <span> <CommunityLink community={mtc.community} /> @@ -492,28 +457,24 @@ export class Modlog extends Component<any, ModlogState> { } case ModlogActionType.ModBan: { let mb = i.view as ModBanView; + let reason = mb.mod_ban.reason; + let expires = mb.mod_ban.expires; return ( <> - <span> - {mb.mod_ban.banned.unwrapOr(false) ? "Banned " : "Unbanned "}{" "} - </span> + <span>{mb.mod_ban.banned ? "Banned " : "Unbanned "} </span> <span> <PersonListing person={mb.banned_person} /> </span> - <span> - {mb.mod_ban.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> - <span> - {mb.mod_ban.expires.match({ - some: expires => ( - <div>expires: {moment.utc(expires).fromNow()}</div> - ), - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} + {expires && ( + <span> + <div>expires: {moment.utc(expires).fromNow()}</div> + </span> + )} </> ); } @@ -521,9 +482,7 @@ export class Modlog extends Component<any, ModlogState> { let ma = i.view as ModAddView; return ( <> - <span> - {ma.mod_add.removed.unwrapOr(false) ? "Removed " : "Appointed "}{" "} - </span> + <span>{ma.mod_add.removed ? "Removed " : "Appointed "} </span> <span> <PersonListing person={ma.modded_person} /> </span> @@ -533,61 +492,61 @@ export class Modlog extends Component<any, ModlogState> { } case ModlogActionType.AdminPurgePerson: { let ap = i.view as AdminPurgePersonView; + let reason = ap.admin_purge_person.reason; return ( <> <span>Purged a Person</span> - <span> - {ap.admin_purge_person.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} </> ); } case ModlogActionType.AdminPurgeCommunity: { let ap = i.view as AdminPurgeCommunityView; + let reason = ap.admin_purge_community.reason; return ( <> <span>Purged a Community</span> - <span> - {ap.admin_purge_community.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} </> ); } case ModlogActionType.AdminPurgePost: { let ap = i.view as AdminPurgePostView; + let reason = ap.admin_purge_post.reason; return ( <> <span>Purged a Post from from </span> <CommunityLink community={ap.community} /> - <span> - {ap.admin_purge_post.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} </> ); } case ModlogActionType.AdminPurgeComment: { let ap = i.view as AdminPurgeCommentView; + let reason = ap.admin_purge_comment.reason; return ( <> <span> Purged a Comment from{" "} <Link to={`/post/${ap.post.id}`}>{ap.post.name}</Link> </span> - <span> - {ap.admin_purge_comment.reason.match({ - some: reason => <div>reason: {reason}</div>, - none: <></>, - })} - </span> + {reason && ( + <span> + <div>reason: {reason}</div> + </span> + )} </> ); } @@ -597,18 +556,19 @@ export class Modlog extends Component<any, ModlogState> { } combined() { - let combined = this.state.res.map(this.buildCombined).unwrapOr([]); + let res = this.state.res; + let combined = res ? this.buildCombined(res) : []; return ( <tbody> {combined.map(i => ( <tr key={i.id}> <td> - <MomentTime published={i.when_} updated={None} /> + <MomentTime published={i.when_} /> </td> <td> - {this.amAdminOrMod ? ( - <PersonListing person={i.moderator.unwrap()} /> + {this.amAdminOrMod && i.moderator ? ( + <PersonListing person={i.moderator} /> ) : ( <div>{this.modOrAdminText(i.moderator)}</div> )} @@ -624,14 +584,12 @@ export class Modlog extends Component<any, ModlogState> { return amAdmin() || amMod(this.state.communityMods); } - modOrAdminText(person: Option<PersonSafe>): string { - return person.match({ - some: res => - this.isoData.site_res.admins.map(a => a.person.id).includes(res.id) - ? i18n.t("admin") - : i18n.t("mod"), - none: i18n.t("mod"), - }); + modOrAdminText(person?: PersonSafe): string { + return person + ? this.isoData.site_res.admins.map(a => a.person.id).includes(person.id) + ? i18n.t("admin") + : i18n.t("mod") + : i18n.t("mod"); } get documentTitle(): string { @@ -639,13 +597,12 @@ export class Modlog extends Component<any, ModlogState> { } render() { + let communityName = this.state.communityName; return ( <div className="container-lg"> <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> {this.state.loading ? ( <h5> @@ -654,14 +611,11 @@ export class Modlog extends Component<any, ModlogState> { ) : ( <div> <h5> - {this.state.communityName.match({ - some: name => ( - <Link className="text-body" to={`/c/${name}`}> - /c/{name}{" "} - </Link> - ), - none: <></>, - })} + {communityName && ( + <Link className="text-body" to={`/c/${communityName}`}> + /c/{communityName}{" "} + </Link> + )} <span>{i18n.t("modlog")}</span> </h5> <div className="form-row"> @@ -714,7 +668,7 @@ export class Modlog extends Component<any, ModlogState> { <select id="filter-mod" className="form-control" - value={toUndefined(this.state.filter_mod)} + value={this.state.filter_mod} > <option>{i18n.t("filter_by_mod")}</option> </select> @@ -724,7 +678,7 @@ export class Modlog extends Component<any, ModlogState> { <select id="filter-user" className="form-control" - value={toUndefined(this.state.filter_user)} + value={this.state.filter_user} > <option>{i18n.t("filter_by_user")}</option> </select> @@ -763,28 +717,26 @@ export class Modlog extends Component<any, ModlogState> { } refetch() { - let modlogForm = new GetModlog({ + let auth = myAuth(false); + let modlogForm: GetModlog = { community_id: this.state.communityId, - page: Some(this.state.page), - limit: Some(fetchLimit), - auth: auth(false).ok(), + page: this.state.page, + limit: fetchLimit, type_: this.state.filter_action, other_person_id: this.state.filter_user, mod_person_id: this.state.filter_mod, - }); + auth, + }; WebSocketService.Instance.send(wsClient.getModlog(modlogForm)); - this.state.communityId.match({ - some: id => { - let communityForm = new GetCommunity({ - id: Some(id), - name: None, - auth: auth(false).ok(), - }); - WebSocketService.Instance.send(wsClient.getCommunity(communityForm)); - }, - none: void 0, - }); + let communityId = this.state.communityId; + if (communityId) { + let communityForm: GetCommunity = { + id: communityId, + auth, + }; + WebSocketService.Instance.send(wsClient.getCommunity(communityForm)); + } } setupUserFilter() { @@ -795,7 +747,7 @@ export class Modlog extends Component<any, ModlogState> { this.userChoices.passedElement.element.addEventListener( "choice", (e: any) => { - this.setState({ filter_user: Some(Number(e.detail.choice.value)) }); + this.setState({ filter_user: Number(e.detail.choice.value) }); this.refetch(); }, false @@ -834,7 +786,7 @@ export class Modlog extends Component<any, ModlogState> { this.modChoices.passedElement.element.addEventListener( "choice", (e: any) => { - this.setState({ filter_mod: Some(Number(e.detail.choice.value)) }); + this.setState({ filter_mod: Number(e.detail.choice.value) }); this.refetch(); }, false @@ -867,27 +819,25 @@ export class Modlog extends Component<any, ModlogState> { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { let pathSplit = req.path.split("/"); - let communityId = Some(pathSplit[3]).map(Number); + let communityId = pathSplit[3] ? Number(pathSplit[3]) : undefined; + let auth = req.auth; let promises: Promise<any>[] = []; - let modlogForm = new GetModlog({ - page: Some(1), - limit: Some(fetchLimit), + let modlogForm: GetModlog = { + page: 1, + limit: fetchLimit, community_id: communityId, - mod_person_id: None, - auth: req.auth, type_: ModlogActionType.All, - other_person_id: None, - }); + auth, + }; promises.push(req.client.getModlog(modlogForm)); - if (communityId.isSome()) { - let communityForm = new GetCommunity({ + if (communityId) { + let communityForm: GetCommunity = { id: communityId, - name: None, auth: req.auth, - }); + }; promises.push(req.client.getCommunity(communityForm)); } else { promises.push(Promise.resolve()); @@ -902,16 +852,16 @@ export class Modlog extends Component<any, ModlogState> { toast(i18n.t(msg.error), "danger"); return; } else if (op == UserOperation.GetModlog) { - let data = wsJsonToRes<GetModlogResponse>(msg, GetModlogResponse); + let data = wsJsonToRes<GetModlogResponse>(msg); window.scrollTo(0, 0); - this.setState({ res: Some(data), loading: false }); + this.setState({ res: data, loading: false }); this.setupUserFilter(); this.setupModFilter(); } else if (op == UserOperation.GetCommunity) { - let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse); + let data = wsJsonToRes<GetCommunityResponse>(msg); this.setState({ - communityMods: Some(data.moderators), - communityName: Some(data.community_view.community.name), + communityMods: data.moderators, + communityName: data.community_view.community.name, }); } } diff --git a/src/shared/components/person/inbox.tsx b/src/shared/components/person/inbox.tsx index 00d8af8..2254a40 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -1,4 +1,3 @@ -import { None, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { BlockPersonResponse, @@ -30,13 +29,13 @@ import { i18n } from "../../i18next"; import { CommentViewType, InitialFetchRequest } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { - auth, commentsToFlatNodes, createCommentLikeRes, editCommentRes, enableDownvotes, fetchLimit, isBrowser, + myAuth, relTags, saveCommentRes, setIsoData, @@ -91,14 +90,9 @@ interface InboxState { } export class Inbox extends Component<any, InboxState> { - private isoData = setIsoData( - this.context, - GetRepliesResponse, - GetPersonMentionsResponse, - PrivateMessagesResponse - ); - private subscription: Subscription; - private emptyState: InboxState = { + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: InboxState = { unreadOrAll: UnreadOrAll.Unread, messageType: MessageType.All, replies: [], @@ -114,11 +108,10 @@ export class Inbox extends Component<any, InboxState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleSortChange = this.handleSortChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); - if (UserService.Instance.myUserInfo.isNone() && isBrowser()) { + if (!UserService.Instance.myUserInfo && isBrowser()) { toast(i18n.t("not_logged_in"), "danger"); this.context.router.history.push(`/login`); } @@ -148,24 +141,22 @@ export class Inbox extends Component<any, InboxState> { componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } get documentTitle(): string { - return UserService.Instance.myUserInfo.match({ - some: mui => - `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${ + let mui = UserService.Instance.myUserInfo; + return mui + ? `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${ this.state.siteRes.site_view.site.name - }`, - none: "", - }); + }` + : ""; } render() { - let inboxRss = auth() - .ok() - .map(a => `/feeds/inbox/${a}.xml`); + let auth = myAuth(); + let inboxRss = auth ? `/feeds/inbox/${auth}.xml` : undefined; return ( <div className="container-lg"> {this.state.loading ? ( @@ -178,26 +169,21 @@ export class Inbox extends Component<any, InboxState> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <h5 className="mb-2"> {i18n.t("inbox")} - {inboxRss.match({ - some: rss => ( - <small> - <a href={rss} title="RSS" rel={relTags}> - <Icon icon="rss" classes="ml-2 text-muted small" /> - </a> - <link - rel="alternate" - type="application/atom+xml" - href={rss} - /> - </small> - ), - none: <></>, - })} + {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 + @@ -387,9 +373,6 @@ export class Inbox extends Component<any, InboxState> { { comment_view: i.view as CommentView, children: [], depth: 0 }, ]} viewType={CommentViewType.Flat} - moderators={None} - admins={None} - maxCommentsShown={None} noIndent markable showCommunity @@ -411,9 +394,6 @@ export class Inbox extends Component<any, InboxState> { }, ]} viewType={CommentViewType.Flat} - moderators={None} - admins={None} - maxCommentsShown={None} noIndent markable showCommunity @@ -445,9 +425,6 @@ export class Inbox extends Component<any, InboxState> { <CommentNodes nodes={commentsToFlatNodes(this.state.replies)} viewType={CommentViewType.Flat} - moderators={None} - admins={None} - maxCommentsShown={None} noIndent markable showCommunity @@ -468,9 +445,6 @@ export class Inbox extends Component<any, InboxState> { key={umv.person_mention.id} nodes={[{ comment_view: umv, children: [], depth: 0 }]} viewType={CommentViewType.Flat} - moderators={None} - admins={None} - maxCommentsShown={None} noIndent markable showCommunity @@ -515,73 +489,79 @@ export class Inbox extends Component<any, InboxState> { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { let promises: Promise<any>[] = []; - let sort = Some(CommentSortType.New); - - // It can be /u/me, or /username/1 - let repliesForm = new GetReplies({ - sort, - unread_only: Some(true), - page: Some(1), - limit: Some(fetchLimit), - auth: req.auth.unwrap(), - }); - promises.push(req.client.getReplies(repliesForm)); - - let personMentionsForm = new GetPersonMentions({ - sort, - unread_only: Some(true), - page: Some(1), - limit: Some(fetchLimit), - auth: req.auth.unwrap(), - }); - promises.push(req.client.getPersonMentions(personMentionsForm)); - - let privateMessagesForm = new GetPrivateMessages({ - unread_only: Some(true), - page: Some(1), - limit: Some(fetchLimit), - auth: req.auth.unwrap(), - }); - promises.push(req.client.getPrivateMessages(privateMessagesForm)); + let sort = CommentSortType.New; + let auth = req.auth; + + if (auth) { + // It can be /u/me, or /username/1 + let repliesForm: GetReplies = { + sort, + unread_only: true, + page: 1, + limit: fetchLimit, + auth, + }; + promises.push(req.client.getReplies(repliesForm)); + + let personMentionsForm: GetPersonMentions = { + sort, + unread_only: true, + page: 1, + limit: fetchLimit, + auth, + }; + promises.push(req.client.getPersonMentions(personMentionsForm)); + + let privateMessagesForm: GetPrivateMessages = { + unread_only: true, + page: 1, + limit: fetchLimit, + auth, + }; + promises.push(req.client.getPrivateMessages(privateMessagesForm)); + } return promises; } refetch() { - let sort = Some(this.state.sort); - let unread_only = Some(this.state.unreadOrAll == UnreadOrAll.Unread); - let page = Some(this.state.page); - let limit = Some(fetchLimit); - - let repliesForm = new GetReplies({ - sort, - unread_only, - page, - limit, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.getReplies(repliesForm)); - - let personMentionsForm = new GetPersonMentions({ - sort, - unread_only, - page, - limit, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send( - wsClient.getPersonMentions(personMentionsForm) - ); + let sort = this.state.sort; + let unread_only = this.state.unreadOrAll == UnreadOrAll.Unread; + let page = this.state.page; + let limit = fetchLimit; + let auth = myAuth(); + + if (auth) { + let repliesForm: GetReplies = { + sort, + unread_only, + page, + limit, + auth, + }; + WebSocketService.Instance.send(wsClient.getReplies(repliesForm)); + + let personMentionsForm: GetPersonMentions = { + sort, + unread_only, + page, + limit, + auth, + }; + WebSocketService.Instance.send( + wsClient.getPersonMentions(personMentionsForm) + ); - let privateMessagesForm = new GetPrivateMessages({ - unread_only, - page, - limit, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send( - wsClient.getPrivateMessages(privateMessagesForm) - ); + let privateMessagesForm: GetPrivateMessages = { + unread_only, + page, + limit, + auth, + }; + WebSocketService.Instance.send( + wsClient.getPrivateMessages(privateMessagesForm) + ); + } } handleSortChange(val: CommentSortType) { @@ -590,16 +570,19 @@ export class Inbox extends Component<any, InboxState> { } markAllAsRead(i: Inbox) { - WebSocketService.Instance.send( - wsClient.markAllAsRead({ - auth: auth().unwrap(), - }) - ); - i.setState({ replies: [], mentions: [], messages: [] }); - i.setState({ combined: i.buildCombined() }); - UserService.Instance.unreadInboxCountSub.next(0); - window.scrollTo(0, 0); - i.setState(i.state); + let 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); + } } sendUnreadCount(read: boolean) { @@ -620,73 +603,62 @@ export class Inbox extends Component<any, InboxState> { } else if (msg.reconnect) { this.refetch(); } else if (op == UserOperation.GetReplies) { - let data = wsJsonToRes<GetRepliesResponse>(msg, GetRepliesResponse); + let 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) { - let data = wsJsonToRes<GetPersonMentionsResponse>( - msg, - GetPersonMentionsResponse - ); + let data = wsJsonToRes<GetPersonMentionsResponse>(msg); this.setState({ mentions: data.mentions }); this.setState({ combined: this.buildCombined() }); window.scrollTo(0, 0); setupTippy(); } else if (op == UserOperation.GetPrivateMessages) { - let data = wsJsonToRes<PrivateMessagesResponse>( - msg, - PrivateMessagesResponse - ); + let 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) { - let data = wsJsonToRes<PrivateMessageResponse>( - msg, - PrivateMessageResponse - ); - let found: PrivateMessageView = this.state.messages.find( + let data = wsJsonToRes<PrivateMessageResponse>(msg); + let found = this.state.messages.find( m => m.private_message.id === data.private_message_view.private_message.id ); if (found) { let combinedView = this.state.combined.find( i => i.id == data.private_message_view.private_message.id - ).view as PrivateMessageView; - 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; + )?.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; + } } this.setState(this.state); } else if (op == UserOperation.DeletePrivateMessage) { - let data = wsJsonToRes<PrivateMessageResponse>( - msg, - PrivateMessageResponse - ); - let found: PrivateMessageView = this.state.messages.find( + let data = wsJsonToRes<PrivateMessageResponse>(msg); + let found = this.state.messages.find( m => m.private_message.id === data.private_message_view.private_message.id ); if (found) { let combinedView = this.state.combined.find( i => i.id == data.private_message_view.private_message.id - ).view as PrivateMessageView; - 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; + )?.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; + } } this.setState(this.state); } else if (op == UserOperation.MarkPrivateMessageAsRead) { - let data = wsJsonToRes<PrivateMessageResponse>( - msg, - PrivateMessageResponse - ); - let found: PrivateMessageView = this.state.messages.find( + let data = wsJsonToRes<PrivateMessageResponse>(msg); + let found = this.state.messages.find( m => m.private_message.id === data.private_message_view.private_message.id ); @@ -696,30 +668,32 @@ export class Inbox extends Component<any, InboxState> { i => i.id == data.private_message_view.private_message.id && i.type_ == ReplyEnum.Message - ).view as PrivateMessageView; - 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; + )?.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; + } } } this.sendUnreadCount(data.private_message_view.private_message.read); @@ -731,11 +705,11 @@ export class Inbox extends Component<any, InboxState> { op == UserOperation.DeleteComment || op == UserOperation.RemoveComment ) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); editCommentRes(data.comment_view, this.state.replies); this.setState(this.state); } else if (op == UserOperation.MarkCommentReplyAsRead) { - let data = wsJsonToRes<CommentReplyResponse>(msg, CommentReplyResponse); + let data = wsJsonToRes<CommentReplyResponse>(msg); let found = this.state.replies.find( c => c.comment_reply.id == data.comment_reply_view.comment_reply.id @@ -746,47 +720,50 @@ export class Inbox extends Component<any, InboxState> { i => i.id == data.comment_reply_view.comment_reply.id && i.type_ == ReplyEnum.Reply - ).view as CommentReplyView; - 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; + )?.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; + } } } this.sendUnreadCount(data.comment_reply_view.comment_reply.read); this.setState(this.state); } else if (op == UserOperation.MarkPersonMentionAsRead) { - let data = wsJsonToRes<PersonMentionResponse>(msg, PersonMentionResponse); + let data = wsJsonToRes<PersonMentionResponse>(msg); // TODO this might not be correct, it might need to use the comment id let found = this.state.mentions.find( @@ -798,94 +775,85 @@ export class Inbox extends Component<any, InboxState> { i => i.id == data.person_mention_view.person_mention.id && i.type_ == ReplyEnum.Mention - ).view as PersonMentionView; - 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; + )?.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; + } } } this.sendUnreadCount(data.person_mention_view.person_mention.read); this.setState(this.state); } else if (op == UserOperation.CreatePrivateMessage) { - let data = wsJsonToRes<PrivateMessageResponse>( - msg, - PrivateMessageResponse - ); - UserService.Instance.myUserInfo.match({ - some: mui => { - 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) - ); - this.setState(this.state); - } - }, - none: void 0, - }); + let data = wsJsonToRes<PrivateMessageResponse>(msg); + let 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) + ); + this.setState(this.state); + } } else if (op == UserOperation.SaveComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); saveCommentRes(data.comment_view, this.state.replies); this.setState(this.state); setupTippy(); } else if (op == UserOperation.CreateCommentLike) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); createCommentLikeRes(data.comment_view, this.state.replies); this.setState(this.state); } else if (op == UserOperation.BlockPerson) { - let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse); + let data = wsJsonToRes<BlockPersonResponse>(msg); updatePersonBlock(data); } else if (op == UserOperation.CreatePostReport) { - let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse); + let data = wsJsonToRes<PostReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } } else if (op == UserOperation.CreateCommentReport) { - let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse); + let data = wsJsonToRes<CommentReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } } else if (op == UserOperation.CreatePrivateMessageReport) { - let data = wsJsonToRes<PrivateMessageReportResponse>( - msg, - PrivateMessageReportResponse - ); + let data = wsJsonToRes<PrivateMessageReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } diff --git a/src/shared/components/person/password-change.tsx b/src/shared/components/person/password-change.tsx index d3d8182..32ebad6 100644 --- a/src/shared/components/person/password-change.tsx +++ b/src/shared/components/person/password-change.tsx @@ -1,9 +1,8 @@ -import { None } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { GetSiteResponse, LoginResponse, - PasswordChange as PasswordChangeForm, + PasswordChange as PWordChange, UserOperation, wsJsonToRes, wsUserOp, @@ -23,21 +22,23 @@ import { HtmlTags } from "../common/html-tags"; import { Spinner } from "../common/icon"; interface State { - passwordChangeForm: PasswordChangeForm; + 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; + private subscription?: Subscription; - emptyState: State = { - passwordChangeForm: new PasswordChangeForm({ + state: State = { + form: { token: this.props.match.params.token, - password: undefined, - password_verify: undefined, - }), + }, loading: false, siteRes: this.isoData.site_res, }; @@ -45,15 +46,13 @@ export class PasswordChange extends Component<any, State> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; - this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); } componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -69,8 +68,6 @@ export class PasswordChange extends Component<any, State> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <div className="row"> <div className="col-12 col-lg-6 offset-lg-3 mb-4"> @@ -93,7 +90,7 @@ export class PasswordChange extends Component<any, State> { <input id="new-password" type="password" - value={this.state.passwordChangeForm.password} + value={this.state.form.password} onInput={linkEvent(this, this.handlePasswordChange)} className="form-control" required @@ -109,7 +106,7 @@ export class PasswordChange extends Component<any, State> { <input id="verify-password" type="password" - value={this.state.passwordChangeForm.password_verify} + value={this.state.form.password_verify} onInput={linkEvent(this, this.handleVerifyPasswordChange)} className="form-control" required @@ -133,12 +130,12 @@ export class PasswordChange extends Component<any, State> { } handlePasswordChange(i: PasswordChange, event: any) { - i.state.passwordChangeForm.password = event.target.value; + i.state.form.password = event.target.value; i.setState(i.state); } handleVerifyPasswordChange(i: PasswordChange, event: any) { - i.state.passwordChangeForm.password_verify = event.target.value; + i.state.form.password_verify = event.target.value; i.setState(i.state); } @@ -146,9 +143,18 @@ export class PasswordChange extends Component<any, State> { event.preventDefault(); i.setState({ loading: true }); - WebSocketService.Instance.send( - wsClient.passwordChange(i.state.passwordChangeForm) - ); + let password = i.state.form.password; + let password_verify = i.state.form.password_verify; + + if (password && password_verify) { + let form: PWordChange = { + token: i.state.form.token, + password, + password_verify, + }; + + WebSocketService.Instance.send(wsClient.passwordChange(form)); + } } parseMessage(msg: any) { @@ -159,8 +165,7 @@ export class PasswordChange extends Component<any, State> { this.setState({ loading: false }); return; } else if (op == UserOperation.PasswordChange) { - let data = wsJsonToRes<LoginResponse>(msg, LoginResponse); - this.setState(this.emptyState); + let data = wsJsonToRes<LoginResponse>(msg); UserService.Instance.login(data); this.props.history.push("/"); location.reload(); diff --git a/src/shared/components/person/person-details.tsx b/src/shared/components/person/person-details.tsx index f50b6d3..450f6c5 100644 --- a/src/shared/components/person/person-details.tsx +++ b/src/shared/components/person/person-details.tsx @@ -1,4 +1,3 @@ -import { None, Some } from "@sniptt/monads/build"; import { Component } from "inferno"; import { CommentView, @@ -94,9 +93,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> { key={i.id} nodes={[{ comment_view: c, children: [], depth: 0 }]} viewType={CommentViewType.Flat} - admins={Some(this.props.admins)} - moderators={None} - maxCommentsShown={None} + admins={this.props.admins} noBorder noIndent showCommunity @@ -113,9 +110,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> { <PostListing key={i.id} post_view={p} - admins={Some(this.props.admins)} - duplicates={None} - moderators={None} + admins={this.props.admins} showCommunity enableDownvotes={this.props.enableDownvotes} enableNsfw={this.props.enableNsfw} @@ -171,9 +166,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> { <CommentNodes nodes={commentsToFlatNodes(this.props.personRes.comments)} viewType={CommentViewType.Flat} - admins={Some(this.props.admins)} - moderators={None} - maxCommentsShown={None} + admins={this.props.admins} noIndent showCommunity showContext @@ -192,10 +185,8 @@ export class PersonDetails extends Component<PersonDetailsProps, any> { <> <PostListing post_view={post} - admins={Some(this.props.admins)} + admins={this.props.admins} showCommunity - duplicates={None} - moderators={None} enableDownvotes={this.props.enableDownvotes} enableNsfw={this.props.enableNsfw} allLanguages={this.props.allLanguages} diff --git a/src/shared/components/person/person-listing.tsx b/src/shared/components/person/person-listing.tsx index 88b8882..3050450 100644 --- a/src/shared/components/person/person-listing.tsx +++ b/src/shared/components/person/person-listing.tsx @@ -37,9 +37,9 @@ export class PersonListing extends Component<PersonListingProps, any> { let displayName = this.props.useApubName ? apubName - : person.display_name.unwrapOr(apubName); + : person.display_name ?? apubName; - if (this.props.showApubName && !local && person.display_name.isSome()) { + if (this.props.showApubName && !local && person.display_name) { displayName = `${displayName} (${apubName})`; } @@ -70,14 +70,12 @@ export class PersonListing extends Component<PersonListingProps, any> { } avatarAndName(displayName: string) { + let avatar = this.props.person.avatar; return ( <> - {this.props.person.avatar.match({ - some: avatar => - !this.props.hideAvatar && - showAvatars() && <PictrsImage src={avatar} icon />, - none: <></>, - })} + {avatar && !this.props.hideAvatar && showAvatars() && ( + <PictrsImage src={avatar} icon /> + )} <span>{displayName}</span> </> ); diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx index 4739984..d12a981 100644 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; import { @@ -14,7 +13,6 @@ import { PostResponse, PurgeItemResponse, SortType, - toUndefined, UserOperation, wsJsonToRes, wsUserOp, @@ -25,7 +23,6 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest, PersonDetailsView } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { - auth, canMod, capitalizeFirstLetter, createCommentLikeRes, @@ -40,6 +37,7 @@ import { isAdmin, isBanned, mdToHtml, + myAuth, numToSI, relTags, restoreScrollPosition, @@ -63,15 +61,15 @@ import { PersonDetails } from "./person-details"; import { PersonListing } from "./person-listing"; interface ProfileState { - personRes: Option<GetPersonDetailsResponse>; + personRes?: GetPersonDetailsResponse; userName: string; view: PersonDetailsView; sort: SortType; page: number; loading: boolean; personBlocked: boolean; - banReason: Option<string>; - banExpireDays: Option<number>; + banReason?: string; + banExpireDays?: number; showBanDialog: boolean; removeData: boolean; siteRes: GetSiteResponse; @@ -81,7 +79,7 @@ interface ProfileProps { view: PersonDetailsView; sort: SortType; page: number; - person_id: number | null; + person_id?: number; username: string; } @@ -92,10 +90,9 @@ interface UrlParams { } export class Profile extends Component<any, ProfileState> { - private isoData = setIsoData(this.context, GetPersonDetailsResponse); - private subscription: Subscription; - private emptyState: ProfileState = { - personRes: None, + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: ProfileState = { userName: getUsernameFromProps(this.props), loading: true, view: Profile.getViewFromProps(this.props.match.view), @@ -104,15 +101,12 @@ export class Profile extends Component<any, ProfileState> { personBlocked: false, siteRes: this.isoData.site_res, showBanDialog: false, - banReason: null, - banExpireDays: null, removeData: false, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleSortChange = this.handleSortChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); @@ -123,7 +117,7 @@ export class Profile extends Component<any, ProfileState> { if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - personRes: Some(this.isoData.routeData[0] as GetPersonDetailsResponse), + personRes: this.isoData.routeData[0] as GetPersonDetailsResponse, loading: false, }; } else { @@ -132,45 +126,34 @@ export class Profile extends Component<any, ProfileState> { } fetchUserData() { - let form = new GetPersonDetails({ - username: Some(this.state.userName), - person_id: None, - community_id: None, - sort: Some(this.state.sort), - saved_only: Some(this.state.view === PersonDetailsView.Saved), - page: Some(this.state.page), - limit: Some(fetchLimit), - auth: auth(false).ok(), - }); + let form: GetPersonDetails = { + username: this.state.userName, + sort: this.state.sort, + saved_only: this.state.view === PersonDetailsView.Saved, + page: this.state.page, + limit: fetchLimit, + auth: myAuth(false), + }; WebSocketService.Instance.send(wsClient.getPersonDetails(form)); } get amCurrentUser() { - return UserService.Instance.myUserInfo.match({ - some: mui => - this.state.personRes.match({ - some: res => - mui.local_user_view.person.id == res.person_view.person.id, - none: false, - }), - none: false, - }); + return ( + UserService.Instance.myUserInfo?.local_user_view.person.id == + this.state.personRes?.person_view.person.id + ); } setPersonBlock() { - UserService.Instance.myUserInfo.match({ - some: mui => - this.state.personRes.match({ - some: res => - this.setState({ - personBlocked: mui.person_blocks - .map(a => a.target.id) - .includes(res.person_view.person.id), - }), - none: void 0, - }), - none: void 0, - }); + let mui = UserService.Instance.myUserInfo; + let res = this.state.personRes; + if (mui && res) { + this.setState({ + personBlocked: mui.person_blocks + .map(a => a.target.id) + .includes(res.person_view.person.id), + }); + } } static getViewFromProps(view: string): PersonDetailsView { @@ -190,19 +173,17 @@ export class Profile extends Component<any, ProfileState> { let username = pathSplit[2]; let view = this.getViewFromProps(pathSplit[4]); - let sort = Some(this.getSortTypeFromProps(pathSplit[6])); - let page = Some(this.getPageFromProps(Number(pathSplit[8]))); + let sort = this.getSortTypeFromProps(pathSplit[6]); + let page = this.getPageFromProps(Number(pathSplit[8])); - let form = new GetPersonDetails({ - username: Some(username), - person_id: None, - community_id: None, + let form: GetPersonDetails = { + username: username, sort, - saved_only: Some(view === PersonDetailsView.Saved), + saved_only: view === PersonDetailsView.Saved, page, - limit: Some(fetchLimit), + limit: fetchLimit, auth: req.auth, - }); + }; return [req.client.getPersonDetails(form)]; } @@ -212,7 +193,7 @@ export class Profile extends Component<any, ProfileState> { } componentWillUnmount() { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); saveScrollPosition(this.context); } @@ -221,7 +202,7 @@ export class Profile extends Component<any, ProfileState> { view: this.getViewFromProps(props.match.params.view), sort: this.getSortTypeFromProps(props.match.params.sort), page: this.getPageFromProps(props.match.params.page), - person_id: Number(props.match.params.id) || null, + person_id: Number(props.match.params.id), username: props.match.params.username, }; } @@ -238,14 +219,14 @@ export class Profile extends Component<any, ProfileState> { } get documentTitle(): string { - return this.state.personRes.match({ - some: res => - `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`, - none: "", - }); + let res = this.state.personRes; + return res + ? `@${res.person_view.person.name} - ${this.state.siteRes.site_view.site.name}` + : ""; } render() { + let res = this.state.personRes; return ( <div className="container-lg"> {this.state.loading ? ( @@ -253,46 +234,43 @@ export class Profile extends Component<any, ProfileState> { <Spinner large /> </h5> ) : ( - this.state.personRes.match({ - some: res => ( - <div className="row"> - <div className="col-12 col-md-8"> - <> - <HtmlTags - title={this.documentTitle} - path={this.context.router.route.match.url} - description={res.person_view.person.bio} - image={res.person_view.person.avatar} - /> - {this.userInfo()} - <hr /> - </> - {!this.state.loading && this.selects()} - <PersonDetails - personRes={res} - admins={this.state.siteRes.admins} - sort={this.state.sort} - page={this.state.page} - limit={fetchLimit} - enableDownvotes={enableDownvotes(this.state.siteRes)} - enableNsfw={enableNsfw(this.state.siteRes)} - view={this.state.view} - onPageChange={this.handlePageChange} - allLanguages={this.state.siteRes.all_languages} - siteLanguages={this.state.siteRes.discussion_languages} + res && ( + <div className="row"> + <div className="col-12 col-md-8"> + <> + <HtmlTags + title={this.documentTitle} + path={this.context.router.route.match.url} + description={res.person_view.person.bio} + image={res.person_view.person.avatar} /> - </div> - - {!this.state.loading && ( - <div className="col-12 col-md-4"> - {this.moderates()} - {this.amCurrentUser && this.follows()} - </div> - )} + {this.userInfo()} + <hr /> + </> + {!this.state.loading && this.selects()} + <PersonDetails + personRes={res} + admins={this.state.siteRes.admins} + sort={this.state.sort} + page={this.state.page} + limit={fetchLimit} + enableDownvotes={enableDownvotes(this.state.siteRes)} + enableNsfw={enableNsfw(this.state.siteRes)} + view={this.state.view} + onPageChange={this.handlePageChange} + allLanguages={this.state.siteRes.all_languages} + siteLanguages={this.state.siteRes.discussion_languages} + /> </div> - ), - none: <></>, - }) + + {!this.state.loading && ( + <div className="col-12 col-md-4"> + {this.moderates()} + {this.amCurrentUser && this.follows()} + </div> + )} + </div> + ) )} </div> ); @@ -377,349 +355,316 @@ export class Profile extends Component<any, ProfileState> { ); } handleBlockPerson(personId: number) { - if (personId != 0) { - let blockUserForm = new BlockPerson({ - person_id: personId, - block: true, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + let auth = myAuth(); + if (auth) { + if (personId != 0) { + let blockUserForm: BlockPerson = { + person_id: personId, + block: true, + auth, + }; + WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + } } } handleUnblockPerson(recipientId: number) { - let blockUserForm = new BlockPerson({ - person_id: recipientId, - block: false, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + let auth = myAuth(); + if (auth) { + let blockUserForm: BlockPerson = { + person_id: recipientId, + block: false, + auth, + }; + WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + } } userInfo() { - return this.state.personRes - .map(r => r.person_view) - .match({ - some: pv => ( - <div> - <BannerIconHeader - banner={pv.person.banner} - icon={pv.person.avatar} - /> - <div className="mb-3"> - <div className=""> - <div className="mb-0 d-flex flex-wrap"> - <div> - {pv.person.display_name.match({ - some: displayName => ( - <h5 className="mb-0">{displayName}</h5> - ), - none: <></>, - })} - <ul className="list-inline mb-2"> - <li className="list-inline-item"> - <PersonListing - person={pv.person} - realLink - useApubName - muted - hideAvatar - /> + let pv = this.state.personRes?.person_view; + return ( + pv && ( + <div> + <BannerIconHeader banner={pv.person.banner} icon={pv.person.avatar} /> + <div className="mb-3"> + <div className=""> + <div className="mb-0 d-flex flex-wrap"> + <div> + {pv.person.display_name && ( + <h5 className="mb-0">{pv.person.display_name}</h5> + )} + <ul className="list-inline mb-2"> + <li className="list-inline-item"> + <PersonListing + person={pv.person} + realLink + useApubName + muted + hideAvatar + /> + </li> + {isBanned(pv.person) && ( + <li className="list-inline-item badge badge-danger"> + {i18n.t("banned")} </li> - {isBanned(pv.person) && ( - <li className="list-inline-item badge badge-danger"> - {i18n.t("banned")} - </li> - )} - {pv.person.deleted && ( - <li className="list-inline-item badge badge-danger"> - {i18n.t("deleted")} - </li> - )} - {pv.person.admin && ( - <li className="list-inline-item badge badge-light"> - {i18n.t("admin")} - </li> - )} - {pv.person.bot_account && ( - <li className="list-inline-item badge badge-light"> - {i18n.t("bot_account").toLowerCase()} - </li> - )} - </ul> - </div> - {this.banDialog()} - <div className="flex-grow-1 unselectable pointer mx-2"></div> - {!this.amCurrentUser && - UserService.Instance.myUserInfo.isSome() && ( - <> - <a - className={`d-flex align-self-start btn btn-secondary mr-2 ${ - !pv.person.matrix_user_id && "invisible" - }`} - rel={relTags} - href={`https://matrix.to/#/${pv.person.matrix_user_id}`} - > - {i18n.t("send_secure_message")} - </a> - <Link - className={ - "d-flex align-self-start btn btn-secondary mr-2" - } - to={`/create_private_message/recipient/${pv.person.id}`} - > - {i18n.t("send_message")} - </Link> - {this.state.personBlocked ? ( - <button - className={ - "d-flex align-self-start btn btn-secondary mr-2" - } - onClick={linkEvent( - pv.person.id, - this.handleUnblockPerson - )} - > - {i18n.t("unblock_user")} - </button> - ) : ( - <button - className={ - "d-flex align-self-start btn btn-secondary mr-2" - } - onClick={linkEvent( - pv.person.id, - this.handleBlockPerson - )} - > - {i18n.t("block_user")} - </button> - )} - </> )} - - {canMod( - None, - Some(this.state.siteRes.admins), - pv.person.id - ) && - !isAdmin(Some(this.state.siteRes.admins), pv.person.id) && - !this.state.showBanDialog && - (!isBanned(pv.person) ? ( + {pv.person.deleted && ( + <li className="list-inline-item badge badge-danger"> + {i18n.t("deleted")} + </li> + )} + {pv.person.admin && ( + <li className="list-inline-item badge badge-light"> + {i18n.t("admin")} + </li> + )} + {pv.person.bot_account && ( + <li className="list-inline-item badge badge-light"> + {i18n.t("bot_account").toLowerCase()} + </li> + )} + </ul> + </div> + {this.banDialog()} + <div className="flex-grow-1 unselectable pointer mx-2"></div> + {!this.amCurrentUser && UserService.Instance.myUserInfo && ( + <> + <a + className={`d-flex align-self-start btn btn-secondary mr-2 ${ + !pv.person.matrix_user_id && "invisible" + }`} + rel={relTags} + href={`https://matrix.to/#/${pv.person.matrix_user_id}`} + > + {i18n.t("send_secure_message")} + </a> + <Link + className={ + "d-flex align-self-start btn btn-secondary mr-2" + } + to={`/create_private_message/recipient/${pv.person.id}`} + > + {i18n.t("send_message")} + </Link> + {this.state.personBlocked ? ( <button className={ "d-flex align-self-start btn btn-secondary mr-2" } - onClick={linkEvent(this, this.handleModBanShow)} - aria-label={i18n.t("ban")} + onClick={linkEvent( + pv.person.id, + this.handleUnblockPerson + )} > - {capitalizeFirstLetter(i18n.t("ban"))} + {i18n.t("unblock_user")} </button> ) : ( <button className={ "d-flex align-self-start btn btn-secondary mr-2" } - onClick={linkEvent(this, this.handleModBanSubmit)} - aria-label={i18n.t("unban")} + onClick={linkEvent( + pv.person.id, + this.handleBlockPerson + )} > - {capitalizeFirstLetter(i18n.t("unban"))} + {i18n.t("block_user")} </button> - ))} - </div> - {pv.person.bio.match({ - some: bio => ( - <div className="d-flex align-items-center mb-2"> - <div - className="md-div" - dangerouslySetInnerHTML={mdToHtml(bio)} - /> - </div> - ), - none: <></>, - })} - <div> - <ul className="list-inline mb-2"> - <li className="list-inline-item badge badge-light"> - {i18n.t("number_of_posts", { - count: pv.counts.post_count, - formattedCount: numToSI(pv.counts.post_count), - })} - </li> - <li className="list-inline-item badge badge-light"> - {i18n.t("number_of_comments", { - count: pv.counts.comment_count, - formattedCount: numToSI(pv.counts.comment_count), - })} - </li> - </ul> - </div> - <div className="text-muted"> - {i18n.t("joined")}{" "} - <MomentTime - published={pv.person.published} - updated={None} - showAgo - ignoreUpdated + )} + </> + )} + + {canMod(pv.person.id, undefined, this.state.siteRes.admins) && + !isAdmin(pv.person.id, this.state.siteRes.admins) && + !this.state.showBanDialog && + (!isBanned(pv.person) ? ( + <button + className={ + "d-flex align-self-start btn btn-secondary mr-2" + } + onClick={linkEvent(this, this.handleModBanShow)} + aria-label={i18n.t("ban")} + > + {capitalizeFirstLetter(i18n.t("ban"))} + </button> + ) : ( + <button + className={ + "d-flex align-self-start btn btn-secondary mr-2" + } + onClick={linkEvent(this, this.handleModBanSubmit)} + aria-label={i18n.t("unban")} + > + {capitalizeFirstLetter(i18n.t("unban"))} + </button> + ))} + </div> + {pv.person.bio && ( + <div className="d-flex align-items-center mb-2"> + <div + className="md-div" + dangerouslySetInnerHTML={mdToHtml(pv.person.bio)} /> </div> - <div className="d-flex align-items-center text-muted mb-2"> - <Icon icon="cake" /> - <span className="ml-2"> - {i18n.t("cake_day_title")}{" "} - {moment - .utc(pv.person.published) - .local() - .format("MMM DD, YYYY")} - </span> - </div> + )} + <div> + <ul className="list-inline mb-2"> + <li className="list-inline-item badge badge-light"> + {i18n.t("number_of_posts", { + count: pv.counts.post_count, + formattedCount: numToSI(pv.counts.post_count), + })} + </li> + <li className="list-inline-item badge badge-light"> + {i18n.t("number_of_comments", { + count: pv.counts.comment_count, + formattedCount: numToSI(pv.counts.comment_count), + })} + </li> + </ul> + </div> + <div className="text-muted"> + {i18n.t("joined")}{" "} + <MomentTime + published={pv.person.published} + showAgo + ignoreUpdated + /> + </div> + <div className="d-flex align-items-center text-muted mb-2"> + <Icon icon="cake" /> + <span className="ml-2"> + {i18n.t("cake_day_title")}{" "} + {moment + .utc(pv.person.published) + .local() + .format("MMM DD, YYYY")} + </span> </div> </div> </div> - ), - none: <></>, - }); + </div> + ) + ); } banDialog() { - return this.state.personRes - .map(r => r.person_view) - .match({ - some: pv => ( - <> - {this.state.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={toUndefined(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={toUndefined(this.state.banExpireDays)} - onInput={linkEvent(this, this.handleModBanExpireDaysChange)} - /> - <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> + let pv = this.state.personRes?.person_view; + return ( + pv && ( + <> + {this.state.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 + 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> - {/* 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> - )} - </> - ), - none: <></>, - }); + </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> + )} + </> + ) + ); } moderates() { - return this.state.personRes - .map(r => r.moderates) - .match({ - some: moderates => { - if (moderates.length > 0) { - return ( - <div className="card border-secondary mb-3"> - <div className="card-body"> - <h5>{i18n.t("moderates")}</h5> - <ul className="list-unstyled mb-0"> - {moderates.map(cmv => ( - <li key={cmv.community.id}> - <CommunityLink community={cmv.community} /> - </li> - ))} - </ul> - </div> - </div> - ); - } else { - return <></>; - } - }, - none: void 0, - }); + let moderates = this.state.personRes?.moderates; + return ( + moderates && + moderates.length > 0 && ( + <div className="card border-secondary mb-3"> + <div className="card-body"> + <h5>{i18n.t("moderates")}</h5> + <ul className="list-unstyled mb-0"> + {moderates.map(cmv => ( + <li key={cmv.community.id}> + <CommunityLink community={cmv.community} /> + </li> + ))} + </ul> + </div> + </div> + ) + ); } follows() { - return UserService.Instance.myUserInfo - .map(m => m.follows) - .match({ - some: follows => { - if (follows.length > 0) { - return ( - <div className="card border-secondary mb-3"> - <div className="card-body"> - <h5>{i18n.t("subscribed")}</h5> - <ul className="list-unstyled mb-0"> - {follows.map(cfv => ( - <li key={cfv.community.id}> - <CommunityLink community={cfv.community} /> - </li> - ))} - </ul> - </div> - </div> - ); - } else { - return <></>; - } - }, - none: void 0, - }); + let follows = UserService.Instance.myUserInfo?.follows; + return ( + follows && + follows.length > 0 && ( + <div className="card border-secondary mb-3"> + <div className="card-body"> + <h5>{i18n.t("subscribed")}</h5> + <ul className="list-unstyled mb-0"> + {follows.map(cfv => ( + <li key={cfv.community.id}> + <CommunityLink community={cfv.community} /> + </li> + ))} + </ul> + </div> + </div> + ) + ); } updateUrl(paramUpdates: UrlParams) { @@ -774,30 +719,26 @@ export class Profile extends Component<any, ProfileState> { handleModBanSubmit(i: Profile, event?: any) { if (event) event.preventDefault(); + let person = i.state.personRes?.person_view.person; + let auth = myAuth(); + if (person && auth) { + // If its an unban, restore all their data + let ban = !person.banned; + if (ban == false) { + i.setState({ removeData: false }); + } + let form: BanPerson = { + person_id: person.id, + ban, + remove_data: i.state.removeData, + reason: i.state.banReason, + expires: futureDaysToUnixTime(i.state.banExpireDays), + auth, + }; + WebSocketService.Instance.send(wsClient.banPerson(form)); - i.state.personRes - .map(r => r.person_view.person) - .match({ - some: person => { - // If its an unban, restore all their data - let ban = !person.banned; - if (ban == false) { - i.setState({ removeData: false }); - } - let form = new BanPerson({ - person_id: person.id, - ban, - remove_data: Some(i.state.removeData), - reason: i.state.banReason, - expires: i.state.banExpireDays.map(futureDaysToUnixTime), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.banPerson(form)); - - i.setState({ showBanDialog: false }); - }, - none: void 0, - }); + i.setState({ showBanDialog: false }); + } } parseMessage(msg: any) { @@ -815,50 +756,34 @@ export class Profile extends Component<any, ProfileState> { // 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 - let data = wsJsonToRes<GetPersonDetailsResponse>( - msg, - GetPersonDetailsResponse - ); - this.setState({ personRes: Some(data), loading: false }); + let data = wsJsonToRes<GetPersonDetailsResponse>(msg); + this.setState({ personRes: data, loading: false }); this.setPersonBlock(); restoreScrollPosition(this.context); } else if (op == UserOperation.AddAdmin) { - let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse); + let data = wsJsonToRes<AddAdminResponse>(msg); this.setState(s => ((s.siteRes.admins = data.admins), s)); } else if (op == UserOperation.CreateCommentLike) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); - createCommentLikeRes( - data.comment_view, - this.state.personRes.map(r => r.comments).unwrapOr([]) - ); + let data = wsJsonToRes<CommentResponse>(msg); + createCommentLikeRes(data.comment_view, this.state.personRes?.comments); this.setState(this.state); } else if ( op == UserOperation.EditComment || op == UserOperation.DeleteComment || op == UserOperation.RemoveComment ) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); - editCommentRes( - data.comment_view, - this.state.personRes.map(r => r.comments).unwrapOr([]) - ); + let data = wsJsonToRes<CommentResponse>(msg); + editCommentRes(data.comment_view, this.state.personRes?.comments); this.setState(this.state); } else if (op == UserOperation.CreateComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); - UserService.Instance.myUserInfo.match({ - some: mui => { - if (data.comment_view.creator.id == mui.local_user_view.person.id) { - toast(i18n.t("reply_sent")); - } - }, - none: void 0, - }); + let data = wsJsonToRes<CommentResponse>(msg); + let mui = UserService.Instance.myUserInfo; + if (data.comment_view.creator.id == mui?.local_user_view.person.id) { + toast(i18n.t("reply_sent")); + } } else if (op == UserOperation.SaveComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); - saveCommentRes( - data.comment_view, - this.state.personRes.map(r => r.comments).unwrapOr([]) - ); + let data = wsJsonToRes<CommentResponse>(msg); + saveCommentRes(data.comment_view, this.state.personRes?.comments); this.setState(this.state); } else if ( op == UserOperation.EditPost || @@ -868,40 +793,30 @@ export class Profile extends Component<any, ProfileState> { op == UserOperation.FeaturePost || op == UserOperation.SavePost ) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); - editPostFindRes( - data.post_view, - this.state.personRes.map(r => r.posts).unwrapOr([]) - ); + let data = wsJsonToRes<PostResponse>(msg); + editPostFindRes(data.post_view, this.state.personRes?.posts); this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); - createPostLikeFindRes( - data.post_view, - this.state.personRes.map(r => r.posts).unwrapOr([]) - ); + let data = wsJsonToRes<PostResponse>(msg); + createPostLikeFindRes(data.post_view, this.state.personRes?.posts); this.setState(this.state); } else if (op == UserOperation.BanPerson) { - let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse); - this.state.personRes.match({ - some: res => { - 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)); - let pv = res.person_view; - - if (pv.person.id == data.person_view.person.id) { - pv.person.banned = data.banned; - } - this.setState(this.state); - }, - none: void 0, - }); + let data = wsJsonToRes<BanPersonResponse>(msg); + let 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)); + let pv = res?.person_view; + + if (pv?.person.id == data.person_view.person.id) { + pv.person.banned = data.banned; + } + this.setState(this.state); } else if (op == UserOperation.BlockPerson) { - let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse); + let data = wsJsonToRes<BlockPersonResponse>(msg); updatePersonBlock(data); this.setPersonBlock(); this.setState(this.state); @@ -911,7 +826,7 @@ export class Profile extends Component<any, ProfileState> { op == UserOperation.PurgeComment || op == UserOperation.PurgeCommunity ) { - let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse); + let data = wsJsonToRes<PurgeItemResponse>(msg); if (data.success) { toast(i18n.t("purge_success")); this.context.router.history.push(`/`); diff --git a/src/shared/components/person/registration-applications.tsx b/src/shared/components/person/registration-applications.tsx index e523508..1816741 100644 --- a/src/shared/components/person/registration-applications.tsx +++ b/src/shared/components/person/registration-applications.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { GetSiteResponse, @@ -14,9 +13,9 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { - auth, fetchLimit, isBrowser, + myAuth, setIsoData, setupTippy, toast, @@ -35,7 +34,7 @@ enum UnreadOrAll { } interface RegistrationApplicationsState { - listRegistrationApplicationsResponse: Option<ListRegistrationApplicationsResponse>; + listRegistrationApplicationsResponse?: ListRegistrationApplicationsResponse; siteRes: GetSiteResponse; unreadOrAll: UnreadOrAll; page: number; @@ -46,13 +45,9 @@ export class RegistrationApplications extends Component< any, RegistrationApplicationsState > { - private isoData = setIsoData( - this.context, - ListRegistrationApplicationsResponse - ); - private subscription: Subscription; - private emptyState: RegistrationApplicationsState = { - listRegistrationApplicationsResponse: None, + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: RegistrationApplicationsState = { siteRes: this.isoData.site_res, unreadOrAll: UnreadOrAll.Unread, page: 1, @@ -62,10 +57,9 @@ export class RegistrationApplications extends Component< constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handlePageChange = this.handlePageChange.bind(this); - if (UserService.Instance.myUserInfo.isNone() && isBrowser()) { + if (!UserService.Instance.myUserInfo && isBrowser()) { toast(i18n.t("not_logged_in"), "danger"); this.context.router.history.push(`/login`); } @@ -77,9 +71,8 @@ export class RegistrationApplications extends Component< if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - listRegistrationApplicationsResponse: Some( - this.isoData.routeData[0] as ListRegistrationApplicationsResponse - ), + listRegistrationApplicationsResponse: this.isoData + .routeData[0] as ListRegistrationApplicationsResponse, loading: false, }; } else { @@ -93,18 +86,17 @@ export class RegistrationApplications extends Component< componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } get documentTitle(): string { - return UserService.Instance.myUserInfo.match({ - some: mui => - `@${mui.local_user_view.person.name} ${i18n.t( + let mui = UserService.Instance.myUserInfo; + return mui + ? `@${mui.local_user_view.person.name} ${i18n.t( "registration_applications" - )} - ${this.state.siteRes.site_view.site.name}`, - none: "", - }); + )} - ${this.state.siteRes.site_view.site.name}` + : ""; } render() { @@ -120,8 +112,6 @@ export class RegistrationApplications extends Component< <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <h5 className="mb-2">{i18n.t("registration_applications")}</h5> {this.selects()} @@ -179,8 +169,9 @@ export class RegistrationApplications extends Component< } applicationList() { - return this.state.listRegistrationApplicationsResponse.match({ - some: res => ( + let res = this.state.listRegistrationApplicationsResponse; + return ( + res && ( <div> {res.registration_applications.map(ra => ( <> @@ -192,9 +183,8 @@ export class RegistrationApplications extends Component< </> ))} </div> - ), - none: <></>, - }); + ) + ); } handleUnreadOrAllChange(i: RegistrationApplications, event: any) { @@ -210,26 +200,34 @@ export class RegistrationApplications extends Component< static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { let promises: Promise<any>[] = []; - let form = new ListRegistrationApplications({ - unread_only: Some(true), - page: Some(1), - limit: Some(fetchLimit), - auth: req.auth.unwrap(), - }); - promises.push(req.client.listRegistrationApplications(form)); + let auth = req.auth; + if (auth) { + let form: ListRegistrationApplications = { + unread_only: true, + page: 1, + limit: fetchLimit, + auth, + }; + promises.push(req.client.listRegistrationApplications(form)); + } return promises; } refetch() { let unread_only = this.state.unreadOrAll == UnreadOrAll.Unread; - let form = new ListRegistrationApplications({ - unread_only: Some(unread_only), - page: Some(this.state.page), - limit: Some(fetchLimit), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.listRegistrationApplications(form)); + let auth = myAuth(); + if (auth) { + let form: ListRegistrationApplications = { + unread_only: unread_only, + page: this.state.page, + limit: fetchLimit, + auth, + }; + WebSocketService.Instance.send( + wsClient.listRegistrationApplications(form) + ); + } } parseMessage(msg: any) { @@ -241,25 +239,18 @@ export class RegistrationApplications extends Component< } else if (msg.reconnect) { this.refetch(); } else if (op == UserOperation.ListRegistrationApplications) { - let data = wsJsonToRes<ListRegistrationApplicationsResponse>( - msg, - ListRegistrationApplicationsResponse - ); + let data = wsJsonToRes<ListRegistrationApplicationsResponse>(msg); this.setState({ - listRegistrationApplicationsResponse: Some(data), + listRegistrationApplicationsResponse: data, loading: false, }); window.scrollTo(0, 0); } else if (op == UserOperation.ApproveRegistrationApplication) { - let data = wsJsonToRes<RegistrationApplicationResponse>( - msg, - RegistrationApplicationResponse - ); + let data = wsJsonToRes<RegistrationApplicationResponse>(msg); updateRegistrationApplicationRes( data.registration_application, this.state.listRegistrationApplicationsResponse - .map(r => r.registration_applications) - .unwrapOr([]) + ?.registration_applications ); let uacs = UserService.Instance.unreadApplicationCountSub; // Minor bug, where if the application switches from deny to approve, the count will still go down diff --git a/src/shared/components/person/reports.tsx b/src/shared/components/person/reports.tsx index f28188e..0af56b5 100644 --- a/src/shared/components/person/reports.tsx +++ b/src/shared/components/person/reports.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { CommentReportResponse, @@ -24,9 +23,9 @@ import { InitialFetchRequest } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { amAdmin, - auth, fetchLimit, isBrowser, + myAuth, setIsoData, setupTippy, toast, @@ -69,9 +68,9 @@ type ItemType = { }; interface ReportsState { - listCommentReportsResponse: Option<ListCommentReportsResponse>; - listPostReportsResponse: Option<ListPostReportsResponse>; - listPrivateMessageReportsResponse: Option<ListPrivateMessageReportsResponse>; + listCommentReportsResponse?: ListCommentReportsResponse; + listPostReportsResponse?: ListPostReportsResponse; + listPrivateMessageReportsResponse?: ListPrivateMessageReportsResponse; unreadOrAll: UnreadOrAll; messageType: MessageType; combined: ItemType[]; @@ -81,17 +80,9 @@ interface ReportsState { } export class Reports extends Component<any, ReportsState> { - private isoData = setIsoData( - this.context, - ListCommentReportsResponse, - ListPostReportsResponse, - ListPrivateMessageReportsResponse - ); - private subscription: Subscription; - private emptyState: ReportsState = { - listCommentReportsResponse: None, - listPostReportsResponse: None, - listPrivateMessageReportsResponse: None, + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: ReportsState = { unreadOrAll: UnreadOrAll.Unread, messageType: MessageType.All, combined: [], @@ -103,10 +94,9 @@ export class Reports extends Component<any, ReportsState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handlePageChange = this.handlePageChange.bind(this); - if (UserService.Instance.myUserInfo.isNone() && isBrowser()) { + if (!UserService.Instance.myUserInfo && isBrowser()) { toast(i18n.t("not_logged_in"), "danger"); this.context.router.history.push(`/login`); } @@ -118,19 +108,16 @@ export class Reports extends Component<any, ReportsState> { if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - listCommentReportsResponse: Some( - this.isoData.routeData[0] as ListCommentReportsResponse - ), - listPostReportsResponse: Some( - this.isoData.routeData[1] as ListPostReportsResponse - ), + listCommentReportsResponse: this.isoData + .routeData[0] as ListCommentReportsResponse, + listPostReportsResponse: this.isoData + .routeData[1] as ListPostReportsResponse, }; if (amAdmin()) { this.state = { ...this.state, - listPrivateMessageReportsResponse: Some( - this.isoData.routeData[2] as ListPrivateMessageReportsResponse - ), + listPrivateMessageReportsResponse: this.isoData + .routeData[2] as ListPrivateMessageReportsResponse, }; } this.state = { @@ -145,18 +132,17 @@ export class Reports extends Component<any, ReportsState> { componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } get documentTitle(): string { - return UserService.Instance.myUserInfo.match({ - some: mui => - `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${ + let mui = UserService.Instance.myUserInfo; + return mui + ? `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${ this.state.siteRes.site_view.site.name - }`, - none: "", - }); + }` + : ""; } render() { @@ -172,8 +158,6 @@ export class Reports extends Component<any, ReportsState> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <h5 className="mb-2">{i18n.t("reports")}</h5> {this.selects()} @@ -331,19 +315,22 @@ export class Reports extends Component<any, ReportsState> { } buildCombined(): ItemType[] { - let comments: ItemType[] = this.state.listCommentReportsResponse - .map(r => r.comment_reports) - .unwrapOr([]) - .map(r => this.commentReportToItemType(r)); - let posts: ItemType[] = this.state.listPostReportsResponse - .map(r => r.post_reports) - .unwrapOr([]) - .map(r => this.postReportToItemType(r)); - let privateMessages: ItemType[] = - this.state.listPrivateMessageReportsResponse - .map(r => r.private_message_reports) - .unwrapOr([]) - .map(r => this.privateMessageReportToItemType(r)); + // let comments: ItemType[] = this.state.listCommentReportsResponse + // .map(r => r.comment_reports) + // .unwrapOr([]) + // .map(r => this.commentReportToItemType(r)); + let comments = + this.state.listCommentReportsResponse?.comment_reports.map( + this.commentReportToItemType + ) ?? []; + let posts = + this.state.listPostReportsResponse?.post_reports.map( + this.postReportToItemType + ) ?? []; + let privateMessages = + this.state.listPrivateMessageReportsResponse?.private_message_reports.map( + this.privateMessageReportToItemType + ) ?? []; return [...comments, ...posts, ...privateMessages].sort((a, b) => b.published.localeCompare(a.published) @@ -384,42 +371,44 @@ export class Reports extends Component<any, ReportsState> { } commentReports() { - return this.state.listCommentReportsResponse.match({ - some: res => ( + let reports = this.state.listCommentReportsResponse?.comment_reports; + return ( + reports && ( <div> - {res.comment_reports.map(cr => ( + {reports.map(cr => ( <> <hr /> <CommentReport key={cr.comment_report.id} report={cr} /> </> ))} </div> - ), - none: <></>, - }); + ) + ); } postReports() { - return this.state.listPostReportsResponse.match({ - some: res => ( + let reports = this.state.listPostReportsResponse?.post_reports; + return ( + reports && ( <div> - {res.post_reports.map(pr => ( + {reports.map(pr => ( <> <hr /> <PostReport key={pr.post_report.id} report={pr} /> </> ))} </div> - ), - none: <></>, - }); + ) + ); } privateMessageReports() { - return this.state.listPrivateMessageReportsResponse.match({ - some: res => ( + let reports = + this.state.listPrivateMessageReportsResponse?.private_message_reports; + return ( + reports && ( <div> - {res.private_message_reports.map(pmr => ( + {reports.map(pmr => ( <> <hr /> <PrivateMessageReport @@ -429,9 +418,8 @@ export class Reports extends Component<any, ReportsState> { </> ))} </div> - ), - none: <></>, - }); + ) + ); } handlePageChange(page: number) { @@ -452,85 +440,79 @@ export class Reports extends Component<any, ReportsState> { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { let promises: Promise<any>[] = []; - let unresolved_only = Some(true); - let page = Some(1); - let limit = Some(fetchLimit); - let community_id = None; - let auth = req.auth.unwrap(); - - let commentReportsForm = new ListCommentReports({ - // TODO community_id - unresolved_only, - community_id, - page, - limit, - auth, - }); - promises.push(req.client.listCommentReports(commentReportsForm)); - - let postReportsForm = new ListPostReports({ - // TODO community_id - unresolved_only, - community_id, - page, - limit, - auth, - }); - promises.push(req.client.listPostReports(postReportsForm)); - - if (amAdmin()) { - let privateMessageReportsForm = new ListPrivateMessageReports({ + let unresolved_only = true; + let page = 1; + let limit = fetchLimit; + let auth = req.auth; + + if (auth) { + let commentReportsForm: ListCommentReports = { unresolved_only, page, limit, auth, - }); - promises.push( - req.client.listPrivateMessageReports(privateMessageReportsForm) - ); + }; + promises.push(req.client.listCommentReports(commentReportsForm)); + + let postReportsForm: ListPostReports = { + unresolved_only, + page, + limit, + auth, + }; + promises.push(req.client.listPostReports(postReportsForm)); + + if (amAdmin()) { + let privateMessageReportsForm: ListPrivateMessageReports = { + unresolved_only, + page, + limit, + auth, + }; + promises.push( + req.client.listPrivateMessageReports(privateMessageReportsForm) + ); + } } return promises; } refetch() { - let unresolved_only = Some(this.state.unreadOrAll == UnreadOrAll.Unread); - let community_id = None; - let page = Some(this.state.page); - let limit = Some(fetchLimit); - - let commentReportsForm = new ListCommentReports({ - unresolved_only, - // TODO community_id - community_id, - page, - limit, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send( - wsClient.listCommentReports(commentReportsForm) - ); - - let postReportsForm = new ListPostReports({ - unresolved_only, - // TODO community_id - community_id, - page, - limit, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.listPostReports(postReportsForm)); - - if (amAdmin()) { - let privateMessageReportsForm = new ListPrivateMessageReports({ + let unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread; + let page = this.state.page; + let limit = fetchLimit; + let auth = myAuth(); + if (auth) { + let commentReportsForm: ListCommentReports = { unresolved_only, page, limit, - auth: auth().unwrap(), - }); + auth, + }; WebSocketService.Instance.send( - wsClient.listPrivateMessageReports(privateMessageReportsForm) + wsClient.listCommentReports(commentReportsForm) ); + + let postReportsForm: ListPostReports = { + unresolved_only, + page, + limit, + auth, + }; + WebSocketService.Instance.send(wsClient.listPostReports(postReportsForm)); + + if (amAdmin()) { + let privateMessageReportsForm: ListPrivateMessageReports = { + unresolved_only, + page, + limit, + auth, + }; + WebSocketService.Instance.send( + wsClient.listPrivateMessageReports(privateMessageReportsForm) + ); + } } } @@ -543,40 +525,31 @@ export class Reports extends Component<any, ReportsState> { } else if (msg.reconnect) { this.refetch(); } else if (op == UserOperation.ListCommentReports) { - let data = wsJsonToRes<ListCommentReportsResponse>( - msg, - ListCommentReportsResponse - ); - this.setState({ listCommentReportsResponse: Some(data) }); + let 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) { - let data = wsJsonToRes<ListPostReportsResponse>( - msg, - ListPostReportsResponse - ); - this.setState({ listPostReportsResponse: Some(data) }); + let 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) { - let data = wsJsonToRes<ListPrivateMessageReportsResponse>( - msg, - ListPrivateMessageReportsResponse - ); - this.setState({ listPrivateMessageReportsResponse: Some(data) }); + let 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) { - let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse); + let data = wsJsonToRes<PostReportResponse>(msg); updatePostReportRes( data.post_report_view, - this.state.listPostReportsResponse.map(r => r.post_reports).unwrapOr([]) + this.state.listPostReportsResponse?.post_reports ); let urcs = UserService.Instance.unreadReportCountSub; if (data.post_report_view.post_report.resolved) { @@ -586,12 +559,10 @@ export class Reports extends Component<any, ReportsState> { } this.setState(this.state); } else if (op == UserOperation.ResolveCommentReport) { - let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse); + let data = wsJsonToRes<CommentReportResponse>(msg); updateCommentReportRes( data.comment_report_view, - this.state.listCommentReportsResponse - .map(r => r.comment_reports) - .unwrapOr([]) + this.state.listCommentReportsResponse?.comment_reports ); let urcs = UserService.Instance.unreadReportCountSub; if (data.comment_report_view.comment_report.resolved) { @@ -601,15 +572,10 @@ export class Reports extends Component<any, ReportsState> { } this.setState(this.state); } else if (op == UserOperation.ResolvePrivateMessageReport) { - let data = wsJsonToRes<PrivateMessageReportResponse>( - msg, - PrivateMessageReportResponse - ); + let data = wsJsonToRes<PrivateMessageReportResponse>(msg); updatePrivateMessageReportRes( data.private_message_report_view, - this.state.listPrivateMessageReportsResponse - .map(r => r.private_message_reports) - .unwrapOr([]) + this.state.listPrivateMessageReportsResponse?.private_message_reports ); let urcs = UserService.Instance.unreadReportCountSub; if (data.private_message_report_view.private_message_report.resolved) { diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx index 0f904d5..734ae71 100644 --- a/src/shared/components/person/settings.tsx +++ b/src/shared/components/person/settings.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { BlockCommunity, @@ -16,7 +15,6 @@ import { PersonViewSafe, SaveUserSettings, SortType, - toUndefined, UserOperation, wsJsonToRes, wsUserOp, @@ -25,7 +23,6 @@ import { Subscription } from "rxjs"; import { i18n, languages } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; import { - auth, capitalizeFirstLetter, choicesConfig, communitySelectName, @@ -38,6 +35,7 @@ import { fetchUsers, getLanguages, isBrowser, + myAuth, personSelectName, personToChoice, relTags, @@ -67,11 +65,38 @@ if (isBrowser()) { } interface SettingsState { - saveUserSettingsForm: SaveUserSettings; - changePasswordForm: ChangePassword; - deleteAccountForm: DeleteAccount; + // TODO redo these forms + saveUserSettingsForm: { + show_nsfw?: boolean; + theme?: string; + default_sort_type?: number; + default_listing_type?: number; + interface_language?: string; + avatar?: string; + banner?: string; + display_name?: string; + email?: string; + bio?: string; + matrix_user_id?: string; + show_avatars?: boolean; + show_scores?: boolean; + send_notifications_to_email?: boolean; + bot_account?: boolean; + show_bot_accounts?: boolean; + show_read_posts?: boolean; + show_new_post_notifs?: boolean; + discussion_languages?: number[]; + }; + changePasswordForm: { + new_password?: string; + new_password_verify?: string; + old_password?: string; + }; + deleteAccountForm: { + password?: string; + }; personBlocks: PersonBlockView[]; - blockPerson: Option<PersonViewSafe>; + blockPerson?: PersonViewSafe; communityBlocks: CommunityBlockView[]; blockCommunityId: number; blockCommunity?: CommunityView; @@ -88,46 +113,16 @@ export class Settings extends Component<any, SettingsState> { private isoData = setIsoData(this.context); private blockPersonChoices: any; private blockCommunityChoices: any; - private subscription: Subscription; - private emptyState: SettingsState = { - saveUserSettingsForm: new SaveUserSettings({ - show_nsfw: None, - show_scores: None, - show_avatars: None, - show_read_posts: None, - show_bot_accounts: None, - show_new_post_notifs: None, - default_sort_type: None, - default_listing_type: None, - theme: None, - interface_language: None, - discussion_languages: None, - avatar: None, - banner: None, - display_name: None, - email: None, - bio: None, - matrix_user_id: None, - send_notifications_to_email: None, - bot_account: None, - auth: undefined, - }), - changePasswordForm: new ChangePassword({ - new_password: undefined, - new_password_verify: undefined, - old_password: undefined, - auth: undefined, - }), + private subscription?: Subscription; + state: SettingsState = { + saveUserSettingsForm: {}, + changePasswordForm: {}, saveUserSettingsLoading: false, changePasswordLoading: false, deleteAccountLoading: false, deleteAccountShowConfirm: false, - deleteAccountForm: new DeleteAccount({ - password: undefined, - auth: undefined, - }), + deleteAccountForm: {}, personBlocks: [], - blockPerson: None, communityBlocks: [], blockCommunityId: 0, currentTab: "settings", @@ -138,7 +133,6 @@ export class Settings extends Component<any, SettingsState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleSortTypeChange = this.handleSortTypeChange.bind(this); this.handleListingTypeChange = this.handleListingTypeChange.bind(this); this.handleBioChange = this.handleBioChange.bind(this); @@ -154,8 +148,8 @@ export class Settings extends Component<any, SettingsState> { this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); - if (UserService.Instance.myUserInfo.isSome()) { - let mui = UserService.Instance.myUserInfo.unwrap(); + let mui = UserService.Instance.myUserInfo; + if (mui) { let luv = mui.local_user_view; this.state = { ...this.state, @@ -163,26 +157,25 @@ export class Settings extends Component<any, SettingsState> { communityBlocks: mui.community_blocks, saveUserSettingsForm: { ...this.state.saveUserSettingsForm, - show_nsfw: Some(luv.local_user.show_nsfw), - theme: Some(luv.local_user.theme ? luv.local_user.theme : "browser"), - default_sort_type: Some(luv.local_user.default_sort_type), - default_listing_type: Some(luv.local_user.default_listing_type), - interface_language: Some(luv.local_user.interface_language), - discussion_languages: Some(mui.discussion_languages), + show_nsfw: luv.local_user.show_nsfw, + theme: luv.local_user.theme ? luv.local_user.theme : "browser", + default_sort_type: luv.local_user.default_sort_type, + default_listing_type: luv.local_user.default_listing_type, + interface_language: luv.local_user.interface_language, + discussion_languages: mui.discussion_languages, avatar: luv.person.avatar, banner: luv.person.banner, display_name: luv.person.display_name, - show_avatars: Some(luv.local_user.show_avatars), - bot_account: Some(luv.person.bot_account), - show_bot_accounts: Some(luv.local_user.show_bot_accounts), - show_scores: Some(luv.local_user.show_scores), - show_read_posts: Some(luv.local_user.show_read_posts), - show_new_post_notifs: Some(luv.local_user.show_new_post_notifs), + show_avatars: luv.local_user.show_avatars, + bot_account: luv.person.bot_account, + show_bot_accounts: luv.local_user.show_bot_accounts, + show_scores: luv.local_user.show_scores, + show_read_posts: luv.local_user.show_read_posts, + show_new_post_notifs: luv.local_user.show_new_post_notifs, email: luv.local_user.email, bio: luv.person.bio, - send_notifications_to_email: Some( - luv.local_user.send_notifications_to_email - ), + send_notifications_to_email: + luv.local_user.send_notifications_to_email, matrix_user_id: luv.person.matrix_user_id, }, }; @@ -195,7 +188,7 @@ export class Settings extends Component<any, SettingsState> { } componentWillUnmount() { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } get documentTitle(): string { @@ -209,7 +202,7 @@ export class Settings extends Component<any, SettingsState> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={Some(this.documentTitle)} + description={this.documentTitle} image={this.state.saveUserSettingsForm.avatar} /> <ul className="nav nav-tabs mb-2"> @@ -391,6 +384,7 @@ export class Settings extends Component<any, SettingsState> { } blockUserForm() { + let blockPerson = this.state.blockPerson; return ( <div className="form-group row"> <label @@ -403,17 +397,14 @@ export class Settings extends Component<any, SettingsState> { <select className="form-control" id="block-person-filter" - value={this.state.blockPerson.map(p => p.person.id).unwrapOr(0)} + value={blockPerson?.person.id ?? 0} > <option value="0">—</option> - {this.state.blockPerson.match({ - some: personView => ( - <option value={personView.person.id}> - {personSelectName(personView)} - </option> - ), - none: <></>, - })} + {blockPerson && ( + <option value={blockPerson.person.id}> + {personSelectName(blockPerson)} + </option> + )} </select> </div> </div> @@ -500,9 +491,7 @@ export class Settings extends Component<any, SettingsState> { type="text" className="form-control" placeholder={i18n.t("optional")} - value={toUndefined( - this.state.saveUserSettingsForm.display_name - )} + value={this.state.saveUserSettingsForm.display_name} onInput={linkEvent(this, this.handleDisplayNameChange)} pattern="^(?!@)(.+)$" minLength={3} @@ -516,11 +505,8 @@ export class Settings extends Component<any, SettingsState> { <div className="col-sm-9"> <MarkdownTextArea initialContent={this.state.saveUserSettingsForm.bio} - initialLanguageId={None} onContentChange={this.handleBioChange} - maxLength={Some(300)} - placeholder={None} - buttonTitle={None} + maxLength={300} hideNavigationWarnings allLanguages={this.state.siteRes.all_languages} siteLanguages={this.state.siteRes.discussion_languages} @@ -537,7 +523,7 @@ export class Settings extends Component<any, SettingsState> { id="user-email" className="form-control" placeholder={i18n.t("optional")} - value={toUndefined(this.state.saveUserSettingsForm.email)} + value={this.state.saveUserSettingsForm.email} onInput={linkEvent(this, this.handleEmailChange)} minLength={3} /> @@ -555,9 +541,7 @@ export class Settings extends Component<any, SettingsState> { type="text" className="form-control" placeholder="@user:example.com" - value={toUndefined( - this.state.saveUserSettingsForm.matrix_user_id - )} + value={this.state.saveUserSettingsForm.matrix_user_id} onInput={linkEvent(this, this.handleMatrixUserIdChange)} pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$" /> @@ -593,9 +577,7 @@ export class Settings extends Component<any, SettingsState> { <div className="col-sm-9"> <select id="user-language" - value={toUndefined( - this.state.saveUserSettingsForm.interface_language - )} + value={this.state.saveUserSettingsForm.interface_language} onChange={linkEvent(this, this.handleInterfaceLangChange)} className="custom-select w-auto" > @@ -631,7 +613,7 @@ export class Settings extends Component<any, SettingsState> { <div className="col-sm-9"> <select id="user-theme" - value={toUndefined(this.state.saveUserSettingsForm.theme)} + value={this.state.saveUserSettingsForm.theme} onChange={linkEvent(this, this.handleThemeChange)} className="custom-select w-auto" > @@ -653,9 +635,7 @@ export class Settings extends Component<any, SettingsState> { <ListingTypeSelect type_={ Object.values(ListingType)[ - this.state.saveUserSettingsForm.default_listing_type.unwrapOr( - 1 - ) + this.state.saveUserSettingsForm.default_listing_type ?? 1 ] } showLocal={showLocal(this.isoData)} @@ -670,9 +650,7 @@ export class Settings extends Component<any, SettingsState> { <SortSelect sort={ Object.values(SortType)[ - this.state.saveUserSettingsForm.default_sort_type.unwrapOr( - 0 - ) + this.state.saveUserSettingsForm.default_sort_type ?? 0 ] } onChange={this.handleSortTypeChange} @@ -686,9 +664,7 @@ export class Settings extends Component<any, SettingsState> { className="form-check-input" id="user-show-nsfw" type="checkbox" - checked={toUndefined( - this.state.saveUserSettingsForm.show_nsfw - )} + checked={this.state.saveUserSettingsForm.show_nsfw} onChange={linkEvent(this, this.handleShowNsfwChange)} /> <label className="form-check-label" htmlFor="user-show-nsfw"> @@ -703,9 +679,7 @@ export class Settings extends Component<any, SettingsState> { className="form-check-input" id="user-show-scores" type="checkbox" - checked={toUndefined( - this.state.saveUserSettingsForm.show_scores - )} + checked={this.state.saveUserSettingsForm.show_scores} onChange={linkEvent(this, this.handleShowScoresChange)} /> <label className="form-check-label" htmlFor="user-show-scores"> @@ -719,9 +693,7 @@ export class Settings extends Component<any, SettingsState> { className="form-check-input" id="user-show-avatars" type="checkbox" - checked={toUndefined( - this.state.saveUserSettingsForm.show_avatars - )} + checked={this.state.saveUserSettingsForm.show_avatars} onChange={linkEvent(this, this.handleShowAvatarsChange)} /> <label className="form-check-label" htmlFor="user-show-avatars"> @@ -735,9 +707,7 @@ export class Settings extends Component<any, SettingsState> { className="form-check-input" id="user-bot-account" type="checkbox" - checked={toUndefined( - this.state.saveUserSettingsForm.bot_account - )} + checked={this.state.saveUserSettingsForm.bot_account} onChange={linkEvent(this, this.handleBotAccount)} /> <label className="form-check-label" htmlFor="user-bot-account"> @@ -751,9 +721,7 @@ export class Settings extends Component<any, SettingsState> { className="form-check-input" id="user-show-bot-accounts" type="checkbox" - checked={toUndefined( - this.state.saveUserSettingsForm.show_bot_accounts - )} + checked={this.state.saveUserSettingsForm.show_bot_accounts} onChange={linkEvent(this, this.handleShowBotAccounts)} /> <label @@ -770,9 +738,7 @@ export class Settings extends Component<any, SettingsState> { className="form-check-input" id="user-show-read-posts" type="checkbox" - checked={toUndefined( - this.state.saveUserSettingsForm.show_read_posts - )} + checked={this.state.saveUserSettingsForm.show_read_posts} onChange={linkEvent(this, this.handleReadPosts)} /> <label @@ -789,9 +755,7 @@ export class Settings extends Component<any, SettingsState> { className="form-check-input" id="user-show-new-post-notifs" type="checkbox" - checked={toUndefined( - this.state.saveUserSettingsForm.show_new_post_notifs - )} + checked={this.state.saveUserSettingsForm.show_new_post_notifs} onChange={linkEvent(this, this.handleShowNewPostNotifs)} /> <label @@ -809,9 +773,9 @@ export class Settings extends Component<any, SettingsState> { id="user-send-notifications-to-email" type="checkbox" disabled={!this.state.saveUserSettingsForm.email} - checked={toUndefined( + checked={ this.state.saveUserSettingsForm.send_notifications_to_email - )} + } onChange={linkEvent( this, this.handleSendNotificationsToEmailChange @@ -959,32 +923,37 @@ export class Settings extends Component<any, SettingsState> { } handleBlockPerson(personId: number) { - if (personId != 0) { - let blockUserForm = new BlockPerson({ + let auth = myAuth(); + if (auth && personId != 0) { + let blockUserForm: BlockPerson = { person_id: personId, block: true, - auth: auth().unwrap(), - }); + auth, + }; WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); } } handleUnblockPerson(i: { ctx: Settings; recipientId: number }) { - let blockUserForm = new BlockPerson({ - person_id: i.recipientId, - block: false, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + let auth = myAuth(); + if (auth) { + let blockUserForm: BlockPerson = { + person_id: i.recipientId, + block: false, + auth, + }; + WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + } } handleBlockCommunity(community_id: number) { - if (community_id != 0) { - let blockCommunityForm = new BlockCommunity({ + let auth = myAuth(); + if (auth && community_id != 0) { + let blockCommunityForm: BlockCommunity = { community_id, block: true, - auth: auth().unwrap(), - }); + auth, + }; WebSocketService.Instance.send( wsClient.blockCommunity(blockCommunityForm) ); @@ -992,94 +961,93 @@ export class Settings extends Component<any, SettingsState> { } handleUnblockCommunity(i: { ctx: Settings; communityId: number }) { - let blockCommunityForm = new BlockCommunity({ - community_id: i.communityId, - block: false, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm)); + let auth = myAuth(); + if (auth) { + let blockCommunityForm: BlockCommunity = { + community_id: i.communityId, + block: false, + auth, + }; + WebSocketService.Instance.send( + wsClient.blockCommunity(blockCommunityForm) + ); + } } handleShowNsfwChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.show_nsfw = Some(event.target.checked); + i.state.saveUserSettingsForm.show_nsfw = event.target.checked; i.setState(i.state); } handleShowAvatarsChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.show_avatars = Some(event.target.checked); - UserService.Instance.myUserInfo.match({ - some: mui => - (mui.local_user_view.local_user.show_avatars = event.target.checked), - none: void 0, - }); + i.state.saveUserSettingsForm.show_avatars = event.target.checked; + let mui = UserService.Instance.myUserInfo; + if (mui) { + mui.local_user_view.local_user.show_avatars = event.target.checked; + } i.setState(i.state); } handleBotAccount(i: Settings, event: any) { - i.state.saveUserSettingsForm.bot_account = Some(event.target.checked); + i.state.saveUserSettingsForm.bot_account = event.target.checked; i.setState(i.state); } handleShowBotAccounts(i: Settings, event: any) { - i.state.saveUserSettingsForm.show_bot_accounts = Some(event.target.checked); + i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked; i.setState(i.state); } handleReadPosts(i: Settings, event: any) { - i.state.saveUserSettingsForm.show_read_posts = Some(event.target.checked); + i.state.saveUserSettingsForm.show_read_posts = event.target.checked; i.setState(i.state); } handleShowNewPostNotifs(i: Settings, event: any) { - i.state.saveUserSettingsForm.show_new_post_notifs = Some( - event.target.checked - ); + i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked; i.setState(i.state); } handleShowScoresChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.show_scores = Some(event.target.checked); - UserService.Instance.myUserInfo.match({ - some: mui => - (mui.local_user_view.local_user.show_scores = event.target.checked), - none: void 0, - }); + i.state.saveUserSettingsForm.show_scores = event.target.checked; + let mui = UserService.Instance.myUserInfo; + if (mui) { + mui.local_user_view.local_user.show_scores = event.target.checked; + } i.setState(i.state); } handleSendNotificationsToEmailChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.send_notifications_to_email = Some( - event.target.checked - ); + i.state.saveUserSettingsForm.send_notifications_to_email = + event.target.checked; i.setState(i.state); } handleThemeChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.theme = Some(event.target.value); + i.state.saveUserSettingsForm.theme = event.target.value; setTheme(event.target.value, true); i.setState(i.state); } handleInterfaceLangChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.interface_language = Some(event.target.value); + i.state.saveUserSettingsForm.interface_language = event.target.value; i18n.changeLanguage( - getLanguages(i.state.saveUserSettingsForm.interface_language.unwrap())[0] + getLanguages(i.state.saveUserSettingsForm.interface_language).at(0) ); i.setState(i.state); } handleDiscussionLanguageChange(val: number[]) { this.setState( - s => ((s.saveUserSettingsForm.discussion_languages = Some(val)), s) + s => ((s.saveUserSettingsForm.discussion_languages = val), s) ); } handleSortTypeChange(val: SortType) { this.setState( s => ( - (s.saveUserSettingsForm.default_sort_type = Some( - Object.keys(SortType).indexOf(val) - )), + (s.saveUserSettingsForm.default_sort_type = + Object.keys(SortType).indexOf(val)), s ) ); @@ -1088,46 +1056,45 @@ export class Settings extends Component<any, SettingsState> { handleListingTypeChange(val: ListingType) { this.setState( s => ( - (s.saveUserSettingsForm.default_listing_type = Some( - Object.keys(ListingType).indexOf(val) - )), + (s.saveUserSettingsForm.default_listing_type = + Object.keys(ListingType).indexOf(val)), s ) ); } handleEmailChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.email = Some(event.target.value); + i.state.saveUserSettingsForm.email = event.target.value; i.setState(i.state); } handleBioChange(val: string) { - this.setState(s => ((s.saveUserSettingsForm.bio = Some(val)), s)); + this.setState(s => ((s.saveUserSettingsForm.bio = val), s)); } handleAvatarUpload(url: string) { - this.setState(s => ((s.saveUserSettingsForm.avatar = Some(url)), s)); + this.setState(s => ((s.saveUserSettingsForm.avatar = url), s)); } handleAvatarRemove() { - this.setState(s => ((s.saveUserSettingsForm.avatar = Some("")), s)); + this.setState(s => ((s.saveUserSettingsForm.avatar = ""), s)); } handleBannerUpload(url: string) { - this.setState(s => ((s.saveUserSettingsForm.banner = Some(url)), s)); + this.setState(s => ((s.saveUserSettingsForm.banner = url), s)); } handleBannerRemove() { - this.setState(s => ((s.saveUserSettingsForm.banner = Some("")), s)); + this.setState(s => ((s.saveUserSettingsForm.banner = ""), s)); } handleDisplayNameChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.display_name = Some(event.target.value); + i.state.saveUserSettingsForm.display_name = event.target.value; i.setState(i.state); } handleMatrixUserIdChange(i: Settings, event: any) { - i.state.saveUserSettingsForm.matrix_user_id = Some(event.target.value); + i.state.saveUserSettingsForm.matrix_user_id = event.target.value; i.setState(i.state); } @@ -1158,20 +1125,31 @@ export class Settings extends Component<any, SettingsState> { handleSaveSettingsSubmit(i: Settings, event: any) { event.preventDefault(); i.setState({ saveUserSettingsLoading: true }); - i.setState(s => ((s.saveUserSettingsForm.auth = auth().unwrap()), s)); - - let form = new SaveUserSettings({ ...i.state.saveUserSettingsForm }); - WebSocketService.Instance.send(wsClient.saveUserSettings(form)); + let auth = myAuth(); + if (auth) { + let form: SaveUserSettings = { ...i.state.saveUserSettingsForm, auth }; + WebSocketService.Instance.send(wsClient.saveUserSettings(form)); + } } handleChangePasswordSubmit(i: Settings, event: any) { event.preventDefault(); i.setState({ changePasswordLoading: true }); - i.setState(s => ((s.changePasswordForm.auth = auth().unwrap()), s)); - - let form = new ChangePassword({ ...i.state.changePasswordForm }); + let auth = myAuth(); + let pForm = i.state.changePasswordForm; + let new_password = pForm.new_password; + let new_password_verify = pForm.new_password_verify; + let old_password = pForm.old_password; + if (auth && new_password && old_password && new_password_verify) { + let form: ChangePassword = { + new_password, + new_password_verify, + old_password, + auth, + }; - WebSocketService.Instance.send(wsClient.changePassword(form)); + WebSocketService.Instance.send(wsClient.changePassword(form)); + } } handleDeleteAccountShowConfirmToggle(i: Settings, event: any) { @@ -1187,11 +1165,15 @@ export class Settings extends Component<any, SettingsState> { handleDeleteAccount(i: Settings, event: any) { event.preventDefault(); i.setState({ deleteAccountLoading: true }); - i.setState(s => ((s.deleteAccountForm.auth = auth().unwrap()), s)); - - let form = new DeleteAccount({ ...i.state.deleteAccountForm }); - - WebSocketService.Instance.send(wsClient.deleteAccount(form)); + let auth = myAuth(); + let password = i.state.deleteAccountForm.password; + if (auth && password) { + let form: DeleteAccount = { + password, + auth, + }; + WebSocketService.Instance.send(wsClient.deleteAccount(form)); + } } handleSwitchTab(i: { ctx: Settings; tab: string }) { @@ -1215,14 +1197,14 @@ export class Settings extends Component<any, SettingsState> { toast(i18n.t(msg.error), "danger"); return; } else if (op == UserOperation.SaveUserSettings) { - let data = wsJsonToRes<LoginResponse>(msg, LoginResponse); + let data = wsJsonToRes<LoginResponse>(msg); UserService.Instance.login(data); location.reload(); this.setState({ saveUserSettingsLoading: false }); toast(i18n.t("saved")); window.scrollTo(0, 0); } else if (op == UserOperation.ChangePassword) { - let data = wsJsonToRes<LoginResponse>(msg, LoginResponse); + let data = wsJsonToRes<LoginResponse>(msg); UserService.Instance.login(data); this.setState({ changePasswordLoading: false }); window.scrollTo(0, 0); @@ -1235,20 +1217,19 @@ export class Settings extends Component<any, SettingsState> { UserService.Instance.logout(); window.location.href = "/"; } else if (op == UserOperation.BlockPerson) { - let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse); - updatePersonBlock(data).match({ - some: blocks => this.setState({ personBlocks: blocks }), - none: void 0, - }); + let data = wsJsonToRes<BlockPersonResponse>(msg); + updatePersonBlock(data); + let mui = UserService.Instance.myUserInfo; + if (mui) { + this.setState({ personBlocks: mui.person_blocks }); + } } else if (op == UserOperation.BlockCommunity) { - let data = wsJsonToRes<BlockCommunityResponse>( - msg, - BlockCommunityResponse - ); - updateCommunityBlock(data).match({ - some: blocks => this.setState({ communityBlocks: blocks }), - none: void 0, - }); + let data = wsJsonToRes<BlockCommunityResponse>(msg); + updateCommunityBlock(data); + let mui = UserService.Instance.myUserInfo; + if (mui) { + this.setState({ communityBlocks: mui.community_blocks }); + } } } } diff --git a/src/shared/components/person/verify-email.tsx b/src/shared/components/person/verify-email.tsx index 819db29..14231b8 100644 --- a/src/shared/components/person/verify-email.tsx +++ b/src/shared/components/person/verify-email.tsx @@ -1,4 +1,3 @@ -import { None } from "@sniptt/monads/build"; import { Component } from "inferno"; import { GetSiteResponse, @@ -27,20 +26,18 @@ interface State { export class VerifyEmail extends Component<any, State> { private isoData = setIsoData(this.context); - private subscription: Subscription; + private subscription?: Subscription; - emptyState: State = { - verifyEmailForm: new VerifyEmailForm({ + state: State = { + verifyEmailForm: { token: this.props.match.params.token, - }), + }, siteRes: this.isoData.site_res, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; - this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); } @@ -53,7 +50,7 @@ export class VerifyEmail extends Component<any, State> { componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -69,8 +66,6 @@ export class VerifyEmail extends Component<any, State> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <div className="row"> <div className="col-12 col-lg-6 offset-lg-3 mb-4"> @@ -90,10 +85,9 @@ export class VerifyEmail extends Component<any, State> { this.props.history.push("/"); return; } else if (op == UserOperation.VerifyEmail) { - let data = wsJsonToRes<VerifyEmailResponse>(msg, VerifyEmailResponse); + let data = wsJsonToRes<VerifyEmailResponse>(msg); if (data) { toast(i18n.t("email_verified")); - this.setState(this.emptyState); this.props.history.push("/login"); } } diff --git a/src/shared/components/post/create-post.tsx b/src/shared/components/post/create-post.tsx index 37c855f..c1d282e 100644 --- a/src/shared/components/post/create-post.tsx +++ b/src/shared/components/post/create-post.tsx @@ -1,4 +1,3 @@ -import { Either, Left, None, Option, Right, Some } from "@sniptt/monads"; import { Component } from "inferno"; import { GetCommunity, @@ -9,7 +8,6 @@ import { ListingType, PostView, SortType, - toOption, UserOperation, wsJsonToRes, wsUserOp, @@ -19,11 +17,11 @@ import { InitialFetchRequest, PostFormParams } from "shared/interfaces"; import { i18n } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; import { - auth, enableDownvotes, enableNsfw, fetchLimit, isBrowser, + myAuth, setIsoData, toast, wsClient, @@ -34,30 +32,28 @@ import { Spinner } from "../common/icon"; import { PostForm } from "./post-form"; interface CreatePostState { - listCommunitiesResponse: Option<ListCommunitiesResponse>; + listCommunitiesResponse?: ListCommunitiesResponse; siteRes: GetSiteResponse; loading: boolean; } export class CreatePost extends Component<any, CreatePostState> { - private isoData = setIsoData(this.context, ListCommunitiesResponse); - private subscription: Subscription; - private emptyState: CreatePostState = { + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: CreatePostState = { siteRes: this.isoData.site_res, - listCommunitiesResponse: None, loading: true, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handlePostCreate = this.handlePostCreate.bind(this); this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); - if (UserService.Instance.myUserInfo.isNone() && isBrowser()) { + if (!UserService.Instance.myUserInfo && isBrowser()) { toast(i18n.t("not_logged_in"), "danger"); this.context.router.history.push(`/login`); } @@ -66,9 +62,8 @@ export class CreatePost extends Component<any, CreatePostState> { if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - listCommunitiesResponse: Some( - this.isoData.routeData[0] as ListCommunitiesResponse - ), + listCommunitiesResponse: this.isoData + .routeData[0] as ListCommunitiesResponse, loading: false, }; } else { @@ -77,44 +72,38 @@ export class CreatePost extends Component<any, CreatePostState> { } refetch() { - this.params.nameOrId.match({ - some: opt => - opt.match({ - left: name => { - let form = new GetCommunity({ - name: Some(name), - id: None, - auth: auth(false).ok(), - }); - WebSocketService.Instance.send(wsClient.getCommunity(form)); - }, - right: id => { - let form = new GetCommunity({ - id: Some(id), - name: None, - auth: auth(false).ok(), - }); - WebSocketService.Instance.send(wsClient.getCommunity(form)); - }, - }), - none: () => { - let listCommunitiesForm = new ListCommunities({ - type_: Some(ListingType.All), - sort: Some(SortType.TopAll), - limit: Some(fetchLimit), - page: None, - auth: auth(false).ok(), - }); - WebSocketService.Instance.send( - wsClient.listCommunities(listCommunitiesForm) - ); - }, - }); + let nameOrId = this.params.nameOrId; + let auth = myAuth(false); + if (nameOrId) { + if (typeof nameOrId === "string") { + let form: GetCommunity = { + name: nameOrId, + auth, + }; + WebSocketService.Instance.send(wsClient.getCommunity(form)); + } else { + let form: GetCommunity = { + id: nameOrId, + auth, + }; + WebSocketService.Instance.send(wsClient.getCommunity(form)); + } + } else { + let listCommunitiesForm: ListCommunities = { + type_: ListingType.All, + sort: SortType.TopAll, + limit: fetchLimit, + auth, + }; + WebSocketService.Instance.send( + wsClient.listCommunities(listCommunitiesForm) + ); + } } componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } @@ -125,39 +114,34 @@ export class CreatePost extends Component<any, CreatePostState> { } render() { + let res = this.state.listCommunitiesResponse; return ( <div className="container-lg"> <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> {this.state.loading ? ( <h5> <Spinner large /> </h5> ) : ( - this.state.listCommunitiesResponse.match({ - some: res => ( - <div className="row"> - <div className="col-12 col-lg-6 offset-lg-3 mb-4"> - <h5>{i18n.t("create_post")}</h5> - <PostForm - post_view={None} - communities={Some(res.communities)} - onCreate={this.handlePostCreate} - params={Some(this.params)} - enableDownvotes={enableDownvotes(this.state.siteRes)} - enableNsfw={enableNsfw(this.state.siteRes)} - allLanguages={this.state.siteRes.all_languages} - siteLanguages={this.state.siteRes.discussion_languages} - /> - </div> + res && ( + <div className="row"> + <div className="col-12 col-lg-6 offset-lg-3 mb-4"> + <h5>{i18n.t("create_post")}</h5> + <PostForm + communities={res.communities} + onCreate={this.handlePostCreate} + params={this.params} + enableDownvotes={enableDownvotes(this.state.siteRes)} + enableNsfw={enableNsfw(this.state.siteRes)} + allLanguages={this.state.siteRes.all_languages} + siteLanguages={this.state.siteRes.discussion_languages} + /> </div> - ), - none: <></>, - }) + </div> + ) )} </div> ); @@ -165,48 +149,42 @@ export class CreatePost extends Component<any, CreatePostState> { get params(): PostFormParams { let urlParams = new URLSearchParams(this.props.location.search); - let name = toOption(urlParams.get("community_name")).or( - this.prevCommunityName - ); - let id = toOption(urlParams.get("community_id")) - .map(Number) - .or(this.prevCommunityId); - let nameOrId: Option<Either<string, number>>; - if (name.isSome()) { - nameOrId = Some(Left(name.unwrap())); - } else if (id.isSome()) { - nameOrId = Some(Right(id.unwrap())); - } else { - nameOrId = None; + let name = urlParams.get("community_name") ?? this.prevCommunityName; + let communityIdParam = urlParams.get("community_id"); + let id = communityIdParam ? Number(communityIdParam) : this.prevCommunityId; + let nameOrId: string | number | undefined; + if (name) { + nameOrId = name; + } else if (id) { + nameOrId = id; } let params: PostFormParams = { - name: toOption(urlParams.get("title")), + name: urlParams.get("title") ?? undefined, nameOrId, - body: toOption(urlParams.get("body")), - url: toOption(urlParams.get("url")), + body: urlParams.get("body") ?? undefined, + url: urlParams.get("url") ?? undefined, }; return params; } - get prevCommunityName(): Option<string> { + get prevCommunityName(): string | undefined { if (this.props.match.params.name) { - return toOption(this.props.match.params.name); + return this.props.match.params.name; } else if (this.props.location.state) { let lastLocation = this.props.location.state.prevPath; if (lastLocation.includes("/c/")) { - return toOption(lastLocation.split("/c/")[1]); + return lastLocation.split("/c/").at(1); } } - return None; + return undefined; } - get prevCommunityId(): Option<number> { - if (this.props.match.params.id) { - return toOption(this.props.match.params.id); - } - return None; + get prevCommunityId(): number | undefined { + // TODO is this actually a number? Whats the real return type + let id = this.props.match.params.id; + return id ?? undefined; } handlePostCreate(post_view: PostView) { @@ -214,13 +192,12 @@ export class CreatePost extends Component<any, CreatePostState> { } static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { - let listCommunitiesForm = new ListCommunities({ - type_: Some(ListingType.All), - sort: Some(SortType.TopAll), - limit: Some(fetchLimit), - page: None, + let listCommunitiesForm: ListCommunities = { + type_: ListingType.All, + sort: SortType.TopAll, + limit: fetchLimit, auth: req.auth, - }); + }; return [req.client.listCommunities(listCommunitiesForm)]; } @@ -231,17 +208,14 @@ export class CreatePost extends Component<any, CreatePostState> { toast(i18n.t(msg.error), "danger"); return; } else if (op == UserOperation.ListCommunities) { - let data = wsJsonToRes<ListCommunitiesResponse>( - msg, - ListCommunitiesResponse - ); - this.setState({ listCommunitiesResponse: Some(data), loading: false }); + let data = wsJsonToRes<ListCommunitiesResponse>(msg); + this.setState({ listCommunitiesResponse: data, loading: false }); } else if (op == UserOperation.GetCommunity) { - let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse); + let data = wsJsonToRes<GetCommunityResponse>(msg); this.setState({ - listCommunitiesResponse: Some({ + listCommunitiesResponse: { communities: [data.community_view], - }), + }, loading: false, }); } diff --git a/src/shared/components/post/metadata-card.tsx b/src/shared/components/post/metadata-card.tsx index a586064..74a4ba6 100644 --- a/src/shared/components/post/metadata-card.tsx +++ b/src/shared/components/post/metadata-card.tsx @@ -17,84 +17,66 @@ export class MetadataCard extends Component< MetadataCardProps, MetadataCardState > { - private emptyState: MetadataCardState = { + state: MetadataCardState = { expanded: false, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; } render() { let post = this.props.post; return ( <> - {!this.state.expanded && - post.embed_title.match({ - some: embedTitle => - post.url.match({ - some: url => ( - <div className="card border-secondary mt-3 mb-2"> - <div className="row"> - <div className="col-12"> - <div className="card-body"> - {post.name !== embedTitle && ( - <> - <h5 className="card-title d-inline"> - <a - className="text-body" - href={url} - rel={relTags} - > - {embedTitle} - </a> - </h5> - <span className="d-inline-block ml-2 mb-2 small text-muted"> - <a - className="text-muted font-italic" - href={url} - rel={relTags} - > - {new URL(url).hostname} - <Icon icon="external-link" classes="ml-1" /> - </a> - </span> - </> - )} - {post.embed_description.match({ - some: desc => ( - <div - className="card-text small text-muted md-div" - dangerouslySetInnerHTML={{ - __html: sanitizeHtml(desc), - }} - /> - ), - none: <></>, - })} - {post.embed_video_url.isSome() && ( - <button - className="mt-2 btn btn-secondary text-monospace" - onClick={linkEvent(this, this.handleIframeExpand)} - > - {i18n.t("expand_here")} - </button> - )} - </div> - </div> - </div> - </div> - ), - none: <></>, - }), - none: <></>, - })} - {this.state.expanded && - post.embed_video_url.match({ - some: video_url => <iframe src={video_url}></iframe>, - none: <></>, - })} + {!this.state.expanded && post.embed_title && post.url && ( + <div className="card border-secondary mt-3 mb-2"> + <div className="row"> + <div className="col-12"> + <div className="card-body"> + {post.name !== post.embed_title && ( + <> + <h5 className="card-title d-inline"> + <a className="text-body" href={post.url} rel={relTags}> + {post.embed_title} + </a> + </h5> + <span className="d-inline-block ml-2 mb-2 small text-muted"> + <a + className="text-muted font-italic" + href={post.url} + rel={relTags} + > + {new URL(post.url).hostname} + <Icon icon="external-link" classes="ml-1" /> + </a> + </span> + </> + )} + {post.embed_description && ( + <div + className="card-text small text-muted md-div" + dangerouslySetInnerHTML={{ + __html: sanitizeHtml(post.embed_description), + }} + /> + )} + {post.embed_video_url && ( + <button + className="mt-2 btn btn-secondary text-monospace" + onClick={linkEvent(this, this.handleIframeExpand)} + > + {i18n.t("expand_here")} + </button> + )} + </div> + </div> + </div> + </div> + )} + {this.state.expanded && post.embed_video_url && ( + <iframe src={post.embed_video_url}></iframe> + )} </> ); } diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index abfb543..490f7dc 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import autosize from "autosize"; import { Component, linkEvent } from "inferno"; import { Prompt } from "inferno-router"; @@ -14,7 +13,6 @@ import { SearchResponse, SearchType, SortType, - toUndefined, UserOperation, wsJsonToRes, wsUserOp, @@ -26,7 +24,6 @@ import { PostFormParams } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { archiveTodayUrl, - auth, capitalizeFirstLetter, choicesConfig, communitySelectName, @@ -37,6 +34,7 @@ import { ghostArchiveUrl, isBrowser, isImage, + myAuth, myFirstDiscussionLanguageId, pictrsDeleteToast, relTags, @@ -62,11 +60,11 @@ if (isBrowser()) { const MAX_POST_TITLE_LENGTH = 200; interface PostFormProps { - post_view: Option<PostView>; // If a post is given, that means this is an edit + post_view?: PostView; // If a post is given, that means this is an edit allLanguages: Language[]; siteLanguages: number[]; - communities: Option<CommunityView[]>; - params: Option<PostFormParams>; + communities?: CommunityView[]; + params?: PostFormParams; onCancel?(): any; onCreate?(post: PostView): any; onEdit?(post: PostView): any; @@ -75,10 +73,18 @@ interface PostFormProps { } interface PostFormState { - postForm: CreatePost; - suggestedTitle: Option<string>; - suggestedPosts: Option<PostView[]>; - crossPosts: Option<PostView[]>; + form: { + name?: string; + url?: string; + body?: string; + nsfw?: boolean; + language_id?: number; + community_id?: number; + honeypot?: string; + }; + suggestedTitle?: string; + suggestedPosts?: PostView[]; + crossPosts?: PostView[]; loading: boolean; imageLoading: boolean; communitySearchLoading: boolean; @@ -86,26 +92,14 @@ interface PostFormState { } export class PostForm extends Component<PostFormProps, PostFormState> { - private subscription: Subscription; + private subscription?: Subscription; private choices: any; - private emptyState: PostFormState = { - postForm: new CreatePost({ - community_id: undefined, - name: undefined, - nsfw: Some(false), - url: None, - body: None, - honeypot: None, - language_id: None, - auth: undefined, - }), + state: PostFormState = { + form: {}, loading: false, imageLoading: false, communitySearchLoading: false, previewMode: false, - suggestedTitle: None, - suggestedPosts: None, - crossPosts: None, }; constructor(props: any, context: any) { @@ -115,37 +109,32 @@ export class PostForm extends Component<PostFormProps, PostFormState> { this.handlePostBodyChange = this.handlePostBodyChange.bind(this); this.handleLanguageChange = this.handleLanguageChange.bind(this); - this.state = this.emptyState; - this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); // Means its an edit - if (this.props.post_view.isSome()) { - let pv = this.props.post_view.unwrap(); - + let pv = this.props.post_view; + if (pv) { this.state = { ...this.state, - postForm: new CreatePost({ + form: { body: pv.post.body, name: pv.post.name, community_id: pv.community.id, url: pv.post.url, - nsfw: Some(pv.post.nsfw), - honeypot: None, - language_id: Some(pv.post.language_id), - auth: auth().unwrap(), - }), + nsfw: pv.post.nsfw, + language_id: pv.post.language_id, + }, }; } - if (this.props.params.isSome()) { - let params = this.props.params.unwrap(); + let params = this.props.params; + if (params) { this.state = { ...this.state, - postForm: { - ...this.state.postForm, - name: toUndefined(params.name), + form: { + ...this.state.form, + name: params.name, url: params.url, body: params.body, }, @@ -165,41 +154,39 @@ export class PostForm extends Component<PostFormProps, PostFormState> { componentDidUpdate() { if ( !this.state.loading && - (this.state.postForm.name || - this.state.postForm.url.isSome() || - this.state.postForm.body.isSome()) + (this.state.form.name || this.state.form.url || this.state.form.body) ) { window.onbeforeunload = () => true; } else { - window.onbeforeunload = undefined; + window.onbeforeunload = null; } } componentWillUnmount() { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); /* this.choices && this.choices.destroy(); */ window.onbeforeunload = null; } render() { - let selectedLangs = this.state.postForm.language_id - .or( - myFirstDiscussionLanguageId( - this.props.allLanguages, - this.props.siteLanguages, - UserService.Instance.myUserInfo - ) - ) - .map(Array.of); + let firstLang = + this.state.form.language_id ?? + myFirstDiscussionLanguageId( + this.props.allLanguages, + this.props.siteLanguages, + UserService.Instance.myUserInfo + ); + let selectedLangs = firstLang ? Array.of(firstLang) : undefined; + let url = this.state.form.url; return ( <div> <Prompt when={ !this.state.loading && - (this.state.postForm.name || - this.state.postForm.url.isSome() || - this.state.postForm.body.isSome()) + (this.state.form.name || + this.state.form.url || + this.state.form.body) } message={i18n.t("block_leaving")} /> @@ -213,27 +200,25 @@ export class PostForm extends Component<PostFormProps, PostFormState> { type="url" id="post-url" className="form-control" - value={toUndefined(this.state.postForm.url)} + value={this.state.form.url} onInput={linkEvent(this, this.handlePostUrlChange)} onPaste={linkEvent(this, this.handleImageUploadPaste)} /> - {this.state.suggestedTitle.match({ - some: title => ( - <div - className="mt-1 text-muted small font-weight-bold pointer" - role="button" - onClick={linkEvent(this, this.copySuggestedTitle)} - > - {i18n.t("copy_suggested_title", { title: "" })} {title} - </div> - ), - none: <></>, - })} + {this.state.suggestedTitle && ( + <div + className="mt-1 text-muted small font-weight-bold pointer" + role="button" + onClick={linkEvent(this, this.copySuggestedTitle)} + > + {i18n.t("copy_suggested_title", { title: "" })}{" "} + {this.state.suggestedTitle} + </div> + )} <form> <label htmlFor="file-upload" className={`${ - UserService.Instance.myUserInfo.isSome() && "pointer" + UserService.Instance.myUserInfo && "pointer" } d-inline-block float-right text-muted font-weight-bold`} data-tippy-content={i18n.t("upload_image")} > @@ -245,72 +230,58 @@ export class PostForm extends Component<PostFormProps, PostFormState> { accept="image/*,video/*" name="file" className="d-none" - disabled={UserService.Instance.myUserInfo.isNone()} + disabled={!UserService.Instance.myUserInfo} onChange={linkEvent(this, this.handleImageUpload)} /> </form> - {this.state.postForm.url.match({ - some: 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> - </div> - ), - none: <></>, - })} + {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> + </div> + )} {this.state.imageLoading && <Spinner />} - {this.state.postForm.url.match({ - some: url => - isImage(url) && ( - <img src={url} className="img-fluid" alt="" /> - ), - none: <></>, - })} - {this.state.crossPosts.match({ - some: xPosts => - xPosts.length > 0 && ( - <> - <div className="my-1 text-muted small font-weight-bold"> - {i18n.t("cross_posts")} - </div> - <PostListings - showCommunity - posts={xPosts} - enableDownvotes={this.props.enableDownvotes} - enableNsfw={this.props.enableNsfw} - allLanguages={this.props.allLanguages} - siteLanguages={this.props.siteLanguages} - /> - </> - ), - none: <></>, - })} + {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> </div> <div className="form-group row"> @@ -319,41 +290,38 @@ export class PostForm extends Component<PostFormProps, PostFormState> { </label> <div className="col-sm-10"> <textarea - value={this.state.postForm.name} + value={this.state.form.name} id="post-title" onInput={linkEvent(this, this.handlePostNameChange)} className={`form-control ${ - !validTitle(this.state.postForm.name) && "is-invalid" + !validTitle(this.state.form.name) && "is-invalid" }`} required rows={1} minLength={3} maxLength={MAX_POST_TITLE_LENGTH} /> - {!validTitle(this.state.postForm.name) && ( + {!validTitle(this.state.form.name) && ( <div className="invalid-feedback"> {i18n.t("invalid_post_title")} </div> )} - {this.state.suggestedPosts.match({ - some: sPosts => - sPosts.length > 0 && ( - <> - <div className="my-1 text-muted small font-weight-bold"> - {i18n.t("related_posts")} - </div> - <PostListings - showCommunity - posts={sPosts} - enableDownvotes={this.props.enableDownvotes} - enableNsfw={this.props.enableNsfw} - allLanguages={this.props.allLanguages} - siteLanguages={this.props.siteLanguages} - /> - </> - ), - none: <></>, - })} + {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> @@ -361,18 +329,14 @@ export class PostForm extends Component<PostFormProps, PostFormState> { <label className="col-sm-2 col-form-label">{i18n.t("body")}</label> <div className="col-sm-10"> <MarkdownTextArea - initialContent={this.state.postForm.body} - initialLanguageId={None} + initialContent={this.state.form.body} onContentChange={this.handlePostBodyChange} - placeholder={None} - buttonTitle={None} - maxLength={None} allLanguages={this.props.allLanguages} siteLanguages={this.props.siteLanguages} /> </div> </div> - {this.props.post_view.isNone() && ( + {!this.props.post_view && ( <div className="form-group row"> <label className="col-sm-2 col-form-label" @@ -388,11 +352,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> { <select className="form-control" id="post-community" - value={this.state.postForm.community_id} + value={this.state.form.community_id} onInput={linkEvent(this, this.handlePostCommunityChange)} > <option>{i18n.t("select_a_community")}</option> - {this.props.communities.unwrapOr([]).map(cv => ( + {this.props.communities?.map(cv => ( <option key={cv.community.id} value={cv.community.id}> {communitySelectName(cv)} </option> @@ -412,7 +376,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> { className="form-check-input position-static" id="post-nsfw" type="checkbox" - checked={toUndefined(this.state.postForm.nsfw)} + checked={this.state.form.nsfw} onChange={linkEvent(this, this.handlePostNsfwChange)} /> </div> @@ -433,27 +397,25 @@ export class PostForm extends Component<PostFormProps, PostFormState> { type="text" className="form-control honeypot" id="register-honey" - value={toUndefined(this.state.postForm.honeypot)} + value={this.state.form.honeypot} onInput={linkEvent(this, this.handleHoneyPotChange)} /> <div className="form-group row"> <div className="col-sm-10"> <button - disabled={ - !this.state.postForm.community_id || this.state.loading - } + 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.isSome() ? ( + ) : this.props.post_view ? ( capitalizeFirstLetter(i18n.t("save")) ) : ( capitalizeFirstLetter(i18n.t("create")) )} </button> - {this.props.post_view.isSome() && ( + {this.props.post_view && ( <button type="button" className="btn btn-secondary" @@ -475,139 +437,133 @@ export class PostForm extends Component<PostFormProps, PostFormState> { i.setState({ loading: true }); // Coerce empty url string to undefined - if ( - i.state.postForm.url.isSome() && - i.state.postForm.url.unwrapOr("blank") === "" - ) { - i.setState(s => ((s.postForm.url = None), s)); + if ((i.state.form.url ?? "blank") === "") { + i.setState(s => ((s.form.url = undefined), s)); } - let pForm = i.state.postForm; - i.props.post_view.match({ - some: pv => { - let form = new EditPost({ - name: Some(pForm.name), + let pForm = i.state.form; + let pv = i.props.post_view; + let auth = myAuth(); + if (auth) { + if (pv) { + let form: EditPost = { + name: pForm.name, url: pForm.url, body: pForm.body, nsfw: pForm.nsfw, post_id: pv.post.id, - language_id: Some(pv.post.language_id), - auth: auth().unwrap(), - }); + language_id: pv.post.language_id, + auth, + }; WebSocketService.Instance.send(wsClient.editPost(form)); - }, - none: () => { - i.setState(s => ((s.postForm.auth = auth().unwrap()), s)); - let form = new CreatePost({ ...i.state.postForm }); - WebSocketService.Instance.send(wsClient.createPost(form)); - }, - }); + } else { + if (pForm.name && pForm.community_id) { + let 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)); + } + } + } } copySuggestedTitle(i: PostForm) { - i.state.suggestedTitle.match({ - some: sTitle => { - i.setState( - s => ( - (s.postForm.name = sTitle.substring(0, MAX_POST_TITLE_LENGTH)), s - ) - ); - i.setState({ suggestedTitle: None }); - setTimeout(() => { - let textarea: any = document.getElementById("post-title"); - autosize.update(textarea); - }, 10); - }, - none: void 0, - }); + let sTitle = i.state.suggestedTitle; + if (sTitle) { + i.setState( + s => ((s.form.name = sTitle?.substring(0, MAX_POST_TITLE_LENGTH)), s) + ); + i.setState({ suggestedTitle: undefined }); + setTimeout(() => { + let textarea: any = document.getElementById("post-title"); + autosize.update(textarea); + }, 10); + } } handlePostUrlChange(i: PostForm, event: any) { - i.setState(s => ((s.postForm.url = Some(event.target.value)), s)); + i.setState(s => ((s.form.url = event.target.value), s)); i.fetchPageTitle(); } fetchPageTitle() { - this.state.postForm.url.match({ - some: url => { - if (validURL(url)) { - let form = new Search({ - q: url, - community_id: None, - community_name: None, - creator_id: None, - type_: Some(SearchType.Url), - sort: Some(SortType.TopAll), - listing_type: Some(ListingType.All), - page: Some(1), - limit: Some(trendingFetchLimit), - auth: auth(false).ok(), - }); - - WebSocketService.Instance.send(wsClient.search(form)); - - // Fetch the page title - getSiteMetadata(url).then(d => { - this.setState({ suggestedTitle: d.metadata.title }); - }); - } else { - this.setState({ suggestedTitle: None, crossPosts: None }); - } - }, - none: void 0, - }); + let url = this.state.form.url; + if (url && validURL(url)) { + let form: Search = { + q: url, + type_: SearchType.Url, + sort: SortType.TopAll, + listing_type: ListingType.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 }); + }); + } else { + this.setState({ suggestedTitle: undefined, crossPosts: undefined }); + } } handlePostNameChange(i: PostForm, event: any) { - i.setState(s => ((s.postForm.name = event.target.value), s)); + i.setState(s => ((s.form.name = event.target.value), s)); i.fetchSimilarPosts(); } fetchSimilarPosts() { - let form = new Search({ - q: this.state.postForm.name, - type_: Some(SearchType.Posts), - sort: Some(SortType.TopAll), - listing_type: Some(ListingType.All), - community_id: Some(this.state.postForm.community_id), - community_name: None, - creator_id: None, - page: Some(1), - limit: Some(trendingFetchLimit), - auth: auth(false).ok(), - }); - - if (this.state.postForm.name !== "") { + let q = this.state.form.name; + if (q && q !== "") { + let form: Search = { + q, + type_: SearchType.Posts, + sort: SortType.TopAll, + listing_type: ListingType.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: None }); + this.setState({ suggestedPosts: undefined }); } } handlePostBodyChange(val: string) { - this.setState(s => ((s.postForm.body = Some(val)), s)); + this.setState(s => ((s.form.body = val), s)); } handlePostCommunityChange(i: PostForm, event: any) { - i.setState( - s => ((s.postForm.community_id = Number(event.target.value)), s) - ); + i.setState(s => ((s.form.community_id = Number(event.target.value)), s)); } handlePostNsfwChange(i: PostForm, event: any) { - i.setState(s => ((s.postForm.nsfw = Some(event.target.checked)), s)); + i.setState(s => ((s.form.nsfw = event.target.checked), s)); } handleLanguageChange(val: number[]) { - this.setState(s => ((s.postForm.language_id = Some(val[0])), s)); + this.setState(s => ((s.form.language_id = val.at(0)), s)); } handleHoneyPotChange(i: PostForm, event: any) { - i.setState(s => ((s.postForm.honeypot = Some(event.target.value)), s)); + i.setState(s => ((s.form.honeypot = event.target.value), s)); } handleCancel(i: PostForm) { - i.props.onCancel(); + i.props.onCancel?.(); } handlePreviewToggle(i: PostForm, event: any) { @@ -649,7 +605,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> { let url = `${pictrsUri}/${hash}`; let deleteToken = res.files[0].delete_token; let deleteUrl = `${pictrsUri}/delete/${deleteToken}/${hash}`; - i.state.postForm.url = Some(url); + i.state.form.url = url; i.setState({ imageLoading: false }); pictrsDeleteToast( `${i18n.t("click_to_delete_picture")}: ${file.name}`, @@ -679,9 +635,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> { "choice", (e: any) => { this.setState( - s => ( - (s.postForm.community_id = Number(e.detail.choice.value)), s - ) + s => ((s.form.community_id = Number(e.detail.choice.value)), s) ); }, false @@ -711,41 +665,31 @@ export class PostForm extends Component<PostFormProps, PostFormState> { } } - this.props.post_view.match({ - some: pv => - this.setState(s => ((s.postForm.community_id = pv.community.id), s)), - none: void 0, - }); - this.props.params.match({ - some: params => - params.nameOrId.match({ - some: nameOrId => - nameOrId.match({ - left: name => { - let foundCommunityId = this.props.communities - .unwrapOr([]) - .find(r => r.community.name == name).community.id; - this.setState( - s => ((s.postForm.community_id = foundCommunityId), s) - ); - }, - right: id => - this.setState(s => ((s.postForm.community_id = id), s)), - }), - none: void 0, - }), - none: void 0, - }); - - if (isBrowser() && this.state.postForm.community_id) { - this.choices.setChoiceByValue( - this.state.postForm.community_id.toString() - ); + let pv = this.props.post_view; + this.setState(s => ((s.form.community_id = pv?.community.id), s)); + + let nameOrId = this.props.params?.nameOrId; + if (nameOrId) { + if (typeof nameOrId === "string") { + let name_ = nameOrId; + let foundCommunityId = this.props.communities?.find( + r => r.community.name == name_ + )?.community.id; + this.setState(s => ((s.form.community_id = foundCommunityId), s)); + } else { + let id = nameOrId; + this.setState(s => ((s.form.community_id = id), s)); + } + } + + if (isBrowser() && this.state.form.community_id) { + this.choices.setChoiceByValue(this.state.form.community_id.toString()); } this.setState(this.state); } parseMessage(msg: any) { + let mui = UserService.Instance.myUserInfo; let op = wsUserOp(msg); console.log(msg); if (msg.error) { @@ -754,33 +698,23 @@ export class PostForm extends Component<PostFormProps, PostFormState> { this.setState({ loading: false }); return; } else if (op == UserOperation.CreatePost) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); - UserService.Instance.myUserInfo.match({ - some: mui => { - if (data.post_view.creator.id == mui.local_user_view.person.id) { - this.props.onCreate(data.post_view); - } - }, - none: void 0, - }); + let 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) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); - UserService.Instance.myUserInfo.match({ - some: mui => { - if (data.post_view.creator.id == mui.local_user_view.person.id) { - this.setState({ loading: false }); - this.props.onEdit(data.post_view); - } - }, - none: void 0, - }); + let 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) { - let data = wsJsonToRes<SearchResponse>(msg, SearchResponse); + let data = wsJsonToRes<SearchResponse>(msg); if (data.type_ == SearchType[SearchType.Posts]) { - this.setState({ suggestedPosts: Some(data.posts) }); + this.setState({ suggestedPosts: data.posts }); } else if (data.type_ == SearchType[SearchType.Url]) { - this.setState({ crossPosts: Some(data.posts) }); + this.setState({ crossPosts: data.posts }); } } } diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index d5e26ce..f8140c3 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import classNames from "classnames"; import { Component, linkEvent } from "inferno"; import { Link } from "inferno-router"; @@ -22,7 +21,6 @@ import { PurgePost, RemovePost, SavePost, - toUndefined, TransferCommunity, } from "lemmy-js-client"; import { externalHost } from "../../env"; @@ -32,7 +30,6 @@ import { UserService, WebSocketService } from "../../services"; import { amAdmin, amCommunityCreator, - auth, canAdmin, canMod, futureDaysToUnixTime, @@ -45,6 +42,7 @@ import { mdNoImages, mdToHtml, mdToHtmlInline, + myAuth, numToSI, relTags, setupTippy, @@ -63,15 +61,15 @@ interface PostListingState { showEdit: boolean; showRemoveDialog: boolean; showPurgeDialog: boolean; - purgeReason: Option<string>; - purgeType: PurgeType; + purgeReason?: string; + purgeType?: PurgeType; purgeLoading: boolean; - removeReason: Option<string>; + removeReason?: string; showBanDialog: boolean; - banReason: Option<string>; - banExpireDays: Option<number>; - banType: BanType; - removeData: boolean; + banReason?: string; + banExpireDays?: number; + banType?: BanType; + removeData?: boolean; showConfirmTransferSite: boolean; showConfirmTransferCommunity: boolean; imageExpanded: boolean; @@ -80,8 +78,8 @@ interface PostListingState { showMoreMobile: boolean; showBody: boolean; showReportDialog: boolean; - reportReason: Option<string>; - my_vote: Option<number>; + reportReason?: string; + my_vote?: number; score: number; upvotes: number; downvotes: number; @@ -89,9 +87,9 @@ interface PostListingState { interface PostListingProps { post_view: PostView; - duplicates: Option<PostView[]>; - moderators: Option<CommunityModeratorView[]>; - admins: Option<PersonViewSafe[]>; + duplicates?: PostView[]; + moderators?: CommunityModeratorView[]; + admins?: PersonViewSafe[]; allLanguages: Language[]; siteLanguages: number[]; showCommunity?: boolean; @@ -103,17 +101,13 @@ interface PostListingProps { } export class PostListing extends Component<PostListingProps, PostListingState> { - private emptyState: PostListingState = { + state: PostListingState = { showEdit: false, showRemoveDialog: false, showPurgeDialog: false, - purgeReason: None, purgeType: PurgeType.Person, purgeLoading: false, - removeReason: None, showBanDialog: false, - banReason: None, - banExpireDays: None, banType: BanType.Community, removeData: false, showConfirmTransferSite: false, @@ -124,7 +118,6 @@ export class PostListing extends Component<PostListingProps, PostListingState> { showMoreMobile: false, showBody: false, showReportDialog: false, - reportReason: None, my_vote: this.props.post_view.my_vote, score: this.props.post_view.counts.score, upvotes: this.props.post_view.counts.upvotes, @@ -134,7 +127,6 @@ export class PostListing extends Component<PostListingProps, PostListingState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handlePostLike = this.handlePostLike.bind(this); this.handlePostDisLike = this.handlePostDisLike.bind(this); this.handleEditPost = this.handleEditPost.bind(this); @@ -161,17 +153,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <> {this.listing()} {this.state.imageExpanded && !this.props.hideImage && this.img} - {post.url.isSome() && - this.showBody && - post.embed_title.isSome() && <MetadataCard post={post} />} + {post.url && this.showBody && post.embed_title && ( + <MetadataCard post={post} /> + )} {this.showBody && this.body()} </> ) : ( <div className="col-12"> <PostForm - post_view={Some(this.props.post_view)} - communities={None} - params={None} + post_view={this.props.post_view} onEdit={this.handleEditPost} onCancel={this.handleEditCancel} enableNsfw={this.props.enableNsfw} @@ -186,41 +176,41 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } body() { - return this.props.post_view.post.body.match({ - some: body => ( - <div className="col-12 card my-2 p-2"> - {this.state.viewSource ? ( - <pre>{body}</pre> - ) : ( - <div className="md-div" dangerouslySetInnerHTML={mdToHtml(body)} /> - )} - </div> - ), - none: <></>, - }); + let body = this.props.post_view.post.body; + return body ? ( + <div className="col-12 card my-2 p-2"> + {this.state.viewSource ? ( + <pre>{body}</pre> + ) : ( + <div className="md-div" dangerouslySetInnerHTML={mdToHtml(body)} /> + )} + </div> + ) : ( + <></> + ); } get img() { - return this.imageSrc.match({ - some: src => ( - <> - <div className="offset-sm-3 my-2 d-none d-sm-block"> - <a href={src} className="d-inline-block"> - <PictrsImage src={src} /> - </a> - </div> - <div className="my-2 d-block d-sm-none"> - <a - className="d-inline-block" - onClick={linkEvent(this, this.handleImageExpandClick)} - > - <PictrsImage src={src} /> - </a> - </div> - </> - ), - none: <></>, - }); + let src = this.imageSrc; + return src ? ( + <> + <div className="offset-sm-3 my-2 d-none d-sm-block"> + <a href={src} className="d-inline-block"> + <PictrsImage src={src} /> + </a> + </div> + <div className="my-2 d-block d-sm-none"> + <a + className="d-inline-block" + onClick={linkEvent(this, this.handleImageExpandClick)} + > + <PictrsImage src={src} /> + </a> + </div> + </> + ) : ( + <></> + ); } imgThumb(src: string) { @@ -235,23 +225,23 @@ export class PostListing extends Component<PostListingProps, PostListingState> { ); } - get imageSrc(): Option<string> { + get imageSrc(): string | undefined { let post = this.props.post_view.post; let url = post.url; let thumbnail = post.thumbnail_url; - if (url.isSome() && isImage(url.unwrap())) { - if (url.unwrap().includes("pictrs")) { + if (url && isImage(url)) { + if (url.includes("pictrs")) { return url; - } else if (thumbnail.isSome()) { + } else if (thumbnail) { return thumbnail; } else { return url; } - } else if (thumbnail.isSome()) { + } else if (thumbnail) { return thumbnail; } else { - return None; + return undefined; } } @@ -260,33 +250,33 @@ export class PostListing extends Component<PostListingProps, PostListingState> { let url = post.url; let thumbnail = post.thumbnail_url; - if (!this.props.hideImage && url.isSome() && isImage(url.unwrap())) { + if (!this.props.hideImage && url && isImage(url) && this.imageSrc) { return ( <a - href={this.imageSrc.unwrap()} + href={this.imageSrc} className="text-body d-inline-block position-relative mb-2" data-tippy-content={i18n.t("expand_here")} onClick={linkEvent(this, this.handleImageExpandClick)} aria-label={i18n.t("expand_here")} > - {this.imgThumb(this.imageSrc.unwrap())} + {this.imgThumb(this.imageSrc)} <Icon icon="image" classes="mini-overlay" /> </a> ); - } else if (!this.props.hideImage && url.isSome() && thumbnail.isSome()) { + } else if (!this.props.hideImage && url && thumbnail && this.imageSrc) { return ( <a className="text-body d-inline-block position-relative mb-2" - href={url.unwrap()} + href={url} rel={relTags} - title={url.unwrap()} + title={url} > - {this.imgThumb(this.imageSrc.unwrap())} + {this.imgThumb(this.imageSrc)} <Icon icon="external-link" classes="mini-overlay" /> </a> ); - } else if (url.isSome()) { - if (!this.props.hideImage && isVideo(url.unwrap())) { + } else if (url) { + if (!this.props.hideImage && isVideo(url)) { return ( <div className="embed-responsive embed-responsive-16by9"> <video @@ -296,18 +286,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> { controls className="embed-responsive-item" > - <source src={url.unwrap()} type="video/mp4" /> + <source src={url} type="video/mp4" /> </video> </div> ); } else { return ( - <a - className="text-body" - href={url.unwrap()} - title={url.unwrap()} - rel={relTags} - > + <a className="text-body" href={url} title={url} rel={relTags}> <div className="thumbnail rounded bg-light d-flex justify-content-center"> <Icon icon="external-link" classes="d-flex align-items-center" /> </div> @@ -331,6 +316,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> { createdLine() { let post_view = this.props.post_view; + let url = post_view.post.url; + let body = post_view.post.body; return ( <ul className="list-inline mb-1 text-muted small"> <li className="list-inline-item"> @@ -362,25 +349,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> { )} </li> <li className="list-inline-item">•</li> - {post_view.post.url.match({ - some: url => - !(hostname(url) == externalHost) && ( - <> - <li className="list-inline-item"> - <a - className="text-muted font-italic" - href={url} - title={url} - rel={relTags} - > - {hostname(url)} - </a> - </li> - <li className="list-inline-item">•</li> - </> - ), - none: <></>, - })} + {url && !(hostname(url) == externalHost) && ( + <> + <li className="list-inline-item"> + <a + className="text-muted font-italic" + href={url} + title={url} + rel={relTags} + > + {hostname(url)} + </a> + </li> + <li className="list-inline-item">•</li> + </> + )} <li className="list-inline-item"> <span> <MomentTime @@ -389,24 +372,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> { /> </span> </li> - {post_view.post.body.match({ - some: body => ( - <> - <li className="list-inline-item">•</li> - <li className="list-inline-item"> - <button - className="text-muted btn btn-sm btn-link p-0" - data-tippy-content={mdNoImages.render(body)} - data-tippy-allowHtml={true} - onClick={linkEvent(this, this.handleShowBody)} - > - <Icon icon="book-open" classes="icon-inline mr-1" /> - </button> - </li> - </> - ), - none: <></>, - })} + {body && ( + <> + <li className="list-inline-item">•</li> + <li className="list-inline-item"> + <button + className="text-muted btn btn-sm btn-link p-0" + data-tippy-content={mdNoImages.render(body)} + data-tippy-allowHtml={true} + onClick={linkEvent(this, this.handleShowBody)} + > + <Icon icon="book-open" classes="icon-inline mr-1" /> + </button> + </li> + </> + )} </ul> ); } @@ -416,7 +396,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <div className={`vote-bar col-1 pr-0 small text-center`}> <button className={`btn-animate btn btn-link p-0 ${ - this.state.my_vote.unwrapOr(0) == 1 ? "text-info" : "text-muted" + this.state.my_vote == 1 ? "text-info" : "text-muted" }`} onClick={this.handlePostLike} data-tippy-content={i18n.t("upvote")} @@ -437,9 +417,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { {this.props.enableDownvotes && ( <button className={`btn-animate btn btn-link p-0 ${ - this.state.my_vote.unwrapOr(0) == -1 - ? "text-danger" - : "text-muted" + this.state.my_vote == -1 ? "text-danger" : "text-muted" }`} onClick={this.handlePostDisLike} data-tippy-content={i18n.t("downvote")} @@ -471,43 +449,46 @@ export class PostListing extends Component<PostListingProps, PostListingState> { postTitleLine() { let post = this.props.post_view.post; + let url = post.url; + return ( <div className="post-title overflow-hidden"> <h5> - {post.url.match({ - some: url => - this.props.showBody ? ( - <a - className={ - !post.featured_community && !post.featured_local - ? "text-body" - : "text-primary" - } - href={url} - title={url} - rel={relTags} - > - <div dangerouslySetInnerHTML={mdToHtmlInline(post.name)} /> - </a> - ) : ( - this.postLink - ), - none: this.postLink, - })} - {post.url.map(isImage).or(post.thumbnail_url).unwrapOr(false) && ( - <button - className="btn btn-link text-monospace text-muted small d-inline-block ml-2" - data-tippy-content={i18n.t("expand_here")} - onClick={linkEvent(this, this.handleImageExpandClick)} - > - <Icon - icon={ - !this.state.imageExpanded ? "plus-square" : "minus-square" + {url ? ( + this.props.showBody ? ( + <a + className={ + !post.featured_community && !post.featured_local + ? "text-body" + : "text-primary" } - classes="icon-inline" - /> - </button> + href={url} + title={url} + rel={relTags} + > + <div dangerouslySetInnerHTML={mdToHtmlInline(post.name)} /> + </a> + ) : ( + this.postLink + ) + ) : ( + this.postLink )} + {(url && isImage(url)) || + (post.thumbnail_url && ( + <button + className="btn btn-link text-monospace text-muted small d-inline-block ml-2" + data-tippy-content={i18n.t("expand_here")} + onClick={linkEvent(this, this.handleImageExpandClick)} + > + <Icon + icon={ + !this.state.imageExpanded ? "plus-square" : "minus-square" + } + classes="icon-inline" + /> + </button> + ))} {post.removed && ( <small className="ml-2 text-muted font-italic"> {i18n.t("removed")} @@ -556,30 +537,25 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } duplicatesLine() { - return this.props.duplicates.match({ - some: dupes => - dupes.length > 0 && ( - <ul className="list-inline mb-1 small text-muted"> - <> - <li className="list-inline-item mr-2"> - {i18n.t("cross_posted_to")} - </li> - {dupes.map(pv => ( - <li key={pv.post.id} className="list-inline-item mr-2"> - <Link to={`/post/${pv.post.id}`}> - {pv.community.local - ? pv.community.name - : `${pv.community.name}@${hostname( - pv.community.actor_id - )}`} - </Link> - </li> - ))} - </> - </ul> - ), - none: <></>, - }); + let dupes = this.props.duplicates; + return dupes && dupes.length > 0 ? ( + <ul className="list-inline mb-1 small text-muted"> + <> + <li className="list-inline-item mr-2">{i18n.t("cross_posted_to")}</li> + {dupes.map(pv => ( + <li key={pv.post.id} className="list-inline-item mr-2"> + <Link to={`/post/${pv.post.id}`}> + {pv.community.local + ? pv.community.name + : `${pv.community.name}@${hostname(pv.community.actor_id)}`} + </Link> + </li> + ))} + </> + </ul> + ) : ( + <></> + ); } commentsLine(mobile = false) { @@ -597,7 +573,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { </a> )} {mobile && !this.props.viewOnly && this.mobileVotes} - {UserService.Instance.myUserInfo.isSome() && + {UserService.Instance.myUserInfo && !this.props.viewOnly && this.postActions(mobile)} </div> @@ -631,9 +607,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { )} {this.state.showAdvanced && ( <> - {this.showBody && - post_view.post.body.isSome() && - this.viewSourceButton} + {this.showBody && post_view.post.body && this.viewSourceButton} {this.canModOnSelf_ && ( <> {this.lockButton} @@ -667,26 +641,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> { formattedCount: numToSI(post_view.counts.comments), })} </span> - {this.unreadCount.match({ - some: unreadCount => ( - <span className="small text-warning"> - ({unreadCount} {i18n.t("new")}) - </span> - ), - none: <></>, - })} + {this.unreadCount && ( + <span className="small text-warning"> + ({this.unreadCount} {i18n.t("new")}) + </span> + )} </Link> </button> ); } - get unreadCount(): Option<number> { + get unreadCount(): number | undefined { let pv = this.props.post_view; - if (pv.unread_comments == pv.counts.comments || pv.unread_comments == 0) { - return None; - } else { - return Some(pv.unread_comments); - } + return pv.unread_comments == pv.counts.comments || pv.unread_comments == 0 + ? undefined + : pv.unread_comments; } get mobileVotes() { @@ -697,7 +666,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { <div> <button className={`btn-animate btn py-0 px-1 ${ - this.state.my_vote.unwrapOr(0) == 1 ? "text-info" : "text-muted" + this.state.my_vote == 1 ? "text-info" : "text-muted" }`} {...tippy} onClick={this.handlePostLike} @@ -711,9 +680,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { {this.props.enableDownvotes && ( <button className={`ml-2 btn-animate btn py-0 px-1 ${ - this.state.my_vote.unwrapOr(0) == -1 - ? "text-danger" - : "text-muted" + this.state.my_vote == -1 ? "text-danger" : "text-muted" }`} onClick={this.handlePostDisLike} {...tippy} @@ -988,7 +955,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { </> )} {/* Community creators and admins can transfer community to another mod */} - {(amCommunityCreator(this.props.moderators, post_view.creator.id) || + {(amCommunityCreator(post_view.creator.id, this.props.moderators) || this.canAdmin_) && this.creatorIsMod_ && (!this.state.showConfirmTransferCommunity ? ( @@ -1091,12 +1058,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> { removeAndBanDialogs() { let post = this.props.post_view; - let purgeTypeText: string; - if (this.state.purgeType == PurgeType.Post) { - purgeTypeText = i18n.t("purge_post"); - } else if (this.state.purgeType == PurgeType.Person) { - purgeTypeText = `${i18n.t("purge")} ${post.creator.name}`; - } + let purgeTypeText = + this.state.purgeType == PurgeType.Post + ? i18n.t("purge_post") + : `${i18n.t("purge")} ${post.creator.name}`; return ( <> {this.state.showRemoveDialog && ( @@ -1112,7 +1077,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { id="post-listing-remove-reason" className="form-control mr-2" placeholder={i18n.t("reason")} - value={toUndefined(this.state.removeReason)} + value={this.state.removeReason} onInput={linkEvent(this, this.handleModRemoveReasonChange)} /> <button @@ -1138,7 +1103,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { id="post-listing-ban-reason" className="form-control mr-2" placeholder={i18n.t("reason")} - value={toUndefined(this.state.banReason)} + value={this.state.banReason} onInput={linkEvent(this, this.handleModBanReasonChange)} /> <label className="col-form-label" htmlFor={`mod-ban-expires`}> @@ -1149,7 +1114,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { id={`mod-ban-expires`} className="form-control mr-2" placeholder={i18n.t("number_of_days")} - value={toUndefined(this.state.banExpireDays)} + value={this.state.banExpireDays} onInput={linkEvent(this, this.handleModBanExpireDaysChange)} /> <div className="form-group"> @@ -1201,7 +1166,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { className="form-control mr-2" placeholder={i18n.t("reason")} required - value={toUndefined(this.state.reportReason)} + value={this.state.reportReason} onInput={linkEvent(this, this.handleReportReasonChange)} /> <button @@ -1227,7 +1192,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { id="purge-reason" className="form-control mr-2" placeholder={i18n.t("reason")} - value={toUndefined(this.state.purgeReason)} + value={this.state.purgeReason} onInput={linkEvent(this, this.handlePurgeReasonChange)} /> {this.state.purgeLoading ? ( @@ -1249,8 +1214,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { mobileThumbnail() { let post = this.props.post_view.post; - return post.thumbnail_url.isSome() || - post.url.map(isImage).unwrapOr(false) ? ( + return post.thumbnail_url || (post.url && isImage(post.url)) ? ( <div className="row"> <div className={`${this.state.imageExpanded ? "col-12" : "col-8"}`}> {this.postTitleLine()} @@ -1266,13 +1230,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } showMobilePreview() { - let post = this.props.post_view.post; - return ( - !this.showBody && - post.body.match({ - some: body => <div className="md-div mb-1 preview-lines">{body}</div>, - none: <></>, - }) + let body = this.props.post_view.post.body; + return !this.showBody && body ? ( + <div className="md-div mb-1 preview-lines">{body}</div> + ) : ( + <></> ); } @@ -1325,20 +1287,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } private get myPost(): boolean { - return UserService.Instance.myUserInfo.match({ - some: mui => - this.props.post_view.creator.id == mui.local_user_view.person.id, - none: false, - }); + return ( + this.props.post_view.creator.id == + UserService.Instance.myUserInfo?.local_user_view.person.id + ); } handlePostLike(event: any) { event.preventDefault(); - if (UserService.Instance.myUserInfo.isNone()) { + if (!UserService.Instance.myUserInfo) { this.context.router.history.push(`/login`); } - let myVote = this.state.my_vote.unwrapOr(0); + let myVote = this.state.my_vote; let newVote = myVote == 1 ? 0 : 1; if (myVote == 1) { @@ -1359,26 +1320,29 @@ export class PostListing extends Component<PostListingProps, PostListingState> { }); } - this.setState({ my_vote: Some(newVote) }); + this.setState({ my_vote: newVote }); - let form = new CreatePostLike({ - post_id: this.props.post_view.post.id, - score: newVote, - auth: auth().unwrap(), - }); + let auth = myAuth(); + if (auth) { + let form: CreatePostLike = { + post_id: this.props.post_view.post.id, + score: newVote, + auth, + }; - WebSocketService.Instance.send(wsClient.likePost(form)); - this.setState(this.state); + WebSocketService.Instance.send(wsClient.likePost(form)); + this.setState(this.state); + } setupTippy(); } handlePostDisLike(event: any) { event.preventDefault(); - if (UserService.Instance.myUserInfo.isNone()) { + if (!UserService.Instance.myUserInfo) { this.context.router.history.push(`/login`); } - let myVote = this.state.my_vote.unwrapOr(0); + let myVote = this.state.my_vote; let newVote = myVote == -1 ? 0 : -1; if (myVote == 1) { @@ -1399,16 +1363,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> { }); } - this.setState({ my_vote: Some(newVote) }); + this.setState({ my_vote: newVote }); - let form = new CreatePostLike({ - post_id: this.props.post_view.post.id, - score: newVote, - auth: auth().unwrap(), - }); + let auth = myAuth(); + if (auth) { + let form: CreatePostLike = { + post_id: this.props.post_view.post.id, + score: newVote, + auth, + }; - WebSocketService.Instance.send(wsClient.likePost(form)); - this.setState(this.state); + WebSocketService.Instance.send(wsClient.likePost(form)); + this.setState(this.state); + } setupTippy(); } @@ -1430,70 +1397,87 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } handleReportReasonChange(i: PostListing, event: any) { - i.setState({ reportReason: Some(event.target.value) }); + i.setState({ reportReason: event.target.value }); } handleReportSubmit(i: PostListing, event: any) { event.preventDefault(); - let form = new CreatePostReport({ - post_id: i.props.post_view.post.id, - reason: toUndefined(i.state.reportReason), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.createPostReport(form)); + let auth = myAuth(); + let reason = i.state.reportReason; + if (auth && reason) { + let form: CreatePostReport = { + post_id: i.props.post_view.post.id, + reason, + auth, + }; + WebSocketService.Instance.send(wsClient.createPostReport(form)); - i.setState({ showReportDialog: false }); + i.setState({ showReportDialog: false }); + } } handleBlockUserClick(i: PostListing) { - let blockUserForm = new BlockPerson({ - person_id: i.props.post_view.creator.id, - block: true, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + let auth = myAuth(); + if (auth) { + let blockUserForm: BlockPerson = { + person_id: i.props.post_view.creator.id, + block: true, + auth, + }; + WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm)); + } } handleDeleteClick(i: PostListing) { - let deleteForm = new DeletePost({ - post_id: i.props.post_view.post.id, - deleted: !i.props.post_view.post.deleted, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.deletePost(deleteForm)); + let auth = myAuth(); + if (auth) { + let deleteForm: DeletePost = { + post_id: i.props.post_view.post.id, + deleted: !i.props.post_view.post.deleted, + auth, + }; + WebSocketService.Instance.send(wsClient.deletePost(deleteForm)); + } } handleSavePostClick(i: PostListing) { - let saved = - i.props.post_view.saved == undefined ? true : !i.props.post_view.saved; - let form = new SavePost({ - post_id: i.props.post_view.post.id, - save: saved, - auth: auth().unwrap(), - }); - - WebSocketService.Instance.send(wsClient.savePost(form)); + let auth = myAuth(); + if (auth) { + let saved = + i.props.post_view.saved == undefined ? true : !i.props.post_view.saved; + let form: SavePost = { + post_id: i.props.post_view.post.id, + save: saved, + auth, + }; + WebSocketService.Instance.send(wsClient.savePost(form)); + } } get crossPostParams(): string { let post = this.props.post_view.post; let params = `?title=${encodeURIComponent(post.name)}`; - if (post.url.isSome()) { - params += `&url=${encodeURIComponent(post.url.unwrap())}`; + if (post.url) { + params += `&url=${encodeURIComponent(post.url)}`; } - if (post.body.isSome()) { - params += `&body=${encodeURIComponent(this.crossPostBody())}`; + let crossPostBody = this.crossPostBody(); + if (crossPostBody) { + params += `&body=${encodeURIComponent(crossPostBody)}`; } return params; } - crossPostBody(): string { + crossPostBody(): string | undefined { let post = this.props.post_view.post; - let body = `${i18n.t("cross_posted_from")} ${post.ap_id}\n\n${post.body - .unwrap() - .replace(/^/gm, "> ")}`; - return body; + let body = post.body; + + return body + ? `${i18n.t("cross_posted_from")} ${post.ap_id}\n\n${body.replace( + /^/gm, + "> " + )}` + : undefined; } get showBody(): boolean { @@ -1508,7 +1492,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } handleModRemoveReasonChange(i: PostListing, event: any) { - i.setState({ removeReason: Some(event.target.value) }); + i.setState({ removeReason: event.target.value }); } handleModRemoveDataChange(i: PostListing, event: any) { @@ -1517,38 +1501,50 @@ export class PostListing extends Component<PostListingProps, PostListingState> { handleModRemoveSubmit(i: PostListing, event: any) { event.preventDefault(); - let form = new RemovePost({ - post_id: i.props.post_view.post.id, - removed: !i.props.post_view.post.removed, - reason: i.state.removeReason, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.removePost(form)); - i.setState({ showRemoveDialog: false }); + let auth = myAuth(); + if (auth) { + let 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 }); + } } handleModLock(i: PostListing) { - let form = new LockPost({ - post_id: i.props.post_view.post.id, - locked: !i.props.post_view.post.locked, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.lockPost(form)); + let auth = myAuth(); + if (auth) { + let form: LockPost = { + post_id: i.props.post_view.post.id, + locked: !i.props.post_view.post.locked, + auth, + }; + WebSocketService.Instance.send(wsClient.lockPost(form)); + } } handleModFeaturePost(i: PostListing, is_community: boolean) { - let form = new FeaturePost({ - post_id: i.props.post_view.post.id, - featured: is_community - ? !i.props.post_view.post.featured_community - : !i.props.post_view.post.featured_local, - feature_type: is_community - ? PostFeatureType.Community - : PostFeatureType.Local, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.featurePost(form)); + let auth = myAuth(); + if (auth) { + let featured: [PostFeatureType, boolean] = is_community + ? [ + PostFeatureType.Community, + !i.props.post_view.post.featured_community, + ] + : [PostFeatureType.Local, !i.props.post_view.post.featured_local]; + + let form: FeaturePost = { + post_id: i.props.post_view.post.id, + feature_type: featured[0], + featured: featured[1], + auth, + }; + WebSocketService.Instance.send(wsClient.featurePost(form)); + } } handleModBanFromCommunityShow(i: PostListing) { @@ -1584,37 +1580,40 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } handlePurgeReasonChange(i: PostListing, event: any) { - i.setState({ purgeReason: Some(event.target.value) }); + i.setState({ purgeReason: event.target.value }); } handlePurgeSubmit(i: PostListing, event: any) { event.preventDefault(); - if (i.state.purgeType == PurgeType.Person) { - let form = new PurgePerson({ - person_id: i.props.post_view.creator.id, - reason: i.state.purgeReason, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.purgePerson(form)); - } else if (i.state.purgeType == PurgeType.Post) { - let form = new PurgePost({ - post_id: i.props.post_view.post.id, - reason: i.state.purgeReason, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.purgePost(form)); - } + let auth = myAuth(); + if (auth) { + if (i.state.purgeType == PurgeType.Person) { + let 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) { + let 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 }); + } } handleModBanReasonChange(i: PostListing, event: any) { - i.setState({ banReason: Some(event.target.value) }); + i.setState({ banReason: event.target.value }); } handleModBanExpireDaysChange(i: PostListing, event: any) { - i.setState({ banExpireDays: Some(event.target.value) }); + i.setState({ banExpireDays: event.target.value }); } handleModBanFromCommunitySubmit(i: PostListing) { @@ -1629,62 +1628,76 @@ export class PostListing extends Component<PostListingProps, PostListingState> { handleModBanBothSubmit(i: PostListing, event?: any) { if (event) event.preventDefault(); - - if (i.state.banType == BanType.Community) { - // If its an unban, restore all their data + let auth = myAuth(); + if (auth) { let ban = !i.props.post_view.creator_banned_from_community; - if (ban == false) { - i.setState({ removeData: false }); - } - let form = new BanFromCommunity({ - person_id: i.props.post_view.creator.id, - community_id: i.props.post_view.community.id, - ban, - remove_data: Some(i.state.removeData), - reason: i.state.banReason, - expires: i.state.banExpireDays.map(futureDaysToUnixTime), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.banFromCommunity(form)); - } else { - // If its an unban, restore all their data - let ban = !i.props.post_view.creator.banned; - if (ban == false) { - i.setState({ removeData: false }); + let person_id = i.props.post_view.creator.id; + let remove_data = i.state.removeData; + let reason = i.state.banReason; + let 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 }); + } + + let 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 + let ban = !i.props.post_view.creator.banned; + if (ban == false) { + i.setState({ removeData: false }); + } + let form: BanPerson = { + person_id, + ban, + remove_data, + reason, + expires, + auth, + }; + WebSocketService.Instance.send(wsClient.banPerson(form)); } - let form = new BanPerson({ - person_id: i.props.post_view.creator.id, - ban, - remove_data: Some(i.state.removeData), - reason: i.state.banReason, - expires: i.state.banExpireDays.map(futureDaysToUnixTime), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.banPerson(form)); - } - i.setState({ showBanDialog: false }); + i.setState({ showBanDialog: false }); + } } handleAddModToCommunity(i: PostListing) { - let form = new AddModToCommunity({ - person_id: i.props.post_view.creator.id, - community_id: i.props.post_view.community.id, - added: !i.creatorIsMod_, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.addModToCommunity(form)); - i.setState(i.state); + let auth = myAuth(); + if (auth) { + let 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); + } } handleAddAdmin(i: PostListing) { - let form = new AddAdmin({ - person_id: i.props.post_view.creator.id, - added: !i.creatorIsAdmin_, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.addAdmin(form)); - i.setState(i.state); + let auth = myAuth(); + if (auth) { + let form: AddAdmin = { + person_id: i.props.post_view.creator.id, + added: !i.creatorIsAdmin_, + auth, + }; + WebSocketService.Instance.send(wsClient.addAdmin(form)); + i.setState(i.state); + } } handleShowConfirmTransferCommunity(i: PostListing) { @@ -1696,13 +1709,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> { } handleTransferCommunity(i: PostListing) { - let form = new TransferCommunity({ - community_id: i.props.post_view.community.id, - person_id: i.props.post_view.creator.id, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.transferCommunity(form)); - i.setState({ showConfirmTransferCommunity: false }); + let auth = myAuth(); + if (auth) { + let 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 }); + } } handleShowConfirmTransferSite(i: PostListing) { @@ -1762,9 +1778,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> { get canModOnSelf_(): boolean { return canMod( + this.props.post_view.creator.id, this.props.moderators, this.props.admins, - this.props.post_view.creator.id, undefined, true ); @@ -1772,21 +1788,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> { get canMod_(): boolean { return canMod( + this.props.post_view.creator.id, this.props.moderators, - this.props.admins, - this.props.post_view.creator.id + this.props.admins ); } get canAdmin_(): boolean { - return canAdmin(this.props.admins, this.props.post_view.creator.id); + return canAdmin(this.props.post_view.creator.id, this.props.admins); } get creatorIsMod_(): boolean { - return isMod(this.props.moderators, this.props.post_view.creator.id); + return isMod(this.props.post_view.creator.id, this.props.moderators); } get creatorIsAdmin_(): boolean { - return isAdmin(this.props.admins, this.props.post_view.creator.id); + return isAdmin(this.props.post_view.creator.id, this.props.admins); } } diff --git a/src/shared/components/post/post-listings.tsx b/src/shared/components/post/post-listings.tsx index e5d3e3b..ab7692d 100644 --- a/src/shared/components/post/post-listings.tsx +++ b/src/shared/components/post/post-listings.tsx @@ -1,4 +1,3 @@ -import { None, Some } from "@sniptt/monads"; import { Component } from "inferno"; import { T } from "inferno-i18next-dess"; import { Link } from "inferno-router"; @@ -12,8 +11,8 @@ interface PostListingsProps { siteLanguages: number[]; showCommunity?: boolean; removeDuplicates?: boolean; - enableDownvotes: boolean; - enableNsfw: boolean; + enableDownvotes?: boolean; + enableNsfw?: boolean; } export class PostListings extends Component<PostListingsProps, any> { @@ -37,9 +36,7 @@ export class PostListings extends Component<PostListingsProps, any> { <> <PostListing post_view={post_view} - duplicates={Some(this.duplicatesMap.get(post_view.post.id))} - moderators={None} - admins={None} + duplicates={this.duplicatesMap.get(post_view.post.id)} showCommunity={this.props.showCommunity} enableDownvotes={this.props.enableDownvotes} enableNsfw={this.props.enableNsfw} @@ -72,20 +69,20 @@ export class PostListings extends Component<PostListingsProps, any> { // Loop over the posts, find ones with same urls for (let pv of posts) { - !pv.post.deleted && + let url = pv.post.url; + if ( + !pv.post.deleted && !pv.post.removed && !pv.community.deleted && !pv.community.removed && - pv.post.url.match({ - some: url => { - if (!urlMap.get(url)) { - urlMap.set(url, [pv]); - } else { - urlMap.get(url).push(pv); - } - }, - none: void 0, - }); + url + ) { + if (!urlMap.get(url)) { + urlMap.set(url, [pv]); + } else { + urlMap.get(url)?.push(pv); + } + } } // Sort by oldest @@ -100,22 +97,20 @@ export class PostListings extends Component<PostListingsProps, any> { for (let i = 0; i < posts.length; i++) { let pv = posts[i]; - pv.post.url.match({ - some: url => { - let found = urlMap.get(url); - if (found) { - // If its the oldest, add - if (pv.post.id == found[0].post.id) { - this.duplicatesMap.set(pv.post.id, found.slice(1)); - } - // Otherwise, delete it - else { - posts.splice(i--, 1); - } + let url = pv.post.url; + if (url) { + let found = urlMap.get(url); + if (found) { + // If its the oldest, add + if (pv.post.id == found[0].post.id) { + this.duplicatesMap.set(pv.post.id, found.slice(1)); + } + // Otherwise, delete it + else { + posts.splice(i--, 1); } - }, - none: void 0, - }); + } + } } return posts; diff --git a/src/shared/components/post/post-report.tsx b/src/shared/components/post/post-report.tsx index 7cb9e43..2b1540e 100644 --- a/src/shared/components/post/post-report.tsx +++ b/src/shared/components/post/post-report.tsx @@ -1,4 +1,3 @@ -import { None } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { @@ -9,7 +8,7 @@ import { } from "lemmy-js-client"; import { i18n } from "../../i18next"; import { WebSocketService } from "../../services"; -import { auth, wsClient } from "../../utils"; +import { myAuth, wsClient } from "../../utils"; import { Icon } from "../common/icon"; import { PersonListing } from "../person/person-listing"; import { PostListing } from "./post-listing"; @@ -25,6 +24,7 @@ export class PostReport extends Component<PostReportProps, any> { render() { let r = this.props.report; + let resolver = r.resolver; let post = r.post; let tippyContent = i18n.t( r.post_report.resolved ? "unresolve_report" : "resolve_report" @@ -52,9 +52,6 @@ export class PostReport extends Component<PostReportProps, any> { <div> <PostListing post_view={pv} - duplicates={None} - moderators={None} - admins={None} showCommunity={true} enableDownvotes={true} enableNsfw={true} @@ -69,24 +66,21 @@ export class PostReport extends Component<PostReportProps, any> { <div> {i18n.t("reason")}: {r.post_report.reason} </div> - {r.resolver.match({ - some: resolver => ( - <div> - {r.post_report.resolved ? ( - <T i18nKey="resolved_by"> - # - <PersonListing person={resolver} /> - </T> - ) : ( - <T i18nKey="unresolved_by"> - # - <PersonListing person={resolver} /> - </T> - )} - </div> - ), - none: <></>, - })} + {resolver && ( + <div> + {r.post_report.resolved ? ( + <T i18nKey="resolved_by"> + # + <PersonListing person={resolver} /> + </T> + ) : ( + <T i18nKey="unresolved_by"> + # + <PersonListing person={resolver} /> + </T> + )} + </div> + )} <button className="btn btn-link btn-animate text-muted py-0" onClick={linkEvent(this, this.handleResolveReport)} @@ -105,11 +99,14 @@ export class PostReport extends Component<PostReportProps, any> { } handleResolveReport(i: PostReport) { - let form = new ResolvePostReport({ - report_id: i.props.report.post_report.id, - resolved: !i.props.report.post_report.resolved, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.resolvePostReport(form)); + let auth = myAuth(); + if (auth) { + let form: ResolvePostReport = { + report_id: i.props.report.post_report.id, + resolved: !i.props.report.post_report.resolved, + auth, + }; + WebSocketService.Instance.send(wsClient.resolvePostReport(form)); + } } } diff --git a/src/shared/components/post/post.tsx b/src/shared/components/post/post.tsx index d2d5f84..637cc92 100644 --- a/src/shared/components/post/post.tsx +++ b/src/shared/components/post/post.tsx @@ -1,4 +1,3 @@ -import { None, Option, Right, Some } from "@sniptt/monads"; import autosize from "autosize"; import { Component, createRef, linkEvent, RefObject } from "inferno"; import { @@ -27,7 +26,6 @@ import { SearchResponse, SearchType, SortType, - toOption, UserOperation, wsJsonToRes, wsUserOp, @@ -37,7 +35,6 @@ import { i18n } from "../../i18next"; import { CommentViewType, InitialFetchRequest } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { - auth, buildCommentsTree, commentsToFlatNodes, commentTreeMaxDepth, @@ -54,6 +51,7 @@ import { insertCommentIntoTree, isBrowser, isImage, + myAuth, restoreScrollPosition, saveCommentRes, saveScrollPosition, @@ -75,16 +73,16 @@ import { PostListing } from "./post-listing"; const commentsShownInterval = 15; interface PostState { - postId: Option<number>; - commentId: Option<number>; - postRes: Option<GetPostResponse>; - commentsRes: Option<GetCommentsResponse>; + postId?: number; + commentId?: number; + postRes?: GetPostResponse; + commentsRes?: GetCommentsResponse; commentTree: CommentNodeI[]; commentSort: CommentSortType; commentViewType: CommentViewType; scrolled?: boolean; loading: boolean; - crossPosts: Option<PostView[]>; + crossPosts?: PostView[]; siteRes: GetSiteResponse; commentSectionRef?: RefObject<HTMLDivElement>; showSidebarMobile: boolean; @@ -92,16 +90,10 @@ interface PostState { } export class Post extends Component<any, PostState> { - private subscription: Subscription; - private isoData = setIsoData( - this.context, - GetPostResponse, - GetCommentsResponse - ); + private subscription?: Subscription; + private isoData = setIsoData(this.context); private commentScrollDebounced: () => void; - private emptyState: PostState = { - postRes: None, - commentsRes: None, + state: PostState = { postId: getIdFromProps(this.props), commentId: getCommentIdFromProps(this.props), commentTree: [], @@ -109,9 +101,7 @@ export class Post extends Component<any, PostState> { commentViewType: CommentViewType.Tree, scrolled: false, loading: true, - crossPosts: None, siteRes: this.isoData.site_res, - commentSectionRef: null, showSidebarMobile: false, maxCommentsShown: commentsShownInterval, }; @@ -119,8 +109,6 @@ export class Post extends Component<any, PostState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; - this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); @@ -130,16 +118,16 @@ export class Post extends Component<any, PostState> { if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - postRes: Some(this.isoData.routeData[0] as GetPostResponse), - commentsRes: Some(this.isoData.routeData[1] as GetCommentsResponse), + postRes: this.isoData.routeData[0] as GetPostResponse, + commentsRes: this.isoData.routeData[1] as GetCommentsResponse, }; - if (this.state.commentsRes.isSome()) { + if (this.state.commentsRes) { this.state = { ...this.state, commentTree: buildCommentsTree( - this.state.commentsRes.unwrap().comments, - this.state.commentId.isSome() + this.state.commentsRes.comments, + !!this.state.commentId ), }; } @@ -147,18 +135,19 @@ export class Post extends Component<any, PostState> { this.state = { ...this.state, loading: false }; if (isBrowser()) { - WebSocketService.Instance.send( - wsClient.communityJoin({ - community_id: - this.state.postRes.unwrap().community_view.community.id, - }) - ); + if (this.state.postRes) { + WebSocketService.Instance.send( + wsClient.communityJoin({ + community_id: this.state.postRes.community_view.community.id, + }) + ); + } - this.state.postId.match({ - some: post_id => - WebSocketService.Instance.send(wsClient.postJoin({ post_id })), - none: void 0, - }); + if (this.state.postId) { + WebSocketService.Instance.send( + wsClient.postJoin({ post_id: this.state.postId }) + ); + } this.fetchCrossPosts(); @@ -172,87 +161,70 @@ export class Post extends Component<any, PostState> { } fetchPost() { - this.setState({ commentsRes: None }); - let postForm = new GetPost({ + this.setState({ commentsRes: undefined, postRes: undefined }); + let auth = myAuth(false); + let postForm: GetPost = { id: this.state.postId, comment_id: this.state.commentId, - auth: auth(false).ok(), - }); + auth, + }; WebSocketService.Instance.send(wsClient.getPost(postForm)); - let commentsForm = new GetComments({ + let commentsForm: GetComments = { post_id: this.state.postId, parent_id: this.state.commentId, - max_depth: Some(commentTreeMaxDepth), - page: None, - limit: None, - sort: Some(this.state.commentSort), - type_: Some(ListingType.All), - community_name: None, - community_id: None, - saved_only: Some(false), - auth: auth(false).ok(), - }); + max_depth: commentTreeMaxDepth, + sort: this.state.commentSort, + type_: ListingType.All, + saved_only: false, + auth, + }; WebSocketService.Instance.send(wsClient.getComments(commentsForm)); } fetchCrossPosts() { - this.state.postRes - .andThen(r => r.post_view.post.url) - .match({ - some: url => { - let form = new Search({ - q: url, - type_: Some(SearchType.Url), - sort: Some(SortType.TopAll), - listing_type: Some(ListingType.All), - page: Some(1), - limit: Some(trendingFetchLimit), - community_id: None, - community_name: None, - creator_id: None, - auth: auth(false).ok(), - }); - WebSocketService.Instance.send(wsClient.search(form)); - }, - none: void 0, - }); + let q = this.state.postRes?.post_view.post.url; + if (q) { + let form: Search = { + q, + type_: SearchType.Url, + sort: SortType.TopAll, + listing_type: ListingType.All, + page: 1, + limit: trendingFetchLimit, + auth: myAuth(false), + }; + WebSocketService.Instance.send(wsClient.search(form)); + } } static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { let pathSplit = req.path.split("/"); let promises: Promise<any>[] = []; - let pathType = pathSplit[1]; - let id = Number(pathSplit[2]); + let pathType = pathSplit.at(1); + let id = pathSplit.at(2) ? Number(pathSplit.at(2)) : undefined; + let auth = req.auth; - let postForm = new GetPost({ - id: None, - comment_id: None, - auth: req.auth, - }); + let postForm: GetPost = { + auth, + }; - let commentsForm = new GetComments({ - post_id: None, - parent_id: None, - max_depth: Some(commentTreeMaxDepth), - page: None, - limit: None, - sort: Some(CommentSortType.Hot), - type_: Some(ListingType.All), - community_name: None, - community_id: None, - saved_only: Some(false), - auth: req.auth, - }); + let commentsForm: GetComments = { + max_depth: commentTreeMaxDepth, + sort: CommentSortType.Hot, + type_: ListingType.All, + saved_only: false, + auth, + }; // Set the correct id based on the path type if (pathType == "post") { - postForm.id = Some(id); - commentsForm.post_id = Some(id); + postForm.id = id; + commentsForm.post_id = id; } else { - postForm.comment_id = Some(id); - commentsForm.parent_id = Some(id); + postForm.comment_id = id; + commentsForm.parent_id = id; } promises.push(req.client.getPost(postForm)); @@ -262,7 +234,7 @@ export class Post extends Component<any, PostState> { } componentWillUnmount() { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); document.removeEventListener("scroll", this.commentScrollDebounced); saveScrollPosition(this.context); @@ -295,7 +267,7 @@ export class Post extends Component<any, PostState> { } scrollIntoCommentSection() { - this.state.commentSectionRef.current?.scrollIntoView(); + this.state.commentSectionRef?.current?.scrollIntoView(); } isBottom(el: Element): boolean { @@ -315,31 +287,21 @@ export class Post extends Component<any, PostState> { }; get documentTitle(): string { - return this.state.postRes.match({ - some: res => - `${res.post_view.post.name} - ${this.state.siteRes.site_view.site.name}`, - none: "", - }); + let name_ = this.state.postRes?.post_view.post.name; + let siteName = this.state.siteRes.site_view.site.name; + return name_ ? `${name_} - ${siteName}` : ""; } - get imageTag(): Option<string> { - return this.state.postRes.match({ - some: res => - res.post_view.post.thumbnail_url.or( - res.post_view.post.url.match({ - some: url => (isImage(url) ? Some(url) : None), - none: None, - }) - ), - none: None, - }); - } - - get descriptionTag(): Option<string> { - return this.state.postRes.andThen(r => r.post_view.post.body); + get imageTag(): string | undefined { + let post = this.state.postRes?.post_view.post; + let thumbnail = post?.thumbnail_url; + let url = post?.url; + return thumbnail || (url && isImage(url) ? url : undefined); } render() { + let res = this.state.postRes; + let description = res?.post_view.post.body; return ( <div className="container-lg"> {this.state.loading ? ( @@ -347,65 +309,60 @@ export class Post extends Component<any, PostState> { <Spinner large /> </h5> ) : ( - this.state.postRes.match({ - some: 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={this.descriptionTag} - /> - <PostListing - post_view={res.post_view} - duplicates={this.state.crossPosts} - showBody - showCommunity - moderators={Some(res.moderators)} - admins={Some(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={Right(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()} - </div> - <div className="d-none d-md-block col-md-4"> - {this.sidebar()} + 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()} </div> - ), - none: <></>, - }) + <div className="d-none d-md-block col-md-4">{this.sidebar()}</div> + </div> + ) )} </div> ); @@ -493,35 +450,34 @@ export class Post extends Component<any, PostState> { commentsFlat() { // These are already sorted by new - return this.state.commentsRes.match({ - some: commentsRes => - this.state.postRes.match({ - some: postRes => ( - <div> - <CommentNodes - nodes={commentsToFlatNodes(commentsRes.comments)} - viewType={this.state.commentViewType} - maxCommentsShown={Some(this.state.maxCommentsShown)} - noIndent - locked={postRes.post_view.post.locked} - moderators={Some(postRes.moderators)} - admins={Some(this.state.siteRes.admins)} - enableDownvotes={enableDownvotes(this.state.siteRes)} - showContext - allLanguages={this.state.siteRes.all_languages} - siteLanguages={this.state.siteRes.discussion_languages} - /> - </div> - ), - none: <></>, - }), - none: <></>, - }); + let commentsRes = this.state.commentsRes; + let postRes = this.state.postRes; + return ( + commentsRes && + postRes && ( + <div> + <CommentNodes + nodes={commentsToFlatNodes(commentsRes.comments)} + viewType={this.state.commentViewType} + maxCommentsShown={this.state.maxCommentsShown} + noIndent + locked={postRes.post_view.post.locked} + moderators={postRes.moderators} + admins={this.state.siteRes.admins} + enableDownvotes={enableDownvotes(this.state.siteRes)} + showContext + allLanguages={this.state.siteRes.all_languages} + siteLanguages={this.state.siteRes.discussion_languages} + /> + </div> + ) + ); } sidebar() { - return this.state.postRes.match({ - some: res => ( + let res = this.state.postRes; + return ( + res && ( <div className="mb-3"> <Sidebar community_view={res.community_view} @@ -532,12 +488,10 @@ export class Post extends Component<any, PostState> { showIcon allLanguages={this.state.siteRes.all_languages} siteLanguages={this.state.siteRes.discussion_languages} - communityLanguages={None} /> </div> - ), - none: <></>, - }); + ) + ); } handleCommentSortChange(i: Post, event: any) { @@ -549,14 +503,14 @@ export class Post extends Component<any, PostState> { } handleCommentViewTypeChange(i: Post, event: any) { - i.setState({ - commentViewType: Number(event.target.value), - commentSort: CommentSortType.New, - commentTree: buildCommentsTree( - i.state.commentsRes.map(r => r.comments).unwrapOr([]), - i.state.commentId.isSome() - ), - }); + let comments = i.state.commentsRes?.comments; + if (comments) { + i.setState({ + commentViewType: Number(event.target.value), + commentSort: CommentSortType.New, + commentTree: buildCommentsTree(comments, !!i.state.commentId), + }); + } } handleShowSidebarMobile(i: Post) { @@ -564,33 +518,31 @@ export class Post extends Component<any, PostState> { } handleViewPost(i: Post) { - i.state.postRes.match({ - some: res => - i.context.router.history.push(`/post/${res.post_view.post.id}`), - none: void 0, - }); + let id = i.state.postRes?.post_view.post.id; + if (id) { + i.context.router.history.push(`/post/${id}`); + } } handleViewContext(i: Post) { - i.state.commentsRes.match({ - some: res => - i.context.router.history.push( - `/comment/${getCommentParentId(res.comments[0].comment).unwrap()}` - ), - none: void 0, - }); + let parentId = getCommentParentId( + i.state.commentsRes?.comments?.at(0)?.comment + ); + if (parentId) { + i.context.router.history.push(`/comment/${parentId}`); + } } commentsTree() { - let showContextButton = toOption(this.state.commentTree[0]).match({ - some: comment => getDepthFromComment(comment.comment_view.comment) > 0, - none: false, - }); + let res = this.state.postRes; + let firstComment = this.state.commentTree.at(0)?.comment_view.comment; + let depth = getDepthFromComment(firstComment); + let showContextButton = depth ? depth > 0 : false; - return this.state.postRes.match({ - some: res => ( + return ( + res && ( <div> - {this.state.commentId.isSome() && ( + {!!this.state.commentId && ( <> <button className="pl-0 d-block btn btn-link text-muted" @@ -611,18 +563,17 @@ export class Post extends Component<any, PostState> { <CommentNodes nodes={this.state.commentTree} viewType={this.state.commentViewType} - maxCommentsShown={Some(this.state.maxCommentsShown)} + maxCommentsShown={this.state.maxCommentsShown} locked={res.post_view.post.locked} - moderators={Some(res.moderators)} - admins={Some(this.state.siteRes.admins)} + moderators={res.moderators} + admins={this.state.siteRes.admins} enableDownvotes={enableDownvotes(this.state.siteRes)} allLanguages={this.state.siteRes.all_languages} siteLanguages={this.state.siteRes.discussion_languages} /> </div> - ), - none: <></>, - }); + ) + ); } parseMessage(msg: any) { @@ -632,25 +583,19 @@ export class Post extends Component<any, PostState> { toast(i18n.t(msg.error), "danger"); return; } else if (msg.reconnect) { - this.state.postRes.match({ - some: res => { - let postId = res.post_view.post.id; - WebSocketService.Instance.send( - wsClient.postJoin({ post_id: postId }) - ); - WebSocketService.Instance.send( - wsClient.getPost({ - id: Some(postId), - comment_id: None, - auth: auth(false).ok(), - }) - ); - }, - none: void 0, - }); + let 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) { - let data = wsJsonToRes<GetPostResponse>(msg, GetPostResponse); - this.setState({ postRes: Some(data) }); + let data = wsJsonToRes<GetPostResponse>(msg); + this.setState({ postRes: data }); // join the rooms WebSocketService.Instance.send( @@ -666,60 +611,55 @@ export class Post extends Component<any, PostState> { // TODO move this into initial fetch and refetch this.fetchCrossPosts(); setupTippy(); - if (this.state.commentId.isNone()) restoreScrollPosition(this.context); + if (!this.state.commentId) restoreScrollPosition(this.context); if (this.checkScrollIntoCommentsParam) { this.scrollIntoCommentSection(); } } else if (op == UserOperation.GetComments) { - let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse); - // You might need to append here, since this could be building more comments from a tree fetch - this.state.commentsRes.match({ - some: res => { - // Remove the first comment, since it is the parent - let newComments = data.comments; - newComments.shift(); - res.comments.push(...newComments); - }, - none: () => this.setState({ commentsRes: Some(data) }), - }); - // this.state.commentsRes = Some(data); + let data = wsJsonToRes<GetCommentsResponse>(msg); + // This section sets the comments res + let 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 + let newComments = data.comments; + newComments.shift(); + comments.push(...newComments); + } else { + this.setState({ commentsRes: data }); + } + + let cComments = this.state.commentsRes?.comments ?? []; this.setState({ - commentTree: buildCommentsTree( - this.state.commentsRes.map(r => r.comments).unwrapOr([]), - this.state.commentId.isSome() - ), + commentTree: buildCommentsTree(cComments, !!this.state.commentId), loading: false, }); - this.setState(this.state); } else if (op == UserOperation.CreateComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); // Don't get comments from the post room, if the creator is blocked - let creatorBlocked = UserService.Instance.myUserInfo - .map(m => m.person_blocks) - .unwrapOr([]) + let creatorBlocked = UserService.Instance.myUserInfo?.person_blocks .map(pb => pb.target.id) .includes(data.comment_view.creator.id); // Necessary since it might be a user reply, which has the recipients, to avoid double - if (data.recipient_ids.length == 0 && !creatorBlocked) { - this.state.postRes.match({ - some: postRes => - this.state.commentsRes.match({ - some: commentsRes => { - commentsRes.comments.unshift(data.comment_view); - insertCommentIntoTree( - this.state.commentTree, - data.comment_view, - this.state.commentId.isSome() - ); - postRes.post_view.counts.comments++; - }, - none: void 0, - }), - none: void 0, - }); + let postRes = this.state.postRes; + let commentsRes = this.state.commentsRes; + if ( + data.recipient_ids.length == 0 && + !creatorBlocked && + postRes && + commentsRes + ) { + 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(); } @@ -728,34 +668,22 @@ export class Post extends Component<any, PostState> { op == UserOperation.DeleteComment || op == UserOperation.RemoveComment ) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); - editCommentRes( - data.comment_view, - this.state.commentsRes.map(r => r.comments).unwrapOr([]) - ); + let data = wsJsonToRes<CommentResponse>(msg); + editCommentRes(data.comment_view, this.state.commentsRes?.comments); this.setState(this.state); setupTippy(); } else if (op == UserOperation.SaveComment) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); - saveCommentRes( - data.comment_view, - this.state.commentsRes.map(r => r.comments).unwrapOr([]) - ); + let data = wsJsonToRes<CommentResponse>(msg); + saveCommentRes(data.comment_view, this.state.commentsRes?.comments); this.setState(this.state); setupTippy(); } else if (op == UserOperation.CreateCommentLike) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); - createCommentLikeRes( - data.comment_view, - this.state.commentsRes.map(r => r.comments).unwrapOr([]) - ); + let data = wsJsonToRes<CommentResponse>(msg); + createCommentLikeRes(data.comment_view, this.state.commentsRes?.comments); this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); - this.state.postRes.match({ - some: res => createPostLikeRes(data.post_view, res.post_view), - none: void 0, - }); + let data = wsJsonToRes<PostResponse>(msg); + createPostLikeRes(data.post_view, this.state.postRes?.post_view); this.setState(this.state); } else if ( op == UserOperation.EditPost || @@ -765,112 +693,91 @@ export class Post extends Component<any, PostState> { op == UserOperation.FeaturePost || op == UserOperation.SavePost ) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); - this.state.postRes.match({ - some: res => (res.post_view = data.post_view), - none: void 0, - }); - this.setState(this.state); - setupTippy(); + let data = wsJsonToRes<PostResponse>(msg); + let pv = this.state.postRes?.post_view; + if (pv) { + pv = data.post_view; + this.setState(this.state); + setupTippy(); + } } else if ( op == UserOperation.EditCommunity || op == UserOperation.DeleteCommunity || op == UserOperation.RemoveCommunity || op == UserOperation.FollowCommunity ) { - let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse); - this.state.postRes.match({ - some: res => { - res.community_view = data.community_view; - res.post_view.community = data.community_view.community; - this.setState(this.state); - }, - none: void 0, - }); + let data = wsJsonToRes<CommunityResponse>(msg); + let 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) { - let data = wsJsonToRes<BanFromCommunityResponse>( - msg, - BanFromCommunityResponse - ); - this.state.postRes.match({ - some: postRes => - this.state.commentsRes.match({ - some: commentsRes => { - commentsRes.comments - .filter(c => c.creator.id == data.person_view.person.id) - .forEach(c => (c.creator_banned_from_community = data.banned)); - if (postRes.post_view.creator.id == data.person_view.person.id) { - postRes.post_view.creator_banned_from_community = data.banned; - } - this.setState(this.state); - }, - none: void 0, - }), - none: void 0, - }); + let data = wsJsonToRes<BanFromCommunityResponse>(msg); + + let postRes = this.state.postRes; + if (postRes) { + if (postRes.post_view.creator.id == data.person_view.person.id) { + postRes.post_view.creator_banned_from_community = data.banned; + } + } + + 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) { - let data = wsJsonToRes<AddModToCommunityResponse>( - msg, - AddModToCommunityResponse - ); - this.state.postRes.match({ - some: res => { - res.moderators = data.moderators; - this.setState(this.state); - }, - none: void 0, - }); + let data = wsJsonToRes<AddModToCommunityResponse>(msg); + let postRes = this.state.postRes; + if (postRes) { + postRes.moderators = data.moderators; + this.setState(this.state); + } } else if (op == UserOperation.BanPerson) { - let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse); - this.state.postRes.match({ - some: postRes => - this.state.commentsRes.match({ - some: commentsRes => { - commentsRes.comments - .filter(c => c.creator.id == data.person_view.person.id) - .forEach(c => (c.creator.banned = data.banned)); - if (postRes.post_view.creator.id == data.person_view.person.id) { - postRes.post_view.creator.banned = data.banned; - } - this.setState(this.state); - }, - none: void 0, - }), - none: void 0, - }); + let 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)); + + let postRes = this.state.postRes; + if (postRes) { + if (postRes.post_view.creator.id == data.person_view.person.id) { + postRes.post_view.creator.banned = data.banned; + } + } + this.setState(this.state); } else if (op == UserOperation.AddAdmin) { - let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse); + let data = wsJsonToRes<AddAdminResponse>(msg); this.setState(s => ((s.siteRes.admins = data.admins), s)); } else if (op == UserOperation.Search) { - let data = wsJsonToRes<SearchResponse>(msg, SearchResponse); + let data = wsJsonToRes<SearchResponse>(msg); let xPosts = data.posts.filter( p => p.post.id != Number(this.props.match.params.id) ); - this.setState({ crossPosts: xPosts.length > 0 ? Some(xPosts) : None }); + this.setState({ crossPosts: xPosts.length > 0 ? xPosts : undefined }); } else if (op == UserOperation.LeaveAdmin) { - let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse); + let data = wsJsonToRes<GetSiteResponse>(msg); this.setState({ siteRes: data }); } else if (op == UserOperation.TransferCommunity) { - let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse); - this.state.postRes.match({ - some: res => { - res.community_view = data.community_view; - res.post_view.community = data.community_view.community; - res.moderators = data.moderators; - this.setState(this.state); - }, - none: void 0, - }); + let data = wsJsonToRes<GetCommunityResponse>(msg); + let postRes = this.state.postRes; + if (postRes) { + postRes.community_view = data.community_view; + postRes.post_view.community = data.community_view.community; + postRes.moderators = data.moderators; + this.setState(this.state); + } } else if (op == UserOperation.BlockPerson) { - let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse); + let data = wsJsonToRes<BlockPersonResponse>(msg); updatePersonBlock(data); } else if (op == UserOperation.CreatePostReport) { - let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse); + let data = wsJsonToRes<PostReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } } else if (op == UserOperation.CreateCommentReport) { - let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse); + let data = wsJsonToRes<CommentReportResponse>(msg); if (data) { toast(i18n.t("report_created")); } @@ -880,7 +787,7 @@ export class Post extends Component<any, PostState> { op == UserOperation.PurgeComment || op == UserOperation.PurgeCommunity ) { - let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse); + let data = wsJsonToRes<PurgeItemResponse>(msg); if (data.success) { toast(i18n.t("purge_success")); this.context.router.history.push(`/`); diff --git a/src/shared/components/private_message/create-private-message.tsx b/src/shared/components/private_message/create-private-message.tsx index ae7fb21..e3aba7a 100644 --- a/src/shared/components/private_message/create-private-message.tsx +++ b/src/shared/components/private_message/create-private-message.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component } from "inferno"; import { GetPersonDetails, @@ -14,9 +13,9 @@ import { i18n } from "../../i18next"; import { InitialFetchRequest } from "../../interfaces"; import { UserService, WebSocketService } from "../../services"; import { - auth, getRecipientIdFromProps, isBrowser, + myAuth, setIsoData, toast, wsClient, @@ -28,7 +27,7 @@ import { PrivateMessageForm } from "./private-message-form"; interface CreatePrivateMessageState { siteRes: GetSiteResponse; - recipientDetailsRes: Option<GetPersonDetailsResponse>; + recipientDetailsRes?: GetPersonDetailsResponse; recipient_id: number; loading: boolean; } @@ -37,25 +36,23 @@ export class CreatePrivateMessage extends Component< any, CreatePrivateMessageState > { - private isoData = setIsoData(this.context, GetPersonDetailsResponse); - private subscription: Subscription; - private emptyState: CreatePrivateMessageState = { + private isoData = setIsoData(this.context); + private subscription?: Subscription; + state: CreatePrivateMessageState = { siteRes: this.isoData.site_res, - recipientDetailsRes: None, recipient_id: getRecipientIdFromProps(this.props), loading: true, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handlePrivateMessageCreate = this.handlePrivateMessageCreate.bind(this); this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); - if (UserService.Instance.myUserInfo.isNone() && isBrowser()) { + if (!UserService.Instance.myUserInfo && isBrowser()) { toast(i18n.t("not_logged_in"), "danger"); this.context.router.history.push(`/login`); } @@ -64,9 +61,8 @@ export class CreatePrivateMessage extends Component< if (this.isoData.path == this.context.router.route.match.url) { this.state = { ...this.state, - recipientDetailsRes: Some( - this.isoData.routeData[0] as GetPersonDetailsResponse - ), + recipientDetailsRes: this.isoData + .routeData[0] as GetPersonDetailsResponse, loading: false, }; } else { @@ -75,77 +71,61 @@ export class CreatePrivateMessage extends Component< } fetchPersonDetails() { - let form = new GetPersonDetails({ - person_id: Some(this.state.recipient_id), - sort: Some(SortType.New), - saved_only: Some(false), - username: None, - page: None, - limit: None, - community_id: None, - auth: auth(false).ok(), - }); + let form: GetPersonDetails = { + person_id: this.state.recipient_id, + sort: SortType.New, + saved_only: false, + auth: myAuth(false), + }; WebSocketService.Instance.send(wsClient.getPersonDetails(form)); } static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { - let person_id = Some(Number(req.path.split("/").pop())); - let form = new GetPersonDetails({ + let person_id = Number(req.path.split("/").pop()); + let form: GetPersonDetails = { person_id, - sort: Some(SortType.New), - saved_only: Some(false), - username: None, - page: None, - limit: None, - community_id: None, + sort: SortType.New, + saved_only: false, auth: req.auth, - }); + }; return [req.client.getPersonDetails(form)]; } get documentTitle(): string { - return this.state.recipientDetailsRes.match({ - some: res => - `${i18n.t("create_private_message")} - ${res.person_view.person.name}`, - none: "", - }); + let name_ = this.state.recipientDetailsRes?.person_view.person.name; + return name_ ? `${i18n.t("create_private_message")} - ${name_}` : ""; } componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); } } render() { + let res = this.state.recipientDetailsRes; return ( <div className="container-lg"> <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> {this.state.loading ? ( <h5> <Spinner large /> </h5> ) : ( - this.state.recipientDetailsRes.match({ - some: res => ( - <div className="row"> - <div className="col-12 col-lg-6 offset-lg-3 mb-4"> - <h5>{i18n.t("create_private_message")}</h5> - <PrivateMessageForm - privateMessageView={None} - onCreate={this.handlePrivateMessageCreate} - recipient={res.person_view.person} - /> - </div> + 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> - ), - none: <></>, - }) + </div> + ) )} </div> ); @@ -166,11 +146,8 @@ export class CreatePrivateMessage extends Component< this.setState({ loading: false }); return; } else if (op == UserOperation.GetPersonDetails) { - let data = wsJsonToRes<GetPersonDetailsResponse>( - msg, - GetPersonDetailsResponse - ); - this.setState({ recipientDetailsRes: Some(data), loading: false }); + let data = wsJsonToRes<GetPersonDetailsResponse>(msg); + this.setState({ recipientDetailsRes: data, loading: false }); } } } diff --git a/src/shared/components/private_message/private-message-form.tsx b/src/shared/components/private_message/private-message-form.tsx index 86b58be..31dc292 100644 --- a/src/shared/components/private_message/private-message-form.tsx +++ b/src/shared/components/private_message/private-message-form.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { T } from "inferno-i18next-dess"; import { Prompt } from "inferno-router"; @@ -16,9 +15,9 @@ import { Subscription } from "rxjs"; import { i18n } from "../../i18next"; import { WebSocketService } from "../../services"; import { - auth, capitalizeFirstLetter, isBrowser, + myAuth, relTags, setupTippy, toast, @@ -31,14 +30,14 @@ import { PersonListing } from "../person/person-listing"; interface PrivateMessageFormProps { recipient: PersonSafe; - privateMessageView: Option<PrivateMessageView>; // If a pm is given, that means this is an edit + privateMessageView?: PrivateMessageView; // If a pm is given, that means this is an edit onCancel?(): any; onCreate?(message: PrivateMessageView): any; onEdit?(message: PrivateMessageView): any; } interface PrivateMessageFormState { - privateMessageForm: CreatePrivateMessage; + content?: string; loading: boolean; previewMode: boolean; showDisclaimer: boolean; @@ -48,13 +47,8 @@ export class PrivateMessageForm extends Component< PrivateMessageFormProps, PrivateMessageFormState > { - private subscription: Subscription; - private emptyState: PrivateMessageFormState = { - privateMessageForm: new CreatePrivateMessage({ - content: null, - recipient_id: this.props.recipient.id, - auth: auth().unwrap(), - }), + private subscription?: Subscription; + state: PrivateMessageFormState = { loading: false, previewMode: false, showDisclaimer: false, @@ -63,17 +57,15 @@ export class PrivateMessageForm extends Component< constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; - this.handleContentChange = this.handleContentChange.bind(this); this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); // Its an edit - if (this.props.privateMessageView.isSome()) { - this.state.privateMessageForm.content = - this.props.privateMessageView.unwrap().private_message.content; + if (this.props.privateMessageView) { + this.state.content = + this.props.privateMessageView.private_message.content; } } @@ -82,16 +74,16 @@ export class PrivateMessageForm extends Component< } componentDidUpdate() { - if (!this.state.loading && this.state.privateMessageForm.content) { + if (!this.state.loading && this.state.content) { window.onbeforeunload = () => true; } else { - window.onbeforeunload = undefined; + window.onbeforeunload = null; } } componentWillUnmount() { if (isBrowser()) { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); window.onbeforeunload = null; } } @@ -100,11 +92,11 @@ export class PrivateMessageForm extends Component< return ( <div> <Prompt - when={!this.state.loading && this.state.privateMessageForm.content} + when={!this.state.loading && this.state.content} message={i18n.t("block_leaving")} /> <form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}> - {this.props.privateMessageView.isNone() && ( + {!this.props.privateMessageView && ( <div className="form-group row"> <label className="col-sm-2 col-form-label"> {capitalizeFirstLetter(i18n.t("to"))} @@ -129,11 +121,7 @@ export class PrivateMessageForm extends Component< </label> <div className="col-sm-10"> <MarkdownTextArea - initialContent={Some(this.state.privateMessageForm.content)} - initialLanguageId={None} - placeholder={None} - buttonTitle={None} - maxLength={None} + initialContent={this.state.content} onContentChange={this.handleContentChange} allLanguages={[]} siteLanguages={[]} @@ -168,13 +156,13 @@ export class PrivateMessageForm extends Component< > {this.state.loading ? ( <Spinner /> - ) : this.props.privateMessageView.isSome() ? ( + ) : this.props.privateMessageView ? ( capitalizeFirstLetter(i18n.t("save")) ) : ( capitalizeFirstLetter(i18n.t("send_message")) )} </button> - {this.props.privateMessageView.isSome() && ( + {this.props.privateMessageView && ( <button type="button" className="btn btn-secondary" @@ -195,28 +183,35 @@ export class PrivateMessageForm extends Component< handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) { event.preventDefault(); - i.props.privateMessageView.match({ - some: pm => { - let form = new EditPrivateMessage({ + let pm = i.props.privateMessageView; + let auth = myAuth(); + let content = i.state.content; + if (auth && content) { + if (pm) { + let form: EditPrivateMessage = { private_message_id: pm.private_message.id, - content: i.state.privateMessageForm.content, - auth: auth().unwrap(), - }); + content, + auth, + }; WebSocketService.Instance.send(wsClient.editPrivateMessage(form)); - }, - none: WebSocketService.Instance.send( - wsClient.createPrivateMessage(i.state.privateMessageForm) - ), - }); - i.setState({ loading: true }); + } else { + let form: CreatePrivateMessage = { + content, + recipient_id: i.props.recipient.id, + auth, + }; + WebSocketService.Instance.send(wsClient.createPrivateMessage(form)); + } + i.setState({ loading: true }); + } } handleContentChange(val: string) { - this.setState(s => ((s.privateMessageForm.content = val), s)); + this.setState({ content: val }); } handleCancel(i: PrivateMessageForm) { - i.props.onCancel(); + i.props.onCancel?.(); } handlePreviewToggle(i: PrivateMessageForm, event: any) { @@ -240,18 +235,12 @@ export class PrivateMessageForm extends Component< op == UserOperation.DeletePrivateMessage || op == UserOperation.MarkPrivateMessageAsRead ) { - let data = wsJsonToRes<PrivateMessageResponse>( - msg, - PrivateMessageResponse - ); + let data = wsJsonToRes<PrivateMessageResponse>(msg); this.setState({ loading: false }); - this.props.onEdit(data.private_message_view); + this.props.onEdit?.(data.private_message_view); } else if (op == UserOperation.CreatePrivateMessage) { - let data = wsJsonToRes<PrivateMessageResponse>( - msg, - PrivateMessageResponse - ); - this.props.onCreate(data.private_message_view); + let data = wsJsonToRes<PrivateMessageResponse>(msg); + this.props.onCreate?.(data.private_message_view); } } } diff --git a/src/shared/components/private_message/private-message-report.tsx b/src/shared/components/private_message/private-message-report.tsx index 3d6198c..68ca0ed 100644 --- a/src/shared/components/private_message/private-message-report.tsx +++ b/src/shared/components/private_message/private-message-report.tsx @@ -6,7 +6,7 @@ import { } from "lemmy-js-client"; import { i18n } from "../../i18next"; import { WebSocketService } from "../../services"; -import { auth, mdToHtml, wsClient } from "../../utils"; +import { mdToHtml, myAuth, wsClient } from "../../utils"; import { Icon } from "../common/icon"; import { PersonListing } from "../person/person-listing"; @@ -45,24 +45,21 @@ export class PrivateMessageReport extends Component<Props, any> { <div> {i18n.t("reason")}: {pmr.reason} </div> - {r.resolver.match({ - some: resolver => ( - <div> - {pmr.resolved ? ( - <T i18nKey="resolved_by"> - # - <PersonListing person={resolver} /> - </T> - ) : ( - <T i18nKey="unresolved_by"> - # - <PersonListing person={resolver} /> - </T> - )} - </div> - ), - none: <></>, - })} + {r.resolver && ( + <div> + {pmr.resolved ? ( + <T i18nKey="resolved_by"> + # + <PersonListing person={r.resolver} /> + </T> + ) : ( + <T i18nKey="unresolved_by"> + # + <PersonListing person={r.resolver} /> + </T> + )} + </div> + )} <button className="btn btn-link btn-animate text-muted py-0" onClick={linkEvent(this, this.handleResolveReport)} @@ -82,11 +79,16 @@ export class PrivateMessageReport extends Component<Props, any> { handleResolveReport(i: PrivateMessageReport) { let pmr = i.props.report.private_message_report; - let form = new ResolvePrivateMessageReport({ - report_id: pmr.id, - resolved: !pmr.resolved, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.resolvePrivateMessageReport(form)); + let auth = myAuth(); + if (auth) { + let form: ResolvePrivateMessageReport = { + report_id: pmr.id, + resolved: !pmr.resolved, + auth, + }; + WebSocketService.Instance.send( + wsClient.resolvePrivateMessageReport(form) + ); + } } } diff --git a/src/shared/components/private_message/private-message.tsx b/src/shared/components/private_message/private-message.tsx index b7d1bd1..f9b30a1 100644 --- a/src/shared/components/private_message/private-message.tsx +++ b/src/shared/components/private_message/private-message.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads/build"; import { Component, linkEvent } from "inferno"; import { CreatePrivateMessageReport, @@ -6,11 +5,10 @@ import { MarkPrivateMessageAsRead, PersonSafe, PrivateMessageView, - toUndefined, } from "lemmy-js-client"; import { i18n } from "../../i18next"; import { UserService, WebSocketService } from "../../services"; -import { auth, mdToHtml, toast, wsClient } from "../../utils"; +import { mdToHtml, myAuth, toast, wsClient } from "../../utils"; import { Icon } from "../common/icon"; import { MomentTime } from "../common/moment-time"; import { PersonListing } from "../person/person-listing"; @@ -22,7 +20,7 @@ interface PrivateMessageState { collapsed: boolean; viewSource: boolean; showReportDialog: boolean; - reportReason: Option<string>; + reportReason?: string; } interface PrivateMessageProps { @@ -33,19 +31,17 @@ export class PrivateMessage extends Component< PrivateMessageProps, PrivateMessageState > { - private emptyState: PrivateMessageState = { + state: PrivateMessageState = { showReply: false, showEdit: false, collapsed: false, viewSource: false, showReportDialog: false, - reportReason: None, }; constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleReplyCancel = this.handleReplyCancel.bind(this); this.handlePrivateMessageCreate = this.handlePrivateMessageCreate.bind(this); @@ -53,13 +49,10 @@ export class PrivateMessage extends Component< } get mine(): boolean { - return UserService.Instance.myUserInfo - .map( - m => - m.local_user_view.person.id == - this.props.private_message_view.creator.id - ) - .unwrapOr(false); + return ( + UserService.Instance.myUserInfo?.local_user_view.person.id == + this.props.private_message_view.creator.id + ); } render() { @@ -104,7 +97,7 @@ export class PrivateMessage extends Component< {this.state.showEdit && ( <PrivateMessageForm recipient={otherPerson} - privateMessageView={Some(message_view)} + privateMessageView={message_view} onEdit={this.handlePrivateMessageEdit} onCreate={this.handlePrivateMessageCreate} onCancel={this.handleReplyCancel} @@ -230,7 +223,7 @@ export class PrivateMessage extends Component< className="form-control mr-2" placeholder={i18n.t("reason")} required - value={toUndefined(this.state.reportReason)} + value={this.state.reportReason} onInput={linkEvent(this, this.handleReportReasonChange)} /> <button @@ -245,7 +238,6 @@ export class PrivateMessage extends Component< {this.state.showReply && ( <PrivateMessageForm recipient={otherPerson} - privateMessageView={None} onCreate={this.handlePrivateMessageCreate} /> )} @@ -283,12 +275,15 @@ export class PrivateMessage extends Component< } handleDeleteClick(i: PrivateMessage) { - let form = new DeletePrivateMessage({ - private_message_id: i.props.private_message_view.private_message.id, - deleted: !i.props.private_message_view.private_message.deleted, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.deletePrivateMessage(form)); + let auth = myAuth(); + if (auth) { + let 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)); + } } handleReplyCancel() { @@ -296,12 +291,15 @@ export class PrivateMessage extends Component< } handleMarkRead(i: PrivateMessage) { - let form = new MarkPrivateMessageAsRead({ - private_message_id: i.props.private_message_view.private_message.id, - read: !i.props.private_message_view.private_message.read, - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.markPrivateMessageAsRead(form)); + let auth = myAuth(); + if (auth) { + let 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)); + } } handleMessageCollapse(i: PrivateMessage) { @@ -317,19 +315,23 @@ export class PrivateMessage extends Component< } handleReportReasonChange(i: PrivateMessage, event: any) { - i.setState({ reportReason: Some(event.target.value) }); + i.setState({ reportReason: event.target.value }); } handleReportSubmit(i: PrivateMessage, event: any) { event.preventDefault(); - let form = new CreatePrivateMessageReport({ - private_message_id: i.props.private_message_view.private_message.id, - reason: toUndefined(i.state.reportReason), - auth: auth().unwrap(), - }); - WebSocketService.Instance.send(wsClient.createPrivateMessageReport(form)); + let auth = myAuth(); + let reason = i.state.reportReason; + if (auth && reason) { + let 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 }); + i.setState({ showReportDialog: false }); + } } handlePrivateMessageEdit() { @@ -337,14 +339,12 @@ export class PrivateMessage extends Component< } handlePrivateMessageCreate(message: PrivateMessageView) { - UserService.Instance.myUserInfo.match({ - some: mui => { - if (message.creator.id == mui.local_user_view.person.id) { - this.setState({ showReply: false }); - toast(i18n.t("message_sent")); - } - }, - none: void 0, - }); + if ( + message.creator.id == + UserService.Instance.myUserInfo?.local_user_view.person.id + ) { + this.setState({ showReply: false }); + toast(i18n.t("message_sent")); + } } } diff --git a/src/shared/components/search.tsx b/src/shared/components/search.tsx index b9aacf3..102e0fc 100644 --- a/src/shared/components/search.tsx +++ b/src/shared/components/search.tsx @@ -1,4 +1,3 @@ -import { None, Option, Some } from "@sniptt/monads"; import { Component, linkEvent } from "inferno"; import { CommentResponse, @@ -30,7 +29,6 @@ import { i18n } from "../i18next"; import { CommentViewType, InitialFetchRequest } from "../interfaces"; import { WebSocketService } from "../services"; import { - auth, capitalizeFirstLetter, choicesConfig, commentsToFlatNodes, @@ -45,6 +43,7 @@ import { fetchLimit, fetchUsers, isBrowser, + myAuth, numToSI, personSelectName, personToChoice, @@ -93,13 +92,13 @@ interface SearchState { communityId: number; creatorId: number; page: number; - searchResponse: Option<SearchResponse>; + searchResponse?: SearchResponse; communities: CommunityView[]; - creatorDetails: Option<GetPersonDetailsResponse>; + creatorDetails?: GetPersonDetailsResponse; loading: boolean; siteRes: GetSiteResponse; searchText: string; - resolveObjectResponse: Option<ResolveObjectResponse>; + resolveObjectResponse?: ResolveObjectResponse; } interface UrlParams { @@ -119,18 +118,11 @@ interface Combined { } export class Search extends Component<any, SearchState> { - private isoData = setIsoData( - this.context, - GetCommunityResponse, - ListCommunitiesResponse, - GetPersonDetailsResponse, - SearchResponse, - ResolveObjectResponse - ); + private isoData = setIsoData(this.context); private communityChoices: any; private creatorChoices: any; - private subscription: Subscription; - private emptyState: SearchState = { + private subscription?: Subscription; + state: SearchState = { q: Search.getSearchQueryFromProps(this.props.match.params.q), type_: Search.getSearchTypeFromProps(this.props.match.params.type), sort: Search.getSortTypeFromProps(this.props.match.params.sort), @@ -143,9 +135,6 @@ export class Search extends Component<any, SearchState> { this.props.match.params.community_id ), creatorId: Search.getCreatorIdFromProps(this.props.match.params.creator_id), - searchResponse: None, - resolveObjectResponse: None, - creatorDetails: None, loading: true, siteRes: this.isoData.site_res, communities: [], @@ -182,7 +171,6 @@ export class Search extends Component<any, SearchState> { constructor(props: any, context: any) { super(props, context); - this.state = this.emptyState; this.handleSortChange = this.handleSortChange.bind(this); this.handleListingTypeChange = this.handleListingTypeChange.bind(this); this.handlePageChange = this.handlePageChange.bind(this); @@ -192,42 +180,38 @@ export class Search extends Component<any, SearchState> { // Only fetch the data if coming from another route if (this.isoData.path == this.context.router.route.match.url) { - let communityRes = Some( - this.isoData.routeData[0] as GetCommunityResponse - ); - let communitiesRes = Some( - this.isoData.routeData[1] as ListCommunitiesResponse - ); - + let communityRes = this.isoData.routeData[0] as + | GetCommunityResponse + | undefined; + let communitiesRes = this.isoData.routeData[1] as + | ListCommunitiesResponse + | undefined; // This can be single or multiple communities given - if (communitiesRes.isSome()) { + if (communitiesRes) { this.state = { ...this.state, - communities: communitiesRes.unwrap().communities, + communities: communitiesRes.communities, }; } - if (communityRes.isSome()) { + if (communityRes) { this.state = { ...this.state, - communities: [communityRes.unwrap().community_view], + communities: [communityRes.community_view], }; } this.state = { ...this.state, - creatorDetails: Some( - this.isoData.routeData[2] as GetPersonDetailsResponse - ), + creatorDetails: this.isoData.routeData[2] as GetPersonDetailsResponse, }; if (this.state.q != "") { this.state = { ...this.state, - searchResponse: Some(this.isoData.routeData[3] as SearchResponse), - resolveObjectResponse: Some( - this.isoData.routeData[4] as ResolveObjectResponse - ), + searchResponse: this.isoData.routeData[3] as SearchResponse, + resolveObjectResponse: this.isoData + .routeData[4] as ResolveObjectResponse, loading: false, }; } else { @@ -240,7 +224,7 @@ export class Search extends Component<any, SearchState> { } componentWillUnmount() { - this.subscription.unsubscribe(); + this.subscription?.unsubscribe(); saveScrollPosition(this.context); } @@ -266,13 +250,12 @@ export class Search extends Component<any, SearchState> { } fetchCommunities() { - let listCommunitiesForm = new ListCommunities({ - type_: Some(ListingType.All), - sort: Some(SortType.TopAll), - limit: Some(fetchLimit), - page: None, - auth: auth(false).ok(), - }); + let listCommunitiesForm: ListCommunities = { + type_: ListingType.All, + sort: SortType.TopAll, + limit: fetchLimit, + auth: myAuth(false), + }; WebSocketService.Instance.send( wsClient.listCommunities(listCommunitiesForm) ); @@ -281,71 +264,56 @@ export class Search extends Component<any, SearchState> { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { let pathSplit = req.path.split("/"); let promises: Promise<any>[] = []; + let auth = req.auth; let communityId = this.getCommunityIdFromProps(pathSplit[11]); - let community_id: Option<number> = - communityId == 0 ? None : Some(communityId); - community_id.match({ - some: id => { - let getCommunityForm = new GetCommunity({ - id: Some(id), - name: None, - auth: req.auth, - }); - promises.push(req.client.getCommunity(getCommunityForm)); - promises.push(Promise.resolve()); - }, - none: () => { - let listCommunitiesForm = new ListCommunities({ - type_: Some(ListingType.All), - sort: Some(SortType.TopAll), - limit: Some(fetchLimit), - page: None, - auth: req.auth, - }); - promises.push(Promise.resolve()); - promises.push(req.client.listCommunities(listCommunitiesForm)); - }, - }); + let community_id = communityId == 0 ? undefined : communityId; + if (community_id) { + let getCommunityForm: GetCommunity = { + id: community_id, + auth, + }; + promises.push(req.client.getCommunity(getCommunityForm)); + promises.push(Promise.resolve()); + } else { + let listCommunitiesForm: ListCommunities = { + type_: ListingType.All, + sort: SortType.TopAll, + limit: fetchLimit, + auth: req.auth, + }; + promises.push(Promise.resolve()); + promises.push(req.client.listCommunities(listCommunitiesForm)); + } let creatorId = this.getCreatorIdFromProps(pathSplit[13]); - let creator_id: Option<number> = creatorId == 0 ? None : Some(creatorId); - creator_id.match({ - some: id => { - let getCreatorForm = new GetPersonDetails({ - person_id: Some(id), - username: None, - sort: None, - page: None, - limit: None, - community_id: None, - saved_only: None, - auth: req.auth, - }); - promises.push(req.client.getPersonDetails(getCreatorForm)); - }, - none: () => { - promises.push(Promise.resolve()); - }, - }); + let creator_id = creatorId == 0 ? undefined : creatorId; + if (creator_id) { + let getCreatorForm: GetPersonDetails = { + person_id: creator_id, + auth: req.auth, + }; + promises.push(req.client.getPersonDetails(getCreatorForm)); + } else { + promises.push(Promise.resolve()); + } - let form = new SearchForm({ + let form: SearchForm = { q: this.getSearchQueryFromProps(pathSplit[3]), community_id, - community_name: None, creator_id, - type_: Some(this.getSearchTypeFromProps(pathSplit[5])), - sort: Some(this.getSortTypeFromProps(pathSplit[7])), - listing_type: Some(this.getListingTypeFromProps(pathSplit[9])), - page: Some(this.getPageFromProps(pathSplit[15])), - limit: Some(fetchLimit), + type_: this.getSearchTypeFromProps(pathSplit[5]), + sort: this.getSortTypeFromProps(pathSplit[7]), + listing_type: this.getListingTypeFromProps(pathSplit[9]), + page: this.getPageFromProps(pathSplit[15]), + limit: fetchLimit, auth: req.auth, - }); + }; - let resolveObjectForm = new ResolveObject({ + let resolveObjectForm: ResolveObject = { q: this.getSearchQueryFromProps(pathSplit[3]), auth: req.auth, - }); + }; if (form.q != "") { promises.push(req.client.search(form)); @@ -371,8 +339,6 @@ export class Search extends Component<any, SearchState> { this.setState({ loading: true, searchText: this.state.q, - searchResponse: None, - resolveObjectResponse: None, }); this.search(); } @@ -391,8 +357,6 @@ export class Search extends Component<any, SearchState> { <HtmlTags title={this.documentTitle} path={this.context.router.route.match.url} - description={None} - image={None} /> <h5>{i18n.t("search")}</h5> {this.selects()} @@ -512,53 +476,47 @@ export class Search extends Component<any, SearchState> { buildCombined(): Combined[] { let combined: Combined[] = []; + let resolveRes = this.state.resolveObjectResponse; // Push the possible resolve / federated objects first - this.state.resolveObjectResponse.match({ - some: res => { - let resolveComment = res.comment; - if (resolveComment.isSome()) { - combined.push(this.commentViewToCombined(resolveComment.unwrap())); - } - let resolvePost = res.post; - if (resolvePost.isSome()) { - combined.push(this.postViewToCombined(resolvePost.unwrap())); - } - let resolveCommunity = res.community; - if (resolveCommunity.isSome()) { - combined.push( - this.communityViewToCombined(resolveCommunity.unwrap()) - ); - } - let resolveUser = res.person; - if (resolveUser.isSome()) { - combined.push(this.personViewSafeToCombined(resolveUser.unwrap())); - } - }, - none: void 0, - }); + if (resolveRes) { + let resolveComment = resolveRes.comment; + if (resolveComment) { + combined.push(this.commentViewToCombined(resolveComment)); + } + let resolvePost = resolveRes.post; + if (resolvePost) { + combined.push(this.postViewToCombined(resolvePost)); + } + let resolveCommunity = resolveRes.community; + if (resolveCommunity) { + combined.push(this.communityViewToCombined(resolveCommunity)); + } + let resolveUser = resolveRes.person; + if (resolveUser) { + combined.push(this.personViewSafeToCombined(resolveUser)); + } + } // Push the search results - this.state.searchResponse.match({ - some: res => { - pushNotNull( - combined, - res.comments?.map(e => this.commentViewToCombined(e)) - ); - pushNotNull( - combined, - res.posts?.map(e => this.postViewToCombined(e)) - ); - pushNotNull( - combined, - res.communities?.map(e => this.communityViewToCombined(e)) - ); - pushNotNull( - combined, - res.users?.map(e => this.personViewSafeToCombined(e)) - ); - }, - none: void 0, - }); + let searchRes = this.state.searchResponse; + if (searchRes) { + pushNotNull( + combined, + searchRes.comments?.map(e => this.commentViewToCombined(e)) + ); + pushNotNull( + combined, + searchRes.posts?.map(e => this.postViewToCombined(e)) + ); + pushNotNull( + combined, + searchRes.communities?.map(e => this.communityViewToCombined(e)) + ); + pushNotNull( + combined, + searchRes.users?.map(e => this.personViewSafeToCombined(e)) + ); + } // Sort it if (this.state.sort == SortType.New) { @@ -588,9 +546,6 @@ export class Search extends Component<any, SearchState> { <PostListing key={(i.data as PostView).post.id} post_view={i.data as PostView} - duplicates={None} - moderators={None} - admins={None} showCommunity enableDownvotes={enableDownvotes(this.state.siteRes)} enableNsfw={enableNsfw(this.state.siteRes)} @@ -611,9 +566,6 @@ export class Search extends Component<any, SearchState> { ]} viewType={CommentViewType.Flat} viewOnly - moderators={None} - admins={None} - maxCommentsShown={None} locked noIndent enableDownvotes={enableDownvotes(this.state.siteRes)} @@ -636,15 +588,8 @@ export class Search extends Component<any, SearchState> { comments() { let comments: CommentView[] = []; - - this.state.resolveObjectResponse.match({ - some: res => pushNotNull(comments, res.comment), - none: void 0, - }); - this.state.searchResponse.match({ - some: res => pushNotNull(comments, res.comments), - none: void 0, - }); + pushNotNull(comments, this.state.resolveObjectResponse?.comment); + pushNotNull(comments, this.state.searchResponse?.comments); return ( <CommentNodes @@ -653,9 +598,6 @@ export class Search extends Component<any, SearchState> { viewOnly locked noIndent - moderators={None} - admins={None} - maxCommentsShown={None} enableDownvotes={enableDownvotes(this.state.siteRes)} allLanguages={this.state.siteRes.all_languages} siteLanguages={this.state.siteRes.discussion_languages} @@ -666,14 +608,8 @@ export class Search extends Component<any, SearchState> { posts() { let posts: PostView[] = []; - this.state.resolveObjectResponse.match({ - some: res => pushNotNull(posts, res.post), - none: void 0, - }); - this.state.searchResponse.match({ - some: res => pushNotNull(posts, res.posts), - none: void 0, - }); + pushNotNull(posts, this.state.resolveObjectResponse?.post); + pushNotNull(posts, this.state.searchResponse?.posts); return ( <> @@ -683,9 +619,6 @@ export class Search extends Component<any, SearchState> { <PostListing post_view={pv} showCommunity - duplicates={None} - moderators={None} - admins={None} enableDownvotes={enableDownvotes(this.state.siteRes)} enableNsfw={enableNsfw(this.state.siteRes)} allLanguages={this.state.siteRes.all_languages} @@ -702,14 +635,8 @@ export class Search extends Component<any, SearchState> { communities() { let communities: CommunityView[] = []; - this.state.resolveObjectResponse.match({ - some: res => pushNotNull(communities, res.community), - none: void 0, - }); - this.state.searchResponse.match({ - some: res => pushNotNull(communities, res.communities), - none: void 0, - }); + pushNotNull(communities, this.state.resolveObjectResponse?.community); + pushNotNull(communities, this.state.searchResponse?.communities); return ( <> @@ -725,14 +652,8 @@ export class Search extends Component<any, SearchState> { users() { let users: PersonViewSafe[] = []; - this.state.resolveObjectResponse.match({ - some: res => pushNotNull(users, res.person), - none: void 0, - }); - this.state.searchResponse.match({ - some: res => pushNotNull(users, res.users), - none: void 0, - }); + pushNotNull(users, this.state.resolveObjectResponse?.person); + pushNotNull(users, this.state.searchResponse?.users); return ( <> @@ -800,6 +721,7 @@ export class Search extends Component<any, SearchState> { } creatorFilter() { + let creatorPv = this.state.creatorDetails?.person_view; return ( <div className="form-group col-sm-6"> <label className="col-form-label" htmlFor="creator-filter"> @@ -812,14 +734,11 @@ export class Search extends Component<any, SearchState> { value={this.state.creatorId} > <option value="0">{i18n.t("all")}</option> - {this.state.creatorDetails.match({ - some: creator => ( - <option value={creator.person_view.person.id}> - {personSelectName(creator.person_view)} - </option> - ), - none: <></>, - })} + {creatorPv && ( + <option value={creatorPv.person.id}> + {personSelectName(creatorPv)} + </option> + )} </select> </div> </div> @@ -827,19 +746,24 @@ export class Search extends Component<any, SearchState> { } resultsCount(): number { - let searchCount = this.state.searchResponse - .map( - r => - r.posts?.length + - r.comments?.length + - r.communities?.length + - r.users?.length - ) - .unwrapOr(0); - - let resObjCount = this.state.resolveObjectResponse - .map(r => (r.post || r.person || r.community || r.comment ? 1 : 0)) - .unwrapOr(0); + let r = this.state.searchResponse; + + let searchCount = r + ? r.posts?.length + + r.comments?.length + + r.communities?.length + + r.users?.length + : 0; + + let resolveRes = this.state.resolveObjectResponse; + let resObjCount = resolveRes + ? resolveRes.post || + resolveRes.person || + resolveRes.community || + resolveRes.comment + ? 1 + : 0 + : 0; return resObjCount + searchCount; } @@ -849,33 +773,33 @@ export class Search extends Component<any, SearchState> { } search() { - let community_id: Option<number> = - this.state.communityId == 0 ? None : Some(this.state.communityId); - let creator_id: Option<number> = - this.state.creatorId == 0 ? None : Some(this.state.creatorId); + let community_id = + this.state.communityId == 0 ? undefined : this.state.communityId; + let creator_id = + this.state.creatorId == 0 ? undefined : this.state.creatorId; - let form = new SearchForm({ + let auth = myAuth(false); + let form: SearchForm = { q: this.state.q, community_id, - community_name: None, creator_id, - type_: Some(this.state.type_), - sort: Some(this.state.sort), - listing_type: Some(this.state.listingType), - page: Some(this.state.page), - limit: Some(fetchLimit), - auth: auth(false).ok(), - }); + type_: this.state.type_, + sort: this.state.sort, + listing_type: this.state.listingType, + page: this.state.page, + limit: fetchLimit, + auth, + }; - let resolveObjectForm = new ResolveObject({ + let resolveObjectForm: ResolveObject = { q: this.state.q, - auth: auth(false).ok(), - }); + auth, + }; if (this.state.q != "") { this.setState({ - searchResponse: None, - resolveObjectResponse: None, + searchResponse: undefined, + resolveObjectResponse: undefined, loading: true, }); WebSocketService.Instance.send(wsClient.search(form)); @@ -1019,12 +943,7 @@ export class Search extends Component<any, SearchState> { if (msg.error) { if (msg.error == "couldnt_find_object") { this.setState({ - resolveObjectResponse: Some({ - comment: None, - post: None, - community: None, - person: None, - }), + resolveObjectResponse: {}, }); this.checkFinishedLoading(); } else { @@ -1032,44 +951,35 @@ export class Search extends Component<any, SearchState> { return; } } else if (op == UserOperation.Search) { - let data = wsJsonToRes<SearchResponse>(msg, SearchResponse); - this.setState({ searchResponse: Some(data) }); + let data = wsJsonToRes<SearchResponse>(msg); + this.setState({ searchResponse: data }); window.scrollTo(0, 0); this.checkFinishedLoading(); restoreScrollPosition(this.context); } else if (op == UserOperation.CreateCommentLike) { - let data = wsJsonToRes<CommentResponse>(msg, CommentResponse); + let data = wsJsonToRes<CommentResponse>(msg); createCommentLikeRes( data.comment_view, - this.state.searchResponse.map(r => r.comments).unwrapOr([]) + this.state.searchResponse?.comments ); this.setState(this.state); } else if (op == UserOperation.CreatePostLike) { - let data = wsJsonToRes<PostResponse>(msg, PostResponse); - createPostLikeFindRes( - data.post_view, - this.state.searchResponse.map(r => r.posts).unwrapOr([]) - ); + let data = wsJsonToRes<PostResponse>(msg); + createPostLikeFindRes(data.post_view, this.state.searchResponse?.posts); this.setState(this.state); } else if (op == UserOperation.ListCommunities) { - let data = wsJsonToRes<ListCommunitiesResponse>( - msg, - ListCommunitiesResponse - ); + let data = wsJsonToRes<ListCommunitiesResponse>(msg); this.setState({ communities: data.communities }); this.setupCommunityFilter(); } else if (op == UserOperation.ResolveObject) { - let data = wsJsonToRes<ResolveObjectResponse>(msg, ResolveObjectResponse); - this.setState({ resolveObjectResponse: Some(data) }); + let data = wsJsonToRes<ResolveObjectResponse>(msg); + this.setState({ resolveObjectResponse: data }); this.checkFinishedLoading(); } } checkFinishedLoading() { - if ( - this.state.searchResponse.isSome() && - this.state.resolveObjectResponse.isSome() - ) { + if (this.state.searchResponse && this.state.resolveObjectResponse) { this.setState({ loading: false }); } } diff --git a/src/shared/interfaces.ts b/src/shared/interfaces.ts index c25f5b1..2d24bd5 100644 --- a/src/shared/interfaces.ts +++ b/src/shared/interfaces.ts @@ -1,4 +1,3 @@ -import { Either, Option } from "@sniptt/monads"; import { GetSiteResponse, LemmyHttp } from "lemmy-js-client"; /** @@ -22,16 +21,16 @@ declare global { } export interface InitialFetchRequest { - auth: Option<string>; + auth?: string; client: LemmyHttp; path: string; } export interface PostFormParams { - name: Option<string>; - url: Option<string>; - body: Option<string>; - nameOrId: Option<Either<string, number>>; + name?: string; + url?: string; + body?: string; + nameOrId?: string | number; } export enum CommentViewType { diff --git a/src/shared/routes.ts b/src/shared/routes.ts index 336503f..b4404a7 100644 --- a/src/shared/routes.ts +++ b/src/shared/routes.ts @@ -1,3 +1,4 @@ +import { Inferno } from "inferno"; import { IRouteProps } from "inferno-router/dist/Route"; import { Communities } from "./components/community/communities"; import { Community } from "./components/community/community"; @@ -24,6 +25,8 @@ import { Search } from "./components/search"; import { InitialFetchRequest } from "./interfaces"; interface IRoutePropsWithFetch extends IRouteProps { + // TODO Make sure this one is good. + component: Inferno.ComponentClass; fetchInitialData?(req: InitialFetchRequest): Promise<any>[]; } diff --git a/src/shared/services/UserService.ts b/src/shared/services/UserService.ts index 02b7421..34a08b3 100644 --- a/src/shared/services/UserService.ts +++ b/src/shared/services/UserService.ts @@ -1,5 +1,4 @@ // import Cookies from 'js-cookie'; -import { Err, None, Ok, Option, Result, Some } from "@sniptt/monads"; import IsomorphicCookie from "isomorphic-cookie"; import jwt_decode from "jwt-decode"; import { LoginResponse, MyUserInfo } from "lemmy-js-client"; @@ -21,8 +20,8 @@ interface JwtInfo { export class UserService { private static _instance: UserService; - public myUserInfo: Option<MyUserInfo> = None; - public jwtInfo: Option<JwtInfo> = None; + public myUserInfo?: MyUserInfo; + public jwtInfo?: JwtInfo; public unreadInboxCountSub: BehaviorSubject<number> = new BehaviorSubject<number>(0); public unreadReportCountSub: BehaviorSubject<number> = @@ -37,45 +36,41 @@ export class UserService { public login(res: LoginResponse) { let expires = new Date(); expires.setDate(expires.getDate() + 365); - res.jwt.match({ - some: jwt => { - toast(i18n.t("logged_in")); - IsomorphicCookie.save("jwt", jwt, { expires, secure: isHttps }); - this.setJwtInfo(); - }, - none: void 0, - }); + if (res.jwt) { + toast(i18n.t("logged_in")); + IsomorphicCookie.save("jwt", res.jwt, { expires, secure: isHttps }); + this.setJwtInfo(); + } } public logout() { - this.jwtInfo = None; - this.myUserInfo = None; + this.jwtInfo = undefined; + this.myUserInfo = undefined; IsomorphicCookie.remove("jwt"); // TODO is sometimes unreliable for some reason document.cookie = "jwt=; Max-Age=0; path=/; domain=" + location.hostname; location.reload(); } - public auth(throwErr = true): Result<string, string> { - // Can't use match to convert to result for some reason - let jwt = this.jwtInfo.map(j => j.jwt); - if (jwt.isSome()) { - return Ok(jwt.unwrap()); + public auth(throwErr = true): string | undefined { + let jwt = this.jwtInfo?.jwt; + if (jwt) { + return jwt; } else { let msg = "No JWT cookie found"; if (throwErr && isBrowser()) { - console.log(msg); + console.error(msg); toast(i18n.t("not_logged_in"), "danger"); } - return Err(msg); + return undefined; + // throw msg; } } private setJwtInfo() { - let jwt = IsomorphicCookie.load("jwt"); + let jwt: string | undefined = IsomorphicCookie.load("jwt"); if (jwt) { - let jwtInfo: JwtInfo = { jwt, claims: jwt_decode(jwt) }; - this.jwtInfo = Some(jwtInfo); + this.jwtInfo = { jwt, claims: jwt_decode(jwt) }; } } diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 5309832..09da9db 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -1,5 +1,3 @@ -import { Err, None, Ok, Option, Result, Some } from "@sniptt/monads"; -import { ClassConstructor, deserialize, serialize } from "class-transformer"; import emojiShortName from "emoji-short-name"; import { BlockCommunityResponse, @@ -9,7 +7,6 @@ import { CommentReportView, CommentSortType, CommentView, - CommunityBlockView, CommunityModeratorView, CommunityView, GetSiteMetadata, @@ -18,8 +15,6 @@ import { LemmyHttp, LemmyWebsocket, ListingType, - MyUserInfo, - PersonBlockView, PersonSafe, PersonViewSafe, PostReportView, @@ -30,7 +25,6 @@ import { Search, SearchType, SortType, - toUndefined, } from "lemmy-js-client"; import { default as MarkdownIt } from "markdown-it"; import markdown_it_container from "markdown-it-container"; @@ -190,11 +184,11 @@ export function mdToHtmlInline(text: string) { return { __html: md.renderInline(text) }; } -export function getUnixTime(text: string): number { +export function getUnixTime(text?: string): number | undefined { return text ? new Date(text).getTime() / 1000 : undefined; } -export function futureDaysToUnixTime(days: number): number { +export function futureDaysToUnixTime(days?: number): number | undefined { return days ? Math.trunc( new Date(Date.now() + 1000 * 60 * 60 * 24 * days).getTime() / 1000 @@ -203,132 +197,89 @@ export function futureDaysToUnixTime(days: number): number { } export function canMod( - mods: Option<CommunityModeratorView[]>, - admins: Option<PersonViewSafe[]>, creator_id: number, + mods?: CommunityModeratorView[], + admins?: PersonViewSafe[], myUserInfo = UserService.Instance.myUserInfo, onSelf = false ): boolean { // You can do moderator actions only on the mods added after you. - let adminsThenMods = admins - .unwrapOr([]) - .map(a => a.person.id) - .concat(mods.unwrapOr([]).map(m => m.moderator.id)); - - return myUserInfo.match({ - some: me => { - let myIndex = adminsThenMods.findIndex( - id => id == me.local_user_view.person.id - ); - if (myIndex == -1) { - return false; - } else { - // onSelf +1 on mod actions not for yourself, IE ban, remove, etc - adminsThenMods = adminsThenMods.slice(0, myIndex + (onSelf ? 0 : 1)); - return !adminsThenMods.includes(creator_id); - } - }, - none: false, - }); + let adminsThenMods = + admins + ?.map(a => a.person.id) + .concat(mods?.map(m => m.moderator.id) ?? []) ?? []; + + if (myUserInfo) { + let myIndex = adminsThenMods.findIndex( + id => id == myUserInfo.local_user_view.person.id + ); + if (myIndex == -1) { + return false; + } else { + // onSelf +1 on mod actions not for yourself, IE ban, remove, etc + adminsThenMods = adminsThenMods.slice(0, myIndex + (onSelf ? 0 : 1)); + return !adminsThenMods.includes(creator_id); + } + } else { + return false; + } } export function canAdmin( - admins: Option<PersonViewSafe[]>, - creator_id: number, + creatorId: number, + admins?: PersonViewSafe[], myUserInfo = UserService.Instance.myUserInfo, onSelf = false ): boolean { - return canMod(None, admins, creator_id, myUserInfo, onSelf); + return canMod(creatorId, undefined, admins, myUserInfo, onSelf); } export function isMod( - mods: Option<CommunityModeratorView[]>, - creator_id: number + creatorId: number, + mods?: CommunityModeratorView[] ): boolean { - return mods.match({ - some: mods => mods.map(m => m.moderator.id).includes(creator_id), - none: false, - }); + return mods?.map(m => m.moderator.id).includes(creatorId) ?? false; } export function amMod( - mods: Option<CommunityModeratorView[]>, + mods?: CommunityModeratorView[], myUserInfo = UserService.Instance.myUserInfo ): boolean { - return myUserInfo.match({ - some: mui => isMod(mods, mui.local_user_view.person.id), - none: false, - }); + return myUserInfo ? isMod(myUserInfo.local_user_view.person.id, mods) : false; } -export function isAdmin( - admins: Option<PersonViewSafe[]>, - creator_id: number -): boolean { - return admins.match({ - some: admins => admins.map(a => a.person.id).includes(creator_id), - none: false, - }); +export function isAdmin(creatorId: number, admins?: PersonViewSafe[]): boolean { + return admins?.map(a => a.person.id).includes(creatorId) ?? false; } export function amAdmin(myUserInfo = UserService.Instance.myUserInfo): boolean { - return myUserInfo - .map(mui => mui.local_user_view.person.admin) - .unwrapOr(false); + return myUserInfo?.local_user_view.person.admin ?? false; } export function amCommunityCreator( - mods: Option<CommunityModeratorView[]>, creator_id: number, + mods?: CommunityModeratorView[], myUserInfo = UserService.Instance.myUserInfo ): boolean { - return mods.match({ - some: mods => - myUserInfo - .map(mui => mui.local_user_view.person.id) - .match({ - some: myId => - myId == mods[0].moderator.id && - // Don't allow mod actions on yourself - myId != creator_id, - none: false, - }), - none: false, - }); + let myId = myUserInfo?.local_user_view.person.id; + // Don't allow mod actions on yourself + return myId == mods?.at(0)?.moderator.id && myId != creator_id; } export function amSiteCreator( - admins: Option<PersonViewSafe[]>, creator_id: number, + admins?: PersonViewSafe[], myUserInfo = UserService.Instance.myUserInfo ): boolean { - return admins.match({ - some: admins => - myUserInfo - .map(mui => mui.local_user_view.person.id) - .match({ - some: myId => - myId == admins[0].person.id && - // Don't allow mod actions on yourself - myId != creator_id, - none: false, - }), - none: false, - }); + let myId = myUserInfo?.local_user_view.person.id; + return myId == admins?.at(0)?.person.id && myId != creator_id; } export function amTopMod( - mods: Option<CommunityModeratorView[]>, + mods: CommunityModeratorView[], myUserInfo = UserService.Instance.myUserInfo ): boolean { - return mods.match({ - some: mods => - myUserInfo.match({ - some: mui => mods[0].moderator.id == mui.local_user_view.person.id, - none: false, - }), - none: false, - }); + return mods.at(0)?.moderator.id == myUserInfo?.local_user_view.person.id; } const imageRegex = /(http)?s?:?(\/\/[^"']*\.(?:jpg|jpeg|gif|png|svg|webp))/; @@ -386,9 +337,7 @@ export function routeSearchTypeToEnum(type: string): SearchType { } export async function getSiteMetadata(url: string) { - let form = new GetSiteMetadata({ - url, - }); + let form: GetSiteMetadata = { url }; let client = new LemmyHttp(httpBase); return client.getSiteMetadata(form); } @@ -438,10 +387,8 @@ export function getLanguages( override?: string, myUserInfo = UserService.Instance.myUserInfo ): string[] { - let myLang = myUserInfo - .map(m => m.local_user_view.local_user.interface_language) - .unwrapOr("browser"); - let lang = override || myLang; + let myLang = myUserInfo?.local_user_view.local_user.interface_language; + let lang = override || myLang || "browser"; if (lang == "browser" && isBrowser()) { return getBrowserLanguages(); @@ -496,7 +443,7 @@ export async function setTheme(theme: string, forceReload = false) { let cssLoc = `/css/themes/${theme}.css`; loadCss(theme, cssLoc); - document.getElementById(theme).removeAttribute("disabled"); + document.getElementById(theme)?.removeAttribute("disabled"); } export function loadCss(id: string, loc: string) { @@ -521,19 +468,15 @@ export function objectFlip(obj: any) { } export function showAvatars( - myUserInfo: Option<MyUserInfo> = UserService.Instance.myUserInfo + myUserInfo = UserService.Instance.myUserInfo ): boolean { - return myUserInfo - .map(m => m.local_user_view.local_user.show_avatars) - .unwrapOr(true); + return myUserInfo?.local_user_view.local_user.show_avatars ?? true; } export function showScores( - myUserInfo: Option<MyUserInfo> = UserService.Instance.myUserInfo + myUserInfo = UserService.Instance.myUserInfo ): boolean { - return myUserInfo - .map(m => m.local_user_view.local_user.show_scores) - .unwrapOr(true); + return myUserInfo?.local_user_view.local_user.show_scores ?? true; } export function isCakeDay(published: string): boolean { @@ -595,9 +538,9 @@ export function pictrsDeleteToast( interface NotifyInfo { name: string; - icon: Option<string>; + icon?: string; link: string; - body: string; + body?: string; } export function messageToastify(info: NotifyInfo, router: any) { @@ -607,7 +550,7 @@ export function messageToastify(info: NotifyInfo, router: any) { let toast = Toastify({ text: `${htmlBody}<br />${info.name}`, - avatar: toUndefined(info.icon), + avatar: info.icon, backgroundColor: backgroundColor, className: "text-dark", close: true, @@ -663,7 +606,7 @@ function notify(info: NotifyInfo, router: any) { else { var notification = new Notification(info.name, { ...{ body: info.body }, - ...(info.icon.isSome() && { icon: info.icon.unwrap() }), + ...(info.icon && { icon: info.icon }), }); notification.onclick = (ev: Event): any => { @@ -790,15 +733,12 @@ export function getListingTypeFromProps( defaultListingType: ListingType, myUserInfo = UserService.Instance.myUserInfo ): ListingType { + let myLt = myUserInfo?.local_user_view.local_user.default_listing_type; return props.match.params.listing_type ? routeListingTypeToEnum(props.match.params.listing_type) - : myUserInfo.match({ - some: me => - Object.values(ListingType)[ - me.local_user_view.local_user.default_listing_type - ], - none: defaultListingType, - }); + : myLt + ? Object.values(ListingType)[myLt] + : defaultListingType; } export function getListingTypeFromPropsNoDefault(props: any): ListingType { @@ -817,15 +757,12 @@ export function getSortTypeFromProps( props: any, myUserInfo = UserService.Instance.myUserInfo ): SortType { + let mySortType = myUserInfo?.local_user_view.local_user.default_sort_type; return props.match.params.sort ? routeSortTypeToEnum(props.match.params.sort) - : myUserInfo.match({ - some: mui => - Object.values(SortType)[ - mui.local_user_view.local_user.default_sort_type - ], - none: SortType.Active, - }); + : mySortType + ? Object.values(SortType)[mySortType] + : SortType.Active; } export function getPageFromProps(props: any): number { @@ -838,22 +775,22 @@ export function getRecipientIdFromProps(props: any): number { : 1; } -export function getIdFromProps(props: any): Option<number> { - let id: string = props.match.params.post_id; - return id ? Some(Number(id)) : None; +export function getIdFromProps(props: any): number | undefined { + let id = props.match.params.post_id; + return id ? Number(id) : undefined; } -export function getCommentIdFromProps(props: any): Option<number> { - let id: string = props.match.params.comment_id; - return id ? Some(Number(id)) : None; +export function getCommentIdFromProps(props: any): number | undefined { + let id = props.match.params.comment_id; + return id ? Number(id) : undefined; } export function getUsernameFromProps(props: any): string { return props.match.params.username; } -export function editCommentRes(data: CommentView, comments: CommentView[]) { - let found = comments.find(c => c.comment.id == data.comment.id); +export function editCommentRes(data: CommentView, comments?: CommentView[]) { + let found = comments?.find(c => c.comment.id == data.comment.id); if (found) { found.comment.content = data.comment.content; found.comment.distinguished = data.comment.distinguished; @@ -866,67 +803,60 @@ export function editCommentRes(data: CommentView, comments: CommentView[]) { } } -export function saveCommentRes(data: CommentView, comments: CommentView[]) { - let found = comments.find(c => c.comment.id == data.comment.id); +export function saveCommentRes(data: CommentView, comments?: CommentView[]) { + let found = comments?.find(c => c.comment.id == data.comment.id); if (found) { found.saved = data.saved; } } -// TODO Should only use the return now, no state? export function updatePersonBlock( data: BlockPersonResponse, myUserInfo = UserService.Instance.myUserInfo -): Option<PersonBlockView[]> { - return myUserInfo.match({ - some: (mui: MyUserInfo) => { - if (data.blocked) { - mui.person_blocks.push({ - person: mui.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 - ); - toast(`${i18n.t("unblocked")} ${data.person_view.person.name}`); - } - return Some(mui.person_blocks); - }, - none: None, - }); +) { + let mui = myUserInfo; + if (mui) { + if (data.blocked) { + mui.person_blocks.push({ + person: mui.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 + ); + toast(`${i18n.t("unblocked")} ${data.person_view.person.name}`); + } + } } export function updateCommunityBlock( data: BlockCommunityResponse, myUserInfo = UserService.Instance.myUserInfo -): Option<CommunityBlockView[]> { - return myUserInfo.match({ - some: (mui: MyUserInfo) => { - if (data.blocked) { - mui.community_blocks.push({ - person: mui.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 - ); - toast(`${i18n.t("unblocked")} ${data.community_view.community.name}`); - } - return Some(mui.community_blocks); - }, - none: None, - }); +) { + let mui = myUserInfo; + if (mui) { + if (data.blocked) { + mui.community_blocks.push({ + person: mui.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 + ); + toast(`${i18n.t("unblocked")} ${data.community_view.community.name}`); + } + } } export function createCommentLikeRes( data: CommentView, - comments: CommentView[] + comments?: CommentView[] ) { - let found = comments.find(c => c.comment.id === data.comment.id); + let found = comments?.find(c => c.comment.id === data.comment.id); if (found) { found.counts.score = data.counts.score; found.counts.upvotes = data.counts.upvotes; @@ -937,14 +867,14 @@ export function createCommentLikeRes( } } -export function createPostLikeFindRes(data: PostView, posts: PostView[]) { - let found = posts.find(p => p.post.id == data.post.id); +export function createPostLikeFindRes(data: PostView, posts?: PostView[]) { + let found = posts?.find(p => p.post.id == data.post.id); if (found) { createPostLikeRes(data, found); } } -export function createPostLikeRes(data: PostView, post_view: PostView) { +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; @@ -955,8 +885,8 @@ export function createPostLikeRes(data: PostView, post_view: PostView) { } } -export function editPostFindRes(data: PostView, posts: PostView[]) { - let found = posts.find(p => p.post.id == data.post.id); +export function editPostFindRes(data: PostView, posts?: PostView[]) { + let found = posts?.find(p => p.post.id == data.post.id); if (found) { editPostRes(data, found); } @@ -980,9 +910,9 @@ export function editPostRes(data: PostView, post: PostView) { // TODO possible to make these generic? export function updatePostReportRes( data: PostReportView, - reports: PostReportView[] + reports?: PostReportView[] ) { - let found = reports.find(p => p.post_report.id == data.post_report.id); + let found = reports?.find(p => p.post_report.id == data.post_report.id); if (found) { found.post_report = data.post_report; } @@ -990,9 +920,9 @@ export function updatePostReportRes( export function updateCommentReportRes( data: CommentReportView, - reports: CommentReportView[] + reports?: CommentReportView[] ) { - let found = reports.find(c => c.comment_report.id == data.comment_report.id); + let found = reports?.find(c => c.comment_report.id == data.comment_report.id); if (found) { found.comment_report = data.comment_report; } @@ -1000,9 +930,9 @@ export function updateCommentReportRes( export function updatePrivateMessageReportRes( data: PrivateMessageReportView, - reports: PrivateMessageReportView[] + reports?: PrivateMessageReportView[] ) { - let found = reports.find( + let found = reports?.find( c => c.private_message_report.id == data.private_message_report.id ); if (found) { @@ -1012,9 +942,9 @@ export function updatePrivateMessageReportRes( export function updateRegistrationApplicationRes( data: RegistrationApplicationView, - applications: RegistrationApplicationView[] + applications?: RegistrationApplicationView[] ) { - let found = applications.find( + let found = applications?.find( ra => ra.registration_application.id == data.registration_application.id ); if (found) { @@ -1057,13 +987,15 @@ export function buildCommentsTree( let map = new Map<number, CommentNodeI>(); let depthOffset = !parentComment ? 0 - : getDepthFromComment(comments[0].comment); + : getDepthFromComment(comments[0].comment) ?? 0; for (let comment_view of comments) { + let depthI = getDepthFromComment(comment_view.comment) ?? 0; + let depth = depthI ? depthI - depthOffset : 0; let node: CommentNodeI = { - comment_view: comment_view, + comment_view, children: [], - depth: getDepthFromComment(comment_view.comment) - depthOffset, + depth, }; map.set(comment_view.comment.id, { ...node }); } @@ -1072,45 +1004,46 @@ export function buildCommentsTree( // if its a parent comment fetch, then push the first comment to the top node. if (parentComment) { - tree.push(map.get(comments[0].comment.id)); + let cNode = map.get(comments[0].comment.id); + if (cNode) { + tree.push(cNode); + } } for (let comment_view of comments) { let child = map.get(comment_view.comment.id); - let parent_id = getCommentParentId(comment_view.comment); - parent_id.match({ - some: parentId => { - let parent = map.get(parentId); + if (child) { + let parent_id = getCommentParentId(comment_view.comment); + if (parent_id) { + let parent = map.get(parent_id); // Necessary because blocked comment might not exist if (parent) { parent.children.push(child); } - }, - none: () => { + } else { if (!parentComment) { tree.push(child); } - }, - }); + } + } } return tree; } -export function getCommentParentId(comment: CommentI): Option<number> { - let split = comment.path.split("."); +export function getCommentParentId(comment?: CommentI): number | undefined { + let split = comment?.path.split("."); // remove the 0 - split.shift(); + split?.shift(); - if (split.length > 1) { - return Some(Number(split[split.length - 2])); - } else { - return None; - } + return split && split.length > 1 + ? Number(split.at(split.length - 2)) + : undefined; } -export function getDepthFromComment(comment: CommentI): number { - return comment.path.split(".").length - 2; +export function getDepthFromComment(comment?: CommentI): number | undefined { + let len = comment?.path.split(".").length; + return len ? len - 2 : undefined; } export function insertCommentIntoTree( @@ -1125,43 +1058,36 @@ export function insertCommentIntoTree( depth: 0, }; - getCommentParentId(cv.comment).match({ - some: parentId => { - let parentComment = searchCommentTree(tree, parentId); - parentComment.match({ - some: pComment => { - node.depth = pComment.depth + 1; - pComment.children.unshift(node); - }, - none: void 0, - }); - }, - none: () => { - if (!parentComment) { - tree.unshift(node); - } - }, - }); + let parentId = getCommentParentId(cv.comment); + if (parentId) { + let parent_comment = searchCommentTree(tree, parentId); + if (parent_comment) { + node.depth = parent_comment.depth + 1; + parent_comment.children.unshift(node); + } + } else if (!parentComment) { + tree.unshift(node); + } } export function searchCommentTree( tree: CommentNodeI[], id: number -): Option<CommentNodeI> { +): CommentNodeI | undefined { for (let node of tree) { if (node.comment_view.comment.id === id) { - return Some(node); + return node; } for (const child of node.children) { let res = searchCommentTree([child], id); - if (res.isSome()) { + if (res) { return res; } } } - return None; + return undefined; } export const colorList: string[] = [ @@ -1209,55 +1135,23 @@ export function isBrowser() { return typeof window !== "undefined"; } -export function setIsoData<Type1, Type2, Type3, Type4, Type5>( - context: any, - cls1?: ClassConstructor<Type1>, - cls2?: ClassConstructor<Type2>, - cls3?: ClassConstructor<Type3>, - cls4?: ClassConstructor<Type4>, - cls5?: ClassConstructor<Type5> -): IsoData { +export function setIsoData(context: any): IsoData { // If its the browser, you need to deserialize the data from the window if (isBrowser()) { let json = window.isoData; let routeData = json.routeData; - let routeDataOut: any[] = []; - - // Can't do array looping because of specific type constructor required - if (routeData[0]) { - routeDataOut[0] = convertWindowJson(cls1, routeData[0]); - } - if (routeData[1]) { - routeDataOut[1] = convertWindowJson(cls2, routeData[1]); - } - if (routeData[2]) { - routeDataOut[2] = convertWindowJson(cls3, routeData[2]); - } - if (routeData[3]) { - routeDataOut[3] = convertWindowJson(cls4, routeData[3]); - } - if (routeData[4]) { - routeDataOut[4] = convertWindowJson(cls5, routeData[4]); - } - let site_res = convertWindowJson(GetSiteResponse, json.site_res); + let site_res = json.site_res; let isoData: IsoData = { path: json.path, site_res, - routeData: routeDataOut, + routeData, }; return isoData; } else return context.router.staticContext; } -/** - * Necessary since window ISOData can't store function types like Option - */ -export function convertWindowJson<T>(cls: ClassConstructor<T>, data: any): T { - return deserialize(cls, serialize(data)); -} - -export function wsSubscribe(parseMessage: any): Subscription { +export function wsSubscribe(parseMessage: any): Subscription | undefined { if (isBrowser()) { return WebSocketService.Instance.subject .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) @@ -1267,7 +1161,7 @@ export function wsSubscribe(parseMessage: any): Subscription { () => console.log("complete") ); } else { - return null; + return undefined; } } @@ -1305,9 +1199,8 @@ export function restoreScrollPosition(context: any) { } export function showLocal(isoData: IsoData): boolean { - return isoData.site_res.federated_instances - .map(f => f.linked.length > 0) - .unwrapOr(false); + let linked = isoData.site_res.federated_instances?.linked; + return linked ? linked.length > 0 : false; } export interface ChoicesValue { @@ -1332,35 +1225,29 @@ export function personToChoice(pvs: PersonViewSafe): ChoicesValue { } export async function fetchCommunities(q: string) { - let form = new Search({ + let form: Search = { q, - type_: Some(SearchType.Communities), - sort: Some(SortType.TopAll), - listing_type: Some(ListingType.All), - page: Some(1), - limit: Some(fetchLimit), - community_id: None, - community_name: None, - creator_id: None, - auth: auth(false).ok(), - }); + type_: SearchType.Communities, + sort: SortType.TopAll, + listing_type: ListingType.All, + page: 1, + limit: fetchLimit, + auth: myAuth(false), + }; let client = new LemmyHttp(httpBase); return client.search(form); } export async function fetchUsers(q: string) { - let form = new Search({ + let form: Search = { q, - type_: Some(SearchType.Users), - sort: Some(SortType.TopAll), - listing_type: Some(ListingType.All), - page: Some(1), - limit: Some(fetchLimit), - community_id: None, - community_name: None, - creator_id: None, - auth: auth(false).ok(), - }); + type_: SearchType.Users, + sort: SortType.TopAll, + listing_type: ListingType.All, + page: 1, + limit: fetchLimit, + auth: myAuth(false), + }; let client = new LemmyHttp(httpBase); return client.search(form); } @@ -1406,7 +1293,7 @@ export function communitySelectName(cv: CommunityView): string { } export function personSelectName(pvs: PersonViewSafe): string { - let pName = pvs.person.display_name.unwrapOr(pvs.person.name); + let pName = pvs.person.display_name ?? pvs.person.name; return pvs.person.local ? pName : `${hostname(pvs.person.actor_id)}/${pName}`; } @@ -1430,8 +1317,8 @@ export function isBanned(ps: PersonSafe): boolean { let expires = ps.ban_expires; // Add Z to convert from UTC date // TODO this check probably isn't necessary anymore - if (expires.isSome()) { - if (ps.banned && new Date(expires.unwrap() + "Z") > new Date()) { + if (expires) { + if (ps.banned && new Date(expires + "Z") > new Date()) { return true; } else { return false; @@ -1447,7 +1334,7 @@ export function pushNotNull(array: any[], new_item?: any) { } } -export function auth(throwErr = true): Result<string, string> { +export function myAuth(throwErr = true): string | undefined { return UserService.Instance.auth(throwErr); } @@ -1471,26 +1358,18 @@ export function postToCommentSortType(sort: SortType): CommentSortType { } } -export function arrayGet<T>(arr: Array<T>, index: number): Result<T, string> { - let out = arr.at(index); - if (out == undefined) { - return Err("Index undefined"); - } else { - return Ok(out); - } -} - export function myFirstDiscussionLanguageId( allLanguages: Language[], siteLanguages: number[], myUserInfo = UserService.Instance.myUserInfo -): Option<number> { - return arrayGet( - selectableLanguages(allLanguages, siteLanguages, false, false, myUserInfo), - 0 - ) - .map(l => l.id) - .ok(); +): number | undefined { + return selectableLanguages( + allLanguages, + siteLanguages, + false, + false, + myUserInfo + ).at(0)?.id; } export function canCreateCommunity( @@ -1505,15 +1384,15 @@ export function isPostBlocked( pv: PostView, myUserInfo = UserService.Instance.myUserInfo ): boolean { - return myUserInfo - .map( - mui => - mui.community_blocks - .map(c => c.community.id) - .includes(pv.community.id) || - mui.person_blocks.map(p => p.target.id).includes(pv.creator.id) - ) - .unwrapOr(false); + return ( + (myUserInfo?.community_blocks + .map(c => c.community.id) + .includes(pv.community.id) || + myUserInfo?.person_blocks + .map(p => p.target.id) + .includes(pv.creator.id)) ?? + false + ); } /// Checks to make sure you can view NSFW posts. Returns true if you can. @@ -1522,17 +1401,12 @@ export function nsfwCheck( myUserInfo = UserService.Instance.myUserInfo ): boolean { let nsfw = pv.post.nsfw || pv.community.nsfw; - return ( - !nsfw || - (nsfw && - myUserInfo - .map(m => m.local_user_view.local_user.show_nsfw) - .unwrapOr(false)) - ); + let myShowNsfw = myUserInfo?.local_user_view.local_user.show_nsfw ?? false; + return !nsfw || (nsfw && myShowNsfw); } -export function getRandomFromList<T>(list: T[]): T { - return list[Math.floor(Math.random() * list.length)]; +export function getRandomFromList<T>(list?: T[]): T | undefined { + return list?.at(Math.floor(Math.random() * list.length)); } /** @@ -1545,14 +1419,12 @@ export function getRandomFromList<T>(list: T[]): T { export function selectableLanguages( allLanguages: Language[], siteLanguages: number[], - showAll: boolean, - showSite: boolean, + showAll?: boolean, + showSite?: boolean, myUserInfo = UserService.Instance.myUserInfo ): Language[] { let allLangIds = allLanguages.map(l => l.id); - let myLangs = myUserInfo - .map(mui => mui.discussion_languages) - .unwrapOr(allLangIds); + let myLangs = myUserInfo?.discussion_languages ?? allLangIds; myLangs = myLangs.length == 0 ? allLangIds : myLangs; let siteLangs = siteLanguages.length == 0 ? allLangIds : siteLanguages; diff --git a/tsconfig.json b/tsconfig.json index c3dc6cf..d7c8803 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,7 @@ "noUnusedParameters": true, "noImplicitReturns": true, "experimentalDecorators": true, - "strictNullChecks": false, + "strictNullChecks": true, "noFallthroughCasesInSwitch": true }, "include": [ diff --git a/yarn.lock b/yarn.lock index 163e620..cceba43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1105,11 +1105,6 @@ domhandler "^4.2.0" selderee "^0.6.0" -"@sniptt/monads@^0.5.10": - version "0.5.10" - resolved "https://registry.yarnpkg.com/@sniptt/monads/-/monads-0.5.10.tgz#a80cd00738bbd682d36d36dd36bdc0bddc96eb76" - integrity sha512-+agDOv9DpDV+9e2zN/Vmdk+XaqGx5Sykl0fqhqgiJ90r18nsBkxe44DmZ2sA1HYK+MSsBeZBiAr6pq4w+5uhfw== - "@types/autosize@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/autosize/-/autosize-4.0.1.tgz#999a7c305b96766248044ebaac1a0299961f3b61" @@ -2263,11 +2258,6 @@ cidr-regex@1.0.6: resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-1.0.6.tgz#74abfd619df370b9d54ab14475568e97dd64c0c1" integrity sha512-vIIQZtDT0y3GmcVqi4Uhd43s7HKn5DtH8/CcmHe/XG1Vb4JpUgOfTynZzYSo1zeB+j4GbA38Eu2P9UTbIzDw5g== -class-transformer@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" - integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== - classnames@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" @@ -4982,15 +4972,12 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -lemmy-js-client@0.17.0-rc.57: - version "0.17.0-rc.57" - resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.57.tgz#f7a243ed53542810e7446b0a28ad162f3e913254" - integrity sha512-7kZHi0B+jiKc50itTwngkS5Vxcuvux3LjgD28IXZ049cWQgZDqer6BCmudcbViP+dAoyWs9Fh2SyWkYFhv7bwQ== +lemmy-js-client@0.17.0-rc.61: + version "0.17.0-rc.61" + resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.61.tgz#c01e129a3d4c3483ecf337f1e4acf0ad91f9684f" + integrity sha512-xauBCD5i4vlUEWqsTMIXLCXeIjAK7ivVIN3C/g+RMAM7mD3CTcRkDZUerwnvLipIfr7V/4iYLWZW0orBaiV1CQ== dependencies: - "@sniptt/monads" "^0.5.10" - class-transformer "^0.5.1" node-fetch "2.6.6" - reflect-metadata "^0.1.13" levn@^0.4.1: version "0.4.1" @@ -6969,11 +6956,6 @@ redux@^4.1.2: dependencies: "@babel/runtime" "^7.9.2" -reflect-metadata@^0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== - regenerate-unicode-properties@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" -- 2.44.1