@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
514 lines (481 loc) • 20.3 kB
JavaScript
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);
});
}
}
}
}
}
});
};
};