All files / app/features/article/pages/article-page/tabs/article-content-tab article-content-tab.component.ts

90.47% Statements 38/42
100% Branches 15/15
66.66% Functions 4/6
90.24% Lines 37/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 871x 1x 1x 1x 1x 1x 1x 1x   1x               1x 8x 8x 8x 8x 8x 8x 8x 8x 8x                     8x 12x 12x         12x   12x 4x     8x 8x 8x                 8x 2x     6x 6x 4x 4x 4x   4x         2x                    
import { ArticleContentComponent } from '../../../../components/article-content/article-content.component';
import { ArticleSidebarActionsComponent } from '../../../../components/article-sidebar-actions/article-sidebar-actions.component';
import { ArticlePageService } from '../../../../services/article-page.service';
import { DOCUMENT } from '@angular/common';
import { afterNextRender, ChangeDetectionStrategy, Component, DestroyRef, inject, Injector } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import { LoggerService } from '@drevo-web/core';
import { ModerationResult } from '@drevo-web/shared';
import { distinctUntilChanged } from 'rxjs/operators';
 
@Component({
    selector: 'app-article-content-tab',
    imports: [ArticleContentComponent, ArticleSidebarActionsComponent],
    templateUrl: './article-content-tab.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleContentTabComponent {
    private readonly pageService = inject(ArticlePageService);
    readonly article = this.pageService.article;
    readonly editUrl = this.pageService.editUrl;
    private readonly document = inject(DOCUMENT);
    private readonly route = inject(ActivatedRoute);
    private readonly destroyRef = inject(DestroyRef);
    private readonly injector = inject(Injector);
    private readonly logger = inject(LoggerService).withContext('ArticleContentTab');
    private currentFragment: string | undefined = undefined;
 
    onModerated(result: ModerationResult): void {
        this.pageService.updateApproval(result.approved, result.comment);
    }
 
    onTopicsChanged(topics: ReadonlyArray<number>): void {
        this.pageService.updateTopics(topics);
    }
 
    constructor() {
        this.route.fragment.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe(fragment => {
            this.currentFragment = fragment ?? undefined;
            this.scrollToFragment();
        });
    }
 
    private scrollToFragment(): void {
        afterNextRender(
            () => {
                if (!this.currentFragment || !this.article()) {
                    return;
                }
 
                let targetElement: Element | undefined = undefined;
                try {
                    targetElement =
                        this.document.getElementById(this.currentFragment) ||
                        this.document.querySelector(`a[name="${CSS.escape(this.currentFragment)}"]`) ||
                        undefined;
                } catch (error) {
                    this.logger.error('scrollToFragment: querySelector failed', { error });
                    return;
                }
 
                if (!targetElement) {
                    return;
                }
 
                const mainContainer = this.document.getElementById('content');
                if (mainContainer) {
                    const targetRect = targetElement.getBoundingClientRect();
                    const containerRect = mainContainer.getBoundingClientRect();
                    const scrollTop = targetRect.top - containerRect.top + mainContainer.scrollTop;
 
                    mainContainer.scrollTo({
                        top: scrollTop,
                        behavior: 'smooth',
                    });
                } else {
                    targetElement.scrollIntoView({
                        behavior: 'smooth',
                        block: 'start',
                    });
                }
            },
            { injector: this.injector },
        );
    }
}