@prairielearn/html 1.0.1 → 2.1.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.
@@ -1,3 +1,3 @@
1
- @prairielearn/html:build: cache hit, replaying output f1676fe5da8e40e3
1
+ @prairielearn/html:build: cache hit, replaying output ce1aac287c735a34
2
2
  @prairielearn/html:build: warning package.json: No license field
3
3
  @prairielearn/html:build: $ tsc
package/CHANGELOG.md CHANGED
@@ -1,6 +1,27 @@
1
1
  # @prairielearn/html
2
2
 
3
+ ## 2.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c23684a26: Type constraints for interpolated values
8
+
9
+ ## 2.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - 56f1333fc: `renderEjs` function moved to `@prairielearn/html-ejs`
14
+
15
+ In order to be able to use the `@prairielearn/html` package inside client scripts, EJS functionality was moved to a separate package (`@prairielearn/html-ejs`). The `ejs` package relies on Node-only packages like `fs` and `path`, which renders it unusable in browsers.
16
+
17
+ ## 1.0.2
18
+
19
+ ### Patch Changes
20
+
21
+ - d3ed76de3: Change transpiled code to use ES2020 syntax
22
+
3
23
  ## 1.0.1
24
+
4
25
  ### Patch Changes
5
26
 
6
27
  - 5752fab5b: Fix rendering of null values in templates
package/README.md CHANGED
@@ -40,26 +40,6 @@ console.log(html`
40
40
  `);
41
41
  ```
42
42
 
43
- ### Using with EJS
44
-
45
- If you have an EJS partial that you'd like to use inside of an `html` tagged template literal, you can use the `renderEjs` helper:
46
-
47
- ```html
48
- <!-- hello.ejs -->
49
- Hello, <%= name %>!
50
- ```
51
-
52
- ```js
53
- const { hmtl, renderEjs } = require('@prairielearn/html');
54
-
55
- console.log(
56
- html`
57
- <div>Hello, world!</div>
58
- <div>${renderEjs(__filename, "<%- include('./hello'); %>", { name: 'Anjali' })}</div>
59
- `.toString()
60
- );
61
- ```
62
-
63
43
  ## Why not EJS?
64
44
 
65
45
  PrairieLearn used (and still uses) EJS to render most views. However, using a tagged template literal and pure JavaScript to render views has a number of advantages:
@@ -67,3 +47,5 @@ PrairieLearn used (and still uses) EJS to render most views. However, using a ta
67
47
  - Prettier will automatically format the content of any `html` tagged template literal; EJS does not have any automatic formatters.
68
48
  - Authoring views in pure JavaScript allows for easier and more explicit composition of components.
69
49
  - It's possible to use ESLint and TypeScript to type-check JavaScript views; EJS does not offer support for either.
50
+
51
+ If you want to use existing EJS partials inside of an `html` tagged template literal, check out the `@prairielearn/html-ejs` package. EJS-related functionality is deliberately located in a separate package so that `@prairielearn/html` can be used in the browser, since the `ejs` package makes use of Node-only features.
package/dist/index.d.ts CHANGED
@@ -4,7 +4,8 @@ export declare class HtmlSafeString {
4
4
  constructor(strings: ReadonlyArray<string>, values: unknown[]);
5
5
  toString(): string;
6
6
  }
7
- export declare function html(strings: TemplateStringsArray, ...values: any[]): HtmlSafeString;
7
+ export declare type HtmlValue = string | number | boolean | HtmlSafeString | undefined | null | HtmlValue[];
8
+ export declare function html(strings: TemplateStringsArray, ...values: HtmlValue[]): HtmlSafeString;
8
9
  /**
9
10
  * Pre-escpapes the rendered HTML. Useful for when you want to inline the HTML
10
11
  * in something else, for instance in a `data-content` attribute for a Bootstrap
@@ -19,16 +20,3 @@ export declare function escapeHtml(html: HtmlSafeString): HtmlSafeString;
19
20
  * @returns An {@link HtmlSafeString} representing the provided value.
20
21
  */
21
22
  export declare function unsafeHtml(value: string): HtmlSafeString;
22
- /**
23
- * This is a shim to allow for the use of EJS templates inside of HTML tagged
24
- * template literals.
25
- *
26
- * The resulting string is assumed to be appropriately escaped and will be used
27
- * verbatim in the resulting HTML.
28
- *
29
- * @param filename The name of the file from which relative includes should be resolved.
30
- * @param template The raw EJS template string.
31
- * @param data Any data to be made available to the template.
32
- * @returns The rendered EJS.
33
- */
34
- export declare function renderEjs(filename: string, template: string, data?: any): HtmlSafeString;
package/dist/index.js CHANGED
@@ -1,28 +1,40 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unsafeHtml = exports.escapeHtml = exports.html = exports.HtmlSafeString = void 0;
4
+ const ENCODE_HTML_RULES = {
5
+ '&': '&amp;',
6
+ '<': '&lt;',
7
+ '>': '&gt;',
8
+ '"': '&#34;',
9
+ "'": '&#39;',
4
10
  };
5
- exports.__esModule = true;
6
- exports.renderEjs = exports.unsafeHtml = exports.escapeHtml = exports.html = exports.HtmlSafeString = void 0;
7
- var ejs_1 = __importDefault(require("ejs"));
8
- var path_1 = __importDefault(require("path"));
11
+ var MATCH_HTML = /[&<>'"]/g;
12
+ function encodeCharacter(c) {
13
+ return ENCODE_HTML_RULES[c] || c;
14
+ }
15
+ /**
16
+ * Based on the `escapeXML` function from the `ejs` library.
17
+ */
18
+ function escapeHtmlRaw(value) {
19
+ return value == undefined ? '' : String(value).replace(MATCH_HTML, encodeCharacter);
20
+ }
9
21
  function escapeValue(value) {
10
22
  if (value instanceof HtmlSafeString) {
11
23
  // Already escaped!
12
24
  return value.toString();
13
25
  }
14
26
  else if (Array.isArray(value)) {
15
- return value.map(function (val) { return escapeValue(val); }).join('');
27
+ return value.map((val) => escapeValue(val)).join('');
16
28
  }
17
29
  else if (typeof value === 'string' || typeof value === 'number') {
18
- return ejs_1["default"].escapeXML(String(value));
30
+ return escapeHtmlRaw(String(value));
19
31
  }
20
32
  else if (value == null) {
21
33
  // undefined or null -- render nothing
22
34
  return '';
23
35
  }
24
36
  else if (typeof value === 'object') {
25
- throw new Error("Cannot interpolate object in template: ".concat(JSON.stringify(value)));
37
+ throw new Error(`Cannot interpolate object in template: ${JSON.stringify(value)}`);
26
38
  }
27
39
  else {
28
40
  // This is boolean - don't render anything here.
@@ -30,25 +42,19 @@ function escapeValue(value) {
30
42
  }
31
43
  }
32
44
  // Based on https://github.com/Janpot/escape-html-template-tag
33
- var HtmlSafeString = /** @class */ (function () {
34
- function HtmlSafeString(strings, values) {
45
+ class HtmlSafeString {
46
+ constructor(strings, values) {
35
47
  this.strings = strings;
36
48
  this.values = values;
37
49
  }
38
- HtmlSafeString.prototype.toString = function () {
39
- var _this = this;
40
- return this.values.reduce(function (acc, val, i) {
41
- return acc + escapeValue(val) + _this.strings[i + 1];
50
+ toString() {
51
+ return this.values.reduce((acc, val, i) => {
52
+ return acc + escapeValue(val) + this.strings[i + 1];
42
53
  }, this.strings[0]);
43
- };
44
- return HtmlSafeString;
45
- }());
46
- exports.HtmlSafeString = HtmlSafeString;
47
- function html(strings) {
48
- var values = [];
49
- for (var _i = 1; _i < arguments.length; _i++) {
50
- values[_i - 1] = arguments[_i];
51
54
  }
55
+ }
56
+ exports.HtmlSafeString = HtmlSafeString;
57
+ function html(strings, ...values) {
52
58
  return new HtmlSafeString(strings, values);
53
59
  }
54
60
  exports.html = html;
@@ -58,7 +64,7 @@ exports.html = html;
58
64
  * popover.
59
65
  */
60
66
  function escapeHtml(html) {
61
- return unsafeHtml(ejs_1["default"].escapeXML(html.toString()));
67
+ return unsafeHtml(escapeHtmlRaw(html.toString()));
62
68
  }
63
69
  exports.escapeHtml = escapeHtml;
64
70
  /**
@@ -72,21 +78,4 @@ function unsafeHtml(value) {
72
78
  return new HtmlSafeString([value], []);
73
79
  }
74
80
  exports.unsafeHtml = unsafeHtml;
75
- /**
76
- * This is a shim to allow for the use of EJS templates inside of HTML tagged
77
- * template literals.
78
- *
79
- * The resulting string is assumed to be appropriately escaped and will be used
80
- * verbatim in the resulting HTML.
81
- *
82
- * @param filename The name of the file from which relative includes should be resolved.
83
- * @param template The raw EJS template string.
84
- * @param data Any data to be made available to the template.
85
- * @returns The rendered EJS.
86
- */
87
- function renderEjs(filename, template, data) {
88
- if (data === void 0) { data = {}; }
89
- return unsafeHtml(ejs_1["default"].render(template, data, { views: [path_1["default"].dirname(filename)] }));
90
- }
91
- exports.renderEjs = renderEjs;
92
81
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAsB;AACtB,8CAAwB;AAExB,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,KAAK,YAAY,cAAc,EAAE;QACnC,mBAAmB;QACnB,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;KACzB;SAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,UAAC,GAAG,IAAK,OAAA,WAAW,CAAC,GAAG,CAAC,EAAhB,CAAgB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;KACtD;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACjE,OAAO,gBAAG,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;KACrC;SAAM,IAAI,KAAK,IAAI,IAAI,EAAE;QACxB,sCAAsC;QACtC,OAAO,EAAE,CAAC;KACX;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACpC,MAAM,IAAI,KAAK,CAAC,iDAA0C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAE,CAAC,CAAC;KACpF;SAAM;QACL,gDAAgD;QAChD,OAAO,EAAE,CAAC;KACX;AACH,CAAC;AAED,8DAA8D;AAC9D;IAIE,wBAAY,OAA8B,EAAE,MAAiB;QAC3D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,iCAAQ,GAAR;QAAA,iBAIC;QAHC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAS,UAAC,GAAG,EAAE,GAAG,EAAE,CAAC;YAC5C,OAAO,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,KAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtD,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;IACH,qBAAC;AAAD,CAAC,AAdD,IAcC;AAdY,wCAAc;AAgB3B,SAAgB,IAAI,CAAC,OAA6B;IAAE,gBAAgB;SAAhB,UAAgB,EAAhB,qBAAgB,EAAhB,IAAgB;QAAhB,+BAAgB;;IAClE,OAAO,IAAI,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC7C,CAAC;AAFD,oBAEC;AAED;;;;GAIG;AACH,SAAgB,UAAU,CAAC,IAAoB;IAC7C,OAAO,UAAU,CAAC,gBAAG,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AAFD,gCAEC;AAED;;;;;;GAMG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,cAAc,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAFD,gCAEC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,SAAS,CAAC,QAAgB,EAAE,QAAgB,EAAE,IAAc;IAAd,qBAAA,EAAA,SAAc;IAC1E,OAAO,UAAU,CAAC,gBAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,iBAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACrF,CAAC;AAFD,8BAEC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,MAAM,iBAAiB,GAAG;IACxB,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;CACb,CAAC;AACF,IAAI,UAAU,GAAG,UAAU,CAAC;AAE5B,SAAS,eAAe,CAAC,CAAS;IAChC,OAAO,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;AACtF,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,KAAK,YAAY,cAAc,EAAE;QACnC,mBAAmB;QACnB,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;KACzB;SAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;KACtD;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACjE,OAAO,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;KACrC;SAAM,IAAI,KAAK,IAAI,IAAI,EAAE;QACxB,sCAAsC;QACtC,OAAO,EAAE,CAAC;KACX;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACpC,MAAM,IAAI,KAAK,CAAC,0CAA0C,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;KACpF;SAAM;QACL,gDAAgD;QAChD,OAAO,EAAE,CAAC;KACX;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAa,cAAc;IAIzB,YAAY,OAA8B,EAAE,MAAiB;QAC3D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAS,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE;YAChD,OAAO,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtD,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,CAAC;CACF;AAdD,wCAcC;AAID,SAAgB,IAAI,CAAC,OAA6B,EAAE,GAAG,MAAmB;IACxE,OAAO,IAAI,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC7C,CAAC;AAFD,oBAEC;AAED;;;;GAIG;AACH,SAAgB,UAAU,CAAC,IAAoB;IAC7C,OAAO,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AAFD,gCAEC;AAED;;;;;;GAMG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,cAAc,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAFD,gCAEC"}
@@ -1,55 +1,44 @@
1
1
  "use strict";
2
- var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
3
- if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
4
- return cooked;
5
- };
6
- exports.__esModule = true;
7
- var chai_1 = require("chai");
8
- var index_1 = require("./index");
9
- describe('html', function () {
10
- it('escapes string value', function () {
11
- chai_1.assert.equal((0, index_1.html)(templateObject_1 || (templateObject_1 = __makeTemplateObject(["<p>", "</p>"], ["<p>", "</p>"])), '<script>').toString(), '<p>&lt;script&gt;</p>');
12
- });
13
- it('interpolates multiple values', function () {
14
- chai_1.assert.equal((0, index_1.html)(templateObject_2 || (templateObject_2 = __makeTemplateObject(["<p>", " and ", "</p>"], ["<p>", " and ", "</p>"])), 'cats', 'dogs').toString(), '<p>cats and dogs</p>');
15
- });
16
- it('escapes values when rendering array', function () {
17
- var arr = ['cats>', '<dogs'];
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const index_1 = require("./index");
5
+ describe('html', () => {
6
+ it('escapes string value', () => {
7
+ chai_1.assert.equal((0, index_1.html) `<p>${'<script>'}</p>`.toString(), '<p>&lt;script&gt;</p>');
8
+ });
9
+ it('interpolates multiple values', () => {
10
+ chai_1.assert.equal((0, index_1.html) `<p>${'cats'} and ${'dogs'}</p>`.toString(), '<p>cats and dogs</p>');
11
+ });
12
+ it('escapes values when rendering array', () => {
13
+ const arr = ['cats>', '<dogs'];
18
14
  chai_1.assert.equal(
19
15
  // prettier-ignore
20
- (0, index_1.html)(templateObject_3 || (templateObject_3 = __makeTemplateObject(["<ul>", "</ul>"], ["<ul>", "</ul>"])), arr).toString(), '<ul>cats&gt;&lt;dogs</ul>');
16
+ (0, index_1.html) `<ul>${arr}</ul>`.toString(), '<ul>cats&gt;&lt;dogs</ul>');
21
17
  });
22
- it('does not double-escape values when rendering array', function () {
23
- var arr = ['cats', 'dogs'];
18
+ it('does not double-escape values when rendering array', () => {
19
+ const arr = ['cats', 'dogs'];
24
20
  chai_1.assert.equal(
25
21
  // prettier-ignore
26
- (0, index_1.html)(templateObject_5 || (templateObject_5 = __makeTemplateObject(["<ul>", "</ul>"], ["<ul>", "</ul>"])), arr.map(function (e) { return (0, index_1.html)(templateObject_4 || (templateObject_4 = __makeTemplateObject(["<li>", "</li>"], ["<li>", "</li>"])), e); })).toString(), '<ul><li>cats</li><li>dogs</li></ul>');
22
+ (0, index_1.html) `<ul>${arr.map((e) => (0, index_1.html) `<li>${e}</li>`)}</ul>`.toString(), '<ul><li>cats</li><li>dogs</li></ul>');
27
23
  });
28
- it('errors when interpolating object', function () {
29
- chai_1.assert.throws(function () { return (0, index_1.html)(templateObject_6 || (templateObject_6 = __makeTemplateObject(["<p>", "</p>"], ["<p>", "</p>"])), { foo: 'bar' }).toString(); }, 'Cannot interpolate object in template');
24
+ it('errors when interpolating object', () => {
25
+ chai_1.assert.throws(
26
+ // @ts-expect-error
27
+ () => (0, index_1.html) `<p>${{ foo: 'bar' }}</p>`.toString(), 'Cannot interpolate object in template');
30
28
  });
31
- it('omits boolean values from template', function () {
32
- chai_1.assert.equal((0, index_1.html)(templateObject_7 || (templateObject_7 = __makeTemplateObject(["<p>", "", "</p>"], ["<p>", "", "</p>"])), true, false).toString(), '<p></p>');
29
+ it('omits boolean values from template', () => {
30
+ chai_1.assert.equal((0, index_1.html) `<p>${true}${false}</p>`.toString(), '<p></p>');
33
31
  });
34
- it('omits nullish values from template', function () {
35
- chai_1.assert.equal((0, index_1.html)(templateObject_8 || (templateObject_8 = __makeTemplateObject(["<p>", "", "</p>"], ["<p>", "", "</p>"])), null, undefined).toString(), '<p></p>');
32
+ it('omits nullish values from template', () => {
33
+ chai_1.assert.equal((0, index_1.html) `<p>${null}${undefined}</p>`.toString(), '<p></p>');
36
34
  });
37
35
  });
38
- describe('escapeHtml', function () {
39
- it('escapes rendered HTML', function () {
40
- chai_1.assert.equal((0, index_1.escapeHtml)((0, index_1.html)(templateObject_9 || (templateObject_9 = __makeTemplateObject(["<p>Hello</p>"], ["<p>Hello</p>"])))).toString(), '&lt;p&gt;Hello&lt;/p&gt;');
36
+ describe('escapeHtml', () => {
37
+ it('escapes rendered HTML', () => {
38
+ chai_1.assert.equal((0, index_1.escapeHtml)((0, index_1.html) `<p>Hello</p>`).toString(), '&lt;p&gt;Hello&lt;/p&gt;');
41
39
  });
42
- it('works when nested inside html tag', function () {
43
- chai_1.assert.equal((0, index_1.html)(templateObject_11 || (templateObject_11 = __makeTemplateObject(["a", "b"], ["a", "b"])), (0, index_1.escapeHtml)((0, index_1.html)(templateObject_10 || (templateObject_10 = __makeTemplateObject(["<p></p>"], ["<p></p>"]))))).toString(), 'a&lt;p&gt;&lt;/p&gt;b');
40
+ it('works when nested inside html tag', () => {
41
+ chai_1.assert.equal((0, index_1.html) `a${(0, index_1.escapeHtml)((0, index_1.html) `<p></p>`)}b`.toString(), 'a&lt;p&gt;&lt;/p&gt;b');
44
42
  });
45
43
  });
46
- describe('renderEjs', function () {
47
- it('renders EJS template without data', function () {
48
- chai_1.assert.equal((0, index_1.renderEjs)(__filename, '<p>Hello</p>', {}).toString(), '<p>Hello</p>');
49
- });
50
- it('renders EJS template with data', function () {
51
- chai_1.assert.equal((0, index_1.renderEjs)(__filename, '<p>Hello <%= name %></p>', { name: 'Divya' }).toString(), '<p>Hello Divya</p>');
52
- });
53
- });
54
- var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5, templateObject_6, templateObject_7, templateObject_8, templateObject_9, templateObject_10, templateObject_11;
55
44
  //# sourceMappingURL=index.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":";;AAAA,+BAA8B;AAE9B,mCAA2C;AAE3C,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;IACpB,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,aAAM,CAAC,KAAK,CAAC,IAAA,YAAI,EAAA,MAAM,UAAU,MAAM,CAAC,QAAQ,EAAE,EAAE,uBAAuB,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,aAAM,CAAC,KAAK,CAAC,IAAA,YAAI,EAAA,MAAM,MAAM,QAAQ,MAAM,MAAM,CAAC,QAAQ,EAAE,EAAE,sBAAsB,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/B,aAAM,CAAC,KAAK;QACV,kBAAkB;QAClB,IAAA,YAAI,EAAA,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,EAChC,2BAA2B,CAC5B,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7B,aAAM,CAAC,KAAK;QACV,kBAAkB;QAClB,IAAA,YAAI,EAAA,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,YAAI,EAAA,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAChE,qCAAqC,CACtC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,aAAM,CAAC,MAAM;QACX,mBAAmB;QACnB,GAAG,EAAE,CAAC,IAAA,YAAI,EAAA,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,EAC/C,uCAAuC,CACxC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,aAAM,CAAC,KAAK,CAAC,IAAA,YAAI,EAAA,MAAM,IAAI,GAAG,KAAK,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,aAAM,CAAC,KAAK,CAAC,IAAA,YAAI,EAAA,MAAM,IAAI,GAAG,SAAS,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,aAAM,CAAC,KAAK,CAAC,IAAA,kBAAU,EAAC,IAAA,YAAI,EAAA,cAAc,CAAC,CAAC,QAAQ,EAAE,EAAE,0BAA0B,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,aAAM,CAAC,KAAK,CAAC,IAAA,YAAI,EAAA,IAAI,IAAA,kBAAU,EAAC,IAAA,YAAI,EAAA,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,uBAAuB,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,19 +1,16 @@
1
1
  {
2
2
  "name": "@prairielearn/html",
3
- "version": "1.0.1",
3
+ "version": "2.1.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "tsc",
7
7
  "dev": "tsc --watch --preserveWatchOutput",
8
- "test": "mocha --require ts-node/register src/index.test.ts"
9
- },
10
- "dependencies": {
11
- "ejs": "^3.1.6"
8
+ "test": "mocha --no-config --require ts-node/register src/index.test.ts"
12
9
  },
13
10
  "devDependencies": {
14
- "@types/ejs": "^3.1.0",
15
- "mocha": "^9.2.0",
16
- "ts-node": "^10.5.0",
17
- "typescript": "^4.5.5"
11
+ "@prairielearn/tsconfig": "*",
12
+ "mocha": "^9.2.2",
13
+ "ts-node": "^10.7.0",
14
+ "typescript": "^4.6.3"
18
15
  }
19
16
  }
package/src/index.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { assert } from 'chai';
2
2
 
3
- import { escapeHtml, html, renderEjs } from './index';
3
+ import { escapeHtml, html } from './index';
4
4
 
5
5
  describe('html', () => {
6
6
  it('escapes string value', () => {
@@ -31,6 +31,7 @@ describe('html', () => {
31
31
 
32
32
  it('errors when interpolating object', () => {
33
33
  assert.throws(
34
+ // @ts-expect-error
34
35
  () => html`<p>${{ foo: 'bar' }}</p>`.toString(),
35
36
  'Cannot interpolate object in template'
36
37
  );
@@ -54,16 +55,3 @@ describe('escapeHtml', () => {
54
55
  assert.equal(html`a${escapeHtml(html`<p></p>`)}b`.toString(), 'a&lt;p&gt;&lt;/p&gt;b');
55
56
  });
56
57
  });
57
-
58
- describe('renderEjs', () => {
59
- it('renders EJS template without data', () => {
60
- assert.equal(renderEjs(__filename, '<p>Hello</p>', {}).toString(), '<p>Hello</p>');
61
- });
62
-
63
- it('renders EJS template with data', () => {
64
- assert.equal(
65
- renderEjs(__filename, '<p>Hello <%= name %></p>', { name: 'Divya' }).toString(),
66
- '<p>Hello Divya</p>'
67
- );
68
- });
69
- });
package/src/index.ts CHANGED
@@ -1,5 +1,22 @@
1
- import ejs from 'ejs';
2
- import path from 'path';
1
+ const ENCODE_HTML_RULES = {
2
+ '&': '&amp;',
3
+ '<': '&lt;',
4
+ '>': '&gt;',
5
+ '"': '&#34;',
6
+ "'": '&#39;',
7
+ };
8
+ var MATCH_HTML = /[&<>'"]/g;
9
+
10
+ function encodeCharacter(c: string) {
11
+ return ENCODE_HTML_RULES[c] || c;
12
+ }
13
+
14
+ /**
15
+ * Based on the `escapeXML` function from the `ejs` library.
16
+ */
17
+ function escapeHtmlRaw(value: string): string {
18
+ return value == undefined ? '' : String(value).replace(MATCH_HTML, encodeCharacter);
19
+ }
3
20
 
4
21
  function escapeValue(value: unknown): string {
5
22
  if (value instanceof HtmlSafeString) {
@@ -8,7 +25,7 @@ function escapeValue(value: unknown): string {
8
25
  } else if (Array.isArray(value)) {
9
26
  return value.map((val) => escapeValue(val)).join('');
10
27
  } else if (typeof value === 'string' || typeof value === 'number') {
11
- return ejs.escapeXML(String(value));
28
+ return escapeHtmlRaw(String(value));
12
29
  } else if (value == null) {
13
30
  // undefined or null -- render nothing
14
31
  return '';
@@ -37,7 +54,9 @@ export class HtmlSafeString {
37
54
  }
38
55
  }
39
56
 
40
- export function html(strings: TemplateStringsArray, ...values: any[]): HtmlSafeString {
57
+ export type HtmlValue = string | number | boolean | HtmlSafeString | undefined | null | HtmlValue[];
58
+
59
+ export function html(strings: TemplateStringsArray, ...values: HtmlValue[]): HtmlSafeString {
41
60
  return new HtmlSafeString(strings, values);
42
61
  }
43
62
 
@@ -47,7 +66,7 @@ export function html(strings: TemplateStringsArray, ...values: any[]): HtmlSafeS
47
66
  * popover.
48
67
  */
49
68
  export function escapeHtml(html: HtmlSafeString): HtmlSafeString {
50
- return unsafeHtml(ejs.escapeXML(html.toString()));
69
+ return unsafeHtml(escapeHtmlRaw(html.toString()));
51
70
  }
52
71
 
53
72
  /**
@@ -60,19 +79,3 @@ export function escapeHtml(html: HtmlSafeString): HtmlSafeString {
60
79
  export function unsafeHtml(value: string): HtmlSafeString {
61
80
  return new HtmlSafeString([value], []);
62
81
  }
63
-
64
- /**
65
- * This is a shim to allow for the use of EJS templates inside of HTML tagged
66
- * template literals.
67
- *
68
- * The resulting string is assumed to be appropriately escaped and will be used
69
- * verbatim in the resulting HTML.
70
- *
71
- * @param filename The name of the file from which relative includes should be resolved.
72
- * @param template The raw EJS template string.
73
- * @param data Any data to be made available to the template.
74
- * @returns The rendered EJS.
75
- */
76
- export function renderEjs(filename: string, template: string, data: any = {}): HtmlSafeString {
77
- return unsafeHtml(ejs.render(template, data, { views: [path.dirname(filename)] }));
78
- }
package/tsconfig.json CHANGED
@@ -1,10 +1,7 @@
1
1
  {
2
+ "extends": "@prairielearn/tsconfig",
2
3
  "compilerOptions": {
3
- "allowJs": true,
4
- "declaration": true,
5
- "esModuleInterop": true,
6
4
  "outDir": "./dist",
7
- "rootDir": "src/",
8
- "sourceMap": true
5
+ "rootDir": "./src",
9
6
  }
10
7
  }