diff options
8 files changed, 47 insertions, 7 deletions
diff --git a/client/src/app/shared/video/sort-field.type.ts b/client/src/app/shared/video/sort-field.type.ts index d1088d244..65b24d946 100644 --- a/client/src/app/shared/video/sort-field.type.ts +++ b/client/src/app/shared/video/sort-field.type.ts | |||
@@ -5,3 +5,6 @@ export type VideoSortField = 'name' | '-name' | |||
5 | | 'views' | '-views' | 5 | | 'views' | '-views' |
6 | | 'likes' | '-likes' | 6 | | 'likes' | '-likes' |
7 | | 'trending' | '-trending' | 7 | | 'trending' | '-trending' |
8 | |||
9 | export type CommentSortField = 'createdAt' | '-createdAt' | ||
10 | | 'totalReplies' | '-totalReplies' | ||
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.service.ts b/client/src/app/videos/+video-watch/comment/video-comment.service.ts index 550d42fa8..72fbf5d25 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.service.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.service.ts | |||
@@ -12,7 +12,7 @@ import { | |||
12 | import { environment } from '../../../../environments/environment' | 12 | import { environment } from '../../../../environments/environment' |
13 | import { RestExtractor, RestService } from '../../../shared/rest' | 13 | import { RestExtractor, RestService } from '../../../shared/rest' |
14 | import { ComponentPagination } from '../../../shared/rest/component-pagination.model' | 14 | import { ComponentPagination } from '../../../shared/rest/component-pagination.model' |
15 | import { VideoSortField } from '../../../shared/video/sort-field.type' | 15 | import { CommentSortField } from '../../../shared/video/sort-field.type' |
16 | import { VideoComment } from './video-comment.model' | 16 | import { VideoComment } from './video-comment.model' |
17 | 17 | ||
18 | @Injectable() | 18 | @Injectable() |
@@ -51,7 +51,7 @@ export class VideoCommentService { | |||
51 | getVideoCommentThreads (parameters: { | 51 | getVideoCommentThreads (parameters: { |
52 | videoId: number | string, | 52 | videoId: number | string, |
53 | componentPagination: ComponentPagination, | 53 | componentPagination: ComponentPagination, |
54 | sort: VideoSortField | 54 | sort: CommentSortField |
55 | }): Observable<ResultList<VideoComment>> { | 55 | }): Observable<ResultList<VideoComment>> { |
56 | const { videoId, componentPagination, sort } = parameters | 56 | const { videoId, componentPagination, sort } = parameters |
57 | 57 | ||
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.html b/client/src/app/videos/+video-watch/comment/video-comments.component.html index 5fabb7dfe..e284eab0a 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.html +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.html | |||
@@ -10,6 +10,16 @@ | |||
10 | </div> | 10 | </div> |
11 | 11 | ||
12 | <my-feed [syndicationItems]="syndicationItems"></my-feed> | 12 | <my-feed [syndicationItems]="syndicationItems"></my-feed> |
13 | |||
14 | <div ngbDropdown class="d-inline-block ml-4"> | ||
15 | <button class="btn btn-sm btn-outline-secondary" id="dropdownSortComments" ngbDropdownToggle i18n> | ||
16 | Sort by | ||
17 | </button> | ||
18 | <div ngbDropdownMenu aria-labelledby="dropdownSortComments"> | ||
19 | <button (click)="handleSortChange('-createdAt')" ngbDropdownItem i18n>Most recent first (default)</button> | ||
20 | <button (click)="handleSortChange('-totalReplies')" ngbDropdownItem i18n>Most replies first</button> | ||
21 | </div> | ||
22 | </div> | ||
13 | </div> | 23 | </div> |
14 | 24 | ||
15 | <ng-template [ngIf]="video.commentsEnabled === true"> | 25 | <ng-template [ngIf]="video.commentsEnabled === true"> |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.scss b/client/src/app/videos/+video-watch/comment/video-comments.component.scss index dde10b068..9e3682295 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.scss | |||
@@ -23,6 +23,12 @@ | |||
23 | margin-right: 0; | 23 | margin-right: 0; |
24 | } | 24 | } |
25 | 25 | ||
26 | #dropdownSortComments { | ||
27 | font-weight: 600; | ||
28 | text-transform: uppercase; | ||
29 | border: none; | ||
30 | } | ||
31 | |||
26 | my-feed { | 32 | my-feed { |
27 | display: inline-block; | 33 | display: inline-block; |
28 | margin-left: 5px; | 34 | margin-left: 5px; |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.ts b/client/src/app/videos/+video-watch/comment/video-comments.component.ts index e81401553..974c61d6c 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.ts | |||
@@ -6,7 +6,7 @@ import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/v | |||
6 | import { AuthService } from '../../../core/auth' | 6 | import { AuthService } from '../../../core/auth' |
7 | import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model' | 7 | import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model' |
8 | import { User } from '../../../shared/users' | 8 | import { User } from '../../../shared/users' |
9 | import { VideoSortField } from '../../../shared/video/sort-field.type' | 9 | import { CommentSortField } from '../../../shared/video/sort-field.type' |
10 | import { VideoDetails } from '../../../shared/video/video-details.model' | 10 | import { VideoDetails } from '../../../shared/video/video-details.model' |
11 | import { VideoComment } from './video-comment.model' | 11 | import { VideoComment } from './video-comment.model' |
12 | import { VideoCommentService } from './video-comment.service' | 12 | import { VideoCommentService } from './video-comment.service' |
@@ -28,7 +28,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
28 | 28 | ||
29 | comments: VideoComment[] = [] | 29 | comments: VideoComment[] = [] |
30 | highlightedThread: VideoComment | 30 | highlightedThread: VideoComment |
31 | sort: VideoSortField = '-createdAt' | 31 | sort: CommentSortField = '-createdAt' |
32 | componentPagination: ComponentPagination = { | 32 | componentPagination: ComponentPagination = { |
33 | currentPage: 1, | 33 | currentPage: 1, |
34 | itemsPerPage: 10, | 34 | itemsPerPage: 10, |
@@ -152,6 +152,13 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
152 | this.viewReplies(commentTree.comment.id) | 152 | this.viewReplies(commentTree.comment.id) |
153 | } | 153 | } |
154 | 154 | ||
155 | handleSortChange (sort: CommentSortField) { | ||
156 | if (this.sort === sort) return | ||
157 | |||
158 | this.sort = sort | ||
159 | this.resetVideo() | ||
160 | } | ||
161 | |||
155 | handleTimestampClicked (timestamp: number) { | 162 | handleTimestampClicked (timestamp: number) { |
156 | this.timestampClicked.emit(timestamp) | 163 | this.timestampClicked.emit(timestamp) |
157 | } | 164 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 79fcd0edf..1b7b94d74 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -46,7 +46,7 @@ const SORTABLE_COLUMNS = { | |||
46 | VIDEO_ABUSES: [ 'id', 'createdAt', 'state' ], | 46 | VIDEO_ABUSES: [ 'id', 'createdAt', 'state' ], |
47 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], | 47 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], |
48 | VIDEO_IMPORTS: [ 'createdAt' ], | 48 | VIDEO_IMPORTS: [ 'createdAt' ], |
49 | VIDEO_COMMENT_THREADS: [ 'createdAt' ], | 49 | VIDEO_COMMENT_THREADS: [ 'createdAt', 'totalReplies' ], |
50 | VIDEO_RATES: [ 'createdAt' ], | 50 | VIDEO_RATES: [ 'createdAt' ], |
51 | BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], | 51 | BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], |
52 | FOLLOWERS: [ 'createdAt', 'state', 'score' ], | 52 | FOLLOWERS: [ 'createdAt', 'state', 'score' ], |
diff --git a/server/models/utils.ts b/server/models/utils.ts index b53a52a05..4199cc443 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -22,6 +22,19 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt | |||
22 | return [ [ finalField, direction ], lastSort ] | 22 | return [ [ finalField, direction ], lastSort ] |
23 | } | 23 | } |
24 | 24 | ||
25 | function getCommentSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { | ||
26 | const { direction, field } = buildDirectionAndField(value) | ||
27 | |||
28 | if (field === 'totalReplies') { | ||
29 | return [ | ||
30 | [ Sequelize.literal('"totalReplies"'), direction ], | ||
31 | lastSort | ||
32 | ] | ||
33 | } | ||
34 | |||
35 | return getSort(value, lastSort) | ||
36 | } | ||
37 | |||
25 | function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { | 38 | function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
26 | const { direction, field } = buildDirectionAndField(value) | 39 | const { direction, field } = buildDirectionAndField(value) |
27 | 40 | ||
@@ -167,6 +180,7 @@ export { | |||
167 | SortType, | 180 | SortType, |
168 | buildLocalAccountIdsIn, | 181 | buildLocalAccountIdsIn, |
169 | getSort, | 182 | getSort, |
183 | getCommentSort, | ||
170 | getVideoSort, | 184 | getVideoSort, |
171 | getBlacklistSort, | 185 | getBlacklistSort, |
172 | createSimilarityAttribute, | 186 | createSimilarityAttribute, |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 869a42afe..28f011b03 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -6,7 +6,7 @@ import { isActivityPubUrlValid } from '../../helpers/custom-validators/activityp | |||
6 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | 6 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
7 | import { AccountModel } from '../account/account' | 7 | import { AccountModel } from '../account/account' |
8 | import { ActorModel } from '../activitypub/actor' | 8 | import { ActorModel } from '../activitypub/actor' |
9 | import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' | 9 | import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils' |
10 | import { VideoModel } from './video' | 10 | import { VideoModel } from './video' |
11 | import { VideoChannelModel } from './video-channel' | 11 | import { VideoChannelModel } from './video-channel' |
12 | import { getServerActor } from '../../helpers/utils' | 12 | import { getServerActor } from '../../helpers/utils' |
@@ -259,7 +259,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
259 | const query = { | 259 | const query = { |
260 | offset: start, | 260 | offset: start, |
261 | limit: count, | 261 | limit: count, |
262 | order: getSort(sort), | 262 | order: getCommentSort(sort), |
263 | where: { | 263 | where: { |
264 | videoId, | 264 | videoId, |
265 | inReplyToCommentId: null, | 265 | inReplyToCommentId: null, |