diff --git a/public/boot/ctrl_boot_frontoffice.js b/public/boot/ctrl_boot_frontoffice.js index a00605f61..a7e1d70ca 100644 --- a/public/boot/ctrl_boot_frontoffice.js +++ b/public/boot/ctrl_boot_frontoffice.js @@ -57,13 +57,13 @@ function setup_translation() { selectedLanguage = "zh_tw"; break; default: - const userLanguage = window.navigator.language.split("-")[0]; + const userLanguage = window.navigator.language.split("-")[0] || "en"; const idx = [ "az", "be", "bg", "ca", "cs", "da", "de", "el", "es", "et", "eu", "fi", "fr", "gl", "hr", "hu", "id", "is", "it", "ja", "ka", "ko", "lt", "lv", "mn", "nb", "nl", "pl", "pt", "ro", "ru", "sk", "sl", "sr", "sv", "th", "tr", "uk", "vi", "zh" - ].indexOf(window.navigator.language.split("-")[0]); + ].indexOf(window.navigator.language.split("-")[0] || ""); if (idx !== -1) { selectedLanguage = userLanguage; } diff --git a/public/components/breadcrumb.js b/public/components/breadcrumb.js index 5540a0269..f1ffb0e2e 100644 --- a/public/components/breadcrumb.js +++ b/public/components/breadcrumb.js @@ -6,7 +6,7 @@ const css = await CSS(import.meta.url, "breadcrumb.css"); class ComponentBreadcrumb extends HTMLDivElement { constructor() { super(); - if (new window.URL(location.href).searchParams.get("nav") === "false") return null; + if (new window.URL(location.href).searchParams.get("nav") === "false") return; const htmlLogout = isRunningFromAnIframe ? "" diff --git a/public/components/form.js b/public/components/form.js index bd996f430..135e012aa 100644 --- a/public/components/form.js +++ b/public/components/form.js @@ -1,5 +1,6 @@ import { createElement } from "../lib/skeleton/index.js"; import { gid } from "../lib/random.js"; +import { ApplicationError } from "../lib/error.js"; import "./icon.js"; @@ -76,7 +77,8 @@ export function $renderInput(options = {}) { class="component_input" /> `); - $input.setAttribute("value", value || ""); + if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); if (!datalist) return $input; @@ -94,17 +96,19 @@ export function $renderInput(options = {}) { $datalist.appendChild(new Option(value)); }); if (!props.multi) return $wrapper; + // @ts-ignore $input.refresh = () => { - const _datalist = $input.getAttribute("datalist").split(","); + const _datalist = $input?.getAttribute("datalist")?.split(","); $datalist.innerHTML = ""; multicomplete($input.getAttribute("value"), _datalist).forEach((value) => { $datalist.appendChild(new Option(value)); }); }; - $input.oninput = (e) => { + $input.oninput = () => { for (const $option of $datalist.children) { $option.remove(); } + // @ts-ignore $input.refresh(); }; return $wrapper; @@ -130,7 +134,8 @@ export function $renderInput(options = {}) { class="component_input" /> `); - $input.setAttribute("value", value); + if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } @@ -145,14 +150,16 @@ export function $renderInput(options = {}) { `); const $input = $div.querySelector("input"); - $input.value = value; + if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); const $icon = $div.querySelector("component-icon"); if ($icon instanceof window.HTMLElement) { $icon.onclick = function(e) { if (!(e.target instanceof window.HTMLElement)) return; - const $input = e.target.parentElement.previousElementSibling; + const $input = e.target?.parentElement?.previousElementSibling; + if (!$input) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); if ($input.getAttribute("type") === "password") $input.setAttribute("type", "text"); else $input.setAttribute("type", "password"); }; @@ -166,7 +173,8 @@ export function $renderInput(options = {}) { rows="8" > `); - $textarea.setAttribute("value", value); + if (!($textarea instanceof window.HTMLTextAreaElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $textarea.value = value; attrs.map((setAttribute) => setAttribute($textarea)); return $textarea; } @@ -178,7 +186,8 @@ export function $renderInput(options = {}) { readonly /> `); - $input.setAttribute("value", value); + if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } @@ -186,7 +195,8 @@ export function $renderInput(options = {}) { const $input = createElement(` `); - $input.setAttribute("value", value); + if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $input.value = value; $input.setAttribute("name", path.join(".")); return $input; } @@ -208,7 +218,8 @@ export function $renderInput(options = {}) { const $select = createElement(` `); - $select.setAttribute("value", value || props.default); + if (!($select instanceof window.HTMLSelectElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $select.value = value || props.default; attrs.map((setAttribute) => setAttribute($select)); (options || []).forEach((name) => { const $option = createElement(` @@ -230,7 +241,8 @@ export function $renderInput(options = {}) { class="component_input" /> `); - $input.setAttribute("value", value); + if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } @@ -241,7 +253,8 @@ export function $renderInput(options = {}) { class="component_input" /> `); - $input.setAttribute("value", value); + if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); + else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } @@ -285,7 +298,7 @@ export function format(name) { if (word.length < 1) { return word; } - return word[0].toUpperCase() + word.substring(1); + return (word[0] || "").toUpperCase() + word.substring(1); }) .join(" "); }; diff --git a/public/components/loader.js b/public/components/loader.js index 2a09618a5..ca3dc49b6 100644 --- a/public/components/loader.js +++ b/public/components/loader.js @@ -8,7 +8,7 @@ class Loader extends window.HTMLElement { this.innerHTML = this.render({ inline: this.hasAttribute("inlined"), }); - }, parseInt(this.getAttribute("delay")) || 0); + }, parseInt(this.getAttribute("delay") || "0")); } disconnectedCallback() { diff --git a/public/components/notification.js b/public/components/notification.js index af32beff8..91b84264c 100644 --- a/public/components/notification.js +++ b/public/components/notification.js @@ -25,7 +25,8 @@ class NotificationComponent extends window.HTMLElement { this.buffer.push({ message, type }); if (this.buffer.length !== 1) { const $close = this.querySelector(".close"); - if ($close && typeof $close.onclick === "function") $close.onclick(); + if (!($close instanceof window.HTMLElement) || !$close.onclick) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: notification close button missing"); + $close.onclick(new window.MouseEvent("mousedown")); return; } await this.run(); @@ -42,8 +43,14 @@ class NotificationComponent extends window.HTMLElement { }); const ids = []; await Promise.race([ - new Promise((done) => ids.push(window.setTimeout(done, this.buffer.length === 1 ? 8000 : 800))), - new Promise((done) => ids.push(window.setTimeout(() => $notification.querySelector(".close").onclick = done, 1000))), + new Promise((done) => ids.push(window.setTimeout(() => { + done(new window.MouseEvent("mousedown")); + }, this.buffer.length === 1 ? 8000 : 800))), + new Promise((done) => ids.push(window.setTimeout(() => { + const $close = $notification.querySelector(".close"); + if (!($close instanceof window.HTMLElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: notification close button missing"); + $close.onclick = done; + }, 1000))), ]); ids.forEach((id) => window.clearTimeout(id)); await animate($notification, { diff --git a/public/helpers/loader.js b/public/helpers/loader.js index d9615d7fa..a19dcbadf 100644 --- a/public/helpers/loader.js +++ b/public/helpers/loader.js @@ -22,7 +22,7 @@ async function loadSingleCSS(baseURL, filename) { cache: "force-cache", }); if (res.status !== 200) return `/* ERROR: ${res.status} */`; - else if (!res.headers.get("Content-Type").startsWith("text/css")) return `/* ERROR: wrong type, got "${res.headers.get("Content-Type")}"*/`; + else if (!res.headers.get("Content-Type")?.startsWith("text/css")) `/* ERROR: wrong type, got "${res.headers?.get("Content-Type")}"*/`; return await res.text(); } diff --git a/public/lib/form.js b/public/lib/form.js index 211cf9053..2fbf1fa31 100644 --- a/public/lib/form.js +++ b/public/lib/form.js @@ -1,4 +1,5 @@ import { createElement } from "./skeleton/index.js"; +import { ApplicationError } from "./error.js"; import { animate } from "./animate.js"; export function mutateForm(formSpec, formState) { @@ -7,8 +8,12 @@ export function mutateForm(formSpec, formState) { const keys = inputName.split("."); let ptr = formSpec; - while (keys.length > 1) ptr = ptr[keys.shift()]; - const key = keys.shift(); + while (keys.length > 1) { + let k = keys.shift(); + if (!k) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing key"); + ptr = ptr[k]; + } + const key = keys.shift() || ""; if (ptr && ptr[key]) ptr[key].value = (value === "" ? null : value); }); return formSpec; diff --git a/public/lib/locales.js b/public/lib/locales.js index 6dfab72d8..7c367a149 100644 --- a/public/lib/locales.js +++ b/public/lib/locales.js @@ -1,21 +1,20 @@ export default function t(str = "", replacementString, requestedKey) { - return str; - // const calculatedKey = str.toUpperCase() - // .replace(/ /g, "_") - // .replace(/[^a-zA-Z0-9\-\_\*\{\}\?]/g, "") - // .replace(/\_+$/, ""); - // const value = requestedKey === undefined ? - // window.LNG && window.LNG[calculatedKey] : - // window.LNG && window.LNG[requestedKey]; - // return reformat( - // value || str || "", - // str, - // ).replace("{{VALUE}}", replacementString); + const calculatedKey = str.toUpperCase() + .replace(/ /g, "_") + .replace(/[^a-zA-Z0-9\-\_\*\{\}\?]/g, "") + .replace(/\_+$/, ""); + const value = requestedKey === undefined ? + window.LNG && window.LNG[calculatedKey] : + window.LNG && window.LNG[requestedKey]; + return reformat( + value || str || "", + str, + ).replace("{{VALUE}}", replacementString); } -// function reformat(translated, initial) { -// if (initial[0] && initial[0].toLowerCase() === initial[0]) { -// return translated || ""; -// } -// return (translated[0] && translated[0].toUpperCase() + translated.substring(1)) || ""; -// } +function reformat(translated, initial) { + if (initial[0] && initial[0].toLowerCase() === initial[0]) { + return translated || ""; + } + return (translated[0] && translated[0].toUpperCase() + translated.substring(1)) || ""; +} diff --git a/public/lib/skeleton/router.test.js b/public/lib/skeleton/router.test.js index 2016f5090..7e182960f 100644 --- a/public/lib/skeleton/router.test.js +++ b/public/lib/skeleton/router.test.js @@ -43,7 +43,7 @@ describe("router", () => { // then expect(fn).toBeCalled(); }); - xit("trigger a page change when clicking on a link with [data-link] attribute", () => { + it("trigger a page change when clicking on a link with [data-link] attribute", () => { // given const fn = jest.fn(); const $link = createElement(""); diff --git a/public/lib/vendor/bcrypt.js b/public/lib/vendor/bcrypt.js index cfd12ca73..150725da1 100644 --- a/public/lib/vendor/bcrypt.js +++ b/public/lib/vendor/bcrypt.js @@ -1,3 +1,4 @@ +// @ts-nocheck // code was adapted from https://github.com/dcodeIO/bcrypt.js, meaning: // - we took the code from a CDN https://cdnjs.cloudflare.com/ajax/libs/bcryptjs/2.2.0/bcrypt.js // - remove the amd,commonJS stuff on the top of the file diff --git a/public/pages/adminpage/component_box-item.js b/public/pages/adminpage/component_box-item.js index 073f4fee5..97cd9686d 100644 --- a/public/pages/adminpage/component_box-item.js +++ b/public/pages/adminpage/component_box-item.js @@ -1,3 +1,5 @@ +import { ApplicationError } from "../../lib/error.js"; + class BoxItem extends window.HTMLDivElement { constructor() { super(); @@ -11,12 +13,11 @@ class BoxItem extends window.HTMLDivElement { attributeChangedCallback() { this.innerHTML = this.render({ label: this.getAttribute("data-label"), - selected: false, }); this.classList.add("box-item", "pointer", "no-select"); } - render({ label, selected }) { + render({ label }) { return `