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 neighbours = function neighbours(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(), neighbours(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) })); };