@bartificer/linkify 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bartificer/linkify",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "An module for converting URLs into pretty links in any format.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -26,6 +26,16 @@ export class Linkifier {
26
26
  }
27
27
  };
28
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
+
29
39
  /**
30
40
  * The registered link templates.
31
41
  *
@@ -50,13 +60,6 @@ export class Linkifier {
50
60
  }
51
61
  }
52
62
 
53
- /**
54
- * @type {string[]} A list of the names of the registered link templates.
55
- */
56
- get templateNames() {
57
- return Object.keys(this._linkTemplates);
58
- }
59
-
60
63
  /**
61
64
  * @type {Object.<string, Function>}
62
65
  */
@@ -126,6 +129,39 @@ export class Linkifier {
126
129
  return this._pageDataToLinkDataTransmformers['.'];
127
130
  }
128
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
+
129
165
  /**
130
166
  * Register a link template.
131
167
  *
@@ -136,8 +172,88 @@ export class Linkifier {
136
172
  */
137
173
  registerTemplate(name, template){
138
174
  // TO DO - add validation
175
+ const tplName = String(name);
176
+
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
139
232
 
140
- this._linkTemplates[name] = template;
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['.'];
141
257
  }
142
258
 
143
259
  /**
@@ -183,7 +299,9 @@ export class Linkifier {
183
299
  }
184
300
 
185
301
  /**
186
- * 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`).
187
305
  *
188
306
  * @async
189
307
  * @param {URL} url
@@ -194,16 +312,52 @@ export class Linkifier {
194
312
  */
195
313
  async generateLink(url, templateName){
196
314
  // TO DO - add validation
197
-
198
- let tplName = templateName && typeof templateName === 'string' ? templateName : 'html';
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];
199
335
 
200
336
  // get the page data
201
- let pData = await this.fetchPageData(url);
337
+ const pageData = await this.fetchPageData(url);
202
338
 
203
339
  // transform the page data to link data
204
- let lData = this.getTransformerForDomain(pData.uri.hostname())(pData);
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
+ }
205
359
 
206
360
  // render the link
207
- return Mustache.render(this._linkTemplates[tplName].templateString, lData.asPlainObject());
361
+ return Mustache.render(this._linkTemplates[tplName].templateString, templateData);
208
362
  }
209
363
  };
package/src/defaults.mjs CHANGED
@@ -1,10 +1,31 @@
1
1
  import { LinkTemplate } from './LinkTemplate.class.mjs';
2
+ import * as utilities from "./utilities.mjs";
2
3
 
3
4
  /**
4
5
  * @type {Object.<string, LinkTemplate>} A collection of named link templates.
5
6
  */
6
7
  export const linkTemplates = {
7
- html: new LinkTemplate('<a href="{{{url}}}" title="{{description}}">{{text}}</a>'),
8
- htmlNewTab: new LinkTemplate('<a href="{{{url}}}" title="{{description}}" target="_blank" rel="noopener">{{text}}</a>'),
9
- markdown: new LinkTemplate('[{{{text}}}]({{{url}}})')
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
+ )
10
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
  *