Add ToC to generated pages

This commit is contained in:
Pieter Vander Vennet 2021-11-30 22:50:48 +01:00
parent b4529e4f63
commit 752538ec14
18 changed files with 346 additions and 243 deletions

View file

@ -47,18 +47,10 @@ export default class Combine extends BaseUIElement {
return el;
}
public getToC(): Title[]{
const titles = []
for (const uiElement of this.uiElements) {
if(uiElement instanceof Combine){
titles.push(...uiElement.getToC())
}else if(uiElement instanceof Title){
titles.push(uiElement)
}
}
return titles
public getElements(): BaseUIElement[]{
return this.uiElements
}
}

View file

@ -1,25 +1,26 @@
import BaseUIElement from "../BaseUIElement";
export class FixedUiElement extends BaseUIElement {
private _html: string;
public readonly content: string;
constructor(html: string) {
super();
this._html = html ?? "";
this.content = html ?? "";
}
InnerRender(): string {
return this._html;
return this.content;
}
AsMarkdown(): string {
return this._html;
return this.content;
}
protected InnerConstructElement(): HTMLElement {
const e = document.createElement("span")
e.innerHTML = this._html
e.innerHTML = this.content
return e;
}
}

View file

@ -10,7 +10,7 @@ export default class List extends BaseUIElement {
super();
this._ordered = ordered;
this.uiElements = Utils.NoNull(uiElements)
.map(Translations.W);
.map(s => Translations.W(s));
}
AsMarkdown(): string {

123
UI/Base/TableOfContents.ts Normal file
View file

@ -0,0 +1,123 @@
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"
}
}

View file

@ -1,6 +1,5 @@
import BaseUIElement from "../BaseUIElement";
import {FixedUiElement} from "./FixedUiElement";
import Hash from "../../Logic/Web/Hash";
export default class Title extends BaseUIElement {
public readonly title: BaseUIElement;
@ -17,7 +16,19 @@ export default class Title extends BaseUIElement {
this.title = embedded
}
this.level = level;
this.id = this.title.ConstructElement()?.innerText?.replace(/ /g, '_') ?? ""
let innerText : string = undefined;
if(typeof embedded === "string" ) {
innerText = embedded
}else if(embedded instanceof FixedUiElement){
innerText = embedded.content
}else{
this.title.ConstructElement()?.innerText
}
this.id = innerText?.replace(/ /g, '-')
?.replace(/[?#.;:/]/, "")
?.toLowerCase() ?? ""
this.SetClass(Title.defaultClassesPerLevel[level] ?? "")
}