import { Component, linkEvent } from 'inferno';
+import { Helmet } from 'inferno-helmet';
import { Subscription } from 'rxjs';
import { retryWhen, delay, take } from 'rxjs/operators';
import {
GetPostResponse,
PostResponse,
Comment,
- CommentForm as CommentFormI,
+ MarkCommentAsReadForm,
CommentResponse,
- CommentSortType,
CommunityUser,
CommunityResponse,
CommentNode as CommentNodeI,
BanUserResponse,
AddModToCommunityResponse,
AddAdminResponse,
- UserView,
SearchType,
SortType,
SearchForm,
GetSiteResponse,
GetCommunityResponse,
WebSocketJsonResponse,
-} from '../interfaces';
+} from 'lemmy-js-client';
+import { CommentSortType, CommentViewType } from '../interfaces';
import { WebSocketService, UserService } from '../services';
import {
wsJsonToRes,
- hotRank,
toast,
editCommentRes,
saveCommentRes,
createCommentLikeRes,
createPostLikeRes,
commentsToFlatNodes,
+ setupTippy,
+ favIconUrl,
} from '../utils';
import { PostListing } from './post-listing';
-import { PostListings } from './post-listings';
import { Sidebar } from './sidebar';
import { CommentForm } from './comment-form';
import { CommentNodes } from './comment-nodes';
post: PostI;
comments: Array<Comment>;
commentSort: CommentSortType;
+ commentViewType: CommentViewType;
community: Community;
moderators: Array<CommunityUser>;
- admins: Array<UserView>;
online: number;
scrolled?: boolean;
scrolled_comment_id?: number;
loading: boolean;
crossPosts: Array<PostI>;
+ siteRes: GetSiteResponse;
}
export class Post extends Component<any, PostState> {
post: null,
comments: [],
commentSort: CommentSortType.Hot,
+ commentViewType: CommentViewType.Tree,
community: null,
moderators: [],
- admins: [],
online: null,
scrolled: false,
loading: true,
crossPosts: [],
+ siteRes: {
+ admins: [],
+ banned: [],
+ site: {
+ id: undefined,
+ name: undefined,
+ creator_id: undefined,
+ published: undefined,
+ creator_name: undefined,
+ number_of_users: undefined,
+ number_of_posts: undefined,
+ number_of_comments: undefined,
+ number_of_communities: undefined,
+ enable_downvotes: undefined,
+ open_registration: undefined,
+ enable_nsfw: undefined,
+ icon: undefined,
+ banner: undefined,
+ },
+ online: null,
+ version: null,
+ federated_instances: undefined,
+ },
};
constructor(props: any, context: any) {
id: postId,
};
WebSocketService.Instance.getPost(form);
+ WebSocketService.Instance.getSite();
}
componentWillUnmount() {
UserService.Instance.user &&
UserService.Instance.user.id == parent_user_id
) {
- let form: CommentFormI = {
- content: found.content,
+ let form: MarkCommentAsReadForm = {
edit_id: found.id,
- creator_id: found.creator_id,
- post_id: found.post_id,
- parent_id: found.parent_id,
read: true,
auth: null,
};
- WebSocketService.Instance.editComment(form);
+ WebSocketService.Instance.markCommentAsRead(form);
+ UserService.Instance.unreadCountSub.next(
+ UserService.Instance.unreadCountSub.value - 1
+ );
}
}
+ get documentTitle(): string {
+ if (this.state.post) {
+ return `${this.state.post.name} - ${this.state.siteRes.site.name}`;
+ } else {
+ return 'Lemmy';
+ }
+ }
+
+ get favIcon(): string {
+ return this.state.siteRes.site.icon
+ ? this.state.siteRes.site.icon
+ : favIconUrl;
+ }
+
render() {
return (
<div class="container">
+ <Helmet title={this.documentTitle}>
+ <link
+ id="favicon"
+ rel="icon"
+ type="image/x-icon"
+ href={this.favIcon}
+ />
+ </Helmet>
{this.state.loading ? (
<h5>
<svg class="icon icon-spinner spin">
showBody
showCommunity
moderators={this.state.moderators}
- admins={this.state.admins}
+ admins={this.state.siteRes.admins}
+ enableDownvotes={this.state.siteRes.site.enable_downvotes}
+ enableNsfw={this.state.siteRes.site.enable_nsfw}
/>
- {this.state.crossPosts.length > 0 && (
- <>
- <div class="my-1 text-muted small font-weight-bold">
- {i18n.t('cross_posts')}
- </div>
- <PostListings showCommunity posts={this.state.crossPosts} />
- </>
- )}
<div className="mb-2" />
<CommentForm
postId={this.state.post.id}
disabled={this.state.post.locked}
/>
{this.state.comments.length > 0 && this.sortRadios()}
- {this.commentsTree()}
- </div>
- <div class="col-12 col-sm-12 col-md-4">
- {this.state.comments.length > 0 && this.newComments()}
- {this.sidebar()}
+ {this.state.commentViewType == CommentViewType.Tree &&
+ this.commentsTree()}
+ {this.state.commentViewType == CommentViewType.Chat &&
+ this.commentsFlat()}
</div>
+ <div class="col-12 col-sm-12 col-md-4">{this.sidebar()}</div>
</div>
)}
</div>
sortRadios() {
return (
- <div class="btn-group btn-group-toggle mb-3">
- <label
- className={`btn btn-sm btn-secondary pointer ${this.state
- .commentSort === CommentSortType.Hot && 'active'}`}
- >
- {i18n.t('hot')}
- <input
- type="radio"
- value={CommentSortType.Hot}
- checked={this.state.commentSort === CommentSortType.Hot}
- onChange={linkEvent(this, this.handleCommentSortChange)}
- />
- </label>
- <label
- className={`btn btn-sm btn-secondary pointer ${this.state
- .commentSort === CommentSortType.Top && 'active'}`}
- >
- {i18n.t('top')}
- <input
- type="radio"
- value={CommentSortType.Top}
- checked={this.state.commentSort === CommentSortType.Top}
- onChange={linkEvent(this, this.handleCommentSortChange)}
- />
- </label>
- <label
- className={`btn btn-sm btn-secondary pointer ${this.state
- .commentSort === CommentSortType.New && 'active'}`}
- >
- {i18n.t('new')}
- <input
- type="radio"
- value={CommentSortType.New}
- checked={this.state.commentSort === CommentSortType.New}
- onChange={linkEvent(this, this.handleCommentSortChange)}
- />
- </label>
- <label
- className={`btn btn-sm btn-secondary pointer ${this.state
- .commentSort === CommentSortType.Old && 'active'}`}
- >
- {i18n.t('old')}
- <input
- type="radio"
- value={CommentSortType.Old}
- checked={this.state.commentSort === CommentSortType.Old}
- onChange={linkEvent(this, this.handleCommentSortChange)}
- />
- </label>
- </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'
+ }`}
+ >
+ {i18n.t('hot')}
+ <input
+ type="radio"
+ value={CommentSortType.Hot}
+ checked={this.state.commentSort === CommentSortType.Hot}
+ onChange={linkEvent(this, this.handleCommentSortChange)}
+ />
+ </label>
+ <label
+ className={`btn btn-outline-secondary pointer ${
+ this.state.commentSort === CommentSortType.Top && 'active'
+ }`}
+ >
+ {i18n.t('top')}
+ <input
+ type="radio"
+ value={CommentSortType.Top}
+ checked={this.state.commentSort === CommentSortType.Top}
+ onChange={linkEvent(this, this.handleCommentSortChange)}
+ />
+ </label>
+ <label
+ className={`btn btn-outline-secondary pointer ${
+ this.state.commentSort === CommentSortType.New && 'active'
+ }`}
+ >
+ {i18n.t('new')}
+ <input
+ type="radio"
+ value={CommentSortType.New}
+ checked={this.state.commentSort === CommentSortType.New}
+ onChange={linkEvent(this, this.handleCommentSortChange)}
+ />
+ </label>
+ <label
+ className={`btn btn-outline-secondary pointer ${
+ this.state.commentSort === CommentSortType.Old && 'active'
+ }`}
+ >
+ {i18n.t('old')}
+ <input
+ type="radio"
+ value={CommentSortType.Old}
+ checked={this.state.commentSort === CommentSortType.Old}
+ onChange={linkEvent(this, this.handleCommentSortChange)}
+ />
+ </label>
+ </div>
+ <div class="btn-group btn-group-toggle flex-wrap mb-2">
+ <label
+ className={`btn btn-outline-secondary pointer ${
+ this.state.commentViewType === CommentViewType.Chat && 'active'
+ }`}
+ >
+ {i18n.t('chat')}
+ <input
+ type="radio"
+ value={CommentViewType.Chat}
+ checked={this.state.commentViewType === CommentViewType.Chat}
+ onChange={linkEvent(this, this.handleCommentViewTypeChange)}
+ />
+ </label>
+ </div>
+ </>
);
}
- newComments() {
+ commentsFlat() {
return (
- <div class="d-none d-md-block new-comments mb-3 card border-secondary">
- <div class="card-body small">
- <h6>{i18n.t('recent_comments')}</h6>
- <CommentNodes
- nodes={commentsToFlatNodes(this.state.comments)}
- noIndent
- locked={this.state.post.locked}
- moderators={this.state.moderators}
- admins={this.state.admins}
- postCreatorId={this.state.post.creator_id}
- />
- </div>
+ <div>
+ <CommentNodes
+ nodes={commentsToFlatNodes(this.state.comments)}
+ noIndent
+ locked={this.state.post.locked}
+ moderators={this.state.moderators}
+ admins={this.state.siteRes.admins}
+ postCreatorId={this.state.post.creator_id}
+ showContext
+ enableDownvotes={this.state.siteRes.site.enable_downvotes}
+ sort={this.state.commentSort}
+ />
</div>
);
}
<Sidebar
community={this.state.community}
moderators={this.state.moderators}
- admins={this.state.admins}
+ admins={this.state.siteRes.admins}
online={this.state.online}
+ enableNsfw={this.state.siteRes.site.enable_nsfw}
+ showIcon
/>
</div>
);
handleCommentSortChange(i: Post, event: any) {
i.state.commentSort = Number(event.target.value);
+ i.state.commentViewType = CommentViewType.Tree;
+ i.setState(i.state);
+ }
+
+ handleCommentViewTypeChange(i: Post, event: any) {
+ i.state.commentViewType = Number(event.target.value);
+ i.state.commentSort = CommentSortType.New;
i.setState(i.state);
}
- private buildCommentsTree(): Array<CommentNodeI> {
+ buildCommentsTree(): Array<CommentNodeI> {
let map = new Map<number, CommentNodeI>();
for (let comment of this.state.comments) {
let node: CommentNodeI = {
}
let tree: Array<CommentNodeI> = [];
for (let comment of this.state.comments) {
+ let child = map.get(comment.id);
if (comment.parent_id) {
- map.get(comment.parent_id).children.push(map.get(comment.id));
+ let parent_ = map.get(comment.parent_id);
+ parent_.children.push(child);
} else {
- tree.push(map.get(comment.id));
+ tree.push(child);
}
- }
- this.sortTree(tree);
+ this.setDepth(child);
+ }
return tree;
}
- sortTree(tree: Array<CommentNodeI>) {
- // First, put removed and deleted comments at the bottom, then do your other sorts
- if (this.state.commentSort == CommentSortType.Top) {
- tree.sort(
- (a, b) =>
- +a.comment.removed - +b.comment.removed ||
- +a.comment.deleted - +b.comment.deleted ||
- b.comment.score - a.comment.score
- );
- } else if (this.state.commentSort == CommentSortType.New) {
- tree.sort(
- (a, b) =>
- +a.comment.removed - +b.comment.removed ||
- +a.comment.deleted - +b.comment.deleted ||
- b.comment.published.localeCompare(a.comment.published)
- );
- } else if (this.state.commentSort == CommentSortType.Old) {
- tree.sort(
- (a, b) =>
- +a.comment.removed - +b.comment.removed ||
- +a.comment.deleted - +b.comment.deleted ||
- a.comment.published.localeCompare(b.comment.published)
- );
- } else if (this.state.commentSort == CommentSortType.Hot) {
- tree.sort(
- (a, b) =>
- +a.comment.removed - +b.comment.removed ||
- +a.comment.deleted - +b.comment.deleted ||
- hotRank(b.comment) - hotRank(a.comment)
- );
- }
-
- for (let node of tree) {
- this.sortTree(node.children);
+ setDepth(node: CommentNodeI, i: number = 0): void {
+ for (let child of node.children) {
+ child.comment.depth = i;
+ this.setDepth(child, i + 1);
}
}
nodes={nodes}
locked={this.state.post.locked}
moderators={this.state.moderators}
- admins={this.state.admins}
+ admins={this.state.siteRes.admins}
postCreatorId={this.state.post.creator_id}
+ sort={this.state.commentSort}
+ enableDownvotes={this.state.siteRes.site.enable_downvotes}
/>
</div>
);
this.state.comments = data.comments;
this.state.community = data.community;
this.state.moderators = data.moderators;
- this.state.admins = data.admins;
this.state.online = data.online;
this.state.loading = false;
- document.title = `${this.state.post.name} - ${WebSocketService.Instance.site.name}`;
// Get cross-posts
if (this.state.post.url) {
let form: SearchForm = {
q: this.state.post.url,
- type_: SearchType[SearchType.Url],
- sort: SortType[SortType.TopAll],
+ type_: SearchType.Url,
+ sort: SortType.TopAll,
page: 1,
limit: 6,
};
}
this.setState(this.state);
+ setupTippy();
} else if (res.op == UserOperation.CreateComment) {
let data = res.data as CommentResponse;
this.state.comments.unshift(data.comment);
this.setState(this.state);
}
- } else if (res.op == UserOperation.EditComment) {
+ } else if (
+ res.op == UserOperation.EditComment ||
+ res.op == UserOperation.DeleteComment ||
+ res.op == UserOperation.RemoveComment
+ ) {
let data = res.data as CommentResponse;
editCommentRes(data, this.state.comments);
this.setState(this.state);
let data = res.data as CommentResponse;
saveCommentRes(data, this.state.comments);
this.setState(this.state);
+ setupTippy();
} else if (res.op == UserOperation.CreateCommentLike) {
let data = res.data as CommentResponse;
createCommentLikeRes(data, this.state.comments);
let data = res.data as PostResponse;
createPostLikeRes(data, this.state.post);
this.setState(this.state);
- } else if (res.op == UserOperation.EditPost) {
+ } else if (
+ res.op == UserOperation.EditPost ||
+ res.op == UserOperation.DeletePost ||
+ res.op == UserOperation.RemovePost ||
+ res.op == UserOperation.LockPost ||
+ res.op == UserOperation.StickyPost
+ ) {
let data = res.data as PostResponse;
this.state.post = data.post;
this.setState(this.state);
+ setupTippy();
} else if (res.op == UserOperation.SavePost) {
let data = res.data as PostResponse;
this.state.post = data.post;
this.setState(this.state);
- } else if (res.op == UserOperation.EditCommunity) {
+ setupTippy();
+ } else if (
+ res.op == UserOperation.EditCommunity ||
+ res.op == UserOperation.DeleteCommunity ||
+ res.op == UserOperation.RemoveCommunity
+ ) {
let data = res.data as CommunityResponse;
this.state.community = data.community;
this.state.post.community_id = data.community.id;
this.setState(this.state);
} else if (res.op == UserOperation.AddAdmin) {
let data = res.data as AddAdminResponse;
- this.state.admins = data.admins;
+ this.state.siteRes.admins = data.admins;
this.setState(this.state);
} else if (res.op == UserOperation.Search) {
let data = res.data as SearchResponse;
this.state.crossPosts = data.posts.filter(
- p => p.id != this.state.post.id
+ p => p.id != Number(this.props.match.params.id)
);
+ if (this.state.crossPosts.length) {
+ this.state.post.duplicates = this.state.crossPosts;
+ }
this.setState(this.state);
- } else if (res.op == UserOperation.TransferSite) {
+ } else if (
+ res.op == UserOperation.TransferSite ||
+ res.op == UserOperation.GetSite
+ ) {
let data = res.data as GetSiteResponse;
- this.state.admins = data.admins;
+ this.state.siteRes = data;
this.setState(this.state);
} else if (res.op == UserOperation.TransferCommunity) {
let data = res.data as GetCommunityResponse;
this.state.community = data.community;
this.state.moderators = data.moderators;
- this.state.admins = data.admins;
this.setState(this.state);
}
}