Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hyperlink support for cells #292

Merged
merged 3 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions basic-usage.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,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 @@ -57,24 +64,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 @@ -371,6 +389,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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));
});
});
});