From 4e052ff529ae6adfde12594e7ae5b082e34637ce Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Mon, 26 Aug 2024 01:45:12 +0700 Subject: [PATCH] Export functions based on return type (#43) --- index.d.ts | 54 +++++++++++++++---------------- index.js | 34 ++++++------------- index.test-d.ts | 15 ++++----- readme.md | 35 ++++++++++---------- test.js | 86 +++++++++++++++++++++---------------------------- 5 files changed, 96 insertions(+), 128 deletions(-) diff --git a/index.d.ts b/index.d.ts index 71681a3..e68dd00 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,15 +6,6 @@ export interface Options { */ readonly attributes?: HTMLAttributes; - /** - The format of the generated content. - - `'string'` will return it as a flat string like `'Visit https://example.com'`. - - `'dom'` will return it as a `DocumentFragment` ready to be appended in a DOM safely, like `DocumentFragment(TextNode('Visit '), HTMLAnchorElement('https://example.com'))`. This type only works in the browser. - */ - readonly type?: 'string' | 'dom'; - /** Set a custom HTML value for the link. @@ -22,9 +13,9 @@ export interface Options { @example ``` - import linkifyUrls from 'linkify-urls'; + import {linkifyUrlsToHtml} from 'linkify-urls'; - linkifyUrls('See https://sindresorhus.com/foo', { + linkifyUrlsToHtml('See https://sindresorhus.com/foo', { value: url => new URL(url).pathname }); //=> 'See /foo' @@ -34,20 +25,18 @@ export interface Options { } -export interface TypeDomOptions extends Options { - readonly type: 'dom'; -} - /** -Linkify URLs in a string. +Linkify URLs in a string, returns an HTML string. @param string - A string with URLs to linkify. +@returns An HTML string like `'Visit https://example.com'`. + @example ``` -import linkifyUrls from 'linkify-urls'; +import {linkifyUrlsToHtml} from 'linkify-urls'; -linkifyUrls('See https://sindresorhus.com', { +linkifyUrlsToHtml('See https://sindresorhus.com', { attributes: { class: 'unicorn', one: 1, @@ -56,10 +45,25 @@ linkifyUrls('See https://sindresorhus.com', { } }); //=> 'See https://sindresorhus.com' +``` +*/ +export function linkifyUrlsToHtml( + string: string, + options?: Options +): string; -// In the browser -const fragment = linkifyUrls('See https://sindresorhus.com', { - type: 'dom', +/** +Linkify URLs in a string, returns a `DocumentFragment`. + +@param string - A string with URLs to linkify. + +@returns a `DocumentFragment` ready to be appended in a DOM safely, like `DocumentFragment(TextNode('Visit '), HTMLAnchorElement('https://example.com'))`. This type only works in the browser. + +@example +``` +import {linkifyUrlsToDom} from 'linkify-urls'; + +const fragment = linkifyUrlsToDom('See https://sindresorhus.com', { attributes: { class: 'unicorn', } @@ -67,13 +71,9 @@ const fragment = linkifyUrls('See https://sindresorhus.com', { document.body.appendChild(fragment); ``` */ -export default function linkifyUrls( - string: string, - options: TypeDomOptions -): DocumentFragment; -export default function linkifyUrls( +export function linkifyUrlsToDom( string: string, options?: Options -): string; +): DocumentFragment; export {HTMLAttributes} from 'create-html-element'; diff --git a/index.js b/index.js index 98702cc..3f6c626 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ import createHtmlElement from 'create-html-element'; const urlRegex = () => (/((?` element as string -const linkify = (href, options) => createHtmlElement({ +const linkify = (href, options = {}) => createHtmlElement({ name: 'a', attributes: { href: '', @@ -23,12 +23,16 @@ const isTruncated = (url, peek) => url.endsWith('...') // `...` is a matched by the URL regex || peek.startsWith('…'); // `…` can follow the match -const getAsString = (string, options) => string.replace(urlRegex(), (url, _, offset) => - (isTruncated(url, string.charAt(offset + url.length))) - ? url // Don't linkify truncated URLs - : linkify(url, options)); +export function linkifyUrlsToHtml(string, options) { + const replacer = (url, _, offset) => + isTruncated(url, string.charAt(offset + url.length)) + ? url // Don't linkify truncated URLs + : linkify(url, options); -const getAsDocumentFragment = (string, options) => { + return string.replace(urlRegex(), replacer); +} + +export function linkifyUrlsToDom(string, options) { const fragment = document.createDocumentFragment(); const parts = string.split(urlRegex()); @@ -42,22 +46,4 @@ const getAsDocumentFragment = (string, options) => { } return fragment; -}; - -export default function linkifyUrls(string, options) { - options = { - attributes: {}, - type: 'string', - ...options, - }; - - if (options.type === 'string') { - return getAsString(string, options); - } - - if (options.type === 'dom') { - return getAsDocumentFragment(string, options); - } - - throw new TypeError('The type option must be either `dom` or `string`'); } diff --git a/index.test-d.ts b/index.test-d.ts index 5437263..536f2eb 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,8 +1,8 @@ import {expectType} from 'tsd'; -import linkifyUrls from './index.js'; +import {linkifyUrlsToHtml, linkifyUrlsToDom} from './index.js'; expectType( - linkifyUrls('See https://sindresorhus.com', { + linkifyUrlsToHtml('See https://sindresorhus.com', { attributes: { class: 'unicorn', one: 1, @@ -12,12 +12,12 @@ expectType( }), ); expectType( - linkifyUrls('See https://sindresorhus.com', { + linkifyUrlsToHtml('See https://sindresorhus.com', { value: 'foo', }), ); expectType( - linkifyUrls('See https://sindresorhus.com/foo', { + linkifyUrlsToHtml('See https://sindresorhus.com/foo', { value: url => { expectType(url); return url; @@ -25,13 +25,10 @@ expectType( }), ); expectType( - linkifyUrls('See https://sindresorhus.com/foo', { - type: 'string', - }), + linkifyUrlsToHtml('See https://sindresorhus.com/foo'), ); -const fragment = linkifyUrls('See https://sindresorhus.com', { - type: 'dom', +const fragment = linkifyUrlsToDom('See https://sindresorhus.com', { attributes: { class: 'unicorn', }, diff --git a/readme.md b/readme.md index f055be5..205ab19 100644 --- a/readme.md +++ b/readme.md @@ -11,9 +11,9 @@ npm install linkify-urls ## Usage ```js -import linkifyUrls from 'linkify-urls'; +import {linkifyUrlsToHtml, linkifyUrlsToDom} from 'linkify-urls'; -linkifyUrls('See https://sindresorhus.com', { +linkifyUrlsToHtml('See https://sindresorhus.com', { attributes: { class: 'unicorn', one: 1, @@ -28,8 +28,7 @@ linkifyUrls('See https://sindresorhus.com', { // In the browser -const fragment = linkifyUrls('See https://sindresorhus.com', { - type: 'dom', +const fragment = linkifyUrlsToDom('See https://sindresorhus.com', { attributes: { class: 'unicorn', } @@ -39,7 +38,9 @@ document.body.appendChild(fragment); ## API -### linkifyUrls(string, options?) +### linkifyUrlsToHtml(string, options?) + +Returns an HTML string like `'Visit https://example.com'`. #### string @@ -57,18 +58,6 @@ Type: `object` HTML attributes to add to the link. -##### type - -Type: `string`\ -Values: `'string' | 'dom'`\ -Default: `'string'` - -The format of the generated content. - -`'string'` will return it as a flat string like `'Visit https://example.com'`. - -`'dom'` will return it as a `DocumentFragment` ready to be appended in a DOM safely, like `DocumentFragment(TextNode('Visit '), HTMLAnchorElement('https://example.com'))`. This type only works in the browser. - ##### value Type: `string | Function`\ @@ -79,12 +68,22 @@ Set a custom HTML value for the link. If it's a function, it will receive the URL as a string: ```js -linkifyUrls('See https://sindresorhus.com/foo', { +linkifyUrlsToHtml('See https://sindresorhus.com/foo', { value: url => new URL(url).pathname }); //=> 'See /foo' ``` +### linkifyUrlsToDom(string, options?) + +Returns a `DocumentFragment` ready to be appended in a DOM safely, like `DocumentFragment(TextNode('Visit '), HTMLAnchorElement('https://example.com'))`. + +This type only works in the browser. + +#### options + +See [options](#options) above. + ## Browser compatibility Version 3 of this package uses [negative lookbehind regex syntax](https://kangax.github.io/compat-table/es2016plus/#test-RegExp_Lookbehind_Assertions). Stay on version 2 if you need to support browsers that doesn't support this feature. diff --git a/test.js b/test.js index a4e157c..7b0dc3d 100644 --- a/test.js +++ b/test.js @@ -1,7 +1,7 @@ import {URL} from 'node:url'; import test from 'ava'; import jsdom from 'jsdom'; -import linkifyUrls from './index.js'; +import {linkifyUrlsToDom, linkifyUrlsToHtml} from './index.js'; const dom = new jsdom.JSDOM(); globalThis.window = dom.window; @@ -29,12 +29,12 @@ const html = dom => { test('main', t => { t.is( - linkifyUrls('See https://sindresorhus.com and https://github.com/sindresorhus/got'), + linkifyUrlsToHtml('See https://sindresorhus.com and https://github.com/sindresorhus/got'), 'See https://sindresorhus.com and https://github.com/sindresorhus/got', ); t.is( - linkifyUrls('See https://sindresorhus.com', { + linkifyUrlsToHtml('See https://sindresorhus.com', { attributes: { class: 'unicorn', target: '_blank', @@ -44,14 +44,14 @@ test('main', t => { ); t.is( - linkifyUrls('[![Build Status](https://travis-ci.org/sindresorhus/caprine.svg?branch=main)](https://travis-ci.org/sindresorhus/caprine)'), + linkifyUrlsToHtml('[![Build Status](https://travis-ci.org/sindresorhus/caprine.svg?branch=main)](https://travis-ci.org/sindresorhus/caprine)'), '[![Build Status](https://travis-ci.org/sindresorhus/caprine.svg?branch=main)](https://travis-ci.org/sindresorhus/caprine)', ); }); test('supports boolean and non-string attribute values', t => { t.is( - linkifyUrls('https://sindresorhus.com', { + linkifyUrlsToHtml('https://sindresorhus.com', { attributes: { foo: true, bar: false, @@ -64,15 +64,12 @@ test('supports boolean and non-string attribute values', t => { test('DocumentFragment support', t => { t.is( - html(linkifyUrls('See https://sindresorhus.com and https://github.com/sindresorhus/got', { - type: 'dom', - })), + html(linkifyUrlsToDom('See https://sindresorhus.com and https://github.com/sindresorhus/got')), html(domify('See https://sindresorhus.com and https://github.com/sindresorhus/got')), ); t.is( - html(linkifyUrls('See https://sindresorhus.com', { - type: 'dom', + html(linkifyUrlsToDom('See https://sindresorhus.com', { attributes: { class: 'unicorn', target: '_blank', @@ -82,110 +79,99 @@ test('DocumentFragment support', t => { ); t.is( - html(linkifyUrls('[![Build Status](https://travis-ci.org/sindresorhus/caprine.svg?branch=main)](https://travis-ci.org/sindresorhus/caprine)', { - type: 'dom', - })), + html(linkifyUrlsToDom('[![Build Status](https://travis-ci.org/sindresorhus/caprine.svg?branch=main)](https://travis-ci.org/sindresorhus/caprine)')), html(domify('[![Build Status](https://travis-ci.org/sindresorhus/caprine.svg?branch=main)](https://travis-ci.org/sindresorhus/caprine)')), ); }); test('escapes the URL', t => { - t.is(linkifyUrls('https://mysite.com/?emp=1&=2'), 'https://mysite.com/?emp=1&amp=2'); + t.is(linkifyUrlsToHtml('https://mysite.com/?emp=1&=2'), 'https://mysite.com/?emp=1&amp=2'); }); test('supports `@` in the URL path', t => { - t.is(linkifyUrls('https://sindresorhus.com/@foo'), 'https://sindresorhus.com/@foo'); + t.is(linkifyUrlsToHtml('https://sindresorhus.com/@foo'), 'https://sindresorhus.com/@foo'); }); test('supports `#!` in the URL path', t => { - t.is(linkifyUrls('https://twitter.com/#!/sindresorhus'), 'https://twitter.com/#!/sindresorhus'); + t.is(linkifyUrlsToHtml('https://twitter.com/#!/sindresorhus'), 'https://twitter.com/#!/sindresorhus'); }); test('supports *$ in the URL path', t => { - t.is(linkifyUrls('https://sindresorhus.com/#1_*'), 'https://sindresorhus.com/#1_*'); - t.is(linkifyUrls('https://sindresorhus.com/#1_$'), 'https://sindresorhus.com/#1_$'); + t.is(linkifyUrlsToHtml('https://sindresorhus.com/#1_*'), 'https://sindresorhus.com/#1_*'); + t.is(linkifyUrlsToHtml('https://sindresorhus.com/#1_$'), 'https://sindresorhus.com/#1_$'); }); test('supports `,` in the URL path, but not at the end', t => { - t.is(linkifyUrls('https://sindresorhus.com/?id=foo,bar'), 'https://sindresorhus.com/?id=foo,bar'); - t.is(linkifyUrls('https://sindresorhus.com/?id=foo, bar'), 'https://sindresorhus.com/?id=foo, bar'); + t.is(linkifyUrlsToHtml('https://sindresorhus.com/?id=foo,bar'), 'https://sindresorhus.com/?id=foo,bar'); + t.is(linkifyUrlsToHtml('https://sindresorhus.com/?id=foo, bar'), 'https://sindresorhus.com/?id=foo, bar'); }); test('supports `value` option', t => { - t.is(linkifyUrls('See https://github.com/sindresorhus.com/linkify-urls for a solution', { - type: 'string', + t.is(linkifyUrlsToHtml('See https://github.com/sindresorhus.com/linkify-urls for a solution', { value: 0, }), 'See 0 for a solution'); }); test('supports `value` option as function', t => { - t.is(linkifyUrls('See https://github.com/sindresorhus.com/linkify-urls for a solution', { + t.is(linkifyUrlsToHtml('See https://github.com/sindresorhus.com/linkify-urls for a solution', { value: url => new URL(url).hostname, }), 'See github.com for a solution'); }); test.failing('skips the trailing period', t => { - t.is(linkifyUrls('Visit https://fregante.com.'), 'Visit https://fregante.com.'); + t.is(linkifyUrlsToHtml('Visit https://fregante.com.'), 'Visit https://fregante.com.'); }); test('skips URLs preceded by a `+` sign', t => { const fixture = 'git+https://github.com/sindresorhus/ava'; - t.is(linkifyUrls(fixture), fixture); + t.is(linkifyUrlsToHtml(fixture), fixture); }); test('supports username in url', t => { - t.is(linkifyUrls('https://user@sindresorhus.com/@foo'), 'https://user@sindresorhus.com/@foo'); + t.is(linkifyUrlsToHtml('https://user@sindresorhus.com/@foo'), 'https://user@sindresorhus.com/@foo'); }); test('supports a URL with a subdomain', t => { - t.is(linkifyUrls('https://docs.google.com'), 'https://docs.google.com'); + t.is(linkifyUrlsToHtml('https://docs.google.com'), 'https://docs.google.com'); }); test('skips email addresses', t => { - t.is(linkifyUrls('sindre@example.com'), 'sindre@example.com'); - t.is(linkifyUrls('www.sindre@example.com'), 'www.sindre@example.com'); - t.is(linkifyUrls('sindre@www.example.com'), 'sindre@www.example.com'); + t.is(linkifyUrlsToHtml('sindre@example.com'), 'sindre@example.com'); + t.is(linkifyUrlsToHtml('www.sindre@example.com'), 'www.sindre@example.com'); + t.is(linkifyUrlsToHtml('sindre@www.example.com'), 'sindre@www.example.com'); }); test('supports localhost URLs', t => { - t.is(linkifyUrls('https://localhost'), 'https://localhost'); - t.is(linkifyUrls('https://localhost/foo/bar'), 'https://localhost/foo/bar'); + t.is(linkifyUrlsToHtml('https://localhost'), 'https://localhost'); + t.is(linkifyUrlsToHtml('https://localhost/foo/bar'), 'https://localhost/foo/bar'); }); test('skips truncated URLs', t => { - t.is(linkifyUrls('https://github.com/sindresorhus/linkify-…'), 'https://github.com/sindresorhus/linkify-…'); - t.is(linkifyUrls('https://github.com/sindresorhus/linkify-… and https://github.com/sindresorhus/linkify-…'), 'https://github.com/sindresorhus/linkify-… and https://github.com/sindresorhus/linkify-…'); - t.is(linkifyUrls('https://github.com/sindresorhus/linkify-urls and more…'), 'https://github.com/sindresorhus/linkify-urls and more…'); + t.is(linkifyUrlsToHtml('https://github.com/sindresorhus/linkify-…'), 'https://github.com/sindresorhus/linkify-…'); + t.is(linkifyUrlsToHtml('https://github.com/sindresorhus/linkify-… and https://github.com/sindresorhus/linkify-…'), 'https://github.com/sindresorhus/linkify-… and https://github.com/sindresorhus/linkify-…'); + t.is(linkifyUrlsToHtml('https://github.com/sindresorhus/linkify-urls and more…'), 'https://github.com/sindresorhus/linkify-urls and more…'); - t.is(linkifyUrls('https://github.com/sindresorhus/linkify-...'), 'https://github.com/sindresorhus/linkify-...'); - t.is(linkifyUrls('https://github.com/sindresorhus/linkify-... and https://github.com/sindresorhus/linkify-...'), 'https://github.com/sindresorhus/linkify-... and https://github.com/sindresorhus/linkify-...'); - t.is(linkifyUrls('https://github.com/sindresorhus/linkify-urls and more...'), 'https://github.com/sindresorhus/linkify-urls and more...'); + t.is(linkifyUrlsToHtml('https://github.com/sindresorhus/linkify-...'), 'https://github.com/sindresorhus/linkify-...'); + t.is(linkifyUrlsToHtml('https://github.com/sindresorhus/linkify-... and https://github.com/sindresorhus/linkify-...'), 'https://github.com/sindresorhus/linkify-... and https://github.com/sindresorhus/linkify-...'); + t.is(linkifyUrlsToHtml('https://github.com/sindresorhus/linkify-urls and more...'), 'https://github.com/sindresorhus/linkify-urls and more...'); }); test('skips truncated URLs (DocumentFragment)', t => { t.is( - html(linkifyUrls('See https://github.com/sindresorhus/linkify-urls and https://github.com/sindresorhus/linkify-…', { - type: 'dom', - })), + html(linkifyUrlsToDom('See https://github.com/sindresorhus/linkify-urls and https://github.com/sindresorhus/linkify-…')), html(domify('See https://github.com/sindresorhus/linkify-urls and https://github.com/sindresorhus/linkify-…')), ); t.is( - html(linkifyUrls('See https://github.com/sindresorhus/linkify-urls… and https://github.com/sindresorhus/linkify-…', { - type: 'dom', - })), + html(linkifyUrlsToDom('See https://github.com/sindresorhus/linkify-urls… and https://github.com/sindresorhus/linkify-…')), html(domify('See https://github.com/sindresorhus/linkify-urls… and https://github.com/sindresorhus/linkify-…')), ); t.is( - html(linkifyUrls('See https://github.com/sindresorhus/linkify-urls and https://github.com/sindresorhus/linkify-...', { - type: 'dom', - })), + html(linkifyUrlsToDom('See https://github.com/sindresorhus/linkify-urls and https://github.com/sindresorhus/linkify-...')), html(domify('See https://github.com/sindresorhus/linkify-urls and https://github.com/sindresorhus/linkify-...')), ); t.is( - html(linkifyUrls('See https://github.com/sindresorhus/linkify-... and https://github.com/sindresorhus/linkify-...', { - type: 'dom', - })), + html(linkifyUrlsToDom('See https://github.com/sindresorhus/linkify-... and https://github.com/sindresorhus/linkify-...')), html(domify('See https://github.com/sindresorhus/linkify-... and https://github.com/sindresorhus/linkify-...')), ); });