parent
469594fac8
commit
54b215a587
7 changed files with 164 additions and 53 deletions
6
ui/src/components/comment-node.tsx
vendored
6
ui/src/components/comment-node.tsx
vendored
|
@ -15,6 +15,8 @@ import {
|
|||
TransferCommunityForm,
|
||||
TransferSiteForm,
|
||||
BanType,
|
||||
CommentSortType,
|
||||
SortType,
|
||||
} from '../interfaces';
|
||||
import { WebSocketService, UserService } from '../services';
|
||||
import {
|
||||
|
@ -61,6 +63,8 @@ interface CommentNodeProps {
|
|||
// TODO is this necessary, can't I get it from the node itself?
|
||||
postCreatorId?: number;
|
||||
showCommunity?: boolean;
|
||||
sort?: CommentSortType;
|
||||
sortType?: SortType;
|
||||
}
|
||||
|
||||
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||
|
@ -630,6 +634,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
moderators={this.props.moderators}
|
||||
admins={this.props.admins}
|
||||
postCreatorId={this.props.postCreatorId}
|
||||
sort={this.props.sort}
|
||||
sortType={this.props.sortType}
|
||||
/>
|
||||
)}
|
||||
{/* A collapsed clearfix */}
|
||||
|
|
19
ui/src/components/comment-nodes.tsx
vendored
19
ui/src/components/comment-nodes.tsx
vendored
|
@ -3,7 +3,10 @@ import {
|
|||
CommentNode as CommentNodeI,
|
||||
CommunityUser,
|
||||
UserView,
|
||||
CommentSortType,
|
||||
SortType,
|
||||
} from '../interfaces';
|
||||
import { commentSort, commentSortSortType } from '../utils';
|
||||
import { CommentNode } from './comment-node';
|
||||
|
||||
interface CommentNodesState {}
|
||||
|
@ -18,6 +21,8 @@ interface CommentNodesProps {
|
|||
locked?: boolean;
|
||||
markable?: boolean;
|
||||
showCommunity?: boolean;
|
||||
sort?: CommentSortType;
|
||||
sortType?: SortType;
|
||||
}
|
||||
|
||||
export class CommentNodes extends Component<
|
||||
|
@ -31,7 +36,7 @@ export class CommentNodes extends Component<
|
|||
render() {
|
||||
return (
|
||||
<div className="comments">
|
||||
{this.props.nodes.map(node => (
|
||||
{this.sorter().map(node => (
|
||||
<CommentNode
|
||||
node={node}
|
||||
noIndent={this.props.noIndent}
|
||||
|
@ -42,9 +47,21 @@ export class CommentNodes extends Component<
|
|||
postCreatorId={this.props.postCreatorId}
|
||||
markable={this.props.markable}
|
||||
showCommunity={this.props.showCommunity}
|
||||
sort={this.props.sort}
|
||||
sortType={this.props.sortType}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
sorter(): Array<CommentNodeI> {
|
||||
if (this.props.sort !== undefined) {
|
||||
commentSort(this.props.nodes, this.props.sort);
|
||||
} else if (this.props.sortType !== undefined) {
|
||||
commentSortSortType(this.props.nodes, this.props.sortType);
|
||||
}
|
||||
|
||||
return this.props.nodes;
|
||||
}
|
||||
}
|
||||
|
|
12
ui/src/components/community.tsx
vendored
12
ui/src/components/community.tsx
vendored
|
@ -178,9 +178,17 @@ export class Community extends Component<any, State> {
|
|||
|
||||
listings() {
|
||||
return this.state.dataType == DataType.Post ? (
|
||||
<PostListings posts={this.state.posts} removeDuplicates />
|
||||
<PostListings
|
||||
posts={this.state.posts}
|
||||
removeDuplicates
|
||||
sort={this.state.sort}
|
||||
/>
|
||||
) : (
|
||||
<CommentNodes nodes={commentsToFlatNodes(this.state.comments)} noIndent />
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(this.state.comments)}
|
||||
noIndent
|
||||
sortType={this.state.sort}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
9
ui/src/components/main.tsx
vendored
9
ui/src/components/main.tsx
vendored
|
@ -51,6 +51,7 @@ import {
|
|||
createPostLikeFindRes,
|
||||
editPostFindRes,
|
||||
commentsToFlatNodes,
|
||||
commentSortSortType,
|
||||
} from '../utils';
|
||||
import { i18n } from '../i18next';
|
||||
import { T } from 'inferno-i18next';
|
||||
|
@ -404,12 +405,18 @@ export class Main extends Component<any, MainState> {
|
|||
|
||||
listings() {
|
||||
return this.state.dataType == DataType.Post ? (
|
||||
<PostListings posts={this.state.posts} showCommunity removeDuplicates />
|
||||
<PostListings
|
||||
posts={this.state.posts}
|
||||
showCommunity
|
||||
removeDuplicates
|
||||
sort={this.state.sort}
|
||||
/>
|
||||
) : (
|
||||
<CommentNodes
|
||||
nodes={commentsToFlatNodes(this.state.comments)}
|
||||
noIndent
|
||||
showCommunity
|
||||
sortType={this.state.sort}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
22
ui/src/components/post-listings.tsx
vendored
22
ui/src/components/post-listings.tsx
vendored
|
@ -1,6 +1,7 @@
|
|||
import { Component } from 'inferno';
|
||||
import { Link } from 'inferno-router';
|
||||
import { Post } from '../interfaces';
|
||||
import { Post, SortType } from '../interfaces';
|
||||
import { postSort } from '../utils';
|
||||
import { PostListing } from './post-listing';
|
||||
import { i18n } from '../i18next';
|
||||
import { T } from 'inferno-i18next';
|
||||
|
@ -9,6 +10,7 @@ interface PostListingsProps {
|
|||
posts: Array<Post>;
|
||||
showCommunity?: boolean;
|
||||
removeDuplicates?: boolean;
|
||||
sort?: SortType;
|
||||
}
|
||||
|
||||
export class PostListings extends Component<PostListingsProps, any> {
|
||||
|
@ -20,10 +22,7 @@ export class PostListings extends Component<PostListingsProps, any> {
|
|||
return (
|
||||
<div>
|
||||
{this.props.posts.length > 0 ? (
|
||||
(this.props.removeDuplicates
|
||||
? this.removeDuplicates(this.props.posts)
|
||||
: this.props.posts
|
||||
).map(post => (
|
||||
this.outer().map(post => (
|
||||
<>
|
||||
<PostListing
|
||||
post={post}
|
||||
|
@ -47,6 +46,19 @@ export class PostListings extends Component<PostListingsProps, any> {
|
|||
);
|
||||
}
|
||||
|
||||
outer(): Array<Post> {
|
||||
let out = this.props.posts;
|
||||
if (this.props.removeDuplicates) {
|
||||
out = this.removeDuplicates(out);
|
||||
}
|
||||
|
||||
if (this.props.sort !== undefined) {
|
||||
postSort(out, this.props.sort);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
removeDuplicates(posts: Array<Post>): Array<Post> {
|
||||
// A map from post url to list of posts (dupes)
|
||||
let urlMap = new Map<string, Array<Post>>();
|
||||
|
|
41
ui/src/components/post.tsx
vendored
41
ui/src/components/post.tsx
vendored
|
@ -31,7 +31,6 @@ import {
|
|||
import { WebSocketService, UserService } from '../services';
|
||||
import {
|
||||
wsJsonToRes,
|
||||
hotRank,
|
||||
toast,
|
||||
editCommentRes,
|
||||
saveCommentRes,
|
||||
|
@ -314,48 +313,9 @@ export class Post extends Component<any, PostState> {
|
|||
}
|
||||
}
|
||||
|
||||
this.sortTree(tree);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
commentsTree() {
|
||||
let nodes = this.buildCommentsTree();
|
||||
return (
|
||||
|
@ -366,6 +326,7 @@ export class Post extends Component<any, PostState> {
|
|||
moderators={this.state.moderators}
|
||||
admins={this.state.admins}
|
||||
postCreatorId={this.state.post.creator_id}
|
||||
sort={this.state.commentSort}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
108
ui/src/utils.ts
vendored
108
ui/src/utils.ts
vendored
|
@ -20,6 +20,7 @@ import {
|
|||
PrivateMessage,
|
||||
User,
|
||||
SortType,
|
||||
CommentSortType,
|
||||
ListingType,
|
||||
DataType,
|
||||
SearchType,
|
||||
|
@ -93,15 +94,22 @@ md.renderer.rules.emoji = function(token, idx) {
|
|||
return twemoji.parse(token[idx].content);
|
||||
};
|
||||
|
||||
export function hotRank(comment: Comment): number {
|
||||
// Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity
|
||||
export function hotRankComment(comment: Comment): number {
|
||||
return hotRank(comment.score, comment.published);
|
||||
}
|
||||
|
||||
let date: Date = new Date(comment.published + 'Z'); // Add Z to convert from UTC date
|
||||
export function hotRankPost(post: Post): number {
|
||||
return hotRank(post.score, post.newest_activity_time);
|
||||
}
|
||||
|
||||
export function hotRank(score: number, timeStr: string): number {
|
||||
// Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity
|
||||
let date: Date = new Date(timeStr + 'Z'); // Add Z to convert from UTC date
|
||||
let now: Date = new Date();
|
||||
let hoursElapsed: number = (now.getTime() - date.getTime()) / 36e5;
|
||||
|
||||
let rank =
|
||||
(10000 * Math.log10(Math.max(1, 3 + comment.score))) /
|
||||
(10000 * Math.log10(Math.max(1, 3 + score))) /
|
||||
Math.pow(hoursElapsed + 2, 1.8);
|
||||
|
||||
// console.log(`Comment: ${comment.content}\nRank: ${rank}\nScore: ${comment.score}\nHours: ${hoursElapsed}`);
|
||||
|
@ -639,3 +647,95 @@ export function commentsToFlatNodes(
|
|||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
export function commentSort(tree: Array<CommentNode>, 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.removed - +b.comment.removed ||
|
||||
+a.comment.deleted - +b.comment.deleted ||
|
||||
b.comment.score - a.comment.score
|
||||
);
|
||||
} else if (sort == 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 (sort == 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 (sort == CommentSortType.Hot) {
|
||||
tree.sort(
|
||||
(a, b) =>
|
||||
+a.comment.removed - +b.comment.removed ||
|
||||
+a.comment.deleted - +b.comment.deleted ||
|
||||
hotRankComment(b.comment) - hotRankComment(a.comment)
|
||||
);
|
||||
}
|
||||
|
||||
// Go through the children recursively
|
||||
for (let node of tree) {
|
||||
if (node.children) {
|
||||
commentSort(node.children, sort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function commentSortSortType(tree: Array<CommentNode>, sort: SortType) {
|
||||
commentSort(tree, convertCommentSortType(sort));
|
||||
}
|
||||
|
||||
function convertCommentSortType(sort: SortType): CommentSortType {
|
||||
if (
|
||||
sort == SortType.TopAll ||
|
||||
sort == SortType.TopDay ||
|
||||
sort == SortType.TopWeek ||
|
||||
sort == SortType.TopMonth ||
|
||||
sort == SortType.TopYear
|
||||
) {
|
||||
return CommentSortType.Top;
|
||||
} else if (sort == SortType.New) {
|
||||
return CommentSortType.New;
|
||||
} else if (sort == SortType.Hot) {
|
||||
return CommentSortType.Hot;
|
||||
} else {
|
||||
return CommentSortType.Hot;
|
||||
}
|
||||
}
|
||||
|
||||
export function postSort(posts: Array<Post>, sort: SortType) {
|
||||
// First, put removed and deleted comments at the bottom, then do your other sorts
|
||||
if (
|
||||
sort == SortType.TopAll ||
|
||||
sort == SortType.TopDay ||
|
||||
sort == SortType.TopWeek ||
|
||||
sort == SortType.TopMonth ||
|
||||
sort == SortType.TopYear
|
||||
) {
|
||||
posts.sort(
|
||||
(a, b) =>
|
||||
+a.removed - +b.removed || +a.deleted - +b.deleted || b.score - a.score
|
||||
);
|
||||
} else if (sort == SortType.New) {
|
||||
posts.sort(
|
||||
(a, b) =>
|
||||
+a.removed - +b.removed ||
|
||||
+a.deleted - +b.deleted ||
|
||||
b.published.localeCompare(a.published)
|
||||
);
|
||||
} else if (sort == SortType.Hot) {
|
||||
posts.sort(
|
||||
(a, b) =>
|
||||
+a.removed - +b.removed ||
|
||||
+a.deleted - +b.deleted ||
|
||||
hotRankPost(b) - hotRankPost(a)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue