mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
Current file header is : /*! showdown v 2.0.0-alpha1 - 25-09-2018 */ Official 2.0.0-alpha doesn't match neither on content nor date :( Considering date we would be at 1.8.7, but it is also quite different (and has vulnerabilities) In consequence switching to 2.0.0.
303 lines
9.7 KiB
JavaScript
303 lines
9.7 KiB
JavaScript
'use strict';
|
|
|
|
const align = {
|
|
right: alignRight,
|
|
center: alignCenter
|
|
};
|
|
const top = 0;
|
|
const right = 1;
|
|
const bottom = 2;
|
|
const left = 3;
|
|
class UI {
|
|
constructor(opts) {
|
|
var _a;
|
|
this.width = opts.width;
|
|
this.wrap = (_a = opts.wrap) !== null && _a !== void 0 ? _a : true;
|
|
this.rows = [];
|
|
}
|
|
span(...args) {
|
|
const cols = this.div(...args);
|
|
cols.span = true;
|
|
}
|
|
resetOutput() {
|
|
this.rows = [];
|
|
}
|
|
div(...args) {
|
|
if (args.length === 0) {
|
|
this.div('');
|
|
}
|
|
if (this.wrap && this.shouldApplyLayoutDSL(...args) && typeof args[0] === 'string') {
|
|
return this.applyLayoutDSL(args[0]);
|
|
}
|
|
const cols = args.map(arg => {
|
|
if (typeof arg === 'string') {
|
|
return this.colFromString(arg);
|
|
}
|
|
return arg;
|
|
});
|
|
this.rows.push(cols);
|
|
return cols;
|
|
}
|
|
shouldApplyLayoutDSL(...args) {
|
|
return args.length === 1 && typeof args[0] === 'string' &&
|
|
/[\t\n]/.test(args[0]);
|
|
}
|
|
applyLayoutDSL(str) {
|
|
const rows = str.split('\n').map(row => row.split('\t'));
|
|
let leftColumnWidth = 0;
|
|
// simple heuristic for layout, make sure the
|
|
// second column lines up along the left-hand.
|
|
// don't allow the first column to take up more
|
|
// than 50% of the screen.
|
|
rows.forEach(columns => {
|
|
if (columns.length > 1 && mixin.stringWidth(columns[0]) > leftColumnWidth) {
|
|
leftColumnWidth = Math.min(Math.floor(this.width * 0.5), mixin.stringWidth(columns[0]));
|
|
}
|
|
});
|
|
// generate a table:
|
|
// replacing ' ' with padding calculations.
|
|
// using the algorithmically generated width.
|
|
rows.forEach(columns => {
|
|
this.div(...columns.map((r, i) => {
|
|
return {
|
|
text: r.trim(),
|
|
padding: this.measurePadding(r),
|
|
width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined
|
|
};
|
|
}));
|
|
});
|
|
return this.rows[this.rows.length - 1];
|
|
}
|
|
colFromString(text) {
|
|
return {
|
|
text,
|
|
padding: this.measurePadding(text)
|
|
};
|
|
}
|
|
measurePadding(str) {
|
|
// measure padding without ansi escape codes
|
|
const noAnsi = mixin.stripAnsi(str);
|
|
return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length];
|
|
}
|
|
toString() {
|
|
const lines = [];
|
|
this.rows.forEach(row => {
|
|
this.rowToString(row, lines);
|
|
});
|
|
// don't display any lines with the
|
|
// hidden flag set.
|
|
return lines
|
|
.filter(line => !line.hidden)
|
|
.map(line => line.text)
|
|
.join('\n');
|
|
}
|
|
rowToString(row, lines) {
|
|
this.rasterize(row).forEach((rrow, r) => {
|
|
let str = '';
|
|
rrow.forEach((col, c) => {
|
|
const { width } = row[c]; // the width with padding.
|
|
const wrapWidth = this.negatePadding(row[c]); // the width without padding.
|
|
let ts = col; // temporary string used during alignment/padding.
|
|
if (wrapWidth > mixin.stringWidth(col)) {
|
|
ts += ' '.repeat(wrapWidth - mixin.stringWidth(col));
|
|
}
|
|
// align the string within its column.
|
|
if (row[c].align && row[c].align !== 'left' && this.wrap) {
|
|
const fn = align[row[c].align];
|
|
ts = fn(ts, wrapWidth);
|
|
if (mixin.stringWidth(ts) < wrapWidth) {
|
|
ts += ' '.repeat((width || 0) - mixin.stringWidth(ts) - 1);
|
|
}
|
|
}
|
|
// apply border and padding to string.
|
|
const padding = row[c].padding || [0, 0, 0, 0];
|
|
if (padding[left]) {
|
|
str += ' '.repeat(padding[left]);
|
|
}
|
|
str += addBorder(row[c], ts, '| ');
|
|
str += ts;
|
|
str += addBorder(row[c], ts, ' |');
|
|
if (padding[right]) {
|
|
str += ' '.repeat(padding[right]);
|
|
}
|
|
// if prior row is span, try to render the
|
|
// current row on the prior line.
|
|
if (r === 0 && lines.length > 0) {
|
|
str = this.renderInline(str, lines[lines.length - 1]);
|
|
}
|
|
});
|
|
// remove trailing whitespace.
|
|
lines.push({
|
|
text: str.replace(/ +$/, ''),
|
|
span: row.span
|
|
});
|
|
});
|
|
return lines;
|
|
}
|
|
// if the full 'source' can render in
|
|
// the target line, do so.
|
|
renderInline(source, previousLine) {
|
|
const match = source.match(/^ */);
|
|
const leadingWhitespace = match ? match[0].length : 0;
|
|
const target = previousLine.text;
|
|
const targetTextWidth = mixin.stringWidth(target.trimRight());
|
|
if (!previousLine.span) {
|
|
return source;
|
|
}
|
|
// if we're not applying wrapping logic,
|
|
// just always append to the span.
|
|
if (!this.wrap) {
|
|
previousLine.hidden = true;
|
|
return target + source;
|
|
}
|
|
if (leadingWhitespace < targetTextWidth) {
|
|
return source;
|
|
}
|
|
previousLine.hidden = true;
|
|
return target.trimRight() + ' '.repeat(leadingWhitespace - targetTextWidth) + source.trimLeft();
|
|
}
|
|
rasterize(row) {
|
|
const rrows = [];
|
|
const widths = this.columnWidths(row);
|
|
let wrapped;
|
|
// word wrap all columns, and create
|
|
// a data-structure that is easy to rasterize.
|
|
row.forEach((col, c) => {
|
|
// leave room for left and right padding.
|
|
col.width = widths[c];
|
|
if (this.wrap) {
|
|
wrapped = mixin.wrap(col.text, this.negatePadding(col), { hard: true }).split('\n');
|
|
}
|
|
else {
|
|
wrapped = col.text.split('\n');
|
|
}
|
|
if (col.border) {
|
|
wrapped.unshift('.' + '-'.repeat(this.negatePadding(col) + 2) + '.');
|
|
wrapped.push("'" + '-'.repeat(this.negatePadding(col) + 2) + "'");
|
|
}
|
|
// add top and bottom padding.
|
|
if (col.padding) {
|
|
wrapped.unshift(...new Array(col.padding[top] || 0).fill(''));
|
|
wrapped.push(...new Array(col.padding[bottom] || 0).fill(''));
|
|
}
|
|
wrapped.forEach((str, r) => {
|
|
if (!rrows[r]) {
|
|
rrows.push([]);
|
|
}
|
|
const rrow = rrows[r];
|
|
for (let i = 0; i < c; i++) {
|
|
if (rrow[i] === undefined) {
|
|
rrow.push('');
|
|
}
|
|
}
|
|
rrow.push(str);
|
|
});
|
|
});
|
|
return rrows;
|
|
}
|
|
negatePadding(col) {
|
|
let wrapWidth = col.width || 0;
|
|
if (col.padding) {
|
|
wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0);
|
|
}
|
|
if (col.border) {
|
|
wrapWidth -= 4;
|
|
}
|
|
return wrapWidth;
|
|
}
|
|
columnWidths(row) {
|
|
if (!this.wrap) {
|
|
return row.map(col => {
|
|
return col.width || mixin.stringWidth(col.text);
|
|
});
|
|
}
|
|
let unset = row.length;
|
|
let remainingWidth = this.width;
|
|
// column widths can be set in config.
|
|
const widths = row.map(col => {
|
|
if (col.width) {
|
|
unset--;
|
|
remainingWidth -= col.width;
|
|
return col.width;
|
|
}
|
|
return undefined;
|
|
});
|
|
// any unset widths should be calculated.
|
|
const unsetWidth = unset ? Math.floor(remainingWidth / unset) : 0;
|
|
return widths.map((w, i) => {
|
|
if (w === undefined) {
|
|
return Math.max(unsetWidth, _minWidth(row[i]));
|
|
}
|
|
return w;
|
|
});
|
|
}
|
|
}
|
|
function addBorder(col, ts, style) {
|
|
if (col.border) {
|
|
if (/[.']-+[.']/.test(ts)) {
|
|
return '';
|
|
}
|
|
if (ts.trim().length !== 0) {
|
|
return style;
|
|
}
|
|
return ' ';
|
|
}
|
|
return '';
|
|
}
|
|
// calculates the minimum width of
|
|
// a column, based on padding preferences.
|
|
function _minWidth(col) {
|
|
const padding = col.padding || [];
|
|
const minWidth = 1 + (padding[left] || 0) + (padding[right] || 0);
|
|
if (col.border) {
|
|
return minWidth + 4;
|
|
}
|
|
return minWidth;
|
|
}
|
|
function getWindowWidth() {
|
|
/* istanbul ignore next: depends on terminal */
|
|
if (typeof process === 'object' && process.stdout && process.stdout.columns) {
|
|
return process.stdout.columns;
|
|
}
|
|
return 80;
|
|
}
|
|
function alignRight(str, width) {
|
|
str = str.trim();
|
|
const strWidth = mixin.stringWidth(str);
|
|
if (strWidth < width) {
|
|
return ' '.repeat(width - strWidth) + str;
|
|
}
|
|
return str;
|
|
}
|
|
function alignCenter(str, width) {
|
|
str = str.trim();
|
|
const strWidth = mixin.stringWidth(str);
|
|
/* istanbul ignore next */
|
|
if (strWidth >= width) {
|
|
return str;
|
|
}
|
|
return ' '.repeat((width - strWidth) >> 1) + str;
|
|
}
|
|
let mixin;
|
|
function cliui(opts, _mixin) {
|
|
mixin = _mixin;
|
|
return new UI({
|
|
width: (opts === null || opts === void 0 ? void 0 : opts.width) || getWindowWidth(),
|
|
wrap: opts === null || opts === void 0 ? void 0 : opts.wrap
|
|
});
|
|
}
|
|
|
|
// Bootstrap cliui with CommonJS dependencies:
|
|
const stringWidth = require('string-width');
|
|
const stripAnsi = require('strip-ansi');
|
|
const wrap = require('wrap-ansi');
|
|
function ui(opts) {
|
|
return cliui(opts, {
|
|
stringWidth,
|
|
stripAnsi,
|
|
wrap
|
|
});
|
|
}
|
|
|
|
module.exports = ui;
|