Skip to content

Commit b3bef66

Browse files
authored
[ENG-9234] P10.1 - Bring back support for table of contents in wiki markdown #773
- Ticket: [ENG-9234] - Feature flag: n/a ## Summary of Changes 1. Added support for table of contents in wiki.
1 parent f58325d commit b3bef66

File tree

6 files changed

+105
-3
lines changed

6 files changed

+105
-3
lines changed

angular.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
"cedar-embeddable-editor",
3030
"cedar-artifact-viewer",
3131
"markdown-it-video",
32+
"markdown-it-anchor",
33+
"markdown-it-toc-done-right",
3234
"ace-builds/src-noconflict/ext-language_tools",
3335
"@traptitech/markdown-it-katex",
3436
"@citation-js/core",

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
"chart.js": "^4.4.9",
6060
"diff": "^8.0.2",
6161
"markdown-it": "^14.1.0",
62+
"markdown-it-anchor": "^9.2.0",
63+
"markdown-it-toc-done-right": "^4.2.0",
6264
"markdown-it-video": "^0.6.3",
6365
"ngx-captcha": "^13.0.0",
6466
"ngx-cookie-service": "^19.1.2",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
declare module 'markdown-it-toc-done-right' {
2+
import { PluginWithOptions } from 'markdown-it';
3+
4+
export interface TocOptions {
5+
placeholder: string;
6+
slugify: (s: string) => string;
7+
uniqueSlugStartIndex: number;
8+
containerClass: string;
9+
containerId: string;
10+
listClass: string;
11+
itemClass: string;
12+
linkClass: string;
13+
level: number | number[];
14+
listType: 'ol' | 'ul';
15+
format: (s: string) => string;
16+
callback: (tocCode: string, ast: TocAst) => void;
17+
}
18+
19+
export interface TocAst {
20+
l: number;
21+
n: string;
22+
c: TocAst[];
23+
}
24+
25+
const markdownItTocDoneRight: PluginWithOptions<Partial<TocOptions>>;
26+
export default markdownItTocDoneRight;
27+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<div class="md-editor-container" [innerHTML]="renderedHtml()"></div>
1+
<div #container class="md-editor-container" [innerHTML]="renderedHtml()"></div>

src/app/shared/components/markdown/markdown.component.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
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';
213
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
314

415
import { legacyImgSize } from '@mdit/plugin-img-size';
516
import markdownItKatex from '@traptitech/markdown-it-katex';
617
import MarkdownIt from 'markdown-it';
18+
import markdownItAnchor from 'markdown-it-anchor';
19+
import markdownItTocDoneRight from 'markdown-it-toc-done-right';
720
import 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

Comments
 (0)