| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import BaseUIElement from "../BaseUIElement" | 
					
						
							|  |  |  | import List from "./List" | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  | import { marked } from "marked" | 
					
						
							|  |  |  | import { parse as parse_html } from "node-html-parser" | 
					
						
							| 
									
										
										
										
											2024-05-07 00:42:52 +02:00
										 |  |  | import { default as turndown } from "turndown" | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import { Utils } from "../../Utils" | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  | export default class TableOfContents { | 
					
						
							|  |  |  |     private static asLinkableId(text: string): string { | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |         return ( | 
					
						
							|  |  |  |             text | 
					
						
							|  |  |  |                 ?.replace(/ /g, "-") | 
					
						
							|  |  |  |                 ?.replace(/[?#.;:/]/, "") | 
					
						
							|  |  |  |                 ?.toLowerCase() ?? "" | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-18 18:52:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private static mergeLevel( | 
					
						
							|  |  |  |         elements: { level: number; content: BaseUIElement }[] | 
					
						
							|  |  |  |     ): BaseUIElement[] { | 
					
						
							|  |  |  |         const maxLevel = Math.max(...elements.map((e) => e.level)) | 
					
						
							|  |  |  |         const minLevel = Math.min(...elements.map((e) => e.level)) | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  |         if (maxLevel === minLevel) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             return elements.map((e) => e.content) | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const result: { level: number; content: BaseUIElement }[] = [] | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  |         let running: BaseUIElement[] = [] | 
					
						
							|  |  |  |         for (const element of elements) { | 
					
						
							|  |  |  |             if (element.level === maxLevel) { | 
					
						
							|  |  |  |                 running.push(element.content) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (running.length !== undefined) { | 
					
						
							|  |  |  |                 result.push({ | 
					
						
							|  |  |  |                     content: new List(running), | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |                     level: maxLevel - 1, | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  |                 }) | 
					
						
							|  |  |  |                 running = [] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             result.push(element) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (running.length !== undefined) { | 
					
						
							|  |  |  |             result.push({ | 
					
						
							|  |  |  |                 content: new List(running), | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |                 level: maxLevel - 1, | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return TableOfContents.mergeLevel(result) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |     public static insertTocIntoMd(md: string): string { | 
					
						
							|  |  |  |         const htmlSource = <string>marked.parse(md) | 
					
						
							|  |  |  |         const el = parse_html(htmlSource) | 
					
						
							|  |  |  |         const structure = TableOfContents.generateStructure(<any>el) | 
					
						
							| 
									
										
										
										
											2024-05-07 00:42:52 +02:00
										 |  |  |         const firstTitle = structure[1] | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |         let minDepth = undefined | 
					
						
							|  |  |  |         do { | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |             minDepth = Math.min(...structure.map((s) => s.depth)) | 
					
						
							|  |  |  |             const minDepthCount = structure.filter((s) => s.depth === minDepth) | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |             if (minDepthCount.length > 1) { | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             // Erase a single top level heading
 | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |             structure.splice( | 
					
						
							|  |  |  |                 structure.findIndex((s) => s.depth === minDepth), | 
					
						
							|  |  |  |                 1 | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |         } while (structure.length > 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (structure.length <= 1) { | 
					
						
							|  |  |  |             return md | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |         const separators = { | 
					
						
							|  |  |  |             1: "  -", | 
					
						
							|  |  |  |             2: "    +", | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |             3: "       *", | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let toc = "" | 
					
						
							|  |  |  |         let topLevelCount = 0 | 
					
						
							|  |  |  |         for (const el of structure) { | 
					
						
							|  |  |  |             const depthDiff = el.depth - minDepth | 
					
						
							| 
									
										
										
										
											2024-05-07 00:42:52 +02:00
										 |  |  |             const link = `[${el.title}](#${TableOfContents.asLinkableId(el.title)})` | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |             if (depthDiff === 0) { | 
					
						
							|  |  |  |                 topLevelCount++ | 
					
						
							|  |  |  |                 toc += `${topLevelCount}. ${link}\n` | 
					
						
							|  |  |  |             } else if (depthDiff <= 3) { | 
					
						
							|  |  |  |                 toc += `${separators[depthDiff]} ${link}\n` | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const heading = Utils.Times(() => "#", firstTitle.depth) | 
					
						
							| 
									
										
										
										
											2024-05-07 00:42:52 +02:00
										 |  |  |         toc = heading + " Table of contents\n\n" + toc | 
					
						
							| 
									
										
										
										
											2022-01-18 18:52:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-07 00:42:52 +02:00
										 |  |  |         const firstTitleIndex = md.indexOf(firstTitle.title) | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-07 00:42:52 +02:00
										 |  |  |         const intro = md.substring(0, firstTitleIndex) | 
					
						
							|  |  |  |         const splitPoint = intro.lastIndexOf("\n") | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-12 03:17:15 +02:00
										 |  |  |         return md.substring(0, splitPoint) +"\n" toc + md.substring(splitPoint) | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |     public static generateStructure( | 
					
						
							|  |  |  |         html: Element | 
					
						
							|  |  |  |     ): { depth: number; title: string; el: Element }[] { | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |         if (html === undefined) { | 
					
						
							|  |  |  |             return [] | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |         return [].concat( | 
					
						
							|  |  |  |             ...Array.from(html.childNodes ?? []).map((child) => { | 
					
						
							| 
									
										
										
										
											2024-04-28 03:48:07 +02:00
										 |  |  |                 const tag: string = child["tagName"]?.toLowerCase() | 
					
						
							|  |  |  |                 if (!tag) { | 
					
						
							|  |  |  |                     return [] | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (tag.match(/h[0-9]/)) { | 
					
						
							|  |  |  |                     const depth = Number(tag.substring(1)) | 
					
						
							|  |  |  |                     return [{ depth, title: child.textContent, el: child }] | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return TableOfContents.generateStructure(<Element>child) | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |             }) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-11-30 22:50:48 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | } |