@tanstack/markdown
Version:
A tiny, fast, deterministic Markdown renderer for blogs and documentation.
146 lines (145 loc) • 5.21 kB
JavaScript
import { blocksToText, slugify, splitByHeading } from './shared.js';
const bundlers = ['vite', 'rsbuild'];
export function transformTabsComponent(node) {
const variant = node.attributes.variant?.toLowerCase();
if (variant === 'files')
return transformFileTabs(node);
if (variant === 'package-manager' || variant === 'package-managers')
return transformPackageManagerTabs(node);
if (variant === 'bundler')
return transformBundlerTabs(node);
return transformHeadingTabs(node);
}
export function transformFileTabs(node) {
const files = node.children.filter((child) => child.type === 'code');
if (!files.length)
return node;
const tabs = files.map((file, index) => ({
slug: `file-${index}`,
name: file.title || file.file || 'Untitled',
}));
return {
...node,
properties: {
...(node.properties ?? {}),
'data-attributes': JSON.stringify({ tabs }),
'data-files-meta': JSON.stringify({
files: files.map(file => ({
title: file.title || file.file || 'Untitled',
code: file.value,
language: file.lang ?? 'plaintext',
})),
}),
},
children: files.map((file, index) => ({
type: 'component',
name: 'tab-panel',
tagName: 'md-tab-panel',
attributes: {},
properties: {
'data-tab-slug': `file-${index}`,
'data-tab-index': String(index),
},
children: [file],
})),
};
}
export function transformPackageManagerTabs(node) {
const packagesByFramework = {};
for (const line of blocksToText(node.children).split('\n')) {
const trimmed = line.trim();
if (!trimmed)
continue;
const colon = trimmed.indexOf(':');
if (colon === -1)
continue;
const framework = trimmed.slice(0, colon).trim().toLowerCase();
const packages = trimmed.slice(colon + 1).trim().split(/\s+/).filter(Boolean);
if (!framework || packages.length === 0)
continue;
packagesByFramework[framework] ??= [];
packagesByFramework[framework].push(packages);
}
if (!Object.keys(packagesByFramework).length)
return node;
return {
...node,
properties: {
...(node.properties ?? {}),
'data-package-manager-meta': JSON.stringify({
packagesByFramework,
mode: resolveInstallMode(node.attributes.mode),
}),
},
children: [],
};
}
export function transformBundlerTabs(node) {
const sections = splitByHeading(node.children);
const selected = sections.filter(section => isBundler(section.name.toLowerCase()));
if (!selected.length)
return node;
const tabs = bundlers
.map(bundler => selected.find(section => section.name.toLowerCase() === bundler))
.filter((section) => Boolean(section))
.map(section => ({ slug: section.name.toLowerCase(), name: section.name.toLowerCase() }));
return {
...node,
properties: {
...(node.properties ?? {}),
'data-attributes': JSON.stringify({ tabs }),
'data-bundler-meta': JSON.stringify({ bundlers: tabs.map(tab => tab.slug) }),
},
children: tabs.map((tab, index) => {
const section = selected.find(section => section.name.toLowerCase() === tab.slug);
return {
type: 'component',
name: 'tab-panel',
tagName: 'md-tab-panel',
attributes: {},
properties: {
'data-tab-slug': tab.slug,
'data-tab-index': String(index),
'data-content': section.children.length === 1 && section.children[0]?.type === 'code' ? 'code-only' : 'mixed',
},
children: section.children,
};
}),
};
}
export function transformHeadingTabs(node) {
const sections = splitByHeading(node.children);
if (!sections.length)
return node;
const tabs = sections.map((section, index) => ({
slug: section.id || slugify(section.name, `tab-${index + 1}`),
name: section.name,
}));
return {
...node,
properties: {
...(node.properties ?? {}),
'data-attributes': JSON.stringify({ tabs }),
},
children: sections.map((section, index) => ({
type: 'component',
name: 'tab-panel',
tagName: 'md-tab-panel',
attributes: {},
properties: {
'data-tab-slug': tabs[index]?.slug ?? `tab-${index + 1}`,
'data-tab-index': String(index),
},
children: section.children,
})),
};
}
function resolveInstallMode(value) {
const mode = value?.toLowerCase();
if (mode === 'dev-install' || mode === 'local-install')
return mode;
return 'install';
}
function isBundler(value) {
return bundlers.includes(value);
}