* Extracting user settings and profile page.
- Auto-collapsing dropdown and navbar on link clicks.
- Fixes #180
* Adding User and Community blocking. Fixes #295
- Added a new settings page.
- Switched to myUserInfo.
- Removing GetFollowedCommunities endpoint
* Fixing blocks
"husky": "^7.0.1",
"import-sort-style-module": "^6.0.0",
"iso-639-1": "^2.1.9",
- "lemmy-js-client": "0.11.4-rc.12",
+ "lemmy-js-client": "0.11.4-rc.14",
"lint-staged": "^11.0.1",
"mini-css-extract-plugin": "^2.1.0",
"node-fetch": "^2.6.1",
}
}
-.dropdown-menu {
+.dropdown-content {
+ position: absolute;
+ background-color: var(--light);
+ min-width: 160px;
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 2000;
}
<>
<Provider i18next={i18n}>
<div>
- <Theme localUserView={siteRes.my_user} />
+ <Theme myUserInfo={siteRes.my_user} />
{siteRes &&
siteRes.site_view &&
this.props.siteRes.site_view.site.icon && (
unreadCount: number;
searchParam: string;
toggleSearch: boolean;
+ showDropdown: boolean;
onSiteBanner?(url: string): any;
}
expanded: false,
searchParam: "",
toggleSearch: false,
+ showDropdown: false,
};
subscription: any;
}
}
- handleSearchParam(i: Navbar, event: any) {
- i.state.searchParam = event.target.value;
- i.setState(i.state);
+ componentWillUnmount() {
+ this.wsSub.unsubscribe();
+ this.userSub.unsubscribe();
+ this.unreadCountSub.unsubscribe();
}
updateUrl() {
const searchParam = this.state.searchParam;
this.setState({ searchParam: "" });
this.setState({ toggleSearch: false });
+ this.setState({ showDropdown: false, expanded: false });
if (searchParam === "") {
this.context.router.history.push(`/search/`);
} else {
}
}
- handleSearchSubmit(i: Navbar, event: any) {
- event.preventDefault();
- i.updateUrl();
- }
-
- handleSearchBtn(i: Navbar, event: any) {
- event.preventDefault();
- i.setState({ toggleSearch: true });
-
- i.searchTextField.current.focus();
- const offsetWidth = i.searchTextField.current.offsetWidth;
- if (i.state.searchParam && offsetWidth > 100) {
- i.updateUrl();
- }
- }
-
- handleSearchBlur(i: Navbar, event: any) {
- if (!(event.relatedTarget && event.relatedTarget.name !== "search-btn")) {
- i.state.toggleSearch = false;
- i.setState(i.state);
- }
- }
-
render() {
return this.navbar();
}
- componentWillUnmount() {
- this.wsSub.unsubscribe();
- this.userSub.unsubscribe();
- this.unreadCountSub.unsubscribe();
- }
-
// TODO class active corresponding to current page
navbar() {
- let localUserView =
- UserService.Instance.localUserView || this.props.site_res.my_user;
+ 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">
<div class="container">
{this.props.site_res.site_view && (
- <Link
+ <button
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"
- to="/"
+ className="d-flex align-items-center navbar-brand mr-md-3 btn btn-link"
+ onClick={linkEvent(this, this.handleGotoHome)}
>
{this.props.site_res.site_view.site.icon && showAvatars() && (
<PictrsImage
/>
)}
{this.props.site_res.site_view.site.name}
- </Link>
+ </button>
)}
{this.state.isLoggedIn && (
- <Link
- className="ml-auto p-1 navbar-toggler nav-link border-0"
- to="/inbox"
+ <button
+ className="ml-auto p-1 navbar-toggler nav-link border-0 btn btn-link"
+ onClick={linkEvent(this, this.handleGotoInbox)}
title={i18n.t("inbox")}
>
<Icon icon="bell" />
{this.state.unreadCount}
</span>
)}
- </Link>
+ </button>
)}
<button
class="navbar-toggler border-0 p-1"
>
<ul class="navbar-nav my-2 mr-auto">
<li class="nav-item">
- <Link
- className="nav-link"
- to="/communities"
+ <button
+ className="nav-link btn btn-link"
+ onClick={linkEvent(this, this.handleGotoCommunities)}
title={i18n.t("communities")}
>
{i18n.t("communities")}
- </Link>
+ </button>
</li>
<li class="nav-item">
- <Link
- className="nav-link"
- to={{
- pathname: "/create_post",
- state: { prevPath: this.currentLocation },
- }}
+ <button
+ className="nav-link btn btn-link"
+ onClick={linkEvent(this, this.handleGotoCreatePost)}
title={i18n.t("create_post")}
>
{i18n.t("create_post")}
- </Link>
+ </button>
</li>
{this.canCreateCommunity && (
<li class="nav-item">
- <Link
- className="nav-link"
- to="/create_community"
+ <button
+ className="nav-link btn btn-link"
+ onClick={linkEvent(this, this.handleGotoCreateCommunity)}
title={i18n.t("create_community")}
>
{i18n.t("create_community")}
- </Link>
+ </button>
</li>
)}
<li class="nav-item">
<ul class="navbar-nav my-2">
{this.canAdmin && (
<li className="nav-item">
- <Link
- className="nav-link"
- to={`/admin`}
+ <button
+ className="nav-link btn btn-link"
+ onClick={linkEvent(this, this.handleGotoAdmin)}
title={i18n.t("admin_settings")}
>
<Icon icon="settings" />
- </Link>
+ </button>
</li>
)}
</ul>
</li>
</ul>
<ul class="navbar-nav">
- <li className="nav-item">
- <Link
- className="nav-link"
- to={`/u/${localUserView.person.name}`}
- title={i18n.t("settings")}
+ <li class="nav-item dropdown">
+ <button
+ class="nav-link btn btn-link dropdown-toggle"
+ onClick={linkEvent(this, this.handleShowDropdown)}
+ id="navbarDropdown"
+ role="button"
+ aria-expanded="false"
>
<span>
- {localUserView.person.avatar && showAvatars() && (
- <PictrsImage src={localUserView.person.avatar} icon />
+ {person.avatar && showAvatars() && (
+ <PictrsImage src={person.avatar} icon />
)}
- {localUserView.person.display_name
- ? localUserView.person.display_name
- : localUserView.person.name}
+ {person.display_name
+ ? person.display_name
+ : person.name}
</span>
- </Link>
+ </button>
+ {this.state.showDropdown && (
+ <div class="dropdown-content">
+ <li className="nav-item">
+ <button
+ className="nav-link btn btn-link"
+ onClick={linkEvent(this, this.handleGotoProfile)}
+ title={i18n.t("profile")}
+ >
+ <Icon icon="user" classes="mr-1" />
+ {i18n.t("profile")}
+ </button>
+ </li>
+ <li className="nav-item">
+ <button
+ className="nav-link btn btn-link"
+ onClick={linkEvent(this, this.handleGotoSettings)}
+ title={i18n.t("settings")}
+ >
+ <Icon icon="settings" classes="mr-1" />
+ {i18n.t("settings")}
+ </button>
+ </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>
</ul>
</>
) : (
<ul class="navbar-nav my-2">
<li className="ml-2 nav-item">
- <Link
+ <button
className="btn btn-success"
- to="/login"
+ onClick={linkEvent(this, this.handleGotoLogin)}
title={i18n.t("login_sign_up")}
>
{i18n.t("login_sign_up")}
- </Link>
+ </button>
</li>
</ul>
)}
i.setState(i.state);
}
+ handleSearchParam(i: Navbar, event: any) {
+ i.state.searchParam = event.target.value;
+ i.setState(i.state);
+ }
+
+ handleSearchSubmit(i: Navbar, event: any) {
+ event.preventDefault();
+ i.updateUrl();
+ }
+
+ handleSearchBtn(i: Navbar, event: any) {
+ event.preventDefault();
+ i.setState({ toggleSearch: true });
+
+ i.searchTextField.current.focus();
+ const offsetWidth = i.searchTextField.current.offsetWidth;
+ if (i.state.searchParam && offsetWidth > 100) {
+ i.updateUrl();
+ }
+ }
+
+ handleSearchBlur(i: Navbar, event: any) {
+ if (!(event.relatedTarget && event.relatedTarget.name !== "search-btn")) {
+ i.state.toggleSearch = false;
+ i.setState(i.state);
+ }
+ }
+
+ handleLogoutClick(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ UserService.Instance.logout();
+ i.context.router.history.push("/");
+ location.reload();
+ }
+
+ handleGotoSettings(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push("/settings");
+ }
+
+ handleGotoProfile(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push(
+ `/u/${UserService.Instance.myUserInfo.local_user_view.person.name}`
+ );
+ }
+
+ handleGotoCreatePost(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push("/create_post", {
+ prevPath: i.currentLocation,
+ });
+ }
+
+ handleGotoCreateCommunity(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push(`/create_community`);
+ }
+
+ handleGotoCommunities(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push(`/communities`);
+ }
+
+ handleGotoHome(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push(`/`);
+ }
+
+ handleGotoInbox(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push(`/inbox`);
+ }
+
+ handleGotoAdmin(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push(`/admin`);
+ }
+
+ handleGotoLogin(i: Navbar) {
+ i.setState({ showDropdown: false, expanded: false });
+ i.context.router.history.push(`/login`);
+ }
+
+ handleShowDropdown(i: Navbar) {
+ i.state.showDropdown = !i.state.showDropdown;
+ i.setState(i.state);
+ }
+
parseMessage(msg: any) {
let op = wsUserOp(msg);
console.log(msg);
// This is only called on a successful login
let data = wsJsonToRes<GetSiteResponse>(msg).data;
console.log(data.my_user);
- UserService.Instance.localUserView = data.my_user;
- setTheme(UserService.Instance.localUserView.local_user.theme);
+ UserService.Instance.myUserInfo = data.my_user;
+ setTheme(
+ UserService.Instance.myUserInfo.local_user_view.local_user.theme
+ );
i18n.changeLanguage(getLanguage());
this.state.isLoggedIn = true;
this.setState(this.state);
if (this.state.isLoggedIn) {
if (
data.recipient_ids.includes(
- UserService.Instance.localUserView.local_user.id
+ UserService.Instance.myUserInfo.local_user_view.local_user.id
)
) {
this.state.replies.push(data.comment_view);
if (this.state.isLoggedIn) {
if (
data.private_message_view.recipient.id ==
- UserService.Instance.localUserView.person.id
+ UserService.Instance.myUserInfo.local_user_view.person.id
) {
this.state.messages.push(data.private_message_view);
this.state.unreadCount++;
get canAdmin(): boolean {
return (
- UserService.Instance.localUserView &&
+ UserService.Instance.myUserInfo &&
this.props.site_res.admins
.map(a => a.person.id)
- .includes(UserService.Instance.localUserView.person.id)
+ .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
);
}
}
requestNotificationPermission() {
- if (UserService.Instance.localUserView) {
+ if (UserService.Instance.myUserInfo) {
document.addEventListener("DOMContentLoaded", function () {
if (!Notification) {
toast(i18n.t("notifications_error"), "danger");
import { Component } from "inferno";
import { Helmet } from "inferno-helmet";
-import { LocalUserSettingsView } from "lemmy-js-client";
+import { MyUserInfo } from "lemmy-js-client";
interface Props {
- localUserView: LocalUserSettingsView | undefined;
+ myUserInfo: MyUserInfo | undefined;
}
export class Theme extends Component<Props> {
render() {
- let user = this.props.localUserView;
- let hasTheme = user && user.local_user.theme !== "browser";
+ let user = this.props.myUserInfo;
+ let hasTheme = user && user.local_user_view.local_user.theme !== "browser";
return (
<Helmet>
<link
rel="stylesheet"
type="text/css"
- href={`/static/assets/css/themes/${user.local_user.theme}.min.css`}
+ href={`/static/assets/css/themes/${user.local_user_view.local_user.theme}.min.css`}
/>
) : (
[
render() {
return (
<div class="mb-3">
- {UserService.Instance.localUserView ? (
+ {UserService.Instance.myUserInfo ? (
<MarkdownTextArea
initialContent={
this.props.edit
console.log(msg);
// Only do the showing and hiding if logged in
- if (UserService.Instance.localUserView) {
+ if (UserService.Instance.myUserInfo) {
if (
op == UserOperation.CreateComment ||
op == UserOperation.EditComment
AddModToCommunity,
BanFromCommunity,
BanPerson,
+ BlockPerson,
CommentView,
CommunityModeratorView,
CreateCommentLike,
)}
</button>
)}
- {UserService.Instance.localUserView && !this.props.viewOnly && (
+ {UserService.Instance.myUserInfo && !this.props.viewOnly && (
<>
<button
className={`btn btn-link btn-animate ${
) : (
<>
{!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()}
+ <>
+ <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.handleBlockUserClick
+ )}
+ data-tippy-content={i18n.t("block_user")}
+ aria-label={i18n.t("block_user")}
>
- <Icon icon="mail" />
- </Link>
- </button>
+ <Icon icon="slash" />
+ </button>
+ </>
)}
<button
class="btn btn-link btn-animate text-muted"
get myComment(): boolean {
return (
this.props.node.comment_view.creator.id ==
- UserService.Instance.localUserView?.person.id
+ UserService.Instance.myUserInfo?.local_user_view.person.id
);
}
.concat(this.props.moderators.map(m => m.moderator.id));
return canMod(
- UserService.Instance.localUserView,
+ UserService.Instance.myUserInfo,
adminsThenMods,
this.props.node.comment_view.creator.id
);
return (
this.props.admins &&
canMod(
- UserService.Instance.localUserView,
+ 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.localUserView &&
+ UserService.Instance.myUserInfo &&
this.props.node.comment_view.creator.id !=
- UserService.Instance.localUserView.person.id &&
- UserService.Instance.localUserView.person.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.localUserView &&
+ UserService.Instance.myUserInfo &&
this.props.node.comment_view.creator.id !=
- UserService.Instance.localUserView.person.id &&
- UserService.Instance.localUserView.person.id ==
+ UserService.Instance.myUserInfo.local_user_view.person.id &&
+ UserService.Instance.myUserInfo.local_user_view.person.id ==
this.props.admins[0].person.id
);
}
i.setState(i.state);
}
+ handleBlockUserClick(i: CommentNode) {
+ let blockUserForm: BlockPerson = {
+ person_id: i.props.node.comment_view.creator.id,
+ block: true,
+ auth: authField(),
+ };
+ WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
+ }
+
handleDeleteClick(i: CommentNode) {
let comment = i.props.node.comment_view.comment;
let deleteForm: DeleteComment = {
accept="image/*,video/*"
name={this.id}
class="d-none"
- disabled={!UserService.Instance.localUserView}
+ disabled={!UserService.Instance.myUserInfo}
onChange={linkEvent(this, this.handleImageUpload)}
/>
</form>
className={`btn btn-outline-secondary
${this.state.type_ == ListingType.Subscribed && "active"}
${
- UserService.Instance.localUserView == undefined
+ UserService.Instance.myUserInfo == undefined
? "disabled"
: "pointer"
}
value={ListingType.Subscribed}
checked={this.state.type_ == ListingType.Subscribed}
onChange={linkEvent(this, this.handleTypeChange)}
- disabled={UserService.Instance.localUserView == undefined}
+ disabled={UserService.Instance.myUserInfo == undefined}
/>
{i18n.t("subscribed")}
</label>
<label
htmlFor={`file-upload-${this.id}`}
className={`mb-0 ${
- UserService.Instance.localUserView && "pointer"
+ UserService.Instance.myUserInfo && "pointer"
}`}
data-tippy-content={i18n.t("upload_image")}
>
accept="image/*,video/*"
name="file"
class="d-none"
- disabled={!UserService.Instance.localUserView}
+ disabled={!UserService.Instance.myUserInfo}
onChange={linkEvent(this, this.handleImageUpload)}
/>
</form>
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<defs>
+ <symbol id="icon-log-out" viewBox="0 0 24 24">
+ <path d="M9 20h-4c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-14c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293h4c0.552 0 1-0.448 1-1s-0.448-1-1-1h-4c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v14c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h4c0.552 0 1-0.448 1-1s-0.448-1-1-1zM18.586 11h-9.586c-0.552 0-1 0.448-1 1s0.448 1 1 1h9.586l-3.293 3.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5-5c0.092-0.092 0.166-0.202 0.217-0.324 0.15-0.362 0.078-0.795-0.217-1.090l-5-5c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"></path>
+ </symbol>
+ <symbol id="icon-user" viewBox="0 0 24 24">
+ <path d="M21 21v-2c0-1.38-0.561-2.632-1.464-3.536s-2.156-1.464-3.536-1.464h-8c-1.38 0-2.632 0.561-3.536 1.464s-1.464 2.156-1.464 3.536v2c0 0.552 0.448 1 1 1s1-0.448 1-1v-2c0-0.829 0.335-1.577 0.879-2.121s1.292-0.879 2.121-0.879h8c0.829 0 1.577 0.335 2.121 0.879s0.879 1.292 0.879 2.121v2c0 0.552 0.448 1 1 1s1-0.448 1-1zM17 7c0-1.38-0.561-2.632-1.464-3.536s-2.156-1.464-3.536-1.464-2.632 0.561-3.536 1.464-1.464 2.156-1.464 3.536 0.561 2.632 1.464 3.536 2.156 1.464 3.536 1.464 2.632-0.561 3.536-1.464 1.464-2.156 1.464-3.536zM15 7c0 0.829-0.335 1.577-0.879 2.121s-1.292 0.879-2.121 0.879-1.577-0.335-2.121-0.879-0.879-1.292-0.879-2.121 0.335-1.577 0.879-2.121 1.292-0.879 2.121-0.879 1.577 0.335 2.121 0.879 0.879 1.292 0.879 2.121z"></path>
+ </symbol>
+ <symbol id="icon-slash" viewBox="0 0 24 24">
+ <path d="M23 12c0-3.037-1.232-5.789-3.222-7.778s-4.741-3.222-7.778-3.222-5.789 1.232-7.778 3.222-3.222 4.741-3.222 7.778 1.232 5.789 3.222 7.778 4.741 3.222 7.778 3.222 5.789-1.232 7.778-3.222 3.222-4.741 3.222-7.778zM19.032 17.618l-12.65-12.65c1.54-1.232 3.493-1.968 5.618-1.968 2.486 0 4.734 1.006 6.364 2.636s2.636 3.878 2.636 6.364c0 2.125-0.736 4.078-1.968 5.618zM4.968 6.382l12.65 12.65c-1.54 1.232-3.493 1.968-5.618 1.968-2.486 0-4.734-1.006-6.364-2.636s-2.636-3.878-2.636-6.364c0-2.125 0.736-4.078 1.968-5.618z"></path>
+ </symbol>
<symbol id="icon-menu" viewBox="0 0 24 24">
<path d="M3 13h18c0.552 0 1-0.448 1-1s-0.448-1-1-1h-18c-0.552 0-1 0.448-1 1s0.448 1 1 1zM3 7h18c0.552 0 1-0.448 1-1s-0.448-1-1-1h-18c-0.552 0-1 0.448-1 1s0.448 1 1 1zM3 19h18c0.552 0 1-0.448 1-1s-0.448-1-1-1h-18c-0.552 0-1 0.448-1 1s0.448 1 1 1z"></path>
</symbol>
} from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
-import { WebSocketService } from "../../services";
+import { UserService, WebSocketService } from "../../services";
import {
authField,
capitalizeFirstLetter,
let data = wsJsonToRes<CommunityResponse>(msg).data;
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,
+ });
} else if (op == UserOperation.EditCommunity) {
let data = wsJsonToRes<CommunityResponse>(msg).data;
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;
+ }
}
}
}
import {
AddModToCommunityResponse,
BanFromCommunityResponse,
+ BlockPersonResponse,
CommentResponse,
CommentView,
CommunityResponse,
setOptionalAuth,
setupTippy,
toast,
+ updatePersonBlock,
wsClient,
wsJsonToRes,
wsSubscribe,
let sort: SortType = pathSplit[6]
? SortType[pathSplit[6]]
- : UserService.Instance.localUserView
+ : UserService.Instance.myUserInfo
? Object.values(SortType)[
- UserService.Instance.localUserView.local_user.default_sort_type
+ UserService.Instance.myUserInfo.local_user_view.local_user
+ .default_sort_type
]
: SortType.Active;
} else if (op == UserOperation.CreatePost) {
let data = wsJsonToRes<PostResponse>(msg).data;
this.state.posts.unshift(data.post_view);
- if (UserService.Instance.localUserView?.local_user.show_new_post_notifs) {
+ if (
+ UserService.Instance.myUserInfo?.local_user_view.local_user
+ .show_new_post_notifs
+ ) {
notifyPost(data.post_view, this.context.router);
}
this.setState(this.state);
let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data.comment_view, this.state.comments);
this.setState(this.state);
+ } else if (op == UserOperation.BlockPerson) {
+ let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ updatePersonBlock(data);
}
}
}
this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage);
- if (!UserService.Instance.localUserView && isBrowser()) {
+ if (!UserService.Instance.myUserInfo && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
<a
class="btn btn-secondary btn-sm mr-2"
href="#"
- onClick={linkEvent(community.id, this.handleUnsubscribe)}
+ onClick={linkEvent(this, this.handleUnsubscribe)}
>
<Icon icon="check" classes="icon-inline text-success mr-1" />
{i18n.t("joined")}
<a
class="btn btn-secondary btn-block"
href="#"
- onClick={linkEvent(
- community_view.community.id,
- this.handleSubscribe
- )}
+ onClick={linkEvent(this, this.handleSubscribe)}
>
{i18n.t("subscribe")}
</a>
handleLeaveModTeamClick(i: Sidebar) {
let form: AddModToCommunity = {
- person_id: UserService.Instance.localUserView.person.id,
+ person_id: UserService.Instance.myUserInfo.local_user_view.person.id,
community_id: i.props.community_view.community.id,
added: false,
auth: authField(),
i.setState(i.state);
}
- handleUnsubscribe(communityId: number, event: any) {
+ handleUnsubscribe(i: Sidebar, event: any) {
event.preventDefault();
+ let community_id = i.props.community_view.community.id;
let form: FollowCommunity = {
- community_id: communityId,
+ community_id,
follow: false,
auth: authField(),
};
WebSocketService.Instance.send(wsClient.followCommunity(form));
+
+ // Update myUserInfo
+ UserService.Instance.myUserInfo.follows =
+ UserService.Instance.myUserInfo.follows.filter(
+ i => i.community.id != community_id
+ );
}
- handleSubscribe(communityId: number, event: any) {
+ handleSubscribe(i: Sidebar, event: any) {
event.preventDefault();
+ let community_id = i.props.community_view.community.id;
let form: FollowCommunity = {
- community_id: communityId,
+ community_id,
follow: true,
auth: authField(),
};
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,
+ });
}
private get amTopMod(): boolean {
return (
this.props.moderators[0].moderator.id ==
- UserService.Instance.localUserView.person.id
+ UserService.Instance.myUserInfo.local_user_view.person.id
);
}
get canMod(): boolean {
return (
- UserService.Instance.localUserView &&
+ UserService.Instance.myUserInfo &&
this.props.moderators
.map(m => m.moderator.id)
- .includes(UserService.Instance.localUserView.person.id)
+ .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
);
}
get canAdmin(): boolean {
return (
- UserService.Instance.localUserView &&
+ UserService.Instance.myUserInfo &&
this.props.admins
.map(a => a.person.id)
- .includes(UserService.Instance.localUserView.person.id)
+ .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
);
}
import {
AddAdminResponse,
BanPersonResponse,
+ BlockPersonResponse,
CommentResponse,
CommentView,
- CommunityFollowerView,
CommunityView,
GetComments,
GetCommentsResponse,
- GetFollowedCommunitiesResponse,
GetPosts,
GetPostsResponse,
GetSiteResponse,
setupTippy,
showLocal,
toast,
+ updatePersonBlock,
wsClient,
wsJsonToRes,
wsSubscribe,
import { SiteForm } from "./site-form";
interface HomeState {
- subscribedCommunities: CommunityFollowerView[];
trendingCommunities: CommunityView[];
siteRes: GetSiteResponse;
showEditSite: boolean;
private isoData = setIsoData(this.context);
private subscription: Subscription;
private emptyState: HomeState = {
- subscribedCommunities: [],
trendingCommunities: [],
siteRes: this.isoData.site_res,
showEditSite: false,
this.state.comments = this.isoData.routeData[0].comments;
}
this.state.trendingCommunities = this.isoData.routeData[1].communities;
- if (UserService.Instance.localUserView) {
- this.state.subscribedCommunities =
- this.isoData.routeData[2].communities;
- }
this.state.loading = false;
} else {
this.fetchTrendingCommunities();
this.fetchData();
- if (UserService.Instance.localUserView) {
- WebSocketService.Instance.send(
- wsClient.getFollowedCommunities({
- auth: authField(),
- })
- );
- }
}
setupTippy();
// TODO figure out auth default_listingType, default_sort_type
let type_: ListingType = pathSplit[5]
? ListingType[pathSplit[5]]
- : UserService.Instance.localUserView
+ : UserService.Instance.myUserInfo
? Object.values(ListingType)[
- UserService.Instance.localUserView.local_user.default_listing_type
+ UserService.Instance.myUserInfo.local_user_view.local_user
+ .default_listing_type
]
: ListingType.Local;
let sort: SortType = pathSplit[7]
? SortType[pathSplit[7]]
- : UserService.Instance.localUserView
+ : UserService.Instance.myUserInfo
? Object.values(SortType)[
- UserService.Instance.localUserView.local_user.default_sort_type
+ UserService.Instance.myUserInfo.local_user_view.local_user
+ .default_sort_type
]
: SortType.Active;
};
promises.push(req.client.listCommunities(trendingCommunitiesForm));
- if (req.auth) {
- promises.push(req.client.getFollowedCommunities({ auth: req.auth }));
- }
-
return promises;
}
return (
<div class="row">
<div class="col-12">
- {UserService.Instance.localUserView &&
- this.state.subscribedCommunities.length > 0 && (
+ {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)}
</div>
</div>
- {UserService.Instance.localUserView &&
- this.state.subscribedCommunities.length > 0 && (
+ {UserService.Instance.myUserInfo &&
+ UserService.Instance.myUserInfo.follows.length > 0 && (
<div class="card border-secondary mb-3">
<div class="card-body">{this.subscribedCommunities()}</div>
</div>
</T>
</h5>
<ul class="list-inline mb-0">
- {this.state.subscribedCommunities.map(cfv => (
+ {UserService.Instance.myUserInfo.follows.map(cfv => (
<li class="list-inline-item d-inline-block">
<CommunityLink community={cfv.community} />
</li>
<Icon icon="rss" classes="text-muted small" />
</a>
)}
- {UserService.Instance.localUserView &&
+ {UserService.Instance.myUserInfo &&
this.state.listingType == ListingType.Subscribed && (
<a
href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`}
get canAdmin(): boolean {
return (
- UserService.Instance.localUserView &&
+ UserService.Instance.myUserInfo &&
this.state.siteRes.admins
.map(a => a.person.id)
- .includes(UserService.Instance.localUserView.person.id)
+ .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
);
}
wsClient.communityJoin({ community_id: 0 })
);
this.fetchData();
- } else if (op == UserOperation.GetFollowedCommunities) {
- let data = wsJsonToRes<GetFollowedCommunitiesResponse>(msg).data;
- this.state.subscribedCommunities = data.communities;
- this.setState(this.state);
} else if (op == UserOperation.ListCommunities) {
let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
this.state.trendingCommunities = data.communities;
let nsfwCheck =
!nsfw ||
(nsfw &&
- UserService.Instance.localUserView &&
- UserService.Instance.localUserView.local_user.show_nsfw);
+ UserService.Instance.myUserInfo &&
+ UserService.Instance.myUserInfo.local_user_view.local_user.show_nsfw);
// 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 (
- this.state.subscribedCommunities
+ UserService.Instance.myUserInfo.follows
.map(c => c.community.id)
.includes(data.post_view.community.id)
) {
this.state.posts.unshift(data.post_view);
if (
- UserService.Instance.localUserView?.local_user
+ UserService.Instance.myUserInfo?.local_user_view.local_user
.show_new_post_notifs
) {
notifyPost(data.post_view, this.context.router);
if (data.post_view.post.local) {
this.state.posts.unshift(data.post_view);
if (
- UserService.Instance.localUserView?.local_user
+ UserService.Instance.myUserInfo?.local_user_view.local_user
.show_new_post_notifs
) {
notifyPost(data.post_view, this.context.router);
} else {
this.state.posts.unshift(data.post_view);
if (
- UserService.Instance.localUserView?.local_user.show_new_post_notifs
+ UserService.Instance.myUserInfo?.local_user_view.local_user
+ .show_new_post_notifs
) {
notifyPost(data.post_view, this.context.router);
}
// If you're on subscribed, only push it if you're subscribed.
if (this.state.listingType == ListingType.Subscribed) {
if (
- this.state.subscribedCommunities
+ UserService.Instance.myUserInfo.follows
.map(c => c.community.id)
.includes(data.comment_view.community.id)
) {
let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data.comment_view, this.state.comments);
this.setState(this.state);
+ } else if (op == UserOperation.BlockPerson) {
+ let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ updatePersonBlock(data);
}
}
}
get isAdminOrMod(): boolean {
let isAdmin =
- UserService.Instance.localUserView &&
+ UserService.Instance.myUserInfo &&
this.isoData.site_res.admins
.map(a => a.person.id)
- .includes(UserService.Instance.localUserView.person.id);
+ .includes(UserService.Instance.myUserInfo.local_user_view.person.id);
let isMod =
- UserService.Instance.localUserView &&
+ UserService.Instance.myUserInfo &&
this.state.communityMods &&
this.state.communityMods
.map(m => m.moderator.id)
- .includes(UserService.Instance.localUserView.person.id);
+ .includes(UserService.Instance.myUserInfo.local_user_view.person.id);
return isAdmin || isMod;
}
import { Component, linkEvent } from "inferno";
import {
+ BlockPersonResponse,
CommentResponse,
CommentView,
GetPersonMentions,
setIsoData,
setupTippy,
toast,
+ updatePersonBlock,
wsClient,
wsJsonToRes,
wsSubscribe,
this.handleSortChange = this.handleSortChange.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
- if (!UserService.Instance.localUserView && isBrowser()) {
+ if (!UserService.Instance.myUserInfo && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
}
get documentTitle(): string {
- return `@${UserService.Instance.localUserView.person.name} ${i18n.t(
- "inbox"
- )} - ${this.state.site_view.site.name}`;
+ return `@${
+ UserService.Instance.myUserInfo.local_user_view.person.name
+ } ${i18n.t("inbox")} - ${this.state.site_view.site.name}`;
}
render() {
if (
data.recipient_ids.includes(
- UserService.Instance.localUserView.local_user.id
+ UserService.Instance.myUserInfo.local_user_view.local_user.id
)
) {
this.state.replies.unshift(data.comment_view);
this.setState(this.state);
} else if (
data.comment_view.creator.id ==
- UserService.Instance.localUserView.person.id
+ UserService.Instance.myUserInfo.local_user_view.person.id
) {
// TODO this seems wrong, you should be using form_id
toast(i18n.t("reply_sent"));
let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
if (
data.private_message_view.recipient.id ==
- UserService.Instance.localUserView.person.id
+ UserService.Instance.myUserInfo.local_user_view.person.id
) {
this.state.messages.unshift(data.private_message_view);
this.state.combined.unshift(
let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data.comment_view, this.state.replies);
this.setState(this.state);
+ } else if (op == UserOperation.BlockPerson) {
+ let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ updatePersonBlock(data);
}
}
this.state.mentions.filter(r => !r.person_mention.read).length +
this.state.messages.filter(
r =>
- UserService.Instance.localUserView &&
+ UserService.Instance.myUserInfo &&
!r.private_message.read &&
// TODO also seems very strange and wrong
- r.creator.id !== UserService.Instance.localUserView.person.id
+ r.creator.id !==
+ UserService.Instance.myUserInfo.local_user_view.person.id
).length
);
}
+++ /dev/null
-import { Component, linkEvent } from "inferno";
-import { Link } from "inferno-router";
-import ISO6391 from "iso-639-1";
-import {
- AddAdminResponse,
- BanPersonResponse,
- ChangePassword,
- CommentResponse,
- DeleteAccount,
- GetPersonDetails,
- GetPersonDetailsResponse,
- GetSiteResponse,
- ListingType,
- LoginResponse,
- PostResponse,
- SaveUserSettings,
- SortType,
- UserOperation,
-} from "lemmy-js-client";
-import moment from "moment";
-import { Subscription } from "rxjs";
-import { i18n } from "../../i18next";
-import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
-import { UserService, WebSocketService } from "../../services";
-import {
- authField,
- capitalizeFirstLetter,
- createCommentLikeRes,
- createPostLikeFindRes,
- editCommentRes,
- editPostFindRes,
- elementUrl,
- fetchLimit,
- getLanguage,
- getUsernameFromProps,
- languages,
- mdToHtml,
- previewLines,
- restoreScrollPosition,
- routeSortTypeToEnum,
- saveCommentRes,
- saveScrollPosition,
- setIsoData,
- setOptionalAuth,
- setTheme,
- setupTippy,
- showLocal,
- themes,
- toast,
- wsClient,
- wsJsonToRes,
- wsSubscribe,
- wsUserOp,
-} from "../../utils";
-import { BannerIconHeader } from "../common/banner-icon-header";
-import { HtmlTags } from "../common/html-tags";
-import { Icon, Spinner } from "../common/icon";
-import { ImageUploadForm } from "../common/image-upload-form";
-import { ListingTypeSelect } from "../common/listing-type-select";
-import { MarkdownTextArea } from "../common/markdown-textarea";
-import { MomentTime } from "../common/moment-time";
-import { SortSelect } from "../common/sort-select";
-import { CommunityLink } from "../community/community-link";
-import { PersonDetails } from "./person-details";
-import { PersonListing } from "./person-listing";
-
-interface PersonState {
- personRes: GetPersonDetailsResponse;
- userName: string;
- view: PersonDetailsView;
- sort: SortType;
- page: number;
- loading: boolean;
- saveUserSettingsForm: SaveUserSettings;
- changePasswordForm: ChangePassword;
- saveUserSettingsLoading: boolean;
- changePasswordLoading: boolean;
- deleteAccountLoading: boolean;
- deleteAccountShowConfirm: boolean;
- deleteAccountForm: DeleteAccount;
- siteRes: GetSiteResponse;
-}
-
-interface PersonProps {
- view: PersonDetailsView;
- sort: SortType;
- page: number;
- person_id: number | null;
- username: string;
-}
-
-interface UrlParams {
- view?: string;
- sort?: SortType;
- page?: number;
-}
-
-export class Person extends Component<any, PersonState> {
- private isoData = setIsoData(this.context);
- private subscription: Subscription;
- private emptyState: PersonState = {
- personRes: undefined,
- userName: getUsernameFromProps(this.props),
- loading: true,
- view: Person.getViewFromProps(this.props.match.view),
- sort: Person.getSortTypeFromProps(this.props.match.sort),
- page: Person.getPageFromProps(this.props.match.page),
- saveUserSettingsForm: {
- auth: authField(false),
- },
- changePasswordForm: {
- new_password: null,
- new_password_verify: null,
- old_password: null,
- auth: authField(false),
- },
- saveUserSettingsLoading: null,
- changePasswordLoading: false,
- deleteAccountLoading: null,
- deleteAccountShowConfirm: false,
- deleteAccountForm: {
- password: null,
- auth: authField(false),
- },
- siteRes: this.isoData.site_res,
- };
-
- constructor(props: any, context: any) {
- super(props, context);
-
- this.state = this.emptyState;
- this.handleSortChange = this.handleSortChange.bind(this);
- this.handleUserSettingsSortTypeChange =
- this.handleUserSettingsSortTypeChange.bind(this);
- this.handleUserSettingsListingTypeChange =
- this.handleUserSettingsListingTypeChange.bind(this);
- this.handlePageChange = this.handlePageChange.bind(this);
- this.handleUserSettingsBioChange =
- this.handleUserSettingsBioChange.bind(this);
-
- this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
- this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
-
- this.handleBannerUpload = this.handleBannerUpload.bind(this);
- this.handleBannerRemove = this.handleBannerRemove.bind(this);
-
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
-
- // Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
- this.state.personRes = this.isoData.routeData[0];
- this.setUserInfo();
- this.state.loading = false;
- } else {
- this.fetchUserData();
- }
-
- setupTippy();
- }
-
- 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),
- };
- WebSocketService.Instance.send(wsClient.getPersonDetails(form));
- }
-
- get isCurrentUser() {
- return (
- UserService.Instance.localUserView?.person.id ==
- this.state.personRes.person_view.person.id
- );
- }
-
- static getViewFromProps(view: string): PersonDetailsView {
- return view ? PersonDetailsView[view] : PersonDetailsView.Overview;
- }
-
- static getSortTypeFromProps(sort: string): SortType {
- return sort ? routeSortTypeToEnum(sort) : SortType.New;
- }
-
- static getPageFromProps(page: number): number {
- return page ? Number(page) : 1;
- }
-
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- let pathSplit = req.path.split("/");
- let promises: Promise<any>[] = [];
-
- // It can be /u/me, or /username/1
- let idOrName = pathSplit[2];
- let person_id: number;
- let username: string;
- if (isNaN(Number(idOrName))) {
- username = idOrName;
- } else {
- person_id = Number(idOrName);
- }
-
- let view = this.getViewFromProps(pathSplit[4]);
- let sort = this.getSortTypeFromProps(pathSplit[6]);
- let page = this.getPageFromProps(Number(pathSplit[8]));
-
- let form: GetPersonDetails = {
- sort,
- saved_only: view === PersonDetailsView.Saved,
- page,
- limit: fetchLimit,
- };
- setOptionalAuth(form, req.auth);
- this.setIdOrName(form, person_id, username);
- promises.push(req.client.getPersonDetails(form));
- return promises;
- }
-
- static setIdOrName(obj: any, id: number, name_: string) {
- if (id) {
- obj.person_id = id;
- } else {
- obj.username = name_;
- }
- }
-
- componentWillUnmount() {
- this.subscription.unsubscribe();
- saveScrollPosition(this.context);
- }
-
- static getDerivedStateFromProps(props: any): PersonProps {
- return {
- view: this.getViewFromProps(props.match.params.view),
- sort: this.getSortTypeFromProps(props.match.params.sort),
- page: this.getPageFromProps(props.match.params.page),
- person_id: Number(props.match.params.id) || null,
- username: props.match.params.username,
- };
- }
-
- componentDidUpdate(lastProps: any) {
- // Necessary if you are on a post and you click another post (same route)
- if (
- lastProps.location.pathname.split("/")[2] !==
- lastProps.history.location.pathname.split("/")[2]
- ) {
- // Couldnt get a refresh working. This does for now.
- location.reload();
- }
- }
-
- 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
- ? previewLines(this.state.personRes.person_view.person.bio)
- : undefined;
- }
-
- render() {
- return (
- <div class="container">
- {this.state.loading ? (
- <h5>
- <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.loading && (
- <div class="col-12 col-md-4">
- {this.isCurrentUser && this.userSettings()}
- {this.moderates()}
- {this.follows()}
- </div>
- )}
- </div>
- )}
- </div>
- );
- }
-
- viewRadios() {
- return (
- <div class="btn-group btn-group-toggle flex-wrap mb-2">
- <label
- className={`btn btn-outline-secondary pointer
- ${this.state.view == PersonDetailsView.Overview && "active"}
- `}
- >
- <input
- type="radio"
- value={PersonDetailsView.Overview}
- checked={this.state.view === PersonDetailsView.Overview}
- onChange={linkEvent(this, this.handleViewChange)}
- />
- {i18n.t("overview")}
- </label>
- <label
- className={`btn btn-outline-secondary pointer
- ${this.state.view == PersonDetailsView.Comments && "active"}
- `}
- >
- <input
- type="radio"
- value={PersonDetailsView.Comments}
- checked={this.state.view == PersonDetailsView.Comments}
- onChange={linkEvent(this, this.handleViewChange)}
- />
- {i18n.t("comments")}
- </label>
- <label
- className={`btn btn-outline-secondary pointer
- ${this.state.view == PersonDetailsView.Posts && "active"}
- `}
- >
- <input
- type="radio"
- value={PersonDetailsView.Posts}
- checked={this.state.view == PersonDetailsView.Posts}
- onChange={linkEvent(this, this.handleViewChange)}
- />
- {i18n.t("posts")}
- </label>
- <label
- className={`btn btn-outline-secondary pointer
- ${this.state.view == PersonDetailsView.Saved && "active"}
- `}
- >
- <input
- type="radio"
- value={PersonDetailsView.Saved}
- checked={this.state.view == PersonDetailsView.Saved}
- onChange={linkEvent(this, this.handleViewChange)}
- />
- {i18n.t("saved")}
- </label>
- </div>
- );
- }
-
- selects() {
- return (
- <div className="mb-2">
- <span class="mr-3">{this.viewRadios()}</span>
- <SortSelect
- sort={this.state.sort}
- onChange={this.handleSortChange}
- hideHot
- hideMostComments
- />
- <a
- href={`/feeds/u/${this.state.userName}.xml?sort=${this.state.sort}`}
- rel="noopener"
- title="RSS"
- >
- <Icon icon="rss" classes="text-muted small mx-2" />
- </a>
- </div>
- );
- }
-
- 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>
- {pv.person.banned && (
- <li className="list-inline-item badge badge-danger">
- {i18n.t("banned")}
- </li>
- )}
- </ul>
- </div>
- <div className="flex-grow-1 unselectable pointer mx-2"></div>
- {this.isCurrentUser ? (
- <button
- class="d-flex align-self-start btn btn-secondary mr-2"
- onClick={linkEvent(this, this.handleLogoutClick)}
- >
- {i18n.t("logout")}
- </button>
- ) : (
- <>
- <a
- className={`d-flex align-self-start btn btn-secondary mr-2 ${
- !pv.person.matrix_user_id && "invisible"
- }`}
- rel="noopener"
- 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"}
- to={`/create_private_message/recipient/${pv.person.id}`}
- >
- {i18n.t("send_message")}
- </Link>
- </>
- )}
- </div>
- {pv.person.bio && (
- <div className="d-flex align-items-center mb-2">
- <div
- className="md-div"
- dangerouslySetInnerHTML={mdToHtml(pv.person.bio)}
- />
- </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 })}
- </li>
- <li className="list-inline-item badge badge-light">
- {i18n.t("number_of_comments", {
- count: 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>
- );
- }
-
- userSettings() {
- return (
- <div>
- <div class="card border-secondary mb-3">
- <div class="card-body">
- {this.saveUserSettingsHtmlForm()}
- <br />
- {this.changePasswordHtmlForm()}
- </div>
- </div>
- </div>
- );
- }
-
- changePasswordHtmlForm() {
- return (
- <>
- <h5>{i18n.t("change_password")}</h5>
- <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
- <div class="form-group row">
- <label class="col-lg-5 col-form-label" htmlFor="user-password">
- {i18n.t("new_password")}
- </label>
- <div class="col-lg-7">
- <input
- type="password"
- id="user-password"
- class="form-control"
- value={this.state.changePasswordForm.new_password}
- autoComplete="new-password"
- maxLength={60}
- onInput={linkEvent(this, this.handleNewPasswordChange)}
- />
- </div>
- </div>
- <div class="form-group row">
- <label
- class="col-lg-5 col-form-label"
- htmlFor="user-verify-password"
- >
- {i18n.t("verify_password")}
- </label>
- <div class="col-lg-7">
- <input
- type="password"
- id="user-verify-password"
- class="form-control"
- value={this.state.changePasswordForm.new_password_verify}
- autoComplete="new-password"
- maxLength={60}
- onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
- />
- </div>
- </div>
- <div class="form-group row">
- <label class="col-lg-5 col-form-label" htmlFor="user-old-password">
- {i18n.t("old_password")}
- </label>
- <div class="col-lg-7">
- <input
- type="password"
- id="user-old-password"
- class="form-control"
- value={this.state.changePasswordForm.old_password}
- autoComplete="new-password"
- maxLength={60}
- onInput={linkEvent(this, this.handleOldPasswordChange)}
- />
- </div>
- </div>
- <div class="form-group">
- <button type="submit" class="btn btn-block btn-secondary mr-4">
- {this.state.changePasswordLoading ? (
- <Spinner />
- ) : (
- capitalizeFirstLetter(i18n.t("save"))
- )}
- </button>
- </div>
- </form>
- </>
- );
- }
-
- saveUserSettingsHtmlForm() {
- return (
- <>
- <h5>{i18n.t("settings")}</h5>
- <form onSubmit={linkEvent(this, this.handleSaveUserSettingsSubmit)}>
- <div class="form-group">
- <label>{i18n.t("avatar")}</label>
- <ImageUploadForm
- uploadTitle={i18n.t("upload_avatar")}
- imageSrc={this.state.saveUserSettingsForm.avatar}
- onUpload={this.handleAvatarUpload}
- onRemove={this.handleAvatarRemove}
- rounded
- />
- </div>
- <div class="form-group">
- <label>{i18n.t("banner")}</label>
- <ImageUploadForm
- uploadTitle={i18n.t("upload_banner")}
- imageSrc={this.state.saveUserSettingsForm.banner}
- onUpload={this.handleBannerUpload}
- onRemove={this.handleBannerRemove}
- />
- </div>
- <div class="form-group">
- <label htmlFor="user-language">{i18n.t("language")}</label>
- <select
- id="user-language"
- value={this.state.saveUserSettingsForm.lang}
- onChange={linkEvent(this, this.handleUserSettingsLangChange)}
- class="ml-2 custom-select w-auto"
- >
- <option disabled aria-hidden="true">
- {i18n.t("language")}
- </option>
- <option value="browser">{i18n.t("browser_default")}</option>
- <option disabled aria-hidden="true">
- ──
- </option>
- {languages.sort().map(lang => (
- <option value={lang.code}>
- {ISO6391.getNativeName(lang.code) || lang.code}
- </option>
- ))}
- </select>
- </div>
- <div class="form-group">
- <label htmlFor="user-theme">{i18n.t("theme")}</label>
- <select
- id="user-theme"
- value={this.state.saveUserSettingsForm.theme}
- onChange={linkEvent(this, this.handleUserSettingsThemeChange)}
- class="ml-2 custom-select w-auto"
- >
- <option disabled aria-hidden="true">
- {i18n.t("theme")}
- </option>
- <option value="browser">{i18n.t("browser_default")}</option>
- {themes.map(theme => (
- <option value={theme}>{theme}</option>
- ))}
- </select>
- </div>
- <form className="form-group">
- <label>
- <div class="mr-2">{i18n.t("type")}</div>
- </label>
- <ListingTypeSelect
- type_={
- Object.values(ListingType)[
- this.state.saveUserSettingsForm.default_listing_type
- ]
- }
- showLocal={showLocal(this.isoData)}
- onChange={this.handleUserSettingsListingTypeChange}
- />
- </form>
- <form className="form-group">
- <label>
- <div class="mr-2">{i18n.t("sort_type")}</div>
- </label>
- <SortSelect
- sort={
- Object.values(SortType)[
- this.state.saveUserSettingsForm.default_sort_type
- ]
- }
- onChange={this.handleUserSettingsSortTypeChange}
- />
- </form>
- <div class="form-group row">
- <label class="col-lg-5 col-form-label" htmlFor="display-name">
- {i18n.t("display_name")}
- </label>
- <div class="col-lg-7">
- <input
- id="display-name"
- type="text"
- class="form-control"
- placeholder={i18n.t("optional")}
- value={this.state.saveUserSettingsForm.display_name}
- onInput={linkEvent(
- this,
- this.handleUserSettingsPreferredUsernameChange
- )}
- pattern="^(?!@)(.+)$"
- minLength={3}
- />
- </div>
- </div>
- <div class="form-group row">
- <label class="col-lg-3 col-form-label" htmlFor="user-bio">
- {i18n.t("bio")}
- </label>
- <div class="col-lg-9">
- <MarkdownTextArea
- initialContent={this.state.saveUserSettingsForm.bio}
- onContentChange={this.handleUserSettingsBioChange}
- maxLength={300}
- hideNavigationWarnings
- />
- </div>
- </div>
- <div class="form-group row">
- <label class="col-lg-3 col-form-label" htmlFor="user-email">
- {i18n.t("email")}
- </label>
- <div class="col-lg-9">
- <input
- type="email"
- id="user-email"
- class="form-control"
- placeholder={i18n.t("optional")}
- value={this.state.saveUserSettingsForm.email}
- onInput={linkEvent(this, this.handleUserSettingsEmailChange)}
- minLength={3}
- />
- </div>
- </div>
- <div class="form-group row">
- <label class="col-lg-5 col-form-label" htmlFor="matrix-user-id">
- <a href={elementUrl} rel="noopener">
- {i18n.t("matrix_user_id")}
- </a>
- </label>
- <div class="col-lg-7">
- <input
- id="matrix-user-id"
- type="text"
- class="form-control"
- placeholder="@user:example.com"
- value={this.state.saveUserSettingsForm.matrix_user_id}
- onInput={linkEvent(
- this,
- this.handleUserSettingsMatrixUserIdChange
- )}
- pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
- />
- </div>
- </div>
- {this.state.siteRes.site_view.site.enable_nsfw && (
- <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}
- onChange={linkEvent(
- this,
- this.handleUserSettingsShowNsfwChange
- )}
- />
- <label class="form-check-label" htmlFor="user-show-nsfw">
- {i18n.t("show_nsfw")}
- </label>
- </div>
- </div>
- )}
- <div class="form-group">
- <div class="form-check">
- <input
- class="form-check-input"
- id="user-show-scores"
- type="checkbox"
- checked={this.state.saveUserSettingsForm.show_scores}
- onChange={linkEvent(
- this,
- this.handleUserSettingsShowScoresChange
- )}
- />
- <label class="form-check-label" htmlFor="user-show-scores">
- {i18n.t("show_scores")}
- </label>
- </div>
- </div>
- <div class="form-group">
- <div class="form-check">
- <input
- class="form-check-input"
- id="user-show-avatars"
- type="checkbox"
- checked={this.state.saveUserSettingsForm.show_avatars}
- onChange={linkEvent(
- this,
- this.handleUserSettingsShowAvatarsChange
- )}
- />
- <label class="form-check-label" htmlFor="user-show-avatars">
- {i18n.t("show_avatars")}
- </label>
- </div>
- </div>
- <div class="form-group">
- <div class="form-check">
- <input
- class="form-check-input"
- id="user-bot-account"
- type="checkbox"
- checked={this.state.saveUserSettingsForm.bot_account}
- onChange={linkEvent(this, this.handleUserSettingsBotAccount)}
- />
- <label class="form-check-label" htmlFor="user-bot-account">
- {i18n.t("bot_account")}
- </label>
- </div>
- </div>
- <div class="form-group">
- <div class="form-check">
- <input
- class="form-check-input"
- id="user-show-bot-accounts"
- type="checkbox"
- checked={this.state.saveUserSettingsForm.show_bot_accounts}
- onChange={linkEvent(
- this,
- this.handleUserSettingsShowBotAccounts
- )}
- />
- <label class="form-check-label" htmlFor="user-show-bot-accounts">
- {i18n.t("show_bot_accounts")}
- </label>
- </div>
- </div>
- <div class="form-group">
- <div class="form-check">
- <input
- class="form-check-input"
- id="user-show-read-posts"
- type="checkbox"
- checked={this.state.saveUserSettingsForm.show_read_posts}
- onChange={linkEvent(this, this.handleUserSettingsShowReadPosts)}
- />
- <label class="form-check-label" htmlFor="user-show-read-posts">
- {i18n.t("show_read_posts")}
- </label>
- </div>
- </div>
- <div class="form-group">
- <div class="form-check">
- <input
- class="form-check-input"
- id="user-show-new-post-notifs"
- type="checkbox"
- checked={this.state.saveUserSettingsForm.show_new_post_notifs}
- onChange={linkEvent(
- this,
- this.handleUserSettingsShowNewPostNotifs
- )}
- />
- <label
- class="form-check-label"
- htmlFor="user-show-new-post-notifs"
- >
- {i18n.t("show_new_post_notifs")}
- </label>
- </div>
- </div>
- <div class="form-group">
- <div class="form-check">
- <input
- class="form-check-input"
- id="user-send-notifications-to-email"
- type="checkbox"
- disabled={!this.state.saveUserSettingsForm.email}
- checked={
- this.state.saveUserSettingsForm.send_notifications_to_email
- }
- onChange={linkEvent(
- this,
- this.handleUserSettingsSendNotificationsToEmailChange
- )}
- />
- <label
- class="form-check-label"
- htmlFor="user-send-notifications-to-email"
- >
- {i18n.t("send_notifications_to_email")}
- </label>
- </div>
- </div>
- <div class="form-group">
- <button type="submit" class="btn btn-block btn-secondary mr-4">
- {this.state.saveUserSettingsLoading ? (
- <Spinner />
- ) : (
- capitalizeFirstLetter(i18n.t("save"))
- )}
- </button>
- </div>
- <hr />
- <div class="form-group">
- <button
- class="btn btn-block btn-danger"
- onClick={linkEvent(
- this,
- this.handleDeleteAccountShowConfirmToggle
- )}
- >
- {i18n.t("delete_account")}
- </button>
- {this.state.deleteAccountShowConfirm && (
- <>
- <div class="my-2 alert alert-danger" role="alert">
- {i18n.t("delete_account_confirm")}
- </div>
- <input
- type="password"
- value={this.state.deleteAccountForm.password}
- autoComplete="new-password"
- maxLength={60}
- onInput={linkEvent(
- this,
- this.handleDeleteAccountPasswordChange
- )}
- class="form-control my-2"
- />
- <button
- class="btn btn-danger mr-4"
- disabled={!this.state.deleteAccountForm.password}
- onClick={linkEvent(this, this.handleDeleteAccount)}
- >
- {this.state.deleteAccountLoading ? (
- <Spinner />
- ) : (
- capitalizeFirstLetter(i18n.t("delete"))
- )}
- </button>
- <button
- class="btn btn-secondary"
- onClick={linkEvent(
- this,
- this.handleDeleteAccountShowConfirmToggle
- )}
- >
- {i18n.t("cancel")}
- </button>
- </>
- )}
- </div>
- </form>
- </>
- );
- }
-
- 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>
- );
- }
-
- follows() {
- return (
- <div>
- {this.state.personRes.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">
- {this.state.personRes.follows.map(cfv => (
- <li>
- <CommunityLink community={cfv.community} />
- </li>
- ))}
- </ul>
- </div>
- </div>
- )}
- </div>
- );
- }
-
- updateUrl(paramUpdates: UrlParams) {
- const page = paramUpdates.page || this.state.page;
- const viewStr = paramUpdates.view || PersonDetailsView[this.state.view];
- const sortStr = paramUpdates.sort || this.state.sort;
-
- let typeView = `/u/${this.state.userName}`;
-
- this.props.history.push(
- `${typeView}/view/${viewStr}/sort/${sortStr}/page/${page}`
- );
- this.state.loading = true;
- this.setState(this.state);
- this.fetchUserData();
- }
-
- handlePageChange(page: number) {
- this.updateUrl({ page });
- }
-
- handleSortChange(val: SortType) {
- this.updateUrl({ sort: val, page: 1 });
- }
-
- handleViewChange(i: Person, event: any) {
- i.updateUrl({
- view: PersonDetailsView[Number(event.target.value)],
- page: 1,
- });
- }
-
- handleUserSettingsShowNsfwChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.show_nsfw = event.target.checked;
- i.setState(i.state);
- }
-
- handleUserSettingsShowAvatarsChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.show_avatars = event.target.checked;
- UserService.Instance.localUserView.local_user.show_avatars =
- event.target.checked; // Just for instant updates
- i.setState(i.state);
- }
-
- handleUserSettingsBotAccount(i: Person, event: any) {
- i.state.saveUserSettingsForm.bot_account = event.target.checked;
- i.setState(i.state);
- }
-
- handleUserSettingsShowBotAccounts(i: Person, event: any) {
- i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
- i.setState(i.state);
- }
-
- handleUserSettingsShowReadPosts(i: Person, event: any) {
- i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
- i.setState(i.state);
- }
-
- handleUserSettingsShowNewPostNotifs(i: Person, event: any) {
- i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked;
- i.setState(i.state);
- }
-
- handleUserSettingsShowScoresChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.show_scores = event.target.checked;
- UserService.Instance.localUserView.local_user.show_scores =
- event.target.checked; // Just for instant updates
- i.setState(i.state);
- }
-
- handleUserSettingsSendNotificationsToEmailChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.send_notifications_to_email =
- event.target.checked;
- i.setState(i.state);
- }
-
- handleUserSettingsThemeChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.theme = event.target.value;
- setTheme(event.target.value, true);
- i.setState(i.state);
- }
-
- handleUserSettingsLangChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.lang = event.target.value;
- i18n.changeLanguage(getLanguage(i.state.saveUserSettingsForm.lang));
- i.setState(i.state);
- }
-
- handleUserSettingsSortTypeChange(val: SortType) {
- this.state.saveUserSettingsForm.default_sort_type =
- Object.keys(SortType).indexOf(val);
- this.setState(this.state);
- }
-
- handleUserSettingsListingTypeChange(val: ListingType) {
- this.state.saveUserSettingsForm.default_listing_type =
- Object.keys(ListingType).indexOf(val);
- this.setState(this.state);
- }
-
- handleUserSettingsEmailChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.email = event.target.value;
- i.setState(i.state);
- }
-
- handleUserSettingsBioChange(val: string) {
- this.state.saveUserSettingsForm.bio = val;
- this.setState(this.state);
- }
-
- handleAvatarUpload(url: string) {
- this.state.saveUserSettingsForm.avatar = url;
- this.setState(this.state);
- }
-
- handleAvatarRemove() {
- this.state.saveUserSettingsForm.avatar = "";
- this.setState(this.state);
- }
-
- handleBannerUpload(url: string) {
- this.state.saveUserSettingsForm.banner = url;
- this.setState(this.state);
- }
-
- handleBannerRemove() {
- this.state.saveUserSettingsForm.banner = "";
- this.setState(this.state);
- }
-
- handleUserSettingsPreferredUsernameChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.display_name = event.target.value;
- i.setState(i.state);
- }
-
- handleUserSettingsMatrixUserIdChange(i: Person, event: any) {
- i.state.saveUserSettingsForm.matrix_user_id = event.target.value;
- if (
- i.state.saveUserSettingsForm.matrix_user_id == "" &&
- !UserService.Instance.localUserView.person.matrix_user_id
- ) {
- i.state.saveUserSettingsForm.matrix_user_id = undefined;
- }
- i.setState(i.state);
- }
-
- handleNewPasswordChange(i: Person, event: any) {
- i.state.changePasswordForm.new_password = event.target.value;
- if (i.state.changePasswordForm.new_password == "") {
- i.state.changePasswordForm.new_password = undefined;
- }
- i.setState(i.state);
- }
-
- handleNewPasswordVerifyChange(i: Person, event: any) {
- i.state.changePasswordForm.new_password_verify = event.target.value;
- if (i.state.changePasswordForm.new_password_verify == "") {
- i.state.changePasswordForm.new_password_verify = undefined;
- }
- i.setState(i.state);
- }
-
- handleOldPasswordChange(i: Person, event: any) {
- i.state.changePasswordForm.old_password = event.target.value;
- if (i.state.changePasswordForm.old_password == "") {
- i.state.changePasswordForm.old_password = undefined;
- }
- i.setState(i.state);
- }
-
- handleSaveUserSettingsSubmit(i: Person, event: any) {
- event.preventDefault();
- i.state.saveUserSettingsLoading = true;
- i.setState(i.state);
-
- WebSocketService.Instance.send(
- wsClient.saveUserSettings(i.state.saveUserSettingsForm)
- );
- }
-
- handleChangePasswordSubmit(i: Person, event: any) {
- event.preventDefault();
- i.state.changePasswordLoading = true;
- i.setState(i.state);
-
- WebSocketService.Instance.send(
- wsClient.changePassword(i.state.changePasswordForm)
- );
- }
-
- handleDeleteAccountShowConfirmToggle(i: Person, event: any) {
- event.preventDefault();
- i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm;
- i.setState(i.state);
- }
-
- handleDeleteAccountPasswordChange(i: Person, event: any) {
- i.state.deleteAccountForm.password = event.target.value;
- i.setState(i.state);
- }
-
- handleLogoutClick(i: Person) {
- UserService.Instance.logout();
- i.context.router.history.push("/");
- }
-
- handleDeleteAccount(i: Person, event: any) {
- event.preventDefault();
- i.state.deleteAccountLoading = true;
- i.setState(i.state);
-
- WebSocketService.Instance.send(
- wsClient.deleteAccount(i.state.deleteAccountForm)
- );
- }
-
- setUserInfo() {
- if (this.isCurrentUser) {
- this.state.saveUserSettingsForm.show_nsfw =
- UserService.Instance.localUserView.local_user.show_nsfw;
- this.state.saveUserSettingsForm.theme = UserService.Instance.localUserView
- .local_user.theme
- ? UserService.Instance.localUserView.local_user.theme
- : "browser";
- this.state.saveUserSettingsForm.default_sort_type =
- UserService.Instance.localUserView.local_user.default_sort_type;
- this.state.saveUserSettingsForm.default_listing_type =
- UserService.Instance.localUserView.local_user.default_listing_type;
- this.state.saveUserSettingsForm.lang =
- UserService.Instance.localUserView.local_user.lang;
- this.state.saveUserSettingsForm.avatar =
- UserService.Instance.localUserView.person.avatar;
- this.state.saveUserSettingsForm.banner =
- UserService.Instance.localUserView.person.banner;
- this.state.saveUserSettingsForm.display_name =
- UserService.Instance.localUserView.person.display_name;
- this.state.saveUserSettingsForm.show_avatars =
- UserService.Instance.localUserView.local_user.show_avatars;
- this.state.saveUserSettingsForm.bot_account =
- UserService.Instance.localUserView.person.bot_account;
- this.state.saveUserSettingsForm.show_bot_accounts =
- UserService.Instance.localUserView.local_user.show_bot_accounts;
- this.state.saveUserSettingsForm.show_scores =
- UserService.Instance.localUserView.local_user.show_scores;
- this.state.saveUserSettingsForm.show_read_posts =
- UserService.Instance.localUserView.local_user.show_read_posts;
- this.state.saveUserSettingsForm.show_new_post_notifs =
- UserService.Instance.localUserView.local_user.show_new_post_notifs;
- this.state.saveUserSettingsForm.email =
- UserService.Instance.localUserView.local_user.email;
- this.state.saveUserSettingsForm.bio =
- UserService.Instance.localUserView.person.bio;
- this.state.saveUserSettingsForm.send_notifications_to_email =
- UserService.Instance.localUserView.local_user.send_notifications_to_email;
- this.state.saveUserSettingsForm.matrix_user_id =
- UserService.Instance.localUserView.person.matrix_user_id;
- }
- }
-
- parseMessage(msg: any) {
- let op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- if (msg.error == "couldnt_find_that_username_or_email") {
- this.context.router.history.push("/");
- }
- this.setState({
- deleteAccountLoading: false,
- saveUserSettingsLoading: false,
- changePasswordLoading: false,
- });
- return;
- } else if (msg.reconnect) {
- this.fetchUserData();
- } else if (op == UserOperation.GetPersonDetails) {
- // Since the PersonDetails contains posts/comments as well as some general user info we listen here as well
- // and set the parent state if it is not set or differs
- // TODO this might need to get abstracted
- let data = wsJsonToRes<GetPersonDetailsResponse>(msg).data;
- this.state.personRes = data;
- console.log(data);
- this.setUserInfo();
- this.state.loading = false;
- this.setState(this.state);
- restoreScrollPosition(this.context);
- } else if (op == UserOperation.SaveUserSettings) {
- let data = wsJsonToRes<LoginResponse>(msg).data;
- UserService.Instance.login(data);
- this.state.personRes.person_view.person.bio =
- this.state.saveUserSettingsForm.bio;
- this.state.personRes.person_view.person.display_name =
- this.state.saveUserSettingsForm.display_name;
- this.state.personRes.person_view.person.banner =
- this.state.saveUserSettingsForm.banner;
- this.state.personRes.person_view.person.avatar =
- this.state.saveUserSettingsForm.avatar;
- this.state.saveUserSettingsLoading = false;
- this.setState(this.state);
-
- window.scrollTo(0, 0);
- } else if (op == UserOperation.ChangePassword) {
- let data = wsJsonToRes<LoginResponse>(msg).data;
- UserService.Instance.login(data);
- this.state.changePasswordLoading = false;
- this.setState(this.state);
- window.scrollTo(0, 0);
- toast(i18n.t("password_changed"));
- } else if (op == UserOperation.DeleteAccount) {
- this.setState({
- deleteAccountLoading: false,
- deleteAccountShowConfirm: false,
- });
- UserService.Instance.logout();
- window.location.href = "/";
- } else if (op == UserOperation.AddAdmin) {
- let data = wsJsonToRes<AddAdminResponse>(msg).data;
- 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);
- 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);
- this.setState(this.state);
- } else if (op == UserOperation.CreateComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- if (
- UserService.Instance.localUserView &&
- data.comment_view.creator.id ==
- UserService.Instance.localUserView.person.id
- ) {
- toast(i18n.t("reply_sent"));
- }
- } else if (op == UserOperation.SaveComment) {
- let data = wsJsonToRes<CommentResponse>(msg).data;
- saveCommentRes(data.comment_view, this.state.personRes.comments);
- this.setState(this.state);
- } else if (
- op == UserOperation.EditPost ||
- op == UserOperation.DeletePost ||
- op == UserOperation.RemovePost ||
- op == UserOperation.LockPost ||
- op == UserOperation.StickyPost ||
- op == UserOperation.SavePost
- ) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- editPostFindRes(data.post_view, this.state.personRes.posts);
- this.setState(this.state);
- } else if (op == UserOperation.CreatePostLike) {
- let data = wsJsonToRes<PostResponse>(msg).data;
- createPostLikeFindRes(data.post_view, this.state.personRes.posts);
- 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));
- this.setState(this.state);
- }
- }
-}
--- /dev/null
+import { Component, linkEvent } from "inferno";
+import { Link } from "inferno-router";
+import {
+ AddAdminResponse,
+ BanPersonResponse,
+ BlockPersonResponse,
+ CommentResponse,
+ GetPersonDetails,
+ GetPersonDetailsResponse,
+ GetSiteResponse,
+ PostResponse,
+ SortType,
+ UserOperation,
+} from "lemmy-js-client";
+import moment from "moment";
+import { Subscription } from "rxjs";
+import { i18n } from "../../i18next";
+import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
+import { UserService, WebSocketService } from "../../services";
+import {
+ authField,
+ createCommentLikeRes,
+ createPostLikeFindRes,
+ editCommentRes,
+ editPostFindRes,
+ fetchLimit,
+ getUsernameFromProps,
+ mdToHtml,
+ previewLines,
+ restoreScrollPosition,
+ routeSortTypeToEnum,
+ 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 { Icon, Spinner } from "../common/icon";
+import { MomentTime } from "../common/moment-time";
+import { SortSelect } from "../common/sort-select";
+import { CommunityLink } from "../community/community-link";
+import { PersonDetails } from "./person-details";
+import { PersonListing } from "./person-listing";
+
+interface ProfileState {
+ personRes: GetPersonDetailsResponse;
+ userName: string;
+ view: PersonDetailsView;
+ sort: SortType;
+ page: number;
+ loading: boolean;
+ siteRes: GetSiteResponse;
+}
+
+interface ProfileProps {
+ view: PersonDetailsView;
+ sort: SortType;
+ page: number;
+ person_id: number | null;
+ username: string;
+}
+
+interface UrlParams {
+ view?: string;
+ sort?: SortType;
+ page?: number;
+}
+
+export class Profile extends Component<any, ProfileState> {
+ private isoData = setIsoData(this.context);
+ private subscription: Subscription;
+ private emptyState: ProfileState = {
+ personRes: undefined,
+ userName: getUsernameFromProps(this.props),
+ loading: true,
+ view: Profile.getViewFromProps(this.props.match.view),
+ sort: Profile.getSortTypeFromProps(this.props.match.sort),
+ page: Profile.getPageFromProps(this.props.match.page),
+ siteRes: this.isoData.site_res,
+ };
+
+ constructor(props: any, context: any) {
+ super(props, context);
+
+ this.state = this.emptyState;
+ this.handleSortChange = this.handleSortChange.bind(this);
+
+ this.parseMessage = this.parseMessage.bind(this);
+ this.subscription = wsSubscribe(this.parseMessage);
+
+ // Only fetch the data if coming from another route
+ if (this.isoData.path == this.context.router.route.match.url) {
+ this.state.personRes = this.isoData.routeData[0];
+ this.state.loading = false;
+ } else {
+ this.fetchUserData();
+ }
+
+ setupTippy();
+ }
+
+ 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),
+ };
+ WebSocketService.Instance.send(wsClient.getPersonDetails(form));
+ }
+
+ get isCurrentUser() {
+ return (
+ UserService.Instance.myUserInfo?.local_user_view.person.id ==
+ this.state.personRes.person_view.person.id
+ );
+ }
+
+ static getViewFromProps(view: string): PersonDetailsView {
+ return view ? PersonDetailsView[view] : PersonDetailsView.Overview;
+ }
+
+ static getSortTypeFromProps(sort: string): SortType {
+ return sort ? routeSortTypeToEnum(sort) : SortType.New;
+ }
+
+ static getPageFromProps(page: number): number {
+ return page ? Number(page) : 1;
+ }
+
+ static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
+ let pathSplit = req.path.split("/");
+ let promises: Promise<any>[] = [];
+
+ // It can be /u/me, or /username/1
+ let idOrName = pathSplit[2];
+ let person_id: number;
+ let username: string;
+ if (isNaN(Number(idOrName))) {
+ username = idOrName;
+ } else {
+ person_id = Number(idOrName);
+ }
+
+ let view = this.getViewFromProps(pathSplit[4]);
+ let sort = this.getSortTypeFromProps(pathSplit[6]);
+ let page = this.getPageFromProps(Number(pathSplit[8]));
+
+ let form: GetPersonDetails = {
+ sort,
+ saved_only: view === PersonDetailsView.Saved,
+ page,
+ limit: fetchLimit,
+ };
+ setOptionalAuth(form, req.auth);
+ this.setIdOrName(form, person_id, username);
+ promises.push(req.client.getPersonDetails(form));
+ return promises;
+ }
+
+ static setIdOrName(obj: any, id: number, name_: string) {
+ if (id) {
+ obj.person_id = id;
+ } else {
+ obj.username = name_;
+ }
+ }
+
+ componentWillUnmount() {
+ this.subscription.unsubscribe();
+ saveScrollPosition(this.context);
+ }
+
+ static getDerivedStateFromProps(props: any): ProfileProps {
+ return {
+ view: this.getViewFromProps(props.match.params.view),
+ sort: this.getSortTypeFromProps(props.match.params.sort),
+ page: this.getPageFromProps(props.match.params.page),
+ person_id: Number(props.match.params.id) || null,
+ username: props.match.params.username,
+ };
+ }
+
+ componentDidUpdate(lastProps: any) {
+ // Necessary if you are on a post and you click another post (same route)
+ if (
+ lastProps.location.pathname.split("/")[2] !==
+ lastProps.history.location.pathname.split("/")[2]
+ ) {
+ // Couldnt get a refresh working. This does for now.
+ location.reload();
+ }
+ }
+
+ 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
+ ? previewLines(this.state.personRes.person_view.person.bio)
+ : undefined;
+ }
+
+ render() {
+ return (
+ <div class="container">
+ {this.state.loading ? (
+ <h5>
+ <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.loading && (
+ <div class="col-12 col-md-4">
+ {this.moderates()}
+ {UserService.Instance.myUserInfo && this.follows()}
+ </div>
+ )}
+ </div>
+ )}
+ </div>
+ );
+ }
+
+ viewRadios() {
+ return (
+ <div class="btn-group btn-group-toggle flex-wrap mb-2">
+ <label
+ className={`btn btn-outline-secondary pointer
+ ${this.state.view == PersonDetailsView.Overview && "active"}
+ `}
+ >
+ <input
+ type="radio"
+ value={PersonDetailsView.Overview}
+ checked={this.state.view === PersonDetailsView.Overview}
+ onChange={linkEvent(this, this.handleViewChange)}
+ />
+ {i18n.t("overview")}
+ </label>
+ <label
+ className={`btn btn-outline-secondary pointer
+ ${this.state.view == PersonDetailsView.Comments && "active"}
+ `}
+ >
+ <input
+ type="radio"
+ value={PersonDetailsView.Comments}
+ checked={this.state.view == PersonDetailsView.Comments}
+ onChange={linkEvent(this, this.handleViewChange)}
+ />
+ {i18n.t("comments")}
+ </label>
+ <label
+ className={`btn btn-outline-secondary pointer
+ ${this.state.view == PersonDetailsView.Posts && "active"}
+ `}
+ >
+ <input
+ type="radio"
+ value={PersonDetailsView.Posts}
+ checked={this.state.view == PersonDetailsView.Posts}
+ onChange={linkEvent(this, this.handleViewChange)}
+ />
+ {i18n.t("posts")}
+ </label>
+ <label
+ className={`btn btn-outline-secondary pointer
+ ${this.state.view == PersonDetailsView.Saved && "active"}
+ `}
+ >
+ <input
+ type="radio"
+ value={PersonDetailsView.Saved}
+ checked={this.state.view == PersonDetailsView.Saved}
+ onChange={linkEvent(this, this.handleViewChange)}
+ />
+ {i18n.t("saved")}
+ </label>
+ </div>
+ );
+ }
+
+ selects() {
+ return (
+ <div className="mb-2">
+ <span class="mr-3">{this.viewRadios()}</span>
+ <SortSelect
+ sort={this.state.sort}
+ onChange={this.handleSortChange}
+ hideHot
+ hideMostComments
+ />
+ <a
+ href={`/feeds/u/${this.state.userName}.xml?sort=${this.state.sort}`}
+ rel="noopener"
+ title="RSS"
+ >
+ <Icon icon="rss" classes="text-muted small mx-2" />
+ </a>
+ </div>
+ );
+ }
+
+ 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>
+ {pv.person.banned && (
+ <li className="list-inline-item badge badge-danger">
+ {i18n.t("banned")}
+ </li>
+ )}
+ </ul>
+ </div>
+ <div className="flex-grow-1 unselectable pointer mx-2"></div>
+ {!this.isCurrentUser && (
+ <>
+ <a
+ className={`d-flex align-self-start btn btn-secondary mr-2 ${
+ !pv.person.matrix_user_id && "invisible"
+ }`}
+ rel="noopener"
+ 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"}
+ to={`/create_private_message/recipient/${pv.person.id}`}
+ >
+ {i18n.t("send_message")}
+ </Link>
+ </>
+ )}
+ </div>
+ {pv.person.bio && (
+ <div className="d-flex align-items-center mb-2">
+ <div
+ className="md-div"
+ dangerouslySetInnerHTML={mdToHtml(pv.person.bio)}
+ />
+ </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 })}
+ </li>
+ <li className="list-inline-item badge badge-light">
+ {i18n.t("number_of_comments", {
+ count: 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>
+ );
+ }
+
+ 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>
+ );
+ }
+
+ 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>
+ );
+ }
+
+ updateUrl(paramUpdates: UrlParams) {
+ const page = paramUpdates.page || this.state.page;
+ const viewStr = paramUpdates.view || PersonDetailsView[this.state.view];
+ const sortStr = paramUpdates.sort || this.state.sort;
+
+ let typeView = `/u/${this.state.userName}`;
+
+ this.props.history.push(
+ `${typeView}/view/${viewStr}/sort/${sortStr}/page/${page}`
+ );
+ this.state.loading = true;
+ this.setState(this.state);
+ this.fetchUserData();
+ }
+
+ handlePageChange(page: number) {
+ this.updateUrl({ page });
+ }
+
+ handleSortChange(val: SortType) {
+ this.updateUrl({ sort: val, page: 1 });
+ }
+
+ handleViewChange(i: Profile, event: any) {
+ i.updateUrl({
+ view: PersonDetailsView[Number(event.target.value)],
+ page: 1,
+ });
+ }
+
+ parseMessage(msg: any) {
+ let op = wsUserOp(msg);
+ console.log(msg);
+ if (msg.error) {
+ toast(i18n.t(msg.error), "danger");
+ if (msg.error == "couldnt_find_that_username_or_email") {
+ this.context.router.history.push("/");
+ }
+ return;
+ } else if (msg.reconnect) {
+ this.fetchUserData();
+ } else if (op == UserOperation.GetPersonDetails) {
+ // Since the PersonDetails contains posts/comments as well as some general user info we listen here as well
+ // and set the parent state if it is not set or differs
+ // TODO this might need to get abstracted
+ let data = wsJsonToRes<GetPersonDetailsResponse>(msg).data;
+ this.state.personRes = data;
+ console.log(data);
+ this.state.loading = false;
+ this.setState(this.state);
+ restoreScrollPosition(this.context);
+ } else if (op == UserOperation.AddAdmin) {
+ let data = wsJsonToRes<AddAdminResponse>(msg).data;
+ 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);
+ 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);
+ 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"));
+ }
+ } else if (op == UserOperation.SaveComment) {
+ let data = wsJsonToRes<CommentResponse>(msg).data;
+ saveCommentRes(data.comment_view, this.state.personRes.comments);
+ this.setState(this.state);
+ } else if (
+ op == UserOperation.EditPost ||
+ op == UserOperation.DeletePost ||
+ op == UserOperation.RemovePost ||
+ op == UserOperation.LockPost ||
+ op == UserOperation.StickyPost ||
+ op == UserOperation.SavePost
+ ) {
+ let data = wsJsonToRes<PostResponse>(msg).data;
+ editPostFindRes(data.post_view, this.state.personRes.posts);
+ this.setState(this.state);
+ } else if (op == UserOperation.CreatePostLike) {
+ let data = wsJsonToRes<PostResponse>(msg).data;
+ createPostLikeFindRes(data.post_view, this.state.personRes.posts);
+ 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));
+ this.setState(this.state);
+ } else if (op == UserOperation.BlockPerson) {
+ let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ updatePersonBlock(data);
+ }
+ }
+}
--- /dev/null
+import { Component, linkEvent } from "inferno";
+import ISO6391 from "iso-639-1";
+import {
+ BlockCommunity,
+ BlockCommunityResponse,
+ BlockPerson,
+ BlockPersonResponse,
+ ChangePassword,
+ CommunityBlockView,
+ CommunityView,
+ DeleteAccount,
+ GetSiteResponse,
+ ListingType,
+ LoginResponse,
+ PersonBlockView,
+ PersonViewSafe,
+ SaveUserSettings,
+ SortType,
+ UserOperation,
+} from "lemmy-js-client";
+import { Subscription } from "rxjs";
+import { i18n } from "../../i18next";
+import { UserService, WebSocketService } from "../../services";
+import {
+ authField,
+ capitalizeFirstLetter,
+ choicesConfig,
+ communitySelectName,
+ communityToChoice,
+ debounce,
+ elementUrl,
+ fetchCommunities,
+ fetchUsers,
+ getLanguage,
+ isBrowser,
+ languages,
+ personSelectName,
+ personToChoice,
+ setIsoData,
+ setTheme,
+ setupTippy,
+ showLocal,
+ themes,
+ toast,
+ updateCommunityBlock,
+ updatePersonBlock,
+ wsClient,
+ wsJsonToRes,
+ wsSubscribe,
+ wsUserOp,
+} from "../../utils";
+import { HtmlTags } from "../common/html-tags";
+import { Icon, Spinner } from "../common/icon";
+import { ImageUploadForm } from "../common/image-upload-form";
+import { ListingTypeSelect } from "../common/listing-type-select";
+import { MarkdownTextArea } from "../common/markdown-textarea";
+import { SortSelect } from "../common/sort-select";
+import { CommunityLink } from "../community/community-link";
+import { PersonListing } from "./person-listing";
+
+var Choices: any;
+if (isBrowser()) {
+ Choices = require("choices.js");
+}
+
+interface SettingsState {
+ saveUserSettingsForm: SaveUserSettings;
+ changePasswordForm: ChangePassword;
+ saveUserSettingsLoading: boolean;
+ changePasswordLoading: boolean;
+ deleteAccountLoading: boolean;
+ deleteAccountShowConfirm: boolean;
+ deleteAccountForm: DeleteAccount;
+ personBlocks: PersonBlockView[];
+ blockPersonId: number;
+ blockPerson?: PersonViewSafe;
+ communityBlocks: CommunityBlockView[];
+ blockCommunityId: number;
+ blockCommunity?: CommunityView;
+ currentTab: string;
+ siteRes: GetSiteResponse;
+}
+
+export class Settings extends Component<any, SettingsState> {
+ private isoData = setIsoData(this.context);
+ private blockPersonChoices: any;
+ private blockCommunityChoices: any;
+ private subscription: Subscription;
+ private emptyState: SettingsState = {
+ saveUserSettingsForm: {
+ auth: authField(false),
+ },
+ changePasswordForm: {
+ new_password: null,
+ new_password_verify: null,
+ old_password: null,
+ auth: authField(false),
+ },
+ saveUserSettingsLoading: null,
+ changePasswordLoading: false,
+ deleteAccountLoading: null,
+ deleteAccountShowConfirm: false,
+ deleteAccountForm: {
+ password: null,
+ auth: authField(false),
+ },
+ personBlocks: [],
+ blockPersonId: 0,
+ communityBlocks: [],
+ blockCommunityId: 0,
+ currentTab: "settings",
+ siteRes: this.isoData.site_res,
+ };
+
+ constructor(props: any, context: any) {
+ super(props, context);
+
+ this.state = this.emptyState;
+ this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
+ this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
+ this.handleBioChange = this.handleBioChange.bind(this);
+
+ this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
+ this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
+
+ this.handleBannerUpload = this.handleBannerUpload.bind(this);
+ this.handleBannerRemove = this.handleBannerRemove.bind(this);
+
+ this.parseMessage = this.parseMessage.bind(this);
+ this.subscription = wsSubscribe(this.parseMessage);
+
+ this.setUserInfo();
+
+ setupTippy();
+ }
+
+ componentWillUnmount() {
+ this.subscription.unsubscribe();
+ }
+
+ get documentTitle(): string {
+ return i18n.t("settings");
+ }
+
+ render() {
+ return (
+ <div class="container">
+ <>
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ description={this.documentTitle}
+ image={this.state.saveUserSettingsForm.avatar}
+ />
+ <ul class="nav nav-tabs mb-2">
+ <li class="nav-item">
+ <button
+ class={`nav-link btn ${
+ this.state.currentTab == "settings" && "active"
+ }`}
+ onClick={linkEvent(
+ { ctx: this, tab: "settings" },
+ this.handleSwitchTab
+ )}
+ >
+ {i18n.t("settings")}
+ </button>
+ </li>
+ <li class="nav-item">
+ <button
+ class={`nav-link btn ${
+ this.state.currentTab == "blocks" && "active"
+ }`}
+ onClick={linkEvent(
+ { ctx: this, tab: "blocks" },
+ this.handleSwitchTab
+ )}
+ >
+ {i18n.t("blocks")}
+ </button>
+ </li>
+ </ul>
+ {this.state.currentTab == "settings" && this.userSettings()}
+ {this.state.currentTab == "blocks" && this.blockCards()}
+ </>
+ </div>
+ );
+ }
+
+ userSettings() {
+ return (
+ <div class="row">
+ <div class="col-12 col-md-6">
+ <div class="card border-secondary mb-3">
+ <div class="card-body">{this.saveUserSettingsHtmlForm()}</div>
+ </div>
+ </div>
+ <div class="col-12 col-md-6">
+ <div class="card border-secondary mb-3">
+ <div class="card-body">{this.changePasswordHtmlForm()}</div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ blockCards() {
+ return (
+ <div class="row">
+ <div class="col-12 col-md-6">
+ <div class="card border-secondary mb-3">
+ <div class="card-body">{this.blockUserCard()}</div>
+ </div>
+ </div>
+ <div class="col-12 col-md-6">
+ <div class="card border-secondary mb-3">
+ <div class="card-body">{this.blockCommunityCard()}</div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ changePasswordHtmlForm() {
+ return (
+ <>
+ <h5>{i18n.t("change_password")}</h5>
+ <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
+ <div class="form-group row">
+ <label class="col-lg-5 col-form-label" htmlFor="user-password">
+ {i18n.t("new_password")}
+ </label>
+ <div class="col-lg-7">
+ <input
+ type="password"
+ id="user-password"
+ class="form-control"
+ value={this.state.changePasswordForm.new_password}
+ autoComplete="new-password"
+ maxLength={60}
+ onInput={linkEvent(this, this.handleNewPasswordChange)}
+ />
+ </div>
+ </div>
+ <div class="form-group row">
+ <label
+ class="col-lg-5 col-form-label"
+ htmlFor="user-verify-password"
+ >
+ {i18n.t("verify_password")}
+ </label>
+ <div class="col-lg-7">
+ <input
+ type="password"
+ id="user-verify-password"
+ class="form-control"
+ value={this.state.changePasswordForm.new_password_verify}
+ autoComplete="new-password"
+ maxLength={60}
+ onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
+ />
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="col-lg-5 col-form-label" htmlFor="user-old-password">
+ {i18n.t("old_password")}
+ </label>
+ <div class="col-lg-7">
+ <input
+ type="password"
+ id="user-old-password"
+ class="form-control"
+ value={this.state.changePasswordForm.old_password}
+ autoComplete="new-password"
+ maxLength={60}
+ onInput={linkEvent(this, this.handleOldPasswordChange)}
+ />
+ </div>
+ </div>
+ <div class="form-group">
+ <button type="submit" class="btn btn-block btn-secondary mr-4">
+ {this.state.changePasswordLoading ? (
+ <Spinner />
+ ) : (
+ capitalizeFirstLetter(i18n.t("save"))
+ )}
+ </button>
+ </div>
+ </form>
+ </>
+ );
+ }
+
+ blockUserCard() {
+ return (
+ <div>
+ {this.blockUserForm()}
+ {this.blockedUsersList()}
+ </div>
+ );
+ }
+
+ blockedUsersList() {
+ return (
+ <>
+ <h5>{i18n.t("blocked_users")}</h5>
+ <ul class="list-unstyled mb-0">
+ {this.state.personBlocks.map(pb => (
+ <li>
+ <span>
+ <PersonListing person={pb.target} />
+ <button
+ className="btn btn-sm"
+ onClick={linkEvent(
+ { ctx: this, recipientId: pb.target.id },
+ this.handleUnblockPerson
+ )}
+ data-tippy-content={i18n.t("unblock_user")}
+ >
+ <Icon icon="x" classes="icon-inline" />
+ </button>
+ </span>
+ </li>
+ ))}
+ </ul>
+ </>
+ );
+ }
+
+ blockUserForm() {
+ return (
+ <div class="form-group row">
+ <label class="col-md-4 col-form-label" htmlFor="block-person-filter">
+ {i18n.t("block_user")}
+ </label>
+ <div class="col-md-8">
+ <select
+ class="form-control"
+ id="block-person-filter"
+ value={this.state.blockPersonId}
+ >
+ <option value="0">—</option>
+ {this.state.blockPerson && (
+ <option value={this.state.blockPerson.person.id}>
+ {personSelectName(this.state.blockPerson)}
+ </option>
+ )}
+ </select>
+ </div>
+ </div>
+ );
+ }
+
+ blockCommunityCard() {
+ return (
+ <div>
+ {this.blockCommunityForm()}
+ {this.blockedCommunitiesList()}
+ </div>
+ );
+ }
+
+ blockedCommunitiesList() {
+ return (
+ <>
+ <h5>{i18n.t("blocked_communities")}</h5>
+ <ul class="list-unstyled mb-0">
+ {this.state.communityBlocks.map(cb => (
+ <li>
+ <span>
+ <CommunityLink community={cb.community} />
+ <button
+ className="btn btn-sm"
+ onClick={linkEvent(
+ { ctx: this, communityId: cb.community.id },
+ this.handleUnblockCommunity
+ )}
+ data-tippy-content={i18n.t("unblock_community")}
+ >
+ <Icon icon="x" classes="icon-inline" />
+ </button>
+ </span>
+ </li>
+ ))}
+ </ul>
+ </>
+ );
+ }
+
+ blockCommunityForm() {
+ return (
+ <div class="form-group row">
+ <label class="col-md-4 col-form-label" htmlFor="block-community-filter">
+ {i18n.t("block_community")}
+ </label>
+ <div class="col-md-8">
+ <select
+ class="form-control"
+ id="block-community-filter"
+ value={this.state.blockCommunityId}
+ >
+ <option value="0">—</option>
+ {this.state.blockCommunity && (
+ <option value={this.state.blockCommunity.community.id}>
+ {communitySelectName(this.state.blockCommunity)}
+ </option>
+ )}
+ </select>
+ </div>
+ </div>
+ );
+ }
+
+ saveUserSettingsHtmlForm() {
+ return (
+ <>
+ <h5>{i18n.t("settings")}</h5>
+ <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
+ <div class="form-group row">
+ <label class="col-lg-5 col-form-label" htmlFor="display-name">
+ {i18n.t("display_name")}
+ </label>
+ <div class="col-lg-7">
+ <input
+ id="display-name"
+ type="text"
+ class="form-control"
+ placeholder={i18n.t("optional")}
+ value={this.state.saveUserSettingsForm.display_name}
+ onInput={linkEvent(this, this.handleDisplayNameChange)}
+ pattern="^(?!@)(.+)$"
+ minLength={3}
+ />
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="col-lg-3 col-form-label" htmlFor="user-bio">
+ {i18n.t("bio")}
+ </label>
+ <div class="col-lg-9">
+ <MarkdownTextArea
+ initialContent={this.state.saveUserSettingsForm.bio}
+ onContentChange={this.handleBioChange}
+ maxLength={300}
+ hideNavigationWarnings
+ />
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="col-lg-3 col-form-label" htmlFor="user-email">
+ {i18n.t("email")}
+ </label>
+ <div class="col-lg-9">
+ <input
+ type="email"
+ id="user-email"
+ class="form-control"
+ placeholder={i18n.t("optional")}
+ value={this.state.saveUserSettingsForm.email}
+ onInput={linkEvent(this, this.handleEmailChange)}
+ minLength={3}
+ />
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="col-lg-5 col-form-label" htmlFor="matrix-user-id">
+ <a href={elementUrl} rel="noopener">
+ {i18n.t("matrix_user_id")}
+ </a>
+ </label>
+ <div class="col-lg-7">
+ <input
+ id="matrix-user-id"
+ type="text"
+ class="form-control"
+ placeholder="@user:example.com"
+ value={this.state.saveUserSettingsForm.matrix_user_id}
+ onInput={linkEvent(this, this.handleMatrixUserIdChange)}
+ pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
+ />
+ </div>
+ </div>
+ <div class="form-group">
+ <label>{i18n.t("avatar")}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t("upload_avatar")}
+ imageSrc={this.state.saveUserSettingsForm.avatar}
+ onUpload={this.handleAvatarUpload}
+ onRemove={this.handleAvatarRemove}
+ rounded
+ />
+ </div>
+ <div class="form-group">
+ <label>{i18n.t("banner")}</label>
+ <ImageUploadForm
+ uploadTitle={i18n.t("upload_banner")}
+ imageSrc={this.state.saveUserSettingsForm.banner}
+ onUpload={this.handleBannerUpload}
+ onRemove={this.handleBannerRemove}
+ />
+ </div>
+ <div class="form-group">
+ <label htmlFor="user-language">{i18n.t("language")}</label>
+ <select
+ id="user-language"
+ value={this.state.saveUserSettingsForm.lang}
+ onChange={linkEvent(this, this.handleLangChange)}
+ class="ml-2 custom-select w-auto"
+ >
+ <option disabled aria-hidden="true">
+ {i18n.t("language")}
+ </option>
+ <option value="browser">{i18n.t("browser_default")}</option>
+ <option disabled aria-hidden="true">
+ ──
+ </option>
+ {languages.sort().map(lang => (
+ <option value={lang.code}>
+ {ISO6391.getNativeName(lang.code) || lang.code}
+ </option>
+ ))}
+ </select>
+ </div>
+ <div class="form-group">
+ <label htmlFor="user-theme">{i18n.t("theme")}</label>
+ <select
+ id="user-theme"
+ value={this.state.saveUserSettingsForm.theme}
+ onChange={linkEvent(this, this.handleThemeChange)}
+ class="ml-2 custom-select w-auto"
+ >
+ <option disabled aria-hidden="true">
+ {i18n.t("theme")}
+ </option>
+ <option value="browser">{i18n.t("browser_default")}</option>
+ {themes.map(theme => (
+ <option value={theme}>{theme}</option>
+ ))}
+ </select>
+ </div>
+ <form className="form-group">
+ <label>
+ <div class="mr-2">{i18n.t("type")}</div>
+ </label>
+ <ListingTypeSelect
+ type_={
+ Object.values(ListingType)[
+ this.state.saveUserSettingsForm.default_listing_type
+ ]
+ }
+ showLocal={showLocal(this.isoData)}
+ onChange={this.handleListingTypeChange}
+ />
+ </form>
+ <form className="form-group">
+ <label>
+ <div class="mr-2">{i18n.t("sort_type")}</div>
+ </label>
+ <SortSelect
+ sort={
+ Object.values(SortType)[
+ this.state.saveUserSettingsForm.default_sort_type
+ ]
+ }
+ onChange={this.handleSortTypeChange}
+ />
+ </form>
+ {this.state.siteRes.site_view.site.enable_nsfw && (
+ <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}
+ onChange={linkEvent(this, this.handleShowNsfwChange)}
+ />
+ <label class="form-check-label" htmlFor="user-show-nsfw">
+ {i18n.t("show_nsfw")}
+ </label>
+ </div>
+ </div>
+ )}
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="user-show-scores"
+ type="checkbox"
+ checked={this.state.saveUserSettingsForm.show_scores}
+ onChange={linkEvent(this, this.handleShowScoresChange)}
+ />
+ <label class="form-check-label" htmlFor="user-show-scores">
+ {i18n.t("show_scores")}
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="user-show-avatars"
+ type="checkbox"
+ checked={this.state.saveUserSettingsForm.show_avatars}
+ onChange={linkEvent(this, this.handleShowAvatarsChange)}
+ />
+ <label class="form-check-label" htmlFor="user-show-avatars">
+ {i18n.t("show_avatars")}
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="user-bot-account"
+ type="checkbox"
+ checked={this.state.saveUserSettingsForm.bot_account}
+ onChange={linkEvent(this, this.handleBotAccount)}
+ />
+ <label class="form-check-label" htmlFor="user-bot-account">
+ {i18n.t("bot_account")}
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="user-show-bot-accounts"
+ type="checkbox"
+ checked={this.state.saveUserSettingsForm.show_bot_accounts}
+ onChange={linkEvent(this, this.handleShowBotAccounts)}
+ />
+ <label class="form-check-label" htmlFor="user-show-bot-accounts">
+ {i18n.t("show_bot_accounts")}
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="user-show-read-posts"
+ type="checkbox"
+ checked={this.state.saveUserSettingsForm.show_read_posts}
+ onChange={linkEvent(this, this.handleReadPosts)}
+ />
+ <label class="form-check-label" htmlFor="user-show-read-posts">
+ {i18n.t("show_read_posts")}
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="user-show-new-post-notifs"
+ type="checkbox"
+ checked={this.state.saveUserSettingsForm.show_new_post_notifs}
+ onChange={linkEvent(this, this.handleShowNewPostNotifs)}
+ />
+ <label
+ class="form-check-label"
+ htmlFor="user-show-new-post-notifs"
+ >
+ {i18n.t("show_new_post_notifs")}
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ id="user-send-notifications-to-email"
+ type="checkbox"
+ disabled={!this.state.saveUserSettingsForm.email}
+ checked={
+ this.state.saveUserSettingsForm.send_notifications_to_email
+ }
+ onChange={linkEvent(
+ this,
+ this.handleSendNotificationsToEmailChange
+ )}
+ />
+ <label
+ class="form-check-label"
+ htmlFor="user-send-notifications-to-email"
+ >
+ {i18n.t("send_notifications_to_email")}
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <button type="submit" class="btn btn-block btn-secondary mr-4">
+ {this.state.saveUserSettingsLoading ? (
+ <Spinner />
+ ) : (
+ capitalizeFirstLetter(i18n.t("save"))
+ )}
+ </button>
+ </div>
+ <hr />
+ <div class="form-group">
+ <button
+ class="btn btn-block btn-danger"
+ onClick={linkEvent(
+ this,
+ this.handleDeleteAccountShowConfirmToggle
+ )}
+ >
+ {i18n.t("delete_account")}
+ </button>
+ {this.state.deleteAccountShowConfirm && (
+ <>
+ <div class="my-2 alert alert-danger" role="alert">
+ {i18n.t("delete_account_confirm")}
+ </div>
+ <input
+ type="password"
+ value={this.state.deleteAccountForm.password}
+ autoComplete="new-password"
+ maxLength={60}
+ onInput={linkEvent(
+ this,
+ this.handleDeleteAccountPasswordChange
+ )}
+ class="form-control my-2"
+ />
+ <button
+ class="btn btn-danger mr-4"
+ disabled={!this.state.deleteAccountForm.password}
+ onClick={linkEvent(this, this.handleDeleteAccount)}
+ >
+ {this.state.deleteAccountLoading ? (
+ <Spinner />
+ ) : (
+ capitalizeFirstLetter(i18n.t("delete"))
+ )}
+ </button>
+ <button
+ class="btn btn-secondary"
+ onClick={linkEvent(
+ this,
+ this.handleDeleteAccountShowConfirmToggle
+ )}
+ >
+ {i18n.t("cancel")}
+ </button>
+ </>
+ )}
+ </div>
+ </form>
+ </>
+ );
+ }
+
+ setupBlockPersonChoices() {
+ if (isBrowser()) {
+ let selectId: any = document.getElementById("block-person-filter");
+ if (selectId) {
+ this.blockPersonChoices = new Choices(selectId, choicesConfig);
+ this.blockPersonChoices.passedElement.element.addEventListener(
+ "choice",
+ (e: any) => {
+ this.handleBlockPerson(Number(e.detail.choice.value));
+ },
+ false
+ );
+ this.blockPersonChoices.passedElement.element.addEventListener(
+ "search",
+ debounce(async (e: any) => {
+ let persons = (await fetchUsers(e.detail.value)).users;
+ let choices = persons.map(pvs => personToChoice(pvs));
+ this.blockPersonChoices.setChoices(choices, "value", "label", true);
+ }, 400),
+ false
+ );
+ }
+ }
+ }
+
+ setupBlockCommunityChoices() {
+ if (isBrowser()) {
+ let selectId: any = document.getElementById("block-community-filter");
+ if (selectId) {
+ this.blockCommunityChoices = new Choices(selectId, choicesConfig);
+ this.blockCommunityChoices.passedElement.element.addEventListener(
+ "choice",
+ (e: any) => {
+ this.handleBlockCommunity(Number(e.detail.choice.value));
+ },
+ false
+ );
+ this.blockCommunityChoices.passedElement.element.addEventListener(
+ "search",
+ debounce(async (e: any) => {
+ let communities = (await fetchCommunities(e.detail.value))
+ .communities;
+ let choices = communities.map(cv => communityToChoice(cv));
+ this.blockCommunityChoices.setChoices(
+ choices,
+ "value",
+ "label",
+ true
+ );
+ }, 400),
+ false
+ );
+ }
+ }
+ }
+
+ handleBlockPerson(personId: number) {
+ if (personId != 0) {
+ let blockUserForm: BlockPerson = {
+ person_id: personId,
+ block: true,
+ auth: authField(),
+ };
+ WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
+ }
+ }
+
+ handleUnblockPerson(i: { ctx: Settings; recipientId: number }) {
+ let blockUserForm: BlockPerson = {
+ person_id: i.recipientId,
+ block: false,
+ auth: authField(),
+ };
+ WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
+ }
+
+ handleBlockCommunity(community_id: number) {
+ if (community_id != 0) {
+ let blockCommunityForm: BlockCommunity = {
+ community_id,
+ block: true,
+ auth: authField(),
+ };
+ WebSocketService.Instance.send(
+ wsClient.blockCommunity(blockCommunityForm)
+ );
+ }
+ }
+
+ handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
+ let blockCommunityForm: BlockCommunity = {
+ community_id: i.communityId,
+ block: false,
+ auth: authField(),
+ };
+ WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
+ }
+
+ handleShowNsfwChange(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.show_nsfw = 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.setState(i.state);
+ }
+
+ handleBotAccount(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.bot_account = event.target.checked;
+ i.setState(i.state);
+ }
+
+ handleShowBotAccounts(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
+ i.setState(i.state);
+ }
+
+ handleReadPosts(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
+ i.setState(i.state);
+ }
+
+ handleShowNewPostNotifs(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.show_new_post_notifs = 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.setState(i.state);
+ }
+
+ handleSendNotificationsToEmailChange(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.send_notifications_to_email =
+ event.target.checked;
+ i.setState(i.state);
+ }
+
+ handleThemeChange(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.theme = 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(getLanguage(i.state.saveUserSettingsForm.lang));
+ i.setState(i.state);
+ }
+
+ handleSortTypeChange(val: SortType) {
+ this.state.saveUserSettingsForm.default_sort_type =
+ Object.keys(SortType).indexOf(val);
+ this.setState(this.state);
+ }
+
+ handleListingTypeChange(val: ListingType) {
+ this.state.saveUserSettingsForm.default_listing_type =
+ Object.keys(ListingType).indexOf(val);
+ this.setState(this.state);
+ }
+
+ handleEmailChange(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.email = event.target.value;
+ i.setState(i.state);
+ }
+
+ handleBioChange(val: string) {
+ this.state.saveUserSettingsForm.bio = val;
+ this.setState(this.state);
+ }
+
+ handleAvatarUpload(url: string) {
+ this.state.saveUserSettingsForm.avatar = url;
+ this.setState(this.state);
+ }
+
+ handleAvatarRemove() {
+ this.state.saveUserSettingsForm.avatar = "";
+ this.setState(this.state);
+ }
+
+ handleBannerUpload(url: string) {
+ this.state.saveUserSettingsForm.banner = url;
+ this.setState(this.state);
+ }
+
+ handleBannerRemove() {
+ this.state.saveUserSettingsForm.banner = "";
+ this.setState(this.state);
+ }
+
+ handleDisplayNameChange(i: Settings, event: any) {
+ i.state.saveUserSettingsForm.display_name = 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.setState(i.state);
+ }
+
+ handleNewPasswordChange(i: Settings, event: any) {
+ i.state.changePasswordForm.new_password = event.target.value;
+ if (i.state.changePasswordForm.new_password == "") {
+ i.state.changePasswordForm.new_password = undefined;
+ }
+ i.setState(i.state);
+ }
+
+ handleNewPasswordVerifyChange(i: Settings, event: any) {
+ i.state.changePasswordForm.new_password_verify = event.target.value;
+ if (i.state.changePasswordForm.new_password_verify == "") {
+ i.state.changePasswordForm.new_password_verify = undefined;
+ }
+ i.setState(i.state);
+ }
+
+ handleOldPasswordChange(i: Settings, event: any) {
+ i.state.changePasswordForm.old_password = event.target.value;
+ if (i.state.changePasswordForm.old_password == "") {
+ i.state.changePasswordForm.old_password = undefined;
+ }
+ i.setState(i.state);
+ }
+
+ handleSaveSettingsSubmit(i: Settings, event: any) {
+ event.preventDefault();
+ i.state.saveUserSettingsLoading = true;
+ i.setState(i.state);
+
+ WebSocketService.Instance.send(
+ wsClient.saveUserSettings(i.state.saveUserSettingsForm)
+ );
+ }
+
+ handleChangePasswordSubmit(i: Settings, event: any) {
+ event.preventDefault();
+ i.state.changePasswordLoading = true;
+ i.setState(i.state);
+
+ WebSocketService.Instance.send(
+ wsClient.changePassword(i.state.changePasswordForm)
+ );
+ }
+
+ handleDeleteAccountShowConfirmToggle(i: Settings, event: any) {
+ event.preventDefault();
+ i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm;
+ i.setState(i.state);
+ }
+
+ handleDeleteAccountPasswordChange(i: Settings, event: any) {
+ i.state.deleteAccountForm.password = event.target.value;
+ i.setState(i.state);
+ }
+
+ handleLogoutClick(i: Settings) {
+ UserService.Instance.logout();
+ i.context.router.history.push("/");
+ }
+
+ handleDeleteAccount(i: Settings, event: any) {
+ event.preventDefault();
+ i.state.deleteAccountLoading = true;
+ i.setState(i.state);
+
+ WebSocketService.Instance.send(
+ wsClient.deleteAccount(i.state.deleteAccountForm)
+ );
+ }
+
+ handleSwitchTab(i: { ctx: Settings; tab: string }) {
+ i.ctx.setState({ currentTab: i.tab });
+
+ if (i.ctx.state.currentTab == "blocks") {
+ i.ctx.setupBlockPersonChoices();
+ i.ctx.setupBlockCommunityChoices();
+ }
+ }
+
+ 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;
+ }
+
+ parseMessage(msg: any) {
+ let op = wsUserOp(msg);
+ console.log(msg);
+ if (msg.error) {
+ toast(i18n.t(msg.error), "danger");
+ return;
+ } else if (op == UserOperation.SaveUserSettings) {
+ let data = wsJsonToRes<LoginResponse>(msg).data;
+ UserService.Instance.login(data);
+ this.state.saveUserSettingsLoading = false;
+ this.setState(this.state);
+
+ window.scrollTo(0, 0);
+ } else if (op == UserOperation.ChangePassword) {
+ let data = wsJsonToRes<LoginResponse>(msg).data;
+ UserService.Instance.login(data);
+ this.state.changePasswordLoading = false;
+ this.setState(this.state);
+ window.scrollTo(0, 0);
+ toast(i18n.t("password_changed"));
+ } else if (op == UserOperation.DeleteAccount) {
+ this.setState({
+ deleteAccountLoading: false,
+ deleteAccountShowConfirm: false,
+ });
+ UserService.Instance.logout();
+ window.location.href = "/";
+ } else if (op == UserOperation.BlockPerson) {
+ let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ this.setState({ personBlocks: updatePersonBlock(data) });
+ } else if (op == UserOperation.BlockCommunity) {
+ let data = wsJsonToRes<BlockCommunityResponse>(msg).data;
+ this.setState({ communityBlocks: updateCommunityBlock(data) });
+ }
+ }
+}
this.handlePostCreate = this.handlePostCreate.bind(this);
this.state = this.emptyState;
- if (!UserService.Instance.localUserView && isBrowser()) {
+ if (!UserService.Instance.myUserInfo && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
<label
htmlFor="file-upload"
className={`${
- UserService.Instance.localUserView && "pointer"
+ UserService.Instance.myUserInfo && "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.localUserView}
+ disabled={!UserService.Instance.myUserInfo}
onChange={linkEvent(this, this.handleImageUpload)}
/>
</form>
let data = wsJsonToRes<PostResponse>(msg).data;
if (
data.post_view.creator.id ==
- UserService.Instance.localUserView.person.id
+ UserService.Instance.myUserInfo.local_user_view.person.id
) {
this.state.loading = false;
this.props.onCreate(data.post_view);
let data = wsJsonToRes<PostResponse>(msg).data;
if (
data.post_view.creator.id ==
- UserService.Instance.localUserView.person.id
+ UserService.Instance.myUserInfo.local_user_view.person.id
) {
this.state.loading = false;
this.props.onEdit(data.post_view);
AddModToCommunity,
BanFromCommunity,
BanPerson,
+ BlockPerson,
CommunityModeratorView,
CreatePostLike,
DeletePost,
post_view.creator.banned) && (
<span className="mx-1 badge badge-danger">{i18n.t("banned")}</span>
)}
+ {post_view.creator_blocked && (
+ <span className="mx-1 badge badge-danger">{"blocked"}</span>
+ )}
{this.props.showCommunity && (
<span>
<span class="mx-1"> {i18n.t("to")} </span>
postActions(mobile = false) {
let post_view = this.props.post_view;
return (
- UserService.Instance.localUserView && (
+ UserService.Instance.myUserInfo && (
<>
{this.showBody && (
<>
>
<Icon icon="copy" classes="icon-inline" />
</Link>
+ {!this.myPost && (
+ <button
+ class="btn btn-link btn-animate text-muted py-0"
+ onClick={linkEvent(this, this.handleBlockUserClick)}
+ data-tippy-content={i18n.t("block_user")}
+ aria-label={i18n.t("block_user")}
+ >
+ <Icon icon="slash" classes="icon-inline" />
+ </button>
+ )}
</>
)}
{this.myPost && this.showBody && (
private get myPost(): boolean {
return (
- UserService.Instance.localUserView &&
+ UserService.Instance.myUserInfo &&
this.props.post_view.creator.id ==
- UserService.Instance.localUserView.person.id
+ UserService.Instance.myUserInfo.local_user_view.person.id
);
}
.concat(this.props.moderators.map(m => m.moderator.id));
return canMod(
- UserService.Instance.localUserView,
+ UserService.Instance.myUserInfo,
adminsThenMods,
this.props.post_view.creator.id
);
.concat(this.props.moderators.map(m => m.moderator.id));
return canMod(
- UserService.Instance.localUserView,
+ UserService.Instance.myUserInfo,
adminsThenMods,
this.props.post_view.creator.id,
true
return (
this.props.admins &&
canMod(
- UserService.Instance.localUserView,
+ 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.localUserView &&
+ UserService.Instance.myUserInfo &&
this.props.post_view.creator.id !=
- UserService.Instance.localUserView.person.id &&
- UserService.Instance.localUserView.person.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.localUserView &&
+ UserService.Instance.myUserInfo &&
this.props.post_view.creator.id !=
- UserService.Instance.localUserView.person.id &&
- UserService.Instance.localUserView.person.id ==
+ UserService.Instance.myUserInfo.local_user_view.person.id &&
+ UserService.Instance.myUserInfo.local_user_view.person.id ==
this.props.admins[0].person.id
);
}
handlePostLike(i: PostListing, event: any) {
event.preventDefault();
- if (!UserService.Instance.localUserView) {
+ if (!UserService.Instance.myUserInfo) {
this.context.router.history.push(`/login`);
}
handlePostDisLike(i: PostListing, event: any) {
event.preventDefault();
- if (!UserService.Instance.localUserView) {
+ if (!UserService.Instance.myUserInfo) {
this.context.router.history.push(`/login`);
}
this.setState(this.state);
}
+ handleBlockUserClick(i: PostListing) {
+ let blockUserForm: BlockPerson = {
+ person_id: i.props.post_view.creator.id,
+ block: true,
+ auth: authField(),
+ };
+ WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
+ }
+
handleDeleteClick(i: PostListing) {
let deleteForm: DeletePost = {
post_id: i.props.post_view.post.id,
AddModToCommunityResponse,
BanFromCommunityResponse,
BanPersonResponse,
+ BlockPersonResponse,
CommentResponse,
CommunityResponse,
GetCommunityResponse,
setOptionalAuth,
setupTippy,
toast,
+ updatePersonBlock,
wsClient,
wsJsonToRes,
wsSubscribe,
: this.state.postRes.post_view.creator.id;
if (
- UserService.Instance.localUserView &&
- UserService.Instance.localUserView.person.id == parent_person_id
+ UserService.Instance.myUserInfo &&
+ UserService.Instance.myUserInfo.local_user_view.person.id ==
+ parent_person_id
) {
let form: MarkCommentAsRead = {
comment_id: found.comment.id,
this.state.postRes.post_view.community = data.community_view.community;
this.state.postRes.moderators = data.moderators;
this.setState(this.state);
+ } else if (op == UserOperation.BlockPerson) {
+ let data = wsJsonToRes<BlockPersonResponse>(msg).data;
+ updatePersonBlock(data);
}
}
}
this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage);
- if (!UserService.Instance.localUserView) {
+ if (!UserService.Instance.myUserInfo) {
toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`);
}
get mine(): boolean {
return (
- UserService.Instance.localUserView &&
- UserService.Instance.localUserView.person.id ==
+ UserService.Instance.myUserInfo &&
+ UserService.Instance.myUserInfo.local_user_view.person.id ==
this.props.private_message_view.creator.id
);
}
handlePrivateMessageCreate(message: PrivateMessageView) {
if (
- UserService.Instance.localUserView &&
- message.creator.id == UserService.Instance.localUserView.person.id
+ UserService.Instance.myUserInfo &&
+ message.creator.id ==
+ UserService.Instance.myUserInfo.local_user_view.person.id
) {
this.state.showReply = false;
this.setState(this.state);
import { Setup } from "./components/home/setup";
import { Modlog } from "./components/modlog";
import { Inbox } from "./components/person/inbox";
-import { Person } from "./components/person/person";
+import { Profile } from "./components/person/profile";
+import { Settings } from "./components/person/settings";
import { CreatePost } from "./components/post/create-post";
import { Post } from "./components/post/post";
import { CreatePrivateMessage } from "./components/private_message/create-private-message";
},
{
path: `/u/:username/view/:view/sort/:sort/page/:page`,
- component: Person,
- fetchInitialData: req => Person.fetchInitialData(req),
+ component: Profile,
+ fetchInitialData: req => Profile.fetchInitialData(req),
},
{
path: `/u/:username`,
- component: Person,
- fetchInitialData: req => Person.fetchInitialData(req),
+ component: Profile,
+ fetchInitialData: req => Profile.fetchInitialData(req),
},
{
path: `/inbox`,
component: Inbox,
fetchInitialData: req => Inbox.fetchInitialData(req),
},
+ {
+ path: `/settings`,
+ component: Settings,
+ },
{
path: `/modlog/community/:community_id`,
component: Modlog,
// import Cookies from 'js-cookie';
import IsomorphicCookie from "isomorphic-cookie";
import jwt_decode from "jwt-decode";
-import { LocalUserSettingsView, LoginResponse } from "lemmy-js-client";
+import { LoginResponse, MyUserInfo } from "lemmy-js-client";
import { BehaviorSubject, Subject } from "rxjs";
interface Claims {
export class UserService {
private static _instance: UserService;
- public localUserView: LocalUserSettingsView;
+ public myUserInfo: MyUserInfo;
public claims: Claims;
public jwtSub: Subject<string> = new Subject<string>();
public unreadCountSub: BehaviorSubject<number> = new BehaviorSubject<number>(
public logout() {
IsomorphicCookie.remove("jwt", { secure: false });
this.claims = undefined;
- this.localUserView = undefined;
+ this.myUserInfo = undefined;
// setTheme();
this.jwtSub.next("");
console.log("Logged out.");
import emojiShortName from "emoji-short-name";
import {
+ BlockCommunityResponse,
+ BlockPersonResponse,
CommentView,
+ CommunityBlockView,
CommunityView,
GetSiteMetadata,
GetSiteResponse,
LemmyHttp,
LemmyWebsocket,
ListingType,
- LocalUserSettingsView,
+ MyUserInfo,
+ PersonBlockView,
PersonViewSafe,
PostView,
PrivateMessageView,
}
export function canMod(
- localUserView: LocalUserSettingsView,
+ myUserInfo: MyUserInfo,
modIds: number[],
creator_id: number,
onSelf = false
): boolean {
// You can do moderator actions only on the mods added after you.
- if (localUserView) {
- let yourIndex = modIds.findIndex(id => id == localUserView.person.id);
+ if (myUserInfo) {
+ let yourIndex = modIds.findIndex(
+ id => id == myUserInfo.local_user_view.person.id
+ );
if (yourIndex == -1) {
return false;
} else {
// TODO
export function getLanguage(override?: string): string {
- let localUserView = UserService.Instance.localUserView;
+ let myUserInfo = UserService.Instance.myUserInfo;
let lang =
override ||
- (localUserView?.local_user.lang
- ? localUserView.local_user.lang
+ (myUserInfo?.local_user_view.local_user.lang
+ ? myUserInfo.local_user_view.local_user.lang
: "browser");
if (lang == "browser" && isBrowser()) {
export function showAvatars(): boolean {
return (
- UserService.Instance.localUserView?.local_user.show_avatars ||
- !UserService.Instance.localUserView
+ UserService.Instance.myUserInfo?.local_user_view.local_user.show_avatars ||
+ !UserService.Instance.myUserInfo
);
}
export function showScores(): boolean {
return (
- UserService.Instance.localUserView?.local_user.show_scores ||
- !UserService.Instance.localUserView
+ UserService.Instance.myUserInfo?.local_user_view.local_user.show_scores ||
+ !UserService.Instance.myUserInfo
);
}
export function getListingTypeFromProps(props: any): ListingType {
return props.match.params.listing_type
? routeListingTypeToEnum(props.match.params.listing_type)
- : UserService.Instance.localUserView
+ : UserService.Instance.myUserInfo
? Object.values(ListingType)[
- UserService.Instance.localUserView.local_user.default_listing_type
+ UserService.Instance.myUserInfo.local_user_view.local_user
+ .default_listing_type
]
: ListingType.Local;
}
export function getSortTypeFromProps(props: any): SortType {
return props.match.params.sort
? routeSortTypeToEnum(props.match.params.sort)
- : UserService.Instance.localUserView
+ : UserService.Instance.myUserInfo
? Object.values(SortType)[
- UserService.Instance.localUserView.local_user.default_sort_type
+ UserService.Instance.myUserInfo.local_user_view.local_user
+ .default_sort_type
]
: SortType.Active;
}
}
}
+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;
+}
+
+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;
+}
+
export function createCommentLikeRes(
data: CommentView,
comments: CommentView[]
let tree: CommentNodeI[] = [];
for (let comment_view of comments) {
let child = map.get(comment_view.comment.id);
- if (comment_view.comment.parent_id) {
- let parent_ = map.get(comment_view.comment.parent_id);
- parent_.children.push(child);
+ 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);
}
searchResultLimit: fetchLimit,
classNames: {
containerOuter: "choices",
- containerInner: "choices__inner bg-light border-0",
+ containerInner: "choices__inner bg-secondary border-0",
input: "form-control",
inputCloned: "choices__input--cloned",
list: "choices__list",
listItems: "choices__list--multiple",
listSingle: "choices__list--single",
listDropdown: "choices__list--dropdown",
- item: "choices__item bg-light",
+ item: "choices__item bg-secondary",
itemSelectable: "choices__item--selectable",
itemDisabled: "choices__item--disabled",
itemChoice: "choices__item--choice",
}
export function initializeSite(site: GetSiteResponse) {
- UserService.Instance.localUserView = site.my_user;
+ UserService.Instance.myUserInfo = site.my_user;
i18n.changeLanguage(getLanguage());
}
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.811:
- version "1.3.812"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.812.tgz#4c4fb407e0e1335056097f172e9f2c0a09efe77d"
- integrity sha512-7KiUHsKAWtSrjVoTSzxQ0nPLr/a+qoxNZwkwd9LkylTOgOXSVXkQbpIVT0WAUQcI5gXq3SwOTCrK+WfINHOXQg==
+ version "1.3.813"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.813.tgz#751a007d71c00faed8b5e9edaf3634c14b9c5a1f"
+ integrity sha512-YcSRImHt6JZZ2sSuQ4Bzajtk98igQ0iKkksqlzZLzbh4p0OIyJRSvUbsgqfcR8txdfsoYCc4ym306t4p2kP/aw==
emoji-regex@^7.0.1:
version "7.0.3"
dependencies:
invert-kv "^1.0.0"
-lemmy-js-client@0.11.4-rc.12:
- version "0.11.4-rc.12"
- resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.11.4-rc.12.tgz#a238da35dbde18c9fc8f6f14cb6849d541d0051b"
- integrity sha512-PzIFA/Q2j8i0ZXOWo0u/rR/RTt97v+G8a8jObHplq8UyyI3EzNIWZ5AS9514H5AoCIAMcDbwP4c/CQPiYf8yhA==
+lemmy-js-client@0.11.4-rc.14:
+ version "0.11.4-rc.14"
+ resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.11.4-rc.14.tgz#dcac5b8dc78c3b04e6b3630ff9351a94aa73e109"
+ integrity sha512-R8M+myyriNQljQlTweVqtUKGBpgmaM7RI4ebYb7N7sYr5Bk5Ip6v2qTNvKAV6BlsDOCTWANOonfeoz/cIerLEg==
levn@^0.4.1:
version "0.4.1"