Add bulk action on following/followers

This commit is contained in:
Chocobozzz 2022-07-27 13:44:40 +02:00
parent 073deef886
commit e3d6c6434f
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
11 changed files with 240 additions and 86 deletions

View File

@ -9,9 +9,18 @@
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} followers" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} followers"
[(selection)]="selectedFollows"
> >
<ng-template pTemplate="caption"> <ng-template pTemplate="caption">
<div class="caption"> <div class="caption">
<div class="left-buttons">
<my-action-dropdown
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
[actions]="bulkFollowsActions" [entry]="selectedFollows"
>
</my-action-dropdown>
</div>
<div class="ms-auto"> <div class="ms-auto">
<my-advanced-input-filter [filters]="searchFilters" (search)="onSearch($event)"></my-advanced-input-filter> <my-advanced-input-filter [filters]="searchFilters" (search)="onSearch($event)"></my-advanced-input-filter>
</div> </div>
@ -20,6 +29,9 @@
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>
<th style="width: 40px">
<p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
</th>
<th style="width: 150px;" i18n>Actions</th> <th style="width: 150px;" i18n>Actions</th>
<th i18n>Follower</th> <th i18n>Follower</th>
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th> <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
@ -30,15 +42,19 @@
<ng-template pTemplate="body" let-follow> <ng-template pTemplate="body" let-follow>
<tr> <tr>
<td class="action-cell"> <td class="checkbox-cell">
<my-button *ngIf="follow.state !== 'accepted'" i18n-title title="Accept" icon="tick" (click)="acceptFollower(follow)"></my-button> <p-tableCheckbox [value]="follow" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
<my-button *ngIf="follow.state !== 'rejected'" i18n-title title="Refuse" icon="cross" (click)="rejectFollower(follow)"></my-button> </td>
<my-delete-button *ngIf="follow.state === 'rejected'" (click)="deleteFollower(follow)"></my-delete-button> <td class="action-cell">
<my-button *ngIf="follow.state !== 'accepted'" i18n-title title="Accept" icon="tick" (click)="acceptFollower([ follow ])"></my-button>
<my-button *ngIf="follow.state !== 'rejected'" i18n-title title="Reject" icon="cross" (click)="rejectFollower([ follow ])"></my-button>
<my-delete-button *ngIf="follow.state === 'rejected'" (click)="deleteFollowers([ follow ])"></my-delete-button>
</td> </td>
<td> <td>
<a [href]="follow.follower.url" i18n-title title="Open actor page in a new tab" target="_blank" rel="noopener noreferrer"> <a [href]="follow.follower.url" i18n-title title="Open actor page in a new tab" target="_blank" rel="noopener noreferrer">
{{ follow.follower.name + '@' + follow.follower.host }} {{ buildFollowerName(follow) }}
<my-global-icon iconName="external-link"></my-global-icon> <my-global-icon iconName="external-link"></my-global-icon>
</a> </a>
</td> </td>
@ -56,7 +72,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="5"> <td colspan="6">
<div class="no-results"> <div class="no-results">
<ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container> <ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container>

View File

@ -1,8 +1,10 @@
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
import { prepareIcu } from '@app/helpers'
import { AdvancedInputFilter } from '@app/shared/shared-forms' import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { InstanceFollowService } from '@app/shared/shared-instance' import { InstanceFollowService } from '@app/shared/shared-instance'
import { DropdownAction } from '@app/shared/shared-main'
import { ActorFollow } from '@shared/models' import { ActorFollow } from '@shared/models'
@Component({ @Component({
@ -16,7 +18,10 @@ export class FollowersListComponent extends RestTable implements OnInit {
sort: SortMeta = { field: 'createdAt', order: -1 } sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
searchFilters: AdvancedInputFilter[] searchFilters: AdvancedInputFilter[] = []
selectedFollows: ActorFollow[] = []
bulkFollowsActions: DropdownAction<ActorFollow[]>[] = []
constructor ( constructor (
private confirmService: ConfirmService, private confirmService: ConfirmService,
@ -24,66 +29,73 @@ export class FollowersListComponent extends RestTable implements OnInit {
private followService: InstanceFollowService private followService: InstanceFollowService
) { ) {
super() super()
this.searchFilters = this.followService.buildFollowsListFilters()
} }
ngOnInit () { ngOnInit () {
this.initialize() this.initialize()
this.searchFilters = this.followService.buildFollowsListFilters()
this.bulkFollowsActions = [
{
label: $localize`Reject`,
handler: follows => this.rejectFollower(follows),
isDisplayed: follows => follows.every(f => f.state !== 'rejected')
},
{
label: $localize`Accept`,
handler: follows => this.acceptFollower(follows),
isDisplayed: follows => follows.every(f => f.state !== 'accepted')
},
{
label: $localize`Delete`,
handler: follows => this.deleteFollowers(follows),
isDisplayed: follows => follows.every(f => f.state === 'rejected')
}
]
} }
getIdentifier () { getIdentifier () {
return 'FollowersListComponent' return 'FollowersListComponent'
} }
acceptFollower (follow: ActorFollow) { acceptFollower (follows: ActorFollow[]) {
follow.state = 'accepted' this.followService.acceptFollower(follows)
this.followService.acceptFollower(follow)
.subscribe({ .subscribe({
next: () => { next: () => {
const handle = follow.follower.name + '@' + follow.follower.host // eslint-disable-next-line max-len
this.notifier.success($localize`${handle} accepted in instance followers`) const message = prepareIcu($localize`Accepted {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Follow requests accepted`
)
this.notifier.success(message)
this.reloadData()
}, },
error: err => { error: err => this.notifier.error(err.message)
follow.state = 'pending'
this.notifier.error(err.message)
}
}) })
} }
async rejectFollower (follow: ActorFollow) { async rejectFollower (follows: ActorFollow[]) {
const message = $localize`Do you really want to reject this follower?` // eslint-disable-next-line max-len
const message = prepareIcu($localize`Do you really want to reject {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Do you really want to reject these follow requests?`
)
const res = await this.confirmService.confirm(message, $localize`Reject`) const res = await this.confirmService.confirm(message, $localize`Reject`)
if (res === false) return if (res === false) return
this.followService.rejectFollower(follow) this.followService.rejectFollower(follows)
.subscribe({ .subscribe({
next: () => { next: () => {
const handle = follow.follower.name + '@' + follow.follower.host // eslint-disable-next-line max-len
this.notifier.success($localize`${handle} rejected from instance followers`) const message = prepareIcu($localize`Rejected {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
this.reloadData() $localize`Follow requests rejected`
}, )
this.notifier.success(message)
error: err => {
follow.state = 'pending'
this.notifier.error(err.message)
}
})
}
async deleteFollower (follow: ActorFollow) {
const message = $localize`Do you really want to delete this follower? It will be able to send again another follow request.`
const res = await this.confirmService.confirm(message, $localize`Delete`)
if (res === false) return
this.followService.removeFollower(follow)
.subscribe({
next: () => {
const handle = follow.follower.name + '@' + follow.follower.host
this.notifier.success($localize`${handle} removed from instance followers`)
this.reloadData() this.reloadData()
}, },
@ -92,6 +104,45 @@ export class FollowersListComponent extends RestTable implements OnInit {
}) })
} }
async deleteFollowers (follows: ActorFollow[]) {
let message = $localize`Deleted followers will be able to send again a follow request.`
message += '<br /><br />'
// eslint-disable-next-line max-len
message += prepareIcu($localize`Do you really want to delete {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Do you really want to delete these follow requests?`
)
const res = await this.confirmService.confirm(message, $localize`Delete`)
if (res === false) return
this.followService.removeFollower(follows)
.subscribe({
next: () => {
// eslint-disable-next-line max-len
const message = prepareIcu($localize`Removed {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Follow requests removed`
)
this.notifier.success(message)
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
buildFollowerName (follow: ActorFollow) {
return follow.follower.name + '@' + follow.follower.host
}
isInSelectionMode () {
return this.selectedFollows.length !== 0
}
protected reloadData () { protected reloadData () {
this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search }) this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search })
.subscribe({ .subscribe({

View File

@ -9,11 +9,18 @@
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts"
[(selection)]="selectedFollows"
> >
<ng-template pTemplate="caption"> <ng-template pTemplate="caption">
<div class="caption"> <div class="caption">
<div class="left-buttons"> <div class="left-buttons">
<a class="follow-button" (click)="openFollowModal()" (key.enter)="openFollowModal()"> <my-action-dropdown
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
[actions]="bulkFollowsActions" [entry]="selectedFollows"
>
</my-action-dropdown>
<a *ngIf="!isInSelectionMode()" class="follow-button" (click)="openFollowModal()" (key.enter)="openFollowModal()">
<my-global-icon iconName="following" aria-hidden="true"></my-global-icon> <my-global-icon iconName="following" aria-hidden="true"></my-global-icon>
<ng-container i18n>Follow</ng-container> <ng-container i18n>Follow</ng-container>
</a> </a>
@ -27,6 +34,9 @@
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>
<th style="width: 40px">
<p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
</th>
<th style="width: 150px;" i18n>Action</th> <th style="width: 150px;" i18n>Action</th>
<th i18n>Following</th> <th i18n>Following</th>
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th> <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
@ -35,14 +45,18 @@
</tr> </tr>
</ng-template> </ng-template>
<ng-template pTemplate="body" let-follow> <ng-template pSelectableRow="follow" pTemplate="body" let-follow>
<tr> <tr>
<td class="checkbox-cell">
<p-tableCheckbox [value]="follow" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
</td>
<td class="action-cell"> <td class="action-cell">
<my-delete-button label (click)="removeFollowing(follow)"></my-delete-button> <my-delete-button label (click)="removeFollowing([ follow ])"></my-delete-button>
</td> </td>
<td> <td>
<a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer"> <a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer">
{{ follow.following.name + '@' + follow.following.host }} {{ buildFollowingName(follow) }}
<my-global-icon iconName="external-link"></my-global-icon> <my-global-icon iconName="external-link"></my-global-icon>
</a> </a>
</td> </td>
@ -65,7 +79,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="5"> <td colspan="6">
<div class="no-results"> <div class="no-results">
<ng-container *ngIf="search" i18n>No host found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No host found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>Your instance is not following anyone.</ng-container> <ng-container *ngIf="!search" i18n>Your instance is not following anyone.</ng-container>

View File

@ -5,6 +5,8 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { InstanceFollowService } from '@app/shared/shared-instance' import { InstanceFollowService } from '@app/shared/shared-instance'
import { ActorFollow } from '@shared/models' import { ActorFollow } from '@shared/models'
import { FollowModalComponent } from './follow-modal.component' import { FollowModalComponent } from './follow-modal.component'
import { DropdownAction } from '@app/shared/shared-main'
import { prepareIcu } from '@app/helpers'
@Component({ @Component({
templateUrl: './following-list.component.html', templateUrl: './following-list.component.html',
@ -18,7 +20,10 @@ export class FollowingListComponent extends RestTable implements OnInit {
sort: SortMeta = { field: 'createdAt', order: -1 } sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
searchFilters: AdvancedInputFilter[] searchFilters: AdvancedInputFilter[] = []
selectedFollows: ActorFollow[] = []
bulkFollowsActions: DropdownAction<ActorFollow[]>[] = []
constructor ( constructor (
private notifier: Notifier, private notifier: Notifier,
@ -26,12 +31,19 @@ export class FollowingListComponent extends RestTable implements OnInit {
private followService: InstanceFollowService private followService: InstanceFollowService
) { ) {
super() super()
this.searchFilters = this.followService.buildFollowsListFilters()
} }
ngOnInit () { ngOnInit () {
this.initialize() this.initialize()
this.searchFilters = this.followService.buildFollowsListFilters()
this.bulkFollowsActions = [
{
label: $localize`Delete`,
handler: follows => this.removeFollowing(follows)
}
]
} }
getIdentifier () { getIdentifier () {
@ -46,17 +58,33 @@ export class FollowingListComponent extends RestTable implements OnInit {
return follow.following.name === 'peertube' return follow.following.name === 'peertube'
} }
async removeFollowing (follow: ActorFollow) { isInSelectionMode () {
const res = await this.confirmService.confirm( return this.selectedFollows.length !== 0
$localize`Do you really want to unfollow ${follow.following.host}?`, }
$localize`Unfollow`
buildFollowingName (follow: ActorFollow) {
return follow.following.name + '@' + follow.following.host
}
async removeFollowing (follows: ActorFollow[]) {
const message = prepareIcu($localize`Do you really want to unfollow {count, plural, =1 {{entryName}?} other {{count} entries?}}`)(
{ count: follows.length, entryName: this.buildFollowingName(follows[0]) },
$localize`Do you really want to unfollow these entries?`
) )
const res = await this.confirmService.confirm(message, $localize`Unfollow`)
if (res === false) return if (res === false) return
this.followService.unfollow(follow) this.followService.unfollow(follows)
.subscribe({ .subscribe({
next: () => { next: () => {
this.notifier.success($localize`You are not following ${follow.following.host} anymore.`) // eslint-disable-next-line max-len
const message = prepareIcu($localize`You are not following {count, plural, =1 {{entryName} anymore.} other {these {count} entries anymore.}}`)(
{ count: follows.length, entryName: this.buildFollowingName(follows[0]) },
$localize`You are not following them anymore.`
)
this.notifier.success(message)
this.reloadData() this.reloadData()
}, },

View File

@ -1,9 +1,10 @@
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { Observable } from 'rxjs' import { from, Observable } from 'rxjs'
import { catchError, map } from 'rxjs/operators' import { catchError, concatMap, map, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { ActivityPubActorType, ActorFollow, FollowState, ResultList, ServerFollowCreate } from '@shared/models' import { ActivityPubActorType, ActorFollow, FollowState, ResultList, ServerFollowCreate } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { AdvancedInputFilter } from '../shared-forms' import { AdvancedInputFilter } from '../shared-forms'
@ -81,32 +82,64 @@ export class InstanceFollowService {
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(catchError(res => this.restExtractor.handleError(res)))
} }
unfollow (follow: ActorFollow) { unfollow (followsArg: ActorFollow[] | ActorFollow) {
const handle = follow.following.name + '@' + follow.following.host const follows = arrayify(followsArg)
return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + handle) return from(follows)
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(
concatMap(follow => {
const handle = follow.following.name + '@' + follow.following.host
return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + handle)
}),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
} }
acceptFollower (follow: ActorFollow) { acceptFollower (followsArg: ActorFollow[] | ActorFollow) {
const handle = follow.follower.name + '@' + follow.follower.host const follows = arrayify(followsArg)
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {}) return from(follows)
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {})
}),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
} }
rejectFollower (follow: ActorFollow) { rejectFollower (followsArg: ActorFollow[] | ActorFollow) {
const handle = follow.follower.name + '@' + follow.follower.host const follows = arrayify(followsArg)
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {}) return from(follows)
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {})
}),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
} }
removeFollower (follow: ActorFollow) { removeFollower (followsArg: ActorFollow[] | ActorFollow) {
const handle = follow.follower.name + '@' + follow.follower.host const follows = arrayify(followsArg)
return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`) return from(follows)
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`)
}),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
} }
buildFollowsListFilters (): AdvancedInputFilter[] { buildFollowsListFilters (): AdvancedInputFilter[] {

View File

@ -5,6 +5,7 @@ import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AuthService, ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core' import { AuthService, ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core'
import { objectToFormData } from '@app/helpers' import { objectToFormData } from '@app/helpers'
import { arrayify } from '@shared/core-utils'
import { import {
BooleanBothQuery, BooleanBothQuery,
FeedFormat, FeedFormat,
@ -285,7 +286,7 @@ export class VideoService {
} }
removeVideo (idArg: number | number[]) { removeVideo (idArg: number | number[]) {
const ids = Array.isArray(idArg) ? idArg : [ idArg ] const ids = arrayify(idArg)
return from(ids) return from(ids)
.pipe( .pipe(

View File

@ -4,6 +4,7 @@ import { catchError, concatMap, map, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { AccountBlock as AccountBlockServer, BlockStatus, ResultList, ServerBlock } from '@shared/models' import { AccountBlock as AccountBlockServer, BlockStatus, ResultList, ServerBlock } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { Account } from '../shared-main' import { Account } from '../shared-main'
@ -122,7 +123,7 @@ export class BlocklistService {
} }
blockAccountByInstance (accountsArg: Pick<Account, 'nameWithHost'> | Pick<Account, 'nameWithHost'>[]) { blockAccountByInstance (accountsArg: Pick<Account, 'nameWithHost'> | Pick<Account, 'nameWithHost'>[]) {
const accounts = Array.isArray(accountsArg) ? accountsArg : [ accountsArg ] const accounts = arrayify(accountsArg)
return from(accounts) return from(accounts)
.pipe( .pipe(

View File

@ -4,6 +4,7 @@ import { catchError, concatMap, map, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models' import { ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
@ -53,7 +54,7 @@ export class VideoBlockService {
} }
unblockVideo (videoIdArgs: number | number[]) { unblockVideo (videoIdArgs: number | number[]) {
const videoIds = Array.isArray(videoIdArgs) ? videoIdArgs : [ videoIdArgs ] const videoIds = arrayify(videoIdArgs)
return observableFrom(videoIds) return observableFrom(videoIds)
.pipe( .pipe(

View File

@ -5,6 +5,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService, UserService } from '@app/core' import { RestExtractor, RestPagination, RestService, UserService } from '@app/core'
import { getBytes } from '@root-helpers/bytes' import { getBytes } from '@root-helpers/bytes'
import { arrayify } from '@shared/core-utils'
import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate } from '@shared/models' import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate } from '@shared/models'
@Injectable() @Injectable()
@ -65,7 +66,7 @@ export class UserAdminService {
} }
removeUser (usersArg: UserServerModel | UserServerModel[]) { removeUser (usersArg: UserServerModel | UserServerModel[]) {
const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] const users = arrayify(usersArg)
return from(users) return from(users)
.pipe( .pipe(
@ -77,7 +78,7 @@ export class UserAdminService {
banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) { banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
const body = reason ? { reason } : {} const body = reason ? { reason } : {}
const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] const users = arrayify(usersArg)
return from(users) return from(users)
.pipe( .pipe(
@ -88,7 +89,7 @@ export class UserAdminService {
} }
unbanUsers (usersArg: UserServerModel | UserServerModel[]) { unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] const users = arrayify(usersArg)
return from(users) return from(users)
.pipe( .pipe(

View File

@ -276,7 +276,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
}) })
} }
const where: WhereAttributeHash<Attributes<ActorFollowModel>> = { actorId} const where: WhereAttributeHash<Attributes<ActorFollowModel>> = { actorId }
if (state) where.state = state if (state) where.state = state
const query: FindOptions<Attributes<ActorFollowModel>> = { const query: FindOptions<Attributes<ActorFollowModel>> = {

View File

@ -8,6 +8,14 @@ function findCommonElement <T> (array1: T[], array2: T[]) {
return null return null
} }
export { // Avoid conflict with other toArray() functions
findCommonElement function arrayify <T> (element: T | T[]) {
if (Array.isArray(element)) return element
return [ element ]
}
export {
findCommonElement,
arrayify
} }