@prairielearn/browser-utils 1.0.1

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.
@@ -0,0 +1,10 @@
1
+ CLI Building entry: src/index.ts
2
+ CLI Using tsconfig: tsconfig.json
3
+ CLI tsup v6.7.0
4
+ CLI Target: es2021
5
+ CJS Build start
6
+ ESM Build start
7
+ ESM dist/index.mjs 1.47 KB
8
+ ESM ⚡️ Build success in 79ms
9
+ CJS dist/index.js 2.68 KB
10
+ CJS ⚡️ Build success in 80ms
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # @prairielearn/browser-utils
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [2c5504f1f]
8
+ - @prairielearn/html@3.0.2
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # `@prairielearn/browser-utils`
2
+
3
+ Helpful utilities for writing client-side vanilla JavaScript.
4
+
5
+ ## Usage
6
+
7
+ ### `onDocumentReady`
8
+
9
+ Runs the provided function once the document is ready.
10
+
11
+ ```ts
12
+ import { onDocumentReady } from '@prairielearn/browser-utils';
13
+
14
+ onDocumentReady(() => {
15
+ console.log('Document is ready!');
16
+ });
17
+ ```
18
+
19
+ To be precise, if `document.readyState` is `interactive` or `complete`, the function is run immediately. Otherwise, it will be run once the `DOMContentLoaded` event is fired.
20
+
21
+ ### `parseHTML` and `parseHTMLElement`
22
+
23
+ These functions return a `DocumentFragment` and `Element` from the provided HTML, respectively. The HTML can be an `HtmlSafeString` from `@prairielearn/html` or a plain string.
24
+
25
+ ```ts
26
+ import { parseHTML, parseHTMLElement } from '@prairielearn/browser-utils';
27
+
28
+ const elements = parseHTML(
29
+ document,
30
+ html`
31
+ <div>Hello, world</div>
32
+ <div>Goodbye, world</div>
33
+ `
34
+ );
35
+ const div = parseHTMLElement(document, html`<div>Hello, world</div>`);
36
+ ```
37
+
38
+ ### `EncodedData` and `decodeData`
39
+
40
+ These functions can be used to encode some state on the server and retrieve it on the client. For example, one could encode a list of courses on the server:
41
+
42
+ ```ts
43
+ import { EncodedData } from '@prairielearn/browser-utils';
44
+
45
+ app.get('/', (req, res) => {
46
+ const courses = ['CS 101', 'PHYS 512'];
47
+ res.send(`<html><body>${EncodedData(courses, 'courses-data')}</body></html>`);
48
+ });
49
+ ```
50
+
51
+ On the client, they can be retrieved with `decodeData`:
52
+
53
+ ```ts
54
+ import { onDocumentReady, decodeData } from '@prairielearn/browser-utils';
55
+
56
+ onDocumentReady(() => {
57
+ const data = decodeData<string[]>('courses-data');
58
+ console.log(data);
59
+ });
60
+ ```
61
+
62
+ ## Development
63
+
64
+ Unlike most other `@prairielearn` packages, this one is built with [`tsup`](https://tsup.egoist.dev/), which is used to generate both CJS and ESM output. The latter is important for ensuring that tree-shaking works correctly when building client bundles, which is important for minimizing bundle sizes.
@@ -0,0 +1,16 @@
1
+ import { HtmlSafeString } from '@prairielearn/html';
2
+ /**
3
+ * Use this function as an HTML component encode data that will be passed to the client.
4
+ *
5
+ * @param data The data to encode.
6
+ * @param elementId The element ID to use for the encoded data.
7
+ *
8
+ */
9
+ export declare function EncodedData<T = unknown>(data: T, elementId: string): HtmlSafeString;
10
+ /**
11
+ * Decode data that was passed to the client from in HTML component using EncodeData().
12
+ *
13
+ * @param elementId The element ID that stores the encoded data, from from EncodedData().
14
+ * @returns The decoded data.
15
+ */
16
+ export declare function decodeData<T = any>(elementId: string): T;
@@ -0,0 +1,3 @@
1
+ export { onDocumentReady } from './on-document-ready';
2
+ export { parseHTML, parseHTMLElement } from './parse-html';
3
+ export { EncodedData, decodeData } from './encode-data';
package/dist/index.js ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ EncodedData: () => EncodedData,
24
+ decodeData: () => decodeData,
25
+ onDocumentReady: () => onDocumentReady,
26
+ parseHTML: () => parseHTML,
27
+ parseHTMLElement: () => parseHTMLElement
28
+ });
29
+ module.exports = __toCommonJS(src_exports);
30
+
31
+ // src/on-document-ready.ts
32
+ function onDocumentReady(fn) {
33
+ if (document.readyState === "interactive" || document.readyState === "complete") {
34
+ fn();
35
+ } else {
36
+ document.addEventListener("DOMContentLoaded", () => {
37
+ fn();
38
+ });
39
+ }
40
+ }
41
+
42
+ // src/parse-html.ts
43
+ function parseHTML(document2, html2) {
44
+ if (typeof html2 !== "string")
45
+ html2 = html2.toString();
46
+ const template = document2.createElement("template");
47
+ template.innerHTML = html2;
48
+ return document2.importNode(template.content, true);
49
+ }
50
+ function parseHTMLElement(document2, html2) {
51
+ const documentFragment = parseHTML(document2, html2);
52
+ if (documentFragment.childElementCount !== 1) {
53
+ throw new Error("Expected HTML to contain exactly one element");
54
+ }
55
+ return documentFragment.firstElementChild;
56
+ }
57
+
58
+ // src/encode-data.ts
59
+ var import_js_base64 = require("js-base64");
60
+ var import_html = require("@prairielearn/html");
61
+ function EncodedData(data, elementId) {
62
+ const encodedData = (0, import_html.unsafeHtml)((0, import_js_base64.encode)(JSON.stringify(data)));
63
+ return import_html.html`<script id="${elementId}" type="application/base64">
64
+ ${encodedData}
65
+ </script>`;
66
+ }
67
+ function decodeData(elementId) {
68
+ const base64Data = document.getElementById(elementId)?.textContent;
69
+ if (base64Data == null) {
70
+ throw new Error(`No data found in element with ID "${elementId}"`);
71
+ }
72
+ const jsonData = (0, import_js_base64.decode)(base64Data);
73
+ const data = JSON.parse(jsonData);
74
+ return data;
75
+ }
76
+ // Annotate the CommonJS export names for ESM import in node:
77
+ 0 && (module.exports = {
78
+ EncodedData,
79
+ decodeData,
80
+ onDocumentReady,
81
+ parseHTML,
82
+ parseHTMLElement
83
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,52 @@
1
+ // src/on-document-ready.ts
2
+ function onDocumentReady(fn) {
3
+ if (document.readyState === "interactive" || document.readyState === "complete") {
4
+ fn();
5
+ } else {
6
+ document.addEventListener("DOMContentLoaded", () => {
7
+ fn();
8
+ });
9
+ }
10
+ }
11
+
12
+ // src/parse-html.ts
13
+ function parseHTML(document2, html2) {
14
+ if (typeof html2 !== "string")
15
+ html2 = html2.toString();
16
+ const template = document2.createElement("template");
17
+ template.innerHTML = html2;
18
+ return document2.importNode(template.content, true);
19
+ }
20
+ function parseHTMLElement(document2, html2) {
21
+ const documentFragment = parseHTML(document2, html2);
22
+ if (documentFragment.childElementCount !== 1) {
23
+ throw new Error("Expected HTML to contain exactly one element");
24
+ }
25
+ return documentFragment.firstElementChild;
26
+ }
27
+
28
+ // src/encode-data.ts
29
+ import { encode, decode } from "js-base64";
30
+ import { html, unsafeHtml } from "@prairielearn/html";
31
+ function EncodedData(data, elementId) {
32
+ const encodedData = unsafeHtml(encode(JSON.stringify(data)));
33
+ return html`<script id="${elementId}" type="application/base64">
34
+ ${encodedData}
35
+ </script>`;
36
+ }
37
+ function decodeData(elementId) {
38
+ const base64Data = document.getElementById(elementId)?.textContent;
39
+ if (base64Data == null) {
40
+ throw new Error(`No data found in element with ID "${elementId}"`);
41
+ }
42
+ const jsonData = decode(base64Data);
43
+ const data = JSON.parse(jsonData);
44
+ return data;
45
+ }
46
+ export {
47
+ EncodedData,
48
+ decodeData,
49
+ onDocumentReady,
50
+ parseHTML,
51
+ parseHTMLElement
52
+ };
@@ -0,0 +1 @@
1
+ export declare function onDocumentReady(fn: () => void): void;
@@ -0,0 +1,8 @@
1
+ import type { HtmlSafeString } from '@prairielearn/html';
2
+ export declare function parseHTML(document: Document, html: string | HtmlSafeString): DocumentFragment;
3
+ /**
4
+ * Like {@link parseHTML}, but returns an {@link Element} instead of a
5
+ * {@link DocumentFragment}. If the HTML being parsed does not contain
6
+ * exactly one element, an error is thrown.
7
+ */
8
+ export declare function parseHTMLElement(document: Document, html: string | HtmlSafeString): Element;
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@prairielearn/browser-utils",
3
+ "version": "1.0.1",
4
+ "main": "dist/index.js",
5
+ "sideEffects": false,
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/PrairieLearn/PrairieLearn.git",
9
+ "directory": "packages/browser-utils"
10
+ },
11
+ "exports": {
12
+ "./package.json": "./package.json",
13
+ ".": {
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsc && tsup --format cjs,esm src/index.ts",
20
+ "dev": "tsup --watch src/index.ts"
21
+ },
22
+ "dependencies": {
23
+ "@prairielearn/html": "^3.0.2",
24
+ "js-base64": "^3.7.5"
25
+ },
26
+ "devDependencies": {
27
+ "@prairielearn/tsconfig": "^0.0.0",
28
+ "ts-node": "^10.9.1",
29
+ "tsup": "^6.7.0",
30
+ "typescript": "^5.1.3"
31
+ }
32
+ }
@@ -0,0 +1,32 @@
1
+ import { encode, decode } from 'js-base64';
2
+ import { html, unsafeHtml, HtmlSafeString } from '@prairielearn/html';
3
+
4
+ /**
5
+ * Use this function as an HTML component encode data that will be passed to the client.
6
+ *
7
+ * @param data The data to encode.
8
+ * @param elementId The element ID to use for the encoded data.
9
+ *
10
+ */
11
+ export function EncodedData<T = unknown>(data: T, elementId: string): HtmlSafeString {
12
+ const encodedData = unsafeHtml(encode(JSON.stringify(data)));
13
+ return html`<script id="${elementId}" type="application/base64">
14
+ ${encodedData}
15
+ </script>`;
16
+ }
17
+
18
+ /**
19
+ * Decode data that was passed to the client from in HTML component using EncodeData().
20
+ *
21
+ * @param elementId The element ID that stores the encoded data, from from EncodedData().
22
+ * @returns The decoded data.
23
+ */
24
+ export function decodeData<T = any>(elementId: string): T {
25
+ const base64Data = document.getElementById(elementId)?.textContent;
26
+ if (base64Data == null) {
27
+ throw new Error(`No data found in element with ID "${elementId}"`);
28
+ }
29
+ const jsonData = decode(base64Data);
30
+ const data = JSON.parse(jsonData);
31
+ return data;
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { onDocumentReady } from './on-document-ready';
2
+ export { parseHTML, parseHTMLElement } from './parse-html';
3
+ export { EncodedData, decodeData } from './encode-data';
@@ -0,0 +1,9 @@
1
+ export function onDocumentReady(fn: () => void): void {
2
+ if (document.readyState === 'interactive' || document.readyState === 'complete') {
3
+ fn();
4
+ } else {
5
+ document.addEventListener('DOMContentLoaded', () => {
6
+ fn();
7
+ });
8
+ }
9
+ }
@@ -0,0 +1,21 @@
1
+ import type { HtmlSafeString } from '@prairielearn/html';
2
+
3
+ export function parseHTML(document: Document, html: string | HtmlSafeString): DocumentFragment {
4
+ if (typeof html !== 'string') html = html.toString();
5
+ const template = document.createElement('template');
6
+ template.innerHTML = html;
7
+ return document.importNode(template.content, true);
8
+ }
9
+
10
+ /**
11
+ * Like {@link parseHTML}, but returns an {@link Element} instead of a
12
+ * {@link DocumentFragment}. If the HTML being parsed does not contain
13
+ * exactly one element, an error is thrown.
14
+ */
15
+ export function parseHTMLElement(document: Document, html: string | HtmlSafeString): Element {
16
+ const documentFragment = parseHTML(document, html);
17
+ if (documentFragment.childElementCount !== 1) {
18
+ throw new Error('Expected HTML to contain exactly one element');
19
+ }
20
+ return documentFragment.firstElementChild as Element;
21
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@prairielearn/tsconfig/tsconfig.package.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ // Unlike other PrairieLearn packages, we use another tool to actually
7
+ // emit transpiled JavaScript. See note in `README.md`.
8
+ "emitDeclarationOnly": true,
9
+ },
10
+ }