* Not working, because of wrong API types.
* Adding Rust-style Result and Option types.
- Fixes #646
* Updating to use new lemmy-js-client with Options.
},
"devDependencies": {
"@babel/core": "^7.17.9",
+ "@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/plugin-transform-typescript": "^7.16.1",
"@babel/preset-env": "7.16.11",
"@babel/preset-typescript": "^7.16.0",
"@babel/runtime": "^7.17.9",
+ "@sniptt/monads": "^0.5.10",
"@types/autosize": "^4.0.0",
"@types/express": "^4.17.13",
"@types/node": "^17.0.29",
"babel-plugin-inferno": "^6.4.0",
"bootstrap": "^5.1.3",
"bootswatch": "^5.1.3",
+ "class-transformer": "^0.5.1",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.4",
"css-loader": "^6.7.1",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^7.0.4",
"import-sort-style-module": "^6.0.0",
- "lemmy-js-client": "0.16.4",
+ "lemmy-js-client": "0.17.0-rc.30",
"lint-staged": "^12.4.1",
"mini-css-extract-plugin": "^2.6.0",
"node-fetch": "^2.6.1",
"prettier-plugin-import-sort": "^0.0.7",
"prettier-plugin-organize-imports": "^2.3.4",
"prettier-plugin-packagejson": "^2.2.17",
+ "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"run-node-webpack-plugin": "^1.3.0",
"sass-loader": "^12.6.0",
import { hydrate } from "inferno-hydrate";
import { BrowserRouter } from "inferno-router";
+import { GetSiteResponse } from "lemmy-js-client";
import { App } from "../shared/components/app/app";
-import { initializeSite } from "../shared/utils";
+import { convertWindowJson, initializeSite } from "../shared/utils";
-const site = window.isoData.site_res;
+const site = convertWindowJson(GetSiteResponse, window.isoData.site_res);
initializeSite(site);
const wrapper = (
<BrowserRouter>
- <App siteRes={window.isoData.site_res} />
+ <App />
</BrowserRouter>
);
+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";
import { matchPath, StaticRouter } from "inferno-router";
import { renderToString } from "inferno-server";
import IsomorphicCookie from "isomorphic-cookie";
-import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
+import { GetSite, GetSiteResponse, LemmyHttp, toOption } from "lemmy-js-client";
import path from "path";
import process from "process";
import serialize from "serialize-javascript";
IsoData,
} from "../shared/interfaces";
import { routes } from "../shared/routes";
-import { initializeSite, setOptionalAuth } from "../shared/utils";
+import { initializeSite } from "../shared/utils";
const server = express();
const [hostname, port] = process.env["LEMMY_UI_HOST"]
try {
const activeRoute = routes.find(route => matchPath(req.path, route)) || {};
const context = {} as any;
- let auth: string = IsomorphicCookie.load("jwt", req);
+ let auth: Option<string> = toOption(IsomorphicCookie.load("jwt", req));
- let getSiteForm: GetSite = {};
- setOptionalAuth(getSiteForm, auth);
+ let getSiteForm = new GetSite({ auth });
let promises: Promise<any>[] = [];
console.error(
"Incorrect JWT token, skipping auth so frontend can remove jwt cookie"
);
- delete getSiteForm.auth;
- delete initialFetchReq.auth;
+ getSiteForm.auth = None;
+ initialFetchReq.auth = None;
try_site = await initialFetchReq.client.getSite(getSiteForm);
}
let site: GetSiteResponse = try_site;
const wrapper = (
<StaticRouter location={req.url} context={isoData}>
- <App siteRes={isoData.site_res} />
+ <App />
</StaticRouter>
);
if (context.url) {
<!DOCTYPE html>
<html ${helmet.htmlAttributes.toString()} lang="en">
<head>
- <script>window.isoData = ${serialize(isoData)}</script>
+ <script>window.isoData = ${serializeO(isoData)}</script>
<script>window.lemmyConfig = ${serialize(config)}</script>
<!-- A remote debugging utility for mobile -->
import { Helmet } from "inferno-helmet";
import { Provider } from "inferno-i18next-dess";
import { Route, Switch } from "inferno-router";
-import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { routes } from "../../routes";
-import { favIconPngUrl, favIconUrl } from "../../utils";
+import { favIconPngUrl, favIconUrl, setIsoData } from "../../utils";
import { Footer } from "./footer";
import { Navbar } from "./navbar";
import { NoMatch } from "./no-match";
import "./styles.scss";
import { Theme } from "./theme";
-export interface AppProps {
- siteRes: GetSiteResponse;
-}
-
-export class App extends Component<AppProps, any> {
+export class App extends Component<any, any> {
+ private isoData = setIsoData(this.context);
constructor(props: any, context: any) {
super(props, context);
}
render() {
- let siteRes = this.props.siteRes;
+ let siteRes = this.isoData.site_res;
+ let siteView = siteRes.site_view;
+
return (
<>
<Provider i18next={i18n}>
<div>
- <Theme
- myUserInfo={siteRes.my_user}
- defaultTheme={siteRes?.site_view?.site?.default_theme}
- />
- {siteRes &&
- siteRes.site_view &&
- this.props.siteRes.site_view.site.icon && (
- <Helmet>
- <link
- id="favicon"
- rel="shortcut icon"
- type="image/x-icon"
- href={this.props.siteRes.site_view.site.icon || favIconUrl}
- />
- <link
- rel="apple-touch-icon"
- href={
- this.props.siteRes.site_view.site.icon || favIconPngUrl
- }
- />
- </Helmet>
- )}
- <Navbar site_res={this.props.siteRes} />
+ <Theme defaultTheme={siteView.map(s => s.site.default_theme)} />
+ {siteView
+ .andThen(s => s.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: <></>,
+ })}
+ <Navbar siteRes={siteRes} />
<div class="mt-4 p-0 fl-1">
<Switch>
{routes.map(({ path, exact, component: C, ...rest }) => (
<Route render={props => <NoMatch {...props} />} />
</Switch>
</div>
- <Footer site={this.props.siteRes} />
+ <Footer site={siteRes} />
</div>
</Provider>
</>
{i18n.t("modlog")}
</NavLink>
</li>
- {this.props.site.site_view?.site.legal_information && (
+ {this.props.site.site_view
+ .andThen(s => s.site.legal_information)
+ .isSome() && (
<li className="nav-item">
<NavLink className="nav-link" to="/legal">
{i18n.t("legal_information")}
+import { None, Some } from "@sniptt/monads";
import { Component, createRef, linkEvent, RefObject } from "inferno";
import { NavLink } from "inferno-router";
import {
GetUnreadRegistrationApplicationCountResponse,
PrivateMessageResponse,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ amAdmin,
+ auth,
donateLemmyUrl,
getLanguages,
isBrowser,
showAvatars,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { Icon } from "../common/icon";
import { PictrsImage } from "../common/pictrs-image";
interface NavbarProps {
- site_res: GetSiteResponse;
+ siteRes: GetSiteResponse;
}
interface NavbarState {
- isLoggedIn: boolean;
expanded: boolean;
unreadInboxCount: number;
unreadReportCount: number;
private unreadApplicationCountSub: Subscription;
private searchTextField: RefObject<HTMLInputElement>;
emptyState: NavbarState = {
- isLoggedIn: !!this.props.site_res.my_user,
unreadInboxCount: 0,
unreadReportCount: 0,
unreadApplicationCount: 0,
// Subscribe to jwt changes
if (isBrowser()) {
this.searchTextField = createRef();
- console.log(`isLoggedIn = ${this.state.isLoggedIn}`);
// On the first load, check the unreads
- if (this.state.isLoggedIn == false) {
- // setTheme(data.my_user.theme, true);
- // i18n.changeLanguage(getLanguage());
- // i18n.changeLanguage('de');
- } else {
+ if (UserService.Instance.myUserInfo.isSome()) {
this.requestNotificationPermission();
WebSocketService.Instance.send(
wsClient.userJoin({
- auth: authField(),
+ auth: auth().unwrap(),
})
);
this.fetchUnreads();
this.userSub = UserService.Instance.jwtSub.subscribe(res => {
// A login
- if (res !== undefined) {
+ if (res.isSome()) {
this.requestNotificationPermission();
WebSocketService.Instance.send(
- wsClient.getSite({ auth: authField() })
+ wsClient.getSite({ auth: res.map(r => r.jwt) })
);
- } else {
- this.setState({ isLoggedIn: false });
}
});
// TODO class active corresponding to current page
navbar() {
- let myUserInfo =
- UserService.Instance.myUserInfo || this.props.site_res.my_user;
- let person = myUserInfo?.local_user_view.person;
return (
- <nav class="navbar navbar-expand-lg navbar-light shadow-sm p-0 px-3">
+ <nav class="navbar navbar-expand-md navbar-light shadow-sm p-0 px-3">
<div class="container">
- {this.props.site_res.site_view && (
- <NavLink
- to="/"
- onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
- title={
- this.props.site_res.site_view.site.description ||
- this.props.site_res.site_view.site.name
- }
- className="d-flex align-items-center navbar-brand mr-md-3"
- >
- {this.props.site_res.site_view.site.icon && showAvatars() && (
- <PictrsImage
- src={this.props.site_res.site_view.site.icon}
- icon
- />
- )}
- {this.props.site_res.site_view.site.name}
- </NavLink>
- )}
- {this.state.isLoggedIn && (
+ {this.props.siteRes.site_view.match({
+ some: siteView => (
+ <NavLink
+ to="/"
+ onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
+ title={siteView.site.description.unwrapOr(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.name}
+ </NavLink>
+ ),
+ none: <></>,
+ })}
+ {UserService.Instance.myUserInfo.isSome() && (
<>
<ul class="navbar-nav ml-auto">
<li className="nav-item">
</NavLink>
</li>
</ul>
- {UserService.Instance.myUserInfo?.moderates.length > 0 && (
+ {this.moderatesSomething && (
<ul class="navbar-nav ml-1">
<li className="nav-item">
<NavLink
</li>
</ul>
)}
- {UserService.Instance.myUserInfo?.local_user_view.person
- .admin && (
+ {this.amAdmin && (
<ul class="navbar-nav ml-1">
<li className="nav-item">
<NavLink
</li>
</ul>
<ul class="navbar-nav my-2">
- {this.canAdmin && (
+ {this.amAdmin && (
<li className="nav-item">
<NavLink
to="/admin"
</button>
</form>
)}
- {this.state.isLoggedIn ? (
+ {UserService.Instance.myUserInfo.isSome() ? (
<>
<ul class="navbar-nav my-2">
<li className="nav-item">
</NavLink>
</li>
</ul>
- {UserService.Instance.myUserInfo?.moderates.length > 0 && (
+ {this.moderatesSomething && (
<ul class="navbar-nav my-2">
<li className="nav-item">
<NavLink
</li>
</ul>
)}
- {UserService.Instance.myUserInfo?.local_user_view.person
- .admin && (
+ {this.amAdmin && (
<ul class="navbar-nav my-2">
<li className="nav-item">
<NavLink
</li>
</ul>
)}
- <ul class="navbar-nav">
- <li class="nav-item dropdown">
- <button
- class="nav-link btn btn-link dropdown-toggle"
- onClick={linkEvent(this, this.handleToggleDropdown)}
- id="navbarDropdown"
- role="button"
- aria-expanded="false"
- >
- <span>
- {person.avatar && showAvatars() && (
- <PictrsImage src={person.avatar} icon />
- )}
- {person.display_name
- ? person.display_name
- : person.name}
- </span>
- </button>
- {this.state.showDropdown && (
- <div
- class="dropdown-content"
- onMouseLeave={linkEvent(
- this,
- this.handleToggleDropdown
- )}
- >
- <li className="nav-item">
- <NavLink
- to={`/u/${UserService.Instance.myUserInfo.local_user_view.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 class="dropdown-divider" />
- </li>
- <li className="nav-item">
+ {UserService.Instance.myUserInfo
+ .map(m => m.local_user_view.person)
+ .match({
+ some: person => (
+ <ul class="navbar-nav">
+ <li class="nav-item dropdown">
<button
- className="nav-link btn btn-link"
- onClick={linkEvent(this, this.handleLogoutClick)}
- title="test"
+ class="nav-link btn btn-link dropdown-toggle"
+ onClick={linkEvent(this, this.handleToggleDropdown)}
+ id="navbarDropdown"
+ role="button"
+ aria-expanded="false"
>
- <Icon icon="log-out" classes="mr-1" />
- {i18n.t("logout")}
+ <span>
+ {showAvatars() &&
+ person.avatar.match({
+ some: avatar => (
+ <PictrsImage src={avatar} icon />
+ ),
+ none: <></>,
+ })}
+ {person.display_name.unwrapOr(person.name)}
+ </span>
</button>
+ {this.state.showDropdown && (
+ <div
+ class="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 class="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>
- </div>
- )}
- </li>
- </ul>
+ </ul>
+ ),
+ none: <></>,
+ })}
</>
) : (
<ul class="navbar-nav my-2">
);
}
+ get moderatesSomething(): boolean {
+ return (
+ UserService.Instance.myUserInfo.map(m => m.moderates).unwrapOr([])
+ .length > 0
+ );
+ }
+
+ get amAdmin(): boolean {
+ return amAdmin(Some(this.props.siteRes.admins));
+ }
+
+ get canCreateCommunity(): boolean {
+ let adminOnly = this.props.siteRes.site_view
+ .map(s => s.site.community_creation_admin_only)
+ .unwrapOr(false);
+ return !adminOnly || this.amAdmin;
+ }
+
handleToggleExpandNavbar(i: Navbar) {
i.state.expanded = !i.state.expanded;
i.setState(i.state);
handleLogoutClick(i: Navbar) {
i.setState({ showDropdown: false, expanded: false });
UserService.Instance.logout();
- window.location.href = "/";
- location.reload();
}
handleToggleDropdown(i: Navbar) {
if (msg.error) {
if (msg.error == "not_logged_in") {
UserService.Instance.logout();
- location.reload();
}
return;
} else if (msg.reconnect) {
console.log(i18n.t("websocket_reconnected"));
- WebSocketService.Instance.send(
- wsClient.userJoin({
- auth: authField(),
- })
- );
- this.fetchUnreads();
+ if (UserService.Instance.myUserInfo.isSome()) {
+ WebSocketService.Instance.send(
+ wsClient.userJoin({
+ auth: auth().unwrap(),
+ })
+ );
+ this.fetchUnreads();
+ }
} else if (op == UserOperation.GetUnreadCount) {
- let data = wsJsonToRes<GetUnreadCountResponse>(msg).data;
+ let data = wsJsonToRes<GetUnreadCountResponse>(
+ msg,
+ GetUnreadCountResponse
+ );
this.state.unreadInboxCount =
data.replies + data.mentions + data.private_messages;
this.setState(this.state);
this.sendUnreadCount();
} else if (op == UserOperation.GetReportCount) {
- let data = wsJsonToRes<GetReportCountResponse>(msg).data;
+ let data = wsJsonToRes<GetReportCountResponse>(
+ msg,
+ GetReportCountResponse
+ );
this.state.unreadReportCount = data.post_reports + data.comment_reports;
this.setState(this.state);
this.sendReportUnread();
} else if (op == UserOperation.GetUnreadRegistrationApplicationCount) {
- let data =
- wsJsonToRes<GetUnreadRegistrationApplicationCountResponse>(msg).data;
+ let data = wsJsonToRes<GetUnreadRegistrationApplicationCountResponse>(
+ msg,
+ GetUnreadRegistrationApplicationCountResponse
+ );
this.state.unreadApplicationCount = data.registration_applications;
this.setState(this.state);
this.sendApplicationUnread();
} else if (op == UserOperation.GetSite) {
// This is only called on a successful login
- let data = wsJsonToRes<GetSiteResponse>(msg).data;
- console.log(data.my_user);
+ let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse);
UserService.Instance.myUserInfo = data.my_user;
- setTheme(
- UserService.Instance.myUserInfo.local_user_view.local_user.theme
- );
- i18n.changeLanguage(getLanguages()[0]);
- this.state.isLoggedIn = true;
- this.setState(this.state);
- } else if (op == UserOperation.CreateComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
-
- if (this.state.isLoggedIn) {
- if (
- data.recipient_ids.includes(
- UserService.Instance.myUserInfo.local_user_view.local_user.id
- )
- ) {
- this.state.unreadInboxCount++;
+ UserService.Instance.myUserInfo.match({
+ some: mui => {
+ setTheme(mui.local_user_view.local_user.theme);
+ i18n.changeLanguage(getLanguages()[0]);
this.setState(this.state);
- this.sendUnreadCount();
- notifyComment(data.comment_view, this.context.router);
- }
- }
+ },
+ none: void 0,
+ });
+ } 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.state.unreadInboxCount++;
+ this.setState(this.state);
+ this.sendUnreadCount();
+ notifyComment(data.comment_view, this.context.router);
+ }
+ },
+ none: void 0,
+ });
} else if (op == UserOperation.CreatePrivateMessage) {
- let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
-
- if (this.state.isLoggedIn) {
- if (
- data.private_message_view.recipient.id ==
- UserService.Instance.myUserInfo.local_user_view.person.id
- ) {
- this.state.unreadInboxCount++;
- this.setState(this.state);
- this.sendUnreadCount();
- notifyPrivateMessage(data.private_message_view, this.context.router);
- }
- }
+ 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.unreadInboxCount++;
+ this.setState(this.state);
+ this.sendUnreadCount();
+ notifyPrivateMessage(
+ data.private_message_view,
+ this.context.router
+ );
+ }
+ },
+ none: void 0,
+ });
}
}
fetchUnreads() {
console.log("Fetching inbox unreads...");
- let unreadForm: GetUnreadCount = {
- auth: authField(),
- };
+ let unreadForm = new GetUnreadCount({
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm));
console.log("Fetching reports...");
- let reportCountForm: GetReportCount = {
- auth: authField(),
- };
+ let reportCountForm = new GetReportCount({
+ community_id: None,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.getReportCount(reportCountForm));
- if (UserService.Instance.myUserInfo?.local_user_view.person.admin) {
+ if (this.amAdmin) {
console.log("Fetching applications...");
- let applicationCountForm: GetUnreadRegistrationApplicationCount = {
- auth: authField(),
- };
+ let applicationCountForm = new GetUnreadRegistrationApplicationCount({
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(
wsClient.getUnreadRegistrationApplicationCount(applicationCountForm)
);
);
}
- get canAdmin(): boolean {
- return (
- UserService.Instance.myUserInfo &&
- this.props.site_res.admins
- .map(a => a.person.id)
- .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
- );
- }
-
- get canCreateCommunity(): boolean {
- let adminOnly =
- this.props.site_res.site_view?.site.community_creation_admin_only;
- return !adminOnly || this.canAdmin;
- }
-
requestNotificationPermission() {
- if (UserService.Instance.myUserInfo) {
+ if (UserService.Instance.myUserInfo.isSome()) {
document.addEventListener("DOMContentLoaded", function () {
if (!Notification) {
toast(i18n.t("notifications_error"), "danger");
+import { Option } from "@sniptt/monads";
import { Component } from "inferno";
import { Helmet } from "inferno-helmet";
-import { MyUserInfo } from "lemmy-js-client";
+import { UserService } from "../../services";
interface Props {
- myUserInfo: MyUserInfo | undefined;
- defaultTheme?: string;
+ defaultTheme: Option<string>;
}
export class Theme extends Component<Props> {
render() {
- let user = this.props.myUserInfo;
- let hasTheme = user && user.local_user_view.local_user.theme !== "browser";
+ let user = UserService.Instance.myUserInfo;
+ let hasTheme = user
+ .map(m => m.local_user_view.local_user.theme !== "browser")
+ .unwrapOr(false);
if (hasTheme) {
return (
<link
rel="stylesheet"
type="text/css"
- href={`/css/themes/${user.local_user_view.local_user.theme}.css`}
+ href={`/css/themes/${
+ user.unwrap().local_user_view.local_user.theme
+ }.css`}
/>
</Helmet>
);
} else if (
- this.props.defaultTheme != null &&
- this.props.defaultTheme != "browser"
+ this.props.defaultTheme.isSome() &&
+ this.props.defaultTheme.unwrap() != "browser"
) {
return (
<Helmet>
<link
rel="stylesheet"
type="text/css"
- href={`/css/themes/${this.props.defaultTheme}.css`}
+ href={`/css/themes/${this.props.defaultTheme.unwrap()}.css`}
/>
</Helmet>
);
+import { Either, None, Option, Some } from "@sniptt/monads";
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
CreateComment,
EditComment,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { CommentNode as CommentNodeI } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
capitalizeFirstLetter,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { Icon } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
interface CommentFormProps {
- postId?: number;
- node?: CommentNodeI; // Can either be the parent, or the editable comment
+ /**
+ * Can either be the parent, or the editable comment. The right side is a postId.
+ */
+ node: Either<CommentNodeI, number>;
edit?: boolean;
disabled?: boolean;
focus?: boolean;
interface CommentFormState {
buttonTitle: string;
finished: boolean;
- formId: string;
+ formId: Option<string>;
}
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
private subscription: Subscription;
private emptyState: CommentFormState = {
- buttonTitle: !this.props.node
+ buttonTitle: this.props.node.isRight()
? capitalizeFirstLetter(i18n.t("post"))
: this.props.edit
? capitalizeFirstLetter(i18n.t("save"))
: capitalizeFirstLetter(i18n.t("reply")),
finished: false,
- formId: "empty_form",
+ formId: None,
};
constructor(props: any, context: any) {
}
render() {
+ let initialContent = this.props.node.match({
+ left: node =>
+ this.props.edit ? Some(node.comment_view.comment.content) : None,
+ right: () => None,
+ });
return (
<div class="mb-3">
- {UserService.Instance.myUserInfo ? (
+ {UserService.Instance.myUserInfo.isSome() ? (
<MarkdownTextArea
- initialContent={
- this.props.edit
- ? this.props.node.comment_view.comment.content
- : null
- }
- buttonTitle={this.state.buttonTitle}
+ initialContent={initialContent}
+ buttonTitle={Some(this.state.buttonTitle)}
+ maxLength={None}
finished={this.state.finished}
- replyType={!!this.props.node}
+ replyType={this.props.node.isLeft()}
focus={this.props.focus}
disabled={this.props.disabled}
onSubmit={this.handleCommentSubmit}
onReplyCancel={this.handleReplyCancel}
- placeholder={i18n.t("comment_here")}
+ placeholder={Some(i18n.t("comment_here"))}
/>
) : (
<div class="alert alert-warning" role="alert">
handleCommentSubmit(msg: { val: string; formId: string }) {
let content = msg.val;
- this.state.formId = msg.formId;
-
- let node = this.props.node;
-
- if (this.props.edit) {
- let form: EditComment = {
- content,
- form_id: this.state.formId,
- comment_id: node.comment_view.comment.id,
- auth: authField(),
- };
- WebSocketService.Instance.send(wsClient.editComment(form));
- } else {
- let form: CreateComment = {
- content,
- form_id: this.state.formId,
- post_id: node ? node.comment_view.post.id : this.props.postId,
- parent_id: node ? node.comment_view.comment.id : null,
- auth: authField(),
- };
- WebSocketService.Instance.send(wsClient.createComment(form));
- }
+ this.state.formId = Some(msg.formId);
+
+ this.props.node.match({
+ left: node => {
+ if (this.props.edit) {
+ let form = new EditComment({
+ content,
+ form_id: this.state.formId,
+ comment_id: node.comment_view.comment.id,
+ auth: auth().unwrap(),
+ });
+ WebSocketService.Instance.send(wsClient.editComment(form));
+ } else {
+ let form = new CreateComment({
+ content,
+ form_id: this.state.formId,
+ post_id: node.comment_view.post.id,
+ parent_id: Some(node.comment_view.comment.id),
+ auth: auth().unwrap(),
+ });
+ WebSocketService.Instance.send(wsClient.createComment(form));
+ }
+ },
+ right: postId => {
+ let form = new CreateComment({
+ content,
+ form_id: this.state.formId,
+ post_id: postId,
+ parent_id: None,
+ auth: auth().unwrap(),
+ });
+ WebSocketService.Instance.send(wsClient.createComment(form));
+ },
+ });
this.setState(this.state);
}
console.log(msg);
// Only do the showing and hiding if logged in
- if (UserService.Instance.myUserInfo) {
+ if (UserService.Instance.myUserInfo.isSome()) {
if (
op == UserOperation.CreateComment ||
op == UserOperation.EditComment
) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
// This only finishes this form, if the randomly generated form_id matches the one received
- if (this.state.formId == data.form_id) {
+ if (this.state.formId.unwrapOr("") == data.form_id.unwrapOr("")) {
this.setState({ finished: true });
// Necessary because it broke tribute for some reason
+import { Left, None, Option, Some } from "@sniptt/monads";
import classNames from "classnames";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
PersonViewSafe,
RemoveComment,
SaveComment,
+ toUndefined,
TransferCommunity,
} from "lemmy-js-client";
import moment from "moment";
import { BanType, CommentNode as CommentNodeI } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ amCommunityCreator,
+ auth,
+ canAdmin,
canMod,
colorList,
futureDaysToUnixTime,
+ isAdmin,
isBanned,
isMod,
mdToHtml,
showReply: boolean;
showEdit: boolean;
showRemoveDialog: boolean;
- removeReason: string;
+ removeReason: Option<string>;
showBanDialog: boolean;
removeData: boolean;
- banReason: string;
- banExpireDays: number;
+ banReason: Option<string>;
+ banExpireDays: Option<number>;
banType: BanType;
showConfirmTransferSite: boolean;
showConfirmTransferCommunity: boolean;
showAdvanced: boolean;
showReportDialog: boolean;
reportReason: string;
- my_vote: number;
+ my_vote: Option<number>;
score: number;
upvotes: number;
downvotes: number;
interface CommentNodeProps {
node: CommentNodeI;
+ moderators: Option<CommunityModeratorView[]>;
+ admins: Option<PersonViewSafe[]>;
noBorder?: boolean;
noIndent?: boolean;
viewOnly?: boolean;
locked?: boolean;
markable?: boolean;
showContext?: boolean;
- moderators: CommunityModeratorView[];
- admins: PersonViewSafe[];
- // TODO is this necessary, can't I get it from the node itself?
- postCreatorId?: number;
showCommunity?: boolean;
enableDownvotes: boolean;
}
showReply: false,
showEdit: false,
showRemoveDialog: false,
- removeReason: null,
+ removeReason: None,
showBanDialog: false,
removeData: false,
- banReason: null,
- banExpireDays: null,
+ banReason: None,
+ banExpireDays: None,
banType: BanType.Community,
collapsed: false,
viewSource: false,
render() {
let node = this.props.node;
let cv = this.props.node.comment_view;
+
+ let canMod_ = canMod(
+ this.props.moderators,
+ this.props.admins,
+ cv.creator.id
+ );
+ let canAdmin_ = canAdmin(this.props.admins, cv.creator.id);
+ let isMod_ = isMod(this.props.moderators, cv.creator.id);
+ let isAdmin_ = isAdmin(this.props.admins, cv.creator.id);
+ let amCommunityCreator_ = amCommunityCreator(
+ this.props.moderators,
+ cv.creator.id
+ );
+
return (
<div
className={`comment ${
- cv.comment.parent_id && !this.props.noIndent ? "ml-1" : ""
+ cv.comment.parent_id.isSome() && !this.props.noIndent ? "ml-1" : ""
}`}
>
<div
} ${this.isCommentNew ? "mark" : ""}`}
style={
!this.props.noIndent &&
- cv.comment.parent_id &&
+ cv.comment.parent_id.isSome() &&
`border-left: 2px ${this.state.borderColor} solid !important`
}
>
<div
- class={`${!this.props.noIndent && cv.comment.parent_id && "ml-2"}`}
+ class={`${
+ !this.props.noIndent && cv.comment.parent_id.isSome() && "ml-2"
+ }`}
>
<div class="d-flex flex-wrap align-items-center text-muted small">
<span class="mr-2">
<PersonListing person={cv.creator} />
</span>
- {this.isMod && (
+ {isMod_ && (
<div className="badge badge-light d-none d-sm-inline mr-2">
{i18n.t("mod")}
</div>
)}
- {this.isAdmin && (
+ {isAdmin_ && (
<div className="badge badge-light d-none d-sm-inline mr-2">
{i18n.t("admin")}
</div>
</>
)}
<span>
- <MomentTime data={cv.comment} />
+ <MomentTime
+ published={cv.comment.published}
+ updated={cv.comment.updated}
+ />
</span>
</div>
{/* end of user row */}
{this.state.showEdit && (
<CommentForm
- node={node}
+ node={Left(node)}
edit
onReplyCancel={this.handleReplyCancel}
disabled={this.props.locked}
)}
</button>
)}
- {UserService.Instance.myUserInfo && !this.props.viewOnly && (
- <>
- <button
- className={`btn btn-link btn-animate ${
- this.state.my_vote == 1 ? "text-info" : "text-muted"
- }`}
- onClick={linkEvent(node, 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 class="ml-1">
- {numToSI(this.state.upvotes)}
- </span>
- )}
- </button>
- {this.props.enableDownvotes && (
+ {UserService.Instance.myUserInfo.isSome() &&
+ !this.props.viewOnly && (
+ <>
<button
className={`btn btn-link btn-animate ${
- this.state.my_vote == -1
- ? "text-danger"
+ this.state.my_vote.unwrapOr(0) == 1
+ ? "text-info"
: "text-muted"
}`}
- onClick={linkEvent(node, this.handleCommentDownvote)}
- data-tippy-content={i18n.t("downvote")}
- aria-label={i18n.t("downvote")}
+ onClick={linkEvent(node, this.handleCommentUpvote)}
+ data-tippy-content={i18n.t("upvote")}
+ aria-label={i18n.t("upvote")}
>
- <Icon icon="arrow-down1" classes="icon-inline" />
+ <Icon icon="arrow-up1" classes="icon-inline" />
{showScores() &&
this.state.upvotes !== this.state.score && (
<span class="ml-1">
- {numToSI(this.state.downvotes)}
+ {numToSI(this.state.upvotes)}
</span>
)}
</button>
- )}
- <button
- class="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.handleShowAdvanced)}
- data-tippy-content={i18n.t("more")}
- aria-label={i18n.t("more")}
- >
- <Icon icon="more-vertical" classes="icon-inline" />
- </button>
- ) : (
- <>
- {!this.myComment && (
- <>
- <button class="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
- class="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
- class="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>
- </>
- )}
+ {this.props.enableDownvotes && (
<button
- class="btn btn-link btn-animate text-muted"
+ className={`btn btn-link btn-animate ${
+ this.state.my_vote.unwrapOr(0) == -1
+ ? "text-danger"
+ : "text-muted"
+ }`}
onClick={linkEvent(
- this,
- this.handleSaveCommentClick
+ node,
+ this.handleCommentDownvote
)}
- data-tippy-content={
- cv.saved ? i18n.t("unsave") : i18n.t("save")
- }
- aria-label={
- cv.saved ? i18n.t("unsave") : i18n.t("save")
- }
+ data-tippy-content={i18n.t("downvote")}
+ aria-label={i18n.t("downvote")}
>
- {this.state.saveLoading ? (
- this.loadingIcon
- ) : (
- <Icon
- icon="star"
- classes={`icon-inline ${
- cv.saved && "text-warning"
- }`}
- />
- )}
+ <Icon icon="arrow-down1" classes="icon-inline" />
+ {showScores() &&
+ this.state.upvotes !== this.state.score && (
+ <span class="ml-1">
+ {numToSI(this.state.downvotes)}
+ </span>
+ )}
</button>
+ )}
+ <button
+ class="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.handleViewSource)}
- data-tippy-content={i18n.t("view_source")}
- aria-label={i18n.t("view_source")}
+ onClick={linkEvent(this, this.handleShowAdvanced)}
+ data-tippy-content={i18n.t("more")}
+ aria-label={i18n.t("more")}
>
- <Icon
- icon="file-text"
- classes={`icon-inline ${
- this.state.viewSource && "text-success"
- }`}
- />
+ <Icon icon="more-vertical" classes="icon-inline" />
</button>
- {this.myComment && (
- <>
- <button
- class="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
- class="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")
- }
- >
+ ) : (
+ <>
+ {!this.myComment && (
+ <>
+ <button class="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
+ class="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
+ class="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
+ class="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="trash"
+ icon="star"
classes={`icon-inline ${
- cv.comment.deleted && "text-danger"
+ cv.saved && "text-warning"
}`}
/>
- </button>
- </>
- )}
- {/* Admins and mods can remove comments */}
- {(this.canMod || this.canAdmin) && (
- <>
- {!cv.comment.removed ? (
+ )}
+ </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
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleModRemoveShow
+ this.handleEditClick
)}
- aria-label={i18n.t("remove")}
+ data-tippy-content={i18n.t("edit")}
+ aria-label={i18n.t("edit")}
>
- {i18n.t("remove")}
+ <Icon icon="edit" classes="icon-inline" />
</button>
- ) : (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleModRemoveSubmit
+ this.handleDeleteClick
)}
- aria-label={i18n.t("restore")}
+ data-tippy-content={
+ !cv.comment.deleted
+ ? i18n.t("delete")
+ : i18n.t("restore")
+ }
+ aria-label={
+ !cv.comment.deleted
+ ? i18n.t("delete")
+ : i18n.t("restore")
+ }
>
- {i18n.t("restore")}
+ <Icon
+ icon="trash"
+ classes={`icon-inline ${
+ cv.comment.deleted && "text-danger"
+ }`}
+ />
</button>
- )}
- </>
- )}
- {/* Mods can ban from community, and appoint as mods to community */}
- {this.canMod && (
- <>
- {!this.isMod &&
- (!cv.creator_banned_from_community ? (
+ </>
+ )}
+ {/* Admins and mods can remove comments */}
+ {(canMod_ || canAdmin_) && (
+ <>
+ {!cv.comment.removed ? (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleModBanFromCommunityShow
+ this.handleModRemoveShow
)}
- aria-label={i18n.t("ban")}
+ aria-label={i18n.t("remove")}
>
- {i18n.t("ban")}
+ {i18n.t("remove")}
</button>
) : (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleModBanFromCommunitySubmit
+ this.handleModRemoveSubmit
)}
- aria-label={i18n.t("unban")}
+ aria-label={i18n.t("restore")}
>
- {i18n.t("unban")}
+ {i18n.t("restore")}
</button>
- ))}
- {!cv.creator_banned_from_community &&
- (!this.state.showConfirmAppointAsMod ? (
- <button
- class="btn btn-link btn-animate text-muted"
- onClick={linkEvent(
- this,
- this.handleShowConfirmAppointAsMod
- )}
- aria-label={
- this.isMod
- ? i18n.t("remove_as_mod")
- : i18n.t("appoint_as_mod")
- }
- >
- {this.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
class="btn btn-link btn-animate text-muted"
- aria-label={i18n.t("are_you_sure")}
+ onClick={linkEvent(
+ this,
+ this.handleModBanFromCommunityShow
+ )}
+ aria-label={i18n.t("ban")}
>
- {i18n.t("are_you_sure")}
+ {i18n.t("ban")}
</button>
+ ) : (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleAddModToCommunity
+ this.handleModBanFromCommunitySubmit
)}
- aria-label={i18n.t("yes")}
+ aria-label={i18n.t("unban")}
>
- {i18n.t("yes")}
+ {i18n.t("unban")}
</button>
+ ))}
+ {!cv.creator_banned_from_community &&
+ (!this.state.showConfirmAppointAsMod ? (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleCancelConfirmAppointAsMod
+ this.handleShowConfirmAppointAsMod
)}
- aria-label={i18n.t("no")}
+ aria-label={
+ isMod_
+ ? i18n.t("remove_as_mod")
+ : i18n.t("appoint_as_mod")
+ }
>
- {i18n.t("no")}
+ {isMod_
+ ? i18n.t("remove_as_mod")
+ : i18n.t("appoint_as_mod")}
</button>
- </>
- ))}
- </>
- )}
- {/* Community creators and admins can transfer community to another mod */}
- {(this.amCommunityCreator || this.canAdmin) &&
- this.isMod &&
- cv.creator.local &&
- (!this.state.showConfirmTransferCommunity ? (
- <button
- class="btn btn-link btn-animate text-muted"
- onClick={linkEvent(
- this,
- this.handleShowConfirmTransferCommunity
- )}
- aria-label={i18n.t("transfer_community")}
- >
- {i18n.t("transfer_community")}
- </button>
- ) : (
- <>
- <button
- class="btn btn-link btn-animate text-muted"
- aria-label={i18n.t("are_you_sure")}
- >
- {i18n.t("are_you_sure")}
- </button>
- <button
- class="btn btn-link btn-animate text-muted"
- onClick={linkEvent(
- this,
- this.handleTransferCommunity
- )}
- aria-label={i18n.t("yes")}
- >
- {i18n.t("yes")}
- </button>
+ ) : (
+ <>
+ <button
+ class="btn btn-link btn-animate text-muted"
+ aria-label={i18n.t("are_you_sure")}
+ >
+ {i18n.t("are_you_sure")}
+ </button>
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(
+ this,
+ this.handleAddModToCommunity
+ )}
+ aria-label={i18n.t("yes")}
+ >
+ {i18n.t("yes")}
+ </button>
+ <button
+ class="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 ? (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this
- .handleCancelShowConfirmTransferCommunity
+ this.handleShowConfirmTransferCommunity
)}
- aria-label={i18n.t("no")}
+ aria-label={i18n.t("transfer_community")}
>
- {i18n.t("no")}
+ {i18n.t("transfer_community")}
</button>
- </>
- ))}
- {/* Admins can ban from all, and appoint other admins */}
- {this.canAdmin && (
- <>
- {!this.isAdmin &&
- (!isBanned(cv.creator) ? (
+ ) : (
+ <>
<button
class="btn btn-link btn-animate text-muted"
- onClick={linkEvent(
- this,
- this.handleModBanShow
- )}
- aria-label={i18n.t("ban_from_site")}
+ aria-label={i18n.t("are_you_sure")}
>
- {i18n.t("ban_from_site")}
+ {i18n.t("are_you_sure")}
</button>
- ) : (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleModBanSubmit
+ this.handleTransferCommunity
)}
- aria-label={i18n.t("unban_from_site")}
+ aria-label={i18n.t("yes")}
>
- {i18n.t("unban_from_site")}
+ {i18n.t("yes")}
</button>
- ))}
- {!isBanned(cv.creator) &&
- cv.creator.local &&
- (!this.state.showConfirmAppointAsAdmin ? (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleShowConfirmAppointAsAdmin
+ this
+ .handleCancelShowConfirmTransferCommunity
)}
- aria-label={
- this.isAdmin
- ? i18n.t("remove_as_admin")
- : i18n.t("appoint_as_admin")
- }
+ aria-label={i18n.t("no")}
>
- {this.isAdmin
- ? i18n.t("remove_as_admin")
- : i18n.t("appoint_as_admin")}
+ {i18n.t("no")}
</button>
- ) : (
- <>
- <button class="btn btn-link btn-animate text-muted">
- {i18n.t("are_you_sure")}
+ </>
+ ))}
+ {/* Admins can ban from all, and appoint other admins */}
+ {canAdmin_ && (
+ <>
+ {!isAdmin_ &&
+ (!isBanned(cv.creator) ? (
+ <button
+ class="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
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleAddAdmin
+ this.handleModBanSubmit
)}
- aria-label={i18n.t("yes")}
+ aria-label={i18n.t("unban_from_site")}
>
- {i18n.t("yes")}
+ {i18n.t("unban_from_site")}
</button>
+ ))}
+ {!isBanned(cv.creator) &&
+ cv.creator.local &&
+ (!this.state.showConfirmAppointAsAdmin ? (
<button
class="btn btn-link btn-animate text-muted"
onClick={linkEvent(
this,
- this.handleCancelConfirmAppointAsAdmin
+ this.handleShowConfirmAppointAsAdmin
)}
- aria-label={i18n.t("no")}
+ aria-label={
+ isAdmin_
+ ? i18n.t("remove_as_admin")
+ : i18n.t("appoint_as_admin")
+ }
>
- {i18n.t("no")}
+ {isAdmin_
+ ? i18n.t("remove_as_admin")
+ : i18n.t("appoint_as_admin")}
</button>
- </>
- ))}
- </>
- )}
- </>
- )}
- </>
- )}
+ ) : (
+ <>
+ <button class="btn btn-link btn-animate text-muted">
+ {i18n.t("are_you_sure")}
+ </button>
+ <button
+ class="btn btn-link btn-animate text-muted"
+ onClick={linkEvent(
+ this,
+ this.handleAddAdmin
+ )}
+ aria-label={i18n.t("yes")}
+ >
+ {i18n.t("yes")}
+ </button>
+ <button
+ class="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>
id={`mod-remove-reason-${cv.comment.id}`}
class="form-control mr-2"
placeholder={i18n.t("reason")}
- value={this.state.removeReason}
+ value={toUndefined(this.state.removeReason)}
onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/>
<button
id={`mod-ban-reason-${cv.comment.id}`}
class="form-control mr-2"
placeholder={i18n.t("reason")}
- value={this.state.banReason}
+ value={toUndefined(this.state.banReason)}
onInput={linkEvent(this, this.handleModBanReasonChange)}
/>
<label
id={`mod-ban-expires-${cv.comment.id}`}
class="form-control mr-2"
placeholder={i18n.t("number_of_days")}
- value={this.state.banExpireDays}
+ value={toUndefined(this.state.banExpireDays)}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/>
<div class="form-group">
)}
{this.state.showReply && (
<CommentForm
- node={node}
+ node={Left(node)}
onReplyCancel={this.handleReplyCancel}
disabled={this.props.locked}
focus
/>
)}
- {node.children && !this.state.collapsed && (
+ {!this.state.collapsed && node.children && (
<CommentNodes
nodes={node.children}
locked={this.props.locked}
moderators={this.props.moderators}
admins={this.props.admins}
- postCreatorId={this.props.postCreatorId}
+ maxCommentsShown={None}
enableDownvotes={this.props.enableDownvotes}
/>
)}
}
get myComment(): boolean {
- return (
- this.props.node.comment_view.creator.id ==
- UserService.Instance.myUserInfo?.local_user_view.person.id
- );
- }
-
- get isMod(): boolean {
- return (
- this.props.moderators &&
- isMod(
- this.props.moderators.map(m => m.moderator.id),
- this.props.node.comment_view.creator.id
- )
- );
- }
-
- get isAdmin(): boolean {
- return (
- this.props.admins &&
- isMod(
- this.props.admins.map(a => a.person.id),
- this.props.node.comment_view.creator.id
+ return UserService.Instance.myUserInfo
+ .map(
+ m =>
+ m.local_user_view.person.id == this.props.node.comment_view.creator.id
)
- );
+ .unwrapOr(false);
}
get isPostCreator(): boolean {
- return this.props.node.comment_view.creator.id == this.props.postCreatorId;
- }
-
- get canMod(): boolean {
- if (this.props.admins && this.props.moderators) {
- let adminsThenMods = this.props.admins
- .map(a => a.person.id)
- .concat(this.props.moderators.map(m => m.moderator.id));
-
- return canMod(
- UserService.Instance.myUserInfo,
- adminsThenMods,
- this.props.node.comment_view.creator.id
- );
- } else {
- return false;
- }
- }
-
- get canAdmin(): boolean {
return (
- this.props.admins &&
- canMod(
- UserService.Instance.myUserInfo,
- this.props.admins.map(a => a.person.id),
- this.props.node.comment_view.creator.id
- )
- );
- }
-
- get amCommunityCreator(): boolean {
- return (
- this.props.moderators &&
- UserService.Instance.myUserInfo &&
- this.props.node.comment_view.creator.id !=
- UserService.Instance.myUserInfo.local_user_view.person.id &&
- UserService.Instance.myUserInfo.local_user_view.person.id ==
- this.props.moderators[0].moderator.id
- );
- }
-
- get amSiteCreator(): boolean {
- return (
- this.props.admins &&
- UserService.Instance.myUserInfo &&
- this.props.node.comment_view.creator.id !=
- UserService.Instance.myUserInfo.local_user_view.person.id &&
- UserService.Instance.myUserInfo.local_user_view.person.id ==
- this.props.admins[0].person.id
+ this.props.node.comment_view.creator.id ==
+ this.props.node.comment_view.post.creator_id
);
}
}
handleBlockUserClick(i: CommentNode) {
- let blockUserForm: BlockPerson = {
+ let blockUserForm = new BlockPerson({
person_id: i.props.node.comment_view.creator.id,
block: true,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
}
handleDeleteClick(i: CommentNode) {
let comment = i.props.node.comment_view.comment;
- let deleteForm: DeleteComment = {
+ let deleteForm = new DeleteComment({
comment_id: comment.id,
deleted: !comment.deleted,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
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: SaveComment = {
+ let form = new SaveComment({
comment_id: cv.comment.id,
save,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.saveComment(form));
handleCommentUpvote(i: CommentNodeI, event: any) {
event.preventDefault();
- let new_vote = this.state.my_vote == 1 ? 0 : 1;
+ let myVote = this.state.my_vote.unwrapOr(0);
+ let newVote = myVote == 1 ? 0 : 1;
- if (this.state.my_vote == 1) {
+ if (myVote == 1) {
this.state.score--;
this.state.upvotes--;
- } else if (this.state.my_vote == -1) {
+ } else if (myVote == -1) {
this.state.downvotes--;
this.state.upvotes++;
this.state.score += 2;
this.state.score++;
}
- this.state.my_vote = new_vote;
+ this.state.my_vote = Some(newVote);
- let form: CreateCommentLike = {
+ let form = new CreateCommentLike({
comment_id: i.comment_view.comment.id,
- score: this.state.my_vote,
- auth: authField(),
- };
+ score: newVote,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.likeComment(form));
this.setState(this.state);
handleCommentDownvote(i: CommentNodeI, event: any) {
event.preventDefault();
- let new_vote = this.state.my_vote == -1 ? 0 : -1;
+ let myVote = this.state.my_vote.unwrapOr(0);
+ let newVote = myVote == -1 ? 0 : -1;
- if (this.state.my_vote == 1) {
+ if (myVote == 1) {
this.state.score -= 2;
this.state.upvotes--;
this.state.downvotes++;
- } else if (this.state.my_vote == -1) {
+ } else if (myVote == -1) {
this.state.downvotes--;
this.state.score++;
} else {
this.state.score--;
}
- this.state.my_vote = new_vote;
+ this.state.my_vote = Some(newVote);
- let form: CreateCommentLike = {
+ let form = new CreateCommentLike({
comment_id: i.comment_view.comment.id,
- score: this.state.my_vote,
- auth: authField(),
- };
+ score: newVote,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.likeComment(form));
this.setState(this.state);
handleReportSubmit(i: CommentNode) {
let comment = i.props.node.comment_view.comment;
- let form: CreateCommentReport = {
+ let form = new CreateCommentReport({
comment_id: comment.id,
reason: i.state.reportReason,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.createCommentReport(form));
i.state.showReportDialog = false;
}
handleModRemoveReasonChange(i: CommentNode, event: any) {
- i.state.removeReason = event.target.value;
+ i.state.removeReason = Some(event.target.value);
i.setState(i.state);
}
handleModRemoveSubmit(i: CommentNode) {
let comment = i.props.node.comment_view.comment;
- let form: RemoveComment = {
+ let form = new RemoveComment({
comment_id: comment.id,
removed: !comment.removed,
reason: i.state.removeReason,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.removeComment(form));
i.state.showRemoveDialog = false;
handleMarkRead(i: CommentNode) {
if (i.isPersonMentionType(i.props.node.comment_view)) {
- let form: MarkPersonMentionAsRead = {
+ 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: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
} else {
- let form: MarkCommentAsRead = {
+ let form = new MarkCommentAsRead({
comment_id: i.props.node.comment_view.comment.id,
read: !i.props.node.comment_view.comment.read,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
}
}
handleModBanReasonChange(i: CommentNode, event: any) {
- i.state.banReason = event.target.value;
+ i.state.banReason = Some(event.target.value);
i.setState(i.state);
}
handleModBanExpireDaysChange(i: CommentNode, event: any) {
- i.state.banExpireDays = event.target.value;
+ i.state.banExpireDays = Some(event.target.value);
i.setState(i.state);
}
if (ban == false) {
i.state.removeData = false;
}
- let form: BanFromCommunity = {
+ let form = new BanFromCommunity({
person_id: cv.creator.id,
community_id: cv.community.id,
ban,
- remove_data: i.state.removeData,
+ remove_data: Some(i.state.removeData),
reason: i.state.banReason,
- expires: futureDaysToUnixTime(i.state.banExpireDays),
- auth: authField(),
- };
+ expires: i.state.banExpireDays.map(futureDaysToUnixTime),
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.banFromCommunity(form));
} else {
// If its an unban, restore all their data
if (ban == false) {
i.state.removeData = false;
}
- let form: BanPerson = {
+ let form = new BanPerson({
person_id: cv.creator.id,
ban,
- remove_data: i.state.removeData,
+ remove_data: Some(i.state.removeData),
reason: i.state.banReason,
- expires: futureDaysToUnixTime(i.state.banExpireDays),
- auth: authField(),
- };
+ expires: i.state.banExpireDays.map(futureDaysToUnixTime),
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.banPerson(form));
}
handleAddModToCommunity(i: CommentNode) {
let cv = i.props.node.comment_view;
- let form: AddModToCommunity = {
+ let form = new AddModToCommunity({
person_id: cv.creator.id,
community_id: cv.community.id,
- added: !i.isMod,
- auth: authField(),
- };
+ added: !isMod(i.props.moderators, cv.creator.id),
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.addModToCommunity(form));
i.state.showConfirmAppointAsMod = false;
i.setState(i.state);
}
handleAddAdmin(i: CommentNode) {
- let form: AddAdmin = {
- person_id: i.props.node.comment_view.creator.id,
- added: !i.isAdmin,
- auth: authField(),
- };
+ 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.state.showConfirmAppointAsAdmin = false;
i.setState(i.state);
handleTransferCommunity(i: CommentNode) {
let cv = i.props.node.comment_view;
- let form: TransferCommunity = {
+ let form = new TransferCommunity({
community_id: cv.community.id,
person_id: cv.creator.id,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.transferCommunity(form));
i.state.showConfirmTransferCommunity = false;
i.setState(i.state);
}
get scoreColor() {
- if (this.state.my_vote == 1) {
+ if (this.state.my_vote.unwrapOr(0) == 1) {
return "text-info";
- } else if (this.state.my_vote == -1) {
+ } else if (this.state.my_vote.unwrapOr(0) == -1) {
return "text-danger";
} else {
return "text-muted";
+import { Option } from "@sniptt/monads";
import { Component } from "inferno";
import { CommunityModeratorView, PersonViewSafe } from "lemmy-js-client";
import { CommentNode as CommentNodeI } from "../../interfaces";
interface CommentNodesProps {
nodes: CommentNodeI[];
- moderators?: CommunityModeratorView[];
- admins?: PersonViewSafe[];
- postCreatorId?: number;
+ moderators: Option<CommunityModeratorView[]>;
+ admins: Option<PersonViewSafe[]>;
+ maxCommentsShown: Option<number>;
noBorder?: boolean;
noIndent?: boolean;
viewOnly?: boolean;
markable?: boolean;
showContext?: boolean;
showCommunity?: boolean;
- maxCommentsShown?: number;
- enableDownvotes: boolean;
+ enableDownvotes?: boolean;
}
export class CommentNodes extends Component<CommentNodesProps, any> {
}
render() {
- let maxComments = this.props.maxCommentsShown
- ? this.props.maxCommentsShown
- : this.props.nodes.length;
+ let maxComments = this.props.maxCommentsShown.unwrapOr(
+ this.props.nodes.length
+ );
return (
<div className="comments">
locked={this.props.locked}
moderators={this.props.moderators}
admins={this.props.admins}
- postCreatorId={this.props.postCreatorId}
markable={this.props.markable}
showContext={this.props.showContext}
showCommunity={this.props.showCommunity}
+import { None } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
import { i18n } from "../../i18next";
import { CommentNode as CommentNodeI } from "../../interfaces";
import { WebSocketService } from "../../services";
-import { authField, wsClient } from "../../utils";
+import { auth, wsClient } from "../../utils";
import { Icon } from "../common/icon";
import { PersonListing } from "../person/person-listing";
import { CommentNode } from "./comment-node";
subscribed: false,
saved: false,
creator_blocked: false,
+ recipient: None,
my_vote: r.my_vote,
};
<div>
<CommentNode
node={node}
- moderators={[]}
- admins={[]}
+ moderators={None}
+ admins={None}
enableDownvotes={true}
viewOnly={true}
showCommunity={true}
<div>
{i18n.t("reason")}: {r.comment_report.reason}
</div>
- {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>
- )}
+ {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: <></>,
+ })}
<button
className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleResolveReport)}
}
handleResolveReport(i: CommentReport) {
- let form: ResolveCommentReport = {
+ let form = new ResolveCommentReport({
report_id: i.props.report.comment_report.id,
resolved: !i.props.report.comment_report.resolved,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.resolveCommentReport(form));
}
}
+import { Option } from "@sniptt/monads";
import { Component } from "inferno";
import { PictrsImage } from "./pictrs-image";
interface BannerIconHeaderProps {
- banner?: string;
- icon?: string;
+ banner: Option<string>;
+ icon: Option<string>;
}
export class BannerIconHeader extends Component<BannerIconHeaderProps, any> {
render() {
return (
<div class="position-relative mb-2">
- {this.props.banner && (
- <PictrsImage src={this.props.banner} banner alt="" />
- )}
- {this.props.icon && (
- <PictrsImage
- src={this.props.icon}
- iconOverlay
- pushup={!!this.props.banner}
- alt=""
- />
- )}
+ {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: <></>,
+ })}
</div>
);
}
+import { Option } from "@sniptt/monads";
import { Component } from "inferno";
import { Helmet } from "inferno-helmet";
import { httpExternalPath } from "../../env";
interface HtmlTagsProps {
title: string;
path: string;
- description?: string;
- image?: string;
+ description: Option<string>;
+ image: Option<string>;
}
/// Taken from https://metatags.io/
<meta property="twitter:card" content="summary_large_image" />
{/* Optional desc and images */}
- {this.props.description &&
+ {this.props.description.isSome() &&
["description", "og:description", "twitter:description"].map(n => (
- <meta name={n} content={md.renderInline(this.props.description)} />
+ <meta
+ name={n}
+ content={md.renderInline(this.props.description.unwrap())}
+ />
))}
- {this.props.image &&
+ {this.props.image.isSome() &&
["og:image", "twitter:image"].map(p => (
- <meta property={p} content={this.props.image} />
+ <meta property={p} content={this.props.image.unwrap()} />
))}
</Helmet>
);
+import { Option } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { pictrsUri } from "../../env";
import { i18n } from "../../i18next";
interface ImageUploadFormProps {
uploadTitle: string;
- imageSrc: string;
+ imageSrc: Option<string>;
onUpload(url: string): any;
onRemove(): any;
rounded?: boolean;
htmlFor={this.id}
class="pointer text-muted small font-weight-bold"
>
- {!this.props.imageSrc ? (
- <span class="btn btn-secondary">{this.props.uploadTitle}</span>
- ) : (
- <span class="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>
- )}
+ {this.props.imageSrc.match({
+ some: imageSrc => (
+ <span class="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 class="btn btn-secondary">{this.props.uploadTitle}</span>
+ ),
+ })}
</label>
<input
id={this.id}
accept="image/*,video/*"
name={this.id}
class="d-none"
- disabled={!UserService.Instance.myUserInfo}
+ disabled={UserService.Instance.myUserInfo.isNone()}
onChange={linkEvent(this, this.handleImageUpload)}
/>
</form>
title={i18n.t("subscribed_description")}
className={`btn btn-outline-secondary
${this.state.type_ == ListingType.Subscribed && "active"}
- ${
- UserService.Instance.myUserInfo == undefined
- ? "disabled"
- : "pointer"
- }
+ ${UserService.Instance.myUserInfo.isNone() ? "disabled" : "pointer"}
`}
>
<input
value={ListingType.Subscribed}
checked={this.state.type_ == ListingType.Subscribed}
onChange={linkEvent(this, this.handleTypeChange)}
- disabled={UserService.Instance.myUserInfo == undefined}
+ disabled={UserService.Instance.myUserInfo.isNone()}
/>
{i18n.t("subscribed")}
</label>
+import { None, Option, Some } from "@sniptt/monads";
import autosize from "autosize";
import { Component, linkEvent } from "inferno";
import { Prompt } from "inferno-router";
+import { toUndefined } from "lemmy-js-client";
import { pictrsUri } from "../../env";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { Icon, Spinner } from "./icon";
interface MarkdownTextAreaProps {
- initialContent?: string;
- finished?: boolean;
- buttonTitle?: string;
+ initialContent: Option<string>;
+ placeholder: Option<string>;
+ buttonTitle: Option<string>;
+ maxLength: Option<number>;
replyType?: boolean;
focus?: boolean;
disabled?: boolean;
- maxLength?: number;
- onSubmit?(msg: { val: string; formId: string }): any;
+ finished?: boolean;
+ hideNavigationWarnings?: boolean;
onContentChange?(val: string): any;
onReplyCancel?(): any;
- hideNavigationWarnings?: boolean;
- placeholder?: string;
+ onSubmit?(msg: { val: string; formId: string }): any;
}
interface MarkdownTextAreaState {
- content: string;
+ content: Option<string>;
previewMode: boolean;
loading: boolean;
imageLoading: boolean;
autosize(textarea);
this.tribute.attach(textarea);
textarea.addEventListener("tribute-replaced", () => {
- this.state.content = textarea.value;
+ this.state.content = Some(textarea.value);
this.setState(this.state);
autosize.update(textarea);
});
}
componentDidUpdate() {
- if (!this.props.hideNavigationWarnings && this.state.content) {
+ if (!this.props.hideNavigationWarnings && this.state.content.isSome()) {
window.onbeforeunload = () => true;
} else {
window.onbeforeunload = undefined;
if (nextProps.finished) {
this.state.previewMode = false;
this.state.loading = false;
- this.state.content = "";
+ this.state.content = None;
this.setState(this.state);
if (this.props.replyType) {
this.props.onReplyCancel();
return (
<form id={this.formId} onSubmit={linkEvent(this, this.handleSubmit)}>
<Prompt
- when={!this.props.hideNavigationWarnings && this.state.content}
+ when={
+ !this.props.hideNavigationWarnings && this.state.content.isSome()
+ }
message={i18n.t("block_leaving")}
/>
<div class="form-group row">
<textarea
id={this.id}
className={`form-control ${this.state.previewMode && "d-none"}`}
- value={this.state.content}
+ value={toUndefined(this.state.content)}
onInput={linkEvent(this, this.handleContentChange)}
onPaste={linkEvent(this, this.handleImageUploadPaste)}
required
disabled={this.props.disabled}
rows={2}
- maxLength={this.props.maxLength || 10000}
- placeholder={this.props.placeholder}
+ maxLength={this.props.maxLength.unwrapOr(10000)}
+ placeholder={toUndefined(this.props.placeholder)}
/>
- {this.state.previewMode && (
- <div
- className="card border-secondary card-body md-div"
- dangerouslySetInnerHTML={mdToHtml(this.state.content)}
- />
- )}
+ {this.state.previewMode &&
+ this.state.content.match({
+ some: content => (
+ <div
+ className="card border-secondary card-body md-div"
+ dangerouslySetInnerHTML={mdToHtml(content)}
+ />
+ ),
+ none: <></>,
+ })}
</div>
<label class="sr-only" htmlFor={this.id}>
{i18n.t("body")}
</div>
<div class="row">
<div class="col-sm-12 d-flex flex-wrap">
- {this.props.buttonTitle && (
- <button
- type="submit"
- class="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.buttonTitle.match({
+ some: buttonTitle => (
+ <button
+ type="submit"
+ class="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.replyType && (
<button
type="button"
{i18n.t("cancel")}
</button>
)}
- {this.state.content && (
+ {this.state.content.isSome() && (
<button
className={`btn btn-sm btn-secondary mr-2 ${
this.state.previewMode && "active"
<label
htmlFor={`file-upload-${this.id}`}
className={`mb-0 ${
- UserService.Instance.myUserInfo && "pointer"
+ UserService.Instance.myUserInfo.isSome() && "pointer"
}`}
data-tippy-content={i18n.t("upload_image")}
>
accept="image/*,video/*"
name="file"
class="d-none"
- disabled={!UserService.Instance.myUserInfo}
+ disabled={UserService.Instance.myUserInfo.isNone()}
onChange={linkEvent(this, this.handleImageUpload)}
/>
</form>
let deleteToken = res.files[0].delete_token;
let deleteUrl = `${pictrsUri}/delete/${deleteToken}/${hash}`;
let imageMarkdown = `![](${url})`;
- let content = i.state.content;
- content = content ? `${content}\n${imageMarkdown}` : imageMarkdown;
- i.state.content = content;
+ i.state.content = Some(
+ i.state.content.match({
+ some: content => `${content}\n${imageMarkdown}`,
+ none: imageMarkdown,
+ })
+ );
i.state.imageLoading = false;
i.contentChange();
i.setState(i.state);
contentChange() {
if (this.props.onContentChange) {
- this.props.onContentChange(this.state.content);
+ this.props.onContentChange(toUndefined(this.state.content));
}
}
handleContentChange(i: MarkdownTextArea, event: any) {
- i.state.content = event.target.value;
+ i.state.content = Some(event.target.value);
i.contentChange();
i.setState(i.state);
}
event.preventDefault();
i.state.loading = true;
i.setState(i.state);
- let msg = { val: i.state.content, formId: i.formId };
+ let msg = { val: toUndefined(i.state.content), formId: i.formId };
i.props.onSubmit(msg);
}
handleInsertLink(i: MarkdownTextArea, event: any) {
event.preventDefault();
- if (!i.state.content) {
- i.state.content = "";
- }
+
let textarea: any = document.getElementById(i.id);
let start: number = textarea.selectionStart;
let end: number = textarea.selectionEnd;
+ if (i.state.content.isNone()) {
+ i.state.content = Some("");
+ }
+
+ let content = i.state.content.unwrap();
+
if (start !== end) {
- let selectedText = i.state.content.substring(start, end);
- i.state.content = `${i.state.content.substring(
- 0,
- start
- )}[${selectedText}]()${i.state.content.substring(end)}`;
+ let selectedText = content.substring(start, end);
+ i.state.content = Some(
+ `${content.substring(0, start)}[${selectedText}]()${content.substring(
+ end
+ )}`
+ );
textarea.focus();
setTimeout(() => (textarea.selectionEnd = end + 3), 10);
} else {
- i.state.content += "[]()";
+ i.state.content = Some(`${content} []()`);
textarea.focus();
setTimeout(() => (textarea.selectionEnd -= 1), 10);
}
}
simpleBeginningofLine(chars: string) {
- this.simpleSurroundBeforeAfter(`${chars} `, "", "");
+ this.simpleSurroundBeforeAfter(`${chars}`, "", "");
}
simpleSurroundBeforeAfter(
afterChars: string,
emptyChars = "___"
) {
- if (!this.state.content) {
- this.state.content = "";
+ if (this.state.content.isNone()) {
+ this.state.content = Some("");
}
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 = this.state.content.substring(start, end);
- this.state.content = `${this.state.content.substring(
- 0,
- start
- )}${beforeChars}${selectedText}${afterChars}${this.state.content.substring(
- end
- )}`;
+ let selectedText = content.substring(start, end);
+ this.state.content = Some(
+ `${content.substring(
+ 0,
+ start
+ )}${beforeChars}${selectedText}${afterChars}${content.substring(end)}`
+ );
} else {
- this.state.content += `${beforeChars}${emptyChars}${afterChars}`;
+ this.state.content = Some(
+ `${content}${beforeChars}${emptyChars}${afterChars}`
+ );
}
this.contentChange();
this.setState(this.state);
}
simpleInsert(chars: string) {
- if (!this.state.content) {
- this.state.content = `${chars} `;
+ if (this.state.content.isNone()) {
+ this.state.content = Some(`${chars} `);
} else {
- this.state.content += `\n${chars} `;
+ this.state.content = Some(`${this.state.content.unwrap()}\n${chars} `);
}
let textarea: any = document.getElementById(this.id);
.split("\n")
.map(t => `> ${t}`)
.join("\n") + "\n\n";
- if (this.state.content == null) {
- this.state.content = "";
+ if (this.state.content.isNone()) {
+ this.state.content = Some("");
} else {
- this.state.content += "\n";
+ this.state.content = Some(`${this.state.content.unwrap()}\n`);
}
- this.state.content += quotedText;
+ this.state.content = Some(`${this.state.content.unwrap()}${quotedText}`);
this.contentChange();
this.setState(this.state);
// Not sure why this needs a delay
let textarea: any = document.getElementById(this.id);
let start: number = textarea.selectionStart;
let end: number = textarea.selectionEnd;
- return start !== end ? this.state.content.substring(start, end) : "";
+ return start !== end
+ ? this.state.content.unwrap().substring(start, end)
+ : "";
}
}
+import { Option } from "@sniptt/monads";
import { Component } from "inferno";
import moment from "moment";
import { i18n } from "../../i18next";
import { Icon } from "./icon";
interface MomentTimeProps {
- data: {
- published?: string;
- when_?: string;
- updated?: string;
- };
+ published: string;
+ updated: Option<string>;
showAgo?: boolean;
ignoreUpdated?: boolean;
}
}
createdAndModifiedTimes() {
- let created = this.props.data.published || this.props.data.when_;
- return `
- <div>
- <div>
- ${capitalizeFirstLetter(i18n.t("created"))}: ${this.format(created)}
- </div>
- <div>
- ${capitalizeFirstLetter(i18n.t("modified"))} ${this.format(
- this.props.data.updated
- )}
- </div>
- </div>`;
+ return `${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())}`;
}
render() {
- if (!this.props.ignoreUpdated && this.props.data.updated) {
+ if (!this.props.ignoreUpdated && this.props.updated.isSome()) {
return (
<span
data-tippy-content={this.createdAndModifiedTimes()}
- data-tippy-allowHtml={true}
className="font-italics pointer unselectable"
>
<Icon icon="edit-2" classes="icon-inline mr-1" />
- {moment.utc(this.props.data.updated).fromNow(!this.props.showAgo)}
+ {moment.utc(this.props.updated.unwrap()).fromNow(!this.props.showAgo)}
</span>
);
} else {
- let created = this.props.data.published || this.props.data.when_;
+ let published = this.props.published;
return (
<span
className="pointer unselectable"
- data-tippy-content={this.format(created)}
+ data-tippy-content={this.format(published)}
>
- {moment.utc(created).fromNow(!this.props.showAgo)}
+ {moment.utc(published).fromNow(!this.props.showAgo)}
</span>
);
}
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { WebSocketService } from "../../services";
-import { authField, mdToHtml, wsClient } from "../../utils";
+import { auth, mdToHtml, wsClient } from "../../utils";
import { PersonListing } from "../person/person-listing";
import { MarkdownTextArea } from "./markdown-textarea";
import { MomentTime } from "./moment-time";
}
interface RegistrationApplicationState {
- denyReason?: string;
+ denyReason: Option<string>;
denyExpanded: boolean;
}
{i18n.t("applicant")}: <PersonListing person={a.creator} />
</div>
<div>
- {i18n.t("created")}: <MomentTime showAgo data={ra} />
+ {i18n.t("created")}:{" "}
+ <MomentTime showAgo published={ra.published} updated={None} />
</div>
<div>{i18n.t("answer")}:</div>
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(ra.answer)} />
- {a.admin && (
- <div>
- {accepted ? (
- <T i18nKey="approved_by">
- #
- <PersonListing person={a.admin} />
- </T>
- ) : (
- <div>
- <T i18nKey="denied_by">
+ {a.admin.match({
+ some: admin => (
+ <div>
+ {accepted ? (
+ <T i18nKey="approved_by">
#
- <PersonListing person={a.admin} />
+ <PersonListing person={admin} />
</T>
+ ) : (
<div>
- {i18n.t("deny_reason")}:{" "}
- <div
- className="md-div d-inline-flex"
- dangerouslySetInnerHTML={mdToHtml(ra.deny_reason || "")}
- />
+ <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>
- )}
- </div>
- )}
+ )}
+ </div>
+ ),
+ none: <></>,
+ })}
{this.state.denyExpanded && (
<div class="form-group row">
<MarkdownTextArea
initialContent={this.state.denyReason}
onContentChange={this.handleDenyReasonChange}
+ placeholder={None}
+ buttonTitle={None}
+ maxLength={None}
hideNavigationWarnings
/>
</div>
handleApprove(i: RegistrationApplication) {
i.setState({ denyExpanded: false });
- let form: ApproveRegistrationApplication = {
+ let form = new ApproveRegistrationApplication({
id: i.props.application.registration_application.id,
- deny_reason: "",
+ deny_reason: None,
approve: true,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(
wsClient.approveRegistrationApplication(form)
);
handleDeny(i: RegistrationApplication) {
if (i.state.denyExpanded) {
i.setState({ denyExpanded: false });
- let form: ApproveRegistrationApplication = {
+ let form = new ApproveRegistrationApplication({
id: i.props.application.registration_application.id,
approve: false,
deny_reason: i.state.denyReason,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(
wsClient.approveRegistrationApplication(form)
);
}
handleDenyReasonChange(val: string) {
- this.state.denyReason = val;
+ this.state.denyReason = Some(val);
this.setState(this.state);
}
}
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
CommunityResponse,
- CommunityView,
FollowCommunity,
+ GetSiteResponse,
ListCommunities,
ListCommunitiesResponse,
ListingType,
- SiteView,
SortType,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { InitialFetchRequest } from "shared/interfaces";
import { i18n } from "../../i18next";
import { WebSocketService } from "../../services";
import {
- authField,
+ auth,
getListingTypeFromPropsNoDefault,
getPageFromProps,
isBrowser,
numToSI,
setIsoData,
- setOptionalAuth,
showLocal,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
const communityLimit = 100;
interface CommunitiesState {
- communities: CommunityView[];
+ listCommunitiesResponse: Option<ListCommunitiesResponse>;
page: number;
loading: boolean;
- site_view: SiteView;
+ siteRes: GetSiteResponse;
searchText: string;
listingType: ListingType;
}
export class Communities extends Component<any, CommunitiesState> {
private subscription: Subscription;
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(this.context, ListCommunitiesResponse);
private emptyState: CommunitiesState = {
- communities: [],
+ listCommunitiesResponse: None,
loading: true,
page: getPageFromProps(this.props),
listingType: getListingTypeFromPropsNoDefault(this.props),
- site_view: this.isoData.site_res.site_view,
+ siteRes: this.isoData.site_res,
searchText: "",
};
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.communities = this.isoData.routeData[0].communities;
+ let listRes = Some(this.isoData.routeData[0] as ListCommunitiesResponse);
+ this.state.listCommunitiesResponse = listRes;
this.state.loading = false;
} else {
this.refetch();
}
get documentTitle(): string {
- return `${i18n.t("communities")} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${i18n.t("communities")} - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
{this.state.loading ? (
<h5>
</tr>
</thead>
<tbody>
- {this.state.communities.map(cv => (
- <tr>
- <td>
- <CommunityLink community={cv.community} />
- </td>
- <td class="text-right">
- {numToSI(cv.counts.subscribers)}
- </td>
- <td class="text-right">
- {numToSI(cv.counts.users_active_month)}
- </td>
- <td class="text-right d-none d-lg-table-cell">
- {numToSI(cv.counts.posts)}
- </td>
- <td class="text-right d-none d-lg-table-cell">
- {numToSI(cv.counts.comments)}
- </td>
- <td class="text-right">
- {cv.subscribed ? (
- <button
- class="btn btn-link d-inline-block"
- onClick={linkEvent(
- cv.community.id,
- this.handleUnsubscribe
- )}
- >
- {i18n.t("unsubscribe")}
- </button>
- ) : (
- <button
- class="btn btn-link d-inline-block"
- onClick={linkEvent(
- cv.community.id,
- this.handleSubscribe
- )}
- >
- {i18n.t("subscribe")}
- </button>
- )}
- </td>
- </tr>
- ))}
+ {this.state.listCommunitiesResponse
+ .map(l => l.communities)
+ .unwrapOr([])
+ .map(cv => (
+ <tr>
+ <td>
+ <CommunityLink community={cv.community} />
+ </td>
+ <td class="text-right">
+ {numToSI(cv.counts.subscribers)}
+ </td>
+ <td class="text-right">
+ {numToSI(cv.counts.users_active_month)}
+ </td>
+ <td class="text-right d-none d-lg-table-cell">
+ {numToSI(cv.counts.posts)}
+ </td>
+ <td class="text-right d-none d-lg-table-cell">
+ {numToSI(cv.counts.comments)}
+ </td>
+ <td class="text-right">
+ {cv.subscribed ? (
+ <button
+ class="btn btn-link d-inline-block"
+ onClick={linkEvent(
+ cv.community.id,
+ this.handleUnsubscribe
+ )}
+ >
+ {i18n.t("unsubscribe")}
+ </button>
+ ) : (
+ <button
+ class="btn btn-link d-inline-block"
+ onClick={linkEvent(
+ cv.community.id,
+ this.handleSubscribe
+ )}
+ >
+ {i18n.t("subscribe")}
+ </button>
+ )}
+ </td>
+ </tr>
+ ))}
</tbody>
</table>
</div>
}
handleUnsubscribe(communityId: number) {
- let form: FollowCommunity = {
+ let form = new FollowCommunity({
community_id: communityId,
follow: false,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.followCommunity(form));
}
handleSubscribe(communityId: number) {
- let form: FollowCommunity = {
+ let form = new FollowCommunity({
community_id: communityId,
follow: true,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.followCommunity(form));
}
}
refetch() {
- let listCommunitiesForm: ListCommunities = {
- type_: this.state.listingType,
- sort: SortType.TopMonth,
- limit: communityLimit,
- page: this.state.page,
- auth: authField(false),
- };
+ 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(),
+ });
WebSocketService.Instance.send(
wsClient.listCommunities(listCommunitiesForm)
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let pathSplit = req.path.split("/");
- let type_: ListingType = pathSplit[3]
- ? ListingType[pathSplit[3]]
- : ListingType.Local;
- let page = pathSplit[5] ? Number(pathSplit[5]) : 1;
- let listCommunitiesForm: ListCommunities = {
+ 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({
type_,
- sort: SortType.TopMonth,
- limit: communityLimit,
+ sort: Some(SortType.TopMonth),
+ limit: Some(communityLimit),
page,
- };
- setOptionalAuth(listCommunitiesForm, req.auth);
+ auth: req.auth,
+ });
return [req.client.listCommunities(listCommunitiesForm)];
}
toast(i18n.t(msg.error), "danger");
return;
} else if (op == UserOperation.ListCommunities) {
- let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
- this.state.communities = data.communities;
+ let data = wsJsonToRes<ListCommunitiesResponse>(
+ msg,
+ ListCommunitiesResponse
+ );
+ this.state.listCommunitiesResponse = Some(data);
this.state.loading = false;
window.scrollTo(0, 0);
this.setState(this.state);
} else if (op == UserOperation.FollowCommunity) {
- let data = wsJsonToRes<CommunityResponse>(msg).data;
- let found = this.state.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;
+ 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);
}
}
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { Prompt } from "inferno-router";
import {
CommunityView,
CreateCommunity,
EditCommunity,
+ toUndefined,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
capitalizeFirstLetter,
randomStr,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { MarkdownTextArea } from "../common/markdown-textarea";
interface CommunityFormProps {
- community_view?: CommunityView; // If a community is given, that means this is an edit
+ community_view: Option<CommunityView>; // If a community is given, that means this is an edit
onCancel?(): any;
onCreate?(community: CommunityView): any;
onEdit?(community: CommunityView): any;
- enableNsfw: boolean;
+ enableNsfw?: boolean;
}
interface CommunityFormState {
private subscription: Subscription;
private emptyState: CommunityFormState = {
- communityForm: {
- name: null,
- title: null,
- nsfw: false,
- icon: null,
- banner: null,
- posting_restricted_to_mods: false,
- auth: authField(false),
- },
+ communityForm: new CreateCommunity({
+ name: undefined,
+ title: undefined,
+ description: None,
+ nsfw: None,
+ icon: None,
+ banner: None,
+ posting_restricted_to_mods: None,
+ auth: undefined,
+ }),
loading: false,
};
this.handleBannerUpload = this.handleBannerUpload.bind(this);
this.handleBannerRemove = this.handleBannerRemove.bind(this);
- let cv = this.props.community_view;
- if (cv) {
- this.state.communityForm = {
- name: cv.community.name,
- title: cv.community.title,
- description: cv.community.description,
- nsfw: cv.community.nsfw,
- icon: cv.community.icon,
- banner: cv.community.banner,
- posting_restricted_to_mods: cv.community.posting_restricted_to_mods,
- auth: authField(),
- };
- }
+ this.props.community_view.match({
+ some: cv => {
+ this.state.communityForm = new CreateCommunity({
+ name: cv.community.name,
+ title: cv.community.title,
+ description: cv.community.description,
+ nsfw: Some(cv.community.nsfw),
+ icon: cv.community.icon,
+ banner: cv.community.banner,
+ posting_restricted_to_mods: Some(
+ cv.community.posting_restricted_to_mods
+ ),
+ auth: auth().unwrap(),
+ });
+ },
+ none: void 0,
+ });
this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage);
}
- // TODO this should be checked out
componentDidUpdate() {
if (
!this.state.loading &&
(this.state.communityForm.name ||
this.state.communityForm.title ||
- this.state.communityForm.description)
+ this.state.communityForm.description.isSome())
) {
window.onbeforeunload = () => true;
} else {
!this.state.loading &&
(this.state.communityForm.name ||
this.state.communityForm.title ||
- this.state.communityForm.description)
+ this.state.communityForm.description.isSome())
}
message={i18n.t("block_leaving")}
/>
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
- {!this.props.community_view && (
+ {this.props.community_view.isNone() && (
<div class="form-group row">
<label
class="col-12 col-sm-2 col-form-label"
<div class="col-12 col-sm-10">
<MarkdownTextArea
initialContent={this.state.communityForm.description}
+ placeholder={Some("description")}
+ buttonTitle={None}
+ maxLength={None}
onContentChange={this.handleCommunityDescriptionChange}
/>
</div>
class="form-check-input position-static"
id="community-nsfw"
type="checkbox"
- checked={this.state.communityForm.nsfw}
+ checked={toUndefined(this.state.communityForm.nsfw)}
onChange={linkEvent(this, this.handleCommunityNsfwChange)}
/>
</div>
class="form-check-input position-static"
id="community-only-mods-can-post"
type="checkbox"
- checked={this.state.communityForm.posting_restricted_to_mods}
+ checked={toUndefined(
+ this.state.communityForm.posting_restricted_to_mods
+ )}
onChange={linkEvent(
this,
this.handleCommunityPostingRestrictedToMods
>
{this.state.loading ? (
<Spinner />
- ) : this.props.community_view ? (
+ ) : this.props.community_view.isSome() ? (
capitalizeFirstLetter(i18n.t("save"))
) : (
capitalizeFirstLetter(i18n.t("create"))
)}
</button>
- {this.props.community_view && (
+ {this.props.community_view.isSome() && (
<button
type="button"
class="btn btn-secondary"
handleCreateCommunitySubmit(i: CommunityForm, event: any) {
event.preventDefault();
i.state.loading = true;
- if (i.props.community_view) {
- let form: EditCommunity = {
- ...i.state.communityForm,
- community_id: i.props.community_view.community.id,
- };
- WebSocketService.Instance.send(wsClient.editCommunity(form));
- } else {
- WebSocketService.Instance.send(
- wsClient.createCommunity(i.state.communityForm)
- );
- }
+ let cForm = i.state.communityForm;
+
+ i.props.community_view.match({
+ some: cv => {
+ let form = new EditCommunity({
+ community_id: cv.community.id,
+ title: Some(cForm.title),
+ description: cForm.description,
+ icon: cForm.icon,
+ banner: cForm.banner,
+ nsfw: cForm.nsfw,
+ posting_restricted_to_mods: cForm.posting_restricted_to_mods,
+ auth: auth().unwrap(),
+ });
+
+ WebSocketService.Instance.send(wsClient.editCommunity(form));
+ },
+ none: () => {
+ WebSocketService.Instance.send(
+ wsClient.createCommunity(i.state.communityForm)
+ );
+ },
+ });
i.setState(i.state);
}
}
handleCommunityDescriptionChange(val: string) {
- this.state.communityForm.description = val;
+ this.state.communityForm.description = Some(val);
this.setState(this.state);
}
}
handleIconUpload(url: string) {
- this.state.communityForm.icon = url;
+ this.state.communityForm.icon = Some(url);
this.setState(this.state);
}
handleIconRemove() {
- this.state.communityForm.icon = "";
+ this.state.communityForm.icon = Some("");
this.setState(this.state);
}
handleBannerUpload(url: string) {
- this.state.communityForm.banner = url;
+ this.state.communityForm.banner = Some(url);
this.setState(this.state);
}
handleBannerRemove() {
- this.state.communityForm.banner = "";
+ this.state.communityForm.banner = Some("");
this.setState(this.state);
}
this.setState(this.state);
return;
} else if (op == UserOperation.CreateCommunity) {
- let data = wsJsonToRes<CommunityResponse>(msg).data;
+ let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
this.state.loading = false;
this.props.onCreate(data.community_view);
// Update myUserInfo
let community = data.community_view.community;
- let person = UserService.Instance.myUserInfo.local_user_view.person;
- UserService.Instance.myUserInfo.follows.push({
- community,
- follower: person,
- });
- UserService.Instance.myUserInfo.moderates.push({
- community,
- moderator: person,
+
+ 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,
});
} else if (op == UserOperation.EditCommunity) {
- let data = wsJsonToRes<CommunityResponse>(msg).data;
+ let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
this.state.loading = false;
this.props.onEdit(data.community_view);
let community = data.community_view.community;
- let followFound = UserService.Instance.myUserInfo.follows.findIndex(
- f => f.community.id == community.id
- );
- if (followFound) {
- UserService.Instance.myUserInfo.follows[followFound].community =
- community;
- }
-
- let moderatesFound = UserService.Instance.myUserInfo.moderates.findIndex(
- f => f.community.id == community.id
- );
- if (moderatesFound) {
- UserService.Instance.myUserInfo.moderates[moderatesFound].community =
- 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 moderatesFound = mui.moderates.findIndex(
+ f => f.community.id == community.id
+ );
+ if (moderatesFound) {
+ mui.moderates[moderatesFound].community = community;
+ }
+ },
+ none: void 0,
+ });
}
}
}
}
avatarAndName(displayName: string) {
- let community = this.props.community;
return (
<>
- {!this.props.hideAvatar && community.icon && showAvatars() && (
- <PictrsImage src={community.icon} icon />
- )}
+ {!this.props.hideAvatar &&
+ showAvatars() &&
+ this.props.community.icon.match({
+ some: icon => <PictrsImage src={icon} icon />,
+ none: <></>,
+ })}
<span class="overflow-wrap-anywhere">{displayName}</span>
</>
);
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
AddModToCommunityResponse,
PostResponse,
PostView,
SortType,
+ toOption,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { DataType, InitialFetchRequest } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
commentsToFlatNodes,
communityRSSUrl,
createCommentLikeRes,
createPostLikeFindRes,
editCommentRes,
editPostFindRes,
+ enableDownvotes,
+ enableNsfw,
fetchLimit,
getDataTypeFromProps,
getPageFromProps,
saveCommentRes,
saveScrollPosition,
setIsoData,
- setOptionalAuth,
setupTippy,
showLocal,
toast,
updatePersonBlock,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes";
import { BannerIconHeader } from "../common/banner-icon-header";
import { CommunityLink } from "./community-link";
interface State {
- communityRes: GetCommunityResponse;
+ communityRes: Option<GetCommunityResponse>;
siteRes: GetSiteResponse;
communityName: string;
communityLoading: boolean;
}
export class Community extends Component<any, State> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(
+ this.context,
+ GetCommunityResponse,
+ GetPostsResponse,
+ GetCommentsResponse
+ );
private subscription: Subscription;
private emptyState: State = {
- communityRes: undefined,
+ communityRes: None,
communityName: this.props.match.params.name,
communityLoading: true,
postsLoading: true,
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.communityRes = this.isoData.routeData[0];
- if (this.state.dataType == DataType.Post) {
- this.state.posts = this.isoData.routeData[1].posts;
- } else {
- this.state.comments = this.isoData.routeData[1].comments;
- }
+ this.state.communityRes = Some(
+ this.isoData.routeData[0] as GetCommunityResponse
+ );
+ let postsRes = Some(this.isoData.routeData[1] as GetPostsResponse);
+ let commentsRes = Some(this.isoData.routeData[2] as GetCommentsResponse);
+
+ postsRes.match({
+ some: pvs => (this.state.posts = pvs.posts),
+ none: void 0,
+ });
+ commentsRes.match({
+ some: cvs => (this.state.comments = cvs.comments),
+ none: void 0,
+ });
+
this.state.communityLoading = false;
this.state.postsLoading = false;
this.state.commentsLoading = false;
}
fetchCommunity() {
- let form: GetCommunity = {
- name: this.state.communityName ? this.state.communityName : null,
- auth: authField(false),
- };
+ let form = new GetCommunity({
+ name: Some(this.state.communityName),
+ id: None,
+ auth: auth(false).ok(),
+ });
WebSocketService.Instance.send(wsClient.getCommunity(form));
}
let promises: Promise<any>[] = [];
let communityName = pathSplit[2];
- let communityForm: GetCommunity = { name: communityName };
- setOptionalAuth(communityForm, req.auth);
+ let communityForm = new GetCommunity({
+ name: Some(communityName),
+ id: None,
+ auth: req.auth,
+ });
promises.push(req.client.getCommunity(communityForm));
let dataType: DataType = pathSplit[4]
? DataType[pathSplit[4]]
: DataType.Post;
- let sort: SortType = pathSplit[6]
- ? SortType[pathSplit[6]]
- : UserService.Instance.myUserInfo
- ? Object.values(SortType)[
- UserService.Instance.myUserInfo.local_user_view.local_user
- .default_sort_type
- ]
- : SortType.Active;
+ 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 page = pathSplit[8] ? Number(pathSplit[8]) : 1;
+ let page = toOption(pathSplit[8] ? Number(pathSplit[8]) : 1);
if (dataType == DataType.Post) {
- let getPostsForm: GetPosts = {
+ let getPostsForm = new GetPosts({
+ community_name: Some(communityName),
+ community_id: None,
page,
- limit: fetchLimit,
+ limit: Some(fetchLimit),
sort,
- type_: ListingType.Community,
- saved_only: false,
- };
- setOptionalAuth(getPostsForm, req.auth);
- this.setName(getPostsForm, communityName);
+ type_: Some(ListingType.Community),
+ saved_only: Some(false),
+ auth: req.auth,
+ });
promises.push(req.client.getPosts(getPostsForm));
+ promises.push(Promise.resolve());
} else {
- let getCommentsForm: GetComments = {
+ let getCommentsForm = new GetComments({
+ community_name: Some(communityName),
+ community_id: None,
page,
- limit: fetchLimit,
+ limit: Some(fetchLimit),
sort,
- type_: ListingType.Community,
- saved_only: false,
- };
- this.setName(getCommentsForm, communityName);
- setOptionalAuth(getCommentsForm, req.auth);
+ type_: Some(ListingType.Community),
+ saved_only: Some(false),
+ auth: req.auth,
+ });
+ promises.push(Promise.resolve());
promises.push(req.client.getComments(getCommentsForm));
}
return promises;
}
- static setName(obj: any, name_: string) {
- obj.community_name = name_;
- }
-
componentDidUpdate(_: any, lastState: State) {
if (
lastState.dataType !== this.state.dataType ||
}
get documentTitle(): string {
- return `${this.state.communityRes.community_view.community.title} - ${this.state.siteRes.site_view.site.name}`;
+ return this.state.communityRes.match({
+ some: res =>
+ this.state.siteRes.site_view.match({
+ some: siteView =>
+ `${res.community_view.community.title} - ${siteView.site.name}`,
+ none: "",
+ }),
+ none: "",
+ });
}
render() {
- let cv = this.state.communityRes?.community_view;
return (
<div class="container">
{this.state.communityLoading ? (
<Spinner large />
</h5>
) : (
- <>
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- description={cv.community.description}
- image={cv.community.icon}
- />
+ 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 class="row">
- <div class="col-12 col-md-8">
- {this.communityInfo()}
- <div class="d-block d-md-none">
- <button
- class="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={cv}
- moderators={this.state.communityRes.moderators}
- admins={this.state.siteRes.admins}
- online={this.state.communityRes.online}
- enableNsfw={
- this.state.siteRes.site_view.site.enable_nsfw
- }
- />
- {!cv.community.local && this.state.communityRes.site && (
- <SiteSidebar
- site={this.state.communityRes.site}
- showLocal={showLocal(this.isoData)}
+ <div class="row">
+ <div class="col-12 col-md-8">
+ {this.communityInfo()}
+ <div class="d-block d-md-none">
+ <button
+ class="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)}
+ />
+ {!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 class="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)}
+ />
+ {!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>
</div>
- {this.selects()}
- {this.listings()}
- <Paginator
- page={this.state.page}
- onChange={this.handlePageChange}
- />
- </div>
- <div class="d-none d-md-block col-md-4">
- <Sidebar
- community_view={cv}
- moderators={this.state.communityRes.moderators}
- admins={this.state.siteRes.admins}
- online={this.state.communityRes.online}
- enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
- />
- {!cv.community.local && this.state.communityRes.site && (
- <SiteSidebar
- site={this.state.communityRes.site}
- showLocal={showLocal(this.isoData)}
- />
- )}
- </div>
- </div>
- </>
+ </>
+ ),
+ none: <></>,
+ })
)}
</div>
);
}
listings() {
- let site = this.state.siteRes.site_view.site;
return this.state.dataType == DataType.Post ? (
this.state.postsLoading ? (
<h5>
<PostListings
posts={this.state.posts}
removeDuplicates
- enableDownvotes={site.enable_downvotes}
- enableNsfw={site.enable_nsfw}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ enableNsfw={enableNsfw(this.state.siteRes)}
/>
)
) : this.state.commentsLoading ? (
nodes={commentsToFlatNodes(this.state.comments)}
noIndent
showContext
- enableDownvotes={site.enable_downvotes}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ moderators={this.state.communityRes.map(r => r.moderators)}
+ admins={Some(this.state.siteRes.admins)}
+ maxCommentsShown={None}
/>
);
}
communityInfo() {
- let community = this.state.communityRes.community_view.community;
- return (
- <div class="mb-2">
- <BannerIconHeader banner={community.banner} icon={community.icon} />
- <h5 class="mb-0 overflow-wrap-anywhere">{community.title}</h5>
- <CommunityLink
- community={community}
- realLink
- useApubName
- muted
- hideAvatar
- />
- </div>
- );
+ return this.state.communityRes
+ .map(r => r.community_view.community)
+ .match({
+ some: community => (
+ <div class="mb-2">
+ <BannerIconHeader banner={community.banner} icon={community.icon} />
+ <h5 class="mb-0 overflow-wrap-anywhere">{community.title}</h5>
+ <CommunityLink
+ community={community}
+ realLink
+ useApubName
+ muted
+ hideAvatar
+ />
+ </div>
+ ),
+ none: <></>,
+ });
}
selects() {
- let communityRss = communityRSSUrl(
- this.state.communityRes.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)
);
return (
<div class="mb-3">
<span class="mr-2">
<SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
</span>
- <a href={communityRss} title="RSS" rel={relTags}>
- <Icon icon="rss" classes="text-muted small" />
- </a>
- <link rel="alternate" type="application/atom+xml" href={communityRss} />
+ {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: <></>,
+ })}
</div>
);
}
fetchData() {
if (this.state.dataType == DataType.Post) {
- let form: GetPosts = {
- page: this.state.page,
- limit: fetchLimit,
- sort: this.state.sort,
- type_: ListingType.Community,
- community_name: this.state.communityName,
- saved_only: false,
- auth: authField(false),
- };
+ let form = new GetPosts({
+ page: Some(this.state.page),
+ limit: Some(fetchLimit),
+ sort: Some(this.state.sort),
+ type_: Some(ListingType.Community),
+ community_name: Some(this.state.communityName),
+ community_id: None,
+ saved_only: Some(false),
+ auth: auth(false).ok(),
+ });
WebSocketService.Instance.send(wsClient.getPosts(form));
} else {
- let form: GetComments = {
- page: this.state.page,
- limit: fetchLimit,
- sort: this.state.sort,
- type_: ListingType.Community,
- community_name: this.state.communityName,
- saved_only: false,
- auth: authField(false),
- };
+ let form = new GetComments({
+ page: Some(this.state.page),
+ limit: Some(fetchLimit),
+ sort: Some(this.state.sort),
+ type_: Some(ListingType.Community),
+ community_name: Some(this.state.communityName),
+ community_id: None,
+ saved_only: Some(false),
+ auth: auth(false).ok(),
+ });
WebSocketService.Instance.send(wsClient.getComments(form));
}
}
this.context.router.history.push("/");
return;
} else if (msg.reconnect) {
- WebSocketService.Instance.send(
- wsClient.communityJoin({
- community_id: this.state.communityRes.community_view.community.id,
- })
- );
+ this.state.communityRes.match({
+ some: res => {
+ WebSocketService.Instance.send(
+ wsClient.communityJoin({
+ community_id: res.community_view.community.id,
+ })
+ );
+ },
+ none: void 0,
+ });
this.fetchData();
} else if (op == UserOperation.GetCommunity) {
- let data = wsJsonToRes<GetCommunityResponse>(msg).data;
- this.state.communityRes = data;
+ let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
+ this.state.communityRes = Some(data);
this.state.communityLoading = false;
this.setState(this.state);
// TODO why is there no auth in this form?
op == UserOperation.DeleteCommunity ||
op == UserOperation.RemoveCommunity
) {
- let data = wsJsonToRes<CommunityResponse>(msg).data;
- this.state.communityRes.community_view = data.community_view;
+ let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
+ this.state.communityRes.match({
+ some: res => (res.community_view = data.community_view),
+ none: void 0,
+ });
this.setState(this.state);
} else if (op == UserOperation.FollowCommunity) {
- let data = wsJsonToRes<CommunityResponse>(msg).data;
- this.state.communityRes.community_view.subscribed =
- data.community_view.subscribed;
- this.state.communityRes.community_view.counts.subscribers =
- data.community_view.counts.subscribers;
+ 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,
+ });
this.setState(this.state);
} else if (op == UserOperation.GetPosts) {
- let data = wsJsonToRes<GetPostsResponse>(msg).data;
+ let data = wsJsonToRes<GetPostsResponse>(msg, GetPostsResponse);
this.state.posts = data.posts;
this.state.postsLoading = false;
this.setState(this.state);
op == UserOperation.StickyPost ||
op == UserOperation.SavePost
) {
- let data = wsJsonToRes<PostResponse>(msg).data;
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
editPostFindRes(data.post_view, this.state.posts);
this.setState(this.state);
} else if (op == UserOperation.CreatePost) {
- let data = wsJsonToRes<PostResponse>(msg).data;
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
this.state.posts.unshift(data.post_view);
if (
- UserService.Instance.myUserInfo?.local_user_view.local_user
- .show_new_post_notifs
+ UserService.Instance.myUserInfo
+ .map(m => m.local_user_view.local_user.show_new_post_notifs)
+ .unwrapOr(false)
) {
notifyPost(data.post_view, this.context.router);
}
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
- let data = wsJsonToRes<PostResponse>(msg).data;
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
createPostLikeFindRes(data.post_view, this.state.posts);
this.setState(this.state);
} else if (op == UserOperation.AddModToCommunity) {
- let data = wsJsonToRes<AddModToCommunityResponse>(msg).data;
- this.state.communityRes.moderators = data.moderators;
+ let data = wsJsonToRes<AddModToCommunityResponse>(
+ msg,
+ AddModToCommunityResponse
+ );
+ this.state.communityRes.match({
+ some: res => (res.moderators = data.moderators),
+ none: void 0,
+ });
this.setState(this.state);
} else if (op == UserOperation.BanFromCommunity) {
- let data = wsJsonToRes<BanFromCommunityResponse>(msg).data;
+ let data = wsJsonToRes<BanFromCommunityResponse>(
+ msg,
+ BanFromCommunityResponse
+ );
// TODO this might be incorrect
this.state.posts
this.setState(this.state);
} else if (op == UserOperation.GetComments) {
- let data = wsJsonToRes<GetCommentsResponse>(msg).data;
+ let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse);
this.state.comments = data.comments;
this.state.commentsLoading = false;
this.setState(this.state);
op == UserOperation.DeleteComment ||
op == UserOperation.RemoveComment
) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
editCommentRes(data.comment_view, this.state.comments);
this.setState(this.state);
} else if (op == UserOperation.CreateComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
// Necessary since it might be a user reply
if (data.form_id) {
this.setState(this.state);
}
} else if (op == UserOperation.SaveComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
saveCommentRes(data.comment_view, this.state.comments);
this.setState(this.state);
} else if (op == UserOperation.CreateCommentLike) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
createCommentLikeRes(data.comment_view, this.state.comments);
this.setState(this.state);
} else if (op == UserOperation.BlockPerson) {
- let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
updatePersonBlock(data);
} else if (op == UserOperation.CreatePostReport) {
- let data = wsJsonToRes<PostReportResponse>(msg).data;
+ let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
if (data) {
toast(i18n.t("report_created"));
}
} else if (op == UserOperation.CreateCommentReport) {
- let data = wsJsonToRes<CommentReportResponse>(msg).data;
+ let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
if (data) {
toast(i18n.t("report_created"));
}
+import { None } from "@sniptt/monads";
import { Component } from "inferno";
-import { CommunityView, SiteView } from "lemmy-js-client";
+import { CommunityView, GetSiteResponse } from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
-import { isBrowser, setIsoData, toast, wsSubscribe } from "../../utils";
+import {
+ enableNsfw,
+ isBrowser,
+ setIsoData,
+ toast,
+ wsSubscribe,
+} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { CommunityForm } from "./community-form";
interface CreateCommunityState {
- site_view: SiteView;
+ siteRes: GetSiteResponse;
loading: boolean;
}
private isoData = setIsoData(this.context);
private subscription: Subscription;
private emptyState: CreateCommunityState = {
- site_view: this.isoData.site_res.site_view,
+ siteRes: this.isoData.site_res,
loading: false,
};
constructor(props: any, context: any) {
this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage);
- if (!UserService.Instance.myUserInfo && isBrowser()) {
+ if (UserService.Instance.myUserInfo.isNone() && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
}
get documentTitle(): string {
- return `${i18n.t("create_community")} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${i18n.t("create_community")} - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
{this.state.loading ? (
<h5>
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t("create_community")}</h5>
<CommunityForm
+ community_view={None}
onCreate={this.handleCommunityCreate}
- enableNsfw={this.state.site_view.site.enable_nsfw}
+ enableNsfw={enableNsfw(this.state.siteRes)}
/>
</div>
</div>
+import { Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
import {
FollowCommunity,
PersonViewSafe,
RemoveCommunity,
+ toUndefined,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ amAdmin,
+ amMod,
+ amTopMod,
+ auth,
getUnixTime,
mdToHtml,
numToSI,
moderators: CommunityModeratorView[];
admins: PersonViewSafe[];
online: number;
- enableNsfw: boolean;
+ enableNsfw?: boolean;
showIcon?: boolean;
}
interface SidebarState {
+ removeReason: Option<string>;
+ removeExpires: Option<string>;
showEdit: boolean;
showRemoveDialog: boolean;
- removeReason: string;
- removeExpires: string;
showConfirmLeaveModTeam: boolean;
}
this.sidebar()
) : (
<CommunityForm
- community_view={this.props.community_view}
+ community_view={Some(this.props.community_view)}
onEdit={this.handleEditCommunity}
onCancel={this.handleEditCancel}
enableNsfw={this.props.enableNsfw}
description() {
let description = this.props.community_view.community.description;
- return (
- description && (
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(description)}
- />
- )
- );
+ return description.match({
+ some: desc => (
+ <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
+ ),
+ none: <></>,
+ });
}
adminButtons() {
return (
<>
<ul class="list-inline mb-1 text-muted font-weight-bold">
- {this.canMod && (
+ {amMod(Some(this.props.moderators)) && (
<>
<li className="list-inline-item-action">
<button
<Icon icon="edit" classes="icon-inline" />
</button>
</li>
- {!this.amTopMod &&
+ {!amTopMod(Some(this.props.moderators)) &&
(!this.state.showConfirmLeaveModTeam ? (
<li className="list-inline-item-action">
<button
</li>
</>
))}
- {this.amTopMod && (
+ {amTopMod(Some(this.props.moderators)) && (
<li className="list-inline-item-action">
<button
class="btn btn-link text-muted d-inline-block"
)}
</>
)}
- {this.canAdmin && (
+ {amAdmin(Some(this.props.admins)) && (
<li className="list-inline-item">
{!this.props.community_view.community.removed ? (
<button
id="remove-reason"
class="form-control mr-2"
placeholder={i18n.t("optional")}
- value={this.state.removeReason}
+ value={toUndefined(this.state.removeReason)}
onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/>
</div>
handleDeleteClick(i: Sidebar, event: any) {
event.preventDefault();
- let deleteForm: DeleteCommunity = {
+ let deleteForm = new DeleteCommunity({
community_id: i.props.community_view.community.id,
deleted: !i.props.community_view.community.deleted,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
}
}
handleLeaveModTeamClick(i: Sidebar) {
- let form: AddModToCommunity = {
- person_id: UserService.Instance.myUserInfo.local_user_view.person.id,
- community_id: i.props.community_view.community.id,
- added: false,
- auth: authField(),
- };
- WebSocketService.Instance.send(wsClient.addModToCommunity(form));
- i.state.showConfirmLeaveModTeam = false;
- i.setState(i.state);
+ 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.state.showConfirmLeaveModTeam = false;
+ i.setState(i.state);
+ },
+ none: void 0,
+ });
}
handleCancelLeaveModTeamClick(i: Sidebar) {
handleUnsubscribe(i: Sidebar, event: any) {
event.preventDefault();
let community_id = i.props.community_view.community.id;
- let form: FollowCommunity = {
+ let form = new FollowCommunity({
community_id,
follow: false,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.followCommunity(form));
// Update myUserInfo
- UserService.Instance.myUserInfo.follows =
- UserService.Instance.myUserInfo.follows.filter(
- i => i.community.id != community_id
- );
+ UserService.Instance.myUserInfo.match({
+ some: mui =>
+ (mui.follows = mui.follows.filter(i => i.community.id != community_id)),
+ none: void 0,
+ });
}
handleSubscribe(i: Sidebar, event: any) {
event.preventDefault();
let community_id = i.props.community_view.community.id;
- let form: FollowCommunity = {
+ let form = new FollowCommunity({
community_id,
follow: true,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.followCommunity(form));
// Update myUserInfo
- UserService.Instance.myUserInfo.follows.push({
- community: i.props.community_view.community,
- follower: UserService.Instance.myUserInfo.local_user_view.person,
+ UserService.Instance.myUserInfo.match({
+ some: mui =>
+ mui.follows.push({
+ community: i.props.community_view.community,
+ follower: mui.local_user_view.person,
+ }),
+ none: void 0,
});
}
- private get amTopMod(): boolean {
- return (
- this.props.moderators[0].moderator.id ==
- UserService.Instance.myUserInfo.local_user_view.person.id
- );
- }
-
- get canMod(): boolean {
- return (
- UserService.Instance.myUserInfo &&
- this.props.moderators
- .map(m => m.moderator.id)
- .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
- );
- }
-
- get canAdmin(): boolean {
- return (
- UserService.Instance.myUserInfo &&
- this.props.admins
- .map(a => a.person.id)
- .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
- );
- }
-
get canPost(): boolean {
return (
!this.props.community_view.community.posting_restricted_to_mods ||
- this.canMod ||
- this.canAdmin
+ amMod(Some(this.props.moderators)) ||
+ amAdmin(Some(this.props.admins))
);
}
handleModRemoveSubmit(i: Sidebar, event: any) {
event.preventDefault();
- let removeForm: RemoveCommunity = {
+ let removeForm = new 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: authField(),
- };
+ expires: i.state.removeExpires.map(getUnixTime),
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
i.state.showRemoveDialog = false;
+import { None, Option, Some } from "@sniptt/monads";
import autosize from "autosize";
import { Component, linkEvent } from "inferno";
import {
PersonViewSafe,
SaveSiteConfig,
SiteResponse,
+ toUndefined,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { WebSocketService } from "../../services";
import {
- authField,
+ auth,
capitalizeFirstLetter,
isBrowser,
randomStr,
showLocal,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
interface AdminSettingsState {
siteRes: GetSiteResponse;
- siteConfigRes: GetSiteConfigResponse;
- siteConfigHjson: string;
- loading: boolean;
+ siteConfigRes: Option<GetSiteConfigResponse>;
+ siteConfigHjson: Option<string>;
banned: PersonViewSafe[];
+ loading: boolean;
siteConfigLoading: boolean;
leaveAdminTeamLoading: boolean;
}
export class AdminSettings extends Component<any, AdminSettingsState> {
private siteConfigTextAreaId = `site-config-${randomStr()}`;
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(
+ this.context,
+ GetSiteConfigResponse,
+ BannedPersonsResponse
+ );
private subscription: Subscription;
private emptyState: AdminSettingsState = {
siteRes: this.isoData.site_res,
- siteConfigHjson: null,
- siteConfigRes: {
- config_hjson: null,
- },
+ siteConfigHjson: None,
+ siteConfigRes: None,
banned: [],
loading: true,
siteConfigLoading: null,
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.siteConfigRes = this.isoData.routeData[0];
- this.state.siteConfigHjson = this.state.siteConfigRes.config_hjson;
- this.state.banned = this.isoData.routeData[1].banned;
+ this.state.siteConfigRes = Some(
+ this.isoData.routeData[0] as GetSiteConfigResponse
+ );
+ this.state.siteConfigHjson = this.state.siteConfigRes.map(
+ s => s.config_hjson
+ );
+ this.state.banned = (
+ this.isoData.routeData[1] as BannedPersonsResponse
+ ).banned;
this.state.siteConfigLoading = false;
this.state.loading = false;
} else {
WebSocketService.Instance.send(
wsClient.getSiteConfig({
- auth: authField(),
+ auth: auth().unwrap(),
})
);
WebSocketService.Instance.send(
wsClient.getBannedPersons({
- auth: authField(),
+ auth: auth().unwrap(),
})
);
}
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let promises: Promise<any>[] = [];
- let siteConfigForm: GetSiteConfig = { auth: req.auth };
+ let siteConfigForm = new GetSiteConfig({ auth: req.auth.unwrap() });
promises.push(req.client.getSiteConfig(siteConfigForm));
- let bannedPersonsForm: GetBannedPersons = { auth: req.auth };
+ let bannedPersonsForm = new GetBannedPersons({ auth: req.auth.unwrap() });
promises.push(req.client.getBannedPersons(bannedPersonsForm));
return promises;
}
get documentTitle(): string {
- return `${i18n.t("admin_settings")} - ${
- this.state.siteRes.site_view.site.name
- }`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${i18n.t("admin_settings")} - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
- {this.state.siteRes.site_view.site.id && (
- <SiteForm
- site={this.state.siteRes.site_view.site}
- showLocal={showLocal(this.isoData)}
- />
- )}
+ {this.state.siteRes.site_view.match({
+ some: siteView => (
+ <SiteForm
+ site={Some(siteView.site)}
+ showLocal={showLocal(this.isoData)}
+ />
+ ),
+ none: <></>,
+ })}
{this.admins()}
{this.bannedUsers()}
</div>
<div class="col-12">
<textarea
id={this.siteConfigTextAreaId}
- value={this.state.siteConfigHjson}
+ value={toUndefined(this.state.siteConfigHjson)}
onInput={linkEvent(this, this.handleSiteConfigHjsonChange)}
class="form-control text-monospace"
rows={3}
handleSiteConfigSubmit(i: AdminSettings, event: any) {
event.preventDefault();
i.state.siteConfigLoading = true;
- let form: SaveSiteConfig = {
- config_hjson: i.state.siteConfigHjson,
- auth: authField(),
- };
+ let form = new SaveSiteConfig({
+ config_hjson: toUndefined(i.state.siteConfigHjson),
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.saveSiteConfig(form));
i.setState(i.state);
}
handleLeaveAdminTeam(i: AdminSettings) {
i.state.leaveAdminTeamLoading = true;
- WebSocketService.Instance.send(wsClient.leaveAdmin({ auth: authField() }));
+ WebSocketService.Instance.send(
+ wsClient.leaveAdmin({ auth: auth().unwrap() })
+ );
i.setState(i.state);
}
this.setState(this.state);
return;
} else if (op == UserOperation.EditSite) {
- let data = wsJsonToRes<SiteResponse>(msg).data;
- this.state.siteRes.site_view = data.site_view;
+ let data = wsJsonToRes<SiteResponse>(msg, SiteResponse);
+ this.state.siteRes.site_view = Some(data.site_view);
this.setState(this.state);
toast(i18n.t("site_saved"));
} else if (op == UserOperation.GetBannedPersons) {
- let data = wsJsonToRes<BannedPersonsResponse>(msg).data;
+ let data = wsJsonToRes<BannedPersonsResponse>(msg, BannedPersonsResponse);
this.state.banned = data.banned;
this.setState(this.state);
} else if (op == UserOperation.GetSiteConfig) {
- let data = wsJsonToRes<GetSiteConfigResponse>(msg).data;
- this.state.siteConfigRes = data;
+ let data = wsJsonToRes<GetSiteConfigResponse>(msg, GetSiteConfigResponse);
+ this.state.siteConfigRes = Some(data);
this.state.loading = false;
- this.state.siteConfigHjson = this.state.siteConfigRes.config_hjson;
+ this.state.siteConfigHjson = this.state.siteConfigRes.map(
+ s => s.config_hjson
+ );
this.setState(this.state);
var textarea: any = document.getElementById(this.siteConfigTextAreaId);
autosize(textarea);
} else if (op == UserOperation.LeaveAdmin) {
- let data = wsJsonToRes<GetSiteResponse>(msg).data;
+ let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse);
this.state.siteRes.site_view = data.site_view;
this.setState(this.state);
this.state.leaveAdminTeamLoading = false;
this.setState(this.state);
this.context.router.history.push("/");
} else if (op == UserOperation.SaveSiteConfig) {
- let data = wsJsonToRes<GetSiteConfigResponse>(msg).data;
- this.state.siteConfigRes = data;
- this.state.siteConfigHjson = this.state.siteConfigRes.config_hjson;
+ let data = wsJsonToRes<GetSiteConfigResponse>(msg, GetSiteConfigResponse);
+ this.state.siteConfigRes = Some(data);
+ this.state.siteConfigHjson = this.state.siteConfigRes.map(
+ s => s.config_hjson
+ );
this.state.siteConfigLoading = false;
toast(i18n.t("site_saved"));
this.setState(this.state);
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
SiteResponse,
SortType,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { DataType, InitialFetchRequest } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
commentsToFlatNodes,
createCommentLikeRes,
createPostLikeFindRes,
editCommentRes,
editPostFindRes,
+ enableDownvotes,
+ enableNsfw,
fetchLimit,
getDataTypeFromProps,
getListingTypeFromProps,
getPageFromProps,
getSortTypeFromProps,
+ isBrowser,
notifyPost,
relTags,
restoreScrollPosition,
saveCommentRes,
saveScrollPosition,
setIsoData,
- setOptionalAuth,
setupTippy,
showLocal,
toast,
+ trendingFetchLimit,
updatePersonBlock,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes";
import { DataTypeSelect } from "../common/data-type-select";
interface HomeState {
trendingCommunities: CommunityView[];
siteRes: GetSiteResponse;
- showSubscribedMobile: boolean;
- showTrendingMobile: boolean;
- showSidebarMobile: boolean;
- subscribedCollapsed: boolean;
- loading: boolean;
posts: PostView[];
comments: CommentView[];
listingType: ListingType;
dataType: DataType;
sort: SortType;
page: number;
+ showSubscribedMobile: boolean;
+ showTrendingMobile: boolean;
+ showSidebarMobile: boolean;
+ subscribedCollapsed: boolean;
+ loading: boolean;
}
interface HomeProps {
}
export class Home extends Component<any, HomeState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(
+ this.context,
+ GetPostsResponse,
+ GetCommentsResponse,
+ ListCommunitiesResponse
+ );
private subscription: Subscription;
private emptyState: HomeState = {
trendingCommunities: [],
listingType: getListingTypeFromProps(
this.props,
ListingType[
- this.isoData.site_res.site_view?.site.default_post_listing_type
+ this.isoData.site_res.site_view.unwrap().site.default_post_listing_type
]
),
dataType: getDataTypeFromProps(this.props),
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- if (this.state.dataType == DataType.Post) {
- this.state.posts = this.isoData.routeData[0].posts;
- } else {
- this.state.comments = this.isoData.routeData[0].comments;
+ 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;
+
+ postsRes.match({
+ some: pvs => (this.state.posts = pvs.posts),
+ none: void 0,
+ });
+ commentsRes.match({
+ some: cvs => (this.state.comments = cvs.comments),
+ none: void 0,
+ });
+ this.state.trendingCommunities = trendingRes.communities;
+
+ if (isBrowser()) {
+ WebSocketService.Instance.send(
+ wsClient.communityJoin({ community_id: 0 })
+ );
}
- this.state.trendingCommunities = this.isoData.routeData[1].communities;
this.state.loading = false;
} else {
this.fetchTrendingCommunities();
}
fetchTrendingCommunities() {
- let listCommunitiesForm: ListCommunities = {
- type_: ListingType.Local,
- sort: SortType.Hot,
- limit: 6,
- auth: authField(false),
- };
+ let listCommunitiesForm = new ListCommunities({
+ type_: Some(ListingType.Local),
+ sort: Some(SortType.Hot),
+ limit: Some(trendingFetchLimit),
+ page: None,
+ auth: auth(false).ok(),
+ });
WebSocketService.Instance.send(
wsClient.listCommunities(listCommunitiesForm)
);
if (!this.state.siteRes.site_view) {
this.context.router.history.push("/setup");
}
-
- WebSocketService.Instance.send(wsClient.communityJoin({ community_id: 0 }));
setupTippy();
}
: DataType.Post;
// TODO figure out auth default_listingType, default_sort_type
- let type_: ListingType = pathSplit[5]
- ? ListingType[pathSplit[5]]
- : UserService.Instance.myUserInfo
- ? Object.values(ListingType)[
- UserService.Instance.myUserInfo.local_user_view.local_user
- .default_listing_type
- ]
- : null;
- let sort: SortType = pathSplit[7]
- ? SortType[pathSplit[7]]
- : UserService.Instance.myUserInfo
- ? Object.values(SortType)[
- UserService.Instance.myUserInfo.local_user_view.local_user
- .default_sort_type
- ]
- : SortType.Active;
-
- let page = pathSplit[9] ? Number(pathSplit[9]) : 1;
+ 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 promises: Promise<any>[] = [];
if (dataType == DataType.Post) {
- let getPostsForm: GetPosts = {
+ let getPostsForm = new GetPosts({
+ community_id: None,
+ community_name: None,
+ type_,
page,
- limit: fetchLimit,
+ limit: Some(fetchLimit),
sort,
- saved_only: false,
- };
- if (type_) {
- getPostsForm.type_ = type_;
- }
+ saved_only: Some(false),
+ auth: req.auth,
+ });
- setOptionalAuth(getPostsForm, req.auth);
promises.push(req.client.getPosts(getPostsForm));
+ promises.push(Promise.resolve());
} else {
- let getCommentsForm: GetComments = {
+ let getCommentsForm = new GetComments({
+ community_id: None,
+ community_name: None,
page,
- limit: fetchLimit,
+ limit: Some(fetchLimit),
sort,
- type_: type_ || ListingType.Local,
- saved_only: false,
- };
- setOptionalAuth(getCommentsForm, req.auth);
+ type_,
+ saved_only: Some(false),
+ auth: req.auth,
+ });
+ promises.push(Promise.resolve());
promises.push(req.client.getComments(getCommentsForm));
}
- let trendingCommunitiesForm: ListCommunities = {
- type_: ListingType.Local,
- sort: SortType.Hot,
- limit: 6,
- };
- setOptionalAuth(trendingCommunitiesForm, req.auth);
+ let trendingCommunitiesForm = new ListCommunities({
+ type_: Some(ListingType.Local),
+ sort: Some(SortType.Hot),
+ limit: Some(trendingFetchLimit),
+ page: None,
+ auth: req.auth,
+ });
promises.push(req.client.listCommunities(trendingCommunitiesForm));
return promises;
}
get documentTitle(): string {
- return `${
- this.state.siteRes.site_view
- ? this.state.siteRes.site_view.site.description
- ? `${this.state.siteRes.site_view.site.name} - ${this.state.siteRes.site_view.site.description}`
- : this.state.siteRes.site_view.site.name
- : "Lemmy"
- }`;
+ return this.state.siteRes.site_view.match({
+ some: siteView =>
+ siteView.site.description.match({
+ some: desc => `${siteView.site.name} - ${desc}`,
+ none: siteView.site.name,
+ }),
+ none: "Lemmy",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
- {this.state.siteRes.site_view?.site && (
+ {this.state.siteRes.site_view.isSome() && (
<div class="row">
<main role="main" class="col-12 col-md-8">
<div class="d-block d-md-none">{this.mobileView()}</div>
);
}
+ get hasFollows(): boolean {
+ return UserService.Instance.myUserInfo.match({
+ some: mui => mui.follows.length > 0,
+ none: false,
+ });
+ }
+
mobileView() {
let siteRes = this.state.siteRes;
return (
<div class="row">
<div class="col-12">
- {UserService.Instance.myUserInfo &&
- UserService.Instance.myUserInfo.follows.length > 0 && (
- <button
- class="btn btn-secondary d-inline-block mb-2 mr-3"
- onClick={linkEvent(this, this.handleShowSubscribedMobile)}
- >
- {i18n.t("subscribed")}{" "}
- <Icon
- icon={
- this.state.showSubscribedMobile
- ? `minus-square`
- : `plus-square`
- }
- classes="icon-inline"
- />
- </button>
- )}
+ {this.hasFollows && (
+ <button
+ class="btn btn-secondary d-inline-block mb-2 mr-3"
+ onClick={linkEvent(this, this.handleShowSubscribedMobile)}
+ >
+ {i18n.t("subscribed")}{" "}
+ <Icon
+ icon={
+ this.state.showSubscribedMobile
+ ? `minus-square`
+ : `plus-square`
+ }
+ classes="icon-inline"
+ />
+ </button>
+ )}
<button
class="btn btn-secondary d-inline-block mb-2 mr-3"
onClick={linkEvent(this, this.handleShowTrendingMobile)}
classes="icon-inline"
/>
</button>
- {this.state.showSidebarMobile && (
- <SiteSidebar
- site={siteRes.site_view.site}
- admins={siteRes.admins}
- counts={siteRes.site_view.counts}
- online={siteRes.online}
- showLocal={showLocal(this.isoData)}
- />
- )}
+ {this.state.showSidebarMobile &&
+ siteRes.site_view.match({
+ some: siteView => (
+ <SiteSidebar
+ site={siteView.site}
+ admins={Some(siteRes.admins)}
+ counts={Some(siteView.counts)}
+ online={Some(siteRes.online)}
+ showLocal={showLocal(this.isoData)}
+ />
+ ),
+ none: <></>,
+ })}
{this.state.showTrendingMobile && (
<div class="col-12 card border-secondary mb-3">
<div class="card-body">{this.trendingCommunities()}</div>
{this.exploreCommunitiesButton()}
</div>
</div>
-
- <SiteSidebar
- site={siteRes.site_view.site}
- admins={siteRes.admins}
- counts={siteRes.site_view.counts}
- online={siteRes.online}
- showLocal={showLocal(this.isoData)}
- />
-
- {UserService.Instance.myUserInfo &&
- UserService.Instance.myUserInfo.follows.length > 0 && (
- <div class="card border-secondary mb-3">
- <div class="card-body">{this.subscribedCommunities()}</div>
- </div>
- )}
+ {siteRes.site_view.match({
+ some: siteView => (
+ <SiteSidebar
+ site={siteView.site}
+ admins={Some(siteRes.admins)}
+ counts={Some(siteView.counts)}
+ online={Some(siteRes.online)}
+ showLocal={showLocal(this.isoData)}
+ />
+ ),
+ none: <></>,
+ })}
+ {this.hasFollows && (
+ <div class="card border-secondary mb-3">
+ <div class="card-body">{this.subscribedCommunities()}</div>
+ </div>
+ )}
</div>
)}
</div>
</h5>
{!this.state.subscribedCollapsed && (
<ul class="list-inline mb-0">
- {UserService.Instance.myUserInfo.follows.map(cfv => (
- <li class="list-inline-item d-inline-block">
- <CommunityLink community={cfv.community} />
- </li>
- ))}
+ {UserService.Instance.myUserInfo
+ .map(m => m.follows)
+ .unwrapOr([])
+ .map(cfv => (
+ <li class="list-inline-item d-inline-block">
+ <CommunityLink community={cfv.community} />
+ </li>
+ ))}
</ul>
)}
</div>
}
listings() {
- let site = this.state.siteRes.site_view.site;
return this.state.dataType == DataType.Post ? (
<PostListings
posts={this.state.posts}
showCommunity
removeDuplicates
- enableDownvotes={site.enable_downvotes}
- enableNsfw={site.enable_nsfw}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ enableNsfw={enableNsfw(this.state.siteRes)}
/>
) : (
<CommentNodes
nodes={commentsToFlatNodes(this.state.comments)}
+ moderators={None}
+ admins={None}
+ maxCommentsShown={None}
noIndent
showCommunity
showContext
- enableDownvotes={site.enable_downvotes}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
/>
);
}
selects() {
let allRss = `/feeds/all.xml?sort=${this.state.sort}`;
let localRss = `/feeds/local.xml?sort=${this.state.sort}`;
- let frontRss = UserService.Instance.myUserInfo
- ? `/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`
- : "";
+ let frontRss = auth(false)
+ .ok()
+ .map(auth => `/feeds/front/${auth}.xml?sort=${this.state.sort}`);
return (
<div className="mb-3">
<link rel="alternate" type="application/atom+xml" href={localRss} />
</>
)}
- {UserService.Instance.myUserInfo &&
- this.state.listingType == ListingType.Subscribed && (
- <>
- <a href={frontRss} title="RSS" rel={relTags}>
- <Icon icon="rss" classes="text-muted small" />
- </a>
- <link
- rel="alternate"
- type="application/atom+xml"
- href={frontRss}
- />
- </>
- )}
+ {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: <></>,
+ })}
</div>
);
}
fetchData() {
if (this.state.dataType == DataType.Post) {
- let getPostsForm: GetPosts = {
- page: this.state.page,
- limit: fetchLimit,
- sort: this.state.sort,
- saved_only: false,
- auth: authField(false),
- };
- if (this.state.listingType) {
- getPostsForm.type_ = this.state.listingType;
- }
+ 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),
+ });
WebSocketService.Instance.send(wsClient.getPosts(getPostsForm));
} else {
- let getCommentsForm: GetComments = {
- page: this.state.page,
- limit: fetchLimit,
- sort: this.state.sort,
- type_: this.state.listingType,
- saved_only: false,
- auth: authField(false),
- };
+ let getCommentsForm = new GetComments({
+ 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),
+ });
WebSocketService.Instance.send(wsClient.getComments(getCommentsForm));
}
}
);
this.fetchData();
} else if (op == UserOperation.ListCommunities) {
- let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
+ let data = wsJsonToRes<ListCommunitiesResponse>(
+ msg,
+ ListCommunitiesResponse
+ );
this.state.trendingCommunities = data.communities;
this.setState(this.state);
} else if (op == UserOperation.EditSite) {
- let data = wsJsonToRes<SiteResponse>(msg).data;
- this.state.siteRes.site_view = data.site_view;
+ let data = wsJsonToRes<SiteResponse>(msg, SiteResponse);
+ this.state.siteRes.site_view = Some(data.site_view);
this.setState(this.state);
toast(i18n.t("site_saved"));
} else if (op == UserOperation.GetPosts) {
- let data = wsJsonToRes<GetPostsResponse>(msg).data;
+ let data = wsJsonToRes<GetPostsResponse>(msg, GetPostsResponse);
this.state.posts = data.posts;
this.state.loading = false;
this.setState(this.state);
+ WebSocketService.Instance.send(
+ wsClient.communityJoin({ community_id: 0 })
+ );
restoreScrollPosition(this.context);
setupTippy();
} else if (op == UserOperation.CreatePost) {
- let data = wsJsonToRes<PostResponse>(msg).data;
-
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
// NSFW check
let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
let nsfwCheck =
!nsfw ||
(nsfw &&
- UserService.Instance.myUserInfo &&
- UserService.Instance.myUserInfo.local_user_view.local_user.show_nsfw);
+ UserService.Instance.myUserInfo
+ .map(m => m.local_user_view.local_user.show_nsfw)
+ .unwrapOr(false));
+
+ let showPostNotifs = UserService.Instance.myUserInfo
+ .map(m => m.local_user_view.local_user.show_new_post_notifs)
+ .unwrapOr(false);
// Only push these if you're on the first page, and you pass the nsfw check
if (this.state.page == 1 && nsfwCheck) {
// If you're on subscribed, only push it if you're subscribed.
if (this.state.listingType == ListingType.Subscribed) {
if (
- UserService.Instance.myUserInfo.follows
+ UserService.Instance.myUserInfo
+ .map(m => m.follows)
+ .unwrapOr([])
.map(c => c.community.id)
.includes(data.post_view.community.id)
) {
this.state.posts.unshift(data.post_view);
- if (
- UserService.Instance.myUserInfo?.local_user_view.local_user
- .show_new_post_notifs
- ) {
+ if (showPostNotifs) {
notifyPost(data.post_view, this.context.router);
}
}
// If you're on the local view, only push it if its local
if (data.post_view.post.local) {
this.state.posts.unshift(data.post_view);
- if (
- UserService.Instance.myUserInfo?.local_user_view.local_user
- .show_new_post_notifs
- ) {
+ if (showPostNotifs) {
notifyPost(data.post_view, this.context.router);
}
}
} else {
this.state.posts.unshift(data.post_view);
- if (
- UserService.Instance.myUserInfo?.local_user_view.local_user
- .show_new_post_notifs
- ) {
+ if (showPostNotifs) {
notifyPost(data.post_view, this.context.router);
}
}
op == UserOperation.StickyPost ||
op == UserOperation.SavePost
) {
- let data = wsJsonToRes<PostResponse>(msg).data;
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
editPostFindRes(data.post_view, this.state.posts);
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
- let data = wsJsonToRes<PostResponse>(msg).data;
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
createPostLikeFindRes(data.post_view, this.state.posts);
this.setState(this.state);
} else if (op == UserOperation.AddAdmin) {
- let data = wsJsonToRes<AddAdminResponse>(msg).data;
+ let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse);
this.state.siteRes.admins = data.admins;
this.setState(this.state);
} else if (op == UserOperation.BanPerson) {
- let data = wsJsonToRes<BanPersonResponse>(msg).data;
+ let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse);
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).data;
+ let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse);
this.state.comments = data.comments;
this.state.loading = false;
this.setState(this.state);
op == UserOperation.DeleteComment ||
op == UserOperation.RemoveComment
) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
editCommentRes(data.comment_view, this.state.comments);
this.setState(this.state);
} else if (op == UserOperation.CreateComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
// 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.follows
+ UserService.Instance.myUserInfo
+ .map(m => m.follows)
+ .unwrapOr([])
.map(c => c.community.id)
.includes(data.comment_view.community.id)
) {
this.setState(this.state);
}
} else if (op == UserOperation.SaveComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
saveCommentRes(data.comment_view, this.state.comments);
this.setState(this.state);
} else if (op == UserOperation.CreateCommentLike) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
createCommentLikeRes(data.comment_view, this.state.comments);
this.setState(this.state);
} else if (op == UserOperation.BlockPerson) {
- let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
updatePersonBlock(data);
} else if (op == UserOperation.CreatePostReport) {
- let data = wsJsonToRes<PostReportResponse>(msg).data;
+ let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
if (data) {
toast(i18n.t("report_created"));
}
} else if (op == UserOperation.CreateCommentReport) {
- let data = wsJsonToRes<CommentReportResponse>(msg).data;
+ let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
if (data) {
toast(i18n.t("report_created"));
}
+import { None } from "@sniptt/monads";
import { Component } from "inferno";
import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
}
get documentTitle(): string {
- return `${i18n.t("instances")} - ${this.state.siteRes.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${i18n.t("instances")} - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
- let federated_instances = this.state.siteRes?.federated_instances;
- return (
- federated_instances && (
+ return this.state.siteRes.federated_instances.match({
+ some: federated_instances => (
<div class="container">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<div class="row">
<div class="col-md-6">
<h5>{i18n.t("linked_instances")}</h5>
{this.itemList(federated_instances.linked)}
</div>
- {federated_instances.allowed?.length > 0 && (
- <div class="col-md-6">
- <h5>{i18n.t("allowed_instances")}</h5>
- {this.itemList(federated_instances.allowed)}
- </div>
- )}
- {federated_instances.blocked?.length > 0 && (
- <div class="col-md-6">
- <h5>{i18n.t("blocked_instances")}</h5>
- {this.itemList(federated_instances.blocked)}
- </div>
- )}
+ {federated_instances.allowed.match({
+ some: allowed =>
+ allowed.length > 0 && (
+ <div class="col-md-6">
+ <h5>{i18n.t("allowed_instances")}</h5>
+ {this.itemList(allowed)}
+ </div>
+ ),
+ none: <></>,
+ })}
+ {federated_instances.blocked.match({
+ some: blocked =>
+ blocked.length > 0 && (
+ <div class="col-md-6">
+ <h5>{i18n.t("blocked_instances")}</h5>
+ {this.itemList(blocked)}
+ </div>
+ ),
+ none: <></>,
+ })}
</div>
</div>
- )
- );
+ ),
+ none: <></>,
+ });
}
itemList(items: string[]) {
+ let noneFound = <div>{i18n.t("none_found")}</div>;
return items.length > 0 ? (
<ul>
{items.map(i => (
))}
</ul>
) : (
- <div>{i18n.t("none_found")}</div>
+ noneFound
);
}
}
+import { None } from "@sniptt/monads";
import { Component } from "inferno";
import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(
- this.state.siteRes.site_view.site.legal_information
- )}
- />
+ {this.state.siteRes.site_view.match({
+ some: siteView =>
+ siteView.site.legal_information.match({
+ some: legal => (
+ <div
+ className="md-div"
+ dangerouslySetInnerHTML={mdToHtml(legal)}
+ />
+ ),
+ none: <></>,
+ }),
+ none: <></>,
+ })}
</div>
);
}
+import { None } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
GetSiteResponse,
Login as LoginForm,
LoginResponse,
PasswordReset,
- SiteView,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
isBrowser,
setIsoData,
toast,
validEmail,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
interface State {
loginForm: LoginForm;
loginLoading: boolean;
- site_view: SiteView;
+ siteRes: GetSiteResponse;
}
export class Login extends Component<any, State> {
private subscription: Subscription;
emptyState: State = {
- loginForm: {
+ loginForm: new LoginForm({
username_or_email: undefined,
password: undefined,
- },
+ }),
loginLoading: false,
- site_view: this.isoData.site_res.site_view,
+ siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
componentDidMount() {
// Navigate to home if already logged in
- if (UserService.Instance.myUserInfo) {
+ if (UserService.Instance.myUserInfo.isSome()) {
this.context.router.history.push("/");
}
}
}
get documentTitle(): string {
- return `${i18n.t("login")} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${i18n.t("login")} - ${siteView.site.name}`,
+ none: "",
+ });
}
get isLemmyMl(): boolean {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</div>
handlePasswordReset(i: Login, event: any) {
event.preventDefault();
- let resetForm: PasswordReset = {
+ let resetForm = new PasswordReset({
email: i.state.loginForm.username_or_email,
- };
+ });
WebSocketService.Instance.send(wsClient.passwordReset(resetForm));
}
return;
} else {
if (op == UserOperation.Login) {
- let data = wsJsonToRes<LoginResponse>(msg).data;
+ let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
this.state = this.emptyState;
this.setState(this.state);
UserService.Instance.login(data);
WebSocketService.Instance.send(
wsClient.userJoin({
- auth: authField(),
+ auth: auth().unwrap(),
})
);
toast(i18n.t("logged_in"));
} else if (op == UserOperation.PasswordReset) {
toast(i18n.t("reset_password_mail_sent"));
} else if (op == UserOperation.GetSite) {
- let data = wsJsonToRes<GetSiteResponse>(msg).data;
- this.state.site_view = data.site_view;
+ let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse);
+ this.state.siteRes = data;
this.setState(this.state);
}
}
+import { None, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet";
-import { LoginResponse, Register, UserOperation } from "lemmy-js-client";
+import {
+ LoginResponse,
+ Register,
+ toUndefined,
+ UserOperation,
+ wsJsonToRes,
+ wsUserOp,
+} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { delay, retryWhen, take } from "rxjs/operators";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
-import { toast, wsClient, wsJsonToRes, wsUserOp } from "../../utils";
+import { toast, wsClient } from "../../utils";
import { Spinner } from "../common/icon";
import { SiteForm } from "./site-form";
private subscription: Subscription;
private emptyState: State = {
- userForm: {
+ userForm: new Register({
username: undefined,
password: undefined,
password_verify: undefined,
show_nsfw: true,
// The first admin signup doesn't need a captcha
- captcha_uuid: "",
- captcha_answer: "",
- },
+ captcha_uuid: None,
+ captcha_answer: None,
+ email: None,
+ honeypot: None,
+ answer: None,
+ }),
doneRegisteringUser: false,
userLoading: false,
};
{!this.state.doneRegisteringUser ? (
this.registerUser()
) : (
- <SiteForm showLocal />
+ <SiteForm site={None} showLocal />
)}
</div>
</div>
id="email"
class="form-control"
placeholder={i18n.t("optional")}
- value={this.state.userForm.email}
+ value={toUndefined(this.state.userForm.email)}
onInput={linkEvent(this, this.handleRegisterEmailChange)}
minLength={3}
/>
}
handleRegisterEmailChange(i: Setup, event: any) {
- i.state.userForm.email = event.target.value;
+ i.state.userForm.email = Some(event.target.value);
i.setState(i.state);
}
this.setState(this.state);
return;
} else if (op == UserOperation.Register) {
- let data = wsJsonToRes<LoginResponse>(msg).data;
+ let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
this.state.userLoading = false;
this.state.doneRegisteringUser = true;
UserService.Instance.login(data);
+import { None, Option, Some } from "@sniptt/monads";
import { Options, passwordStrength } from "check-password-strength";
import { I18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
+ CaptchaResponse,
GetCaptchaResponse,
GetSiteResponse,
LoginResponse,
Register,
SiteView,
+ toUndefined,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
isBrowser,
joinLemmyUrl,
mdToHtml,
toast,
validEmail,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
interface State {
registerForm: Register;
registerLoading: boolean;
- captcha: GetCaptchaResponse;
+ captcha: Option<GetCaptchaResponse>;
captchaPlaying: boolean;
- site_view: SiteView;
+ siteRes: GetSiteResponse;
}
export class Signup extends Component<any, State> {
private audio: HTMLAudioElement;
emptyState: State = {
- registerForm: {
+ registerForm: new Register({
username: undefined,
password: undefined,
password_verify: undefined,
show_nsfw: false,
- captcha_uuid: undefined,
- captcha_answer: undefined,
- honeypot: undefined,
- answer: undefined,
- },
+ captcha_uuid: None,
+ captcha_answer: None,
+ honeypot: None,
+ answer: None,
+ email: None,
+ }),
registerLoading: false,
- captcha: undefined,
+ captcha: None,
captchaPlaying: false,
- site_view: this.isoData.site_res.site_view,
+ siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
}
get documentTitle(): string {
- return `${this.titleName} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${this.titleName(siteView)} - ${siteView.site.name}`,
+ none: "",
+ });
}
- get titleName(): string {
- return `${i18n.t(
- this.state.site_view.site.private_instance ? "apply_to_join" : "sign_up"
- )}`;
+ titleName(siteView: SiteView): string {
+ return i18n.t(siteView.site.private_instance ? "apply_to_join" : "sign_up");
}
get isLemmyMl(): boolean {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3">{this.registerForm()}</div>
}
registerForm() {
- return (
- <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
- <h5>{this.titleName}</h5>
+ return this.state.siteRes.site_view.match({
+ some: siteView => (
+ <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
+ <h5>{this.titleName(siteView)}</h5>
- {this.isLemmyMl && (
- <div class="form-group row">
- <div class="mt-2 mb-0 alert alert-warning" role="alert">
- <T i18nKey="lemmy_ml_registration_message">
- #<a href={joinLemmyUrl}>#</a>
- </T>
+ {this.isLemmyMl && (
+ <div class="form-group row">
+ <div class="mt-2 mb-0 alert alert-warning" role="alert">
+ <T i18nKey="lemmy_ml_registration_message">
+ #<a href={joinLemmyUrl}>#</a>
+ </T>
+ </div>
</div>
- </div>
- )}
-
- <div class="form-group row">
- <label class="col-sm-2 col-form-label" htmlFor="register-username">
- {i18n.t("username")}
- </label>
-
- <div class="col-sm-10">
- <input
- type="text"
- id="register-username"
- class="form-control"
- value={this.state.registerForm.username}
- onInput={linkEvent(this, this.handleRegisterUsernameChange)}
- required
- minLength={3}
- pattern="[a-zA-Z0-9_]+"
- title={i18n.t("community_reqs")}
- />
- </div>
- </div>
+ )}
- <div class="form-group row">
- <label class="col-sm-2 col-form-label" htmlFor="register-email">
- {i18n.t("email")}
- </label>
- <div class="col-sm-10">
- <input
- type="email"
- id="register-email"
- class="form-control"
- placeholder={
- this.state.site_view.site.require_email_verification
- ? i18n.t("required")
- : i18n.t("optional")
- }
- value={this.state.registerForm.email}
- autoComplete="email"
- onInput={linkEvent(this, this.handleRegisterEmailChange)}
- required={this.state.site_view.site.require_email_verification}
- minLength={3}
- />
- {!this.state.site_view.site.require_email_verification &&
- !validEmail(this.state.registerForm.email) && (
- <div class="mt-2 mb-0 alert alert-warning" role="alert">
- <Icon icon="alert-triangle" classes="icon-inline mr-2" />
- {i18n.t("no_password_reset")}
- </div>
- )}
- </div>
- </div>
+ <div class="form-group row">
+ <label class="col-sm-2 col-form-label" htmlFor="register-username">
+ {i18n.t("username")}
+ </label>
- <div class="form-group row">
- <label class="col-sm-2 col-form-label" htmlFor="register-password">
- {i18n.t("password")}
- </label>
- <div class="col-sm-10">
- <input
- type="password"
- id="register-password"
- value={this.state.registerForm.password}
- autoComplete="new-password"
- onInput={linkEvent(this, this.handleRegisterPasswordChange)}
- minLength={10}
- maxLength={60}
- class="form-control"
- required
- />
- {this.state.registerForm.password && (
- <div class={this.passwordColorClass}>
- {i18n.t(this.passwordStrength as I18nKeys)}
- </div>
- )}
+ <div class="col-sm-10">
+ <input
+ type="text"
+ id="register-username"
+ class="form-control"
+ value={this.state.registerForm.username}
+ onInput={linkEvent(this, this.handleRegisterUsernameChange)}
+ required
+ minLength={3}
+ pattern="[a-zA-Z0-9_]+"
+ title={i18n.t("community_reqs")}
+ />
+ </div>
</div>
- </div>
- <div class="form-group row">
- <label
- class="col-sm-2 col-form-label"
- htmlFor="register-verify-password"
- >
- {i18n.t("verify_password")}
- </label>
- <div class="col-sm-10">
- <input
- type="password"
- id="register-verify-password"
- value={this.state.registerForm.password_verify}
- autoComplete="new-password"
- onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
- maxLength={60}
- class="form-control"
- required
- />
+ <div class="form-group row">
+ <label class="col-sm-2 col-form-label" htmlFor="register-email">
+ {i18n.t("email")}
+ </label>
+ <div class="col-sm-10">
+ <input
+ type="email"
+ id="register-email"
+ class="form-control"
+ placeholder={
+ siteView.site.require_email_verification
+ ? i18n.t("required")
+ : i18n.t("optional")
+ }
+ value={toUndefined(this.state.registerForm.email)}
+ autoComplete="email"
+ onInput={linkEvent(this, this.handleRegisterEmailChange)}
+ required={siteView.site.require_email_verification}
+ minLength={3}
+ />
+ {!siteView.site.require_email_verification &&
+ !this.state.registerForm.email
+ .map(validEmail)
+ .unwrapOr(true) && (
+ <div class="mt-2 mb-0 alert alert-warning" role="alert">
+ <Icon icon="alert-triangle" classes="icon-inline mr-2" />
+ {i18n.t("no_password_reset")}
+ </div>
+ )}
+ </div>
</div>
- </div>
- {this.state.site_view.site.require_application && (
- <>
- <div class="form-group row">
- <div class="offset-sm-2 col-sm-10">
- <div class="mt-2 alert alert-warning" role="alert">
- <Icon icon="alert-triangle" classes="icon-inline mr-2" />
- {i18n.t("fill_out_application")}
+ <div class="form-group row">
+ <label class="col-sm-2 col-form-label" htmlFor="register-password">
+ {i18n.t("password")}
+ </label>
+ <div class="col-sm-10">
+ <input
+ type="password"
+ id="register-password"
+ value={this.state.registerForm.password}
+ autoComplete="new-password"
+ onInput={linkEvent(this, this.handleRegisterPasswordChange)}
+ minLength={10}
+ maxLength={60}
+ class="form-control"
+ required
+ />
+ {this.state.registerForm.password && (
+ <div class={this.passwordColorClass}>
+ {i18n.t(this.passwordStrength as I18nKeys)}
</div>
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(
- this.state.site_view.site.application_question || ""
- )}
- />
- </div>
- </div>
-
- <div class="form-group row">
- <label
- class="col-sm-2 col-form-label"
- htmlFor="application_answer"
- >
- {i18n.t("answer")}
- </label>
- <div class="col-sm-10">
- <MarkdownTextArea
- onContentChange={this.handleAnswerChange}
- hideNavigationWarnings
- />
- </div>
+ )}
</div>
- </>
- )}
+ </div>
- {this.state.captcha && (
<div class="form-group row">
- <label class="col-sm-2" htmlFor="register-captcha">
- <span class="mr-2">{i18n.t("enter_code")}</span>
- <button
- type="button"
- class="btn btn-secondary"
- onClick={linkEvent(this, this.handleRegenCaptcha)}
- aria-label={i18n.t("captcha")}
- >
- <Icon icon="refresh-cw" classes="icon-refresh-cw" />
- </button>
+ <label
+ class="col-sm-2 col-form-label"
+ htmlFor="register-verify-password"
+ >
+ {i18n.t("verify_password")}
</label>
- {this.showCaptcha()}
- <div class="col-sm-6">
+ <div class="col-sm-10">
<input
- type="text"
- class="form-control"
- id="register-captcha"
- value={this.state.registerForm.captcha_answer}
+ type="password"
+ id="register-verify-password"
+ value={this.state.registerForm.password_verify}
+ autoComplete="new-password"
onInput={linkEvent(
this,
- this.handleRegisterCaptchaAnswerChange
+ this.handleRegisterPasswordVerifyChange
)}
+ maxLength={60}
+ class="form-control"
required
/>
</div>
</div>
- )}
- {this.state.site_view.site.enable_nsfw && (
- <div class="form-group row">
- <div class="col-sm-10">
- <div class="form-check">
+
+ {siteView.site.require_application && (
+ <>
+ <div class="form-group row">
+ <div class="offset-sm-2 col-sm-10">
+ <div class="mt-2 alert alert-warning" role="alert">
+ <Icon icon="alert-triangle" classes="icon-inline mr-2" />
+ {i18n.t("fill_out_application")}
+ </div>
+ {siteView.site.application_question.match({
+ some: question => (
+ <div
+ className="md-div"
+ dangerouslySetInnerHTML={mdToHtml(question)}
+ />
+ ),
+ none: <></>,
+ })}
+ </div>
+ </div>
+
+ <div class="form-group row">
+ <label
+ class="col-sm-2 col-form-label"
+ htmlFor="application_answer"
+ >
+ {i18n.t("answer")}
+ </label>
+ <div class="col-sm-10">
+ <MarkdownTextArea
+ initialContent={None}
+ placeholder={None}
+ buttonTitle={None}
+ maxLength={None}
+ onContentChange={this.handleAnswerChange}
+ hideNavigationWarnings
+ />
+ </div>
+ </div>
+ </>
+ )}
+
+ {this.state.captcha.isSome() && (
+ <div class="form-group row">
+ <label class="col-sm-2" htmlFor="register-captcha">
+ <span class="mr-2">{i18n.t("enter_code")}</span>
+ <button
+ type="button"
+ class="btn btn-secondary"
+ onClick={linkEvent(this, this.handleRegenCaptcha)}
+ aria-label={i18n.t("captcha")}
+ >
+ <Icon icon="refresh-cw" classes="icon-refresh-cw" />
+ </button>
+ </label>
+ {this.showCaptcha()}
+ <div class="col-sm-6">
<input
- class="form-check-input"
- id="register-show-nsfw"
- type="checkbox"
- checked={this.state.registerForm.show_nsfw}
- onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
+ type="text"
+ class="form-control"
+ id="register-captcha"
+ value={toUndefined(this.state.registerForm.captcha_answer)}
+ onInput={linkEvent(
+ this,
+ this.handleRegisterCaptchaAnswerChange
+ )}
+ required
/>
- <label class="form-check-label" htmlFor="register-show-nsfw">
- {i18n.t("show_nsfw")}
- </label>
</div>
</div>
+ )}
+ {siteView.site.enable_nsfw && (
+ <div class="form-group row">
+ <div class="col-sm-10">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="register-show-nsfw"
+ type="checkbox"
+ checked={this.state.registerForm.show_nsfw}
+ onChange={linkEvent(
+ this,
+ this.handleRegisterShowNsfwChange
+ )}
+ />
+ <label class="form-check-label" htmlFor="register-show-nsfw">
+ {i18n.t("show_nsfw")}
+ </label>
+ </div>
+ </div>
+ </div>
+ )}
+ <input
+ tabIndex={-1}
+ autoComplete="false"
+ name="a_password"
+ type="text"
+ class="form-control honeypot"
+ id="register-honey"
+ value={toUndefined(this.state.registerForm.honeypot)}
+ onInput={linkEvent(this, this.handleHoneyPotChange)}
+ />
+ <div class="form-group row">
+ <div class="col-sm-10">
+ <button type="submit" class="btn btn-secondary">
+ {this.state.registerLoading ? (
+ <Spinner />
+ ) : (
+ this.titleName(siteView)
+ )}
+ </button>
+ </div>
</div>
- )}
- <input
- tabIndex={-1}
- autoComplete="false"
- name="a_password"
- type="text"
- class="form-control honeypot"
- id="register-honey"
- value={this.state.registerForm.honeypot}
- onInput={linkEvent(this, this.handleHoneyPotChange)}
- />
- <div class="form-group row">
- <div class="col-sm-10">
- <button type="submit" class="btn btn-secondary">
- {this.state.registerLoading ? <Spinner /> : this.titleName}
- </button>
- </div>
- </div>
- </form>
- );
+ </form>
+ ),
+ none: <></>,
+ });
}
showCaptcha() {
- return (
- <div class="col-sm-4">
- {this.state.captcha.ok && (
- <>
- <img
- class="rounded-top img-fluid"
- src={this.captchaPngSrc()}
- style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
- alt={i18n.t("captcha")}
- />
- {this.state.captcha.ok.wav && (
- <button
- class="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>
- );
+ return this.state.captcha.match({
+ some: captcha => (
+ <div class="col-sm-4">
+ {captcha.ok.match({
+ some: res => (
+ <>
+ <img
+ class="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
+ class="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: <></>,
+ });
}
get passwordStrength() {
}
handleRegisterEmailChange(i: Signup, event: any) {
- i.state.registerForm.email = event.target.value;
- if (i.state.registerForm.email == "") {
- i.state.registerForm.email = undefined;
+ i.state.registerForm.email = Some(event.target.value);
+ if (i.state.registerForm.email.unwrap() == "") {
+ i.state.registerForm.email = None;
}
i.setState(i.state);
}
}
handleRegisterCaptchaAnswerChange(i: Signup, event: any) {
- i.state.registerForm.captcha_answer = event.target.value;
+ i.state.registerForm.captcha_answer = Some(event.target.value);
i.setState(i.state);
}
handleAnswerChange(val: string) {
- this.state.registerForm.answer = val;
+ this.state.registerForm.answer = Some(val);
this.setState(this.state);
}
handleHoneyPotChange(i: Signup, event: any) {
- i.state.registerForm.honeypot = event.target.value;
+ i.state.registerForm.honeypot = Some(event.target.value);
i.setState(i.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.
- if (i.audio == null) {
- let base64 = `data:audio/wav;base64,${i.state.captcha.ok.wav}`;
- i.audio = new Audio(base64);
- }
-
- i.audio.play();
-
- i.state.captchaPlaying = true;
- i.setState(i.state);
-
- i.audio.addEventListener("ended", () => {
- i.audio.currentTime = 0;
- i.state.captchaPlaying = false;
- i.setState(i.state);
+ 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.state.captchaPlaying = true;
+ i.setState(i.state);
+
+ i.audio.addEventListener("ended", () => {
+ i.audio.currentTime = 0;
+ i.state.captchaPlaying = false;
+ i.setState(i.state);
+ });
+ },
+ none: void 0,
+ }),
+ none: void 0,
});
}
- captchaPngSrc() {
- return `data:image/png;base64,${this.state.captcha.ok.png}`;
+ captchaPngSrc(captcha: CaptchaResponse) {
+ return `data:image/png;base64,${captcha.png}`;
}
parseMessage(msg: any) {
return;
} else {
if (op == UserOperation.Register) {
- let data = wsJsonToRes<LoginResponse>(msg).data;
+ let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
this.state = this.emptyState;
this.setState(this.state);
// Only log them in if a jwt was set
UserService.Instance.login(data);
WebSocketService.Instance.send(
wsClient.userJoin({
- auth: authField(),
+ auth: auth().unwrap(),
})
);
this.props.history.push("/communities");
this.props.history.push("/");
}
} else if (op == UserOperation.GetCaptcha) {
- let data = wsJsonToRes<GetCaptchaResponse>(msg).data;
- if (data.ok) {
- this.state.captcha = data;
- this.state.registerForm.captcha_uuid = data.ok.uuid;
- this.setState(this.state);
- }
+ let data = wsJsonToRes<GetCaptchaResponse>(msg, GetCaptchaResponse);
+ data.ok.match({
+ some: res => {
+ this.state.captcha = Some(data);
+ this.state.registerForm.captcha_uuid = Some(res.uuid);
+ this.setState(this.state);
+ },
+ none: void 0,
+ });
} else if (op == UserOperation.PasswordReset) {
toast(i18n.t("reset_password_mail_sent"));
} else if (op == UserOperation.GetSite) {
- let data = wsJsonToRes<GetSiteResponse>(msg).data;
- this.state.site_view = data.site_view;
+ let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse);
+ this.state.siteRes = data;
this.setState(this.state);
}
}
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { Prompt } from "inferno-router";
-import { CreateSite, EditSite, ListingType, Site } from "lemmy-js-client";
+import {
+ CreateSite,
+ EditSite,
+ ListingType,
+ Site,
+ toUndefined,
+} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { WebSocketService } from "../../services";
import {
- authField,
+ auth,
capitalizeFirstLetter,
fetchThemeList,
wsClient,
import { MarkdownTextArea } from "../common/markdown-textarea";
interface SiteFormProps {
- site?: Site; // If a site is given, that means this is an edit
- showLocal: boolean;
- onCancel?(): any;
- onEdit?(): any;
+ site: Option<Site>; // If a site is given, that means this is an edit
+ showLocal?: boolean;
+ onCancel?(): void;
+ onEdit?(): void;
}
interface SiteFormState {
siteForm: EditSite;
loading: boolean;
- themeList: string[];
+ themeList: Option<string[]>;
}
export class SiteForm extends Component<SiteFormProps, SiteFormState> {
private emptyState: SiteFormState = {
- siteForm: {
- enable_downvotes: true,
- open_registration: true,
- enable_nsfw: true,
- name: null,
- icon: null,
- banner: null,
- require_email_verification: null,
- require_application: null,
- application_question: null,
- private_instance: null,
- default_theme: null,
- default_post_listing_type: null,
- legal_information: null,
- auth: authField(false),
- },
+ siteForm: new EditSite({
+ enable_downvotes: Some(true),
+ open_registration: Some(true),
+ enable_nsfw: Some(true),
+ 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,
+ auth: undefined,
+ }),
loading: false,
- themeList: [],
+ themeList: None,
};
constructor(props: any, context: any) {
this.handleDefaultPostListingTypeChange =
this.handleDefaultPostListingTypeChange.bind(this);
- if (this.props.site) {
- let site = this.props.site;
- this.state.siteForm = {
- name: site.name,
- sidebar: site.sidebar,
- description: site.description,
- enable_downvotes: site.enable_downvotes,
- open_registration: site.open_registration,
- enable_nsfw: site.enable_nsfw,
- community_creation_admin_only: site.community_creation_admin_only,
- icon: site.icon,
- banner: site.banner,
- require_email_verification: site.require_email_verification,
- require_application: site.require_application,
- application_question: site.application_question,
- private_instance: site.private_instance,
- default_theme: site.default_theme,
- default_post_listing_type: site.default_post_listing_type,
- legal_information: site.legal_information,
- auth: authField(false),
- };
- }
+ this.props.site.match({
+ some: site => {
+ this.state.siteForm = new EditSite({
+ name: Some(site.name),
+ sidebar: site.sidebar,
+ description: site.description,
+ enable_downvotes: Some(site.enable_downvotes),
+ open_registration: Some(site.open_registration),
+ enable_nsfw: Some(site.enable_nsfw),
+ community_creation_admin_only: Some(
+ site.community_creation_admin_only
+ ),
+ icon: site.icon,
+ banner: site.banner,
+ require_email_verification: Some(site.require_email_verification),
+ require_application: Some(site.require_application),
+ application_question: site.application_question,
+ private_instance: Some(site.private_instance),
+ default_theme: Some(site.default_theme),
+ default_post_listing_type: Some(site.default_post_listing_type),
+ legal_information: site.legal_information,
+ auth: auth(false).unwrap(),
+ });
+ },
+ none: void 0,
+ });
}
async componentDidMount() {
- this.state.themeList = await fetchThemeList();
+ this.state.themeList = Some(await fetchThemeList());
this.setState(this.state);
}
componentDidUpdate() {
if (
!this.state.loading &&
- !this.props.site &&
+ this.props.site.isNone() &&
(this.state.siteForm.name ||
this.state.siteForm.sidebar ||
this.state.siteForm.application_question ||
<Prompt
when={
!this.state.loading &&
- !this.props.site &&
+ this.props.site.isNone() &&
(this.state.siteForm.name ||
this.state.siteForm.sidebar ||
this.state.siteForm.application_question ||
/>
<form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
<h5>{`${
- this.props.site
+ this.props.site.isSome()
? capitalizeFirstLetter(i18n.t("save"))
: capitalizeFirstLetter(i18n.t("name"))
} ${i18n.t("your_site")}`}</h5>
type="text"
id="create-site-name"
class="form-control"
- value={this.state.siteForm.name}
+ value={toUndefined(this.state.siteForm.name)}
onInput={linkEvent(this, this.handleSiteNameChange)}
required
minLength={3}
type="text"
class="form-control"
id="site-desc"
- value={this.state.siteForm.description}
+ value={toUndefined(this.state.siteForm.description)}
onInput={linkEvent(this, this.handleSiteDescChange)}
maxLength={150}
/>
<div class="col-12">
<MarkdownTextArea
initialContent={this.state.siteForm.sidebar}
+ placeholder={None}
+ buttonTitle={None}
+ maxLength={None}
onContentChange={this.handleSiteSidebarChange}
hideNavigationWarnings
/>
<div class="col-12">
<MarkdownTextArea
initialContent={this.state.siteForm.legal_information}
+ placeholder={None}
+ buttonTitle={None}
+ maxLength={None}
onContentChange={this.handleSiteLegalInfoChange}
hideNavigationWarnings
/>
</div>
</div>
- {this.state.siteForm.require_application && (
+ {this.state.siteForm.require_application.unwrapOr(false) && (
<div class="form-group row">
<label class="col-12 col-form-label">
{i18n.t("application_questionnaire")}
<div class="col-12">
<MarkdownTextArea
initialContent={this.state.siteForm.application_question}
+ placeholder={None}
+ buttonTitle={None}
+ maxLength={None}
onContentChange={this.handleSiteApplicationQuestionChange}
hideNavigationWarnings
/>
class="form-check-input"
id="create-site-downvotes"
type="checkbox"
- checked={this.state.siteForm.enable_downvotes}
+ checked={toUndefined(this.state.siteForm.enable_downvotes)}
onChange={linkEvent(
this,
this.handleSiteEnableDownvotesChange
class="form-check-input"
id="create-site-enable-nsfw"
type="checkbox"
- checked={this.state.siteForm.enable_nsfw}
+ checked={toUndefined(this.state.siteForm.enable_nsfw)}
onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
/>
<label
class="form-check-input"
id="create-site-open-registration"
type="checkbox"
- checked={this.state.siteForm.open_registration}
+ checked={toUndefined(this.state.siteForm.open_registration)}
onChange={linkEvent(
this,
this.handleSiteOpenRegistrationChange
class="form-check-input"
id="create-site-community-creation-admin-only"
type="checkbox"
- checked={this.state.siteForm.community_creation_admin_only}
+ checked={toUndefined(
+ this.state.siteForm.community_creation_admin_only
+ )}
onChange={linkEvent(
this,
this.handleSiteCommunityCreationAdminOnly
class="form-check-input"
id="create-site-require-email-verification"
type="checkbox"
- checked={this.state.siteForm.require_email_verification}
+ checked={toUndefined(
+ this.state.siteForm.require_email_verification
+ )}
onChange={linkEvent(
this,
this.handleSiteRequireEmailVerification
class="form-check-input"
id="create-site-require-application"
type="checkbox"
- checked={this.state.siteForm.require_application}
+ checked={toUndefined(this.state.siteForm.require_application)}
onChange={linkEvent(this, this.handleSiteRequireApplication)}
/>
<label
</label>
<select
id="create-site-default-theme"
- value={this.state.siteForm.default_theme}
+ value={toUndefined(this.state.siteForm.default_theme)}
onChange={linkEvent(this, this.handleSiteDefaultTheme)}
class="custom-select w-auto"
>
<option value="browser">{i18n.t("browser_default")}</option>
- {this.state.themeList.map(theme => (
+ {this.state.themeList.unwrapOr([]).map(theme => (
<option value={theme}>{theme}</option>
))}
</select>
<div class="col-sm-9">
<ListingTypeSelect
type_={
- ListingType[this.state.siteForm.default_post_listing_type]
+ ListingType[
+ this.state.siteForm.default_post_listing_type.unwrapOr(
+ "Local"
+ )
+ ]
}
showLocal
showSubscribed={false}
class="form-check-input"
id="create-site-private-instance"
type="checkbox"
- value={this.state.siteForm.default_theme}
+ value={toUndefined(this.state.siteForm.default_theme)}
onChange={linkEvent(this, this.handleSitePrivateInstance)}
/>
<label
>
{this.state.loading ? (
<Spinner />
- ) : this.props.site ? (
+ ) : this.props.site.isSome() ? (
capitalizeFirstLetter(i18n.t("save"))
) : (
capitalizeFirstLetter(i18n.t("create"))
)}
</button>
- {this.props.site && (
+ {this.props.site.isSome() && (
<button
type="button"
class="btn btn-secondary"
handleCreateSiteSubmit(i: SiteForm, event: any) {
event.preventDefault();
i.state.loading = true;
- if (i.props.site) {
+ i.state.siteForm.auth = auth().unwrap();
+
+ if (i.props.site.isSome()) {
WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
i.props.onEdit();
} else {
- let form: CreateSite = {
- name: i.state.siteForm.name || "My site",
- ...i.state.siteForm,
- };
+ let sForm = i.state.siteForm;
+ let form = new CreateSite({
+ name: sForm.name.unwrapOr("My site"),
+ sidebar: sForm.sidebar,
+ description: sForm.description,
+ icon: sForm.icon,
+ banner: sForm.banner,
+ community_creation_admin_only: sForm.community_creation_admin_only,
+ enable_nsfw: sForm.enable_nsfw,
+ enable_downvotes: sForm.enable_downvotes,
+ require_application: sForm.require_application,
+ application_question: sForm.application_question,
+ open_registration: sForm.open_registration,
+ 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,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.createSite(form));
}
i.setState(i.state);
}
handleSiteNameChange(i: SiteForm, event: any) {
- i.state.siteForm.name = event.target.value;
+ i.state.siteForm.name = Some(event.target.value);
i.setState(i.state);
}
handleSiteSidebarChange(val: string) {
- this.state.siteForm.sidebar = val;
+ this.state.siteForm.sidebar = Some(val);
this.setState(this.state);
}
handleSiteLegalInfoChange(val: string) {
- this.state.siteForm.legal_information = val;
+ this.state.siteForm.legal_information = Some(val);
this.setState(this.state);
}
handleSiteApplicationQuestionChange(val: string) {
- this.state.siteForm.application_question = val;
+ this.state.siteForm.application_question = Some(val);
this.setState(this.state);
}
handleSiteDescChange(i: SiteForm, event: any) {
- i.state.siteForm.description = event.target.value;
+ i.state.siteForm.description = Some(event.target.value);
i.setState(i.state);
}
handleSiteEnableNsfwChange(i: SiteForm, event: any) {
- i.state.siteForm.enable_nsfw = event.target.checked;
+ i.state.siteForm.enable_nsfw = Some(event.target.checked);
i.setState(i.state);
}
handleSiteOpenRegistrationChange(i: SiteForm, event: any) {
- i.state.siteForm.open_registration = event.target.checked;
+ i.state.siteForm.open_registration = Some(event.target.checked);
i.setState(i.state);
}
handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
- i.state.siteForm.community_creation_admin_only = event.target.checked;
+ i.state.siteForm.community_creation_admin_only = Some(event.target.checked);
i.setState(i.state);
}
handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
- i.state.siteForm.enable_downvotes = event.target.checked;
+ i.state.siteForm.enable_downvotes = Some(event.target.checked);
i.setState(i.state);
}
handleSiteRequireApplication(i: SiteForm, event: any) {
- i.state.siteForm.require_application = event.target.checked;
+ i.state.siteForm.require_application = Some(event.target.checked);
i.setState(i.state);
}
handleSiteRequireEmailVerification(i: SiteForm, event: any) {
- i.state.siteForm.require_email_verification = event.target.checked;
+ i.state.siteForm.require_email_verification = Some(event.target.checked);
i.setState(i.state);
}
handleSitePrivateInstance(i: SiteForm, event: any) {
- i.state.siteForm.private_instance = event.target.checked;
+ i.state.siteForm.private_instance = Some(event.target.checked);
i.setState(i.state);
}
handleSiteDefaultTheme(i: SiteForm, event: any) {
- i.state.siteForm.default_theme = event.target.value;
+ i.state.siteForm.default_theme = Some(event.target.value);
i.setState(i.state);
}
}
handleIconUpload(url: string) {
- this.state.siteForm.icon = url;
+ this.state.siteForm.icon = Some(url);
this.setState(this.state);
}
handleIconRemove() {
- this.state.siteForm.icon = "";
+ this.state.siteForm.icon = Some("");
this.setState(this.state);
}
handleBannerUpload(url: string) {
- this.state.siteForm.banner = url;
+ this.state.siteForm.banner = Some(url);
this.setState(this.state);
}
handleBannerRemove() {
- this.state.siteForm.banner = "";
+ this.state.siteForm.banner = Some("");
this.setState(this.state);
}
handleDefaultPostListingTypeChange(val: ListingType) {
- this.state.siteForm.default_post_listing_type =
- ListingType[ListingType[val]];
+ this.state.siteForm.default_post_listing_type = Some(
+ ListingType[ListingType[val]]
+ );
this.setState(this.state);
}
}
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
import { PersonViewSafe, Site, SiteAggregates } from "lemmy-js-client";
import { i18n } from "../../i18next";
-import { UserService } from "../../services";
-import { mdToHtml, numToSI } from "../../utils";
+import { amAdmin, mdToHtml, numToSI } from "../../utils";
import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon } from "../common/icon";
import { PersonListing } from "../person/person-listing";
interface SiteSidebarProps {
site: Site;
showLocal: boolean;
- counts?: SiteAggregates;
- admins?: PersonViewSafe[];
- online?: number;
+ counts: Option<SiteAggregates>;
+ admins: Option<PersonViewSafe[]>;
+ online: Option<number>;
}
interface SiteSidebarState {
<div>
<div class="mb-2">
{this.siteName()}
- {this.props.admins && this.adminButtons()}
+ {this.props.admins.isSome() && this.adminButtons()}
</div>
{!this.state.collapsed && (
<>
- <BannerIconHeader banner={site.banner} />
+ <BannerIconHeader banner={site.banner} icon={None} />
{this.siteInfo()}
</>
)}
</div>
) : (
<SiteForm
- site={site}
+ site={Some(site)}
showLocal={this.props.showLocal}
onEdit={this.handleEditSite}
onCancel={this.handleEditCancel}
siteName() {
let site = this.props.site;
return (
- site.name && (
- <h5 class="mb-0 d-inline">
- {site.name}
- <button
- class="btn btn-sm text-muted"
- onClick={linkEvent(this, this.handleCollapseSidebar)}
- aria-label={i18n.t("collapse")}
- data-tippy-content={i18n.t("collapse")}
- >
- {this.state.collapsed ? (
- <Icon icon="plus-square" classes="icon-inline" />
- ) : (
- <Icon icon="minus-square" classes="icon-inline" />
- )}
- </button>
- </h5>
- )
+ <h5 class="mb-0 d-inline">
+ {site.name}
+ <button
+ class="btn btn-sm text-muted"
+ onClick={linkEvent(this, this.handleCollapseSidebar)}
+ aria-label={i18n.t("collapse")}
+ data-tippy-content={i18n.t("collapse")}
+ >
+ {this.state.collapsed ? (
+ <Icon icon="plus-square" classes="icon-inline" />
+ ) : (
+ <Icon icon="minus-square" classes="icon-inline" />
+ )}
+ </button>
+ </h5>
);
}
let site = this.props.site;
return (
<div>
- {site.description && <h6>{site.description}</h6>}
- {site.sidebar && this.siteSidebar()}
- {this.props.counts && this.badges()}
- {this.props.admins && this.admins()}
+ {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: <></>,
+ })}
</div>
);
}
adminButtons() {
return (
- this.canAdmin && (
+ amAdmin(this.props.admins) && (
<ul class="list-inline mb-1 text-muted font-weight-bold">
<li className="list-inline-item-action">
<button
);
}
- siteSidebar() {
+ siteSidebar(sidebar: string) {
return (
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(this.props.site.sidebar)}
- />
+ <div className="md-div" dangerouslySetInnerHTML={mdToHtml(sidebar)} />
);
}
- admins() {
+ admins(admins: PersonViewSafe[]) {
return (
<ul class="mt-1 list-inline small mb-0">
<li class="list-inline-item">{i18n.t("admins")}:</li>
- {this.props.admins?.map(av => (
+ {admins.map(av => (
<li class="list-inline-item">
<PersonListing person={av.person} />
</li>
);
}
- badges() {
- let counts = this.props.counts;
- let online = this.props.online;
+ badges(siteAggregates: SiteAggregates) {
+ let counts = siteAggregates;
+ let online = this.props.online.unwrapOr(1);
return (
<ul class="my-2 list-inline">
<li className="list-inline-item badge badge-secondary">
);
}
- get canAdmin(): boolean {
- return (
- UserService.Instance.myUserInfo &&
- this.props.admins
- .map(a => a.person.id)
- .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
- );
- }
-
handleCollapseSidebar(i: SiteSidebar) {
i.state.collapsed = !i.state.collapsed;
i.setState(i.state);
+import { None, Option, Some } from "@sniptt/monads";
import { Component } from "inferno";
import { Link } from "inferno-router";
import {
GetCommunityResponse,
GetModlog,
GetModlogResponse,
+ GetSiteResponse,
ModAddCommunityView,
ModAddView,
ModBanFromCommunityView,
ModStickyPostView,
ModTransferCommunityView,
PersonSafe,
- SiteView,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import moment from "moment";
import { Subscription } from "rxjs";
import { i18n } from "../i18next";
import { InitialFetchRequest } from "../interfaces";
-import { UserService, WebSocketService } from "../services";
+import { WebSocketService } from "../services";
import {
- authField,
+ amAdmin,
+ amMod,
+ auth,
fetchLimit,
isBrowser,
setIsoData,
- setOptionalAuth,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../utils";
import { HtmlTags } from "./common/html-tags";
import { Spinner } from "./common/icon";
};
interface ModlogState {
- res: GetModlogResponse;
- communityId?: number;
- communityName?: string;
- communityMods?: CommunityModeratorView[];
+ res: Option<GetModlogResponse>;
+ communityId: Option<number>;
+ communityMods: Option<CommunityModeratorView[]>;
page: number;
- site_view: SiteView;
+ siteRes: GetSiteResponse;
loading: boolean;
}
export class Modlog extends Component<any, ModlogState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(
+ this.context,
+ GetModlogResponse,
+ GetCommunityResponse
+ );
private subscription: Subscription;
private emptyState: ModlogState = {
- res: {
- removed_posts: [],
- locked_posts: [],
- stickied_posts: [],
- removed_comments: [],
- removed_communities: [],
- banned_from_community: [],
- banned: [],
- added_to_community: [],
- transferred_to_community: [],
- added: [],
- },
+ res: None,
+ communityId: None,
+ communityMods: None,
page: 1,
loading: true,
- site_view: this.isoData.site_res.site_view,
+ siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
this.handlePageChange = this.handlePageChange.bind(this);
this.state.communityId = this.props.match.params.community_id
- ? Number(this.props.match.params.community_id)
- : undefined;
+ ? Some(Number(this.props.match.params.community_id))
+ : None;
this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage);
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- let data = this.isoData.routeData[0];
- this.state.res = data;
- this.state.loading = false;
+ this.state.res = Some(this.isoData.routeData[0] as GetModlogResponse);
// Getting the moderators
- if (this.isoData.routeData[1]) {
- this.state.communityMods = this.isoData.routeData[1].moderators;
- }
+ let communityRes = Some(
+ this.isoData.routeData[1] as GetCommunityResponse
+ );
+ this.state.communityMods = communityRes.map(c => c.moderators);
+
+ this.state.loading = false;
} else {
this.refetch();
}
combined.push(...added);
combined.push(...banned);
- if (this.state.communityId && combined.length > 0) {
- this.state.communityName = (
- combined[0].view as ModRemovePostView
- ).community.name;
- }
-
// Sort them by time
combined.sort((a, b) => b.when_.localeCompare(a.when_));
<span>
Community <CommunityLink community={mrco.community} />
</span>,
- mrco.mod_remove_community.reason &&
- ` reason: ${mrco.mod_remove_community.reason}`,
- mrco.mod_remove_community.expires &&
+ mrco.mod_remove_community.reason.isSome() &&
+ ` reason: ${mrco.mod_remove_community.reason.unwrap()}`,
+ mrco.mod_remove_community.expires.isSome() &&
` expires: ${moment
- .utc(mrco.mod_remove_community.expires)
+ .utc(mrco.mod_remove_community.expires.unwrap())
.fromNow()}`,
];
}
<CommunityLink community={mbfc.community} />
</span>,
<div>
- {mbfc.mod_ban_from_community.reason &&
- ` reason: ${mbfc.mod_ban_from_community.reason}`}
+ {mbfc.mod_ban_from_community.reason.isSome() &&
+ ` reason: ${mbfc.mod_ban_from_community.reason.unwrap()}`}
</div>,
<div>
- {mbfc.mod_ban_from_community.expires &&
+ {mbfc.mod_ban_from_community.expires.isSome() &&
` expires: ${moment
- .utc(mbfc.mod_ban_from_community.expires)
+ .utc(mbfc.mod_ban_from_community.expires.unwrap())
.fromNow()}`}
</div>,
];
<span>
<PersonListing person={mb.banned_person} />
</span>,
- <div>{mb.mod_ban.reason && ` reason: ${mb.mod_ban.reason}`}</div>,
<div>
- {mb.mod_ban.expires &&
- ` expires: ${moment.utc(mb.mod_ban.expires).fromNow()}`}
+ {mb.mod_ban.reason.isSome() &&
+ ` reason: ${mb.mod_ban.reason.unwrap()}`}
+ </div>,
+ <div>
+ {mb.mod_ban.expires.isSome() &&
+ ` expires: ${moment.utc(mb.mod_ban.expires.unwrap()).fromNow()}`}
</div>,
];
}
case ModlogEnum.ModAdd: {
let ma = i.view as ModAddView;
return [
- <span>{ma.mod_add.removed ? "Removed " : "Appointed "} </span>,
+ <span>
+ {ma.mod_add.removed.isSome() && ma.mod_add.removed.unwrap()
+ ? "Removed "
+ : "Appointed "}{" "}
+ </span>,
<span>
<PersonListing person={ma.modded_person} />
</span>,
}
combined() {
- let combined = this.buildCombined(this.state.res);
+ let combined = this.state.res.map(this.buildCombined).unwrapOr([]);
return (
<tbody>
{combined.map(i => (
<tr>
<td>
- <MomentTime data={i} />
+ <MomentTime published={i.when_} updated={None} />
</td>
<td>
- {this.isAdminOrMod ? (
+ {this.amAdminOrMod ? (
<PersonListing person={i.view.moderator} />
) : (
<div>{this.modOrAdminText(i.view.moderator)}</div>
);
}
- get isAdminOrMod(): boolean {
- let isAdmin =
- UserService.Instance.myUserInfo &&
- this.isoData.site_res.admins
- .map(a => a.person.id)
- .includes(UserService.Instance.myUserInfo.local_user_view.person.id);
- let isMod =
- UserService.Instance.myUserInfo &&
- this.state.communityMods &&
- this.state.communityMods
- .map(m => m.moderator.id)
- .includes(UserService.Instance.myUserInfo.local_user_view.person.id);
- return isAdmin || isMod;
+ get amAdminOrMod(): boolean {
+ return (
+ amAdmin(Some(this.state.siteRes.admins)) ||
+ amMod(this.state.communityMods)
+ );
}
modOrAdminText(person: PersonSafe): Text {
}
get documentTitle(): string {
- return `Modlog - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `Modlog - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
{this.state.loading ? (
<h5>
</h5>
) : (
<div>
- <h5>
- {this.state.communityName && (
- <Link
- className="text-body"
- to={`/c/${this.state.communityName}`}
- >
- /c/{this.state.communityName}{" "}
- </Link>
- )}
- <span>{i18n.t("modlog")}</span>
- </h5>
<div class="table-responsive">
<table id="modlog_table" class="table table-sm table-hover">
<thead class="pointer">
}
refetch() {
- let modlogForm: GetModlog = {
+ let modlogForm = new GetModlog({
community_id: this.state.communityId,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(false),
- };
+ mod_person_id: None,
+ page: Some(this.state.page),
+ limit: Some(fetchLimit),
+ auth: auth(false).ok(),
+ });
WebSocketService.Instance.send(wsClient.getModlog(modlogForm));
- if (this.state.communityId) {
- let communityForm: GetCommunity = {
- id: this.state.communityId,
- name: this.state.communityName,
- };
- WebSocketService.Instance.send(wsClient.getCommunity(communityForm));
- }
+ 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,
+ });
}
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let pathSplit = req.path.split("/");
- let communityId = pathSplit[3];
+ let communityId = Some(pathSplit[3]).map(Number);
let promises: Promise<any>[] = [];
- let modlogForm: GetModlog = {
- page: 1,
- limit: fetchLimit,
- };
-
- if (communityId) {
- modlogForm.community_id = Number(communityId);
- }
- setOptionalAuth(modlogForm, req.auth);
+ let modlogForm = new GetModlog({
+ page: Some(1),
+ limit: Some(fetchLimit),
+ community_id: communityId,
+ mod_person_id: None,
+ auth: req.auth,
+ });
promises.push(req.client.getModlog(modlogForm));
- if (communityId) {
- let communityForm: GetCommunity = {
- id: Number(communityId),
- };
- setOptionalAuth(communityForm, req.auth);
+ if (communityId.isSome()) {
+ let communityForm = new GetCommunity({
+ id: communityId,
+ name: None,
+ auth: req.auth,
+ });
promises.push(req.client.getCommunity(communityForm));
+ } else {
+ promises.push(Promise.resolve());
}
return promises;
}
toast(i18n.t(msg.error), "danger");
return;
} else if (op == UserOperation.GetModlog) {
- let data = wsJsonToRes<GetModlogResponse>(msg).data;
+ let data = wsJsonToRes<GetModlogResponse>(msg, GetModlogResponse);
this.state.loading = false;
window.scrollTo(0, 0);
- this.state.res = data;
+ this.state.res = Some(data);
this.setState(this.state);
} else if (op == UserOperation.GetCommunity) {
- let data = wsJsonToRes<GetCommunityResponse>(msg).data;
- this.state.communityMods = data.moderators;
+ let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
+ this.state.communityMods = Some(data.moderators);
}
}
}
+import { None, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
BlockPersonResponse,
GetPrivateMessages,
GetReplies,
GetRepliesResponse,
+ GetSiteResponse,
PersonMentionResponse,
PersonMentionView,
PostReportResponse,
PrivateMessageResponse,
PrivateMessagesResponse,
PrivateMessageView,
- SiteView,
SortType,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
commentsToFlatNodes,
createCommentLikeRes,
editCommentRes,
+ enableDownvotes,
fetchLimit,
isBrowser,
relTags,
toast,
updatePersonBlock,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes";
import { HtmlTags } from "../common/html-tags";
combined: ReplyType[];
sort: SortType;
page: number;
- site_view: SiteView;
+ siteRes: GetSiteResponse;
loading: boolean;
}
export class Inbox extends Component<any, InboxState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(
+ this.context,
+ GetRepliesResponse,
+ GetPersonMentionsResponse,
+ PrivateMessagesResponse
+ );
private subscription: Subscription;
private emptyState: InboxState = {
unreadOrAll: UnreadOrAll.Unread,
combined: [],
sort: SortType.New,
page: 1,
- site_view: this.isoData.site_res.site_view,
+ siteRes: this.isoData.site_res,
loading: true,
};
this.handleSortChange = this.handleSortChange.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
- if (!UserService.Instance.myUserInfo && isBrowser()) {
+ if (UserService.Instance.myUserInfo.isNone() && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.replies = this.isoData.routeData[0].replies || [];
- this.state.mentions = this.isoData.routeData[1].mentions || [];
- this.state.messages = this.isoData.routeData[2].messages || [];
+ this.state.replies =
+ (this.isoData.routeData[0] as GetRepliesResponse).replies || [];
+ this.state.mentions =
+ (this.isoData.routeData[1] as GetPersonMentionsResponse).mentions || [];
+ this.state.messages =
+ (this.isoData.routeData[2] as PrivateMessagesResponse)
+ .private_messages || [];
this.state.combined = this.buildCombined();
this.state.loading = false;
} else {
}
get documentTitle(): string {
- return `@${
- UserService.Instance.myUserInfo.local_user_view.person.name
- } ${i18n.t("inbox")} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView =>
+ UserService.Instance.myUserInfo.match({
+ some: mui =>
+ `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${
+ siteView.site.name
+ }`,
+ none: "",
+ }),
+ none: "",
+ });
}
render() {
- let inboxRss = `/feeds/inbox/${UserService.Instance.auth}.xml`;
+ let inboxRss = auth()
+ .ok()
+ .map(a => `/feeds/inbox/${a}.xml`);
return (
<div class="container">
{this.state.loading ? (
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<h5 class="mb-2">
{i18n.t("inbox")}
- <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>
+ {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: <></>,
+ })}
</h5>
{this.state.replies.length +
this.state.mentions.length +
<CommentNodes
key={i.id}
nodes={[{ comment_view: i.view as CommentView }]}
+ moderators={None}
+ admins={None}
+ maxCommentsShown={None}
noIndent
markable
showCommunity
showContext
- enableDownvotes={this.state.site_view.site.enable_downvotes}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
/>
);
case ReplyEnum.Mention:
<CommentNodes
key={i.id}
nodes={[{ comment_view: i.view as PersonMentionView }]}
+ moderators={None}
+ admins={None}
+ maxCommentsShown={None}
noIndent
markable
showCommunity
showContext
- enableDownvotes={this.state.site_view.site.enable_downvotes}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
/>
);
case ReplyEnum.Message:
<div>
<CommentNodes
nodes={commentsToFlatNodes(this.state.replies)}
+ moderators={None}
+ admins={None}
+ maxCommentsShown={None}
noIndent
markable
showCommunity
showContext
- enableDownvotes={this.state.site_view.site.enable_downvotes}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
/>
</div>
);
<CommentNodes
key={umv.person_mention.id}
nodes={[{ comment_view: umv }]}
+ moderators={None}
+ admins={None}
+ maxCommentsShown={None}
noIndent
markable
showCommunity
showContext
- enableDownvotes={this.state.site_view.site.enable_downvotes}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
/>
))}
</div>
let promises: Promise<any>[] = [];
// It can be /u/me, or /username/1
- let repliesForm: GetReplies = {
- sort: SortType.New,
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- auth: req.auth,
- };
+ let repliesForm = new GetReplies({
+ sort: Some(SortType.New),
+ unread_only: Some(true),
+ page: Some(1),
+ limit: Some(fetchLimit),
+ auth: req.auth.unwrap(),
+ });
promises.push(req.client.getReplies(repliesForm));
- let personMentionsForm: GetPersonMentions = {
- sort: SortType.New,
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- auth: req.auth,
- };
+ let personMentionsForm = new GetPersonMentions({
+ sort: Some(SortType.New),
+ unread_only: Some(true),
+ page: Some(1),
+ limit: Some(fetchLimit),
+ auth: req.auth.unwrap(),
+ });
promises.push(req.client.getPersonMentions(personMentionsForm));
- let privateMessagesForm: GetPrivateMessages = {
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- auth: req.auth,
- };
+ let privateMessagesForm = new GetPrivateMessages({
+ unread_only: Some(true),
+ page: Some(1),
+ limit: Some(fetchLimit),
+ auth: req.auth.unwrap(),
+ });
promises.push(req.client.getPrivateMessages(privateMessagesForm));
return promises;
}
refetch() {
- let repliesForm: GetReplies = {
- sort: this.state.sort,
- unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(),
- };
+ 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: GetPersonMentions = {
- sort: this.state.sort,
- unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(),
- };
+ let personMentionsForm = new GetPersonMentions({
+ sort,
+ unread_only,
+ page,
+ limit,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(
wsClient.getPersonMentions(personMentionsForm)
);
- let privateMessagesForm: GetPrivateMessages = {
- unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(),
- };
+ let privateMessagesForm = new GetPrivateMessages({
+ unread_only,
+ page,
+ limit,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(
wsClient.getPrivateMessages(privateMessagesForm)
);
markAllAsRead(i: Inbox) {
WebSocketService.Instance.send(
wsClient.markAllAsRead({
- auth: authField(),
+ auth: auth().unwrap(),
})
);
i.state.replies = [];
} else if (msg.reconnect) {
this.refetch();
} else if (op == UserOperation.GetReplies) {
- let data = wsJsonToRes<GetRepliesResponse>(msg).data;
+ let data = wsJsonToRes<GetRepliesResponse>(msg, GetRepliesResponse);
this.state.replies = data.replies;
this.state.combined = this.buildCombined();
this.state.loading = false;
this.setState(this.state);
setupTippy();
} else if (op == UserOperation.GetPersonMentions) {
- let data = wsJsonToRes<GetPersonMentionsResponse>(msg).data;
+ let data = wsJsonToRes<GetPersonMentionsResponse>(
+ msg,
+ GetPersonMentionsResponse
+ );
this.state.mentions = data.mentions;
this.state.combined = this.buildCombined();
window.scrollTo(0, 0);
this.setState(this.state);
setupTippy();
} else if (op == UserOperation.GetPrivateMessages) {
- let data = wsJsonToRes<PrivateMessagesResponse>(msg).data;
+ let data = wsJsonToRes<PrivateMessagesResponse>(
+ msg,
+ PrivateMessagesResponse
+ );
this.state.messages = data.private_messages;
this.state.combined = this.buildCombined();
window.scrollTo(0, 0);
this.setState(this.state);
setupTippy();
} else if (op == UserOperation.EditPrivateMessage) {
- let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
+ let data = wsJsonToRes<PrivateMessageResponse>(
+ msg,
+ PrivateMessageResponse
+ );
let found: PrivateMessageView = this.state.messages.find(
m =>
m.private_message.id === data.private_message_view.private_message.id
}
this.setState(this.state);
} else if (op == UserOperation.DeletePrivateMessage) {
- let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
+ let data = wsJsonToRes<PrivateMessageResponse>(
+ msg,
+ PrivateMessageResponse
+ );
let found: PrivateMessageView = this.state.messages.find(
m =>
m.private_message.id === data.private_message_view.private_message.id
}
this.setState(this.state);
} else if (op == UserOperation.MarkPrivateMessageAsRead) {
- let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
+ let data = wsJsonToRes<PrivateMessageResponse>(
+ msg,
+ PrivateMessageResponse
+ );
let found: PrivateMessageView = this.state.messages.find(
m =>
m.private_message.id === data.private_message_view.private_message.id
op == UserOperation.DeleteComment ||
op == UserOperation.RemoveComment
) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
editCommentRes(data.comment_view, this.state.replies);
this.setState(this.state);
} else if (op == UserOperation.MarkCommentAsRead) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
// If youre in the unread view, just remove it from the list
if (
this.setState(this.state);
setupTippy();
} else if (op == UserOperation.MarkPersonMentionAsRead) {
- let data = wsJsonToRes<PersonMentionResponse>(msg).data;
+ let data = wsJsonToRes<PersonMentionResponse>(msg, PersonMentionResponse);
// TODO this might not be correct, it might need to use the comment id
let found = this.state.mentions.find(
this.sendUnreadCount(data.person_mention_view.person_mention.read);
this.setState(this.state);
} else if (op == UserOperation.CreateComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
-
- if (
- data.recipient_ids.includes(
- UserService.Instance.myUserInfo.local_user_view.local_user.id
- )
- ) {
- this.state.replies.unshift(data.comment_view);
- this.state.combined.unshift(this.replyToReplyType(data.comment_view));
- this.setState(this.state);
- } else if (
- data.comment_view.creator.id ==
- UserService.Instance.myUserInfo.local_user_view.person.id
- ) {
- // If youre in the unread view, just remove it from the list
- if (this.state.unreadOrAll == UnreadOrAll.Unread) {
- this.state.replies = this.state.replies.filter(
- r => r.comment.id !== data.comment_view.comment.parent_id
- );
- this.state.mentions = this.state.mentions.filter(
- m => m.comment.id !== data.comment_view.comment.parent_id
- );
- this.state.combined = this.state.combined.filter(r => {
- if (this.isMention(r.view))
- return r.view.comment.id !== data.comment_view.comment.parent_id;
- else return r.id !== data.comment_view.comment.parent_id;
- });
- } else {
- let mention_found = this.state.mentions.find(
- i => i.comment.id == data.comment_view.comment.parent_id
- );
- if (mention_found) {
- mention_found.person_mention.read = true;
- }
- let reply_found = this.state.replies.find(
- i => i.comment.id == data.comment_view.comment.parent_id
- );
- if (reply_found) {
- reply_found.comment.read = true;
+ 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.state.replies.unshift(data.comment_view);
+ this.state.combined.unshift(
+ this.replyToReplyType(data.comment_view)
+ );
+ this.setState(this.state);
+ } else if (
+ data.comment_view.creator.id == mui.local_user_view.person.id
+ ) {
+ // If youre in the unread view, just remove it from the list
+ if (this.state.unreadOrAll == UnreadOrAll.Unread) {
+ this.state.replies = this.state.replies.filter(
+ r =>
+ r.comment.id !==
+ data.comment_view.comment.parent_id.unwrapOr(0)
+ );
+ this.state.mentions = this.state.mentions.filter(
+ m =>
+ m.comment.id !==
+ data.comment_view.comment.parent_id.unwrapOr(0)
+ );
+ this.state.combined = this.state.combined.filter(r => {
+ if (this.isMention(r.view))
+ return (
+ r.view.comment.id !==
+ data.comment_view.comment.parent_id.unwrapOr(0)
+ );
+ else
+ return (
+ r.id !== data.comment_view.comment.parent_id.unwrapOr(0)
+ );
+ });
+ } else {
+ let mention_found = this.state.mentions.find(
+ i =>
+ i.comment.id ==
+ data.comment_view.comment.parent_id.unwrapOr(0)
+ );
+ if (mention_found) {
+ mention_found.person_mention.read = true;
+ }
+ let reply_found = this.state.replies.find(
+ i =>
+ i.comment.id ==
+ data.comment_view.comment.parent_id.unwrapOr(0)
+ );
+ if (reply_found) {
+ reply_found.comment.read = true;
+ }
+ this.state.combined = this.buildCombined();
+ }
+ this.sendUnreadCount(true);
+ this.setState(this.state);
+ setupTippy();
+ // TODO this seems wrong, you should be using form_id
+ toast(i18n.t("reply_sent"));
}
- this.state.combined = this.buildCombined();
- }
- this.sendUnreadCount(true);
- this.setState(this.state);
- setupTippy();
- // TODO this seems wrong, you should be using form_id
- toast(i18n.t("reply_sent"));
- }
+ },
+ none: void 0,
+ });
} else if (op == UserOperation.CreatePrivateMessage) {
- let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
- if (
- data.private_message_view.recipient.id ==
- UserService.Instance.myUserInfo.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);
- }
+ 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,
+ });
} else if (op == UserOperation.SaveComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
saveCommentRes(data.comment_view, this.state.replies);
this.setState(this.state);
setupTippy();
} else if (op == UserOperation.CreateCommentLike) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
createCommentLikeRes(data.comment_view, this.state.replies);
this.setState(this.state);
} else if (op == UserOperation.BlockPerson) {
- let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
updatePersonBlock(data);
} else if (op == UserOperation.CreatePostReport) {
- let data = wsJsonToRes<PostReportResponse>(msg).data;
+ let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
if (data) {
toast(i18n.t("report_created"));
}
} else if (op == UserOperation.CreateCommentReport) {
- let data = wsJsonToRes<CommentReportResponse>(msg).data;
+ let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
if (data) {
toast(i18n.t("report_created"));
}
+import { None } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
+ GetSiteResponse,
LoginResponse,
PasswordChange as PasswordChangeForm,
- SiteView,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
setIsoData,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
interface State {
passwordChangeForm: PasswordChangeForm;
loading: boolean;
- site_view: SiteView;
+ siteRes: GetSiteResponse;
}
export class PasswordChange extends Component<any, State> {
private subscription: Subscription;
emptyState: State = {
- passwordChangeForm: {
+ passwordChangeForm: new PasswordChangeForm({
token: this.props.match.params.token,
password: undefined,
password_verify: undefined,
- },
+ }),
loading: false,
- site_view: this.isoData.site_res.site_view,
+ siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
}
get documentTitle(): string {
- return `${i18n.t("password_change")} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${i18n.t("password_change")} - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
this.setState(this.state);
return;
} else if (op == UserOperation.PasswordChange) {
- let data = wsJsonToRes<LoginResponse>(msg).data;
+ let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
this.state = this.emptyState;
this.setState(this.state);
UserService.Instance.login(data);
+import { None, Some } from "@sniptt/monads/build";
import { Component } from "inferno";
import {
CommentView,
<CommentNodes
key={i.id}
nodes={[{ comment_view: c }]}
- admins={this.props.admins}
+ admins={Some(this.props.admins)}
+ moderators={None}
+ maxCommentsShown={None}
noBorder
noIndent
showCommunity
<PostListing
key={i.id}
post_view={p}
- admins={this.props.admins}
+ admins={Some(this.props.admins)}
+ duplicates={None}
+ moderators={None}
showCommunity
enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw}
<div>
<CommentNodes
nodes={commentsToFlatNodes(this.props.personRes.comments)}
- admins={this.props.admins}
+ admins={Some(this.props.admins)}
+ moderators={None}
+ maxCommentsShown={None}
noIndent
showCommunity
showContext
<>
<PostListing
post_view={post}
- admins={this.props.admins}
+ admins={Some(this.props.admins)}
showCommunity
+ duplicates={None}
+ moderators={None}
enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw}
/>
render() {
let person = this.props.person;
- let local = person.local == null ? true : person.local;
+ let local = person.local;
let apubName: string, link: string;
if (local) {
let displayName = this.props.useApubName
? apubName
- : person.display_name
- ? person.display_name
- : apubName;
+ : person.display_name.unwrapOr(apubName);
- if (this.props.showApubName && !local && person.display_name) {
+ if (this.props.showApubName && !local && person.display_name.isSome()) {
displayName = `${displayName} (${apubName})`;
}
}
avatarAndName(displayName: string) {
- let person = this.props.person;
return (
<>
- {!this.props.hideAvatar && person.avatar && showAvatars() && (
- <PictrsImage src={person.avatar} icon />
- )}
+ {this.props.person.avatar.match({
+ some: avatar =>
+ !this.props.hideAvatar &&
+ showAvatars() && <PictrsImage src={avatar} icon />,
+ none: <></>,
+ })}
<span>{displayName}</span>
</>
);
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
import {
GetSiteResponse,
PostResponse,
SortType,
+ toUndefined,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import moment from "moment";
import { Subscription } from "rxjs";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
canMod,
capitalizeFirstLetter,
createCommentLikeRes,
createPostLikeFindRes,
editCommentRes,
editPostFindRes,
+ enableDownvotes,
+ enableNsfw,
fetchLimit,
futureDaysToUnixTime,
getUsernameFromProps,
+ isAdmin,
isBanned,
- isMod,
mdToHtml,
numToSI,
relTags,
saveCommentRes,
saveScrollPosition,
setIsoData,
- setOptionalAuth,
setupTippy,
toast,
updatePersonBlock,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { BannerIconHeader } from "../common/banner-icon-header";
import { HtmlTags } from "../common/html-tags";
import { PersonListing } from "./person-listing";
interface ProfileState {
- personRes: GetPersonDetailsResponse;
+ personRes: Option<GetPersonDetailsResponse>;
userName: string;
view: PersonDetailsView;
sort: SortType;
page: number;
loading: boolean;
personBlocked: boolean;
- siteRes: GetSiteResponse;
+ banReason: Option<string>;
+ banExpireDays: Option<number>;
showBanDialog: boolean;
- banReason: string;
- banExpireDays: number;
removeData: boolean;
+ siteRes: GetSiteResponse;
}
interface ProfileProps {
}
export class Profile extends Component<any, ProfileState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(this.context, GetPersonDetailsResponse);
private subscription: Subscription;
private emptyState: ProfileState = {
- personRes: undefined,
+ personRes: None,
userName: getUsernameFromProps(this.props),
loading: true,
view: Profile.getViewFromProps(this.props.match.view),
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.personRes = this.isoData.routeData[0];
+ this.state.personRes = Some(
+ this.isoData.routeData[0] as GetPersonDetailsResponse
+ );
this.state.loading = false;
} else {
this.fetchUserData();
}
fetchUserData() {
- 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: authField(false),
- };
+ 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(),
+ });
WebSocketService.Instance.send(wsClient.getPersonDetails(form));
}
- get isCurrentUser() {
- return (
- UserService.Instance.myUserInfo?.local_user_view.person.id ==
- this.state.personRes?.person_view.person.id
- );
+ 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,
+ });
}
setPersonBlock() {
- this.state.personBlocked = UserService.Instance.myUserInfo?.person_blocks
- .map(a => a.target.id)
- .includes(this.state.personRes?.person_view.person.id);
+ UserService.Instance.myUserInfo.match({
+ some: mui =>
+ this.state.personRes.match({
+ some: res => {
+ this.state.personBlocked = mui.person_blocks
+ .map(a => a.target.id)
+ .includes(res.person_view.person.id);
+ },
+ none: void 0,
+ }),
+ none: void 0,
+ });
}
static getViewFromProps(view: string): PersonDetailsView {
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let pathSplit = req.path.split("/");
- let promises: Promise<any>[] = [];
let username = pathSplit[2];
let view = this.getViewFromProps(pathSplit[4]);
- let sort = this.getSortTypeFromProps(pathSplit[6]);
- let page = this.getPageFromProps(Number(pathSplit[8]));
+ let sort = Some(this.getSortTypeFromProps(pathSplit[6]));
+ let page = Some(this.getPageFromProps(Number(pathSplit[8])));
- let form: GetPersonDetails = {
+ let form = new GetPersonDetails({
+ username: Some(username),
+ person_id: None,
+ community_id: None,
sort,
- saved_only: view === PersonDetailsView.Saved,
+ saved_only: Some(view === PersonDetailsView.Saved),
page,
- limit: fetchLimit,
- username: username,
- };
- setOptionalAuth(form, req.auth);
- promises.push(req.client.getPersonDetails(form));
- return promises;
+ limit: Some(fetchLimit),
+ auth: req.auth,
+ });
+ return [req.client.getPersonDetails(form)];
}
componentDidMount() {
}
get documentTitle(): string {
- return `@${this.state.personRes.person_view.person.name} - ${this.state.siteRes.site_view.site.name}`;
- }
-
- get bioTag(): string {
- return this.state.personRes.person_view.person.bio
- ? this.state.personRes.person_view.person.bio
- : undefined;
+ return this.state.siteRes.site_view.match({
+ some: siteView =>
+ this.state.personRes.match({
+ some: res =>
+ `@${res.person_view.person.name} - ${siteView.site.name}`,
+ none: "",
+ }),
+ none: "",
+ });
}
render() {
<Spinner large />
</h5>
) : (
- <div class="row">
- <div class="col-12 col-md-8">
- <>
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- description={this.bioTag}
- image={this.state.personRes.person_view.person.avatar}
- />
- {this.userInfo()}
- <hr />
- </>
- {!this.state.loading && this.selects()}
- <PersonDetails
- personRes={this.state.personRes}
- admins={this.state.siteRes.admins}
- sort={this.state.sort}
- page={this.state.page}
- limit={fetchLimit}
- enableDownvotes={
- this.state.siteRes.site_view.site.enable_downvotes
- }
- enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
- view={this.state.view}
- onPageChange={this.handlePageChange}
- />
- </div>
+ this.state.personRes.match({
+ some: res => (
+ <div class="row">
+ <div class="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}
+ />
+ </div>
- {!this.state.loading && (
- <div class="col-12 col-md-4">
- {this.moderates()}
- {this.isCurrentUser && this.follows()}
+ {!this.state.loading && (
+ <div class="col-12 col-md-4">
+ {this.moderates()}
+ {this.amCurrentUser && this.follows()}
+ </div>
+ )}
</div>
- )}
- </div>
+ ),
+ none: <></>,
+ })
)}
</div>
);
}
handleBlockPerson(personId: number) {
if (personId != 0) {
- let blockUserForm: BlockPerson = {
+ let blockUserForm = new BlockPerson({
person_id: personId,
block: true,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
}
}
handleUnblockPerson(recipientId: number) {
- let blockUserForm: BlockPerson = {
+ let blockUserForm = new BlockPerson({
person_id: recipientId,
block: false,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
}
userInfo() {
- let pv = this.state.personRes?.person_view;
-
- return (
- <div>
- <BannerIconHeader banner={pv.person.banner} icon={pv.person.avatar} />
- <div class="mb-3">
- <div class="">
- <div class="mb-0 d-flex flex-wrap">
- <div>
- {pv.person.display_name && (
- <h5 class="mb-0">{pv.person.display_name}</h5>
- )}
- <ul class="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>
- )}
- {pv.person.admin && (
+ return this.state.personRes
+ .map(r => r.person_view)
+ .match({
+ some: pv => (
+ <div>
+ <BannerIconHeader
+ banner={pv.person.banner}
+ icon={pv.person.avatar}
+ />
+ <div class="mb-3">
+ <div class="">
+ <div class="mb-0 d-flex flex-wrap">
+ <div>
+ {pv.person.display_name && (
+ <h5 class="mb-0">{pv.person.display_name}</h5>
+ )}
+ <ul class="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>
+ )}
+ {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) ? (
+ <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.match({
+ some: bio => (
+ <div className="d-flex align-items-center mb-2">
+ <div
+ className="md-div"
+ dangerouslySetInnerHTML={mdToHtml(bio)}
+ />
+ </div>
+ ),
+ none: <></>,
+ })}
+ <div>
+ <ul class="list-inline mb-2">
<li className="list-inline-item badge badge-light">
- {i18n.t("admin")}
+ {i18n.t("number_of_posts", {
+ count: pv.counts.post_count,
+ formattedCount: numToSI(pv.counts.post_count),
+ })}
</li>
- )}
- {pv.person.bot_account && (
<li className="list-inline-item badge badge-light">
- {i18n.t("bot_account").toLowerCase()}
+ {i18n.t("number_of_comments", {
+ count: pv.counts.comment_count,
+ formattedCount: numToSI(pv.counts.comment_count),
+ })}
</li>
- )}
- </ul>
- </div>
- {this.banDialog()}
- <div className="flex-grow-1 unselectable pointer mx-2"></div>
- {!this.isCurrentUser && 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(
- 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>
- )}
- </>
- )}
-
- {this.canAdmin &&
- !this.personIsAdmin &&
- !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)}
- />
+ </ul>
+ </div>
+ <div class="text-muted">
+ {i18n.t("joined")}{" "}
+ <MomentTime
+ published={pv.person.published}
+ updated={None}
+ 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>
- <ul class="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 class="text-muted">
- {i18n.t("joined")}{" "}
- <MomentTime data={pv.person} 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>
- </div>
- );
+ ),
+ none: <></>,
+ });
}
banDialog() {
- let pv = this.state.personRes?.person_view;
- return (
- <>
- {this.state.showBanDialog && (
- <form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
- <div class="form-group row col-12">
- <label class="col-form-label" htmlFor="profile-ban-reason">
- {i18n.t("reason")}
- </label>
- <input
- type="text"
- id="profile-ban-reason"
- class="form-control mr-2"
- placeholder={i18n.t("reason")}
- value={this.state.banReason}
- onInput={linkEvent(this, this.handleModBanReasonChange)}
- />
- <label class="col-form-label" htmlFor={`mod-ban-expires`}>
- {i18n.t("expires")}
- </label>
- <input
- type="number"
- id={`mod-ban-expires`}
- class="form-control mr-2"
- placeholder={i18n.t("number_of_days")}
- value={this.state.banExpireDays}
- onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
- />
- <div class="form-group">
- <div class="form-check">
+ return this.state.personRes
+ .map(r => r.person_view)
+ .match({
+ some: pv => (
+ <>
+ {this.state.showBanDialog && (
+ <form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
+ <div class="form-group row col-12">
+ <label class="col-form-label" htmlFor="profile-ban-reason">
+ {i18n.t("reason")}
+ </label>
<input
- class="form-check-input"
- id="mod-ban-remove-data"
- type="checkbox"
- checked={this.state.removeData}
- onChange={linkEvent(this, this.handleModRemoveDataChange)}
+ type="text"
+ id="profile-ban-reason"
+ class="form-control mr-2"
+ placeholder={i18n.t("reason")}
+ value={toUndefined(this.state.banReason)}
+ onInput={linkEvent(this, this.handleModBanReasonChange)}
/>
- <label
- class="form-check-label"
- htmlFor="mod-ban-remove-data"
- title={i18n.t("remove_content_more")}
- >
- {i18n.t("remove_content")}
+ <label class="col-form-label" htmlFor={`mod-ban-expires`}>
+ {i18n.t("expires")}
</label>
+ <input
+ type="number"
+ id={`mod-ban-expires`}
+ class="form-control mr-2"
+ placeholder={i18n.t("number_of_days")}
+ value={toUndefined(this.state.banExpireDays)}
+ onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
+ />
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="mod-ban-remove-data"
+ type="checkbox"
+ checked={this.state.removeData}
+ onChange={linkEvent(
+ this,
+ this.handleModRemoveDataChange
+ )}
+ />
+ <label
+ class="form-check-label"
+ htmlFor="mod-ban-remove-data"
+ title={i18n.t("remove_content_more")}
+ >
+ {i18n.t("remove_content")}
+ </label>
+ </div>
+ </div>
</div>
- </div>
- </div>
- {/* TODO hold off on expires until later */}
- {/* <div class="form-group row"> */}
- {/* <label class="col-form-label">Expires</label> */}
- {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
- {/* </div> */}
- <div class="form-group row">
- <button
- type="cancel"
- class="btn btn-secondary mr-2"
- aria-label={i18n.t("cancel")}
- onClick={linkEvent(this, this.handleModBanSubmitCancel)}
- >
- {i18n.t("cancel")}
- </button>
- <button
- type="submit"
- class="btn btn-secondary"
- aria-label={i18n.t("ban")}
- >
- {i18n.t("ban")} {pv.person.name}
- </button>
- </div>
- </form>
- )}
- </>
- );
+ {/* 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 class="form-group row">
+ <button
+ type="cancel"
+ class="btn btn-secondary mr-2"
+ aria-label={i18n.t("cancel")}
+ onClick={linkEvent(this, this.handleModBanSubmitCancel)}
+ >
+ {i18n.t("cancel")}
+ </button>
+ <button
+ type="submit"
+ class="btn btn-secondary"
+ aria-label={i18n.t("ban")}
+ >
+ {i18n.t("ban")} {pv.person.name}
+ </button>
+ </div>
+ </form>
+ )}
+ </>
+ ),
+ none: <></>,
+ });
}
+ // TODO test this, make sure its good
moderates() {
- return (
- <div>
- {this.state.personRes.moderates.length > 0 && (
- <div class="card border-secondary mb-3">
- <div class="card-body">
- <h5>{i18n.t("moderates")}</h5>
- <ul class="list-unstyled mb-0">
- {this.state.personRes.moderates.map(cmv => (
- <li>
- <CommunityLink community={cmv.community} />
- </li>
- ))}
- </ul>
- </div>
- </div>
- )}
- </div>
- );
+ return this.state.personRes
+ .map(r => r.moderates)
+ .match({
+ some: moderates => {
+ if (moderates.length > 0) {
+ <div class="card border-secondary mb-3">
+ <div class="card-body">
+ <h5>{i18n.t("moderates")}</h5>
+ <ul class="list-unstyled mb-0">
+ {moderates.map(cmv => (
+ <li>
+ <CommunityLink community={cmv.community} />
+ </li>
+ ))}
+ </ul>
+ </div>
+ </div>;
+ }
+ },
+ none: void 0,
+ });
}
follows() {
- let follows = UserService.Instance.myUserInfo.follows;
- return (
- <div>
- {follows.length > 0 && (
- <div class="card border-secondary mb-3">
- <div class="card-body">
- <h5>{i18n.t("subscribed")}</h5>
- <ul class="list-unstyled mb-0">
- {follows.map(cfv => (
- <li>
- <CommunityLink community={cfv.community} />
- </li>
- ))}
- </ul>
- </div>
- </div>
- )}
- </div>
- );
+ return UserService.Instance.myUserInfo
+ .map(m => m.follows)
+ .match({
+ some: follows => {
+ if (follows.length > 0) {
+ <div class="card border-secondary mb-3">
+ <div class="card-body">
+ <h5>{i18n.t("subscribed")}</h5>
+ <ul class="list-unstyled mb-0">
+ {follows.map(cfv => (
+ <li>
+ <CommunityLink community={cfv.community} />
+ </li>
+ ))}
+ </ul>
+ </div>
+ </div>;
+ }
+ },
+ none: void 0,
+ });
}
updateUrl(paramUpdates: UrlParams) {
this.fetchUserData();
}
- get canAdmin(): boolean {
- return (
- this.state.siteRes?.admins &&
- canMod(
- UserService.Instance.myUserInfo,
- this.state.siteRes.admins.map(a => a.person.id),
- this.state.personRes?.person_view.person.id
- )
- );
- }
-
- get personIsAdmin(): boolean {
- return (
- this.state.siteRes?.admins &&
- isMod(
- this.state.siteRes.admins.map(a => a.person.id),
- this.state.personRes?.person_view.person.id
- )
- );
- }
-
handlePageChange(page: number) {
- this.updateUrl({ page });
+ this.updateUrl({ page: page });
}
handleSortChange(val: SortType) {
handleModBanSubmit(i: Profile, event?: any) {
if (event) event.preventDefault();
- let pv = i.state.personRes.person_view;
- // If its an unban, restore all their data
- let ban = !pv.person.banned;
- if (ban == false) {
- i.state.removeData = false;
- }
- let form: BanPerson = {
- person_id: pv.person.id,
- ban,
- remove_data: i.state.removeData,
- reason: i.state.banReason,
- expires: futureDaysToUnixTime(i.state.banExpireDays),
- auth: authField(),
- };
- WebSocketService.Instance.send(wsClient.banPerson(form));
-
- i.state.showBanDialog = false;
- i.setState(i.state);
+ 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.state.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.state.showBanDialog = false;
+ i.setState(i.state);
+ },
+ none: void 0,
+ });
}
parseMessage(msg: any) {
// 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).data;
- this.state.personRes = data;
- console.log(data);
+ let data = wsJsonToRes<GetPersonDetailsResponse>(
+ msg,
+ GetPersonDetailsResponse
+ );
+ this.state.personRes = Some(data);
this.state.loading = false;
this.setPersonBlock();
this.setState(this.state);
restoreScrollPosition(this.context);
} else if (op == UserOperation.AddAdmin) {
- let data = wsJsonToRes<AddAdminResponse>(msg).data;
+ let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse);
this.state.siteRes.admins = data.admins;
this.setState(this.state);
} else if (op == UserOperation.CreateCommentLike) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- createCommentLikeRes(data.comment_view, this.state.personRes.comments);
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
+ createCommentLikeRes(
+ data.comment_view,
+ this.state.personRes.map(r => r.comments).unwrapOr([])
+ );
this.setState(this.state);
} else if (
op == UserOperation.EditComment ||
op == UserOperation.DeleteComment ||
op == UserOperation.RemoveComment
) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- editCommentRes(data.comment_view, this.state.personRes.comments);
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
+ editCommentRes(
+ data.comment_view,
+ this.state.personRes.map(r => r.comments).unwrapOr([])
+ );
this.setState(this.state);
} else if (op == UserOperation.CreateComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- if (
- UserService.Instance.myUserInfo &&
- data.comment_view.creator.id ==
- UserService.Instance.myUserInfo?.local_user_view.person.id
- ) {
- toast(i18n.t("reply_sent"));
- }
+ 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,
+ });
} else if (op == UserOperation.SaveComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- saveCommentRes(data.comment_view, this.state.personRes.comments);
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
+ saveCommentRes(
+ data.comment_view,
+ this.state.personRes.map(r => r.comments).unwrapOr([])
+ );
this.setState(this.state);
} else if (
op == UserOperation.EditPost ||
op == UserOperation.StickyPost ||
op == UserOperation.SavePost
) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- editPostFindRes(data.post_view, this.state.personRes.posts);
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
+ editPostFindRes(
+ data.post_view,
+ this.state.personRes.map(r => r.posts).unwrapOr([])
+ );
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- createPostLikeFindRes(data.post_view, this.state.personRes.posts);
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
+ createPostLikeFindRes(
+ data.post_view,
+ this.state.personRes.map(r => r.posts).unwrapOr([])
+ );
this.setState(this.state);
} else if (op == UserOperation.BanPerson) {
- let data = wsJsonToRes<BanPersonResponse>(msg).data;
- this.state.personRes.comments
- .filter(c => c.creator.id == data.person_view.person.id)
- .forEach(c => (c.creator.banned = data.banned));
- this.state.personRes.posts
- .filter(c => c.creator.id == data.person_view.person.id)
- .forEach(c => (c.creator.banned = data.banned));
- let pv = this.state.personRes.person_view;
-
- if (pv.person.id == data.person_view.person.id) {
- pv.person.banned = data.banned;
- }
- this.setState(this.state);
+ 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,
+ });
} else if (op == UserOperation.BlockPerson) {
- let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
updatePersonBlock(data);
this.setPersonBlock();
this.setState(this.state);
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
+ GetSiteResponse,
ListRegistrationApplications,
ListRegistrationApplicationsResponse,
RegistrationApplicationResponse,
- RegistrationApplicationView,
- SiteView,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
fetchLimit,
isBrowser,
setIsoData,
toast,
updateRegistrationApplicationRes,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
}
interface RegistrationApplicationsState {
- applications: RegistrationApplicationView[];
- page: number;
- site_view: SiteView;
+ listRegistrationApplicationsResponse: Option<ListRegistrationApplicationsResponse>;
+ siteRes: GetSiteResponse;
unreadOrAll: UnreadOrAll;
+ page: number;
loading: boolean;
}
any,
RegistrationApplicationsState
> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(
+ this.context,
+ ListRegistrationApplicationsResponse
+ );
private subscription: Subscription;
private emptyState: RegistrationApplicationsState = {
+ listRegistrationApplicationsResponse: None,
+ siteRes: this.isoData.site_res,
unreadOrAll: UnreadOrAll.Unread,
- applications: [],
page: 1,
- site_view: this.isoData.site_res.site_view,
loading: true,
};
this.state = this.emptyState;
this.handlePageChange = this.handlePageChange.bind(this);
- if (!UserService.Instance.myUserInfo && isBrowser()) {
+ if (UserService.Instance.myUserInfo.isNone() && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.applications =
- this.isoData.routeData[0].registration_applications || []; // TODO test
+ this.state.listRegistrationApplicationsResponse = Some(
+ this.isoData.routeData[0] as ListRegistrationApplicationsResponse
+ );
this.state.loading = false;
} else {
this.refetch();
}
get documentTitle(): string {
- return `@${
- UserService.Instance.myUserInfo.local_user_view.person.name
- } ${i18n.t("registration_applications")} - ${
- this.state.site_view.site.name
- }`;
+ return this.state.siteRes.site_view.match({
+ some: siteView =>
+ UserService.Instance.myUserInfo.match({
+ some: mui =>
+ `@${mui.local_user_view.person.name} ${i18n.t(
+ "registration_applications"
+ )} - ${siteView.site.name}`,
+ none: "",
+ }),
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<h5 class="mb-2">{i18n.t("registration_applications")}</h5>
{this.selects()}
}
applicationList() {
- return (
- <div>
- {this.state.applications.map(ra => (
- <>
- <hr />
- <RegistrationApplication
- key={ra.registration_application.id}
- application={ra}
- />
- </>
- ))}
- </div>
- );
+ return this.state.listRegistrationApplicationsResponse.match({
+ some: res => (
+ <div>
+ {res.registration_applications.map(ra => (
+ <>
+ <hr />
+ <RegistrationApplication
+ key={ra.registration_application.id}
+ application={ra}
+ />
+ </>
+ ))}
+ </div>
+ ),
+ none: <></>,
+ });
}
handleUnreadOrAllChange(i: RegistrationApplications, event: any) {
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let promises: Promise<any>[] = [];
- let form: ListRegistrationApplications = {
- unread_only: true,
- page: 1,
- limit: fetchLimit,
- auth: req.auth,
- };
+ let form = new ListRegistrationApplications({
+ unread_only: Some(true),
+ page: Some(1),
+ limit: Some(fetchLimit),
+ auth: req.auth.unwrap(),
+ });
promises.push(req.client.listRegistrationApplications(form));
return promises;
refetch() {
let unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
- let form: ListRegistrationApplications = {
- unread_only: unread_only,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(),
- };
+ 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));
}
} else if (msg.reconnect) {
this.refetch();
} else if (op == UserOperation.ListRegistrationApplications) {
- let data = wsJsonToRes<ListRegistrationApplicationsResponse>(msg).data;
- this.state.applications = data.registration_applications;
+ let data = wsJsonToRes<ListRegistrationApplicationsResponse>(
+ msg,
+ ListRegistrationApplicationsResponse
+ );
+ this.state.listRegistrationApplicationsResponse = Some(data);
this.state.loading = false;
window.scrollTo(0, 0);
this.setState(this.state);
} else if (op == UserOperation.ApproveRegistrationApplication) {
- let data = wsJsonToRes<RegistrationApplicationResponse>(msg).data;
+ let data = wsJsonToRes<RegistrationApplicationResponse>(
+ msg,
+ RegistrationApplicationResponse
+ );
updateRegistrationApplicationRes(
data.registration_application,
- this.state.applications
+ this.state.listRegistrationApplicationsResponse
+ .map(r => r.registration_applications)
+ .unwrapOr([])
);
let uacs = UserService.Instance.unreadApplicationCountSub;
// Minor bug, where if the application switches from deny to approve, the count will still go down
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
CommentReportResponse,
CommentReportView,
+ GetSiteResponse,
ListCommentReports,
ListCommentReportsResponse,
ListPostReports,
ListPostReportsResponse,
PostReportResponse,
PostReportView,
- SiteView,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
fetchLimit,
isBrowser,
setIsoData,
updateCommentReportRes,
updatePostReportRes,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { CommentReport } from "../comment/comment-report";
import { HtmlTags } from "../common/html-tags";
};
interface ReportsState {
+ listCommentReportsResponse: Option<ListCommentReportsResponse>;
+ listPostReportsResponse: Option<ListPostReportsResponse>;
unreadOrAll: UnreadOrAll;
messageType: MessageType;
- commentReports: CommentReportView[];
- postReports: PostReportView[];
combined: ItemType[];
+ siteRes: GetSiteResponse;
page: number;
- site_view: SiteView;
loading: boolean;
}
export class Reports extends Component<any, ReportsState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(
+ this.context,
+ ListCommentReportsResponse,
+ ListPostReportsResponse
+ );
private subscription: Subscription;
private emptyState: ReportsState = {
+ listCommentReportsResponse: None,
+ listPostReportsResponse: None,
unreadOrAll: UnreadOrAll.Unread,
messageType: MessageType.All,
- commentReports: [],
- postReports: [],
combined: [],
page: 1,
- site_view: this.isoData.site_res.site_view,
+ siteRes: this.isoData.site_res,
loading: true,
};
this.state = this.emptyState;
this.handlePageChange = this.handlePageChange.bind(this);
- if (!UserService.Instance.myUserInfo && isBrowser()) {
+ if (UserService.Instance.myUserInfo.isNone() && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.commentReports =
- this.isoData.routeData[0].comment_reports || [];
- this.state.postReports = this.isoData.routeData[1].post_reports || [];
+ this.state.listCommentReportsResponse = Some(
+ this.isoData.routeData[0] as ListCommentReportsResponse
+ );
+ this.state.listPostReportsResponse = Some(
+ this.isoData.routeData[1] as ListPostReportsResponse
+ );
this.state.combined = this.buildCombined();
this.state.loading = false;
} else {
}
get documentTitle(): string {
- return `@${
- UserService.Instance.myUserInfo.local_user_view.person.name
- } ${i18n.t("reports")} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView =>
+ UserService.Instance.myUserInfo.match({
+ some: mui =>
+ `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${
+ siteView.site.name
+ }`,
+ none: "",
+ }),
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<h5 class="mb-2">{i18n.t("reports")}</h5>
{this.selects()}
}
buildCombined(): ItemType[] {
- let comments: ItemType[] = this.state.commentReports.map(r =>
- this.replyToReplyType(r)
- );
- let posts: ItemType[] = this.state.postReports.map(r =>
- this.mentionToReplyType(r)
- );
+ let comments: ItemType[] = this.state.listCommentReportsResponse
+ .map(r => r.comment_reports)
+ .unwrapOr([])
+ .map(r => this.replyToReplyType(r));
+ let posts: ItemType[] = this.state.listPostReportsResponse
+ .map(r => r.post_reports)
+ .unwrapOr([])
+ .map(r => this.mentionToReplyType(r));
return [...comments, ...posts].sort((a, b) =>
b.published.localeCompare(a.published)
}
commentReports() {
- return (
- <div>
- {this.state.commentReports.map(cr => (
- <>
- <hr />
- <CommentReport key={cr.comment_report.id} report={cr} />
- </>
- ))}
- </div>
- );
+ return this.state.listCommentReportsResponse.match({
+ some: res => (
+ <div>
+ {res.comment_reports.map(cr => (
+ <>
+ <hr />
+ <CommentReport key={cr.comment_report.id} report={cr} />
+ </>
+ ))}
+ </div>
+ ),
+ none: <></>,
+ });
}
postReports() {
- return (
- <div>
- {this.state.postReports.map(pr => (
- <>
- <hr />
- <PostReport key={pr.post_report.id} report={pr} />
- </>
- ))}
- </div>
- );
+ return this.state.listPostReportsResponse.match({
+ some: res => (
+ <div>
+ {res.post_reports.map(pr => (
+ <>
+ <hr />
+ <PostReport key={pr.post_report.id} report={pr} />
+ </>
+ ))}
+ </div>
+ ),
+ none: <></>,
+ });
}
handlePageChange(page: number) {
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let promises: Promise<any>[] = [];
- let commentReportsForm: ListCommentReports = {
+ 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: true,
- page: 1,
- limit: fetchLimit,
- auth: req.auth,
- };
+ unresolved_only,
+ community_id,
+ page,
+ limit,
+ auth,
+ });
promises.push(req.client.listCommentReports(commentReportsForm));
- let postReportsForm: ListPostReports = {
+ let postReportsForm = new ListPostReports({
// TODO community_id
- unresolved_only: true,
- page: 1,
- limit: fetchLimit,
- auth: req.auth,
- };
+ unresolved_only,
+ community_id,
+ page,
+ limit,
+ auth,
+ });
promises.push(req.client.listPostReports(postReportsForm));
return promises;
}
refetch() {
- let unresolved_only = this.state.unreadOrAll == UnreadOrAll.Unread;
- let commentReportsForm: ListCommentReports = {
- // TODO community_id
+ 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,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(),
- };
+ // TODO community_id
+ community_id,
+ page,
+ limit,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(
wsClient.listCommentReports(commentReportsForm)
);
- let postReportsForm: ListPostReports = {
- // TODO community_id
+ let postReportsForm = new ListPostReports({
unresolved_only,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(),
- };
+ // TODO community_id
+ community_id,
+ page,
+ limit,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.listPostReports(postReportsForm));
}
} else if (msg.reconnect) {
this.refetch();
} else if (op == UserOperation.ListCommentReports) {
- let data = wsJsonToRes<ListCommentReportsResponse>(msg).data;
- this.state.commentReports = data.comment_reports;
+ let data = wsJsonToRes<ListCommentReportsResponse>(
+ msg,
+ ListCommentReportsResponse
+ );
+ this.state.listCommentReportsResponse = Some(data);
this.state.combined = this.buildCombined();
this.state.loading = false;
// this.sendUnreadCount();
this.setState(this.state);
setupTippy();
} else if (op == UserOperation.ListPostReports) {
- let data = wsJsonToRes<ListPostReportsResponse>(msg).data;
- this.state.postReports = data.post_reports;
+ let data = wsJsonToRes<ListPostReportsResponse>(
+ msg,
+ ListPostReportsResponse
+ );
+ this.state.listPostReportsResponse = Some(data);
this.state.combined = this.buildCombined();
this.state.loading = false;
// this.sendUnreadCount();
this.setState(this.state);
setupTippy();
} else if (op == UserOperation.ResolvePostReport) {
- let data = wsJsonToRes<PostReportResponse>(msg).data;
- updatePostReportRes(data.post_report_view, this.state.postReports);
+ let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
+ updatePostReportRes(
+ data.post_report_view,
+ this.state.listPostReportsResponse.map(r => r.post_reports).unwrapOr([])
+ );
let urcs = UserService.Instance.unreadReportCountSub;
if (data.post_report_view.post_report.resolved) {
urcs.next(urcs.getValue() - 1);
}
this.setState(this.state);
} else if (op == UserOperation.ResolveCommentReport) {
- let data = wsJsonToRes<CommentReportResponse>(msg).data;
+ let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
updateCommentReportRes(
data.comment_report_view,
- this.state.commentReports
+ this.state.listCommentReportsResponse
+ .map(r => r.comment_reports)
+ .unwrapOr([])
);
let urcs = UserService.Instance.unreadReportCountSub;
if (data.comment_report_view.comment_report.resolved) {
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
BlockCommunity,
PersonViewSafe,
SaveUserSettings,
SortType,
+ toUndefined,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n, languages } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
capitalizeFirstLetter,
choicesConfig,
communitySelectName,
communityToChoice,
debounce,
elementUrl,
+ enableNsfw,
fetchCommunities,
fetchThemeList,
fetchUsers,
updateCommunityBlock,
updatePersonBlock,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
interface SettingsState {
saveUserSettingsForm: SaveUserSettings;
changePasswordForm: ChangePassword;
- saveUserSettingsLoading: boolean;
- changePasswordLoading: boolean;
- deleteAccountLoading: boolean;
- deleteAccountShowConfirm: boolean;
deleteAccountForm: DeleteAccount;
personBlocks: PersonBlockView[];
- blockPersonId: number;
- blockPerson?: PersonViewSafe;
+ blockPerson: Option<PersonViewSafe>;
communityBlocks: CommunityBlockView[];
blockCommunityId: number;
blockCommunity?: CommunityView;
currentTab: string;
- siteRes: GetSiteResponse;
themeList: string[];
+ saveUserSettingsLoading: boolean;
+ changePasswordLoading: boolean;
+ deleteAccountLoading: boolean;
+ deleteAccountShowConfirm: boolean;
+ siteRes: GetSiteResponse;
}
export class Settings extends Component<any, SettingsState> {
private blockCommunityChoices: any;
private subscription: Subscription;
private emptyState: SettingsState = {
- saveUserSettingsForm: {
- auth: authField(false),
- },
- changePasswordForm: {
- new_password: null,
- new_password_verify: null,
- old_password: null,
- auth: authField(false),
- },
- saveUserSettingsLoading: null,
+ 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,
+ lang: 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,
+ }),
+ saveUserSettingsLoading: false,
changePasswordLoading: false,
- deleteAccountLoading: null,
+ deleteAccountLoading: false,
deleteAccountShowConfirm: false,
- deleteAccountForm: {
- password: null,
- auth: authField(false),
- },
+ deleteAccountForm: new DeleteAccount({
+ password: undefined,
+ auth: undefined,
+ }),
personBlocks: [],
- blockPersonId: 0,
+ blockPerson: None,
communityBlocks: [],
blockCommunityId: 0,
currentTab: "settings",
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
- description={this.documentTitle}
+ description={Some(this.documentTitle)}
image={this.state.saveUserSettingsForm.avatar}
/>
<ul class="nav nav-tabs mb-2">
<select
class="form-control"
id="block-person-filter"
- value={this.state.blockPersonId}
+ value={this.state.blockPerson.map(p => p.person.id).unwrapOr(0)}
>
<option value="0">—</option>
- {this.state.blockPerson && (
- <option value={this.state.blockPerson.person.id}>
- {personSelectName(this.state.blockPerson)}
- </option>
- )}
+ {this.state.blockPerson.match({
+ some: personView => (
+ <option value={personView.person.id}>
+ {personSelectName(personView)}
+ </option>
+ ),
+ none: <></>,
+ })}
</select>
</div>
</div>
type="text"
class="form-control"
placeholder={i18n.t("optional")}
- value={this.state.saveUserSettingsForm.display_name}
+ value={toUndefined(
+ this.state.saveUserSettingsForm.display_name
+ )}
onInput={linkEvent(this, this.handleDisplayNameChange)}
pattern="^(?!@)(.+)$"
minLength={3}
<MarkdownTextArea
initialContent={this.state.saveUserSettingsForm.bio}
onContentChange={this.handleBioChange}
- maxLength={300}
+ maxLength={Some(300)}
+ placeholder={None}
+ buttonTitle={None}
hideNavigationWarnings
/>
</div>
id="user-email"
class="form-control"
placeholder={i18n.t("optional")}
- value={this.state.saveUserSettingsForm.email}
+ value={toUndefined(this.state.saveUserSettingsForm.email)}
onInput={linkEvent(this, this.handleEmailChange)}
minLength={3}
/>
type="text"
class="form-control"
placeholder="@user:example.com"
- value={this.state.saveUserSettingsForm.matrix_user_id}
+ value={toUndefined(
+ this.state.saveUserSettingsForm.matrix_user_id
+ )}
onInput={linkEvent(this, this.handleMatrixUserIdChange)}
pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
/>
<div class="col-sm-9">
<select
id="user-language"
- value={this.state.saveUserSettingsForm.lang}
+ value={toUndefined(this.state.saveUserSettingsForm.lang)}
onChange={linkEvent(this, this.handleLangChange)}
class="custom-select w-auto"
>
<div class="col-sm-9">
<select
id="user-theme"
- value={this.state.saveUserSettingsForm.theme}
+ value={toUndefined(this.state.saveUserSettingsForm.theme)}
onChange={linkEvent(this, this.handleThemeChange)}
class="custom-select w-auto"
>
<ListingTypeSelect
type_={
Object.values(ListingType)[
- this.state.saveUserSettingsForm.default_listing_type
+ this.state.saveUserSettingsForm.default_listing_type.unwrapOr(
+ 1
+ )
]
}
showLocal={showLocal(this.isoData)}
<SortSelect
sort={
Object.values(SortType)[
- this.state.saveUserSettingsForm.default_sort_type
+ this.state.saveUserSettingsForm.default_sort_type.unwrapOr(
+ 0
+ )
]
}
onChange={this.handleSortTypeChange}
/>
</div>
</form>
- {this.state.siteRes.site_view.site.enable_nsfw && (
+ {enableNsfw(this.state.siteRes) && (
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
id="user-show-nsfw"
type="checkbox"
- checked={this.state.saveUserSettingsForm.show_nsfw}
+ checked={toUndefined(
+ this.state.saveUserSettingsForm.show_nsfw
+ )}
onChange={linkEvent(this, this.handleShowNsfwChange)}
/>
<label class="form-check-label" htmlFor="user-show-nsfw">
class="form-check-input"
id="user-show-scores"
type="checkbox"
- checked={this.state.saveUserSettingsForm.show_scores}
+ checked={toUndefined(
+ this.state.saveUserSettingsForm.show_scores
+ )}
onChange={linkEvent(this, this.handleShowScoresChange)}
/>
<label class="form-check-label" htmlFor="user-show-scores">
class="form-check-input"
id="user-show-avatars"
type="checkbox"
- checked={this.state.saveUserSettingsForm.show_avatars}
+ checked={toUndefined(
+ this.state.saveUserSettingsForm.show_avatars
+ )}
onChange={linkEvent(this, this.handleShowAvatarsChange)}
/>
<label class="form-check-label" htmlFor="user-show-avatars">
class="form-check-input"
id="user-bot-account"
type="checkbox"
- checked={this.state.saveUserSettingsForm.bot_account}
+ checked={toUndefined(
+ this.state.saveUserSettingsForm.bot_account
+ )}
onChange={linkEvent(this, this.handleBotAccount)}
/>
<label class="form-check-label" htmlFor="user-bot-account">
class="form-check-input"
id="user-show-bot-accounts"
type="checkbox"
- checked={this.state.saveUserSettingsForm.show_bot_accounts}
+ checked={toUndefined(
+ this.state.saveUserSettingsForm.show_bot_accounts
+ )}
onChange={linkEvent(this, this.handleShowBotAccounts)}
/>
<label class="form-check-label" htmlFor="user-show-bot-accounts">
class="form-check-input"
id="user-show-read-posts"
type="checkbox"
- checked={this.state.saveUserSettingsForm.show_read_posts}
+ checked={toUndefined(
+ this.state.saveUserSettingsForm.show_read_posts
+ )}
onChange={linkEvent(this, this.handleReadPosts)}
/>
<label class="form-check-label" htmlFor="user-show-read-posts">
class="form-check-input"
id="user-show-new-post-notifs"
type="checkbox"
- checked={this.state.saveUserSettingsForm.show_new_post_notifs}
+ checked={toUndefined(
+ this.state.saveUserSettingsForm.show_new_post_notifs
+ )}
onChange={linkEvent(this, this.handleShowNewPostNotifs)}
/>
<label
id="user-send-notifications-to-email"
type="checkbox"
disabled={!this.state.saveUserSettingsForm.email}
- checked={
+ checked={toUndefined(
this.state.saveUserSettingsForm.send_notifications_to_email
- }
+ )}
onChange={linkEvent(
this,
this.handleSendNotificationsToEmailChange
handleBlockPerson(personId: number) {
if (personId != 0) {
- let blockUserForm: BlockPerson = {
+ let blockUserForm = new BlockPerson({
person_id: personId,
block: true,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
}
}
handleUnblockPerson(i: { ctx: Settings; recipientId: number }) {
- let blockUserForm: BlockPerson = {
+ let blockUserForm = new BlockPerson({
person_id: i.recipientId,
block: false,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
}
handleBlockCommunity(community_id: number) {
if (community_id != 0) {
- let blockCommunityForm: BlockCommunity = {
+ let blockCommunityForm = new BlockCommunity({
community_id,
block: true,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(
wsClient.blockCommunity(blockCommunityForm)
);
}
handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
- let blockCommunityForm: BlockCommunity = {
+ let blockCommunityForm = new BlockCommunity({
community_id: i.communityId,
block: false,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
}
handleShowNsfwChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_nsfw = event.target.checked;
+ i.state.saveUserSettingsForm.show_nsfw = Some(event.target.checked);
i.setState(i.state);
}
handleShowAvatarsChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_avatars = event.target.checked;
- UserService.Instance.myUserInfo.local_user_view.local_user.show_avatars =
- event.target.checked; // Just for instant updates
+ 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.setState(i.state);
}
handleBotAccount(i: Settings, event: any) {
- i.state.saveUserSettingsForm.bot_account = event.target.checked;
+ i.state.saveUserSettingsForm.bot_account = Some(event.target.checked);
i.setState(i.state);
}
handleShowBotAccounts(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
+ i.state.saveUserSettingsForm.show_bot_accounts = Some(event.target.checked);
i.setState(i.state);
}
handleReadPosts(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
+ i.state.saveUserSettingsForm.show_read_posts = Some(event.target.checked);
i.setState(i.state);
}
handleShowNewPostNotifs(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked;
+ i.state.saveUserSettingsForm.show_new_post_notifs = Some(
+ event.target.checked
+ );
i.setState(i.state);
}
handleShowScoresChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.show_scores = event.target.checked;
- UserService.Instance.myUserInfo.local_user_view.local_user.show_scores =
- event.target.checked; // Just for instant updates
+ 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.setState(i.state);
}
handleSendNotificationsToEmailChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.send_notifications_to_email =
- event.target.checked;
+ i.state.saveUserSettingsForm.send_notifications_to_email = Some(
+ event.target.checked
+ );
i.setState(i.state);
}
handleThemeChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.theme = event.target.value;
+ i.state.saveUserSettingsForm.theme = Some(event.target.value);
setTheme(event.target.value, true);
i.setState(i.state);
}
handleLangChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.lang = event.target.value;
- i18n.changeLanguage(getLanguages(i.state.saveUserSettingsForm.lang)[0]);
+ i.state.saveUserSettingsForm.lang = Some(event.target.value);
+ i18n.changeLanguage(
+ getLanguages(i.state.saveUserSettingsForm.lang.unwrap())[0]
+ );
i.setState(i.state);
}
handleSortTypeChange(val: SortType) {
- this.state.saveUserSettingsForm.default_sort_type =
- Object.keys(SortType).indexOf(val);
+ this.state.saveUserSettingsForm.default_sort_type = Some(
+ Object.keys(SortType).indexOf(val)
+ );
this.setState(this.state);
}
handleListingTypeChange(val: ListingType) {
- this.state.saveUserSettingsForm.default_listing_type =
- Object.keys(ListingType).indexOf(val);
+ this.state.saveUserSettingsForm.default_listing_type = Some(
+ Object.keys(ListingType).indexOf(val)
+ );
this.setState(this.state);
}
handleEmailChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.email = event.target.value;
+ i.state.saveUserSettingsForm.email = Some(event.target.value);
i.setState(i.state);
}
handleBioChange(val: string) {
- this.state.saveUserSettingsForm.bio = val;
+ this.state.saveUserSettingsForm.bio = Some(val);
this.setState(this.state);
}
handleAvatarUpload(url: string) {
- this.state.saveUserSettingsForm.avatar = url;
+ this.state.saveUserSettingsForm.avatar = Some(url);
this.setState(this.state);
}
handleAvatarRemove() {
- this.state.saveUserSettingsForm.avatar = "";
+ this.state.saveUserSettingsForm.avatar = Some("");
this.setState(this.state);
}
handleBannerUpload(url: string) {
- this.state.saveUserSettingsForm.banner = url;
+ this.state.saveUserSettingsForm.banner = Some(url);
this.setState(this.state);
}
handleBannerRemove() {
- this.state.saveUserSettingsForm.banner = "";
+ this.state.saveUserSettingsForm.banner = Some("");
this.setState(this.state);
}
handleDisplayNameChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.display_name = event.target.value;
+ i.state.saveUserSettingsForm.display_name = Some(event.target.value);
i.setState(i.state);
}
handleMatrixUserIdChange(i: Settings, event: any) {
- i.state.saveUserSettingsForm.matrix_user_id = event.target.value;
- if (
- i.state.saveUserSettingsForm.matrix_user_id == "" &&
- !UserService.Instance.myUserInfo.local_user_view.person.matrix_user_id
- ) {
- i.state.saveUserSettingsForm.matrix_user_id = undefined;
- }
+ i.state.saveUserSettingsForm.matrix_user_id = Some(event.target.value);
i.setState(i.state);
}
handleSaveSettingsSubmit(i: Settings, event: any) {
event.preventDefault();
i.state.saveUserSettingsLoading = true;
+ i.state.saveUserSettingsForm.auth = auth().unwrap();
i.setState(i.state);
WebSocketService.Instance.send(
handleChangePasswordSubmit(i: Settings, event: any) {
event.preventDefault();
i.state.changePasswordLoading = true;
+ i.state.changePasswordForm.auth = auth().unwrap();
i.setState(i.state);
WebSocketService.Instance.send(
handleDeleteAccount(i: Settings, event: any) {
event.preventDefault();
i.state.deleteAccountLoading = true;
+ i.state.deleteAccountForm.auth = auth().unwrap();
i.setState(i.state);
WebSocketService.Instance.send(
}
setUserInfo() {
- let luv = UserService.Instance.myUserInfo.local_user_view;
- this.state.saveUserSettingsForm.show_nsfw = luv.local_user.show_nsfw;
- this.state.saveUserSettingsForm.theme = luv.local_user.theme
- ? luv.local_user.theme
- : "browser";
- this.state.saveUserSettingsForm.default_sort_type =
- luv.local_user.default_sort_type;
- this.state.saveUserSettingsForm.default_listing_type =
- luv.local_user.default_listing_type;
- this.state.saveUserSettingsForm.lang = luv.local_user.lang;
- this.state.saveUserSettingsForm.avatar = luv.person.avatar;
- this.state.saveUserSettingsForm.banner = luv.person.banner;
- this.state.saveUserSettingsForm.display_name = luv.person.display_name;
- this.state.saveUserSettingsForm.show_avatars = luv.local_user.show_avatars;
- this.state.saveUserSettingsForm.bot_account = luv.person.bot_account;
- this.state.saveUserSettingsForm.show_bot_accounts =
- luv.local_user.show_bot_accounts;
- this.state.saveUserSettingsForm.show_scores = luv.local_user.show_scores;
- this.state.saveUserSettingsForm.show_read_posts =
- luv.local_user.show_read_posts;
- this.state.saveUserSettingsForm.show_new_post_notifs =
- luv.local_user.show_new_post_notifs;
- this.state.saveUserSettingsForm.email = luv.local_user.email;
- this.state.saveUserSettingsForm.bio = luv.person.bio;
- this.state.saveUserSettingsForm.send_notifications_to_email =
- luv.local_user.send_notifications_to_email;
- this.state.saveUserSettingsForm.matrix_user_id = luv.person.matrix_user_id;
- this.state.personBlocks = UserService.Instance.myUserInfo.person_blocks;
- this.state.communityBlocks =
- UserService.Instance.myUserInfo.community_blocks;
+ UserService.Instance.myUserInfo.match({
+ some: mui => {
+ let luv = mui.local_user_view;
+ this.state.saveUserSettingsForm.show_nsfw = Some(
+ luv.local_user.show_nsfw
+ );
+ this.state.saveUserSettingsForm.theme = Some(
+ luv.local_user.theme ? luv.local_user.theme : "browser"
+ );
+ this.state.saveUserSettingsForm.default_sort_type = Some(
+ luv.local_user.default_sort_type
+ );
+ this.state.saveUserSettingsForm.default_listing_type = Some(
+ luv.local_user.default_listing_type
+ );
+ this.state.saveUserSettingsForm.lang = Some(luv.local_user.lang);
+ this.state.saveUserSettingsForm.avatar = luv.person.avatar;
+ this.state.saveUserSettingsForm.banner = luv.person.banner;
+ this.state.saveUserSettingsForm.display_name = luv.person.display_name;
+ this.state.saveUserSettingsForm.show_avatars = Some(
+ luv.local_user.show_avatars
+ );
+ this.state.saveUserSettingsForm.bot_account = Some(
+ luv.person.bot_account
+ );
+ this.state.saveUserSettingsForm.show_bot_accounts = Some(
+ luv.local_user.show_bot_accounts
+ );
+ this.state.saveUserSettingsForm.show_scores = Some(
+ luv.local_user.show_scores
+ );
+ this.state.saveUserSettingsForm.show_read_posts = Some(
+ luv.local_user.show_read_posts
+ );
+ this.state.saveUserSettingsForm.show_new_post_notifs = Some(
+ luv.local_user.show_new_post_notifs
+ );
+ this.state.saveUserSettingsForm.email = luv.local_user.email;
+ this.state.saveUserSettingsForm.bio = luv.person.bio;
+ this.state.saveUserSettingsForm.send_notifications_to_email = Some(
+ luv.local_user.send_notifications_to_email
+ );
+ this.state.saveUserSettingsForm.matrix_user_id =
+ luv.person.matrix_user_id;
+ this.state.personBlocks = mui.person_blocks;
+ this.state.communityBlocks = mui.community_blocks;
+ },
+ none: void 0,
+ });
}
parseMessage(msg: any) {
toast(i18n.t(msg.error), "danger");
return;
} else if (op == UserOperation.SaveUserSettings) {
- let data = wsJsonToRes<LoginResponse>(msg).data;
+ let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
UserService.Instance.login(data);
this.state.saveUserSettingsLoading = false;
this.setState(this.state);
-
+ toast(i18n.t("saved"));
window.scrollTo(0, 0);
} else if (op == UserOperation.ChangePassword) {
- let data = wsJsonToRes<LoginResponse>(msg).data;
+ let data = wsJsonToRes<LoginResponse>(msg, LoginResponse);
UserService.Instance.login(data);
this.state.changePasswordLoading = false;
this.setState(this.state);
});
UserService.Instance.logout();
window.location.href = "/";
- location.reload();
} else if (op == UserOperation.BlockPerson) {
- let data = wsJsonToRes<BlockPersonResponse>(msg).data;
- this.setState({ personBlocks: updatePersonBlock(data) });
+ let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
+ updatePersonBlock(data).match({
+ some: blocks => this.setState({ personBlocks: blocks }),
+ none: void 0,
+ });
} else if (op == UserOperation.BlockCommunity) {
- let data = wsJsonToRes<BlockCommunityResponse>(msg).data;
- this.setState({ communityBlocks: updateCommunityBlock(data) });
+ let data = wsJsonToRes<BlockCommunityResponse>(
+ msg,
+ BlockCommunityResponse
+ );
+ updateCommunityBlock(data).match({
+ some: blocks => this.setState({ communityBlocks: blocks }),
+ none: void 0,
+ });
}
}
}
+import { None } from "@sniptt/monads/build";
import { Component } from "inferno";
import {
- SiteView,
+ GetSiteResponse,
UserOperation,
VerifyEmail as VerifyEmailForm,
VerifyEmailResponse,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
setIsoData,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
interface State {
verifyEmailForm: VerifyEmailForm;
- site_view: SiteView;
+ siteRes: GetSiteResponse;
}
export class VerifyEmail extends Component<any, State> {
private subscription: Subscription;
emptyState: State = {
- verifyEmailForm: {
+ verifyEmailForm: new VerifyEmailForm({
token: this.props.match.params.token,
- },
- site_view: this.isoData.site_res.site_view,
+ }),
+ siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
}
get documentTitle(): string {
- return `${i18n.t("verify_email")} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${i18n.t("verify_email")} - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<div class="row">
<div class="col-12 col-lg-6 offset-lg-3 mb-4">
this.props.history.push("/");
return;
} else if (op == UserOperation.VerifyEmail) {
- let data = wsJsonToRes<VerifyEmailResponse>(msg).data;
+ let data = wsJsonToRes<VerifyEmailResponse>(msg, VerifyEmailResponse);
if (data) {
toast(i18n.t("email_verified"));
this.state = this.emptyState;
+import { Either, Left, None, Option, Right, Some } from "@sniptt/monads";
import { Component } from "inferno";
import {
- CommunityView,
GetCommunity,
GetCommunityResponse,
+ GetSiteResponse,
ListCommunities,
ListCommunitiesResponse,
ListingType,
PostView,
- SiteView,
SortType,
+ toOption,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { InitialFetchRequest, PostFormParams } from "shared/interfaces";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
+ enableDownvotes,
+ enableNsfw,
fetchLimit,
isBrowser,
setIsoData,
- setOptionalAuth,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { PostForm } from "./post-form";
interface CreatePostState {
- site_view: SiteView;
- communities: CommunityView[];
+ listCommunitiesResponse: Option<ListCommunitiesResponse>;
+ siteRes: GetSiteResponse;
loading: boolean;
}
export class CreatePost extends Component<any, CreatePostState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(this.context, ListCommunitiesResponse);
private subscription: Subscription;
private emptyState: CreatePostState = {
- site_view: this.isoData.site_res.site_view,
- communities: [],
+ siteRes: this.isoData.site_res,
+ listCommunitiesResponse: None,
loading: true,
};
this.handlePostCreate = this.handlePostCreate.bind(this);
this.state = this.emptyState;
- if (!UserService.Instance.myUserInfo && isBrowser()) {
+ if (UserService.Instance.myUserInfo.isNone() && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.communities = this.isoData.routeData[0].communities;
+ this.state.listCommunitiesResponse = Some(
+ this.isoData.routeData[0] as ListCommunitiesResponse
+ );
this.state.loading = false;
} else {
this.refetch();
}
refetch() {
- if (this.params.community_id) {
- let form: GetCommunity = {
- id: this.params.community_id,
- };
- WebSocketService.Instance.send(wsClient.getCommunity(form));
- } else if (this.params.community_name) {
- let form: GetCommunity = {
- name: this.params.community_name,
- };
- WebSocketService.Instance.send(wsClient.getCommunity(form));
- } else {
- let listCommunitiesForm: ListCommunities = {
- type_: ListingType.All,
- sort: SortType.TopAll,
- limit: fetchLimit,
- auth: authField(false),
- };
- WebSocketService.Instance.send(
- wsClient.listCommunities(listCommunitiesForm)
- );
- }
+ 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)
+ );
+ },
+ });
}
componentWillUnmount() {
}
get documentTitle(): string {
- return `${i18n.t("create_post")} - ${this.state.site_view.site.name}`;
+ return this.state.siteRes.site_view.match({
+ some: siteView => `${i18n.t("create_post")} - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
{this.state.loading ? (
<h5>
<Spinner large />
</h5>
) : (
- <div class="row">
- <div class="col-12 col-lg-6 offset-lg-3 mb-4">
- <h5>{i18n.t("create_post")}</h5>
- <PostForm
- communities={this.state.communities}
- onCreate={this.handlePostCreate}
- params={this.params}
- enableDownvotes={this.state.site_view.site.enable_downvotes}
- enableNsfw={this.state.site_view.site.enable_nsfw}
- />
- </div>
- </div>
+ this.state.listCommunitiesResponse.match({
+ some: res => (
+ <div class="row">
+ <div class="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)}
+ />
+ </div>
+ </div>
+ ),
+ none: <></>,
+ })
)}
</div>
);
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 params: PostFormParams = {
- name: urlParams.get("title"),
- community_name: urlParams.get("community_name") || this.prevCommunityName,
- community_id: urlParams.get("community_id")
- ? Number(urlParams.get("community_id")) || this.prevCommunityId
- : null,
- body: urlParams.get("body"),
- url: urlParams.get("url"),
+ name: toOption(urlParams.get("title")),
+ nameOrId,
+ body: toOption(urlParams.get("body")),
+ url: toOption(urlParams.get("url")),
};
return params;
}
- get prevCommunityName(): string {
+ get prevCommunityName(): Option<string> {
if (this.props.match.params.name) {
- return this.props.match.params.name;
+ return toOption(this.props.match.params.name);
} else if (this.props.location.state) {
let lastLocation = this.props.location.state.prevPath;
if (lastLocation.includes("/c/")) {
- return lastLocation.split("/c/")[1];
+ return toOption(lastLocation.split("/c/")[1]);
}
}
- return null;
+ return None;
}
- get prevCommunityId(): number {
+ get prevCommunityId(): Option<number> {
if (this.props.match.params.id) {
- return this.props.match.params.id;
+ return toOption(this.props.match.params.id);
}
- return null;
+ return None;
}
handlePostCreate(post_view: PostView) {
}
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- let listCommunitiesForm: ListCommunities = {
- type_: ListingType.All,
- sort: SortType.TopAll,
- limit: fetchLimit,
- };
- setOptionalAuth(listCommunitiesForm, req.auth);
+ let listCommunitiesForm = new ListCommunities({
+ type_: Some(ListingType.All),
+ sort: Some(SortType.TopAll),
+ limit: Some(fetchLimit),
+ page: None,
+ auth: req.auth,
+ });
return [req.client.listCommunities(listCommunitiesForm)];
}
toast(i18n.t(msg.error), "danger");
return;
} else if (op == UserOperation.ListCommunities) {
- let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
- this.state.communities = data.communities;
+ let data = wsJsonToRes<ListCommunitiesResponse>(
+ msg,
+ ListCommunitiesResponse
+ );
+ this.state.listCommunitiesResponse = Some(data);
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.GetCommunity) {
- let data = wsJsonToRes<GetCommunityResponse>(msg).data;
- this.state.communities = [data.community_view];
+ let data = wsJsonToRes<GetCommunityResponse>(msg, GetCommunityResponse);
+ this.state.listCommunitiesResponse = Some({
+ communities: [data.community_view],
+ });
this.state.loading = false;
this.setState(this.state);
}
let post = this.props.post;
return (
<>
- {post.embed_title && !this.state.expanded && (
- <div class="card border-secondary mt-3 mb-2">
- <div class="row">
- <div class="col-12">
- <div class="card-body">
- {post.name !== post.embed_title && [
- <h5 class="card-title d-inline">
- <a class="text-body" href={post.url} rel={relTags}>
- {post.embed_title}
- </a>
- </h5>,
- <span class="d-inline-block ml-2 mb-2 small text-muted">
- <a
- class="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: post.embed_description,
- }}
- />
- )}
- {post.embed_html && (
- <button
- class="mt-2 btn btn-secondary text-monospace"
- onClick={linkEvent(this, this.handleIframeExpand)}
- data-tippy-content={i18n.t("expand_here")}
- >
- {this.state.expanded ? "-" : "+"}
- </button>
- )}
- </div>
- </div>
- </div>
- </div>
- )}
- {this.state.expanded && (
- <div
- class="mt-3 mb-2"
- dangerouslySetInnerHTML={{ __html: post.embed_html }}
- />
- )}
+ {!this.state.expanded &&
+ post.embed_title.match({
+ some: embedTitle =>
+ post.url.match({
+ some: url => (
+ <div class="card border-secondary mt-3 mb-2">
+ <div class="row">
+ <div class="col-12">
+ <div class="card-body">
+ {post.name !== embedTitle && [
+ <h5 class="card-title d-inline">
+ <a class="text-body" href={url} rel={relTags}>
+ {embedTitle}
+ </a>
+ </h5>,
+ <span class="d-inline-block ml-2 mb-2 small text-muted">
+ <a
+ class="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: desc,
+ }}
+ />
+ ),
+ none: <></>,
+ })}
+ {post.embed_html.isSome() && (
+ <button
+ class="mt-2 btn btn-secondary text-monospace"
+ onClick={linkEvent(this, this.handleIframeExpand)}
+ data-tippy-content={i18n.t("expand_here")}
+ >
+ {this.state.expanded ? "-" : "+"}
+ </button>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ ),
+ none: <></>,
+ }),
+ none: <></>,
+ })}
+ {this.state.expanded &&
+ post.embed_html.match({
+ some: html => (
+ <div
+ class="mt-3 mb-2"
+ dangerouslySetInnerHTML={{ __html: html }}
+ />
+ ),
+ none: <></>,
+ })}
</>
);
}
+import { None, Option, Some } from "@sniptt/monads";
import autosize from "autosize";
import { Component, linkEvent } from "inferno";
import { Prompt } from "inferno-router";
SearchResponse,
SearchType,
SortType,
+ toUndefined,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { pictrsUri } from "../../env";
import { UserService, WebSocketService } from "../../services";
import {
archiveTodayUrl,
- authField,
+ auth,
capitalizeFirstLetter,
choicesConfig,
communitySelectName,
relTags,
setupTippy,
toast,
+ trendingFetchLimit,
validTitle,
validURL,
webArchiveUrl,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
const MAX_POST_TITLE_LENGTH = 200;
interface PostFormProps {
- post_view?: PostView; // If a post is given, that means this is an edit
- communities?: CommunityView[];
- params?: PostFormParams;
+ post_view: Option<PostView>; // If a post is given, that means this is an edit
+ communities: Option<CommunityView[]>;
+ params: Option<PostFormParams>;
onCancel?(): any;
onCreate?(post: PostView): any;
onEdit?(post: PostView): any;
- enableNsfw: boolean;
- enableDownvotes: boolean;
+ enableNsfw?: boolean;
+ enableDownvotes?: boolean;
}
interface PostFormState {
postForm: CreatePost;
+ suggestedTitle: Option<string>;
+ suggestedPosts: Option<PostView[]>;
+ crossPosts: Option<PostView[]>;
loading: boolean;
imageLoading: boolean;
previewMode: boolean;
- suggestedTitle: string;
- suggestedPosts: PostView[];
- crossPosts: PostView[];
}
export class PostForm extends Component<PostFormProps, PostFormState> {
private subscription: Subscription;
private choices: any;
private emptyState: PostFormState = {
- postForm: {
- community_id: null,
- name: null,
- nsfw: false,
- auth: authField(false),
- },
+ postForm: new CreatePost({
+ community_id: undefined,
+ name: undefined,
+ nsfw: Some(false),
+ url: None,
+ body: None,
+ honeypot: None,
+ auth: undefined,
+ }),
loading: false,
imageLoading: false,
previewMode: false,
- suggestedTitle: undefined,
- suggestedPosts: [],
- crossPosts: [],
+ suggestedTitle: None,
+ suggestedPosts: None,
+ crossPosts: None,
};
constructor(props: any, context: any) {
this.state = this.emptyState;
// Means its an edit
- if (this.props.post_view) {
- this.state.postForm = {
- body: this.props.post_view.post.body,
- name: this.props.post_view.post.name,
- community_id: this.props.post_view.community.id,
- url: this.props.post_view.post.url,
- nsfw: this.props.post_view.post.nsfw,
- auth: authField(),
- };
- }
-
- if (this.props.params) {
- this.state.postForm.name = this.props.params.name;
- if (this.props.params.url) {
- this.state.postForm.url = this.props.params.url;
- }
- if (this.props.params.body) {
- this.state.postForm.body = this.props.params.body;
- }
- }
+ this.props.post_view.match({
+ some: pv =>
+ (this.state.postForm = new CreatePost({
+ body: pv.post.body,
+ name: pv.post.name,
+ community_id: pv.community.id,
+ url: pv.post.url,
+ nsfw: Some(pv.post.nsfw),
+ honeypot: None,
+ auth: auth().unwrap(),
+ })),
+ none: void 0,
+ });
+
+ this.props.params.match({
+ some: params => {
+ this.state.postForm.name = toUndefined(params.name);
+ this.state.postForm.url = params.url;
+ this.state.postForm.body = params.body;
+ },
+ none: void 0,
+ });
this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage);
if (
!this.state.loading &&
(this.state.postForm.name ||
- this.state.postForm.url ||
- this.state.postForm.body)
+ this.state.postForm.url.isSome() ||
+ this.state.postForm.body.isSome())
) {
window.onbeforeunload = () => true;
} else {
when={
!this.state.loading &&
(this.state.postForm.name ||
- this.state.postForm.url ||
- this.state.postForm.body)
+ this.state.postForm.url.isSome() ||
+ this.state.postForm.body.isSome())
}
message={i18n.t("block_leaving")}
/>
type="url"
id="post-url"
class="form-control"
- value={this.state.postForm.url}
+ value={toUndefined(this.state.postForm.url)}
onInput={linkEvent(this, this.handlePostUrlChange)}
onPaste={linkEvent(this, this.handleImageUploadPaste)}
/>
- {this.state.suggestedTitle && (
- <div
- class="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>
- )}
+ {this.state.suggestedTitle.match({
+ some: title => (
+ <div
+ class="mt-1 text-muted small font-weight-bold pointer"
+ role="button"
+ onClick={linkEvent(this, this.copySuggestedTitle)}
+ >
+ {i18n.t("copy_suggested_title", {
+ title,
+ })}
+ </div>
+ ),
+ none: <></>,
+ })}
<form>
<label
htmlFor="file-upload"
className={`${
- UserService.Instance.myUserInfo && "pointer"
+ UserService.Instance.myUserInfo.isSome() && "pointer"
} d-inline-block float-right text-muted font-weight-bold`}
data-tippy-content={i18n.t("upload_image")}
>
accept="image/*,video/*"
name="file"
class="d-none"
- disabled={!UserService.Instance.myUserInfo}
+ disabled={UserService.Instance.myUserInfo.isNone()}
onChange={linkEvent(this, this.handleImageUpload)}
/>
</form>
- {this.state.postForm.url && validURL(this.state.postForm.url) && (
- <div>
- <a
- href={`${webArchiveUrl}/save/${encodeURIComponent(
- this.state.postForm.url
- )}`}
- class="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(
- this.state.postForm.url
- )}`}
- class="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(
- this.state.postForm.url
- )}`}
- class="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.postForm.url.match({
+ some: url =>
+ validURL(url) && (
+ <div>
+ <a
+ href={`${webArchiveUrl}/save/${encodeURIComponent(
+ url
+ )}`}
+ class="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
+ )}`}
+ class="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
+ )}`}
+ class="mr-2 d-inline-block float-right text-muted small font-weight-bold"
+ rel={relTags}
+ >
+ archive.today {i18n.t("archive_link")}
+ </a>
+ </div>
+ ),
+ none: <></>,
+ })}
{this.state.imageLoading && <Spinner />}
- {isImage(this.state.postForm.url) && (
- <img src={this.state.postForm.url} class="img-fluid" alt="" />
- )}
- {this.state.crossPosts.length > 0 && (
- <>
- <div class="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}
- />
- </>
- )}
+ {this.state.postForm.url.match({
+ some: url =>
+ isImage(url) && <img src={url} class="img-fluid" alt="" />,
+ none: <></>,
+ })}
+ {this.state.crossPosts.match({
+ some: xPosts =>
+ xPosts.length > 0 && (
+ <>
+ <div class="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}
+ />
+ </>
+ ),
+ none: <></>,
+ })}
</div>
</div>
<div class="form-group row">
{i18n.t("invalid_post_title")}
</div>
)}
- {this.state.suggestedPosts.length > 0 && (
- <>
- <div class="my-1 text-muted small font-weight-bold">
- {i18n.t("related_posts")}
- </div>
- <PostListings
- posts={this.state.suggestedPosts}
- enableDownvotes={this.props.enableDownvotes}
- enableNsfw={this.props.enableNsfw}
- />
- </>
- )}
+ {this.state.suggestedPosts.match({
+ some: sPosts =>
+ sPosts.length > 0 && (
+ <>
+ <div class="my-1 text-muted small font-weight-bold">
+ {i18n.t("related_posts")}
+ </div>
+ <PostListings
+ posts={sPosts}
+ enableDownvotes={this.props.enableDownvotes}
+ enableNsfw={this.props.enableNsfw}
+ />
+ </>
+ ),
+ none: <></>,
+ })}
</div>
</div>
<MarkdownTextArea
initialContent={this.state.postForm.body}
onContentChange={this.handlePostBodyChange}
+ placeholder={None}
+ buttonTitle={None}
+ maxLength={None}
/>
</div>
</div>
- {!this.props.post_view && (
+ {this.props.post_view.isNone() && (
<div class="form-group row">
<label class="col-sm-2 col-form-label" htmlFor="post-community">
{i18n.t("community")}
onInput={linkEvent(this, this.handlePostCommunityChange)}
>
<option>{i18n.t("select_a_community")}</option>
- {this.props.communities.map(cv => (
+ {this.props.communities.unwrapOr([]).map(cv => (
<option value={cv.community.id}>
{communitySelectName(cv)}
</option>
class="form-check-input position-static"
id="post-nsfw"
type="checkbox"
- checked={this.state.postForm.nsfw}
+ checked={toUndefined(this.state.postForm.nsfw)}
onChange={linkEvent(this, this.handlePostNsfwChange)}
/>
</div>
type="text"
class="form-control honeypot"
id="register-honey"
- value={this.state.postForm.honeypot}
+ value={toUndefined(this.state.postForm.honeypot)}
onInput={linkEvent(this, this.handleHoneyPotChange)}
/>
<div class="form-group row">
>
{this.state.loading ? (
<Spinner />
- ) : this.props.post_view ? (
+ ) : this.props.post_view.isSome() ? (
capitalizeFirstLetter(i18n.t("save"))
) : (
capitalizeFirstLetter(i18n.t("create"))
)}
</button>
- {this.props.post_view && (
+ {this.props.post_view.isSome() && (
<button
type="button"
class="btn btn-secondary"
event.preventDefault();
// Coerce empty url string to undefined
- if (i.state.postForm.url !== undefined && i.state.postForm.url === "") {
- i.state.postForm.url = undefined;
+ if (
+ i.state.postForm.url.isSome() &&
+ i.state.postForm.url.unwrapOr("blank") === ""
+ ) {
+ i.state.postForm.url = None;
}
- if (i.props.post_view) {
- let form: EditPost = {
- ...i.state.postForm,
- post_id: i.props.post_view.post.id,
- };
- WebSocketService.Instance.send(wsClient.editPost(form));
- } else {
- WebSocketService.Instance.send(wsClient.createPost(i.state.postForm));
- }
+ let pForm = i.state.postForm;
+ i.props.post_view.match({
+ some: pv => {
+ let form = new EditPost({
+ name: Some(pForm.name),
+ url: pForm.url,
+ body: pForm.body,
+ nsfw: pForm.nsfw,
+ post_id: pv.post.id,
+ auth: auth().unwrap(),
+ });
+ WebSocketService.Instance.send(wsClient.editPost(form));
+ },
+ none: () => {
+ i.state.postForm.auth = auth().unwrap();
+ WebSocketService.Instance.send(wsClient.createPost(i.state.postForm));
+ },
+ });
i.state.loading = true;
i.setState(i.state);
}
copySuggestedTitle(i: PostForm) {
- i.state.postForm.name = i.state.suggestedTitle.substring(
- 0,
- MAX_POST_TITLE_LENGTH
- );
- i.state.suggestedTitle = undefined;
- setTimeout(() => {
- let textarea: any = document.getElementById("post-title");
- autosize.update(textarea);
- }, 10);
- i.setState(i.state);
+ i.state.suggestedTitle.match({
+ some: sTitle => {
+ i.state.postForm.name = sTitle.substring(0, MAX_POST_TITLE_LENGTH);
+ i.state.suggestedTitle = None;
+ setTimeout(() => {
+ let textarea: any = document.getElementById("post-title");
+ autosize.update(textarea);
+ }, 10);
+ i.setState(i.state);
+ },
+ none: void 0,
+ });
}
handlePostUrlChange(i: PostForm, event: any) {
- i.state.postForm.url = event.target.value;
+ i.state.postForm.url = Some(event.target.value);
i.setState(i.state);
i.fetchPageTitle();
}
fetchPageTitle() {
- if (validURL(this.state.postForm.url)) {
- let form: Search = {
- q: this.state.postForm.url,
- type_: SearchType.Url,
- sort: SortType.TopAll,
- listing_type: ListingType.All,
- page: 1,
- limit: 6,
- auth: authField(false),
- };
-
- WebSocketService.Instance.send(wsClient.search(form));
-
- // Fetch the page title
- getSiteMetadata(this.state.postForm.url).then(d => {
- this.state.suggestedTitle = d.metadata.title;
- this.setState(this.state);
- });
- } else {
- this.state.suggestedTitle = undefined;
- this.state.crossPosts = [];
- }
+ 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.state.suggestedTitle = d.metadata.title;
+ this.setState(this.state);
+ });
+ } else {
+ this.state.suggestedTitle = None;
+ this.state.crossPosts = None;
+ }
+ },
+ none: void 0,
+ });
}
handlePostNameChange(i: PostForm, event: any) {
}
fetchSimilarPosts() {
- let form: Search = {
+ let form = new Search({
q: this.state.postForm.name,
- type_: SearchType.Posts,
- sort: SortType.TopAll,
- listing_type: ListingType.All,
- community_id: this.state.postForm.community_id,
- page: 1,
- limit: 6,
- auth: authField(false),
- };
+ 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 !== "") {
WebSocketService.Instance.send(wsClient.search(form));
} else {
- this.state.suggestedPosts = [];
+ this.state.suggestedPosts = None;
}
this.setState(this.state);
}
handlePostBodyChange(val: string) {
- this.state.postForm.body = val;
+ this.state.postForm.body = Some(val);
this.setState(this.state);
}
}
handlePostNsfwChange(i: PostForm, event: any) {
- i.state.postForm.nsfw = event.target.checked;
+ i.state.postForm.nsfw = Some(event.target.checked);
i.setState(i.state);
}
handleHoneyPotChange(i: PostForm, event: any) {
- i.state.postForm.honeypot = event.target.value;
+ i.state.postForm.honeypot = Some(event.target.value);
i.setState(i.state);
}
let url = `${pictrsUri}/${hash}`;
let deleteToken = res.files[0].delete_token;
let deleteUrl = `${pictrsUri}/delete/${deleteToken}/${hash}`;
- i.state.postForm.url = url;
+ i.state.postForm.url = Some(url);
i.state.imageLoading = false;
i.setState(i.state);
pictrsDeleteToast(
}
}
- if (this.props.post_view) {
- this.state.postForm.community_id = this.props.post_view.community.id;
- } else if (
- this.props.params &&
- (this.props.params.community_id || this.props.params.community_name)
- ) {
- if (this.props.params.community_name) {
- let foundCommunityId = this.props.communities.find(
- r => r.community.name == this.props.params.community_name
- ).community.id;
- this.state.postForm.community_id = foundCommunityId;
- } else if (this.props.params.community_id) {
- this.state.postForm.community_id = this.props.params.community_id;
- }
-
- if (isBrowser()) {
- this.choices.setChoiceByValue(
- this.state.postForm.community_id.toString()
- );
- }
- this.setState(this.state);
- } else {
- // By default, the null valued 'Select a Community'
+ this.props.post_view.match({
+ some: pv => (this.state.postForm.community_id = pv.community.id),
+ 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.state.postForm.community_id = foundCommunityId;
+ },
+ right: id => (this.state.postForm.community_id = id),
+ }),
+ none: void 0,
+ }),
+ none: void 0,
+ });
+
+ if (isBrowser() && this.state.postForm.community_id) {
+ this.choices.setChoiceByValue(
+ this.state.postForm.community_id.toString()
+ );
}
+ this.setState(this.state);
}
parseMessage(msg: any) {
this.setState(this.state);
return;
} else if (op == UserOperation.CreatePost) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- if (
- data.post_view.creator.id ==
- UserService.Instance.myUserInfo.local_user_view.person.id
- ) {
- this.state.loading = false;
- this.props.onCreate(data.post_view);
- }
+ 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.state.loading = false;
+ this.props.onCreate(data.post_view);
+ }
+ },
+ none: void 0,
+ });
} else if (op == UserOperation.EditPost) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- if (
- data.post_view.creator.id ==
- UserService.Instance.myUserInfo.local_user_view.person.id
- ) {
- this.state.loading = false;
- this.props.onEdit(data.post_view);
- }
+ 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.state.loading = false;
+ this.props.onEdit(data.post_view);
+ }
+ },
+ none: void 0,
+ });
} else if (op == UserOperation.Search) {
- let data = wsJsonToRes<SearchResponse>(msg).data;
+ let data = wsJsonToRes<SearchResponse>(msg, SearchResponse);
if (data.type_ == SearchType[SearchType.Posts]) {
- this.state.suggestedPosts = data.posts;
+ this.state.suggestedPosts = Some(data.posts);
} else if (data.type_ == SearchType[SearchType.Url]) {
- this.state.crossPosts = data.posts;
+ this.state.crossPosts = Some(data.posts);
}
this.setState(this.state);
}
+import { None, Option, Some } from "@sniptt/monads";
import classNames from "classnames";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
RemovePost,
SavePost,
StickyPost,
+ toUndefined,
TransferCommunity,
} from "lemmy-js-client";
import { externalHost } from "../../env";
import { BanType } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ amCommunityCreator,
+ auth,
+ canAdmin,
canMod,
futureDaysToUnixTime,
hostname,
+ isAdmin,
isBanned,
isImage,
isMod,
interface PostListingState {
showEdit: boolean;
showRemoveDialog: boolean;
- removeReason: string;
+ removeReason: Option<string>;
showBanDialog: boolean;
- banReason: string;
- banExpireDays: number;
+ banReason: Option<string>;
+ banExpireDays: Option<number>;
banType: BanType;
removeData: boolean;
showConfirmTransferSite: boolean;
showMoreMobile: boolean;
showBody: boolean;
showReportDialog: boolean;
- reportReason: string;
- my_vote: number;
+ reportReason: Option<string>;
+ my_vote: Option<number>;
score: number;
upvotes: number;
downvotes: number;
interface PostListingProps {
post_view: PostView;
- duplicates?: PostView[];
+ duplicates: Option<PostView[]>;
+ moderators: Option<CommunityModeratorView[]>;
+ admins: Option<PersonViewSafe[]>;
showCommunity?: boolean;
showBody?: boolean;
- moderators?: CommunityModeratorView[];
- admins?: PersonViewSafe[];
- enableDownvotes: boolean;
- enableNsfw: boolean;
+ enableDownvotes?: boolean;
+ enableNsfw?: boolean;
viewOnly?: boolean;
}
private emptyState: PostListingState = {
showEdit: false,
showRemoveDialog: false,
- removeReason: null,
+ removeReason: None,
showBanDialog: false,
- banReason: null,
- banExpireDays: null,
+ banReason: None,
+ banExpireDays: None,
banType: BanType.Community,
removeData: false,
showConfirmTransferSite: false,
showMoreMobile: false,
showBody: false,
showReportDialog: false,
- reportReason: null,
+ reportReason: None,
my_vote: this.props.post_view.my_vote,
score: this.props.post_view.counts.score,
upvotes: this.props.post_view.counts.upvotes,
<>
{this.listing()}
{this.state.imageExpanded && this.img}
- {post.url && this.showBody && post.embed_title && (
- <MetadataCard post={post} />
- )}
- {this.showBody && post.body && this.body()}
+ {post.url.isSome() &&
+ this.showBody &&
+ post.embed_title.isSome() && <MetadataCard post={post} />}
+ {this.showBody && this.body()}
</>
) : (
<div class="col-12">
<PostForm
- post_view={this.props.post_view}
+ post_view={Some(this.props.post_view)}
+ communities={None}
+ params={None}
onEdit={this.handleEditPost}
onCancel={this.handleEditCancel}
enableNsfw={this.props.enableNsfw}
}
body() {
- let post = this.props.post_view.post;
- return (
- <div class="col-12 card my-2 p-2">
- {this.state.viewSource ? (
- <pre>{post.body}</pre>
- ) : (
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(post.body)}
- />
- )}
- </div>
- );
+ return this.props.post_view.post.body.match({
+ some: body => (
+ <div class="col-12 card my-2 p-2">
+ {this.state.viewSource ? (
+ <pre>{body}</pre>
+ ) : (
+ <div className="md-div" dangerouslySetInnerHTML={mdToHtml(body)} />
+ )}
+ </div>
+ ),
+ none: <></>,
+ });
}
get img() {
- return (
- <>
- <div class="offset-sm-3 my-2 d-none d-sm-block">
- <a href={this.imageSrc} class="d-inline-block">
- <PictrsImage src={this.imageSrc} />
- </a>
- </div>
- <div className="my-2 d-block d-sm-none">
- <a
- class="d-inline-block"
- onClick={linkEvent(this, this.handleImageExpandClick)}
- >
- <PictrsImage src={this.imageSrc} />
- </a>
- </div>
- </>
- );
+ return this.imageSrc.match({
+ some: src => (
+ <>
+ <div class="offset-sm-3 my-2 d-none d-sm-block">
+ <a href={src} class="d-inline-block">
+ <PictrsImage src={src} />
+ </a>
+ </div>
+ <div className="my-2 d-block d-sm-none">
+ <a
+ class="d-inline-block"
+ onClick={linkEvent(this, this.handleImageExpandClick)}
+ >
+ <PictrsImage src={src} />
+ </a>
+ </div>
+ </>
+ ),
+ none: <></>,
+ });
}
imgThumb(src: string) {
);
}
- get imageSrc(): string {
+ get imageSrc(): Option<string> {
let post = this.props.post_view.post;
- if (isImage(post.url)) {
- if (post.url.includes("pictrs")) {
- return post.url;
- } else if (post.thumbnail_url) {
- return post.thumbnail_url;
+ let url = post.url;
+ let thumbnail = post.thumbnail_url;
+
+ if (url.isSome() && isImage(url.unwrap())) {
+ if (url.unwrap().includes("pictrs")) {
+ return url;
+ } else if (thumbnail.isSome()) {
+ return thumbnail;
} else {
- return post.url;
+ return url;
}
- } else if (post.thumbnail_url) {
- return post.thumbnail_url;
+ } else if (thumbnail.isSome()) {
+ return thumbnail;
} else {
- return null;
+ return None;
}
}
thumbnail() {
let post = this.props.post_view.post;
+ let url = post.url;
+ let thumbnail = post.thumbnail_url;
- if (isImage(post.url)) {
+ if (url.isSome() && isImage(url.unwrap())) {
return (
<a
- href={this.imageSrc}
+ href={this.imageSrc.unwrap()}
class="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)}
+ {this.imgThumb(this.imageSrc.unwrap())}
<Icon icon="image" classes="mini-overlay" />
</a>
);
- } else if (post.thumbnail_url) {
+ } else if (url.isSome() && thumbnail.isSome()) {
return (
<a
class="text-body d-inline-block position-relative mb-2"
- href={post.url}
+ href={url.unwrap()}
rel={relTags}
- title={post.url}
+ title={url.unwrap()}
>
- {this.imgThumb(this.imageSrc)}
+ {this.imgThumb(this.imageSrc.unwrap())}
<Icon icon="external-link" classes="mini-overlay" />
</a>
);
- } else if (post.url) {
- if (isVideo(post.url)) {
+ } else if (url.isSome()) {
+ if (isVideo(url.unwrap())) {
return (
<div class="embed-responsive embed-responsive-16by9">
<video
controls
class="embed-responsive-item"
>
- <source src={post.url} type="video/mp4" />
+ <source src={url.unwrap()} type="video/mp4" />
</video>
</div>
);
return (
<a
className="text-body"
- href={post.url}
- title={post.url}
+ href={url.unwrap()}
+ title={url.unwrap()}
rel={relTags}
>
<div class="thumbnail rounded bg-light d-flex justify-content-center">
<li className="list-inline-item">
<PersonListing person={post_view.creator} />
- {this.creatorIsMod && (
+ {this.creatorIsMod_ && (
<span className="mx-1 badge badge-light">{i18n.t("mod")}</span>
)}
- {this.creatorIsAdmin && (
+ {this.creatorIsAdmin_ && (
<span className="mx-1 badge badge-light">{i18n.t("admin")}</span>
)}
{post_view.creator.bot_account && (
)}
</li>
<li className="list-inline-item">•</li>
- {post_view.post.url && !(hostname(post_view.post.url) == externalHost) && (
- <>
- <li className="list-inline-item">
- <a
- className="text-muted font-italic"
- href={post_view.post.url}
- title={post_view.post.url}
- rel={relTags}
- >
- {hostname(post_view.post.url)}
- </a>
- </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: <></>,
+ })}
<li className="list-inline-item">
<span>
- <MomentTime data={post_view.post} />
+ <MomentTime
+ published={post_view.post.published}
+ updated={post_view.post.updated}
+ />
</span>
</li>
- {post_view.post.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={md.render(post_view.post.body)}
- data-tippy-allowHtml={true}
- onClick={linkEvent(this, this.handleShowBody)}
- >
- <Icon icon="book-open" classes="icon-inline mr-1" />
- </button>
- </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={md.render(body)}
+ data-tippy-allowHtml={true}
+ onClick={linkEvent(this, this.handleShowBody)}
+ >
+ <Icon icon="book-open" classes="icon-inline mr-1" />
+ </button>
+ </li>
+ </>
+ ),
+ none: <></>,
+ })}
</ul>
);
}
<div className={`vote-bar col-1 pr-0 small text-center`}>
<button
className={`btn-animate btn btn-link p-0 ${
- this.state.my_vote == 1 ? "text-info" : "text-muted"
+ this.state.my_vote.unwrapOr(0) == 1 ? "text-info" : "text-muted"
}`}
onClick={linkEvent(this, this.handlePostLike)}
data-tippy-content={i18n.t("upvote")}
{this.props.enableDownvotes && (
<button
className={`btn-animate btn btn-link p-0 ${
- this.state.my_vote == -1 ? "text-danger" : "text-muted"
+ this.state.my_vote.unwrapOr(0) == -1
+ ? "text-danger"
+ : "text-muted"
}`}
onClick={linkEvent(this, this.handlePostDisLike)}
data-tippy-content={i18n.t("downvote")}
return (
<div className="post-title overflow-hidden">
<h5>
- {this.showBody && post.url ? (
- <a
- className={!post.stickied ? "text-body" : "text-primary"}
- href={post.url}
- title={post.url}
- rel={relTags}
- >
- {post.name}
- </a>
- ) : (
- <Link
- className={!post.stickied ? "text-body" : "text-primary"}
- to={`/post/${post.id}`}
- title={i18n.t("comments")}
- >
- {post.name}
- </Link>
- )}
- {(isImage(post.url) || post.thumbnail_url) && (
+ {post.url.match({
+ some: url => (
+ <a
+ className={!post.stickied ? "text-body" : "text-primary"}
+ href={url}
+ title={url}
+ rel={relTags}
+ >
+ {post.name}
+ </a>
+ ),
+ none: (
+ <Link
+ className={!post.stickied ? "text-body" : "text-primary"}
+ to={`/post/${post.id}`}
+ title={i18n.t("comments")}
+ >
+ {post.name}
+ </Link>
+ ),
+ })}
+ {post.url.map(isImage).or(post.thumbnail_url).unwrapOr(false) && (
<button
class="btn btn-link text-monospace text-muted small d-inline-block ml-2"
data-tippy-content={i18n.t("expand_here")}
}
duplicatesLine() {
- let dupes = this.props.duplicates;
- return (
- dupes &&
- dupes.length > 0 && (
- <ul class="list-inline mb-1 small text-muted">
- <>
- <li className="list-inline-item mr-2">
- {i18n.t("cross_posted_to")}
- </li>
- {dupes.map(pv => (
+ return this.props.duplicates.match({
+ some: dupes =>
+ dupes.length > 0 && (
+ <ul class="list-inline mb-1 small text-muted">
+ <>
<li 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>
+ {i18n.t("cross_posted_to")}
</li>
- ))}
- </>
- </ul>
- )
- );
+ {dupes.map(pv => (
+ <li 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: <></>,
+ });
}
commentsLine(mobile = false) {
</a>
)}
{mobile && !this.props.viewOnly && this.mobileVotes}
- {UserService.Instance.myUserInfo &&
+ {UserService.Instance.myUserInfo.isSome() &&
!this.props.viewOnly &&
this.postActions(mobile)}
</div>
)}
{this.state.showAdvanced && (
<>
- {this.showBody && post_view.post.body && this.viewSourceButton}
- {this.canModOnSelf && (
+ {this.showBody &&
+ post_view.post.body.isSome() &&
+ this.viewSourceButton}
+ {this.canModOnSelf_ && (
<>
{this.lockButton}
{this.stickyButton}
</>
)}
- {(this.canMod || this.canAdmin || true) && (
- <>{this.modRemoveButton}</>
- )}
+ {(this.canMod_ || this.canAdmin_) && <>{this.modRemoveButton}</>}
</>
)}
{!mobile && this.showMoreButton}
<div>
<button
className={`btn-animate btn py-0 px-1 ${
- this.state.my_vote == 1 ? "text-info" : "text-muted"
+ this.state.my_vote.unwrapOr(0) == 1 ? "text-info" : "text-muted"
}`}
{...tippy}
onClick={linkEvent(this, this.handlePostLike)}
{this.props.enableDownvotes && (
<button
className={`ml-2 btn-animate btn py-0 px-1 ${
- this.state.my_vote == -1 ? "text-danger" : "text-muted"
+ this.state.my_vote.unwrapOr(0) == -1
+ ? "text-danger"
+ : "text-muted"
}`}
onClick={linkEvent(this, this.handlePostDisLike)}
{...tippy}
return (
this.state.showAdvanced && (
<>
- {this.canMod && (
+ {this.canMod_ && (
<>
- {!this.creatorIsMod &&
+ {!this.creatorIsMod_ &&
(!post_view.creator_banned_from_community ? (
<button
class="btn btn-link btn-animate text-muted py-0"
class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleAddModToCommunity)}
aria-label={
- this.creatorIsMod
+ this.creatorIsMod_
? i18n.t("remove_as_mod")
: i18n.t("appoint_as_mod")
}
>
- {this.creatorIsMod
+ {this.creatorIsMod_
? i18n.t("remove_as_mod")
: i18n.t("appoint_as_mod")}
</button>
</>
)}
{/* Community creators and admins can transfer community to another mod */}
- {(this.amCommunityCreator || this.canAdmin) &&
- this.creatorIsMod &&
+ {(amCommunityCreator(this.props.moderators, post_view.creator.id) ||
+ this.canAdmin_) &&
+ this.creatorIsMod_ &&
(!this.state.showConfirmTransferCommunity ? (
<button
class="btn btn-link btn-animate text-muted py-0"
</>
))}
{/* Admins can ban from all, and appoint other admins */}
- {this.canAdmin && (
+ {this.canAdmin_ && (
<>
- {!this.creatorIsAdmin &&
+ {!this.creatorIsAdmin_ &&
(!isBanned(post_view.creator) ? (
<button
class="btn btn-link btn-animate text-muted py-0"
class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleAddAdmin)}
aria-label={
- this.creatorIsAdmin
+ this.creatorIsAdmin_
? i18n.t("remove_as_admin")
: i18n.t("appoint_as_admin")
}
>
- {this.creatorIsAdmin
+ {this.creatorIsAdmin_
? i18n.t("remove_as_admin")
: i18n.t("appoint_as_admin")}
</button>
id="post-listing-remove-reason"
class="form-control mr-2"
placeholder={i18n.t("reason")}
- value={this.state.removeReason}
+ value={toUndefined(this.state.removeReason)}
onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/>
<button
id="post-listing-ban-reason"
class="form-control mr-2"
placeholder={i18n.t("reason")}
- value={this.state.banReason}
+ value={toUndefined(this.state.banReason)}
onInput={linkEvent(this, this.handleModBanReasonChange)}
/>
<label class="col-form-label" htmlFor={`mod-ban-expires`}>
id={`mod-ban-expires`}
class="form-control mr-2"
placeholder={i18n.t("number_of_days")}
- value={this.state.banExpireDays}
+ value={toUndefined(this.state.banExpireDays)}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/>
<div class="form-group">
class="form-control mr-2"
placeholder={i18n.t("reason")}
required
- value={this.state.reportReason}
+ value={toUndefined(this.state.reportReason)}
onInput={linkEvent(this, this.handleReportReasonChange)}
/>
<button
mobileThumbnail() {
let post = this.props.post_view.post;
- return post.thumbnail_url || isImage(post.url) ? (
+ return post.thumbnail_url.isSome() ||
+ post.url.map(isImage).unwrapOr(false) ? (
<div class="row">
<div className={`${this.state.imageExpanded ? "col-12" : "col-8"}`}>
{this.postTitleLine()}
showMobilePreview() {
let post = this.props.post_view.post;
return (
- post.body &&
- !this.showBody && (
- <div className="md-div mb-1 preview-lines">{post.body}</div>
- )
+ !this.showBody &&
+ post.body.match({
+ some: body => <div className="md-div mb-1 preview-lines">{body}</div>,
+ none: <></>,
+ })
);
}
}
private get myPost(): boolean {
- return (
- UserService.Instance.myUserInfo &&
- this.props.post_view.creator.id ==
- UserService.Instance.myUserInfo.local_user_view.person.id
- );
- }
-
- get creatorIsMod(): boolean {
- return (
- this.props.moderators &&
- isMod(
- this.props.moderators.map(m => m.moderator.id),
- this.props.post_view.creator.id
- )
- );
- }
-
- get creatorIsAdmin(): boolean {
- return (
- this.props.admins &&
- isMod(
- this.props.admins.map(a => a.person.id),
- this.props.post_view.creator.id
- )
- );
- }
-
- /**
- * If the current user is allowed to mod this post.
- * The creator of this post is not allowed even if they are a mod.
- */
- get canMod(): boolean {
- if (this.props.admins && this.props.moderators) {
- let adminsThenMods = this.props.admins
- .map(a => a.person.id)
- .concat(this.props.moderators.map(m => m.moderator.id));
-
- return canMod(
- UserService.Instance.myUserInfo,
- adminsThenMods,
- this.props.post_view.creator.id
- );
- } else {
- return false;
- }
- }
-
- /**
- * If the current user is allowed to mod this post.
- * The creator of this post is allowed if they are a mod.
- */
- get canModOnSelf(): boolean {
- if (this.props.admins && this.props.moderators) {
- let adminsThenMods = this.props.admins
- .map(a => a.person.id)
- .concat(this.props.moderators.map(m => m.moderator.id));
-
- return canMod(
- UserService.Instance.myUserInfo,
- adminsThenMods,
- this.props.post_view.creator.id,
- true
- );
- } else {
- return false;
- }
- }
-
- get canAdmin(): boolean {
- return (
- this.props.admins &&
- canMod(
- UserService.Instance.myUserInfo,
- this.props.admins.map(a => a.person.id),
- this.props.post_view.creator.id
- )
- );
- }
-
- get amCommunityCreator(): boolean {
- return (
- this.props.moderators &&
- UserService.Instance.myUserInfo &&
- this.props.post_view.creator.id !=
- UserService.Instance.myUserInfo.local_user_view.person.id &&
- UserService.Instance.myUserInfo.local_user_view.person.id ==
- this.props.moderators[0].moderator.id
- );
- }
-
- get amSiteCreator(): boolean {
- return (
- this.props.admins &&
- UserService.Instance.myUserInfo &&
- this.props.post_view.creator.id !=
- UserService.Instance.myUserInfo.local_user_view.person.id &&
- UserService.Instance.myUserInfo.local_user_view.person.id ==
- this.props.admins[0].person.id
- );
+ return UserService.Instance.myUserInfo.match({
+ some: mui =>
+ this.props.post_view.creator.id == mui.local_user_view.person.id,
+ none: false,
+ });
}
handlePostLike(i: PostListing, event: any) {
event.preventDefault();
- if (!UserService.Instance.myUserInfo) {
+ if (UserService.Instance.myUserInfo.isNone()) {
this.context.router.history.push(`/login`);
}
- let new_vote = i.state.my_vote == 1 ? 0 : 1;
+ let myVote = this.state.my_vote.unwrapOr(0);
+ let newVote = myVote == 1 ? 0 : 1;
- if (i.state.my_vote == 1) {
+ if (myVote == 1) {
i.state.score--;
i.state.upvotes--;
- } else if (i.state.my_vote == -1) {
+ } else if (myVote == -1) {
i.state.downvotes--;
i.state.upvotes++;
i.state.score += 2;
i.state.score++;
}
- i.state.my_vote = new_vote;
+ i.state.my_vote = Some(newVote);
- let form: CreatePostLike = {
+ let form = new CreatePostLike({
post_id: i.props.post_view.post.id,
- score: i.state.my_vote,
- auth: authField(),
- };
+ score: newVote,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.likePost(form));
i.setState(i.state);
handlePostDisLike(i: PostListing, event: any) {
event.preventDefault();
- if (!UserService.Instance.myUserInfo) {
+ if (UserService.Instance.myUserInfo.isNone()) {
this.context.router.history.push(`/login`);
}
- let new_vote = i.state.my_vote == -1 ? 0 : -1;
+ let myVote = this.state.my_vote.unwrapOr(0);
+ let newVote = myVote == -1 ? 0 : -1;
- if (i.state.my_vote == 1) {
+ if (myVote == 1) {
i.state.score -= 2;
i.state.upvotes--;
i.state.downvotes++;
- } else if (i.state.my_vote == -1) {
+ } else if (myVote == -1) {
i.state.downvotes--;
i.state.score++;
} else {
i.state.score--;
}
- i.state.my_vote = new_vote;
+ i.state.my_vote = Some(newVote);
- let form: CreatePostLike = {
+ let form = new CreatePostLike({
post_id: i.props.post_view.post.id,
- score: i.state.my_vote,
- auth: authField(),
- };
+ score: newVote,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.likePost(form));
i.setState(i.state);
}
handleReportReasonChange(i: PostListing, event: any) {
- i.state.reportReason = event.target.value;
+ i.state.reportReason = Some(event.target.value);
i.setState(i.state);
}
handleReportSubmit(i: PostListing, event: any) {
event.preventDefault();
- let form: CreatePostReport = {
+ let form = new CreatePostReport({
post_id: i.props.post_view.post.id,
- reason: i.state.reportReason,
- auth: authField(),
- };
+ reason: toUndefined(i.state.reportReason),
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.createPostReport(form));
i.state.showReportDialog = false;
}
handleBlockUserClick(i: PostListing) {
- let blockUserForm: BlockPerson = {
+ let blockUserForm = new BlockPerson({
person_id: i.props.post_view.creator.id,
block: true,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
}
handleDeleteClick(i: PostListing) {
- let deleteForm: DeletePost = {
+ let deleteForm = new DeletePost({
post_id: i.props.post_view.post.id,
deleted: !i.props.post_view.post.deleted,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
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: SavePost = {
+ let form = new SavePost({
post_id: i.props.post_view.post.id,
save: saved,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.savePost(form));
}
let post = this.props.post_view.post;
let params = `?title=${encodeURIComponent(post.name)}`;
- if (post.url) {
- params += `&url=${encodeURIComponent(post.url)}`;
+ if (post.url.isSome()) {
+ params += `&url=${encodeURIComponent(post.url.unwrap())}`;
}
- if (post.body) {
+ if (post.body.isSome()) {
params += `&body=${encodeURIComponent(this.crossPostBody())}`;
}
return params;
crossPostBody(): string {
let post = this.props.post_view.post;
- let body = `${i18n.t("cross_posted_from")} ${
- post.ap_id
- }\n\n${post.body.replace(/^/gm, "> ")}`;
+ let body = `${i18n.t("cross_posted_from")} ${post.ap_id}\n\n${post.body
+ .unwrap()
+ .replace(/^/gm, "> ")}`;
return body;
}
}
handleModRemoveReasonChange(i: PostListing, event: any) {
- i.state.removeReason = event.target.value;
+ i.state.removeReason = Some(event.target.value);
i.setState(i.state);
}
handleModRemoveSubmit(i: PostListing, event: any) {
event.preventDefault();
- let form: RemovePost = {
+ let form = new RemovePost({
post_id: i.props.post_view.post.id,
removed: !i.props.post_view.post.removed,
reason: i.state.removeReason,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.removePost(form));
i.state.showRemoveDialog = false;
}
handleModLock(i: PostListing) {
- let form: LockPost = {
+ let form = new LockPost({
post_id: i.props.post_view.post.id,
locked: !i.props.post_view.post.locked,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.lockPost(form));
}
handleModSticky(i: PostListing) {
- let form: StickyPost = {
+ let form = new StickyPost({
post_id: i.props.post_view.post.id,
stickied: !i.props.post_view.post.stickied,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.stickyPost(form));
}
}
handleModBanReasonChange(i: PostListing, event: any) {
- i.state.banReason = event.target.value;
+ i.state.banReason = Some(event.target.value);
i.setState(i.state);
}
handleModBanExpireDaysChange(i: PostListing, event: any) {
- i.state.banExpireDays = event.target.value;
+ i.state.banExpireDays = Some(event.target.value);
i.setState(i.state);
}
if (ban == false) {
i.state.removeData = false;
}
- let form: BanFromCommunity = {
+ let form = new BanFromCommunity({
person_id: i.props.post_view.creator.id,
community_id: i.props.post_view.community.id,
ban,
- remove_data: i.state.removeData,
+ remove_data: Some(i.state.removeData),
reason: i.state.banReason,
- expires: futureDaysToUnixTime(i.state.banExpireDays),
- auth: authField(),
- };
+ expires: i.state.banExpireDays.map(futureDaysToUnixTime),
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.banFromCommunity(form));
} else {
// If its an unban, restore all their data
if (ban == false) {
i.state.removeData = false;
}
- let form: BanPerson = {
+ let form = new BanPerson({
person_id: i.props.post_view.creator.id,
ban,
- remove_data: i.state.removeData,
+ remove_data: Some(i.state.removeData),
reason: i.state.banReason,
- expires: futureDaysToUnixTime(i.state.banExpireDays),
- auth: authField(),
- };
+ expires: i.state.banExpireDays.map(futureDaysToUnixTime),
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.banPerson(form));
}
}
handleAddModToCommunity(i: PostListing) {
- let form: AddModToCommunity = {
+ let form = new AddModToCommunity({
person_id: i.props.post_view.creator.id,
community_id: i.props.post_view.community.id,
- added: !i.creatorIsMod,
- auth: authField(),
- };
+ added: !i.creatorIsMod_,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.addModToCommunity(form));
i.setState(i.state);
}
handleAddAdmin(i: PostListing) {
- let form: AddAdmin = {
+ let form = new AddAdmin({
person_id: i.props.post_view.creator.id,
- added: !i.creatorIsAdmin,
- auth: authField(),
- };
+ added: !i.creatorIsAdmin_,
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.addAdmin(form));
i.setState(i.state);
}
}
handleTransferCommunity(i: PostListing) {
- let form: TransferCommunity = {
+ let form = new TransferCommunity({
community_id: i.props.post_view.community.id,
person_id: i.props.post_view.creator.id,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.transferCommunity(form));
i.state.showConfirmTransferCommunity = false;
i.setState(i.state);
return `${points} • ${upvotes} • ${downvotes}`;
}
+
+ get canModOnSelf_(): boolean {
+ return canMod(
+ this.props.moderators,
+ this.props.admins,
+ this.props.post_view.creator.id,
+ undefined,
+ true
+ );
+ }
+
+ get canMod_(): boolean {
+ return canMod(
+ this.props.moderators,
+ this.props.admins,
+ this.props.post_view.creator.id
+ );
+ }
+
+ get canAdmin_(): boolean {
+ return canAdmin(this.props.admins, this.props.post_view.creator.id);
+ }
+
+ get creatorIsMod_(): boolean {
+ return isMod(this.props.moderators, this.props.post_view.creator.id);
+ }
+
+ get creatorIsAdmin_(): boolean {
+ return isAdmin(this.props.admins, this.props.post_view.creator.id);
+ }
}
+import { None, Some } from "@sniptt/monads";
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
enableNsfw: boolean;
}
-interface PostListingsState {
- posts: PostView[];
-}
-
-export class PostListings extends Component<
- PostListingsProps,
- PostListingsState
-> {
+export class PostListings extends Component<PostListingsProps, any> {
duplicatesMap = new Map<number, PostView[]>();
- private emptyState: PostListingsState = {
- posts: [],
- };
-
constructor(props: any, context: any) {
super(props, context);
- this.state = this.emptyState;
- if (this.props.removeDuplicates) {
- this.state.posts = this.removeDuplicates();
- } else {
- this.state.posts = this.props.posts;
- }
+ }
+
+ get posts() {
+ return this.props.removeDuplicates
+ ? this.removeDuplicates()
+ : this.props.posts;
}
render() {
return (
<div>
- {this.state.posts.length > 0 ? (
- this.state.posts.map(post_view => (
+ {this.posts.length > 0 ? (
+ this.posts.map(post_view => (
<>
<PostListing
post_view={post_view}
- duplicates={this.duplicatesMap.get(post_view.post.id)}
+ duplicates={Some(this.duplicatesMap.get(post_view.post.id))}
+ moderators={None}
+ admins={None}
showCommunity={this.props.showCommunity}
enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw}
) : (
<>
<div>{i18n.t("no_posts")}</div>
- {this.props.showCommunity !== undefined && (
+ {this.props.showCommunity && (
<T i18nKey="subscribe_to_communities">
#<Link to="/communities">#</Link>
</T>
// Loop over the posts, find ones with same urls
for (let pv of posts) {
- if (
- pv.post.url &&
- !pv.post.deleted &&
+ !pv.post.deleted &&
!pv.post.removed &&
!pv.community.deleted &&
- !pv.community.removed
- ) {
- if (!urlMap.get(pv.post.url)) {
- urlMap.set(pv.post.url, [pv]);
- } else {
- urlMap.get(pv.post.url).push(pv);
- }
- }
+ !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,
+ });
}
// Sort by oldest
for (let i = 0; i < posts.length; i++) {
let pv = posts[i];
- if (pv.post.url) {
- let found = urlMap.get(pv.post.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));
+ 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);
+ }
}
- // Otherwise, delete it
- else {
- posts.splice(i--, 1);
- }
- }
- }
+ },
+ none: void 0,
+ });
}
return posts;
+import { None } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import { PostReportView, PostView, ResolvePostReport } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { WebSocketService } from "../../services";
-import { authField, wsClient } from "../../utils";
+import { auth, wsClient } from "../../utils";
import { Icon } from "../common/icon";
import { PersonListing } from "../person/person-listing";
import { PostListing } from "./post-listing";
<div>
<PostListing
post_view={pv}
+ duplicates={None}
+ moderators={None}
+ admins={None}
showCommunity={true}
enableDownvotes={true}
enableNsfw={true}
<div>
{i18n.t("reason")}: {r.post_report.reason}
</div>
- {r.resolver && (
- <div>
- {r.post_report.resolved ? (
- <T i18nKey="resolved_by">
- #
- <PersonListing person={r.resolver} />
- </T>
- ) : (
- <T i18nKey="unresolved_by">
- #
- <PersonListing person={r.resolver} />
- </T>
- )}
- </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: <></>,
+ })}
<button
className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleResolveReport)}
}
handleResolveReport(i: PostReport) {
- let form: ResolvePostReport = {
+ let form = new ResolvePostReport({
report_id: i.props.report.post_report.id,
resolved: !i.props.report.post_report.resolved,
- auth: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.resolvePostReport(form));
}
}
+import { None, Option, Right, Some } from "@sniptt/monads";
import autosize from "autosize";
import { Component, createRef, linkEvent, RefObject } from "inferno";
import {
SearchType,
SortType,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
} from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
buildCommentsTree,
commentsToFlatNodes,
createCommentLikeRes,
createPostLikeRes,
debounce,
editCommentRes,
+ enableDownvotes,
+ enableNsfw,
getCommentIdFromProps,
getIdFromProps,
insertCommentIntoTree,
saveCommentRes,
saveScrollPosition,
setIsoData,
- setOptionalAuth,
setupTippy,
toast,
+ trendingFetchLimit,
updatePersonBlock,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { CommentForm } from "../comment/comment-form";
import { CommentNodes } from "../comment/comment-nodes";
const commentsShownInterval = 15;
interface PostState {
- postRes: GetPostResponse;
+ postRes: Option<GetPostResponse>;
postId: number;
commentTree: CommentNodeI[];
commentId?: number;
commentViewType: CommentViewType;
scrolled?: boolean;
loading: boolean;
- crossPosts: PostView[];
+ crossPosts: Option<PostView[]>;
siteRes: GetSiteResponse;
commentSectionRef?: RefObject<HTMLDivElement>;
showSidebarMobile: boolean;
export class Post extends Component<any, PostState> {
private subscription: Subscription;
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(this.context, GetPostResponse);
private commentScrollDebounced: () => void;
private emptyState: PostState = {
- postRes: null,
+ postRes: None,
postId: getIdFromProps(this.props),
commentTree: [],
commentId: getCommentIdFromProps(this.props),
commentViewType: CommentViewType.Tree,
scrolled: false,
loading: true,
- crossPosts: [],
+ crossPosts: None,
siteRes: this.isoData.site_res,
commentSectionRef: null,
showSidebarMobile: false,
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.postRes = this.isoData.routeData[0];
+ this.state.postRes = Some(this.isoData.routeData[0] as GetPostResponse);
this.state.commentTree = buildCommentsTree(
- this.state.postRes.comments,
+ this.state.postRes.unwrap().comments,
this.state.commentSort
);
this.state.loading = false;
if (isBrowser()) {
+ WebSocketService.Instance.send(
+ wsClient.communityJoin({
+ community_id:
+ this.state.postRes.unwrap().community_view.community.id,
+ })
+ );
+ WebSocketService.Instance.send(
+ wsClient.postJoin({ post_id: this.state.postId })
+ );
+
this.fetchCrossPosts();
if (this.state.commentId) {
this.scrollCommentIntoView();
}
fetchPost() {
- let form: GetPost = {
+ let form = new GetPost({
id: this.state.postId,
- auth: authField(false),
- };
+ auth: auth(false).ok(),
+ });
WebSocketService.Instance.send(wsClient.getPost(form));
}
fetchCrossPosts() {
- if (this.state.postRes.post_view.post.url) {
- let form: Search = {
- q: this.state.postRes.post_view.post.url,
- type_: SearchType.Url,
- sort: SortType.TopAll,
- listing_type: ListingType.All,
- page: 1,
- limit: 6,
- auth: authField(false),
- };
- WebSocketService.Instance.send(wsClient.search(form));
- }
+ 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,
+ });
}
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let pathSplit = req.path.split("/");
- let promises: Promise<any>[] = [];
let id = Number(pathSplit[2]);
- let postForm: GetPost = {
+ let postForm = new GetPost({
id,
- };
- setOptionalAuth(postForm, req.auth);
-
- promises.push(req.client.getPost(postForm));
+ auth: req.auth,
+ });
- return promises;
+ return [req.client.getPost(postForm)];
}
componentWillUnmount() {
}
componentDidMount() {
- WebSocketService.Instance.send(
- wsClient.postJoin({ post_id: this.state.postId })
- );
autosize(document.querySelectorAll("textarea"));
this.commentScrollDebounced = debounce(this.trackCommentsBoxScrolling, 100);
// TODO this needs some re-work
markScrolledAsRead(commentId: number) {
- let found = this.state.postRes.comments.find(
- c => c.comment.id == commentId
- );
- let parent = this.state.postRes.comments.find(
- c => found.comment.parent_id == c.comment.id
- );
- let parent_person_id = parent
- ? parent.creator.id
- : this.state.postRes.post_view.creator.id;
-
- if (
- UserService.Instance.myUserInfo &&
- UserService.Instance.myUserInfo.local_user_view.person.id ==
- parent_person_id
- ) {
- let form: MarkCommentAsRead = {
- comment_id: found.comment.id,
- read: true,
- auth: authField(),
- };
- WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
- UserService.Instance.unreadInboxCountSub.next(
- UserService.Instance.unreadInboxCountSub.value - 1
- );
- }
+ this.state.postRes.match({
+ some: res => {
+ let found = res.comments.find(c => c.comment.id == commentId);
+ let parent = res.comments.find(
+ c => found.comment.parent_id.unwrapOr(0) == c.comment.id
+ );
+ let parent_person_id = parent
+ ? parent.creator.id
+ : res.post_view.creator.id;
+
+ UserService.Instance.myUserInfo.match({
+ some: mui => {
+ if (mui.local_user_view.person.id == parent_person_id) {
+ let form = new MarkCommentAsRead({
+ comment_id: found.comment.id,
+ read: true,
+ auth: auth().unwrap(),
+ });
+ WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
+ UserService.Instance.unreadInboxCountSub.next(
+ UserService.Instance.unreadInboxCountSub.value - 1
+ );
+ }
+ },
+ none: void 0,
+ });
+ },
+ none: void 0,
+ });
}
- isBottom(el: Element) {
+ isBottom(el: Element): boolean {
return el?.getBoundingClientRect().bottom <= window.innerHeight;
}
};
get documentTitle(): string {
- return `${this.state.postRes.post_view.post.name} - ${this.state.siteRes.site_view.site.name}`;
+ return this.state.postRes.match({
+ some: res =>
+ this.state.siteRes.site_view.match({
+ some: siteView =>
+ `${res.post_view.post.name} - ${siteView.site.name}`,
+ none: "",
+ }),
+ none: "",
+ });
}
- get imageTag(): string {
- let post = this.state.postRes.post_view.post;
- return (
- post.thumbnail_url ||
- (post.url ? (isImage(post.url) ? post.url : undefined) : undefined)
- );
+ 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(): string {
- return this.state.postRes.post_view.post.body;
+ get descriptionTag(): Option<string> {
+ return this.state.postRes.andThen(r => r.post_view.post.body);
}
render() {
- let pv = this.state.postRes?.post_view;
return (
<div class="container">
{this.state.loading ? (
<Spinner large />
</h5>
) : (
- <div class="row">
- <div class="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={pv}
- duplicates={this.state.crossPosts}
- showBody
- showCommunity
- moderators={this.state.postRes.moderators}
- admins={this.state.siteRes.admins}
- enableDownvotes={
- this.state.siteRes.site_view.site.enable_downvotes
- }
- enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
- />
- <div ref={this.state.commentSectionRef} className="mb-2" />
- <CommentForm
- postId={this.state.postId}
- disabled={pv.post.locked}
- />
- <div class="d-block d-md-none">
- <button
- class="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"
+ this.state.postRes.match({
+ some: res => (
+ <div class="row">
+ <div class="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)}
/>
- </button>
- {this.state.showSidebarMobile && this.sidebar()}
+ <div ref={this.state.commentSectionRef} className="mb-2" />
+ <CommentForm
+ node={Right(this.state.postId)}
+ disabled={res.post_view.post.locked}
+ />
+ <div class="d-block d-md-none">
+ <button
+ class="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>
+ {res.comments.length > 0 && this.sortRadios()}
+ {this.state.commentViewType == CommentViewType.Tree &&
+ this.commentsTree()}
+ {this.state.commentViewType == CommentViewType.Chat &&
+ this.commentsFlat()}
+ </div>
+ <div class="d-none d-md-block col-md-4">{this.sidebar()}</div>
</div>
- {this.state.postRes.comments.length > 0 && this.sortRadios()}
- {this.state.commentViewType == CommentViewType.Tree &&
- this.commentsTree()}
- {this.state.commentViewType == CommentViewType.Chat &&
- this.commentsFlat()}
- </div>
- <div class="d-none d-md-block col-md-4">{this.sidebar()}</div>
- </div>
+ ),
+ none: <></>,
+ })
)}
</div>
);
commentsFlat() {
// These are already sorted by new
- return (
- <div>
- <CommentNodes
- nodes={commentsToFlatNodes(this.state.postRes.comments)}
- maxCommentsShown={this.state.maxCommentsShown}
- noIndent
- locked={this.state.postRes.post_view.post.locked}
- moderators={this.state.postRes.moderators}
- admins={this.state.siteRes.admins}
- postCreatorId={this.state.postRes.post_view.creator.id}
- showContext
- enableDownvotes={this.state.siteRes.site_view.site.enable_downvotes}
- />
- </div>
- );
+ return this.state.postRes.match({
+ some: res => (
+ <div>
+ <CommentNodes
+ nodes={commentsToFlatNodes(res.comments)}
+ maxCommentsShown={Some(this.state.maxCommentsShown)}
+ noIndent
+ locked={res.post_view.post.locked}
+ moderators={Some(res.moderators)}
+ admins={Some(this.state.siteRes.admins)}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ showContext
+ />
+ </div>
+ ),
+ none: <></>,
+ });
}
sidebar() {
- return (
- <div class="mb-3">
- <Sidebar
- community_view={this.state.postRes.community_view}
- moderators={this.state.postRes.moderators}
- admins={this.state.siteRes.admins}
- online={this.state.postRes.online}
- enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
- showIcon
- />
- </div>
- );
+ return this.state.postRes.match({
+ some: res => (
+ <div class="mb-3">
+ <Sidebar
+ community_view={res.community_view}
+ moderators={res.moderators}
+ admins={this.state.siteRes.admins}
+ online={res.online}
+ enableNsfw={enableNsfw(this.state.siteRes)}
+ showIcon
+ />
+ </div>
+ ),
+ none: <></>,
+ });
}
handleCommentSortChange(i: Post, event: any) {
i.state.commentSort = Number(event.target.value);
i.state.commentViewType = CommentViewType.Tree;
i.state.commentTree = buildCommentsTree(
- i.state.postRes.comments,
+ i.state.postRes.map(r => r.comments).unwrapOr([]),
i.state.commentSort
);
i.setState(i.state);
i.state.commentViewType = Number(event.target.value);
i.state.commentSort = CommentSortType.New;
i.state.commentTree = buildCommentsTree(
- i.state.postRes.comments,
+ i.state.postRes.map(r => r.comments).unwrapOr([]),
i.state.commentSort
);
i.setState(i.state);
}
commentsTree() {
- return (
- <div>
- <CommentNodes
- nodes={this.state.commentTree}
- maxCommentsShown={this.state.maxCommentsShown}
- locked={this.state.postRes.post_view.post.locked}
- moderators={this.state.postRes.moderators}
- admins={this.state.siteRes.admins}
- postCreatorId={this.state.postRes.post_view.creator.id}
- enableDownvotes={this.state.siteRes.site_view.site.enable_downvotes}
- />
- </div>
- );
+ return this.state.postRes.match({
+ some: res => (
+ <div>
+ <CommentNodes
+ nodes={this.state.commentTree}
+ maxCommentsShown={Some(this.state.maxCommentsShown)}
+ locked={res.post_view.post.locked}
+ moderators={Some(res.moderators)}
+ admins={Some(this.state.siteRes.admins)}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ />
+ </div>
+ ),
+ none: <></>,
+ });
}
parseMessage(msg: any) {
WebSocketService.Instance.send(
wsClient.getPost({
id: postId,
- auth: authField(false),
+ auth: auth(false).ok(),
})
);
} else if (op == UserOperation.GetPost) {
- let data = wsJsonToRes<GetPostResponse>(msg).data;
- this.state.postRes = data;
+ let data = wsJsonToRes<GetPostResponse>(msg, GetPostResponse);
+ this.state.postRes = Some(data);
+
this.state.commentTree = buildCommentsTree(
- this.state.postRes.comments,
+ this.state.postRes.map(r => r.comments).unwrapOr([]),
this.state.commentSort
);
this.state.loading = false;
+ // join the rooms
+ WebSocketService.Instance.send(
+ wsClient.postJoin({ post_id: this.state.postId })
+ );
+ WebSocketService.Instance.send(
+ wsClient.communityJoin({
+ community_id: data.community_view.community.id,
+ })
+ );
+
// Get cross-posts
this.fetchCrossPosts();
this.setState(this.state);
this.scrollCommentIntoView();
}
} else if (op == UserOperation.CreateComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
// Don't get comments from the post room, if the creator is blocked
- let creatorBlocked = UserService.Instance.myUserInfo?.person_blocks
+ let creatorBlocked = UserService.Instance.myUserInfo
+ .map(m => m.person_blocks)
+ .unwrapOr([])
.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.comments.unshift(data.comment_view);
- insertCommentIntoTree(this.state.commentTree, data.comment_view);
- this.state.postRes.post_view.counts.comments++;
+ this.state.postRes.match({
+ some: res => {
+ res.comments.unshift(data.comment_view);
+ insertCommentIntoTree(this.state.commentTree, data.comment_view);
+ res.post_view.counts.comments++;
+ },
+ none: void 0,
+ });
this.setState(this.state);
setupTippy();
}
op == UserOperation.DeleteComment ||
op == UserOperation.RemoveComment
) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- editCommentRes(data.comment_view, this.state.postRes.comments);
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
+ editCommentRes(
+ data.comment_view,
+ this.state.postRes.map(r => r.comments).unwrapOr([])
+ );
this.setState(this.state);
} else if (op == UserOperation.SaveComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- saveCommentRes(data.comment_view, this.state.postRes.comments);
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
+ saveCommentRes(
+ data.comment_view,
+ this.state.postRes.map(r => r.comments).unwrapOr([])
+ );
this.setState(this.state);
setupTippy();
} else if (op == UserOperation.CreateCommentLike) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- createCommentLikeRes(data.comment_view, this.state.postRes.comments);
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
+ createCommentLikeRes(
+ data.comment_view,
+ this.state.postRes.map(r => r.comments).unwrapOr([])
+ );
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- createPostLikeRes(data.post_view, this.state.postRes.post_view);
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
+ this.state.postRes.match({
+ some: res => createPostLikeRes(data.post_view, res.post_view),
+ none: void 0,
+ });
this.setState(this.state);
} else if (
op == UserOperation.EditPost ||
op == UserOperation.StickyPost ||
op == UserOperation.SavePost
) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- this.state.postRes.post_view = data.post_view;
+ 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();
} else if (
op == UserOperation.RemoveCommunity ||
op == UserOperation.FollowCommunity
) {
- let data = wsJsonToRes<CommunityResponse>(msg).data;
- this.state.postRes.community_view = data.community_view;
- this.state.postRes.post_view.community = data.community_view.community;
- this.setState(this.state);
- this.setState(this.state);
+ 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,
+ });
} else if (op == UserOperation.BanFromCommunity) {
- let data = wsJsonToRes<BanFromCommunityResponse>(msg).data;
- this.state.postRes.comments
- .filter(c => c.creator.id == data.person_view.person.id)
- .forEach(c => (c.creator_banned_from_community = data.banned));
- if (
- this.state.postRes.post_view.creator.id == data.person_view.person.id
- ) {
- this.state.postRes.post_view.creator_banned_from_community =
- data.banned;
- }
- this.setState(this.state);
+ let data = wsJsonToRes<BanFromCommunityResponse>(
+ msg,
+ BanFromCommunityResponse
+ );
+ this.state.postRes.match({
+ some: res => {
+ res.comments
+ .filter(c => c.creator.id == data.person_view.person.id)
+ .forEach(c => (c.creator_banned_from_community = data.banned));
+ if (res.post_view.creator.id == data.person_view.person.id) {
+ res.post_view.creator_banned_from_community = data.banned;
+ }
+ this.setState(this.state);
+ },
+ none: void 0,
+ });
} else if (op == UserOperation.AddModToCommunity) {
- let data = wsJsonToRes<AddModToCommunityResponse>(msg).data;
- this.state.postRes.moderators = data.moderators;
- this.setState(this.state);
+ let data = wsJsonToRes<AddModToCommunityResponse>(
+ msg,
+ AddModToCommunityResponse
+ );
+ this.state.postRes.match({
+ some: res => {
+ res.moderators = data.moderators;
+ this.setState(this.state);
+ },
+ none: void 0,
+ });
} else if (op == UserOperation.BanPerson) {
- let data = wsJsonToRes<BanPersonResponse>(msg).data;
- this.state.postRes.comments
- .filter(c => c.creator.id == data.person_view.person.id)
- .forEach(c => (c.creator.banned = data.banned));
- if (
- this.state.postRes.post_view.creator.id == data.person_view.person.id
- ) {
- this.state.postRes.post_view.creator.banned = data.banned;
- }
- this.setState(this.state);
+ let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse);
+ this.state.postRes.match({
+ some: res => {
+ res.comments
+ .filter(c => c.creator.id == data.person_view.person.id)
+ .forEach(c => (c.creator.banned = data.banned));
+ if (res.post_view.creator.id == data.person_view.person.id) {
+ res.post_view.creator.banned = data.banned;
+ }
+ this.setState(this.state);
+ },
+ none: void 0,
+ });
} else if (op == UserOperation.AddAdmin) {
- let data = wsJsonToRes<AddAdminResponse>(msg).data;
+ let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse);
this.state.siteRes.admins = data.admins;
this.setState(this.state);
} else if (op == UserOperation.Search) {
- let data = wsJsonToRes<SearchResponse>(msg).data;
- this.state.crossPosts = data.posts.filter(
+ let data = wsJsonToRes<SearchResponse>(msg, SearchResponse);
+ let xPosts = data.posts.filter(
p => p.post.id != Number(this.props.match.params.id)
);
+ this.state.crossPosts = xPosts.length > 0 ? Some(xPosts) : None;
this.setState(this.state);
} else if (op == UserOperation.LeaveAdmin) {
- let data = wsJsonToRes<GetSiteResponse>(msg).data;
+ let data = wsJsonToRes<GetSiteResponse>(msg, GetSiteResponse);
this.state.siteRes = data;
this.setState(this.state);
} else if (op == UserOperation.TransferCommunity) {
- let data = wsJsonToRes<GetCommunityResponse>(msg).data;
- this.state.postRes.community_view = data.community_view;
- this.state.postRes.post_view.community = data.community_view.community;
- this.state.postRes.moderators = data.moderators;
- this.setState(this.state);
+ 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,
+ });
} else if (op == UserOperation.BlockPerson) {
- let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
updatePersonBlock(data);
} else if (op == UserOperation.CreatePostReport) {
- let data = wsJsonToRes<PostReportResponse>(msg).data;
+ let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
if (data) {
toast(i18n.t("report_created"));
}
} else if (op == UserOperation.CreateCommentReport) {
- let data = wsJsonToRes<CommentReportResponse>(msg).data;
+ let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
if (data) {
toast(i18n.t("report_created"));
}
+import { None, Option, Some } from "@sniptt/monads";
import { Component } from "inferno";
import {
GetPersonDetails,
GetPersonDetailsResponse,
- PersonViewSafe,
- SiteView,
+ GetSiteResponse,
SortType,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { UserService, WebSocketService } from "../../services";
import {
- authField,
+ auth,
getRecipientIdFromProps,
isBrowser,
setIsoData,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { PrivateMessageForm } from "./private-message-form";
interface CreatePrivateMessageState {
- site_view: SiteView;
- recipient: PersonViewSafe;
+ siteRes: GetSiteResponse;
+ recipientDetailsRes: Option<GetPersonDetailsResponse>;
recipient_id: number;
loading: boolean;
}
any,
CreatePrivateMessageState
> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(this.context, GetPersonDetailsResponse);
private subscription: Subscription;
private emptyState: CreatePrivateMessageState = {
- site_view: this.isoData.site_res.site_view,
- recipient: undefined,
+ siteRes: this.isoData.site_res,
+ recipientDetailsRes: None,
recipient_id: getRecipientIdFromProps(this.props),
loading: true,
};
this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage);
- if (!UserService.Instance.myUserInfo) {
+ if (UserService.Instance.myUserInfo.isNone() && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- this.state.recipient = this.isoData.routeData[0].user;
+ this.state.recipientDetailsRes = Some(
+ this.isoData.routeData[0] as GetPersonDetailsResponse
+ );
this.state.loading = false;
} else {
this.fetchPersonDetails();
}
fetchPersonDetails() {
- let form: GetPersonDetails = {
- person_id: this.state.recipient_id,
- sort: SortType.New,
- saved_only: false,
- auth: authField(false),
- };
+ 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(),
+ });
WebSocketService.Instance.send(wsClient.getPersonDetails(form));
}
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- let person_id = Number(req.path.split("/").pop());
- let form: GetPersonDetails = {
+ let person_id = Some(Number(req.path.split("/").pop()));
+ let form = new GetPersonDetails({
person_id,
- sort: SortType.New,
- saved_only: false,
+ sort: Some(SortType.New),
+ saved_only: Some(false),
+ username: None,
+ page: None,
+ limit: None,
+ community_id: None,
auth: req.auth,
- };
+ });
return [req.client.getPersonDetails(form)];
}
get documentTitle(): string {
- return `${i18n.t("create_private_message")} - ${
- this.state.site_view.site.name
- }`;
+ return this.state.recipientDetailsRes.match({
+ some: res =>
+ `${i18n.t("create_private_message")} - ${res.person_view.person.name}`,
+ none: "",
+ });
}
componentWillUnmount() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
{this.state.loading ? (
<h5>
<Spinner large />
</h5>
) : (
- <div class="row">
- <div class="col-12 col-lg-6 offset-lg-3 mb-4">
- <h5>{i18n.t("create_private_message")}</h5>
- <PrivateMessageForm
- onCreate={this.handlePrivateMessageCreate}
- recipient={this.state.recipient.person}
- />
- </div>
- </div>
+ this.state.recipientDetailsRes.match({
+ some: res => (
+ <div class="row">
+ <div class="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>
+ </div>
+ ),
+ none: <></>,
+ })
)}
</div>
);
this.setState(this.state);
return;
} else if (op == UserOperation.GetPersonDetails) {
- let data = wsJsonToRes<GetPersonDetailsResponse>(msg).data;
- this.state.recipient = data.person_view;
+ let data = wsJsonToRes<GetPersonDetailsResponse>(
+ msg,
+ GetPersonDetailsResponse
+ );
+ this.state.recipientDetailsRes = Some(data);
this.state.loading = false;
this.setState(this.state);
}
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import { Prompt } from "inferno-router";
PrivateMessageResponse,
PrivateMessageView,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { WebSocketService } from "../../services";
import {
- authField,
+ auth,
capitalizeFirstLetter,
isBrowser,
relTags,
setupTippy,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../../utils";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
interface PrivateMessageFormProps {
recipient: PersonSafe;
- privateMessage?: PrivateMessageView; // If a pm is given, that means this is an edit
+ privateMessageView: Option<PrivateMessageView>; // If a pm is given, that means this is an edit
onCancel?(): any;
onCreate?(message: PrivateMessageView): any;
onEdit?(message: PrivateMessageView): any;
> {
private subscription: Subscription;
private emptyState: PrivateMessageFormState = {
- privateMessageForm: {
+ privateMessageForm: new CreatePrivateMessage({
content: null,
recipient_id: this.props.recipient.id,
- auth: authField(),
- },
+ auth: auth().unwrap(),
+ }),
loading: false,
previewMode: false,
showDisclaimer: false,
this.subscription = wsSubscribe(this.parseMessage);
// Its an edit
- if (this.props.privateMessage) {
- this.state.privateMessageForm.content =
- this.props.privateMessage.private_message.content;
- }
+ this.props.privateMessageView.match({
+ some: pm =>
+ (this.state.privateMessageForm.content = pm.private_message.content),
+ none: void 0,
+ });
}
componentDidMount() {
message={i18n.t("block_leaving")}
/>
<form onSubmit={linkEvent(this, this.handlePrivateMessageSubmit)}>
- {!this.props.privateMessage && (
+ {this.props.privateMessageView.isNone() && (
<div class="form-group row">
<label class="col-sm-2 col-form-label">
{capitalizeFirstLetter(i18n.t("to"))}
</label>
<div class="col-sm-10">
<MarkdownTextArea
- initialContent={this.state.privateMessageForm.content}
+ initialContent={Some(this.state.privateMessageForm.content)}
+ placeholder={None}
+ buttonTitle={None}
+ maxLength={None}
onContentChange={this.handleContentChange}
/>
</div>
>
{this.state.loading ? (
<Spinner />
- ) : this.props.privateMessage ? (
+ ) : this.props.privateMessageView.isSome() ? (
capitalizeFirstLetter(i18n.t("save"))
) : (
capitalizeFirstLetter(i18n.t("send_message"))
)}
</button>
- {this.props.privateMessage && (
+ {this.props.privateMessageView.isSome() && (
<button
type="button"
class="btn btn-secondary"
handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) {
event.preventDefault();
- if (i.props.privateMessage) {
- let form: EditPrivateMessage = {
- private_message_id: i.props.privateMessage.private_message.id,
- content: i.state.privateMessageForm.content,
- auth: authField(),
- };
- WebSocketService.Instance.send(wsClient.editPrivateMessage(form));
- } else {
- WebSocketService.Instance.send(
+ i.props.privateMessageView.match({
+ some: pm => {
+ let form = new EditPrivateMessage({
+ private_message_id: pm.private_message.id,
+ content: i.state.privateMessageForm.content,
+ auth: auth().unwrap(),
+ });
+ WebSocketService.Instance.send(wsClient.editPrivateMessage(form));
+ },
+ none: WebSocketService.Instance.send(
wsClient.createPrivateMessage(i.state.privateMessageForm)
- );
- }
+ ),
+ });
i.state.loading = true;
i.setState(i.state);
}
op == UserOperation.DeletePrivateMessage ||
op == UserOperation.MarkPrivateMessageAsRead
) {
- let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
+ let data = wsJsonToRes<PrivateMessageResponse>(
+ msg,
+ PrivateMessageResponse
+ );
this.state.loading = false;
this.props.onEdit(data.private_message_view);
} else if (op == UserOperation.CreatePrivateMessage) {
- let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
+ let data = wsJsonToRes<PrivateMessageResponse>(
+ msg,
+ PrivateMessageResponse
+ );
this.state.loading = false;
this.props.onCreate(data.private_message_view);
this.setState(this.state);
+import { None, Some } from "@sniptt/monads/build";
import { Component, linkEvent } from "inferno";
import {
DeletePrivateMessage,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService, WebSocketService } from "../../services";
-import { authField, mdToHtml, toast, wsClient } from "../../utils";
+import { auth, mdToHtml, toast, wsClient } from "../../utils";
import { Icon } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { PersonListing } from "../person/person-listing";
}
get mine(): boolean {
- return (
- UserService.Instance.myUserInfo &&
- UserService.Instance.myUserInfo.local_user_view.person.id ==
- this.props.private_message_view.creator.id
- );
+ return UserService.Instance.myUserInfo
+ .map(
+ m =>
+ m.local_user_view.person.id ==
+ this.props.private_message_view.creator.id
+ )
+ .unwrapOr(false);
}
render() {
let message_view = this.props.private_message_view;
- // TODO check this again
let otherPerson: PersonSafe = this.mine
? message_view.recipient
: message_view.creator;
</li>
<li className="list-inline-item">
<span>
- <MomentTime data={message_view.private_message} />
+ <MomentTime
+ published={message_view.private_message.published}
+ updated={message_view.private_message.updated}
+ />
</span>
</li>
<li className="list-inline-item">
{this.state.showEdit && (
<PrivateMessageForm
recipient={otherPerson}
- privateMessage={message_view}
+ privateMessageView={Some(message_view)}
onEdit={this.handlePrivateMessageEdit}
onCreate={this.handlePrivateMessageCreate}
onCancel={this.handleReplyCancel}
{this.state.showReply && (
<PrivateMessageForm
recipient={otherPerson}
+ privateMessageView={None}
onCreate={this.handlePrivateMessageCreate}
/>
)}
}
handleDeleteClick(i: PrivateMessage) {
- let form: DeletePrivateMessage = {
+ 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: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.deletePrivateMessage(form));
}
}
handleMarkRead(i: PrivateMessage) {
- let form: MarkPrivateMessageAsRead = {
+ 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: authField(),
- };
+ auth: auth().unwrap(),
+ });
WebSocketService.Instance.send(wsClient.markPrivateMessageAsRead(form));
}
}
handlePrivateMessageCreate(message: PrivateMessageView) {
- if (
- UserService.Instance.myUserInfo &&
- message.creator.id ==
- UserService.Instance.myUserInfo.local_user_view.person.id
- ) {
- this.state.showReply = false;
- this.setState(this.state);
- toast(i18n.t("message_sent"));
- }
+ UserService.Instance.myUserInfo.match({
+ some: mui => {
+ if (message.creator.id == mui.local_user_view.person.id) {
+ this.state.showReply = false;
+ this.setState(this.state);
+ toast(i18n.t("message_sent"));
+ }
+ },
+ none: void 0,
+ });
}
}
+import { None, Option, Some } from "@sniptt/monads";
import { Component, linkEvent } from "inferno";
import {
CommentResponse,
CommentView,
CommunityView,
GetCommunity,
+ GetCommunityResponse,
GetPersonDetails,
+ GetPersonDetailsResponse,
+ GetSiteResponse,
ListCommunities,
ListCommunitiesResponse,
ListingType,
Search as SearchForm,
SearchResponse,
SearchType,
- Site,
SortType,
UserOperation,
+ wsJsonToRes,
+ wsUserOp,
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { InitialFetchRequest } from "shared/interfaces";
import { i18n } from "../i18next";
import { WebSocketService } from "../services";
import {
- authField,
+ auth,
capitalizeFirstLetter,
choicesConfig,
commentsToFlatNodes,
createCommentLikeRes,
createPostLikeFindRes,
debounce,
+ enableDownvotes,
+ enableNsfw,
fetchCommunities,
fetchLimit,
fetchUsers,
routeSortTypeToEnum,
saveScrollPosition,
setIsoData,
- setOptionalAuth,
showLocal,
toast,
wsClient,
- wsJsonToRes,
wsSubscribe,
- wsUserOp,
} from "../utils";
import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags";
communityId: number;
creatorId: number;
page: number;
- searchResponse?: SearchResponse;
+ searchResponse: Option<SearchResponse>;
communities: CommunityView[];
- creator?: PersonViewSafe;
+ creatorDetails: Option<GetPersonDetailsResponse>;
loading: boolean;
- site: Site;
+ siteRes: GetSiteResponse;
searchText: string;
- resolveObjectResponse?: ResolveObjectResponse;
+ resolveObjectResponse: Option<ResolveObjectResponse>;
}
interface UrlParams {
}
export class Search extends Component<any, SearchState> {
- private isoData = setIsoData(this.context);
+ private isoData = setIsoData(
+ this.context,
+ GetCommunityResponse,
+ ListCommunitiesResponse,
+ GetPersonDetailsResponse,
+ SearchResponse,
+ ResolveObjectResponse
+ );
private communityChoices: any;
private creatorChoices: any;
private subscription: Subscription;
this.props.match.params.community_id
),
creatorId: Search.getCreatorIdFromProps(this.props.match.params.creator_id),
- searchResponse: null,
- resolveObjectResponse: null,
+ searchResponse: None,
+ resolveObjectResponse: None,
+ creatorDetails: None,
loading: true,
- site: this.isoData.site_res.site_view.site,
+ siteRes: this.isoData.site_res,
communities: [],
};
// Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) {
- let singleOrMultipleCommunities = this.isoData.routeData[0];
- if (singleOrMultipleCommunities.communities) {
- this.state.communities = this.isoData.routeData[0].communities;
- } else {
- this.state.communities = [this.isoData.routeData[0].community_view];
- }
+ let communityRes = Some(
+ this.isoData.routeData[0] as GetCommunityResponse
+ );
+ let communitiesRes = Some(
+ this.isoData.routeData[1] as ListCommunitiesResponse
+ );
+
+ // This can be single or multiple communities given
+ this.state.communities = communitiesRes
+ .map(c => c.communities)
+ .unwrapOr([communityRes.map(c => c.community_view).unwrap()]);
+
+ this.state.creatorDetails = Some(
+ this.isoData.routeData[2] as GetPersonDetailsResponse
+ );
- let creator = this.isoData.routeData[1];
- if (creator?.person_view) {
- this.state.creator = this.isoData.routeData[1].person_view;
- }
if (this.state.q != "") {
- this.state.searchResponse = this.isoData.routeData[2];
- this.state.resolveObjectResponse = this.isoData.routeData[3];
+ this.state.searchResponse = Some(
+ this.isoData.routeData[3] as SearchResponse
+ );
+ this.state.resolveObjectResponse = Some(
+ this.isoData.routeData[4] as ResolveObjectResponse
+ );
this.state.loading = false;
} else {
this.search();
}
fetchCommunities() {
- let listCommunitiesForm: ListCommunities = {
- type_: ListingType.All,
- sort: SortType.TopAll,
- limit: fetchLimit,
- auth: authField(false),
- };
+ 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 promises: Promise<any>[] = [];
let communityId = this.getCommunityIdFromProps(pathSplit[11]);
- if (communityId !== 0) {
- let getCommunityForm: GetCommunity = {
- id: communityId,
- };
- setOptionalAuth(getCommunityForm, req.auth);
- promises.push(req.client.getCommunity(getCommunityForm));
- } else {
- let listCommunitiesForm: ListCommunities = {
- type_: ListingType.All,
- sort: SortType.TopAll,
- limit: fetchLimit,
- };
- setOptionalAuth(listCommunitiesForm, req.auth);
- promises.push(req.client.listCommunities(listCommunitiesForm));
- }
+ 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 creatorId = this.getCreatorIdFromProps(pathSplit[13]);
- if (creatorId !== 0) {
- let getCreatorForm: GetPersonDetails = {
- person_id: creatorId,
- };
- setOptionalAuth(getCreatorForm, req.auth);
- promises.push(req.client.getPersonDetails(getCreatorForm));
- } else {
- promises.push(Promise.resolve());
- }
+ 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 form: SearchForm = {
+ let form = new SearchForm({
q: this.getSearchQueryFromProps(pathSplit[3]),
- type_: this.getSearchTypeFromProps(pathSplit[5]),
- sort: this.getSortTypeFromProps(pathSplit[7]),
- listing_type: this.getListingTypeFromProps(pathSplit[9]),
- page: this.getPageFromProps(pathSplit[15]),
- limit: fetchLimit,
- };
- if (communityId !== 0) {
- form.community_id = communityId;
- }
- if (creatorId !== 0) {
- form.creator_id = creatorId;
- }
- setOptionalAuth(form, req.auth);
+ 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),
+ auth: req.auth,
+ });
- let resolveObjectForm: ResolveObject = {
+ let resolveObjectForm = new ResolveObject({
q: this.getSearchQueryFromProps(pathSplit[3]),
- };
- setOptionalAuth(resolveObjectForm, req.auth);
+ auth: req.auth,
+ });
if (form.q != "") {
- //this.state.loading = false;
- //this.setState(this.state);
promises.push(req.client.search(form));
promises.push(req.client.resolveObject(resolveObjectForm));
+ } else {
+ promises.push(Promise.resolve());
+ promises.push(Promise.resolve());
}
return promises;
this.setState({
loading: true,
searchText: this.state.q,
- searchResponse: null,
- resolveObjectResponse: null,
+ searchResponse: None,
+ resolveObjectResponse: None,
});
this.search();
}
}
get documentTitle(): string {
- if (this.state.q) {
- return `${i18n.t("search")} - ${this.state.q} - ${this.state.site.name}`;
- } else {
- return `${i18n.t("search")} - ${this.state.site.name}`;
- }
+ return this.state.siteRes.site_view.match({
+ some: siteView =>
+ this.state.q
+ ? `${i18n.t("search")} - ${this.state.q} - ${siteView.site.name}`
+ : `${i18n.t("search")} - ${siteView.site.name}`,
+ none: "",
+ });
}
render() {
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
+ description={None}
+ image={None}
/>
<h5>{i18n.t("search")}</h5>
{this.selects()}
let combined: Combined[] = [];
// Push the possible resolve / federated objects first
- let resolveComment = this.state.resolveObjectResponse?.comment;
- if (resolveComment) {
- combined.push(this.commentViewToCombined(resolveComment));
- }
- let resolvePost = this.state.resolveObjectResponse?.post;
- if (resolvePost) {
- combined.push(this.postViewToCombined(resolvePost));
- }
- let resolveCommunity = this.state.resolveObjectResponse?.community;
- if (resolveCommunity) {
- combined.push(this.communityViewToCombined(resolveCommunity));
- }
- let resolveUser = this.state.resolveObjectResponse?.person;
- if (resolveUser) {
- combined.push(this.personViewSafeToCombined(resolveUser));
- }
+ 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,
+ });
// Push the search results
- pushNotNull(
- combined,
- this.state.searchResponse?.comments?.map(e =>
- this.commentViewToCombined(e)
- )
- );
- pushNotNull(
- combined,
- this.state.searchResponse?.posts?.map(e => this.postViewToCombined(e))
- );
- pushNotNull(
- combined,
- this.state.searchResponse?.communities?.map(e =>
- this.communityViewToCombined(e)
- )
- );
- pushNotNull(
- combined,
- this.state.searchResponse?.users?.map(e =>
- this.personViewSafeToCombined(e)
- )
- );
+ 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,
+ });
// Sort it
if (this.state.sort == SortType.New) {
<PostListing
key={(i.data as PostView).post.id}
post_view={i.data as PostView}
+ duplicates={None}
+ moderators={None}
+ admins={None}
showCommunity
- enableDownvotes={this.state.site.enable_downvotes}
- enableNsfw={this.state.site.enable_nsfw}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ enableNsfw={enableNsfw(this.state.siteRes)}
/>
)}
{i.type_ == "comments" && (
<CommentNodes
key={(i.data as CommentView).comment.id}
nodes={[{ comment_view: i.data as CommentView }]}
+ moderators={None}
+ admins={None}
+ maxCommentsShown={None}
locked
noIndent
- enableDownvotes={this.state.site.enable_downvotes}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
/>
)}
{i.type_ == "communities" && (
comments() {
let comments: CommentView[] = [];
- pushNotNull(comments, this.state.resolveObjectResponse?.comment);
- pushNotNull(comments, this.state.searchResponse?.comments);
+ 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,
+ });
return (
<CommentNodes
nodes={commentsToFlatNodes(comments)}
locked
noIndent
- enableDownvotes={this.state.site.enable_downvotes}
+ moderators={None}
+ admins={None}
+ maxCommentsShown={None}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
/>
);
}
posts() {
let posts: PostView[] = [];
- pushNotNull(posts, this.state.resolveObjectResponse?.post);
- pushNotNull(posts, this.state.searchResponse?.posts);
+ 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,
+ });
return (
<>
<PostListing
post_view={post}
showCommunity
- enableDownvotes={this.state.site.enable_downvotes}
- enableNsfw={this.state.site.enable_nsfw}
+ duplicates={None}
+ moderators={None}
+ admins={None}
+ enableDownvotes={enableDownvotes(this.state.siteRes)}
+ enableNsfw={enableNsfw(this.state.siteRes)}
/>
</div>
</div>
communities() {
let communities: CommunityView[] = [];
- pushNotNull(communities, this.state.resolveObjectResponse?.community);
- pushNotNull(communities, this.state.searchResponse?.communities);
+ 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,
+ });
return (
<>
users() {
let users: PersonViewSafe[] = [];
- pushNotNull(users, this.state.resolveObjectResponse?.person);
- pushNotNull(users, this.state.searchResponse?.users);
+ 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,
+ });
return (
<>
value={this.state.creatorId}
>
<option value="0">{i18n.t("all")}</option>
- {this.state.creator && (
- <option value={this.state.creator.person.id}>
- {personSelectName(this.state.creator)}
- </option>
- )}
+ {this.state.creatorDetails.match({
+ some: creator => (
+ <option value={creator.person_view.person.id}>
+ {personSelectName(creator.person_view)}
+ </option>
+ ),
+ none: <></>,
+ })}
</select>
</div>
</div>
}
resultsCount(): number {
- let res = this.state.searchResponse;
- let resObj = this.state.resolveObjectResponse;
- let resObjCount =
- resObj?.post || resObj?.person || resObj?.community || resObj?.comment
- ? 1
- : 0;
- return (
- res?.posts?.length +
- res?.comments?.length +
- res?.communities?.length +
- res?.users?.length +
- resObjCount
- );
+ 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);
+
+ return resObjCount + searchCount;
}
handlePageChange(page: number) {
}
search() {
- let form: SearchForm = {
+ 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);
+
+ console.log(community_id.unwrapOr(-22));
+
+ let form = new SearchForm({
q: this.state.q,
- type_: this.state.type_,
- sort: this.state.sort,
- listing_type: this.state.listingType,
- page: this.state.page,
- limit: fetchLimit,
- auth: authField(false),
- };
- if (this.state.communityId !== 0) {
- form.community_id = this.state.communityId;
- }
- if (this.state.creatorId !== 0) {
- form.creator_id = this.state.creatorId;
- }
+ 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(),
+ });
- let resolveObjectForm: ResolveObject = {
+ let resolveObjectForm = new ResolveObject({
q: this.state.q,
- auth: authField(false),
- };
+ auth: auth(false).ok(),
+ });
if (this.state.q != "") {
- this.state.searchResponse = null;
- this.state.resolveObjectResponse = null;
+ this.state.searchResponse = None;
+ this.state.resolveObjectResponse = None;
this.state.loading = true;
this.setState(this.state);
WebSocketService.Instance.send(wsClient.search(form));
let op = wsUserOp(msg);
if (msg.error) {
if (msg.error == "couldnt_find_object") {
- this.state.resolveObjectResponse = {
- comment: null,
- post: null,
- community: null,
- person: null,
- };
+ this.state.resolveObjectResponse = Some({
+ comment: None,
+ post: None,
+ community: None,
+ person: None,
+ });
this.checkFinishedLoading();
} else {
toast(i18n.t(msg.error), "danger");
return;
}
} else if (op == UserOperation.Search) {
- let data = wsJsonToRes<SearchResponse>(msg).data;
- this.state.searchResponse = data;
+ let data = wsJsonToRes<SearchResponse>(msg, SearchResponse);
+ this.state.searchResponse = Some(data);
window.scrollTo(0, 0);
this.checkFinishedLoading();
restoreScrollPosition(this.context);
} else if (op == UserOperation.CreateCommentLike) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
+ let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
createCommentLikeRes(
data.comment_view,
- this.state.searchResponse?.comments
+ this.state.searchResponse.map(r => r.comments).unwrapOr([])
);
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- createPostLikeFindRes(data.post_view, this.state.searchResponse?.posts);
+ let data = wsJsonToRes<PostResponse>(msg, PostResponse);
+ createPostLikeFindRes(
+ data.post_view,
+ this.state.searchResponse.map(r => r.posts).unwrapOr([])
+ );
this.setState(this.state);
} else if (op == UserOperation.ListCommunities) {
- let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
+ let data = wsJsonToRes<ListCommunitiesResponse>(
+ msg,
+ ListCommunitiesResponse
+ );
this.state.communities = data.communities;
this.setState(this.state);
this.setupCommunityFilter();
} else if (op == UserOperation.ResolveObject) {
- let data = wsJsonToRes<ResolveObjectResponse>(msg).data;
- this.state.resolveObjectResponse = data;
+ let data = wsJsonToRes<ResolveObjectResponse>(msg, ResolveObjectResponse);
+ this.state.resolveObjectResponse = Some(data);
this.checkFinishedLoading();
}
}
checkFinishedLoading() {
if (
- this.state.searchResponse != null &&
- this.state.resolveObjectResponse != null
+ this.state.searchResponse.isSome() &&
+ this.state.resolveObjectResponse.isSome()
) {
this.state.loading = false;
this.setState(this.state);
+import { Either, Option } from "@sniptt/monads";
import {
CommentView,
GetSiteResponse,
PersonMentionView,
} from "lemmy-js-client";
+/**
+ * This contains serialized data, it needs to be deserialized before use.
+ */
export interface IsoData {
path: string;
routeData: any[];
}
export interface InitialFetchRequest {
- auth: string;
- path: string;
+ auth: Option<string>;
client: LemmyHttp;
+ path: string;
}
export interface CommentNode {
}
export interface PostFormParams {
- name: string;
- url?: string;
- body?: string;
- community_name?: string;
- community_id?: number;
+ name: Option<string>;
+ url: Option<string>;
+ body: Option<string>;
+ nameOrId: Option<Either<string, number>>;
}
export enum CommentSortType {
// 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";
import { BehaviorSubject, Subject } from "rxjs";
import { isHttps } from "../env";
+import { i18n } from "../i18next";
+import { isBrowser, toast } from "../utils";
interface Claims {
sub: number;
iat: number;
}
+interface JwtInfo {
+ claims: Claims;
+ jwt: string;
+}
+
export class UserService {
private static _instance: UserService;
- public myUserInfo: MyUserInfo;
- public claims: Claims;
- public jwtSub: Subject<string> = new Subject<string>();
+ public myUserInfo: Option<MyUserInfo> = None;
+ public jwtInfo: Option<JwtInfo> = None;
+ public jwtSub: Subject<Option<JwtInfo>> = new Subject<Option<JwtInfo>>();
public unreadInboxCountSub: BehaviorSubject<number> =
new BehaviorSubject<number>(0);
public unreadReportCountSub: BehaviorSubject<number> =
new BehaviorSubject<number>(0);
private constructor() {
- if (this.auth) {
- this.setClaims(this.auth);
- } else {
- // setTheme();
- console.log("No JWT cookie found.");
- }
+ this.setJwtInfo();
}
public login(res: LoginResponse) {
expires.setDate(expires.getDate() + 365);
IsomorphicCookie.save("jwt", res.jwt, { expires, secure: isHttps });
console.log("jwt cookie set");
- this.setClaims(res.jwt);
+ this.setJwtInfo();
}
public logout() {
- this.claims = undefined;
- this.myUserInfo = undefined;
- // setTheme();
- this.jwtSub.next("");
+ this.jwtInfo = None;
+ this.myUserInfo = None;
+ this.jwtSub.next(this.jwtInfo);
IsomorphicCookie.remove("jwt"); // TODO is sometimes unreliable for some reason
document.cookie = "jwt=; Max-Age=0; path=/; domain=" + location.host;
+ location.reload(); // TODO may not be necessary anymore
console.log("Logged out.");
}
- public get auth(): string {
- return IsomorphicCookie.load("jwt");
+ 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());
+ } else {
+ let msg = "No JWT cookie found";
+ if (throwErr && isBrowser()) {
+ console.log(msg);
+ toast(i18n.t("not_logged_in"), "danger");
+ }
+ return Err(msg);
+ }
}
- private setClaims(jwt: string) {
- this.claims = jwt_decode(jwt);
- this.jwtSub.next(jwt);
+ private setJwtInfo() {
+ let jwt = IsomorphicCookie.load("jwt");
+
+ if (jwt) {
+ let jwtInfo: JwtInfo = { jwt, claims: jwt_decode(jwt) };
+ this.jwtInfo = Some(jwtInfo);
+ this.jwtSub.next(this.jwtInfo);
+ }
}
public static get Instance() {
-import { PersonViewSafe, WebSocketJsonResponse } from "lemmy-js-client";
import { Observable } from "rxjs";
import { share } from "rxjs/operators";
import {
private ws: WS;
public subject: Observable<any>;
- public admins: PersonViewSafe[];
- public banned: PersonViewSafe[];
-
private constructor() {
let firstConnect = true;
console.log(`Connected to ${wsUri}`);
if (!firstConnect) {
- let res: WebSocketJsonResponse<any> = {
+ let res = {
reconnect: true,
};
obs.next(res);
+import { None, Option, Result, Some } from "@sniptt/monads";
+import { ClassConstructor, deserialize, serialize } from "class-transformer";
import emojiShortName from "emoji-short-name";
import {
BlockCommunityResponse,
CommentReportView,
CommentView,
CommunityBlockView,
+ CommunityModeratorView,
CommunityView,
GetSiteMetadata,
GetSiteResponse,
Search,
SearchType,
SortType,
- UserOperation,
- WebSocketJsonResponse,
- WebSocketResponse,
} from "lemmy-js-client";
import markdown_it from "markdown-it";
import markdown_it_container from "markdown-it-container";
export const postRefetchSeconds: number = 60 * 1000;
export const fetchLimit = 20;
+export const trendingFetchLimit = 6;
export const mentionDropdownFetchLimit = 10;
export const relTags = "noopener nofollow";
.join("");
}
-export function wsJsonToRes<ResponseType>(
- msg: WebSocketJsonResponse<ResponseType>
-): WebSocketResponse<ResponseType> {
- return {
- op: wsUserOp(msg),
- data: msg.data,
- };
-}
-
-export function wsUserOp(msg: any): UserOperation {
- let opStr: string = msg.op;
- return UserOperation[opStr];
-}
-
export const md = new markdown_it({
html: false,
linkify: true,
}
export function canMod(
- myUserInfo: MyUserInfo,
- modIds: number[],
+ mods: Option<CommunityModeratorView[]>,
+ admins: Option<PersonViewSafe[]>,
creator_id: number,
+ myUserInfo = UserService.Instance.myUserInfo,
onSelf = false
): boolean {
// You can do moderator actions only on the mods added after you.
- if (myUserInfo) {
- let yourIndex = modIds.findIndex(
- id => id == myUserInfo.local_user_view.person.id
- );
- if (yourIndex == -1) {
- return false;
- } else {
- // onSelf +1 on mod actions not for yourself, IE ban, remove, etc
- modIds = modIds.slice(0, yourIndex + (onSelf ? 0 : 1));
- return !modIds.includes(creator_id);
- }
- } else {
- return false;
- }
+ 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,
+ });
+}
+
+export function canAdmin(
+ admins: Option<PersonViewSafe[]>,
+ creator_id: number,
+ myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+ return canMod(None, admins, creator_id, myUserInfo);
+}
+
+export function isMod(
+ mods: Option<CommunityModeratorView[]>,
+ creator_id: number
+): boolean {
+ return mods.match({
+ some: mods => mods.map(m => m.moderator.id).includes(creator_id),
+ none: false,
+ });
+}
+
+export function amMod(
+ mods: Option<CommunityModeratorView[]>,
+ myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+ return myUserInfo.match({
+ some: mui => isMod(mods, mui.local_user_view.person.id),
+ none: false,
+ });
}
-export function isMod(modIds: number[], creator_id: number): boolean {
- return modIds.includes(creator_id);
+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 amAdmin(
+ admins: Option<PersonViewSafe[]>,
+ myUserInfo = UserService.Instance.myUserInfo
+): boolean {
+ return myUserInfo.match({
+ some: mui => isAdmin(admins, mui.local_user_view.person.id),
+ none: false,
+ });
+}
+
+export function amCommunityCreator(
+ mods: Option<CommunityModeratorView[]>,
+ creator_id: number,
+ 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,
+ });
+}
+
+export function amSiteCreator(
+ admins: Option<PersonViewSafe[]>,
+ creator_id: number,
+ 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,
+ });
+}
+
+export function amTopMod(
+ mods: Option<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,
+ });
}
const imageRegex = /(http)?s?:?(\/\/[^"']*\.(?:jpg|jpeg|gif|png|svg|webp))/;
};
}
-export function getLanguages(override?: string): string[] {
- let myUserInfo = UserService.Instance.myUserInfo;
- let lang =
- override ||
- (myUserInfo?.local_user_view.local_user.lang
- ? myUserInfo.local_user_view.local_user.lang
- : "browser");
+export function getLanguages(
+ override?: string,
+ myUserInfo = UserService.Instance.myUserInfo
+): string[] {
+ let myLang = myUserInfo
+ .map(m => m.local_user_view.local_user.lang)
+ .unwrapOr("browser");
+ let lang = override || myLang;
if (lang == "browser" && isBrowser()) {
return getBrowserLanguages();
return ret;
}
-export function showAvatars(): boolean {
- return (
- UserService.Instance.myUserInfo?.local_user_view.local_user.show_avatars ||
- !UserService.Instance.myUserInfo
- );
+export function showAvatars(
+ myUserInfo: Option<MyUserInfo> = UserService.Instance.myUserInfo
+): boolean {
+ return myUserInfo
+ .map(m => m.local_user_view.local_user.show_avatars)
+ .unwrapOr(true);
}
-export function showScores(): boolean {
- return (
- UserService.Instance.myUserInfo?.local_user_view.local_user.show_scores ||
- !UserService.Instance.myUserInfo
- );
+export function showScores(
+ myUserInfo: Option<MyUserInfo> = UserService.Instance.myUserInfo
+): boolean {
+ return myUserInfo
+ .map(m => m.local_user_view.local_user.show_scores)
+ .unwrapOr(true);
}
export function isCakeDay(published: string): boolean {
// moment(undefined) or moment.utc(undefined) returns the current date/time
// moment(null) or moment.utc(null) returns null
- const createDate = moment.utc(published || null).local();
+ const createDate = moment.utc(published).local();
const currentDate = moment(new Date());
return (
interface NotifyInfo {
name: string;
- icon?: string;
+ icon: Option<string>;
link: string;
body: string;
}
let toast = Toastify({
text: `${htmlBody}<br />${info.name}`,
- avatar: info.icon ? info.icon : null,
+ avatar: info.icon,
backgroundColor: backgroundColor,
className: "text-dark",
close: true,
export function getListingTypeFromProps(
props: any,
- defaultListingType: ListingType
+ defaultListingType: ListingType,
+ myUserInfo = UserService.Instance.myUserInfo
): ListingType {
return props.match.params.listing_type
? routeListingTypeToEnum(props.match.params.listing_type)
- : UserService.Instance.myUserInfo
- ? Object.values(ListingType)[
- UserService.Instance.myUserInfo.local_user_view.local_user
- .default_listing_type
- ]
- : defaultListingType;
+ : myUserInfo.match({
+ some: me =>
+ Object.values(ListingType)[
+ me.local_user_view.local_user.default_listing_type
+ ],
+ none: defaultListingType,
+ });
}
export function getListingTypeFromPropsNoDefault(props: any): ListingType {
: DataType.Post;
}
-export function getSortTypeFromProps(props: any): SortType {
+export function getSortTypeFromProps(
+ props: any,
+ myUserInfo = UserService.Instance.myUserInfo
+): SortType {
return props.match.params.sort
? routeSortTypeToEnum(props.match.params.sort)
- : UserService.Instance.myUserInfo
- ? Object.values(SortType)[
- UserService.Instance.myUserInfo.local_user_view.local_user
- .default_sort_type
- ]
- : SortType.Active;
+ : myUserInfo.match({
+ some: mui =>
+ Object.values(SortType)[
+ mui.local_user_view.local_user.default_sort_type
+ ],
+ none: SortType.Active,
+ });
}
export function getPageFromProps(props: any): number {
}
}
+// TODO Should only use the return now, no state?
export function updatePersonBlock(
- data: BlockPersonResponse
-): PersonBlockView[] {
- if (data.blocked) {
- UserService.Instance.myUserInfo.person_blocks.push({
- person: UserService.Instance.myUserInfo.local_user_view.person,
- target: data.person_view.person,
- });
- toast(`${i18n.t("blocked")} ${data.person_view.person.name}`);
- } else {
- UserService.Instance.myUserInfo.person_blocks =
- UserService.Instance.myUserInfo.person_blocks.filter(
- i => i.target.id != data.person_view.person.id
- );
- toast(`${i18n.t("unblocked")} ${data.person_view.person.name}`);
- }
- return UserService.Instance.myUserInfo.person_blocks;
+ 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,
+ });
}
export function updateCommunityBlock(
- data: BlockCommunityResponse
-): CommunityBlockView[] {
- if (data.blocked) {
- UserService.Instance.myUserInfo.community_blocks.push({
- person: UserService.Instance.myUserInfo.local_user_view.person,
- community: data.community_view.community,
- });
- toast(`${i18n.t("blocked")} ${data.community_view.community.name}`);
- } else {
- UserService.Instance.myUserInfo.community_blocks =
- UserService.Instance.myUserInfo.community_blocks.filter(
- i => i.community.id != data.community_view.community.id
- );
- toast(`${i18n.t("unblocked")} ${data.community_view.community.name}`);
- }
- return UserService.Instance.myUserInfo.community_blocks;
+ 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,
+ });
}
export function createCommentLikeRes(
(a, b) =>
+a.comment_view.comment.removed - +b.comment_view.comment.removed ||
+a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
- hotRankComment(b.comment_view) - hotRankComment(a.comment_view)
+ hotRankComment(b.comment_view as CommentView) -
+ hotRankComment(a.comment_view as CommentView)
);
}
let node: CommentNodeI = {
comment_view: comment_view,
children: [],
+ depth: 0,
};
map.set(comment_view.comment.id, { ...node });
}
for (let comment_view of comments) {
let child = map.get(comment_view.comment.id);
let parent_id = comment_view.comment.parent_id;
- if (parent_id) {
- let parent = map.get(parent_id);
- // Necessary because blocked comment might not exist
- if (parent) {
- parent.children.push(child);
- }
- } else {
- tree.push(child);
- }
+ parent_id.match({
+ some: parentId => {
+ let parent = map.get(parentId);
+ // Necessary because blocked comment might not exist
+ if (parent) {
+ parent.children.push(child);
+ }
+ },
+ none: () => {
+ tree.push(child);
+ },
+ });
setDepth(child);
}
depth: 0,
};
- if (cv.comment.parent_id) {
- let parentComment = searchCommentTree(tree, cv.comment.parent_id);
- if (parentComment) {
- node.depth = parentComment.depth + 1;
- parentComment.children.unshift(node);
- }
- } else {
- tree.unshift(node);
- }
+ cv.comment.parent_id.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: () => {
+ tree.unshift(node);
+ },
+ });
}
export function searchCommentTree(
tree: CommentNodeI[],
id: number
-): CommentNodeI {
+): Option<CommentNodeI> {
for (let node of tree) {
if (node.comment_view.comment.id === id) {
- return node;
+ return Some(node);
}
for (const child of node.children) {
- const res = searchCommentTree([child], id);
+ let res = searchCommentTree([child], id);
- if (res) {
+ if (res.isSome()) {
return res;
}
}
}
- return null;
+ return None;
}
export const colorList: string[] = [
export function validTitle(title?: string): boolean {
// Initial title is null, minimum length is taken care of by textarea's minLength={3}
- if (title === null || title.length < 3) return true;
+ if (!title || title.length < 3) return true;
const regex = new RegExp(/.*\S.*/, "g");
return typeof window !== "undefined";
}
-export function setIsoData(context: any): IsoData {
- let isoData: IsoData = isBrowser()
- ? window.isoData
- : context.router.staticContext;
- return isoData;
+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 {
+ // 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 isoData: IsoData = {
+ path: json.path,
+ site_res: convertWindowJson(GetSiteResponse, json.site_res),
+ routeData: routeDataOut,
+ };
+ 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 setOptionalAuth(obj: any, auth = UserService.Instance.auth) {
- if (auth) {
- obj.auth = auth;
- }
-}
-
-export function authField(
- throwErr = true,
- auth = UserService.Instance.auth
-): string {
- if (auth == null && throwErr) {
- toast(i18n.t("not_logged_in"), "danger");
- throw "Not logged in";
- } else {
- return auth;
- }
-}
-
moment.updateLocale("en", {
relativeTime: {
future: "in %s",
}
export function showLocal(isoData: IsoData): boolean {
- return isoData.site_res.federated_instances?.linked.length > 0;
+ return isoData.site_res.federated_instances
+ .map(f => f.linked.length > 0)
+ .unwrapOr(false);
}
interface ChoicesValue {
export async function fetchCommunities(q: string) {
let form: Search = {
q,
- type_: SearchType.Communities,
- sort: SortType.TopAll,
- listing_type: ListingType.All,
- page: 1,
- limit: fetchLimit,
- auth: authField(false),
+ 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(),
};
let client = new LemmyHttp(httpBase);
return client.search(form);
export async function fetchUsers(q: string) {
let form: Search = {
q,
- type_: SearchType.Users,
- sort: SortType.TopAll,
- listing_type: ListingType.All,
- page: 1,
- limit: fetchLimit,
- auth: authField(false),
+ 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(),
};
let client = new LemmyHttp(httpBase);
return client.search(form);
}
export function personSelectName(pvs: PersonViewSafe): string {
- let pName = pvs.person.display_name || pvs.person.name;
+ let pName = pvs.person.display_name.unwrapOr(pvs.person.name);
return pvs.person.local ? pName : `${hostname(pvs.person.actor_id)}/${pName}`;
}
}
export function isBanned(ps: PersonSafe): boolean {
+ let expires = ps.ban_expires;
// Add Z to convert from UTC date
- if (ps.ban_expires) {
- if (ps.banned && new Date(ps.ban_expires + "Z") > new Date()) {
+ // TODO this check probably isn't necessary anymore
+ if (expires.isSome()) {
+ if (ps.banned && new Date(expires.unwrap() + "Z") > new Date()) {
return true;
} else {
return false;
array.push(...new_item);
}
}
+
+export function auth(throwErr = true): Result<string, string> {
+ return UserService.Instance.auth(throwErr);
+}
+
+export function enableDownvotes(siteRes: GetSiteResponse): boolean {
+ return siteRes.site_view.map(s => s.site.enable_downvotes).unwrapOr(true);
+}
+
+export function enableNsfw(siteRes: GetSiteResponse): boolean {
+ return siteRes.site_view.map(s => s.site.enable_nsfw).unwrapOr(false);
+}
"skipLibCheck": true,\r
"noUnusedParameters": true,\r
"noImplicitReturns": true,\r
- "noFallthroughCasesInSwitch": true\r
+ "experimentalDecorators": true,\r
+ "noFallthroughCasesInSwitch": true\r
},\r
"include": [\r
"src/**/*",\r
jsesc "^2.5.1"
source-map "^0.5.0"
+"@babel/generator@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d"
+ integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==
+ dependencies:
+ "@babel/types" "^7.18.2"
+ "@jridgewell/gen-mapping" "^0.3.0"
+ jsesc "^2.5.1"
+
"@babel/helper-annotate-as-pure@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862"
"@babel/helper-replace-supers" "^7.16.7"
"@babel/helper-split-export-declaration" "^7.16.7"
+"@babel/helper-create-class-features-plugin@^7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz#fac430912606331cb075ea8d82f9a4c145a4da19"
+ integrity sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.16.7"
+ "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-function-name" "^7.17.9"
+ "@babel/helper-member-expression-to-functions" "^7.17.7"
+ "@babel/helper-optimise-call-expression" "^7.16.7"
+ "@babel/helper-replace-supers" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+
"@babel/helper-create-regexp-features-plugin@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.7.tgz#0cb82b9bac358eb73bfbd73985a776bfa6b14d48"
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-environment-visitor@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd"
+ integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==
+
"@babel/helper-explode-assignable-expression@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a"
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-member-expression-to-functions@^7.17.7":
+ version "7.17.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4"
+ integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==
+ dependencies:
+ "@babel/types" "^7.17.0"
+
"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
+"@babel/helper-plugin-utils@^7.17.12":
+ version "7.17.12"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96"
+ integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==
+
"@babel/helper-remap-async-to-generator@^7.16.8":
version "7.16.8"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3"
"@babel/traverse" "^7.16.7"
"@babel/types" "^7.16.7"
+"@babel/helper-replace-supers@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz#41fdfcc9abaf900e18ba6e5931816d9062a7b2e0"
+ integrity sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.18.2"
+ "@babel/helper-member-expression-to-functions" "^7.17.7"
+ "@babel/helper-optimise-call-expression" "^7.16.7"
+ "@babel/traverse" "^7.18.2"
+ "@babel/types" "^7.18.2"
+
"@babel/helper-simple-access@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz#d656654b9ea08dbb9659b69d61063ccd343ff0f7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef"
integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==
+"@babel/parser@^7.18.5":
+ version "7.18.5"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c"
+ integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==
+
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050"
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-syntax-class-static-block" "^7.14.5"
+"@babel/plugin-proposal-decorators@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.2.tgz#dbe4086d2d42db489399783c3aa9272e9700afd4"
+ integrity sha512-kbDISufFOxeczi0v4NQP3p5kIeW6izn/6klfWBrIIdGZZe4UpHR+QU03FAoWjGGd9SUXAwbw2pup1kaL4OQsJQ==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.18.0"
+ "@babel/helper-plugin-utils" "^7.17.12"
+ "@babel/helper-replace-supers" "^7.18.2"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/plugin-syntax-decorators" "^7.17.12"
+ charcodes "^0.2.0"
+
"@babel/plugin-proposal-dynamic-import@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2"
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
+"@babel/plugin-syntax-decorators@^7.17.12":
+ version "7.17.12"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.17.12.tgz#02e8f678602f0af8222235271efea945cfdb018a"
+ integrity sha512-D1Hz0qtGTza8K2xGyEdVNCYLdVHukAcbQr4K3/s6r/esadyEriZovpJimQOpu8ju4/jV8dW/1xdaE0UpDroidw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.17.12"
+
"@babel/plugin-syntax-dynamic-import@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.18.2":
+ version "7.18.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd"
+ integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==
+ dependencies:
+ "@babel/code-frame" "^7.16.7"
+ "@babel/generator" "^7.18.2"
+ "@babel/helper-environment-visitor" "^7.18.2"
+ "@babel/helper-function-name" "^7.17.9"
+ "@babel/helper-hoist-variables" "^7.16.7"
+ "@babel/helper-split-export-declaration" "^7.16.7"
+ "@babel/parser" "^7.18.5"
+ "@babel/types" "^7.18.4"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/types@^7", "@babel/types@^7.0.0-beta.54", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.4.4":
version "7.16.8"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.8.tgz#0ba5da91dd71e0a4e7781a30f22770831062e3c1"
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.18.2", "@babel/types@^7.18.4":
+ version "7.18.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354"
+ integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.16.7"
+ to-fast-properties "^2.0.0"
+
"@discoveryjs/json-ext@^0.5.0":
version "0.5.6"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
update-notifier "^2.2.0"
yargs "^8.0.2"
+"@jridgewell/gen-mapping@^0.3.0":
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9"
+ integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.0"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
"@jridgewell/resolve-uri@^3.0.3":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c"
integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==
+"@jridgewell/set-array@^1.0.0":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea"
+ integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==
+
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.11"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec"
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
+"@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.13"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea"
+ integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
"@leichtgewicht/ip-codec@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
+"@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"
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+charcodes@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/charcodes/-/charcodes-0.2.0.tgz#5208d327e6cc05f99eb80ffc814707572d1f14e4"
+ integrity sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==
+
check-password-strength@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/check-password-strength/-/check-password-strength-2.0.5.tgz#bb10da01d24bd69e5e629c5cea2a6b729e5061af"
resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-1.0.6.tgz#74abfd619df370b9d54ab14475568e97dd64c0c1"
integrity sha1-dKv9YZ3zcLnVSrFEdVaOl91kwME=
+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.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
dependencies:
invert-kv "^1.0.0"
-lemmy-js-client@0.16.4:
- version "0.16.4"
- resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.16.4.tgz#d24bae2b0d93c4d13eb4a5e5ddceaa2999f94740"
- integrity sha512-EFHl6tbFZ0jk8VE68bgZOXoWuNHVzfcsyoAEZeHP6f8PkQ1g9zjxB/e3b5cIG2fFzOLsYIDh2w/SJy21WkFiiA==
+lemmy-js-client@0.17.0-rc.30:
+ version "0.17.0-rc.30"
+ resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.30.tgz#91cc926e662a5cd27f87cd2e6cdfcd210176745a"
+ integrity sha512-AcG8IZNNTa54BAXEqsL/QNlyPPwLntRLWpIOw9S3u84824d5inL7UCKnyx0UMbQklUuH/D3E2K9WNmZiUdvr3A==
levn@^0.4.1:
version "0.4.1"
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@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326"