Plugin: Linkify

name

Linkify

icon

add_link

description

Automatically turns URLs, email addresses and phone numbers into links

instructions

This plugin will turn URLs, email addresses and phone numbers within the selection into links.


The demo above was done with all optional replacements enabled and the country-specific phone number format set to AT.

It is configurable what types of things should get linked:

URLs with protocol (e.g. https://example.com/abc)

URLs without protocol (e.g. example.com, linked as https://example.com)

Email addresses (e.g. hello@example.com, linked as mailto:hello@example.com)

Phone numbers in international format (e.g. +44 20 7123 4567, linked as tel:+442071234567)

Phone numbers in country-specific local format (e.g. 020 7123 4567, linked as tel:+442071234567 according to configuration described below)

The configuration for country-specific local format of phone numbers is a bit complicated. You can specify only one format (because they may conflict). There are a few predefined formats but you can also specify your own.

Custom formats:

General format is detection regex;prefix;trimming regex.

The detection regex is used to detect the phone number in the text. Non-digits are automatically removed, then the trimming regex (optional) is applied by removing anything it matches (e.g. to remove leading zero) and the prefix is added (should be the international prefix such as +44).

Predefined formats:

US (= \b1?\d{10}\b|\b\(\d{3}\) \d{3}-\d{4}\b|\b(?:1-)?\d{3}-\d{3}-\d{4}\b;+1;^1)

AT (= \b0\d[\d \-/]{6,}\d\b;+43;^0)

DE (= \b0\d[\d \-/]{6,}\d\b;+49;^0)

CH (= \b0\d[\d \-/]{6,}\d\b;+41;^0)

UK (= \b0\d[\d \-/]{7,}\d\b;+44;^0)

IT (= \b[03]\d[\d \-/]{7,}\d\b;+39)

FR (= \b0\d ?(?:\d\d ?){4};+33;^0)

IL (= \b0\d[\d \-]{7,}\d\b;+972;^0)

Note: Due to limitations in the Amplenote plugin system, formatting cannot be retained, including other previously existing links!

☕ If you like my work, you can buy me a coffee!

setting

Linkify URLs with protocol (true/false, default=true)

setting

Linkify URLs without protocol (true/false, default=false)

setting

Linkify email addresses (true/false, default=true)

setting

Linkify international phone numbers (true/false, default=true)

setting

Format to linkify local phone numbers (see plugin docs, default=<none>)

({
_settingsDescriptor: {
linkifyUrlsWithProtocol: ['Linkify URLs with protocol (true/false, default=true)', 'true', Boolean],
linkifyUrlsWithoutProtocol: ['Linkify URLs without protocol (true/false, default=false)', 'false', Boolean],
linkifyEmails: ['Linkify email addresses (true/false, default=true)', 'true', Boolean],
linkifyInternationalPhoneNumbers: ['Linkify international phone numbers (true/false, default=true)', 'true', Boolean],
linkifyLocalPhoneNumbersFormat: ['Format to linkify local phone numbers (see plugin docs, default=<none>)', '', String]
},
_getSetting (app, key) {
const [settingName, defaultValue, type] = this._settingsDescriptor[key]
const value = app.settings[settingName] || defaultValue
if (type === Boolean) {
return value.trim().toLowerCase() === 'true'
} else if (type === Number) {
return value === '' ? null : Number(value)
} else {
return value
}
},
_getMatchers (app, text) {
const matchers = []
if (this._getSetting(app, 'linkifyUrlsWithProtocol')) {
matchers.push([/(?<!\]\(|\[)\b(?:[a-z]+:\/\/|mailto:|data:[\d/]+[;,])[^\s\(\)\[\]]{2,}(?<![\s!?:.,\)\]])/ig, url => `[${url}](${url})`])
}
if (this._getSetting(app, 'linkifyUrlsWithoutProtocol')) {
matchers.push([/(?<=^|\s|\()\b[a-z0-9]+\.[a-z]{2,}[^\s\(\)\[\]@]*(?<![\s!?:.,\)\]])(?=[\s!?:.,\)\]]|$)/ig, url => `[${url}](https://${url})`])
}
if (this._getSetting(app, 'linkifyEmails')) {
matchers.push([/(?<=^|\s|\()\b[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+/g, email => `[${email}](mailto:${email})`])
}
if (this._getSetting(app, 'linkifyInternationalPhoneNumbers')) {
matchers.push([/(?<=^|\s|\()\+\d[\d \-/\(\)]{7,}/g, phone => `[${phone}](tel:${phone.replace(/[^+\d]/g, '')})`])
}
let formatStr = this._getSetting(app, 'linkifyLocalPhoneNumbersFormat')
if (formatStr) {
formatStr = {
US: String.raw`\b1?\d{10}\b|\b\(\d{3}\) \d{3}-\d{4}\b|\b(?:1-)?\d{3}-\d{3}-\d{4}\b;+1;^1`,
AT: String.raw`\b0\d[\d \-/]{6,}\d\b;+43;^0`,
DE: String.raw`\b0\d[\d \-/]{6,}\d\b;+49;^0`,
CH: String.raw`\b0\d[\d \-/]{6,}\d\b;+41;^0`,
UK: String.raw`\b0\d[\d \-/]{7,}\d\b;+44;^0`,
IT: String.raw`\b[03]\d[\d \-/]{7,}\d\b;+39`,
FR: String.raw`\b0\d ?(?:\d\d ?){4};+33;^0`,
IL: String.raw`\b0\d[\d \-]{7,}\d\b;+972;^0`
}[formatStr.toUpperCase()] ?? formatStr
if (formatStr.includes(';')) {
const [patternStr, prefix, trimmerStr] = formatStr.split(';')
const pattern = new RegExp('(?<!\\[)' + patternStr, 'ig')
const trimmer = trimmerStr ? new RegExp(trimmerStr, 'ig') : null
matchers.push([pattern, phone => {
let normalizedPhone = phone.replace(/\D/g, '')
if (trimmer) normalizedPhone = normalizedPhone.replace(trimmer, '')
normalizedPhone = prefix + normalizedPhone
return `[${phone}](tel:${normalizedPhone})`
}])
}
}
return matchers
},
replaceText: {
check (app, text) {
return this._getMatchers(app, text).some(([pattern]) => pattern.test(text))
},
async run (app, text) {
try {
for (const [pattern, replacer] of this._getMatchers(app, text)) {
text = text.replace(pattern, replacer)
}
await app.context.replaceSelection(text.replace(/\n/g, '\n\n'))
} catch (e) {
console.error(e)
await app.alert(String(e))
}
}
}
})