@bartificer/linkify 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/Linkifier.class.mjs +173 -21
- package/src/defaults.mjs +31 -0
- package/src/utilities.mjs +10 -0
package/package.json
CHANGED
package/src/Linkifier.class.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { PageData } from './PageData.class.mjs';
|
|
|
2
2
|
import { LinkData } from './LinkData.class.mjs';
|
|
3
3
|
import { LinkTemplate } from './LinkTemplate.class.mjs';
|
|
4
4
|
import * as utilities from "./utilities.mjs";
|
|
5
|
+
import * as defaults from "./defaults.mjs";
|
|
5
6
|
|
|
6
7
|
import fetch from 'node-fetch';
|
|
7
8
|
import * as cheerio from 'cheerio';
|
|
@@ -25,6 +26,16 @@ export class Linkifier {
|
|
|
25
26
|
}
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A mapping of domains names to default template names.
|
|
31
|
+
*
|
|
32
|
+
* @private
|
|
33
|
+
* @type {Object.<FQDN, templateName>}
|
|
34
|
+
*/
|
|
35
|
+
this._pageDataToLinkTemplateName = {
|
|
36
|
+
'.' : 'html' // default to the 'html' template for all domains unless otherwise specified
|
|
37
|
+
};
|
|
38
|
+
|
|
28
39
|
/**
|
|
29
40
|
* The registered link templates.
|
|
30
41
|
*
|
|
@@ -42,21 +53,11 @@ export class Linkifier {
|
|
|
42
53
|
this._utilities = utilities;
|
|
43
54
|
|
|
44
55
|
//
|
|
45
|
-
//
|
|
56
|
+
// -- Create and register the default templates --
|
|
46
57
|
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
new LinkTemplate('<a href="{{{url}}}" title="{{description}}">{{text}}</a>')
|
|
51
|
-
);
|
|
52
|
-
this.registerTemplate(
|
|
53
|
-
'htmlNewTab',
|
|
54
|
-
new LinkTemplate('<a href="{{{url}}}" title="{{description}}" target="_blank" rel="noopener">{{text}}</a>')
|
|
55
|
-
);
|
|
56
|
-
this.registerTemplate(
|
|
57
|
-
'markdown',
|
|
58
|
-
new LinkTemplate('[{{{text}}}]({{{url}}})')
|
|
59
|
-
);
|
|
58
|
+
for (const [name, template] of Object.entries(defaults.linkTemplates)) {
|
|
59
|
+
this.registerTemplate(name, template);
|
|
60
|
+
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
@@ -128,6 +129,39 @@ export class Linkifier {
|
|
|
128
129
|
return this._pageDataToLinkDataTransmformers['.'];
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
/**
|
|
133
|
+
* @type {string[]} A list of the names of the registered link templates.
|
|
134
|
+
*/
|
|
135
|
+
get templateNames() {
|
|
136
|
+
return Object.keys(this._linkTemplates);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @returns {string} The name of the default template.
|
|
141
|
+
*/
|
|
142
|
+
get defaultTemplateName(){
|
|
143
|
+
return this._pageDataToLinkTemplateName['.'];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {string} templateName - The name of the default template to use.
|
|
148
|
+
* @throws {ValidationError} A validation error is thrown if the template name is missing, invalid, or doesn't correspond to a registered template.
|
|
149
|
+
*/
|
|
150
|
+
set defaultTemplateName(templateName){
|
|
151
|
+
const tplName = String(templateName);
|
|
152
|
+
if(!this._linkTemplates[tplName]){
|
|
153
|
+
throw new ValidationError(`No template named '${tplName}' is registered`);
|
|
154
|
+
}
|
|
155
|
+
this._pageDataToLinkTemplateName['.'] = tplName;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @type {LinkTemplate} The default link template.
|
|
160
|
+
*/
|
|
161
|
+
get defaultTemplate(){
|
|
162
|
+
return this._linkTemplates[this._pageDataToLinkTemplateName['.']];
|
|
163
|
+
}
|
|
164
|
+
|
|
131
165
|
/**
|
|
132
166
|
* Register a link template.
|
|
133
167
|
*
|
|
@@ -138,8 +172,88 @@ export class Linkifier {
|
|
|
138
172
|
*/
|
|
139
173
|
registerTemplate(name, template){
|
|
140
174
|
// TO DO - add validation
|
|
175
|
+
const tplName = String(name);
|
|
141
176
|
|
|
142
|
-
this._linkTemplates[
|
|
177
|
+
this._linkTemplates[tplName] = template;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get a registered link template by name.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} templateName
|
|
184
|
+
* @returns {LinkTemplate}
|
|
185
|
+
* @throws {ValidationError} A validation error is thrown unless a valid name is passed and corresponds to a registered template.
|
|
186
|
+
*/
|
|
187
|
+
getTemplate(templateName){
|
|
188
|
+
const tplName = String(templateName);
|
|
189
|
+
|
|
190
|
+
if(!this._linkTemplates[tplName]){
|
|
191
|
+
throw new ValidationError(`No template named '${tplName}' is registered`);
|
|
192
|
+
}
|
|
193
|
+
return this._linkTemplates[tplName];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Register a default template for use with a given domain. This template will
|
|
198
|
+
* override the overall default for this domain and all its subdomains.
|
|
199
|
+
*
|
|
200
|
+
* @param {domainName} domain - The domain for which this template should be used by default.
|
|
201
|
+
* @param {templateName} templateName - The name of the template to use.
|
|
202
|
+
* @throws {ValidationError} A validation error is thrown if either parameter
|
|
203
|
+
* is missing or invalid.
|
|
204
|
+
*/
|
|
205
|
+
registerDefaultTemplateMapping(domain, templateName){
|
|
206
|
+
// TO DO - add validation
|
|
207
|
+
|
|
208
|
+
let fqdn = String(domain);
|
|
209
|
+
if(!fqdn.match(/[.]$/)){
|
|
210
|
+
fqdn += '.';
|
|
211
|
+
}
|
|
212
|
+
this._pageDataToLinkTemplateName[fqdn] = templateName;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get the data transformer function for a given domain.
|
|
217
|
+
*
|
|
218
|
+
* Note that domains are searched from the subdomain up. For example, if passed
|
|
219
|
+
* the domain `www.bartificer.net` the function will first look for a
|
|
220
|
+
* transformer for the domain `www.bartificer.net`, if there's no transformer
|
|
221
|
+
* registered for that domain it will look for a transformer for the domain
|
|
222
|
+
* `bartificer.net`, if there's no transformer for that domain either it will
|
|
223
|
+
* return the default transformer.
|
|
224
|
+
*
|
|
225
|
+
* @param {domainName} domain - The domain to get the data transformer for.
|
|
226
|
+
* @returns {dataTransformer}
|
|
227
|
+
* @throws {ValidationError} A validation error is thrown unless a valid domain
|
|
228
|
+
* name is passed.
|
|
229
|
+
*/
|
|
230
|
+
getTemplateNameForDomain(domain){
|
|
231
|
+
// TO DO - add validation
|
|
232
|
+
|
|
233
|
+
let fqdn = String(domain);
|
|
234
|
+
if(!fqdn.match(/[.]$/)){
|
|
235
|
+
fqdn += '.';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// return the most exact match
|
|
239
|
+
while(fqdn.match(/[.][^.]+[.]$/)){
|
|
240
|
+
if(this._pageDataToLinkTemplateName[fqdn]){
|
|
241
|
+
let tplName = this._pageDataToLinkTemplateName[fqdn];
|
|
242
|
+
|
|
243
|
+
// make sure the template exists
|
|
244
|
+
if(!this._linkTemplates[tplName]){
|
|
245
|
+
console.warn(`No template named '${tplName}' is registered, falling back to global default '${this._pageDataToLinkTemplateName['.']}'`);
|
|
246
|
+
return this._pageDataToLinkTemplateName['.'];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
//console.log(`returning template name for '${fqdn}'`);
|
|
250
|
+
return this._pageDataToLinkTemplateName[fqdn];
|
|
251
|
+
}
|
|
252
|
+
//console.log(`no template name found for '${fqdn}'`);
|
|
253
|
+
fqdn = fqdn.replace(/^[^.]+[.]/, '');
|
|
254
|
+
}
|
|
255
|
+
//console.log('returning default template name');
|
|
256
|
+
return this._pageDataToLinkTemplateName['.'];
|
|
143
257
|
}
|
|
144
258
|
|
|
145
259
|
/**
|
|
@@ -185,7 +299,9 @@ export class Linkifier {
|
|
|
185
299
|
}
|
|
186
300
|
|
|
187
301
|
/**
|
|
188
|
-
* Generate a link given a URL.
|
|
302
|
+
* Generate a link given a URL. By default the registered template for the
|
|
303
|
+
* URL's domain will be used, or, if none is registered, the overall
|
|
304
|
+
* default will be used (`html`).
|
|
189
305
|
*
|
|
190
306
|
* @async
|
|
191
307
|
* @param {URL} url
|
|
@@ -196,16 +312,52 @@ export class Linkifier {
|
|
|
196
312
|
*/
|
|
197
313
|
async generateLink(url, templateName){
|
|
198
314
|
// TO DO - add validation
|
|
199
|
-
|
|
200
|
-
|
|
315
|
+
|
|
316
|
+
//
|
|
317
|
+
// -- resolve the template name to use for this URL --
|
|
318
|
+
//
|
|
319
|
+
let tplName = '';
|
|
320
|
+
|
|
321
|
+
// resolve the template — if a template name is passed, try use it,
|
|
322
|
+
// otherwise resolve the default for this URL's domain
|
|
323
|
+
if(templateName && typeof templateName === 'string'){
|
|
324
|
+
tplName = templateName;
|
|
325
|
+
|
|
326
|
+
// make sure the template exists
|
|
327
|
+
if(!this._linkTemplates[tplName]){
|
|
328
|
+
console.warn(`No template named '${tplName}' is registered, falling back to global default '${this._pageDataToLinkTemplateName['.']}'`);
|
|
329
|
+
tplName = this._pageDataToLinkTemplateName['.'];
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
tplName = this.getTemplateNameForDomain((new URL(url)).hostname);
|
|
333
|
+
}
|
|
334
|
+
const template = this._linkTemplates[tplName];
|
|
201
335
|
|
|
202
336
|
// get the page data
|
|
203
|
-
|
|
337
|
+
const pageData = await this.fetchPageData(url);
|
|
204
338
|
|
|
205
339
|
// transform the page data to link data
|
|
206
|
-
|
|
340
|
+
const linkData = this.getTransformerForDomain(pageData.uri.hostname())(pageData);
|
|
341
|
+
|
|
342
|
+
// apply field-specific filters to the link data
|
|
343
|
+
const fieldNames = ['url', 'text', 'description'];
|
|
344
|
+
const templateData = linkData.asPlainObject();
|
|
345
|
+
for(let fieldName of fieldNames){
|
|
346
|
+
let fieldFilters = template.filtersFor(fieldName);
|
|
347
|
+
for(let filterFn of fieldFilters){
|
|
348
|
+
templateData[fieldName] = filterFn(templateData[fieldName]);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// apply the universal filters to all the link data fields
|
|
353
|
+
let globalFilters = template.filtersFor('all');
|
|
354
|
+
for(let filterFn of globalFilters){
|
|
355
|
+
for(let fieldName of fieldNames){
|
|
356
|
+
templateData[fieldName] = filterFn(templateData[fieldName]);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
207
359
|
|
|
208
360
|
// render the link
|
|
209
|
-
return Mustache.render(this._linkTemplates[tplName].templateString,
|
|
361
|
+
return Mustache.render(this._linkTemplates[tplName].templateString, templateData);
|
|
210
362
|
}
|
|
211
363
|
};
|
package/src/defaults.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LinkTemplate } from './LinkTemplate.class.mjs';
|
|
2
|
+
import * as utilities from "./utilities.mjs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @type {Object.<string, LinkTemplate>} A collection of named link templates.
|
|
6
|
+
*/
|
|
7
|
+
export const linkTemplates = {
|
|
8
|
+
html: new LinkTemplate(
|
|
9
|
+
'<a href="{{{url}}}" title="{{description}}">{{text}}</a>',
|
|
10
|
+
[
|
|
11
|
+
['url', utilities.stripUTMParameters],
|
|
12
|
+
['text', utilities.regulariseWhitespace],
|
|
13
|
+
['description', utilities.regulariseWhitespace]
|
|
14
|
+
]
|
|
15
|
+
),
|
|
16
|
+
htmlNewTab: new LinkTemplate(
|
|
17
|
+
'<a href="{{{url}}}" title="{{description}}" target="_blank" rel="noopener">{{text}}</a>',
|
|
18
|
+
[
|
|
19
|
+
['url', utilities.stripUTMParameters],
|
|
20
|
+
['text', utilities.regulariseWhitespace],
|
|
21
|
+
['description', utilities.regulariseWhitespace]
|
|
22
|
+
]
|
|
23
|
+
),
|
|
24
|
+
markdown: new LinkTemplate(
|
|
25
|
+
'[{{{text}}}]({{{url}}})',
|
|
26
|
+
[
|
|
27
|
+
['url', utilities.stripUTMParameters],
|
|
28
|
+
['text', utilities.regulariseWhitespace]
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
};
|
package/src/utilities.mjs
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import URI from 'urijs';
|
|
2
2
|
import * as urlSlug from 'url-slug';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Regularise white space by replacing all sequences of whitespace characters with a single space and trimming leading and trailing whitespace.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} text
|
|
8
|
+
* @return {string}
|
|
9
|
+
*/
|
|
10
|
+
export function regulariseWhitespace(text){
|
|
11
|
+
return String(text).replace(/[\s\n]+/g, ' ').trim();
|
|
12
|
+
};
|
|
13
|
+
|
|
4
14
|
/**
|
|
5
15
|
* Strip the query string from a URL.
|
|
6
16
|
*
|