Skip to content

Commit

Permalink
Hyperlink support for cells (#292)
Browse files Browse the repository at this point in the history
* Failing tests for hyperlink support
* Add hyperlink support for cells (#73)
* Add hyperlink example to basic usage docs
  • Loading branch information
speedytwenty committed Apr 5, 2022
1 parent 40e86bc commit ed4afb7
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 11 deletions.
28 changes: 28 additions & 0 deletions basic-usage.md
Expand Up @@ -166,3 +166,31 @@
table.push(['Wrap', 'Text']);
```


##### Supports hyperlinking cell content using the href option
┌───────────┬─────┬─────┐
│ Text Link │ Hel │ htt │
│ │ lo │ p:/ │
│ │ Lin │ /ex │
│ │ k │ amp │
│ │ │ le. │
│ │ │ com │
├───────────┴─────┴─────┤
│ http://example.com │
└───────────────────────┘

Note: Links are not displayed in documentation examples.
```javascript
const table = new Table({
colWidths: [11, 5, 5],
style: { border: [], head: [] },
wordWrap: true,
wrapOnWordBoundary: false,
});
const href = 'http://example.com';
table.push(
[{ content: 'Text Link', href }, { content: 'Hello Link', href }, { href }],
[{ href, colSpan: 3 }]
);
```

36 changes: 36 additions & 0 deletions examples/basic-usage-examples.js
@@ -1,5 +1,6 @@
const Table = require('../src/table');
const colors = require('@colors/colors/safe');
const { hyperlink } = require('../src/utils');

// prettier-ignore
// Disable prettier so that examples are formatted more clearly
Expand Down Expand Up @@ -235,6 +236,40 @@ module.exports = function (runTest) {

return [makeTable, expected];
});

it('Supports hyperlinking cell content using the href option', () => {
function link(text) {
return hyperlink('http://example.com', text);
}
function makeTable() {
const table = new Table({
colWidths: [11, 5, 5],
style: { border: [], head: [] },
wordWrap: true,
wrapOnWordBoundary: false,
});
const href = 'http://example.com';
table.push(
[{ content: 'Text Link', href }, { content: 'Hello Link', href }, { href }],
[{ href, colSpan: 3 }]
);
return table;
}

let expected = [
'┌───────────┬─────┬─────┐',
`│ ${link('Text Link')}${link('Hel')}${link('htt')} │`,
`│ │ ${link('lo ')}${link('p:/')} │`,
`│ │ ${link('Lin')}${link('/ex')} │`,
`│ │ ${link('k')}${link('amp')} │`,
`│ │ │ ${link('le.')} │`,
`│ │ │ ${link('com')} │`,
'├───────────┴─────┴─────┤',
`│ ${link('http://example.com')} │`,
'└───────────────────────┘',
];
return [makeTable, expected];
});
};

/* Expectation - ready to be copy/pasted and filled in. DO NOT DELETE THIS
Expand All @@ -250,3 +285,4 @@ module.exports = function (runTest) {
, '└──┴───┴──┴──┘'
];
*/
// Jest Snapshot v1, https://goo.gl/fbAQLP
14 changes: 13 additions & 1 deletion lib/print-example.js
Expand Up @@ -17,6 +17,18 @@ function logExample(fn) {
);
}

function replaceLinks(str) {
const matches = str.match(/\x1B\]8;;[^\x07]+\x07[^\]]+\x1B\]8;;\x07/g);
if (matches) {
matches.forEach((match) => {
const [, text] = match.match(/\x07([^\]|\x1B]+)\x1B/);
str = str.replace(match, text);
});
str += '\n\nNote: Links are not displayed in documentation examples.';
}
return str;
}

function mdExample(fn, file, cb) {
let buffer = [];

Expand All @@ -27,7 +39,7 @@ function mdExample(fn, file, cb) {
},
function logTable(table) {
//md files won't render color strings properly.
table = stripColors(table);
table = replaceLinks(stripColors(table));

// indent table so is displayed preformatted text
table = ' ' + table.split('\n').join('\n ');
Expand Down
39 changes: 29 additions & 10 deletions src/cell.js
Expand Up @@ -31,12 +31,19 @@ class Cell {
if (['boolean', 'number', 'string'].indexOf(typeof content) !== -1) {
this.content = String(content);
} else if (!content) {
this.content = '';
this.content = this.options.href || '';
} else {
throw new Error('Content needs to be a primitive, got: ' + typeof content);
}
this.colSpan = options.colSpan || 1;
this.rowSpan = options.rowSpan || 1;
if (this.options.href) {
Object.defineProperty(this, 'href', {
get() {
return this.options.href;
},
});
}
}

mergeTableOptions(tableOptions, cells) {
Expand All @@ -58,24 +65,35 @@ class Cell {
this.head = style.head || tableStyle.head;
this.border = style.border || tableStyle.border;

let fixedWidth = tableOptions.colWidths[this.x];
if ((tableOptions.wordWrap || tableOptions.textWrap) && fixedWidth) {
fixedWidth -= this.paddingLeft + this.paddingRight;
this.fixedWidth = tableOptions.colWidths[this.x];
this.lines = this.computeLines(tableOptions);

this.desiredWidth = utils.strlen(this.content) + this.paddingLeft + this.paddingRight;
this.desiredHeight = this.lines.length;
}

computeLines(tableOptions) {
if (this.fixedWidth && (tableOptions.wordWrap || tableOptions.textWrap)) {
this.fixedWidth -= this.paddingLeft + this.paddingRight;
if (this.colSpan) {
let i = 1;
while (i < this.colSpan) {
fixedWidth += tableOptions.colWidths[this.x + i];
this.fixedWidth += tableOptions.colWidths[this.x + i];
i++;
}
}
const { wrapOnWordBoundary = true } = tableOptions;
this.lines = utils.colorizeLines(utils.wordWrap(fixedWidth, this.content, wrapOnWordBoundary));
} else {
this.lines = utils.colorizeLines(this.content.split('\n'));
return this.wrapLines(utils.wordWrap(this.fixedWidth, this.content, wrapOnWordBoundary));
}
return this.wrapLines(this.content.split('\n'));
}

this.desiredWidth = utils.strlen(this.content) + this.paddingLeft + this.paddingRight;
this.desiredHeight = this.lines.length;
wrapLines(computedLines) {
const lines = utils.colorizeLines(computedLines);
if (this.href) {
return lines.map((line) => utils.hyperlink(this.href, line));
}
return lines;
}

/**
Expand Down Expand Up @@ -382,6 +400,7 @@ let CHAR_NAMES = [
'right-mid',
'middle',
];

module.exports = Cell;
module.exports.ColSpanCell = ColSpanCell;
module.exports.RowSpanCell = RowSpanCell;
12 changes: 12 additions & 0 deletions src/utils.js
Expand Up @@ -313,6 +313,17 @@ function colorizeLines(input) {
return output;
}

/**
* Credit: Matheus Sampaio https://github.com/matheussampaio
*/
function hyperlink(url, text) {
const OSC = '\u001B]';
const BEL = '\u0007';
const SEP = ';';

return [OSC, '8', SEP, SEP, url || text, BEL, text, OSC, '8', SEP, SEP, BEL].join('');
}

module.exports = {
strlen: strlen,
repeat: repeat,
Expand All @@ -321,4 +332,5 @@ module.exports = {
mergeOptions: mergeOptions,
wordWrap: multiLineWordWrap,
colorizeLines: colorizeLines,
hyperlink,
};
12 changes: 12 additions & 0 deletions test/utils-test.js
Expand Up @@ -387,4 +387,16 @@ describe('utils', function () {
expect(utils.colorizeLines(input)).toEqual([colors.red('漢字'), colors.red('テスト')]);
});
});

describe('hyperlink', function () {
const url = 'http://example.com';
const text = 'hello link';
const expected = (u, t) => `\x1B]8;;${u}\x07${t}\x1B]8;;\x07`;
it('wraps text with link', () => {
expect(utils.hyperlink(url, text)).toEqual(expected(url, text));
});
it('defaults text to link', () => {
expect(utils.hyperlink(url, url)).toEqual(expected(url, url));
});
});
});

0 comments on commit ed4afb7

Please sign in to comment.