A few more fixes.

This commit is contained in:
Dessalines 2023-06-03 23:20:41 -04:00
parent e64b71339d
commit f981f103ef
19 changed files with 249 additions and 116 deletions

View file

@ -1,21 +1,20 @@
# lemmy-ui # Lemmy-UI
The official web app for [Lemmy](https://github.com/LemmyNet/lemmy), written in inferno. The official web app for [Lemmy](https://github.com/LemmyNet/lemmy), written in inferno.
Based off of MrFoxPro's [inferno-isomorphic-template](https://github.com/MrFoxPro/inferno-isomorphic-template). Based off of MrFoxPro's [inferno-isomorphic-template](https://github.com/MrFoxPro/inferno-isomorphic-template).
## Configuration ## Configuration
The following environment variables can be used to configure lemmy-ui: The following environment variables can be used to configure lemmy-ui:
`ENV_VAR` | type | default | description | `ENV_VAR` | type | default | description |
--- | --- | --- | --- | ------------------------------ | -------- | ---------------- | ----------------------------------------------------------------------------------- |
`LEMMY_UI_HOST` | `string` | `0.0.0.0:1234` | The IP / port that the lemmy-ui isomorphic node server is hosted at. | `LEMMY_UI_HOST` | `string` | `0.0.0.0:1234` | The IP / port that the lemmy-ui isomorphic node server is hosted at. |
`LEMMY_UI_LEMMY_INTERNAL_HOST` | `string` | `0.0.0.0:8536` | The internal IP / port that lemmy is hosted at. Often `lemmy:8536` if using docker. | `LEMMY_UI_LEMMY_INTERNAL_HOST` | `string` | `0.0.0.0:8536` | The internal IP / port that lemmy is hosted at. Often `lemmy:8536` if using docker. |
`LEMMY_UI_LEMMY_EXTERNAL_HOST` | `string` | `0.0.0.0:8536` | The external IP / port that lemmy is hosted at. Often `DOMAIN.TLD`. | `LEMMY_UI_LEMMY_EXTERNAL_HOST` | `string` | `0.0.0.0:8536` | The external IP / port that lemmy is hosted at. Often `DOMAIN.TLD`. |
`LEMMY_UI_LEMMY_WS_HOST` | `string` | `0.0.0.0:8536` | An alternate location for lemmy's websocket address. Not usually necessary. | `LEMMY_UI_HTTPS` | `bool` | `false` | Whether to use https. |
`LEMMY_UI_HTTPS` | `bool` | `false` | Whether to use https. | `LEMMY_UI_EXTRA_THEMES_FOLDER` | `string` | `./extra_themes` | A location for additional lemmy css themes. |
`LEMMY_UI_EXTRA_THEMES_FOLDER` | `string` | `./extra_themes` | A location for additional lemmy css themes. | `LEMMY_UI_DEBUG` | `bool` | `false` | Loads the [Eruda](https://github.com/liriliri/eruda) debugging utility. |
`LEMMY_UI_DEBUG` | `bool` | `false` | Loads the [Eruda](https://github.com/liriliri/eruda) debugging utility. | `LEMMY_UI_DISABLE_CSP` | `bool` | `false` | Disables CSP security headers |
`LEMMY_UI_DISABLE_CSP` | `bool` | `false` | Disables CSP security headers | `LEMMY_UI_CUSTOM_HTML_HEADER` | `string` | | Injects a custom script into `<head>`. |
`LEMMY_UI_CUSTOM_HTML_HEADER` | `string` | | Injects a custom script into `<head>`.

View file

@ -14,6 +14,7 @@ interface CommentFormProps {
* Can either be the parent, or the editable comment. The right side is a postId. * Can either be the parent, or the editable comment. The right side is a postId.
*/ */
node: CommentNodeI | number; node: CommentNodeI | number;
finished?: boolean;
edit?: boolean; edit?: boolean;
disabled?: boolean; disabled?: boolean;
focus?: boolean; focus?: boolean;
@ -24,25 +25,11 @@ interface CommentFormProps {
onEditComment(form: EditComment): void; onEditComment(form: EditComment): void;
} }
interface CommentFormState { export class CommentForm extends Component<CommentFormProps, any> {
buttonTitle: string;
}
export class CommentForm extends Component<CommentFormProps, CommentFormState> {
state: CommentFormState = {
buttonTitle:
typeof this.props.node === "number"
? capitalizeFirstLetter(i18n.t("post"))
: this.props.edit
? capitalizeFirstLetter(i18n.t("save"))
: capitalizeFirstLetter(i18n.t("reply")),
};
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
this.handleCommentSubmit = this.handleCommentSubmit.bind(this); this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
this.handleReplyCancel = this.handleReplyCancel.bind(this);
} }
render() { render() {
@ -59,12 +46,13 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
<MarkdownTextArea <MarkdownTextArea
initialContent={initialContent} initialContent={initialContent}
showLanguage showLanguage
buttonTitle={this.state.buttonTitle} buttonTitle={this.buttonTitle}
finished={this.props.finished}
replyType={typeof this.props.node !== "number"} replyType={typeof this.props.node !== "number"}
focus={this.props.focus} focus={this.props.focus}
disabled={this.props.disabled} disabled={this.props.disabled}
onSubmit={this.handleCommentSubmit} onSubmit={this.handleCommentSubmit}
onReplyCancel={this.handleReplyCancel} onReplyCancel={this.props.onReplyCancel}
placeholder={i18n.t("comment_here")} placeholder={i18n.t("comment_here")}
allLanguages={this.props.allLanguages} allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages} siteLanguages={this.props.siteLanguages}
@ -84,6 +72,14 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
); );
} }
get buttonTitle(): string {
return typeof this.props.node === "number"
? capitalizeFirstLetter(i18n.t("post"))
: this.props.edit
? capitalizeFirstLetter(i18n.t("save"))
: capitalizeFirstLetter(i18n.t("reply"));
}
handleCommentSubmit(content: string, form_id: string, language_id?: number) { handleCommentSubmit(content: string, form_id: string, language_id?: number) {
let node = this.props.node; let node = this.props.node;
@ -100,6 +96,7 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
if (this.props.edit) { if (this.props.edit) {
let comment_id = node.comment_view.comment.id; let comment_id = node.comment_view.comment.id;
this.props.onEditComment({ this.props.onEditComment({
content,
comment_id, comment_id,
form_id, form_id,
language_id, language_id,
@ -119,8 +116,4 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
} }
} }
} }
handleReplyCancel() {
this.props.onReplyCancel?.();
}
} }

View file

@ -121,6 +121,7 @@ interface CommentNodeProps {
allLanguages: Language[]; allLanguages: Language[];
siteLanguages: number[]; siteLanguages: number[];
hideImages?: boolean; hideImages?: boolean;
finished: Map<CommentId, boolean | undefined>;
onSaveComment(form: SaveComment): void; onSaveComment(form: SaveComment): void;
onCommentReplyRead(form: MarkCommentReplyAsRead): void; onCommentReplyRead(form: MarkCommentReplyAsRead): void;
onPersonMentionRead(form: MarkPersonMentionAsRead): void; onPersonMentionRead(form: MarkPersonMentionAsRead): void;
@ -197,6 +198,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
): void { ): void {
if (this.props != nextProps) { if (this.props != nextProps) {
this.setState({ this.setState({
showReply: false,
showEdit: false,
showRemoveDialog: false,
showBanDialog: false,
removeData: false,
banType: BanType.Community,
showPurgeDialog: false,
purgeType: PurgeType.Person,
collapsed: false,
viewSource: false,
showAdvanced: false,
showConfirmTransferSite: false,
showConfirmTransferCommunity: false,
showConfirmAppointAsMod: false,
showConfirmAppointAsAdmin: false,
showReportDialog: false,
createOrEditCommentLoading: false, createOrEditCommentLoading: false,
upvoteLoading: false, upvoteLoading: false,
downvoteLoading: false, downvoteLoading: false,
@ -389,6 +406,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
edit edit
onReplyCancel={this.handleReplyCancel} onReplyCancel={this.handleReplyCancel}
disabled={this.props.locked} disabled={this.props.locked}
finished={this.props.finished.get(
this.props.node.comment_view.comment.id
)}
focus focus
allLanguages={this.props.allLanguages} allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages} siteLanguages={this.props.siteLanguages}
@ -1130,6 +1150,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
node={node} node={node}
onReplyCancel={this.handleReplyCancel} onReplyCancel={this.handleReplyCancel}
disabled={this.props.locked} disabled={this.props.locked}
finished={this.props.finished.get(
this.props.node.comment_view.comment.id
)}
focus focus
allLanguages={this.props.allLanguages} allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages} siteLanguages={this.props.siteLanguages}
@ -1148,6 +1171,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
allLanguages={this.props.allLanguages} allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages} siteLanguages={this.props.siteLanguages}
hideImages={this.props.hideImages} hideImages={this.props.hideImages}
finished={this.props.finished}
onCommentReplyRead={this.props.onCommentReplyRead} onCommentReplyRead={this.props.onCommentReplyRead}
onPersonMentionRead={this.props.onPersonMentionRead} onPersonMentionRead={this.props.onPersonMentionRead}
onCreateComment={this.props.onCreateComment} onCreateComment={this.props.onCreateComment}
@ -1457,7 +1481,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
read: !cv.person_mention.read, read: !cv.person_mention.read,
auth: myAuthRequired(), auth: myAuthRequired(),
}); });
} else if (this.isCommentReplyType(cv)) { } else if (i.isCommentReplyType(cv)) {
i.props.onCommentReplyRead({ i.props.onCommentReplyRead({
comment_reply_id: cv.comment_reply.id, comment_reply_id: cv.comment_reply.id,
read: !cv.comment_reply.read, read: !cv.comment_reply.read,

View file

@ -5,6 +5,7 @@ import {
BanFromCommunity, BanFromCommunity,
BanPerson, BanPerson,
BlockPerson, BlockPerson,
CommentId,
CommunityModeratorView, CommunityModeratorView,
CreateComment, CreateComment,
CreateCommentLike, CreateCommentLike,
@ -43,6 +44,7 @@ interface CommentNodesProps {
allLanguages: Language[]; allLanguages: Language[];
siteLanguages: number[]; siteLanguages: number[];
hideImages?: boolean; hideImages?: boolean;
finished: Map<CommentId, boolean | undefined>;
onSaveComment(form: SaveComment): void; onSaveComment(form: SaveComment): void;
onCommentReplyRead(form: MarkCommentReplyAsRead): void; onCommentReplyRead(form: MarkCommentReplyAsRead): void;
onPersonMentionRead(form: MarkPersonMentionAsRead): void; onPersonMentionRead(form: MarkPersonMentionAsRead): void;
@ -94,6 +96,7 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
hideImages={this.props.hideImages} hideImages={this.props.hideImages}
onCommentReplyRead={this.props.onCommentReplyRead} onCommentReplyRead={this.props.onCommentReplyRead}
onPersonMentionRead={this.props.onPersonMentionRead} onPersonMentionRead={this.props.onPersonMentionRead}
finished={this.props.finished}
onCreateComment={this.props.onCreateComment} onCreateComment={this.props.onCreateComment}
onEditComment={this.props.onEditComment} onEditComment={this.props.onEditComment}
onCommentVote={this.props.onCommentVote} onCommentVote={this.props.onCommentVote}

View file

@ -1,4 +1,4 @@
import { Component, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { import {
CommentReportView, CommentReportView,
@ -32,6 +32,14 @@ export class CommentReport extends Component<
super(props, context); super(props, context);
} }
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & CommentReportProps>
): void {
if (this.props != nextProps) {
this.setState({ loading: false });
}
}
render() { render() {
let r = this.props.report; let r = this.props.report;
let comment = r.comment; let comment = r.comment;
@ -73,6 +81,7 @@ export class CommentReport extends Component<
siteLanguages={[]} siteLanguages={[]}
hideImages hideImages
// All of these are unused, since its viewonly // All of these are unused, since its viewonly
finished={new Map()}
onSaveComment={() => {}} onSaveComment={() => {}}
onBlockPerson={() => {}} onBlockPerson={() => {}}
onDeleteComment={() => {}} onDeleteComment={() => {}}

View file

@ -36,6 +36,7 @@ interface MarkdownTextAreaProps {
replyType?: boolean; replyType?: boolean;
focus?: boolean; focus?: boolean;
disabled?: boolean; disabled?: boolean;
finished?: boolean;
showLanguage?: boolean; showLanguage?: boolean;
hideNavigationWarnings?: boolean; hideNavigationWarnings?: boolean;
onContentChange?(val: string): void; onContentChange?(val: string): void;
@ -113,12 +114,7 @@ export class MarkdownTextArea extends Component<
} }
componentWillReceiveProps(nextProps: MarkdownTextAreaProps) { componentWillReceiveProps(nextProps: MarkdownTextAreaProps) {
if ( if (nextProps.finished) {
nextProps != this.props &&
// Don't trigger this on an initial content change (IE the form field version)
// This should only trigger in an
this.props.initialContent == nextProps.initialContent
) {
this.setState({ this.setState({
previewMode: false, previewMode: false,
imageUploadStatus: undefined, imageUploadStatus: undefined,

View file

@ -10,6 +10,7 @@ import {
BanPersonResponse, BanPersonResponse,
BlockCommunity, BlockCommunity,
BlockPerson, BlockPerson,
CommentId,
CommentReplyResponse, CommentReplyResponse,
CommentResponse, CommentResponse,
CommunityResponse, CommunityResponse,
@ -73,6 +74,7 @@ import {
enableDownvotes, enableDownvotes,
enableNsfw, enableNsfw,
fetchLimit, fetchLimit,
getCommentParentId,
getDataTypeString, getDataTypeString,
getPageFromString, getPageFromString,
getQueryParams, getQueryParams,
@ -108,6 +110,7 @@ interface State {
commentsRes: RequestState<GetCommentsResponse>; commentsRes: RequestState<GetCommentsResponse>;
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
showSidebarMobile: boolean; showSidebarMobile: boolean;
finished: Map<CommentId, boolean | undefined>;
} }
interface CommunityProps { interface CommunityProps {
@ -147,6 +150,7 @@ export class Community extends Component<
commentsRes: { state: "empty" }, commentsRes: { state: "empty" },
siteRes: this.isoData.site_res, siteRes: this.isoData.site_res,
showSidebarMobile: false, showSidebarMobile: false,
finished: new Map(),
}; };
constructor(props: RouteComponentProps<{ name: string }>, context: any) { constructor(props: RouteComponentProps<{ name: string }>, context: any) {
@ -445,6 +449,7 @@ export class Community extends Component<
<CommentNodes <CommentNodes
nodes={commentsToFlatNodes(this.state.commentsRes.data.comments)} nodes={commentsToFlatNodes(this.state.commentsRes.data.comments)}
viewType={CommentViewType.Flat} viewType={CommentViewType.Flat}
finished={this.state.finished}
noIndent noIndent
showContext showContext
enableDownvotes={enableDownvotes(site_res)} enableDownvotes={enableDownvotes(site_res)}
@ -706,16 +711,7 @@ export class Community extends Component<
const createCommentRes = await apiWrapper( const createCommentRes = await apiWrapper(
HttpService.client.createComment(form) HttpService.client.createComment(form)
); );
this.createAndUpdateComments(createCommentRes);
this.setState(s => {
if (
s.commentsRes.state == "success" &&
createCommentRes.state == "success"
) {
s.commentsRes.data.comments.unshift(createCommentRes.data.comment_view);
}
return s;
});
} }
async handleEditComment(form: EditComment) { async handleEditComment(form: EditComment) {
@ -924,6 +920,22 @@ export class Community extends Component<
res.data.comment_view, res.data.comment_view,
s.commentsRes.data.comments s.commentsRes.data.comments
); );
s.finished.set(res.data.comment_view.comment.id, true);
}
return s;
});
}
createAndUpdateComments(res: RequestState<CommentResponse>) {
this.setState(s => {
if (s.commentsRes.state == "success" && res.state == "success") {
s.commentsRes.data.comments.unshift(res.data.comment_view);
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true
);
} }
return s; return s;
}); });

View file

@ -10,6 +10,7 @@ import {
BanPerson, BanPerson,
BanPersonResponse, BanPersonResponse,
BlockPerson, BlockPerson,
CommentId,
CommentReplyResponse, CommentReplyResponse,
CommentResponse, CommentResponse,
CreateComment, CreateComment,
@ -67,6 +68,7 @@ import {
enableDownvotes, enableDownvotes,
enableNsfw, enableNsfw,
fetchLimit, fetchLimit,
getCommentParentId,
getDataTypeString, getDataTypeString,
getPageFromString, getPageFromString,
getQueryParams, getQueryParams,
@ -108,6 +110,7 @@ interface HomeState {
subscribedCollapsed: boolean; subscribedCollapsed: boolean;
tagline?: string; tagline?: string;
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
finished: Map<CommentId, boolean | undefined>;
} }
interface HomeProps { interface HomeProps {
@ -186,6 +189,7 @@ export class Home extends Component<any, HomeState> {
showTrendingMobile: false, showTrendingMobile: false,
showSidebarMobile: false, showSidebarMobile: false,
subscribedCollapsed: false, subscribedCollapsed: false,
finished: new Map(),
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -646,6 +650,7 @@ export class Home extends Component<any, HomeState> {
<CommentNodes <CommentNodes
nodes={commentsToFlatNodes(comments)} nodes={commentsToFlatNodes(comments)}
viewType={CommentViewType.Flat} viewType={CommentViewType.Flat}
finished={this.state.finished}
noIndent noIndent
showCommunity showCommunity
showContext showContext
@ -864,15 +869,7 @@ export class Home extends Component<any, HomeState> {
HttpService.client.createComment(form) HttpService.client.createComment(form)
); );
this.setState(s => { this.createAndUpdateComments(createCommentRes);
if (
s.commentsRes.state == "success" &&
createCommentRes.state == "success"
) {
s.commentsRes.data.comments.unshift(createCommentRes.data.comment_view);
}
return s;
});
} }
async handleEditComment(form: EditComment) { async handleEditComment(form: EditComment) {
@ -1057,6 +1054,22 @@ export class Home extends Component<any, HomeState> {
res.data.comment_view, res.data.comment_view,
s.commentsRes.data.comments s.commentsRes.data.comments
); );
s.finished.set(res.data.comment_view.comment.id, true);
}
return s;
});
}
createAndUpdateComments(res: RequestState<CommentResponse>) {
this.setState(s => {
if (s.commentsRes.state == "success" && res.state == "success") {
s.commentsRes.data.comments.unshift(res.data.comment_view);
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true
);
} }
return s; return s;
}); });

View file

@ -7,6 +7,7 @@ import {
BanPerson, BanPerson,
BanPersonResponse, BanPersonResponse,
BlockPerson, BlockPerson,
CommentId,
CommentReplyResponse, CommentReplyResponse,
CommentReplyView, CommentReplyView,
CommentReportResponse, CommentReportResponse,
@ -64,6 +65,7 @@ import {
editPrivateMessages, editPrivateMessages,
enableDownvotes, enableDownvotes,
fetchLimit, fetchLimit,
getCommentParentId,
isBrowser, isBrowser,
isInitialRoute, isInitialRoute,
myAuth, myAuth,
@ -114,6 +116,7 @@ interface InboxState {
sort: CommentSortType; sort: CommentSortType;
page: number; page: number;
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
finished: Map<CommentId, boolean | undefined>;
} }
export class Inbox extends Component<any, InboxState> { export class Inbox extends Component<any, InboxState> {
@ -128,6 +131,7 @@ export class Inbox extends Component<any, InboxState> {
mentionsRes: { state: "empty" }, mentionsRes: { state: "empty" },
messagesRes: { state: "empty" }, messagesRes: { state: "empty" },
markAllAsReadRes: { state: "empty" }, markAllAsReadRes: { state: "empty" },
finished: new Map(),
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -434,6 +438,7 @@ export class Inbox extends Component<any, InboxState> {
{ comment_view: i.view as CommentView, children: [], depth: 0 }, { comment_view: i.view as CommentView, children: [], depth: 0 },
]} ]}
viewType={CommentViewType.Flat} viewType={CommentViewType.Flat}
finished={this.state.finished}
noIndent noIndent
markable markable
showCommunity showCommunity
@ -472,6 +477,7 @@ export class Inbox extends Component<any, InboxState> {
depth: 0, depth: 0,
}, },
]} ]}
finished={this.state.finished}
viewType={CommentViewType.Flat} viewType={CommentViewType.Flat}
noIndent noIndent
markable markable
@ -529,7 +535,9 @@ export class Inbox extends Component<any, InboxState> {
</h5> </h5>
); );
} else { } else {
return <div>{this.buildCombined().map(this.renderReplyType)}</div>; return (
<div>{this.buildCombined().map(r => this.renderReplyType(r))}</div>
);
} }
} }
@ -548,6 +556,7 @@ export class Inbox extends Component<any, InboxState> {
<CommentNodes <CommentNodes
nodes={commentsToFlatNodes(replies)} nodes={commentsToFlatNodes(replies)}
viewType={CommentViewType.Flat} viewType={CommentViewType.Flat}
finished={this.state.finished}
noIndent noIndent
markable markable
showCommunity showCommunity
@ -597,6 +606,7 @@ export class Inbox extends Component<any, InboxState> {
key={umv.person_mention.id} key={umv.person_mention.id}
nodes={[{ comment_view: umv, children: [], depth: 0 }]} nodes={[{ comment_view: umv, children: [], depth: 0 }]}
viewType={CommentViewType.Flat} viewType={CommentViewType.Flat}
finished={this.state.finished}
noIndent noIndent
markable markable
showCommunity showCommunity
@ -683,7 +693,7 @@ export class Inbox extends Component<any, InboxState> {
if (auth) { if (auth) {
// It can be /u/me, or /username/1 // It can be /u/me, or /username/1
let repliesForm: GetReplies = { let repliesForm: GetReplies = {
sort: "New", sort,
unread_only: true, unread_only: true,
page: 1, page: 1,
limit: fetchLimit, limit: fetchLimit,
@ -772,7 +782,7 @@ export class Inbox extends Component<any, InboxState> {
), ),
}); });
if (this.state.markAllAsReadRes.state == "success") { if (i.state.markAllAsReadRes.state == "success") {
i.setState({ i.setState({
repliesRes: { state: "empty" }, repliesRes: { state: "empty" },
mentionsRes: { state: "empty" }, mentionsRes: { state: "empty" },
@ -819,7 +829,8 @@ export class Inbox extends Component<any, InboxState> {
const res = await apiWrapper(HttpService.client.createComment(form)); const res = await apiWrapper(HttpService.client.createComment(form));
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("created")); toast(i18n.t("reply_sent"));
this.findAndUpdateComment(res);
} }
} }
@ -828,6 +839,7 @@ export class Inbox extends Component<any, InboxState> {
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("edit")); toast(i18n.t("edit"));
this.findAndUpdateComment(res);
} }
} }
@ -835,6 +847,7 @@ export class Inbox extends Component<any, InboxState> {
const res = await apiWrapper(HttpService.client.deleteComment(form)); const res = await apiWrapper(HttpService.client.deleteComment(form));
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("deleted")); toast(i18n.t("deleted"));
this.findAndUpdateComment(res);
} }
} }
@ -842,6 +855,7 @@ export class Inbox extends Component<any, InboxState> {
const res = await apiWrapper(HttpService.client.removeComment(form)); const res = await apiWrapper(HttpService.client.removeComment(form));
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("remove_comment")); toast(i18n.t("remove_comment"));
this.findAndUpdateComment(res);
} }
} }
@ -1026,6 +1040,11 @@ export class Inbox extends Component<any, InboxState> {
s.mentionsRes.data.mentions s.mentionsRes.data.mentions
); );
} }
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true
);
return s; return s;
}); });
} }

View file

@ -5,6 +5,7 @@ import {
BanFromCommunity, BanFromCommunity,
BanPerson, BanPerson,
BlockPerson, BlockPerson,
CommentId,
CommentView, CommentView,
CreateComment, CreateComment,
CreateCommentLike, CreateCommentLike,
@ -42,6 +43,7 @@ import { PostListing } from "../post/post-listing";
interface PersonDetailsProps { interface PersonDetailsProps {
personRes: GetPersonDetailsResponse; personRes: GetPersonDetailsResponse;
finished: Map<CommentId, boolean | undefined>;
admins: PersonView[]; admins: PersonView[];
allLanguages: Language[]; allLanguages: Language[];
siteLanguages: number[]; siteLanguages: number[];
@ -147,6 +149,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
key={i.id} key={i.id}
nodes={[{ comment_view: c, children: [], depth: 0 }]} nodes={[{ comment_view: c, children: [], depth: 0 }]}
viewType={CommentViewType.Flat} viewType={CommentViewType.Flat}
finished={this.props.finished}
admins={this.props.admins} admins={this.props.admins}
noBorder noBorder
noIndent noIndent
@ -255,6 +258,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
nodes={commentsToFlatNodes(this.props.personRes.comments)} nodes={commentsToFlatNodes(this.props.personRes.comments)}
viewType={CommentViewType.Flat} viewType={CommentViewType.Flat}
admins={this.props.admins} admins={this.props.admins}
finished={this.props.finished}
noIndent noIndent
showCommunity showCommunity
showContext showContext

View file

@ -11,6 +11,7 @@ import {
BanPerson, BanPerson,
BanPersonResponse, BanPersonResponse,
BlockPerson, BlockPerson,
CommentId,
CommentReplyResponse, CommentReplyResponse,
CommentResponse, CommentResponse,
Community, Community,
@ -65,6 +66,7 @@ import {
enableNsfw, enableNsfw,
fetchLimit, fetchLimit,
futureDaysToUnixTime, futureDaysToUnixTime,
getCommentParentId,
getPageFromString, getPageFromString,
getQueryParams, getQueryParams,
getQueryString, getQueryString,
@ -100,6 +102,7 @@ interface ProfileState {
showBanDialog: boolean; showBanDialog: boolean;
removeData: boolean; removeData: boolean;
siteRes: GetSiteResponse; siteRes: GetSiteResponse;
finished: Map<CommentId, boolean | undefined>;
} }
interface ProfileProps { interface ProfileProps {
@ -163,6 +166,7 @@ export class Profile extends Component<
siteRes: this.isoData.site_res, siteRes: this.isoData.site_res,
showBanDialog: false, showBanDialog: false,
removeData: false, removeData: false,
finished: new Map(),
}; };
constructor(props: RouteComponentProps<{ username: string }>, context: any) { constructor(props: RouteComponentProps<{ username: string }>, context: any) {
@ -333,6 +337,7 @@ export class Profile extends Component<
sort={sort} sort={sort}
page={page} page={page}
limit={fetchLimit} limit={fetchLimit}
finished={this.state.finished}
enableDownvotes={enableDownvotes(siteRes)} enableDownvotes={enableDownvotes(siteRes)}
enableNsfw={enableNsfw(siteRes)} enableNsfw={enableNsfw(siteRes)}
view={view} view={view}
@ -842,16 +847,7 @@ export class Profile extends Component<
const createCommentRes = await apiWrapper( const createCommentRes = await apiWrapper(
HttpService.client.createComment(form) HttpService.client.createComment(form)
); );
this.createAndUpdateComments(createCommentRes);
this.setState(s => {
if (
s.personRes.state == "success" &&
createCommentRes.state == "success"
) {
s.personRes.data.comments.unshift(createCommentRes.data.comment_view);
}
return s;
});
} }
async handleEditComment(form: EditComment) { async handleEditComment(form: EditComment) {
@ -1033,6 +1029,21 @@ export class Profile extends Component<
res.data.comment_view, res.data.comment_view,
s.personRes.data.comments s.personRes.data.comments
); );
s.finished.set(res.data.comment_view.comment.id, true);
}
return s;
});
}
createAndUpdateComments(res: RequestState<CommentResponse>) {
this.setState(s => {
if (s.personRes.state == "success" && res.state == "success") {
s.personRes.data.comments.unshift(res.data.comment_view);
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true
);
} }
return s; return s;
}); });

View file

@ -17,7 +17,6 @@ import {
ResolvePostReport, ResolvePostReport,
ResolvePrivateMessageReport, ResolvePrivateMessageReport,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { Subscription } from "rxjs";
import { i18n } from "../../i18next"; import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces"; import { InitialFetchRequest } from "../../interfaces";
import { HttpService, UserService } from "../../services"; import { HttpService, UserService } from "../../services";
@ -82,7 +81,6 @@ interface ReportsState {
export class Reports extends Component<any, ReportsState> { export class Reports extends Component<any, ReportsState> {
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private subscription?: Subscription;
state: ReportsState = { state: ReportsState = {
commentReportsRes: { state: "empty" }, commentReportsRes: { state: "empty" },
postReportsRes: { state: "empty" }, postReportsRes: { state: "empty" },
@ -130,9 +128,9 @@ export class Reports extends Component<any, ReportsState> {
} }
} }
componentWillUnmount() { async componentDidMount() {
if (isBrowser()) { if (!isInitialRoute(this.isoData, this.context)) {
this.subscription?.unsubscribe(); await this.refetch();
} }
} }
@ -469,14 +467,14 @@ export class Reports extends Component<any, ReportsState> {
await this.refetch(); await this.refetch();
} }
handleUnreadOrAllChange(i: Reports, event: any) { async handleUnreadOrAllChange(i: Reports, event: any) {
i.setState({ unreadOrAll: Number(event.target.value), page: 1 }); i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
i.refetch(); await i.refetch();
} }
handleMessageTypeChange(i: Reports, event: any) { async handleMessageTypeChange(i: Reports, event: any) {
i.setState({ messageType: Number(event.target.value), page: 1 }); i.setState({ messageType: Number(event.target.value), page: 1 });
i.refetch(); await i.refetch();
} }
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {

View file

@ -1,4 +1,4 @@
import { Component, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { PostReportView, PostView, ResolvePostReport } from "lemmy-js-client"; import { PostReportView, PostView, ResolvePostReport } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { i18n } from "../../i18next";
@ -25,6 +25,14 @@ export class PostReport extends Component<PostReportProps, PostReportState> {
super(props, context); super(props, context);
} }
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & PostReportProps>
): void {
if (this.props != nextProps) {
this.setState({ loading: false });
}
}
render() { render() {
let r = this.props.report; let r = this.props.report;
let resolver = r.resolver; let resolver = r.resolver;

View file

@ -10,6 +10,7 @@ import {
BanPersonResponse, BanPersonResponse,
BlockCommunity, BlockCommunity,
BlockPerson, BlockPerson,
CommentId,
CommentReplyResponse, CommentReplyResponse,
CommentResponse, CommentResponse,
CommentSortType, CommentSortType,
@ -108,6 +109,7 @@ interface PostState {
commentSectionRef?: RefObject<HTMLDivElement>; commentSectionRef?: RefObject<HTMLDivElement>;
showSidebarMobile: boolean; showSidebarMobile: boolean;
maxCommentsShown: number; maxCommentsShown: number;
finished: Map<CommentId, boolean | undefined>;
} }
export class Post extends Component<any, PostState> { export class Post extends Component<any, PostState> {
@ -124,6 +126,7 @@ export class Post extends Component<any, PostState> {
siteRes: this.isoData.site_res, siteRes: this.isoData.site_res,
showSidebarMobile: false, showSidebarMobile: false,
maxCommentsShown: commentsShownInterval, maxCommentsShown: commentsShownInterval,
finished: new Map(),
}; };
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -382,6 +385,7 @@ export class Post extends Component<any, PostState> {
siteLanguages={this.state.siteRes.discussion_languages} siteLanguages={this.state.siteRes.discussion_languages}
onCreateComment={this.handleCreateComment} onCreateComment={this.handleCreateComment}
onEditComment={this.handleEditComment} onEditComment={this.handleEditComment}
finished={this.state.finished.get(0)}
/> />
<div className="d-block d-md-none"> <div className="d-block d-md-none">
<button <button
@ -511,6 +515,7 @@ export class Post extends Component<any, PostState> {
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
enableDownvotes={enableDownvotes(this.state.siteRes)} enableDownvotes={enableDownvotes(this.state.siteRes)}
showContext showContext
finished={this.state.finished}
allLanguages={this.state.siteRes.all_languages} allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages} siteLanguages={this.state.siteRes.discussion_languages}
onSaveComment={this.handleSaveComment} onSaveComment={this.handleSaveComment}
@ -600,6 +605,7 @@ export class Post extends Component<any, PostState> {
moderators={res.data.moderators} moderators={res.data.moderators}
admins={this.state.siteRes.admins} admins={this.state.siteRes.admins}
enableDownvotes={enableDownvotes(this.state.siteRes)} enableDownvotes={enableDownvotes(this.state.siteRes)}
finished={this.state.finished}
allLanguages={this.state.siteRes.all_languages} allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages} siteLanguages={this.state.siteRes.discussion_languages}
onSaveComment={this.handleSaveComment} onSaveComment={this.handleSaveComment}
@ -782,16 +788,7 @@ export class Post extends Component<any, PostState> {
const createCommentRes = await apiWrapper( const createCommentRes = await apiWrapper(
HttpService.client.createComment(form) HttpService.client.createComment(form)
); );
this.createAndUpdateComments(createCommentRes);
this.setState(s => {
if (
s.commentsRes.state == "success" &&
createCommentRes.state == "success"
) {
s.commentsRes.data.comments.unshift(createCommentRes.data.comment_view);
}
return s;
});
} }
async handleEditComment(form: EditComment) { async handleEditComment(form: EditComment) {
@ -1021,6 +1018,21 @@ export class Post extends Component<any, PostState> {
} }
} }
createAndUpdateComments(res: RequestState<CommentResponse>) {
this.setState(s => {
if (s.commentsRes.state == "success" && res.state == "success") {
s.commentsRes.data.comments.unshift(res.data.comment_view);
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true
);
}
return s;
});
}
findAndUpdateComment(res: RequestState<CommentResponse>) { findAndUpdateComment(res: RequestState<CommentResponse>) {
this.setState(s => { this.setState(s => {
if (s.commentsRes.state == "success" && res.state == "success") { if (s.commentsRes.state == "success" && res.state == "success") {
@ -1028,6 +1040,7 @@ export class Post extends Component<any, PostState> {
res.data.comment_view, res.data.comment_view,
s.commentsRes.data.comments s.commentsRes.data.comments
); );
s.finished.set(res.data.comment_view.comment.id, true);
} }
return s; return s;
}); });

View file

@ -1,5 +1,6 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { import {
CreatePrivateMessage as CreatePrivateMessageI,
GetPersonDetails, GetPersonDetails,
GetPersonDetailsResponse, GetPersonDetailsResponse,
GetSiteResponse, GetSiteResponse,
@ -151,10 +152,14 @@ export class CreatePrivateMessage extends Component<
); );
} }
handlePrivateMessageCreate() { async handlePrivateMessageCreate(form: CreatePrivateMessageI) {
toast(i18n.t("message_sent")); const res = await apiWrapper(HttpService.client.createPrivateMessage(form));
// Navigate to the front if (res.state == "success") {
this.context.router.history.push("/"); toast(i18n.t("message_sent"));
// Navigate to the front
this.context.router.history.push("/");
}
} }
} }

View file

@ -1,4 +1,4 @@
import { Component, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { Prompt } from "inferno-router"; import { Prompt } from "inferno-router";
import { import {
@ -56,12 +56,11 @@ export class PrivateMessageForm extends Component<
setupTippy(); setupTippy();
} }
// TODO what is this componentWillReceiveProps(
componentDidUpdate() { nextProps: Readonly<{ children?: InfernoNode } & PrivateMessageFormProps>
if (!this.state.loading && this.state.content) { ): void {
window.onbeforeunload = () => true; if (this.props != nextProps) {
} else { this.setState({ loading: false, content: undefined, previewMode: false });
window.onbeforeunload = null;
} }
} }

View file

@ -1,4 +1,4 @@
import { Component, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { import {
PrivateMessageReportView, PrivateMessageReportView,
@ -27,6 +27,14 @@ export class PrivateMessageReport extends Component<Props, State> {
super(props, context); super(props, context);
} }
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & Props>
): void {
if (this.props != nextProps) {
this.setState({ loading: false });
}
}
render() { render() {
let r = this.props.report; let r = this.props.report;
let pmr = r.private_message_report; let pmr = r.private_message_report;

View file

@ -1,4 +1,4 @@
import { Component, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { import {
CreatePrivateMessage, CreatePrivateMessage,
CreatePrivateMessageReport, CreatePrivateMessageReport,
@ -64,6 +64,23 @@ export class PrivateMessage extends Component<
); );
} }
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & PrivateMessageProps>
): void {
if (this.props != nextProps) {
this.setState({
showReply: false,
showEdit: false,
collapsed: false,
viewSource: false,
showReportDialog: false,
deleteLoading: false,
readLoading: false,
reportLoading: false,
});
}
}
render() { render() {
let message_view = this.props.private_message_view; let message_view = this.props.private_message_view;
let otherPerson: Person = this.mine let otherPerson: Person = this.mine

View file

@ -657,6 +657,7 @@ export class Search extends Component<any, SearchState> {
allLanguages={this.state.siteRes.all_languages} allLanguages={this.state.siteRes.all_languages}
siteLanguages={this.state.siteRes.discussion_languages} siteLanguages={this.state.siteRes.discussion_languages}
// All of these are unused, since its viewonly // All of these are unused, since its viewonly
finished={new Map()}
onSaveComment={() => {}} onSaveComment={() => {}}
onBlockPerson={() => {}} onBlockPerson={() => {}}
onDeleteComment={() => {}} onDeleteComment={() => {}}
@ -717,6 +718,7 @@ export class Search extends Component<any, SearchState> {
allLanguages={siteRes.all_languages} allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages} siteLanguages={siteRes.discussion_languages}
// All of these are unused, since its viewonly // All of these are unused, since its viewonly
finished={new Map()}
onSaveComment={() => {}} onSaveComment={() => {}}
onBlockPerson={() => {}} onBlockPerson={() => {}}
onDeleteComment={() => {}} onDeleteComment={() => {}}