summaryrefslogtreecommitdiffstats
path: root/typescript
diff options
context:
space:
mode:
Diffstat (limited to 'typescript')
-rw-r--r--typescript/.dockerignore24
-rw-r--r--typescript/.gitignore24
-rw-r--r--typescript/package.json7
-rw-r--r--typescript/src/AjaxResponse.ts11
-rw-r--r--typescript/src/AjaxSettings.ts30
-rw-r--r--typescript/src/AppID.ts5
-rw-r--r--typescript/src/EntrypointParameter.ts11
-rw-r--r--typescript/src/Pod.ts12
-rw-r--r--typescript/src/State.ts10
-rw-r--r--typescript/src/SubPod.ts11
-rw-r--r--typescript/src/entrypoint.ts33
-rw-r--r--typescript/src/getAjaxResponse.ts16
-rw-r--r--typescript/src/getAjaxSettings.ts23
-rw-r--r--typescript/src/getAppID.ts67
-rw-r--r--typescript/src/getNewHTML.ts199
-rw-r--r--typescript/src/listenToChangeEvent.ts52
-rw-r--r--typescript/src/onload.ts94
-rw-r--r--typescript/src/placeholder.ts13
-rw-r--r--typescript/src/podsClassName.ts3
-rw-r--r--typescript/src/reRenderComponent.ts70
-rw-r--r--typescript/src/setContentEditable.ts34
-rw-r--r--typescript/src/typescriptExhaustive.ts46
-rw-r--r--typescript/src/typescriptNever.ts10
-rw-r--r--typescript/tsconfig.json34
24 files changed, 839 insertions, 0 deletions
diff --git a/typescript/.dockerignore b/typescript/.dockerignore
new file mode 100644
index 0000000..ecec047
--- /dev/null
+++ b/typescript/.dockerignore
@@ -0,0 +1,24 @@
+# Dependencies
+/node_modules
+
+# Production
+/build
+
+# Generated files
+.docusaurus
+.cache-loader
+
+# Misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+package-lock.json
+
+# SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/typescript/.gitignore b/typescript/.gitignore
new file mode 100644
index 0000000..ecec047
--- /dev/null
+++ b/typescript/.gitignore
@@ -0,0 +1,24 @@
+# Dependencies
+/node_modules
+
+# Production
+/build
+
+# Generated files
+.docusaurus
+.cache-loader
+
+# Misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+package-lock.json
+
+# SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/typescript/package.json b/typescript/package.json
new file mode 100644
index 0000000..36e9180
--- /dev/null
+++ b/typescript/package.json
@@ -0,0 +1,7 @@
+{
+ "devDependencies": {
+ "@types/dompurify": "^3.0.2",
+ "@types/jquery": "^3.5.19",
+ "typescript": "^5.2.2"
+ }
+}
diff --git a/typescript/src/AjaxResponse.ts b/typescript/src/AjaxResponse.ts
new file mode 100644
index 0000000..8b578a8
--- /dev/null
+++ b/typescript/src/AjaxResponse.ts
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import type Pod from "./Pod.js";
+
+export default AjaxResponse;
+
+type AjaxResponse = Readonly<{
+ queryresult?: {
+ pods?: Pod[];
+ };
+}>;
diff --git a/typescript/src/AjaxSettings.ts b/typescript/src/AjaxSettings.ts
new file mode 100644
index 0000000..b5b4cf4
--- /dev/null
+++ b/typescript/src/AjaxSettings.ts
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import AppID from "./AppID.js";
+import EntrypointParameter from "./EntrypointParameter.js";
+
+export default AjaxSettings;
+
+type AjaxSettings = Readonly<{
+ url: "https://api.wolframalpha.com/v2/query";
+ dataType: "jsonp";
+ traditional: true;
+ data: Readonly<
+ EntrypointParameter & {
+ appid: AppID;
+ output: "json";
+ reinterpret: true;
+ podtimeout: 30;
+ scantimeout: 30;
+ parsetimeout: 30;
+ totaltimeout: 30;
+ formattimeout: 30;
+ }
+ >;
+}>;
+
+// Wolfram|Alpha Full Results API Reference
+// https://products.wolframalpha.com/api/documentation
+
+// jQuery.ajax() | jQuery API Documentation
+// https://api.jquery.com/jQuery.ajax/
diff --git a/typescript/src/AppID.ts b/typescript/src/AppID.ts
new file mode 100644
index 0000000..af4edb2
--- /dev/null
+++ b/typescript/src/AppID.ts
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+export default AppID;
+
+type AppID = "H9V325-HTALUWHKGK" | "AKJTJT-LR5LL8WTG6" | "LKY83U-XW6ATU9URU";
diff --git a/typescript/src/EntrypointParameter.ts b/typescript/src/EntrypointParameter.ts
new file mode 100644
index 0000000..f78f1fc
--- /dev/null
+++ b/typescript/src/EntrypointParameter.ts
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+export default EntrypointParameter;
+
+type EntrypointParameter = Readonly<{
+ input: string;
+ i2d?: true;
+ podstate: Readonly<
+ ["Step-by-step solution", "Step-by-step", "Show all steps", string?]
+ >;
+}>;
diff --git a/typescript/src/Pod.ts b/typescript/src/Pod.ts
new file mode 100644
index 0000000..169ff5a
--- /dev/null
+++ b/typescript/src/Pod.ts
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import type State from "./State.js";
+import type SubPod from "./SubPod.js";
+
+export default Pod;
+
+type Pod = Readonly<{
+ title?: string;
+ states?: State[];
+ subpods?: SubPod[];
+}>;
diff --git a/typescript/src/State.ts b/typescript/src/State.ts
new file mode 100644
index 0000000..e3bb35e
--- /dev/null
+++ b/typescript/src/State.ts
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+export default State;
+
+type State = Readonly<{
+ value?: string;
+ states?: {
+ name?: string;
+ }[];
+}>;
diff --git a/typescript/src/SubPod.ts b/typescript/src/SubPod.ts
new file mode 100644
index 0000000..1e26a87
--- /dev/null
+++ b/typescript/src/SubPod.ts
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+export default SubPod;
+
+type SubPod = Readonly<{
+ img?: {
+ src?: string;
+ alt?: string;
+ };
+ plaintext?: string;
+}>;
diff --git a/typescript/src/entrypoint.ts b/typescript/src/entrypoint.ts
new file mode 100644
index 0000000..e939d92
--- /dev/null
+++ b/typescript/src/entrypoint.ts
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import type EntrypointParameter from "./EntrypointParameter.js";
+import getAjaxResponse from "./getAjaxResponse.js";
+import getAjaxSettings from "./getAjaxSettings.js";
+import getNewHTML from "./getNewHTML.js";
+import listenToChangeEvent from "./listenToChangeEvent.js";
+import placeholder from "./placeholder.js";
+import reRenderComponent from "./reRenderComponent.js";
+import setContentEditable from "./setContentEditable.js";
+import typescriptExhaustive from "./typescriptExhaustive.js";
+
+export default async (
+ EntrypointParameter: EntrypointParameter
+): Promise<void> => {
+ try {
+ Object.freeze(EntrypointParameter);
+ typescriptExhaustive(EntrypointParameter);
+
+ window.scroll(0, 0);
+ reRenderComponent(placeholder);
+
+ const AjaxSettings = getAjaxSettings(EntrypointParameter);
+ const AjaxResponse = await getAjaxResponse(AjaxSettings);
+ const newHTML = getNewHTML(AjaxSettings, AjaxResponse);
+ reRenderComponent(newHTML);
+
+ listenToChangeEvent(EntrypointParameter);
+ setContentEditable();
+ } catch (error) {
+ console.warn({ error });
+ }
+};
diff --git a/typescript/src/getAjaxResponse.ts b/typescript/src/getAjaxResponse.ts
new file mode 100644
index 0000000..1459114
--- /dev/null
+++ b/typescript/src/getAjaxResponse.ts
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import AjaxResponse from "./AjaxResponse.js";
+import type AjaxSettings from "./AjaxSettings.js";
+
+export default async (AjaxSettings: AjaxSettings): Promise<AjaxResponse> => {
+ try {
+ Object.freeze(AjaxSettings);
+
+ // npm i @types/jquery
+ return Object.freeze(await jQuery.ajax(AjaxSettings));
+ } catch (error) {
+ console.warn({ error });
+ return {};
+ }
+};
diff --git a/typescript/src/getAjaxSettings.ts b/typescript/src/getAjaxSettings.ts
new file mode 100644
index 0000000..5899637
--- /dev/null
+++ b/typescript/src/getAjaxSettings.ts
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import AjaxSettings from "./AjaxSettings.js";
+import EntrypointParameter from "./EntrypointParameter.js";
+import getAppID from "./getAppID.js";
+
+export default (EntrypointParameter: EntrypointParameter): AjaxSettings =>
+ Object.freeze({
+ url: "https://api.wolframalpha.com/v2/query",
+ dataType: "jsonp",
+ traditional: true,
+ data: Object.freeze({
+ ...EntrypointParameter,
+ appid: getAppID(),
+ output: "json",
+ reinterpret: true,
+ podtimeout: 30,
+ scantimeout: 30,
+ parsetimeout: 30,
+ totaltimeout: 30,
+ formattimeout: 30,
+ }),
+ });
diff --git a/typescript/src/getAppID.ts b/typescript/src/getAppID.ts
new file mode 100644
index 0000000..eed7e1b
--- /dev/null
+++ b/typescript/src/getAppID.ts
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import AppID from "./AppID.js";
+import typescriptNever from "./typescriptNever.js";
+
+/**
+ * To generate a new AppID, please follow these steps:
+ *
+ * 1. Open Tor Browser and go to:
+ * https://products.wolframalpha.com/api/
+ * 2. Click the orange "Get API Access" button.
+ * Tor Browser will redirect you to:
+ * https://account.wolfram.com/login/oauth2/sign-in
+ * 3. Click the red "Create one" hyperlink to create a new Wolfram ID.
+ * Tor Browser will redirect you to:
+ * https://account.wolfram.com/login/create
+ * 4. Fill out the form with random alphanumeric characters.
+ * 5. Click the red "Create Wolfram ID" button.
+ * Tor Browser will redirect you to:
+ * https://developer.wolframalpha.com/portal/myapps/index.html
+ * 6. Click the orange "Sign up to get your first AppID" button.
+ * 7. Fill out the form with random alphanumeric characters.
+ * 8. Click the orange "Sign up" button.
+ * 9. Click the orange "Get an AppID" button.
+ * 10. Fill out the form with random alphanumeric characters.
+ * 11. Click the orange "Get AppID" button.
+ */
+const appIDArray: Readonly<AppID[]> = Object.freeze([
+ "H9V325-HTALUWHKGK",
+ "AKJTJT-LR5LL8WTG6",
+ "LKY83U-XW6ATU9URU",
+]);
+
+console.assert(appIDArray.length > 0);
+
+appIDArray.forEach((appID) => {
+ console.assert(appID.length === 17);
+ console.assert(/[0-9A-Z]{6}-[0-9A-Z]{10}/.test(appID));
+});
+
+export default (): AppID => {
+ const random = appIDArray[getRandomInt() % appIDArray.length];
+
+ if (typeof random === "string") {
+ return random;
+ } else if (random === undefined) {
+ console.warn({ random });
+ } else {
+ typescriptNever(random);
+ }
+
+ return "H9V325-HTALUWHKGK";
+};
+
+const getRandomInt = (): number => {
+ const random = crypto.getRandomValues(new Uint32Array(1))[0];
+
+ if (typeof random === "number") {
+ return random;
+ } else if (random === undefined) {
+ console.warn({ random });
+ } else {
+ typescriptNever(random);
+ }
+
+ return 0;
+};
diff --git a/typescript/src/getNewHTML.ts b/typescript/src/getNewHTML.ts
new file mode 100644
index 0000000..f5d2070
--- /dev/null
+++ b/typescript/src/getNewHTML.ts
@@ -0,0 +1,199 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import type AjaxResponse from "./AjaxResponse.js";
+import type AjaxSettings from "./AjaxSettings.js";
+import type Pod from "./Pod.js";
+import PodsClassName from "./podsClassName.js";
+import type State from "./State.js";
+import type SubPod from "./SubPod.js";
+
+export default (
+ AjaxSettings: AjaxSettings,
+ AjaxResponse: AjaxResponse
+): string =>
+ `
+ <div class="${PodsClassName}">
+ <div>
+ <div>
+ <section>
+ ${
+ // Wolfram Alpha does have some limitations when it comes to processing certain types of input.
+ // When "input" is an empty string,
+ // the "pods" property will not be present in the "queryresult" object.
+ // To see an example of an empty input string,
+ // visit the following URL:
+ // https://www.wolframalpha.com/input?i=
+ Object.freeze(AjaxResponse) === undefined
+ ? (console.warn({ AjaxResponse }), "")
+ : AjaxResponse.queryresult === undefined
+ ? (console.warn({ AjaxResponse }), "")
+ : AjaxResponse.queryresult.pods === undefined
+ ? "" // console.warn
+ : AjaxResponse.queryresult.pods.map(buildPod).join("")
+ }
+ <section>
+ <div>
+ <h2>
+ Technical information
+ </h2>
+ </div>
+ <div> </div>
+ <div>
+ <div>
+ <div>
+ <details>
+ <div>
+ If you have programming knowledge, feel free to explore the technical information provided below:
+ </div>
+ <textarea name="technical-information">${
+ Object.freeze(AjaxSettings) === undefined
+ ? (console.warn({ AjaxSettings }), "")
+ : Object.freeze(AjaxResponse) === undefined
+ ? (console.warn({ AjaxResponse }), "")
+ : escapeHTML(tryStringify(AjaxSettings, AjaxResponse))
+ }</textarea>
+ </details>
+ </div>
+ </div>
+ </div>
+ <div> </div>
+ </section>
+ </section>
+ </div>
+ </div>
+ </div>
+ `;
+
+const buildPod = (pod: Pod): string =>
+ `
+ <section>
+ <div>
+ ${
+ pod.title === undefined
+ ? (console.warn({ pod }), "")
+ : `<h2>${escapeHTML(pod.title)}</h2>`
+ }
+ ${
+ // In most cases, pods are stateless and do not have specific states.
+ // As a result, the "states" property of "pod" is typically undefined.
+ pod.states === undefined
+ ? "" // console.warn
+ : pod.states.map(buildSelectElement).join("")
+ }
+ </div>
+ <div> </div>
+ ${
+ pod.subpods === undefined
+ ? (console.warn({ pod }), "")
+ : pod.subpods.map(buildSubpod).join("")
+ }
+ </section>
+ `;
+
+const buildSelectElement = (state: State): string =>
+ // Although it is possible to handle the scenario where the "states" property is undefined,
+ // implementing the necessary logic may result in a cluttered user interface.
+ // This challenge is primarily due to my limited expertise in front-end design.
+ // Consequently, the current implementation of the parsing logic is still insufficient in addressing the situation where the "states" property is undefined.
+ state.states === undefined
+ ? "" // console.warn
+ : `
+ <select name="pod-states">
+ ${
+ state.value === undefined
+ ? (console.warn({ state }), "")
+ : `
+ <option>${escapeHTML(state.value)}</option>
+ `
+ }
+ ${state.states.map(buildOption).join("")}
+ </select>
+ `;
+
+const buildOption = (state: { name?: string }): string =>
+ state.name === undefined
+ ? (console.warn({ state }), "")
+ : `
+ <option>${escapeHTML(state.name)}</option>
+ `;
+
+const buildSubpod = (subpod: SubPod): string =>
+ `
+ ${
+ subpod.img === undefined
+ ? (console.warn({ subpod }), "")
+ : `
+ <div>
+ <div>
+ <img
+ src="${escapeHTML(
+ subpod.img.src === undefined
+ ? (console.warn({ subpod }), "")
+ : subpod.img.src
+ )}"
+ alt="${escapeHTML(
+ subpod.img.alt === undefined
+ ? (console.warn({ subpod }), "")
+ : subpod.img.alt
+ )}"
+ >
+ </div>
+ </div>
+ `
+ }
+ ${
+ subpod.plaintext === undefined
+ ? (console.warn({ subpod }), "")
+ : `
+ <div style="font-family: monospace; overflow: auto;">
+ <div>
+ <div>
+ <details>
+ <summary style="direction: rtl;"></summary>
+ <div>
+ <pre>${escapeHTML(subpod.plaintext)}</pre>
+ </div>
+ <br />
+ </details>
+ </div>
+ </div>
+ </div>
+ `
+ }
+ `;
+
+const tryStringify = (
+ AjaxSettings: AjaxSettings,
+ AjaxResponse: AjaxResponse
+): string => {
+ try {
+ return JSON.stringify(
+ {
+ document,
+ AjaxSettings,
+ AjaxResponse,
+ },
+ null,
+ 4
+ );
+ } catch (error) {
+ console.warn({ error });
+ return error instanceof TypeError ? error.message + "\n" + error.stack : "";
+ // JSON.stringify() - JavaScript | MDN
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#exceptions
+ }
+};
+
+const escapeHTML = (unsafeHTML: string): string =>
+ unsafeHTML
+ .replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&#039;");
+
+// Can I escape HTML special chars in JavaScript? - Stack Overflow
+// https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript
+
+// test case:
+// https://www.wolframalpha.com/input?i=solve+%7By%27%28x%29+%3D+-2+y%2C+y%280%29%3D1%7D+from+0+to+10+using+r+k+f+algorithm
diff --git a/typescript/src/listenToChangeEvent.ts b/typescript/src/listenToChangeEvent.ts
new file mode 100644
index 0000000..7f091a9
--- /dev/null
+++ b/typescript/src/listenToChangeEvent.ts
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import type EntrypointParameter from "./EntrypointParameter.js";
+import entrypoint from "./entrypoint.js";
+import podsClassName from "./podsClassName.js";
+import typescriptNever from "./typescriptNever.js";
+
+export default (EntrypointParameter: EntrypointParameter): void => {
+ try {
+ Object.freeze(EntrypointParameter);
+
+ Array.from(
+ document.querySelectorAll(
+ `html > body > div#__next > div > main > main > div.${podsClassName} > div > div > section > section > div:is(:first-child) > select`
+ )
+ )
+ .filter((element: Element): boolean => {
+ if (element instanceof HTMLSelectElement) {
+ return true;
+ } else if (element instanceof Element) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+
+ return false;
+ })
+ .map((element: Element): void => {
+ element.addEventListener("change", async (event): Promise<void> => {
+ if (event.target instanceof HTMLSelectElement) {
+ await entrypoint({
+ ...EntrypointParameter,
+ podstate: [
+ "Step-by-step solution",
+ "Step-by-step",
+ "Show all steps",
+ event.target.value,
+ ],
+ });
+ } else if (event.target instanceof EventTarget) {
+ console.warn({ event });
+ } else if (event.target === null) {
+ console.warn({ event });
+ } else {
+ typescriptNever(event.target);
+ }
+ });
+ });
+ } catch (error) {
+ console.warn({ error });
+ }
+};
diff --git a/typescript/src/onload.ts b/typescript/src/onload.ts
new file mode 100644
index 0000000..6c864ad
--- /dev/null
+++ b/typescript/src/onload.ts
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import typescriptNever from "./typescriptNever.js";
+
+export default addEventListener(
+ "load",
+ (): void => (
+ setTimeout(
+ (): void =>
+ console.assert(
+ Array.from(
+ document.querySelectorAll(
+ // To select the form element, we use different selectors depending on the input mode.
+ // If the input mode is "natural language", the form is a direct child of the section element, so we use the selector "section > form".
+ // However, if the input mode is "math input", the form is a direct child of the div element, so we use the selector "div > form".
+ `
+ html > body > #__next > div > main > main > div > div > section > form > div > div > input,
+ html > body > #__next > div > main > main > div > div > div form > div > div > input
+ `
+ )
+ )
+ .filter((element: Element): boolean => {
+ if (element instanceof HTMLInputElement) {
+ return true;
+ } else if (element instanceof Element) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+
+ return false;
+ })
+ .map((element: Element): void => {
+ if (element instanceof HTMLElement) {
+ element.focus();
+ } else if (element instanceof Element) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+ }).length === 1
+ ),
+ 1000
+ ),
+ [
+ (): void =>
+ Array.from(
+ document.querySelectorAll(
+ // The positioning of the ul element is dynamically adjusted to ensure it adapts well to different viewport widths.
+ // To specifically target the ul element when the viewport width is larger, we use the selector "div:is(:first-child) > ul".
+ // Conversely, to target the ul element when the viewport width is smaller, we use the selector "div:is(:first-child) + ul".
+ `
+ html > body > #__next > div > main > main > div > div > div > section > section > div:is(:first-child) > ul > li,
+ html > body > #__next > div > main > main > div > div > div > section > section > div:is(:first-child) + ul > li
+ `
+ )
+ )
+ .filter((element: Element): boolean => {
+ if (element instanceof HTMLLIElement) {
+ return true;
+ } else if (element instanceof Element) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+
+ return false;
+ })
+ .forEach((element: Element): void => {
+ if (element instanceof HTMLElement) {
+ if (element.innerHTML.includes("Step-by-step")) {
+ element.style.display = "none";
+ } else {
+ }
+ } else if (element instanceof Element) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+ }),
+ (): void => {
+ document.title = document.title.replace(
+ "- Wolfram|Alpha",
+ "- Free Wolfram|Alpha Step-by-step Solution - Wolfree"
+ );
+ },
+ ].map(
+ (callback: () => void): void => (
+ setInterval(callback, 2000), addEventListener("click", callback)
+ )
+ ),
+ scroll(0, 0)
+ )
+);
diff --git a/typescript/src/placeholder.ts b/typescript/src/placeholder.ts
new file mode 100644
index 0000000..083226c
--- /dev/null
+++ b/typescript/src/placeholder.ts
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+export default `
+ <div class="wolfree-pods wolfree-placeholder">
+ <div>
+ <div>
+ <div><div></div></div>
+ <div><div></div></div>
+ <div><div></div></div>
+ </div>
+ </div>
+ </div>
+`;
diff --git a/typescript/src/podsClassName.ts b/typescript/src/podsClassName.ts
new file mode 100644
index 0000000..fea9f4c
--- /dev/null
+++ b/typescript/src/podsClassName.ts
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+export default "wolfree-pods";
diff --git a/typescript/src/reRenderComponent.ts b/typescript/src/reRenderComponent.ts
new file mode 100644
index 0000000..f73fd1a
--- /dev/null
+++ b/typescript/src/reRenderComponent.ts
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import podsClassName from "./podsClassName.js";
+import typescriptNever from "./typescriptNever.js";
+
+export default (newHTML: string): void => {
+ try {
+ console.assert(
+ Array.from(
+ document.querySelectorAll(
+ `html > body > #__next > div > main > main > div.${podsClassName}`
+ )
+ )
+ .filter((element: Element): boolean => {
+ if (element instanceof HTMLDivElement) {
+ return true;
+ } else if (element instanceof Element) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+
+ return false;
+ })
+ .map((element: Element): void => {
+ if (element instanceof Element) {
+ element.remove();
+ } else {
+ typescriptNever(element);
+ }
+ }).length <= 1
+ );
+
+ console.assert(
+ Array.from(
+ document.querySelectorAll(
+ "html > body > #__next > div > main > main > div:nth-of-type(1)"
+ )
+ )
+ .filter((element: Element): boolean => {
+ if (element instanceof HTMLDivElement) {
+ return true;
+ } else if (element instanceof Element) {
+ console.warn({ element });
+ } else if (element === null) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+
+ return false;
+ })
+ .map((element: Element): void => {
+ if (element instanceof Element) {
+ element.insertAdjacentHTML(
+ "afterend",
+ // npm i @types/dompurify
+ globalThis.DOMPurify.sanitize(newHTML)
+ );
+ } else if (element === null) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+ }).length === 1
+ );
+ } catch (error) {
+ console.warn({ error });
+ }
+};
diff --git a/typescript/src/setContentEditable.ts b/typescript/src/setContentEditable.ts
new file mode 100644
index 0000000..ebfe423
--- /dev/null
+++ b/typescript/src/setContentEditable.ts
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import podsClassName from "./podsClassName.js";
+import typescriptNever from "./typescriptNever.js";
+
+export default (): void => {
+ try {
+ Array.from(
+ document.querySelectorAll(
+ `html > body > div#__next > div > main > main > div.${podsClassName} > div > div > section > section > div > div > div > details > div`
+ )
+ )
+ .filter((element: Element): boolean => {
+ if (element instanceof HTMLDivElement) {
+ return true;
+ } else if (element instanceof Element) {
+ console.warn({ element });
+ } else {
+ typescriptNever(element);
+ }
+
+ return false;
+ })
+ .map((element: Element): void => {
+ if (element instanceof Element) {
+ element.setAttribute("contenteditable", "");
+ } else {
+ typescriptNever(element);
+ }
+ });
+ } catch (error) {
+ console.warn({ error });
+ }
+};
diff --git a/typescript/src/typescriptExhaustive.ts b/typescript/src/typescriptExhaustive.ts
new file mode 100644
index 0000000..656a419
--- /dev/null
+++ b/typescript/src/typescriptExhaustive.ts
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+import type EntrypointParameter from "./EntrypointParameter.js";
+import typescriptNever from "./typescriptNever.js";
+
+export default (EntrypointParameter: EntrypointParameter): void => {
+ Object.freeze(EntrypointParameter);
+
+ if (typeof EntrypointParameter.input !== "string") {
+ typescriptNever(EntrypointParameter.input);
+ }
+
+ if (EntrypointParameter.i2d !== true) {
+ if (EntrypointParameter.i2d !== undefined) {
+ typescriptNever(EntrypointParameter.i2d);
+ }
+ }
+
+ if (!(EntrypointParameter.podstate instanceof Array)) {
+ typescriptNever(EntrypointParameter.podstate);
+ }
+
+ if (EntrypointParameter.podstate.length !== 3) {
+ if (EntrypointParameter.podstate.length !== 4) {
+ typescriptNever(EntrypointParameter.podstate.length);
+ }
+ }
+
+ if (EntrypointParameter.podstate[0] !== "Step-by-step solution") {
+ typescriptNever(EntrypointParameter.podstate[0]);
+ }
+
+ if (EntrypointParameter.podstate[1] !== "Step-by-step") {
+ typescriptNever(EntrypointParameter.podstate[1]);
+ }
+
+ if (EntrypointParameter.podstate[2] !== "Show all steps") {
+ typescriptNever(EntrypointParameter.podstate[2]);
+ }
+
+ if (typeof EntrypointParameter.podstate[3] !== "string") {
+ if (typeof EntrypointParameter.podstate[3] !== "undefined") {
+ typescriptNever(EntrypointParameter.podstate[3]);
+ }
+ }
+};
diff --git a/typescript/src/typescriptNever.ts b/typescript/src/typescriptNever.ts
new file mode 100644
index 0000000..42becd5
--- /dev/null
+++ b/typescript/src/typescriptNever.ts
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+export default (typescriptNeverValue: never): never => {
+ console.warn({ typescriptNeverValue });
+
+ return typescriptNeverValue;
+};
+
+// How do I check that a switch block is exhaustive in TypeScript? - Stack Overflow
+// https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript
diff --git a/typescript/tsconfig.json b/typescript/tsconfig.json
new file mode 100644
index 0000000..71ac60a
--- /dev/null
+++ b/typescript/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "allowUnusedLabels": false,
+ "allowUnreachableCode": false,
+ "exactOptionalPropertyTypes": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitOverride": true,
+ "noImplicitReturns": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+
+ "isolatedModules": true,
+
+ "checkJs": true,
+
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+
+ "target": "ES6",
+ "outDir": "../docusaurus/static/ajax/libs/wolfree/2023.8.31/js/"
+ },
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "display": "Strictest",
+ "_version": "2.0.0"
+}
+
+// GitHub - tsconfig/bases: Hosts TSConfigs to extend in a TypeScript app, tuned to a particular runtime environment
+// https://github.com/tsconfig/bases/blob/main/bases/strictest.json
+
+/* SPDX-License-Identifier: AGPL-3.0-or-later */