diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 367258a06..9596d34af 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html @@ -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> diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index e5340234b..915d60090 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html @@ -118,7 +118,7 @@ <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" @@ -131,7 +131,7 @@ <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" @@ -143,7 +143,7 @@ <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" @@ -154,7 +154,7 @@ <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" @@ -165,7 +165,7 @@ <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" @@ -178,7 +178,7 @@ <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" diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html index 885335313..ca05bac19 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.html +++ b/client/src/app/+admin/users/user-list/user-list.component.html @@ -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> diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index 1083ba291..a596251f6 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts @@ -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) + } + } + ] ] } diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.html b/client/src/app/+my-account/my-account-history/my-account-history.component.html index 86f583b61..4c361cec3 100644 --- a/client/src/app/+my-account/my-account-history/my-account-history.component.html +++ b/client/src/app/+my-account/my-account-history/my-account-history.component.html @@ -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> diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 082d3301d..03eb83cb8 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -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' diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/buttons/action-dropdown.component.html index 99e8b7ec1..54f5bf97c 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.html +++ b/client/src/app/shared/buttons/action-dropdown.component.html @@ -15,17 +15,23 @@ <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> diff --git a/client/src/app/shared/buttons/action-dropdown.component.scss b/client/src/app/shared/buttons/action-dropdown.component.scss index e33aa8d24..442c90984 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.scss +++ b/client/src/app/shared/buttons/action-dropdown.component.scss @@ -52,6 +52,7 @@ .dropdown-menu { .dropdown-item { + display: flex; cursor: pointer; color: #000 !important; diff --git a/client/src/app/shared/buttons/action-dropdown.component.ts b/client/src/app/shared/buttons/action-dropdown.component.ts index 5330ca220..a8b3ab16c 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.ts +++ b/client/src/app/shared/buttons/action-dropdown.component.ts @@ -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 diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts index 89f275a04..7ae5f40e3 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts @@ -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) } diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.html b/client/src/app/shared/user-subscription/remote-subscribe.component.html index 59ee1cb04..acfec0a8e 100644 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.html +++ b/client/src/app/shared/user-subscription/remote-subscribe.component.html @@ -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" diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.html b/client/src/app/shared/user-subscription/subscribe-button.component.html index 2a4df29f7..f08c88f3c 100644 --- a/client/src/app/shared/user-subscription/subscribe-button.component.html +++ b/client/src/app/shared/user-subscription/subscribe-button.component.html @@ -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> diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.scss b/client/src/app/shared/user-subscription/subscribe-button.component.scss index d5b3796a1..114a12f06 100644 --- a/client/src/app/shared/user-subscription/subscribe-button.component.scss +++ b/client/src/app/shared/user-subscription/subscribe-button.component.scss @@ -69,6 +69,15 @@ button { cursor: pointer; } + + .dropdown-item-neutral { + cursor: default; + + &:hover, + &:focus { + background-color: inherit; + } + } } .dropdown-header { diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html index ce977b3e6..46c49c15b 100644 --- a/client/src/app/shared/video/video-miniature.component.html +++ b/client/src/app/shared/video/video-miniature.component.html @@ -10,14 +10,7 @@ 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> @@ -26,6 +19,11 @@ <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 ]"> diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index 238cb8454..0d51818c3 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss @@ -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; }