@f-fjs/tidy-markdown
Version:
Fix ugly markdown.
158 lines • 6.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const wcwidth_1 = __importDefault(require("wcwidth"));
const node_1 = require("./node");
const tree_adapter_1 = __importDefault(require("./tree-adapter"));
const utils_1 = require("./utils");
function pad(text, length, char = ' ') {
if (char == null) {
char = ' ';
}
const invert = typeof text === 'number';
if (invert) {
[length, text] = Array.from([text, length]);
}
text = text.toString();
let res = '';
const padlength = length - wcwidth_1.default(text);
res += char.repeat(padlength);
if (invert) {
return res + text;
}
else {
return text + res;
}
}
/**
* Determines the alignment for a table cell by reading the style attribute
* @return {String|null} One of 'right', 'left', 'center', or null
*/
function getCellAlignment(node) {
var _a, _b;
return ((_b = (_a = utils_1.getAttribute(node, 'style')) === null || _a === void 0 ? void 0 : _a.match(/text-align:\s*(right|left|center)/)) === null || _b === void 0 ? void 0 : _b[1]) || null;
}
/**
* Join an array of cells (columns) from a single row.
* @param {String[]} columns
* @return {String} The joined row.
*/
function joinColumns(columns) {
if (columns.length > 1) {
return columns.join(' | ');
}
else {
// use a leading pipe for single column tables, otherwise the output won't
// render as a table
return `| ${columns[0]}`;
}
}
function extractColumns(row) {
const columns = new Array();
const alignments = new Array();
if (utils_1.isParentNode(row))
row.childNodes
.forEach(column => {
// we don't care if it's a `th` or `td` because we cannot represent the
// difference in markdown anyway - the first row always represents the
// headers
if (utils_1.isElement(column) && ['th', 'td'].includes(column.tagName) && node_1.isConverterNode(column)) {
columns.push(column._replacement);
alignments.push(getCellAlignment(column));
}
else if (tree_adapter_1.default.isTextNode(column)) {
throw new Error(`Cannot handle ${utils_1.isElement(column) && column.tagName} in table row`);
}
});
return { columns, alignments };
}
function extractRows(node) {
const alignments = new Array();
const rows = new Array();
const inqueue = [node];
while (inqueue.length > 0) {
const elem = inqueue.shift();
if (utils_1.isParentNode(elem))
elem.childNodes.forEach(child => {
if (utils_1.isElement(child) && child.tagName === 'tr') {
const row = extractColumns(child);
rows.push(row.columns);
// alignments in markdown are column-wide, so after the first row we just
// want to make sure there aren't any conflicting values within a single
// column
for (let i = 0; i < row.alignments.length; i++) {
const alignment = row.alignments[i];
if (i + 1 > alignments.length) {
// if all previous rows were shorter, or if we are at the beginning
// of the table, then we need to populate the alignments array
alignments.push(alignment);
}
if (alignment !== alignments[i]) {
throw new Error(`Alignment in a table column ${i} is not consistent`);
}
}
}
else if (tree_adapter_1.default.isElementNode(child)) {
inqueue.push(child);
}
});
}
// when there are more alignments than headers (from columns that extend beyond
// the headers), and those alignments aren't doing anything, it looks better to
// remove them
while (alignments.length > rows[0].length && alignments.slice(-1)[0] === null) {
alignments.pop();
}
return { alignments, rows };
}
exports.extractRows = extractRows;
function formatRow(row, alignments, columnWidths) {
// apply padding around each cell for alignment and column width
row = row.map((column, i) => {
switch (alignments[i]) {
case 'right':
return pad(columnWidths[i], column);
case 'center':
// rounding causes a bias to the left because we can't have half a char
const whitespace = columnWidths[i] - column.length;
const leftPadded = pad(Math.floor(whitespace / 2) + column.length, column);
return pad(leftPadded, Math.ceil(whitespace / 2) + leftPadded.length);
default:
// left is the default alignment when formatting
return pad(column, columnWidths[i]);
}
});
// trimRight is to remove any trailing whitespace added by the padding
return joinColumns(row).trimRight();
}
exports.formatRow = formatRow;
function formatHeaderSeparator(alignments, columnWidths) {
const columns = alignments.map((alignment, i) => {
switch (alignment) {
case 'center':
return `:${pad('', columnWidths[i] - 2, '-')}:`;
case 'left':
return `:${pad('', columnWidths[i] - 1, '-')}`;
case 'right':
return pad('', columnWidths[i] - 1, '-') + ':';
default:
return pad('', columnWidths[i], '-');
}
});
return joinColumns(columns);
}
exports.formatHeaderSeparator = formatHeaderSeparator;
function getColumnWidths(rows) {
const columnWidths = rows[0].map((row) => (row || '').length);
rows.forEach((row) => {
row.forEach((column, i) => {
if (i < columnWidths.length)
columnWidths[i] = Math.max(wcwidth_1.default(column), columnWidths[i]);
});
});
return columnWidths;
}
exports.getColumnWidths = getColumnWidths;
//# sourceMappingURL=tables.js.map