forked from MapComplete/MapComplete
		
	
		
			
	
	
		
			123 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			123 lines
		
	
	
	
		
			4.1 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 = title.title | ||
|  |             }else if(Utils.runningFromConsole){ | ||
|  |                 content = new FixedUiElement(title.AsMarkdown()) | ||
|  |             } else { | ||
|  |                 content = new FixedUiElement(title.title.ConstructElement().innerText) | ||
|  |             } | ||
|  | 
 | ||
|  |             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" | ||
|  |     } | ||
|  | } |