UNPKG

@mintlify/common

Version:

Commonly shared code within Mintlify

154 lines (153 loc) 5.7 kB
import { u } from 'unist-builder'; import { visit } from 'unist-util-visit'; import { createMdxJsxAttribute } from '../../lib/remark-utils.js'; const extractTextContent = (node) => { let text = ''; for (const child of node.children) { if (child.type === 'text') { text += child.value; } else if (child.type === 'element') { text += extractTextContent(child); } } return text; }; const PROCESS_NUMERIC_CELLS_THRESHOLD = 0.8; const UNITS = '(GiB|MiB|KiB|TiB|PiB|EiB|ZiB|' + // Storage (binary prefixes - IEC) 'GB|MB|KB|TB|PB|EB|ZB|' + // Storage (decimal prefixes) 'ms|μs|µs|ns|s|m|h|min|' + // Time 'km|cm|mm|' + // Distance 'kg|g|mg|t|' + // Weight 'Hz|kHz|MHz|GHz|THz|' + // Frequency '%|' + // Percent 'px|em|rem|pt|' + // CSS '°C|°F|°K|K|' + // Temperature 'Mbps|Gbps|kbps|' + // Network speed 'W|kW|mW|MW|GW|' + // Power (Watts) 'V|kV|mV|' + // Voltage 'A|mA|kA|' + // Current (Ampere) 'Wh|kWh|mWh|' + // Energy 'Pa|kPa|MPa|bar|psi|atm|' + // Pressure 'L|ml|m³|gal|' + // Volume 'lm|cd|lx|nits|' + // Luminosity 'dB|dBm|' + // Sound 'rpm|' + // Rotation 'bpm|' + // Heart rate 'fps|' + // Frame rate 'Bq|Gy|Sv|rad|' + // Radiation 'mph|km\\/h|m\\/s|kt|' + // Speed 'ft²|m²|ha|ac|' + // Area 'mol\\/L|M|ppm|kg\\/m³|g\\/L|' + // Concentration and density 'bit|byte|ch|MP|dpi|ppi|' + // Digital 'oz|cup|cups|tbsp|tsp|' + // Cooking (includes plural) 'AU|ly|pc|' + // Astronomical 'mmHg|' + // Medical 'mAh|' + // Battery 'in|ft|yd' + // Imperial ')'; const isNumberWithUnit = (text) => { // support fractions (1/2), decimals (1.5), and ranges (5-10) const numberWithUnit = `[+-]?\\d+[\\d\\s.,/]*\\s*${UNITS}`; const pattern = new RegExp(`^${numberWithUnit}(-${numberWithUnit})?$`, 'i'); return pattern.test(text); }; // "0xFF", "0x100", "0b1111", "0o777" const isProgrammerNotation = (text) => { // hexadecimal: 0x or 0X followed by hex digits if (/^0x[0-9a-f]+$/i.test(text)) { return true; } // binary: 0b or 0B followed by binary digits if (/^0b[01]+$/i.test(text)) { return true; } // octal: 0o or 0O followed by octal digits if (/^0o[0-7]+$/i.test(text)) { return true; } return false; }; // "1.0.0", "v2.5.1", "v1", "2.0.0-beta", "3.0.0-rc1", "1.2.3-dev", "1.0.0-alpha" const isVersionNumber = (text) => { // starts with 'v' followed by numbers and dots: v1, v2.5, v10.5.2 if (/^v\d+(\.\d+)*(-[\w.]+)?$/i.test(text)) { return true; } // version with dots and optional suffix: 1.0.0, 2.0.0-beta, 1.2.3-rc1 if (/^\d+\.\d+(\.\d+)?(-[\w.]+)?$/.test(text)) { return true; } return false; }; // "#FFFFFF", "#000", "rgb(255,0,0)", "hsl(0,100%,50%)", "cmyk(0,100,100,0)" const isColorValue = (text) => { // hex colors: #FFF, #FFFFFF, #FF0000 if (/^#[0-9a-f]{3,8}$/i.test(text)) { return true; } // functional notation: rgb(), rgba(), hsl(), hsla(), cmyk() if (/^(rgb|rgba|hsl|hsla|cmyk)\s*\([\d\s,.%]+\)$/i.test(text)) { return true; } return false; }; // "1st", "2nd", "3rd", "95th", "21st" const isOrdinalNumber = (text) => { return /^\d+(st|nd|rd|th)$/i.test(text); }; const hasHighNumericRatio = (text) => { var _a; // only include: digits, spaces, basic punctuation, currency // explicitly exclude: math symbols, Greek letters, super/subscripts (to avoid matching formulas) const numericChars = ((_a = text.match(/[\d\s.,+\-/%$€£¥°:()]/gi)) === null || _a === void 0 ? void 0 : _a.length) || 0; const ratio = numericChars / text.length; return ratio >= PROCESS_NUMERIC_CELLS_THRESHOLD; }; const isPrimarilyNumeric = (text) => { const trimmed = text.trim(); if (trimmed.length === 0) return false; return (isNumberWithUnit(trimmed) || isProgrammerNotation(trimmed) || isOrdinalNumber(trimmed) || isVersionNumber(trimmed) || isColorValue(trimmed) || hasHighNumericRatio(trimmed)); }; const processTableCells = (node) => { visit(node, 'element', (cellNode) => { if (cellNode.tagName === 'td') { const textContent = extractTextContent(cellNode); if (isPrimarilyNumeric(textContent)) { cellNode.properties['data-numeric'] = 'true'; } } }); }; export const rehypeTable = () => { return (tree) => { visit(tree, 'element', (node, index, parent) => { if (node.tagName === 'table' && parent && index !== undefined) { const attributes = Object.entries(node.properties).map(([key, value]) => { const attributeValue = Array.isArray(value) ? value.join(' ') : value; const attributeName = key === 'class' ? 'className' : key; return createMdxJsxAttribute(attributeName, attributeValue); }); // filter out whitespace-only text nodes to prevent hydration errors const filteredChildren = node.children.filter((child) => { if (child.type === 'text' && /^\s*$/.test(child.value)) { return false; } return true; }); processTableCells(node); const tableComponent = u('mdxJsxFlowElement', { name: 'Table', attributes, }, filteredChildren); parent.children.splice(index, 1, tableComponent); } }); }; };