mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-12-23 17:54:23 +00:00
Comment Tree paging (#726)
* Updating translations. * Forgot to add comment-sort-select * Upgrading deps
This commit is contained in:
parent
49ceb00dc8
commit
69b623b8fb
|
@ -1 +1 @@
|
|||
Subproject commit 7c1b691af63845a2fe2f8219b4620b8db3c9c3ba
|
||||
Subproject commit 7c3945745dcd07774b19453803f7f14ab80ab3d3
|
|
@ -77,7 +77,7 @@
|
|||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"husky": "^8.0.1",
|
||||
"import-sort-style-module": "^6.0.0",
|
||||
"lemmy-js-client": "0.17.0-rc.38",
|
||||
"lemmy-js-client": "0.17.0-rc.39",
|
||||
"lint-staged": "^13.0.3",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Component } from "inferno";
|
|||
import { T } from "inferno-i18next-dess";
|
||||
import { Link } from "inferno-router";
|
||||
import {
|
||||
CommentNode as CommentNodeI,
|
||||
CommentResponse,
|
||||
CreateComment,
|
||||
EditComment,
|
||||
|
@ -12,7 +13,6 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import { Subscription } from "rxjs";
|
||||
import { i18n } from "../../i18next";
|
||||
import { CommentNode as CommentNodeI } from "../../interfaces";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
auth,
|
||||
|
|
|
@ -8,12 +8,16 @@ import {
|
|||
BanFromCommunity,
|
||||
BanPerson,
|
||||
BlockPerson,
|
||||
CommentNode as CommentNodeI,
|
||||
CommentReplyView,
|
||||
CommentView,
|
||||
CommunityModeratorView,
|
||||
CreateCommentLike,
|
||||
CreateCommentReport,
|
||||
DeleteComment,
|
||||
MarkCommentAsRead,
|
||||
GetComments,
|
||||
ListingType,
|
||||
MarkCommentReplyAsRead,
|
||||
MarkPersonMentionAsRead,
|
||||
PersonMentionView,
|
||||
PersonViewSafe,
|
||||
|
@ -26,11 +30,7 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import moment from "moment";
|
||||
import { i18n } from "../../i18next";
|
||||
import {
|
||||
BanType,
|
||||
CommentNode as CommentNodeI,
|
||||
PurgeType,
|
||||
} from "../../interfaces";
|
||||
import { BanType, CommentViewType, PurgeType } from "../../interfaces";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
amCommunityCreator,
|
||||
|
@ -38,6 +38,7 @@ import {
|
|||
canAdmin,
|
||||
canMod,
|
||||
colorList,
|
||||
commentTreeMaxDepth,
|
||||
futureDaysToUnixTime,
|
||||
isAdmin,
|
||||
isBanned,
|
||||
|
@ -82,7 +83,6 @@ interface CommentNodeState {
|
|||
score: number;
|
||||
upvotes: number;
|
||||
downvotes: number;
|
||||
borderColor: string;
|
||||
readLoading: boolean;
|
||||
saveLoading: boolean;
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ interface CommentNodeProps {
|
|||
showContext?: boolean;
|
||||
showCommunity?: boolean;
|
||||
enableDownvotes: boolean;
|
||||
viewType: CommentViewType;
|
||||
}
|
||||
|
||||
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||
|
@ -129,9 +130,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
score: this.props.node.comment_view.counts.score,
|
||||
upvotes: this.props.node.comment_view.counts.upvotes,
|
||||
downvotes: this.props.node.comment_view.counts.downvotes,
|
||||
borderColor: this.props.node.depth
|
||||
? colorList[this.props.node.depth % colorList.length]
|
||||
: colorList[0],
|
||||
readLoading: false,
|
||||
saveLoading: false,
|
||||
};
|
||||
|
@ -181,10 +179,23 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
cv.creator.id
|
||||
);
|
||||
|
||||
let borderColor = this.props.node.depth
|
||||
? colorList[(this.props.node.depth - 1) % colorList.length]
|
||||
: colorList[0];
|
||||
let moreRepliesBorderColor = this.props.node.depth
|
||||
? colorList[this.props.node.depth % colorList.length]
|
||||
: colorList[0];
|
||||
|
||||
let showMoreChildren =
|
||||
this.props.viewType == CommentViewType.Tree &&
|
||||
!this.state.collapsed &&
|
||||
node.children.length == 0 &&
|
||||
node.comment_view.counts.child_count > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`comment ${
|
||||
cv.comment.parent_id.isSome() && !this.props.noIndent ? "ml-1" : ""
|
||||
this.props.node.depth && !this.props.noIndent ? "ml-1" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
|
@ -194,14 +205,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
} ${this.isCommentNew ? "mark" : ""}`}
|
||||
style={
|
||||
!this.props.noIndent &&
|
||||
cv.comment.parent_id.isSome() &&
|
||||
`border-left: 2px ${this.state.borderColor} solid !important`
|
||||
this.props.node.depth &&
|
||||
`border-left: 2px ${borderColor} solid !important`
|
||||
}
|
||||
>
|
||||
<div
|
||||
class={`${
|
||||
!this.props.noIndent && cv.comment.parent_id.isSome() && "ml-2"
|
||||
}`}
|
||||
class={`${!this.props.noIndent && this.props.node.depth && "ml-2"}`}
|
||||
>
|
||||
<div class="d-flex flex-wrap align-items-center text-muted small">
|
||||
<span class="mr-2">
|
||||
|
@ -262,7 +271,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
<>
|
||||
<a
|
||||
className={`unselectable pointer ${this.scoreColor}`}
|
||||
onClick={linkEvent(node, this.handleCommentUpvote)}
|
||||
onClick={this.handleCommentUpvote}
|
||||
data-tippy-content={this.pointsTippy}
|
||||
>
|
||||
<span
|
||||
|
@ -314,12 +323,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
class="btn btn-link btn-animate text-muted"
|
||||
onClick={linkEvent(this, this.handleMarkRead)}
|
||||
data-tippy-content={
|
||||
this.commentOrMentionRead
|
||||
this.commentReplyOrMentionRead
|
||||
? i18n.t("mark_as_unread")
|
||||
: i18n.t("mark_as_read")
|
||||
}
|
||||
aria-label={
|
||||
this.commentOrMentionRead
|
||||
this.commentReplyOrMentionRead
|
||||
? i18n.t("mark_as_unread")
|
||||
: i18n.t("mark_as_read")
|
||||
}
|
||||
|
@ -330,7 +339,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
<Icon
|
||||
icon="check"
|
||||
classes={`icon-inline ${
|
||||
this.commentOrMentionRead && "text-success"
|
||||
this.commentReplyOrMentionRead && "text-success"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
|
@ -345,7 +354,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
? "text-info"
|
||||
: "text-muted"
|
||||
}`}
|
||||
onClick={linkEvent(node, this.handleCommentUpvote)}
|
||||
onClick={this.handleCommentUpvote}
|
||||
data-tippy-content={i18n.t("upvote")}
|
||||
aria-label={i18n.t("upvote")}
|
||||
>
|
||||
|
@ -364,10 +373,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
? "text-danger"
|
||||
: "text-muted"
|
||||
}`}
|
||||
onClick={linkEvent(
|
||||
node,
|
||||
this.handleCommentDownvote
|
||||
)}
|
||||
onClick={this.handleCommentDownvote}
|
||||
data-tippy-content={i18n.t("downvote")}
|
||||
aria-label={i18n.t("downvote")}
|
||||
>
|
||||
|
@ -772,6 +778,25 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
{showMoreChildren && (
|
||||
<div
|
||||
className={`details ml-1 comment-node py-2 ${
|
||||
!this.props.noBorder ? "border-top border-light" : ""
|
||||
}`}
|
||||
style={`border-left: 2px ${moreRepliesBorderColor} solid !important`}
|
||||
>
|
||||
<button
|
||||
class="btn btn-link text-muted"
|
||||
onClick={linkEvent(this, this.handleFetchChildren)}
|
||||
>
|
||||
{i18n.t("x_more_replies", {
|
||||
count: node.comment_view.counts.child_count,
|
||||
formattedCount: numToSI(node.comment_view.counts.child_count),
|
||||
})}{" "}
|
||||
➔
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* end of details */}
|
||||
{this.state.showRemoveDialog && (
|
||||
<form
|
||||
|
@ -931,7 +956,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
focus
|
||||
/>
|
||||
)}
|
||||
{!this.state.collapsed && node.children && (
|
||||
{!this.state.collapsed && node.children.length > 0 && (
|
||||
<CommentNodes
|
||||
nodes={node.children}
|
||||
locked={this.props.locked}
|
||||
|
@ -939,6 +964,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
admins={this.props.admins}
|
||||
maxCommentsShown={None}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
viewType={this.props.viewType}
|
||||
/>
|
||||
)}
|
||||
{/* A collapsed clearfix */}
|
||||
|
@ -947,11 +973,16 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
);
|
||||
}
|
||||
|
||||
get commentOrMentionRead() {
|
||||
get commentReplyOrMentionRead(): boolean {
|
||||
let cv = this.props.node.comment_view;
|
||||
return this.isPersonMentionType(cv)
|
||||
? cv.person_mention.read
|
||||
: cv.comment.read;
|
||||
|
||||
if (this.isPersonMentionType(cv)) {
|
||||
return cv.person_mention.read;
|
||||
} else if (this.isCommentReplyType(cv)) {
|
||||
return cv.comment_reply.read;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
linkBtn(small = false) {
|
||||
|
@ -968,7 +999,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
<>
|
||||
<Link
|
||||
className={classnames}
|
||||
to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
|
||||
to={`/comment/${cv.comment.id}`}
|
||||
title={title}
|
||||
>
|
||||
<Icon icon="link" classes="icon-inline" />
|
||||
|
@ -1061,7 +1092,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
this.setState(this.state);
|
||||
}
|
||||
|
||||
handleCommentUpvote(i: CommentNodeI, event: any) {
|
||||
handleCommentUpvote(event: any) {
|
||||
event.preventDefault();
|
||||
let myVote = this.state.my_vote.unwrapOr(0);
|
||||
let newVote = myVote == 1 ? 0 : 1;
|
||||
|
@ -1081,17 +1112,16 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
this.state.my_vote = Some(newVote);
|
||||
|
||||
let form = new CreateCommentLike({
|
||||
comment_id: i.comment_view.comment.id,
|
||||
comment_id: this.props.node.comment_view.comment.id,
|
||||
score: newVote,
|
||||
auth: auth().unwrap(),
|
||||
});
|
||||
|
||||
WebSocketService.Instance.send(wsClient.likeComment(form));
|
||||
this.setState(this.state);
|
||||
setupTippy();
|
||||
}
|
||||
|
||||
handleCommentDownvote(i: CommentNodeI, event: any) {
|
||||
handleCommentDownvote(event: any) {
|
||||
event.preventDefault();
|
||||
let myVote = this.state.my_vote.unwrapOr(0);
|
||||
let newVote = myVote == -1 ? 0 : -1;
|
||||
|
@ -1111,7 +1141,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
this.state.my_vote = Some(newVote);
|
||||
|
||||
let form = new CreateCommentLike({
|
||||
comment_id: i.comment_view.comment.id,
|
||||
comment_id: this.props.node.comment_view.comment.id,
|
||||
score: newVote,
|
||||
auth: auth().unwrap(),
|
||||
});
|
||||
|
@ -1175,11 +1205,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
}
|
||||
|
||||
isPersonMentionType(
|
||||
item: CommentView | PersonMentionView
|
||||
item: CommentView | PersonMentionView | CommentReplyView
|
||||
): item is PersonMentionView {
|
||||
return (item as PersonMentionView).person_mention?.id !== undefined;
|
||||
}
|
||||
|
||||
isCommentReplyType(
|
||||
item: CommentView | PersonMentionView | CommentReplyView
|
||||
): item is CommentReplyView {
|
||||
return (item as CommentReplyView).comment_reply?.id !== undefined;
|
||||
}
|
||||
|
||||
handleMarkRead(i: CommentNode) {
|
||||
if (i.isPersonMentionType(i.props.node.comment_view)) {
|
||||
let form = new MarkPersonMentionAsRead({
|
||||
|
@ -1188,13 +1224,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
auth: auth().unwrap(),
|
||||
});
|
||||
WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
|
||||
} else {
|
||||
let form = new MarkCommentAsRead({
|
||||
comment_id: i.props.node.comment_view.comment.id,
|
||||
read: !i.props.node.comment_view.comment.read,
|
||||
} else if (i.isCommentReplyType(i.props.node.comment_view)) {
|
||||
let form = new MarkCommentReplyAsRead({
|
||||
comment_reply_id: i.props.node.comment_view.comment_reply.id,
|
||||
read: !i.props.node.comment_view.comment_reply.read,
|
||||
auth: auth().unwrap(),
|
||||
});
|
||||
WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
|
||||
WebSocketService.Instance.send(wsClient.markCommentReplyAsRead(form));
|
||||
}
|
||||
|
||||
i.state.readLoading = true;
|
||||
|
@ -1419,6 +1455,24 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
setupTippy();
|
||||
}
|
||||
|
||||
handleFetchChildren(i: CommentNode) {
|
||||
let form = new GetComments({
|
||||
post_id: Some(i.props.node.comment_view.post.id),
|
||||
parent_id: Some(i.props.node.comment_view.comment.id),
|
||||
max_depth: Some(commentTreeMaxDepth),
|
||||
page: None,
|
||||
sort: None,
|
||||
limit: Some(999),
|
||||
type_: Some(ListingType.All),
|
||||
community_name: None,
|
||||
community_id: None,
|
||||
saved_only: Some(false),
|
||||
auth: auth(false).ok(),
|
||||
});
|
||||
|
||||
WebSocketService.Instance.send(wsClient.getComments(form));
|
||||
}
|
||||
|
||||
get scoreColor() {
|
||||
if (this.state.my_vote.unwrapOr(0) == 1) {
|
||||
return "text-info";
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { Option } from "@sniptt/monads";
|
||||
import { Component } from "inferno";
|
||||
import { CommunityModeratorView, PersonViewSafe } from "lemmy-js-client";
|
||||
import { CommentNode as CommentNodeI } from "../../interfaces";
|
||||
import {
|
||||
CommentNode as CommentNodeI,
|
||||
CommunityModeratorView,
|
||||
PersonViewSafe,
|
||||
} from "lemmy-js-client";
|
||||
import { CommentViewType } from "../../interfaces";
|
||||
import { CommentNode } from "./comment-node";
|
||||
|
||||
interface CommentNodesProps {
|
||||
|
@ -17,6 +21,7 @@ interface CommentNodesProps {
|
|||
showContext?: boolean;
|
||||
showCommunity?: boolean;
|
||||
enableDownvotes?: boolean;
|
||||
viewType: CommentViewType;
|
||||
}
|
||||
|
||||
export class CommentNodes extends Component<CommentNodesProps, any> {
|
||||
|
@ -45,6 +50,7 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
|
|||
showContext={this.props.showContext}
|
||||
showCommunity={this.props.showCommunity}
|
||||
enableDownvotes={this.props.enableDownvotes}
|
||||
viewType={this.props.viewType}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -2,13 +2,14 @@ import { None } from "@sniptt/monads";
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import {
|
||||
CommentNode as CommentNodeI,
|
||||
CommentReportView,
|
||||
CommentView,
|
||||
ResolveCommentReport,
|
||||
SubscribedType,
|
||||
} from "lemmy-js-client";
|
||||
import { i18n } from "../../i18next";
|
||||
import { CommentNode as CommentNodeI } from "../../interfaces";
|
||||
import { CommentViewType } from "../../interfaces";
|
||||
import { WebSocketService } from "../../services";
|
||||
import { auth, wsClient } from "../../utils";
|
||||
import { Icon } from "../common/icon";
|
||||
|
@ -44,18 +45,20 @@ export class CommentReport extends Component<CommentReportProps, any> {
|
|||
subscribed: SubscribedType.NotSubscribed,
|
||||
saved: false,
|
||||
creator_blocked: false,
|
||||
recipient: None,
|
||||
my_vote: r.my_vote,
|
||||
};
|
||||
|
||||
let node: CommentNodeI = {
|
||||
comment_view,
|
||||
children: [],
|
||||
depth: 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CommentNode
|
||||
node={node}
|
||||
viewType={CommentViewType.Flat}
|
||||
moderators={None}
|
||||
admins={None}
|
||||
enableDownvotes={true}
|
||||
|
|
70
src/shared/components/common/comment-sort-select.tsx
Normal file
70
src/shared/components/common/comment-sort-select.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import { CommentSortType } from "lemmy-js-client";
|
||||
import { i18n } from "../../i18next";
|
||||
import { randomStr, relTags, sortingHelpUrl } from "../../utils";
|
||||
import { Icon } from "./icon";
|
||||
|
||||
interface CommentSortSelectProps {
|
||||
sort: CommentSortType;
|
||||
onChange?(val: CommentSortType): any;
|
||||
}
|
||||
|
||||
interface CommentSortSelectState {
|
||||
sort: CommentSortType;
|
||||
}
|
||||
|
||||
export class CommentSortSelect extends Component<
|
||||
CommentSortSelectProps,
|
||||
CommentSortSelectState
|
||||
> {
|
||||
private id = `sort-select-${randomStr()}`;
|
||||
private emptyState: CommentSortSelectState = {
|
||||
sort: this.props.sort,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
this.state = this.emptyState;
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: any): CommentSortSelectState {
|
||||
return {
|
||||
sort: props.sort,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<select
|
||||
id={this.id}
|
||||
name={this.id}
|
||||
value={this.state.sort}
|
||||
onChange={linkEvent(this, this.handleSortChange)}
|
||||
class="custom-select w-auto mr-2 mb-2"
|
||||
aria-label={i18n.t("sort_type")}
|
||||
>
|
||||
<option disabled aria-hidden="true">
|
||||
{i18n.t("sort_type")}
|
||||
</option>
|
||||
<option value={CommentSortType.Hot}>{i18n.t("hot")}</option>,
|
||||
<option value={CommentSortType.Top}>{i18n.t("top")}</option>,
|
||||
<option value={CommentSortType.New}>{i18n.t("new")}</option>
|
||||
<option value={CommentSortType.Old}>{i18n.t("old")}</option>
|
||||
</select>
|
||||
<a
|
||||
className="text-muted"
|
||||
href={sortingHelpUrl}
|
||||
rel={relTags}
|
||||
title={i18n.t("sorting_help")}
|
||||
>
|
||||
<Icon icon="help-circle" classes="icon-inline" />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
handleSortChange(i: CommentSortSelect, event: any) {
|
||||
i.props.onChange(event.target.value);
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
|
|||
<option value={SortType.Active}>{i18n.t("active")}</option>,
|
||||
]}
|
||||
<option value={SortType.New}>{i18n.t("new")}</option>
|
||||
<option value={SortType.Old}>{i18n.t("old")}</option>
|
||||
{!this.props.hideMostComments && [
|
||||
<option value={SortType.MostComments}>
|
||||
{i18n.t("most_comments")}
|
||||
|
|
|
@ -29,7 +29,11 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import { Subscription } from "rxjs";
|
||||
import { i18n } from "../../i18next";
|
||||
import { DataType, InitialFetchRequest } from "../../interfaces";
|
||||
import {
|
||||
CommentViewType,
|
||||
DataType,
|
||||
InitialFetchRequest,
|
||||
} from "../../interfaces";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
auth,
|
||||
|
@ -46,6 +50,7 @@ import {
|
|||
getPageFromProps,
|
||||
getSortTypeFromProps,
|
||||
notifyPost,
|
||||
postToCommentSortType,
|
||||
relTags,
|
||||
restoreScrollPosition,
|
||||
saveCommentRes,
|
||||
|
@ -233,9 +238,12 @@ export class Community extends Component<any, State> {
|
|||
community_id: None,
|
||||
page,
|
||||
limit: Some(fetchLimit),
|
||||
sort,
|
||||
max_depth: None,
|
||||
sort: sort.map(postToCommentSortType),
|
||||
type_: Some(ListingType.All),
|
||||
saved_only: Some(false),
|
||||
post_id: None,
|
||||
parent_id: None,
|
||||
auth: req.auth,
|
||||
});
|
||||
promises.push(Promise.resolve());
|
||||
|
@ -389,6 +397,7 @@ export class Community extends Component<any, State> {
|
|||
) : (
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(this.state.comments)}
|
||||
viewType={CommentViewType.Flat}
|
||||
noIndent
|
||||
showContext
|
||||
enableDownvotes={enableDownvotes(this.state.siteRes)}
|
||||
|
@ -499,11 +508,14 @@ export class Community extends Component<any, State> {
|
|||
let form = new GetComments({
|
||||
page: Some(this.state.page),
|
||||
limit: Some(fetchLimit),
|
||||
sort: Some(this.state.sort),
|
||||
max_depth: None,
|
||||
sort: Some(postToCommentSortType(this.state.sort)),
|
||||
type_: Some(ListingType.All),
|
||||
community_name: Some(this.state.communityName),
|
||||
community_id: None,
|
||||
saved_only: Some(false),
|
||||
post_id: None,
|
||||
parent_id: None,
|
||||
auth: auth(false).ok(),
|
||||
});
|
||||
WebSocketService.Instance.send(wsClient.getComments(form));
|
||||
|
|
|
@ -30,7 +30,11 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import { Subscription } from "rxjs";
|
||||
import { i18n } from "../../i18next";
|
||||
import { DataType, InitialFetchRequest } from "../../interfaces";
|
||||
import {
|
||||
CommentViewType,
|
||||
DataType,
|
||||
InitialFetchRequest,
|
||||
} from "../../interfaces";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
auth,
|
||||
|
@ -48,6 +52,7 @@ import {
|
|||
getSortTypeFromProps,
|
||||
isBrowser,
|
||||
notifyPost,
|
||||
postToCommentSortType,
|
||||
relTags,
|
||||
restoreScrollPosition,
|
||||
saveCommentRes,
|
||||
|
@ -263,9 +268,12 @@ export class Home extends Component<any, HomeState> {
|
|||
community_name: None,
|
||||
page,
|
||||
limit: Some(fetchLimit),
|
||||
sort,
|
||||
max_depth: None,
|
||||
sort: sort.map(postToCommentSortType),
|
||||
type_,
|
||||
saved_only: Some(false),
|
||||
post_id: None,
|
||||
parent_id: None,
|
||||
auth: req.auth,
|
||||
});
|
||||
promises.push(Promise.resolve());
|
||||
|
@ -565,6 +573,7 @@ export class Home extends Component<any, HomeState> {
|
|||
) : (
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(this.state.comments)}
|
||||
viewType={CommentViewType.Flat}
|
||||
moderators={None}
|
||||
admins={None}
|
||||
maxCommentsShown={None}
|
||||
|
@ -694,8 +703,11 @@ export class Home extends Component<any, HomeState> {
|
|||
community_name: None,
|
||||
page: Some(this.state.page),
|
||||
limit: Some(fetchLimit),
|
||||
sort: Some(this.state.sort),
|
||||
max_depth: None,
|
||||
sort: Some(postToCommentSortType(this.state.sort)),
|
||||
saved_only: Some(false),
|
||||
post_id: None,
|
||||
parent_id: None,
|
||||
auth: auth(false).ok(),
|
||||
type_: Some(this.state.listingType),
|
||||
});
|
||||
|
|
|
@ -2,8 +2,11 @@ import { None, Some } from "@sniptt/monads";
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import {
|
||||
BlockPersonResponse,
|
||||
CommentReplyResponse,
|
||||
CommentReplyView,
|
||||
CommentReportResponse,
|
||||
CommentResponse,
|
||||
CommentSortType,
|
||||
CommentView,
|
||||
GetPersonMentions,
|
||||
GetPersonMentionsResponse,
|
||||
|
@ -17,14 +20,13 @@ import {
|
|||
PrivateMessageResponse,
|
||||
PrivateMessagesResponse,
|
||||
PrivateMessageView,
|
||||
SortType,
|
||||
UserOperation,
|
||||
wsJsonToRes,
|
||||
wsUserOp,
|
||||
} from "lemmy-js-client";
|
||||
import { Subscription } from "rxjs";
|
||||
import { i18n } from "../../i18next";
|
||||
import { InitialFetchRequest } from "../../interfaces";
|
||||
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
auth,
|
||||
|
@ -44,10 +46,10 @@ import {
|
|||
wsSubscribe,
|
||||
} from "../../utils";
|
||||
import { CommentNodes } from "../comment/comment-nodes";
|
||||
import { CommentSortSelect } from "../common/comment-sort-select";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { SortSelect } from "../common/sort-select";
|
||||
import { PrivateMessage } from "../private_message/private-message";
|
||||
|
||||
enum UnreadOrAll {
|
||||
|
@ -70,18 +72,18 @@ enum ReplyEnum {
|
|||
type ReplyType = {
|
||||
id: number;
|
||||
type_: ReplyEnum;
|
||||
view: CommentView | PrivateMessageView | PersonMentionView;
|
||||
view: CommentView | PrivateMessageView | PersonMentionView | CommentReplyView;
|
||||
published: string;
|
||||
};
|
||||
|
||||
interface InboxState {
|
||||
unreadOrAll: UnreadOrAll;
|
||||
messageType: MessageType;
|
||||
replies: CommentView[];
|
||||
replies: CommentReplyView[];
|
||||
mentions: PersonMentionView[];
|
||||
messages: PrivateMessageView[];
|
||||
combined: ReplyType[];
|
||||
sort: SortType;
|
||||
sort: CommentSortType;
|
||||
page: number;
|
||||
siteRes: GetSiteResponse;
|
||||
loading: boolean;
|
||||
|
@ -102,7 +104,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
mentions: [],
|
||||
messages: [],
|
||||
combined: [],
|
||||
sort: SortType.New,
|
||||
sort: CommentSortType.New,
|
||||
page: 1,
|
||||
siteRes: this.isoData.site_res,
|
||||
loading: true,
|
||||
|
@ -323,19 +325,17 @@ export class Inbox extends Component<any, InboxState> {
|
|||
<div className="mb-2">
|
||||
<span class="mr-3">{this.unreadOrAllRadios()}</span>
|
||||
<span class="mr-3">{this.messageTypeRadios()}</span>
|
||||
<SortSelect
|
||||
<CommentSortSelect
|
||||
sort={this.state.sort}
|
||||
onChange={this.handleSortChange}
|
||||
hideHot
|
||||
hideMostComments
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
replyToReplyType(r: CommentView): ReplyType {
|
||||
replyToReplyType(r: CommentReplyView): ReplyType {
|
||||
return {
|
||||
id: r.comment.id,
|
||||
id: r.comment_reply.id,
|
||||
type_: ReplyEnum.Reply,
|
||||
view: r,
|
||||
published: r.comment.published,
|
||||
|
@ -382,7 +382,10 @@ export class Inbox extends Component<any, InboxState> {
|
|||
return (
|
||||
<CommentNodes
|
||||
key={i.id}
|
||||
nodes={[{ comment_view: i.view as CommentView }]}
|
||||
nodes={[
|
||||
{ comment_view: i.view as CommentView, children: [], depth: 0 },
|
||||
]}
|
||||
viewType={CommentViewType.Flat}
|
||||
moderators={None}
|
||||
admins={None}
|
||||
maxCommentsShown={None}
|
||||
|
@ -397,7 +400,14 @@ export class Inbox extends Component<any, InboxState> {
|
|||
return (
|
||||
<CommentNodes
|
||||
key={i.id}
|
||||
nodes={[{ comment_view: i.view as PersonMentionView }]}
|
||||
nodes={[
|
||||
{
|
||||
comment_view: i.view as PersonMentionView,
|
||||
children: [],
|
||||
depth: 0,
|
||||
},
|
||||
]}
|
||||
viewType={CommentViewType.Flat}
|
||||
moderators={None}
|
||||
admins={None}
|
||||
maxCommentsShown={None}
|
||||
|
@ -429,6 +439,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
<div>
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(this.state.replies)}
|
||||
viewType={CommentViewType.Flat}
|
||||
moderators={None}
|
||||
admins={None}
|
||||
maxCommentsShown={None}
|
||||
|
@ -448,7 +459,8 @@ export class Inbox extends Component<any, InboxState> {
|
|||
{this.state.mentions.map(umv => (
|
||||
<CommentNodes
|
||||
key={umv.person_mention.id}
|
||||
nodes={[{ comment_view: umv }]}
|
||||
nodes={[{ comment_view: umv, children: [], depth: 0 }]}
|
||||
viewType={CommentViewType.Flat}
|
||||
moderators={None}
|
||||
admins={None}
|
||||
maxCommentsShown={None}
|
||||
|
@ -498,9 +510,11 @@ export class Inbox extends Component<any, InboxState> {
|
|||
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
|
||||
let promises: Promise<any>[] = [];
|
||||
|
||||
let sort = Some(CommentSortType.New);
|
||||
|
||||
// It can be /u/me, or /username/1
|
||||
let repliesForm = new GetReplies({
|
||||
sort: Some(SortType.New),
|
||||
sort,
|
||||
unread_only: Some(true),
|
||||
page: Some(1),
|
||||
limit: Some(fetchLimit),
|
||||
|
@ -509,7 +523,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
promises.push(req.client.getReplies(repliesForm));
|
||||
|
||||
let personMentionsForm = new GetPersonMentions({
|
||||
sort: Some(SortType.New),
|
||||
sort,
|
||||
unread_only: Some(true),
|
||||
page: Some(1),
|
||||
limit: Some(fetchLimit),
|
||||
|
@ -565,7 +579,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
);
|
||||
}
|
||||
|
||||
handleSortChange(val: SortType) {
|
||||
handleSortChange(val: CommentSortType) {
|
||||
this.state.sort = val;
|
||||
this.state.page = 1;
|
||||
this.setState(this.state);
|
||||
|
@ -581,6 +595,7 @@ export class Inbox extends Component<any, InboxState> {
|
|||
i.state.replies = [];
|
||||
i.state.mentions = [];
|
||||
i.state.messages = [];
|
||||
i.state.combined = i.buildCombined();
|
||||
UserService.Instance.unreadInboxCountSub.next(0);
|
||||
window.scrollTo(0, 0);
|
||||
i.setState(i.state);
|
||||
|
@ -716,34 +731,51 @@ export class Inbox extends Component<any, InboxState> {
|
|||
let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
|
||||
editCommentRes(data.comment_view, this.state.replies);
|
||||
this.setState(this.state);
|
||||
} else if (op == UserOperation.MarkCommentAsRead) {
|
||||
let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
|
||||
} else if (op == UserOperation.MarkCommentReplyAsRead) {
|
||||
let data = wsJsonToRes<CommentReplyResponse>(msg, CommentReplyResponse);
|
||||
console.log(data);
|
||||
|
||||
// If youre in the unread view, just remove it from the list
|
||||
if (
|
||||
this.state.unreadOrAll == UnreadOrAll.Unread &&
|
||||
data.comment_view.comment.read
|
||||
) {
|
||||
this.state.replies = this.state.replies.filter(
|
||||
r => r.comment.id !== data.comment_view.comment.id
|
||||
);
|
||||
this.state.combined = this.state.combined.filter(
|
||||
r => r.id !== data.comment_view.comment.id
|
||||
);
|
||||
} else {
|
||||
let found = this.state.replies.find(
|
||||
c => c.comment.id == data.comment_view.comment.id
|
||||
);
|
||||
let found = this.state.replies.find(
|
||||
c => c.comment_reply.id == data.comment_reply_view.comment_reply.id
|
||||
);
|
||||
|
||||
if (found) {
|
||||
let combinedView = this.state.combined.find(
|
||||
i => i.id == data.comment_view.comment.id
|
||||
).view as CommentView;
|
||||
found.comment.read = combinedView.comment.read =
|
||||
data.comment_view.comment.read;
|
||||
}
|
||||
i => i.id == data.comment_reply_view.comment_reply.id
|
||||
).view as CommentReplyView;
|
||||
found.comment.content = combinedView.comment.content =
|
||||
data.comment_reply_view.comment.content;
|
||||
found.comment.updated = combinedView.comment.updated =
|
||||
data.comment_reply_view.comment.updated;
|
||||
found.comment.removed = combinedView.comment.removed =
|
||||
data.comment_reply_view.comment.removed;
|
||||
found.comment.deleted = combinedView.comment.deleted =
|
||||
data.comment_reply_view.comment.deleted;
|
||||
found.counts.upvotes = combinedView.counts.upvotes =
|
||||
data.comment_reply_view.counts.upvotes;
|
||||
found.counts.downvotes = combinedView.counts.downvotes =
|
||||
data.comment_reply_view.counts.downvotes;
|
||||
found.counts.score = combinedView.counts.score =
|
||||
data.comment_reply_view.counts.score;
|
||||
|
||||
this.sendUnreadCount(data.comment_view.comment.read);
|
||||
// If youre in the unread view, just remove it from the list
|
||||
if (
|
||||
this.state.unreadOrAll == UnreadOrAll.Unread &&
|
||||
data.comment_reply_view.comment_reply.read
|
||||
) {
|
||||
this.state.replies = this.state.replies.filter(
|
||||
r => r.comment_reply.id !== data.comment_reply_view.comment_reply.id
|
||||
);
|
||||
this.state.combined = this.state.combined.filter(
|
||||
r => r.id !== data.comment_reply_view.comment_reply.id
|
||||
);
|
||||
} else {
|
||||
found.comment_reply.read = combinedView.comment_reply.read =
|
||||
data.comment_reply_view.comment_reply.read;
|
||||
}
|
||||
}
|
||||
this.sendUnreadCount(data.comment_reply_view.comment_reply.read);
|
||||
this.setState(this.state);
|
||||
setupTippy();
|
||||
} else if (op == UserOperation.MarkPersonMentionAsRead) {
|
||||
let data = wsJsonToRes<PersonMentionResponse>(msg, PersonMentionResponse);
|
||||
|
||||
|
@ -791,71 +823,6 @@ export class Inbox extends Component<any, InboxState> {
|
|||
}
|
||||
this.sendUnreadCount(data.person_mention_view.person_mention.read);
|
||||
this.setState(this.state);
|
||||
} else if (op == UserOperation.CreateComment) {
|
||||
let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
|
||||
|
||||
UserService.Instance.myUserInfo.match({
|
||||
some: mui => {
|
||||
if (data.recipient_ids.includes(mui.local_user_view.local_user.id)) {
|
||||
this.state.replies.unshift(data.comment_view);
|
||||
this.state.combined.unshift(
|
||||
this.replyToReplyType(data.comment_view)
|
||||
);
|
||||
this.setState(this.state);
|
||||
} else if (
|
||||
data.comment_view.creator.id == mui.local_user_view.person.id
|
||||
) {
|
||||
// If youre in the unread view, just remove it from the list
|
||||
if (this.state.unreadOrAll == UnreadOrAll.Unread) {
|
||||
this.state.replies = this.state.replies.filter(
|
||||
r =>
|
||||
r.comment.id !==
|
||||
data.comment_view.comment.parent_id.unwrapOr(0)
|
||||
);
|
||||
this.state.mentions = this.state.mentions.filter(
|
||||
m =>
|
||||
m.comment.id !==
|
||||
data.comment_view.comment.parent_id.unwrapOr(0)
|
||||
);
|
||||
this.state.combined = this.state.combined.filter(r => {
|
||||
if (this.isMention(r.view))
|
||||
return (
|
||||
r.view.comment.id !==
|
||||
data.comment_view.comment.parent_id.unwrapOr(0)
|
||||
);
|
||||
else
|
||||
return (
|
||||
r.id !== data.comment_view.comment.parent_id.unwrapOr(0)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
let mention_found = this.state.mentions.find(
|
||||
i =>
|
||||
i.comment.id ==
|
||||
data.comment_view.comment.parent_id.unwrapOr(0)
|
||||
);
|
||||
if (mention_found) {
|
||||
mention_found.person_mention.read = true;
|
||||
}
|
||||
let reply_found = this.state.replies.find(
|
||||
i =>
|
||||
i.comment.id ==
|
||||
data.comment_view.comment.parent_id.unwrapOr(0)
|
||||
);
|
||||
if (reply_found) {
|
||||
reply_found.comment.read = true;
|
||||
}
|
||||
this.state.combined = this.buildCombined();
|
||||
}
|
||||
this.sendUnreadCount(true);
|
||||
this.setState(this.state);
|
||||
setupTippy();
|
||||
// TODO this seems wrong, you should be using form_id
|
||||
toast(i18n.t("reply_sent"));
|
||||
}
|
||||
},
|
||||
none: void 0,
|
||||
});
|
||||
} else if (op == UserOperation.CreatePrivateMessage) {
|
||||
let data = wsJsonToRes<PrivateMessageResponse>(
|
||||
msg,
|
||||
|
@ -904,4 +871,8 @@ export class Inbox extends Component<any, InboxState> {
|
|||
isMention(view: any): view is PersonMentionView {
|
||||
return (view as PersonMentionView).person_mention !== undefined;
|
||||
}
|
||||
|
||||
isReply(view: any): view is CommentReplyView {
|
||||
return (view as CommentReplyView).comment_reply !== undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
PostView,
|
||||
SortType,
|
||||
} from "lemmy-js-client";
|
||||
import { PersonDetailsView } from "../../interfaces";
|
||||
import { CommentViewType, PersonDetailsView } from "../../interfaces";
|
||||
import { commentsToFlatNodes, setupTippy } from "../../utils";
|
||||
import { CommentNodes } from "../comment/comment-nodes";
|
||||
import { Paginator } from "../common/paginator";
|
||||
|
@ -89,7 +89,8 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
|||
return (
|
||||
<CommentNodes
|
||||
key={i.id}
|
||||
nodes={[{ comment_view: c }]}
|
||||
nodes={[{ comment_view: c, children: [], depth: 0 }]}
|
||||
viewType={CommentViewType.Flat}
|
||||
admins={Some(this.props.admins)}
|
||||
moderators={None}
|
||||
maxCommentsShown={None}
|
||||
|
@ -159,6 +160,7 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
|
|||
<div>
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(this.props.personRes.comments)}
|
||||
viewType={CommentViewType.Flat}
|
||||
admins={Some(this.props.admins)}
|
||||
moderators={None}
|
||||
maxCommentsShown={None}
|
||||
|
|
|
@ -408,7 +408,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
className={`btn-animate btn btn-link p-0 ${
|
||||
this.state.my_vote.unwrapOr(0) == 1 ? "text-info" : "text-muted"
|
||||
}`}
|
||||
onClick={linkEvent(this, this.handlePostLike)}
|
||||
onClick={this.handlePostLike}
|
||||
data-tippy-content={i18n.t("upvote")}
|
||||
aria-label={i18n.t("upvote")}
|
||||
>
|
||||
|
@ -431,7 +431,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
? "text-danger"
|
||||
: "text-muted"
|
||||
}`}
|
||||
onClick={linkEvent(this, this.handlePostDisLike)}
|
||||
onClick={this.handlePostDisLike}
|
||||
data-tippy-content={i18n.t("downvote")}
|
||||
aria-label={i18n.t("downvote")}
|
||||
>
|
||||
|
@ -647,7 +647,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
this.state.my_vote.unwrapOr(0) == 1 ? "text-info" : "text-muted"
|
||||
}`}
|
||||
{...tippy}
|
||||
onClick={linkEvent(this, this.handlePostLike)}
|
||||
onClick={this.handlePostLike}
|
||||
aria-label={i18n.t("upvote")}
|
||||
>
|
||||
<Icon icon="arrow-up1" classes="icon-inline small" />
|
||||
|
@ -662,7 +662,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
? "text-danger"
|
||||
: "text-muted"
|
||||
}`}
|
||||
onClick={linkEvent(this, this.handlePostDisLike)}
|
||||
onClick={this.handlePostDisLike}
|
||||
{...tippy}
|
||||
aria-label={i18n.t("downvote")}
|
||||
>
|
||||
|
@ -1250,7 +1250,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
});
|
||||
}
|
||||
|
||||
handlePostLike(i: PostListing, event: any) {
|
||||
handlePostLike(event: any) {
|
||||
event.preventDefault();
|
||||
if (UserService.Instance.myUserInfo.isNone()) {
|
||||
this.context.router.history.push(`/login`);
|
||||
|
@ -1260,31 +1260,31 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
let newVote = myVote == 1 ? 0 : 1;
|
||||
|
||||
if (myVote == 1) {
|
||||
i.state.score--;
|
||||
i.state.upvotes--;
|
||||
this.state.score--;
|
||||
this.state.upvotes--;
|
||||
} else if (myVote == -1) {
|
||||
i.state.downvotes--;
|
||||
i.state.upvotes++;
|
||||
i.state.score += 2;
|
||||
this.state.downvotes--;
|
||||
this.state.upvotes++;
|
||||
this.state.score += 2;
|
||||
} else {
|
||||
i.state.upvotes++;
|
||||
i.state.score++;
|
||||
this.state.upvotes++;
|
||||
this.state.score++;
|
||||
}
|
||||
|
||||
i.state.my_vote = Some(newVote);
|
||||
this.state.my_vote = Some(newVote);
|
||||
|
||||
let form = new CreatePostLike({
|
||||
post_id: i.props.post_view.post.id,
|
||||
post_id: this.props.post_view.post.id,
|
||||
score: newVote,
|
||||
auth: auth().unwrap(),
|
||||
});
|
||||
|
||||
WebSocketService.Instance.send(wsClient.likePost(form));
|
||||
i.setState(i.state);
|
||||
this.setState(this.state);
|
||||
setupTippy();
|
||||
}
|
||||
|
||||
handlePostDisLike(i: PostListing, event: any) {
|
||||
handlePostDisLike(event: any) {
|
||||
event.preventDefault();
|
||||
if (UserService.Instance.myUserInfo.isNone()) {
|
||||
this.context.router.history.push(`/login`);
|
||||
|
@ -1294,27 +1294,27 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
let newVote = myVote == -1 ? 0 : -1;
|
||||
|
||||
if (myVote == 1) {
|
||||
i.state.score -= 2;
|
||||
i.state.upvotes--;
|
||||
i.state.downvotes++;
|
||||
this.state.score -= 2;
|
||||
this.state.upvotes--;
|
||||
this.state.downvotes++;
|
||||
} else if (myVote == -1) {
|
||||
i.state.downvotes--;
|
||||
i.state.score++;
|
||||
this.state.downvotes--;
|
||||
this.state.score++;
|
||||
} else {
|
||||
i.state.downvotes++;
|
||||
i.state.score--;
|
||||
this.state.downvotes++;
|
||||
this.state.score--;
|
||||
}
|
||||
|
||||
i.state.my_vote = Some(newVote);
|
||||
this.state.my_vote = Some(newVote);
|
||||
|
||||
let form = new CreatePostLike({
|
||||
post_id: i.props.post_view.post.id,
|
||||
post_id: this.props.post_view.post.id,
|
||||
score: newVote,
|
||||
auth: auth().unwrap(),
|
||||
});
|
||||
|
||||
WebSocketService.Instance.send(wsClient.likePost(form));
|
||||
i.setState(i.state);
|
||||
this.setState(this.state);
|
||||
setupTippy();
|
||||
}
|
||||
|
||||
|
|
|
@ -7,15 +7,18 @@ import {
|
|||
BanFromCommunityResponse,
|
||||
BanPersonResponse,
|
||||
BlockPersonResponse,
|
||||
CommentNode as CommentNodeI,
|
||||
CommentReportResponse,
|
||||
CommentResponse,
|
||||
CommentSortType,
|
||||
CommunityResponse,
|
||||
GetComments,
|
||||
GetCommentsResponse,
|
||||
GetCommunityResponse,
|
||||
GetPost,
|
||||
GetPostResponse,
|
||||
GetSiteResponse,
|
||||
ListingType,
|
||||
MarkCommentAsRead,
|
||||
PostReportResponse,
|
||||
PostResponse,
|
||||
PostView,
|
||||
|
@ -30,17 +33,13 @@ import {
|
|||
} from "lemmy-js-client";
|
||||
import { Subscription } from "rxjs";
|
||||
import { i18n } from "../../i18next";
|
||||
import {
|
||||
CommentNode as CommentNodeI,
|
||||
CommentSortType,
|
||||
CommentViewType,
|
||||
InitialFetchRequest,
|
||||
} from "../../interfaces";
|
||||
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
auth,
|
||||
buildCommentsTree,
|
||||
commentsToFlatNodes,
|
||||
commentTreeMaxDepth,
|
||||
createCommentLikeRes,
|
||||
createPostLikeRes,
|
||||
debounce,
|
||||
|
@ -48,6 +47,8 @@ import {
|
|||
enableDownvotes,
|
||||
enableNsfw,
|
||||
getCommentIdFromProps,
|
||||
getCommentParentId,
|
||||
getDepthFromComment,
|
||||
getIdFromProps,
|
||||
insertCommentIntoTree,
|
||||
isBrowser,
|
||||
|
@ -73,10 +74,11 @@ import { PostListing } from "./post-listing";
|
|||
const commentsShownInterval = 15;
|
||||
|
||||
interface PostState {
|
||||
postId: Option<number>;
|
||||
commentId: Option<number>;
|
||||
postRes: Option<GetPostResponse>;
|
||||
postId: number;
|
||||
commentsRes: Option<GetCommentsResponse>;
|
||||
commentTree: CommentNodeI[];
|
||||
commentId?: number;
|
||||
commentSort: CommentSortType;
|
||||
commentViewType: CommentViewType;
|
||||
scrolled?: boolean;
|
||||
|
@ -90,14 +92,19 @@ interface PostState {
|
|||
|
||||
export class Post extends Component<any, PostState> {
|
||||
private subscription: Subscription;
|
||||
private isoData = setIsoData(this.context, GetPostResponse);
|
||||
private isoData = setIsoData(
|
||||
this.context,
|
||||
GetPostResponse,
|
||||
GetCommentsResponse
|
||||
);
|
||||
private commentScrollDebounced: () => void;
|
||||
private emptyState: PostState = {
|
||||
postRes: None,
|
||||
commentsRes: None,
|
||||
postId: getIdFromProps(this.props),
|
||||
commentTree: [],
|
||||
commentId: getCommentIdFromProps(this.props),
|
||||
commentSort: CommentSortType.Hot,
|
||||
commentTree: [],
|
||||
commentSort: CommentSortType[CommentSortType.Hot],
|
||||
commentViewType: CommentViewType.Tree,
|
||||
scrolled: false,
|
||||
loading: true,
|
||||
|
@ -120,10 +127,19 @@ export class Post extends Component<any, PostState> {
|
|||
// Only fetch the data if coming from another route
|
||||
if (this.isoData.path == this.context.router.route.match.url) {
|
||||
this.state.postRes = Some(this.isoData.routeData[0] as GetPostResponse);
|
||||
this.state.commentTree = buildCommentsTree(
|
||||
this.state.postRes.unwrap().comments,
|
||||
this.state.commentSort
|
||||
this.state.commentsRes = Some(
|
||||
this.isoData.routeData[1] as GetCommentsResponse
|
||||
);
|
||||
|
||||
this.state.commentsRes.match({
|
||||
some: res => {
|
||||
this.state.commentTree = buildCommentsTree(
|
||||
res.comments,
|
||||
this.state.commentId.isSome()
|
||||
);
|
||||
},
|
||||
none: void 0,
|
||||
});
|
||||
this.state.loading = false;
|
||||
|
||||
if (isBrowser()) {
|
||||
|
@ -133,14 +149,14 @@ export class Post extends Component<any, PostState> {
|
|||
this.state.postRes.unwrap().community_view.community.id,
|
||||
})
|
||||
);
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.postJoin({ post_id: this.state.postId })
|
||||
);
|
||||
|
||||
this.state.postId.match({
|
||||
some: post_id =>
|
||||
WebSocketService.Instance.send(wsClient.postJoin({ post_id })),
|
||||
none: void 0,
|
||||
});
|
||||
|
||||
this.fetchCrossPosts();
|
||||
if (this.state.commentId) {
|
||||
this.scrollCommentIntoView();
|
||||
}
|
||||
|
||||
if (this.checkScrollIntoCommentsParam) {
|
||||
this.scrollIntoCommentSection();
|
||||
|
@ -152,11 +168,28 @@ export class Post extends Component<any, PostState> {
|
|||
}
|
||||
|
||||
fetchPost() {
|
||||
let form = new GetPost({
|
||||
this.setState({ commentsRes: None });
|
||||
let postForm = new GetPost({
|
||||
id: this.state.postId,
|
||||
comment_id: this.state.commentId,
|
||||
auth: auth(false).ok(),
|
||||
});
|
||||
WebSocketService.Instance.send(wsClient.getPost(form));
|
||||
WebSocketService.Instance.send(wsClient.getPost(postForm));
|
||||
|
||||
let commentsForm = new GetComments({
|
||||
post_id: this.state.postId,
|
||||
parent_id: this.state.commentId,
|
||||
max_depth: Some(commentTreeMaxDepth),
|
||||
page: None,
|
||||
limit: None,
|
||||
sort: Some(this.state.commentSort),
|
||||
type_: Some(ListingType.All),
|
||||
community_name: None,
|
||||
community_id: None,
|
||||
saved_only: Some(false),
|
||||
auth: auth(false).ok(),
|
||||
});
|
||||
WebSocketService.Instance.send(wsClient.getComments(commentsForm));
|
||||
}
|
||||
|
||||
fetchCrossPosts() {
|
||||
|
@ -184,15 +217,44 @@ export class Post extends Component<any, PostState> {
|
|||
|
||||
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
|
||||
let pathSplit = req.path.split("/");
|
||||
let promises: Promise<any>[] = [];
|
||||
|
||||
let pathType = pathSplit[1];
|
||||
let id = Number(pathSplit[2]);
|
||||
|
||||
let postForm = new GetPost({
|
||||
id,
|
||||
id: None,
|
||||
comment_id: None,
|
||||
auth: req.auth,
|
||||
});
|
||||
|
||||
return [req.client.getPost(postForm)];
|
||||
let commentsForm = new GetComments({
|
||||
post_id: None,
|
||||
parent_id: None,
|
||||
max_depth: Some(commentTreeMaxDepth),
|
||||
page: None,
|
||||
limit: None,
|
||||
sort: Some(CommentSortType.Hot),
|
||||
type_: Some(ListingType.All),
|
||||
community_name: None,
|
||||
community_id: None,
|
||||
saved_only: Some(false),
|
||||
auth: req.auth,
|
||||
});
|
||||
|
||||
// Set the correct id based on the path type
|
||||
if (pathType == "post") {
|
||||
postForm.id = Some(id);
|
||||
commentsForm.post_id = Some(id);
|
||||
} else {
|
||||
postForm.comment_id = Some(id);
|
||||
commentsForm.parent_id = Some(id);
|
||||
}
|
||||
|
||||
promises.push(req.client.getPost(postForm));
|
||||
promises.push(req.client.getComments(commentsForm));
|
||||
|
||||
return promises;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -222,18 +284,6 @@ export class Post extends Component<any, PostState> {
|
|||
}
|
||||
}
|
||||
|
||||
scrollCommentIntoView() {
|
||||
let commentElement = document.getElementById(
|
||||
`comment-${this.state.commentId}`
|
||||
);
|
||||
if (commentElement) {
|
||||
commentElement.scrollIntoView();
|
||||
commentElement.classList.add("mark");
|
||||
this.state.scrolled = true;
|
||||
this.markScrolledAsRead(this.state.commentId);
|
||||
}
|
||||
}
|
||||
|
||||
get checkScrollIntoCommentsParam() {
|
||||
return Boolean(
|
||||
new URLSearchParams(this.props.location.search).get("scrollToComments")
|
||||
|
@ -244,39 +294,6 @@ export class Post extends Component<any, PostState> {
|
|||
this.state.commentSectionRef.current?.scrollIntoView();
|
||||
}
|
||||
|
||||
// TODO this needs some re-work
|
||||
markScrolledAsRead(commentId: number) {
|
||||
this.state.postRes.match({
|
||||
some: res => {
|
||||
let found = res.comments.find(c => c.comment.id == commentId);
|
||||
let parent = res.comments.find(
|
||||
c => found.comment.parent_id.unwrapOr(0) == c.comment.id
|
||||
);
|
||||
let parent_person_id = parent
|
||||
? parent.creator.id
|
||||
: res.post_view.creator.id;
|
||||
|
||||
UserService.Instance.myUserInfo.match({
|
||||
some: mui => {
|
||||
if (mui.local_user_view.person.id == parent_person_id) {
|
||||
let form = new MarkCommentAsRead({
|
||||
comment_id: found.comment.id,
|
||||
read: true,
|
||||
auth: auth().unwrap(),
|
||||
});
|
||||
WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
|
||||
UserService.Instance.unreadInboxCountSub.next(
|
||||
UserService.Instance.unreadInboxCountSub.value - 1
|
||||
);
|
||||
}
|
||||
},
|
||||
none: void 0,
|
||||
});
|
||||
},
|
||||
none: void 0,
|
||||
});
|
||||
}
|
||||
|
||||
isBottom(el: Element): boolean {
|
||||
return el?.getBoundingClientRect().bottom <= window.innerHeight;
|
||||
}
|
||||
|
@ -351,7 +368,7 @@ export class Post extends Component<any, PostState> {
|
|||
/>
|
||||
<div ref={this.state.commentSectionRef} className="mb-2" />
|
||||
<CommentForm
|
||||
node={Right(this.state.postId)}
|
||||
node={Right(res.post_view.post.id)}
|
||||
disabled={res.post_view.post.locked}
|
||||
/>
|
||||
<div class="d-block d-md-none">
|
||||
|
@ -371,10 +388,10 @@ export class Post extends Component<any, PostState> {
|
|||
</button>
|
||||
{this.state.showSidebarMobile && this.sidebar()}
|
||||
</div>
|
||||
{res.comments.length > 0 && this.sortRadios()}
|
||||
{this.sortRadios()}
|
||||
{this.state.commentViewType == CommentViewType.Tree &&
|
||||
this.commentsTree()}
|
||||
{this.state.commentViewType == CommentViewType.Chat &&
|
||||
{this.state.commentViewType == CommentViewType.Flat &&
|
||||
this.commentsFlat()}
|
||||
</div>
|
||||
<div class="d-none d-md-block col-md-4">{this.sidebar()}</div>
|
||||
|
@ -393,7 +410,8 @@ export class Post extends Component<any, PostState> {
|
|||
<div class="btn-group btn-group-toggle flex-wrap mr-3 mb-2">
|
||||
<label
|
||||
className={`btn btn-outline-secondary pointer ${
|
||||
this.state.commentSort === CommentSortType.Hot && "active"
|
||||
CommentSortType[this.state.commentSort] === CommentSortType.Hot &&
|
||||
"active"
|
||||
}`}
|
||||
>
|
||||
{i18n.t("hot")}
|
||||
|
@ -406,7 +424,8 @@ export class Post extends Component<any, PostState> {
|
|||
</label>
|
||||
<label
|
||||
className={`btn btn-outline-secondary pointer ${
|
||||
this.state.commentSort === CommentSortType.Top && "active"
|
||||
CommentSortType[this.state.commentSort] === CommentSortType.Top &&
|
||||
"active"
|
||||
}`}
|
||||
>
|
||||
{i18n.t("top")}
|
||||
|
@ -419,7 +438,8 @@ export class Post extends Component<any, PostState> {
|
|||
</label>
|
||||
<label
|
||||
className={`btn btn-outline-secondary pointer ${
|
||||
this.state.commentSort === CommentSortType.New && "active"
|
||||
CommentSortType[this.state.commentSort] === CommentSortType.New &&
|
||||
"active"
|
||||
}`}
|
||||
>
|
||||
{i18n.t("new")}
|
||||
|
@ -432,7 +452,8 @@ export class Post extends Component<any, PostState> {
|
|||
</label>
|
||||
<label
|
||||
className={`btn btn-outline-secondary pointer ${
|
||||
this.state.commentSort === CommentSortType.Old && "active"
|
||||
CommentSortType[this.state.commentSort] === CommentSortType.Old &&
|
||||
"active"
|
||||
}`}
|
||||
>
|
||||
{i18n.t("old")}
|
||||
|
@ -447,14 +468,14 @@ export class Post extends Component<any, PostState> {
|
|||
<div class="btn-group btn-group-toggle flex-wrap mb-2">
|
||||
<label
|
||||
className={`btn btn-outline-secondary pointer ${
|
||||
this.state.commentViewType === CommentViewType.Chat && "active"
|
||||
this.state.commentViewType === CommentViewType.Flat && "active"
|
||||
}`}
|
||||
>
|
||||
{i18n.t("chat")}
|
||||
<input
|
||||
type="radio"
|
||||
value={CommentViewType.Chat}
|
||||
checked={this.state.commentViewType === CommentViewType.Chat}
|
||||
value={CommentViewType.Flat}
|
||||
checked={this.state.commentViewType === CommentViewType.Flat}
|
||||
onChange={linkEvent(this, this.handleCommentViewTypeChange)}
|
||||
/>
|
||||
</label>
|
||||
|
@ -465,21 +486,26 @@ export class Post extends Component<any, PostState> {
|
|||
|
||||
commentsFlat() {
|
||||
// These are already sorted by new
|
||||
return this.state.postRes.match({
|
||||
some: res => (
|
||||
<div>
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(res.comments)}
|
||||
maxCommentsShown={Some(this.state.maxCommentsShown)}
|
||||
noIndent
|
||||
locked={res.post_view.post.locked}
|
||||
moderators={Some(res.moderators)}
|
||||
admins={Some(this.state.siteRes.admins)}
|
||||
enableDownvotes={enableDownvotes(this.state.siteRes)}
|
||||
showContext
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
return this.state.commentsRes.match({
|
||||
some: commentsRes =>
|
||||
this.state.postRes.match({
|
||||
some: postRes => (
|
||||
<div>
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(commentsRes.comments)}
|
||||
viewType={this.state.commentViewType}
|
||||
maxCommentsShown={Some(this.state.maxCommentsShown)}
|
||||
noIndent
|
||||
locked={postRes.post_view.post.locked}
|
||||
moderators={Some(postRes.moderators)}
|
||||
admins={Some(this.state.siteRes.admins)}
|
||||
enableDownvotes={enableDownvotes(this.state.siteRes)}
|
||||
showContext
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
none: <></>,
|
||||
}),
|
||||
none: <></>,
|
||||
});
|
||||
}
|
||||
|
@ -503,21 +529,18 @@ export class Post extends Component<any, PostState> {
|
|||
}
|
||||
|
||||
handleCommentSortChange(i: Post, event: any) {
|
||||
i.state.commentSort = Number(event.target.value);
|
||||
i.state.commentSort = CommentSortType[event.target.value];
|
||||
i.state.commentViewType = CommentViewType.Tree;
|
||||
i.state.commentTree = buildCommentsTree(
|
||||
i.state.postRes.map(r => r.comments).unwrapOr([]),
|
||||
i.state.commentSort
|
||||
);
|
||||
i.setState(i.state);
|
||||
i.fetchPost();
|
||||
}
|
||||
|
||||
handleCommentViewTypeChange(i: Post, event: any) {
|
||||
i.state.commentViewType = Number(event.target.value);
|
||||
i.state.commentSort = CommentSortType.New;
|
||||
i.state.commentTree = buildCommentsTree(
|
||||
i.state.postRes.map(r => r.comments).unwrapOr([]),
|
||||
i.state.commentSort
|
||||
i.state.commentsRes.map(r => r.comments).unwrapOr([]),
|
||||
i.state.commentId.isSome()
|
||||
);
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
@ -527,12 +550,52 @@ export class Post extends Component<any, PostState> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleViewPost(i: Post) {
|
||||
i.state.postRes.match({
|
||||
some: res =>
|
||||
i.context.router.history.push(`/post/${res.post_view.post.id}`),
|
||||
none: void 0,
|
||||
});
|
||||
}
|
||||
|
||||
handleViewContext(i: Post) {
|
||||
i.state.commentsRes.match({
|
||||
some: res =>
|
||||
i.context.router.history.push(
|
||||
`/comment/${getCommentParentId(res.comments[0].comment).unwrap()}`
|
||||
),
|
||||
none: void 0,
|
||||
});
|
||||
}
|
||||
|
||||
commentsTree() {
|
||||
let showContextButton =
|
||||
getDepthFromComment(this.state.commentTree[0].comment_view.comment) > 0;
|
||||
|
||||
return this.state.postRes.match({
|
||||
some: res => (
|
||||
<div>
|
||||
{this.state.commentId.isSome() && (
|
||||
<>
|
||||
<button
|
||||
class="pl-0 d-block btn btn-link text-muted"
|
||||
onClick={linkEvent(this, this.handleViewPost)}
|
||||
>
|
||||
{i18n.t("view_all_comments")} ➔
|
||||
</button>
|
||||
{showContextButton && (
|
||||
<button
|
||||
class="pl-0 d-block btn btn-link text-muted"
|
||||
onClick={linkEvent(this, this.handleViewContext)}
|
||||
>
|
||||
{i18n.t("show_context")} ➔
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<CommentNodes
|
||||
nodes={this.state.commentTree}
|
||||
viewType={this.state.commentViewType}
|
||||
maxCommentsShown={Some(this.state.maxCommentsShown)}
|
||||
locked={res.post_view.post.locked}
|
||||
moderators={Some(res.moderators)}
|
||||
|
@ -552,27 +615,29 @@ export class Post extends Component<any, PostState> {
|
|||
toast(i18n.t(msg.error), "danger");
|
||||
return;
|
||||
} else if (msg.reconnect) {
|
||||
let postId = Number(this.props.match.params.id);
|
||||
WebSocketService.Instance.send(wsClient.postJoin({ post_id: postId }));
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.getPost({
|
||||
id: postId,
|
||||
auth: auth(false).ok(),
|
||||
})
|
||||
);
|
||||
this.state.postRes.match({
|
||||
some: res => {
|
||||
let postId = res.post_view.post.id;
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.postJoin({ post_id: postId })
|
||||
);
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.getPost({
|
||||
id: Some(postId),
|
||||
comment_id: None,
|
||||
auth: auth(false).ok(),
|
||||
})
|
||||
);
|
||||
},
|
||||
none: void 0,
|
||||
});
|
||||
} else if (op == UserOperation.GetPost) {
|
||||
let data = wsJsonToRes<GetPostResponse>(msg, GetPostResponse);
|
||||
this.state.postRes = Some(data);
|
||||
|
||||
this.state.commentTree = buildCommentsTree(
|
||||
this.state.postRes.map(r => r.comments).unwrapOr([]),
|
||||
this.state.commentSort
|
||||
);
|
||||
this.state.loading = false;
|
||||
|
||||
// join the rooms
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.postJoin({ post_id: this.state.postId })
|
||||
wsClient.postJoin({ post_id: data.post_view.post.id })
|
||||
);
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.communityJoin({
|
||||
|
@ -581,18 +646,36 @@ export class Post extends Component<any, PostState> {
|
|||
);
|
||||
|
||||
// Get cross-posts
|
||||
// TODO move this into initial fetch and refetch
|
||||
this.fetchCrossPosts();
|
||||
this.setState(this.state);
|
||||
setupTippy();
|
||||
if (!this.state.commentId) restoreScrollPosition(this.context);
|
||||
if (this.state.commentId.isNone()) restoreScrollPosition(this.context);
|
||||
|
||||
if (this.checkScrollIntoCommentsParam) {
|
||||
this.scrollIntoCommentSection();
|
||||
}
|
||||
|
||||
if (this.state.commentId && !this.state.scrolled) {
|
||||
this.scrollCommentIntoView();
|
||||
}
|
||||
} else if (op == UserOperation.GetComments) {
|
||||
let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse);
|
||||
// You might need to append here, since this could be building more comments from a tree fetch
|
||||
this.state.commentsRes.match({
|
||||
some: res => {
|
||||
// Remove the first comment, since it is the parent
|
||||
let newComments = data.comments;
|
||||
newComments.shift();
|
||||
res.comments.push(...newComments);
|
||||
},
|
||||
none: () => {
|
||||
this.state.commentsRes = Some(data);
|
||||
},
|
||||
});
|
||||
// this.state.commentsRes = Some(data);
|
||||
this.state.commentTree = buildCommentsTree(
|
||||
this.state.commentsRes.map(r => r.comments).unwrapOr([]),
|
||||
this.state.commentId.isSome()
|
||||
);
|
||||
this.state.loading = false;
|
||||
this.setState(this.state);
|
||||
} else if (op == UserOperation.CreateComment) {
|
||||
let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
|
||||
|
||||
|
@ -606,11 +689,19 @@ export class Post extends Component<any, PostState> {
|
|||
// Necessary since it might be a user reply, which has the recipients, to avoid double
|
||||
if (data.recipient_ids.length == 0 && !creatorBlocked) {
|
||||
this.state.postRes.match({
|
||||
some: res => {
|
||||
res.comments.unshift(data.comment_view);
|
||||
insertCommentIntoTree(this.state.commentTree, data.comment_view);
|
||||
res.post_view.counts.comments++;
|
||||
},
|
||||
some: postRes =>
|
||||
this.state.commentsRes.match({
|
||||
some: commentsRes => {
|
||||
commentsRes.comments.unshift(data.comment_view);
|
||||
insertCommentIntoTree(
|
||||
this.state.commentTree,
|
||||
data.comment_view,
|
||||
this.state.commentId.isSome()
|
||||
);
|
||||
postRes.post_view.counts.comments++;
|
||||
},
|
||||
none: void 0,
|
||||
}),
|
||||
none: void 0,
|
||||
});
|
||||
this.setState(this.state);
|
||||
|
@ -624,14 +715,14 @@ export class Post extends Component<any, PostState> {
|
|||
let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
|
||||
editCommentRes(
|
||||
data.comment_view,
|
||||
this.state.postRes.map(r => r.comments).unwrapOr([])
|
||||
this.state.commentsRes.map(r => r.comments).unwrapOr([])
|
||||
);
|
||||
this.setState(this.state);
|
||||
} else if (op == UserOperation.SaveComment) {
|
||||
let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
|
||||
saveCommentRes(
|
||||
data.comment_view,
|
||||
this.state.postRes.map(r => r.comments).unwrapOr([])
|
||||
this.state.commentsRes.map(r => r.comments).unwrapOr([])
|
||||
);
|
||||
this.setState(this.state);
|
||||
setupTippy();
|
||||
|
@ -639,7 +730,7 @@ export class Post extends Component<any, PostState> {
|
|||
let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
|
||||
createCommentLikeRes(
|
||||
data.comment_view,
|
||||
this.state.postRes.map(r => r.comments).unwrapOr([])
|
||||
this.state.commentsRes.map(r => r.comments).unwrapOr([])
|
||||
);
|
||||
this.setState(this.state);
|
||||
} else if (op == UserOperation.CreatePostLike) {
|
||||
|
@ -685,15 +776,19 @@ export class Post extends Component<any, PostState> {
|
|||
BanFromCommunityResponse
|
||||
);
|
||||
this.state.postRes.match({
|
||||
some: res => {
|
||||
res.comments
|
||||
.filter(c => c.creator.id == data.person_view.person.id)
|
||||
.forEach(c => (c.creator_banned_from_community = data.banned));
|
||||
if (res.post_view.creator.id == data.person_view.person.id) {
|
||||
res.post_view.creator_banned_from_community = data.banned;
|
||||
}
|
||||
this.setState(this.state);
|
||||
},
|
||||
some: postRes =>
|
||||
this.state.commentsRes.match({
|
||||
some: commentsRes => {
|
||||
commentsRes.comments
|
||||
.filter(c => c.creator.id == data.person_view.person.id)
|
||||
.forEach(c => (c.creator_banned_from_community = data.banned));
|
||||
if (postRes.post_view.creator.id == data.person_view.person.id) {
|
||||
postRes.post_view.creator_banned_from_community = data.banned;
|
||||
}
|
||||
this.setState(this.state);
|
||||
},
|
||||
none: void 0,
|
||||
}),
|
||||
none: void 0,
|
||||
});
|
||||
} else if (op == UserOperation.AddModToCommunity) {
|
||||
|
@ -711,15 +806,19 @@ export class Post extends Component<any, PostState> {
|
|||
} else if (op == UserOperation.BanPerson) {
|
||||
let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse);
|
||||
this.state.postRes.match({
|
||||
some: res => {
|
||||
res.comments
|
||||
.filter(c => c.creator.id == data.person_view.person.id)
|
||||
.forEach(c => (c.creator.banned = data.banned));
|
||||
if (res.post_view.creator.id == data.person_view.person.id) {
|
||||
res.post_view.creator.banned = data.banned;
|
||||
}
|
||||
this.setState(this.state);
|
||||
},
|
||||
some: postRes =>
|
||||
this.state.commentsRes.match({
|
||||
some: commentsRes => {
|
||||
commentsRes.comments
|
||||
.filter(c => c.creator.id == data.person_view.person.id)
|
||||
.forEach(c => (c.creator.banned = data.banned));
|
||||
if (postRes.post_view.creator.id == data.person_view.person.id) {
|
||||
postRes.post_view.creator.banned = data.banned;
|
||||
}
|
||||
this.setState(this.state);
|
||||
},
|
||||
none: void 0,
|
||||
}),
|
||||
none: void 0,
|
||||
});
|
||||
} else if (op == UserOperation.AddAdmin) {
|
||||
|
|
|
@ -26,8 +26,8 @@ import {
|
|||
wsUserOp,
|
||||
} from "lemmy-js-client";
|
||||
import { Subscription } from "rxjs";
|
||||
import { InitialFetchRequest } from "shared/interfaces";
|
||||
import { i18n } from "../i18next";
|
||||
import { CommentViewType, InitialFetchRequest } from "../interfaces";
|
||||
import { WebSocketService } from "../services";
|
||||
import {
|
||||
auth,
|
||||
|
@ -594,7 +594,14 @@ export class Search extends Component<any, SearchState> {
|
|||
{i.type_ == "comments" && (
|
||||
<CommentNodes
|
||||
key={(i.data as CommentView).comment.id}
|
||||
nodes={[{ comment_view: i.data as CommentView }]}
|
||||
nodes={[
|
||||
{
|
||||
comment_view: i.data as CommentView,
|
||||
children: [],
|
||||
depth: 0,
|
||||
},
|
||||
]}
|
||||
viewType={CommentViewType.Flat}
|
||||
moderators={None}
|
||||
admins={None}
|
||||
maxCommentsShown={None}
|
||||
|
@ -631,6 +638,7 @@ export class Search extends Component<any, SearchState> {
|
|||
return (
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(comments)}
|
||||
viewType={CommentViewType.Flat}
|
||||
locked
|
||||
noIndent
|
||||
moderators={None}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import { Either, Option } from "@sniptt/monads";
|
||||
import {
|
||||
CommentView,
|
||||
GetSiteResponse,
|
||||
LemmyHttp,
|
||||
PersonMentionView,
|
||||
} from "lemmy-js-client";
|
||||
import { GetSiteResponse, LemmyHttp } from "lemmy-js-client";
|
||||
|
||||
/**
|
||||
* This contains serialized data, it needs to be deserialized before use.
|
||||
|
@ -32,12 +27,6 @@ export interface InitialFetchRequest {
|
|||
path: string;
|
||||
}
|
||||
|
||||
export interface CommentNode {
|
||||
comment_view: CommentView | PersonMentionView;
|
||||
children?: CommentNode[];
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
export interface PostFormParams {
|
||||
name: Option<string>;
|
||||
url: Option<string>;
|
||||
|
@ -45,16 +34,9 @@ export interface PostFormParams {
|
|||
nameOrId: Option<Either<string, number>>;
|
||||
}
|
||||
|
||||
export enum CommentSortType {
|
||||
Hot,
|
||||
Top,
|
||||
New,
|
||||
Old,
|
||||
}
|
||||
|
||||
export enum CommentViewType {
|
||||
Tree,
|
||||
Chat,
|
||||
Flat,
|
||||
}
|
||||
|
||||
export enum DataType {
|
||||
|
|
|
@ -72,12 +72,12 @@ export const routes: IRoutePropsWithFetch[] = [
|
|||
fetchInitialData: req => Communities.fetchInitialData(req),
|
||||
},
|
||||
{
|
||||
path: `/post/:id/comment/:comment_id`,
|
||||
path: `/post/:post_id`,
|
||||
component: Post,
|
||||
fetchInitialData: req => Post.fetchInitialData(req),
|
||||
},
|
||||
{
|
||||
path: `/post/:id`,
|
||||
path: `/comment/:comment_id`,
|
||||
component: Post,
|
||||
fetchInitialData: req => Post.fetchInitialData(req),
|
||||
},
|
||||
|
|
|
@ -4,7 +4,10 @@ import emojiShortName from "emoji-short-name";
|
|||
import {
|
||||
BlockCommunityResponse,
|
||||
BlockPersonResponse,
|
||||
Comment as CommentI,
|
||||
CommentNode as CommentNodeI,
|
||||
CommentReportView,
|
||||
CommentSortType,
|
||||
CommentView,
|
||||
CommunityBlockView,
|
||||
CommunityModeratorView,
|
||||
|
@ -39,12 +42,7 @@ import tippy from "tippy.js";
|
|||
import Toastify from "toastify-js";
|
||||
import { httpBase } from "./env";
|
||||
import { i18n, languages } from "./i18next";
|
||||
import {
|
||||
CommentNode as CommentNodeI,
|
||||
CommentSortType,
|
||||
DataType,
|
||||
IsoData,
|
||||
} from "./interfaces";
|
||||
import { DataType, IsoData } from "./interfaces";
|
||||
import { UserService, WebSocketService } from "./services";
|
||||
|
||||
var Tribute: any;
|
||||
|
@ -74,6 +72,7 @@ export const postRefetchSeconds: number = 60 * 1000;
|
|||
export const fetchLimit = 20;
|
||||
export const trendingFetchLimit = 6;
|
||||
export const mentionDropdownFetchLimit = 10;
|
||||
export const commentTreeMaxDepth = 8;
|
||||
|
||||
export const relTags = "noopener nofollow";
|
||||
|
||||
|
@ -611,7 +610,7 @@ export function notifyComment(comment_view: CommentView, router: any) {
|
|||
let info: NotifyInfo = {
|
||||
name: comment_view.creator.name,
|
||||
icon: comment_view.creator.avatar,
|
||||
link: `/post/${comment_view.post.id}/comment/${comment_view.comment.id}`,
|
||||
link: `/comment/${comment_view.comment.id}`,
|
||||
body: comment_view.comment.content,
|
||||
};
|
||||
notify(info, router);
|
||||
|
@ -813,12 +812,14 @@ export function getRecipientIdFromProps(props: any): number {
|
|||
: 1;
|
||||
}
|
||||
|
||||
export function getIdFromProps(props: any): number {
|
||||
return Number(props.match.params.id);
|
||||
export function getIdFromProps(props: any): Option<number> {
|
||||
let id: string = props.match.params.post_id;
|
||||
return id ? Some(Number(id)) : None;
|
||||
}
|
||||
|
||||
export function getCommentIdFromProps(props: any): number {
|
||||
return Number(props.match.params.comment_id);
|
||||
export function getCommentIdFromProps(props: any): Option<number> {
|
||||
let id: string = props.match.params.comment_id;
|
||||
return id ? Some(Number(id)) : None;
|
||||
}
|
||||
|
||||
export function getUsernameFromProps(props: any): string {
|
||||
|
@ -985,61 +986,12 @@ export function updateRegistrationApplicationRes(
|
|||
export function commentsToFlatNodes(comments: CommentView[]): CommentNodeI[] {
|
||||
let nodes: CommentNodeI[] = [];
|
||||
for (let comment of comments) {
|
||||
nodes.push({ comment_view: comment });
|
||||
nodes.push({ comment_view: comment, children: [], depth: 0 });
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function commentSort(tree: CommentNodeI[], sort: CommentSortType) {
|
||||
// First, put removed and deleted comments at the bottom, then do your other sorts
|
||||
if (sort == CommentSortType.Top) {
|
||||
tree.sort(
|
||||
(a, b) =>
|
||||
+a.comment_view.comment.removed - +b.comment_view.comment.removed ||
|
||||
+a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
|
||||
b.comment_view.counts.score - a.comment_view.counts.score
|
||||
);
|
||||
} else if (sort == CommentSortType.New) {
|
||||
tree.sort(
|
||||
(a, b) =>
|
||||
+a.comment_view.comment.removed - +b.comment_view.comment.removed ||
|
||||
+a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
|
||||
b.comment_view.comment.published.localeCompare(
|
||||
a.comment_view.comment.published
|
||||
)
|
||||
);
|
||||
} else if (sort == CommentSortType.Old) {
|
||||
tree.sort(
|
||||
(a, b) =>
|
||||
+a.comment_view.comment.removed - +b.comment_view.comment.removed ||
|
||||
+a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
|
||||
a.comment_view.comment.published.localeCompare(
|
||||
b.comment_view.comment.published
|
||||
)
|
||||
);
|
||||
} else if (sort == CommentSortType.Hot) {
|
||||
tree.sort(
|
||||
(a, b) =>
|
||||
+a.comment_view.comment.removed - +b.comment_view.comment.removed ||
|
||||
+a.comment_view.comment.deleted - +b.comment_view.comment.deleted ||
|
||||
hotRankComment(b.comment_view as CommentView) -
|
||||
hotRankComment(a.comment_view as CommentView)
|
||||
);
|
||||
}
|
||||
|
||||
// Go through the children recursively
|
||||
for (let node of tree) {
|
||||
if (node.children) {
|
||||
commentSort(node.children, sort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function commentSortSortType(tree: CommentNodeI[], sort: SortType) {
|
||||
commentSort(tree, convertCommentSortType(sort));
|
||||
}
|
||||
|
||||
function convertCommentSortType(sort: SortType): CommentSortType {
|
||||
export function convertCommentSortType(sort: SortType): CommentSortType {
|
||||
if (
|
||||
sort == SortType.TopAll ||
|
||||
sort == SortType.TopDay ||
|
||||
|
@ -1059,21 +1011,32 @@ function convertCommentSortType(sort: SortType): CommentSortType {
|
|||
|
||||
export function buildCommentsTree(
|
||||
comments: CommentView[],
|
||||
commentSortType: CommentSortType
|
||||
parentComment: boolean
|
||||
): CommentNodeI[] {
|
||||
let map = new Map<number, CommentNodeI>();
|
||||
let depthOffset = !parentComment
|
||||
? 0
|
||||
: getDepthFromComment(comments[0].comment);
|
||||
|
||||
for (let comment_view of comments) {
|
||||
let node: CommentNodeI = {
|
||||
comment_view: comment_view,
|
||||
children: [],
|
||||
depth: 0,
|
||||
depth: getDepthFromComment(comment_view.comment) - depthOffset,
|
||||
};
|
||||
map.set(comment_view.comment.id, { ...node });
|
||||
}
|
||||
|
||||
let tree: CommentNodeI[] = [];
|
||||
|
||||
// if its a parent comment fetch, then push the first comment to the top node.
|
||||
if (parentComment) {
|
||||
tree.push(map.get(comments[0].comment.id));
|
||||
}
|
||||
|
||||
for (let comment_view of comments) {
|
||||
let child = map.get(comment_view.comment.id);
|
||||
let parent_id = comment_view.comment.parent_id;
|
||||
let parent_id = getCommentParentId(comment_view.comment);
|
||||
parent_id.match({
|
||||
some: parentId => {
|
||||
let parent = map.get(parentId);
|
||||
|
@ -1083,26 +1046,37 @@ export function buildCommentsTree(
|
|||
}
|
||||
},
|
||||
none: () => {
|
||||
tree.push(child);
|
||||
if (!parentComment) {
|
||||
tree.push(child);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
setDepth(child);
|
||||
}
|
||||
|
||||
commentSort(tree, commentSortType);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
function setDepth(node: CommentNodeI, i = 0) {
|
||||
for (let child of node.children) {
|
||||
child.depth = i;
|
||||
setDepth(child, i + 1);
|
||||
export function getCommentParentId(comment: CommentI): Option<number> {
|
||||
let split = comment.path.split(".");
|
||||
// remove the 0
|
||||
split.shift();
|
||||
|
||||
if (split.length > 1) {
|
||||
return Some(Number(split[split.length - 2]));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
export function insertCommentIntoTree(tree: CommentNodeI[], cv: CommentView) {
|
||||
export function getDepthFromComment(comment: CommentI): number {
|
||||
return comment.path.split(".").length - 2;
|
||||
}
|
||||
|
||||
export function insertCommentIntoTree(
|
||||
tree: CommentNodeI[],
|
||||
cv: CommentView,
|
||||
parentComment: boolean
|
||||
) {
|
||||
// Building a fake node to be used for later
|
||||
let node: CommentNodeI = {
|
||||
comment_view: cv,
|
||||
|
@ -1110,7 +1084,7 @@ export function insertCommentIntoTree(tree: CommentNodeI[], cv: CommentView) {
|
|||
depth: 0,
|
||||
};
|
||||
|
||||
cv.comment.parent_id.match({
|
||||
getCommentParentId(cv.comment).match({
|
||||
some: parentId => {
|
||||
let parentComment = searchCommentTree(tree, parentId);
|
||||
parentComment.match({
|
||||
|
@ -1122,7 +1096,9 @@ export function insertCommentIntoTree(tree: CommentNodeI[], cv: CommentView) {
|
|||
});
|
||||
},
|
||||
none: () => {
|
||||
tree.unshift(node);
|
||||
if (!parentComment) {
|
||||
tree.unshift(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1149,6 +1125,7 @@ export function searchCommentTree(
|
|||
|
||||
export const colorList: string[] = [
|
||||
hsl(0),
|
||||
hsl(50),
|
||||
hsl(100),
|
||||
hsl(150),
|
||||
hsl(200),
|
||||
|
@ -1439,3 +1416,15 @@ export function enableDownvotes(siteRes: GetSiteResponse): boolean {
|
|||
export function enableNsfw(siteRes: GetSiteResponse): boolean {
|
||||
return siteRes.site_view.map(s => s.site.enable_nsfw).unwrapOr(false);
|
||||
}
|
||||
|
||||
export function postToCommentSortType(sort: SortType): CommentSortType {
|
||||
if ([SortType.Active, SortType.Hot].includes(sort)) {
|
||||
return CommentSortType.Hot;
|
||||
} else if ([SortType.New, SortType.NewComments].includes(sort)) {
|
||||
return CommentSortType.New;
|
||||
} else if (sort == SortType.Old) {
|
||||
return CommentSortType.Old;
|
||||
} else {
|
||||
return CommentSortType.Top;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5056,10 +5056,10 @@ lcid@^1.0.0:
|
|||
dependencies:
|
||||
invert-kv "^1.0.0"
|
||||
|
||||
lemmy-js-client@0.17.0-rc.38:
|
||||
version "0.17.0-rc.38"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.38.tgz#bbe0667ad44bbd0c1e516813d3c81b65c7f6a329"
|
||||
integrity sha512-uDC19s3+Eva+Hu3LhIPkT5j0Ngh7F84TA4VnfxMVJN6BQZFLZUmTvAErwJcqyj5vz3sNnw4jsEeTSGPODSXfeg==
|
||||
lemmy-js-client@0.17.0-rc.39:
|
||||
version "0.17.0-rc.39"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.0-rc.39.tgz#b17c5c0d9a0f36c90c17be99845a5703091ab306"
|
||||
integrity sha512-MsKavo5xOob6DgfjyhbmXyFvXwdW4iwftStJ7Bz3ArlHXy6zGBp+2uy2rU2c5ujivNDX72ol3TupTHBtSXLs4w==
|
||||
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
|
|
Loading…
Reference in a new issue