parent
56cd103209
commit
fd8814677e
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,
|
TransferCommunityForm,
|
||||||
TransferSiteForm,
|
TransferSiteForm,
|
||||||
BanType,
|
BanType,
|
||||||
|
CommentSortType,
|
||||||
|
SortType,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import { WebSocketService, UserService } from '../services';
|
import { WebSocketService, UserService } from '../services';
|
||||||
import {
|
import {
|
||||||
|
@ -61,6 +63,8 @@ interface CommentNodeProps {
|
||||||
// TODO is this necessary, can't I get it from the node itself?
|
// TODO is this necessary, can't I get it from the node itself?
|
||||||
postCreatorId?: number;
|
postCreatorId?: number;
|
||||||
showCommunity?: boolean;
|
showCommunity?: boolean;
|
||||||
|
sort?: CommentSortType;
|
||||||
|
sortType?: SortType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
|
@ -630,6 +634,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
||||||
moderators={this.props.moderators}
|
moderators={this.props.moderators}
|
||||||
admins={this.props.admins}
|
admins={this.props.admins}
|
||||||
postCreatorId={this.props.postCreatorId}
|
postCreatorId={this.props.postCreatorId}
|
||||||
|
sort={this.props.sort}
|
||||||
|
sortType={this.props.sortType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* A collapsed clearfix */}
|
{/* 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,
|
CommentNode as CommentNodeI,
|
||||||
CommunityUser,
|
CommunityUser,
|
||||||
UserView,
|
UserView,
|
||||||
|
CommentSortType,
|
||||||
|
SortType,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
|
import { commentSort, commentSortSortType } from '../utils';
|
||||||
import { CommentNode } from './comment-node';
|
import { CommentNode } from './comment-node';
|
||||||
|
|
||||||
interface CommentNodesState {}
|
interface CommentNodesState {}
|
||||||
|
@ -18,6 +21,8 @@ interface CommentNodesProps {
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
markable?: boolean;
|
markable?: boolean;
|
||||||
showCommunity?: boolean;
|
showCommunity?: boolean;
|
||||||
|
sort?: CommentSortType;
|
||||||
|
sortType?: SortType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommentNodes extends Component<
|
export class CommentNodes extends Component<
|
||||||
|
@ -31,7 +36,7 @@ export class CommentNodes extends Component<
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="comments">
|
<div className="comments">
|
||||||
{this.props.nodes.map(node => (
|
{this.sorter().map(node => (
|
||||||
<CommentNode
|
<CommentNode
|
||||||
node={node}
|
node={node}
|
||||||
noIndent={this.props.noIndent}
|
noIndent={this.props.noIndent}
|
||||||
|
@ -42,9 +47,21 @@ export class CommentNodes extends Component<
|
||||||
postCreatorId={this.props.postCreatorId}
|
postCreatorId={this.props.postCreatorId}
|
||||||
markable={this.props.markable}
|
markable={this.props.markable}
|
||||||
showCommunity={this.props.showCommunity}
|
showCommunity={this.props.showCommunity}
|
||||||
|
sort={this.props.sort}
|
||||||
|
sortType={this.props.sortType}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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() {
|
listings() {
|
||||||
return this.state.dataType == DataType.Post ? (
|
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,
|
createPostLikeFindRes,
|
||||||
editPostFindRes,
|
editPostFindRes,
|
||||||
commentsToFlatNodes,
|
commentsToFlatNodes,
|
||||||
|
commentSortSortType,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -404,12 +405,18 @@ export class Main extends Component<any, MainState> {
|
||||||
|
|
||||||
listings() {
|
listings() {
|
||||||
return this.state.dataType == DataType.Post ? (
|
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
|
<CommentNodes
|
||||||
nodes={commentsToFlatNodes(this.state.comments)}
|
nodes={commentsToFlatNodes(this.state.comments)}
|
||||||
noIndent
|
noIndent
|
||||||
showCommunity
|
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 { Component } from 'inferno';
|
||||||
import { Link } from 'inferno-router';
|
import { Link } from 'inferno-router';
|
||||||
import { Post } from '../interfaces';
|
import { Post, SortType } from '../interfaces';
|
||||||
|
import { postSort } from '../utils';
|
||||||
import { PostListing } from './post-listing';
|
import { PostListing } from './post-listing';
|
||||||
import { i18n } from '../i18next';
|
import { i18n } from '../i18next';
|
||||||
import { T } from 'inferno-i18next';
|
import { T } from 'inferno-i18next';
|
||||||
|
@ -9,6 +10,7 @@ interface PostListingsProps {
|
||||||
posts: Array<Post>;
|
posts: Array<Post>;
|
||||||
showCommunity?: boolean;
|
showCommunity?: boolean;
|
||||||
removeDuplicates?: boolean;
|
removeDuplicates?: boolean;
|
||||||
|
sort?: SortType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostListings extends Component<PostListingsProps, any> {
|
export class PostListings extends Component<PostListingsProps, any> {
|
||||||
|
@ -20,10 +22,7 @@ export class PostListings extends Component<PostListingsProps, any> {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.props.posts.length > 0 ? (
|
{this.props.posts.length > 0 ? (
|
||||||
(this.props.removeDuplicates
|
this.outer().map(post => (
|
||||||
? this.removeDuplicates(this.props.posts)
|
|
||||||
: this.props.posts
|
|
||||||
).map(post => (
|
|
||||||
<>
|
<>
|
||||||
<PostListing
|
<PostListing
|
||||||
post={post}
|
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> {
|
removeDuplicates(posts: Array<Post>): Array<Post> {
|
||||||
// A map from post url to list of posts (dupes)
|
// A map from post url to list of posts (dupes)
|
||||||
let urlMap = new Map<string, Array<Post>>();
|
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 { WebSocketService, UserService } from '../services';
|
||||||
import {
|
import {
|
||||||
wsJsonToRes,
|
wsJsonToRes,
|
||||||
hotRank,
|
|
||||||
toast,
|
toast,
|
||||||
editCommentRes,
|
editCommentRes,
|
||||||
saveCommentRes,
|
saveCommentRes,
|
||||||
|
@ -314,48 +313,9 @@ export class Post extends Component<any, PostState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sortTree(tree);
|
|
||||||
|
|
||||||
return 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() {
|
commentsTree() {
|
||||||
let nodes = this.buildCommentsTree();
|
let nodes = this.buildCommentsTree();
|
||||||
return (
|
return (
|
||||||
|
@ -366,6 +326,7 @@ export class Post extends Component<any, PostState> {
|
||||||
moderators={this.state.moderators}
|
moderators={this.state.moderators}
|
||||||
admins={this.state.admins}
|
admins={this.state.admins}
|
||||||
postCreatorId={this.state.post.creator_id}
|
postCreatorId={this.state.post.creator_id}
|
||||||
|
sort={this.state.commentSort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
108
ui/src/utils.ts
vendored
108
ui/src/utils.ts
vendored
|
@ -20,6 +20,7 @@ import {
|
||||||
PrivateMessage,
|
PrivateMessage,
|
||||||
User,
|
User,
|
||||||
SortType,
|
SortType,
|
||||||
|
CommentSortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
DataType,
|
DataType,
|
||||||
SearchType,
|
SearchType,
|
||||||
|
@ -93,15 +94,22 @@ md.renderer.rules.emoji = function(token, idx) {
|
||||||
return twemoji.parse(token[idx].content);
|
return twemoji.parse(token[idx].content);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function hotRank(comment: Comment): number {
|
export function hotRankComment(comment: Comment): number {
|
||||||
// Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity
|
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 now: Date = new Date();
|
||||||
let hoursElapsed: number = (now.getTime() - date.getTime()) / 36e5;
|
let hoursElapsed: number = (now.getTime() - date.getTime()) / 36e5;
|
||||||
|
|
||||||
let rank =
|
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);
|
Math.pow(hoursElapsed + 2, 1.8);
|
||||||
|
|
||||||
// console.log(`Comment: ${comment.content}\nRank: ${rank}\nScore: ${comment.score}\nHours: ${hoursElapsed}`);
|
// console.log(`Comment: ${comment.content}\nRank: ${rank}\nScore: ${comment.score}\nHours: ${hoursElapsed}`);
|
||||||
|
@ -639,3 +647,95 @@ export function commentsToFlatNodes(
|
||||||
}
|
}
|
||||||
return nodes;
|
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