UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

514 lines (481 loc) 20.3 kB
import _createForOfIteratorHelper from "@babel/runtime/helpers/esm/createForOfIteratorHelper"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import { visit } from 'unist-util-visit'; /** * Remark plugin that transforms code blocks with variants into HTML pre/code structures. * * Transforms consecutive code blocks with variant metadata like: * * ```javascript variant=npm * npm install @mui/internal-docs-infra * ``` * ```javascript variant=pnpm * pnpm install @mui/internal-docs-infra * ``` * ```javascript variant=yarn * yarn add @mui/internal-docs-infra * ``` * * OR with variant-type (with labels between): * * npm * ```bash variant-type=install * npm install @mui/internal-docs-infra * ``` * pnpm * ```bash variant-type=install * pnpm install @mui/internal-docs-infra * ``` * yarn * ```bash variant-type=install * yarn add @mui/internal-docs-infra * ``` * * OR individual code blocks with options: * * ```ts transform * console.log('test' as const) * ``` * * Into HTML that the existing rehype plugin can process: * <pre> * <code data-variant="npm">npm install @mui/internal-docs-infra</code> * <code data-variant="pnpm">pnpm install @mui/internal-docs-infra</code> * <code data-variant="yarn">yarn add @mui/internal-docs-infra</code> * </pre> * * Or for individual blocks: * <pre> * <code data-transform="true">console.log('test' as const)</code> * </pre> */ /** * Parse meta string to extract variant and other properties */ function parseMeta(meta) { var result = { props: {} }; // Parse key=value pairs first, handling quoted values var keyValueRegex = /([\w-]+)=("(?:[^"\\]|\\.)*"|[^\s]+)/g; var match = keyValueRegex.exec(meta); var processedPositions = []; while (match !== null) { var _match = match, _match2 = _slicedToArray(_match, 3), fullMatch = _match2[0], key = _match2[1], rawValue = _match2[2]; var startPos = match.index; var endPos = match.index + fullMatch.length; processedPositions.push([startPos, endPos]); // Remove quotes if present var value = rawValue.startsWith('"') && rawValue.endsWith('"') ? rawValue.slice(1, -1) : rawValue; if (key === 'variant') { result.variant = value; } else if (key === 'variant-type') { result.variantType = value; } else { result.props[key] = value; } match = keyValueRegex.exec(meta); } // Extract remaining parts as standalone flags var remainingMeta = meta; // Remove processed key=value pairs from the string (in reverse order to maintain positions) processedPositions.sort(function (a, b) { return b[0] - a[0]; }).forEach(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), start = _ref2[0], end = _ref2[1]; remainingMeta = remainingMeta.slice(0, start) + remainingMeta.slice(end); }); // Process remaining standalone flags var remainingParts = remainingMeta.trim().split(/\s+/).filter(Boolean); var _iterator = _createForOfIteratorHelper(remainingParts), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var part = _step.value; if (part === 'variant') { // This shouldn't happen, but just in case result.variant = 'true'; } else if (part === 'variant-type') { // This shouldn't happen, but just in case result.variantType = 'true'; } else { // Handle standalone flags (e.g., "transform" becomes "transform": "true") result.props[part] = 'true'; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return result; } export var transformMarkdownCode = function transformMarkdownCode() { return function (tree) { var processedIndices = new Set(); visit(tree, function (node, index, parent) { if (!parent || !Array.isArray(parent.children) || typeof index !== 'number') { return; } // Skip if already processed if (processedIndices.has(index)) { return; } var parentNode = parent; // Look for code blocks with variant metadata or options if (node.type === 'code') { var codeNode = node; // Check if variant metadata is in meta field or lang field (when no language is specified) var metaString = codeNode.meta; var langFromMeta = codeNode.lang || null; // If meta is empty but lang contains '=', it means variant info is in lang if (!metaString && codeNode.lang && codeNode.lang.includes('=')) { metaString = codeNode.lang; langFromMeta = null; } // Check if we have variants/variant-types or individual options var metaData = { props: {} }; if (metaString) { metaData = parseMeta(metaString); } // Use props from meta as the options for individual blocks var allProps = metaData.props; // Handle individual code blocks with options (but no variants) if (!metaData.variant && !metaData.variantType && Object.keys(allProps).length > 0) { // Create a simple pre/code element for individual blocks with options var hProperties = {}; // Add language class if available if (langFromMeta) { hProperties.className = "language-".concat(langFromMeta); } // Add all props as data attributes (in camelCase) Object.entries(allProps).forEach(function (_ref3) { var _ref4 = _slicedToArray(_ref3, 2), key = _ref4[0], value = _ref4[1]; // Convert kebab-case to camelCase for data attributes var camelKey = key.includes('-') ? "data".concat(key.split('-').map(function (part) { return part.charAt(0).toUpperCase() + part.slice(1); }).join('')) : "data".concat(key.charAt(0).toUpperCase() + key.slice(1)); hProperties[camelKey] = value; }); var preElement = { type: 'element', tagName: 'pre', data: { hName: 'pre', hProperties: {} }, children: [{ type: 'element', tagName: 'code', data: { hName: 'code', hProperties: hProperties }, children: [{ type: 'text', value: codeNode.value }] }] }; // Replace this individual code block immediately parentNode.children[index] = preElement; processedIndices.add(index); return; } // Handle variant/variant-type logic (existing code) if (!metaString) { return; } if (metaData.variant || metaData.variantType) { // Collect consecutive code blocks that belong together var codeBlocks = []; var currentIndex = index; // For variant-type, look for pattern: [label] -> code block // For variant, look for adjacent code blocks only if (metaData.variantType) { // Add the current code block as the first one for variant-type var currentLabelFromPrevious; if (index > 0) { var prevNode = parentNode.children[index - 1]; if (prevNode.type === 'paragraph' && prevNode.children.length === 1 && prevNode.children[0].type === 'text') { currentLabelFromPrevious = prevNode.children[0].value.trim(); } } codeBlocks.push({ node: codeNode, index: index, variant: currentLabelFromPrevious || metaData.variantType || 'default', props: allProps, actualLang: langFromMeta, labelFromPrevious: currentLabelFromPrevious }); processedIndices.add(index); // Start looking from the next element currentIndex = index + 1; // Collect all blocks with the same variant-type while (currentIndex < parentNode.children.length) { var currentNode = parentNode.children[currentIndex]; // Check if this is a potential label paragraph if (currentNode.type === 'paragraph' && currentNode.children.length === 1 && currentNode.children[0].type === 'text') { // Look for a code block after this paragraph if (currentIndex + 1 < parentNode.children.length) { var nextNode = parentNode.children[currentIndex + 1]; if (nextNode.type === 'code') { // Check if this code block has the same variant-type var nextMetaString = nextNode.meta; var nextActualLang = nextNode.lang; if (!nextMetaString && nextActualLang && nextActualLang.includes('=')) { nextMetaString = nextActualLang; nextActualLang = null; } if (nextMetaString) { var nextMetaData = parseMeta(nextMetaString); if (nextMetaData.variantType === metaData.variantType) { var labelFromPrevious = currentNode.children[0].value.trim(); codeBlocks.push({ node: nextNode, index: currentIndex + 1, variant: labelFromPrevious || nextMetaData.variantType || 'default', props: nextMetaData.props, actualLang: nextActualLang, labelFromPrevious: labelFromPrevious }); processedIndices.add(currentIndex + 1); // Skip the code block and move to next potential label currentIndex += 2; continue; } } } } // If we didn't find a matching code block, break break; } if (currentNode.type === 'code') { // Parse language and meta for current node var currentMetaString = currentNode.meta; var currentActualLang = currentNode.lang; if (!currentMetaString && currentActualLang && currentActualLang.includes('=')) { currentMetaString = currentActualLang; currentActualLang = null; } if (currentMetaString) { var currentMetaData = parseMeta(currentMetaString); if (currentMetaData.variantType === metaData.variantType) { // Look for label before this code block var _labelFromPrevious = void 0; if (currentIndex > 0) { var _prevNode = parentNode.children[currentIndex - 1]; if (_prevNode.type === 'paragraph' && _prevNode.children.length === 1 && _prevNode.children[0].type === 'text') { _labelFromPrevious = _prevNode.children[0].value.trim(); } } codeBlocks.push({ node: currentNode, index: currentIndex, variant: _labelFromPrevious || currentMetaData.variantType || 'default', props: currentMetaData.props, actualLang: currentActualLang, labelFromPrevious: _labelFromPrevious }); processedIndices.add(currentIndex); // Skip the label and move to next potential code block currentIndex += 1; // Skip ahead past any paragraph that could be a label if (currentIndex < parentNode.children.length) { var _nextNode = parentNode.children[currentIndex]; if (_nextNode.type === 'paragraph' && _nextNode.children.length === 1 && _nextNode.children[0].type === 'text') { currentIndex += 1; // Skip the potential label } } } else { break; // Different group, stop collecting } } else { break; // No meta, stop collecting } } else { break; // Not a code block, stop collecting } } } else if (metaData.variant) { // Add the current code block as the first one for variant codeBlocks.push({ node: codeNode, index: index, variant: metaData.variant, props: allProps, actualLang: langFromMeta }); processedIndices.add(index); // Start looking from the next element currentIndex = index + 1; // Collect adjacent code blocks with variants while (currentIndex < parentNode.children.length) { var _currentNode = parentNode.children[currentIndex]; if (_currentNode.type === 'code') { // Parse language and meta for current node var _currentMetaString = _currentNode.meta; var _currentActualLang = _currentNode.lang; if (!_currentMetaString && _currentActualLang && _currentActualLang.includes('=')) { _currentMetaString = _currentActualLang; _currentActualLang = null; } if (_currentMetaString) { var _currentMetaData = parseMeta(_currentMetaString); if (_currentMetaData.variant) { codeBlocks.push({ node: _currentNode, index: currentIndex, variant: _currentMetaData.variant, props: _currentMetaData.props, actualLang: _currentActualLang }); processedIndices.add(currentIndex); currentIndex += 1; } else { break; // No variant, stop collecting } } else { break; // No meta, stop collecting } } else { break; // Not a code block, stop collecting } } } // Only process if we have multiple blocks if (codeBlocks.length > 1) { // Create proper HTML elements with hProperties for remark-rehype compatibility var _preElement = { type: 'element', tagName: 'pre', data: { hName: 'pre', hProperties: {} }, children: codeBlocks.map(function (block) { // Build hProperties for HTML attributes var hProperties = { dataVariant: block.variant }; // Add language class if available if (block.actualLang) { hProperties.className = "language-".concat(block.actualLang); } // Add additional props as data attributes (in camelCase) Object.entries(block.props).forEach(function (_ref5) { var _ref6 = _slicedToArray(_ref5, 2), key = _ref6[0], value = _ref6[1]; // Convert kebab-case to camelCase for data attributes var camelKey = key.includes('-') ? "data".concat(key.split('-').map(function (part) { return part.charAt(0).toUpperCase() + part.slice(1); }).join('')) : "data".concat(key.charAt(0).toUpperCase() + key.slice(1)); hProperties[camelKey] = value; }); return { type: 'element', tagName: 'code', data: { hName: 'code', hProperties: hProperties, meta: "variant=".concat(block.variant).concat(Object.entries(block.props).map(function (_ref7) { var _ref8 = _slicedToArray(_ref7, 2), key = _ref8[0], value = _ref8[1]; return " ".concat(key, "=").concat(value); }).join('')) }, children: [{ type: 'text', value: block.node.value }] }; }) }; // Replace the first block with the group and mark others for removal parentNode.children[codeBlocks[0].index] = _preElement; // Remove all other code blocks and their labels (in reverse order to maintain indices) var indicesToRemove = codeBlocks.slice(1).map(function (block) { var indices = [block.index]; // Also include label paragraph if it exists if (block.labelFromPrevious && block.index > 0) { var _prevNode2 = parentNode.children[block.index - 1]; if (_prevNode2.type === 'paragraph') { indices.push(block.index - 1); } } return indices; }).flat().sort(function (a, b) { return b - a; }); // Sort in descending order // Remove the marked indices var _iterator2 = _createForOfIteratorHelper(indicesToRemove), _step2; try { var _loop = function _loop() { var removeIdx = _step2.value; if (removeIdx < parentNode.children.length) { parentNode.children.splice(removeIdx, 1); // Update processed indices to account for removed elements var _updatedProcessedIndices = new Set(); processedIndices.forEach(function (processedIdx) { if (processedIdx > removeIdx) { _updatedProcessedIndices.add(processedIdx - 1); } else if (processedIdx < removeIdx) { _updatedProcessedIndices.add(processedIdx); } // Don't add the removed index }); processedIndices.clear(); _updatedProcessedIndices.forEach(function (processedIdx) { processedIndices.add(processedIdx); }); } }; for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { _loop(); } // Also remove the label of the first block if it exists } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } if (codeBlocks[0].labelFromPrevious && codeBlocks[0].index > 0) { var labelIndex = codeBlocks[0].index - 1; var _prevNode3 = parentNode.children[labelIndex]; if (_prevNode3 && _prevNode3.type === 'paragraph') { parentNode.children.splice(labelIndex, 1); // Update processed indices var updatedProcessedIndices = new Set(); processedIndices.forEach(function (processedIdx) { if (processedIdx > labelIndex) { updatedProcessedIndices.add(processedIdx - 1); } else if (processedIdx < labelIndex) { updatedProcessedIndices.add(processedIdx); } }); processedIndices.clear(); updatedProcessedIndices.forEach(function (processedIdx) { processedIndices.add(processedIdx); }); } } } } } }); }; };