Restore videos list components

pull/1759/head
Chocobozzz 2019-03-21 16:49:46 +01:00
parent 7ccddd7b52
commit 489290b8b1
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
25 changed files with 401 additions and 465 deletions

View File

@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Location } from '@angular/common'
import { immutableAssign } from '@app/shared/misc/utils'
import { AuthService } from '../../core/auth'
import { ConfirmService } from '../../core/confirm'
@ -12,7 +11,7 @@ import { tap } from 'rxjs/operators'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { Subscription } from 'rxjs'
import { ScreenService } from '@app/shared/misc/screen.service'
import { Notifier } from '@app/core'
import { Notifier, ServerService } from '@app/core'
@Component({
selector: 'my-account-videos',
@ -25,7 +24,6 @@ import { Notifier } from '@app/core'
export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
marginContent = false // Disable margin
currentRoute = '/accounts/videos'
loadOnInit = false
private account: Account
@ -33,13 +31,13 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
constructor (
protected router: Router,
protected serverService: ServerService,
protected route: ActivatedRoute,
protected authService: AuthService,
protected notifier: Notifier,
protected confirmService: ConfirmService,
protected location: Location,
protected screenService: ScreenService,
protected i18n: I18n,
private i18n: I18n,
private accountService: AccountService,
private videoService: VideoService
) {
@ -55,7 +53,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
this.accountSub = this.accountService.accountLoaded
.subscribe(account => {
this.account = account
this.currentRoute = '/accounts/' + this.account.nameWithHost + '/videos'
this.reloadVideos()
this.generateSyndicationList()

View File

@ -23,6 +23,10 @@ const accountsRoutes: Routes = [
data: {
meta: {
title: 'Account videos'
},
reuse: {
enabled: true,
key: 'account-videos-list'
}
}
},

View File

@ -1,49 +1,42 @@
<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
<div
myInfiniteScroller
[pageHeight]="pageHeight"
(nearOfTop)="onNearOfTop()"
(nearOfBottom)="onNearOfBottom()"
(pageChanged)="onPageChanged($event)"
class="videos" #videosElement
>
<div *ngFor="let videos of videoPages; let i = index" class="videos-page">
<div class="video" *ngFor="let video of videos; let j = index">
<div class="checkbox-container">
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
</div>
<my-video-thumbnail [video]="video"></my-video-thumbnail>
<div class="video-info">
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
<div>{{ video.account.displayName }}</div>
<div>{{ video.publishedAt | myFromNow }}</div>
<div><span i18n>Privacy: </span><span>{{ video.privacy.label }}</span></div>
<div><span i18n>Sensitve: </span><span> {{ video.nsfw }}</span></div>
</div>
<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" class="videos">
<div class="video" *ngFor="let video of videos; let i = index">
<div class="checkbox-container">
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
</div>
<!-- Display only once -->
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
<div class="action-selection-mode-child">
<span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
Cancel
</span>
<my-video-thumbnail [video]="video"></my-video-thumbnail>
<span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
<my-global-icon iconName="tick"></my-global-icon>
<ng-container i18n>Unblacklist</ng-container>
</span>
</div>
</div>
<div class="video-info">
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
<div>{{ video.account.displayName }}</div>
<div>{{ video.publishedAt | myFromNow }}</div>
<div><span i18n>Privacy: </span><span>{{ video.privacy.label }}</span></div>
<div><span i18n>Sensitive: </span><span> {{ video.nsfw }}</span></div>
</div>
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
<my-button
i18n-label
label="Unblacklist"
icon="tick"
(click)="removeVideoFromBlacklist(video)"
></my-button>
<!-- Display only once -->
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
<div class="action-selection-mode-child">
<span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
Cancel
</span>
<span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
<my-global-icon iconName="tick"></my-global-icon>
<ng-container i18n>Unblacklist</ng-container>
</span>
</div>
</div>
</div>
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
<my-button
i18n-label
label="Unblacklist"
icon="tick"
(click)="removeVideoFromBlacklist(video)"
></my-button>
</div>
</div>
</div>

View File

@ -4,7 +4,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
import { Router, ActivatedRoute } from '@angular/router'
import { AbstractVideoList } from '@app/shared/video/abstract-video-list'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { Notifier, AuthService } from '@app/core'
import { Notifier, AuthService, ServerService } from '@app/core'
import { Video } from '@shared/models'
import { VideoBlacklistService } from '@app/shared'
import { immutableAssign } from '@app/shared/misc/utils'
@ -17,7 +17,6 @@ import { ScreenService } from '@app/shared/misc/screen.service'
})
export class VideoAutoBlacklistListComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
currentRoute = '/admin/moderation/video-auto-blacklist/list'
checkedVideos: { [ id: number ]: boolean } = {}
pagination: ComponentPagination = {
currentPage: 1,
@ -25,18 +24,15 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
totalItems: null
}
protected baseVideoWidth = -1
protected baseVideoHeight = 155
constructor (
protected router: Router,
protected route: ActivatedRoute,
protected i18n: I18n,
protected notifier: Notifier,
protected location: Location,
protected authService: AuthService,
protected screenService: ScreenService,
private videoBlacklistService: VideoBlacklistService,
protected serverService: ServerService,
private i18n: I18n,
private videoBlacklistService: VideoBlacklistService
) {
super()
@ -96,5 +92,4 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
error => this.notifier.error(error.message)
)
}
}

View File

@ -13,16 +13,14 @@
<div class="no-history" i18n *ngIf="pagination.totalItems === 0">You don't have videos history yet.</div>
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" class="videos" #videosElement>
<div *ngFor="let videos of videoPages;" class="videos-page">
<div class="video" *ngFor="let video of videos">
<my-video-thumbnail [video]="video"></my-video-thumbnail>
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
<div class="video" *ngFor="let video of videos">
<my-video-thumbnail [video]="video"></my-video-thumbnail>
<div class="video-info">
<a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
<span i18n class="video-info-date-views">{{ video.views | myNumberFormatter }} views</span>
<a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a>
</div>
<div class="video-info">
<a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
<span i18n class="video-info-date-views">{{ video.views | myNumberFormatter }} views</span>
<a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a>
</div>
</div>
</div>

View File

@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Location } from '@angular/common'
import { immutableAssign } from '@app/shared/misc/utils'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { AuthService } from '../../core/auth'
@ -11,7 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
import { ScreenService } from '@app/shared/misc/screen.service'
import { UserHistoryService } from '@app/shared/users/user-history.service'
import { UserService } from '@app/shared'
import { Notifier } from '@app/core'
import { Notifier, ServerService } from '@app/core'
@Component({
selector: 'my-account-history',
@ -20,7 +19,6 @@ import { Notifier } from '@app/core'
})
export class MyAccountHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
currentRoute = '/my-account/history/videos'
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 5,
@ -28,16 +26,13 @@ export class MyAccountHistoryComponent extends AbstractVideoList implements OnIn
}
videosHistoryEnabled: boolean
protected baseVideoWidth = -1
protected baseVideoHeight = 155
constructor (
protected router: Router,
protected serverService: ServerService,
protected route: ActivatedRoute,
protected authService: AuthService,
protected userService: UserService,
protected notifier: Notifier,
protected location: Location,
protected screenService: ScreenService,
protected i18n: I18n,
private confirmService: ConfirmService,

View File

@ -118,6 +118,10 @@ const myAccountRoutes: Routes = [
data: {
meta: {
title: 'Account videos'
},
reuse: {
enabled: true,
key: 'my-account-videos-list'
}
}
},
@ -172,6 +176,10 @@ const myAccountRoutes: Routes = [
data: {
meta: {
title: 'Videos history'
},
reuse: {
enabled: true,
key: 'my-videos-history-list'
}
}
},

View File

@ -1,54 +1,47 @@
<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
<div
myInfiniteScroller
[pageHeight]="pageHeight"
(nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
class="videos" #videosElement
>
<div *ngFor="let videos of videoPages; let i = index" class="videos-page">
<div class="video" *ngFor="let video of videos; let j = index">
<div class="checkbox-container">
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
<div class="video" *ngFor="let video of videos; let i = index">
<div class="checkbox-container">
<my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
</div>
<my-video-thumbnail [video]="video"></my-video-thumbnail>
<div class="video-info">
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
<span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
<div class="video-info-privacy">{{ video.privacy.label }}{{ getStateLabel(video) }}</div>
<div *ngIf="video.blacklisted" class="video-info-blacklisted">
<span class="blacklisted-label" i18n>Blacklisted</span>
<span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
</div>
</div>
<my-video-thumbnail [video]="video"></my-video-thumbnail>
<!-- Display only once -->
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
<div class="action-selection-mode-child">
<span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
Cancel
</span>
<div class="video-info">
<a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
<span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
<div class="video-info-privacy">{{ video.privacy.label }}{{ getStateLabel(video) }}</div>
<div *ngIf="video.blacklisted" class="video-info-blacklisted">
<span class="blacklisted-label" i18n>Blacklisted</span>
<span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
</div>
<span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
<my-global-icon iconName="delete"></my-global-icon>
<ng-container i18n>Delete</ng-container>
</span>
</div>
</div>
<!-- Display only once -->
<div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
<div class="action-selection-mode-child">
<span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
Cancel
</span>
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
<my-delete-button (click)="deleteVideo(video)"></my-delete-button>
<span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
<my-global-icon iconName="delete"></my-global-icon>
<ng-container i18n>Delete</ng-container>
</span>
</div>
</div>
<my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
<div class="video-buttons" *ngIf="isInSelectionMode() === false">
<my-delete-button (click)="deleteVideo(video)"></my-delete-button>
<my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
<my-button i18n-label label="Change ownership"
className="action-button-change-ownership"
icon="im-with-her"
(click)="changeOwnership($event, video)"
></my-button>
</div>
<my-button i18n-label label="Change ownership"
className="action-button-change-ownership"
icon="im-with-her"
(click)="changeOwnership($event, video)"
></my-button>
</div>
</div>
</div>

View File

@ -1,11 +1,10 @@
import { from as observableFrom, Observable } from 'rxjs'
import { concatAll, tap } from 'rxjs/operators'
import { Component, OnDestroy, OnInit, Inject, LOCALE_ID, ViewChild } from '@angular/core'
import { concat, Observable } from 'rxjs'
import { tap, toArray } from 'rxjs/operators'
import { Component, Inject, LOCALE_ID, OnDestroy, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Location } from '@angular/common'
import { immutableAssign } from '@app/shared/misc/utils'
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { Notifier } from '@app/core'
import { Notifier, ServerService } from '@app/core'
import { AuthService } from '../../core/auth'
import { ConfirmService } from '../../core/confirm'
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@ -22,8 +21,9 @@ import { VideoChangeOwnershipComponent } from './video-change-ownership/video-ch
styleUrls: [ './my-account-videos.component.scss' ]
})
export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
@ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
titlePage: string
currentRoute = '/my-account/videos'
checkedVideos: { [ id: number ]: boolean } = {}
pagination: ComponentPagination = {
currentPage: 1,
@ -31,19 +31,14 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
totalItems: null
}
protected baseVideoWidth = -1
protected baseVideoHeight = 155
@ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
constructor (
protected router: Router,
protected serverService: ServerService,
protected route: ActivatedRoute,
protected authService: AuthService,
protected notifier: Notifier,
protected location: Location,
protected screenService: ScreenService,
protected i18n: I18n,
private i18n: I18n,
private confirmService: ConfirmService,
private videoService: VideoService,
@Inject(LOCALE_ID) private localeId: string
@ -93,19 +88,18 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
const observables: Observable<any>[] = []
for (const videoId of toDeleteVideosIds) {
const o = this.videoService.removeVideo(videoId)
.pipe(tap(() => this.spliceVideosById(videoId)))
.pipe(tap(() => this.removeVideoFromArray(videoId)))
observables.push(o)
}
observableFrom(observables)
.pipe(concatAll())
concat(...observables)
.pipe(toArray())
.subscribe(
res => {
() => {
this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }))
this.abortSelectionMode()
this.reloadVideos()
},
err => this.notifier.error(err.message)
@ -156,20 +150,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
return ' - ' + suffix
}
protected buildVideoHeight () {
// In account videos, the video height is fixed
return this.baseVideoHeight
}
private spliceVideosById (id: number) {
for (const key of Object.keys(this.loadedPages)) {
const videos: Video[] = this.loadedPages[ key ]
const index = videos.findIndex(v => v.id === id)
if (index !== -1) {
videos.splice(index, 1)
return
}
}
private removeVideoFromArray (id: number) {
this.videos = this.videos.filter(v => v.id !== id)
}
}

View File

@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Location } from '@angular/common'
import { immutableAssign } from '@app/shared/misc/utils'
import { AuthService } from '../../core/auth'
import { ConfirmService } from '../../core/confirm'
@ -12,7 +11,7 @@ import { tap } from 'rxjs/operators'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { Subscription } from 'rxjs'
import { ScreenService } from '@app/shared/misc/screen.service'
import { Notifier } from '@app/core'
import { Notifier, ServerService } from '@app/core'
@Component({
selector: 'my-video-channel-videos',
@ -25,7 +24,6 @@ import { Notifier } from '@app/core'
export class VideoChannelVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
marginContent = false // Disable margin
currentRoute = '/video-channels/videos'
loadOnInit = false
private videoChannel: VideoChannel
@ -33,13 +31,13 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
constructor (
protected router: Router,
protected serverService: ServerService,
protected route: ActivatedRoute,
protected authService: AuthService,
protected notifier: Notifier,
protected confirmService: ConfirmService,
protected location: Location,
protected screenService: ScreenService,
protected i18n: I18n,
private i18n: I18n,
private videoChannelService: VideoChannelService,
private videoService: VideoService
) {
@ -55,7 +53,6 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
this.videoChannelSub = this.videoChannelService.videoChannelLoaded
.subscribe(videoChannel => {
this.videoChannel = videoChannel
this.currentRoute = '/video-channels/' + this.videoChannel.nameWithHost + '/videos'
this.reloadVideos()
this.generateSyndicationList()

View File

@ -23,6 +23,10 @@ const videoChannelsRoutes: Routes = [
data: {
meta: {
title: 'Video channel videos'
},
reuse: {
enabled: true,
key: 'video-channel-videos-list'
}
}
},

View File

@ -1,8 +1,9 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { RouteReuseStrategy, RouterModule, Routes } from '@angular/router'
import { PreloadSelectedModulesList } from './core'
import { AppComponent } from '@app/app.component'
import { CustomReuseStrategy } from '@app/core/routing/custom-reuse-strategy'
const routes: Routes = [
{
@ -43,12 +44,14 @@ const routes: Routes = [
imports: [
RouterModule.forRoot(routes, {
useHash: Boolean(history.pushState) === false,
scrollPositionRestoration: 'disabled',
preloadingStrategy: PreloadSelectedModulesList,
anchorScrolling: 'enabled'
anchorScrolling: 'disabled'
})
],
providers: [
PreloadSelectedModulesList
PreloadSelectedModulesList,
{ provide: RouteReuseStrategy, useClass: CustomReuseStrategy }
],
exports: [ RouterModule ]
})

View File

@ -1,13 +1,14 @@
import { Component, OnInit } from '@angular/core'
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
import { GuardsCheckStart, NavigationEnd, Router } from '@angular/router'
import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router'
import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core'
import { is18nPath } from '../../../shared/models/i18n'
import { ScreenService } from '@app/shared/misc/screen.service'
import { skip, debounceTime } from 'rxjs/operators'
import { HotkeysService, Hotkey } from 'angular2-hotkeys'
import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators'
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { fromEvent } from 'rxjs'
import { ViewportScroller } from '@angular/common'
@Component({
selector: 'my-app',
@ -22,6 +23,7 @@ export class AppComponent implements OnInit {
constructor (
private i18n: I18n,
private viewportScroller: ViewportScroller,
private router: Router,
private authService: AuthService,
private serverService: ServerService,
@ -52,15 +54,6 @@ export class AppComponent implements OnInit {
ngOnInit () {
document.getElementById('incompatible-browser').className += ' browser-ok'
this.router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
const pathname = window.location.pathname
if (!pathname || pathname === '/' || is18nPath(pathname)) {
this.redirectService.redirectToHomepage(true)
}
}
})
this.authService.loadClientCredentials()
if (this.isUserLoggedIn()) {
@ -81,15 +74,94 @@ export class AppComponent implements OnInit {
this.isMenuDisplayed = false
}
this.router.events.subscribe(
e => {
// User clicked on a link in the menu, change the page
if (e instanceof GuardsCheckStart && this.screenService.isInSmallView()) {
this.isMenuDisplayed = false
}
}
)
this.initRouteEvents()
this.injectJS()
this.injectCSS()
this.initHotkeys()
fromEvent(window, 'resize')
.pipe(debounceTime(200))
.subscribe(() => this.onResize())
}
isUserLoggedIn () {
return this.authService.isLoggedIn()
}
toggleMenu () {
this.isMenuDisplayed = !this.isMenuDisplayed
this.isMenuChangedByUser = true
}
onResize () {
this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
}
private initRouteEvents () {
let resetScroll = true
const eventsObs = this.router.events
const scrollEvent = eventsObs.pipe(filter((e: Event): e is Scroll => e instanceof Scroll))
const navigationEndEvent = eventsObs.pipe(filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd))
scrollEvent.subscribe(e => {
if (e.position) {
return this.viewportScroller.scrollToPosition(e.position)
}
if (e.anchor) {
return this.viewportScroller.scrollToAnchor(e.anchor)
}
if (resetScroll) {
return this.viewportScroller.scrollToPosition([ 0, 0 ])
}
})
// When we add the a-state parameter, we don't want to alter the scroll
navigationEndEvent.pipe(pairwise())
.subscribe(([ e1, e2 ]) => {
try {
resetScroll = false
const previousUrl = new URL(window.location.origin + e1.url)
const nextUrl = new URL(window.location.origin + e2.url)
if (previousUrl.pathname !== nextUrl.pathname) {
resetScroll = true
return
}
const nextSearchParams = nextUrl.searchParams
nextSearchParams.delete('a-state')
const previousSearchParams = previousUrl.searchParams
nextSearchParams.sort()
previousSearchParams.sort()
if (nextSearchParams.toString() !== previousSearchParams.toString()) {
resetScroll = true
}
} catch (e) {
console.error('Cannot parse URL to check next scroll.', e)
resetScroll = true
}
})
navigationEndEvent.pipe(
map(() => window.location.pathname),
filter(pathname => !pathname || pathname === '/' || is18nPath(pathname))
).subscribe(() => this.redirectService.redirectToHomepage(true))
eventsObs.pipe(
filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
filter(() => this.screenService.isInSmallView())
).subscribe(() => this.isMenuDisplayed = false) // User clicked on a link in the menu, change the page
}
private injectJS () {
// Inject JS
this.serverService.configLoaded
.subscribe(() => {
@ -104,7 +176,9 @@ export class AppComponent implements OnInit {
}
}
})
}
private injectCSS () {
// Inject CSS if modified (admin config settings)
this.serverService.configLoaded
.pipe(skip(1)) // We only want to subscribe to reloads, because the CSS is already injected by the server
@ -120,7 +194,9 @@ export class AppComponent implements OnInit {
this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
}
})
}
private initHotkeys () {
this.hotkeysService.add([
new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => {
document.getElementById('search-video').focus()
@ -155,22 +231,5 @@ export class AppComponent implements OnInit {
return false
}, undefined, this.i18n('Toggle Dark theme'))
])
fromEvent(window, 'resize')
.pipe(debounceTime(200))
.subscribe(() => this.onResize())
}
isUserLoggedIn () {
return this.authService.isLoggedIn()
}
toggleMenu () {
this.isMenuDisplayed = !this.isMenuDisplayed
this.isMenuChangedByUser = true
}
onResize () {
this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
}
}

View File

@ -0,0 +1,81 @@
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
export class CustomReuseStrategy implements RouteReuseStrategy {
storedRouteHandles = new Map<string, DetachedRouteHandle>()
recentlyUsed: string
private readonly MAX_SIZE = 2
// Decides if the route should be stored
shouldDetach (route: ActivatedRouteSnapshot): boolean {
return this.isReuseEnabled(route)
}
// Store the information for the route we're destructing
store (route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
if (!handle) return
const key = this.generateKey(route)
this.recentlyUsed = key
console.log('Storing component %s to reuse later.', key);
(handle as any).componentRef.instance.disableForReuse()
this.storedRouteHandles.set(key, handle)
this.gb()
}
// Return true if we have a stored route object for the next route
shouldAttach (route: ActivatedRouteSnapshot): boolean {
const key = this.generateKey(route)
return this.isReuseEnabled(route) && this.storedRouteHandles.has(key)
}
// If we returned true in shouldAttach(), now return the actual route data for restoration
retrieve (route: ActivatedRouteSnapshot): DetachedRouteHandle {
if (!this.isReuseEnabled(route)) return undefined
const key = this.generateKey(route)
this.recentlyUsed = key
console.log('Reusing component %s.', key)
const handle = this.storedRouteHandles.get(key)
if (!handle) return handle;
(handle as any).componentRef.instance.enabledForReuse()
return handle
}
// Reuse the route if we're going to and from the same route
shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig
}
private gb () {
if (this.storedRouteHandles.size >= this.MAX_SIZE) {
this.storedRouteHandles.forEach((r, key) => {
if (key === this.recentlyUsed) return
console.log('Removing stored component %s.', key);
(r as any).componentRef.destroy()
this.storedRouteHandles.delete(key)
})
}
}
private generateKey (route: ActivatedRouteSnapshot) {
const reuse = route.data.reuse
if (!reuse) return undefined
return reuse.key + JSON.stringify(route.queryParams)
}
private isReuseEnabled (route: ActivatedRouteSnapshot) {
return route.data.reuse && route.data.reuse.enabled && route.queryParams['a-state']
}
}

View File

@ -0,0 +1,7 @@
export interface DisableForReuseHook {
disableForReuse (): void
enabledForReuse (): void
}

View File

@ -19,13 +19,10 @@
<div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div>
<div
myInfiniteScroller
[pageHeight]="pageHeight" [firstLoadedPage]="firstLoadedPage"
(nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
class="videos" #videosElement
myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true"
class="videos"
>
<div *ngFor="let videos of videoPages; trackBy: pageByVideoId" class="videos-page">
<my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"></my-video-miniature>
</div>
<my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType">
</my-video-miniature>
</div>
</div>

View File

@ -1,66 +1,52 @@
import { debounceTime } from 'rxjs/operators'
import { ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'
import { OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Location } from '@angular/common'
import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
import { fromEvent, Observable, Subscription } from 'rxjs'
import { AuthService } from '../../core/auth'
import { ComponentPagination } from '../rest/component-pagination.model'
import { VideoSortField } from './sort-field.type'
import { Video } from './video.model'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { ScreenService } from '@app/shared/misc/screen.service'
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
import { Syndication } from '@app/shared/video/syndication.model'
import { Notifier } from '@app/core'
export abstract class AbstractVideoList implements OnInit, OnDestroy {
private static LINES_PER_PAGE = 4
@ViewChild('videosElement') videosElement: ElementRef
@ViewChild(InfiniteScrollerDirective) infiniteScroller: InfiniteScrollerDirective
import { Notifier, ServerService } from '@app/core'
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableForReuseHook {
pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 10,
itemsPerPage: 25,
totalItems: null
}
sort: VideoSortField = '-publishedAt'
categoryOneOf?: number
defaultSort: VideoSortField = '-publishedAt'
syndicationItems: Syndication[] = []
loadOnInit = true
marginContent = true
pageHeight: number
videoWidth: number
videoHeight: number
videoPages: Video[][] = []
videos: Video[] = []
ownerDisplayType: OwnerDisplayType = 'account'
firstLoadedPage: number
displayModerationBlock = false
titleTooltip: string
protected baseVideoWidth = 238
protected baseVideoHeight = 225
disabled = false
protected abstract notifier: Notifier
protected abstract authService: AuthService
protected abstract router: Router
protected abstract route: ActivatedRoute
protected abstract serverService: ServerService
protected abstract screenService: ScreenService
protected abstract i18n: I18n
protected abstract location: Location
protected abstract currentRoute: string
protected abstract router: Router
abstract titlePage: string
protected loadedPages: { [ id: number ]: Video[] } = {}
protected loadingPage: { [ id: number ]: boolean } = {}
protected otherRouteParams = {}
private resizeSubscription: Subscription
private angularState: number
abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }>
abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number}>
abstract generateSyndicationList (): void
get user () {
@ -77,207 +63,87 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
.subscribe(() => this.calcPageSizes())
this.calcPageSizes()
if (this.loadOnInit === true) this.loadMoreVideos(this.pagination.currentPage)
if (this.loadOnInit === true) this.loadMoreVideos()
}
ngOnDestroy () {
if (this.resizeSubscription) this.resizeSubscription.unsubscribe()
}
pageByVideoId (index: number, page: Video[]) {
// Video are unique in all pages
return page.length !== 0 ? page[0].id : 0
disableForReuse () {
this.disabled = true
}
enabledForReuse () {
this.disabled = false
}
videoById (index: number, video: Video) {
return video.id
}
onNearOfTop () {
this.previousPage()
}
onNearOfBottom () {
if (this.hasMoreVideos()) {
this.nextPage()
}
if (this.disabled) return
// Last page
if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
this.pagination.currentPage += 1
this.setScrollRouteParams()
this.loadMoreVideos()
}
onPageChanged (page: number) {
this.pagination.currentPage = page
this.setNewRouteParams()
}
reloadVideos () {
this.loadedPages = {}
this.loadMoreVideos(this.pagination.currentPage)
}
loadMoreVideos (page: number, loadOnTop = false) {
this.adjustVideoPageHeight()
const currentY = window.scrollY
if (this.loadedPages[page] !== undefined) return
if (this.loadingPage[page] === true) return
this.loadingPage[page] = true
const observable = this.getVideosObservable(page)
loadMoreVideos () {
const observable = this.getVideosObservable(this.pagination.currentPage)
observable.subscribe(
({ videos, totalVideos }) => {
this.loadingPage[page] = false
if (this.firstLoadedPage === undefined || this.firstLoadedPage > page) this.firstLoadedPage = page
// Paging is too high, return to the first one
if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) {
this.pagination.currentPage = 1
this.setNewRouteParams()
return this.reloadVideos()
}
this.loadedPages[page] = videos
this.buildVideoPages()
this.pagination.totalItems = totalVideos
// Initialize infinite scroller now we loaded the first page
if (Object.keys(this.loadedPages).length === 1) {
// Wait elements creation
setTimeout(() => {
this.infiniteScroller.initialize()
// At our first load, we did not load the first page
// Load the previous page so the user can move on the top (and browser previous pages)
if (this.pagination.currentPage > 1) this.loadMoreVideos(this.pagination.currentPage - 1, true)
}, 500)
}
// Insert elements on the top but keep the scroll in the previous position
if (loadOnTop) setTimeout(() => { window.scrollTo(0, currentY + this.pageHeight) }, 0)
this.videos = this.videos.concat(videos)
},
error => {
this.loadingPage[page] = false
this.notifier.error(error.message)
}
error => this.notifier.error(error.message)
)
}
reloadVideos () {
this.pagination.currentPage = 1
this.videos = []
this.loadMoreVideos()
}
toggleModerationDisplay () {
throw new Error('toggleModerationDisplay is not implemented')
}
protected hasMoreVideos () {
// No results
if (this.pagination.totalItems === 0) return false
// Not loaded yet
if (!this.pagination.totalItems) return true
const maxPage = this.pagination.totalItems / this.pagination.itemsPerPage
return maxPage > this.maxPageLoaded()
}
protected previousPage () {
const min = this.minPageLoaded()
if (min > 1) {
this.loadMoreVideos(min - 1, true)
}
}
protected nextPage () {
this.loadMoreVideos(this.maxPageLoaded() + 1)
}
protected buildRouteParams () {
// There is always a sort and a current page
const params = {
sort: this.sort,
page: this.pagination.currentPage
}
return Object.assign(params, this.otherRouteParams)
}
protected loadRouteParams (routeParams: { [ key: string ]: any }) {
this.sort = routeParams['sort'] as VideoSortField || this.defaultSort
this.categoryOneOf = routeParams['categoryOneOf']
if (routeParams['page'] !== undefined) {
this.pagination.currentPage = parseInt(routeParams['page'], 10)
} else {
this.pagination.currentPage = 1
}
}
protected setNewRouteParams () {
const paramsObject = this.buildRouteParams()
const queryParams = Object.keys(paramsObject)
.map(p => p + '=' + paramsObject[p])
.join('&')
this.location.replaceState(this.currentRoute, queryParams)
}
protected buildVideoPages () {
this.videoPages = Object.values(this.loadedPages)
}
protected adjustVideoPageHeight () {
const numberOfPagesLoaded = Object.keys(this.loadedPages).length
if (!numberOfPagesLoaded) return
this.pageHeight = this.videosElement.nativeElement.offsetHeight / numberOfPagesLoaded
}
protected buildVideoHeight () {
// Same ratios than base width/height
return this.videosElement.nativeElement.offsetWidth * (this.baseVideoHeight / this.baseVideoWidth)
}
private minPageLoaded () {
return Math.min(...Object.keys(this.loadedPages).map(e => parseInt(e, 10)))
}
private maxPageLoaded () {
return Math.max(...Object.keys(this.loadedPages).map(e => parseInt(e, 10)))
this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort
this.categoryOneOf = routeParams[ 'categoryOneOf' ]
this.angularState = routeParams[ 'a-state' ]
}
private calcPageSizes () {
if (this.screenService.isInMobileView() || this.baseVideoWidth === -1) {
if (this.screenService.isInMobileView()) {
this.pagination.itemsPerPage = 5
}
}
// Video takes all the width
this.videoWidth = -1
this.videoHeight = this.buildVideoHeight()
this.pageHeight = this.pagination.itemsPerPage * this.videoHeight
} else {
this.videoWidth = this.baseVideoWidth
this.videoHeight = this.baseVideoHeight
private setScrollRouteParams () {
// Already set
if (this.angularState) return
const videosWidth = this.videosElement.nativeElement.offsetWidth
this.pagination.itemsPerPage = Math.floor(videosWidth / this.videoWidth) * AbstractVideoList.LINES_PER_PAGE
this.pageHeight = this.videoHeight * AbstractVideoList.LINES_PER_PAGE
this.angularState = 42
const queryParams = {
'a-state': this.angularState,
categoryOneOf: this.categoryOneOf
}
// Rebuild pages because maybe we modified the number of items per page
const videos = [].concat(...this.videoPages)
this.loadedPages = {}
let path = this.router.url
if (!path || path === '/') path = this.serverService.getConfig().instance.defaultClientRoute
let i = 1
// Don't include the last page if it not complete
while (videos.length >= this.pagination.itemsPerPage && i < 10000) { // 10000 -> Hard limit in case of infinite loop
this.loadedPages[i] = videos.splice(0, this.pagination.itemsPerPage)
i++
}
// Re fetch the last page
if (videos.length !== 0) {
this.loadMoreVideos(i)
} else {
this.buildVideoPages()
}
console.log('Rebuilt pages with %s elements per page.', this.pagination.itemsPerPage)
this.router.navigate([ path ], { queryParams, replaceUrl: true, queryParamsHandling: 'merge' })
}
}

View File

@ -6,24 +6,15 @@ import { fromEvent, Subscription } from 'rxjs'
selector: '[myInfiniteScroller]'
})
export class InfiniteScrollerDirective implements OnInit, OnDestroy {
@Input() containerHeight: number
@Input() pageHeight: number
@Input() firstLoadedPage = 1
@Input() percentLimit = 70
@Input() autoInit = false
@Input() onItself = false
@Output() nearOfBottom = new EventEmitter<void>()
@Output() nearOfTop = new EventEmitter<void>()
@Output() pageChanged = new EventEmitter<number>()
private decimalLimit = 0
private lastCurrentBottom = -1
private lastCurrentTop = 0
private scrollDownSub: Subscription
private scrollUpSub: Subscription
private pageChangeSub: Subscription
private middleScreen: number
private container: HTMLElement
constructor (private el: ElementRef) {
@ -36,8 +27,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
ngOnDestroy () {
if (this.scrollDownSub) this.scrollDownSub.unsubscribe()
if (this.scrollUpSub) this.scrollUpSub.unsubscribe()
if (this.pageChangeSub) this.pageChangeSub.unsubscribe()
}
initialize () {
@ -45,8 +34,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
this.container = this.el.nativeElement
}
this.middleScreen = window.innerHeight / 2
// Emit the last value
const throttleOptions = { leading: true, trailing: true }
@ -72,40 +59,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
filter(({ current, maximumScroll }) => maximumScroll <= 0 || (current / maximumScroll) > this.decimalLimit)
)
.subscribe(() => this.nearOfBottom.emit())
// Scroll up
this.scrollUpSub = scrollObservable
.pipe(
// Check we scroll up
filter(({ current }) => {
const res = this.lastCurrentTop > current
this.lastCurrentTop = current
return res
}),
filter(({ current, maximumScroll }) => {
return current !== 0 && (1 - (current / maximumScroll)) > this.decimalLimit
})
)
.subscribe(() => this.nearOfTop.emit())
// Page change
this.pageChangeSub = scrollObservable
.pipe(
distinct(),
map(({ current }) => this.calculateCurrentPage(current)),
distinctUntilChanged()
)
.subscribe(res => this.pageChanged.emit(res))
}
private calculateCurrentPage (current: number) {
const scrollY = current + this.middleScreen
const page = Math.max(1, Math.ceil(scrollY / this.pageHeight))
// Offset page
return page + (this.firstLoadedPage - 1)
}
private getScrollInfo () {

View File

@ -1,7 +1,6 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { immutableAssign } from '@app/shared/misc/utils'
import { Location } from '@angular/common'
import { AuthService } from '../../core/auth'
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { VideoSortField } from '../../shared/video/sort-field.type'
@ -10,7 +9,7 @@ import { VideoFilter } from '../../../../../shared/models/videos/video-query.typ
import { I18n } from '@ngx-translate/i18n-polyfill'
import { ScreenService } from '@app/shared/misc/screen.service'
import { UserRight } from '../../../../../shared/models/users'
import { Notifier } from '@app/core'
import { Notifier, ServerService } from '@app/core'
@Component({
selector: 'my-videos-local',
@ -19,18 +18,17 @@ import { Notifier } from '@app/core'
})
export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
currentRoute = '/videos/local'
sort = '-publishedAt' as VideoSortField
filter: VideoFilter = 'local'
constructor (
protected router: Router,
protected serverService: ServerService,
protected route: ActivatedRoute,
protected notifier: Notifier,
protected authService: AuthService,
protected location: Location,
protected i18n: I18n,
protected screenService: ScreenService,
private i18n: I18n,
private videoService: VideoService
) {
super()

View File

@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Location } from '@angular/common'
import { immutableAssign } from '@app/shared/misc/utils'
import { AuthService } from '../../core/auth'
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@ -8,7 +7,7 @@ import { VideoSortField } from '../../shared/video/sort-field.type'
import { VideoService } from '../../shared/video/video.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { ScreenService } from '@app/shared/misc/screen.service'
import { Notifier } from '@app/core'
import { Notifier, ServerService } from '@app/core'
@Component({
selector: 'my-videos-recently-added',
@ -17,17 +16,16 @@ import { Notifier } from '@app/core'
})
export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
currentRoute = '/videos/recently-added'
sort: VideoSortField = '-publishedAt'
constructor (
protected router: Router,
protected route: ActivatedRoute,
protected location: Location,
protected serverService: ServerService,
protected router: Router,
protected notifier: Notifier,
protected authService: AuthService,
protected i18n: I18n,
protected screenService: ScreenService,
private i18n: I18n,
private videoService: VideoService
) {
super()

View File

@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Location } from '@angular/common'
import { immutableAssign } from '@app/shared/misc/utils'
import { AuthService } from '../../core/auth'
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@ -17,18 +16,16 @@ import { Notifier, ServerService } from '@app/core'
})
export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
currentRoute = '/videos/trending'
defaultSort: VideoSortField = '-trending'
constructor (
protected router: Router,
protected serverService: ServerService,
protected route: ActivatedRoute,
protected notifier: Notifier,
protected authService: AuthService,
protected location: Location,
protected screenService: ScreenService,
private serverService: ServerService,
protected i18n: I18n,
private i18n: I18n,
private videoService: VideoService
) {
super()

View File

@ -1,7 +1,6 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { immutableAssign } from '@app/shared/misc/utils'
import { Location } from '@angular/common'
import { AuthService } from '../../core/auth'
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { VideoSortField } from '../../shared/video/sort-field.type'
@ -9,7 +8,7 @@ import { VideoService } from '../../shared/video/video.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { ScreenService } from '@app/shared/misc/screen.service'
import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
import { Notifier } from '@app/core'
import { Notifier, ServerService } from '@app/core'
@Component({
selector: 'my-videos-user-subscriptions',
@ -18,18 +17,17 @@ import { Notifier } from '@app/core'
})
export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage: string
currentRoute = '/videos/subscriptions'
sort = '-publishedAt' as VideoSortField
ownerDisplayType: OwnerDisplayType = 'auto'
constructor (
protected router: Router,
protected serverService: ServerService,
protected route: ActivatedRoute,
protected notifier: Notifier,
protected authService: AuthService,
protected location: Location,
protected i18n: I18n,
protected screenService: ScreenService,
private i18n: I18n,
private videoService: VideoService
) {
super()

View File

@ -29,6 +29,10 @@ const videosRoutes: Routes = [
data: {
meta: {
title: 'Trending videos'
},
reuse: {
enabled: true,
key: 'trending-videos-list'
}
}
},
@ -38,6 +42,10 @@ const videosRoutes: Routes = [
data: {
meta: {
title: 'Recently added videos'
},
reuse: {
enabled: true,
key: 'recently-added-videos-list'
}
}
},
@ -47,6 +55,10 @@ const videosRoutes: Routes = [
data: {
meta: {
title: 'Subscriptions'
},
reuse: {
enabled: true,
key: 'subscription-videos-list'
}
}
},
@ -56,6 +68,10 @@ const videosRoutes: Routes = [
data: {
meta: {
title: 'Local videos'
},
reuse: {
enabled: true,
key: 'local-videos-list'
}
}
},

View File

@ -1,5 +1,4 @@
import * as Sequelize from 'sequelize'
import { Op } from 'sequelize'
import {
AllowNull,
BeforeDestroy,
@ -458,7 +457,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
const query = {
where: {
updatedAt: {
[Op.lt]: beforeUpdatedAt
[Sequelize.Op.lt]: beforeUpdatedAt
},
videoId
}

View File

@ -1,5 +1,4 @@
import * as Sequelize from 'sequelize'
import { Op } from 'sequelize'
import * as Bluebird from 'bluebird'
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
@ -206,7 +205,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
const query = {
where: {
updatedAt: {
[Op.lt]: beforeUpdatedAt
[Sequelize.Op.lt]: beforeUpdatedAt
},
videoId
}