Add action dropdown descriptions
authorRigel Kent <sendmemail@rigelk.eu>
Wed, 15 Jan 2020 18:25:51 +0000 (19:25 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Tue, 21 Jan 2020 10:59:41 +0000 (11:59 +0100)
15 files changed:
client/src/app/+accounts/accounts.component.html
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+admin/users/user-list/user-list.component.html
client/src/app/+admin/users/user-list/user-list.component.ts
client/src/app/+my-account/my-account-history/my-account-history.component.html
client/src/app/app.component.ts
client/src/app/shared/buttons/action-dropdown.component.html
client/src/app/shared/buttons/action-dropdown.component.scss
client/src/app/shared/buttons/action-dropdown.component.ts
client/src/app/shared/moderation/user-moderation-dropdown.component.ts
client/src/app/shared/user-subscription/remote-subscribe.component.html
client/src/app/shared/user-subscription/subscribe-button.component.html
client/src/app/shared/user-subscription/subscribe-button.component.scss
client/src/app/shared/video/video-miniature.component.html
client/src/sass/primeng-custom.scss

index 367258a0618319b40396a4fac41ab43aa85e4cc9..9596d34af1a522fb41ebc3d4327f2938c5423b82 100644 (file)
@@ -23,7 +23,7 @@
           <span *ngIf="account.mutedServerByInstance" class="badge badge-danger" i18n>Instance muted by your instance</span>
 
           <my-user-moderation-dropdown
-            buttonSize="small" [account]="account" [user]="user" placement="bottom-right auto"
+            buttonSize="small" [account]="account" [user]="user" placement="bottom-left auto"
             (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()"
           ></my-user-moderation-dropdown>
         </div>
index e5340234b121450dc1ebc86a89093a796bcb9d82..915d60090c3b8ab8cbd7c4dec3209d59a3c1a225 100644 (file)
 
           <div class="form-group">
             <label i18n for="instanceModerationInformation">Moderation information</label><my-help helpType="markdownText"></my-help>
-            <div class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div>
+            <div i18n class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div>
 
             <my-markdown-textarea
               name="instanceModerationInformation" formControlName="moderationInformation" textareaWidth="500px" [previewColumn]="true"
 
           <div class="form-group">
             <label i18n for="instanceAdministrator">Who is behind the instance?</label>
-            <div class="label-small-info">A single person? A non-profit? A company?</div>
+            <div i18n class="label-small-info">A single person? A non-profit? A company?</div>
 
             <my-markdown-textarea
               name="instanceAdministrator" formControlName="administrator" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true"
 
           <div class="form-group">
             <label i18n for="instanceCreationReason">Why did you create this instance?</label>
-            <div class="label-small-info">To share your personal videos? To open registrations and allow people to upload what they want?</div>
+            <div i18n class="label-small-info">To share your personal videos? To open registrations and allow people to upload what they want?</div>
 
             <textarea
               id="instanceCreationReason" formControlName="creationReason" class="small"
 
           <div class="form-group">
             <label i18n for="instanceMaintenanceLifetime">How long do you plan to maintain this instance?</label>
-            <div class="label-small-info">It's important to know for users who want to register on your instance</div>
+            <div i18n class="label-small-info">It's important to know for users who want to register on your instance</div>
 
             <textarea
               id="instanceMaintenanceLifetime" formControlName="maintenanceLifetime" class="small"
 
           <div class="form-group">
             <label i18n for="instanceBusinessModel">How will you finance the PeerTube server?</label>
-            <div class="label-small-info">With your own funds? With users donations? Advertising?</div>
+            <div i18n class="label-small-info">With your own funds? With users donations? Advertising?</div>
 
             <textarea
               id="instanceBusinessModel" formControlName="businessModel" class="small"
 
           <div class="form-group">
             <label i18n for="instanceHardwareInformation">What server/hardware does the instance run on?</label>
-            <div class="label-small-info">2vCore 2GB RAM/or directly the link to the server you rent etc</div>
+            <div i18n class="label-small-info">2vCore 2GB RAM/or directly the link to the server you rent etc</div>
 
             <my-markdown-textarea
               name="instanceHardwareInformation" formControlName="hardwareInformation" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true"
index 885335313c24cc2f8cf58d38a019aa59c1ed0d39..ca05bac19170debe62340cfd7e334c1526237048 100644 (file)
@@ -66,7 +66,7 @@
         </a>
       </td>
 
-      <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus">{{ user.email }}</td>
+      <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus" [title]="user.email">{{ user.email }}</td>
 
       <ng-template #emailWithVerificationStatus>
         <td *ngIf="user.emailVerified === false; else emailVerifiedNotFalse" i18n-title title="User's email must be verified to login">
@@ -81,7 +81,7 @@
 
       <td>{{ user.videoQuotaUsed }} / {{ user.videoQuota }}</td>
       <td>{{ user.roleLabel }}</td>
-      <td>{{ user.createdAt }}</td>
+      <td [title]="user.createdAt">{{ user.createdAt }}</td>
       <td class="action-cell">
         <my-user-moderation-dropdown *ngIf="!isInSelectionMode()" [user]="user" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()">
         </my-user-moderation-dropdown>
index 1083ba29112c82d7f7dd69d28ad0368d379effe4..a596251f652a52b82c3d43813289ab7bc5d30d30 100644 (file)
@@ -23,7 +23,7 @@ export class UserListComponent extends RestTable implements OnInit {
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   selectedUsers: User[] = []
-  bulkUserActions: DropdownAction<User[]>[] = []
+  bulkUserActions: DropdownAction<User[]>[][] = []
 
   private serverConfig: ServerConfig
 
@@ -54,29 +54,35 @@ export class UserListComponent extends RestTable implements OnInit {
     this.initialize()
 
     this.bulkUserActions = [
-      {
-        label: this.i18n('Delete'),
-        handler: users => this.removeUsers(users),
-        isDisplayed: users => users.every(u => this.authUser.canManage(u))
-      },
-      {
-        label: this.i18n('Ban'),
-        handler: users => this.openBanUserModal(users),
-        isDisplayed: users => users.every(u => this.authUser.canManage(u) && u.blocked === false)
-      },
-      {
-        label: this.i18n('Unban'),
-        handler: users => this.unbanUsers(users),
-        isDisplayed: users => users.every(u => this.authUser.canManage(u) && u.blocked === true)
-      },
-      {
-        label: this.i18n('Set Email as Verified'),
-        handler: users => this.setEmailsAsVerified(users),
-        isDisplayed: users => {
-          return this.requiresEmailVerification &&
-            users.every(u => this.authUser.canManage(u) && !u.blocked && u.emailVerified === false)
+      [
+        {
+          label: this.i18n('Delete'),
+          description: this.i18n('Videos will be deleted, comments will be tombstoned.'),
+          handler: users => this.removeUsers(users),
+          isDisplayed: users => users.every(u => this.authUser.canManage(u))
+        },
+        {
+          label: this.i18n('Ban'),
+          description: this.i18n('Videos will be kept as private, comments will be kept as is.'),
+          handler: users => this.openBanUserModal(users),
+          isDisplayed: users => users.every(u => this.authUser.canManage(u) && u.blocked === false)
+        },
+        {
+          label: this.i18n('Unban'),
+          handler: users => this.unbanUsers(users),
+          isDisplayed: users => users.every(u => this.authUser.canManage(u) && u.blocked === true)
         }
-      }
+      ],
+      [
+        {
+          label: this.i18n('Set Email as Verified'),
+          handler: users => this.setEmailsAsVerified(users),
+          isDisplayed: users => {
+            return this.requiresEmailVerification &&
+              users.every(u => this.authUser.canManage(u) && !u.blocked && u.emailVerified === false)
+          }
+        }
+      ]
     ]
   }
 
index 86f583b6123ef658a8b7057acdc6cc4e52702193..4c361cec34e7a47cb9ae119b333dcb81a685f180 100644 (file)
@@ -1,7 +1,7 @@
 <div class="top-buttons">
   <div class="history-switch">
     <p-inputSwitch [(ngModel)]="videosHistoryEnabled" (ngModelChange)="onVideosHistoryChange()"></p-inputSwitch>
-    <label i18n>Enable video history</label>
+    <label i18n>Video history</label>
   </div>
 
   <button class="delete-history" (click)="deleteHistory()" i18n>
index 082d3301db14e9c71b2f610becbc7c2a837b29ae..03eb83cb8fa18f68b50c89b203e8411f388c02ea 100644 (file)
@@ -4,7 +4,7 @@ import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular
 import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core'
 import { is18nPath } from '../../../shared/models/i18n'
 import { ScreenService } from '@app/shared/misc/screen.service'
-import { debounceTime, filter, first, map, pairwise, skip, switchMap } from 'rxjs/operators'
+import { debounceTime, filter, map, pairwise } from 'rxjs/operators'
 import { Hotkey, HotkeysService } from 'angular2-hotkeys'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { fromEvent } from 'rxjs'
index 99e8b7ec1651482c39dc0c1285777cb83e3f15dc..54f5bf97c23b589dce495819e03c950386fb1818 100644 (file)
       <ng-container *ngFor="let action of actions">
         <ng-container *ngIf="action.isDisplayed === undefined || action.isDisplayed(entry) === true">
 
-          <a *ngIf="action.linkBuilder" [ngClass]="{ 'with-icon': !!action.iconName }" class="dropdown-item" [routerLink]="action.linkBuilder(entry)">
+          <ng-template #templateActionLabel let-action>
             <my-global-icon *ngIf="action.iconName" [iconName]="action.iconName" [ngClass]="'icon-' + action.iconName"></my-global-icon>
-            {{ action.label }}
+            <div class="d-flex flex-column">
+              <span i18n>{{ action.label }}</span>
+              <small class="text-muted" *ngIf="action.description">{{ action.description }}</small>
+            </div>
+          </ng-template>
+
+          <a *ngIf="action.linkBuilder" [ngClass]="{ 'with-icon': !!action.iconName }" class="dropdown-item" [routerLink]="action.linkBuilder(entry)" [title]="action.title || ''">
+            <ng-container *ngTemplateOutlet="templateActionLabel; context:{ $implicit: action }"></ng-container>
           </a>
 
           <span
             *ngIf="!action.linkBuilder" [ngClass]="{ 'with-icon': !!action.iconName }" (click)="action.handler(entry)"
-            class="custom-action dropdown-item" role="button"
+            class="custom-action dropdown-item" role="button" [title]="action.title || ''"
           >
-            <my-global-icon *ngIf="action.iconName" [iconName]="action.iconName" [ngClass]="'icon-' + action.iconName"></my-global-icon>
-            {{ action.label }}
+            <ng-container *ngTemplateOutlet="templateActionLabel; context:{ $implicit: action }"></ng-container>
           </span>
 
         </ng-container>
index e33aa8d246bcd536fad7595bd4552bf425834491..442c90984da9f5e41b9c58a3ae405d190eaca1ab 100644 (file)
@@ -52,6 +52,7 @@
 
 .dropdown-menu {
   .dropdown-item {
+    display: flex;
     cursor: pointer;
     color: #000 !important;
 
index 5330ca220958e365e7541ffbba0bad207c09ca09..a8b3ab16c312d49582029ee776b344d902f4a9c8 100644 (file)
@@ -4,6 +4,8 @@ import { GlobalIconName } from '@app/shared/images/global-icon.component'
 export type DropdownAction<T> = {
   label?: string
   iconName?: GlobalIconName
+  description?: string
+  title?: string
   handler?: (a: T) => any
   linkBuilder?: (a: T) => (string | number)[]
   isDisplayed?: (a: T) => boolean
index 89f275a04fcd5ef83de3f5c24ea4be980efd5ee5..7ae5f40e378f3da69a28e19d3273b62792f9761f 100644 (file)
@@ -243,20 +243,24 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
       if (this.user && authUser.hasRight(UserRight.MANAGE_USERS) && authUser.canManage(this.user)) {
         this.userActions.push([
           {
-            label: this.i18n('Edit user'),
+            label: this.i18n('Edit'),
+            description: this.i18n('Change quota, role, and more.'),
             linkBuilder: ({ user }) => this.getRouterUserEditLink(user)
           },
           {
-            label: this.i18n('Delete user'),
+            label: this.i18n('Delete'),
+            description: this.i18n('Videos will be deleted, comments will be tombstoned.'),
             handler: ({ user }) => this.removeUser(user)
           },
           {
-            label: this.i18n('Ban user'),
+            label: this.i18n('Ban'),
+            description: this.i18n('Videos will be kept as private, comments will be kept as is.'),
             handler: ({ user }) => this.openBanUserModal(user),
             isDisplayed: ({ user }) => !user.blocked
           },
           {
             label: this.i18n('Unban user'),
+            description: this.i18n('Allow the user to login and create videos/comments again'),
             handler: ({ user }) => this.unbanUser(user),
             isDisplayed: ({ user }) => user.blocked
           },
@@ -274,21 +278,25 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
         this.userActions.push([
           {
             label: this.i18n('Mute this account'),
+            description: this.i18n('Hide any content from that user for you.'),
             isDisplayed: ({ account }) => account.mutedByUser === false,
             handler: ({ account }) => this.blockAccountByUser(account)
           },
           {
             label: this.i18n('Unmute this account'),
+            description: this.i18n('Show back content from that user for you.'),
             isDisplayed: ({ account }) => account.mutedByUser === true,
             handler: ({ account }) => this.unblockAccountByUser(account)
           },
           {
             label: this.i18n('Mute the instance'),
+            description: this.i18n('Hide any content from that instance for you.'),
             isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false,
             handler: ({ account }) => this.blockServerByUser(account.host)
           },
           {
             label: this.i18n('Unmute the instance'),
+            description: this.i18n('Show back content from that instance for you.'),
             isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true,
             handler: ({ account }) => this.unblockServerByUser(account.host)
           }
@@ -301,11 +309,13 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
           instanceActions = instanceActions.concat([
             {
               label: this.i18n('Mute this account by your instance'),
+              description: this.i18n('Hide any content from that user for you, your instance and its users.'),
               isDisplayed: ({ account }) => account.mutedByInstance === false,
               handler: ({ account }) => this.blockAccountByInstance(account)
             },
             {
               label: this.i18n('Unmute this account by your instance'),
+              description: this.i18n('Show back content from that user for you, your instance and its users.'),
               isDisplayed: ({ account }) => account.mutedByInstance === true,
               handler: ({ account }) => this.unblockAccountByInstance(account)
             }
@@ -317,11 +327,13 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
           instanceActions = instanceActions.concat([
             {
               label: this.i18n('Mute the instance by your instance'),
+              description: this.i18n('Hide any content from that instance for you, your instance and its users.'),
               isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false,
               handler: ({ account }) => this.blockServerByInstance(account.host)
             },
             {
               label: this.i18n('Unmute the instance by your instance'),
+              description: this.i18n('Show back content from that instance for you, your instance and its users.'),
               isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true,
               handler: ({ account }) => this.unblockServerByInstance(account.host)
             }
index 59ee1cb046319c583eb6fcef8a5d98db4ab84177..acfec0a8e9014a64840eed52121357192dc4ffec 100644 (file)
@@ -1,5 +1,5 @@
 <form novalidate [formGroup]="form" (ngSubmit)="formValidated()">
-  <div class="form-group">
+  <div class="form-group mb-2">
     <input type="email"
       formControlName="text"
       class="form-control"
index 2a4df29f73af82250d2f72bc06317c1c11cdadb0..f08c88f3caa357fdd95401b9ccb30a315da1501c 100644 (file)
@@ -54,7 +54,7 @@
         <span *ngIf="isUserLoggedIn()" i18n>Subscribe with your local account</span>
       </button>
 
-      <button class="dropdown-item" i18n>Subscribe with a Mastodon account:</button>
+      <button class="dropdown-item dropdown-item-neutral" i18n>Subscribe with a Mastodon account:</button>
       <my-remote-subscribe showHelp="true" [uri]="uri"></my-remote-subscribe>
 
       <div class="dropdown-divider"></div>
index d5b3796a16c29cade40a16a245a7d9d9f24bc7de..114a12f0636a3abeae5ad64073929e38cde41255 100644 (file)
     button {
       cursor: pointer;
     }
+
+    .dropdown-item-neutral {
+      cursor: default;
+
+      &:hover,
+      &:focus {
+        background-color: inherit;
+      }
+    }
   }
 
   .dropdown-header {
index ce977b3e637c33866e26bb885d539bef39bfe18b..46c49c15b93b24d540ca083cee2d683e45391f44 100644 (file)
         tabindex="-1"
         class="video-miniature-name"
         [routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }"
-      >
-        <ng-container *ngIf="displayOptions.privacyLabel">
-          <span *ngIf="isUnlistedVideo()" class="badge badge-warning" i18n>Unlisted</span>
-          <span *ngIf="isPrivateVideo()" class="badge badge-danger" i18n>Private</span>
-        </ng-container>
-
-        {{ video.name }}
-      </a>
+      >{{ video.name }}</a>
 
       <span class="video-miniature-created-at-views">
         <my-date-toggle *ngIf="displayOptions.date" [date]="video.publishedAt"></my-date-toggle>
           <ng-container *ngIf="displayOptions.date && displayOptions.views"> • </ng-container>
           <ng-container i18n *ngIf="displayOptions.views">{video.views, plural, =1 {1 view} other {{{ video.views | myNumberFormatter }} views}}</ng-container>
         </span>
+
+        <ng-container *ngIf="displayOptions.privacyLabel">
+          <span *ngIf="isUnlistedVideo()" class="badge badge-warning ml-1" i18n>Unlisted</span>
+          <span *ngIf="isPrivateVideo()" class="badge badge-danger ml-1" i18n>Private</span>
+        </ng-container>
       </span>
 
       <a tabindex="-1" *ngIf="displayOptions.by && displayOwnerAccount()" class="video-miniature-account" [routerLink]="[ '/accounts', video.byAccount ]">
index 238cb84546f196eb7b9cade5d0e1314c7d7658a6..0d51818c316daed8b98dbf54bba2b713f5afc438 100644 (file)
@@ -374,6 +374,8 @@ p-tablecheckbox:hover div .ui-chkbox-box {
 }
 
 p-inputswitch {
+  height: 26px;
+
   .ui-inputswitch-checked .ui-inputswitch-slider {
     background-color: var(--mainColor) !important;
   }