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 { legacyImgSize } from '@mdit/plugin-img-size' ;
516import markdownItKatex from '@traptitech/markdown-it-katex' ;
617import MarkdownIt from 'markdown-it' ;
18+ import markdownItAnchor from 'markdown-it-anchor' ;
19+ import markdownItTocDoneRight from 'markdown-it-toc-done-right' ;
720import markdownItVideo from 'markdown-it-video' ;
821
922@Component ( {
@@ -13,11 +26,15 @@ import markdownItVideo from 'markdown-it-video';
1326 styleUrl : './markdown.component.scss' ,
1427 changeDetection : ChangeDetectionStrategy . OnPush ,
1528} )
16- export class MarkdownComponent {
29+ export class MarkdownComponent implements AfterViewInit {
1730 markdownText = input < string > ( '' ) ;
1831
32+ @ViewChild ( 'container' , { static : false } ) containerRef ?: ElementRef < HTMLElement > ;
33+
1934 private md : MarkdownIt ;
2035 private sanitizer = inject ( DomSanitizer ) ;
36+ private destroyRef = inject ( DestroyRef ) ;
37+ private clickHandler ?: ( event : MouseEvent ) => void ;
2138
2239 renderedHtml : Signal < SafeHtml > = computed ( ( ) => {
2340 const result = this . md . render ( this . markdownText ( ) ) ;
@@ -39,6 +56,42 @@ export class MarkdownComponent {
3956 output : 'mathml' ,
4057 throwOnError : false ,
4158 } )
59+ . use ( markdownItAnchor )
60+ . use ( markdownItTocDoneRight , {
61+ placeholder : '@\\[toc\\]' ,
62+ listType : 'ul' ,
63+ } )
4264 . use ( legacyImgSize ) ;
4365 }
66+
67+ ngAfterViewInit ( ) : void {
68+ this . setupClickHandler ( ) ;
69+ }
70+
71+ private setupClickHandler ( ) : void {
72+ if ( ! this . containerRef ?. nativeElement ) {
73+ return ;
74+ }
75+
76+ const container = this . containerRef . nativeElement ;
77+
78+ this . clickHandler = ( event : MouseEvent ) => {
79+ const anchor = ( event . target as HTMLElement ) . closest ( 'a' ) ;
80+ if ( ! anchor ?. hash ) {
81+ return ;
82+ }
83+
84+ const targetElement = document . getElementById ( anchor . hash . substring ( 1 ) ) ;
85+ if ( targetElement ) {
86+ event . preventDefault ( ) ;
87+ targetElement . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
88+ }
89+ } ;
90+
91+ container . addEventListener ( 'click' , this . clickHandler ) ;
92+
93+ this . destroyRef . onDestroy ( ( ) => {
94+ container . removeEventListener ( 'click' , this . clickHandler ! ) ;
95+ } ) ;
96+ }
4497}
0 commit comments