@curvenote/cli
Version:
CLI Client library for Curvenote
134 lines (133 loc) • 4.97 kB
JavaScript
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);
}