First pass at v2_api

This commit is contained in:
Dessalines 2020-12-23 20:58:27 -05:00
parent 6ffe0c530d
commit ea317af269
41 changed files with 2217 additions and 2466 deletions

View file

@ -67,7 +67,7 @@
"eslint": "^7.15.0", "eslint": "^7.15.0",
"eslint-plugin-jane": "^9.0.4", "eslint-plugin-jane": "^9.0.4",
"husky": "^4.3.5", "husky": "^4.3.5",
"lemmy-js-client": "^1.0.16", "lemmy-js-client": "1.0.17-beta5",
"lint-staged": "^10.5.3", "lint-staged": "^10.5.3",
"mini-css-extract-plugin": "^1.3.2", "mini-css-extract-plugin": "^1.3.2",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",

View file

@ -3,12 +3,12 @@ import { BrowserRouter } from 'inferno-router';
import { initializeSite } from '../shared/initialize'; import { initializeSite } from '../shared/initialize';
import { App } from '../shared/components/app'; import { App } from '../shared/components/app';
const site = window.isoData.site; const site = window.isoData.site_res;
initializeSite(site); initializeSite(site);
const wrapper = ( const wrapper = (
<BrowserRouter> <BrowserRouter>
<App site={window.isoData.site} /> <App siteRes={window.isoData.site_res} />
</BrowserRouter> </BrowserRouter>
); );

View file

@ -8,8 +8,7 @@ import { App } from '../shared/components/app';
import { InitialFetchRequest, IsoData } from '../shared/interfaces'; import { InitialFetchRequest, IsoData } from '../shared/interfaces';
import { routes } from '../shared/routes'; import { routes } from '../shared/routes';
import IsomorphicCookie from 'isomorphic-cookie'; import IsomorphicCookie from 'isomorphic-cookie';
import { setAuth } from '../shared/utils'; import { GetSite, LemmyHttp } from 'lemmy-js-client';
import { GetSiteForm, LemmyHttp } from 'lemmy-js-client';
import process from 'process'; import process from 'process';
import { Helmet } from 'inferno-helmet'; import { Helmet } from 'inferno-helmet';
import { initializeSite } from '../shared/initialize'; import { initializeSite } from '../shared/initialize';
@ -30,8 +29,7 @@ server.get('/*', async (req, res) => {
const context = {} as any; const context = {} as any;
let auth: string = IsomorphicCookie.load('jwt', req); let auth: string = IsomorphicCookie.load('jwt', req);
let getSiteForm: GetSiteForm = {}; let getSiteForm: GetSite = { auth };
setAuth(getSiteForm, auth);
let promises: Promise<any>[] = []; let promises: Promise<any>[] = [];
@ -70,14 +68,14 @@ server.get('/*', async (req, res) => {
let isoData: IsoData = { let isoData: IsoData = {
path: req.path, path: req.path,
site, site_res: site,
routeData, routeData,
lang, lang,
}; };
const wrapper = ( const wrapper = (
<StaticRouter location={req.url} context={isoData}> <StaticRouter location={req.url} context={isoData}>
<App site={isoData.site} /> <App siteRes={isoData.site_res} />
</StaticRouter> </StaticRouter>
); );
if (context.url) { if (context.url) {

View file

@ -4,12 +4,11 @@ import {
UserOperation, UserOperation,
SiteResponse, SiteResponse,
GetSiteResponse, GetSiteResponse,
SiteConfigForm, SaveSiteConfig,
GetSiteConfigResponse, GetSiteConfigResponse,
WebSocketJsonResponse,
GetSiteConfig, GetSiteConfig,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { import {
wsJsonToRes, wsJsonToRes,
capitalizeFirstLetter, capitalizeFirstLetter,
@ -18,7 +17,7 @@ import {
setIsoData, setIsoData,
wsSubscribe, wsSubscribe,
isBrowser, isBrowser,
setAuth, wsUserOp,
} from '../utils'; } from '../utils';
import autosize from 'autosize'; import autosize from 'autosize';
import { SiteForm } from './site-form'; import { SiteForm } from './site-form';
@ -30,7 +29,7 @@ import { InitialFetchRequest } from 'shared/interfaces';
interface AdminSettingsState { interface AdminSettingsState {
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
siteConfigRes: GetSiteConfigResponse; siteConfigRes: GetSiteConfigResponse;
siteConfigForm: SiteConfigForm; siteConfigForm: SaveSiteConfig;
loading: boolean; loading: boolean;
siteConfigLoading: boolean; siteConfigLoading: boolean;
} }
@ -40,10 +39,10 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private subscription: Subscription; private subscription: Subscription;
private emptyState: AdminSettingsState = { private emptyState: AdminSettingsState = {
siteRes: this.isoData.site, siteRes: this.isoData.site_res,
siteConfigForm: { siteConfigForm: {
config_hjson: null, config_hjson: null,
auth: null, auth: UserService.Instance.authField(),
}, },
siteConfigRes: { siteConfigRes: {
config_hjson: null, config_hjson: null,
@ -67,13 +66,14 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
this.state.siteConfigLoading = false; this.state.siteConfigLoading = false;
this.state.loading = false; this.state.loading = false;
} else { } else {
WebSocketService.Instance.getSiteConfig(); WebSocketService.Instance.client.getSiteConfig({
auth: UserService.Instance.authField(),
});
} }
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let form: GetSiteConfig = {}; let form: GetSiteConfig = { auth: req.auth };
setAuth(form, req.auth);
return [req.client.getSiteConfig(form)]; return [req.client.getSiteConfig(form)];
} }
@ -91,7 +91,9 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t('admin_settings')} - ${this.state.siteRes.site.name}`; return `${i18n.t('admin_settings')} - ${
this.state.siteRes.site_view.site.name
}`;
} }
render() { render() {
@ -110,8 +112,8 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
) : ( ) : (
<div class="row"> <div class="row">
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
{this.state.siteRes.site.id && ( {this.state.siteRes.site_view.site.id && (
<SiteForm site={this.state.siteRes.site} /> <SiteForm site={this.state.siteRes.site_view.site} />
)} )}
{this.admins()} {this.admins()}
{this.bannedUsers()} {this.bannedUsers()}
@ -130,16 +132,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
<ul class="list-unstyled"> <ul class="list-unstyled">
{this.state.siteRes.admins.map(admin => ( {this.state.siteRes.admins.map(admin => (
<li class="list-inline-item"> <li class="list-inline-item">
<UserListing <UserListing user={admin.user} />
user={{
name: admin.name,
preferred_username: admin.preferred_username,
avatar: admin.avatar,
id: admin.id,
local: admin.local,
actor_id: admin.actor_id,
}}
/>
</li> </li>
))} ))}
</ul> </ul>
@ -154,16 +147,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
<ul class="list-unstyled"> <ul class="list-unstyled">
{this.state.siteRes.banned.map(banned => ( {this.state.siteRes.banned.map(banned => (
<li class="list-inline-item"> <li class="list-inline-item">
<UserListing <UserListing user={banned.user} />
user={{
name: banned.name,
preferred_username: banned.preferred_username,
avatar: banned.avatar,
id: banned.id,
local: banned.local,
actor_id: banned.actor_id,
}}
/>
</li> </li>
))} ))}
</ul> </ul>
@ -214,7 +198,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
handleSiteConfigSubmit(i: AdminSettings, event: any) { handleSiteConfigSubmit(i: AdminSettings, event: any) {
event.preventDefault(); event.preventDefault();
i.state.siteConfigLoading = true; i.state.siteConfigLoading = true;
WebSocketService.Instance.saveSiteConfig(i.state.siteConfigForm); WebSocketService.Instance.client.saveSiteConfig(i.state.siteConfigForm);
i.setState(i.state); i.setState(i.state);
} }
@ -223,9 +207,8 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
i.setState(i.state); i.setState(i.state);
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.context.router.history.push('/'); this.context.router.history.push('/');
@ -233,21 +216,21 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
this.setState(this.state); this.setState(this.state);
return; return;
} else if (msg.reconnect) { } else if (msg.reconnect) {
} else if (res.op == UserOperation.EditSite) { } else if (op == UserOperation.EditSite) {
let data = res.data as SiteResponse; let data = wsJsonToRes<SiteResponse>(msg).data;
this.state.siteRes.site = data.site; this.state.siteRes.site_view = data.site_view;
this.setState(this.state); this.setState(this.state);
toast(i18n.t('site_saved')); toast(i18n.t('site_saved'));
} else if (res.op == UserOperation.GetSiteConfig) { } else if (op == UserOperation.GetSiteConfig) {
let data = res.data as GetSiteConfigResponse; let data = wsJsonToRes<GetSiteConfigResponse>(msg).data;
this.state.siteConfigRes = data; this.state.siteConfigRes = data;
this.state.loading = false; this.state.loading = false;
this.state.siteConfigForm.config_hjson = this.state.siteConfigRes.config_hjson; this.state.siteConfigForm.config_hjson = this.state.siteConfigRes.config_hjson;
this.setState(this.state); this.setState(this.state);
var textarea: any = document.getElementById(this.siteConfigTextAreaId); var textarea: any = document.getElementById(this.siteConfigTextAreaId);
autosize(textarea); autosize(textarea);
} else if (res.op == UserOperation.SaveSiteConfig) { } else if (op == UserOperation.SaveSiteConfig) {
let data = res.data as GetSiteConfigResponse; let data = wsJsonToRes<GetSiteConfigResponse>(msg).data;
this.state.siteConfigRes = data; this.state.siteConfigRes = data;
this.state.siteConfigForm.config_hjson = this.state.siteConfigRes.config_hjson; this.state.siteConfigForm.config_hjson = this.state.siteConfigRes.config_hjson;
this.state.siteConfigLoading = false; this.state.siteConfigLoading = false;

View file

@ -13,7 +13,7 @@ import { GetSiteResponse } from 'lemmy-js-client';
import './styles.scss'; import './styles.scss';
export interface AppProps { export interface AppProps {
site: GetSiteResponse; siteRes: GetSiteResponse;
} }
export class App extends Component<AppProps, any> { export class App extends Component<AppProps, any> {
@ -21,24 +21,25 @@ export class App extends Component<AppProps, any> {
super(props, context); super(props, context);
} }
render() { render() {
let siteRes = this.props.siteRes;
return ( return (
<> <>
<Provider i18next={i18n}> <Provider i18next={i18n}>
<div> <div>
<Theme user={this.props.site.my_user} /> <Theme user={siteRes.my_user} />
{this.props.site && {siteRes &&
this.props.site.site && siteRes.site_view.site &&
this.props.site.site.icon && ( this.props.siteRes.site_view.site.icon && (
<Helmet> <Helmet>
<link <link
id="favicon" id="favicon"
rel="icon" rel="icon"
type="image/x-icon" type="image/x-icon"
href={this.props.site.site.icon} href={this.props.siteRes.site_view.site.icon}
/> />
</Helmet> </Helmet>
)} )}
<Navbar site={this.props.site} /> <Navbar site_res={this.props.siteRes} />
<div class="mt-4 p-0 fl-1"> <div class="mt-4 p-0 fl-1">
<Switch> <Switch>
{routes.map(({ path, exact, component: C, ...rest }) => ( {routes.map(({ path, exact, component: C, ...rest }) => (
@ -53,7 +54,7 @@ export class App extends Component<AppProps, any> {
</Switch> </Switch>
<Symbols /> <Symbols />
</div> </div>
<Footer site={this.props.site} /> <Footer site={this.props.siteRes} />
</div> </div>
</Provider> </Provider>
</> </>

View file

@ -2,13 +2,18 @@ import { Component } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
CommentNode as CommentNodeI, CreateComment,
CommentForm as CommentFormI, EditComment,
WebSocketJsonResponse,
UserOperation, UserOperation,
CommentResponse, CommentResponse,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { capitalizeFirstLetter, wsJsonToRes, wsSubscribe } from '../utils'; import { CommentNode as CommentNodeI } from '../interfaces';
import {
capitalizeFirstLetter,
wsJsonToRes,
wsSubscribe,
wsUserOp,
} from '../utils';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
@ -24,30 +29,21 @@ interface CommentFormProps {
} }
interface CommentFormState { interface CommentFormState {
commentForm: CommentFormI;
buttonTitle: string; buttonTitle: string;
finished: boolean; finished: boolean;
formId: string;
} }
export class CommentForm extends Component<CommentFormProps, CommentFormState> { export class CommentForm extends Component<CommentFormProps, CommentFormState> {
private subscription: Subscription; private subscription: Subscription;
private emptyState: CommentFormState = { private emptyState: CommentFormState = {
commentForm: {
auth: null,
content: null,
post_id: this.props.node
? this.props.node.comment.post_id
: this.props.postId,
creator_id: UserService.Instance.user
? UserService.Instance.user.id
: null,
},
buttonTitle: !this.props.node buttonTitle: !this.props.node
? capitalizeFirstLetter(i18n.t('post')) ? capitalizeFirstLetter(i18n.t('post'))
: this.props.edit : this.props.edit
? capitalizeFirstLetter(i18n.t('save')) ? capitalizeFirstLetter(i18n.t('save'))
: capitalizeFirstLetter(i18n.t('reply')), : capitalizeFirstLetter(i18n.t('reply')),
finished: false, finished: false,
formId: null,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -58,18 +54,6 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
this.state = this.emptyState; this.state = this.emptyState;
if (this.props.node) {
if (this.props.edit) {
this.state.commentForm.edit_id = this.props.node.comment.id;
this.state.commentForm.parent_id = this.props.node.comment.parent_id;
this.state.commentForm.content = this.props.node.comment.content;
this.state.commentForm.creator_id = this.props.node.comment.creator_id;
} else {
// A reply gets a new parent id
this.state.commentForm.parent_id = this.props.node.comment.id;
}
}
this.parseMessage = this.parseMessage.bind(this); this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage); this.subscription = wsSubscribe(this.parseMessage);
} }
@ -83,7 +67,11 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
<div class="mb-3"> <div class="mb-3">
{UserService.Instance.user ? ( {UserService.Instance.user ? (
<MarkdownTextArea <MarkdownTextArea
initialContent={this.state.commentForm.content} initialContent={
this.props.node
? this.props.node.comment_view.comment.content
: null
}
buttonTitle={this.state.buttonTitle} buttonTitle={this.state.buttonTitle}
finished={this.state.finished} finished={this.state.finished}
replyType={!!this.props.node} replyType={!!this.props.node}
@ -110,12 +98,28 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
} }
handleCommentSubmit(msg: { val: string; formId: string }) { handleCommentSubmit(msg: { val: string; formId: string }) {
this.state.commentForm.content = msg.val; let content = msg.val;
this.state.commentForm.form_id = msg.formId; this.state.formId = msg.formId;
let node = this.props.node;
if (this.props.edit) { if (this.props.edit) {
WebSocketService.Instance.editComment(this.state.commentForm); let form: EditComment = {
content,
form_id: this.state.formId,
edit_id: node.comment_view.comment.id,
auth: UserService.Instance.authField(),
};
WebSocketService.Instance.client.editComment(form);
} else { } else {
WebSocketService.Instance.createComment(this.state.commentForm); let form: CreateComment = {
content,
form_id: this.state.formId,
post_id: node ? node.comment_view.post.id : this.props.postId,
parent_id: node ? node.comment_view.comment.parent_id : null,
auth: UserService.Instance.authField(),
};
WebSocketService.Instance.client.createComment(form);
} }
this.setState(this.state); this.setState(this.state);
} }
@ -124,22 +128,22 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
this.props.onReplyCancel(); this.props.onReplyCancel();
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
// Only do the showing and hiding if logged in // Only do the showing and hiding if logged in
if (UserService.Instance.user) { if (UserService.Instance.user) {
if ( if (
res.op == UserOperation.CreateComment || op == UserOperation.CreateComment ||
res.op == UserOperation.EditComment op == UserOperation.EditComment
) { ) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
// This only finishes this form, if the randomly generated form_id matches the one received // This only finishes this form, if the randomly generated form_id matches the one received
if (this.state.commentForm.form_id == data.form_id) { if (this.state.formId == data.form_id) {
this.setState({ finished: true }); this.setState({ finished: true });
// Necessary because it broke tribute for some reaso // Necessary because it broke tribute for some reason
this.setState({ finished: false }); this.setState({ finished: false });
} }
} }

View file

@ -1,24 +1,29 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { import {
CommentNode as CommentNodeI, CreateCommentLike,
CommentLikeForm, DeleteComment,
DeleteCommentForm, RemoveComment,
RemoveCommentForm, MarkCommentAsRead,
MarkCommentAsReadForm, MarkUserMentionAsRead,
MarkUserMentionAsReadForm, SaveComment,
SaveCommentForm, BanFromCommunity,
BanFromCommunityForm, BanUser,
BanUserForm, CommunityModeratorView,
CommunityUser, UserViewSafe,
UserView, AddModToCommunity,
AddModToCommunityForm, AddAdmin,
AddAdminForm, TransferCommunity,
TransferCommunityForm, TransferSite,
TransferSiteForm,
SortType, SortType,
CommentView,
UserMentionView,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { CommentSortType, BanType } from '../interfaces'; import {
CommentSortType,
CommentNode as CommentNodeI,
BanType,
} from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { import {
mdToHtml, mdToHtml,
@ -70,8 +75,8 @@ interface CommentNodeProps {
locked?: boolean; locked?: boolean;
markable?: boolean; markable?: boolean;
showContext?: boolean; showContext?: boolean;
moderators: CommunityUser[]; moderators: CommunityModeratorView[];
admins: UserView[]; admins: UserViewSafe[];
// TODO is this necessary, can't I get it from the node itself? // TODO is this necessary, can't I get it from the node itself?
postCreatorId?: number; postCreatorId?: number;
showCommunity?: boolean; showCommunity?: boolean;
@ -98,12 +103,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
showConfirmTransferCommunity: false, showConfirmTransferCommunity: false,
showConfirmAppointAsMod: false, showConfirmAppointAsMod: false,
showConfirmAppointAsAdmin: false, showConfirmAppointAsAdmin: false,
my_vote: this.props.node.comment.my_vote, my_vote: this.props.node.comment_view.my_vote,
score: this.props.node.comment.score, score: this.props.node.comment_view.counts.score,
upvotes: this.props.node.comment.upvotes, upvotes: this.props.node.comment_view.counts.upvotes,
downvotes: this.props.node.comment.downvotes, downvotes: this.props.node.comment_view.counts.downvotes,
borderColor: this.props.node.comment.depth borderColor: this.props.node.depth
? colorList[this.props.node.comment.depth % colorList.length] ? colorList[this.props.node.depth % colorList.length]
: colorList[0], : colorList[0],
readLoading: false, readLoading: false,
saveLoading: false, saveLoading: false,
@ -118,11 +123,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleCommentDownvote = this.handleCommentDownvote.bind(this); this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
} }
// TODO see if there's a better way to do this, and all willReceiveProps
componentWillReceiveProps(nextProps: CommentNodeProps) { componentWillReceiveProps(nextProps: CommentNodeProps) {
this.state.my_vote = nextProps.node.comment.my_vote; let cv = nextProps.node.comment_view;
this.state.upvotes = nextProps.node.comment.upvotes; this.state.my_vote = cv.my_vote;
this.state.downvotes = nextProps.node.comment.downvotes; this.state.upvotes = cv.counts.upvotes;
this.state.score = nextProps.node.comment.score; this.state.downvotes = cv.counts.downvotes;
this.state.score = cv.counts.score;
this.state.readLoading = false; this.state.readLoading = false;
this.state.saveLoading = false; this.state.saveLoading = false;
this.setState(this.state); this.setState(this.state);
@ -130,43 +137,30 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
render() { render() {
let node = this.props.node; let node = this.props.node;
let cv = this.props.node.comment_view;
return ( return (
<div <div
className={`comment ${ className={`comment ${
node.comment.parent_id && !this.props.noIndent ? 'ml-1' : '' cv.comment.parent_id && !this.props.noIndent ? 'ml-1' : ''
}`} }`}
> >
<div <div
id={`comment-${node.comment.id}`} id={`comment-${cv.comment.id}`}
className={`details comment-node py-2 ${ className={`details comment-node py-2 ${
!this.props.noBorder ? 'border-top border-light' : '' !this.props.noBorder ? 'border-top border-light' : ''
} ${this.isCommentNew ? 'mark' : ''}`} } ${this.isCommentNew ? 'mark' : ''}`}
style={ style={
!this.props.noIndent && !this.props.noIndent &&
this.props.node.comment.parent_id && cv.comment.parent_id &&
`border-left: 2px ${this.state.borderColor} solid !important` `border-left: 2px ${this.state.borderColor} solid !important`
} }
> >
<div <div
class={`${ class={`${!this.props.noIndent && cv.comment.parent_id && 'ml-2'}`}
!this.props.noIndent &&
this.props.node.comment.parent_id &&
'ml-2'
}`}
> >
<div class="d-flex flex-wrap align-items-center text-muted small"> <div class="d-flex flex-wrap align-items-center text-muted small">
<span class="mr-2"> <span class="mr-2">
<UserListing <UserListing user={cv.creator} />
user={{
name: node.comment.creator_name,
preferred_username: node.comment.creator_preferred_username,
avatar: node.comment.creator_avatar,
id: node.comment.creator_id,
local: node.comment.creator_local,
actor_id: node.comment.creator_actor_id,
published: node.comment.creator_published,
}}
/>
</span> </span>
{this.isMod && ( {this.isMod && (
@ -184,7 +178,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{i18n.t('creator')} {i18n.t('creator')}
</div> </div>
)} )}
{(node.comment.banned_from_community || node.comment.banned) && ( {(cv.creator_banned_from_community || cv.creator.banned) && (
<div className="badge badge-danger mr-2"> <div className="badge badge-danger mr-2">
{i18n.t('banned')} {i18n.t('banned')}
</div> </div>
@ -192,18 +186,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{this.props.showCommunity && ( {this.props.showCommunity && (
<> <>
<span class="mx-1">{i18n.t('to')}</span> <span class="mx-1">{i18n.t('to')}</span>
<CommunityLink <CommunityLink community={cv.community} />
community={{
name: node.comment.community_name,
id: node.comment.community_id,
local: node.comment.community_local,
actor_id: node.comment.community_actor_id,
icon: node.comment.community_icon,
}}
/>
<span class="mx-2"></span> <span class="mx-2"></span>
<Link className="mr-2" to={`/post/${node.comment.post_id}`}> <Link className="mr-2" to={`/post/${cv.post.id}`}>
{node.comment.post_name} {cv.post.name}
</Link> </Link>
</> </>
)} )}
@ -224,7 +210,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
</a> </a>
<span className="mr-1"></span> <span className="mr-1"></span>
<span> <span>
<MomentTime data={node.comment} /> <MomentTime data={cv.comment} />
</span> </span>
</div> </div>
{/* end of user row */} {/* end of user row */}
@ -256,7 +242,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleMarkRead)} onClick={linkEvent(this, this.handleMarkRead)}
data-tippy-content={ data-tippy-content={
node.comment.read cv.comment.read
? i18n.t('mark_as_unread') ? i18n.t('mark_as_unread')
: i18n.t('mark_as_read') : i18n.t('mark_as_read')
} }
@ -266,7 +252,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
) : ( ) : (
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
node.comment.read && 'text-success' cv.comment.read && 'text-success'
}`} }`}
> >
<use xlinkHref="#icon-check"></use> <use xlinkHref="#icon-check"></use>
@ -333,7 +319,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button class="btn btn-link btn-animate"> <button class="btn btn-link btn-animate">
<Link <Link
className="text-muted" className="text-muted"
to={`/create_private_message/recipient/${node.comment.creator_id}`} to={`/create_private_message/recipient/${cv.creator.id}`}
title={i18n.t('message').toLowerCase()} title={i18n.t('message').toLowerCase()}
> >
<svg class="icon"> <svg class="icon">
@ -350,9 +336,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleSaveCommentClick this.handleSaveCommentClick
)} )}
data-tippy-content={ data-tippy-content={
node.comment.saved cv.saved ? i18n.t('unsave') : i18n.t('save')
? i18n.t('unsave')
: i18n.t('save')
} }
> >
{this.state.saveLoading ? ( {this.state.saveLoading ? (
@ -360,7 +344,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
) : ( ) : (
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
node.comment.saved && 'text-warning' cv.saved && 'text-warning'
}`} }`}
> >
<use xlinkHref="#icon-star"></use> <use xlinkHref="#icon-star"></use>
@ -398,14 +382,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.handleDeleteClick this.handleDeleteClick
)} )}
data-tippy-content={ data-tippy-content={
!node.comment.deleted !cv.comment.deleted
? i18n.t('delete') ? i18n.t('delete')
: i18n.t('restore') : i18n.t('restore')
} }
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
node.comment.deleted && 'text-danger' cv.comment.deleted && 'text-danger'
}`} }`}
> >
<use xlinkHref="#icon-trash"></use> <use xlinkHref="#icon-trash"></use>
@ -416,7 +400,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{/* Admins and mods can remove comments */} {/* Admins and mods can remove comments */}
{(this.canMod || this.canAdmin) && ( {(this.canMod || this.canAdmin) && (
<> <>
{!node.comment.removed ? ( {!cv.comment.removed ? (
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent( onClick={linkEvent(
@ -443,7 +427,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{this.canMod && ( {this.canMod && (
<> <>
{!this.isMod && {!this.isMod &&
(!node.comment.banned_from_community ? ( (!cv.creator_banned_from_community ? (
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent( onClick={linkEvent(
@ -464,8 +448,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{i18n.t('unban')} {i18n.t('unban')}
</button> </button>
))} ))}
{!node.comment.banned_from_community && {!cv.creator_banned_from_community &&
node.comment.creator_local && cv.creator.local &&
(!this.state.showConfirmAppointAsMod ? ( (!this.state.showConfirmAppointAsMod ? (
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
@ -508,7 +492,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{/* Community creators and admins can transfer community to another mod */} {/* Community creators and admins can transfer community to another mod */}
{(this.amCommunityCreator || this.canAdmin) && {(this.amCommunityCreator || this.canAdmin) &&
this.isMod && this.isMod &&
node.comment.creator_local && cv.creator.local &&
(!this.state.showConfirmTransferCommunity ? ( (!this.state.showConfirmTransferCommunity ? (
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
@ -549,7 +533,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{this.canAdmin && ( {this.canAdmin && (
<> <>
{!this.isAdmin && {!this.isAdmin &&
(!node.comment.banned ? ( (!cv.creator.banned ? (
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent( onClick={linkEvent(
@ -570,8 +554,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{i18n.t('unban_from_site')} {i18n.t('unban_from_site')}
</button> </button>
))} ))}
{!node.comment.banned && {!cv.creator.banned &&
node.comment.creator_local && cv.creator.local &&
(!this.state.showConfirmAppointAsAdmin ? ( (!this.state.showConfirmAppointAsAdmin ? (
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
@ -614,7 +598,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{/* Site Creator can transfer to another admin */} {/* Site Creator can transfer to another admin */}
{this.amSiteCreator && {this.amSiteCreator &&
this.isAdmin && this.isAdmin &&
node.comment.creator_local && cv.creator.local &&
(!this.state.showConfirmTransferSite ? ( (!this.state.showConfirmTransferSite ? (
<button <button
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
@ -711,7 +695,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{/* </div> */} {/* </div> */}
<div class="form-group row"> <div class="form-group row">
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
{i18n.t('ban')} {node.comment.creator_name} {i18n.t('ban')} {cv.creator.name}
</button> </button>
</div> </div>
</form> </form>
@ -743,11 +727,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
get linkBtn() { get linkBtn() {
let node = this.props.node; let cv = this.props.node.comment_view;
return ( return (
<Link <Link
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
to={`/post/${node.comment.post_id}/comment/${node.comment.id}`} to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')} title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')}
> >
<svg class="icon icon-inline"> <svg class="icon icon-inline">
@ -768,7 +752,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
get myComment(): boolean { get myComment(): boolean {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
this.props.node.comment.creator_id == UserService.Instance.user.id this.props.node.comment_view.creator.id == UserService.Instance.user.id
); );
} }
@ -776,8 +760,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
return ( return (
this.props.moderators && this.props.moderators &&
isMod( isMod(
this.props.moderators.map(m => m.user_id), this.props.moderators.map(m => m.moderator.id),
this.props.node.comment.creator_id this.props.node.comment_view.creator.id
) )
); );
} }
@ -786,26 +770,26 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
return ( return (
this.props.admins && this.props.admins &&
isMod( isMod(
this.props.admins.map(a => a.id), this.props.admins.map(a => a.user.id),
this.props.node.comment.creator_id this.props.node.comment_view.creator.id
) )
); );
} }
get isPostCreator(): boolean { get isPostCreator(): boolean {
return this.props.node.comment.creator_id == this.props.postCreatorId; return this.props.node.comment_view.creator.id == this.props.postCreatorId;
} }
get canMod(): boolean { get canMod(): boolean {
if (this.props.admins && this.props.moderators) { if (this.props.admins && this.props.moderators) {
let adminsThenMods = this.props.admins let adminsThenMods = this.props.admins
.map(a => a.id) .map(a => a.user.id)
.concat(this.props.moderators.map(m => m.user_id)); .concat(this.props.moderators.map(m => m.moderator.id));
return canMod( return canMod(
UserService.Instance.user, UserService.Instance.user,
adminsThenMods, adminsThenMods,
this.props.node.comment.creator_id this.props.node.comment_view.creator.id
); );
} else { } else {
return false; return false;
@ -817,8 +801,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.props.admins && this.props.admins &&
canMod( canMod(
UserService.Instance.user, UserService.Instance.user,
this.props.admins.map(a => a.id), this.props.admins.map(a => a.user.id),
this.props.node.comment.creator_id this.props.node.comment_view.creator.id
) )
); );
} }
@ -827,8 +811,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
return ( return (
this.props.moderators && this.props.moderators &&
UserService.Instance.user && UserService.Instance.user &&
this.props.node.comment.creator_id != UserService.Instance.user.id && this.props.node.comment_view.creator.id != UserService.Instance.user.id &&
UserService.Instance.user.id == this.props.moderators[0].user_id UserService.Instance.user.id == this.props.moderators[0].moderator.id
); );
} }
@ -836,18 +820,18 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
return ( return (
this.props.admins && this.props.admins &&
UserService.Instance.user && UserService.Instance.user &&
this.props.node.comment.creator_id != UserService.Instance.user.id && this.props.node.comment_view.creator.id != UserService.Instance.user.id &&
UserService.Instance.user.id == this.props.admins[0].id UserService.Instance.user.id == this.props.admins[0].user.id
); );
} }
get commentUnlessRemoved(): string { get commentUnlessRemoved(): string {
let node = this.props.node; let comment = this.props.node.comment_view.comment;
return node.comment.removed return comment.removed
? `*${i18n.t('removed')}*` ? `*${i18n.t('removed')}*`
: node.comment.deleted : comment.deleted
? `*${i18n.t('deleted')}*` ? `*${i18n.t('deleted')}*`
: node.comment.content; : comment.content;
} }
handleReplyClick(i: CommentNode) { handleReplyClick(i: CommentNode) {
@ -861,25 +845,25 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
handleDeleteClick(i: CommentNode) { handleDeleteClick(i: CommentNode) {
let deleteForm: DeleteCommentForm = { let comment = i.props.node.comment_view.comment;
edit_id: i.props.node.comment.id, let deleteForm: DeleteComment = {
deleted: !i.props.node.comment.deleted, edit_id: comment.id,
auth: null, deleted: !comment.deleted,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.deleteComment(deleteForm); WebSocketService.Instance.client.deleteComment(deleteForm);
} }
handleSaveCommentClick(i: CommentNode) { handleSaveCommentClick(i: CommentNode) {
let saved = let cv = i.props.node.comment_view;
i.props.node.comment.saved == undefined let save = cv.saved == undefined ? true : !cv.saved;
? true let form: SaveComment = {
: !i.props.node.comment.saved; comment_id: cv.comment.id,
let form: SaveCommentForm = { save,
comment_id: i.props.node.comment.id, auth: UserService.Instance.authField(),
save: saved,
}; };
WebSocketService.Instance.saveComment(form); WebSocketService.Instance.client.saveComment(form);
i.state.saveLoading = true; i.state.saveLoading = true;
i.setState(this.state); i.setState(this.state);
@ -908,12 +892,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.state.my_vote = new_vote; this.state.my_vote = new_vote;
let form: CommentLikeForm = { let form: CreateCommentLike = {
comment_id: i.comment.id, comment_id: i.comment_view.comment.id,
score: this.state.my_vote, score: this.state.my_vote,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.likeComment(form); WebSocketService.Instance.client.likeComment(form);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} }
@ -935,12 +920,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.state.my_vote = new_vote; this.state.my_vote = new_vote;
let form: CommentLikeForm = { let form: CreateCommentLike = {
comment_id: i.comment.id, comment_id: i.comment_view.comment.id,
score: this.state.my_vote, score: this.state.my_vote,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.likeComment(form); WebSocketService.Instance.client.likeComment(form);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} }
@ -961,34 +947,40 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
handleModRemoveSubmit(i: CommentNode) { handleModRemoveSubmit(i: CommentNode) {
event.preventDefault(); let comment = i.props.node.comment_view.comment;
let form: RemoveCommentForm = { let form: RemoveComment = {
edit_id: i.props.node.comment.id, edit_id: comment.id,
removed: !i.props.node.comment.removed, removed: !comment.removed,
reason: i.state.removeReason, reason: i.state.removeReason,
auth: null, auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.removeComment(form); WebSocketService.Instance.client.removeComment(form);
i.state.showRemoveDialog = false; i.state.showRemoveDialog = false;
i.setState(i.state); i.setState(i.state);
} }
isUserMentionType(
item: CommentView | UserMentionView
): item is UserMentionView {
return (item as UserMentionView).user_mention.id !== undefined;
}
handleMarkRead(i: CommentNode) { handleMarkRead(i: CommentNode) {
// if it has a user_mention_id field, then its a mention if (i.isUserMentionType(i.props.node.comment_view)) {
if (i.props.node.comment.user_mention_id) { let form: MarkUserMentionAsRead = {
let form: MarkUserMentionAsReadForm = { user_mention_id: i.props.node.comment_view.user_mention.id,
user_mention_id: i.props.node.comment.user_mention_id, read: !i.props.node.comment_view.user_mention.read,
read: !i.props.node.comment.read, auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.markUserMentionAsRead(form); WebSocketService.Instance.client.markUserMentionAsRead(form);
} else { } else {
let form: MarkCommentAsReadForm = { let form: MarkCommentAsRead = {
edit_id: i.props.node.comment.id, comment_id: i.props.node.comment_view.comment.id,
read: !i.props.node.comment.read, read: !i.props.node.comment_view.comment.read,
auth: null, auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.markCommentAsRead(form); WebSocketService.Instance.client.markCommentAsRead(form);
} }
i.state.readLoading = true; i.state.readLoading = true;
@ -1030,37 +1022,39 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
handleModBanBothSubmit(i: CommentNode) { handleModBanBothSubmit(i: CommentNode) {
event.preventDefault(); let cv = i.props.node.comment_view;
if (i.state.banType == BanType.Community) { if (i.state.banType == BanType.Community) {
// If its an unban, restore all their data // If its an unban, restore all their data
let ban = !i.props.node.comment.banned_from_community; let ban = !cv.creator_banned_from_community;
if (ban == false) { if (ban == false) {
i.state.removeData = false; i.state.removeData = false;
} }
let form: BanFromCommunityForm = { let form: BanFromCommunity = {
user_id: i.props.node.comment.creator_id, user_id: cv.creator.id,
community_id: i.props.node.comment.community_id, community_id: cv.community.id,
ban, ban,
remove_data: i.state.removeData, remove_data: i.state.removeData,
reason: i.state.banReason, reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires), expires: getUnixTime(i.state.banExpires),
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.banFromCommunity(form); WebSocketService.Instance.client.banFromCommunity(form);
} else { } else {
// If its an unban, restore all their data // If its an unban, restore all their data
let ban = !i.props.node.comment.banned; let ban = !cv.creator.banned;
if (ban == false) { if (ban == false) {
i.state.removeData = false; i.state.removeData = false;
} }
let form: BanUserForm = { let form: BanUser = {
user_id: i.props.node.comment.creator_id, user_id: cv.creator.id,
ban, ban,
remove_data: i.state.removeData, remove_data: i.state.removeData,
reason: i.state.banReason, reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires), expires: getUnixTime(i.state.banExpires),
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.banUser(form); WebSocketService.Instance.client.banUser(form);
} }
i.state.showBanDialog = false; i.state.showBanDialog = false;
@ -1078,12 +1072,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
handleAddModToCommunity(i: CommentNode) { handleAddModToCommunity(i: CommentNode) {
let form: AddModToCommunityForm = { let cv = i.props.node.comment_view;
user_id: i.props.node.comment.creator_id, let form: AddModToCommunity = {
community_id: i.props.node.comment.community_id, user_id: cv.creator.id,
community_id: cv.community.id,
added: !i.isMod, added: !i.isMod,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.addModToCommunity(form); WebSocketService.Instance.client.addModToCommunity(form);
i.state.showConfirmAppointAsMod = false; i.state.showConfirmAppointAsMod = false;
i.setState(i.state); i.setState(i.state);
} }
@ -1099,11 +1095,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
handleAddAdmin(i: CommentNode) { handleAddAdmin(i: CommentNode) {
let form: AddAdminForm = { let form: AddAdmin = {
user_id: i.props.node.comment.creator_id, user_id: i.props.node.comment_view.creator.id,
added: !i.isAdmin, added: !i.isAdmin,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.addAdmin(form); WebSocketService.Instance.client.addAdmin(form);
i.state.showConfirmAppointAsAdmin = false; i.state.showConfirmAppointAsAdmin = false;
i.setState(i.state); i.setState(i.state);
} }
@ -1119,11 +1116,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
handleTransferCommunity(i: CommentNode) { handleTransferCommunity(i: CommentNode) {
let form: TransferCommunityForm = { let cv = i.props.node.comment_view;
community_id: i.props.node.comment.community_id, let form: TransferCommunity = {
user_id: i.props.node.comment.creator_id, community_id: cv.community.id,
user_id: cv.creator.id,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.transferCommunity(form); WebSocketService.Instance.client.transferCommunity(form);
i.state.showConfirmTransferCommunity = false; i.state.showConfirmTransferCommunity = false;
i.setState(i.state); i.setState(i.state);
} }
@ -1139,17 +1138,18 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
handleTransferSite(i: CommentNode) { handleTransferSite(i: CommentNode) {
let form: TransferSiteForm = { let form: TransferSite = {
user_id: i.props.node.comment.creator_id, user_id: i.props.node.comment_view.creator.id,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.transferSite(form); WebSocketService.Instance.client.transferSite(form);
i.state.showConfirmTransferSite = false; i.state.showConfirmTransferSite = false;
i.setState(i.state); i.setState(i.state);
} }
get isCommentNew(): boolean { get isCommentNew(): boolean {
let now = moment.utc().subtract(10, 'minutes'); let now = moment.utc().subtract(10, 'minutes');
let then = moment.utc(this.props.node.comment.published); let then = moment.utc(this.props.node.comment_view.comment.published);
return now.isBefore(then); return now.isBefore(then);
} }

View file

@ -1,9 +1,8 @@
import { Component } from 'inferno'; import { Component } from 'inferno';
import { CommentSortType } from '../interfaces'; import { CommentSortType, CommentNode as CommentNodeI } from '../interfaces';
import { import {
CommentNode as CommentNodeI, CommunityModeratorView,
CommunityUser, UserViewSafe,
UserView,
SortType, SortType,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { commentSort, commentSortSortType } from '../utils'; import { commentSort, commentSortSortType } from '../utils';
@ -13,8 +12,8 @@ interface CommentNodesState {}
interface CommentNodesProps { interface CommentNodesProps {
nodes: CommentNodeI[]; nodes: CommentNodeI[];
moderators?: CommunityUser[]; moderators?: CommunityModeratorView[];
admins?: UserView[]; admins?: UserViewSafe[];
postCreatorId?: number; postCreatorId?: number;
noBorder?: boolean; noBorder?: boolean;
noIndent?: boolean; noIndent?: boolean;
@ -41,7 +40,7 @@ export class CommentNodes extends Component<
<div className="comments"> <div className="comments">
{this.sorter().map(node => ( {this.sorter().map(node => (
<CommentNode <CommentNode
key={node.comment.id} key={node.comment_view.comment.id}
node={node} node={node}
noBorder={this.props.noBorder} noBorder={this.props.noBorder}
noIndent={this.props.noIndent} noIndent={this.props.noIndent}

View file

@ -3,24 +3,23 @@ import { HtmlTags } from './html-tags';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
UserOperation, UserOperation,
Community, CommunityView,
ListCommunitiesResponse, ListCommunitiesResponse,
CommunityResponse, CommunityResponse,
FollowCommunityForm, FollowCommunity,
ListCommunitiesForm, ListCommunities,
SortType, SortType,
WebSocketJsonResponse, SiteView,
Site,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { import {
wsJsonToRes, wsJsonToRes,
toast, toast,
getPageFromProps, getPageFromProps,
isBrowser, isBrowser,
setAuth,
setIsoData, setIsoData,
wsSubscribe, wsSubscribe,
wsUserOp,
} from '../utils'; } from '../utils';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
@ -29,10 +28,10 @@ import { InitialFetchRequest } from 'shared/interfaces';
const communityLimit = 100; const communityLimit = 100;
interface CommunitiesState { interface CommunitiesState {
communities: Community[]; communities: CommunityView[];
page: number; page: number;
loading: boolean; loading: boolean;
site: Site; site_view: SiteView;
} }
interface CommunitiesProps { interface CommunitiesProps {
@ -46,7 +45,7 @@ export class Communities extends Component<any, CommunitiesState> {
communities: [], communities: [],
loading: true, loading: true,
page: getPageFromProps(this.props), page: getPageFromProps(this.props),
site: this.isoData.site.site, site_view: this.isoData.site_res.site_view,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -60,7 +59,7 @@ export class Communities extends Component<any, CommunitiesState> {
if (this.isoData.path == this.context.router.route.match.url) { if (this.isoData.path == this.context.router.route.match.url) {
this.state.communities = this.isoData.routeData[0].communities; this.state.communities = this.isoData.routeData[0].communities;
this.state.communities.sort( this.state.communities.sort(
(a, b) => b.number_of_subscribers - a.number_of_subscribers (a, b) => b.counts.subscribers - a.counts.subscribers
); );
this.state.loading = false; this.state.loading = false;
} else { } else {
@ -88,7 +87,7 @@ export class Communities extends Component<any, CommunitiesState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t('communities')} - ${this.state.site.name}`; return `${i18n.t('communities')} - ${this.state.site_view.site.name}`;
} }
render() { render() {
@ -124,27 +123,25 @@ export class Communities extends Component<any, CommunitiesState> {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{this.state.communities.map(community => ( {this.state.communities.map(cv => (
<tr> <tr>
<td> <td>
<CommunityLink community={community} /> <CommunityLink community={cv.community} />
</td> </td>
<td>{community.category_name}</td> <td>{cv.category.name}</td>
<td class="text-right"> <td class="text-right">{cv.counts.subscribers}</td>
{community.number_of_subscribers} <td class="text-right d-none d-lg-table-cell">
{cv.counts.posts}
</td> </td>
<td class="text-right d-none d-lg-table-cell"> <td class="text-right d-none d-lg-table-cell">
{community.number_of_posts} {cv.counts.comments}
</td>
<td class="text-right d-none d-lg-table-cell">
{community.number_of_comments}
</td> </td>
<td class="text-right"> <td class="text-right">
{community.subscribed ? ( {cv.subscribed ? (
<span <span
class="pointer btn-link" class="pointer btn-link"
onClick={linkEvent( onClick={linkEvent(
community.id, cv.community.id,
this.handleUnsubscribe this.handleUnsubscribe
)} )}
> >
@ -154,7 +151,7 @@ export class Communities extends Component<any, CommunitiesState> {
<span <span
class="pointer btn-link" class="pointer btn-link"
onClick={linkEvent( onClick={linkEvent(
community.id, cv.community.id,
this.handleSubscribe this.handleSubscribe
)} )}
> >
@ -212,64 +209,68 @@ export class Communities extends Component<any, CommunitiesState> {
} }
handleUnsubscribe(communityId: number) { handleUnsubscribe(communityId: number) {
let form: FollowCommunityForm = { let form: FollowCommunity = {
community_id: communityId, community_id: communityId,
follow: false, follow: false,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.followCommunity(form); WebSocketService.Instance.client.followCommunity(form);
} }
handleSubscribe(communityId: number) { handleSubscribe(communityId: number) {
let form: FollowCommunityForm = { let form: FollowCommunity = {
community_id: communityId, community_id: communityId,
follow: true, follow: true,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.followCommunity(form); WebSocketService.Instance.client.followCommunity(form);
} }
refetch() { refetch() {
let listCommunitiesForm: ListCommunitiesForm = { let listCommunitiesForm: ListCommunities = {
sort: SortType.TopAll, sort: SortType.TopAll,
limit: communityLimit, limit: communityLimit,
page: this.state.page, page: this.state.page,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.listCommunities(listCommunitiesForm); WebSocketService.Instance.client.listCommunities(listCommunitiesForm);
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let pathSplit = req.path.split('/'); let pathSplit = req.path.split('/');
let page = pathSplit[3] ? Number(pathSplit[3]) : 1; let page = pathSplit[3] ? Number(pathSplit[3]) : 1;
let listCommunitiesForm: ListCommunitiesForm = { let listCommunitiesForm: ListCommunities = {
sort: SortType.TopAll, sort: SortType.TopAll,
limit: communityLimit, limit: communityLimit,
page, page,
auth: req.auth,
}; };
setAuth(listCommunitiesForm, req.auth);
return [req.client.listCommunities(listCommunitiesForm)]; return [req.client.listCommunities(listCommunitiesForm)];
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
return; return;
} else if (res.op == UserOperation.ListCommunities) { } else if (op == UserOperation.ListCommunities) {
let data = res.data as ListCommunitiesResponse; let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
this.state.communities = data.communities; this.state.communities = data.communities;
this.state.communities.sort( this.state.communities.sort(
(a, b) => b.number_of_subscribers - a.number_of_subscribers (a, b) => b.counts.subscribers - a.counts.subscribers
); );
this.state.loading = false; this.state.loading = false;
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.FollowCommunity) { } else if (op == UserOperation.FollowCommunity) {
let data = res.data as CommunityResponse; let data = wsJsonToRes<CommunityResponse>(msg).data;
let found = this.state.communities.find(c => c.id == data.community.id); let found = this.state.communities.find(
found.subscribed = data.community.subscribed; c => c.community.id == data.community_view.community.id
found.number_of_subscribers = data.community.number_of_subscribers; );
found.subscribed = data.community_view.subscribed;
found.counts.subscribers = data.community_view.counts.subscribers;
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -2,20 +2,21 @@ import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router'; import { Prompt } from 'inferno-router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
CommunityForm as CommunityFormI, EditCommunity,
CreateCommunity,
UserOperation, UserOperation,
Category, Category,
CommunityResponse, CommunityResponse,
WebSocketJsonResponse, CommunityView,
Community,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { import {
wsJsonToRes, wsJsonToRes,
capitalizeFirstLetter, capitalizeFirstLetter,
toast, toast,
randomStr, randomStr,
wsSubscribe, wsSubscribe,
wsUserOp,
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
@ -23,16 +24,16 @@ import { MarkdownTextArea } from './markdown-textarea';
import { ImageUploadForm } from './image-upload-form'; import { ImageUploadForm } from './image-upload-form';
interface CommunityFormProps { interface CommunityFormProps {
community?: Community; // If a community is given, that means this is an edit community_view?: CommunityView; // If a community is given, that means this is an edit
categories: Category[]; categories: Category[];
onCancel?(): any; onCancel?(): any;
onCreate?(community: Community): any; onCreate?(community: CommunityView): any;
onEdit?(community: Community): any; onEdit?(community: CommunityView): any;
enableNsfw: boolean; enableNsfw: boolean;
} }
interface CommunityFormState { interface CommunityFormState {
communityForm: CommunityFormI; communityForm: CreateCommunity;
loading: boolean; loading: boolean;
} }
@ -51,6 +52,7 @@ export class CommunityForm extends Component<
nsfw: false, nsfw: false,
icon: null, icon: null,
banner: null, banner: null,
auth: UserService.Instance.authField(),
}, },
loading: false, loading: false,
}; };
@ -70,17 +72,17 @@ export class CommunityForm extends Component<
this.handleBannerUpload = this.handleBannerUpload.bind(this); this.handleBannerUpload = this.handleBannerUpload.bind(this);
this.handleBannerRemove = this.handleBannerRemove.bind(this); this.handleBannerRemove = this.handleBannerRemove.bind(this);
if (this.props.community) { let cv = this.props.community_view;
if (cv) {
this.state.communityForm = { this.state.communityForm = {
name: this.props.community.name, name: cv.community.name,
title: this.props.community.title, title: cv.community.title,
category_id: this.props.community.category_id, category_id: cv.category.id,
description: this.props.community.description, description: cv.community.description,
edit_id: this.props.community.id, nsfw: cv.community.nsfw,
nsfw: this.props.community.nsfw, icon: cv.community.icon,
icon: this.props.community.icon, banner: cv.community.banner,
banner: this.props.community.banner, auth: UserService.Instance.authField(),
auth: null,
}; };
} }
@ -88,6 +90,7 @@ export class CommunityForm extends Component<
this.subscription = wsSubscribe(this.parseMessage); this.subscription = wsSubscribe(this.parseMessage);
} }
// TODO this should be checked out
componentDidUpdate() { componentDidUpdate() {
if ( if (
!this.state.loading && !this.state.loading &&
@ -119,7 +122,7 @@ export class CommunityForm extends Component<
message={i18n.t('block_leaving')} message={i18n.t('block_leaving')}
/> />
<form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}> <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
{!this.props.community && ( {!this.props.community_view && (
<div class="form-group row"> <div class="form-group row">
<label class="col-12 col-form-label" htmlFor="community-name"> <label class="col-12 col-form-label" htmlFor="community-name">
{i18n.t('name')} {i18n.t('name')}
@ -250,13 +253,13 @@ export class CommunityForm extends Component<
<svg class="icon icon-spinner spin"> <svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use> <use xlinkHref="#icon-spinner"></use>
</svg> </svg>
) : this.props.community ? ( ) : this.props.community_view ? (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
) : ( ) : (
capitalizeFirstLetter(i18n.t('create')) capitalizeFirstLetter(i18n.t('create'))
)} )}
</button> </button>
{this.props.community && ( {this.props.community_view && (
<button <button
type="button" type="button"
class="btn btn-secondary" class="btn btn-secondary"
@ -275,10 +278,14 @@ export class CommunityForm extends Component<
handleCreateCommunitySubmit(i: CommunityForm, event: any) { handleCreateCommunitySubmit(i: CommunityForm, event: any) {
event.preventDefault(); event.preventDefault();
i.state.loading = true; i.state.loading = true;
if (i.props.community) { if (i.props.community_view) {
WebSocketService.Instance.editCommunity(i.state.communityForm); let form: EditCommunity = {
...i.state.communityForm,
edit_id: i.props.community_view.community.id,
};
WebSocketService.Instance.client.editCommunity(form);
} else { } else {
WebSocketService.Instance.createCommunity(i.state.communityForm); WebSocketService.Instance.client.createCommunity(i.state.communityForm);
} }
i.setState(i.state); i.setState(i.state);
} }
@ -332,22 +339,21 @@ export class CommunityForm extends Component<
this.setState(this.state); this.setState(this.state);
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
console.log(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
return; return;
} else if (res.op == UserOperation.CreateCommunity) { } else if (op == UserOperation.CreateCommunity) {
let data = res.data as CommunityResponse; let data = wsJsonToRes<CommunityResponse>(msg).data;
this.state.loading = false; this.state.loading = false;
this.props.onCreate(data.community); this.props.onCreate(data.community_view);
} else if (res.op == UserOperation.EditCommunity) { } else if (op == UserOperation.EditCommunity) {
let data = res.data as CommunityResponse; let data = wsJsonToRes<CommunityResponse>(msg).data;
this.state.loading = false; this.state.loading = false;
this.props.onEdit(data.community); this.props.onEdit(data.community_view);
} }
} }
} }

View file

@ -1,19 +1,12 @@
import { Component } from 'inferno'; import { Component } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Community } from 'lemmy-js-client'; import { CommunitySafe } from 'lemmy-js-client';
import { hostname, showAvatars } from '../utils'; import { hostname, showAvatars } from '../utils';
import { PictrsImage } from './pictrs-image'; import { PictrsImage } from './pictrs-image';
interface CommunityOther {
name: string;
id?: number; // Necessary if its federated
icon?: string;
local?: boolean;
actor_id?: string;
}
interface CommunityLinkProps { interface CommunityLinkProps {
community: Community | CommunityOther; // TODO figure this out better
community: CommunitySafe;
realLink?: boolean; realLink?: boolean;
useApubName?: boolean; useApubName?: boolean;
muted?: boolean; muted?: boolean;

View file

@ -6,19 +6,18 @@ import {
GetCommunityResponse, GetCommunityResponse,
CommunityResponse, CommunityResponse,
SortType, SortType,
Post, PostView,
GetPostsForm, GetPosts,
GetCommunityForm, GetCommunity,
ListingType, ListingType,
GetPostsResponse, GetPostsResponse,
PostResponse, PostResponse,
AddModToCommunityResponse, AddModToCommunityResponse,
BanFromCommunityResponse, BanFromCommunityResponse,
Comment, CommentView,
GetCommentsForm, GetComments,
GetCommentsResponse, GetCommentsResponse,
CommentResponse, CommentResponse,
WebSocketJsonResponse,
GetSiteResponse, GetSiteResponse,
Category, Category,
ListCategoriesResponse, ListCategoriesResponse,
@ -50,8 +49,8 @@ import {
setIsoData, setIsoData,
wsSubscribe, wsSubscribe,
isBrowser, isBrowser,
setAuth,
communityRSSUrl, communityRSSUrl,
wsUserOp,
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
@ -63,8 +62,8 @@ interface State {
communityLoading: boolean; communityLoading: boolean;
postsLoading: boolean; postsLoading: boolean;
commentsLoading: boolean; commentsLoading: boolean;
posts: Post[]; posts: PostView[];
comments: Comment[]; comments: CommentView[];
dataType: DataType; dataType: DataType;
sort: SortType; sort: SortType;
page: number; page: number;
@ -98,7 +97,7 @@ export class Community extends Component<any, State> {
dataType: getDataTypeFromProps(this.props), dataType: getDataTypeFromProps(this.props),
sort: getSortTypeFromProps(this.props), sort: getSortTypeFromProps(this.props),
page: getPageFromProps(this.props), page: getPageFromProps(this.props),
siteRes: this.isoData.site, siteRes: this.isoData.site_res,
categories: [], categories: [],
}; };
@ -127,17 +126,18 @@ export class Community extends Component<any, State> {
} else { } else {
this.fetchCommunity(); this.fetchCommunity();
this.fetchData(); this.fetchData();
WebSocketService.Instance.listCategories(); WebSocketService.Instance.client.listCategories();
} }
setupTippy(); setupTippy();
} }
fetchCommunity() { fetchCommunity() {
let form: GetCommunityForm = { let form: GetCommunity = {
id: this.state.communityId ? this.state.communityId : null, id: this.state.communityId ? this.state.communityId : null,
name: this.state.communityName ? this.state.communityName : null, name: this.state.communityName ? this.state.communityName : null,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.getCommunity(form); WebSocketService.Instance.client.getCommunity(form);
} }
componentWillUnmount() { componentWillUnmount() {
@ -169,8 +169,8 @@ export class Community extends Component<any, State> {
id = Number(idOrName); id = Number(idOrName);
} }
let communityForm: GetCommunityForm = id ? { id } : { name: name_ }; let communityForm: GetCommunity = id ? { id } : { name: name_ };
setAuth(communityForm, req.auth); communityForm.auth = req.auth;
promises.push(req.client.getCommunity(communityForm)); promises.push(req.client.getCommunity(communityForm));
let dataType: DataType = pathSplit[4] let dataType: DataType = pathSplit[4]
@ -186,24 +186,24 @@ export class Community extends Component<any, State> {
let page = pathSplit[8] ? Number(pathSplit[8]) : 1; let page = pathSplit[8] ? Number(pathSplit[8]) : 1;
if (dataType == DataType.Post) { if (dataType == DataType.Post) {
let getPostsForm: GetPostsForm = { let getPostsForm: GetPosts = {
page, page,
limit: fetchLimit, limit: fetchLimit,
sort, sort,
type_: ListingType.Community, type_: ListingType.Community,
auth: req.auth,
}; };
this.setIdOrName(getPostsForm, id, name_); this.setIdOrName(getPostsForm, id, name_);
setAuth(getPostsForm, req.auth);
promises.push(req.client.getPosts(getPostsForm)); promises.push(req.client.getPosts(getPostsForm));
} else { } else {
let getCommentsForm: GetCommentsForm = { let getCommentsForm: GetComments = {
page, page,
limit: fetchLimit, limit: fetchLimit,
sort, sort,
type_: ListingType.Community, type_: ListingType.Community,
auth: req.auth,
}; };
this.setIdOrName(getCommentsForm, id, name_); this.setIdOrName(getCommentsForm, id, name_);
setAuth(getCommentsForm, req.auth);
promises.push(req.client.getComments(getCommentsForm)); promises.push(req.client.getComments(getCommentsForm));
} }
@ -232,10 +232,11 @@ export class Community extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${this.state.communityRes.community.title} - ${this.state.siteRes.site.name}`; return `${this.state.communityRes.community_view.community.title} - ${this.state.siteRes.site_view.site.name}`;
} }
render() { render() {
let cv = this.state.communityRes.community_view;
return ( return (
<div class="container"> <div class="container">
{this.state.communityLoading ? ( {this.state.communityLoading ? (
@ -250,8 +251,8 @@ export class Community extends Component<any, State> {
<HtmlTags <HtmlTags
title={this.documentTitle} title={this.documentTitle}
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
description={this.state.communityRes.community.description} description={cv.community.description}
image={this.state.communityRes.community.icon} image={cv.community.icon}
/> />
{this.communityInfo()} {this.communityInfo()}
{this.selects()} {this.selects()}
@ -260,11 +261,11 @@ export class Community extends Component<any, State> {
</div> </div>
<div class="col-12 col-md-4"> <div class="col-12 col-md-4">
<Sidebar <Sidebar
community={this.state.communityRes.community} community_view={cv}
moderators={this.state.communityRes.moderators} moderators={this.state.communityRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
online={this.state.communityRes.online} online={this.state.communityRes.online}
enableNsfw={this.state.siteRes.site.enable_nsfw} enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
categories={this.state.categories} categories={this.state.categories}
/> />
</div> </div>
@ -275,6 +276,7 @@ export class Community extends Component<any, State> {
} }
listings() { listings() {
let site = this.state.siteRes.site_view.site;
return this.state.dataType == DataType.Post ? ( return this.state.dataType == DataType.Post ? (
this.state.postsLoading ? ( this.state.postsLoading ? (
<h5> <h5>
@ -287,8 +289,8 @@ export class Community extends Component<any, State> {
posts={this.state.posts} posts={this.state.posts}
removeDuplicates removeDuplicates
sort={this.state.sort} sort={this.state.sort}
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={site.enable_downvotes}
enableNsfw={this.state.siteRes.site.enable_nsfw} enableNsfw={site.enable_nsfw}
/> />
) )
) : this.state.commentsLoading ? ( ) : this.state.commentsLoading ? (
@ -303,21 +305,19 @@ export class Community extends Component<any, State> {
noIndent noIndent
sortType={this.state.sort} sortType={this.state.sort}
showContext showContext
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={site.enable_downvotes}
/> />
); );
} }
communityInfo() { communityInfo() {
let community = this.state.communityRes.community_view.community;
return ( return (
<div> <div>
<BannerIconHeader <BannerIconHeader banner={community.banner} icon={community.icon} />
banner={this.state.communityRes.community.banner} <h5 class="mb-0">{community.title}</h5>
icon={this.state.communityRes.community.icon}
/>
<h5 class="mb-0">{this.state.communityRes.community.title}</h5>
<CommunityLink <CommunityLink
community={this.state.communityRes.community} community={community}
realLink realLink
useApubName useApubName
muted muted
@ -342,7 +342,7 @@ export class Community extends Component<any, State> {
</span> </span>
<a <a
href={communityRSSUrl( href={communityRSSUrl(
this.state.communityRes.community.actor_id, this.state.communityRes.community_view.community.actor_id,
this.state.sort this.state.sort
)} )}
target="_blank" target="_blank"
@ -405,137 +405,141 @@ export class Community extends Component<any, State> {
const sortStr = paramUpdates.sort || this.state.sort; const sortStr = paramUpdates.sort || this.state.sort;
const page = paramUpdates.page || this.state.page; const page = paramUpdates.page || this.state.page;
this.props.history.push( this.props.history.push(
`/c/${this.state.communityRes.community.name}/data_type/${dataTypeStr}/sort/${sortStr}/page/${page}` `/c/${this.state.communityRes.community_view.community.name}/data_type/${dataTypeStr}/sort/${sortStr}/page/${page}`
); );
} }
fetchData() { fetchData() {
if (this.state.dataType == DataType.Post) { if (this.state.dataType == DataType.Post) {
let getPostsForm: GetPostsForm = { let form: GetPosts = {
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
sort: this.state.sort, sort: this.state.sort,
type_: ListingType.Community, type_: ListingType.Community,
community_id: this.state.communityId, community_id: this.state.communityId,
community_name: this.state.communityName, community_name: this.state.communityName,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.getPosts(getPostsForm); WebSocketService.Instance.client.getPosts(form);
} else { } else {
let getCommentsForm: GetCommentsForm = { let form: GetComments = {
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
sort: this.state.sort, sort: this.state.sort,
type_: ListingType.Community, type_: ListingType.Community,
community_id: this.state.communityId, community_id: this.state.communityId,
community_name: this.state.communityName, community_name: this.state.communityName,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.getComments(getCommentsForm); WebSocketService.Instance.client.getComments(form);
} }
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.context.router.history.push('/'); this.context.router.history.push('/');
return; return;
} else if (msg.reconnect) { } else if (msg.reconnect) {
WebSocketService.Instance.communityJoin({ WebSocketService.Instance.client.communityJoin({
community_id: this.state.communityRes.community.id, community_id: this.state.communityRes.community_view.community.id,
}); });
this.fetchData(); this.fetchData();
} else if (res.op == UserOperation.GetCommunity) { } else if (op == UserOperation.GetCommunity) {
let data = res.data as GetCommunityResponse; let data = wsJsonToRes<GetCommunityResponse>(msg).data;
this.state.communityRes = data; this.state.communityRes = data;
this.state.communityLoading = false; this.state.communityLoading = false;
this.setState(this.state); this.setState(this.state);
WebSocketService.Instance.communityJoin({ // TODO why is there no auth in this form?
community_id: data.community.id, WebSocketService.Instance.client.communityJoin({
community_id: data.community_view.community.id,
}); });
} else if ( } else if (
res.op == UserOperation.EditCommunity || op == UserOperation.EditCommunity ||
res.op == UserOperation.DeleteCommunity || op == UserOperation.DeleteCommunity ||
res.op == UserOperation.RemoveCommunity op == UserOperation.RemoveCommunity
) { ) {
let data = res.data as CommunityResponse; let data = wsJsonToRes<CommunityResponse>(msg).data;
this.state.communityRes.community = data.community; this.state.communityRes.community_view = data.community_view;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.FollowCommunity) { } else if (op == UserOperation.FollowCommunity) {
let data = res.data as CommunityResponse; let data = wsJsonToRes<CommunityResponse>(msg).data;
this.state.communityRes.community.subscribed = data.community.subscribed; this.state.communityRes.community_view.subscribed =
this.state.communityRes.community.number_of_subscribers = data.community_view.subscribed;
data.community.number_of_subscribers; this.state.communityRes.community_view.counts.subscribers =
data.community_view.counts.subscribers;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.GetPosts) { } else if (op == UserOperation.GetPosts) {
let data = res.data as GetPostsResponse; let data = wsJsonToRes<GetPostsResponse>(msg).data;
this.state.posts = data.posts; this.state.posts = data.posts;
this.state.postsLoading = false; this.state.postsLoading = false;
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if ( } else if (
res.op == UserOperation.EditPost || op == UserOperation.EditPost ||
res.op == UserOperation.DeletePost || op == UserOperation.DeletePost ||
res.op == UserOperation.RemovePost || op == UserOperation.RemovePost ||
res.op == UserOperation.LockPost || op == UserOperation.LockPost ||
res.op == UserOperation.StickyPost || op == UserOperation.StickyPost ||
res.op == UserOperation.SavePost op == UserOperation.SavePost
) { ) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
editPostFindRes(data, this.state.posts); editPostFindRes(data.post_view, this.state.posts);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePost) { } else if (op == UserOperation.CreatePost) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
this.state.posts.unshift(data.post); this.state.posts.unshift(data.post_view);
notifyPost(data.post, this.context.router); notifyPost(data.post_view, this.context.router);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (op == UserOperation.CreatePostLike) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
createPostLikeFindRes(data, this.state.posts); createPostLikeFindRes(data.post_view, this.state.posts);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.AddModToCommunity) { } else if (op == UserOperation.AddModToCommunity) {
let data = res.data as AddModToCommunityResponse; let data = wsJsonToRes<AddModToCommunityResponse>(msg).data;
this.state.communityRes.moderators = data.moderators; this.state.communityRes.moderators = data.moderators;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.BanFromCommunity) { } else if (op == UserOperation.BanFromCommunity) {
let data = res.data as BanFromCommunityResponse; let data = wsJsonToRes<BanFromCommunityResponse>(msg).data;
// TODO this might be incorrect
this.state.posts this.state.posts
.filter(p => p.creator_id == data.user.id) .filter(p => p.creator.id == data.user_view.user.id)
.forEach(p => (p.banned = data.banned)); .forEach(p => (p.creator_banned_from_community = data.banned));
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.GetComments) { } else if (op == UserOperation.GetComments) {
let data = res.data as GetCommentsResponse; let data = wsJsonToRes<GetCommentsResponse>(msg).data;
this.state.comments = data.comments; this.state.comments = data.comments;
this.state.commentsLoading = false; this.state.commentsLoading = false;
this.setState(this.state); this.setState(this.state);
} else if ( } else if (
res.op == UserOperation.EditComment || op == UserOperation.EditComment ||
res.op == UserOperation.DeleteComment || op == UserOperation.DeleteComment ||
res.op == UserOperation.RemoveComment op == UserOperation.RemoveComment
) { ) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
editCommentRes(data, this.state.comments); editCommentRes(data.comment_view, this.state.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateComment) { } else if (op == UserOperation.CreateComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
// Necessary since it might be a user reply // Necessary since it might be a user reply
if (data.recipient_ids.length == 0) { if (data.recipient_ids.length == 0) {
this.state.comments.unshift(data.comment); this.state.comments.unshift(data.comment_view);
this.setState(this.state); this.setState(this.state);
} }
} else if (res.op == UserOperation.SaveComment) { } else if (op == UserOperation.SaveComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
saveCommentRes(data, this.state.comments); saveCommentRes(data.comment_view, this.state.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateCommentLike) { } else if (op == UserOperation.CreateCommentLike) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data, this.state.comments); createCommentLikeRes(data.comment_view, this.state.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.ListCategories) { } else if (op == UserOperation.ListCategories) {
let data = res.data as ListCategoriesResponse; let data = wsJsonToRes<ListCategoriesResponse>(msg).data;
this.state.categories = data.categories; this.state.categories = data.categories;
this.setState(this.state); this.setState(this.state);
} }

View file

@ -3,10 +3,9 @@ import { Subscription } from 'rxjs';
import { CommunityForm } from './community-form'; import { CommunityForm } from './community-form';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { import {
Community, CommunityView,
UserOperation, UserOperation,
WebSocketJsonResponse, SiteView,
Site,
ListCategoriesResponse, ListCategoriesResponse,
Category, Category,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
@ -16,13 +15,14 @@ import {
wsJsonToRes, wsJsonToRes,
wsSubscribe, wsSubscribe,
isBrowser, isBrowser,
wsUserOp,
} from '../utils'; } from '../utils';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { InitialFetchRequest } from 'shared/interfaces'; import { InitialFetchRequest } from 'shared/interfaces';
interface CreateCommunityState { interface CreateCommunityState {
site: Site; site_view: SiteView;
categories: Category[]; categories: Category[];
loading: boolean; loading: boolean;
} }
@ -31,7 +31,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private subscription: Subscription; private subscription: Subscription;
private emptyState: CreateCommunityState = { private emptyState: CreateCommunityState = {
site: this.isoData.site.site, site_view: this.isoData.site_res.site_view,
categories: [], categories: [],
loading: true, loading: true,
}; };
@ -53,7 +53,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
this.state.categories = this.isoData.routeData[0].categories; this.state.categories = this.isoData.routeData[0].categories;
this.state.loading = false; this.state.loading = false;
} else { } else {
WebSocketService.Instance.listCategories(); WebSocketService.Instance.client.listCategories();
} }
} }
@ -64,7 +64,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t('create_community')} - ${this.state.site.name}`; return `${i18n.t('create_community')} - ${this.state.site_view.site.name}`;
} }
render() { render() {
@ -87,7 +87,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
<CommunityForm <CommunityForm
categories={this.state.categories} categories={this.state.categories}
onCreate={this.handleCommunityCreate} onCreate={this.handleCommunityCreate}
enableNsfw={this.state.site.enable_nsfw} enableNsfw={this.state.site_view.site.enable_nsfw}
/> />
</div> </div>
</div> </div>
@ -96,22 +96,21 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
); );
} }
handleCommunityCreate(community: Community) { handleCommunityCreate(cv: CommunityView) {
this.props.history.push(`/c/${community.name}`); this.props.history.push(`/c/${cv.community.name}`);
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
return [req.client.listCategories()]; return [req.client.listCategories()];
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
// Toast errors are already handled by community-form // Toast errors are already handled by community-form
return; return;
} else if (res.op == UserOperation.ListCategories) { } else if (op == UserOperation.ListCategories) {
let data = res.data as ListCategoriesResponse; let data = wsJsonToRes<ListCategoriesResponse>(msg).data;
this.state.categories = data.categories; this.state.categories = data.categories;
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);

View file

@ -4,29 +4,28 @@ import { PostForm } from './post-form';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { import {
isBrowser, isBrowser,
setAuth,
setIsoData, setIsoData,
toast, toast,
wsJsonToRes, wsJsonToRes,
wsSubscribe, wsSubscribe,
wsUserOp,
} from '../utils'; } from '../utils';
import { UserService, WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { import {
UserOperation, UserOperation,
PostFormParams,
WebSocketJsonResponse,
ListCommunitiesResponse, ListCommunitiesResponse,
Community, CommunityView,
Site, SiteView,
ListCommunitiesForm, ListCommunities,
SortType, SortType,
PostView,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { InitialFetchRequest } from 'shared/interfaces'; import { InitialFetchRequest, PostFormParams } from 'shared/interfaces';
interface CreatePostState { interface CreatePostState {
site: Site; site_view: SiteView;
communities: Community[]; communities: CommunityView[];
loading: boolean; loading: boolean;
} }
@ -34,7 +33,7 @@ export class CreatePost extends Component<any, CreatePostState> {
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private subscription: Subscription; private subscription: Subscription;
private emptyState: CreatePostState = { private emptyState: CreatePostState = {
site: this.isoData.site.site, site_view: this.isoData.site_res.site_view,
communities: [], communities: [],
loading: true, loading: true,
}; };
@ -62,11 +61,12 @@ export class CreatePost extends Component<any, CreatePostState> {
} }
refetch() { refetch() {
let listCommunitiesForm: ListCommunitiesForm = { let listCommunitiesForm: ListCommunities = {
sort: SortType.TopAll, sort: SortType.TopAll,
limit: 9999, limit: 9999,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.listCommunities(listCommunitiesForm); WebSocketService.Instance.client.listCommunities(listCommunitiesForm);
} }
componentWillUnmount() { componentWillUnmount() {
@ -76,7 +76,7 @@ export class CreatePost extends Component<any, CreatePostState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t('create_post')} - ${this.state.site.name}`; return `${i18n.t('create_post')} - ${this.state.site_view.site.name}`;
} }
render() { render() {
@ -100,8 +100,8 @@ export class CreatePost extends Component<any, CreatePostState> {
communities={this.state.communities} communities={this.state.communities}
onCreate={this.handlePostCreate} onCreate={this.handlePostCreate}
params={this.params} params={this.params}
enableDownvotes={this.state.site.enable_downvotes} enableDownvotes={this.state.site_view.site.enable_downvotes}
enableNsfw={this.state.site.enable_nsfw} enableNsfw={this.state.site_view.site.enable_nsfw}
/> />
</div> </div>
</div> </div>
@ -149,27 +149,26 @@ export class CreatePost extends Component<any, CreatePostState> {
return null; return null;
} }
handlePostCreate(id: number) { handlePostCreate(post_view: PostView) {
this.props.history.push(`/post/${id}`); this.props.history.push(`/post/${post_view.post.id}`);
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let listCommunitiesForm: ListCommunitiesForm = { let listCommunitiesForm: ListCommunities = {
sort: SortType.TopAll, sort: SortType.TopAll,
limit: 9999, limit: 9999,
auth: req.auth,
}; };
setAuth(listCommunitiesForm, req.auth);
return [req.client.listCommunities(listCommunitiesForm)]; return [req.client.listCommunities(listCommunitiesForm)];
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
return; return;
} else if (res.op == UserOperation.ListCommunities) { } else if (op == UserOperation.ListCommunities) {
let data = res.data as ListCommunitiesResponse; let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
this.state.communities = data.communities; this.state.communities = data.communities;
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);

View file

@ -4,22 +4,21 @@ import { PrivateMessageForm } from './private-message-form';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import { UserService, WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { import {
Site, SiteView,
WebSocketJsonResponse,
UserOperation, UserOperation,
UserDetailsResponse, GetUserDetailsResponse,
UserView, UserViewSafe,
SortType, SortType,
GetUserDetailsForm, GetUserDetails,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { import {
getRecipientIdFromProps, getRecipientIdFromProps,
isBrowser, isBrowser,
setAuth,
setIsoData, setIsoData,
toast, toast,
wsJsonToRes, wsJsonToRes,
wsSubscribe, wsSubscribe,
wsUserOp,
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { InitialFetchRequest } from 'shared/interfaces'; import { InitialFetchRequest } from 'shared/interfaces';
@ -27,8 +26,8 @@ import { InitialFetchRequest } from 'shared/interfaces';
interface CreatePrivateMessageProps {} interface CreatePrivateMessageProps {}
interface CreatePrivateMessageState { interface CreatePrivateMessageState {
site: Site; site_view: SiteView;
recipient: UserView; recipient: UserViewSafe;
recipient_id: number; recipient_id: number;
loading: boolean; loading: boolean;
} }
@ -40,7 +39,7 @@ export class CreatePrivateMessage extends Component<
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private subscription: Subscription; private subscription: Subscription;
private emptyState: CreatePrivateMessageState = { private emptyState: CreatePrivateMessageState = {
site: this.isoData.site.site, site_view: this.isoData.site_res.site_view,
recipient: undefined, recipient: undefined,
recipient_id: getRecipientIdFromProps(this.props), recipient_id: getRecipientIdFromProps(this.props),
loading: true, loading: true,
@ -70,27 +69,30 @@ export class CreatePrivateMessage extends Component<
} }
fetchUserDetails() { fetchUserDetails() {
let form: GetUserDetailsForm = { let form: GetUserDetails = {
user_id: this.state.recipient_id, user_id: this.state.recipient_id,
sort: SortType.New, sort: SortType.New,
saved_only: false, saved_only: false,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.getUserDetails(form); WebSocketService.Instance.client.getUserDetails(form);
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let user_id = Number(req.path.split('/').pop()); let user_id = Number(req.path.split('/').pop());
let form: GetUserDetailsForm = { let form: GetUserDetails = {
user_id, user_id,
sort: SortType.New, sort: SortType.New,
saved_only: false, saved_only: false,
auth: req.auth,
}; };
setAuth(form, req.auth);
return [req.client.getUserDetails(form)]; return [req.client.getUserDetails(form)];
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t('create_private_message')} - ${this.state.site.name}`; return `${i18n.t('create_private_message')} - ${
this.state.site_view.site.name
}`;
} }
componentWillUnmount() { componentWillUnmount() {
@ -118,7 +120,7 @@ export class CreatePrivateMessage extends Component<
<h5>{i18n.t('create_private_message')}</h5> <h5>{i18n.t('create_private_message')}</h5>
<PrivateMessageForm <PrivateMessageForm
onCreate={this.handlePrivateMessageCreate} onCreate={this.handlePrivateMessageCreate}
recipient={this.state.recipient} recipient={this.state.recipient.user}
/> />
</div> </div>
</div> </div>
@ -134,16 +136,16 @@ export class CreatePrivateMessage extends Component<
this.context.router.history.push(`/`); this.context.router.history.push(`/`);
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
return; return;
} else if (res.op == UserOperation.GetUserDetails) { } else if (op == UserOperation.GetUserDetails) {
let data = res.data as UserDetailsResponse; let data = wsJsonToRes<GetUserDetailsResponse>(msg).data;
this.state.recipient = data.user; this.state.recipient = data.user_view;
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
} }

View file

@ -2,26 +2,25 @@ import { Component, linkEvent } from 'inferno';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
UserOperation, UserOperation,
Comment, CommentView,
SortType, SortType,
GetRepliesForm, GetReplies,
GetRepliesResponse, GetRepliesResponse,
GetUserMentionsForm, GetUserMentions,
GetUserMentionsResponse, GetUserMentionsResponse,
UserMentionResponse, UserMentionResponse,
CommentResponse, CommentResponse,
WebSocketJsonResponse, PrivateMessageView,
PrivateMessage as PrivateMessageI, GetPrivateMessages,
GetPrivateMessagesForm,
PrivateMessagesResponse, PrivateMessagesResponse,
PrivateMessageResponse, PrivateMessageResponse,
Site, SiteView,
UserMentionView,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { import {
wsJsonToRes, wsJsonToRes,
fetchLimit, fetchLimit,
isCommentType,
toast, toast,
editCommentRes, editCommentRes,
saveCommentRes, saveCommentRes,
@ -30,8 +29,8 @@ import {
setupTippy, setupTippy,
setIsoData, setIsoData,
wsSubscribe, wsSubscribe,
setAuth,
isBrowser, isBrowser,
wsUserOp,
} from '../utils'; } from '../utils';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { PrivateMessage } from './private-message'; import { PrivateMessage } from './private-message';
@ -52,17 +51,27 @@ enum MessageType {
Messages, Messages,
} }
type ReplyType = Comment | PrivateMessageI; enum ReplyEnum {
Reply,
Mention,
Message,
}
type ReplyType = {
id: number;
type_: ReplyEnum;
view: CommentView | PrivateMessageView | UserMentionView;
published: string;
};
interface InboxState { interface InboxState {
unreadOrAll: UnreadOrAll; unreadOrAll: UnreadOrAll;
messageType: MessageType; messageType: MessageType;
replies: Comment[]; replies: CommentView[];
mentions: Comment[]; mentions: UserMentionView[];
messages: PrivateMessageI[]; messages: PrivateMessageView[];
sort: SortType; sort: SortType;
page: number; page: number;
site: Site; site_view: SiteView;
loading: boolean; loading: boolean;
} }
@ -77,7 +86,7 @@ export class Inbox extends Component<any, InboxState> {
messages: [], messages: [],
sort: SortType.New, sort: SortType.New,
page: 1, page: 1,
site: this.isoData.site.site, site_view: this.isoData.site_res.site_view,
loading: true, loading: true,
}; };
@ -115,7 +124,7 @@ export class Inbox extends Component<any, InboxState> {
get documentTitle(): string { get documentTitle(): string {
return `@${UserService.Instance.user.name} ${i18n.t('inbox')} - ${ return `@${UserService.Instance.user.name} ${i18n.t('inbox')} - ${
this.state.site.name this.state.site_view.site.name
}`; }`;
} }
@ -288,33 +297,71 @@ export class Inbox extends Component<any, InboxState> {
} }
combined(): ReplyType[] { combined(): ReplyType[] {
return [ let id = 0;
...this.state.replies, let replies: ReplyType[] = this.state.replies.map(r => ({
...this.state.mentions, id: id++,
...this.state.messages, type_: ReplyEnum.Reply,
].sort((a, b) => b.published.localeCompare(a.published)); view: r,
published: r.comment.published,
}));
let mentions: ReplyType[] = this.state.mentions.map(r => ({
id: id++,
type_: ReplyEnum.Mention,
view: r,
published: r.comment.published,
}));
let messages: ReplyType[] = this.state.messages.map(r => ({
id: id++,
type_: ReplyEnum.Message,
view: r,
published: r.private_message.published,
}));
return [...replies, ...mentions, ...messages].sort((a, b) =>
b.published.localeCompare(a.published)
);
}
renderReplyType(i: ReplyType) {
switch (i.type_) {
case ReplyEnum.Reply:
return (
<CommentNodes
key={i.id}
nodes={[{ comment_view: i.view as CommentView }]}
noIndent
markable
showCommunity
showContext
enableDownvotes={this.state.site_view.site.enable_downvotes}
/>
);
case ReplyEnum.Mention:
return (
<CommentNodes
key={i.id}
nodes={[{ comment_view: i.view as UserMentionView }]}
noIndent
markable
showCommunity
showContext
enableDownvotes={this.state.site_view.site.enable_downvotes}
/>
);
case ReplyEnum.Message:
return (
<PrivateMessage
key={i.id}
private_message_view={i.view as PrivateMessageView}
/>
);
default:
return <div />;
}
} }
all() { all() {
return ( return <div>{this.combined().map(i => this.renderReplyType(i))}</div>;
<div>
{this.combined().map(i =>
isCommentType(i) ? (
<CommentNodes
key={i.id}
nodes={[{ comment: i }]}
noIndent
markable
showCommunity
showContext
enableDownvotes={this.state.site.enable_downvotes}
/>
) : (
<PrivateMessage key={i.id} privateMessage={i} />
)
)}
</div>
);
} }
replies() { replies() {
@ -326,7 +373,7 @@ export class Inbox extends Component<any, InboxState> {
markable markable
showCommunity showCommunity
showContext showContext
enableDownvotes={this.state.site.enable_downvotes} enableDownvotes={this.state.site_view.site.enable_downvotes}
/> />
</div> </div>
); );
@ -335,15 +382,15 @@ export class Inbox extends Component<any, InboxState> {
mentions() { mentions() {
return ( return (
<div> <div>
{this.state.mentions.map(mention => ( {this.state.mentions.map(umv => (
<CommentNodes <CommentNodes
key={mention.id} key={umv.user_mention.id}
nodes={[{ comment: mention }]} nodes={[{ comment_view: umv }]}
noIndent noIndent
markable markable
showCommunity showCommunity
showContext showContext
enableDownvotes={this.state.site.enable_downvotes} enableDownvotes={this.state.site_view.site.enable_downvotes}
/> />
))} ))}
</div> </div>
@ -353,8 +400,11 @@ export class Inbox extends Component<any, InboxState> {
messages() { messages() {
return ( return (
<div> <div>
{this.state.messages.map(message => ( {this.state.messages.map(pmv => (
<PrivateMessage key={message.id} privateMessage={message} /> <PrivateMessage
key={pmv.private_message.id}
private_message_view={pmv}
/>
))} ))}
</div> </div>
); );
@ -413,58 +463,61 @@ export class Inbox extends Component<any, InboxState> {
let promises: Promise<any>[] = []; let promises: Promise<any>[] = [];
// It can be /u/me, or /username/1 // It can be /u/me, or /username/1
let repliesForm: GetRepliesForm = { let repliesForm: GetReplies = {
sort: SortType.New, sort: SortType.New,
unread_only: true, unread_only: true,
page: 1, page: 1,
limit: fetchLimit, limit: fetchLimit,
auth: req.auth,
}; };
setAuth(repliesForm, req.auth);
promises.push(req.client.getReplies(repliesForm)); promises.push(req.client.getReplies(repliesForm));
let userMentionsForm: GetUserMentionsForm = { let userMentionsForm: GetUserMentions = {
sort: SortType.New, sort: SortType.New,
unread_only: true, unread_only: true,
page: 1, page: 1,
limit: fetchLimit, limit: fetchLimit,
auth: req.auth,
}; };
setAuth(userMentionsForm, req.auth);
promises.push(req.client.getUserMentions(userMentionsForm)); promises.push(req.client.getUserMentions(userMentionsForm));
let privateMessagesForm: GetPrivateMessagesForm = { let privateMessagesForm: GetPrivateMessages = {
unread_only: true, unread_only: true,
page: 1, page: 1,
limit: fetchLimit, limit: fetchLimit,
auth: req.auth,
}; };
setAuth(privateMessagesForm, req.auth);
promises.push(req.client.getPrivateMessages(privateMessagesForm)); promises.push(req.client.getPrivateMessages(privateMessagesForm));
return promises; return promises;
} }
refetch() { refetch() {
let repliesForm: GetRepliesForm = { let repliesForm: GetReplies = {
sort: this.state.sort, sort: this.state.sort,
unread_only: this.state.unreadOrAll == UnreadOrAll.Unread, unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.getReplies(repliesForm); WebSocketService.Instance.client.getReplies(repliesForm);
let userMentionsForm: GetUserMentionsForm = { let userMentionsForm: GetUserMentions = {
sort: this.state.sort, sort: this.state.sort,
unread_only: this.state.unreadOrAll == UnreadOrAll.Unread, unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.getUserMentions(userMentionsForm); WebSocketService.Instance.client.getUserMentions(userMentionsForm);
let privateMessagesForm: GetPrivateMessagesForm = { let privateMessagesForm: GetPrivateMessages = {
unread_only: this.state.unreadOrAll == UnreadOrAll.Unread, unread_only: this.state.unreadOrAll == UnreadOrAll.Unread,
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.getPrivateMessages(privateMessagesForm); WebSocketService.Instance.client.getPrivateMessages(privateMessagesForm);
} }
handleSortChange(val: SortType) { handleSortChange(val: SortType) {
@ -475,7 +528,9 @@ export class Inbox extends Component<any, InboxState> {
} }
markAllAsRead(i: Inbox) { markAllAsRead(i: Inbox) {
WebSocketService.Instance.markAllAsRead(); WebSocketService.Instance.client.markAllAsRead({
auth: UserService.Instance.authField(),
});
i.state.replies = []; i.state.replies = [];
i.state.mentions = []; i.state.mentions = [];
i.state.messages = []; i.state.messages = [];
@ -484,148 +539,182 @@ export class Inbox extends Component<any, InboxState> {
i.setState(i.state); i.setState(i.state);
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
return; return;
} else if (msg.reconnect) { } else if (msg.reconnect) {
this.refetch(); this.refetch();
} else if (res.op == UserOperation.GetReplies) { } else if (op == UserOperation.GetReplies) {
let data = res.data as GetRepliesResponse; let data = wsJsonToRes<GetRepliesResponse>(msg).data;
this.state.replies = data.replies; this.state.replies = data.replies;
this.state.loading = false; this.state.loading = false;
this.sendUnreadCount(); this.sendUnreadCount();
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.GetUserMentions) { } else if (op == UserOperation.GetUserMentions) {
let data = res.data as GetUserMentionsResponse; let data = wsJsonToRes<GetUserMentionsResponse>(msg).data;
this.state.mentions = data.mentions; this.state.mentions = data.mentions;
this.sendUnreadCount(); this.sendUnreadCount();
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.GetPrivateMessages) { } else if (op == UserOperation.GetPrivateMessages) {
let data = res.data as PrivateMessagesResponse; let data = wsJsonToRes<PrivateMessagesResponse>(msg).data;
this.state.messages = data.messages; this.state.messages = data.private_messages;
this.sendUnreadCount(); this.sendUnreadCount();
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.EditPrivateMessage) { } else if (op == UserOperation.EditPrivateMessage) {
let data = res.data as PrivateMessageResponse; let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
let found: PrivateMessageI = this.state.messages.find( let found: PrivateMessageView = this.state.messages.find(
m => m.id === data.message.id m =>
m.private_message.id === data.private_message_view.private_message.id
); );
if (found) { if (found) {
found.content = data.message.content; found.private_message.content =
found.updated = data.message.updated; data.private_message_view.private_message.content;
found.private_message.updated =
data.private_message_view.private_message.updated;
} }
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.DeletePrivateMessage) { } else if (op == UserOperation.DeletePrivateMessage) {
let data = res.data as PrivateMessageResponse; let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
let found: PrivateMessageI = this.state.messages.find( let found: PrivateMessageView = this.state.messages.find(
m => m.id === data.message.id m =>
m.private_message.id === data.private_message_view.private_message.id
); );
if (found) { if (found) {
found.deleted = data.message.deleted; found.private_message.deleted =
found.updated = data.message.updated; data.private_message_view.private_message.deleted;
found.private_message.updated =
data.private_message_view.private_message.updated;
} }
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.MarkPrivateMessageAsRead) { } else if (op == UserOperation.MarkPrivateMessageAsRead) {
let data = res.data as PrivateMessageResponse; let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
let found: PrivateMessageI = this.state.messages.find( let found: PrivateMessageView = this.state.messages.find(
m => m.id === data.message.id m =>
m.private_message.id === data.private_message_view.private_message.id
); );
if (found) { if (found) {
found.updated = data.message.updated; found.private_message.updated =
data.private_message_view.private_message.updated;
// If youre in the unread view, just remove it from the list // If youre in the unread view, just remove it from the list
if (this.state.unreadOrAll == UnreadOrAll.Unread && data.message.read) { if (
this.state.unreadOrAll == UnreadOrAll.Unread &&
data.private_message_view.private_message.read
) {
this.state.messages = this.state.messages.filter( this.state.messages = this.state.messages.filter(
r => r.id !== data.message.id r =>
r.private_message.id !==
data.private_message_view.private_message.id
); );
} else { } else {
let found = this.state.messages.find(c => c.id == data.message.id); let found = this.state.messages.find(
found.read = data.message.read; c =>
c.private_message.id ==
data.private_message_view.private_message.id
);
found.private_message.read =
data.private_message_view.private_message.read;
} }
} }
this.sendUnreadCount(); this.sendUnreadCount();
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.MarkAllAsRead) { } else if (op == UserOperation.MarkAllAsRead) {
// Moved to be instant // Moved to be instant
} else if ( } else if (
res.op == UserOperation.EditComment || op == UserOperation.EditComment ||
res.op == UserOperation.DeleteComment || op == UserOperation.DeleteComment ||
res.op == UserOperation.RemoveComment op == UserOperation.RemoveComment
) { ) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
editCommentRes(data, this.state.replies); editCommentRes(data.comment_view, this.state.replies);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.MarkCommentAsRead) { } else if (op == UserOperation.MarkCommentAsRead) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
// If youre in the unread view, just remove it from the list // If youre in the unread view, just remove it from the list
if (this.state.unreadOrAll == UnreadOrAll.Unread && data.comment.read) { if (
this.state.unreadOrAll == UnreadOrAll.Unread &&
data.comment_view.comment.read
) {
this.state.replies = this.state.replies.filter( this.state.replies = this.state.replies.filter(
r => r.id !== data.comment.id r => r.comment.id !== data.comment_view.comment.id
); );
} else { } else {
let found = this.state.replies.find(c => c.id == data.comment.id); let found = this.state.replies.find(
found.read = data.comment.read; c => c.comment.id == data.comment_view.comment.id
);
found.comment.read = data.comment_view.comment.read;
} }
this.sendUnreadCount(); this.sendUnreadCount();
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.MarkUserMentionAsRead) { } else if (op == UserOperation.MarkUserMentionAsRead) {
let data = res.data as UserMentionResponse; let data = wsJsonToRes<UserMentionResponse>(msg).data;
let found = this.state.mentions.find(c => c.id == data.mention.id); // TODO this might not be correct, it might need to use the comment id
found.content = data.mention.content; let found = this.state.mentions.find(
found.updated = data.mention.updated; c => c.user_mention.id == data.user_mention_view.user_mention.id
found.removed = data.mention.removed; );
found.deleted = data.mention.deleted; found.comment.content = data.user_mention_view.comment.content;
found.upvotes = data.mention.upvotes; found.comment.updated = data.user_mention_view.comment.updated;
found.downvotes = data.mention.downvotes; found.comment.removed = data.user_mention_view.comment.removed;
found.score = data.mention.score; found.comment.deleted = data.user_mention_view.comment.deleted;
found.counts.upvotes = data.user_mention_view.counts.upvotes;
found.counts.downvotes = data.user_mention_view.counts.downvotes;
found.counts.score = data.user_mention_view.counts.score;
// If youre in the unread view, just remove it from the list // If youre in the unread view, just remove it from the list
if (this.state.unreadOrAll == UnreadOrAll.Unread && data.mention.read) { if (
this.state.unreadOrAll == UnreadOrAll.Unread &&
data.user_mention_view.user_mention.read
) {
this.state.mentions = this.state.mentions.filter( this.state.mentions = this.state.mentions.filter(
r => r.id !== data.mention.id r => r.user_mention.id !== data.user_mention_view.user_mention.id
); );
} else { } else {
let found = this.state.mentions.find(c => c.id == data.mention.id); let found = this.state.mentions.find(
found.read = data.mention.read; c => c.user_mention.id == data.user_mention_view.user_mention.id
);
// TODO test to make sure these mentions are getting marked as read
found.user_mention.read = data.user_mention_view.user_mention.read;
} }
this.sendUnreadCount(); this.sendUnreadCount();
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateComment) { } else if (op == UserOperation.CreateComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
if (data.recipient_ids.includes(UserService.Instance.user.id)) { if (data.recipient_ids.includes(UserService.Instance.user.id)) {
this.state.replies.unshift(data.comment); this.state.replies.unshift(data.comment_view);
this.setState(this.state); this.setState(this.state);
} else if (data.comment.creator_id == UserService.Instance.user.id) { } else if (data.comment_view.creator.id == UserService.Instance.user.id) {
// TODO this seems wrong, you should be using form_id
toast(i18n.t('reply_sent')); toast(i18n.t('reply_sent'));
} }
} else if (res.op == UserOperation.CreatePrivateMessage) { } else if (op == UserOperation.CreatePrivateMessage) {
let data = res.data as PrivateMessageResponse; let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
if (data.message.recipient_id == UserService.Instance.user.id) { if (
this.state.messages.unshift(data.message); data.private_message_view.recipient.id == UserService.Instance.user.id
) {
this.state.messages.unshift(data.private_message_view);
this.setState(this.state); this.setState(this.state);
} }
} else if (res.op == UserOperation.SaveComment) { } else if (op == UserOperation.SaveComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
saveCommentRes(data, this.state.replies); saveCommentRes(data.comment_view, this.state.replies);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.CreateCommentLike) { } else if (op == UserOperation.CreateCommentLike) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data, this.state.replies); createCommentLikeRes(data.comment_view, this.state.replies);
this.setState(this.state); this.setState(this.state);
} }
} }
@ -636,13 +725,14 @@ export class Inbox extends Component<any, InboxState> {
unreadCount(): number { unreadCount(): number {
return ( return (
this.state.replies.filter(r => !r.read).length + this.state.replies.filter(r => !r.comment.read).length +
this.state.mentions.filter(r => !r.read).length + this.state.mentions.filter(r => !r.user_mention.read).length +
this.state.messages.filter( this.state.messages.filter(
r => r =>
UserService.Instance.user && UserService.Instance.user &&
!r.read && !r.private_message.read &&
r.creator_id !== UserService.Instance.user.id // TODO also seems very strang and wrong
r.creator.id !== UserService.Instance.user.id
).length ).length
); );
} }

View file

@ -11,7 +11,7 @@ interface InstancesState {
export class Instances extends Component<any, InstancesState> { export class Instances extends Component<any, InstancesState> {
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private emptyState: InstancesState = { private emptyState: InstancesState = {
siteRes: this.isoData.site, siteRes: this.isoData.site_res,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -20,7 +20,7 @@ export class Instances extends Component<any, InstancesState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t('instances')} - ${this.state.siteRes.site.name}`; return `${i18n.t('instances')} - ${this.state.siteRes.site_view.site.name}`;
} }
render() { render() {

View file

@ -1,15 +1,14 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
LoginForm, Login as LoginForm,
RegisterForm, Register,
LoginResponse, LoginResponse,
UserOperation, UserOperation,
PasswordResetForm, PasswordReset,
GetSiteResponse, GetSiteResponse,
GetCaptchaResponse, GetCaptchaResponse,
WebSocketJsonResponse, SiteView,
Site,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { import {
@ -19,18 +18,19 @@ import {
wsSubscribe, wsSubscribe,
isBrowser, isBrowser,
setIsoData, setIsoData,
wsUserOp,
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
interface State { interface State {
loginForm: LoginForm; loginForm: LoginForm;
registerForm: RegisterForm; registerForm: Register;
loginLoading: boolean; loginLoading: boolean;
registerLoading: boolean; registerLoading: boolean;
captcha: GetCaptchaResponse; captcha: GetCaptchaResponse;
captchaPlaying: boolean; captchaPlaying: boolean;
site: Site; site_view: SiteView;
} }
export class Login extends Component<any, State> { export class Login extends Component<any, State> {
@ -55,7 +55,7 @@ export class Login extends Component<any, State> {
registerLoading: false, registerLoading: false,
captcha: undefined, captcha: undefined,
captchaPlaying: false, captchaPlaying: false,
site: this.isoData.site.site, site_view: this.isoData.site_res.site_view,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -67,7 +67,7 @@ export class Login extends Component<any, State> {
this.subscription = wsSubscribe(this.parseMessage); this.subscription = wsSubscribe(this.parseMessage);
if (isBrowser()) { if (isBrowser()) {
WebSocketService.Instance.getCaptcha(); WebSocketService.Instance.client.getCaptcha();
} }
} }
@ -78,7 +78,7 @@ export class Login extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t('login')} - ${this.state.site.name}`; return `${i18n.t('login')} - ${this.state.site_view.site.name}`;
} }
render() { render() {
@ -280,7 +280,7 @@ export class Login extends Component<any, State> {
</div> </div>
</div> </div>
)} )}
{this.state.site.enable_nsfw && ( {this.state.site_view.site.enable_nsfw && (
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-10"> <div class="col-sm-10">
<div class="form-check"> <div class="form-check">
@ -349,7 +349,7 @@ export class Login extends Component<any, State> {
event.preventDefault(); event.preventDefault();
i.state.loginLoading = true; i.state.loginLoading = true;
i.setState(i.state); i.setState(i.state);
WebSocketService.Instance.login(i.state.loginForm); WebSocketService.Instance.client.login(i.state.loginForm);
} }
handleLoginUsernameChange(i: Login, event: any) { handleLoginUsernameChange(i: Login, event: any) {
@ -366,7 +366,7 @@ export class Login extends Component<any, State> {
event.preventDefault(); event.preventDefault();
i.state.registerLoading = true; i.state.registerLoading = true;
i.setState(i.state); i.setState(i.state);
WebSocketService.Instance.register(i.state.registerForm); WebSocketService.Instance.client.register(i.state.registerForm);
} }
handleRegisterUsernameChange(i: Login, event: any) { handleRegisterUsernameChange(i: Login, event: any) {
@ -404,15 +404,15 @@ export class Login extends Component<any, State> {
handleRegenCaptcha(_i: Login, event: any) { handleRegenCaptcha(_i: Login, event: any) {
event.preventDefault(); event.preventDefault();
WebSocketService.Instance.getCaptcha(); WebSocketService.Instance.client.getCaptcha();
} }
handlePasswordReset(i: Login, event: any) { handlePasswordReset(i: Login, event: any) {
event.preventDefault(); event.preventDefault();
let resetForm: PasswordResetForm = { let resetForm: PasswordReset = {
email: i.state.loginForm.username_or_email, email: i.state.loginForm.username_or_email,
}; };
WebSocketService.Instance.passwordReset(resetForm); WebSocketService.Instance.client.passwordReset(resetForm);
} }
handleCaptchaPlay(i: Login, event: any) { handleCaptchaPlay(i: Login, event: any) {
@ -432,44 +432,48 @@ export class Login extends Component<any, State> {
return `data:image/png;base64,${this.state.captcha.ok.png}`; return `data:image/png;base64,${this.state.captcha.ok.png}`;
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.state = this.emptyState; this.state = this.emptyState;
this.state.registerForm.captcha_answer = undefined; this.state.registerForm.captcha_answer = undefined;
// Refetch another captcha // Refetch another captcha
WebSocketService.Instance.getCaptcha(); WebSocketService.Instance.client.getCaptcha();
this.setState(this.state); this.setState(this.state);
return; return;
} else { } else {
if (res.op == UserOperation.Login) { if (op == UserOperation.Login) {
let data = res.data as LoginResponse; let data = wsJsonToRes<LoginResponse>(msg).data;
this.state = this.emptyState; this.state = this.emptyState;
this.setState(this.state); this.setState(this.state);
UserService.Instance.login(data); UserService.Instance.login(data);
WebSocketService.Instance.userJoin(); WebSocketService.Instance.client.userJoin({
auth: UserService.Instance.authField(),
});
toast(i18n.t('logged_in')); toast(i18n.t('logged_in'));
this.props.history.push('/'); this.props.history.push('/');
} else if (res.op == UserOperation.Register) { } else if (op == UserOperation.Register) {
let data = res.data as LoginResponse; let data = wsJsonToRes<LoginResponse>(msg).data;
this.state = this.emptyState; this.state = this.emptyState;
this.setState(this.state); this.setState(this.state);
UserService.Instance.login(data); UserService.Instance.login(data);
WebSocketService.Instance.userJoin(); WebSocketService.Instance.client.userJoin({
auth: UserService.Instance.authField(),
});
this.props.history.push('/communities'); this.props.history.push('/communities');
} else if (res.op == UserOperation.GetCaptcha) { } else if (op == UserOperation.GetCaptcha) {
let data = res.data as GetCaptchaResponse; let data = wsJsonToRes<GetCaptchaResponse>(msg).data;
if (data.ok) { if (data.ok) {
this.state.captcha = data; this.state.captcha = data;
this.state.registerForm.captcha_uuid = data.ok.uuid; this.state.registerForm.captcha_uuid = data.ok.uuid;
this.setState(this.state); this.setState(this.state);
} }
} else if (res.op == UserOperation.PasswordReset) { } else if (op == UserOperation.PasswordReset) {
toast(i18n.t('reset_password_mail_sent')); toast(i18n.t('reset_password_mail_sent'));
} else if (res.op == UserOperation.GetSite) { } else if (op == UserOperation.GetSite) {
let data = res.data as GetSiteResponse; let data = wsJsonToRes<GetSiteResponse>(msg).data;
this.state.site = data.site; this.state.site_view = data.site_view;
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -3,26 +3,25 @@ import { Link } from 'inferno-router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
UserOperation, UserOperation,
CommunityUser, CommunityFollowerView,
GetFollowedCommunitiesResponse, GetFollowedCommunitiesResponse,
ListCommunitiesForm, ListCommunities,
ListCommunitiesResponse, ListCommunitiesResponse,
Community, CommunityView,
SortType, SortType,
GetSiteResponse, GetSiteResponse,
ListingType, ListingType,
SiteResponse, SiteResponse,
GetPostsResponse, GetPostsResponse,
PostResponse, PostResponse,
Post, PostView,
GetPostsForm, GetPosts,
Comment, CommentView,
GetCommentsForm, GetComments,
GetCommentsResponse, GetCommentsResponse,
CommentResponse, CommentResponse,
AddAdminResponse, AddAdminResponse,
BanUserResponse, BanUserResponse,
WebSocketJsonResponse,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { DataType, InitialFetchRequest } from '../interfaces'; import { DataType, InitialFetchRequest } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
@ -55,20 +54,20 @@ import {
setIsoData, setIsoData,
wsSubscribe, wsSubscribe,
isBrowser, isBrowser,
setAuth, wsUserOp,
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
interface MainState { interface MainState {
subscribedCommunities: CommunityUser[]; subscribedCommunities: CommunityFollowerView[];
trendingCommunities: Community[]; trendingCommunities: CommunityView[];
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
showEditSite: boolean; showEditSite: boolean;
loading: boolean; loading: boolean;
posts: Post[]; posts: PostView[];
comments: Comment[]; comments: CommentView[];
listingType: ListingType; listingType: ListingType;
dataType: DataType; dataType: DataType;
sort: SortType; sort: SortType;
@ -95,7 +94,7 @@ export class Main extends Component<any, MainState> {
private emptyState: MainState = { private emptyState: MainState = {
subscribedCommunities: [], subscribedCommunities: [],
trendingCommunities: [], trendingCommunities: [],
siteRes: this.isoData.site, siteRes: this.isoData.site_res,
showEditSite: false, showEditSite: false,
loading: true, loading: true,
posts: [], posts: [],
@ -134,7 +133,9 @@ export class Main extends Component<any, MainState> {
this.fetchTrendingCommunities(); this.fetchTrendingCommunities();
this.fetchData(); this.fetchData();
if (UserService.Instance.user) { if (UserService.Instance.user) {
WebSocketService.Instance.getFollowedCommunities(); WebSocketService.Instance.client.getFollowedCommunities({
auth: UserService.Instance.authField(),
});
} }
} }
@ -142,20 +143,21 @@ export class Main extends Component<any, MainState> {
} }
fetchTrendingCommunities() { fetchTrendingCommunities() {
let listCommunitiesForm: ListCommunitiesForm = { let listCommunitiesForm: ListCommunities = {
sort: SortType.Hot, sort: SortType.Hot,
limit: 6, limit: 6,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.listCommunities(listCommunitiesForm); WebSocketService.Instance.client.listCommunities(listCommunitiesForm);
} }
componentDidMount() { componentDidMount() {
// This means it hasn't been set up yet // This means it hasn't been set up yet
if (!this.state.siteRes.site) { if (!this.state.siteRes.site_view) {
this.context.router.history.push('/setup'); this.context.router.history.push('/setup');
} }
WebSocketService.Instance.communityJoin({ community_id: 0 }); WebSocketService.Instance.client.communityJoin({ community_id: 0 });
} }
componentWillUnmount() { componentWillUnmount() {
@ -199,26 +201,26 @@ export class Main extends Component<any, MainState> {
let promises: Promise<any>[] = []; let promises: Promise<any>[] = [];
if (dataType == DataType.Post) { if (dataType == DataType.Post) {
let getPostsForm: GetPostsForm = { let getPostsForm: GetPosts = {
page, page,
limit: fetchLimit, limit: fetchLimit,
sort, sort,
type_, type_,
auth: req.auth,
}; };
setAuth(getPostsForm, req.auth);
promises.push(req.client.getPosts(getPostsForm)); promises.push(req.client.getPosts(getPostsForm));
} else { } else {
let getCommentsForm: GetCommentsForm = { let getCommentsForm: GetComments = {
page, page,
limit: fetchLimit, limit: fetchLimit,
sort, sort,
type_, type_,
auth: req.auth,
}; };
setAuth(getCommentsForm, req.auth);
promises.push(req.client.getComments(getCommentsForm)); promises.push(req.client.getComments(getCommentsForm));
} }
let trendingCommunitiesForm: ListCommunitiesForm = { let trendingCommunitiesForm: ListCommunities = {
sort: SortType.Hot, sort: SortType.Hot,
limit: 6, limit: 6,
}; };
@ -245,7 +247,9 @@ export class Main extends Component<any, MainState> {
get documentTitle(): string { get documentTitle(): string {
return `${ return `${
this.state.siteRes.site ? this.state.siteRes.site.name : 'Lemmy' this.state.siteRes.site_view
? this.state.siteRes.site_view.site.name
: 'Lemmy'
}`; }`;
} }
@ -256,7 +260,7 @@ export class Main extends Component<any, MainState> {
title={this.documentTitle} title={this.documentTitle}
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
{this.state.siteRes.site && ( {this.state.siteRes.site_view.site && (
<div class="row"> <div class="row">
<main role="main" class="col-12 col-md-8"> <main role="main" class="col-12 col-md-8">
{this.posts()} {this.posts()}
@ -316,9 +320,9 @@ export class Main extends Component<any, MainState> {
</T> </T>
</h5> </h5>
<ul class="list-inline"> <ul class="list-inline">
{this.state.trendingCommunities.map(community => ( {this.state.trendingCommunities.map(cv => (
<li class="list-inline-item d-inline"> <li class="list-inline-item d-inline">
<CommunityLink community={community} /> <CommunityLink community={cv.community} />
</li> </li>
))} ))}
</ul> </ul>
@ -338,17 +342,9 @@ export class Main extends Component<any, MainState> {
</T> </T>
</h5> </h5>
<ul class="list-inline mb-0"> <ul class="list-inline mb-0">
{this.state.subscribedCommunities.map(community => ( {this.state.subscribedCommunities.map(cfv => (
<li class="list-inline-item d-inline"> <li class="list-inline-item d-inline">
<CommunityLink <CommunityLink community={cfv.community} />
community={{
name: community.community_name,
id: community.community_id,
local: community.community_local,
actor_id: community.community_actor_id,
icon: community.community_icon,
}}
/>
</li> </li>
))} ))}
</ul> </ul>
@ -357,6 +353,7 @@ export class Main extends Component<any, MainState> {
} }
sidebar() { sidebar() {
let site = this.state.siteRes.site_view.site;
return ( return (
<div> <div>
{!this.state.showEditSite ? ( {!this.state.showEditSite ? (
@ -365,14 +362,11 @@ export class Main extends Component<any, MainState> {
{this.siteName()} {this.siteName()}
{this.adminButtons()} {this.adminButtons()}
</div> </div>
<BannerIconHeader banner={this.state.siteRes.site.banner} /> <BannerIconHeader banner={site.banner} />
{this.siteInfo()} {this.siteInfo()}
</div> </div>
) : ( ) : (
<SiteForm <SiteForm site={site} onCancel={this.handleEditCancel} />
site={this.state.siteRes.site}
onCancel={this.handleEditCancel}
/>
)} )}
</div> </div>
); );
@ -391,7 +385,8 @@ export class Main extends Component<any, MainState> {
siteInfo() { siteInfo() {
return ( return (
<div> <div>
{this.state.siteRes.site.description && this.siteDescription()} {this.state.siteRes.site_view.site.description &&
this.siteDescription()}
{this.badges()} {this.badges()}
{this.admins()} {this.admins()}
</div> </div>
@ -406,18 +401,9 @@ export class Main extends Component<any, MainState> {
return ( return (
<ul class="mt-1 list-inline small mb-0"> <ul class="mt-1 list-inline small mb-0">
<li class="list-inline-item">{i18n.t('admins')}:</li> <li class="list-inline-item">{i18n.t('admins')}:</li>
{this.state.siteRes.admins.map(admin => ( {this.state.siteRes.admins.map(av => (
<li class="list-inline-item"> <li class="list-inline-item">
<UserListing <UserListing user={av.user} />
user={{
name: admin.name,
preferred_username: admin.preferred_username,
avatar: admin.avatar,
local: admin.local,
actor_id: admin.actor_id,
id: admin.id,
}}
/>
</li> </li>
))} ))}
</ul> </ul>
@ -425,6 +411,7 @@ export class Main extends Component<any, MainState> {
} }
badges() { badges() {
let site_view = this.state.siteRes.site_view;
return ( return (
<ul class="my-2 list-inline"> <ul class="my-2 list-inline">
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
@ -432,22 +419,22 @@ export class Main extends Component<any, MainState> {
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
{i18n.t('number_of_users', { {i18n.t('number_of_users', {
count: this.state.siteRes.site.number_of_users, count: site_view.counts.users,
})} })}
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
{i18n.t('number_of_communities', { {i18n.t('number_of_communities', {
count: this.state.siteRes.site.number_of_communities, count: site_view.counts.communities,
})} })}
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
{i18n.t('number_of_posts', { {i18n.t('number_of_posts', {
count: this.state.siteRes.site.number_of_posts, count: site_view.counts.posts,
})} })}
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
{i18n.t('number_of_comments', { {i18n.t('number_of_comments', {
count: this.state.siteRes.site.number_of_comments, count: site_view.counts.comments,
})} })}
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@ -483,7 +470,9 @@ export class Main extends Component<any, MainState> {
return ( return (
<div <div
className="md-div" className="md-div"
dangerouslySetInnerHTML={mdToHtml(this.state.siteRes.site.description)} dangerouslySetInnerHTML={mdToHtml(
this.state.siteRes.site_view.site.description
)}
/> />
); );
} }
@ -509,14 +498,15 @@ export class Main extends Component<any, MainState> {
} }
listings() { listings() {
let site = this.state.siteRes.site_view.site;
return this.state.dataType == DataType.Post ? ( return this.state.dataType == DataType.Post ? (
<PostListings <PostListings
posts={this.state.posts} posts={this.state.posts}
showCommunity showCommunity
removeDuplicates removeDuplicates
sort={this.state.sort} sort={this.state.sort}
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={site.enable_downvotes}
enableNsfw={this.state.siteRes.site.enable_nsfw} enableNsfw={site.enable_nsfw}
/> />
) : ( ) : (
<CommentNodes <CommentNodes
@ -525,7 +515,7 @@ export class Main extends Component<any, MainState> {
showCommunity showCommunity
sortType={this.state.sort} sortType={this.state.sort}
showContext showContext
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={site.enable_downvotes}
/> />
); );
} }
@ -615,8 +605,8 @@ export class Main extends Component<any, MainState> {
get showLocal(): boolean { get showLocal(): boolean {
return ( return (
this.isoData.site.federated_instances !== null && this.isoData.site_res.federated_instances !== null &&
this.isoData.site.federated_instances.length > 0 this.isoData.site_res.federated_instances.length > 0
); );
} }
@ -624,7 +614,7 @@ export class Main extends Component<any, MainState> {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
this.state.siteRes.admins this.state.siteRes.admins
.map(a => a.id) .map(a => a.user.id)
.includes(UserService.Instance.user.id) .includes(UserService.Instance.user.id)
); );
} }
@ -666,69 +656,70 @@ export class Main extends Component<any, MainState> {
fetchData() { fetchData() {
if (this.state.dataType == DataType.Post) { if (this.state.dataType == DataType.Post) {
let getPostsForm: GetPostsForm = { let getPostsForm: GetPosts = {
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
sort: this.state.sort, sort: this.state.sort,
type_: this.state.listingType, type_: this.state.listingType,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.getPosts(getPostsForm); WebSocketService.Instance.client.getPosts(getPostsForm);
} else { } else {
let getCommentsForm: GetCommentsForm = { let getCommentsForm: GetComments = {
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
sort: this.state.sort, sort: this.state.sort,
type_: this.state.listingType, type_: this.state.listingType,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.getComments(getCommentsForm); WebSocketService.Instance.client.getComments(getCommentsForm);
} }
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
return; return;
} else if (msg.reconnect) { } else if (msg.reconnect) {
WebSocketService.Instance.communityJoin({ community_id: 0 }); WebSocketService.Instance.client.communityJoin({ community_id: 0 });
this.fetchData(); this.fetchData();
} else if (res.op == UserOperation.GetFollowedCommunities) { } else if (op == UserOperation.GetFollowedCommunities) {
let data = res.data as GetFollowedCommunitiesResponse; let data = wsJsonToRes<GetFollowedCommunitiesResponse>(msg).data;
this.state.subscribedCommunities = data.communities; this.state.subscribedCommunities = data.communities;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.ListCommunities) { } else if (op == UserOperation.ListCommunities) {
let data = res.data as ListCommunitiesResponse; let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
this.state.trendingCommunities = data.communities; this.state.trendingCommunities = data.communities;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.EditSite) { } else if (op == UserOperation.EditSite) {
let data = res.data as SiteResponse; let data = wsJsonToRes<SiteResponse>(msg).data;
this.state.siteRes.site = data.site; this.state.siteRes.site_view = data.site_view;
this.state.showEditSite = false; this.state.showEditSite = false;
this.setState(this.state); this.setState(this.state);
toast(i18n.t('site_saved')); toast(i18n.t('site_saved'));
} else if (res.op == UserOperation.GetPosts) { } else if (op == UserOperation.GetPosts) {
let data = res.data as GetPostsResponse; let data = wsJsonToRes<GetPostsResponse>(msg).data;
this.state.posts = data.posts; this.state.posts = data.posts;
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.CreatePost) { } else if (op == UserOperation.CreatePost) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
// 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 this.state.subscribedCommunities
.map(c => c.community_id) .map(c => c.community.id)
.includes(data.post.community_id) .includes(data.post_view.community.id)
) { ) {
this.state.posts.unshift(data.post); this.state.posts.unshift(data.post_view);
notifyPost(data.post, this.context.router); notifyPost(data.post_view, this.context.router);
} }
} else { } else {
// NSFW posts // NSFW posts
let nsfw = data.post.nsfw || data.post.community_nsfw; let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
// Don't push the post if its nsfw, and don't have that setting on // Don't push the post if its nsfw, and don't have that setting on
if ( if (
@ -737,63 +728,65 @@ export class Main extends Component<any, MainState> {
UserService.Instance.user && UserService.Instance.user &&
UserService.Instance.user.show_nsfw) UserService.Instance.user.show_nsfw)
) { ) {
this.state.posts.unshift(data.post); this.state.posts.unshift(data.post_view);
notifyPost(data.post, this.context.router); notifyPost(data.post_view, this.context.router);
} }
} }
this.setState(this.state); this.setState(this.state);
} else if ( } else if (
res.op == UserOperation.EditPost || op == UserOperation.EditPost ||
res.op == UserOperation.DeletePost || op == UserOperation.DeletePost ||
res.op == UserOperation.RemovePost || op == UserOperation.RemovePost ||
res.op == UserOperation.LockPost || op == UserOperation.LockPost ||
res.op == UserOperation.StickyPost || op == UserOperation.StickyPost ||
res.op == UserOperation.SavePost op == UserOperation.SavePost
) { ) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
editPostFindRes(data, this.state.posts); editPostFindRes(data.post_view, this.state.posts);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (op == UserOperation.CreatePostLike) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
createPostLikeFindRes(data, this.state.posts); createPostLikeFindRes(data.post_view, this.state.posts);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.AddAdmin) { } else if (op == UserOperation.AddAdmin) {
let data = res.data as AddAdminResponse; let data = wsJsonToRes<AddAdminResponse>(msg).data;
this.state.siteRes.admins = data.admins; this.state.siteRes.admins = data.admins;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.BanUser) { } else if (op == UserOperation.BanUser) {
let data = res.data as BanUserResponse; let data = wsJsonToRes<BanUserResponse>(msg).data;
let found = this.state.siteRes.banned.find(u => (u.id = data.user.id)); let found = this.state.siteRes.banned.find(
u => (u.user.id = data.user_view.user.id)
);
// Remove the banned if its found in the list, and the action is an unban // Remove the banned if its found in the list, and the action is an unban
if (found && !data.banned) { if (found && !data.banned) {
this.state.siteRes.banned = this.state.siteRes.banned.filter( this.state.siteRes.banned = this.state.siteRes.banned.filter(
i => i.id !== data.user.id i => i.user.id !== data.user_view.user.id
); );
} else { } else {
this.state.siteRes.banned.push(data.user); this.state.siteRes.banned.push(data.user_view);
} }
this.state.posts this.state.posts
.filter(p => p.creator_id == data.user.id) .filter(p => p.creator.id == data.user_view.user.id)
.forEach(p => (p.banned = data.banned)); .forEach(p => (p.creator.banned = data.banned));
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.GetComments) { } else if (op == UserOperation.GetComments) {
let data = res.data as GetCommentsResponse; let data = wsJsonToRes<GetCommentsResponse>(msg).data;
this.state.comments = data.comments; this.state.comments = data.comments;
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
} else if ( } else if (
res.op == UserOperation.EditComment || op == UserOperation.EditComment ||
res.op == UserOperation.DeleteComment || op == UserOperation.DeleteComment ||
res.op == UserOperation.RemoveComment op == UserOperation.RemoveComment
) { ) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
editCommentRes(data, this.state.comments); editCommentRes(data.comment_view, this.state.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateComment) { } else if (op == UserOperation.CreateComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
// Necessary since it might be a user reply // Necessary since it might be a user reply
if (data.recipient_ids.length == 0) { if (data.recipient_ids.length == 0) {
@ -801,23 +794,23 @@ export class Main extends Component<any, MainState> {
if (this.state.listingType == ListingType.Subscribed) { if (this.state.listingType == ListingType.Subscribed) {
if ( if (
this.state.subscribedCommunities this.state.subscribedCommunities
.map(c => c.community_id) .map(c => c.community.id)
.includes(data.comment.community_id) .includes(data.comment_view.community.id)
) { ) {
this.state.comments.unshift(data.comment); this.state.comments.unshift(data.comment_view);
} }
} else { } else {
this.state.comments.unshift(data.comment); this.state.comments.unshift(data.comment_view);
} }
this.setState(this.state); this.setState(this.state);
} }
} else if (res.op == UserOperation.SaveComment) { } else if (op == UserOperation.SaveComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
saveCommentRes(data, this.state.comments); saveCommentRes(data.comment_view, this.state.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateCommentLike) { } else if (op == UserOperation.CreateCommentLike) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data, this.state.comments); createCommentLikeRes(data.comment_view, this.state.comments);
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -3,51 +3,71 @@ import { Link } from 'inferno-router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
UserOperation, UserOperation,
GetModlogForm, GetModlog,
GetModlogResponse, GetModlogResponse,
ModRemovePost, SiteView,
ModLockPost, ModRemovePostView,
ModStickyPost, ModLockPostView,
ModRemoveComment, ModStickyPostView,
ModRemoveCommunity, ModRemoveCommentView,
ModBanFromCommunity, ModRemoveCommunityView,
ModBan, ModBanFromCommunityView,
ModAddCommunity, ModBanView,
ModAdd, ModAddCommunityView,
WebSocketJsonResponse, ModAddView,
Site,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { import {
wsJsonToRes, wsJsonToRes,
addTypeInfo,
fetchLimit, fetchLimit,
toast, toast,
setIsoData, setIsoData,
wsSubscribe, wsSubscribe,
isBrowser, isBrowser,
wsUserOp,
} from '../utils'; } from '../utils';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
import moment from 'moment'; import moment from 'moment';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { InitialFetchRequest } from 'shared/interfaces'; import { InitialFetchRequest } from 'shared/interfaces';
import { UserListing } from './user-listing';
import { CommunityLink } from './community-link';
enum ModlogEnum {
ModRemovePost,
ModLockPost,
ModStickyPost,
ModRemoveComment,
ModRemoveCommunity,
ModBanFromCommunity,
ModAddCommunity,
ModAdd,
ModBan,
}
type ModlogType = {
id: number;
type_: ModlogEnum;
view:
| ModRemovePostView
| ModLockPostView
| ModStickyPostView
| ModRemoveCommentView
| ModRemoveCommunityView
| ModBanFromCommunityView
| ModBanView
| ModAddCommunityView
| ModAddView;
when_: string;
};
interface ModlogState { interface ModlogState {
combined: { res: GetModlogResponse;
type_: string;
data:
| ModRemovePost
| ModLockPost
| ModStickyPost
| ModRemoveCommunity
| ModAdd
| ModBan;
}[];
communityId?: number; communityId?: number;
communityName?: string; communityName?: string;
page: number; page: number;
site: Site; site_view: SiteView;
loading: boolean; loading: boolean;
} }
@ -55,10 +75,20 @@ export class Modlog extends Component<any, ModlogState> {
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private subscription: Subscription; private subscription: Subscription;
private emptyState: ModlogState = { private emptyState: ModlogState = {
combined: [], res: {
removed_posts: [],
locked_posts: [],
stickied_posts: [],
removed_comments: [],
removed_communities: [],
banned_from_community: [],
banned: [],
added_to_community: [],
added: [],
},
page: 1, page: 1,
loading: true, loading: true,
site: this.isoData.site.site, site_view: this.isoData.site_res.site_view,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -75,7 +105,7 @@ export class Modlog extends Component<any, ModlogState> {
// Only fetch the data if coming from another route // Only fetch the data if coming from another route
if (this.isoData.path == this.context.router.route.match.url) { if (this.isoData.path == this.context.router.route.match.url) {
let data = this.isoData.routeData[0]; let data = this.isoData.routeData[0];
this.setCombined(data); this.state.res = data;
this.state.loading = false; this.state.loading = false;
} else { } else {
this.refetch(); this.refetch();
@ -88,264 +118,233 @@ export class Modlog extends Component<any, ModlogState> {
} }
} }
setCombined(res: GetModlogResponse) { buildCombined(res: GetModlogResponse): ModlogType[] {
let removed_posts = addTypeInfo(res.removed_posts, 'removed_posts'); let removed_posts: ModlogType[] = res.removed_posts.map(r => ({
let locked_posts = addTypeInfo(res.locked_posts, 'locked_posts'); id: r.mod_remove_post.id,
let stickied_posts = addTypeInfo(res.stickied_posts, 'stickied_posts'); type_: ModlogEnum.ModRemovePost,
let removed_comments = addTypeInfo( view: r,
res.removed_comments, when_: r.mod_remove_post.when_,
'removed_comments' }));
);
let removed_communities = addTypeInfo(
res.removed_communities,
'removed_communities'
);
let banned_from_community = addTypeInfo(
res.banned_from_community,
'banned_from_community'
);
let added_to_community = addTypeInfo(
res.added_to_community,
'added_to_community'
);
let added = addTypeInfo(res.added, 'added');
let banned = addTypeInfo(res.banned, 'banned');
this.state.combined = [];
this.state.combined.push(...removed_posts); let locked_posts: ModlogType[] = res.locked_posts.map(r => ({
this.state.combined.push(...locked_posts); id: r.mod_lock_post.id,
this.state.combined.push(...stickied_posts); type_: ModlogEnum.ModLockPost,
this.state.combined.push(...removed_comments); view: r,
this.state.combined.push(...removed_communities); when_: r.mod_lock_post.when_,
this.state.combined.push(...banned_from_community); }));
this.state.combined.push(...added_to_community);
this.state.combined.push(...added);
this.state.combined.push(...banned);
if (this.state.communityId && this.state.combined.length > 0) { let stickied_posts: ModlogType[] = res.stickied_posts.map(r => ({
this.state.communityName = (this.state.combined[0] id: r.mod_sticky_post.id,
.data as ModRemovePost).community_name; type_: ModlogEnum.ModStickyPost,
view: r,
when_: r.mod_sticky_post.when_,
}));
let removed_comments: ModlogType[] = res.removed_comments.map(r => ({
id: r.mod_remove_comment.id,
type_: ModlogEnum.ModRemoveComment,
view: r,
when_: r.mod_remove_comment.when_,
}));
let removed_communities: ModlogType[] = res.removed_communities.map(r => ({
id: r.mod_remove_community.id,
type_: ModlogEnum.ModRemoveCommunity,
view: r,
when_: r.mod_remove_community.when_,
}));
let banned_from_community: ModlogType[] = res.banned_from_community.map(
r => ({
id: r.mod_ban_from_community.id,
type_: ModlogEnum.ModBanFromCommunity,
view: r,
when_: r.mod_ban_from_community.when_,
})
);
let added_to_community: ModlogType[] = res.added_to_community.map(r => ({
id: r.mod_add_community.id,
type_: ModlogEnum.ModAddCommunity,
view: r,
when_: r.mod_add_community.when_,
}));
let added: ModlogType[] = res.added.map(r => ({
id: r.mod_add.id,
type_: ModlogEnum.ModAdd,
view: r,
when_: r.mod_add.when_,
}));
let banned: ModlogType[] = res.banned.map(r => ({
id: r.mod_ban.id,
type_: ModlogEnum.ModBan,
view: r,
when_: r.mod_ban.when_,
}));
let combined: ModlogType[] = [];
combined.push(...removed_posts);
combined.push(...locked_posts);
combined.push(...stickied_posts);
combined.push(...removed_comments);
combined.push(...removed_communities);
combined.push(...banned_from_community);
combined.push(...added_to_community);
combined.push(...added);
combined.push(...banned);
if (this.state.communityId && combined.length > 0) {
this.state.communityName = (combined[0]
.view as ModRemovePostView).community.name;
} }
// Sort them by time // Sort them by time
this.state.combined.sort((a, b) => combined.sort((a, b) => b.when_.localeCompare(a.when_));
b.data.when_.localeCompare(a.data.when_)
); return combined;
}
renderModlogType(i: ModlogType) {
switch (i.type_) {
case ModlogEnum.ModRemovePost:
let mrpv = i.view as ModRemovePostView;
return [
mrpv.mod_remove_post.removed ? 'Removed ' : 'Restored ',
<span>
Post <Link to={`/post/${mrpv.post.id}`}>{mrpv.post.name}</Link>
</span>,
mrpv.mod_remove_post.reason &&
` reason: ${mrpv.mod_remove_post.reason}`,
];
case ModlogEnum.ModLockPost:
let mlpv = i.view as ModLockPostView;
return [
mlpv.mod_lock_post.locked ? 'Locked ' : 'Unlocked ',
<span>
Post <Link to={`/post/${mlpv.post.id}`}>{mlpv.post.name}</Link>
</span>,
];
case ModlogEnum.ModStickyPost:
let mspv = i.view as ModStickyPostView;
return [
mspv.mod_sticky_post.stickied ? 'Stickied ' : 'Unstickied ',
<span>
Post <Link to={`/post/${mspv.post.id}`}>{mspv.post.name}</Link>
</span>,
];
case ModlogEnum.ModRemoveComment:
let mrc = i.view as ModRemoveCommentView;
return [
mrc.mod_remove_comment.removed ? 'Removed ' : 'Restored ',
<span>
Comment{' '}
<Link to={`/post/${mrc.post.id}/comment/${mrc.comment.id}`}>
{mrc.comment.content}
</Link>
</span>,
<span>
{' '}
by <UserListing user={mrc.commenter} />
</span>,
mrc.mod_remove_comment.reason &&
` reason: ${mrc.mod_remove_comment.reason}`,
];
case ModlogEnum.ModRemoveCommunity:
let mrco = i.view as ModRemoveCommunityView;
return [
mrco.mod_remove_community.removed ? 'Removed ' : 'Restored ',
<span>
Community <CommunityLink community={mrco.community} />
</span>,
mrco.mod_remove_community.reason &&
` reason: ${mrco.mod_remove_community.reason}`,
mrco.mod_remove_community.expires &&
` expires: ${moment
.utc(mrco.mod_remove_community.expires)
.fromNow()}`,
];
case ModlogEnum.ModBanFromCommunity:
let mbfc = i.view as ModBanFromCommunityView;
return [
<span>
{mbfc.mod_ban_from_community.banned ? 'Banned ' : 'Unbanned '}{' '}
</span>,
<span>
<UserListing user={mbfc.banned_user} />
</span>,
<span> from the community </span>,
<span>
<CommunityLink community={mbfc.community} />
</span>,
<div>
{mbfc.mod_ban_from_community.reason &&
` reason: ${mbfc.mod_ban_from_community.reason}`}
</div>,
<div>
{mbfc.mod_ban_from_community.expires &&
` expires: ${moment
.utc(mbfc.mod_ban_from_community.expires)
.fromNow()}`}
</div>,
];
case ModlogEnum.ModAddCommunity:
let mac = i.view as ModAddCommunityView;
return [
<span>
{mac.mod_add_community.removed ? 'Removed ' : 'Appointed '}{' '}
</span>,
<span>
<UserListing user={mac.modded_user} />
</span>,
<span> as a mod to the community </span>,
<span>
<CommunityLink community={mac.community} />
</span>,
];
case ModlogEnum.ModBan:
let mb = i.view as ModBanView;
return [
<span>{mb.mod_ban.banned ? 'Banned ' : 'Unbanned '} </span>,
<span>
<UserListing user={mb.banned_user} />
</span>,
<div>{mb.mod_ban.reason && ` reason: ${mb.mod_ban.reason}`}</div>,
<div>
{mb.mod_ban.expires &&
` expires: ${moment.utc(mb.mod_ban.expires).fromNow()}`}
</div>,
];
case ModlogEnum.ModAdd:
let ma = i.view as ModAddView;
return [
<span>{ma.mod_add.removed ? 'Removed ' : 'Appointed '} </span>,
<span>
<UserListing user={ma.modded_user} />
</span>,
<span> as an admin </span>,
];
default:
return <div />;
}
} }
combined() { combined() {
let combined = this.buildCombined(this.state.res);
return ( return (
<tbody> <tbody>
{this.state.combined.map(i => ( {combined.map(i => (
<tr> <tr>
<td> <td>
<MomentTime data={i.data} /> <MomentTime data={i} />
</td> </td>
<td> <td>
<Link to={`/u/${i.data.mod_user_name}`}> <UserListing user={i.view.moderator} />
{i.data.mod_user_name}
</Link>
</td>
<td>
{i.type_ == 'removed_posts' && (
<>
{(i.data as ModRemovePost).removed ? 'Removed' : 'Restored'}
<span>
{' '}
Post{' '}
<Link to={`/post/${(i.data as ModRemovePost).post_id}`}>
{(i.data as ModRemovePost).post_name}
</Link>
</span>
<div>
{(i.data as ModRemovePost).reason &&
` reason: ${(i.data as ModRemovePost).reason}`}
</div>
</>
)}
{i.type_ == 'locked_posts' && (
<>
{(i.data as ModLockPost).locked ? 'Locked' : 'Unlocked'}
<span>
{' '}
Post{' '}
<Link to={`/post/${(i.data as ModLockPost).post_id}`}>
{(i.data as ModLockPost).post_name}
</Link>
</span>
</>
)}
{i.type_ == 'stickied_posts' && (
<>
{(i.data as ModStickyPost).stickied
? 'Stickied'
: 'Unstickied'}
<span>
{' '}
Post{' '}
<Link to={`/post/${(i.data as ModStickyPost).post_id}`}>
{(i.data as ModStickyPost).post_name}
</Link>
</span>
</>
)}
{i.type_ == 'removed_comments' && (
<>
{(i.data as ModRemoveComment).removed
? 'Removed'
: 'Restored'}
<span>
{' '}
Comment{' '}
<Link
to={`/post/${
(i.data as ModRemoveComment).post_id
}/comment/${(i.data as ModRemoveComment).comment_id}`}
>
{(i.data as ModRemoveComment).comment_content}
</Link>
</span>
<span>
{' '}
by{' '}
<Link
to={`/u/${
(i.data as ModRemoveComment).comment_user_name
}`}
>
{(i.data as ModRemoveComment).comment_user_name}
</Link>
</span>
<div>
{(i.data as ModRemoveComment).reason &&
` reason: ${(i.data as ModRemoveComment).reason}`}
</div>
</>
)}
{i.type_ == 'removed_communities' && (
<>
{(i.data as ModRemoveCommunity).removed
? 'Removed'
: 'Restored'}
<span>
{' '}
Community{' '}
<Link
to={`/c/${(i.data as ModRemoveCommunity).community_name}`}
>
{(i.data as ModRemoveCommunity).community_name}
</Link>
</span>
<div>
{(i.data as ModRemoveCommunity).reason &&
` reason: ${(i.data as ModRemoveCommunity).reason}`}
</div>
<div>
{(i.data as ModRemoveCommunity).expires &&
` expires: ${moment
.utc((i.data as ModRemoveCommunity).expires)
.fromNow()}`}
</div>
</>
)}
{i.type_ == 'banned_from_community' && (
<>
<span>
{(i.data as ModBanFromCommunity).banned
? 'Banned '
: 'Unbanned '}{' '}
</span>
<span>
<Link
to={`/u/${
(i.data as ModBanFromCommunity).other_user_name
}`}
>
{(i.data as ModBanFromCommunity).other_user_name}
</Link>
</span>
<span> from the community </span>
<span>
<Link
to={`/c/${
(i.data as ModBanFromCommunity).community_name
}`}
>
{(i.data as ModBanFromCommunity).community_name}
</Link>
</span>
<div>
{(i.data as ModBanFromCommunity).reason &&
` reason: ${(i.data as ModBanFromCommunity).reason}`}
</div>
<div>
{(i.data as ModBanFromCommunity).expires &&
` expires: ${moment
.utc((i.data as ModBanFromCommunity).expires)
.fromNow()}`}
</div>
</>
)}
{i.type_ == 'added_to_community' && (
<>
<span>
{(i.data as ModAddCommunity).removed
? 'Removed '
: 'Appointed '}{' '}
</span>
<span>
<Link
to={`/u/${(i.data as ModAddCommunity).other_user_name}`}
>
{(i.data as ModAddCommunity).other_user_name}
</Link>
</span>
<span> as a mod to the community </span>
<span>
<Link
to={`/c/${(i.data as ModAddCommunity).community_name}`}
>
{(i.data as ModAddCommunity).community_name}
</Link>
</span>
</>
)}
{i.type_ == 'banned' && (
<>
<span>
{(i.data as ModBan).banned ? 'Banned ' : 'Unbanned '}{' '}
</span>
<span>
<Link to={`/u/${(i.data as ModBan).other_user_name}`}>
{(i.data as ModBan).other_user_name}
</Link>
</span>
<div>
{(i.data as ModBan).reason &&
` reason: ${(i.data as ModBan).reason}`}
</div>
<div>
{(i.data as ModBan).expires &&
` expires: ${moment
.utc((i.data as ModBan).expires)
.fromNow()}`}
</div>
</>
)}
{i.type_ == 'added' && (
<>
<span>
{(i.data as ModAdd).removed ? 'Removed ' : 'Appointed '}{' '}
</span>
<span>
<Link to={`/u/${(i.data as ModAdd).other_user_name}`}>
{(i.data as ModAdd).other_user_name}
</Link>
</span>
<span> as an admin </span>
</>
)}
</td> </td>
<td>{this.renderModlogType(i)}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@ -353,7 +352,7 @@ export class Modlog extends Component<any, ModlogState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `Modlog - ${this.state.site.name}`; return `Modlog - ${this.state.site_view.site.name}`;
} }
render() { render() {
@ -435,12 +434,12 @@ export class Modlog extends Component<any, ModlogState> {
} }
refetch() { refetch() {
let modlogForm: GetModlogForm = { let modlogForm: GetModlog = {
community_id: this.state.communityId, community_id: this.state.communityId,
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
}; };
WebSocketService.Instance.getModlog(modlogForm); WebSocketService.Instance.client.getModlog(modlogForm);
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
@ -448,7 +447,7 @@ export class Modlog extends Component<any, ModlogState> {
let communityId = pathSplit[3]; let communityId = pathSplit[3];
let promises: Promise<any>[] = []; let promises: Promise<any>[] = [];
let modlogForm: GetModlogForm = { let modlogForm: GetModlog = {
page: 1, page: 1,
limit: fetchLimit, limit: fetchLimit,
}; };
@ -461,17 +460,16 @@ export class Modlog extends Component<any, ModlogState> {
return promises; return promises;
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
return; return;
} else if (res.op == UserOperation.GetModlog) { } else if (op == UserOperation.GetModlog) {
let data = res.data as GetModlogResponse; let data = wsJsonToRes<GetModlogResponse>(msg).data;
this.state.loading = false; this.state.loading = false;
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setCombined(data); this.state.res = data;
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -4,19 +4,18 @@ import { Subscription } from 'rxjs';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { import {
UserOperation, UserOperation,
GetRepliesForm, GetReplies,
GetRepliesResponse, GetRepliesResponse,
GetUserMentionsForm, GetUserMentions,
GetUserMentionsResponse, GetUserMentionsResponse,
GetPrivateMessagesForm, GetPrivateMessages,
PrivateMessagesResponse, PrivateMessagesResponse,
SortType, SortType,
GetSiteResponse, GetSiteResponse,
Comment, CommentView,
CommentResponse, CommentResponse,
PrivateMessage,
PrivateMessageResponse, PrivateMessageResponse,
WebSocketJsonResponse, PrivateMessageView,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { import {
wsJsonToRes, wsJsonToRes,
@ -30,20 +29,21 @@ import {
isBrowser, isBrowser,
wsSubscribe, wsSubscribe,
supportLemmyUrl, supportLemmyUrl,
wsUserOp,
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { PictrsImage } from './pictrs-image'; import { PictrsImage } from './pictrs-image';
interface NavbarProps { interface NavbarProps {
site: GetSiteResponse; site_res: GetSiteResponse;
} }
interface NavbarState { interface NavbarState {
isLoggedIn: boolean; isLoggedIn: boolean;
expanded: boolean; expanded: boolean;
replies: Comment[]; replies: CommentView[];
mentions: Comment[]; mentions: CommentView[];
messages: PrivateMessage[]; messages: PrivateMessageView[];
unreadCount: number; unreadCount: number;
searchParam: string; searchParam: string;
toggleSearch: boolean; toggleSearch: boolean;
@ -56,7 +56,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
private unreadCountSub: Subscription; private unreadCountSub: Subscription;
private searchTextField: RefObject<HTMLInputElement>; private searchTextField: RefObject<HTMLInputElement>;
emptyState: NavbarState = { emptyState: NavbarState = {
isLoggedIn: !!this.props.site.my_user, isLoggedIn: !!this.props.site_res.my_user,
unreadCount: 0, unreadCount: 0,
replies: [], replies: [],
mentions: [], mentions: [],
@ -88,7 +88,9 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
// i18n.changeLanguage('de'); // i18n.changeLanguage('de');
} else { } else {
this.requestNotificationPermission(); this.requestNotificationPermission();
WebSocketService.Instance.userJoin(); WebSocketService.Instance.client.userJoin({
auth: UserService.Instance.authField(),
});
this.fetchUnreads(); this.fetchUnreads();
} }
@ -96,7 +98,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
// A login // A login
if (res !== undefined) { if (res !== undefined) {
this.requestNotificationPermission(); this.requestNotificationPermission();
WebSocketService.Instance.getSite(); WebSocketService.Instance.client.getSite();
} else { } else {
this.setState({ isLoggedIn: false }); this.setState({ isLoggedIn: false });
} }
@ -165,20 +167,23 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
// TODO class active corresponding to current page // TODO class active corresponding to current page
navbar() { navbar() {
let user = this.props.site.my_user; let user = this.props.site_res.my_user;
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.site && ( {this.props.site_res.site_view && (
<Link <Link
title={this.props.site.version} title={this.props.site_res.version}
className="d-flex align-items-center navbar-brand mr-md-3" className="d-flex align-items-center navbar-brand mr-md-3"
to="/" to="/"
> >
{this.props.site.site.icon && showAvatars() && ( {this.props.site_res.site_view.site.icon && showAvatars() && (
<PictrsImage src={this.props.site.site.icon} icon /> <PictrsImage
src={this.props.site_res.site_view.site.icon}
icon
/>
)} )}
{this.props.site.site.name} {this.props.site_res.site_view.site.name}
</Link> </Link>
)} )}
{this.state.isLoggedIn && ( {this.state.isLoggedIn && (
@ -362,8 +367,8 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
i.setState(i.state); i.setState(i.state);
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
if (msg.error == 'not_logged_in') { if (msg.error == 'not_logged_in') {
UserService.Instance.logout(); UserService.Instance.logout();
@ -371,62 +376,68 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
} }
return; return;
} else if (msg.reconnect) { } else if (msg.reconnect) {
WebSocketService.Instance.userJoin(); WebSocketService.Instance.client.userJoin({
auth: UserService.Instance.authField(),
});
this.fetchUnreads(); this.fetchUnreads();
} else if (res.op == UserOperation.GetReplies) { } else if (op == UserOperation.GetReplies) {
let data = res.data as GetRepliesResponse; let data = wsJsonToRes<GetRepliesResponse>(msg).data;
let unreadReplies = data.replies.filter(r => !r.read); let unreadReplies = data.replies.filter(r => !r.comment.read);
this.state.replies = unreadReplies; this.state.replies = unreadReplies;
this.state.unreadCount = this.calculateUnreadCount(); this.state.unreadCount = this.calculateUnreadCount();
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
} else if (res.op == UserOperation.GetUserMentions) { } else if (op == UserOperation.GetUserMentions) {
let data = res.data as GetUserMentionsResponse; let data = wsJsonToRes<GetUserMentionsResponse>(msg).data;
let unreadMentions = data.mentions.filter(r => !r.read); let unreadMentions = data.mentions.filter(r => !r.comment.read);
this.state.mentions = unreadMentions; this.state.mentions = unreadMentions;
this.state.unreadCount = this.calculateUnreadCount(); this.state.unreadCount = this.calculateUnreadCount();
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
} else if (res.op == UserOperation.GetPrivateMessages) { } else if (op == UserOperation.GetPrivateMessages) {
let data = res.data as PrivateMessagesResponse; let data = wsJsonToRes<PrivateMessagesResponse>(msg).data;
let unreadMessages = data.messages.filter(r => !r.read); let unreadMessages = data.private_messages.filter(
r => !r.private_message.read
);
this.state.messages = unreadMessages; this.state.messages = unreadMessages;
this.state.unreadCount = this.calculateUnreadCount(); this.state.unreadCount = this.calculateUnreadCount();
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
} else if (res.op == UserOperation.GetSite) { } else if (op == UserOperation.GetSite) {
// This is only called on a successful login // This is only called on a successful login
let data = res.data as GetSiteResponse; let data = wsJsonToRes<GetSiteResponse>(msg).data;
UserService.Instance.user = data.my_user; UserService.Instance.user = data.my_user;
setTheme(UserService.Instance.user.theme); setTheme(UserService.Instance.user.theme);
i18n.changeLanguage(getLanguage()); i18n.changeLanguage(getLanguage());
this.state.isLoggedIn = true; this.state.isLoggedIn = true;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateComment) { } else if (op == UserOperation.CreateComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
if (this.state.isLoggedIn) { if (this.state.isLoggedIn) {
if (data.recipient_ids.includes(UserService.Instance.user.id)) { if (data.recipient_ids.includes(UserService.Instance.user.id)) {
this.state.replies.push(data.comment); this.state.replies.push(data.comment_view);
this.state.unreadCount++; this.state.unreadCount++;
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
notifyComment(data.comment, this.context.router); notifyComment(data.comment_view, this.context.router);
} }
} }
} else if (res.op == UserOperation.CreatePrivateMessage) { } else if (op == UserOperation.CreatePrivateMessage) {
let data = res.data as PrivateMessageResponse; let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
if (this.state.isLoggedIn) { if (this.state.isLoggedIn) {
if (data.message.recipient_id == UserService.Instance.user.id) { if (
this.state.messages.push(data.message); data.private_message_view.recipient.id == UserService.Instance.user.id
) {
this.state.messages.push(data.private_message_view);
this.state.unreadCount++; this.state.unreadCount++;
this.setState(this.state); this.setState(this.state);
this.sendUnreadCount(); this.sendUnreadCount();
notifyPrivateMessage(data.message, this.context.router); notifyPrivateMessage(data.private_message_view, this.context.router);
} }
} }
} }
@ -434,30 +445,33 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
fetchUnreads() { fetchUnreads() {
console.log('Fetching unreads...'); console.log('Fetching unreads...');
let repliesForm: GetRepliesForm = { let repliesForm: GetReplies = {
sort: SortType.New, sort: SortType.New,
unread_only: true, unread_only: true,
page: 1, page: 1,
limit: fetchLimit, limit: fetchLimit,
auth: UserService.Instance.authField(),
}; };
let userMentionsForm: GetUserMentionsForm = { let userMentionsForm: GetUserMentions = {
sort: SortType.New, sort: SortType.New,
unread_only: true, unread_only: true,
page: 1, page: 1,
limit: fetchLimit, limit: fetchLimit,
auth: UserService.Instance.authField(),
}; };
let privateMessagesForm: GetPrivateMessagesForm = { let privateMessagesForm: GetPrivateMessages = {
unread_only: true, unread_only: true,
page: 1, page: 1,
limit: fetchLimit, limit: fetchLimit,
auth: UserService.Instance.authField(),
}; };
if (this.currentLocation !== '/inbox') { if (this.currentLocation !== '/inbox') {
WebSocketService.Instance.getReplies(repliesForm); WebSocketService.Instance.client.getReplies(repliesForm);
WebSocketService.Instance.getUserMentions(userMentionsForm); WebSocketService.Instance.client.getUserMentions(userMentionsForm);
WebSocketService.Instance.getPrivateMessages(privateMessagesForm); WebSocketService.Instance.client.getPrivateMessages(privateMessagesForm);
} }
} }
@ -471,17 +485,17 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
calculateUnreadCount(): number { calculateUnreadCount(): number {
return ( return (
this.state.replies.filter(r => !r.read).length + this.state.replies.filter(r => !r.comment.read).length +
this.state.mentions.filter(r => !r.read).length + this.state.mentions.filter(r => !r.comment.read).length +
this.state.messages.filter(r => !r.read).length this.state.messages.filter(r => !r.private_message.read).length
); );
} }
get canAdmin(): boolean { get canAdmin(): boolean {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
this.props.site.admins this.props.site_res.admins
.map(a => a.id) .map(a => a.user.id)
.includes(UserService.Instance.user.id) .includes(UserService.Instance.user.id)
); );
} }

View file

@ -3,9 +3,8 @@ import { Subscription } from 'rxjs';
import { import {
UserOperation, UserOperation,
LoginResponse, LoginResponse,
PasswordChangeForm, PasswordChange as PasswordChangeForm,
WebSocketJsonResponse, SiteView,
Site,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { import {
@ -15,6 +14,7 @@ import {
setIsoData, setIsoData,
isBrowser, isBrowser,
wsSubscribe, wsSubscribe,
wsUserOp,
} from '../utils'; } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
@ -22,7 +22,7 @@ import { HtmlTags } from './html-tags';
interface State { interface State {
passwordChangeForm: PasswordChangeForm; passwordChangeForm: PasswordChangeForm;
loading: boolean; loading: boolean;
site: Site; site_view: SiteView;
} }
export class PasswordChange extends Component<any, State> { export class PasswordChange extends Component<any, State> {
@ -36,7 +36,7 @@ export class PasswordChange extends Component<any, State> {
password_verify: undefined, password_verify: undefined,
}, },
loading: false, loading: false,
site: this.isoData.site.site, site_view: this.isoData.site_res.site_view,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -55,7 +55,7 @@ export class PasswordChange extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t('password_change')} - ${this.state.site.name}`; return `${i18n.t('password_change')} - ${this.state.site_view.site.name}`;
} }
render() { render() {
@ -138,18 +138,18 @@ export class PasswordChange extends Component<any, State> {
i.state.loading = true; i.state.loading = true;
i.setState(i.state); i.setState(i.state);
WebSocketService.Instance.passwordChange(i.state.passwordChangeForm); WebSocketService.Instance.client.passwordChange(i.state.passwordChangeForm);
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
return; return;
} else if (res.op == UserOperation.PasswordChange) { } else if (op == UserOperation.PasswordChange) {
let data = res.data as LoginResponse; let data = wsJsonToRes<LoginResponse>(msg).data;
this.state = this.emptyState; this.state = this.emptyState;
this.setState(this.state); this.setState(this.state);
UserService.Instance.login(data); UserService.Instance.login(data);

View file

@ -4,19 +4,19 @@ import { PostListings } from './post-listings';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
PostForm as PostFormI, CreatePost,
PostFormParams, EditPost,
Post, PostView,
PostResponse, PostResponse,
UserOperation, UserOperation,
Community, CommunityView,
SortType, SortType,
SearchForm, Search,
SearchType, SearchType,
SearchResponse, SearchResponse,
WebSocketJsonResponse,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { PostFormParams } from '../interfaces';
import { import {
wsJsonToRes, wsJsonToRes,
getPageTitle, getPageTitle,
@ -33,6 +33,7 @@ import {
validTitle, validTitle,
wsSubscribe, wsSubscribe,
isBrowser, isBrowser,
wsUserOp,
} from '../utils'; } from '../utils';
var Choices; var Choices;
@ -46,24 +47,24 @@ import { pictrsUri } from '../env';
const MAX_POST_TITLE_LENGTH = 200; const MAX_POST_TITLE_LENGTH = 200;
interface PostFormProps { interface PostFormProps {
post?: Post; // If a post is given, that means this is an edit post_view?: PostView; // If a post is given, that means this is an edit
communities?: Community[]; communities?: CommunityView[];
params?: PostFormParams; params?: PostFormParams;
onCancel?(): any; onCancel?(): any;
onCreate?(id: number): any; onCreate?(post: PostView): any;
onEdit?(post: Post): any; onEdit?(post: PostView): any;
enableNsfw: boolean; enableNsfw: boolean;
enableDownvotes: boolean; enableDownvotes: boolean;
} }
interface PostFormState { interface PostFormState {
postForm: PostFormI; postForm: CreatePost;
loading: boolean; loading: boolean;
imageLoading: boolean; imageLoading: boolean;
previewMode: boolean; previewMode: boolean;
suggestedTitle: string; suggestedTitle: string;
suggestedPosts: Post[]; suggestedPosts: PostView[];
crossPosts: Post[]; crossPosts: PostView[];
} }
export class PostForm extends Component<PostFormProps, PostFormState> { export class PostForm extends Component<PostFormProps, PostFormState> {
@ -72,10 +73,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
private choices: any; private choices: any;
private emptyState: PostFormState = { private emptyState: PostFormState = {
postForm: { postForm: {
community_id: null,
name: null, name: null,
nsfw: false, nsfw: false,
auth: null, auth: UserService.Instance.authField(),
community_id: null,
}, },
loading: false, loading: false,
imageLoading: false, imageLoading: false,
@ -93,16 +94,15 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.state = this.emptyState; this.state = this.emptyState;
if (this.props.post) { // Means its an edit
if (this.props.post_view) {
this.state.postForm = { this.state.postForm = {
body: this.props.post.body, body: this.props.post_view.post.body,
// NOTE: debouncing breaks both these for some reason, unless you use defaultValue name: this.props.post_view.post.name,
name: this.props.post.name, community_id: this.props.post_view.community.id,
community_id: this.props.post.community_id, url: this.props.post_view.post.url,
edit_id: this.props.post.id, nsfw: this.props.post_view.post.nsfw,
url: this.props.post.url, auth: UserService.Instance.authField(),
nsfw: this.props.post.nsfw,
auth: null,
}; };
} }
@ -285,7 +285,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
/> />
</div> </div>
</div> </div>
{!this.props.post && ( {!this.props.post_view && (
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-2 col-form-label" htmlFor="post-community"> <label class="col-sm-2 col-form-label" htmlFor="post-community">
{i18n.t('community')} {i18n.t('community')}
@ -298,11 +298,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
onInput={linkEvent(this, this.handlePostCommunityChange)} onInput={linkEvent(this, this.handlePostCommunityChange)}
> >
<option>{i18n.t('select_a_community')}</option> <option>{i18n.t('select_a_community')}</option>
{this.props.communities.map(community => ( {this.props.communities.map(cv => (
<option value={community.id}> <option value={cv.community.id}>
{community.local {cv.community.local
? community.name ? cv.community.name
: `${hostname(community.actor_id)}/${community.name}`} : `${hostname(cv.community.actor_id)}/${
cv.community.name
}`}
</option> </option>
))} ))}
</select> </select>
@ -340,13 +342,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<svg class="icon icon-spinner spin"> <svg class="icon icon-spinner spin">
<use xlinkHref="#icon-spinner"></use> <use xlinkHref="#icon-spinner"></use>
</svg> </svg>
) : this.props.post ? ( ) : this.props.post_view ? (
capitalizeFirstLetter(i18n.t('save')) capitalizeFirstLetter(i18n.t('save'))
) : ( ) : (
capitalizeFirstLetter(i18n.t('create')) capitalizeFirstLetter(i18n.t('create'))
)} )}
</button> </button>
{this.props.post && ( {this.props.post_view && (
<button <button
type="button" type="button"
class="btn btn-secondary" class="btn btn-secondary"
@ -370,10 +372,14 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
i.state.postForm.url = undefined; i.state.postForm.url = undefined;
} }
if (i.props.post) { if (i.props.post_view) {
WebSocketService.Instance.editPost(i.state.postForm); let form: EditPost = {
...i.state.postForm,
edit_id: i.props.post_view.post.id,
};
WebSocketService.Instance.client.editPost(form);
} else { } else {
WebSocketService.Instance.createPost(i.state.postForm); WebSocketService.Instance.client.createPost(i.state.postForm);
} }
i.state.loading = true; i.state.loading = true;
i.setState(i.state); i.setState(i.state);
@ -396,15 +402,16 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
fetchPageTitle() { fetchPageTitle() {
if (validURL(this.state.postForm.url)) { if (validURL(this.state.postForm.url)) {
let form: SearchForm = { let form: Search = {
q: this.state.postForm.url, q: this.state.postForm.url,
type_: SearchType.Url, type_: SearchType.Url,
sort: SortType.TopAll, sort: SortType.TopAll,
page: 1, page: 1,
limit: 6, limit: 6,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.search(form); WebSocketService.Instance.client.search(form);
// Fetch the page title // Fetch the page title
getPageTitle(this.state.postForm.url).then(d => { getPageTitle(this.state.postForm.url).then(d => {
@ -424,17 +431,18 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
} }
fetchSimilarPosts() { fetchSimilarPosts() {
let form: SearchForm = { let form: Search = {
q: this.state.postForm.name, q: this.state.postForm.name,
type_: SearchType.Posts, type_: SearchType.Posts,
sort: SortType.TopAll, sort: SortType.TopAll,
community_id: this.state.postForm.community_id, community_id: this.state.postForm.community_id,
page: 1, page: 1,
limit: 6, limit: 6,
auth: UserService.Instance.authField(false),
}; };
if (this.state.postForm.name !== '') { if (this.state.postForm.name !== '') {
WebSocketService.Instance.search(form); WebSocketService.Instance.client.search(form);
} else { } else {
this.state.suggestedPosts = []; this.state.suggestedPosts = [];
} }
@ -570,16 +578,16 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
} }
} }
if (this.props.post) { if (this.props.post_view) {
this.state.postForm.community_id = this.props.post.community_id; this.state.postForm.community_id = this.props.post_view.community.id;
} else if ( } else if (
this.props.params && this.props.params &&
(this.props.params.community_id || this.props.params.community_name) (this.props.params.community_id || this.props.params.community_name)
) { ) {
if (this.props.params.community_name) { if (this.props.params.community_name) {
let foundCommunityId = this.props.communities.find( let foundCommunityId = this.props.communities.find(
r => r.name == this.props.params.community_name r => r.community.name == this.props.params.community_name
).id; ).community.id;
this.state.postForm.community_id = foundCommunityId; this.state.postForm.community_id = foundCommunityId;
} else if (this.props.params.community_id) { } else if (this.props.params.community_id) {
this.state.postForm.community_id = this.props.params.community_id; this.state.postForm.community_id = this.props.params.community_id;
@ -596,27 +604,27 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
} }
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
return; return;
} else if (res.op == UserOperation.CreatePost) { } else if (op == UserOperation.CreatePost) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
if (data.post.creator_id == UserService.Instance.user.id) { if (data.post_view.creator.id == UserService.Instance.user.id) {
this.state.loading = false; this.state.loading = false;
this.props.onCreate(data.post.id); this.props.onCreate(data.post_view);
} }
} else if (res.op == UserOperation.EditPost) { } else if (op == UserOperation.EditPost) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
if (data.post.creator_id == UserService.Instance.user.id) { if (data.post_view.creator.id == UserService.Instance.user.id) {
this.state.loading = false; this.state.loading = false;
this.props.onEdit(data.post); this.props.onEdit(data.post_view);
} }
} else if (res.op == UserOperation.Search) { } else if (op == UserOperation.Search) {
let data = res.data as SearchResponse; let data = wsJsonToRes<SearchResponse>(msg).data;
if (data.type_ == SearchType[SearchType.Posts]) { if (data.type_ == SearchType[SearchType.Posts]) {
this.state.suggestedPosts = data.posts; this.state.suggestedPosts = data.posts;

View file

@ -2,21 +2,21 @@ import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { import {
Post, PostView,
CreatePostLikeForm, CreatePostLike,
DeletePostForm, DeletePost,
RemovePostForm, RemovePost,
LockPostForm, LockPost,
StickyPostForm, StickyPost,
SavePostForm, SavePost,
CommunityUser, UserViewSafe,
UserView, BanFromCommunity,
BanFromCommunityForm, BanUser,
BanUserForm, AddModToCommunity,
AddModToCommunityForm, AddAdmin,
AddAdminForm, TransferSite,
TransferSiteForm, TransferCommunity,
TransferCommunityForm, CommunityModeratorView,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { BanType } from '../interfaces'; import { BanType } from '../interfaces';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
@ -62,11 +62,12 @@ interface PostListingState {
} }
interface PostListingProps { interface PostListingProps {
post: Post; post_view: PostView;
duplicates?: PostView[];
showCommunity?: boolean; showCommunity?: boolean;
showBody?: boolean; showBody?: boolean;
moderators?: CommunityUser[]; moderators?: CommunityModeratorView[];
admins?: UserView[]; admins?: UserViewSafe[];
enableDownvotes: boolean; enableDownvotes: boolean;
enableNsfw: boolean; enableNsfw: boolean;
} }
@ -87,10 +88,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
viewSource: false, viewSource: false,
showAdvanced: false, showAdvanced: false,
showMoreMobile: false, showMoreMobile: false,
my_vote: this.props.post.my_vote, my_vote: this.props.post_view.my_vote,
score: this.props.post.score, score: this.props.post_view.counts.score,
upvotes: this.props.post.upvotes, upvotes: this.props.post_view.counts.upvotes,
downvotes: this.props.post.downvotes, downvotes: this.props.post_view.counts.downvotes,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -104,11 +105,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
componentWillReceiveProps(nextProps: PostListingProps) { componentWillReceiveProps(nextProps: PostListingProps) {
this.state.my_vote = nextProps.post.my_vote; this.state.my_vote = nextProps.post_view.my_vote;
this.state.upvotes = nextProps.post.upvotes; this.state.upvotes = nextProps.post_view.counts.upvotes;
this.state.downvotes = nextProps.post.downvotes; this.state.downvotes = nextProps.post_view.counts.downvotes;
this.state.score = nextProps.post.score; this.state.score = nextProps.post_view.counts.score;
if (this.props.post.id !== nextProps.post.id) { if (this.props.post_view.post.id !== nextProps.post_view.post.id) {
this.state.imageExpanded = false; this.state.imageExpanded = false;
} }
this.setState(this.state); this.setState(this.state);
@ -125,7 +126,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
) : ( ) : (
<div class="col-12"> <div class="col-12">
<PostForm <PostForm
post={this.props.post} post_view={this.props.post_view}
onEdit={this.handleEditPost} onEdit={this.handleEditPost}
onCancel={this.handleEditCancel} onCancel={this.handleEditCancel}
enableNsfw={this.props.enableNsfw} enableNsfw={this.props.enableNsfw}
@ -138,22 +139,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
body() { body() {
let post = this.props.post_view.post;
return ( return (
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
{this.props.post.url && {post.url && this.props.showBody && post.embed_title && (
this.props.showBody && <IFramelyCard post={post} />
this.props.post.embed_title && ( )}
<IFramelyCard post={this.props.post} />
)}
{this.props.showBody && {this.props.showBody &&
this.props.post.body && post.body &&
(this.state.viewSource ? ( (this.state.viewSource ? (
<pre>{this.props.post.body}</pre> <pre>{post.body}</pre>
) : ( ) : (
<div <div
className="md-div" className="md-div"
dangerouslySetInnerHTML={mdToHtml(this.props.post.body)} dangerouslySetInnerHTML={mdToHtml(post.body)}
/> />
))} ))}
</div> </div>
@ -162,18 +162,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
imgThumb(src: string) { imgThumb(src: string) {
let post = this.props.post; let post_view = this.props.post_view;
return ( return (
<PictrsImage <PictrsImage
src={src} src={src}
thumbnail thumbnail
nsfw={post.nsfw || post.community_nsfw} nsfw={post_view.post.nsfw || post_view.community.nsfw}
/> />
); );
} }
getImageSrc(): string { getImageSrc(): string {
let post = this.props.post; let post = this.props.post_view.post;
if (isImage(post.url)) { if (isImage(post.url)) {
if (post.url.includes('pictrs')) { if (post.url.includes('pictrs')) {
return post.url; return post.url;
@ -190,7 +190,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
thumbnail() { thumbnail() {
let post = this.props.post; let post = this.props.post_view.post;
if (isImage(post.url)) { if (isImage(post.url)) {
return ( return (
@ -270,21 +270,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
createdLine() { createdLine() {
let post = this.props.post; let post_view = this.props.post_view;
return ( return (
<ul class="list-inline mb-1 text-muted small"> <ul class="list-inline mb-1 text-muted small">
<li className="list-inline-item"> <li className="list-inline-item">
<UserListing <UserListing user={post_view.creator} />
user={{
name: post.creator_name,
preferred_username: post.creator_preferred_username,
avatar: post.creator_avatar,
id: post.creator_id,
local: post.creator_local,
actor_id: post.creator_actor_id,
published: post.creator_published,
}}
/>
{this.isMod && ( {this.isMod && (
<span className="mx-1 badge badge-light">{i18n.t('mod')}</span> <span className="mx-1 badge badge-light">{i18n.t('mod')}</span>
@ -292,36 +282,29 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{this.isAdmin && ( {this.isAdmin && (
<span className="mx-1 badge badge-light">{i18n.t('admin')}</span> <span className="mx-1 badge badge-light">{i18n.t('admin')}</span>
)} )}
{(post.banned_from_community || post.banned) && ( {(post_view.creator_banned_from_community ||
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>
)} )}
{this.props.showCommunity && ( {this.props.showCommunity && (
<span> <span>
<span class="mx-1"> {i18n.t('to')} </span> <span class="mx-1"> {i18n.t('to')} </span>
<CommunityLink <CommunityLink community={post_view.community} />
community={{
name: post.community_name,
id: post.community_id,
local: post.community_local,
actor_id: post.community_actor_id,
icon: post.community_icon,
}}
/>
</span> </span>
)} )}
</li> </li>
<li className="list-inline-item"></li> <li className="list-inline-item"></li>
{post.url && !(hostname(post.url) == externalHost) && ( {post_view.post.url && !(hostname(post_view.post.url) == externalHost) && (
<> <>
<li className="list-inline-item"> <li className="list-inline-item">
<a <a
className="text-muted font-italic" className="text-muted font-italic"
href={post.url} href={post_view.post.url}
target="_blank" target="_blank"
title={post.url} title={post_view.post.url}
rel="noopener" rel="noopener"
> >
{hostname(post.url)} {hostname(post_view.post.url)}
</a> </a>
</li> </li>
<li className="list-inline-item"></li> <li className="list-inline-item"></li>
@ -329,19 +312,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
)} )}
<li className="list-inline-item"> <li className="list-inline-item">
<span> <span>
<MomentTime data={post} /> <MomentTime data={post_view.post} />
</span> </span>
</li> </li>
{post.body && ( {post_view.post.body && (
<> <>
<li className="list-inline-item"></li> <li className="list-inline-item"></li>
<li className="list-inline-item"> <li className="list-inline-item">
{/* Using a link with tippy doesn't work on touch devices unfortunately */} {/* Using a link with tippy doesn't work on touch devices unfortunately */}
<Link <Link
className="text-muted" className="text-muted"
data-tippy-content={md.render(previewLines(post.body))} data-tippy-content={md.render(
previewLines(post_view.post.body)
)}
data-tippy-allowHtml={true} data-tippy-allowHtml={true}
to={`/post/${post.id}`} to={`/post/${post_view.post.id}`}
> >
<svg class="mr-1 icon icon-inline"> <svg class="mr-1 icon icon-inline">
<use xlinkHref="#icon-book-open"></use> <use xlinkHref="#icon-book-open"></use>
@ -392,7 +377,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
postTitleLine() { postTitleLine() {
let post = this.props.post; let post = this.props.post_view.post;
return ( return (
<div className="post-title overflow-hidden"> <div className="post-title overflow-hidden">
<h5> <h5>
@ -415,7 +400,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.name} {post.name}
</Link> </Link>
)} )}
{(isImage(post.url) || this.props.post.thumbnail_url) && {(isImage(post.url) || post.thumbnail_url) &&
(!this.state.imageExpanded ? ( (!this.state.imageExpanded ? (
<span <span
class="text-monospace unselectable pointer ml-2 text-muted small" class="text-monospace unselectable pointer ml-2 text-muted small"
@ -492,22 +477,22 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
commentsLine(mobile: boolean = false) { commentsLine(mobile: boolean = false) {
let post = this.props.post; let post_view = this.props.post_view;
return ( return (
<div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold mb-1"> <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold mb-1">
<button class="btn btn-link text-muted p-0"> <button class="btn btn-link text-muted p-0">
<Link <Link
className="text-muted small" className="text-muted small"
title={i18n.t('number_of_comments', { title={i18n.t('number_of_comments', {
count: post.number_of_comments, count: post_view.counts.comments,
})} })}
to={`/post/${post.id}`} to={`/post/${post_view.post.id}`}
> >
<svg class="mr-1 icon icon-inline"> <svg class="mr-1 icon icon-inline">
<use xlinkHref="#icon-message-square"></use> <use xlinkHref="#icon-message-square"></use>
</svg> </svg>
{i18n.t('number_of_comments', { {i18n.t('number_of_comments', {
count: post.number_of_comments, count: post_view.counts.comments,
})} })}
</Link> </Link>
</button> </button>
@ -531,12 +516,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleSavePostClick)} onClick={linkEvent(this, this.handleSavePostClick)}
data-tippy-content={ data-tippy-content={
post.saved ? i18n.t('unsave') : i18n.t('save') post_view.saved ? i18n.t('unsave') : i18n.t('save')
} }
> >
<small> <small>
<svg <svg
class={`icon icon-inline ${post.saved && 'text-warning'}`} class={`icon icon-inline ${
post_view.saved && 'text-warning'
}`}
> >
<use xlinkHref="#icon-star"></use> <use xlinkHref="#icon-star"></use>
</svg> </svg>
@ -583,10 +570,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted py-0 pl-1 pr-0" class="btn btn-link btn-animate text-muted py-0 pl-1 pr-0"
onClick={linkEvent(this, this.handleSavePostClick)} onClick={linkEvent(this, this.handleSavePostClick)}
data-tippy-content={ data-tippy-content={
post.saved ? i18n.t('unsave') : i18n.t('save') post_view.saved ? i18n.t('unsave') : i18n.t('save')
} }
> >
<svg class={`icon icon-inline ${post.saved && 'text-warning'}`}> <svg
class={`icon icon-inline ${post_view.saved && 'text-warning'}`}
>
<use xlinkHref="#icon-star"></use> <use xlinkHref="#icon-star"></use>
</svg> </svg>
</button> </button>
@ -611,20 +600,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
duplicatesLine() { duplicatesLine() {
return ( return (
this.props.post.duplicates && ( this.props.duplicates && (
<ul class="list-inline mb-1 small text-muted"> <ul class="list-inline mb-1 small text-muted">
<> <>
<li className="list-inline-item mr-2"> <li className="list-inline-item mr-2">
{i18n.t('cross_posted_to')} {i18n.t('cross_posted_to')}
</li> </li>
{this.props.post.duplicates.map(post => ( {this.props.duplicates.map(pv => (
<li className="list-inline-item mr-2"> <li className="list-inline-item mr-2">
<Link to={`/post/${post.id}`}> <Link to={`/post/${pv.post.id}`}>
{post.community_local {pv.community.local
? post.community_name ? pv.community.name
: `${post.community_name}@${hostname( : `${pv.community.name}@${hostname(pv.community.actor_id)}`}
post.community_actor_id
)}`}
</Link> </Link>
</li> </li>
))} ))}
@ -635,7 +622,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
postActions(mobile: boolean = false) { postActions(mobile: boolean = false) {
let post = this.props.post; let post_view = this.props.post_view;
return ( return (
UserService.Instance.user && ( UserService.Instance.user && (
<> <>
@ -646,11 +633,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted py-0 pl-0" class="btn btn-link btn-animate text-muted py-0 pl-0"
onClick={linkEvent(this, this.handleSavePostClick)} onClick={linkEvent(this, this.handleSavePostClick)}
data-tippy-content={ data-tippy-content={
post.saved ? i18n.t('unsave') : i18n.t('save') post_view.saved ? i18n.t('unsave') : i18n.t('save')
} }
> >
<svg <svg
class={`icon icon-inline ${post.saved && 'text-warning'}`} class={`icon icon-inline ${
post_view.saved && 'text-warning'
}`}
> >
<use xlinkHref="#icon-star"></use> <use xlinkHref="#icon-star"></use>
</svg> </svg>
@ -682,11 +671,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleDeleteClick)} onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={ data-tippy-content={
!post.deleted ? i18n.t('delete') : i18n.t('restore') !post_view.post.deleted ? i18n.t('delete') : i18n.t('restore')
} }
> >
<svg <svg
class={`icon icon-inline ${post.deleted && 'text-danger'}`} class={`icon icon-inline ${
post_view.post.deleted && 'text-danger'
}`}
> >
<use xlinkHref="#icon-trash"></use> <use xlinkHref="#icon-trash"></use>
</svg> </svg>
@ -706,7 +697,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</button> </button>
) : ( ) : (
<> <>
{this.props.showBody && post.body && ( {this.props.showBody && post_view.post.body && (
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
@ -727,11 +718,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleModLock)} onClick={linkEvent(this, this.handleModLock)}
data-tippy-content={ data-tippy-content={
post.locked ? i18n.t('unlock') : i18n.t('lock') post_view.post.locked ? i18n.t('unlock') : i18n.t('lock')
} }
> >
<svg <svg
class={`icon icon-inline ${post.locked && 'text-danger'}`} class={`icon icon-inline ${
post_view.post.locked && 'text-danger'
}`}
> >
<use xlinkHref="#icon-lock"></use> <use xlinkHref="#icon-lock"></use>
</svg> </svg>
@ -740,12 +733,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleModSticky)} onClick={linkEvent(this, this.handleModSticky)}
data-tippy-content={ data-tippy-content={
post.stickied ? i18n.t('unsticky') : i18n.t('sticky') post_view.post.stickied
? i18n.t('unsticky')
: i18n.t('sticky')
} }
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
post.stickied && 'text-success' post_view.post.stickied && 'text-success'
}`} }`}
> >
<use xlinkHref="#icon-pin"></use> <use xlinkHref="#icon-pin"></use>
@ -755,7 +750,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
)} )}
{/* Mods can ban from community, and appoint as mods to community */} {/* Mods can ban from community, and appoint as mods to community */}
{(this.canMod || this.canAdmin) && {(this.canMod || this.canAdmin) &&
(!post.removed ? ( (!post_view.post.removed ? (
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleModRemoveShow)} onClick={linkEvent(this, this.handleModRemoveShow)}
@ -773,7 +768,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{this.canMod && ( {this.canMod && (
<> <>
{!this.isMod && {!this.isMod &&
(!post.banned_from_community ? ( (!post_view.creator_banned_from_community ? (
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent( onClick={linkEvent(
@ -794,22 +789,23 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{i18n.t('unban')} {i18n.t('unban')}
</button> </button>
))} ))}
{!post.banned_from_community && post.creator_local && ( {!post_view.creator_banned_from_community &&
<button post_view.creator.local && (
class="btn btn-link btn-animate text-muted py-0" <button
onClick={linkEvent(this, this.handleAddModToCommunity)} class="btn btn-link btn-animate text-muted py-0"
> onClick={linkEvent(this, this.handleAddModToCommunity)}
{this.isMod >
? i18n.t('remove_as_mod') {this.isMod
: i18n.t('appoint_as_mod')} ? i18n.t('remove_as_mod')
</button> : i18n.t('appoint_as_mod')}
)} </button>
)}
</> </>
)} )}
{/* Community creators and admins can transfer community to another mod */} {/* Community creators and admins can transfer community to another mod */}
{(this.amCommunityCreator || this.canAdmin) && {(this.amCommunityCreator || this.canAdmin) &&
this.isMod && this.isMod &&
post.creator_local && post_view.creator.local &&
(!this.state.showConfirmTransferCommunity ? ( (!this.state.showConfirmTransferCommunity ? (
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
@ -846,7 +842,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{this.canAdmin && ( {this.canAdmin && (
<> <>
{!this.isAdmin && {!this.isAdmin &&
(!post.banned ? ( (!post_view.creator.banned ? (
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleModBanShow)} onClick={linkEvent(this, this.handleModBanShow)}
@ -861,7 +857,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{i18n.t('unban_from_site')} {i18n.t('unban_from_site')}
</button> </button>
))} ))}
{!post.banned && post.creator_local && ( {!post_view.creator.banned && post_view.creator.local && (
<button <button
class="btn btn-link btn-animate text-muted py-0" class="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleAddAdmin)} onClick={linkEvent(this, this.handleAddAdmin)}
@ -916,7 +912,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
removeAndBanDialogs() { removeAndBanDialogs() {
let post = this.props.post; let post = this.props.post_view;
return ( return (
<> <>
{this.state.showRemoveDialog && ( {this.state.showRemoveDialog && (
@ -972,7 +968,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{/* </div> */} {/* </div> */}
<div class="form-group row"> <div class="form-group row">
<button type="submit" class="btn btn-secondary"> <button type="submit" class="btn btn-secondary">
{i18n.t('ban')} {post.creator_name} {i18n.t('ban')} {post.creator.name}
</button> </button>
</div> </div>
</form> </form>
@ -982,7 +978,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
mobileThumbnail() { mobileThumbnail() {
return this.props.post.thumbnail_url || isImage(this.props.post.url) ? ( let post = this.props.post_view.post;
return post.thumbnail_url || isImage(post.url) ? (
<div class="row"> <div class="row">
<div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}> <div className={`${this.state.imageExpanded ? 'col-12' : 'col-8'}`}>
{this.postTitleLine()} {this.postTitleLine()}
@ -998,13 +995,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
showMobilePreview() { showMobilePreview() {
let post = this.props.post_view.post;
return ( return (
this.props.post.body && post.body &&
!this.props.showBody && ( !this.props.showBody && (
<div <div
className="md-div mb-1" className="md-div mb-1"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: md.render(previewLines(this.props.post.body)), __html: md.render(previewLines(post.body)),
}} }}
/> />
) )
@ -1067,7 +1065,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
private get myPost(): boolean { private get myPost(): boolean {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
this.props.post.creator_id == UserService.Instance.user.id this.props.post_view.creator.id == UserService.Instance.user.id
); );
} }
@ -1075,8 +1073,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return ( return (
this.props.moderators && this.props.moderators &&
isMod( isMod(
this.props.moderators.map(m => m.user_id), this.props.moderators.map(m => m.moderator.id),
this.props.post.creator_id this.props.post_view.creator.id
) )
); );
} }
@ -1085,8 +1083,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return ( return (
this.props.admins && this.props.admins &&
isMod( isMod(
this.props.admins.map(a => a.id), this.props.admins.map(a => a.user.id),
this.props.post.creator_id this.props.post_view.creator.id
) )
); );
} }
@ -1094,13 +1092,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get canMod(): boolean { get canMod(): boolean {
if (this.props.admins && this.props.moderators) { if (this.props.admins && this.props.moderators) {
let adminsThenMods = this.props.admins let adminsThenMods = this.props.admins
.map(a => a.id) .map(a => a.user.id)
.concat(this.props.moderators.map(m => m.user_id)); .concat(this.props.moderators.map(m => m.moderator.id));
return canMod( return canMod(
UserService.Instance.user, UserService.Instance.user,
adminsThenMods, adminsThenMods,
this.props.post.creator_id this.props.post_view.creator.id
); );
} else { } else {
return false; return false;
@ -1110,13 +1108,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get canModOnSelf(): boolean { get canModOnSelf(): boolean {
if (this.props.admins && this.props.moderators) { if (this.props.admins && this.props.moderators) {
let adminsThenMods = this.props.admins let adminsThenMods = this.props.admins
.map(a => a.id) .map(a => a.user.id)
.concat(this.props.moderators.map(m => m.user_id)); .concat(this.props.moderators.map(m => m.moderator.id));
return canMod( return canMod(
UserService.Instance.user, UserService.Instance.user,
adminsThenMods, adminsThenMods,
this.props.post.creator_id, this.props.post_view.creator.id,
true true
); );
} else { } else {
@ -1129,8 +1127,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.props.admins && this.props.admins &&
canMod( canMod(
UserService.Instance.user, UserService.Instance.user,
this.props.admins.map(a => a.id), this.props.admins.map(a => a.user.id),
this.props.post.creator_id this.props.post_view.creator.id
) )
); );
} }
@ -1139,8 +1137,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return ( return (
this.props.moderators && this.props.moderators &&
UserService.Instance.user && UserService.Instance.user &&
this.props.post.creator_id != UserService.Instance.user.id && this.props.post_view.creator.id != UserService.Instance.user.id &&
UserService.Instance.user.id == this.props.moderators[0].user_id UserService.Instance.user.id == this.props.moderators[0].moderator.id
); );
} }
@ -1148,8 +1146,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return ( return (
this.props.admins && this.props.admins &&
UserService.Instance.user && UserService.Instance.user &&
this.props.post.creator_id != UserService.Instance.user.id && this.props.post_view.creator.id != UserService.Instance.user.id &&
UserService.Instance.user.id == this.props.admins[0].id UserService.Instance.user.id == this.props.admins[0].user.id
); );
} }
@ -1174,12 +1172,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
i.state.my_vote = new_vote; i.state.my_vote = new_vote;
let form: CreatePostLikeForm = { let form: CreatePostLike = {
post_id: i.props.post.id, post_id: i.props.post_view.post.id,
score: i.state.my_vote, score: i.state.my_vote,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.likePost(form); WebSocketService.Instance.client.likePost(form);
i.setState(i.state); i.setState(i.state);
setupTippy(); setupTippy();
} }
@ -1205,12 +1204,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
i.state.my_vote = new_vote; i.state.my_vote = new_vote;
let form: CreatePostLikeForm = { let form: CreatePostLike = {
post_id: i.props.post.id, post_id: i.props.post_view.post.id,
score: i.state.my_vote, score: i.state.my_vote,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.likePost(form); WebSocketService.Instance.client.likePost(form);
i.setState(i.state); i.setState(i.state);
setupTippy(); setupTippy();
} }
@ -1232,33 +1232,35 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
handleDeleteClick(i: PostListing) { handleDeleteClick(i: PostListing) {
let deleteForm: DeletePostForm = { let deleteForm: DeletePost = {
edit_id: i.props.post.id, edit_id: i.props.post_view.post.id,
deleted: !i.props.post.deleted, deleted: !i.props.post_view.post.deleted,
auth: null, auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.deletePost(deleteForm); WebSocketService.Instance.client.deletePost(deleteForm);
} }
handleSavePostClick(i: PostListing) { handleSavePostClick(i: PostListing) {
let saved = i.props.post.saved == undefined ? true : !i.props.post.saved; let saved =
let form: SavePostForm = { i.props.post_view.saved == undefined ? true : !i.props.post_view.saved;
post_id: i.props.post.id, let form: SavePost = {
post_id: i.props.post_view.post.id,
save: saved, save: saved,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.savePost(form); WebSocketService.Instance.client.savePost(form);
} }
get crossPostParams(): string { get crossPostParams(): string {
let params = `?title=${this.props.post.name}`; let post = this.props.post_view.post;
let post = this.props.post; let params = `?title=${post.name}`;
if (post.url) { if (post.url) {
params += `&url=${encodeURIComponent(post.url)}`; params += `&url=${encodeURIComponent(post.url)}`;
} }
if (this.props.post.body) { if (post.body) {
params += `&body=${this.props.post.body}`; params += `&body=${post.body}`;
} }
return params; return params;
} }
@ -1280,34 +1282,34 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
handleModRemoveSubmit(i: PostListing) { handleModRemoveSubmit(i: PostListing) {
event.preventDefault(); event.preventDefault();
let form: RemovePostForm = { let form: RemovePost = {
edit_id: i.props.post.id, edit_id: i.props.post_view.post.id,
removed: !i.props.post.removed, removed: !i.props.post_view.post.removed,
reason: i.state.removeReason, reason: i.state.removeReason,
auth: null, auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.removePost(form); WebSocketService.Instance.client.removePost(form);
i.state.showRemoveDialog = false; i.state.showRemoveDialog = false;
i.setState(i.state); i.setState(i.state);
} }
handleModLock(i: PostListing) { handleModLock(i: PostListing) {
let form: LockPostForm = { let form: LockPost = {
edit_id: i.props.post.id, edit_id: i.props.post_view.post.id,
locked: !i.props.post.locked, locked: !i.props.post_view.post.locked,
auth: null, auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.lockPost(form); WebSocketService.Instance.client.lockPost(form);
} }
handleModSticky(i: PostListing) { handleModSticky(i: PostListing) {
let form: StickyPostForm = { let form: StickyPost = {
edit_id: i.props.post.id, edit_id: i.props.post_view.post.id,
stickied: !i.props.post.stickied, stickied: !i.props.post_view.post.stickied,
auth: null, auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.stickyPost(form); WebSocketService.Instance.client.stickyPost(form);
} }
handleModBanFromCommunityShow(i: PostListing) { handleModBanFromCommunityShow(i: PostListing) {
@ -1349,33 +1351,35 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
if (i.state.banType == BanType.Community) { if (i.state.banType == BanType.Community) {
// If its an unban, restore all their data // If its an unban, restore all their data
let ban = !i.props.post.banned_from_community; let ban = !i.props.post_view.creator_banned_from_community;
if (ban == false) { if (ban == false) {
i.state.removeData = false; i.state.removeData = false;
} }
let form: BanFromCommunityForm = { let form: BanFromCommunity = {
user_id: i.props.post.creator_id, user_id: i.props.post_view.creator.id,
community_id: i.props.post.community_id, community_id: i.props.post_view.community.id,
ban, ban,
remove_data: i.state.removeData, remove_data: i.state.removeData,
reason: i.state.banReason, reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires), expires: getUnixTime(i.state.banExpires),
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.banFromCommunity(form); WebSocketService.Instance.client.banFromCommunity(form);
} else { } else {
// If its an unban, restore all their data // If its an unban, restore all their data
let ban = !i.props.post.banned; let ban = !i.props.post_view.creator.banned;
if (ban == false) { if (ban == false) {
i.state.removeData = false; i.state.removeData = false;
} }
let form: BanUserForm = { let form: BanUser = {
user_id: i.props.post.creator_id, user_id: i.props.post_view.creator.id,
ban, ban,
remove_data: i.state.removeData, remove_data: i.state.removeData,
reason: i.state.banReason, reason: i.state.banReason,
expires: getUnixTime(i.state.banExpires), expires: getUnixTime(i.state.banExpires),
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.banUser(form); WebSocketService.Instance.client.banUser(form);
} }
i.state.showBanDialog = false; i.state.showBanDialog = false;
@ -1383,21 +1387,23 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
handleAddModToCommunity(i: PostListing) { handleAddModToCommunity(i: PostListing) {
let form: AddModToCommunityForm = { let form: AddModToCommunity = {
user_id: i.props.post.creator_id, user_id: i.props.post_view.creator.id,
community_id: i.props.post.community_id, community_id: i.props.post_view.community.id,
added: !i.isMod, added: !i.isMod,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.addModToCommunity(form); WebSocketService.Instance.client.addModToCommunity(form);
i.setState(i.state); i.setState(i.state);
} }
handleAddAdmin(i: PostListing) { handleAddAdmin(i: PostListing) {
let form: AddAdminForm = { let form: AddAdmin = {
user_id: i.props.post.creator_id, user_id: i.props.post_view.creator.id,
added: !i.isAdmin, added: !i.isAdmin,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.addAdmin(form); WebSocketService.Instance.client.addAdmin(form);
i.setState(i.state); i.setState(i.state);
} }
@ -1412,11 +1418,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
handleTransferCommunity(i: PostListing) { handleTransferCommunity(i: PostListing) {
let form: TransferCommunityForm = { let form: TransferCommunity = {
community_id: i.props.post.community_id, community_id: i.props.post_view.community.id,
user_id: i.props.post.creator_id, user_id: i.props.post_view.creator.id,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.transferCommunity(form); WebSocketService.Instance.client.transferCommunity(form);
i.state.showConfirmTransferCommunity = false; i.state.showConfirmTransferCommunity = false;
i.setState(i.state); i.setState(i.state);
} }
@ -1432,10 +1439,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
handleTransferSite(i: PostListing) { handleTransferSite(i: PostListing) {
let form: TransferSiteForm = { let form: TransferSite = {
user_id: i.props.post.creator_id, user_id: i.props.post_view.creator.id,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.transferSite(form); WebSocketService.Instance.client.transferSite(form);
i.state.showConfirmTransferSite = false; i.state.showConfirmTransferSite = false;
i.setState(i.state); i.setState(i.state);
} }

View file

@ -1,13 +1,13 @@
import { Component } from 'inferno'; import { Component } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Post, SortType } from 'lemmy-js-client'; import { PostView, SortType } from 'lemmy-js-client';
import { postSort } from '../utils'; import { postSort } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
interface PostListingsProps { interface PostListingsProps {
posts: Post[]; posts: PostView[];
showCommunity?: boolean; showCommunity?: boolean;
removeDuplicates?: boolean; removeDuplicates?: boolean;
sort?: SortType; sort?: SortType;
@ -16,6 +16,8 @@ interface PostListingsProps {
} }
export class PostListings extends Component<PostListingsProps, any> { export class PostListings extends Component<PostListingsProps, any> {
private duplicatesMap = new Map<number, PostView[]>();
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
} }
@ -24,10 +26,11 @@ export class PostListings extends Component<PostListingsProps, any> {
return ( return (
<div> <div>
{this.props.posts.length > 0 ? ( {this.props.posts.length > 0 ? (
this.outer().map(post => ( this.outer().map(post_view => (
<> <>
<PostListing <PostListing
post={post} post_view={post_view}
duplicates={this.duplicatesMap.get(post_view.post.id)}
showCommunity={this.props.showCommunity} showCommunity={this.props.showCommunity}
enableDownvotes={this.props.enableDownvotes} enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw} enableNsfw={this.props.enableNsfw}
@ -49,7 +52,7 @@ export class PostListings extends Component<PostListingsProps, any> {
); );
} }
outer(): Post[] { outer(): PostView[] {
let out = this.props.posts; let out = this.props.posts;
if (this.props.removeDuplicates) { if (this.props.removeDuplicates) {
out = this.removeDuplicates(out); out = this.removeDuplicates(out);
@ -62,23 +65,23 @@ export class PostListings extends Component<PostListingsProps, any> {
return out; return out;
} }
removeDuplicates(posts: Post[]): Post[] { removeDuplicates(posts: PostView[]): PostView[] {
// A map from post url to list of posts (dupes) // A map from post url to list of posts (dupes)
let urlMap = new Map<string, Post[]>(); let urlMap = new Map<string, PostView[]>();
// Loop over the posts, find ones with same urls // Loop over the posts, find ones with same urls
for (let post of posts) { for (let pv of posts) {
if ( if (
post.url && pv.post.url &&
!post.deleted && !pv.post.deleted &&
!post.removed && !pv.post.removed &&
!post.community_deleted && !pv.community.deleted &&
!post.community_removed !pv.community.removed
) { ) {
if (!urlMap.get(post.url)) { if (!urlMap.get(pv.post.url)) {
urlMap.set(post.url, [post]); urlMap.set(pv.post.url, [pv]);
} else { } else {
urlMap.get(post.url).push(post); urlMap.get(pv.post.url).push(pv);
} }
} }
} }
@ -89,18 +92,18 @@ export class PostListings extends Component<PostListingsProps, any> {
if (e[1].length == 1) { if (e[1].length == 1) {
urlMap.delete(e[0]); urlMap.delete(e[0]);
} else { } else {
e[1].sort((a, b) => a.published.localeCompare(b.published)); e[1].sort((a, b) => a.post.published.localeCompare(b.post.published));
} }
} }
for (let i = 0; i < posts.length; i++) { for (let i = 0; i < posts.length; i++) {
let post = posts[i]; let pv = posts[i];
if (post.url) { if (pv.post.url) {
let found = urlMap.get(post.url); let found = urlMap.get(pv.post.url);
if (found) { if (found) {
// If its the oldest, add // If its the oldest, add
if (post.id == found[0].id) { if (pv.post.id == found[0].post.id) {
post.duplicates = found.slice(1); this.duplicatesMap.set(pv.post.id, found.slice(1));
} }
// Otherwise, delete it // Otherwise, delete it
else { else {

View file

@ -3,25 +3,23 @@ import { HtmlTags } from './html-tags';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
UserOperation, UserOperation,
Post as PostI, PostView,
GetPostResponse, GetPostResponse,
PostResponse, PostResponse,
MarkCommentAsReadForm, MarkCommentAsRead,
CommentResponse, CommentResponse,
CommunityResponse, CommunityResponse,
CommentNode as CommentNodeI,
BanFromCommunityResponse, BanFromCommunityResponse,
BanUserResponse, BanUserResponse,
AddModToCommunityResponse, AddModToCommunityResponse,
AddAdminResponse, AddAdminResponse,
SearchType, SearchType,
SortType, SortType,
SearchForm, Search,
GetPostForm, GetPost,
SearchResponse, SearchResponse,
GetSiteResponse, GetSiteResponse,
GetCommunityResponse, GetCommunityResponse,
WebSocketJsonResponse,
ListCategoriesResponse, ListCategoriesResponse,
Category, Category,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
@ -29,6 +27,7 @@ import {
CommentSortType, CommentSortType,
CommentViewType, CommentViewType,
InitialFetchRequest, InitialFetchRequest,
CommentNode as CommentNodeI,
} from '../interfaces'; } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { import {
@ -44,10 +43,10 @@ import {
getIdFromProps, getIdFromProps,
getCommentIdFromProps, getCommentIdFromProps,
wsSubscribe, wsSubscribe,
setAuth,
isBrowser, isBrowser,
previewLines, previewLines,
isImage, isImage,
wsUserOp,
} from '../utils'; } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { Sidebar } from './sidebar'; import { Sidebar } from './sidebar';
@ -64,7 +63,7 @@ interface PostState {
commentViewType: CommentViewType; commentViewType: CommentViewType;
scrolled?: boolean; scrolled?: boolean;
loading: boolean; loading: boolean;
crossPosts: PostI[]; crossPosts: PostView[];
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
categories: Category[]; categories: Category[];
} }
@ -81,7 +80,7 @@ export class Post extends Component<any, PostState> {
scrolled: false, scrolled: false,
loading: true, loading: true,
crossPosts: [], crossPosts: [],
siteRes: this.isoData.site, siteRes: this.isoData.site_res,
categories: [], categories: [],
}; };
@ -104,15 +103,16 @@ export class Post extends Component<any, PostState> {
} }
} else { } else {
this.fetchPost(); this.fetchPost();
WebSocketService.Instance.listCategories(); WebSocketService.Instance.client.listCategories();
} }
} }
fetchPost() { fetchPost() {
let form: GetPostForm = { let form: GetPost = {
id: this.state.postId, id: this.state.postId,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.getPost(form); WebSocketService.Instance.client.getPost(form);
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
@ -121,10 +121,10 @@ export class Post extends Component<any, PostState> {
let id = Number(pathSplit[2]); let id = Number(pathSplit[2]);
let postForm: GetPostForm = { let postForm: GetPost = {
id, id,
auth: req.auth,
}; };
setAuth(postForm, req.auth);
promises.push(req.client.getPost(postForm)); promises.push(req.client.getPost(postForm));
promises.push(req.client.listCategories()); promises.push(req.client.listCategories());
@ -138,7 +138,7 @@ export class Post extends Component<any, PostState> {
} }
componentDidMount() { componentDidMount() {
WebSocketService.Instance.postJoin({ post_id: this.state.postId }); WebSocketService.Instance.client.postJoin({ post_id: this.state.postId });
autosize(document.querySelectorAll('textarea')); autosize(document.querySelectorAll('textarea'));
} }
@ -172,23 +172,28 @@ export class Post extends Component<any, PostState> {
this.markScrolledAsRead(this.state.commentId); this.markScrolledAsRead(this.state.commentId);
} }
// TODO this needs some re-work
markScrolledAsRead(commentId: number) { markScrolledAsRead(commentId: number) {
let found = this.state.postRes.comments.find(c => c.id == commentId); let found = this.state.postRes.comments.find(
let parent = this.state.postRes.comments.find(c => found.parent_id == c.id); c => c.comment.id == commentId
);
let parent = this.state.postRes.comments.find(
c => found.comment.parent_id == c.comment.id
);
let parent_user_id = parent let parent_user_id = parent
? parent.creator_id ? parent.creator.id
: this.state.postRes.post.creator_id; : this.state.postRes.post_view.creator.id;
if ( if (
UserService.Instance.user && UserService.Instance.user &&
UserService.Instance.user.id == parent_user_id UserService.Instance.user.id == parent_user_id
) { ) {
let form: MarkCommentAsReadForm = { let form: MarkCommentAsRead = {
edit_id: found.id, comment_id: found.creator.id,
read: true, read: true,
auth: null, auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.markCommentAsRead(form); WebSocketService.Instance.client.markCommentAsRead(form);
UserService.Instance.unreadCountSub.next( UserService.Instance.unreadCountSub.next(
UserService.Instance.unreadCountSub.value - 1 UserService.Instance.unreadCountSub.value - 1
); );
@ -196,27 +201,24 @@ export class Post extends Component<any, PostState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${this.state.postRes.post.name} - ${this.state.siteRes.site.name}`; return `${this.state.postRes.post_view.post.name} - ${this.state.siteRes.site_view.site.name}`;
} }
get imageTag(): string { get imageTag(): string {
let post = this.state.postRes.post_view.post;
return ( return (
this.state.postRes.post.thumbnail_url || post.thumbnail_url ||
(this.state.postRes.post.url (post.url ? (isImage(post.url) ? post.url : undefined) : undefined)
? isImage(this.state.postRes.post.url)
? this.state.postRes.post.url
: undefined
: undefined)
); );
} }
get descriptionTag(): string { get descriptionTag(): string {
return this.state.postRes.post.body let body = this.state.postRes.post_view.post.body;
? previewLines(this.state.postRes.post.body) return body ? previewLines(body) : undefined;
: undefined;
} }
render() { render() {
let pv = this.state.postRes.post_view;
return ( return (
<div class="container"> <div class="container">
{this.state.loading ? ( {this.state.loading ? (
@ -235,18 +237,21 @@ export class Post extends Component<any, PostState> {
description={this.descriptionTag} description={this.descriptionTag}
/> />
<PostListing <PostListing
post={this.state.postRes.post} post_view={pv}
duplicates={this.state.crossPosts}
showBody showBody
showCommunity showCommunity
moderators={this.state.postRes.moderators} moderators={this.state.postRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={
enableNsfw={this.state.siteRes.site.enable_nsfw} this.state.siteRes.site_view.site.enable_downvotes
}
enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
/> />
<div className="mb-2" /> <div className="mb-2" />
<CommentForm <CommentForm
postId={this.state.postId} postId={this.state.postId}
disabled={this.state.postRes.post.locked} disabled={pv.post.locked}
/> />
{this.state.postRes.comments.length > 0 && this.sortRadios()} {this.state.postRes.comments.length > 0 && this.sortRadios()}
{this.state.commentViewType == CommentViewType.Tree && {this.state.commentViewType == CommentViewType.Tree &&
@ -343,12 +348,12 @@ export class Post extends Component<any, PostState> {
<CommentNodes <CommentNodes
nodes={commentsToFlatNodes(this.state.postRes.comments)} nodes={commentsToFlatNodes(this.state.postRes.comments)}
noIndent noIndent
locked={this.state.postRes.post.locked} locked={this.state.postRes.post_view.post.locked}
moderators={this.state.postRes.moderators} moderators={this.state.postRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
postCreatorId={this.state.postRes.post.creator_id} postCreatorId={this.state.postRes.post_view.creator.id}
showContext showContext
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={this.state.siteRes.site_view.site.enable_downvotes}
sort={this.state.commentSort} sort={this.state.commentSort}
/> />
</div> </div>
@ -359,11 +364,11 @@ export class Post extends Component<any, PostState> {
return ( return (
<div class="mb-3"> <div class="mb-3">
<Sidebar <Sidebar
community={this.state.postRes.community} community_view={this.state.postRes.community_view}
moderators={this.state.postRes.moderators} moderators={this.state.postRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
online={this.state.postRes.online} online={this.state.postRes.online}
enableNsfw={this.state.siteRes.site.enable_nsfw} enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
showIcon showIcon
categories={this.state.categories} categories={this.state.categories}
/> />
@ -385,18 +390,18 @@ export class Post extends Component<any, PostState> {
buildCommentsTree(): CommentNodeI[] { buildCommentsTree(): CommentNodeI[] {
let map = new Map<number, CommentNodeI>(); let map = new Map<number, CommentNodeI>();
for (let comment of this.state.postRes.comments) { for (let comment_view of this.state.postRes.comments) {
let node: CommentNodeI = { let node: CommentNodeI = {
comment: comment, comment_view: comment_view,
children: [], children: [],
}; };
map.set(comment.id, { ...node }); map.set(comment_view.comment.id, { ...node });
} }
let tree: CommentNodeI[] = []; let tree: CommentNodeI[] = [];
for (let comment of this.state.postRes.comments) { for (let comment_view of this.state.postRes.comments) {
let child = map.get(comment.id); let child = map.get(comment_view.comment.id);
if (comment.parent_id) { if (comment_view.comment.parent_id) {
let parent_ = map.get(comment.parent_id); let parent_ = map.get(comment_view.comment.parent_id);
parent_.children.push(child); parent_.children.push(child);
} else { } else {
tree.push(child); tree.push(child);
@ -410,7 +415,7 @@ export class Post extends Component<any, PostState> {
setDepth(node: CommentNodeI, i: number = 0): void { setDepth(node: CommentNodeI, i: number = 0): void {
for (let child of node.children) { for (let child of node.children) {
child.comment.depth = i; child.depth = i;
this.setDepth(child, i + 1); this.setDepth(child, i + 1);
} }
} }
@ -421,155 +426,146 @@ export class Post extends Component<any, PostState> {
<div> <div>
<CommentNodes <CommentNodes
nodes={nodes} nodes={nodes}
locked={this.state.postRes.post.locked} locked={this.state.postRes.post_view.post.locked}
moderators={this.state.postRes.moderators} moderators={this.state.postRes.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
postCreatorId={this.state.postRes.post.creator_id} postCreatorId={this.state.postRes.post_view.creator.id}
sort={this.state.commentSort} sort={this.state.commentSort}
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={this.state.siteRes.site_view.site.enable_downvotes}
/> />
</div> </div>
); );
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); let op = wsUserOp(msg);
let res = wsJsonToRes(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
return; return;
} else if (msg.reconnect) { } else if (msg.reconnect) {
let postId = Number(this.props.match.params.id); let postId = Number(this.props.match.params.id);
WebSocketService.Instance.postJoin({ post_id: postId }); WebSocketService.Instance.client.postJoin({ post_id: postId });
WebSocketService.Instance.getPost({ WebSocketService.Instance.client.getPost({
id: postId, id: postId,
auth: UserService.Instance.authField(false),
}); });
} else if (res.op == UserOperation.GetPost) { } else if (op == UserOperation.GetPost) {
let data = res.data as GetPostResponse; let data = wsJsonToRes<GetPostResponse>(msg).data;
this.state.postRes = data; this.state.postRes = data;
this.state.loading = false; this.state.loading = false;
// Get cross-posts // Get cross-posts
if (this.state.postRes.post.url) { if (this.state.postRes.post_view.post.url) {
let form: SearchForm = { let form: Search = {
q: this.state.postRes.post.url, q: this.state.postRes.post_view.post.url,
type_: SearchType.Url, type_: SearchType.Url,
sort: SortType.TopAll, sort: SortType.TopAll,
page: 1, page: 1,
limit: 6, limit: 6,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.search(form); WebSocketService.Instance.client.search(form);
} }
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.CreateComment) { } else if (op == UserOperation.CreateComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
// Necessary since it might be a user reply // Necessary since it might be a user reply
if (data.recipient_ids.length == 0) { if (data.recipient_ids.length == 0) {
this.state.postRes.comments.unshift(data.comment); this.state.postRes.comments.unshift(data.comment_view);
this.setState(this.state); this.setState(this.state);
} }
} else if ( } else if (
res.op == UserOperation.EditComment || op == UserOperation.EditComment ||
res.op == UserOperation.DeleteComment || op == UserOperation.DeleteComment ||
res.op == UserOperation.RemoveComment op == UserOperation.RemoveComment
) { ) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
editCommentRes(data, this.state.postRes.comments); editCommentRes(data.comment_view, this.state.postRes.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.SaveComment) { } else if (op == UserOperation.SaveComment) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
saveCommentRes(data, this.state.postRes.comments); saveCommentRes(data.comment_view, this.state.postRes.comments);
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if (res.op == UserOperation.CreateCommentLike) { } else if (op == UserOperation.CreateCommentLike) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data, this.state.postRes.comments); createCommentLikeRes(data.comment_view, this.state.postRes.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (op == UserOperation.CreatePostLike) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
createPostLikeRes(data, this.state.postRes.post); createPostLikeRes(data.post_view, this.state.postRes.post_view);
this.setState(this.state); this.setState(this.state);
} else if ( } else if (
res.op == UserOperation.EditPost || op == UserOperation.EditPost ||
res.op == UserOperation.DeletePost || op == UserOperation.DeletePost ||
res.op == UserOperation.RemovePost || op == UserOperation.RemovePost ||
res.op == UserOperation.LockPost || op == UserOperation.LockPost ||
res.op == UserOperation.StickyPost op == UserOperation.StickyPost ||
op == UserOperation.SavePost
) { ) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
this.state.postRes.post = data.post; this.state.postRes.post_view = data.post_view;
this.setState(this.state);
setupTippy();
} else if (res.op == UserOperation.SavePost) {
let data = res.data as PostResponse;
this.state.postRes.post = data.post;
this.setState(this.state); this.setState(this.state);
setupTippy(); setupTippy();
} else if ( } else if (
res.op == UserOperation.EditCommunity || op == UserOperation.EditCommunity ||
res.op == UserOperation.DeleteCommunity || op == UserOperation.DeleteCommunity ||
res.op == UserOperation.RemoveCommunity op == UserOperation.RemoveCommunity ||
op == UserOperation.FollowCommunity
) { ) {
let data = res.data as CommunityResponse; let data = wsJsonToRes<CommunityResponse>(msg).data;
this.state.postRes.community = data.community; this.state.postRes.community_view = data.community_view;
this.state.postRes.post.community_id = data.community.id; this.state.postRes.post_view.community = data.community_view.community;
this.state.postRes.post.community_name = data.community.name;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.FollowCommunity) {
let data = res.data as CommunityResponse;
this.state.postRes.community.subscribed = data.community.subscribed;
this.state.postRes.community.number_of_subscribers =
data.community.number_of_subscribers;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.BanFromCommunity) { } else if (op == UserOperation.BanFromCommunity) {
let data = res.data as BanFromCommunityResponse; let data = wsJsonToRes<BanFromCommunityResponse>(msg).data;
this.state.postRes.comments this.state.postRes.comments
.filter(c => c.creator_id == data.user.id) .filter(c => c.creator.id == data.user_view.user.id)
.forEach(c => (c.banned_from_community = data.banned)); .forEach(c => (c.creator_banned_from_community = data.banned));
if (this.state.postRes.post.creator_id == data.user.id) { if (this.state.postRes.post_view.creator.id == data.user_view.user.id) {
this.state.postRes.post.banned_from_community = data.banned; this.state.postRes.post_view.creator_banned_from_community =
data.banned;
} }
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.AddModToCommunity) { } else if (op == UserOperation.AddModToCommunity) {
let data = res.data as AddModToCommunityResponse; let data = wsJsonToRes<AddModToCommunityResponse>(msg).data;
this.state.postRes.moderators = data.moderators; this.state.postRes.moderators = data.moderators;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.BanUser) { } else if (op == UserOperation.BanUser) {
let data = res.data as BanUserResponse; let data = wsJsonToRes<BanUserResponse>(msg).data;
this.state.postRes.comments this.state.postRes.comments
.filter(c => c.creator_id == data.user.id) .filter(c => c.creator.id == data.user_view.user.id)
.forEach(c => (c.banned = data.banned)); .forEach(c => (c.creator.banned = data.banned));
if (this.state.postRes.post.creator_id == data.user.id) { if (this.state.postRes.post_view.creator.id == data.user_view.user.id) {
this.state.postRes.post.banned = data.banned; this.state.postRes.post_view.creator.banned = data.banned;
} }
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.AddAdmin) { } else if (op == UserOperation.AddAdmin) {
let data = res.data as AddAdminResponse; let data = wsJsonToRes<AddAdminResponse>(msg).data;
this.state.siteRes.admins = data.admins; this.state.siteRes.admins = data.admins;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.Search) { } else if (op == UserOperation.Search) {
let data = res.data as SearchResponse; let data = wsJsonToRes<SearchResponse>(msg).data;
this.state.crossPosts = data.posts.filter( this.state.crossPosts = data.posts.filter(
p => p.id != Number(this.props.match.params.id) p => p.post.id != Number(this.props.match.params.id)
); );
if (this.state.crossPosts.length) {
this.state.postRes.post.duplicates = this.state.crossPosts;
}
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.TransferSite) { } else if (op == UserOperation.TransferSite) {
let data = res.data as GetSiteResponse; let data = wsJsonToRes<GetSiteResponse>(msg).data;
this.state.siteRes = data; this.state.siteRes = data;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.TransferCommunity) { } else if (op == UserOperation.TransferCommunity) {
let data = res.data as GetCommunityResponse; let data = wsJsonToRes<GetCommunityResponse>(msg).data;
this.state.postRes.community = data.community; this.state.postRes.community_view = data.community_view;
this.state.postRes.post_view.community = data.community_view.community;
this.state.postRes.moderators = data.moderators; this.state.postRes.moderators = data.moderators;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.ListCategories) { } else if (op == UserOperation.ListCategories) {
let data = res.data as ListCategoriesResponse; let data = wsJsonToRes<ListCategoriesResponse>(msg).data;
this.state.categories = data.categories; this.state.categories = data.categories;
this.setState(this.state); this.setState(this.state);
} }

View file

@ -2,15 +2,14 @@ import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router'; import { Prompt } from 'inferno-router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
PrivateMessageForm as PrivateMessageFormI, CreatePrivateMessage,
EditPrivateMessageForm, EditPrivateMessage,
PrivateMessage, PrivateMessageView,
PrivateMessageResponse, PrivateMessageResponse,
UserView, UserSafe,
UserOperation, UserOperation,
WebSocketJsonResponse,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { import {
capitalizeFirstLetter, capitalizeFirstLetter,
wsJsonToRes, wsJsonToRes,
@ -18,6 +17,7 @@ import {
setupTippy, setupTippy,
wsSubscribe, wsSubscribe,
isBrowser, isBrowser,
wsUserOp,
} from '../utils'; } from '../utils';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
@ -25,15 +25,15 @@ import { i18n } from '../i18next';
import { T } from 'inferno-i18next'; import { T } from 'inferno-i18next';
interface PrivateMessageFormProps { interface PrivateMessageFormProps {
recipient: UserView; recipient: UserSafe;
privateMessage?: PrivateMessage; // If a pm is given, that means this is an edit privateMessage?: PrivateMessageView; // If a pm is given, that means this is an edit
onCancel?(): any; onCancel?(): any;
onCreate?(message: PrivateMessage): any; onCreate?(message: PrivateMessageView): any;
onEdit?(message: PrivateMessage): any; onEdit?(message: PrivateMessageView): any;
} }
interface PrivateMessageFormState { interface PrivateMessageFormState {
privateMessageForm: PrivateMessageFormI; privateMessageForm: CreatePrivateMessage;
loading: boolean; loading: boolean;
previewMode: boolean; previewMode: boolean;
showDisclaimer: boolean; showDisclaimer: boolean;
@ -48,6 +48,7 @@ export class PrivateMessageForm extends Component<
privateMessageForm: { privateMessageForm: {
content: null, content: null,
recipient_id: this.props.recipient.id, recipient_id: this.props.recipient.id,
auth: UserService.Instance.authField(),
}, },
loading: false, loading: false,
previewMode: false, previewMode: false,
@ -64,11 +65,9 @@ export class PrivateMessageForm extends Component<
this.parseMessage = this.parseMessage.bind(this); this.parseMessage = this.parseMessage.bind(this);
this.subscription = wsSubscribe(this.parseMessage); this.subscription = wsSubscribe(this.parseMessage);
// Its an edit
if (this.props.privateMessage) { if (this.props.privateMessage) {
this.state.privateMessageForm = { this.state.privateMessageForm.content = this.props.privateMessage.private_message.content;
content: this.props.privateMessage.content,
recipient_id: this.props.privateMessage.recipient_id,
};
} }
} }
@ -106,16 +105,7 @@ export class PrivateMessageForm extends Component<
</label> </label>
<div class="col-sm-10 form-control-plaintext"> <div class="col-sm-10 form-control-plaintext">
<UserListing <UserListing user={this.props.recipient} />
user={{
name: this.props.recipient.name,
preferred_username: this.props.recipient.preferred_username,
avatar: this.props.recipient.avatar,
id: this.props.recipient.id,
local: this.props.recipient.local,
actor_id: this.props.recipient.actor_id,
}}
/>
</div> </div>
</div> </div>
)} )}
@ -198,13 +188,14 @@ export class PrivateMessageForm extends Component<
handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) { handlePrivateMessageSubmit(i: PrivateMessageForm, event: any) {
event.preventDefault(); event.preventDefault();
if (i.props.privateMessage) { if (i.props.privateMessage) {
let editForm: EditPrivateMessageForm = { let form: EditPrivateMessage = {
edit_id: i.props.privateMessage.id, edit_id: i.props.privateMessage.private_message.id,
content: i.state.privateMessageForm.content, content: i.state.privateMessageForm.content,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.editPrivateMessage(editForm); WebSocketService.Instance.client.editPrivateMessage(form);
} else { } else {
WebSocketService.Instance.createPrivateMessage( WebSocketService.Instance.client.createPrivateMessage(
i.state.privateMessageForm i.state.privateMessageForm
); );
} }
@ -232,25 +223,25 @@ export class PrivateMessageForm extends Component<
i.setState(i.state); i.setState(i.state);
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
return; return;
} else if ( } else if (
res.op == UserOperation.EditPrivateMessage || op == UserOperation.EditPrivateMessage ||
res.op == UserOperation.DeletePrivateMessage || op == UserOperation.DeletePrivateMessage ||
res.op == UserOperation.MarkPrivateMessageAsRead op == UserOperation.MarkPrivateMessageAsRead
) { ) {
let data = res.data as PrivateMessageResponse; let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
this.state.loading = false; this.state.loading = false;
this.props.onEdit(data.message); this.props.onEdit(data.private_message_view);
} else if (res.op == UserOperation.CreatePrivateMessage) { } else if (op == UserOperation.CreatePrivateMessage) {
let data = res.data as PrivateMessageResponse; let data = wsJsonToRes<PrivateMessageResponse>(msg).data;
this.state.loading = false; this.state.loading = false;
this.props.onCreate(data.message); this.props.onCreate(data.private_message_view);
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -1,9 +1,9 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { import {
PrivateMessage as PrivateMessageI, PrivateMessageView,
DeletePrivateMessageForm, DeletePrivateMessage,
MarkPrivateMessageAsReadForm, MarkPrivateMessageAsRead,
UserView, UserSafe,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { mdToHtml, toast } from '../utils'; import { mdToHtml, toast } from '../utils';
@ -20,7 +20,7 @@ interface PrivateMessageState {
} }
interface PrivateMessageProps { interface PrivateMessageProps {
privateMessage: PrivateMessageI; private_message_view: PrivateMessageView;
} }
export class PrivateMessage extends Component< export class PrivateMessage extends Component<
@ -48,41 +48,16 @@ export class PrivateMessage extends Component<
get mine(): boolean { get mine(): boolean {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
UserService.Instance.user.id == this.props.privateMessage.creator_id UserService.Instance.user.id == this.props.private_message_view.creator.id
); );
} }
render() { render() {
let message = this.props.privateMessage; let message_view = this.props.private_message_view;
let userOther: UserView = this.mine // TODO check this again
? { let userOther: UserSafe = this.mine
name: message.recipient_name, ? message_view.recipient
preferred_username: message.recipient_preferred_username, : message_view.creator;
id: message.recipient_id,
avatar: message.recipient_avatar,
local: message.recipient_local,
actor_id: message.recipient_actor_id,
published: message.published,
number_of_posts: 0,
post_score: 0,
number_of_comments: 0,
comment_score: 0,
banned: false,
}
: {
name: message.creator_name,
preferred_username: message.creator_preferred_username,
id: message.creator_id,
avatar: message.creator_avatar,
local: message.creator_local,
actor_id: message.creator_actor_id,
published: message.published,
number_of_posts: 0,
post_score: 0,
number_of_comments: 0,
comment_score: 0,
banned: false,
};
return ( return (
<div class="border-top border-light"> <div class="border-top border-light">
@ -97,7 +72,7 @@ export class PrivateMessage extends Component<
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<span> <span>
<MomentTime data={message} /> <MomentTime data={message_view.private_message} />
</span> </span>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@ -120,7 +95,7 @@ export class PrivateMessage extends Component<
{this.state.showEdit && ( {this.state.showEdit && (
<PrivateMessageForm <PrivateMessageForm
recipient={userOther} recipient={userOther}
privateMessage={message} privateMessage={message_view}
onEdit={this.handlePrivateMessageEdit} onEdit={this.handlePrivateMessageEdit}
onCreate={this.handlePrivateMessageCreate} onCreate={this.handlePrivateMessageCreate}
onCancel={this.handleReplyCancel} onCancel={this.handleReplyCancel}
@ -144,14 +119,14 @@ export class PrivateMessage extends Component<
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleMarkRead)} onClick={linkEvent(this, this.handleMarkRead)}
data-tippy-content={ data-tippy-content={
message.read message_view.private_message.read
? i18n.t('mark_as_unread') ? i18n.t('mark_as_unread')
: i18n.t('mark_as_read') : i18n.t('mark_as_read')
} }
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
message.read && 'text-success' message_view.private_message.read && 'text-success'
}`} }`}
> >
<use xlinkHref="#icon-check"></use> <use xlinkHref="#icon-check"></use>
@ -189,14 +164,15 @@ export class PrivateMessage extends Component<
class="btn btn-link btn-animate text-muted" class="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleDeleteClick)} onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={ data-tippy-content={
!message.deleted !message_view.private_message.deleted
? i18n.t('delete') ? i18n.t('delete')
: i18n.t('restore') : i18n.t('restore')
} }
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
message.deleted && 'text-danger' message_view.private_message.deleted &&
'text-danger'
}`} }`}
> >
<use xlinkHref="#icon-trash"></use> <use xlinkHref="#icon-trash"></use>
@ -237,7 +213,7 @@ export class PrivateMessage extends Component<
} }
get messageUnlessRemoved(): string { get messageUnlessRemoved(): string {
let message = this.props.privateMessage; let message = this.props.private_message_view.private_message;
return message.deleted ? `*${i18n.t('deleted')}*` : message.content; return message.deleted ? `*${i18n.t('deleted')}*` : message.content;
} }
@ -252,11 +228,12 @@ export class PrivateMessage extends Component<
} }
handleDeleteClick(i: PrivateMessage) { handleDeleteClick(i: PrivateMessage) {
let form: DeletePrivateMessageForm = { let form: DeletePrivateMessage = {
edit_id: i.props.privateMessage.id, edit_id: i.props.private_message_view.private_message.id,
deleted: !i.props.privateMessage.deleted, deleted: !i.props.private_message_view.private_message.deleted,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.deletePrivateMessage(form); WebSocketService.Instance.client.deletePrivateMessage(form);
} }
handleReplyCancel() { handleReplyCancel() {
@ -266,11 +243,12 @@ export class PrivateMessage extends Component<
} }
handleMarkRead(i: PrivateMessage) { handleMarkRead(i: PrivateMessage) {
let form: MarkPrivateMessageAsReadForm = { let form: MarkPrivateMessageAsRead = {
edit_id: i.props.privateMessage.id, edit_id: i.props.private_message_view.private_message.id,
read: !i.props.privateMessage.read, read: !i.props.private_message_view.private_message.read,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.markPrivateMessageAsRead(form); WebSocketService.Instance.client.markPrivateMessageAsRead(form);
} }
handleMessageCollapse(i: PrivateMessage) { handleMessageCollapse(i: PrivateMessage) {
@ -288,10 +266,10 @@ export class PrivateMessage extends Component<
this.setState(this.state); this.setState(this.state);
} }
handlePrivateMessageCreate(message: PrivateMessageI) { handlePrivateMessageCreate(message: PrivateMessageView) {
if ( if (
UserService.Instance.user && UserService.Instance.user &&
message.creator_id == UserService.Instance.user.id message.creator.id == UserService.Instance.user.id
) { ) {
this.state.showReply = false; this.state.showReply = false;
this.setState(this.state); this.setState(this.state);

View file

@ -2,20 +2,19 @@ import { Component, linkEvent } from 'inferno';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
UserOperation, UserOperation,
Post, PostView,
Comment, CommentView,
Community, CommunityView,
UserView, UserViewSafe,
SortType, SortType,
SearchForm, Search as SearchForm,
SearchResponse, SearchResponse,
SearchType, SearchType,
PostResponse, PostResponse,
CommentResponse, CommentResponse,
WebSocketJsonResponse,
Site, Site,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { import {
wsJsonToRes, wsJsonToRes,
fetchLimit, fetchLimit,
@ -27,7 +26,7 @@ import {
commentsToFlatNodes, commentsToFlatNodes,
setIsoData, setIsoData,
wsSubscribe, wsSubscribe,
setAuth, wsUserOp,
} from '../utils'; } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
@ -80,7 +79,7 @@ export class Search extends Component<any, SearchState> {
users: [], users: [],
}, },
loading: false, loading: false,
site: this.isoData.site.site, site: this.isoData.site_res.site_view.site,
}; };
static getSearchQueryFromProps(q: string): string { static getSearchQueryFromProps(q: string): string {
@ -142,8 +141,8 @@ export class Search extends Component<any, SearchState> {
sort: this.getSortTypeFromProps(pathSplit[7]), sort: this.getSortTypeFromProps(pathSplit[7]),
page: this.getPageFromProps(pathSplit[9]), page: this.getPageFromProps(pathSplit[9]),
limit: fetchLimit, limit: fetchLimit,
auth: req.auth,
}; };
setAuth(form, req.auth);
if (form.q != '') { if (form.q != '') {
promises.push(req.client.search(form)); promises.push(req.client.search(form));
@ -252,19 +251,24 @@ export class Search extends Component<any, SearchState> {
all() { all() {
let combined: { let combined: {
type_: string; type_: string;
data: Comment | Post | Community | UserView; data: CommentView | PostView | CommunityView | UserViewSafe;
published: string;
}[] = []; }[] = [];
let comments = this.state.searchResponse.comments.map(e => { let comments = this.state.searchResponse.comments.map(e => {
return { type_: 'comments', data: e }; return { type_: 'comments', data: e, published: e.comment.published };
}); });
let posts = this.state.searchResponse.posts.map(e => { let posts = this.state.searchResponse.posts.map(e => {
return { type_: 'posts', data: e }; return { type_: 'posts', data: e, published: e.post.published };
}); });
let communities = this.state.searchResponse.communities.map(e => { let communities = this.state.searchResponse.communities.map(e => {
return { type_: 'communities', data: e }; return {
type_: 'communities',
data: e,
published: e.community.published,
};
}); });
let users = this.state.searchResponse.users.map(e => { let users = this.state.searchResponse.users.map(e => {
return { type_: 'users', data: e }; return { type_: 'users', data: e, published: e.user.published };
}); });
combined.push(...comments); combined.push(...comments);
@ -274,16 +278,16 @@ export class Search extends Component<any, SearchState> {
// Sort it // Sort it
if (this.state.sort == SortType.New) { if (this.state.sort == SortType.New) {
combined.sort((a, b) => b.data.published.localeCompare(a.data.published)); combined.sort((a, b) => b.published.localeCompare(a.published));
} else { } else {
combined.sort( combined.sort(
(a, b) => (a, b) =>
((b.data as Comment | Post).score | ((b.data as CommentView | PostView).counts.score |
(b.data as Community).number_of_subscribers | (b.data as CommunityView).counts.subscribers |
(b.data as UserView).comment_score) - (b.data as UserViewSafe).counts.comment_score) -
((a.data as Comment | Post).score | ((a.data as CommentView | PostView).counts.score |
(a.data as Community).number_of_subscribers | (a.data as CommunityView).counts.subscribers |
(a.data as UserView).comment_score) (a.data as UserViewSafe).counts.comment_score)
); );
} }
@ -294,8 +298,8 @@ export class Search extends Component<any, SearchState> {
<div class="col-12"> <div class="col-12">
{i.type_ == 'posts' && ( {i.type_ == 'posts' && (
<PostListing <PostListing
key={(i.data as Post).id} key={(i.data as PostView).post.id}
post={i.data as Post} post_view={i.data as PostView}
showCommunity showCommunity
enableDownvotes={this.state.site.enable_downvotes} enableDownvotes={this.state.site.enable_downvotes}
enableNsfw={this.state.site.enable_nsfw} enableNsfw={this.state.site.enable_nsfw}
@ -303,18 +307,18 @@ export class Search extends Component<any, SearchState> {
)} )}
{i.type_ == 'comments' && ( {i.type_ == 'comments' && (
<CommentNodes <CommentNodes
key={(i.data as Comment).id} key={(i.data as CommentView).comment.id}
nodes={[{ comment: i.data as Comment }]} nodes={[{ comment_view: i.data as CommentView }]}
locked locked
noIndent noIndent
enableDownvotes={this.state.site.enable_downvotes} enableDownvotes={this.state.site.enable_downvotes}
/> />
)} )}
{i.type_ == 'communities' && ( {i.type_ == 'communities' && (
<div>{this.communityListing(i.data as Community)}</div> <div>{this.communityListing(i.data as CommunityView)}</div>
)} )}
{i.type_ == 'users' && ( {i.type_ == 'users' && (
<div>{this.userListing(i.data as UserView)}</div> <div>{this.userListing(i.data as UserViewSafe)}</div>
)} )}
</div> </div>
</div> </div>
@ -341,7 +345,7 @@ export class Search extends Component<any, SearchState> {
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<PostListing <PostListing
post={post} post_view={post}
showCommunity showCommunity
enableDownvotes={this.state.site.enable_downvotes} enableDownvotes={this.state.site.enable_downvotes}
enableNsfw={this.state.site.enable_nsfw} enableNsfw={this.state.site.enable_nsfw}
@ -365,28 +369,28 @@ export class Search extends Component<any, SearchState> {
); );
} }
communityListing(community: Community) { communityListing(community_view: CommunityView) {
return ( return (
<> <>
<span> <span>
<CommunityLink community={community} /> <CommunityLink community={community_view.community} />
</span> </span>
<span>{` - ${community.title} - <span>{` - ${community_view.community.title} -
${i18n.t('number_of_subscribers', { ${i18n.t('number_of_subscribers', {
count: community.number_of_subscribers, count: community_view.counts.subscribers,
})} })}
`}</span> `}</span>
</> </>
); );
} }
userListing(user: UserView) { userListing(user_view: UserViewSafe) {
return [ return [
<span> <span>
<UserListing user={user} showApubName /> <UserListing user={user_view.user} showApubName />
</span>, </span>,
<span>{` - ${i18n.t('number_of_comments', { <span>{` - ${i18n.t('number_of_comments', {
count: user.number_of_comments, count: user_view.counts.comment_count,
})}`}</span>, })}`}</span>,
]; ];
} }
@ -452,10 +456,11 @@ export class Search extends Component<any, SearchState> {
sort: this.state.sort, sort: this.state.sort,
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
auth: UserService.Instance.authField(false),
}; };
if (this.state.q != '') { if (this.state.q != '') {
WebSocketService.Instance.search(form); WebSocketService.Instance.client.search(form);
} }
} }
@ -495,25 +500,28 @@ export class Search extends Component<any, SearchState> {
); );
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); console.log(msg);
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
return; return;
} else if (res.op == UserOperation.Search) { } else if (op == UserOperation.Search) {
let data = res.data as SearchResponse; let data = wsJsonToRes<SearchResponse>(msg).data;
this.state.searchResponse = data; this.state.searchResponse = data;
this.state.loading = false; this.state.loading = false;
window.scrollTo(0, 0); window.scrollTo(0, 0);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateCommentLike) { } else if (op == UserOperation.CreateCommentLike) {
let data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data, this.state.searchResponse.comments); createCommentLikeRes(
data.comment_view,
this.state.searchResponse.comments
);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (op == UserOperation.CreatePostLike) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
createPostLikeFindRes(data, this.state.searchResponse.posts); createPostLikeFindRes(data.post_view, this.state.searchResponse.posts);
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -2,19 +2,14 @@ import { Component, linkEvent } from 'inferno';
import { Helmet } from 'inferno-helmet'; import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { import { Register, LoginResponse, UserOperation } from 'lemmy-js-client';
RegisterForm,
LoginResponse,
UserOperation,
WebSocketJsonResponse,
} from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { wsJsonToRes, toast } from '../utils'; import { wsUserOp, wsJsonToRes, toast } from '../utils';
import { SiteForm } from './site-form'; import { SiteForm } from './site-form';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface State { interface State {
userForm: RegisterForm; userForm: Register;
doneRegisteringUser: boolean; doneRegisteringUser: boolean;
userLoading: boolean; userLoading: boolean;
} }
@ -168,7 +163,7 @@ export class Setup extends Component<any, State> {
i.state.userLoading = true; i.state.userLoading = true;
i.setState(i.state); i.setState(i.state);
event.preventDefault(); event.preventDefault();
WebSocketService.Instance.register(i.state.userForm); WebSocketService.Instance.client.register(i.state.userForm);
} }
handleRegisterUsernameChange(i: Setup, event: any) { handleRegisterUsernameChange(i: Setup, event: any) {
@ -191,20 +186,20 @@ export class Setup extends Component<any, State> {
i.setState(i.state); i.setState(i.state);
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
let res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
this.state.userLoading = false; this.state.userLoading = false;
this.setState(this.state); this.setState(this.state);
return; return;
} else if (res.op == UserOperation.Register) { } else if (op == UserOperation.Register) {
let data = res.data as LoginResponse; let data = wsJsonToRes<LoginResponse>(msg).data;
this.state.userLoading = false; this.state.userLoading = false;
this.state.doneRegisteringUser = true; this.state.doneRegisteringUser = true;
UserService.Instance.login(data); UserService.Instance.login(data);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateSite) { } else if (op == UserOperation.CreateSite) {
window.location.href = '/'; window.location.href = '/';
} }
} }

View file

@ -1,13 +1,13 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { import {
Community, CommunityView,
CommunityUser, CommunityModeratorView,
FollowCommunityForm, FollowCommunity,
DeleteCommunityForm, DeleteCommunity,
RemoveCommunityForm, RemoveCommunity,
UserView, UserViewSafe,
AddModToCommunityForm, AddModToCommunity,
Category, Category,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
@ -19,10 +19,10 @@ import { BannerIconHeader } from './banner-icon-header';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
interface SidebarProps { interface SidebarProps {
community: Community; community_view: CommunityView;
categories: Category[]; categories: Category[];
moderators: CommunityUser[]; moderators: CommunityModeratorView[];
admins: UserView[]; admins: UserViewSafe[];
online: number; online: number;
enableNsfw: boolean; enableNsfw: boolean;
showIcon?: boolean; showIcon?: boolean;
@ -60,7 +60,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
) : ( ) : (
<CommunityForm <CommunityForm
categories={this.props.categories} categories={this.props.categories}
community={this.props.community} community_view={this.props.community_view}
onEdit={this.handleEditCommunity} onEdit={this.handleEditCommunity}
onCancel={this.handleEditCancel} onCancel={this.handleEditCancel}
enableNsfw={this.props.enableNsfw} enableNsfw={this.props.enableNsfw}
@ -93,7 +93,8 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
} }
communityTitle() { communityTitle() {
let community = this.props.community; let community = this.props.community_view.community;
let subscribed = this.props.community_view.subscribed;
return ( return (
<div> <div>
<h5 className="mb-0"> <h5 className="mb-0">
@ -101,7 +102,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<BannerIconHeader icon={community.icon} banner={community.banner} /> <BannerIconHeader icon={community.icon} banner={community.banner} />
)} )}
<span class="mr-2">{community.title}</span> <span class="mr-2">{community.title}</span>
{community.subscribed && ( {subscribed && (
<a <a
class="btn btn-secondary btn-sm mr-2" class="btn btn-secondary btn-sm mr-2"
href="#" href="#"
@ -141,7 +142,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
} }
badges() { badges() {
let community = this.props.community; let community_view = this.props.community_view;
return ( return (
<ul class="my-1 list-inline"> <ul class="my-1 list-inline">
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
@ -149,28 +150,28 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
{i18n.t('number_of_subscribers', { {i18n.t('number_of_subscribers', {
count: community.number_of_subscribers, count: community_view.counts.subscribers,
})} })}
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
{i18n.t('number_of_posts', { {i18n.t('number_of_posts', {
count: community.number_of_posts, count: community_view.counts.posts,
})} })}
</li> </li>
<li className="list-inline-item badge badge-secondary"> <li className="list-inline-item badge badge-secondary">
{i18n.t('number_of_comments', { {i18n.t('number_of_comments', {
count: community.number_of_comments, count: community_view.counts.comments,
})} })}
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<Link className="badge badge-secondary" to="/communities"> <Link className="badge badge-secondary" to="/communities">
{community.category_name} {community_view.category.name}
</Link> </Link>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<Link <Link
className="badge badge-secondary" className="badge badge-secondary"
to={`/modlog/community/${this.props.community.id}`} to={`/modlog/community/${this.props.community_view.community.id}`}
> >
{i18n.t('modlog')} {i18n.t('modlog')}
</Link> </Link>
@ -185,16 +186,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<li class="list-inline-item">{i18n.t('mods')}: </li> <li class="list-inline-item">{i18n.t('mods')}: </li>
{this.props.moderators.map(mod => ( {this.props.moderators.map(mod => (
<li class="list-inline-item"> <li class="list-inline-item">
<UserListing <UserListing user={mod.moderator} />
user={{
name: mod.user_name,
preferred_username: mod.user_preferred_username,
avatar: mod.avatar,
id: mod.user_id,
local: mod.user_local,
actor_id: mod.user_actor_id,
}}
/>
</li> </li>
))} ))}
</ul> </ul>
@ -202,14 +194,16 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
} }
createPost() { createPost() {
let community = this.props.community; let community_view = this.props.community_view;
return ( return (
community.subscribed && ( community_view.subscribed && (
<Link <Link
className={`btn btn-secondary btn-block mb-2 ${ className={`btn btn-secondary btn-block mb-2 ${
community.deleted || community.removed ? 'no-click' : '' community_view.community.deleted || community_view.community.removed
? 'no-click'
: ''
}`} }`}
to={`/create_post?community_id=${community.id}`} to={`/create_post?community_id=${community_view.community.id}`}
> >
{i18n.t('create_a_post')} {i18n.t('create_a_post')}
</Link> </Link>
@ -218,14 +212,17 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
} }
subscribe() { subscribe() {
let community = this.props.community; let community_view = this.props.community_view;
return ( return (
<div class="mb-2"> <div class="mb-2">
{!community.subscribed && ( {!community_view.subscribed && (
<a <a
class="btn btn-secondary btn-block" class="btn btn-secondary btn-block"
href="#" href="#"
onClick={linkEvent(community.id, this.handleSubscribe)} onClick={linkEvent(
community_view.community.id,
this.handleSubscribe
)}
> >
{i18n.t('subscribe')} {i18n.t('subscribe')}
</a> </a>
@ -235,19 +232,19 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
} }
description() { description() {
let community = this.props.community; let description = this.props.community_view.community.description;
return ( return (
community.description && ( description && (
<div <div
className="md-div" className="md-div"
dangerouslySetInnerHTML={mdToHtml(community.description)} dangerouslySetInnerHTML={mdToHtml(description)}
/> />
) )
); );
} }
adminButtons() { adminButtons() {
let community = this.props.community; let community_view = this.props.community_view;
return ( return (
<> <>
<ul class="list-inline mb-1 text-muted font-weight-bold"> <ul class="list-inline mb-1 text-muted font-weight-bold">
@ -309,12 +306,14 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleDeleteClick)} onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={ data-tippy-content={
!community.deleted ? i18n.t('delete') : i18n.t('restore') !community_view.community.deleted
? i18n.t('delete')
: i18n.t('restore')
} }
> >
<svg <svg
class={`icon icon-inline ${ class={`icon icon-inline ${
community.deleted && 'text-danger' community_view.community.deleted && 'text-danger'
}`} }`}
> >
<use xlinkHref="#icon-trash"></use> <use xlinkHref="#icon-trash"></use>
@ -326,7 +325,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
)} )}
{this.canAdmin && ( {this.canAdmin && (
<li className="list-inline-item"> <li className="list-inline-item">
{!this.props.community.removed ? ( {!this.props.community_view.community.removed ? (
<span <span
class="pointer" class="pointer"
onClick={linkEvent(this, this.handleModRemoveShow)} onClick={linkEvent(this, this.handleModRemoveShow)}
@ -392,11 +391,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
handleDeleteClick(i: Sidebar, event: any) { handleDeleteClick(i: Sidebar, event: any) {
event.preventDefault(); event.preventDefault();
let deleteForm: DeleteCommunityForm = { let deleteForm: DeleteCommunity = {
edit_id: i.props.community.id, edit_id: i.props.community_view.community.id,
deleted: !i.props.community.deleted, deleted: !i.props.community_view.community.deleted,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.deleteCommunity(deleteForm); WebSocketService.Instance.client.deleteCommunity(deleteForm);
} }
handleShowConfirmLeaveModTeamClick(i: Sidebar) { handleShowConfirmLeaveModTeamClick(i: Sidebar) {
@ -405,12 +405,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
} }
handleLeaveModTeamClick(i: Sidebar) { handleLeaveModTeamClick(i: Sidebar) {
let form: AddModToCommunityForm = { let form: AddModToCommunity = {
user_id: UserService.Instance.user.id, user_id: UserService.Instance.user.id,
community_id: i.props.community.id, community_id: i.props.community_view.community.id,
added: false, added: false,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.addModToCommunity(form); WebSocketService.Instance.client.addModToCommunity(form);
i.state.showConfirmLeaveModTeam = false; i.state.showConfirmLeaveModTeam = false;
i.setState(i.state); i.setState(i.state);
} }
@ -422,31 +423,33 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
handleUnsubscribe(communityId: number, event: any) { handleUnsubscribe(communityId: number, event: any) {
event.preventDefault(); event.preventDefault();
let form: FollowCommunityForm = { let form: FollowCommunity = {
community_id: communityId, community_id: communityId,
follow: false, follow: false,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.followCommunity(form); WebSocketService.Instance.client.followCommunity(form);
} }
handleSubscribe(communityId: number, event: any) { handleSubscribe(communityId: number, event: any) {
event.preventDefault(); event.preventDefault();
let form: FollowCommunityForm = { let form: FollowCommunity = {
community_id: communityId, community_id: communityId,
follow: true, follow: true,
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.followCommunity(form); WebSocketService.Instance.client.followCommunity(form);
} }
private get amCreator(): boolean { private get amCreator(): boolean {
return this.props.community.creator_id == UserService.Instance.user.id; return this.props.community_view.creator.id == UserService.Instance.user.id;
} }
get canMod(): boolean { get canMod(): boolean {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
this.props.moderators this.props.moderators
.map(m => m.user_id) .map(m => m.moderator.id)
.includes(UserService.Instance.user.id) .includes(UserService.Instance.user.id)
); );
} }
@ -454,7 +457,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
get canAdmin(): boolean { get canAdmin(): boolean {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
this.props.admins.map(a => a.id).includes(UserService.Instance.user.id) this.props.admins
.map(a => a.user.id)
.includes(UserService.Instance.user.id)
); );
} }
@ -476,13 +481,14 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
handleModRemoveSubmit(i: Sidebar, event: any) { handleModRemoveSubmit(i: Sidebar, event: any) {
event.preventDefault(); event.preventDefault();
let removeForm: RemoveCommunityForm = { let removeForm: RemoveCommunity = {
edit_id: i.props.community.id, edit_id: i.props.community_view.community.id,
removed: !i.props.community.removed, removed: !i.props.community_view.community.removed,
reason: i.state.removeReason, reason: i.state.removeReason,
expires: getUnixTime(i.state.removeExpires), expires: getUnixTime(i.state.removeExpires),
auth: UserService.Instance.authField(),
}; };
WebSocketService.Instance.removeCommunity(removeForm); WebSocketService.Instance.client.removeCommunity(removeForm);
i.state.showRemoveDialog = false; i.state.showRemoveDialog = false;
i.setState(i.state); i.setState(i.state);

View file

@ -2,8 +2,8 @@ import { Component, linkEvent } from 'inferno';
import { Prompt } from 'inferno-router'; import { Prompt } from 'inferno-router';
import { MarkdownTextArea } from './markdown-textarea'; import { MarkdownTextArea } from './markdown-textarea';
import { ImageUploadForm } from './image-upload-form'; import { ImageUploadForm } from './image-upload-form';
import { Site, SiteForm as SiteFormI } from 'lemmy-js-client'; import { Site, EditSite } from 'lemmy-js-client';
import { WebSocketService } from '../services'; import { UserService, WebSocketService } from '../services';
import { capitalizeFirstLetter, randomStr } from '../utils'; import { capitalizeFirstLetter, randomStr } from '../utils';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
@ -13,7 +13,7 @@ interface SiteFormProps {
} }
interface SiteFormState { interface SiteFormState {
siteForm: SiteFormI; siteForm: EditSite;
loading: boolean; loading: boolean;
} }
@ -27,6 +27,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
name: null, name: null,
icon: null, icon: null,
banner: null, banner: null,
auth: UserService.Instance.authField(),
}, },
loading: false, loading: false,
}; };
@ -54,6 +55,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
enable_nsfw: this.props.site.enable_nsfw, enable_nsfw: this.props.site.enable_nsfw,
icon: this.props.site.icon, icon: this.props.site.icon,
banner: this.props.site.banner, banner: this.props.site.banner,
auth: UserService.Instance.authField(),
}; };
} }
} }
@ -242,9 +244,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
event.preventDefault(); event.preventDefault();
i.state.loading = true; i.state.loading = true;
if (i.props.site) { if (i.props.site) {
WebSocketService.Instance.editSite(i.state.siteForm); WebSocketService.Instance.client.editSite(i.state.siteForm);
} else { } else {
WebSocketService.Instance.createSite(i.state.siteForm); WebSocketService.Instance.client.createSite(i.state.siteForm);
} }
i.setState(i.state); i.setState(i.state);
} }

View file

@ -1,9 +1,9 @@
import { User } from 'lemmy-js-client'; import { User_ } from 'lemmy-js-client';
import { Helmet } from 'inferno-helmet'; import { Helmet } from 'inferno-helmet';
import { Component } from 'inferno'; import { Component } from 'inferno';
interface Props { interface Props {
user: User | undefined; user: User_ | undefined;
} }
export class Theme extends Component<Props> { export class Theme extends Component<Props> {

View file

@ -1,13 +1,20 @@
import { Component, linkEvent } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { i18n } from '../i18next'; import { i18n } from '../i18next';
import { Post, Comment, SortType, UserDetailsResponse } from 'lemmy-js-client'; import {
PostView,
CommentView,
SortType,
GetUserDetailsResponse,
UserViewSafe,
} from 'lemmy-js-client';
import { UserDetailsView } from '../interfaces'; import { UserDetailsView } from '../interfaces';
import { commentsToFlatNodes, setupTippy } from '../utils'; import { commentsToFlatNodes, setupTippy } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
interface UserDetailsProps { interface UserDetailsProps {
userRes: UserDetailsResponse; userRes: GetUserDetailsResponse;
admins: UserViewSafe[];
page: number; page: number;
limit: number; limit: number;
sort: SortType; sort: SortType;
@ -19,6 +26,18 @@ interface UserDetailsProps {
interface UserDetailsState {} interface UserDetailsState {}
enum ItemEnum {
Comment,
Post,
}
type ItemType = {
id: number;
type_: ItemEnum;
view: CommentView | PostView;
published: string;
score: number;
};
export class UserDetails extends Component<UserDetailsProps, UserDetailsState> { export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
@ -60,56 +79,68 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
} }
} }
overview() { renderItemType(i: ItemType) {
const comments = this.props.userRes.comments.map((c: Comment) => { switch (i.type_) {
return { type: 'comments', data: c }; case ItemEnum.Comment:
}); let c = i.view as CommentView;
const posts = this.props.userRes.posts.map((p: Post) => { return (
return { type: 'posts', data: p }; <CommentNodes
}); key={i.id}
nodes={[{ comment_view: c }]}
admins={this.props.admins}
noBorder
noIndent
showCommunity
showContext
enableDownvotes={this.props.enableDownvotes}
/>
);
case ItemEnum.Post:
let p = i.view as PostView;
return (
<PostListing
key={i.id}
post_view={p}
admins={this.props.admins}
showCommunity
enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw}
/>
);
default:
return <div />;
}
}
const combined: { type: string; data: Comment | Post }[] = [ overview() {
...comments, let id = 0;
...posts, let comments: ItemType[] = this.props.userRes.comments.map(r => ({
]; id: id++,
type_: ItemEnum.Comment,
view: r,
published: r.comment.published,
score: r.counts.score,
}));
let posts: ItemType[] = this.props.userRes.posts.map(r => ({
id: id++,
type_: ItemEnum.Comment,
view: r,
published: r.post.published,
score: r.counts.score,
}));
let combined = [...comments, ...posts];
// Sort it // Sort it
if (this.props.sort === SortType.New) { if (this.props.sort === SortType.New) {
combined.sort((a, b) => b.data.published.localeCompare(a.data.published)); combined.sort((a, b) => b.published.localeCompare(a.published));
} else { } else {
combined.sort((a, b) => b.data.score - a.data.score); combined.sort((a, b) => b.score - a.score);
} }
return ( return (
<div> <div>
{combined.map(i => ( {combined.map(i => [this.renderItemType(i), <hr class="my-3" />])}
<>
<div>
{i.type === 'posts' ? (
<PostListing
key={(i.data as Post).id}
post={i.data as Post}
admins={this.props.userRes.admins}
showCommunity
enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw}
/>
) : (
<CommentNodes
key={(i.data as Comment).id}
nodes={[{ comment: i.data as Comment }]}
admins={this.props.userRes.admins}
noBorder
noIndent
showCommunity
showContext
enableDownvotes={this.props.enableDownvotes}
/>
)}
</div>
<hr class="my-3" />
</>
))}
</div> </div>
); );
} }
@ -119,7 +150,7 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
<div> <div>
<CommentNodes <CommentNodes
nodes={commentsToFlatNodes(this.props.userRes.comments)} nodes={commentsToFlatNodes(this.props.userRes.comments)}
admins={this.props.userRes.admins} admins={this.props.admins}
noIndent noIndent
showCommunity showCommunity
showContext showContext
@ -135,8 +166,8 @@ export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
{this.props.userRes.posts.map(post => ( {this.props.userRes.posts.map(post => (
<> <>
<PostListing <PostListing
post={post} post_view={post}
admins={this.props.userRes.admins} admins={this.props.admins}
showCommunity showCommunity
enableDownvotes={this.props.enableDownvotes} enableDownvotes={this.props.enableDownvotes}
enableNsfw={this.props.enableNsfw} enableNsfw={this.props.enableNsfw}

View file

@ -1,22 +1,12 @@
import { Component } from 'inferno'; import { Component } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { UserView } from 'lemmy-js-client'; import { UserSafe } from 'lemmy-js-client';
import { showAvatars, hostname, isCakeDay } from '../utils'; import { showAvatars, hostname, isCakeDay } from '../utils';
import { CakeDay } from './cake-day'; import { CakeDay } from './cake-day';
import { PictrsImage } from './pictrs-image'; import { PictrsImage } from './pictrs-image';
export interface UserOther {
name: string;
preferred_username?: string;
id?: number; // Necessary if its federated
avatar?: string;
local?: boolean;
actor_id?: string;
published?: string;
}
interface UserListingProps { interface UserListingProps {
user: UserView | UserOther; user: UserSafe;
realLink?: boolean; realLink?: boolean;
useApubName?: boolean; useApubName?: boolean;
muted?: boolean; muted?: boolean;

View file

@ -5,14 +5,13 @@ import {
UserOperation, UserOperation,
SortType, SortType,
ListingType, ListingType,
UserSettingsForm, SaveUserSettings,
LoginResponse, LoginResponse,
DeleteAccountForm, DeleteAccount,
WebSocketJsonResponse,
GetSiteResponse, GetSiteResponse,
UserDetailsResponse, GetUserDetailsResponse,
AddAdminResponse, AddAdminResponse,
GetUserDetailsForm, GetUserDetails,
CommentResponse, CommentResponse,
PostResponse, PostResponse,
BanUserResponse, BanUserResponse,
@ -40,9 +39,9 @@ import {
editCommentRes, editCommentRes,
saveCommentRes, saveCommentRes,
createPostLikeFindRes, createPostLikeFindRes,
setAuth,
previewLines, previewLines,
editPostFindRes, editPostFindRes,
wsUserOp,
} from '../utils'; } from '../utils';
import { UserListing } from './user-listing'; import { UserListing } from './user-listing';
import { HtmlTags } from './html-tags'; import { HtmlTags } from './html-tags';
@ -58,18 +57,18 @@ import { BannerIconHeader } from './banner-icon-header';
import { CommunityLink } from './community-link'; import { CommunityLink } from './community-link';
interface UserState { interface UserState {
userRes: UserDetailsResponse; userRes: GetUserDetailsResponse;
userId: number; userId: number;
userName: string; userName: string;
view: UserDetailsView; view: UserDetailsView;
sort: SortType; sort: SortType;
page: number; page: number;
loading: boolean; loading: boolean;
userSettingsForm: UserSettingsForm; userSettingsForm: SaveUserSettings;
userSettingsLoading: boolean; userSettingsLoading: boolean;
deleteAccountLoading: boolean; deleteAccountLoading: boolean;
deleteAccountShowConfirm: boolean; deleteAccountShowConfirm: boolean;
deleteAccountForm: DeleteAccountForm; deleteAccountForm: DeleteAccount;
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
} }
@ -106,17 +105,18 @@ export class User extends Component<any, UserState> {
lang: null, lang: null,
show_avatars: null, show_avatars: null,
send_notifications_to_email: null, send_notifications_to_email: null,
auth: null,
bio: null, bio: null,
preferred_username: null, preferred_username: null,
auth: UserService.Instance.authField(),
}, },
userSettingsLoading: null, userSettingsLoading: null,
deleteAccountLoading: null, deleteAccountLoading: null,
deleteAccountShowConfirm: false, deleteAccountShowConfirm: false,
deleteAccountForm: { deleteAccountForm: {
password: null, password: null,
auth: UserService.Instance.authField(),
}, },
siteRes: this.isoData.site, siteRes: this.isoData.site_res,
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -157,21 +157,22 @@ export class User extends Component<any, UserState> {
} }
fetchUserData() { fetchUserData() {
let form: GetUserDetailsForm = { let form: GetUserDetails = {
user_id: this.state.userId, user_id: this.state.userId,
username: this.state.userName, username: this.state.userName,
sort: this.state.sort, sort: this.state.sort,
saved_only: this.state.view === UserDetailsView.Saved, saved_only: this.state.view === UserDetailsView.Saved,
page: this.state.page, page: this.state.page,
limit: fetchLimit, limit: fetchLimit,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.getUserDetails(form); WebSocketService.Instance.client.getUserDetails(form);
} }
get isCurrentUser() { get isCurrentUser() {
return ( return (
UserService.Instance.user && UserService.Instance.user &&
UserService.Instance.user.id == this.state.userRes.user.id UserService.Instance.user.id == this.state.userRes.user_view.user.id
); );
} }
@ -205,14 +206,14 @@ export class User extends Component<any, UserState> {
let sort = this.getSortTypeFromProps(pathSplit[6]); let sort = this.getSortTypeFromProps(pathSplit[6]);
let page = this.getPageFromProps(Number(pathSplit[8])); let page = this.getPageFromProps(Number(pathSplit[8]));
let form: GetUserDetailsForm = { let form: GetUserDetails = {
sort, sort,
saved_only: view === UserDetailsView.Saved, saved_only: view === UserDetailsView.Saved,
page, page,
limit: fetchLimit, limit: fetchLimit,
auth: req.auth,
}; };
this.setIdOrName(form, user_id, username); this.setIdOrName(form, user_id, username);
setAuth(form, req.auth);
promises.push(req.client.getUserDetails(form)); promises.push(req.client.getUserDetails(form));
return promises; return promises;
} }
@ -251,12 +252,12 @@ export class User extends Component<any, UserState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `@${this.state.userRes.user.name} - ${this.state.siteRes.site.name}`; return `@${this.state.userRes.user_view.user.name} - ${this.state.siteRes.site_view.site.name}`;
} }
get bioTag(): string { get bioTag(): string {
return this.state.userRes.user.bio return this.state.userRes.user_view.user.bio
? previewLines(this.state.userRes.user.bio) ? previewLines(this.state.userRes.user_view.user.bio)
: undefined; : undefined;
} }
@ -277,7 +278,7 @@ export class User extends Component<any, UserState> {
title={this.documentTitle} title={this.documentTitle}
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
description={this.bioTag} description={this.bioTag}
image={this.state.userRes.user.avatar} image={this.state.userRes.user_view.user.avatar}
/> />
{this.userInfo()} {this.userInfo()}
<hr /> <hr />
@ -285,11 +286,14 @@ export class User extends Component<any, UserState> {
{!this.state.loading && this.selects()} {!this.state.loading && this.selects()}
<UserDetails <UserDetails
userRes={this.state.userRes} userRes={this.state.userRes}
admins={this.state.siteRes.admins}
sort={this.state.sort} sort={this.state.sort}
page={this.state.page} page={this.state.page}
limit={fetchLimit} limit={fetchLimit}
enableDownvotes={this.state.siteRes.site.enable_downvotes} enableDownvotes={
enableNsfw={this.state.siteRes.site.enable_nsfw} this.state.siteRes.site_view.site.enable_downvotes
}
enableNsfw={this.state.siteRes.site_view.site.enable_nsfw}
view={this.state.view} view={this.state.view}
onPageChange={this.handlePageChange} onPageChange={this.handlePageChange}
/> />
@ -391,29 +395,29 @@ export class User extends Component<any, UserState> {
} }
userInfo() { userInfo() {
let user = this.state.userRes.user; let uv = this.state.userRes.user_view;
return ( return (
<div> <div>
<BannerIconHeader banner={user.banner} icon={user.avatar} /> <BannerIconHeader banner={uv.user.banner} icon={uv.user.avatar} />
<div class="mb-3"> <div class="mb-3">
<div class=""> <div class="">
<div class="mb-0 d-flex flex-wrap"> <div class="mb-0 d-flex flex-wrap">
<div> <div>
{user.preferred_username && ( {uv.user.preferred_username && (
<h5 class="mb-0">{user.preferred_username}</h5> <h5 class="mb-0">{uv.user.preferred_username}</h5>
)} )}
<ul class="list-inline mb-2"> <ul class="list-inline mb-2">
<li className="list-inline-item"> <li className="list-inline-item">
<UserListing <UserListing
user={user} user={uv.user}
realLink realLink
useApubName useApubName
muted muted
hideAvatar hideAvatar
/> />
</li> </li>
{user.banned && ( {uv.user.banned && (
<li className="list-inline-item badge badge-danger"> <li className="list-inline-item badge badge-danger">
{i18n.t('banned')} {i18n.t('banned')}
</li> </li>
@ -432,45 +436,45 @@ export class User extends Component<any, UserState> {
<> <>
<a <a
className={`d-flex align-self-start btn btn-secondary mr-2 ${ className={`d-flex align-self-start btn btn-secondary mr-2 ${
!user.matrix_user_id && 'invisible' !uv.user.matrix_user_id && 'invisible'
}`} }`}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
href={`https://matrix.to/#/${user.matrix_user_id}`} href={`https://matrix.to/#/${uv.user.matrix_user_id}`}
> >
{i18n.t('send_secure_message')} {i18n.t('send_secure_message')}
</a> </a>
<Link <Link
className={'d-flex align-self-start btn btn-secondary'} className={'d-flex align-self-start btn btn-secondary'}
to={`/create_private_message/recipient/${user.id}`} to={`/create_private_message/recipient/${uv.user.id}`}
> >
{i18n.t('send_message')} {i18n.t('send_message')}
</Link> </Link>
</> </>
)} )}
</div> </div>
{user.bio && ( {uv.user.bio && (
<div className="d-flex align-items-center mb-2"> <div className="d-flex align-items-center mb-2">
<div <div
className="md-div" className="md-div"
dangerouslySetInnerHTML={mdToHtml(user.bio)} dangerouslySetInnerHTML={mdToHtml(uv.user.bio)}
/> />
</div> </div>
)} )}
<div> <div>
<ul class="list-inline mb-2"> <ul class="list-inline mb-2">
<li className="list-inline-item badge badge-light"> <li className="list-inline-item badge badge-light">
{i18n.t('number_of_posts', { count: user.number_of_posts })} {i18n.t('number_of_posts', { count: uv.counts.post_count })}
</li> </li>
<li className="list-inline-item badge badge-light"> <li className="list-inline-item badge badge-light">
{i18n.t('number_of_comments', { {i18n.t('number_of_comments', {
count: user.number_of_comments, count: uv.counts.comment_count,
})} })}
</li> </li>
</ul> </ul>
</div> </div>
<div class="text-muted"> <div class="text-muted">
{i18n.t('joined')} <MomentTime data={user} showAgo /> {i18n.t('joined')} <MomentTime data={uv.user} showAgo />
</div> </div>
<div className="d-flex align-items-center text-muted mb-2"> <div className="d-flex align-items-center text-muted mb-2">
<svg class="icon"> <svg class="icon">
@ -478,7 +482,7 @@ export class User extends Component<any, UserState> {
</svg> </svg>
<span className="ml-2"> <span className="ml-2">
{i18n.t('cake_day_title')}{' '} {i18n.t('cake_day_title')}{' '}
{moment.utc(user.published).local().format('MMM DD, YYYY')} {moment.utc(uv.user.published).local().format('MMM DD, YYYY')}
</span> </span>
</div> </div>
</div> </div>
@ -704,7 +708,7 @@ export class User extends Component<any, UserState> {
/> />
</div> </div>
</div> </div>
{this.state.siteRes.site.enable_nsfw && ( {this.state.siteRes.site_view.site.enable_nsfw && (
<div class="form-group"> <div class="form-group">
<div class="form-check"> <div class="form-check">
<input <input
@ -840,17 +844,9 @@ export class User extends Component<any, UserState> {
<div class="card-body"> <div class="card-body">
<h5>{i18n.t('moderates')}</h5> <h5>{i18n.t('moderates')}</h5>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
{this.state.userRes.moderates.map(community => ( {this.state.userRes.moderates.map(cmv => (
<li> <li>
<CommunityLink <CommunityLink community={cmv.community} />
community={{
name: community.community_name,
id: community.community_id,
local: community.community_local,
actor_id: community.community_actor_id,
icon: community.community_icon,
}}
/>
</li> </li>
))} ))}
</ul> </ul>
@ -869,17 +865,9 @@ export class User extends Component<any, UserState> {
<div class="card-body"> <div class="card-body">
<h5>{i18n.t('subscribed')}</h5> <h5>{i18n.t('subscribed')}</h5>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
{this.state.userRes.follows.map(community => ( {this.state.userRes.follows.map(cfv => (
<li> <li>
<CommunityLink <CommunityLink community={cfv.community} />
community={{
name: community.community_name,
id: community.community_id,
local: community.community_local,
actor_id: community.community_actor_id,
icon: community.community_icon,
}}
/>
</li> </li>
))} ))}
</ul> </ul>
@ -946,16 +934,12 @@ export class User extends Component<any, UserState> {
} }
handleUserSettingsSortTypeChange(val: SortType) { handleUserSettingsSortTypeChange(val: SortType) {
this.state.userSettingsForm.default_sort_type = Object.keys( this.state.userSettingsForm.default_sort_type = val;
SortType
).indexOf(val);
this.setState(this.state); this.setState(this.state);
} }
handleUserSettingsListingTypeChange(val: ListingType) { handleUserSettingsListingTypeChange(val: ListingType) {
this.state.userSettingsForm.default_listing_type = Object.keys( this.state.userSettingsForm.default_listing_type = val;
ListingType
).indexOf(val);
this.setState(this.state); this.setState(this.state);
} }
@ -998,7 +982,7 @@ export class User extends Component<any, UserState> {
i.state.userSettingsForm.matrix_user_id = event.target.value; i.state.userSettingsForm.matrix_user_id = event.target.value;
if ( if (
i.state.userSettingsForm.matrix_user_id == '' && i.state.userSettingsForm.matrix_user_id == '' &&
!i.state.userRes.user.matrix_user_id !i.state.userRes.user_view.user.matrix_user_id
) { ) {
i.state.userSettingsForm.matrix_user_id = undefined; i.state.userSettingsForm.matrix_user_id = undefined;
} }
@ -1034,7 +1018,7 @@ export class User extends Component<any, UserState> {
i.state.userSettingsLoading = true; i.state.userSettingsLoading = true;
i.setState(i.state); i.setState(i.state);
WebSocketService.Instance.saveUserSettings(i.state.userSettingsForm); WebSocketService.Instance.client.saveUserSettings(i.state.userSettingsForm);
} }
handleDeleteAccountShowConfirmToggle(i: User, event: any) { handleDeleteAccountShowConfirmToggle(i: User, event: any) {
@ -1058,7 +1042,7 @@ export class User extends Component<any, UserState> {
i.state.deleteAccountLoading = true; i.state.deleteAccountLoading = true;
i.setState(i.state); i.setState(i.state);
WebSocketService.Instance.deleteAccount(i.state.deleteAccountForm); WebSocketService.Instance.client.deleteAccount(i.state.deleteAccountForm);
i.handleLogoutClick(i); i.handleLogoutClick(i);
} }
@ -1089,9 +1073,9 @@ export class User extends Component<any, UserState> {
} }
} }
parseMessage(msg: WebSocketJsonResponse) { parseMessage(msg: any) {
console.log(msg); console.log(msg);
const res = wsJsonToRes(msg); let op = wsUserOp(msg);
if (msg.error) { if (msg.error) {
toast(i18n.t(msg.error), 'danger'); toast(i18n.t(msg.error), 'danger');
if (msg.error == 'couldnt_find_that_username_or_email') { if (msg.error == 'couldnt_find_that_username_or_email') {
@ -1104,83 +1088,83 @@ export class User extends Component<any, UserState> {
return; return;
} else if (msg.reconnect) { } else if (msg.reconnect) {
this.fetchUserData(); this.fetchUserData();
} else if (res.op == UserOperation.GetUserDetails) { } else if (op == UserOperation.GetUserDetails) {
// Since the UserDetails contains posts/comments as well as some general user info we listen here as well // Since the UserDetails 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 // and set the parent state if it is not set or differs
// TODO this might need to get abstracted // TODO this might need to get abstracted
const data = res.data as UserDetailsResponse; let data = wsJsonToRes<GetUserDetailsResponse>(msg).data;
this.state.userRes = data; this.state.userRes = data;
this.setUserInfo(); this.setUserInfo();
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.SaveUserSettings) { } else if (op == UserOperation.SaveUserSettings) {
const data = res.data as LoginResponse; let data = wsJsonToRes<LoginResponse>(msg).data;
UserService.Instance.login(data); UserService.Instance.login(data);
this.state.userRes.user.bio = this.state.userSettingsForm.bio; this.state.userRes.user_view.user.bio = this.state.userSettingsForm.bio;
this.state.userRes.user.preferred_username = this.state.userSettingsForm.preferred_username; this.state.userRes.user_view.user.preferred_username = this.state.userSettingsForm.preferred_username;
this.state.userRes.user.banner = this.state.userSettingsForm.banner; this.state.userRes.user_view.user.banner = this.state.userSettingsForm.banner;
this.state.userRes.user.avatar = this.state.userSettingsForm.avatar; this.state.userRes.user_view.user.avatar = this.state.userSettingsForm.avatar;
this.state.userSettingsLoading = false; this.state.userSettingsLoading = false;
this.setState(this.state); this.setState(this.state);
window.scrollTo(0, 0); window.scrollTo(0, 0);
} else if (res.op == UserOperation.DeleteAccount) { } else if (op == UserOperation.DeleteAccount) {
this.setState({ this.setState({
deleteAccountLoading: false, deleteAccountLoading: false,
deleteAccountShowConfirm: false, deleteAccountShowConfirm: false,
}); });
this.context.router.history.push('/'); this.context.router.history.push('/');
} else if (res.op == UserOperation.AddAdmin) { } else if (op == UserOperation.AddAdmin) {
const data = res.data as AddAdminResponse; let data = wsJsonToRes<AddAdminResponse>(msg).data;
this.state.siteRes.admins = data.admins; this.state.siteRes.admins = data.admins;
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateCommentLike) { } else if (op == UserOperation.CreateCommentLike) {
const data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
createCommentLikeRes(data, this.state.userRes.comments); createCommentLikeRes(data.comment_view, this.state.userRes.comments);
this.setState(this.state); this.setState(this.state);
} else if ( } else if (
res.op == UserOperation.EditComment || op == UserOperation.EditComment ||
res.op == UserOperation.DeleteComment || op == UserOperation.DeleteComment ||
res.op == UserOperation.RemoveComment op == UserOperation.RemoveComment
) { ) {
const data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
editCommentRes(data, this.state.userRes.comments); editCommentRes(data.comment_view, this.state.userRes.comments);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreateComment) { } else if (op == UserOperation.CreateComment) {
const data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
if ( if (
UserService.Instance.user && UserService.Instance.user &&
data.comment.creator_id == UserService.Instance.user.id data.comment_view.creator.id == UserService.Instance.user.id
) { ) {
toast(i18n.t('reply_sent')); toast(i18n.t('reply_sent'));
} }
} else if (res.op == UserOperation.SaveComment) { } else if (op == UserOperation.SaveComment) {
const data = res.data as CommentResponse; let data = wsJsonToRes<CommentResponse>(msg).data;
saveCommentRes(data, this.state.userRes.comments); saveCommentRes(data.comment_view, this.state.userRes.comments);
this.setState(this.state); this.setState(this.state);
} else if ( } else if (
res.op == UserOperation.EditPost || op == UserOperation.EditPost ||
res.op == UserOperation.DeletePost || op == UserOperation.DeletePost ||
res.op == UserOperation.RemovePost || op == UserOperation.RemovePost ||
res.op == UserOperation.LockPost || op == UserOperation.LockPost ||
res.op == UserOperation.StickyPost || op == UserOperation.StickyPost ||
res.op == UserOperation.SavePost op == UserOperation.SavePost
) { ) {
let data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
editPostFindRes(data, this.state.userRes.posts); editPostFindRes(data.post_view, this.state.userRes.posts);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.CreatePostLike) { } else if (op == UserOperation.CreatePostLike) {
const data = res.data as PostResponse; let data = wsJsonToRes<PostResponse>(msg).data;
createPostLikeFindRes(data, this.state.userRes.posts); createPostLikeFindRes(data.post_view, this.state.userRes.posts);
this.setState(this.state); this.setState(this.state);
} else if (res.op == UserOperation.BanUser) { } else if (op == UserOperation.BanUser) {
const data = res.data as BanUserResponse; let data = wsJsonToRes<BanUserResponse>(msg).data;
this.state.userRes.comments this.state.userRes.comments
.filter(c => c.creator_id == data.user.id) .filter(c => c.creator.id == data.user_view.user.id)
.forEach(c => (c.banned = data.banned)); .forEach(c => (c.creator.banned = data.banned));
this.state.userRes.posts this.state.userRes.posts
.filter(c => c.creator_id == data.user.id) .filter(c => c.creator.id == data.user_view.user.id)
.forEach(c => (c.banned = data.banned)); .forEach(c => (c.creator.banned = data.banned));
this.setState(this.state); this.setState(this.state);
} }
} }

View file

@ -1,9 +1,14 @@
import { GetSiteResponse, LemmyHttp } from 'lemmy-js-client'; import {
CommentView,
GetSiteResponse,
LemmyHttp,
UserMentionView,
} from 'lemmy-js-client';
export interface IsoData { export interface IsoData {
path: string; path: string;
routeData: any[]; routeData: any[];
site: GetSiteResponse; site_res: GetSiteResponse;
// Lang and theme // Lang and theme
lang: string; lang: string;
// communities?: ListCommunitiesResponse; // communities?: ListCommunitiesResponse;
@ -21,6 +26,20 @@ export interface InitialFetchRequest {
client: LemmyHttp; client: LemmyHttp;
} }
export interface CommentNode {
comment_view: CommentView | UserMentionView;
children?: CommentNode[];
depth?: number;
}
export interface PostFormParams {
name: string;
url?: string;
body?: string;
community_name?: string;
community_id?: number;
}
export enum CommentSortType { export enum CommentSortType {
Hot, Hot,
Top, Top,

View file

@ -1,8 +1,10 @@
// import Cookies from 'js-cookie'; // import Cookies from 'js-cookie';
import IsomorphicCookie from 'isomorphic-cookie'; import IsomorphicCookie from 'isomorphic-cookie';
import { User, LoginResponse } from 'lemmy-js-client'; import { User_, LoginResponse } from 'lemmy-js-client';
import jwt_decode from 'jwt-decode'; import jwt_decode from 'jwt-decode';
import { Subject, BehaviorSubject } from 'rxjs'; import { Subject, BehaviorSubject } from 'rxjs';
import { i18n } from '../i18next';
import { toast } from '../utils';
interface Claims { interface Claims {
id: number; id: number;
@ -11,7 +13,7 @@ interface Claims {
export class UserService { export class UserService {
private static _instance: UserService; private static _instance: UserService;
public user: User; public user: User_;
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>(
@ -48,6 +50,15 @@ export class UserService {
return IsomorphicCookie.load('jwt'); return IsomorphicCookie.load('jwt');
} }
public authField(throwErr: boolean = true): string {
if (this.auth == null && throwErr) {
toast(i18n.t('not_logged_in'), 'danger');
throw 'Not logged in';
} else {
return this.auth;
}
}
private setClaims(jwt: string) { private setClaims(jwt: string) {
this.claims = jwt_decode(jwt); this.claims = jwt_decode(jwt);
this.jwtSub.next(jwt); this.jwtSub.next(jwt);

View file

@ -1,66 +1,10 @@
import { wsUri } from '../env'; import { wsUri } from '../env';
import { import {
LemmyWebsocket, LemmyWebsocket,
LoginForm, UserViewSafe,
RegisterForm,
CommunityForm,
DeleteCommunityForm,
RemoveCommunityForm,
PostForm,
DeletePostForm,
RemovePostForm,
LockPostForm,
StickyPostForm,
SavePostForm,
CommentForm,
DeleteCommentForm,
RemoveCommentForm,
MarkCommentAsReadForm,
SaveCommentForm,
CommentLikeForm,
GetPostForm,
GetPostsForm,
CreatePostLikeForm,
GetCommunityForm,
FollowCommunityForm,
GetFollowedCommunitiesForm,
GetUserDetailsForm,
ListCommunitiesForm,
GetModlogForm,
BanFromCommunityForm,
AddModToCommunityForm,
TransferCommunityForm,
AddAdminForm,
TransferSiteForm,
BanUserForm,
SiteForm,
UserView,
GetRepliesForm,
GetUserMentionsForm,
MarkUserMentionAsReadForm,
SearchForm,
UserSettingsForm,
DeleteAccountForm,
PasswordResetForm,
PasswordChangeForm,
PrivateMessageForm,
EditPrivateMessageForm,
DeletePrivateMessageForm,
MarkPrivateMessageAsReadForm,
GetPrivateMessagesForm,
GetCommentsForm,
UserJoinForm,
GetSiteConfig,
GetSiteForm,
SiteConfigForm,
MarkAllAsReadForm,
WebSocketJsonResponse, WebSocketJsonResponse,
CommunityJoinForm,
PostJoinForm,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { UserService } from './'; import { isBrowser } from '../utils';
import { i18n } from '../i18next';
import { toast, isBrowser } from '../utils';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { share } from 'rxjs/operators'; import { share } from 'rxjs/operators';
import { import {
@ -77,9 +21,9 @@ export class WebSocketService {
}; };
public subject: Observable<any>; public subject: Observable<any>;
public admins: UserView[]; public admins: UserViewSafe[];
public banned: UserView[]; public banned: UserViewSafe[];
private client = new LemmyWebsocket(); public client = new LemmyWebsocket();
private constructor() { private constructor() {
this.ws = new ReconnectingWebSocket(wsUri, [], this.wsOptions); this.ws = new ReconnectingWebSocket(wsUri, [], this.wsOptions);
@ -93,7 +37,7 @@ export class WebSocketService {
console.log(`Connected to ${wsUri}`); console.log(`Connected to ${wsUri}`);
if (!firstConnect) { if (!firstConnect) {
let res: WebSocketJsonResponse = { let res: WebSocketJsonResponse<any> = {
reconnect: true, reconnect: true,
}; };
obs.next(res); obs.next(res);
@ -107,307 +51,6 @@ export class WebSocketService {
public static get Instance() { public static get Instance() {
return this._instance || (this._instance = new this()); return this._instance || (this._instance = new this());
} }
public userJoin() {
let form: UserJoinForm = { auth: UserService.Instance.auth };
this.ws.send(this.client.userJoin(form));
}
public postJoin(form: PostJoinForm) {
this.ws.send(this.client.postJoin(form));
}
public communityJoin(form: CommunityJoinForm) {
this.ws.send(this.client.communityJoin(form));
}
public login(form: LoginForm) {
this.ws.send(this.client.login(form));
}
public register(form: RegisterForm) {
this.ws.send(this.client.register(form));
}
public getCaptcha() {
this.ws.send(this.client.getCaptcha());
}
public createCommunity(form: CommunityForm) {
this.setAuth(form); // TODO all these setauths at some point would be good to make required
this.ws.send(this.client.createCommunity(form));
}
public editCommunity(form: CommunityForm) {
this.setAuth(form);
this.ws.send(this.client.editCommunity(form));
}
public deleteCommunity(form: DeleteCommunityForm) {
this.setAuth(form);
this.ws.send(this.client.deleteCommunity(form));
}
public removeCommunity(form: RemoveCommunityForm) {
this.setAuth(form);
this.ws.send(this.client.removeCommunity(form));
}
public followCommunity(form: FollowCommunityForm) {
this.setAuth(form);
this.ws.send(this.client.followCommunity(form));
}
public listCommunities(form: ListCommunitiesForm) {
this.setAuth(form, false);
this.ws.send(this.client.listCommunities(form));
}
public getFollowedCommunities() {
let form: GetFollowedCommunitiesForm = { auth: UserService.Instance.auth };
this.ws.send(this.client.getFollowedCommunities(form));
}
public listCategories() {
this.ws.send(this.client.listCategories());
}
public createPost(form: PostForm) {
this.setAuth(form);
this.ws.send(this.client.createPost(form));
}
public getPost(form: GetPostForm) {
this.setAuth(form, false);
this.ws.send(this.client.getPost(form));
}
public getCommunity(form: GetCommunityForm) {
this.setAuth(form, false);
this.ws.send(this.client.getCommunity(form));
}
public createComment(form: CommentForm) {
this.setAuth(form);
this.ws.send(this.client.createComment(form));
}
public editComment(form: CommentForm) {
this.setAuth(form);
this.ws.send(this.client.editComment(form));
}
public deleteComment(form: DeleteCommentForm) {
this.setAuth(form);
this.ws.send(this.client.deleteComment(form));
}
public removeComment(form: RemoveCommentForm) {
this.setAuth(form);
this.ws.send(this.client.removeComment(form));
}
public markCommentAsRead(form: MarkCommentAsReadForm) {
this.setAuth(form);
this.ws.send(this.client.markCommentAsRead(form));
}
public likeComment(form: CommentLikeForm) {
this.setAuth(form);
this.ws.send(this.client.likeComment(form));
}
public saveComment(form: SaveCommentForm) {
this.setAuth(form);
this.ws.send(this.client.saveComment(form));
}
public getPosts(form: GetPostsForm) {
this.setAuth(form, false);
this.ws.send(this.client.getPosts(form));
}
public getComments(form: GetCommentsForm) {
this.setAuth(form, false);
this.ws.send(this.client.getComments(form));
}
public likePost(form: CreatePostLikeForm) {
this.setAuth(form);
this.ws.send(this.client.likePost(form));
}
public editPost(form: PostForm) {
this.setAuth(form);
this.ws.send(this.client.editPost(form));
}
public deletePost(form: DeletePostForm) {
this.setAuth(form);
this.ws.send(this.client.deletePost(form));
}
public removePost(form: RemovePostForm) {
this.setAuth(form);
this.ws.send(this.client.removePost(form));
}
public lockPost(form: LockPostForm) {
this.setAuth(form);
this.ws.send(this.client.lockPost(form));
}
public stickyPost(form: StickyPostForm) {
this.setAuth(form);
this.ws.send(this.client.stickyPost(form));
}
public savePost(form: SavePostForm) {
this.setAuth(form);
this.ws.send(this.client.savePost(form));
}
public banFromCommunity(form: BanFromCommunityForm) {
this.setAuth(form);
this.ws.send(this.client.banFromCommunity(form));
}
public addModToCommunity(form: AddModToCommunityForm) {
this.setAuth(form);
this.ws.send(this.client.addModToCommunity(form));
}
public transferCommunity(form: TransferCommunityForm) {
this.setAuth(form);
this.ws.send(this.client.transferCommunity(form));
}
public transferSite(form: TransferSiteForm) {
this.setAuth(form);
this.ws.send(this.client.transferSite(form));
}
public banUser(form: BanUserForm) {
this.setAuth(form);
this.ws.send(this.client.banUser(form));
}
public addAdmin(form: AddAdminForm) {
this.setAuth(form);
this.ws.send(this.client.addAdmin(form));
}
public getUserDetails(form: GetUserDetailsForm) {
this.setAuth(form, false);
this.ws.send(this.client.getUserDetails(form));
}
public getReplies(form: GetRepliesForm) {
this.setAuth(form);
this.ws.send(this.client.getReplies(form));
}
public getUserMentions(form: GetUserMentionsForm) {
this.setAuth(form);
this.ws.send(this.client.getUserMentions(form));
}
public markUserMentionAsRead(form: MarkUserMentionAsReadForm) {
this.setAuth(form);
this.ws.send(this.client.markUserMentionAsRead(form));
}
public getModlog(form: GetModlogForm) {
this.ws.send(this.client.getModlog(form));
}
public createSite(form: SiteForm) {
this.setAuth(form);
this.ws.send(this.client.createSite(form));
}
public editSite(form: SiteForm) {
this.setAuth(form);
this.ws.send(this.client.editSite(form));
}
public getSite(form: GetSiteForm = {}) {
this.setAuth(form, false);
this.ws.send(this.client.getSite(form));
}
public getSiteConfig() {
let form: GetSiteConfig = {};
this.setAuth(form);
this.ws.send(this.client.getSiteConfig(form));
}
public search(form: SearchForm) {
this.setAuth(form, false);
this.ws.send(this.client.search(form));
}
public markAllAsRead() {
let form: MarkAllAsReadForm = { auth: null };
this.setAuth(form);
this.ws.send(this.client.markAllAsRead(form));
}
public saveUserSettings(form: UserSettingsForm) {
this.setAuth(form);
this.ws.send(this.client.saveUserSettings(form));
}
public deleteAccount(form: DeleteAccountForm) {
this.setAuth(form);
this.ws.send(this.client.deleteAccount(form));
}
public passwordReset(form: PasswordResetForm) {
this.ws.send(this.client.passwordReset(form));
}
public passwordChange(form: PasswordChangeForm) {
this.ws.send(this.client.passwordChange(form));
}
public createPrivateMessage(form: PrivateMessageForm) {
this.setAuth(form);
this.ws.send(this.client.createPrivateMessage(form));
}
public editPrivateMessage(form: EditPrivateMessageForm) {
this.setAuth(form);
this.ws.send(this.client.editPrivateMessage(form));
}
public deletePrivateMessage(form: DeletePrivateMessageForm) {
this.setAuth(form);
this.ws.send(this.client.deletePrivateMessage(form));
}
public markPrivateMessageAsRead(form: MarkPrivateMessageAsReadForm) {
this.setAuth(form);
this.ws.send(this.client.markPrivateMessageAsRead(form));
}
public getPrivateMessages(form: GetPrivateMessagesForm) {
this.setAuth(form);
this.ws.send(this.client.getPrivateMessages(form));
}
public saveSiteConfig(form: SiteConfigForm) {
this.setAuth(form);
this.ws.send(this.client.saveSiteConfig(form));
}
public setAuth(obj: any, throwErr: boolean = true) {
obj.auth = UserService.Instance.auth;
if (obj.auth == null && throwErr) {
toast(i18n.t('not_logged_in'), 'danger');
throw 'Not logged in';
}
}
} }
if (isBrowser()) { if (isBrowser()) {

View file

@ -30,23 +30,25 @@ import 'moment/locale/da';
import { import {
UserOperation, UserOperation,
Comment, CommentView,
CommentNode as CommentNodeI, User_,
Post,
PrivateMessage,
User,
SortType, SortType,
ListingType, ListingType,
SearchType, SearchType,
WebSocketResponse, WebSocketResponse,
WebSocketJsonResponse, WebSocketJsonResponse,
SearchForm, Search,
SearchResponse, SearchResponse,
CommentResponse, PostView,
PostResponse, PrivateMessageView,
} from 'lemmy-js-client'; } from 'lemmy-js-client';
import { CommentSortType, DataType, IsoData } from './interfaces'; import {
CommentSortType,
DataType,
IsoData,
CommentNode as CommentNodeI,
} from './interfaces';
import { UserService, WebSocketService } from './services'; import { UserService, WebSocketService } from './services';
var Tribute; var Tribute;
@ -154,14 +156,20 @@ export function randomStr(
.join(''); .join('');
} }
export function wsJsonToRes(msg: WebSocketJsonResponse): WebSocketResponse { export function wsJsonToRes<ResponseType>(
let opStr: string = msg.op; msg: WebSocketJsonResponse<ResponseType>
): WebSocketResponse<ResponseType> {
return { return {
op: UserOperation[opStr], op: wsUserOp(msg),
data: msg.data, data: msg.data,
}; };
} }
export function wsUserOp(msg: any): UserOperation {
let opStr: string = msg.op;
return UserOperation[opStr];
}
export const md = new markdown_it({ export const md = new markdown_it({
html: false, html: false,
linkify: true, linkify: true,
@ -190,12 +198,16 @@ export const md = new markdown_it({
defs: objectFlip(emojiShortName), defs: objectFlip(emojiShortName),
}); });
export function hotRankComment(comment: Comment): number { export function hotRankComment(comment_view: CommentView): number {
return hotRank(comment.score, comment.published); return hotRank(comment_view.counts.score, comment_view.comment.published);
} }
export function hotRankPost(post: Post): number { export function hotRankActivePost(post_view: PostView): number {
return hotRank(post.score, post.newest_activity_time); return hotRank(post_view.counts.score, post_view.counts.newest_comment_time);
}
export function hotRankPost(post_view: PostView): number {
return hotRank(post_view.counts.score, post_view.post.published);
} }
export function hotRank(score: number, timeStr: string): number { export function hotRank(score: number, timeStr: string): number {
@ -221,17 +233,8 @@ export function getUnixTime(text: string): number {
return text ? new Date(text).getTime() / 1000 : undefined; return text ? new Date(text).getTime() / 1000 : undefined;
} }
export function addTypeInfo<T>(
arr: T[],
name: string
): { type_: string; data: T }[] {
return arr.map(e => {
return { type_: name, data: e };
});
}
export function canMod( export function canMod(
user: User, user: User_,
modIds: number[], modIds: number[],
creator_id: number, creator_id: number,
onSelf: boolean = false onSelf: boolean = false
@ -509,21 +512,6 @@ export function isCakeDay(published: string): boolean {
); );
} }
export function isCommentType(
item: Comment | PrivateMessage | Post
): item is Comment {
return (
(item as Comment).community_id !== undefined &&
(item as Comment).content !== undefined
);
}
export function isPostType(
item: Comment | PrivateMessage | Post
): item is Post {
return (item as Post).stickied !== undefined;
}
export function toast(text: string, background: string = 'success') { export function toast(text: string, background: string = 'success') {
if (isBrowser()) { if (isBrowser()) {
let backgroundColor = `var(--${background})`; let backgroundColor = `var(--${background})`;
@ -592,32 +580,34 @@ export function messageToastify(info: NotifyInfo, router: any) {
} }
} }
export function notifyPost(post: Post, router: any) { export function notifyPost(post_view: PostView, router: any) {
let info: NotifyInfo = { let info: NotifyInfo = {
name: post.community_name, name: post_view.community.name,
icon: post.community_icon ? post.community_icon : defaultFavIcon, icon: post_view.community.icon ? post_view.community.icon : defaultFavIcon,
link: `/post/${post.id}`, link: `/post/${post_view.post.id}`,
body: post.name, body: post_view.post.name,
}; };
notify(info, router); notify(info, router);
} }
export function notifyComment(comment: Comment, router: any) { export function notifyComment(comment_view: CommentView, router: any) {
let info: NotifyInfo = { let info: NotifyInfo = {
name: comment.creator_name, name: comment_view.creator.name,
icon: comment.creator_avatar ? comment.creator_avatar : defaultFavIcon, icon: comment_view.creator.avatar
link: `/post/${comment.post_id}/comment/${comment.id}`, ? comment_view.creator.avatar
body: comment.content, : defaultFavIcon,
link: `/post/${comment_view.post.id}/comment/${comment_view.comment.id}`,
body: comment_view.comment.content,
}; };
notify(info, router); notify(info, router);
} }
export function notifyPrivateMessage(pm: PrivateMessage, router: any) { export function notifyPrivateMessage(pmv: PrivateMessageView, router: any) {
let info: NotifyInfo = { let info: NotifyInfo = {
name: pm.creator_name, name: pmv.creator.name,
icon: pm.creator_avatar ? pm.creator_avatar : defaultFavIcon, icon: pmv.creator.avatar ? pmv.creator.avatar : defaultFavIcon,
link: `/inbox`, link: `/inbox`,
body: pm.content, body: pmv.private_message.content,
}; };
notify(info, router); notify(info, router);
} }
@ -723,27 +713,28 @@ export function setupTippy() {
function userSearch(text: string, cb: any) { function userSearch(text: string, cb: any) {
if (text) { if (text) {
let form: SearchForm = { let form: Search = {
q: text, q: text,
type_: SearchType.Users, type_: SearchType.Users,
sort: SortType.TopAll, sort: SortType.TopAll,
page: 1, page: 1,
limit: mentionDropdownFetchLimit, limit: mentionDropdownFetchLimit,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.search(form); WebSocketService.Instance.client.search(form);
let userSub = WebSocketService.Instance.subject.subscribe( let userSub = WebSocketService.Instance.subject.subscribe(
msg => { msg => {
let res = wsJsonToRes(msg); let res = wsJsonToRes(msg);
if (res.op == UserOperation.Search) { if (res.op == UserOperation.Search) {
let data = res.data as SearchResponse; let data = res.data as SearchResponse;
let users = data.users.map(u => { let users = data.users.map(uv => {
return { return {
key: `@${u.name}@${hostname(u.actor_id)}`, key: `@${uv.user.name}@${hostname(uv.user.actor_id)}`,
name: u.name, name: uv.user.name,
local: u.local, local: uv.user.local,
id: u.id, id: uv.user.id,
}; };
}); });
cb(users); cb(users);
@ -760,27 +751,28 @@ function userSearch(text: string, cb: any) {
function communitySearch(text: string, cb: any) { function communitySearch(text: string, cb: any) {
if (text) { if (text) {
let form: SearchForm = { let form: Search = {
q: text, q: text,
type_: SearchType.Communities, type_: SearchType.Communities,
sort: SortType.TopAll, sort: SortType.TopAll,
page: 1, page: 1,
limit: mentionDropdownFetchLimit, limit: mentionDropdownFetchLimit,
auth: UserService.Instance.authField(false),
}; };
WebSocketService.Instance.search(form); WebSocketService.Instance.client.search(form);
let communitySub = WebSocketService.Instance.subject.subscribe( let communitySub = WebSocketService.Instance.subject.subscribe(
msg => { msg => {
let res = wsJsonToRes(msg); let res = wsJsonToRes(msg);
if (res.op == UserOperation.Search) { if (res.op == UserOperation.Search) {
let data = res.data as SearchResponse; let data = res.data as SearchResponse;
let communities = data.communities.map(c => { let communities = data.communities.map(cv => {
return { return {
key: `!${c.name}@${hostname(c.actor_id)}`, key: `!${cv.community.name}@${hostname(cv.community.actor_id)}`,
name: c.name, name: cv.community.name,
local: c.local, local: cv.community.local,
id: c.id, id: cv.community.id,
}; };
}); });
cb(communities); cb(communities);
@ -840,84 +832,84 @@ export function getUsernameFromProps(props: any): string {
return props.match.params.username; return props.match.params.username;
} }
export function editCommentRes(data: CommentResponse, comments: Comment[]) { export function editCommentRes(data: CommentView, comments: CommentView[]) {
let found = comments.find(c => c.id == data.comment.id); let found = comments.find(c => c.comment.id == data.comment.id);
if (found) { if (found) {
found.content = data.comment.content; found.comment.content = data.comment.content;
found.updated = data.comment.updated; found.comment.updated = data.comment.updated;
found.removed = data.comment.removed; found.comment.removed = data.comment.removed;
found.deleted = data.comment.deleted; found.comment.deleted = data.comment.deleted;
found.upvotes = data.comment.upvotes; found.counts.upvotes = data.counts.upvotes;
found.downvotes = data.comment.downvotes; found.counts.downvotes = data.counts.downvotes;
found.score = data.comment.score; found.counts.score = data.counts.score;
} }
} }
export function saveCommentRes(data: CommentResponse, comments: Comment[]) { export function saveCommentRes(data: CommentView, comments: CommentView[]) {
let found = comments.find(c => c.id == data.comment.id); let found = comments.find(c => c.comment.id == data.comment.id);
if (found) { if (found) {
found.saved = data.comment.saved; found.saved = data.saved;
} }
} }
export function createCommentLikeRes( export function createCommentLikeRes(
data: CommentResponse, data: CommentView,
comments: Comment[] comments: CommentView[]
) { ) {
let found: Comment = comments.find(c => c.id === data.comment.id); let found = comments.find(c => c.comment.id === data.comment.id);
if (found) { if (found) {
found.score = data.comment.score; found.counts.score = data.counts.score;
found.upvotes = data.comment.upvotes; found.counts.upvotes = data.counts.upvotes;
found.downvotes = data.comment.downvotes; found.counts.downvotes = data.counts.downvotes;
if (data.comment.my_vote !== null) { if (data.my_vote !== null) {
found.my_vote = data.comment.my_vote; found.my_vote = data.my_vote;
} }
} }
} }
export function createPostLikeFindRes(data: PostResponse, posts: Post[]) { export function createPostLikeFindRes(data: PostView, posts: PostView[]) {
let found = posts.find(c => c.id == data.post.id); let found = posts.find(p => p.post.id == data.post.id);
if (found) { if (found) {
createPostLikeRes(data, found); createPostLikeRes(data, found);
} }
} }
export function createPostLikeRes(data: PostResponse, post: Post) { export function createPostLikeRes(data: PostView, post_view: PostView) {
if (post) { if (post_view) {
post.score = data.post.score; post_view.counts.score = data.counts.score;
post.upvotes = data.post.upvotes; post_view.counts.upvotes = data.counts.upvotes;
post.downvotes = data.post.downvotes; post_view.counts.downvotes = data.counts.downvotes;
if (data.post.my_vote !== null) { if (data.my_vote !== null) {
post.my_vote = data.post.my_vote; post_view.my_vote = data.my_vote;
} }
} }
} }
export function editPostFindRes(data: PostResponse, posts: Post[]) { export function editPostFindRes(data: PostView, posts: PostView[]) {
let found = posts.find(c => c.id == data.post.id); let found = posts.find(p => p.post.id == data.post.id);
if (found) { if (found) {
editPostRes(data, found); editPostRes(data, found);
} }
} }
export function editPostRes(data: PostResponse, post: Post) { export function editPostRes(data: PostView, post: PostView) {
if (post) { if (post) {
post.url = data.post.url; post.post.url = data.post.url;
post.name = data.post.name; post.post.name = data.post.name;
post.nsfw = data.post.nsfw; post.post.nsfw = data.post.nsfw;
post.deleted = data.post.deleted; post.post.deleted = data.post.deleted;
post.removed = data.post.removed; post.post.removed = data.post.removed;
post.stickied = data.post.stickied; post.post.stickied = data.post.stickied;
post.body = data.post.body; post.post.body = data.post.body;
post.locked = data.post.locked; post.post.locked = data.post.locked;
post.saved = data.post.saved; post.saved = data.saved;
} }
} }
export function commentsToFlatNodes(comments: Comment[]): CommentNodeI[] { export function commentsToFlatNodes(comments: CommentView[]): CommentNodeI[] {
let nodes: CommentNodeI[] = []; let nodes: CommentNodeI[] = [];
for (let comment of comments) { for (let comment of comments) {
nodes.push({ comment: comment }); nodes.push({ comment_view: comment });
} }
return nodes; return nodes;
} }
@ -927,30 +919,34 @@ export function commentSort(tree: CommentNodeI[], sort: CommentSortType) {
if (sort == CommentSortType.Top) { if (sort == CommentSortType.Top) {
tree.sort( tree.sort(
(a, b) => (a, b) =>
+a.comment.removed - +b.comment.removed || +a.comment_view.comment.removed - +b.comment_view.comment.removed ||
+a.comment.deleted - +b.comment.deleted || +a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
b.comment.score - a.comment.score b.comment_view.counts.score - a.comment_view.counts.score
); );
} else if (sort == CommentSortType.New) { } else if (sort == CommentSortType.New) {
tree.sort( tree.sort(
(a, b) => (a, b) =>
+a.comment.removed - +b.comment.removed || +a.comment_view.comment.removed - +b.comment_view.comment.removed ||
+a.comment.deleted - +b.comment.deleted || +a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
b.comment.published.localeCompare(a.comment.published) b.comment_view.comment.published.localeCompare(
a.comment_view.comment.published
)
); );
} else if (sort == CommentSortType.Old) { } else if (sort == CommentSortType.Old) {
tree.sort( tree.sort(
(a, b) => (a, b) =>
+a.comment.removed - +b.comment.removed || +a.comment_view.comment.removed - +b.comment_view.comment.removed ||
+a.comment.deleted - +b.comment.deleted || +a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
a.comment.published.localeCompare(b.comment.published) a.comment_view.comment.published.localeCompare(
b.comment_view.comment.published
)
); );
} else if (sort == CommentSortType.Hot) { } else if (sort == CommentSortType.Hot) {
tree.sort( tree.sort(
(a, b) => (a, b) =>
+a.comment.removed - +b.comment.removed || +a.comment_view.comment.removed - +b.comment_view.comment.removed ||
+a.comment.deleted - +b.comment.deleted || +a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
hotRankComment(b.comment) - hotRankComment(a.comment) hotRankComment(b.comment_view) - hotRankComment(a.comment_view)
); );
} }
@ -985,7 +981,7 @@ function convertCommentSortType(sort: SortType): CommentSortType {
} }
export function postSort( export function postSort(
posts: Post[], posts: PostView[],
sort: SortType, sort: SortType,
communityType: boolean communityType: boolean
) { ) {
@ -999,34 +995,34 @@ export function postSort(
) { ) {
posts.sort( posts.sort(
(a, b) => (a, b) =>
+a.removed - +b.removed || +a.post.removed - +b.post.removed ||
+a.deleted - +b.deleted || +a.post.deleted - +b.post.deleted ||
(communityType && +b.stickied - +a.stickied) || (communityType && +b.post.stickied - +a.post.stickied) ||
b.score - a.score b.counts.score - a.counts.score
); );
} else if (sort == SortType.New) { } else if (sort == SortType.New) {
posts.sort( posts.sort(
(a, b) => (a, b) =>
+a.removed - +b.removed || +a.post.removed - +b.post.removed ||
+a.deleted - +b.deleted || +a.post.deleted - +b.post.deleted ||
(communityType && +b.stickied - +a.stickied) || (communityType && +b.post.stickied - +a.post.stickied) ||
b.published.localeCompare(a.published) b.post.published.localeCompare(a.post.published)
); );
} else if (sort == SortType.Hot) { } else if (sort == SortType.Hot) {
posts.sort( posts.sort(
(a, b) => (a, b) =>
+a.removed - +b.removed || +a.post.removed - +b.post.removed ||
+a.deleted - +b.deleted || +a.post.deleted - +b.post.deleted ||
(communityType && +b.stickied - +a.stickied) || (communityType && +b.post.stickied - +a.post.stickied) ||
b.hot_rank - a.hot_rank hotRankPost(b) - hotRankPost(a)
); );
} else if (sort == SortType.Active) { } else if (sort == SortType.Active) {
posts.sort( posts.sort(
(a, b) => (a, b) =>
+a.removed - +b.removed || +a.post.removed - +b.post.removed ||
+a.deleted - +b.deleted || +a.post.deleted - +b.post.deleted ||
(communityType && +b.stickied - +a.stickied) || (communityType && +b.post.stickied - +a.post.stickied) ||
b.hot_rank_active - a.hot_rank_active hotRankActivePost(b) - hotRankActivePost(a)
); );
} }
} }
@ -1094,12 +1090,6 @@ export function isBrowser() {
return typeof window !== 'undefined'; return typeof window !== 'undefined';
} }
export function setAuth(obj: any, auth: string) {
if (auth) {
obj.auth = auth;
}
}
export function setIsoData(context: any): IsoData { export function setIsoData(context: any): IsoData {
let isoData: IsoData = isBrowser() let isoData: IsoData = isBrowser()
? window.isoData ? window.isoData

View file

@ -5518,10 +5518,10 @@ lcid@^1.0.0:
dependencies: dependencies:
invert-kv "^1.0.0" invert-kv "^1.0.0"
lemmy-js-client@^1.0.16: lemmy-js-client@1.0.17-beta5:
version "1.0.16" version "1.0.17-beta5"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-1.0.16.tgz#84bf094c246d987f2f192bfac75340430821fee9" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-1.0.17-beta5.tgz#b8d128ef3a6a17bc3ac4eea8e30618b68ea4f2df"
integrity sha512-WvEEGrYNA2dzfzlufWB9LhUcll0O06WaUjSfBn5lYY/SFFsvBW5ImD42P/QwvN8Sgj6xVQiboe+Z8T++iAjKVw== integrity sha512-Z/8HV8tG9aB75GjDX1U2b3pFZnysGIymeVO+oepOkYfhHRB8SKmLS9ATuIw9OW1NjJduxbpGGgDH+bkf0Sx7dA==
leven@^3.1.0: leven@^3.1.0:
version "3.1.0" version "3.1.0"