aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorKim <1877318+kimsible@users.noreply.github.com>2020-08-17 10:13:31 +0200
committerGitHub <noreply@github.com>2020-08-17 10:13:31 +0200
commit30d55e75cae1adec3fc43c84691975bf8b97db34 (patch)
tree74df7fabda72d88b60d6d8d20ba9cd95d1cf4858 /client
parent28fbb88f93859a7f6bbf124cb8df1e1a37fd1285 (diff)
downloadPeerTube-30d55e75cae1adec3fc43c84691975bf8b97db34.tar.gz
PeerTube-30d55e75cae1adec3fc43c84691975bf8b97db34.tar.zst
PeerTube-30d55e75cae1adec3fc43c84691975bf8b97db34.zip
Add restore scroll position on user-dropdown anchors links and scroll to top on active sub-menu links (#3066)
* Add restore scroll position on router same url * Remove settings top anchor * Add scrollToTop on active links fixed sub-menu * Add restore scroll position on notification avatar links * Toggle menu and close pophover when click on active dropdown menu-left link * Add onSameUrlRestoreScrollPosition on user dropdown channels link * Same behavior scrollTop and scroll to anchor everywhere Co-authored-by: kimsible <kimsible@users.noreply.github.com>
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.html2
-rw-r--r--client/src/app/app.component.ts2
-rw-r--r--client/src/app/menu/avatar-notification.component.html3
-rw-r--r--client/src/app/menu/avatar-notification.component.ts7
-rw-r--r--client/src/app/menu/menu.component.html18
-rw-r--r--client/src/app/menu/menu.component.ts33
-rw-r--r--client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html8
-rw-r--r--client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts10
8 files changed, 66 insertions, 17 deletions
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
index de8353851..2ad014f01 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
@@ -1,7 +1,5 @@
1<h1 class="sr-only" i18n>Settings</h1> 1<h1 class="sr-only" i18n>Settings</h1>
2<div class="form-row"> <!-- preview --> 2<div class="form-row"> <!-- preview -->
3 <div class="anchor" id="top"></div> <!-- top anchor -->
4
5 <div class="form-group col-12 col-lg-4 col-xl-3"></div> 3 <div class="form-group col-12 col-lg-4 col-xl-3"></div>
6 4
7 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9"> 5 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9">
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index ef0b1ae56..b8af4e2c7 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -121,7 +121,7 @@ export class AppComponent implements OnInit, AfterViewInit {
121 // scrollToAnchor first to preserve anchor position when using history navigation 121 // scrollToAnchor first to preserve anchor position when using history navigation
122 if (e.anchor) { 122 if (e.anchor) {
123 setTimeout(() => { 123 setTimeout(() => {
124 this.viewportScroller.scrollToAnchor(e.anchor) 124 document.getElementById(e.anchor).scrollIntoView({ behavior: 'smooth', inline: 'nearest' })
125 }) 125 })
126 126
127 return 127 return
diff --git a/client/src/app/menu/avatar-notification.component.html b/client/src/app/menu/avatar-notification.component.html
index 7999b3346..b24bd0309 100644
--- a/client/src/app/menu/avatar-notification.component.html
+++ b/client/src/app/menu/avatar-notification.component.html
@@ -21,6 +21,7 @@
21 <a 21 <a
22 i18n-title title="Update your notification preferences" class="glyphicon glyphicon-cog" 22 i18n-title title="Update your notification preferences" class="glyphicon glyphicon-cog"
23 routerLink="/my-account/settings" fragment="notifications" 23 routerLink="/my-account/settings" fragment="notifications"
24 #settingsNotifications (click)="onNavigate(settingsNotifications)"
24 ></a> 25 ></a>
25 </div> 26 </div>
26 </div> 27 </div>
@@ -34,7 +35,7 @@
34 [markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()" 35 [markAllAsReadSubject]="markAllAsReadSubject" (notificationsLoaded)="onNotificationLoaded()"
35 ></my-user-notifications> 36 ></my-user-notifications>
36 37
37 <a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications"> 38 <a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)">
38 <my-global-icon class="mr-1" iconName="inbox-full" aria-hidden="true"></my-global-icon> 39 <my-global-icon class="mr-1" iconName="inbox-full" aria-hidden="true"></my-global-icon>
39 <span i18n>See all your notifications</span> 40 <span i18n>See all your notifications</span>
40 </a> 41 </a>
diff --git a/client/src/app/menu/avatar-notification.component.ts b/client/src/app/menu/avatar-notification.component.ts
index 9a64faa6a..8b9955069 100644
--- a/client/src/app/menu/avatar-notification.component.ts
+++ b/client/src/app/menu/avatar-notification.component.ts
@@ -1,6 +1,6 @@
1import { Subject, Subscription } from 'rxjs' 1import { Subject, Subscription } from 'rxjs'
2import { filter } from 'rxjs/operators' 2import { filter } from 'rxjs/operators'
3import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core' 3import { Component, EventEmitter, Input, Output, OnDestroy, OnInit, ViewChild } from '@angular/core'
4import { NavigationEnd, Router } from '@angular/router' 4import { NavigationEnd, Router } from '@angular/router'
5import { Notifier, User, UserNotificationSocket } from '@app/core' 5import { Notifier, User, UserNotificationSocket } from '@app/core'
6import { UserNotificationService } from '@app/shared/shared-main' 6import { UserNotificationService } from '@app/shared/shared-main'
@@ -15,6 +15,7 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
15 @ViewChild('popover', { static: true }) popover: NgbPopover 15 @ViewChild('popover', { static: true }) popover: NgbPopover
16 16
17 @Input() user: User 17 @Input() user: User
18 @Output() navigate = new EventEmitter<HTMLAnchorElement>()
18 19
19 unreadNotifications = 0 20 unreadNotifications = 0
20 loaded = false 21 loaded = false
@@ -65,6 +66,10 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
65 this.loaded = true 66 this.loaded = true
66 } 67 }
67 68
69 onNavigate (link: HTMLAnchorElement) {
70 this.navigate.emit(link)
71 }
72
68 markAllAsRead () { 73 markAllAsRead () {
69 this.markAllAsReadSubject.next(true) 74 this.markAllAsReadSubject.next(true)
70 } 75 }
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index 7f83a6fb8..2011899d3 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -2,7 +2,7 @@
2 <menu [ngClass]="{ 'logged-in': isLoggedIn }"> 2 <menu [ngClass]="{ 'logged-in': isLoggedIn }">
3 <div class="top-menu"> 3 <div class="top-menu">
4 <div *ngIf="isLoggedIn" class="logged-in-block"> 4 <div *ngIf="isLoggedIn" class="logged-in-block">
5 <my-avatar-notification [user]="user"></my-avatar-notification> 5 <my-avatar-notification [user]="user" (navigate)="onSameUrlRestoreScrollPosition($event)"></my-avatar-notification>
6 6
7 <div class="logged-in-info"> 7 <div class="logged-in-info">
8 <a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="logged-in-display-name">{{ user.account?.displayName }}</a> 8 <a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="logged-in-display-name">{{ user.account?.displayName }}</a>
@@ -21,11 +21,13 @@
21 21
22 <div class="dropdown-divider"></div> 22 <div class="dropdown-divider"></div>
23 23
24 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="top"> 24 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings"
25 #settingsLink (click)="onSameUrlRestoreScrollPosition(settingsLink)">
25 <my-global-icon iconName="user" aria-hidden="true"></my-global-icon> <ng-container i18n>Account settings</ng-container> 26 <my-global-icon iconName="user" aria-hidden="true"></my-global-icon> <ng-container i18n>Account settings</ng-container>
26 </a> 27 </a>
27 28
28 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/video-channels"> 29 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/video-channels"
30 #channelsLink (click)="onSameUrlRestoreScrollPosition(channelsLink)">
29 <my-global-icon iconName="channel" aria-hidden="true"></my-global-icon> <ng-container i18n>Channels settings</ng-container> 31 <my-global-icon iconName="channel" aria-hidden="true"></my-global-icon> <ng-container i18n>Channels settings</ng-container>
30 </a> 32 </a>
31 33
@@ -37,13 +39,16 @@
37 <span class="ml-auto text-muted">{{ language }}</span> 39 <span class="ml-auto text-muted">{{ language }}</span>
38 </a> 40 </a>
39 41
40 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="video-languages-subtitles"> 42 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="video-languages-subtitles"
43 #settingsLanguagesSubtitles (click)="onSameUrlRestoreScrollPosition(settingsLanguagesSubtitles)">
41 <my-global-icon iconName="video-lang" aria-hidden="true"></my-global-icon> 44 <my-global-icon iconName="video-lang" aria-hidden="true"></my-global-icon>
42 <span i18n>Videos:</span> 45 <span i18n>Videos:</span>
43 <span class="ml-auto text-muted">{{ videoLanguages.join(', ') }}</span> 46 <span class="ml-auto text-muted">{{ videoLanguages.join(', ') }}</span>
44 </a> 47 </a>
45 48
46 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="video-sensitive-content-policy"> 49 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings"
50 fragment="video-sensitive-content-policy" #settingsSensitiveContentPolicy
51 (click)="onSameUrlRestoreScrollPosition(settingsSensitiveContentPolicy)">
47 <my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy === 'display' }" iconName="sensitive" aria-hidden="true"></my-global-icon> 52 <my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy === 'display' }" iconName="sensitive" aria-hidden="true"></my-global-icon>
48 <my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy !== 'display' }" iconName="unsensitive" aria-hidden="true"></my-global-icon> 53 <my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy !== 'display' }" iconName="unsensitive" aria-hidden="true"></my-global-icon>
49 <span i18n>Sensitive:</span> 54 <span i18n>Sensitive:</span>
@@ -56,7 +61,8 @@
56 <input type="checkbox" [checked]="user.webTorrentEnabled"/><label class="ml-auto" for="switch">Toggle p2p</label> 61 <input type="checkbox" [checked]="user.webTorrentEnabled"/><label class="ml-auto" for="switch">Toggle p2p</label>
57 </a> 62 </a>
58 63
59 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" fragment="top"> 64 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings"
65 #settingsMoreLink (click)="onSameUrlRestoreScrollPosition(settingsMoreLink)">
60 <my-global-icon iconName="more-horizontal" aria-hidden="true"></my-global-icon> <ng-container i18n>More account settings</ng-container> 66 <my-global-icon iconName="more-horizontal" aria-hidden="true"></my-global-icon> <ng-container i18n>More account settings</ng-container>
61 </a> 67 </a>
62 68
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts
index 3d6cbda24..f9a0a9f57 100644
--- a/client/src/app/menu/menu.component.ts
+++ b/client/src/app/menu/menu.component.ts
@@ -2,7 +2,8 @@ import { HotkeysService } from 'angular2-hotkeys'
2import * as debug from 'debug' 2import * as debug from 'debug'
3import { switchMap } from 'rxjs/operators' 3import { switchMap } from 'rxjs/operators'
4import { Component, OnInit, ViewChild } from '@angular/core' 4import { Component, OnInit, ViewChild } from '@angular/core'
5import { AuthService, AuthStatus, AuthUser, RedirectService, ScreenService, ServerService, UserService } from '@app/core' 5import { Router } from '@angular/router'
6import { AuthService, AuthStatus, AuthUser, MenuService, RedirectService, ScreenService, ServerService, UserService } from '@app/core'
6import { LanguageChooserComponent } from '@app/menu/language-chooser.component' 7import { LanguageChooserComponent } from '@app/menu/language-chooser.component'
7import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' 8import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component'
8import { ServerConfig, UserRight, VideoConstant } from '@shared/models' 9import { ServerConfig, UserRight, VideoConstant } from '@shared/models'
@@ -43,8 +44,10 @@ export class MenuComponent implements OnInit {
43 private serverService: ServerService, 44 private serverService: ServerService,
44 private redirectService: RedirectService, 45 private redirectService: RedirectService,
45 private hotkeysService: HotkeysService, 46 private hotkeysService: HotkeysService,
46 private screenService: ScreenService 47 private screenService: ScreenService,
47 ) { } 48 private menuService: MenuService,
49 private router: Router
50 ) { }
48 51
49 get isInMobileView () { 52 get isInMobileView () {
50 return this.screenService.isInMobileView() 53 return this.screenService.isInMobileView()
@@ -192,6 +195,30 @@ export class MenuComponent implements OnInit {
192 return this.languages.find(lang => lang.id === localeId).label 195 return this.languages.find(lang => lang.id === localeId).label
193 } 196 }
194 197
198 onSameUrlRestoreScrollPosition (link: HTMLAnchorElement) {
199 const linkURL = link.getAttribute('href')
200 const linkHash = link.getAttribute('fragment')
201
202 // On same url without fragment restore top scroll position
203 if (!linkHash && this.router.url.includes(linkURL)) {
204 window.scrollTo({
205 left: 0,
206 top: 0,
207 behavior: 'smooth'
208 })
209 }
210
211 // On same url with fragment restore anchor scroll position
212 if (linkHash && this.router.url === linkURL) {
213 const anchor = document.getElementById(link.getAttribute('fragment'))
214 anchor.scrollIntoView({ behavior: 'smooth', inline: 'nearest' })
215 }
216
217 if (this.screenService.isInSmallView()) {
218 this.menuService.toggleMenu()
219 }
220 }
221
195 private buildUserLanguages () { 222 private buildUserLanguages () {
196 if (!this.user) { 223 if (!this.user) {
197 this.videoLanguages = [] 224 this.videoLanguages = []
diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html
index 416bd9bc8..ee3346ea9 100644
--- a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html
+++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.html
@@ -1,7 +1,7 @@
1<div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }"> 1<div class="sub-menu" [ngClass]="{ 'sub-menu-fixed': !isBroadcastMessageDisplayed, 'no-scroll': isModalOpened }">
2 <ng-container *ngFor="let menuEntry of menuEntries; index as id"> 2 <ng-container *ngFor="let menuEntry of menuEntries; index as id">
3 3
4 <a *ngIf="menuEntry.routerLink && isDisplayed(menuEntry)" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings">{{ menuEntry.label }}</a> 4 <a *ngIf="menuEntry.routerLink && isDisplayed(menuEntry)" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings" #routerLink (click)="onActiveLinkScrollToTop(routerLink)">{{ menuEntry.label }}</a>
5 5
6 <div *ngIf="!menuEntry.routerLink && isDisplayed(menuEntry)" ngbDropdown class="parent-entry" 6 <div *ngIf="!menuEntry.routerLink && isDisplayed(menuEntry)" ngbDropdown class="parent-entry"
7 #dropdown="ngbDropdown" autoClose="true"> 7 #dropdown="ngbDropdown" autoClose="true">
@@ -28,7 +28,8 @@
28 <ng-container *ngFor="let menuChild of menuEntry.children"> 28 <ng-container *ngFor="let menuChild of menuEntry.children">
29 <a *ngIf="isDisplayed(menuChild)" class="dropdown-item" 29 <a *ngIf="isDisplayed(menuChild)" class="dropdown-item"
30 [ngClass]="{ icon: hasIcons }" 30 [ngClass]="{ icon: hasIcons }"
31 [routerLink]="menuChild.routerLink"> 31 [routerLink]="menuChild.routerLink"
32 #routerLink (click)="onActiveLinkScrollToTop(routerLink)">
32 <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon> 33 <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon>
33 34
34 {{ menuChild.label }} 35 {{ menuChild.label }}
@@ -46,7 +47,8 @@
46 <ng-container *ngFor="let menuChild of menuEntry.children"> 47 <ng-container *ngFor="let menuChild of menuEntry.children">
47 <a *ngIf="isDisplayed(menuChild)" 48 <a *ngIf="isDisplayed(menuChild)"
48 [ngClass]="{ icon: hasIcons }" 49 [ngClass]="{ icon: hasIcons }"
49 [routerLink]="menuChild.routerLink" routerLinkActive="active" (click)="dismissOtherModals()"> 50 [routerLink]="menuChild.routerLink" routerLinkActive="active"
51 #routerLink (click)="dismissOtherModals(); onActiveLinkScrollToTop(routerLink)">
50 <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon> 52 <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon>
51 53
52 {{ menuChild.label }} 54 {{ menuChild.label }}
diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts
index 043b647c9..ba5568595 100644
--- a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts
+++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.ts
@@ -94,6 +94,16 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy {
94 this.isModalOpened = false 94 this.isModalOpened = false
95 } 95 }
96 96
97 onActiveLinkScrollToTop (link: HTMLAnchorElement) {
98 if (!this.isBroadcastMessageDisplayed && this.router.url.includes(link.getAttribute('href'))) {
99 window.scrollTo({
100 left: 0,
101 top: 0,
102 behavior: 'smooth'
103 })
104 }
105 }
106
97 dismissOtherModals () { 107 dismissOtherModals () {
98 this.modalService.dismissAll() 108 this.modalService.dismissAll()
99 } 109 }