Turn the bare links in a markdown text into titled links. Each page is requested, and its title and site replace the URL:
Source: https://en.wikipedia.org/wiki/Bespin
becomes
Source: "[Bespin](https://en.wikipedia.org/wiki/Bespin)", *wikipedia.org*
or whatever a template says. Links that are already markdown links, images, autolinks, reference definitions, HTML attributes or code are left alone, and so is any link whose page fails, times out or has no title. Nothing else in the text changes.
- A library and a command line tool, with TypeScript types, ES module and CommonJS builds.
- Node 20 and later, Bun and Deno.
npm install markdown-plain-link-replacerimport {replacePlainLinks} from 'markdown-plain-link-replacer';
const markdown = await replacePlainLinks('Source: https://en.wikipedia.org/wiki/Bespin');
// 'Source: "[Bespin](https://en.wikipedia.org/wiki/Bespin)", *wikipedia.org*'
const custom = await replacePlainLinks(text, {template: '[{{title}}]({{url}})', timeout: 5000});CommonJS, with a callback, as in 1.x:
const linkReplacer = require('markdown-plain-link-replacer');
linkReplacer.replacePlainLinks(' http://starwars.wikia.com/wiki/Bespin ', newMarkdown => {
console.log(newMarkdown);
}, '[{{title}}]({{url}}) from {{source}}');npm install --global markdown-plain-link-replacer
markdown-plain-link-replacer "Source: https://example.com"
markdown-plain-link-replacer -i notes.md > notes-linked.md
cat notes.md | markdown-plain-link-replacer -t "[{{title}}]({{url}})"Or without installing: npx markdown-plain-link-replacer -i notes.md. The markdown comes from the first argument, from -i <file>, or from stdin (with no argument, or with -). The result goes to stdout. Options: -i, --input <file>, -t, --template <text>, --timeout <ms>, -h, --help, -v, --version. It exits 0 when it printed the markdown, and 1 with a message on stderr for bad arguments or an input it cannot read.
Returns a Promise of the new markdown.
| Option | Type | Default | |
|---|---|---|---|
template |
string | "[{{title}}]({{url}})", *{{source}}* |
The replacement for each link; see Templates. |
timeout |
number | 10000 |
Milliseconds to wait for each page, for the image check and for the title. |
signal |
AbortSignal | Stops every lookup; the Promise rejects with the signal's reason. |
The 1.x form. Calls callback(newMarkdown) once, after returning. template is the template (or undefined for the default); options takes timeout and signal, and an abort gives the callback the markdown unchanged.
Both forms answer a falsy markdown ('', null, undefined) with the same value, at once to a callback. Both throw a TypeError before any request when markdown is neither a string nor falsy, the second argument is neither a callback nor an options object, the template is not a string or uses a mustache feature the renderer does not support, or timeout or signal has the wrong type. A lookup never throws or rejects: a link whose page cannot be read stays as it was.
The default export is {replacePlainLinks}, as require() returned in 1.x.
A template is mustache, rendered as hogan.js rendered it for 1.x, with three values:
| Value | |
|---|---|
{{title}} |
The page's title, as get-title-at-url reads it (a site name after | or - is removed). |
{{url}} |
The link as it is written in the markdown. |
{{source}} |
The site: the registrable domain (www.example.co.uk is example.co.uk, someone.github.io stays whole), or the host name for an IP address or localhost. |
{{name}} escapes HTML (&, <, >, ', "); {{{name}}} and {{&name}} do not. Sections {{#title}}...{{/title}}, inverted sections and comments work. Partials and delimiter changes do not, and throw a TypeError.
The default template writes the title without HTML escaping and escapes the characters that would break the link or format it: \, `, *, _, [, ], < and >.
A link is an http:// or https:// URL in the text. Punctuation that ends a sentence (., ,, ;, :, !, ?), a closing quote, * or ~ stays in the text after the link, and so does a closing bracket the link did not open: (see https://example.com/a). keeps both. https://en.wikipedia.org/wiki/Bent_(band) keeps its parenthesis.
These are left as they are, and never requested:
- the targets of markdown links and images, autolinks (
<https://…>) and reference definitions ([1]: https://…); href="https://…"and other HTML attribute values, and a link followed by a double quote;- links in code spans, fenced code blocks and indented code blocks;
www.example.comand//example.comwithout a scheme, and other schemes (ftp:,mailto:);- links with a user name or password.
A link whose path ends in an image extension is not requested; any other link gets one GET to see whether it is an image (is-an-image-url), and then one GET for its title, started 100 ms after the previous link's so one site does not get every request at once. A link that appears several times is looked up once.
The package requests every http and https link it finds in the text it is given, from wherever it runs, including localhost and private addresses. Run it on text you trust, or in a place where those requests cannot reach anything that matters. It is not a markdown parser (it reads just enough to skip code and existing links), and it does not sanitise the titles it writes beyond the markdown escaping above.
require('markdown-plain-link-replacer').replacePlainLinks(markdown, callback, template)still works.- Titles come from get-title-at-url 3 instead of article-title, so some titles differ (for example,
Name: Siteis no longer cut at the colon, and an<h1>no longer wins over<title>). - 1.x crashed the process on links to IP addresses,
localhostorwww.without a scheme, and never called back for some inputs; 2.x replaces or leaves those links and always answers. - 1.x HTML-decoded the whole text (
&became&everywhere) and replaced links inside code; 2.x does neither. - 1.x swallowed trailing punctuation into the link, and left a link followed by a period unreplaced; 2.x keeps the punctuation in the text and replaces the link.
- The default template no longer HTML-escapes the title and the URL.
- A template that is not a string, or does not compile, throws a
TypeErrorinstead of deleting the link or never calling back. - Each page gets a 10 second timeout (1.x waited forever for a title).
- The command line tool reads stdin, prints errors to stderr and exits 1 on them, and has
--timeout.
The changelog lists every change.
MIT © Mark Rogers
