core/renderers/wiki.renderer.ts
core/renderers/wiki.renderer.ts is a file in Bane's Lab Content. 171 lines of code and 31 definitions.
import {
ATTRIBUTION_SEPARATOR,
DISCUSSIONS_LABEL,
HOME_LABEL,
RELEASES_LABEL,
REPOSITORY_LABEL,
SIDEBAR_LINKS,
SIDEBAR_NAVIGATION,
SIDEBAR_READING,
WIKI_READING_LEAD,
} from "#configuration/strings/chapter.strings";
import type {
Attribution,
Chapter,
ChapterSource,
LinkResolver,
PageSources,
ShapeInput,
WikiPage,
} from "#types/chapter.types";
import {
CHAPTER_SEPARATOR,
DISCUSSIONS_PATH,
FOOTER_FILE,
GROUP_SEPARATOR,
HOME_FILE,
LIST_ITEM,
MARKDOWN_EXTENSION,
PAGE_NUMBER_PAD,
PAGE_NUMBER_WIDTH,
PARAGRAPH_BREAK,
RELEASES_PATH,
SIDEBAR_FILE,
WORD_SEPARATOR,
} from "#configuration/constants/chapter.constants";
import { METHODOLOGY_REPOSITORY, SITE_URL, tabLink } from "@banes-lab/web/core/assets/link.assets.ts";
import { attributionLine, link } from "#core/renderers/markdown.renderer";
import { LINE_END } from "#configuration/constants/inventory.constants";
import { METHODOLOGY_PAGE } from "@banes-lab/web/core/ids/page.ids.ts";
import { SITE_ONLY_TABS } from "#configuration/constants/readme.constants";
import { relinked } from "#core/normalizers/link.normalizer";
const HOME_STEM = HOME_FILE.slice(0, -MARKDOWN_EXTENSION.length);
const RULE = CHAPTER_SEPARATOR.trim();
const GROUP_MARK = "**";
const wordsOf = function wordsOf(label: string): string {
return label
.split(" ")
.filter((word) => word.length > 0)
.join(WORD_SEPARATOR);
};
export const pageStemOf = function pageStemOf(
source: ChapterSource,
index: number,
group: string | null = null,
): string {
const number = String(index).padStart(PAGE_NUMBER_WIDTH, PAGE_NUMBER_PAD);
const words =
group === null || group === source.label
? wordsOf(source.label)
: wordsOf(group) + GROUP_SEPARATOR + wordsOf(source.label);
return number + WORD_SEPARATOR + words;
};
export const isSiteOnly = function isSiteOnly(page: string, tab: string): boolean {
return SITE_ONLY_TABS.some((entry) => entry.page === page && entry.tab === tab);
};
const pagesOf = function pagesOf(pages: readonly PageSources[]): WikiPage[] {
const grouped = pages.length > 1;
let index = 0;
return pages.flatMap((page) =>
page.sources
.filter((source) => !source.first || grouped)
.map((source) => {
if (isSiteOnly(page.page, source.tab)) {
return {
group: page.label,
hosted: false,
href: SITE_URL + tabLink(page.page, source.tab),
page: page.page,
source,
};
}
index += 1;
return {
group: page.label,
hosted: true,
href: pageStemOf(source, index, grouped ? page.label : null),
page: page.page,
source,
};
}),
);
};
export const wikiResolver = function wikiResolver(pages: readonly WikiPage[], front: string | null): LinkResolver {
return (page, tab) => {
const found = pages.find(
(candidate) =>
candidate.page === page && (tab === null ? candidate.source.first : candidate.source.tab === tab),
);
if (found !== undefined) {
return found.href;
}
return page === front && tab === null ? HOME_STEM : null;
};
};
const pageLink = function pageLink(page?: WikiPage): string {
return page === undefined ? link(HOME_LABEL, HOME_STEM) : link(page.source.label, page.href);
};
const readingList = function readingList(pages: readonly WikiPage[]): string {
const groups = [...new Set(pages.map((page) => page.group))];
if (groups.length <= 1) {
return pages.map((page) => LIST_ITEM + pageLink(page)).join(LINE_END);
}
return groups
.map((group) =>
[
GROUP_MARK + group + GROUP_MARK,
...pages.filter((page) => page.group === group).map((page) => LIST_ITEM + pageLink(page)),
].join(LINE_END),
)
.join(PARAGRAPH_BREAK);
};
const sidebarOf = function sidebarOf(pages: readonly WikiPage[]): string {
const lines = [
SIDEBAR_NAVIGATION,
"",
pageLink(),
RULE,
SIDEBAR_READING,
readingList(pages),
RULE,
SIDEBAR_LINKS,
LIST_ITEM + link(REPOSITORY_LABEL, METHODOLOGY_REPOSITORY),
LIST_ITEM + link(DISCUSSIONS_LABEL, METHODOLOGY_REPOSITORY + DISCUSSIONS_PATH),
LIST_ITEM + link(RELEASES_LABEL, METHODOLOGY_REPOSITORY + RELEASES_PATH),
];
return lines.join(LINE_END) + LINE_END;
};
const neighbors = function neighbors(pages: readonly WikiPage[], index: number): string {
const previous = pageLink(pages[index - 1]);
const next = pages[index + 1];
const links = next === undefined ? [previous] : [previous, pageLink(next)];
return links.join(ATTRIBUTION_SEPARATOR);
};
const homeOf = function homeOf(home: ChapterSource, pages: readonly WikiPage[]): Chapter {
const reading = [WIKI_READING_LEAD, "", readingList(pages)].join(LINE_END);
return {
body: [home.markdown.trimEnd(), reading].join(PARAGRAPH_BREAK) + LINE_END,
file: HOME_FILE,
label: HOME_LABEL,
};
};
export const renderWiki = function renderWiki(input: ShapeInput, attribution: Attribution): Chapter[] {
const pages = pagesOf(input.pages);
const front = input.pages.find((page) => page.page === METHODOLOGY_PAGE) ?? input.pages[0];
const home = front?.sources.find((source) => source.first);
const resolve = wikiResolver(pages, front?.page ?? null);
const hosted = pages.filter((page) => page.hosted);
const chapters: Chapter[] = hosted.map((page, index) => ({
body: [page.source.markdown.trimEnd(), neighbors(hosted, index)].join(CHAPTER_SEPARATOR) + LINE_END,
file: page.href + MARKDOWN_EXTENSION,
label: page.source.label,
}));
if (home !== undefined) {
chapters.unshift(homeOf(home, pages));
}
chapters.push(
{ body: sidebarOf(pages), file: SIDEBAR_FILE, label: SIDEBAR_FILE },
{ body: attributionLine(attribution) + LINE_END, file: FOOTER_FILE, label: FOOTER_FILE },
);
return chapters.map((chapter) => ({ ...chapter, body: relinked(chapter.body, resolve) }));
};