# core/renderers/wiki.renderer.ts

> 171 lines of code and 31 definitions.

Tree: Build tree
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/build#file-build-core-renderers-wiki-renderer-ts
Source text: https://banes-lab.com/assets/sources/source.df9e69c04ec70bcbfb5c0bbbb6f26c0af641a18b065a9f0dcc00e2b5a9802c73.generated.txt

## Definitions

- `pageLink` (lexical_declaration, line 112)
- `pageStemOf` (lexical_declaration, line 54, exported)
- `isSiteOnly` (lexical_declaration, line 67, exported)
- `sidebarOf` (lexical_declaration, line 131)
- `homeOf` (lexical_declaration, line 155)
- `renderWiki` (lexical_declaration, line 164, exported)
- `pagesOf` (lexical_declaration, line 71)
- `readingList` (lexical_declaration, line 116)
- `HOME_STEM` (lexical_declaration, line 43)
- `RULE` (lexical_declaration, line 44)
- `GROUP_MARK` (lexical_declaration, line 45)
- `wordsOf` (lexical_declaration, line 47)
- `number` (lexical_declaration, line 59, exported)
- `words` (lexical_declaration, line 60, exported)
- `grouped` (lexical_declaration, line 72)
- `index` (lexical_declaration, line 73)
- `wikiResolver` (lexical_declaration, line 99, exported)
- `found` (lexical_declaration, line 101, exported)
- `groups` (lexical_declaration, line 117)
- `lines` (lexical_declaration, line 132)
- `neighbours` (lexical_declaration, line 148)
- `previous` (lexical_declaration, line 149)
- `next` (lexical_declaration, line 150)
- `links` (lexical_declaration, line 151)
- `reading` (lexical_declaration, line 156)
- `pages` (lexical_declaration, line 165, exported)
- `front` (lexical_declaration, line 166, exported)
- `home` (lexical_declaration, line 167, exported)
- `resolve` (lexical_declaration, line 168, exported)
- `hosted` (lexical_declaration, line 169, exported)
- `chapters` (lexical_declaration, line 170, exported)

## Uses

- [core/normalizers/link.normalizer.ts](https://banes-lab.com/source/build/core/normalizers/link.normalizer.ts.md)
- [core/renderers/markdown.renderer.ts](https://banes-lab.com/source/build/core/renderers/markdown.renderer.ts.md)

## Source

```typescript
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) }));
};
```
