import { ANATOMY_FACE, CHAPTER_FACE } from "#configuration/constants/vocabulary.constants"; import { ANATOMY_PAGE, ONTOLOGY_PAGE } from "#core/ids/page.ids"; import type { ChapterReference, ReferenceTarget } from "#types/reference.types"; import { DEFINITION_ANCHOR, FILE_ANCHOR, FOLDER_ANCHOR } from "#core/ids/anatomy.ids"; import { HOME_PATH, pageFromPath, payloadPath, tabFromPath } from "#assets/link.assets"; import { ANCHOR_PREFIX } from "#configuration/constants/document.constants"; import { FACE_SEPARATOR } from "@govlab/constants"; import { refOfAnchor } from "#domain/converters/ontology.converter"; const LINK_OPEN = "["; const LINK_MIDDLE = "]("; const LINK_CLOSE = ")"; const MARKS: ReadonlySet = new Set(["*", "`"]); const LESSON_KIND = "lesson"; const fragmentOf = function fragmentOf(href: string): string | null { const cut = href.indexOf(ANCHOR_PREFIX); return cut === -1 ? null : href.slice(cut + ANCHOR_PREFIX.length); }; const isNodeFragment = function isNodeFragment(fragment: string): boolean { return ( fragment.startsWith(FILE_ANCHOR) || fragment.startsWith(FOLDER_ANCHOR) || fragment.startsWith(DEFINITION_ANCHOR) ); }; const ontologyTarget = function ontologyTarget(fragment: string): ReferenceTarget | null { const ref = refOfAnchor(fragment); return ref === null ? null : { face: ref.slice(0, ref.indexOf(FACE_SEPARATOR)), ref }; }; export const targetOfHref = function targetOfHref(href: string): ReferenceTarget | null { if (!href.startsWith(HOME_PATH)) { return null; } const fragment = fragmentOf(href); if (fragment === null || fragment.length === 0) { return null; } if (pageFromPath(href, "") === ONTOLOGY_PAGE) { return ontologyTarget(fragment); } if (pageFromPath(href, "") === ANATOMY_PAGE && isNodeFragment(fragment)) { return { face: ANATOMY_FACE, ref: ANATOMY_FACE + FACE_SEPARATOR + fragment }; } return { face: CHAPTER_FACE, ref: CHAPTER_FACE + FACE_SEPARATOR + href }; }; export const payloadOfHref = function payloadOfHref(href: string): string { return payloadPath(pageFromPath(href, ""), tabFromPath(href)); }; const linkEnd = function linkEnd(text: string, at: number): number { const middle = text.indexOf(LINK_MIDDLE, at); return middle === -1 ? -1 : text.indexOf(LINK_CLOSE, middle); }; export const plainMarkdown = function plainMarkdown(text: string): string { let out = ""; let at = 0; while (at < text.length) { const char = text.charAt(at); const close = char === LINK_OPEN ? linkEnd(text, at) : -1; if (close !== -1) { out += text.slice(at + 1, text.indexOf(LINK_MIDDLE, at)); at = close + 1; continue; } if (!MARKS.has(char)) { out += char; } at += 1; } return out; }; interface SectionLike { readonly id: string; readonly intro?: string; readonly subsections?: readonly { readonly blocks?: readonly { readonly kind: string; readonly principle?: string }[]; }[]; readonly title: string; } const isRecord = function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; }; const isSection = function isSection(value: unknown): value is SectionLike { return isRecord(value) && typeof value["id"] === "string" && typeof value["title"] === "string"; }; const sectionsOf = function sectionsOf(node: unknown): readonly SectionLike[] { if (Array.isArray(node)) { return node.flatMap(sectionsOf); } if (typeof node !== "object" || node === null) { return []; } if ("sections" in node && Array.isArray(node.sections)) { return node.sections.filter(isSection); } if ("tabs" in node) { return sectionsOf(node.tabs); } if ("content" in node) { return sectionsOf(node.content); } return []; }; const principleOf = function principleOf(section: SectionLike): string | null { for (const subsection of section.subsections ?? []) { for (const block of subsection.blocks ?? []) { if (block.kind === LESSON_KIND && typeof block.principle === "string") { return plainMarkdown(block.principle); } } } return null; }; export const chapterOf = function chapterOf(payload: unknown, sectionId: string): ChapterReference | null { const section = sectionsOf(payload).find((candidate) => candidate.id === sectionId); if (section === undefined) { return null; } return { intro: section.intro === undefined ? null : plainMarkdown(section.intro), principle: principleOf(section), relations: [], title: section.title, }; };