chore (tsc): type check and linter

This commit is contained in:
MickaelK 2023-10-20 00:20:26 +11:00
parent cdfc614c8b
commit 5a09186770
22 changed files with 124 additions and 68 deletions

View file

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

View file

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

View file

@ -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 = {}) {
</div>
`);
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>
`);
$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 type="hidden" />
`);
$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 class="component_select"></select>
`);
$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(" ");
};

View file

@ -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() {

View file

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

View file

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

View file

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

View file

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

View file

@ -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("<a href=\"/something\" data-link></a>");

View file

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

View file

@ -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 `
<div>
<strong>${label}</strong>
@ -30,6 +31,7 @@ class BoxItem extends window.HTMLDivElement {
toggleSelection(opt = {}) {
const { tmpl, isSelected = !this.classList.contains("active") } = opt;
const $icon = this.querySelector(".icon");
if (!$icon) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: no icon");
if (isSelected) {
this.classList.add("active");
if (tmpl) $icon.innerHTML = tmpl;

View file

@ -2,6 +2,7 @@ import { createElement } from "../../lib/skeleton/index.js";
import rxjs, { effect, applyMutation, applyMutations, onClick } from "../../lib/rx.js";
import { createForm, mutateForm } from "../../lib/form.js";
import { qs, qsa } from "../../lib/dom.js";
import { ApplicationError } from "../../lib/error.js";
import { formTmpl } from "../../components/form.js";
import { generateSkeleton } from "../../components/skeleton.js";
@ -77,7 +78,10 @@ export default async function(render) {
})),
rxjs.map((idpState) => [availableSpecs, idpState]),
)),
rxjs.concatMap(async([availableSpecs, idpState = {}]) => {
rxjs.concatMap(async([
availableSpecs,
idpState = { type: null, params: null },
]) => {
const { type, params } = idpState;
const idps = [];
for (const key in availableSpecs) {
@ -167,7 +171,9 @@ export default async function(render) {
rxjs.map((connections) => connections.map(({ label }) => label)),
rxjs.tap((datalist) => {
const $input = $page.querySelector("[name=\"attribute_mapping.related_backend\"]");
if (!$input) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing related backend");
$input.setAttribute("datalist", datalist.join(","));
// @ts-ignore
$input.refresh();
}),
));
@ -238,6 +244,7 @@ export default async function(render) {
renderLeaf: () => createElement("<label></label>"),
}))),
rxjs.tap(($node) => {
/** @type { Element | undefined} */
let $relatedBackendField;
$page.querySelectorAll("[data-bind=\"attribute-mapping\"] fieldset").forEach(($el, i) => {
if (i === 0) $relatedBackendField = $el;

View file

@ -1,5 +1,6 @@
import rxjs from "../../lib/rx.js";
import { qs } from "../../lib/dom.js";
import { ApplicationError } from "../../lib/error.js";
import { get as getConfig } from "../../model/config.js";
import { get as getAdminConfig } from "./model_config.js";
import { formObjToJSON$ } from "./helper_form.js";
@ -92,15 +93,18 @@ export function getState() {
};
if (!authType) return config;
let formValues = [...new FormData(document.querySelector("[data-bind=\"idp\"]"))];
const $formIDP = document.querySelector("[data-bind=\"idp\"]")
if (!($formIDP instanceof window.HTMLFormElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: idp isn't a form");
let formValues = [...new FormData($formIDP)];
config.middleware.identity_provider = {
type: authType,
params: JSON.stringify(
formValues
.filter(([key, value]) => key.startsWith(`${authType}.`)) // remove elements that aren't in scope
.filter(([key]) => key.startsWith(`${authType}.`)) // remove elements that aren't in scope
.map(([key, value]) => [key.replace(new RegExp(`^${authType}\.`), ""), value]) // format the relevant keys
.reduce((acc, [key, value]) => { // transform onto something ready to be saved
if (key === "type") return acc;
else if (typeof key !== "string") return acc;
return {
...acc,
[key]: value,
@ -109,14 +113,16 @@ export function getState() {
),
};
formValues = [...new FormData(document.querySelector("[data-bind=\"attribute-mapping\"]"))];
const $formAM = document.querySelector("[data-bind=\"attribute-mapping\"]");
if (!($formAM instanceof window.HTMLFormElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: attribute mapping isn't a form");
formValues = [...new FormData($formAM)];
config.middleware.attribute_mapping = {
related_backend: formValues.shift()[1],
related_backend: (formValues.shift() || [])[1],
params: JSON.stringify(formValues.reduce((acc, [key, value]) => {
const k = key.split(".");
if (k.length !== 2) return acc;
if (!acc[k[0]]) acc[k[0]] = {};
if (value !== "") acc[k[0]][k[1]] = value;
if (!acc[`${k[0]}`]) acc[`${k[0]}`] = {};
if (value !== "") acc[`${k[0]}`][`${k[1]}`] = value;
return acc;
}, {})),
};

View file

@ -57,7 +57,7 @@ function updateLoop($page, audit$) {
const p = new URLSearchParams();
for (const [key, value] of formData.entries()) {
if (!value) continue;
p.set(key.replace(new RegExp("^search\."), ""), value);
p.set(key.replace(new RegExp("^search\."), ""), `${value}`);
}
return p;
}),

View file

@ -1,7 +1,7 @@
import { createElement } from "../../lib/skeleton/index.js";
import rxjs from "../../lib/rx.js";
export function renderLeaf({ format, type, label, description }) {
export function renderLeaf({ format, label, description }) {
return createElement(`
<label class="no-select">
<div class="flex">

View file

@ -1,7 +1,7 @@
import rxjs from "../../lib/rx.js";
import ajax from "../../lib/ajax.js";
const sessionSubject$ = new rxjs.Subject(1);
const sessionSubject$ = new rxjs.Subject();
const adminSession$ = rxjs.merge(
sessionSubject$,

View file

@ -8,7 +8,7 @@ const log$ = ajax({
rxjs.map(({ response }) => response),
);
export function url(logSize = null) {
export function url(logSize = 0) {
return "/admin/api/logs" + (logSize ? `?maxSize=${logSize}` : "");
}

View file

@ -12,7 +12,7 @@ export function get() {
const a = document.createElement("html");
a.innerHTML = response;
return {
html: a.querySelector("table").outerHTML,
html: a.querySelector("table")?.outerHTML,
version: responseHeaders["x-powered-by"].trim().replace(/^Filestash\/([v\.0-9]*).*$/, "$1")
};
})

View file

@ -58,7 +58,7 @@ export default async function(render) {
// feature4: insert all the connection form
const tmpl = formTmpl({
renderNode: () => createElement("<div></div>"),
renderLeaf: ({ label, type, format }) => {
renderLeaf: ({ label, type }) => {
if (type === "enable") {
return createElement(`
<label class="advanced">

View file

@ -29,9 +29,9 @@ export default async function(render) {
if (!files[i]) continue;
$fs.appendChild(createThing({
name: files[i].name,
type: files[i].type,
size: files[i].size,
time: files[i].time,
// type: files[i].type,
// size: files[i].size,
// time: files[i].time,
link: (files[i].type === "file" ? "/view" : "/files") + path + files[i].name + (files[i].type === "file" ? "" : "/"),
}));
}

View file

@ -33,17 +33,17 @@ const $tmpl = createElement(`
// drag and drop on other folders and many other non obvious stuff
export function createThing({
name = null,
type = "N/A",
size = 0,
time = null,
// type = "N/A",
// size = 0,
// time = null,
link = "",
permissions = {}
// permissions = {}
}) {
const $thing = $tmpl.cloneNode(true);
if ($thing instanceof HTMLElement) {
const $label = $thing.querySelector(".component_filename .file-details > span");
if ($label instanceof HTMLElement) $label.textContent = name;
$thing.querySelector("a").setAttribute("href", link);
$thing?.querySelector("a")?.setAttribute("href", link);
}
return $thing;
}

View file

@ -3,10 +3,21 @@
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUncheckedIndexedAccess": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strict": false,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"lib": [
"dom",
"dom.iterable",
@ -14,8 +25,13 @@
],
"module": "es2022",
"target": "es2022",
"types": []
"typeRoots": [
"types/app.d.ts"
]
},
"input": ["pages/ctrl_boot.d.ts", "pages/*.js"],
"exclude": ["**/*.test.js", "lib/vendor/bcrypt.js", "worker/sw_cache.js", "coverage", "jest.setup.js"]
"exclude": [
"**/*.test.js", "worker/sw_cache.js",
"coverage", "jest.setup.js"
]
}