1- import { ChangeDetectionStrategy , Component , computed , inject , input , Signal } from '@angular/core' ;
1+ import {
2+ AfterViewInit ,
3+ ChangeDetectionStrategy ,
4+ Component ,
5+ computed ,
6+ DestroyRef ,
7+ ElementRef ,
8+ inject ,
9+ input ,
10+ Signal ,
11+ ViewChild ,
12+ } from '@angular/core' ;
213import { DomSanitizer , SafeHtml } from '@angular/platform-browser' ;
314
415import markdownItKatex from '@traptitech/markdown-it-katex' ;
516import MarkdownIt from 'markdown-it' ;
17+ import markdownItAnchor from 'markdown-it-anchor' ;
18+ import markdownItTocDoneRight from 'markdown-it-toc-done-right' ;
619import markdownItVideo from 'markdown-it-video' ;
720
821@Component ( {
@@ -12,11 +25,15 @@ import markdownItVideo from 'markdown-it-video';
1225 styleUrl : './markdown.component.scss' ,
1326 changeDetection : ChangeDetectionStrategy . OnPush ,
1427} )
15- export class MarkdownComponent {
28+ export class MarkdownComponent implements AfterViewInit {
1629 markdownText = input < string > ( '' ) ;
1730
31+ @ViewChild ( 'container' , { static : false } ) containerRef ?: ElementRef < HTMLElement > ;
32+
1833 private md : MarkdownIt ;
1934 private sanitizer = inject ( DomSanitizer ) ;
35+ private destroyRef = inject ( DestroyRef ) ;
36+ private clickHandler ?: ( event : MouseEvent ) => void ;
2037
2138 renderedHtml : Signal < SafeHtml > = computed ( ( ) => {
2239 const result = this . md . render ( this . markdownText ( ) ) ;
@@ -37,6 +54,42 @@ export class MarkdownComponent {
3754 . use ( markdownItKatex , {
3855 output : 'mathml' ,
3956 throwOnError : false ,
57+ } )
58+ . use ( markdownItAnchor )
59+ . use ( markdownItTocDoneRight , {
60+ placeholder : '@\\[toc\\]' ,
61+ listType : 'ul' ,
4062 } ) ;
4163 }
64+
65+ ngAfterViewInit ( ) : void {
66+ this . setupClickHandler ( ) ;
67+ }
68+
69+ private setupClickHandler ( ) : void {
70+ if ( ! this . containerRef ?. nativeElement ) {
71+ return ;
72+ }
73+
74+ const container = this . containerRef . nativeElement ;
75+
76+ this . clickHandler = ( event : MouseEvent ) => {
77+ const anchor = ( event . target as HTMLElement ) . closest ( 'a' ) ;
78+ if ( ! anchor ?. hash ) {
79+ return ;
80+ }
81+
82+ const targetElement = document . getElementById ( anchor . hash . substring ( 1 ) ) ;
83+ if ( targetElement ) {
84+ event . preventDefault ( ) ;
85+ targetElement . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
86+ }
87+ } ;
88+
89+ container . addEventListener ( 'click' , this . clickHandler ) ;
90+
91+ this . destroyRef . onDestroy ( ( ) => {
92+ container . removeEventListener ( 'click' , this . clickHandler ! ) ;
93+ } ) ;
94+ }
4295}
0 commit comments