Live post and comment resorting. Fixes #522

- Moving sorting to utils.
This commit is contained in:
Dessalines 2020-02-09 11:44:24 -05:00
parent 56cd103209
commit fd8814677e
7 changed files with 164 additions and 53 deletions

View File

@ -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 */}

View File

@ -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;
}
}

View File

@ -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}
/>
);
}

View File

@ -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}
/>
);
}

View File

@ -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>>();

View File

@ -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
View File

@ -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)
);
}
}