forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			126 lines
		
	
	
		
			No EOL
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			126 lines
		
	
	
		
			No EOL
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import Combine from "./Combine";
 | |
| import BaseUIElement from "../BaseUIElement";
 | |
| import {Translation} from "../i18n/Translation";
 | |
| import {FixedUiElement} from "./FixedUiElement";
 | |
| import Title from "./Title";
 | |
| import List from "./List";
 | |
| import Hash from "../../Logic/Web/Hash";
 | |
| import Link from "./Link";
 | |
| import {Utils} from "../../Utils";
 | |
| 
 | |
| export default class TableOfContents extends Combine {
 | |
| 
 | |
|     private readonly titles: Title[]
 | |
| 
 | |
|     constructor(elements: Combine | Title[], options?: {
 | |
|         noTopLevel: false | boolean,
 | |
|         maxDepth?: number
 | |
|     }) {
 | |
|         let titles: Title[]
 | |
|         if (elements instanceof Combine) {
 | |
|             titles = TableOfContents.getTitles(elements.getElements()) ?? []
 | |
|         } else {
 | |
|             titles = elements ?? []
 | |
|         }
 | |
| 
 | |
|         let els: { level: number, content: BaseUIElement }[] = []
 | |
|         for (const title of titles) {
 | |
|             let content: BaseUIElement
 | |
|             if (title.title instanceof Translation) {
 | |
|                 content = title.title.Clone()
 | |
|             } else if (title.title instanceof FixedUiElement) {
 | |
|                 content = new FixedUiElement(title.title.content)
 | |
|             } else if (Utils.runningFromConsole) {
 | |
|                 content = new FixedUiElement(title.AsMarkdown())
 | |
|             } else if (title["title"] !== undefined) {
 | |
|                 content = new FixedUiElement(title.title.ConstructElement().textContent)
 | |
|             } else {
 | |
|                 console.log("Not generating a title for ", title)
 | |
|                 continue
 | |
|             }
 | |
| 
 | |
|             const vis = new Link(content, "#" + title.id)
 | |
| 
 | |
|             Hash.hash.addCallbackAndRun(h => {
 | |
|                 if (h === title.id) {
 | |
|                     vis.SetClass("font-bold")
 | |
|                 } else {
 | |
|                     vis.RemoveClass("font-bold")
 | |
|                 }
 | |
|             })
 | |
|             els.push({level: title.level, content: vis})
 | |
| 
 | |
|         }
 | |
|         const minLevel = Math.min(...els.map(e => e.level))
 | |
|         if (options?.noTopLevel) {
 | |
|             els = els.filter(e => e.level !== minLevel)
 | |
|         }
 | |
| 
 | |
|         if (options?.maxDepth) {
 | |
|             els = els.filter(e => e.level <= (options.maxDepth + minLevel))
 | |
|         }
 | |
| 
 | |
| 
 | |
|         super(TableOfContents.mergeLevel(els).map(el => el.SetClass("mt-2")));
 | |
|         this.SetClass("flex flex-col")
 | |
|         this.titles = titles;
 | |
|     }
 | |
| 
 | |
|     private static getTitles(elements: BaseUIElement[]): Title[] {
 | |
|         const titles = []
 | |
|         for (const uiElement of elements) {
 | |
|             if (uiElement instanceof Combine) {
 | |
|                 titles.push(...TableOfContents.getTitles(uiElement.getElements()))
 | |
|             } else if (uiElement instanceof Title) {
 | |
|                 titles.push(uiElement)
 | |
|             }
 | |
|         }
 | |
|         return titles
 | |
|     }
 | |
| 
 | |
|     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))
 | |
|         if (maxLevel === minLevel) {
 | |
|             return elements.map(e => e.content)
 | |
|         }
 | |
|         const result: { level: number, content: BaseUIElement } [] = []
 | |
|         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),
 | |
|                     level: maxLevel - 1
 | |
|                 })
 | |
|                 running = []
 | |
|             }
 | |
|             result.push(element)
 | |
|         }
 | |
|         if (running.length !== undefined) {
 | |
|             result.push({
 | |
|                 content: new List(running),
 | |
|                 level: maxLevel - 1
 | |
|             })
 | |
|         }
 | |
| 
 | |
|         return TableOfContents.mergeLevel(result)
 | |
|     }
 | |
| 
 | |
|     AsMarkdown(): string {
 | |
|         const depthIcons = ["1.", "  -", "    +", "      *"]
 | |
|         const lines = ["## Table of contents\n"];
 | |
|         const minLevel = Math.min(...this.titles.map(t => t.level))
 | |
|         for (const title of this.titles) {
 | |
|             const prefix = depthIcons[title.level - minLevel] ?? "        ~"
 | |
|             const text = title.title.AsMarkdown().replace("\n", "")
 | |
|             const link = title.id
 | |
|             lines.push(prefix + " [" + text + "](#" + link + ")")
 | |
|         }
 | |
| 
 | |
|         return lines.join("\n") + "\n\n"
 | |
|     }
 | |
| } |