Feature/user community block (#362)

* 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
This commit is contained in:
Dessalines 2021-08-19 22:56:18 -04:00 committed by GitHub
parent 2356b0d62f
commit b27d982a7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 2217 additions and 1603 deletions

View file

@ -69,7 +69,7 @@
"husky": "^7.0.1", "husky": "^7.0.1",
"import-sort-style-module": "^6.0.0", "import-sort-style-module": "^6.0.0",
"iso-639-1": "^2.1.9", "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", "lint-staged": "^11.0.1",
"mini-css-extract-plugin": "^2.1.0", "mini-css-extract-plugin": "^2.1.0",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",

View file

@ -152,7 +152,11 @@
} }
} }
.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; z-index: 2000;
} }

View file

@ -26,7 +26,7 @@ export class App extends Component<AppProps, any> {
<> <>
<Provider i18next={i18n}> <Provider i18next={i18n}>
<div> <div>
<Theme localUserView={siteRes.my_user} /> <Theme myUserInfo={siteRes.my_user} />
{siteRes && {siteRes &&
siteRes.site_view && siteRes.site_view &&
this.props.siteRes.site_view.site.icon && ( this.props.siteRes.site_view.site.icon && (

View file

@ -50,6 +50,7 @@ interface NavbarState {
unreadCount: number; unreadCount: number;
searchParam: string; searchParam: string;
toggleSearch: boolean; toggleSearch: boolean;
showDropdown: boolean;
onSiteBanner?(url: string): any; onSiteBanner?(url: string): any;
} }
@ -67,6 +68,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
expanded: false, expanded: false,
searchParam: "", searchParam: "",
toggleSearch: false, toggleSearch: false,
showDropdown: false,
}; };
subscription: any; subscription: any;
@ -122,15 +124,17 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
} }
} }
handleSearchParam(i: Navbar, event: any) { componentWillUnmount() {
i.state.searchParam = event.target.value; this.wsSub.unsubscribe();
i.setState(i.state); this.userSub.unsubscribe();
this.unreadCountSub.unsubscribe();
} }
updateUrl() { updateUrl() {
const searchParam = this.state.searchParam; const searchParam = this.state.searchParam;
this.setState({ searchParam: "" }); this.setState({ searchParam: "" });
this.setState({ toggleSearch: false }); this.setState({ toggleSearch: false });
this.setState({ showDropdown: false, expanded: false });
if (searchParam === "") { if (searchParam === "") {
this.context.router.history.push(`/search/`); this.context.router.history.push(`/search/`);
} else { } else {
@ -141,54 +145,26 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
} }
} }
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() { render() {
return this.navbar(); return this.navbar();
} }
componentWillUnmount() {
this.wsSub.unsubscribe();
this.userSub.unsubscribe();
this.unreadCountSub.unsubscribe();
}
// TODO class active corresponding to current page // TODO class active corresponding to current page
navbar() { navbar() {
let localUserView = let myUserInfo =
UserService.Instance.localUserView || this.props.site_res.my_user; UserService.Instance.myUserInfo || this.props.site_res.my_user;
let person = myUserInfo?.local_user_view.person;
return ( return (
<nav class="navbar navbar-expand-lg navbar-light shadow-sm p-0 px-3"> <nav class="navbar navbar-expand-lg navbar-light shadow-sm p-0 px-3">
<div class="container"> <div class="container">
{this.props.site_res.site_view && ( {this.props.site_res.site_view && (
<Link <button
title={ title={
this.props.site_res.site_view.site.description || this.props.site_res.site_view.site.description ||
this.props.site_res.site_view.site.name this.props.site_res.site_view.site.name
} }
className="d-flex align-items-center navbar-brand mr-md-3" className="d-flex align-items-center navbar-brand mr-md-3 btn btn-link"
to="/" onClick={linkEvent(this, this.handleGotoHome)}
> >
{this.props.site_res.site_view.site.icon && showAvatars() && ( {this.props.site_res.site_view.site.icon && showAvatars() && (
<PictrsImage <PictrsImage
@ -197,12 +173,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
/> />
)} )}
{this.props.site_res.site_view.site.name} {this.props.site_res.site_view.site.name}
</Link> </button>
)} )}
{this.state.isLoggedIn && ( {this.state.isLoggedIn && (
<Link <button
className="ml-auto p-1 navbar-toggler nav-link border-0" className="ml-auto p-1 navbar-toggler nav-link border-0 btn btn-link"
to="/inbox" onClick={linkEvent(this, this.handleGotoInbox)}
title={i18n.t("inbox")} title={i18n.t("inbox")}
> >
<Icon icon="bell" /> <Icon icon="bell" />
@ -216,7 +192,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
{this.state.unreadCount} {this.state.unreadCount}
</span> </span>
)} )}
</Link> </button>
)} )}
<button <button
class="navbar-toggler border-0 p-1" class="navbar-toggler border-0 p-1"
@ -232,35 +208,32 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
> >
<ul class="navbar-nav my-2 mr-auto"> <ul class="navbar-nav my-2 mr-auto">
<li class="nav-item"> <li class="nav-item">
<Link <button
className="nav-link" className="nav-link btn btn-link"
to="/communities" onClick={linkEvent(this, this.handleGotoCommunities)}
title={i18n.t("communities")} title={i18n.t("communities")}
> >
{i18n.t("communities")} {i18n.t("communities")}
</Link> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<Link <button
className="nav-link" className="nav-link btn btn-link"
to={{ onClick={linkEvent(this, this.handleGotoCreatePost)}
pathname: "/create_post",
state: { prevPath: this.currentLocation },
}}
title={i18n.t("create_post")} title={i18n.t("create_post")}
> >
{i18n.t("create_post")} {i18n.t("create_post")}
</Link> </button>
</li> </li>
{this.canCreateCommunity && ( {this.canCreateCommunity && (
<li class="nav-item"> <li class="nav-item">
<Link <button
className="nav-link" className="nav-link btn btn-link"
to="/create_community" onClick={linkEvent(this, this.handleGotoCreateCommunity)}
title={i18n.t("create_community")} title={i18n.t("create_community")}
> >
{i18n.t("create_community")} {i18n.t("create_community")}
</Link> </button>
</li> </li>
)} )}
<li class="nav-item"> <li class="nav-item">
@ -276,13 +249,13 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<ul class="navbar-nav my-2"> <ul class="navbar-nav my-2">
{this.canAdmin && ( {this.canAdmin && (
<li className="nav-item"> <li className="nav-item">
<Link <button
className="nav-link" className="nav-link btn btn-link"
to={`/admin`} onClick={linkEvent(this, this.handleGotoAdmin)}
title={i18n.t("admin_settings")} title={i18n.t("admin_settings")}
> >
<Icon icon="settings" /> <Icon icon="settings" />
</Link> </button>
</li> </li>
)} )}
</ul> </ul>
@ -343,34 +316,73 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
</li> </li>
</ul> </ul>
<ul class="navbar-nav"> <ul class="navbar-nav">
<li className="nav-item"> <li class="nav-item dropdown">
<Link <button
className="nav-link" class="nav-link btn btn-link dropdown-toggle"
to={`/u/${localUserView.person.name}`} onClick={linkEvent(this, this.handleShowDropdown)}
title={i18n.t("settings")} id="navbarDropdown"
role="button"
aria-expanded="false"
> >
<span> <span>
{localUserView.person.avatar && showAvatars() && ( {person.avatar && showAvatars() && (
<PictrsImage src={localUserView.person.avatar} icon /> <PictrsImage src={person.avatar} icon />
)} )}
{localUserView.person.display_name {person.display_name
? localUserView.person.display_name ? person.display_name
: localUserView.person.name} : person.name}
</span> </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> </li>
</ul> </ul>
</> </>
) : ( ) : (
<ul class="navbar-nav my-2"> <ul class="navbar-nav my-2">
<li className="ml-2 nav-item"> <li className="ml-2 nav-item">
<Link <button
className="btn btn-success" className="btn btn-success"
to="/login" onClick={linkEvent(this, this.handleGotoLogin)}
title={i18n.t("login_sign_up")} title={i18n.t("login_sign_up")}
> >
{i18n.t("login_sign_up")} {i18n.t("login_sign_up")}
</Link> </button>
</li> </li>
</ul> </ul>
)} )}
@ -385,6 +397,95 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
i.setState(i.state); 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) { parseMessage(msg: any) {
let op = wsUserOp(msg); let op = wsUserOp(msg);
console.log(msg); console.log(msg);
@ -432,8 +533,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
// This is only called on a successful login // This is only called on a successful login
let data = wsJsonToRes<GetSiteResponse>(msg).data; let data = wsJsonToRes<GetSiteResponse>(msg).data;
console.log(data.my_user); console.log(data.my_user);
UserService.Instance.localUserView = data.my_user; UserService.Instance.myUserInfo = data.my_user;
setTheme(UserService.Instance.localUserView.local_user.theme); setTheme(
UserService.Instance.myUserInfo.local_user_view.local_user.theme
);
i18n.changeLanguage(getLanguage()); i18n.changeLanguage(getLanguage());
this.state.isLoggedIn = true; this.state.isLoggedIn = true;
this.setState(this.state); this.setState(this.state);
@ -443,7 +546,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
if (this.state.isLoggedIn) { if (this.state.isLoggedIn) {
if ( if (
data.recipient_ids.includes( 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); this.state.replies.push(data.comment_view);
@ -459,7 +562,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
if (this.state.isLoggedIn) { if (this.state.isLoggedIn) {
if ( if (
data.private_message_view.recipient.id == 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.messages.push(data.private_message_view);
this.state.unreadCount++; this.state.unreadCount++;
@ -525,10 +628,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
get canAdmin(): boolean { get canAdmin(): boolean {
return ( return (
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.props.site_res.admins this.props.site_res.admins
.map(a => a.person.id) .map(a => a.person.id)
.includes(UserService.Instance.localUserView.person.id) .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
); );
} }
@ -547,7 +650,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
} }
requestNotificationPermission() { requestNotificationPermission() {
if (UserService.Instance.localUserView) { if (UserService.Instance.myUserInfo) {
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
if (!Notification) { if (!Notification) {
toast(i18n.t("notifications_error"), "danger"); toast(i18n.t("notifications_error"), "danger");

View file

@ -1,15 +1,15 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { Helmet } from "inferno-helmet"; import { Helmet } from "inferno-helmet";
import { LocalUserSettingsView } from "lemmy-js-client"; import { MyUserInfo } from "lemmy-js-client";
interface Props { interface Props {
localUserView: LocalUserSettingsView | undefined; myUserInfo: MyUserInfo | undefined;
} }
export class Theme extends Component<Props> { export class Theme extends Component<Props> {
render() { render() {
let user = this.props.localUserView; let user = this.props.myUserInfo;
let hasTheme = user && user.local_user.theme !== "browser"; let hasTheme = user && user.local_user_view.local_user.theme !== "browser";
return ( return (
<Helmet> <Helmet>
@ -17,7 +17,7 @@ export class Theme extends Component<Props> {
<link <link
rel="stylesheet" rel="stylesheet"
type="text/css" 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`}
/> />
) : ( ) : (
[ [

View file

@ -68,7 +68,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
render() { render() {
return ( return (
<div class="mb-3"> <div class="mb-3">
{UserService.Instance.localUserView ? ( {UserService.Instance.myUserInfo ? (
<MarkdownTextArea <MarkdownTextArea
initialContent={ initialContent={
this.props.edit this.props.edit
@ -135,7 +135,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
console.log(msg); console.log(msg);
// Only do the showing and hiding if logged in // Only do the showing and hiding if logged in
if (UserService.Instance.localUserView) { if (UserService.Instance.myUserInfo) {
if ( if (
op == UserOperation.CreateComment || op == UserOperation.CreateComment ||
op == UserOperation.EditComment op == UserOperation.EditComment

View file

@ -5,6 +5,7 @@ import {
AddModToCommunity, AddModToCommunity,
BanFromCommunity, BanFromCommunity,
BanPerson, BanPerson,
BlockPerson,
CommentView, CommentView,
CommunityModeratorView, CommunityModeratorView,
CreateCommentLike, CreateCommentLike,
@ -279,7 +280,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)} )}
</button> </button>
)} )}
{UserService.Instance.localUserView && !this.props.viewOnly && ( {UserService.Instance.myUserInfo && !this.props.viewOnly && (
<> <>
<button <button
className={`btn btn-link btn-animate ${ className={`btn btn-link btn-animate ${
@ -333,15 +334,28 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
) : ( ) : (
<> <>
{!this.myComment && ( {!this.myComment && (
<button class="btn btn-link btn-animate"> <>
<Link <button class="btn btn-link btn-animate">
className="text-muted" <Link
to={`/create_private_message/recipient/${cv.creator.id}`} className="text-muted"
title={i18n.t("message").toLowerCase()} 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" /> <Icon icon="slash" />
</Link> </button>
</button> </>
)} )}
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
@ -829,7 +843,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
get myComment(): boolean { get myComment(): boolean {
return ( return (
this.props.node.comment_view.creator.id == this.props.node.comment_view.creator.id ==
UserService.Instance.localUserView?.person.id UserService.Instance.myUserInfo?.local_user_view.person.id
); );
} }
@ -864,7 +878,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
.concat(this.props.moderators.map(m => m.moderator.id)); .concat(this.props.moderators.map(m => m.moderator.id));
return canMod( return canMod(
UserService.Instance.localUserView, UserService.Instance.myUserInfo,
adminsThenMods, adminsThenMods,
this.props.node.comment_view.creator.id this.props.node.comment_view.creator.id
); );
@ -877,7 +891,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
return ( return (
this.props.admins && this.props.admins &&
canMod( canMod(
UserService.Instance.localUserView, UserService.Instance.myUserInfo,
this.props.admins.map(a => a.person.id), this.props.admins.map(a => a.person.id),
this.props.node.comment_view.creator.id this.props.node.comment_view.creator.id
) )
@ -887,10 +901,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
get amCommunityCreator(): boolean { get amCommunityCreator(): boolean {
return ( return (
this.props.moderators && this.props.moderators &&
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.props.node.comment_view.creator.id != this.props.node.comment_view.creator.id !=
UserService.Instance.localUserView.person.id && UserService.Instance.myUserInfo.local_user_view.person.id &&
UserService.Instance.localUserView.person.id == UserService.Instance.myUserInfo.local_user_view.person.id ==
this.props.moderators[0].moderator.id this.props.moderators[0].moderator.id
); );
} }
@ -898,10 +912,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
get amSiteCreator(): boolean { get amSiteCreator(): boolean {
return ( return (
this.props.admins && this.props.admins &&
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.props.node.comment_view.creator.id != this.props.node.comment_view.creator.id !=
UserService.Instance.localUserView.person.id && UserService.Instance.myUserInfo.local_user_view.person.id &&
UserService.Instance.localUserView.person.id == UserService.Instance.myUserInfo.local_user_view.person.id ==
this.props.admins[0].person.id this.props.admins[0].person.id
); );
} }
@ -925,6 +939,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
i.setState(i.state); 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) { handleDeleteClick(i: CommentNode) {
let comment = i.props.node.comment_view.comment; let comment = i.props.node.comment_view.comment;
let deleteForm: DeleteComment = { let deleteForm: DeleteComment = {

View file

@ -65,7 +65,7 @@ export class ImageUploadForm extends Component<
accept="image/*,video/*" accept="image/*,video/*"
name={this.id} name={this.id}
class="d-none" class="d-none"
disabled={!UserService.Instance.localUserView} disabled={!UserService.Instance.myUserInfo}
onChange={linkEvent(this, this.handleImageUpload)} onChange={linkEvent(this, this.handleImageUpload)}
/> />
</form> </form>

View file

@ -44,7 +44,7 @@ export class ListingTypeSelect extends Component<
className={`btn btn-outline-secondary className={`btn btn-outline-secondary
${this.state.type_ == ListingType.Subscribed && "active"} ${this.state.type_ == ListingType.Subscribed && "active"}
${ ${
UserService.Instance.localUserView == undefined UserService.Instance.myUserInfo == undefined
? "disabled" ? "disabled"
: "pointer" : "pointer"
} }
@ -56,7 +56,7 @@ export class ListingTypeSelect extends Component<
value={ListingType.Subscribed} value={ListingType.Subscribed}
checked={this.state.type_ == ListingType.Subscribed} checked={this.state.type_ == ListingType.Subscribed}
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
disabled={UserService.Instance.localUserView == undefined} disabled={UserService.Instance.myUserInfo == undefined}
/> />
{i18n.t("subscribed")} {i18n.t("subscribed")}
</label> </label>

View file

@ -209,7 +209,7 @@ export class MarkdownTextArea extends Component<
<label <label
htmlFor={`file-upload-${this.id}`} htmlFor={`file-upload-${this.id}`}
className={`mb-0 ${ className={`mb-0 ${
UserService.Instance.localUserView && "pointer" UserService.Instance.myUserInfo && "pointer"
}`} }`}
data-tippy-content={i18n.t("upload_image")} data-tippy-content={i18n.t("upload_image")}
> >
@ -225,7 +225,7 @@ export class MarkdownTextArea extends Component<
accept="image/*,video/*" accept="image/*,video/*"
name="file" name="file"
class="d-none" class="d-none"
disabled={!UserService.Instance.localUserView} disabled={!UserService.Instance.myUserInfo}
onChange={linkEvent(this, this.handleImageUpload)} onChange={linkEvent(this, this.handleImageUpload)}
/> />
</form> </form>

View file

@ -12,6 +12,15 @@ export const SYMBOLS = (
xmlnsXlink="http://www.w3.org/1999/xlink" xmlnsXlink="http://www.w3.org/1999/xlink"
> >
<defs> <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"> <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> <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> </symbol>

View file

@ -9,7 +9,7 @@ import {
} from "lemmy-js-client"; } from "lemmy-js-client";
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
import { i18n } from "../../i18next"; import { i18n } from "../../i18next";
import { WebSocketService } from "../../services"; import { UserService, WebSocketService } from "../../services";
import { import {
authField, authField,
capitalizeFirstLetter, capitalizeFirstLetter,
@ -321,10 +321,39 @@ export class CommunityForm extends Component<
let data = wsJsonToRes<CommunityResponse>(msg).data; let data = wsJsonToRes<CommunityResponse>(msg).data;
this.state.loading = false; this.state.loading = false;
this.props.onCreate(data.community_view); 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) { } else if (op == UserOperation.EditCommunity) {
let data = wsJsonToRes<CommunityResponse>(msg).data; let data = wsJsonToRes<CommunityResponse>(msg).data;
this.state.loading = false; this.state.loading = false;
this.props.onEdit(data.community_view); 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;
}
} }
} }
} }

View file

@ -2,6 +2,7 @@ import { Component, linkEvent } from "inferno";
import { import {
AddModToCommunityResponse, AddModToCommunityResponse,
BanFromCommunityResponse, BanFromCommunityResponse,
BlockPersonResponse,
CommentResponse, CommentResponse,
CommentView, CommentView,
CommunityResponse, CommunityResponse,
@ -42,6 +43,7 @@ import {
setOptionalAuth, setOptionalAuth,
setupTippy, setupTippy,
toast, toast,
updatePersonBlock,
wsClient, wsClient,
wsJsonToRes, wsJsonToRes,
wsSubscribe, wsSubscribe,
@ -178,9 +180,10 @@ export class Community extends Component<any, State> {
let sort: SortType = pathSplit[6] let sort: SortType = pathSplit[6]
? SortType[pathSplit[6]] ? SortType[pathSplit[6]]
: UserService.Instance.localUserView : UserService.Instance.myUserInfo
? Object.values(SortType)[ ? Object.values(SortType)[
UserService.Instance.localUserView.local_user.default_sort_type UserService.Instance.myUserInfo.local_user_view.local_user
.default_sort_type
] ]
: SortType.Active; : SortType.Active;
@ -490,7 +493,10 @@ export class Community extends Component<any, State> {
} else if (op == UserOperation.CreatePost) { } else if (op == UserOperation.CreatePost) {
let data = wsJsonToRes<PostResponse>(msg).data; let data = wsJsonToRes<PostResponse>(msg).data;
this.state.posts.unshift(data.post_view); 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); notifyPost(data.post_view, this.context.router);
} }
this.setState(this.state); this.setState(this.state);
@ -540,6 +546,9 @@ export class Community extends Component<any, State> {
let data = wsJsonToRes<CommentResponse>(msg).data; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data.comment_view, this.state.comments); createCommentLikeRes(data.comment_view, this.state.comments);
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.BlockPerson) {
let data = wsJsonToRes<BlockPersonResponse>(msg).data;
updatePersonBlock(data);
} }
} }
} }

View file

@ -28,7 +28,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
this.parseMessage = this.parseMessage.bind(this); this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage); this.subscription = wsSubscribe(this.parseMessage);
if (!UserService.Instance.localUserView && isBrowser()) { if (!UserService.Instance.myUserInfo && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger"); toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`); this.context.router.history.push(`/login`);
} }

View file

@ -104,7 +104,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<a <a
class="btn btn-secondary btn-sm mr-2" class="btn btn-secondary btn-sm mr-2"
href="#" href="#"
onClick={linkEvent(community.id, this.handleUnsubscribe)} onClick={linkEvent(this, this.handleUnsubscribe)}
> >
<Icon icon="check" classes="icon-inline text-success mr-1" /> <Icon icon="check" classes="icon-inline text-success mr-1" />
{i18n.t("joined")} {i18n.t("joined")}
@ -257,10 +257,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<a <a
class="btn btn-secondary btn-block" class="btn btn-secondary btn-block"
href="#" href="#"
onClick={linkEvent( onClick={linkEvent(this, this.handleSubscribe)}
community_view.community.id,
this.handleSubscribe
)}
> >
{i18n.t("subscribe")} {i18n.t("subscribe")}
</a> </a>
@ -447,7 +444,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
handleLeaveModTeamClick(i: Sidebar) { handleLeaveModTeamClick(i: Sidebar) {
let form: AddModToCommunity = { 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, community_id: i.props.community_view.community.id,
added: false, added: false,
auth: authField(), auth: authField(),
@ -462,48 +459,62 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
i.setState(i.state); i.setState(i.state);
} }
handleUnsubscribe(communityId: number, event: any) { handleUnsubscribe(i: Sidebar, event: any) {
event.preventDefault(); event.preventDefault();
let community_id = i.props.community_view.community.id;
let form: FollowCommunity = { let form: FollowCommunity = {
community_id: communityId, community_id,
follow: false, follow: false,
auth: authField(), auth: authField(),
}; };
WebSocketService.Instance.send(wsClient.followCommunity(form)); 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(); event.preventDefault();
let community_id = i.props.community_view.community.id;
let form: FollowCommunity = { let form: FollowCommunity = {
community_id: communityId, community_id,
follow: true, follow: true,
auth: authField(), auth: authField(),
}; };
WebSocketService.Instance.send(wsClient.followCommunity(form)); 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 { private get amTopMod(): boolean {
return ( return (
this.props.moderators[0].moderator.id == this.props.moderators[0].moderator.id ==
UserService.Instance.localUserView.person.id UserService.Instance.myUserInfo.local_user_view.person.id
); );
} }
get canMod(): boolean { get canMod(): boolean {
return ( return (
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.props.moderators this.props.moderators
.map(m => m.moderator.id) .map(m => m.moderator.id)
.includes(UserService.Instance.localUserView.person.id) .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
); );
} }
get canAdmin(): boolean { get canAdmin(): boolean {
return ( return (
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.props.admins this.props.admins
.map(a => a.person.id) .map(a => a.person.id)
.includes(UserService.Instance.localUserView.person.id) .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
); );
} }

View file

@ -4,13 +4,12 @@ import { Link } from "inferno-router";
import { import {
AddAdminResponse, AddAdminResponse,
BanPersonResponse, BanPersonResponse,
BlockPersonResponse,
CommentResponse, CommentResponse,
CommentView, CommentView,
CommunityFollowerView,
CommunityView, CommunityView,
GetComments, GetComments,
GetCommentsResponse, GetCommentsResponse,
GetFollowedCommunitiesResponse,
GetPosts, GetPosts,
GetPostsResponse, GetPostsResponse,
GetSiteResponse, GetSiteResponse,
@ -49,6 +48,7 @@ import {
setupTippy, setupTippy,
showLocal, showLocal,
toast, toast,
updatePersonBlock,
wsClient, wsClient,
wsJsonToRes, wsJsonToRes,
wsSubscribe, wsSubscribe,
@ -68,7 +68,6 @@ import { PostListings } from "../post/post-listings";
import { SiteForm } from "./site-form"; import { SiteForm } from "./site-form";
interface HomeState { interface HomeState {
subscribedCommunities: CommunityFollowerView[];
trendingCommunities: CommunityView[]; trendingCommunities: CommunityView[];
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
showEditSite: boolean; showEditSite: boolean;
@ -102,7 +101,6 @@ export class Home extends Component<any, HomeState> {
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private subscription: Subscription; private subscription: Subscription;
private emptyState: HomeState = { private emptyState: HomeState = {
subscribedCommunities: [],
trendingCommunities: [], trendingCommunities: [],
siteRes: this.isoData.site_res, siteRes: this.isoData.site_res,
showEditSite: false, showEditSite: false,
@ -139,21 +137,10 @@ export class Home extends Component<any, HomeState> {
this.state.comments = this.isoData.routeData[0].comments; this.state.comments = this.isoData.routeData[0].comments;
} }
this.state.trendingCommunities = this.isoData.routeData[1].communities; this.state.trendingCommunities = this.isoData.routeData[1].communities;
if (UserService.Instance.localUserView) {
this.state.subscribedCommunities =
this.isoData.routeData[2].communities;
}
this.state.loading = false; this.state.loading = false;
} else { } else {
this.fetchTrendingCommunities(); this.fetchTrendingCommunities();
this.fetchData(); this.fetchData();
if (UserService.Instance.localUserView) {
WebSocketService.Instance.send(
wsClient.getFollowedCommunities({
auth: authField(),
})
);
}
} }
setupTippy(); setupTippy();
@ -204,16 +191,18 @@ export class Home extends Component<any, HomeState> {
// TODO figure out auth default_listingType, default_sort_type // TODO figure out auth default_listingType, default_sort_type
let type_: ListingType = pathSplit[5] let type_: ListingType = pathSplit[5]
? ListingType[pathSplit[5]] ? ListingType[pathSplit[5]]
: UserService.Instance.localUserView : UserService.Instance.myUserInfo
? Object.values(ListingType)[ ? Object.values(ListingType)[
UserService.Instance.localUserView.local_user.default_listing_type UserService.Instance.myUserInfo.local_user_view.local_user
.default_listing_type
] ]
: ListingType.Local; : ListingType.Local;
let sort: SortType = pathSplit[7] let sort: SortType = pathSplit[7]
? SortType[pathSplit[7]] ? SortType[pathSplit[7]]
: UserService.Instance.localUserView : UserService.Instance.myUserInfo
? Object.values(SortType)[ ? Object.values(SortType)[
UserService.Instance.localUserView.local_user.default_sort_type UserService.Instance.myUserInfo.local_user_view.local_user
.default_sort_type
] ]
: SortType.Active; : SortType.Active;
@ -250,10 +239,6 @@ export class Home extends Component<any, HomeState> {
}; };
promises.push(req.client.listCommunities(trendingCommunitiesForm)); promises.push(req.client.listCommunities(trendingCommunitiesForm));
if (req.auth) {
promises.push(req.client.getFollowedCommunities({ auth: req.auth }));
}
return promises; return promises;
} }
@ -303,8 +288,8 @@ export class Home extends Component<any, HomeState> {
return ( return (
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
{UserService.Instance.localUserView && {UserService.Instance.myUserInfo &&
this.state.subscribedCommunities.length > 0 && ( UserService.Instance.myUserInfo.follows.length > 0 && (
<button <button
class="btn btn-secondary d-inline-block mb-2 mr-3" class="btn btn-secondary d-inline-block mb-2 mr-3"
onClick={linkEvent(this, this.handleShowSubscribedMobile)} onClick={linkEvent(this, this.handleShowSubscribedMobile)}
@ -377,8 +362,8 @@ export class Home extends Component<any, HomeState> {
</div> </div>
</div> </div>
{UserService.Instance.localUserView && {UserService.Instance.myUserInfo &&
this.state.subscribedCommunities.length > 0 && ( UserService.Instance.myUserInfo.follows.length > 0 && (
<div class="card border-secondary mb-3"> <div class="card border-secondary mb-3">
<div class="card-body">{this.subscribedCommunities()}</div> <div class="card-body">{this.subscribedCommunities()}</div>
</div> </div>
@ -443,7 +428,7 @@ export class Home extends Component<any, HomeState> {
</T> </T>
</h5> </h5>
<ul class="list-inline mb-0"> <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"> <li class="list-inline-item d-inline-block">
<CommunityLink community={cfv.community} /> <CommunityLink community={cfv.community} />
</li> </li>
@ -704,7 +689,7 @@ export class Home extends Component<any, HomeState> {
<Icon icon="rss" classes="text-muted small" /> <Icon icon="rss" classes="text-muted small" />
</a> </a>
)} )}
{UserService.Instance.localUserView && {UserService.Instance.myUserInfo &&
this.state.listingType == ListingType.Subscribed && ( this.state.listingType == ListingType.Subscribed && (
<a <a
href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`} href={`/feeds/front/${UserService.Instance.auth}.xml?sort=${this.state.sort}`}
@ -720,10 +705,10 @@ export class Home extends Component<any, HomeState> {
get canAdmin(): boolean { get canAdmin(): boolean {
return ( return (
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.state.siteRes.admins this.state.siteRes.admins
.map(a => a.person.id) .map(a => a.person.id)
.includes(UserService.Instance.localUserView.person.id) .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
); );
} }
@ -807,10 +792,6 @@ export class Home extends Component<any, HomeState> {
wsClient.communityJoin({ community_id: 0 }) wsClient.communityJoin({ community_id: 0 })
); );
this.fetchData(); 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) { } else if (op == UserOperation.ListCommunities) {
let data = wsJsonToRes<ListCommunitiesResponse>(msg).data; let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
this.state.trendingCommunities = data.communities; this.state.trendingCommunities = data.communities;
@ -836,21 +817,21 @@ export class Home extends Component<any, HomeState> {
let nsfwCheck = let nsfwCheck =
!nsfw || !nsfw ||
(nsfw && (nsfw &&
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
UserService.Instance.localUserView.local_user.show_nsfw); 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 // Only push these if you're on the first page, and you pass the nsfw check
if (this.state.page == 1 && nsfwCheck) { if (this.state.page == 1 && nsfwCheck) {
// If you're on subscribed, only push it if you're subscribed. // If you're on subscribed, only push it if you're subscribed.
if (this.state.listingType == ListingType.Subscribed) { if (this.state.listingType == ListingType.Subscribed) {
if ( if (
this.state.subscribedCommunities UserService.Instance.myUserInfo.follows
.map(c => c.community.id) .map(c => c.community.id)
.includes(data.post_view.community.id) .includes(data.post_view.community.id)
) { ) {
this.state.posts.unshift(data.post_view); this.state.posts.unshift(data.post_view);
if ( if (
UserService.Instance.localUserView?.local_user UserService.Instance.myUserInfo?.local_user_view.local_user
.show_new_post_notifs .show_new_post_notifs
) { ) {
notifyPost(data.post_view, this.context.router); notifyPost(data.post_view, this.context.router);
@ -861,7 +842,7 @@ export class Home extends Component<any, HomeState> {
if (data.post_view.post.local) { if (data.post_view.post.local) {
this.state.posts.unshift(data.post_view); this.state.posts.unshift(data.post_view);
if ( if (
UserService.Instance.localUserView?.local_user UserService.Instance.myUserInfo?.local_user_view.local_user
.show_new_post_notifs .show_new_post_notifs
) { ) {
notifyPost(data.post_view, this.context.router); notifyPost(data.post_view, this.context.router);
@ -870,7 +851,8 @@ export class Home extends Component<any, HomeState> {
} else { } else {
this.state.posts.unshift(data.post_view); this.state.posts.unshift(data.post_view);
if ( 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); notifyPost(data.post_view, this.context.router);
} }
@ -937,7 +919,7 @@ export class Home extends Component<any, HomeState> {
// If you're on subscribed, only push it if you're subscribed. // If you're on subscribed, only push it if you're subscribed.
if (this.state.listingType == ListingType.Subscribed) { if (this.state.listingType == ListingType.Subscribed) {
if ( if (
this.state.subscribedCommunities UserService.Instance.myUserInfo.follows
.map(c => c.community.id) .map(c => c.community.id)
.includes(data.comment_view.community.id) .includes(data.comment_view.community.id)
) { ) {
@ -956,6 +938,9 @@ export class Home extends Component<any, HomeState> {
let data = wsJsonToRes<CommentResponse>(msg).data; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data.comment_view, this.state.comments); createCommentLikeRes(data.comment_view, this.state.comments);
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.BlockPerson) {
let data = wsJsonToRes<BlockPersonResponse>(msg).data;
updatePersonBlock(data);
} }
} }
} }

View file

@ -409,16 +409,16 @@ export class Modlog extends Component<any, ModlogState> {
get isAdminOrMod(): boolean { get isAdminOrMod(): boolean {
let isAdmin = let isAdmin =
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.isoData.site_res.admins this.isoData.site_res.admins
.map(a => a.person.id) .map(a => a.person.id)
.includes(UserService.Instance.localUserView.person.id); .includes(UserService.Instance.myUserInfo.local_user_view.person.id);
let isMod = let isMod =
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.state.communityMods && this.state.communityMods &&
this.state.communityMods this.state.communityMods
.map(m => m.moderator.id) .map(m => m.moderator.id)
.includes(UserService.Instance.localUserView.person.id); .includes(UserService.Instance.myUserInfo.local_user_view.person.id);
return isAdmin || isMod; return isAdmin || isMod;
} }

View file

@ -1,5 +1,6 @@
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
BlockPersonResponse,
CommentResponse, CommentResponse,
CommentView, CommentView,
GetPersonMentions, GetPersonMentions,
@ -31,6 +32,7 @@ import {
setIsoData, setIsoData,
setupTippy, setupTippy,
toast, toast,
updatePersonBlock,
wsClient, wsClient,
wsJsonToRes, wsJsonToRes,
wsSubscribe, wsSubscribe,
@ -103,7 +105,7 @@ export class Inbox extends Component<any, InboxState> {
this.handleSortChange = this.handleSortChange.bind(this); this.handleSortChange = this.handleSortChange.bind(this);
this.handlePageChange = this.handlePageChange.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"); toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`); this.context.router.history.push(`/login`);
} }
@ -130,9 +132,9 @@ export class Inbox extends Component<any, InboxState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `@${UserService.Instance.localUserView.person.name} ${i18n.t( return `@${
"inbox" UserService.Instance.myUserInfo.local_user_view.person.name
)} - ${this.state.site_view.site.name}`; } ${i18n.t("inbox")} - ${this.state.site_view.site.name}`;
} }
render() { render() {
@ -722,7 +724,7 @@ export class Inbox extends Component<any, InboxState> {
if ( if (
data.recipient_ids.includes( 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.state.replies.unshift(data.comment_view);
@ -730,7 +732,7 @@ export class Inbox extends Component<any, InboxState> {
this.setState(this.state); this.setState(this.state);
} else if ( } else if (
data.comment_view.creator.id == 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 // TODO this seems wrong, you should be using form_id
toast(i18n.t("reply_sent")); toast(i18n.t("reply_sent"));
@ -739,7 +741,7 @@ export class Inbox extends Component<any, InboxState> {
let data = wsJsonToRes<PrivateMessageResponse>(msg).data; let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
if ( if (
data.private_message_view.recipient.id == 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.messages.unshift(data.private_message_view);
this.state.combined.unshift( this.state.combined.unshift(
@ -756,6 +758,9 @@ export class Inbox extends Component<any, InboxState> {
let data = wsJsonToRes<CommentResponse>(msg).data; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data.comment_view, this.state.replies); createCommentLikeRes(data.comment_view, this.state.replies);
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.BlockPerson) {
let data = wsJsonToRes<BlockPersonResponse>(msg).data;
updatePersonBlock(data);
} }
} }
@ -769,10 +774,11 @@ export class Inbox extends Component<any, InboxState> {
this.state.mentions.filter(r => !r.person_mention.read).length + this.state.mentions.filter(r => !r.person_mention.read).length +
this.state.messages.filter( this.state.messages.filter(
r => r =>
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
!r.private_message.read && !r.private_message.read &&
// TODO also seems very strange and wrong // 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 ).length
); );
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,583 @@
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);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -51,7 +51,7 @@ export class CreatePost extends Component<any, CreatePostState> {
this.handlePostCreate = this.handlePostCreate.bind(this); this.handlePostCreate = this.handlePostCreate.bind(this);
this.state = this.emptyState; this.state = this.emptyState;
if (!UserService.Instance.localUserView && isBrowser()) { if (!UserService.Instance.myUserInfo && isBrowser()) {
toast(i18n.t("not_logged_in"), "danger"); toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`); this.context.router.history.push(`/login`);
} }

View file

@ -194,7 +194,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<label <label
htmlFor="file-upload" htmlFor="file-upload"
className={`${ className={`${
UserService.Instance.localUserView && "pointer" UserService.Instance.myUserInfo && "pointer"
} d-inline-block float-right text-muted font-weight-bold`} } d-inline-block float-right text-muted font-weight-bold`}
data-tippy-content={i18n.t("upload_image")} data-tippy-content={i18n.t("upload_image")}
> >
@ -206,7 +206,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
accept="image/*,video/*" accept="image/*,video/*"
name="file" name="file"
class="d-none" class="d-none"
disabled={!UserService.Instance.localUserView} disabled={!UserService.Instance.myUserInfo}
onChange={linkEvent(this, this.handleImageUpload)} onChange={linkEvent(this, this.handleImageUpload)}
/> />
</form> </form>
@ -601,7 +601,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
let data = wsJsonToRes<PostResponse>(msg).data; let data = wsJsonToRes<PostResponse>(msg).data;
if ( if (
data.post_view.creator.id == data.post_view.creator.id ==
UserService.Instance.localUserView.person.id UserService.Instance.myUserInfo.local_user_view.person.id
) { ) {
this.state.loading = false; this.state.loading = false;
this.props.onCreate(data.post_view); this.props.onCreate(data.post_view);
@ -610,7 +610,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
let data = wsJsonToRes<PostResponse>(msg).data; let data = wsJsonToRes<PostResponse>(msg).data;
if ( if (
data.post_view.creator.id == data.post_view.creator.id ==
UserService.Instance.localUserView.person.id UserService.Instance.myUserInfo.local_user_view.person.id
) { ) {
this.state.loading = false; this.state.loading = false;
this.props.onEdit(data.post_view); this.props.onEdit(data.post_view);

View file

@ -5,6 +5,7 @@ import {
AddModToCommunity, AddModToCommunity,
BanFromCommunity, BanFromCommunity,
BanPerson, BanPerson,
BlockPerson,
CommunityModeratorView, CommunityModeratorView,
CreatePostLike, CreatePostLike,
DeletePost, DeletePost,
@ -285,6 +286,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
post_view.creator.banned) && ( post_view.creator.banned) && (
<span className="mx-1 badge badge-danger">{i18n.t("banned")}</span> <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 && ( {this.props.showCommunity && (
<span> <span>
<span class="mx-1"> {i18n.t("to")} </span> <span class="mx-1"> {i18n.t("to")} </span>
@ -627,7 +631,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
postActions(mobile = false) { postActions(mobile = false) {
let post_view = this.props.post_view; let post_view = this.props.post_view;
return ( return (
UserService.Instance.localUserView && ( UserService.Instance.myUserInfo && (
<> <>
{this.showBody && ( {this.showBody && (
<> <>
@ -655,6 +659,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
> >
<Icon icon="copy" classes="icon-inline" /> <Icon icon="copy" classes="icon-inline" />
</Link> </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 && ( {this.myPost && this.showBody && (
@ -1113,9 +1127,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
private get myPost(): boolean { private get myPost(): boolean {
return ( return (
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.props.post_view.creator.id == this.props.post_view.creator.id ==
UserService.Instance.localUserView.person.id UserService.Instance.myUserInfo.local_user_view.person.id
); );
} }
@ -1146,7 +1160,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
.concat(this.props.moderators.map(m => m.moderator.id)); .concat(this.props.moderators.map(m => m.moderator.id));
return canMod( return canMod(
UserService.Instance.localUserView, UserService.Instance.myUserInfo,
adminsThenMods, adminsThenMods,
this.props.post_view.creator.id this.props.post_view.creator.id
); );
@ -1162,7 +1176,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
.concat(this.props.moderators.map(m => m.moderator.id)); .concat(this.props.moderators.map(m => m.moderator.id));
return canMod( return canMod(
UserService.Instance.localUserView, UserService.Instance.myUserInfo,
adminsThenMods, adminsThenMods,
this.props.post_view.creator.id, this.props.post_view.creator.id,
true true
@ -1176,7 +1190,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return ( return (
this.props.admins && this.props.admins &&
canMod( canMod(
UserService.Instance.localUserView, UserService.Instance.myUserInfo,
this.props.admins.map(a => a.person.id), this.props.admins.map(a => a.person.id),
this.props.post_view.creator.id this.props.post_view.creator.id
) )
@ -1186,10 +1200,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get amCommunityCreator(): boolean { get amCommunityCreator(): boolean {
return ( return (
this.props.moderators && this.props.moderators &&
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.props.post_view.creator.id != this.props.post_view.creator.id !=
UserService.Instance.localUserView.person.id && UserService.Instance.myUserInfo.local_user_view.person.id &&
UserService.Instance.localUserView.person.id == UserService.Instance.myUserInfo.local_user_view.person.id ==
this.props.moderators[0].moderator.id this.props.moderators[0].moderator.id
); );
} }
@ -1197,17 +1211,17 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get amSiteCreator(): boolean { get amSiteCreator(): boolean {
return ( return (
this.props.admins && this.props.admins &&
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
this.props.post_view.creator.id != this.props.post_view.creator.id !=
UserService.Instance.localUserView.person.id && UserService.Instance.myUserInfo.local_user_view.person.id &&
UserService.Instance.localUserView.person.id == UserService.Instance.myUserInfo.local_user_view.person.id ==
this.props.admins[0].person.id this.props.admins[0].person.id
); );
} }
handlePostLike(i: PostListing, event: any) { handlePostLike(i: PostListing, event: any) {
event.preventDefault(); event.preventDefault();
if (!UserService.Instance.localUserView) { if (!UserService.Instance.myUserInfo) {
this.context.router.history.push(`/login`); this.context.router.history.push(`/login`);
} }
@ -1240,7 +1254,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
handlePostDisLike(i: PostListing, event: any) { handlePostDisLike(i: PostListing, event: any) {
event.preventDefault(); event.preventDefault();
if (!UserService.Instance.localUserView) { if (!UserService.Instance.myUserInfo) {
this.context.router.history.push(`/login`); this.context.router.history.push(`/login`);
} }
@ -1287,6 +1301,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.setState(this.state); 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) { handleDeleteClick(i: PostListing) {
let deleteForm: DeletePost = { let deleteForm: DeletePost = {
post_id: i.props.post_view.post.id, post_id: i.props.post_view.post.id,

View file

@ -5,6 +5,7 @@ import {
AddModToCommunityResponse, AddModToCommunityResponse,
BanFromCommunityResponse, BanFromCommunityResponse,
BanPersonResponse, BanPersonResponse,
BlockPersonResponse,
CommentResponse, CommentResponse,
CommunityResponse, CommunityResponse,
GetCommunityResponse, GetCommunityResponse,
@ -50,6 +51,7 @@ import {
setOptionalAuth, setOptionalAuth,
setupTippy, setupTippy,
toast, toast,
updatePersonBlock,
wsClient, wsClient,
wsJsonToRes, wsJsonToRes,
wsSubscribe, wsSubscribe,
@ -237,8 +239,9 @@ export class Post extends Component<any, PostState> {
: this.state.postRes.post_view.creator.id; : this.state.postRes.post_view.creator.id;
if ( if (
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
UserService.Instance.localUserView.person.id == parent_person_id UserService.Instance.myUserInfo.local_user_view.person.id ==
parent_person_id
) { ) {
let form: MarkCommentAsRead = { let form: MarkCommentAsRead = {
comment_id: found.comment.id, comment_id: found.comment.id,
@ -617,6 +620,9 @@ export class Post extends Component<any, PostState> {
this.state.postRes.post_view.community = data.community_view.community; this.state.postRes.post_view.community = data.community_view.community;
this.state.postRes.moderators = data.moderators; this.state.postRes.moderators = data.moderators;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.BlockPerson) {
let data = wsJsonToRes<BlockPersonResponse>(msg).data;
updatePersonBlock(data);
} }
} }
} }

View file

@ -54,7 +54,7 @@ export class CreatePrivateMessage extends Component<
this.parseMessage = this.parseMessage.bind(this); this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage); this.subscription = wsSubscribe(this.parseMessage);
if (!UserService.Instance.localUserView) { if (!UserService.Instance.myUserInfo) {
toast(i18n.t("not_logged_in"), "danger"); toast(i18n.t("not_logged_in"), "danger");
this.context.router.history.push(`/login`); this.context.router.history.push(`/login`);
} }

View file

@ -47,8 +47,8 @@ export class PrivateMessage extends Component<
get mine(): boolean { get mine(): boolean {
return ( return (
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
UserService.Instance.localUserView.person.id == UserService.Instance.myUserInfo.local_user_view.person.id ==
this.props.private_message_view.creator.id this.props.private_message_view.creator.id
); );
} }
@ -272,8 +272,9 @@ export class PrivateMessage extends Component<
handlePrivateMessageCreate(message: PrivateMessageView) { handlePrivateMessageCreate(message: PrivateMessageView) {
if ( if (
UserService.Instance.localUserView && UserService.Instance.myUserInfo &&
message.creator.id == UserService.Instance.localUserView.person.id message.creator.id ==
UserService.Instance.myUserInfo.local_user_view.person.id
) { ) {
this.state.showReply = false; this.state.showReply = false;
this.setState(this.state); this.setState(this.state);

View file

@ -10,7 +10,8 @@ import { PasswordChange } from "./components/home/password_change";
import { Setup } from "./components/home/setup"; import { Setup } from "./components/home/setup";
import { Modlog } from "./components/modlog"; import { Modlog } from "./components/modlog";
import { Inbox } from "./components/person/inbox"; 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 { CreatePost } from "./components/post/create-post";
import { Post } from "./components/post/post"; import { Post } from "./components/post/post";
import { CreatePrivateMessage } from "./components/private_message/create-private-message"; import { CreatePrivateMessage } from "./components/private_message/create-private-message";
@ -83,19 +84,23 @@ export const routes: IRoutePropsWithFetch[] = [
}, },
{ {
path: `/u/:username/view/:view/sort/:sort/page/:page`, path: `/u/:username/view/:view/sort/:sort/page/:page`,
component: Person, component: Profile,
fetchInitialData: req => Person.fetchInitialData(req), fetchInitialData: req => Profile.fetchInitialData(req),
}, },
{ {
path: `/u/:username`, path: `/u/:username`,
component: Person, component: Profile,
fetchInitialData: req => Person.fetchInitialData(req), fetchInitialData: req => Profile.fetchInitialData(req),
}, },
{ {
path: `/inbox`, path: `/inbox`,
component: Inbox, component: Inbox,
fetchInitialData: req => Inbox.fetchInitialData(req), fetchInitialData: req => Inbox.fetchInitialData(req),
}, },
{
path: `/settings`,
component: Settings,
},
{ {
path: `/modlog/community/:community_id`, path: `/modlog/community/:community_id`,
component: Modlog, component: Modlog,

View file

@ -1,7 +1,7 @@
// import Cookies from 'js-cookie'; // import Cookies from 'js-cookie';
import IsomorphicCookie from "isomorphic-cookie"; import IsomorphicCookie from "isomorphic-cookie";
import jwt_decode from "jwt-decode"; 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"; import { BehaviorSubject, Subject } from "rxjs";
interface Claims { interface Claims {
@ -12,7 +12,7 @@ interface Claims {
export class UserService { export class UserService {
private static _instance: UserService; private static _instance: UserService;
public localUserView: LocalUserSettingsView; public myUserInfo: MyUserInfo;
public claims: Claims; public claims: Claims;
public jwtSub: Subject<string> = new Subject<string>(); public jwtSub: Subject<string> = new Subject<string>();
public unreadCountSub: BehaviorSubject<number> = new BehaviorSubject<number>( public unreadCountSub: BehaviorSubject<number> = new BehaviorSubject<number>(
@ -39,7 +39,7 @@ export class UserService {
public logout() { public logout() {
IsomorphicCookie.remove("jwt", { secure: false }); IsomorphicCookie.remove("jwt", { secure: false });
this.claims = undefined; this.claims = undefined;
this.localUserView = undefined; this.myUserInfo = undefined;
// setTheme(); // setTheme();
this.jwtSub.next(""); this.jwtSub.next("");
console.log("Logged out."); console.log("Logged out.");

View file

@ -1,13 +1,17 @@
import emojiShortName from "emoji-short-name"; import emojiShortName from "emoji-short-name";
import { import {
BlockCommunityResponse,
BlockPersonResponse,
CommentView, CommentView,
CommunityBlockView,
CommunityView, CommunityView,
GetSiteMetadata, GetSiteMetadata,
GetSiteResponse, GetSiteResponse,
LemmyHttp, LemmyHttp,
LemmyWebsocket, LemmyWebsocket,
ListingType, ListingType,
LocalUserSettingsView, MyUserInfo,
PersonBlockView,
PersonViewSafe, PersonViewSafe,
PostView, PostView,
PrivateMessageView, PrivateMessageView,
@ -249,14 +253,16 @@ export function getUnixTime(text: string): number {
} }
export function canMod( export function canMod(
localUserView: LocalUserSettingsView, myUserInfo: MyUserInfo,
modIds: number[], modIds: number[],
creator_id: number, creator_id: number,
onSelf = false onSelf = false
): boolean { ): boolean {
// You can do moderator actions only on the mods added after you. // You can do moderator actions only on the mods added after you.
if (localUserView) { if (myUserInfo) {
let yourIndex = modIds.findIndex(id => id == localUserView.person.id); let yourIndex = modIds.findIndex(
id => id == myUserInfo.local_user_view.person.id
);
if (yourIndex == -1) { if (yourIndex == -1) {
return false; return false;
} else { } else {
@ -380,11 +386,11 @@ export function debounce(func: any, wait = 1000, immediate = false) {
// TODO // TODO
export function getLanguage(override?: string): string { export function getLanguage(override?: string): string {
let localUserView = UserService.Instance.localUserView; let myUserInfo = UserService.Instance.myUserInfo;
let lang = let lang =
override || override ||
(localUserView?.local_user.lang (myUserInfo?.local_user_view.local_user.lang
? localUserView.local_user.lang ? myUserInfo.local_user_view.local_user.lang
: "browser"); : "browser");
if (lang == "browser" && isBrowser()) { if (lang == "browser" && isBrowser()) {
@ -537,15 +543,15 @@ export function objectFlip(obj: any) {
export function showAvatars(): boolean { export function showAvatars(): boolean {
return ( return (
UserService.Instance.localUserView?.local_user.show_avatars || UserService.Instance.myUserInfo?.local_user_view.local_user.show_avatars ||
!UserService.Instance.localUserView !UserService.Instance.myUserInfo
); );
} }
export function showScores(): boolean { export function showScores(): boolean {
return ( return (
UserService.Instance.localUserView?.local_user.show_scores || UserService.Instance.myUserInfo?.local_user_view.local_user.show_scores ||
!UserService.Instance.localUserView !UserService.Instance.myUserInfo
); );
} }
@ -850,9 +856,10 @@ function communitySearch(
export function getListingTypeFromProps(props: any): ListingType { export function getListingTypeFromProps(props: any): ListingType {
return props.match.params.listing_type return props.match.params.listing_type
? routeListingTypeToEnum(props.match.params.listing_type) ? routeListingTypeToEnum(props.match.params.listing_type)
: UserService.Instance.localUserView : UserService.Instance.myUserInfo
? Object.values(ListingType)[ ? Object.values(ListingType)[
UserService.Instance.localUserView.local_user.default_listing_type UserService.Instance.myUserInfo.local_user_view.local_user
.default_listing_type
] ]
: ListingType.Local; : ListingType.Local;
} }
@ -873,9 +880,10 @@ export function getDataTypeFromProps(props: any): DataType {
export function getSortTypeFromProps(props: any): SortType { export function getSortTypeFromProps(props: any): SortType {
return props.match.params.sort return props.match.params.sort
? routeSortTypeToEnum(props.match.params.sort) ? routeSortTypeToEnum(props.match.params.sort)
: UserService.Instance.localUserView : UserService.Instance.myUserInfo
? Object.values(SortType)[ ? Object.values(SortType)[
UserService.Instance.localUserView.local_user.default_sort_type UserService.Instance.myUserInfo.local_user_view.local_user
.default_sort_type
] ]
: SortType.Active; : SortType.Active;
} }
@ -922,6 +930,44 @@ export function saveCommentRes(data: CommentView, comments: CommentView[]) {
} }
} }
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( export function createCommentLikeRes(
data: CommentView, data: CommentView,
comments: CommentView[] comments: CommentView[]
@ -1065,9 +1111,13 @@ export function buildCommentsTree(
let tree: CommentNodeI[] = []; let tree: CommentNodeI[] = [];
for (let comment_view of comments) { for (let comment_view of comments) {
let child = map.get(comment_view.comment.id); let child = map.get(comment_view.comment.id);
if (comment_view.comment.parent_id) { let parent_id = comment_view.comment.parent_id;
let parent_ = map.get(comment_view.comment.parent_id); if (parent_id) {
parent_.children.push(child); let parent = map.get(parent_id);
// Necessary because blocked comment might not exist
if (parent) {
parent.children.push(child);
}
} else { } else {
tree.push(child); tree.push(child);
} }
@ -1315,14 +1365,14 @@ export const choicesConfig = {
searchResultLimit: fetchLimit, searchResultLimit: fetchLimit,
classNames: { classNames: {
containerOuter: "choices", containerOuter: "choices",
containerInner: "choices__inner bg-light border-0", containerInner: "choices__inner bg-secondary border-0",
input: "form-control", input: "form-control",
inputCloned: "choices__input--cloned", inputCloned: "choices__input--cloned",
list: "choices__list", list: "choices__list",
listItems: "choices__list--multiple", listItems: "choices__list--multiple",
listSingle: "choices__list--single", listSingle: "choices__list--single",
listDropdown: "choices__list--dropdown", listDropdown: "choices__list--dropdown",
item: "choices__item bg-light", item: "choices__item bg-secondary",
itemSelectable: "choices__item--selectable", itemSelectable: "choices__item--selectable",
itemDisabled: "choices__item--disabled", itemDisabled: "choices__item--disabled",
itemChoice: "choices__item--choice", itemChoice: "choices__item--choice",
@ -1356,6 +1406,6 @@ export function personSelectName(pvs: PersonViewSafe): string {
} }
export function initializeSite(site: GetSiteResponse) { export function initializeSite(site: GetSiteResponse) {
UserService.Instance.localUserView = site.my_user; UserService.Instance.myUserInfo = site.my_user;
i18n.changeLanguage(getLanguage()); i18n.changeLanguage(getLanguage());
} }

View file

@ -2863,9 +2863,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.811: electron-to-chromium@^1.3.811:
version "1.3.812" version "1.3.813"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.812.tgz#4c4fb407e0e1335056097f172e9f2c0a09efe77d" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.813.tgz#751a007d71c00faed8b5e9edaf3634c14b9c5a1f"
integrity sha512-7KiUHsKAWtSrjVoTSzxQ0nPLr/a+qoxNZwkwd9LkylTOgOXSVXkQbpIVT0WAUQcI5gXq3SwOTCrK+WfINHOXQg== integrity sha512-YcSRImHt6JZZ2sSuQ4Bzajtk98igQ0iKkksqlzZLzbh4p0OIyJRSvUbsgqfcR8txdfsoYCc4ym306t4p2kP/aw==
emoji-regex@^7.0.1: emoji-regex@^7.0.1:
version "7.0.3" version "7.0.3"
@ -4996,10 +4996,10 @@ lcid@^1.0.0:
dependencies: dependencies:
invert-kv "^1.0.0" invert-kv "^1.0.0"
lemmy-js-client@0.11.4-rc.12: lemmy-js-client@0.11.4-rc.14:
version "0.11.4-rc.12" version "0.11.4-rc.14"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.11.4-rc.12.tgz#a238da35dbde18c9fc8f6f14cb6849d541d0051b" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.11.4-rc.14.tgz#dcac5b8dc78c3b04e6b3630ff9351a94aa73e109"
integrity sha512-PzIFA/Q2j8i0ZXOWo0u/rR/RTt97v+G8a8jObHplq8UyyI3EzNIWZ5AS9514H5AoCIAMcDbwP4c/CQPiYf8yhA== integrity sha512-R8M+myyriNQljQlTweVqtUKGBpgmaM7RI4ebYb7N7sYr5Bk5Ip6v2qTNvKAV6BlsDOCTWANOonfeoz/cIerLEg==
levn@^0.4.1: levn@^0.4.1:
version "0.4.1" version "0.4.1"