@mintlify/common
Version:
Commonly shared code within Mintlify
154 lines (153 loc) • 5.7 kB
JavaScript
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);
}
});
};
};