Add avatar in comments
authorChocobozzz <me@florianbigard.com>
Wed, 3 Jan 2018 16:25:47 +0000 (17:25 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 3 Jan 2018 16:35:00 +0000 (17:35 +0100)
17 files changed:
client/src/app/menu/menu.component.html
client/src/app/videos/+video-watch/comment/video-comment-add.component.html
client/src/app/videos/+video-watch/comment/video-comment-add.component.scss
client/src/app/videos/+video-watch/comment/video-comment-add.component.ts
client/src/app/videos/+video-watch/comment/video-comment.component.html
client/src/app/videos/+video-watch/comment/video-comment.component.scss
client/src/app/videos/+video-watch/comment/video-comment.component.ts
client/src/app/videos/+video-watch/comment/video-comment.model.ts
client/src/app/videos/+video-watch/comment/video-comments.component.html
client/src/app/videos/+video-watch/comment/video-comments.component.scss
client/src/app/videos/+video-watch/comment/video-comments.component.ts
client/src/app/videos/+video-watch/video-watch.component.html
client/src/app/videos/+video-watch/video-watch.component.scss
server/helpers/custom-validators/activitypub/actor.ts
server/models/video/video-comment.ts
server/tests/api/videos/video-comments.ts
shared/models/videos/video-comment.model.ts

index 5ea859fd2c144dbfaecc599423bc98055fdae07f..d138d2ba7234fa89dbc45a834df6c2e8ad16eb73 100644 (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>
index 365fc8f6c1dca3f650e871754a8d3b1fbfbb4306..0eaa0d447b4554df83de46bec175782d0e0b85cb 100644 (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>
 
index 9661062e8f3e3cb1007efff70e60b507835f1587..37097da724db20cf795f73c66554530c75d995da 100644 (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 {
index a6525987eee084d437c18d86a1fb691e6bcbe2b4..27655eca79c018db8f25b71feaece704faa4633a 100644 (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)
index ffaf722cd3ff360b4842fe4ae04a6cfec3cde41a..e9c23929caeac0da37a622bfce15c63dbc82c6ad 100644 (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>
+
+    <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>
index 7e1a32f480c5d034e28d4a2753c5c3e3bd243c1e..aae03ab6de1538b68f9210011760efacc74ae7de 100644 (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 {
+    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;
+      }
+    }
   }
 }
index 5af6e33353d2e0954d7740d509e721cd2d139174..b305c639a7ee25f1d5df7677cf18bbaa227a6832 100644 (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)
+  }
 }
index df7d5244c4534fe219e8b4395cbb195f8bf37f88..abecae3032b1b16573437f1a544e4b6ca00e9f8d 100644 (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
index 078900e0659aa6035f6e4fa32e729601dac69153..4a424807302564646db1e7d812cfe013045323a8 100644 (file)
@@ -7,6 +7,7 @@
     <my-video-comment-add
       *ngIf="isUserLoggedIn()"
       [video]="video"
+      [user]="user"
       (commentCreated)="onCommentThreadCreated($event)"
     ></my-video-comment-add>
 
index 2f6e4663bf5567a930315a5105080702e3170cb5..be122eb2c13c02c9427f4d8e66195988f6084bb0 100644 (file)
@@ -5,6 +5,7 @@
   font-weight: $font-semibold;
   font-size: 15px;
   cursor: pointer;
+  margin-left: 56px;
 }
 
 .glyphicon, .comment-thread-loading {
index 4d801c97057b4a1e67a9055da9535e79aeb9e3d7..1230725c1e3fc60d79fd7775a87a28aef4d7dc7e 100644 (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'
 
index 48d1bb47426dd9ce76d3e165d9259bc6376eba93..514a86e280cc2d801ea3f885fb2eb132c2a28c08 100644 (file)
 
         <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>
 
index c101aa04ede5c12437b577d8a12a2f1fb8aef34c..7ebdfc0c4b7fffc8726e54f8579069a46f55d803 100644 (file)
       font-size: 13px;
 
       img {
-        width: 16px;
-        height: 16px;
-        margin-left: 3px;
+        @include avatar(18px);
+
+        margin-left: 7px;
       }
     }
 
index 8820bb2a453e326aebabdb9d09f5647f9be82eb2..e1a4b5b8f8a40d880b281ebaa1757e2708aeea72 100644 (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' &&
index 63675c20bf688abde380b46fc6820a30c6c782c2..d2d8945c3cd8eec776735dbe2ed7af364b02626a 100644 (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
   }
 
index 3b6578f04137c0988f1570292540aa08d2796e04..604a3027de73daebe7a7ccccdec4e2cd45826f61 100644 (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
index d572927c27f414ba0d17299c7c11211cc6250dae..7ac4024fbd7c69c7e95ee94e9f27aa6fa4099262 100644 (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 {