Add avatar in comments

This commit is contained in:
Chocobozzz 2018-01-03 17:25:47 +01:00
parent 265ba139eb
commit cf117aaafc
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
17 changed files with 143 additions and 88 deletions

View File

@ -1,6 +1,8 @@
<menu>
<div *ngIf="isLoggedIn" class="logged-in-block">
<img [src]="getUserAvatarUrl()" alt="Avatar" />
<a routerLink="/account/settings">
<img [src]="getUserAvatarUrl()" alt="Avatar" />
</a>
<div class="logged-in-info">
<a routerLink="/account/settings" class="logged-in-username">{{ user.username }}</a>

View File

@ -1,9 +1,13 @@
<form novalidate [formGroup]="form" (ngSubmit)="formValidated()">
<div class="form-group">
<textarea placeholder="Add comment..." formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }" #textarea>
</textarea>
<div *ngIf="formErrors.text" class="form-error">
{{ formErrors.text }}
<div class="avatar-and-textarea">
<img [src]="getUserAvatarUrl()" alt="Avatar" />
<div class="form-group">
<textarea placeholder="Add comment..." formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }" #textarea>
</textarea>
<div *ngIf="formErrors.text" class="form-error">
{{ formErrors.text }}
</div>
</div>
</div>

View File

@ -1,12 +1,25 @@
@import '_variables';
@import '_mixins';
.form-group {
.avatar-and-textarea {
display: flex;
margin-bottom: 10px;
}
textarea {
@include peertube-textarea(100%, 150px);
img {
@include avatar(36px);
vertical-align: top;
margin-right: 20px;
}
.form-group {
flex-grow: 1;
margin: 0;
textarea {
@include peertube-textarea(100%, 60px);
}
}
}
.submit-comment {

View File

@ -5,6 +5,7 @@ import { Observable } from 'rxjs/Observable'
import { VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model'
import { FormReactive } from '../../../shared'
import { VIDEO_COMMENT_TEXT } from '../../../shared/forms/form-validators/video-comment'
import { User } from '../../../shared/users'
import { Video } from '../../../shared/video/video.model'
import { VideoComment } from './video-comment.model'
import { VideoCommentService } from './video-comment.service'
@ -15,6 +16,7 @@ import { VideoCommentService } from './video-comment.service'
styleUrls: ['./video-comment-add.component.scss']
})
export class VideoCommentAddComponent extends FormReactive implements OnInit {
@Input() user: User
@Input() video: Video
@Input() parentComment: VideoComment
@Input() focusOnInit = false
@ -79,6 +81,10 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit {
return this.form.value['text']
}
getUserAvatarUrl () {
return this.user.getAvatarUrl()
}
private addCommentReply (commentCreate: VideoCommentCreate) {
return this.videoCommentService
.addCommentReply(this.video.id, this.parentComment.id, commentCreate)

View File

@ -1,29 +1,37 @@
<div class="comment">
<div class="comment-account-date">
<div class="comment-account">{{ comment.by }}</div>
<div class="comment-date">{{ comment.createdAt | myFromNow }}</div>
</div>
<div>{{ comment.text }}</div>
<div class="root-comment">
<img [src]="getAvatarUrl(comment.account)" alt="Avatar" />
<div class="comment-actions">
<div *ngIf="isUserLoggedIn()" (click)="onWantToReply()" class="comment-action-reply">Reply</div>
</div>
<div class="comment">
<div class="comment-account-date">
<div class="comment-account">{{ comment.by }}</div>
<div class="comment-date">{{ comment.createdAt | myFromNow }}</div>
</div>
<div>{{ comment.text }}</div>
<my-video-comment-add
*ngIf="isUserLoggedIn() && inReplyToCommentId === comment.id" [video]="video" [parentComment]="comment" [focusOnInit]="true"
(commentCreated)="onCommentReplyCreated($event)"
></my-video-comment-add>
<div class="comment-actions">
<div *ngIf="isUserLoggedIn()" (click)="onWantToReply()" class="comment-action-reply">Reply</div>
</div>
<div *ngIf="commentTree" class="children">
<div *ngFor="let commentChild of commentTree.children">
<my-video-comment
[comment]="commentChild.comment"
[video]="video"
[inReplyToCommentId]="inReplyToCommentId"
[commentTree]="commentChild"
(wantedToReply)="onWantedToReply($event)"
(resetReply)="onResetReply()"
></my-video-comment>
<my-video-comment-add
*ngIf="isUserLoggedIn() && inReplyToCommentId === comment.id"
[user]="user"
[video]="video"
[parentComment]="comment"
[focusOnInit]="true"
(commentCreated)="onCommentReplyCreated($event)"
></my-video-comment-add>
<div *ngIf="commentTree" class="children">
<div *ngFor="let commentChild of commentTree.children">
<my-video-comment
[comment]="commentChild.comment"
[video]="video"
[inReplyToCommentId]="inReplyToCommentId"
[commentTree]="commentChild"
(wantedToReply)="onWantedToReply($event)"
(resetReply)="onResetReply()"
></my-video-comment>
</div>
</div>
</div>
</div>

View File

@ -1,38 +1,41 @@
@import '_variables';
@import '_mixins';
.comment {
.root-comment {
font-size: 15px;
margin-top: 30px;
display: flex;
.comment-account-date {
display: flex;
margin-bottom: 4px;
img {
@include avatar(36px);
.comment-account {
font-weight: $font-bold;
}
.comment-date {
color: #585858;
margin-left: 10px;
}
margin-top: 5px;
margin-right: 20px;
}
.comment-actions {
margin: 10px 0;
.comment-action-reply {
color: #585858;
cursor: pointer;
}
}
}
.children {
margin-left: 20px;
.comment {
margin-top: 15px;
flex-grow: 1;
.comment-account-date {
display: flex;
margin-bottom: 4px;
.comment-account {
font-weight: $font-bold;
}
.comment-date {
color: #585858;
margin-left: 10px;
}
}
.comment-actions {
margin: 10px 0;
.comment-action-reply {
color: #585858;
cursor: pointer;
}
}
}
}

View File

@ -1,6 +1,8 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { Account as AccountInterface } from '../../../../../../shared/models/actors'
import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model'
import { AuthService } from '../../../core/auth'
import { Account } from '../../../shared/account/account.model'
import { Video } from '../../../shared/video/video.model'
import { VideoComment } from './video-comment.model'
@ -18,7 +20,10 @@ export class VideoCommentComponent {
@Output() wantedToReply = new EventEmitter<VideoComment>()
@Output() resetReply = new EventEmitter()
constructor (private authService: AuthService) {
constructor (private authService: AuthService) {}
get user () {
return this.authService.getUser()
}
onCommentReplyCreated (createdComment: VideoComment) {
@ -52,4 +57,8 @@ export class VideoCommentComponent {
onResetReply () {
this.resetReply.emit()
}
getAvatarUrl (account: AccountInterface) {
return Account.GET_ACCOUNT_AVATAR_URL(account)
}
}

View File

@ -1,3 +1,4 @@
import { Account } from '../../../../../../shared/models/actors'
import { VideoComment as VideoCommentServerModel } from '../../../../../../shared/models/videos/video-comment.model'
export class VideoComment implements VideoCommentServerModel {
@ -9,10 +10,7 @@ export class VideoComment implements VideoCommentServerModel {
videoId: number
createdAt: Date | string
updatedAt: Date | string
account: {
name: string
host: string
}
account: Account
totalReplies: number
by: string

View File

@ -7,6 +7,7 @@
<my-video-comment-add
*ngIf="isUserLoggedIn()"
[video]="video"
[user]="user"
(commentCreated)="onCommentThreadCreated($event)"
></my-video-comment-add>

View File

@ -5,6 +5,7 @@
font-weight: $font-semibold;
font-size: 15px;
cursor: pointer;
margin-left: 56px;
}
.glyphicon, .comment-thread-loading {

View File

@ -6,7 +6,6 @@ import { ComponentPagination } from '../../../shared/rest/component-pagination.m
import { User } from '../../../shared/users'
import { SortField } from '../../../shared/video/sort-field.type'
import { VideoDetails } from '../../../shared/video/video-details.model'
import { Video } from '../../../shared/video/video.model'
import { VideoComment } from './video-comment.model'
import { VideoCommentService } from './video-comment.service'

View File

@ -14,14 +14,16 @@
<div class="video-info-actions">
<div
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()"
class="action-button action-button-like">
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()"
class="action-button action-button-like"
>
<span class="icon icon-like" title="Like this video" ></span>
</div>
<div
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
class="action-button action-button-dislike">
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
class="action-button action-button-dislike"
>
<span class="icon icon-dislike" title="Dislike this video"></span>
</div>

View File

@ -176,9 +176,9 @@
font-size: 13px;
img {
width: 16px;
height: 16px;
margin-left: 3px;
@include avatar(18px);
margin-left: 7px;
}
}

View File

@ -1,8 +1,6 @@
import * as validator from 'validator'
import { CONSTRAINTS_FIELDS } from '../../../initializers'
import { isAccountNameValid } from '../accounts'
import { exists } from '../misc'
import { isVideoChannelNameValid } from '../video-channels'
import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
function isActorEndpointsObjectValid (endpointObject: any) {
@ -32,10 +30,6 @@ function isActorPreferredUsernameValid (preferredUsername: string) {
return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
}
function isActorNameValid (name: string) {
return isAccountNameValid(name) || isVideoChannelNameValid(name)
}
function isActorPrivateKeyValid (privateKey: string) {
return exists(privateKey) &&
typeof privateKey === 'string' &&

View File

@ -9,6 +9,7 @@ import { isActivityPubUrlValid } from '../../helpers/custom-validators/activityp
import { CONSTRAINTS_FIELDS } from '../../initializers'
import { AccountModel } from '../account/account'
import { ActorModel } from '../activitypub/actor'
import { AvatarModel } from '../avatar/avatar'
import { ServerModel } from '../server/server'
import { getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
@ -46,6 +47,10 @@ enum ScopeNames {
{
model: () => ServerModel,
required: false
},
{
model: () => AvatarModel,
required: false
}
]
}
@ -243,10 +248,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
createdAt: this.createdAt,
updatedAt: this.updatedAt,
totalReplies: this.get('totalReplies') || 0,
account: {
name: this.Account.name,
host: this.Account.Actor.getHost()
}
account: this.Account.toFormattedJSON()
} as VideoComment
}

View File

@ -3,7 +3,11 @@
import * as chai from 'chai'
import 'mocha'
import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
import { dateIsValid, flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../utils/index'
import { testVideoImage } from '../../utils'
import {
dateIsValid, flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, updateMyAvatar,
uploadVideo
} from '../../utils/index'
import {
addVideoCommentReply, addVideoCommentThread, getVideoCommentThreads,
getVideoThreadComments
@ -29,6 +33,12 @@ describe('Test video comments', function () {
const res = await uploadVideo(server.url, server.accessToken, {})
videoUUID = res.body.video.uuid
videoId = res.body.video.id
await updateMyAvatar({
url: server.url,
accessToken: server.accessToken,
fixture: 'avatar.png'
})
})
it('Should not have threads on this video', async function () {
@ -70,6 +80,10 @@ describe('Test video comments', function () {
expect(comment.id).to.equal(comment.threadId)
expect(comment.account.name).to.equal('root')
expect(comment.account.host).to.equal('localhost:9001')
const test = await testVideoImage(server.url, 'avatar-resized', comment.account.avatar.path, '.png')
expect(test).to.equal(true)
expect(comment.totalReplies).to.equal(0)
expect(dateIsValid(comment.createdAt as string)).to.be.true
expect(dateIsValid(comment.updatedAt as string)).to.be.true

View File

@ -1,3 +1,5 @@
import { Account } from '../actors'
export interface VideoComment {
id: number
url: string
@ -8,10 +10,7 @@ export interface VideoComment {
createdAt: Date | string
updatedAt: Date | string
totalReplies: number
account: {
name: string
host: string
}
account: Account
}
export interface VideoCommentThreadTree {