diff --git a/src/app/app.component.html b/src/app/app.component.html
index e2324427..2abe8c1e 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -4,7 +4,7 @@
-
+
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 0ce5615f..78b67cfe 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -9,6 +9,7 @@ import { MarkdownModule } from 'ngx-markdown';
import { SharedModule } from './shared/shared.module';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { ScrollingModule } from '@angular/cdk/scrolling';
@NgModule({
declarations: [AppComponent],
@@ -21,6 +22,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
SharedModule,
MarkdownModule.forRoot({ loader: HttpClient }),
FontAwesomeModule,
+ ScrollingModule,
],
providers: [],
bootstrap: [AppComponent],
diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.css b/src/app/shared/components/scroll-spy/scroll-spy.component.css
new file mode 100644
index 00000000..39257af9
--- /dev/null
+++ b/src/app/shared/components/scroll-spy/scroll-spy.component.css
@@ -0,0 +1,40 @@
+.root {
+ padding: 4px;
+ padding-left: 8px;
+ display: inline-table;
+ position: sticky;
+ top: 20px;
+ border-left: 1px solid gray;
+ display: inline-table;
+}
+
+.section {
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.6em;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+.actual-section-indicator {
+ width: 0;
+ height: 10px;
+ margin-left: -17px;
+ margin-right: 15px;
+ border-radius: 100px;
+ background-color: white;
+ border: 1px solid white;
+ flex: none;
+ display: inline-block;
+ transition: all 0.3s;
+}
+
+.section:hover {
+ color: rgb(148, 7, 78);
+}
+
+.actual-section-indicator.active {
+ width: 16px;
+ margin-right: 5px;
+ border: 1px solid black;
+}
diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.html b/src/app/shared/components/scroll-spy/scroll-spy.component.html
new file mode 100644
index 00000000..4078c7bb
--- /dev/null
+++ b/src/app/shared/components/scroll-spy/scroll-spy.component.html
@@ -0,0 +1,13 @@
+
diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.spec.ts b/src/app/shared/components/scroll-spy/scroll-spy.component.spec.ts
new file mode 100644
index 00000000..a99f11b7
--- /dev/null
+++ b/src/app/shared/components/scroll-spy/scroll-spy.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ScrollSpyComponent } from './scroll-spy.component';
+
+describe('ScrollSpyComponent', () => {
+ let component: ScrollSpyComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ ScrollSpyComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ScrollSpyComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.ts b/src/app/shared/components/scroll-spy/scroll-spy.component.ts
new file mode 100644
index 00000000..2cd5db07
--- /dev/null
+++ b/src/app/shared/components/scroll-spy/scroll-spy.component.ts
@@ -0,0 +1,88 @@
+import {
+ Component,
+ AfterContentChecked,
+ ChangeDetectorRef,
+ OnInit,
+} from '@angular/core';
+import { debounce, throttle, debounceTime } from 'rxjs/operators';
+import { ScrollDispatcher, CdkScrollable } from '@angular/cdk/overlay';
+import { interval } from 'rxjs';
+
+@Component({
+ selector: 'rxjs-scroll-spy',
+ templateUrl: './scroll-spy.component.html',
+ styleUrls: ['./scroll-spy.component.css'],
+})
+export class ScrollSpyComponent implements AfterContentChecked, OnInit {
+ currentSection = '';
+ sections = [];
+ private sectionsHeader: NodeListOf;
+
+ constructor(
+ private scroll: ScrollDispatcher,
+ private changeDetector: ChangeDetectorRef
+ ) {}
+
+ ngOnInit() {
+ this.scroll
+ .scrolled()
+ .pipe(throttle((ev) => interval(100)))
+ .subscribe((scroll: CdkScrollable) => this.onScroll(scroll));
+ }
+
+ ngAfterContentChecked(): void {
+ this.loadSections();
+ }
+
+ scrollTo(sectionId) {
+ document.querySelector('#' + sectionId).scrollIntoView();
+ }
+
+ isActualSection(sectionId: string) {
+ return sectionId === this.currentSection;
+ }
+
+ private loadSections() {
+ this.sectionsHeader = document.querySelectorAll('h2');
+ this.sections = [];
+ this.sectionsHeader.forEach((header: HTMLHeadingElement) => {
+ this.sections.push({
+ name: header.textContent,
+ id: header.id,
+ });
+ });
+ }
+
+ private onScroll(scroll: CdkScrollable) {
+ const scrollTop = scroll.getElementRef().nativeElement.scrollTop || 0;
+ const parentOffset = scroll.getElementRef().nativeElement.offsetTop;
+ const currentSection = this.getCurrentSection({ scrollTop, parentOffset });
+
+ if (!currentSection || currentSection.id === this.currentSection) {
+ return;
+ }
+
+ this.currentSection = currentSection.id;
+ this.changeDetector.detectChanges();
+ }
+
+ private getCurrentSection({ scrollTop, parentOffset }) {
+ const sectionsCount = this.sectionsHeader.length;
+ let actualHeader;
+ for (let i = 0; i < sectionsCount; i++) {
+ const header = this.sectionsHeader[i];
+ if (header.offsetTop - parentOffset <= scrollTop) {
+ actualHeader = header;
+ }
+ }
+
+ return actualHeader;
+
+ /* For some reason, this (and similar) DONT work. Only get first element
+ return [].find.call(
+ this.sectionsHeader,
+ (header: HTMLHeadingElement) =>
+ header.offsetTop - parentOffset <= scrollTop
+ ); */
+ }
+}
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 9c8910ce..fcac3058 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -23,10 +23,16 @@ import { NgModule } from '@angular/core';
import { MatDividerModule } from '@angular/material/divider';
import { SidenavComponent } from './components/sidenav/sidenav.component';
import { RouterModule } from '@angular/router';
+import { ScrollSpyComponent } from './components/scroll-spy/scroll-spy.component';
const CORE_MODULES = [CommonModule, FormsModule, ReactiveFormsModule];
-const COMPONENTS = [FooterComponent, HeaderComponent, SidenavComponent];
+const COMPONENTS = [
+ FooterComponent,
+ HeaderComponent,
+ SidenavComponent,
+ ScrollSpyComponent,
+];
const MATERIAL_MODULES = [
MatInputModule,
@@ -49,7 +55,7 @@ const MATERIAL_MODULES = [
];
@NgModule({
- declarations: COMPONENTS,
+ declarations: [COMPONENTS, ScrollSpyComponent],
imports: [MATERIAL_MODULES, CORE_MODULES, FlexLayoutModule],
exports: [COMPONENTS, MATERIAL_MODULES, CORE_MODULES, FlexLayoutModule],
})
diff --git a/src/app/views/home/content/content.component.css b/src/app/views/home/content/content.component.css
index c22fa93e..c91e4da1 100644
--- a/src/app/views/home/content/content.component.css
+++ b/src/app/views/home/content/content.component.css
@@ -1,3 +1,12 @@
+.md-container {
+ display: flex;
+}
+
+.md-container > * {
+ margin: 0 24px;
+ min-width: 0; /* Fix flex horizontal overflow */
+}
+
details {
border-top: 2px solid #e3e7e7;
margin: 50px 0 0;
@@ -65,6 +74,10 @@ th {
padding: 25px;
}
+.scrollspy-container {
+ max-width: 450px;
+}
+
.page-heading {
display: flex;
justify-content: space-between;
@@ -114,3 +127,10 @@ summary:hover {
padding: 40px;
}
}
+
+@media only screen and (max-width: 700px) {
+ .scrollspy-container {
+ display: none;
+ }
+}
+
diff --git a/src/app/views/home/content/content.component.html b/src/app/views/home/content/content.component.html
index d85837d4..89942c31 100644
--- a/src/app/views/home/content/content.component.html
+++ b/src/app/views/home/content/content.component.html
@@ -1,3 +1,8 @@