Rich reporter field and video embed in moderation abuse list
authorRigel Kent <sendmemail@rigelk.eu>
Mon, 13 Apr 2020 17:57:57 +0000 (19:57 +0200)
committerRigel Kent <sendmemail@rigelk.eu>
Mon, 13 Apr 2020 17:57:57 +0000 (19:57 +0200)
client/src/app/+admin/moderation/moderation.component.scss
client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
client/src/app/+admin/users/user-list/user-list.component.html
client/src/app/+admin/users/user-list/user-list.component.scss
client/src/app/+admin/users/user-list/user-list.component.ts
client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts
client/src/sass/include/_mixins.scss

index 13b019c5bdd34dd506e900dd2dbaf0f156c8f18f..89e9b47d381b67a2ee076975042967f0adf31594 100644 (file)
@@ -8,18 +8,35 @@
 
 .moderation-expanded-label {
   font-weight: $font-semibold;
-  min-width: 200px;
   display: inline-block;
   vertical-align: top;
+  text-align: right;
 }
 
 .moderation-expanded-text {
   display: inline-block;
+  word-wrap: break-word;
+
+  ::ng-deep p:last-child {
+    margin-bottom: 0px !important;
+  }
 }
 
-.moderation-expanded {
-  word-wrap: break-word;
-  overflow: visible !important;
-  text-overflow: unset !important;
-  white-space: unset !important;
+.screenratio {
+  position: relative;
+  width: 100%;
+  height: 0;
+  padding-bottom: 56%;
+
+  ::ng-deep iframe {
+    position: absolute;
+    width: 100% !important;
+    height: 100% !important;
+    left: 0;
+    top: 0;
+  }
+}
+
+.chip {
+  @include chip;
 }
index cf7b61d2aa86424e15cd3b0ac0757684bebb8541..155d10ddab4ef64fe41286db4d5576333f1def31 100644 (file)
       </td>
 
       <td>
-        <a [href]="videoAbuse.reporterAccount.url" i18n-title title="Go to the account" target="_blank" rel="noopener noreferrer">
-          {{ createByString(videoAbuse.reporterAccount) }}
+        <a [href]="videoAbuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
+          <div class="chip two-lines">
+            <img
+              class="avatar"
+              [src]="videoAbuse.reporterAccount.avatar.path"
+              (error)="switchToDefaultAvatar($event)"
+              alt="Avatar"
+            >
+            <div>
+              {{ videoAbuse.reporterAccount.displayName }}
+              <span class="text-muted">{{ createByString(videoAbuse.reporterAccount) }}</span>
+            </div>
+          </div>
         </a>
       </td>
 
 
   <ng-template pTemplate="rowexpansion" let-videoAbuse>
       <tr>
-        <td class="moderation-expanded" colspan="6">
-          <div>
-            <span i18n class="moderation-expanded-label">Reason:</span>
-            <span class="moderation-expanded-text" [innerHTML]="videoAbuse.reasonHtml"></span>
-          </div>
-          <div *ngIf="videoAbuse.moderationComment">
-            <span i18n class="moderation-expanded-label">Moderation comment:</span>
-            <span class="moderation-expanded-text" [innerHTML]="videoAbuse.moderationCommentHtml"></span>
+        <td class="expand-cell" colspan="6">
+          <div class="d-flex">
+            <div class="col-8">
+              <div class="d-flex">
+                <span class="col-3 moderation-expanded-label" i18n>Reason:</span>
+                <span class="col-9 moderation-expanded-text" [innerHTML]="videoAbuse.reasonHtml"></span>
+              </div>
+              <div class="mt-3 d-flex" *ngIf="videoAbuse.moderationComment">
+                <span class="col-3 moderation-expanded-label" i18n>Note:</span>
+                <span class="col-9 moderation-expanded-text" [innerHTML]="videoAbuse.moderationCommentHtml"></span>
+              </div>
+            </div>
+  
+            <div class="col-4">
+              <div class="screenratio" [innerHTML]="videoAbuse.embedHtml"></div>
+            </div>
           </div>
         </td>
       </tr>
index 2d991aae93e2d3568cfd1df87dadac8e03662187..b135792a7108694a7ded539f8180f30571aff231 100644 (file)
@@ -10,6 +10,10 @@ import { ConfirmService } from '../../../core/index'
 import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
 import { Video } from '../../../shared/video/video.model'
 import { MarkdownService } from '@app/shared/renderer'
+import { Actor } from '@app/shared/actor/actor.model'
+import { buildVideoLink, buildVideoEmbed } from 'src/assets/player/utils'
+import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
+import { DomSanitizer } from '@angular/platform-browser'
 
 @Component({
   selector: 'my-video-abuse-list',
@@ -32,7 +36,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
     private videoAbuseService: VideoAbuseService,
     private confirmService: ConfirmService,
     private i18n: I18n,
-    private markdownRenderer: MarkdownService
+    private markdownRenderer: MarkdownService,
+    private sanitizer: DomSanitizer
   ) {
     super()
 
@@ -42,8 +47,14 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
         handler: videoAbuse => this.removeVideoAbuse(videoAbuse)
       },
       {
-        label: this.i18n('Update moderation comment'),
-        handler: videoAbuse => this.openModerationCommentModal(videoAbuse)
+        label: this.i18n('Add note'),
+        handler: videoAbuse => this.openModerationCommentModal(videoAbuse),
+        isDisplayed: videoAbuse => !videoAbuse.moderationComment
+      },
+      {
+        label: this.i18n('Update note'),
+        handler: videoAbuse => this.openModerationCommentModal(videoAbuse),
+        isDisplayed: videoAbuse => !!videoAbuse.moderationComment
       },
       {
         label: this.i18n('Mark as accepted'),
@@ -90,6 +101,19 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
     return Video.buildClientUrl(videoAbuse.video.uuid)
   }
 
+  getVideoEmbed (videoAbuse: VideoAbuse) {
+    const absoluteAPIUrl = 'http://localhost:9000' || getAbsoluteAPIUrl()
+    const embedUrl = buildVideoLink({
+      baseUrl: absoluteAPIUrl + '/videos/embed/' + videoAbuse.video.uuid,
+      warningTitle: false
+    })
+    return buildVideoEmbed(embedUrl)
+  }
+
+  switchToDefaultAvatar ($event: Event) {
+    ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
+  }
+
   async removeVideoAbuse (videoAbuse: VideoAbuse) {
     const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse report?'), this.i18n('Delete'))
     if (res === false) return
@@ -125,7 +149,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
                    for (const abuse of this.videoAbuses) {
                      Object.assign(abuse, {
                        reasonHtml: await this.toHtml(abuse.reason),
-                       moderationCommentHtml: await this.toHtml(abuse.moderationComment)
+                       moderationCommentHtml: await this.toHtml(abuse.moderationComment),
+                       embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse))
                      })
                    }
 
index a39e7639e1d031165e0b0e26b38b862b6dafe600..320172e374b67da1a2e20c80e21f0080017bf09a 100644 (file)
@@ -42,9 +42,9 @@
 
   <ng-template pTemplate="rowexpansion" let-videoBlacklist>
     <tr>
-      <td class="moderation-expanded" colspan="6">
-        <span i18n class="moderation-expanded-label">Blacklist reason:</span>
-        <span class="moderation-expanded-text" [innerHTML]="videoBlacklist.reasonHtml"></span>
+      <td class="expand-cell" colspan="6">
+        <span class="col-2 moderation-expanded-label" i18n>Blacklist reason:</span>
+        <span class="col-9 moderation-expanded-text" [innerHTML]="videoBlacklist.reasonHtml"></span>
       </td>
     </tr>
   </ng-template>
index 15bdb6398b4538824b8238bf94dc442e9fe23f40..e9ff61283a00e0ea8f66cb6e8f39d477a01a20e5 100644 (file)
   <ng-template pTemplate="body" let-expanded="expanded" let-user>
 
     <tr [pSelectableRow]="user" [ngClass]="{ banned: user.blocked }">
-      <td class="expand-cell">
+      <td>
         <p-tableCheckbox [value]="user"></p-tableCheckbox>
       </td>
 
-      <td>
+      <td class="expand-cell">
         <span *ngIf="user.blockedReason" class="expander" [pRowToggler]="user">
           <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
         </span>
       </td>
 
       <td>
-        <a i18n-title title="Go to the account page" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]">
-          {{ user.username }}
+        <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]">
+          <div class="chip two-lines">
+            <img
+              class="avatar"
+              [src]="user?.account?.avatar?.path"
+              (error)="switchToDefaultAvatar($event)"
+              alt="Avatar"
+            >
+            <div>
+              {{ user.account.displayName }}
+              <span class="text-muted">{{ user.username }}</span>
+            </div>
+          </div>
           <span i18n *ngIf="user.blocked" class="banned-info">(banned)</span>
         </a>
       </td>
index 40f08027f5bf031396eece6605a60e463e5529b3..99b22aaea1ab78891e7cec4d412b5aec3547baf3 100644 (file)
@@ -29,3 +29,7 @@ p-tableCheckbox {
   position: relative;
   top: -2.5px;
 }
+
+.chip {
+  @include chip;
+}
index 0de123e93a7a4a153852d340b369f02090a6277d..667a0e1fd77a7f457dc22ce672219a946fb0cf8b 100644 (file)
@@ -7,6 +7,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
 import { ServerConfig, User } from '../../../../../../shared'
 import { UserBanModalComponent } from '@app/shared/moderation'
 import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
+import { Actor } from '@app/shared/actor/actor.model'
 
 @Component({
   selector: 'my-user-list',
@@ -105,6 +106,10 @@ export class UserListComponent extends RestTable implements OnInit {
     this.loadData()
   }
 
+  switchToDefaultAvatar ($event: Event) {
+    ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
+  }
+
   async unbanUsers (users: User[]) {
     const message = this.i18n('Do you really want to unban {{num}} users?', { num: users.length })
 
index 153fc01270977f1e1a18a711fb3bbb23521166a3..75d6d8acd0c4f21868e45fd0100d92b3171d6035 100644 (file)
@@ -56,7 +56,7 @@ export class MyAccountVideoChannelsComponent implements OnInit {
           display: false,
           ticks: {
             min: Math.max(0, this.videoChannelsMinimumDailyViews - (3 * this.videoChannelsMaximumDailyViews / 100)),
-            max: this.videoChannelsMaximumDailyViews
+            max: Math.max(1, this.videoChannelsMaximumDailyViews)
           }
         }]
       },
index 975072637b8a93e6be807cdede79e11908ecf043..3c420f547ba9b40dd37ec9c73a02417898171e75 100644 (file)
     }
   }
 }
+
+@mixin chip {
+  $avatar-height: 1.2rem;
+
+  align-items: center;
+  border-radius: 5rem;
+  display: inline-flex;
+  font-size: 90%;
+  color: var(--mainForegroundColor);
+  height: $avatar-height;
+  line-height: .8rem;
+  margin: .1rem;
+  max-width: 320px;
+  overflow: hidden;
+  padding: .2rem .4rem;
+  text-decoration: none;
+  text-overflow: ellipsis;
+  vertical-align: middle;
+  white-space: nowrap;
+
+  .avatar {
+    margin-left: -.4rem;
+    margin-right: .2rem;
+    height: $avatar-height;
+    width: $avatar-height;
+
+    border-radius: 50%;
+    display: inline-block;
+    line-height: 1.25;
+    position: relative;
+    vertical-align: middle;
+  }
+
+  &.two-lines {
+    $avatar-height: 1.8rem;
+
+    height: $avatar-height;
+
+    .avatar {
+      height: $avatar-height;
+      width: $avatar-height;
+    }
+
+    div {
+      display: flex;
+      flex-direction: column;
+      font-size: 80%;
+      height: $avatar-height;
+      margin-left: .1rem;
+      margin-right: .1rem;
+      justify-content: center;
+    }
+  }
+}