* Updating translations.
* Forgot to add comment-sort-select
* Upgrading deps
-Subproject commit 7c1b691af63845a2fe2f8219b4620b8db3c9c3ba
+Subproject commit 7c3945745dcd07774b19453803f7f14ab80ab3d3
"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",
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
import {
+ CommentNode as CommentNodeI,
CommentResponse,
CreateComment,
EditComment,
} 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,
BanFromCommunity,
BanPerson,
BlockPerson,
+ CommentNode as CommentNodeI,
+ CommentReplyView,
CommentView,
CommunityModeratorView,
CreateCommentLike,
CreateCommentReport,
DeleteComment,
- MarkCommentAsRead,
+ GetComments,
+ ListingType,
+ MarkCommentReplyAsRead,
MarkPersonMentionAsRead,
PersonMentionView,
PersonViewSafe,
} 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,
canAdmin,
canMod,
colorList,
+ commentTreeMaxDepth,
futureDaysToUnixTime,
isAdmin,
isBanned,
score: number;
upvotes: number;
downvotes: number;
- borderColor: string;
readLoading: boolean;
saveLoading: boolean;
}
showContext?: boolean;
showCommunity?: boolean;
enableDownvotes: boolean;
+ viewType: CommentViewType;
}
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,
};
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
} ${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">
<>
<a
className={`unselectable pointer ${this.scoreColor}`}
- onClick={linkEvent(node, this.handleCommentUpvote)}
+ onClick={this.handleCommentUpvote}
data-tippy-content={this.pointsTippy}
>
<span
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")
}
<Icon
icon="check"
classes={`icon-inline ${
- this.commentOrMentionRead && "text-success"
+ this.commentReplyOrMentionRead && "text-success"
}`}
/>
)}
? "text-info"
: "text-muted"
}`}
- onClick={linkEvent(node, this.handleCommentUpvote)}
+ onClick={this.handleCommentUpvote}
data-tippy-content={i18n.t("upvote")}
aria-label={i18n.t("upvote")}
>
? "text-danger"
: "text-muted"
}`}
- onClick={linkEvent(
- node,
- this.handleCommentDownvote
- )}
+ onClick={this.handleCommentDownvote}
data-tippy-content={i18n.t("downvote")}
aria-label={i18n.t("downvote")}
>
)}
</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
focus
/>
)}
- {!this.state.collapsed && node.children && (
+ {!this.state.collapsed && node.children.length > 0 && (
<CommentNodes
nodes={node.children}
locked={this.props.locked}
admins={this.props.admins}
maxCommentsShown={None}
enableDownvotes={this.props.enableDownvotes}
+ viewType={this.props.viewType}
/>
)}
{/* A collapsed clearfix */}
);
}
- 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) {
<>
<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" />
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;
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;
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(),
});
}
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({
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;
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";
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 {
showContext?: boolean;
showCommunity?: boolean;
enableDownvotes?: boolean;
+ viewType: CommentViewType;
}
export class CommentNodes extends Component<CommentNodesProps, any> {
showContext={this.props.showContext}
showCommunity={this.props.showCommunity}
enableDownvotes={this.props.enableDownvotes}
+ viewType={this.props.viewType}
/>
))}
</div>
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";
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}
--- /dev/null
+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);
+ }
+}
<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")}
} 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,
getPageFromProps,
getSortTypeFromProps,
notifyPost,
+ postToCommentSortType,
relTags,
restoreScrollPosition,
saveCommentRes,
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());
) : (
<CommentNodes
nodes={commentsToFlatNodes(this.state.comments)}
+ viewType={CommentViewType.Flat}
noIndent
showContext
enableDownvotes={enableDownvotes(this.state.siteRes)}
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));
} 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,
getSortTypeFromProps,
isBrowser,
notifyPost,
+ postToCommentSortType,
relTags,
restoreScrollPosition,
saveCommentRes,
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());
) : (
<CommentNodes
nodes={commentsToFlatNodes(this.state.comments)}
+ viewType={CommentViewType.Flat}
moderators={None}
admins={None}
maxCommentsShown={None}
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),
});
import { Component, linkEvent } from "inferno";
import {
BlockPersonResponse,
+ CommentReplyResponse,
+ CommentReplyView,
CommentReportResponse,
CommentResponse,
+ CommentSortType,
CommentView,
GetPersonMentions,
GetPersonMentionsResponse,
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,
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 {
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;
mentions: [],
messages: [],
combined: [],
- sort: SortType.New,
+ sort: CommentSortType.New,
page: 1,
siteRes: this.isoData.site_res,
loading: true,
<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,
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}
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}
<div>
<CommentNodes
nodes={commentsToFlatNodes(this.state.replies)}
+ viewType={CommentViewType.Flat}
moderators={None}
admins={None}
maxCommentsShown={None}
{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}
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),
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),
);
}
- handleSortChange(val: SortType) {
+ handleSortChange(val: CommentSortType) {
this.state.sort = val;
this.state.page = 1;
this.setState(this.state);
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);
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);
}
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,
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;
+ }
}
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";
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}
<div>
<CommentNodes
nodes={commentsToFlatNodes(this.props.personRes.comments)}
+ viewType={CommentViewType.Flat}
admins={Some(this.props.admins)}
moderators={None}
maxCommentsShown={None}
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")}
>
? "text-danger"
: "text-muted"
}`}
- onClick={linkEvent(this, this.handlePostDisLike)}
+ onClick={this.handlePostDisLike}
data-tippy-content={i18n.t("downvote")}
aria-label={i18n.t("downvote")}
>
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" />
? "text-danger"
: "text-muted"
}`}
- onClick={linkEvent(this, this.handlePostDisLike)}
+ onClick={this.handlePostDisLike}
{...tippy}
aria-label={i18n.t("downvote")}
>
});
}
- handlePostLike(i: PostListing, event: any) {
+ handlePostLike(event: any) {
event.preventDefault();
if (UserService.Instance.myUserInfo.isNone()) {
this.context.router.history.push(`/login`);
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`);
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();
}
BanFromCommunityResponse,
BanPersonResponse,
BlockPersonResponse,
+ CommentNode as CommentNodeI,
CommentReportResponse,
CommentResponse,
+ CommentSortType,
CommunityResponse,
+ GetComments,
+ GetCommentsResponse,
GetCommunityResponse,
GetPost,
GetPostResponse,
GetSiteResponse,
ListingType,
- MarkCommentAsRead,
PostReportResponse,
PostResponse,
PostView,
} 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,
enableDownvotes,
enableNsfw,
getCommentIdFromProps,
+ getCommentParentId,
+ getDepthFromComment,
getIdFromProps,
insertCommentIntoTree,
isBrowser,
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;
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,
// 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()) {
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();
}
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(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.getPost(form));
+ WebSocketService.Instance.send(wsClient.getComments(commentsForm));
}
fetchCrossPosts() {
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,
+ });
+
+ 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,
});
- return [req.client.getPost(postForm)];
+ // 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() {
}
}
- 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")
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;
}
/>
<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">
</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>
<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")}
</label>
<label
className={`btn btn-outline-secondary pointer ${
- this.state.commentSort === CommentSortType.Top && "active"
+ CommentSortType[this.state.commentSort] === CommentSortType.Top &&
+ "active"
}`}
>
{i18n.t("top")}
</label>
<label
className={`btn btn-outline-secondary pointer ${
- this.state.commentSort === CommentSortType.New && "active"
+ CommentSortType[this.state.commentSort] === CommentSortType.New &&
+ "active"
}`}
>
{i18n.t("new")}
</label>
<label
className={`btn btn-outline-secondary pointer ${
- this.state.commentSort === CommentSortType.Old && "active"
+ CommentSortType[this.state.commentSort] === CommentSortType.Old &&
+ "active"
}`}
>
{i18n.t("old")}
<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>
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: <></>,
});
}
}
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);
}
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)}
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({
);
// 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);
// 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);
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();
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) {
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) {
} 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) {
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,
{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}
return (
<CommentNodes
nodes={commentsToFlatNodes(comments)}
+ viewType={CommentViewType.Flat}
locked
noIndent
moderators={None}
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.
path: string;
}
-export interface CommentNode {
- comment_view: CommentView | PersonMentionView;
- children?: CommentNode[];
- depth?: number;
-}
-
export interface PostFormParams {
name: Option<string>;
url: Option<string>;
nameOrId: Option<Either<string, number>>;
}
-export enum CommentSortType {
- Hot,
- Top,
- New,
- Old,
-}
-
export enum CommentViewType {
Tree,
- Chat,
+ Flat,
}
export enum DataType {
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),
},
import {
BlockCommunityResponse,
BlockPersonResponse,
+ Comment as CommentI,
+ CommentNode as CommentNodeI,
CommentReportView,
+ CommentSortType,
CommentView,
CommunityBlockView,
CommunityModeratorView,
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;
export const fetchLimit = 20;
export const trendingFetchLimit = 6;
export const mentionDropdownFetchLimit = 10;
+export const commentTreeMaxDepth = 8;
export const relTags = "noopener nofollow";
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);
: 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 {
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 ||
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);
}
},
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,
depth: 0,
};
- cv.comment.parent_id.match({
+ getCommentParentId(cv.comment).match({
some: parentId => {
let parentComment = searchCommentTree(tree, parentId);
parentComment.match({
});
},
none: () => {
- tree.unshift(node);
+ if (!parentComment) {
+ tree.unshift(node);
+ }
},
});
}
export const colorList: string[] = [
hsl(0),
+ hsl(50),
hsl(100),
hsl(150),
hsl(200),
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;
+ }
+}
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"