<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>
<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>
@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 {
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'
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
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)
-<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>
+
+ <div class="comment-actions">
+ <div *ngIf="isUserLoggedIn()" (click)="onWantToReply()" class="comment-action-reply">Reply</div>
+ </div>
- <my-video-comment-add
- *ngIf="isUserLoggedIn() && inReplyToCommentId === comment.id" [video]="video" [parentComment]="comment" [focusOnInit]="true"
- (commentCreated)="onCommentReplyCreated($event)"
- ></my-video-comment-add>
+ <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 *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>
@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 {
+ flex-grow: 1;
+
+ .comment-account-date {
+ display: flex;
+ margin-bottom: 4px;
- .comment-action-reply {
- color: #585858;
- cursor: pointer;
+ .comment-account {
+ font-weight: $font-bold;
+ }
+
+ .comment-date {
+ color: #585858;
+ margin-left: 10px;
+ }
}
- }
-}
-.children {
- margin-left: 20px;
+ .comment-actions {
+ margin: 10px 0;
- .comment {
- margin-top: 15px;
+ .comment-action-reply {
+ color: #585858;
+ cursor: pointer;
+ }
+ }
}
}
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'
@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) {
onResetReply () {
this.resetReply.emit()
}
+
+ getAvatarUrl (account: AccountInterface) {
+ return Account.GET_ACCOUNT_AVATAR_URL(account)
+ }
}
+import { Account } from '../../../../../../shared/models/actors'
import { VideoComment as VideoCommentServerModel } from '../../../../../../shared/models/videos/video-comment.model'
export class VideoComment implements VideoCommentServerModel {
videoId: number
createdAt: Date | string
updatedAt: Date | string
- account: {
- name: string
- host: string
- }
+ account: Account
totalReplies: number
by: string
<my-video-comment-add
*ngIf="isUserLoggedIn()"
[video]="video"
+ [user]="user"
(commentCreated)="onCommentThreadCreated($event)"
></my-video-comment-add>
font-weight: $font-semibold;
font-size: 15px;
cursor: pointer;
+ margin-left: 56px;
}
.glyphicon, .comment-thread-loading {
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'
<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>
font-size: 13px;
img {
- width: 16px;
- height: 16px;
- margin-left: 3px;
+ @include avatar(18px);
+
+ margin-left: 7px;
}
}
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) {
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' &&
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'
{
model: () => ServerModel,
required: false
+ },
+ {
+ model: () => AvatarModel,
+ required: false
}
]
}
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
}
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
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 () {
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
+import { Account } from '../actors'
+
export interface VideoComment {
id: number
url: string
createdAt: Date | string
updatedAt: Date | string
totalReplies: number
- account: {
- name: string
- host: string
- }
+ account: Account
}
export interface VideoCommentThreadTree {