2023-09-26

This commit is contained in:
name
2023-09-27 11:34:23 +00:00
parent e4fc871937
commit 72d8b4e6be
101 changed files with 3032 additions and 1934 deletions

24
typescript/.dockerignore Normal file
View File

@ -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

24
typescript/.gitignore vendored Normal file
View File

@ -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

7
typescript/package.json Normal file
View File

@ -0,0 +1,7 @@
{
"devDependencies": {
"@types/dompurify": "^3.0.2",
"@types/jquery": "^3.5.19",
"typescript": "^5.2.2"
}
}

View File

@ -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[];
};
}>;

View File

@ -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/

5
typescript/src/AppID.ts Normal file
View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
export default AppID;
type AppID = "H9V325-HTALUWHKGK" | "AKJTJT-LR5LL8WTG6" | "LKY83U-XW6ATU9URU";

View File

@ -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?]
>;
}>;

12
typescript/src/Pod.ts Normal file
View File

@ -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[];
}>;

10
typescript/src/State.ts Normal file
View File

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
export default State;
type State = Readonly<{
value?: string;
states?: {
name?: string;
}[];
}>;

11
typescript/src/SubPod.ts Normal file
View File

@ -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;
}>;

View File

@ -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 });
}
};

View File

@ -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 {};
}
};

View File

@ -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,
}),
});

View File

@ -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;
};

View File

@ -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

View File

@ -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 });
}
};

94
typescript/src/onload.ts Normal file
View File

@ -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)
)
);

View File

@ -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>
`;

View File

@ -0,0 +1,3 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
export default "wolfree-pods";

View File

@ -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 });
}
};

View File

@ -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 });
}
};

View File

@ -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]);
}
}
};

View File

@ -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

34
typescript/tsconfig.json Normal file
View File

@ -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 */