@bedrockio/templates 0.1.1 → 0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.2.0
2
+
3
+ - Allow params in URLs.
4
+ - Prevent escaping of basic HTML.
5
+ - Handling null templates.
6
+ - Allow passing file extensions.
7
+ - Better custom handlers.
8
+
1
9
  ## 0.1.1
2
10
 
3
11
  - `lodash-es` -> `lodash.memoize`
@@ -20,12 +20,6 @@ class TemplateRenderer {
20
20
  }
21
21
  run(options) {
22
22
  const { template, params, helpers, ...rest } = this.resolveOptions(options);
23
- if (!template) {
24
- return {
25
- body: '',
26
- sections: [],
27
- };
28
- }
29
23
  const compiled = this.loadTemplate(template, rest);
30
24
  return compiled(params, {
31
25
  helpers: (0, helpers_1.resolveHelpers)(helpers, rest),
@@ -35,7 +29,7 @@ class TemplateRenderer {
35
29
  // Private
36
30
  /** @returns {Object} */
37
31
  resolveOptions(options = {}) {
38
- return {
32
+ const resolved = {
39
33
  ...this.options,
40
34
  ...options,
41
35
  params: {
@@ -43,12 +37,15 @@ class TemplateRenderer {
43
37
  ...options.params,
44
38
  },
45
39
  };
40
+ resolved.template ||= resolved.body;
41
+ return resolved;
46
42
  }
47
- loadTemplate = (0, lodash_memoize_1.default)((input, options) => {
43
+ loadTemplate = (0, lodash_memoize_1.default)((input = '', options) => {
48
44
  const source = (0, utils_1.resolveTemplateSource)(input, options);
49
45
  const template = handlebars_1.default.compile(source.trim());
50
46
  return (params, options) => {
51
- const output = template(params, options);
47
+ let output = template(params, options);
48
+ output = (0, utils_1.unescapeHtml)(output);
52
49
  const { body, meta } = (0, utils_1.runFrontMatter)(output);
53
50
  const sections = (0, utils_1.getSections)(body);
54
51
  return {
@@ -95,15 +95,14 @@ exports.DEFAULT_HELPERS = {
95
95
  }
96
96
  return index + 1;
97
97
  },
98
- link(url, text) {
98
+ link(text, url) {
99
99
  return new handlebars_1.default.SafeString(`[${text}](${url})`);
100
100
  },
101
- button(url, text) {
101
+ button(text, url) {
102
102
  return generateHtml('a', {
103
103
  text,
104
104
  href: url,
105
105
  class: 'button',
106
- target: '_blank',
107
106
  });
108
107
  },
109
108
  list(arr) {
@@ -124,7 +123,8 @@ function resolveHelpers(helpers, options) {
124
123
  function resolveHelper(arg, options) {
125
124
  const { names, handler } = resolveArgumentNames(arg);
126
125
  return (...args) => {
127
- return handler(...resolveHelperArgs(args, names, options));
126
+ const result = handler(...resolveHelperArgs(args, names, options));
127
+ return generateHtml(result);
128
128
  };
129
129
  }
130
130
  // Arguments
@@ -162,7 +162,12 @@ function resolveHelperArgs(args, names, options) {
162
162
  params[name] = value;
163
163
  });
164
164
  const resolved = names.map((name) => {
165
- return params[name];
165
+ if (name === 'params') {
166
+ return meta.hash;
167
+ }
168
+ let value = params[name];
169
+ value = normalizeParam(name, value, params, options);
170
+ return value;
166
171
  });
167
172
  options = {
168
173
  options,
@@ -170,7 +175,34 @@ function resolveHelperArgs(args, names, options) {
170
175
  };
171
176
  return [...resolved, options];
172
177
  }
173
- function generateHtml(tag, props) {
178
+ // Param normalization
179
+ function normalizeParam(name, value, params, options) {
180
+ if (name === 'url' || name === 'href') {
181
+ value = normalizeUrl(value, params, options);
182
+ }
183
+ return value;
184
+ }
185
+ const PARAM_REG = /:([a-z]+)/gi;
186
+ function normalizeUrl(str, params, options) {
187
+ const { baseUrl } = options;
188
+ if (baseUrl && str.startsWith('/')) {
189
+ str = baseUrl + str;
190
+ }
191
+ str = str.replace(PARAM_REG, (_, key) => {
192
+ const value = params[key];
193
+ // Need to delete the injected params or they
194
+ // will be passed on to the HTML element.
195
+ delete params[key];
196
+ return value;
197
+ });
198
+ return str;
199
+ }
200
+ function generateHtml(...args) {
201
+ const arg = args.length > 1 ? args : args[0];
202
+ if (!Array.isArray(arg)) {
203
+ return arg || '';
204
+ }
205
+ const [tag, props] = arg;
174
206
  const { text, ...rest } = props;
175
207
  const attr = Object.entries(rest)
176
208
  .map((entry) => {
@@ -181,9 +213,14 @@ function generateHtml(tag, props) {
181
213
  })
182
214
  .filter((a) => a)
183
215
  .join(' ');
184
- let html = `<${tag} ${attr}>`;
216
+ let html = `<${tag} ${attr}`;
185
217
  if (text) {
186
- html += `${text}</${tag}>`;
218
+ html += '>';
219
+ html += generateHtml(text);
220
+ html += `</${tag}>`;
221
+ }
222
+ else {
223
+ html += ` />`;
187
224
  }
188
225
  return new handlebars_1.default.SafeString(html);
189
226
  }
package/dist/cjs/utils.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveTemplateSource = resolveTemplateSource;
7
7
  exports.runFrontMatter = runFrontMatter;
8
8
  exports.getSections = getSections;
9
+ exports.unescapeHtml = unescapeHtml;
9
10
  const fs_1 = require("fs");
10
11
  const path_1 = __importDefault(require("path"));
11
12
  const front_matter_1 = __importDefault(require("front-matter"));
@@ -26,9 +27,9 @@ function runFrontMatter(str) {
26
27
  body: body.trim(),
27
28
  };
28
29
  }
29
- function tryReadFile(filepath, ext) {
30
+ function tryReadFile(filepath) {
30
31
  try {
31
- return (0, fs_1.readFileSync)(filepath + ext, 'utf-8');
32
+ return (0, fs_1.readFileSync)(filepath, 'utf-8');
32
33
  }
33
34
  catch (error) {
34
35
  if (error.code !== 'ENOENT') {
@@ -37,7 +38,12 @@ function tryReadFile(filepath, ext) {
37
38
  }
38
39
  }
39
40
  function readSource(filepath) {
40
- return tryReadFile(filepath, '.md') || tryReadFile(filepath, '.txt');
41
+ if (path_1.default.extname(filepath)) {
42
+ return tryReadFile(filepath);
43
+ }
44
+ else {
45
+ return tryReadFile(filepath + '.md') || tryReadFile(filepath + '.txt');
46
+ }
41
47
  }
42
48
  // Sections
43
49
  const SECTIONS_REG = /^=== (\w+) ===\n\n/gm;
@@ -60,3 +66,13 @@ function getSections(str) {
60
66
  }
61
67
  return sections;
62
68
  }
69
+ // Whitespace
70
+ // Handlebars doesn't allow a way to selectively
71
+ // escape so instead unescape some basic tokens.
72
+ function unescapeHtml(html) {
73
+ html = html.replace(/&#39;/g, "'");
74
+ html = html.replace(/&quot;/g, '"');
75
+ html = html.replace(/&#x27;/g, "'");
76
+ html = html.replace(/&#x3D;/g, '=');
77
+ return html;
78
+ }
@@ -1,7 +1,7 @@
1
1
  import Handlebars from 'handlebars';
2
2
  import memoize from 'lodash.memoize';
3
3
  import { DEFAULT_HELPERS, resolveHelpers } from './helpers.js';
4
- import { getSections, resolveTemplateSource, runFrontMatter } from './utils.js';
4
+ import { getSections, resolveTemplateSource, runFrontMatter, unescapeHtml, } from './utils.js';
5
5
  export default class TemplateRenderer {
6
6
  constructor(options = {}) {
7
7
  /** @type {Object} */
@@ -15,12 +15,6 @@ export default class TemplateRenderer {
15
15
  }
16
16
  run(options) {
17
17
  const { template, params, helpers, ...rest } = this.resolveOptions(options);
18
- if (!template) {
19
- return {
20
- body: '',
21
- sections: [],
22
- };
23
- }
24
18
  const compiled = this.loadTemplate(template, rest);
25
19
  return compiled(params, {
26
20
  helpers: resolveHelpers(helpers, rest),
@@ -30,7 +24,7 @@ export default class TemplateRenderer {
30
24
  // Private
31
25
  /** @returns {Object} */
32
26
  resolveOptions(options = {}) {
33
- return {
27
+ const resolved = {
34
28
  ...this.options,
35
29
  ...options,
36
30
  params: {
@@ -38,12 +32,15 @@ export default class TemplateRenderer {
38
32
  ...options.params,
39
33
  },
40
34
  };
35
+ resolved.template ||= resolved.body;
36
+ return resolved;
41
37
  }
42
- loadTemplate = memoize((input, options) => {
38
+ loadTemplate = memoize((input = '', options) => {
43
39
  const source = resolveTemplateSource(input, options);
44
40
  const template = Handlebars.compile(source.trim());
45
41
  return (params, options) => {
46
- const output = template(params, options);
42
+ let output = template(params, options);
43
+ output = unescapeHtml(output);
47
44
  const { body, meta } = runFrontMatter(output);
48
45
  const sections = getSections(body);
49
46
  return {
@@ -88,15 +88,14 @@ export const DEFAULT_HELPERS = {
88
88
  }
89
89
  return index + 1;
90
90
  },
91
- link(url, text) {
91
+ link(text, url) {
92
92
  return new Handlebars.SafeString(`[${text}](${url})`);
93
93
  },
94
- button(url, text) {
94
+ button(text, url) {
95
95
  return generateHtml('a', {
96
96
  text,
97
97
  href: url,
98
98
  class: 'button',
99
- target: '_blank',
100
99
  });
101
100
  },
102
101
  list(arr) {
@@ -117,7 +116,8 @@ export function resolveHelpers(helpers, options) {
117
116
  function resolveHelper(arg, options) {
118
117
  const { names, handler } = resolveArgumentNames(arg);
119
118
  return (...args) => {
120
- return handler(...resolveHelperArgs(args, names, options));
119
+ const result = handler(...resolveHelperArgs(args, names, options));
120
+ return generateHtml(result);
121
121
  };
122
122
  }
123
123
  // Arguments
@@ -155,7 +155,12 @@ function resolveHelperArgs(args, names, options) {
155
155
  params[name] = value;
156
156
  });
157
157
  const resolved = names.map((name) => {
158
- return params[name];
158
+ if (name === 'params') {
159
+ return meta.hash;
160
+ }
161
+ let value = params[name];
162
+ value = normalizeParam(name, value, params, options);
163
+ return value;
159
164
  });
160
165
  options = {
161
166
  options,
@@ -163,7 +168,34 @@ function resolveHelperArgs(args, names, options) {
163
168
  };
164
169
  return [...resolved, options];
165
170
  }
166
- function generateHtml(tag, props) {
171
+ // Param normalization
172
+ function normalizeParam(name, value, params, options) {
173
+ if (name === 'url' || name === 'href') {
174
+ value = normalizeUrl(value, params, options);
175
+ }
176
+ return value;
177
+ }
178
+ const PARAM_REG = /:([a-z]+)/gi;
179
+ function normalizeUrl(str, params, options) {
180
+ const { baseUrl } = options;
181
+ if (baseUrl && str.startsWith('/')) {
182
+ str = baseUrl + str;
183
+ }
184
+ str = str.replace(PARAM_REG, (_, key) => {
185
+ const value = params[key];
186
+ // Need to delete the injected params or they
187
+ // will be passed on to the HTML element.
188
+ delete params[key];
189
+ return value;
190
+ });
191
+ return str;
192
+ }
193
+ function generateHtml(...args) {
194
+ const arg = args.length > 1 ? args : args[0];
195
+ if (!Array.isArray(arg)) {
196
+ return arg || '';
197
+ }
198
+ const [tag, props] = arg;
167
199
  const { text, ...rest } = props;
168
200
  const attr = Object.entries(rest)
169
201
  .map((entry) => {
@@ -174,9 +206,14 @@ function generateHtml(tag, props) {
174
206
  })
175
207
  .filter((a) => a)
176
208
  .join(' ');
177
- let html = `<${tag} ${attr}>`;
209
+ let html = `<${tag} ${attr}`;
178
210
  if (text) {
179
- html += `${text}</${tag}>`;
211
+ html += '>';
212
+ html += generateHtml(text);
213
+ html += `</${tag}>`;
214
+ }
215
+ else {
216
+ html += ` />`;
180
217
  }
181
218
  return new Handlebars.SafeString(html);
182
219
  }
package/dist/esm/utils.js CHANGED
@@ -18,9 +18,9 @@ export function runFrontMatter(str) {
18
18
  body: body.trim(),
19
19
  };
20
20
  }
21
- function tryReadFile(filepath, ext) {
21
+ function tryReadFile(filepath) {
22
22
  try {
23
- return readFileSync(filepath + ext, 'utf-8');
23
+ return readFileSync(filepath, 'utf-8');
24
24
  }
25
25
  catch (error) {
26
26
  if (error.code !== 'ENOENT') {
@@ -29,7 +29,12 @@ function tryReadFile(filepath, ext) {
29
29
  }
30
30
  }
31
31
  function readSource(filepath) {
32
- return tryReadFile(filepath, '.md') || tryReadFile(filepath, '.txt');
32
+ if (path.extname(filepath)) {
33
+ return tryReadFile(filepath);
34
+ }
35
+ else {
36
+ return tryReadFile(filepath + '.md') || tryReadFile(filepath + '.txt');
37
+ }
33
38
  }
34
39
  // Sections
35
40
  const SECTIONS_REG = /^=== (\w+) ===\n\n/gm;
@@ -52,3 +57,13 @@ export function getSections(str) {
52
57
  }
53
58
  return sections;
54
59
  }
60
+ // Whitespace
61
+ // Handlebars doesn't allow a way to selectively
62
+ // escape so instead unescape some basic tokens.
63
+ export function unescapeHtml(html) {
64
+ html = html.replace(/&#39;/g, "'");
65
+ html = html.replace(/&quot;/g, '"');
66
+ html = html.replace(/&#x27;/g, "'");
67
+ html = html.replace(/&#x3D;/g, '=');
68
+ return html;
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/templates",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Bedrock utility for custom templates.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1 +1 @@
1
- {"version":3,"file":"TemplateRenderer.d.ts","sourceRoot":"","sources":["../src/TemplateRenderer.js"],"names":[],"mappings":"AAMA;IACE,0BASC;IARC,qBAAqB;IACrB,aAMC;IAGH,uBAgBC;IAID,wBAAwB;IACxB,kCASC;IAED,kBAgBG;CACJ"}
1
+ {"version":3,"file":"TemplateRenderer.d.ts","sourceRoot":"","sources":["../src/TemplateRenderer.js"],"names":[],"mappings":"AAYA;IACE,0BASC;IARC,qBAAqB;IACrB,aAMC;IAGH,uBASC;IAID,wBAAwB;IACxB,kCAWC;IAED,kBAkBG;CACJ"}
@@ -22,8 +22,8 @@ export namespace DEFAULT_HELPERS {
22
22
  function dateTimeShort(arg: any, meridiem: any): any;
23
23
  function relTime(arg: any, min: any, max: any): any;
24
24
  function number(options: any): any;
25
- function link(url: any, text: any): Handlebars.SafeString;
26
- function button(url: any, text: any): Handlebars.SafeString;
25
+ function link(text: any, url: any): Handlebars.SafeString;
26
+ function button(text: any, url: any): any;
27
27
  function list(arr: any): any;
28
28
  }
29
29
  import Handlebars from 'handlebars';
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.js"],"names":[],"mappings":"AAyHA,+DAMC;;IA1HC,gCAEC;IACD,iCAEC;IACD,mCAEC;IACD,kCAEC;IAGD,4CAIC;IAED;;OAEG;IACH,mDAFW,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,OAOpD;IACD,gDAIC;IACD,kDAIC;IACD,iDAIC;IAGD,gDAIC;IAED;;OAEG;IACH,uDAFW,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,OAOpD;IACD,oDAIC;IACD,sDAIC;IACD,qDAIC;IAID,oDAKC;IAED,mCAMC;IAED,0DAEC;IAED,4DAOC;IAED,6BAMC;;uBArHoB,YAAY"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.js"],"names":[],"mappings":"AAwHA,+DAMC;;IAzHC,gCAEC;IACD,iCAEC;IACD,mCAEC;IACD,kCAEC;IAGD,4CAIC;IAED;;OAEG;IACH,mDAFW,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,OAOpD;IACD,gDAIC;IACD,kDAIC;IACD,iDAIC;IAGD,gDAIC;IAED;;OAEG;IACH,uDAFW,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,OAOpD;IACD,oDAIC;IACD,sDAIC;IACD,qDAIC;IAID,oDAKC;IAED,mCAMC;IAED,0DAEC;IAED,0CAMC;IAED,6BAMC;;uBApHoB,YAAY"}
package/types/utils.d.ts CHANGED
@@ -9,4 +9,5 @@ export function getSections(str: any): {
9
9
  title: any;
10
10
  content: any;
11
11
  }[];
12
+ export function unescapeHtml(html: any): any;
12
13
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAKA,mEASC;AAED;;;EAOC;AAoBD;;;;;IAsBC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.js"],"names":[],"mappings":"AAKA,mEASC;AAED;;;EAOC;AAwBD;;;;;IAsBC;AAMD,6CAMC"}