diff options
author | Kim <1877318+kimsible@users.noreply.github.com> | 2020-08-17 10:13:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-17 10:13:31 +0200 |
commit | 30d55e75cae1adec3fc43c84691975bf8b97db34 (patch) | |
tree | 74df7fabda72d88b60d6d8d20ba9cd95d1cf4858 | |
parent | 28fbb88f93859a7f6bbf124cb8df1e1a37fd1285 (diff) | |
download | PeerTube-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>
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 @@ | |||
1 | import { Subject, Subscription } from 'rxjs' | 1 | import { Subject, Subscription } from 'rxjs' |
2 | import { filter } from 'rxjs/operators' | 2 | import { filter } from 'rxjs/operators' |
3 | import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core' | 3 | import { Component, EventEmitter, Input, Output, OnDestroy, OnInit, ViewChild } from '@angular/core' |
4 | import { NavigationEnd, Router } from '@angular/router' | 4 | import { NavigationEnd, Router } from '@angular/router' |
5 | import { Notifier, User, UserNotificationSocket } from '@app/core' | 5 | import { Notifier, User, UserNotificationSocket } from '@app/core' |
6 | import { UserNotificationService } from '@app/shared/shared-main' | 6 | import { 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' | |||
2 | import * as debug from 'debug' | 2 | import * as debug from 'debug' |
3 | import { switchMap } from 'rxjs/operators' | 3 | import { switchMap } from 'rxjs/operators' |
4 | import { Component, OnInit, ViewChild } from '@angular/core' | 4 | import { Component, OnInit, ViewChild } from '@angular/core' |
5 | import { AuthService, AuthStatus, AuthUser, RedirectService, ScreenService, ServerService, UserService } from '@app/core' | 5 | import { Router } from '@angular/router' |
6 | import { AuthService, AuthStatus, AuthUser, MenuService, RedirectService, ScreenService, ServerService, UserService } from '@app/core' | ||
6 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' | 7 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' |
7 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' | 8 | import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' |
8 | import { ServerConfig, UserRight, VideoConstant } from '@shared/models' | 9 | import { 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 | } |