});
})();
+ /**
+ * @returns {Object.<string, string>}
+ */
+ function getCookieMap() {
+ return document.cookie
+ .split(";")
+ .reduce((obj, entry) => {
+ const trimmed = entry.trim();
+ const eqI = trimmed.indexOf('=');
+ const key = trimmed.substring(0, eqI).trimEnd();
+ const value = trimmed.substring(eqI + 1).trimStart();
+ return {...obj, [key]: value};
+ }, {});
+ }
+
+ /**
+ * @param {ParentNode} element
+ */
+ function clearChildren(element) {
+ while (element.hasChildNodes()) {
+ element.firstChild.remove();
+ }
+ }
+
+ /**
+ * @param {number} amount
+ * @return {Promise<void>}
+ */
function delay(amount) {
return new Promise(resolve => window.setTimeout(resolve, amount));
}
+ /**
+ * @return {Promise<DOMHighResTimeStamp>}
+ */
function frame() {
return new Promise(resolve => window.requestAnimationFrame(resolve));
}
+ /**
+ * @param {string} url
+ * @return {Promise<void>}
+ */
function loadScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
- script.onload = () => resolve();
- script.onerror = event => reject(event);
+ script.addEventListener("load", () => resolve());
+ script.addEventListener("error", e => reject(e));
script.src = url;
document.head.appendChild(script);
});
}
+ /**
+ * @param {ParentNode} element
+ * @param {string} text
+ * @return {void}
+ */
function appendWithLineBreaks(element, text) {
const lines = text.split("\n");
let isFirst = true;
}
}
- window.addEventListener("load", function () {
- // Mechyrdian font
- async function mechyrdianToFont(input, boldOpt, italicOpt, alignOpt, output, delayLength) {
- const inText = input.value;
-
- await delay(delayLength);
- if (inText !== input.value) return;
-
- let outBlob;
- if (inText.trim().length === 0) {
- outBlob = new Blob([
- "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
- "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"0\" height=\"0\">\n",
- "</svg>\n"
- ], {type: "image/svg+xml"});
- } else {
- const urlParams = new URLSearchParams();
- if (boldOpt.checked) urlParams.set("bold", "true");
- if (italicOpt.checked) urlParams.set("italic", "true");
- urlParams.set("align", alignOpt.value);
-
- for (const line of inText.split("\n"))
- urlParams.append("lines", line.trim());
-
- outBlob = await (await fetch('/utils/mechyrdia-sans', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: urlParams,
- })).blob();
-
- if (inText !== input.value) return;
- }
-
- const prevObjectUrl = output.src;
- if (prevObjectUrl != null && prevObjectUrl.length > 0)
- URL.revokeObjectURL(prevObjectUrl);
-
- output.src = URL.createObjectURL(outBlob);
- }
-
- const mechyrdiaSansBoxes = document.getElementsByClassName("mechyrdia-sans-box");
- for (const mechyrdiaSansBox of mechyrdiaSansBoxes) {
- const inputBox = mechyrdiaSansBox.getElementsByClassName("input-box")[0];
- const boldOpt = mechyrdiaSansBox.getElementsByClassName("bold-option")[0];
- const italicOpt = mechyrdiaSansBox.getElementsByClassName("ital-option")[0];
- const alignOpt = mechyrdiaSansBox.getElementsByClassName("align-opts")[0];
- const outputBox = mechyrdiaSansBox.getElementsByClassName("output-img")[0];
-
- const inputListener = () => mechyrdianToFont(inputBox, boldOpt, italicOpt, alignOpt, outputBox, 750);
- const optChangeListener = () => mechyrdianToFont(inputBox, boldOpt, italicOpt, alignOpt, outputBox, 250);
- inputBox.addEventListener("input", inputListener);
- boldOpt.addEventListener("change", optChangeListener);
- italicOpt.addEventListener("change", optChangeListener);
- alignOpt.addEventListener("change", optChangeListener);
- }
- });
-
- window.addEventListener("load", function () {
- // Tylan alphabet
- async function tylanToFont(input, output) {
- const inText = input.value;
-
- const urlParams = new URLSearchParams();
- for (const line of inText.split("\n"))
- urlParams.append("lines", line.trim());
-
- const outText = await (await fetch('/utils/tylan-lang', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: urlParams,
- })).text();
-
- if (inText === input.value)
- output.value = outText;
- }
-
- const tylanAlphabetBoxes = document.getElementsByClassName("tylan-alphabet-box");
- for (const tylanAlphabetBox of tylanAlphabetBoxes) {
- const inputBox = tylanAlphabetBox.getElementsByClassName("input-box")[0];
- const outputBox = tylanAlphabetBox.getElementsByClassName("output-box")[0];
-
- inputBox.addEventListener("input", () => tylanToFont(inputBox, outputBox));
- }
- });
-
- window.addEventListener("load", function () {
- // Thedish alphabet
- const thedishAlphabetBoxes = document.getElementsByClassName("thedish-alphabet-box");
- for (const thedishAlphabetBox of thedishAlphabetBoxes) {
- const inputBox = thedishAlphabetBox.getElementsByClassName("input-box")[0];
- const outputBox = thedishAlphabetBox.getElementsByClassName("output-box")[0];
-
- inputBox.addEventListener("input", () => {
- outputBox.value = inputBox.value;
- });
- }
- });
-
- window.addEventListener("load", function () {
- // Kishari alphabet
- const kishariAlphabetBoxes = document.getElementsByClassName("kishari-alphabet-box");
- for (const kishariAlphabetBox of kishariAlphabetBoxes) {
- const inputBox = kishariAlphabetBox.getElementsByClassName("input-box")[0];
- const outputBox = kishariAlphabetBox.getElementsByClassName("output-box")[0];
-
- inputBox.addEventListener("input", () => {
- outputBox.value = inputBox.value;
- });
- }
- });
-
- window.addEventListener("load", function () {
- // Pokhwalish alphabet
- async function pokhwalToFont(input, output) {
- const inText = input.value;
-
- const urlParams = new URLSearchParams();
- for (const line of inText.split("\n"))
- urlParams.append("lines", line.trim());
-
- const outText = await (await fetch('/utils/pokhwal-lang', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: urlParams,
- })).text();
-
- if (inText === input.value)
- output.value = outText;
- }
-
- const pokhwalAlphabetBoxes = document.getElementsByClassName("pokhwal-alphabet-box");
- for (const pokhwalAlphabetBox of pokhwalAlphabetBoxes) {
- const inputBox = pokhwalAlphabetBox.getElementsByClassName("input-box")[0];
- const outputBox = pokhwalAlphabetBox.getElementsByClassName("output-box")[0];
-
- inputBox.addEventListener("input", () => pokhwalToFont(inputBox, outputBox));
- }
- });
-
- window.addEventListener("load", function () {
- // Set client preferences when selected
- const themeChoices = document.getElementsByName("theme");
- for (const themeChoice of themeChoices) {
- themeChoice.addEventListener("click", e => {
- const theme = e.currentTarget.value;
- if (theme === "null") {
- document.documentElement.removeAttribute("data-theme");
- } else {
- document.documentElement.setAttribute("data-theme", theme);
- }
- document.cookie = "FACTBOOK_THEME=" + theme + "; Secure; SameSite=Lax; Max-Age=" + (Math.pow(2, 31) - 1).toString();
- });
- }
-
- const april1stChoices = document.getElementsByName("april1st");
- for (const april1stChoice of april1stChoices) {
- april1stChoice.addEventListener("click", e => {
- const mode = e.currentTarget.value;
- document.cookie = "APRIL_1ST_MODE=" + mode + "; Secure; SameSite=None; Max-Age=" + (Math.pow(2, 31) - 1).toString();
- });
- }
- });
-
- window.addEventListener("load", function () {
- // Localize dates and times
- const moments = document.getElementsByClassName("moment");
- for (const moment of moments) {
- let date = new Date(Number(moment.textContent.trim()));
- moment.innerHTML = date.toLocaleString();
- moment.style.display = "inline";
- }
- });
-
- window.addEventListener("load", function () {
- // Login button
- const viewChecksumButtons = document.getElementsByClassName("view-checksum");
- for (const viewChecksumButton of viewChecksumButtons) {
- const token = viewChecksumButton.getAttribute("data-token");
- const url = (token != null && token !== "") ? ("https://www.nationstates.net/page=verify_login?token=" + token) : "https://www.nationstates.net/page=verify_login"
- viewChecksumButton.addEventListener("click", e => {
- e.preventDefault();
- window.open(url);
- });
- }
- });
-
- window.appendImageThumb = function (src, sizeStyle) {
- // Image previewing (1)
- const imgElement = document.createElement("img");
- imgElement.src = src;
- imgElement.setAttribute("title", "Click to view full size");
- imgElement.setAttribute("style", sizeStyle);
-
- imgElement.onclick = e => {
- e.preventDefault();
-
- const thumbView = document.getElementById("thumb-view");
- const thumbViewImg = thumbView.getElementsByTagName("img")[0];
- thumbViewImg.src = e.currentTarget.src;
- thumbView.classList.add("visible");
- }
-
- document.currentScript.after(imgElement);
- };
-
- window.handleFullSizeImages = function () {
- // Image previewing (2)
- document.getElementById("thumb-view").addEventListener("click", e => {
- e.preventDefault();
-
- e.currentTarget.classList.remove("visible");
- e.currentTarget.getElementsByTagName("img")[0].src = "";
- });
- };
-
- window.addEventListener("load", function () {
- // Mesh viewing
-
- async function loadThree() {
- await loadScript("/static/obj-viewer/three.js");
- await loadScript("/static/obj-viewer/three-examples.js");
- }
-
+ /**
+ * @returns {Promise<function(string): Promise<THREE.Mesh>>}
+ */
+ async function loadThreeJs() {
+ await loadScript("/static/obj-viewer/three.js");
+ await loadScript("/static/obj-viewer/three-examples.js");
+
+ /**
+ * @param {string} modelName
+ * @returns {Promise<THREE.Mesh>}
+ */
async function loadObj(modelName) {
+ const THREE = window.THREE;
const mtlLib = await (new THREE.MTLLoader()).setPath("/assets/meshes/").setResourcePath("/assets/meshes/").loadAsync(modelName + ".mtl");
mtlLib.preload();
return await (new THREE.OBJLoader()).setPath("/assets/meshes/").setResourcePath("/assets/meshes/").setMaterials(mtlLib).loadAsync(modelName + ".obj");
}
- const canvases = document.getElementsByTagName("canvas");
- if (canvases.length > 0) {
- (async () => {
- await loadThree();
-
- const promises = [];
- for (const canvas of canvases) {
- const modelName = canvas.getAttribute("data-model");
- if (modelName == null || modelName === "") continue;
-
- promises.push((async () => {
- const modelAsync = loadObj(modelName);
-
- const camera = new THREE.PerspectiveCamera(69, 1, 0.01, 1000.0);
-
- const scene = new THREE.Scene();
- scene.add(new THREE.AmbientLight("#555555", 1.0));
-
- const renderer = new THREE.WebGLRenderer({"canvas": canvas, "antialias": true});
-
- const controls = new THREE.OrbitControls(camera, canvas);
-
- function render() {
- controls.update();
- renderer.render(scene, camera);
- window.requestAnimationFrame(render);
- }
-
- function onResize() {
- const dim = canvas.getBoundingClientRect();
- camera.aspect = dim.width / dim.height;
- camera.updateProjectionMatrix();
- renderer.setSize(dim.width, dim.height, false);
- }
-
- window.addEventListener('resize', onResize);
- await frame();
- onResize();
-
- const model = await modelAsync;
- scene.add(model);
-
- const bbox = new THREE.Box3().setFromObject(scene);
- bbox.dimensions = {
- x: bbox.max.x - bbox.min.x,
- y: bbox.max.y - bbox.min.y,
- z: bbox.max.z - bbox.min.z
- };
- model.position.sub(new THREE.Vector3(bbox.min.x + bbox.dimensions.x / 2, bbox.min.y + bbox.dimensions.y / 2, bbox.min.z + bbox.dimensions.z / 2));
-
- camera.position.set(bbox.dimensions.x / 2, bbox.dimensions.y / 2, Math.max(bbox.dimensions.x, bbox.dimensions.y, bbox.dimensions.z));
-
- const light = new THREE.PointLight("#AAAAAA", 1.0);
- scene.add(camera);
- camera.add(light);
- light.position.set(0, 0, 0);
-
- render();
- })());
- }
-
- await Promise.all(promises);
- })().catch(reason => {
- console.error("Error rendering models", reason);
- });
- }
- });
-
- window.addEventListener("load", function () {
- // Allow POSTing with <a>s
- const anchors = document.getElementsByTagName("a");
- for (const anchor of anchors) {
- const method = anchor.getAttribute("data-method");
- if (method == null) continue;
-
- anchor.onclick = e => {
- e.preventDefault();
-
- let form = document.createElement("form");
- form.style.display = "none";
- form.action = e.currentTarget.href;
- form.method = e.currentTarget.getAttribute("data-method");
-
- const csrfToken = e.currentTarget.getAttribute("data-csrf-token");
- if (csrfToken != null) {
- let csrfInput = document.createElement("input");
- csrfInput.name = "csrfToken";
- csrfInput.type = "hidden";
- csrfInput.value = csrfToken;
- form.append(csrfInput);
- }
-
- document.body.append(form);
- form.submit();
- };
- }
- });
+ return loadObj;
+ }
- window.renderVocab = function (vocab) {
+ /**
+ * @typedef {{tag: string, attrs: Object.<string, *>, text: (string|{form: string, regexp: string, replacement: string})}} VocabInflectionTableCell
+ * @typedef {Array.<VocabInflectionTableCell>} VocabInflectionTableRow
+ * @typedef {Array.<VocabInflectionTableRow>} VocabInflectionTable
+ * @typedef {{type: string, inEnglish: Array.<string>, forms: Array.<string>, definitions: Array.<string>}} VocabWordEntry
+ * @typedef {Array.<VocabWordEntry>} VocabWord
+ * @typedef {{langName: string, inflections: Object.<string, VocabInflectionTable>, words: Object.<string, VocabWord>}} Vocab
+ *
+ * @param {Vocab} vocab
+ * @returns {HTMLDivElement}
+ */
+ function renderVocab(vocab) {
+ /**
+ * @param {string} word
+ * @param {number} index
+ * @returns {HTMLDivElement}
+ */
function renderWord(word, index) {
const wordRoot = document.createElement("div");
vocabSearchButton.type = "submit";
vocabSearchButton.value = "Search";
- vocabSearchRoot.onsubmit = function (ev) {
- ev.preventDefault();
+ vocabSearchRoot.addEventListener("submit", function (e) {
+ e.preventDefault();
const searchTerm = vocabSearch.value.trim();
- while (vocabSearchResults.hasChildNodes()) {
- vocabSearchResults.firstChild.remove();
- }
+ clearChildren(vocabSearchResults);
const searchResults = [];
if (vocabEnglishToLang.checked) {
for (const searchResult of searchResults) {
vocabSearchResults.append(renderWord(searchResult.word, searchResult.index));
}
- };
+ });
- document.currentScript.after(vocabRoot);
- };
+ return vocabRoot;
+ }
- window.renderQuiz = function (quiz) {
- const quizFunctions = {};
+ /**
+ * @typedef {{name: string, desc: string, img: string, url: string}} QuizOutcome
+ * @typedef {{answer: string, result: Object.<string, number>}} QuizQuestionAnswer
+ * @typedef {{asks: string, answers: Object.<string, QuizQuestionAnswer>}} QuizQuestion
+ * @typedef {{title: string, intro: string, image: string, outcomes: Object.<string, QuizOutcome>, questions: Array.<QuizQuestion>}} Quiz
+ *
+ * @param {Quiz} quiz
+ * @returns {HTMLTableElement}
+ */
+ function renderQuiz(quiz) {
const quizRoot = document.createElement("table");
const questionAnswers = [];
- quizFunctions.clearRoot = function () {
- while (quizRoot.hasChildNodes()) {
- quizRoot.firstChild.remove();
- }
- };
-
- quizFunctions.renderIntro = function () {
- quizFunctions.clearRoot();
+ function renderIntro() {
+ clearChildren(quizRoot);
const firstRow = document.createElement("tr");
const firstCell = document.createElement("td");
firstCell.style.textAlign = "center";
firstCell.style.fontSize = "1.5em";
firstCell.style.fontWeight = "bold";
- firstCell.append(quiz.title.toString());
+ firstCell.append(quiz.title);
firstRow.appendChild(firstCell);
quizRoot.appendChild(firstRow);
const secondCell = document.createElement("td");
secondCell.style.textAlign = "center";
secondCell.appendChild(document.createElement("img")).src = quiz.image;
- for (const paragraph of quiz.intro.toString().split('\n')) {
+ for (const paragraph of quiz.intro.split('\n')) {
secondCell.appendChild(document.createElement("p")).append(paragraph);
}
secondRow.appendChild(secondCell);
const beginLink = thirdCell.appendChild(document.createElement("a"));
beginLink.href = "#";
beginLink.append("Begin Quiz (" + quiz.questions.length + " questions)");
- beginLink.onclick = e => {
+ beginLink.addEventListener("click", e => {
e.preventDefault();
- quizFunctions.renderQuestion(0);
- };
+ renderQuestion(0);
+ });
thirdRow.appendChild(thirdCell);
quizRoot.appendChild(thirdRow);
- };
+ }
- quizFunctions.renderOutro = function (outcome) {
- quizFunctions.clearRoot();
+ /**
+ * @param {QuizOutcome} outcome
+ */
+ function renderOutro(outcome) {
+ clearChildren(quizRoot);
const firstRow = document.createElement("tr");
const firstCell = document.createElement("td");
firstCell.style.textAlign = "center";
firstCell.style.fontSize = "1.5em";
firstCell.style.fontWeight = "bold";
- firstCell.append(outcome.name.toString());
+ firstCell.append(outcome.name);
firstRow.appendChild(firstCell);
quizRoot.appendChild(firstRow);
const secondCell = document.createElement("td");
secondCell.style.textAlign = "center";
secondCell.appendChild(document.createElement("img")).src = outcome.img;
- for (const paragraph of outcome.desc.toString().split('\n')) {
+ for (const paragraph of outcome.desc.split('\n')) {
secondCell.appendChild(document.createElement("p")).append(paragraph);
}
secondRow.appendChild(secondCell);
moreInfoLink.append("More Information");
thirdRow.appendChild(thirdCell);
quizRoot.appendChild(thirdRow);
- };
+ }
- quizFunctions.calculateResults = function () {
+ /**
+ * @returns {QuizOutcome}
+ */
+ function calculateResults() {
const total = {};
for (const result of questionAnswers) {
for (const resKey of Object.keys(result)) {
}
return quiz.outcomes[maxKey];
- };
+ }
- quizFunctions.renderQuestion = function (index) {
- quizFunctions.clearRoot();
+ /**
+ * @param {number} index
+ */
+ function renderQuestion(index) {
+ clearChildren(quizRoot);
const question = quiz.questions[index];
firstCell.style.fontWeight = "bold";
firstCell.append("Question " + (index + 1) + "/" + quiz.questions.length);
firstCell.append(document.createElement("br"));
- firstCell.append(question.asks.toString());
+ firstCell.append(question.asks);
firstRow.appendChild(firstCell);
quizRoot.appendChild(firstRow);
secondCell.style.textAlign = "center";
const answerLink = secondCell.appendChild(document.createElement("a"));
answerLink.href = "#";
- answerLink.append(answer.answer.toString());
- answerLink.onclick = e => {
+ answerLink.append(answer.answer);
+ answerLink.addEventListener("click", e => {
e.preventDefault();
questionAnswers[index] = answer.result;
if (index === quiz.questions.length - 1) {
- quizFunctions.renderOutro(quizFunctions.calculateResults());
+ renderOutro(calculateResults());
} else {
- quizFunctions.renderQuestion(index + 1);
+ renderQuestion(index + 1);
}
- };
+ });
secondRow.appendChild(secondCell);
quizRoot.appendChild(secondRow);
}
const prevLink = thirdCell.appendChild(document.createElement("a"));
prevLink.href = "#";
prevLink.append("Previous Question");
- prevLink.onclick = e => {
+ prevLink.addEventListener("click", e => {
e.preventDefault();
if (index === 0) {
- quizFunctions.renderIntro();
+ renderIntro();
} else {
- quizFunctions.renderQuestion(index - 1);
+ renderQuestion(index - 1);
}
- };
+ });
thirdRow.appendChild(thirdCell);
quizRoot.appendChild(thirdRow);
- };
+ }
- document.currentScript.after(quizRoot);
+ renderIntro();
- quizFunctions.renderIntro();
- };
+ return quizRoot;
+ }
- window.addEventListener("load", function () {
- // Comment previews
- async function commentPreview(input, output) {
- const inText = input.value;
+ /**
+ * @param {HTMLElement} dom
+ */
+ function onDomLoad(dom) {
+ (function () {
+ // Mechyrdian font
+
+ /**
+ * @param {HTMLInputElement} input
+ * @param {HTMLInputElement} boldOpt
+ * @param {HTMLInputElement} italicOpt
+ * @param {HTMLSelectElement} alignOpt
+ * @param {HTMLImageElement} output
+ * @param {number} delayLength
+ * @returns {Promise<void>}
+ */
+ async function mechyrdianToFont(input, boldOpt, italicOpt, alignOpt, output, delayLength) {
+ const inText = input.value;
+
+ await delay(delayLength);
+ if (inText !== input.value) return;
- await delay(500);
- if (input.value !== inText)
- return;
+ let outBlob;
+ if (inText.trim().length === 0) {
+ outBlob = new Blob([
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"0\" height=\"0\">\n",
+ "</svg>\n"
+ ], {type: "image/svg+xml"});
+ } else {
+ const urlParams = new URLSearchParams();
+ if (boldOpt.checked) urlParams.set("bold", "true");
+ if (italicOpt.checked) urlParams.set("italic", "true");
+ urlParams.set("align", alignOpt.value);
+
+ for (const line of inText.split("\n"))
+ urlParams.append("lines", line.trim());
+
+ outBlob = await (await fetch('/utils/mechyrdia-sans', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: urlParams,
+ })).blob();
+
+ if (inText !== input.value) return;
+ }
- if (inText.length === 0) {
- output.innerHTML = "";
- return;
+ const prevObjectUrl = output.src;
+ if (prevObjectUrl != null && prevObjectUrl.length > 0)
+ URL.revokeObjectURL(prevObjectUrl);
+
+ output.src = URL.createObjectURL(outBlob);
}
- const urlParams = new URLSearchParams();
- for (const line of inText.split("\n"))
- urlParams.append("lines", line.trim());
+ const mechyrdiaSansBoxes = dom.querySelectorAll("div.mechyrdia-sans-box");
+ for (const mechyrdiaSansBox of mechyrdiaSansBoxes) {
+ const inputBox = mechyrdiaSansBox.querySelector("textarea.input-box");
+ const boldOpt = mechyrdiaSansBox.querySelector("input.bold-option");
+ const italicOpt = mechyrdiaSansBox.querySelector("input.ital-option");
+ const alignOpt = mechyrdiaSansBox.querySelector("select.align-opts");
+ const outputBox = mechyrdiaSansBox.querySelector("img.output-img");
+
+ const inputListener = () => mechyrdianToFont(inputBox, boldOpt, italicOpt, alignOpt, outputBox, 750);
+ const optChangeListener = () => mechyrdianToFont(inputBox, boldOpt, italicOpt, alignOpt, outputBox, 250);
+ inputBox.addEventListener("input", inputListener);
+ boldOpt.addEventListener("change", optChangeListener);
+ italicOpt.addEventListener("change", optChangeListener);
+ alignOpt.addEventListener("change", optChangeListener);
+ }
+ })();
- const outText = await (await fetch('/utils/preview-comment', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: urlParams,
- })).text();
- if (input.value !== inText)
- return;
+ (function () {
+ // Tylan alphabet
- output.innerHTML = "<h3>Preview:</h3>" + outText;
- }
+ /**
+ * @param {HTMLTextAreaElement} input
+ * @param {HTMLTextAreaElement} output
+ * @returns {Promise<void>}
+ */
+ async function tylanToFont(input, output) {
+ const inText = input.value;
- const commentInputBoxes = document.getElementsByClassName("comment-input");
- for (const commentInputBox of commentInputBoxes) {
- const inputBox = commentInputBox.getElementsByClassName("comment-markup")[0];
- const outputBox = commentInputBox.getElementsByClassName("comment-preview")[0];
+ const urlParams = new URLSearchParams();
+ for (const line of inText.split("\n"))
+ urlParams.append("lines", line.trim());
- inputBox.addEventListener("input", () => commentPreview(inputBox, outputBox));
- }
- });
+ const outText = await (await fetch('/utils/tylan-lang', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: urlParams,
+ })).text();
- window.addEventListener("load", function () {
- // Comment editing
- const commentEditLinks = document.getElementsByClassName("comment-edit-link");
- for (const commentEditLink of commentEditLinks) {
- commentEditLink.onclick = e => {
- e.preventDefault();
+ if (inText === input.value)
+ output.value = outText;
+ }
- const elementId = e.currentTarget.getAttribute("data-edit-id");
- document.getElementById(elementId).classList.add("visible");
- };
- }
+ const tylanAlphabetBoxes = dom.querySelectorAll("div.tylan-alphabet-box");
+ for (const tylanAlphabetBox of tylanAlphabetBoxes) {
+ const inputBox = tylanAlphabetBox.querySelector("textarea.input-box");
+ const outputBox = tylanAlphabetBox.querySelector("textarea.output-box");
- const commentEditCancelButtons = document.getElementsByClassName("comment-cancel-edit");
- for (const commentEditCancelButton of commentEditCancelButtons) {
- commentEditCancelButton.onclick = e => {
- e.preventDefault();
+ inputBox.addEventListener("input", () => tylanToFont(inputBox, outputBox));
+ }
+ })();
- e.currentTarget.parentElement.classList.remove("visible");
- };
- }
- });
+ (function () {
+ // Thedish alphabet
- window.addEventListener("load", function () {
- // Copying text
- const copyTextElements = document.getElementsByClassName("copy-text");
- for (const copyTextElement of copyTextElements) {
- copyTextElement.onclick = e => {
- e.preventDefault();
+ const thedishAlphabetBoxes = dom.querySelectorAll("div.thedish-alphabet-box");
+ for (const thedishAlphabetBox of thedishAlphabetBoxes) {
+ const inputBox = thedishAlphabetBox.querySelector("textarea.input-box");
+ const outputBox = thedishAlphabetBox.querySelector("textarea.output-box");
- const thisElement = e.currentTarget;
- if (thisElement.hasAttribute("data-copying"))
- return;
+ inputBox.addEventListener("input", () => {
+ outputBox.value = inputBox.value;
+ });
+ }
+ })();
+
+ (function () {
+ // Kishari alphabet
+
+ const kishariAlphabetBoxes = dom.querySelectorAll("div.kishari-alphabet-box");
+ for (const kishariAlphabetBox of kishariAlphabetBoxes) {
+ const inputBox = kishariAlphabetBox.querySelector("textarea.input-box");
+ const outputBox = kishariAlphabetBox.querySelector("textarea.output-box");
+
+ inputBox.addEventListener("input", () => {
+ outputBox.value = inputBox.value;
+ });
+ }
+ })();
+
+ (function () {
+ // Pokhwalish alphabet
+
+ /**
+ * @param {HTMLTextAreaElement} input
+ * @param {HTMLTextAreaElement} output
+ * @returns {Promise<void>}
+ */
+ async function pokhwalToFont(input, output) {
+ const inText = input.value;
+
+ const urlParams = new URLSearchParams();
+ for (const line of inText.split("\n"))
+ urlParams.append("lines", line.trim());
+
+ const outText = await (await fetch('/utils/pokhwal-lang', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: urlParams,
+ })).text();
+
+ if (inText === input.value)
+ output.value = outText;
+ }
+
+ const pokhwalAlphabetBoxes = dom.querySelectorAll("div.pokhwal-alphabet-box");
+ for (const pokhwalAlphabetBox of pokhwalAlphabetBoxes) {
+ const inputBox = pokhwalAlphabetBox.querySelector("textarea.input-box");
+ const outputBox = pokhwalAlphabetBox.querySelector("textarea.output-box");
+
+ inputBox.addEventListener("input", () => pokhwalToFont(inputBox, outputBox));
+ }
+ })();
+
+ (function () {
+ // Set client preferences when selected
+ const themeChoices = dom.querySelectorAll("input.pref-theme");
+ for (const themeChoice of themeChoices) {
+ themeChoice.addEventListener("click", e => {
+ const theme = e.currentTarget.value;
+ if (theme === "null") {
+ document.documentElement.removeAttribute("data-theme");
+ } else {
+ document.documentElement.setAttribute("data-theme", theme);
+ }
+ document.cookie = "FACTBOOK_THEME=" + theme + "; Secure; SameSite=Lax; Max-Age=" + (Math.pow(2, 31) - 1).toString();
+ });
+ }
+
+ const april1stChoices = dom.querySelectorAll("input.pref-april1st");
+ for (const april1stChoice of april1stChoices) {
+ april1stChoice.addEventListener("click", e => {
+ const mode = e.currentTarget.value;
+ document.cookie = "APRIL_1ST_MODE=" + mode + "; Secure; SameSite=None; Max-Age=" + (Math.pow(2, 31) - 1).toString();
+ });
+ }
+ })();
+
+ (function () {
+ // Localize dates and times
+
+ const moments = dom.querySelectorAll("span.moment");
+ for (const moment of moments) {
+ let date = new Date(Number(moment.textContent.trim()));
+ moment.innerHTML = date.toLocaleString();
+ moment.style.display = "inline";
+ }
+ })();
+
+ (function () {
+ // Login button
+
+ const viewChecksumButtons = dom.querySelectorAll("button.view-checksum");
+ for (const viewChecksumButton of viewChecksumButtons) {
+ const token = viewChecksumButton.getAttribute("data-token");
+ const url = (token != null && token !== "") ? ("https://www.nationstates.net/page=verify_login?token=" + token) : "https://www.nationstates.net/page=verify_login"
+ viewChecksumButton.addEventListener("click", e => {
+ e.preventDefault();
+ window.open(url);
+ });
+ }
+ })();
+
+ (function () {
+ // Image previewing
+
+ const imageThumbs = dom.querySelectorAll("span.image-thumb");
+ for (const imageThumb of imageThumbs) {
+ const imgElement = document.createElement("img");
+ imgElement.src = imageThumb.getAttribute("data-src");
+ imgElement.style.cssText = imageThumb.getAttribute("data-style");
+ imgElement.title = "Click to view full size";
- const elementHtml = thisElement.innerHTML;
-
- thisElement.setAttribute("data-copying", "copying");
-
- const text = thisElement.getAttribute("data-text");
- navigator.clipboard.writeText(text)
- .then(() => {
- thisElement.innerHTML = "Text copied!";
- window.setTimeout(() => {
- thisElement.innerHTML = elementHtml;
- thisElement.removeAttribute("data-copying");
- }, 750);
- })
- .catch(reason => {
- console.error("Error copying text to clipboard", reason);
-
- thisElement.innerHTML = "Text copy failed";
- window.setTimeout(() => {
- thisElement.innerHTML = elementHtml;
- thisElement.removeAttribute("data-copying");
- }, 1500);
+ imgElement.addEventListener("click", e => {
+ e.preventDefault();
+
+ const thumbView = document.createElement("div");
+ thumbView.id = "thumb-view";
+
+ const thumbViewBg = document.createElement("div");
+ thumbViewBg.classList.add("bg");
+
+ const thumbViewImg = document.createElement("img");
+ thumbViewImg.src = e.currentTarget.src;
+ thumbViewImg.title = thumbViewImg.alt = "Click to close full size";
+ thumbView.classList.add("visible");
+
+ thumbView.append(thumbViewBg, thumbViewImg);
+ thumbView.addEventListener("click", e => {
+ e.preventDefault();
+
+ e.currentTarget.remove();
});
- thisElement.innerHTML = "Copying text...";
- };
- }
- });
+ document.body.append(thumbView);
+ });
- window.addEventListener("load", function () {
- // Error popup
- const errorPopup = document.getElementById("error-popup");
- if (errorPopup != null) {
- errorPopup.addEventListener("click", e => {
- e.preventDefault();
+ imageThumb.after(imgElement);
+ imageThumb.remove();
+ }
+ })();
- const thisElement = e.currentTarget;
- const newUrl = window.location.origin + thisElement.getAttribute("data-redirect-url") + window.location.hash;
- window.history.replaceState({}, '', newUrl);
+ (function () {
+ // Mesh viewing
- thisElement.remove();
- });
- }
- });
+ const canvases = dom.querySelectorAll("canvas[data-model]");
+ if (canvases.length > 0) {
+ (async function () {
+ const loadObj = await loadThreeJs();
+ const THREE = window.THREE;
- window.factbookRedirect = function (redirectTo) {
- if (window.disableFactbookRedirect) {
- const redirectTarget = new URL(redirectTo, window.location);
+ const promises = [];
+ for (const canvas of canvases) {
+ promises.push((async () => {
+ const modelAsync = loadObj(canvas.getAttribute("data-model"));
- const redirectTargetUrl = redirectTarget.pathname + redirectTarget.search + redirectTarget.hash;
+ const camera = new THREE.PerspectiveCamera(69, 1, 0.01, 1000.0);
- const pElement = document.createElement("p");
- pElement.style.fontWeight = "800";
- pElement.append(document.createTextNode("Redirect to "));
+ const scene = new THREE.Scene();
+ scene.add(new THREE.AmbientLight("#555555", 1.0));
- const aElement = document.createElement("a");
- aElement.href = redirectTargetUrl;
- aElement.append(document.createTextNode(redirectTarget.pathname));
+ const renderer = new THREE.WebGLRenderer({"canvas": canvas, "antialias": true});
- pElement.append(aElement);
- document.currentScript.after(pElement);
- } else {
- window.localStorage.setItem("redirectedFrom", window.location.pathname);
- window.location = redirectTo;
- }
- };
+ const controls = new THREE.OrbitControls(camera, canvas);
- window.checkRedirectTarget = function (anchorHash) {
- const redirectTargetValue = window.location.hash;
- if (redirectTargetValue !== anchorHash) return;
+ function render() {
+ controls.update();
+ renderer.render(scene, camera);
+ window.requestAnimationFrame(render);
+ }
- const redirectSourceValue = window.localStorage.getItem("redirectedFrom");
- if (redirectSourceValue != null) {
- const redirectSource = new URL(redirectSourceValue, window.location.origin);
- if (redirectSource.search.length > 0) {
- redirectSource.search += "&redirect=no";
- } else {
- redirectSource.search = "?redirect=no";
+ function onResize() {
+ const dim = canvas.getBoundingClientRect();
+ camera.aspect = dim.width / dim.height;
+ camera.updateProjectionMatrix();
+ renderer.setSize(dim.width, dim.height, false);
+ }
+
+ window.addEventListener('resize', onResize);
+ await frame();
+ onResize();
+
+ const model = await modelAsync;
+ scene.add(model);
+
+ const bbox = new THREE.Box3().setFromObject(scene);
+ bbox.dimensions = {
+ x: bbox.max.x - bbox.min.x,
+ y: bbox.max.y - bbox.min.y,
+ z: bbox.max.z - bbox.min.z
+ };
+ model.position.sub(new THREE.Vector3(bbox.min.x + bbox.dimensions.x / 2, bbox.min.y + bbox.dimensions.y / 2, bbox.min.z + bbox.dimensions.z / 2));
+
+ camera.position.set(bbox.dimensions.x / 2, bbox.dimensions.y / 2, Math.max(bbox.dimensions.x, bbox.dimensions.y, bbox.dimensions.z));
+
+ const light = new THREE.PointLight("#AAAAAA", 1.0);
+ scene.add(camera);
+ camera.add(light);
+ light.position.set(0, 0, 0);
+
+ render();
+ })());
+ }
+
+ await Promise.all(promises);
+ })().catch(reason => {
+ console.error("Error rendering models", reason);
+ });
}
+ })();
- const redirectSourceUrl = redirectSource.pathname + redirectSource.search + redirectSource.hash;
+ (function () {
+ // Allow POSTing with <a>s
- const pElement = document.createElement("p");
- pElement.style.fontSize = "0.8em";
- pElement.append(document.createTextNode("Redirected from "));
+ // TODO implement Fetch+History loading
+ const anchors = dom.querySelectorAll("a[data-method]");
+ for (const anchor of anchors) {
+ anchor.addEventListener("click", e => {
+ e.preventDefault();
- const aElement = document.createElement("a");
- aElement.href = redirectSourceUrl;
- aElement.append(document.createTextNode(redirectSource.pathname));
+ let form = document.createElement("form");
+ form.style.display = "none";
+ form.action = e.currentTarget.href;
+ form.method = e.currentTarget.getAttribute("data-method");
+
+ const csrfToken = e.currentTarget.getAttribute("data-csrf-token");
+ if (csrfToken != null) {
+ let csrfInput = document.createElement("input");
+ csrfInput.name = "csrfToken";
+ csrfInput.type = "hidden";
+ csrfInput.value = csrfToken;
+ form.append(csrfInput);
+ }
- pElement.append(aElement);
- document.currentScript.after(pElement);
- }
+ document.body.appendChild(form).submit();
+ });
+ }
+ })();
- window.localStorage.removeItem("redirectedFrom");
- };
-
- window.createNukeBox = function (csrfToken) {
- const chatHistory = document.createElement("blockquote");
- chatHistory.style.overflowY = "scroll";
- chatHistory.style.height = "40vh";
-
- const inputBox = document.createElement("input");
- inputBox.classList.add("inline");
- inputBox.style.flexGrow = "1";
- inputBox.type = "text";
- inputBox.placeholder = "Enter your message";
-
- const enterBtn = document.createElement("input");
- enterBtn.classList.add("inline");
- enterBtn.style.flexShrink = "0";
- enterBtn.type = "submit";
- enterBtn.value = "Send";
- enterBtn.disabled = true;
-
- const inputForm = document.createElement("form");
- inputForm.style.display = "flex";
- inputForm.append(inputBox, enterBtn);
-
- const container = document.createElement("div");
- container.append(chatHistory, inputForm);
- document.currentScript.after(container);
-
- const targetUrl = "ws" + window.location.href.substring(4) + "/ws?csrfToken=" + csrfToken;
- const webSock = new WebSocket(targetUrl);
-
- inputForm.onsubmit = (ev) => {
- ev.preventDefault();
- if (!ev.submitter.disabled) {
- webSock.send(inputBox.value);
- inputBox.value = "";
- enterBtn.disabled = true;
+ (function () {
+ // Render vocab
+
+ const vocabSpans = dom.querySelectorAll("span.vocab");
+ for (const vocabSpan of vocabSpans) {
+ const vocab = JSON.parse(vocabSpan.getAttribute("data-vocab"));
+ vocabSpan.after(renderVocab(vocab));
+ vocabSpan.remove();
}
- };
-
- webSock.onmessage = (ev) => {
- const data = JSON.parse(ev.data);
- if (data.type === "ready") {
- enterBtn.disabled = false;
- } else if (data.type === "user") {
- const userP = document.createElement("p");
- userP.style.textAlign = "right";
- userP.style.paddingLeft = "50%";
- appendWithLineBreaks(userP, data.text);
- chatHistory.appendChild(userP);
-
- const robotP = document.createElement("p");
- robotP.style.textAlign = "left";
- robotP.style.paddingRight = "50%";
- chatHistory.appendChild(robotP);
- } else if (data.type === "robot") {
- const robotP = chatHistory.lastElementChild;
- appendWithLineBreaks(robotP, data.text);
- } else if (data.type === "cite") {
- const robotP = chatHistory.lastElementChild;
- const robotCiteList = robotP.appendChild(document.createElement("ol"));
- for (const url of data.urls) {
- const urlLink = robotCiteList.appendChild(document.createElement("li")).appendChild(document.createElement("a"));
- urlLink.href = url;
- urlLink.append(url);
+ })();
+
+ (function () {
+ // Render quizzes
+
+ const quizSpans = dom.querySelectorAll("span.quiz");
+ for (const quizSpan of quizSpans) {
+ const quiz = JSON.parse(quizSpan.getAttribute("data-quiz"));
+ quizSpan.after(renderQuiz(quiz));
+ quizSpan.remove();
+ }
+ })();
+
+ (function () {
+ // Comment previews
+
+ /**
+ * @param {HTMLTextAreaElement} input
+ * @param {HTMLDivElement} output
+ * @returns {Promise<void>}
+ */
+ async function commentPreview(input, output) {
+ const inText = input.value;
+
+ await delay(500);
+ if (input.value !== inText)
+ return;
+
+ if (inText.length === 0) {
+ output.innerHTML = "";
+ return;
}
+
+ const urlParams = new URLSearchParams();
+ for (const line of inText.split("\n"))
+ urlParams.append("lines", line.trim());
+
+ const outText = await (await fetch('/utils/preview-comment', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: urlParams,
+ })).text();
+ if (input.value !== inText)
+ return;
+
+ output.innerHTML = "<h3>Preview:</h3>" + outText;
}
- };
- webSock.onclose = (ev) => {
- const statusP = document.createElement("p");
- statusP.style.textAlign = "center";
- statusP.style.paddingLeft = "25%";
- statusP.style.paddingRight = "25%";
- appendWithLineBreaks(statusP, "The connection has been closed\n" + ev.reason);
- chatHistory.appendChild(statusP);
+ const commentInputBoxes = dom.querySelectorAll("form.comment-input");
+ for (const commentInputBox of commentInputBoxes) {
+ const inputBox = commentInputBox.querySelector("textarea.comment-markup");
+ const outputBox = commentInputBox.querySelector("div.comment-preview");
+
+ inputBox.addEventListener("input", () => commentPreview(inputBox, outputBox));
+ }
+ })();
+
+ (function () {
+ // Comment editing
+
+ const commentEditLinks = dom.querySelectorAll("a.comment-edit-link");
+ for (const commentEditLink of commentEditLinks) {
+ const targetElement = dom.querySelector("#" + commentEditLink.getAttribute("data-edit-id"));
+ commentEditLink.addEventListener("click", e => {
+ e.preventDefault();
+
+ targetElement.classList.add("visible");
+ });
+ }
+
+ const commentEditCancelButtons = dom.querySelectorAll("button.comment-cancel-edit");
+ for (const commentEditCancelButton of commentEditCancelButtons) {
+ const targetElement = dom.querySelector("#" + commentEditCancelButton.getAttribute("data-edit-id"));
+
+ commentEditCancelButton.addEventListener("click", e => {
+ e.preventDefault();
+
+ targetElement.classList.remove("visible");
+ });
+ }
+ })();
+
+ (function () {
+ // Copying text
+
+ const copyTextElements = dom.querySelectorAll("a.copy-text");
+ for (const copyTextElement of copyTextElements) {
+ copyTextElement.addEventListener("click", e => {
+ e.preventDefault();
+
+ const thisElement = e.currentTarget;
+ if (thisElement.hasAttribute("data-copying"))
+ return;
+
+ const elementHtml = thisElement.innerHTML;
+
+ thisElement.setAttribute("data-copying", "copying");
+
+ const text = thisElement.getAttribute("data-text");
+ navigator.clipboard.writeText(text)
+ .then(() => {
+ thisElement.innerHTML = "Text copied!";
+ window.setTimeout(() => {
+ thisElement.innerHTML = elementHtml;
+ thisElement.removeAttribute("data-copying");
+ }, 750);
+ })
+ .catch(reason => {
+ console.error("Error copying text to clipboard", reason);
+
+ thisElement.innerHTML = "Text copy failed";
+ window.setTimeout(() => {
+ thisElement.innerHTML = elementHtml;
+ thisElement.removeAttribute("data-copying");
+ }, 1500);
+ });
+
+ thisElement.innerHTML = "Copying text...";
+ });
+ }
+ })();
+
+ (function () {
+ // Error popup
+
+ const errorMsg = getCookieMap()["ERROR_MSG"];
+ if (errorMsg != null) {
+ document.cookie = "ERROR_MSG=; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax; Secure";
+
+ const errorPopup = document.createElement("div");
+ errorPopup.id = "error-popup";
+
+ const errorPopupBg = document.createElement("div");
+ errorPopupBg.classList.add("bg");
+
+ const errorPopupMsg = document.createElement("div");
+ errorPopupMsg.classList.add("msg");
+
+ const msgP = document.createElement("p");
+ msgP.append(errorMsg);
+ const c2cP = document.createElement("p");
+ c2cP.append("Click to close this popup");
+
+ errorPopupMsg.append(msgP, c2cP);
+ errorPopup.append(errorPopupBg, errorPopupMsg);
- enterBtn.disabled = true;
- };
- };
+ errorPopup.addEventListener("click", e => {
+ e.preventDefault();
+
+ e.currentTarget.remove();
+ });
+
+ document.body.append(errorPopup);
+ }
+ })();
+
+ (function () {
+ // Factbook redirecting (1)
+
+ const redirectLink = dom.querySelector("a.redirect-link");
+ if (redirectLink != null) {
+ const redirectTarget = new URL(redirectLink.href, window.location);
+ const redirectTargetUrl = redirectTarget.pathname + redirectTarget.search + redirectTarget.hash;
+
+ if (window.localStorage.getItem("disableRedirect") === "true") {
+ clearChildren(redirectLink);
+ redirectLink.append(redirectTarget.pathname);
+ } else {
+ // The scope-block immediately below - labeled "Factbook redirecting (2)"
+ // checks if the key "redirectedFrom" is present in localStorage and, if
+ // so, removes it after some other processing. We don't want that to happen
+ // if we're setting it and then redirecting, so we have to put this code
+ // into a microtask so it waits until after the rest of this function executes.
+ window.queueMicrotask(() => {
+ window.localStorage.setItem("redirectedFrom", window.location.pathname);
+ window.location = redirectTargetUrl;
+ });
+ }
+ }
+
+ window.localStorage.removeItem("disableRedirect");
+ })();
+
+ (function () {
+ // Factbook redirecting (2)
+
+ const redirectSourceValue = window.localStorage.getItem("redirectedFrom");
+ if (redirectSourceValue != null) {
+ const redirectSource = new URL(redirectSourceValue, window.location.origin);
+ const redirectSourceUrl = redirectSource.pathname + redirectSource.search + redirectSource.hash;
+
+ const redirectIdValue = window.location.hash;
+ const redirectIds = dom.querySelectorAll("h1[data-redirect-id], h2[data-redirect-id], h3[data-redirect-id], h4[data-redirect-id], h5[data-redirect-id], h6[data-redirect-id]");
+ for (const redirectId of redirectIds) {
+ if (redirectId.getAttribute("data-redirect-id") !== redirectIdValue)
+ continue;
+
+ const pElement = document.createElement("p");
+ pElement.style.fontSize = "0.8em";
+ pElement.append("Redirected from ");
+
+ const aElement = document.createElement("a");
+ aElement.href = redirectSourceUrl;
+ aElement.append(redirectSource.pathname);
+ aElement.addEventListener("click", e => {
+ e.preventDefault();
+ window.localStorage.setItem("disableRedirect", "true");
+
+ // TODO implement Fetch+History loading
+ window.location = e.currentTarget.href;
+ });
+
+ pElement.append(aElement);
+ redirectId.after(pElement);
+ }
+
+ window.localStorage.removeItem("redirectedFrom");
+ }
+ })();
+
+ (function () {
+ // NUKE
+
+ const nukeBoxes = dom.querySelectorAll("span.nuke-box");
+ for (const nukeBox of nukeBoxes) {
+ const chatHistory = document.createElement("blockquote");
+ chatHistory.style.overflowY = "scroll";
+ chatHistory.style.height = "40vh";
+
+ const inputBox = document.createElement("input");
+ inputBox.classList.add("inline");
+ inputBox.style.flexGrow = "1";
+ inputBox.type = "text";
+ inputBox.placeholder = "Enter your message";
+
+ const enterBtn = document.createElement("input");
+ enterBtn.classList.add("inline");
+ enterBtn.style.flexShrink = "0";
+ enterBtn.type = "submit";
+ enterBtn.value = "Send";
+ enterBtn.disabled = true;
+
+ const inputForm = document.createElement("form");
+ inputForm.style.display = "flex";
+ inputForm.append(inputBox, enterBtn);
+
+ const container = document.createElement("div");
+ container.append(chatHistory, inputForm);
+ nukeBox.after(container);
+ nukeBox.remove();
+
+ const targetUrl = "ws" + window.location.href.substring(4) + "/ws?csrfToken=" + nukeBox.getAttribute("data-ws-csrf-token");
+ const webSock = new WebSocket(targetUrl);
+
+ inputForm.addEventListener("submit", e => {
+ e.preventDefault();
+ if (!e.submitter.disabled) {
+ webSock.send(inputBox.value);
+ inputBox.value = "";
+ enterBtn.disabled = true;
+ }
+ });
+
+ webSock.addEventListener("message", e => {
+ const data = JSON.parse(e.data);
+ if (data.type === "ready") {
+ enterBtn.disabled = false;
+ } else if (data.type === "user") {
+ const userP = document.createElement("p");
+ userP.style.textAlign = "right";
+ userP.style.paddingLeft = "50%";
+ appendWithLineBreaks(userP, data.text);
+ chatHistory.appendChild(userP);
+
+ const robotP = document.createElement("p");
+ robotP.style.textAlign = "left";
+ robotP.style.paddingRight = "50%";
+ chatHistory.appendChild(robotP);
+ } else if (data.type === "robot") {
+ const robotP = chatHistory.lastElementChild;
+ appendWithLineBreaks(robotP, data.text);
+ } else if (data.type === "cite") {
+ const robotP = chatHistory.lastElementChild;
+ const robotCiteList = robotP.appendChild(document.createElement("ol"));
+ for (const url of data.urls) {
+ const urlLink = robotCiteList.appendChild(document.createElement("li")).appendChild(document.createElement("a"));
+ urlLink.href = url;
+ urlLink.append(url);
+ }
+ }
+ });
+
+ webSock.addEventListener("close", e => {
+ const statusP = document.createElement("p");
+ statusP.style.textAlign = "center";
+ statusP.style.paddingLeft = "25%";
+ statusP.style.paddingRight = "25%";
+ appendWithLineBreaks(statusP, "The connection has been closed\n" + e.reason);
+ chatHistory.appendChild(statusP);
+
+ enterBtn.disabled = true;
+ });
+ }
+ })();
+ }
+
+ window.addEventListener("load", () => {
+ onDomLoad(document.documentElement);
+ });
})();