UNPKG

@curvenote/cli

Version:
134 lines (133 loc) 4.97 kB
import { join } from 'node:path'; import YAML from 'js-yaml'; import { TOC_FORMAT } from 'myst-cli'; import { writeFileToFolder } from 'myst-cli-utils'; import { NavListItemKindEnum } from '@curvenote/blocks'; import { Block } from '../../../models.js'; function getName(block) { return block.data.name || block.id.block; } function recurseTOCChapters(item) { if (!item.block) return null; const chapter = { file: getName(item.block) }; if (item.children && item.children.length > 0) { chapter.sections = item.children .map(recurseTOCChapters) .filter((c) => c); } return chapter; } function itemsToChapters(items) { const chapters = items.map(recurseTOCChapters).filter((c) => c); return chapters; } /** * This brings the first `item` in the tree to be used as the root. */ function spliceRootFromNav(items) { const next = [...items]; for (let i = 0; i < next.length; i += 1) { const item = next[i]; if (item.kind === NavListItemKindEnum.Item) { next.splice(i, 1); return [item, next]; } const recurse = spliceRootFromNav(item.children); if (recurse) return recurse; } return null; } function handleErrorMessage(session, shouldThrow, msg) { if (shouldThrow) { throw new Error(msg); } session.log.error(msg); } export function unflattenNavBlocks(loadedBlocks) { const nest = {}; const items = []; const groupNavItems = loadedBlocks.filter(({ kind }) => kind === NavListItemKindEnum.Group); const hasParts = groupNavItems.length > 0; const totalDocuments = loadedBlocks.length - groupNavItems.length; let skipCounter = 0; loadedBlocks.forEach((data) => { const children = []; const { id, kind } = data; nest[id] = children; if (kind === NavListItemKindEnum.Group) { const { title } = data; items.push({ id, kind, title, children }); return; } const { parentId, block } = data; if (!block || block.data.hidden) { skipCounter++; return; } if (parentId && nest[parentId]) { const folder = nest[parentId]; folder.push({ id, kind, block, children }); return; } items.push({ id, kind, block, children }); }); return { items, hasParts, skipCounter, totalDocuments }; } export async function writeTOC(session, nav, opts) { var _a, _b; const filename = join((opts === null || opts === void 0 ? void 0 : opts.path) || '.', (opts === null || opts === void 0 ? void 0 : opts.filename) || '_toc.yml'); const loadedBlocks = await Promise.all(nav.data.items.map(async (item) => { const { id, kind } = item; if (kind === NavListItemKindEnum.Group) { const { title } = item; return { id, kind, title }; } const { parentId } = item; const block = await new Block(session, item.blockId).get().catch(() => null); return { id, kind, parentId, block }; })); const { items, hasParts, skipCounter, totalDocuments } = unflattenNavBlocks(loadedBlocks); if (totalDocuments === 0) { handleErrorMessage(session, (_a = opts === null || opts === void 0 ? void 0 : opts.ci) !== null && _a !== void 0 ? _a : false, 'The table of contents has no documents.'); return; } const header = '# Table of contents\n# Learn more at https://jupyterbook.org/customize/toc.html'; if (skipCounter === totalDocuments) { handleErrorMessage(session, (_b = opts === null || opts === void 0 ? void 0 : opts.ci) !== null && _b !== void 0 ? _b : false, 'All documents in the table of contents are hidden.'); return; } if (!hasParts) { // There are no parts, just chapters const tocData = { format: TOC_FORMAT, root: getName(items[0].block), chapters: itemsToChapters([...items[0].children, ...items.slice(1)]), }; const toc = `${header}\n\n${YAML.dump(tocData)}\n`; writeFileToFolder(filename, toc); return; } // Deal with the parts const maybeSplit = spliceRootFromNav(items); if (!maybeSplit) throw new Error('Must have at least one content page.'); const [root, rest] = maybeSplit; let index = 0; const contents = [...root.children, ...rest]; const parts = contents.map((item) => { if (item.kind === NavListItemKindEnum.Group) { return { caption: item.title, chapters: itemsToChapters(item.children) }; } index += 1; return { caption: `Part ${index}`, chapters: itemsToChapters([item]) }; }); const tocData = { format: TOC_FORMAT, root: getName(root.block), parts, }; const toc = `${header}\n\n${YAML.dump(tocData)}\n`; writeFileToFolder(filename, toc); }