From c88b3699cbd43aa193d76c7e1eedff39b78f0a72 Mon Sep 17 00:00:00 2001 From: Max Berger Date: Mon, 2 Mar 2026 21:52:49 +0100 Subject: [PATCH] Added Proper type hinting and type checking --- .../web/internal_data/CollectionsScene.js | 36 +++++++++---------- .../CreateEditCollectionScene.js | 36 +++++++++---------- .../internal_data/DeleteCollectionScene.js | 16 ++++----- radicale/web/internal_data/LoginScene.js | 18 +++++----- .../web/internal_data/ShareCollectionScene.js | 19 +++++----- .../internal_data/UploadCollectionScene.js | 32 +++++++---------- radicale/web/jsconfig.json | 10 ++++++ 7 files changed, 87 insertions(+), 80 deletions(-) create mode 100644 radicale/web/jsconfig.json diff --git a/radicale/web/internal_data/CollectionsScene.js b/radicale/web/internal_data/CollectionsScene.js index 2c8b05fa..50ac10e3 100644 --- a/radicale/web/internal_data/CollectionsScene.js +++ b/radicale/web/internal_data/CollectionsScene.js @@ -25,7 +25,7 @@ import { UploadCollectionScene } from "./UploadCollectionScene.js"; import { DeleteCollectionScene } from "./DeleteCollectionScene.js"; import { LoadingScene } from "./LoadingScene.js"; import { get_collections } from "./api.js"; -import { CollectionType } from "./models.js"; +import { Collection, CollectionType } from "./models.js"; import { bytesToHumanReadable } from "./utils.js"; import { SERVER } from "./constants.js"; @@ -35,19 +35,19 @@ import { SERVER } from "./constants.js"; * @param {string} user * @param {string} password * @param {Collection} collection The principal collection. - * @param {function(string)} onerror Called when an error occurs, before the + * @param {function(string):void} onerror Called when an error occurs, before the * scene is popped. */ export function CollectionsScene(user, password, collection, onerror) { - let html_scene = document.getElementById("collectionsscene"); - let template = html_scene.querySelector("[data-name=collectiontemplate]"); - let new_btn = html_scene.querySelector("[data-name=new]"); - let upload_btn = html_scene.querySelector("[data-name=upload]"); + /** @type {HTMLElement} */ let html_scene = document.getElementById("collectionsscene"); + /** @type {HTMLElement} */ let template = html_scene.querySelector("[data-name=collectiontemplate]"); + /** @type {HTMLElement} */ let new_btn = html_scene.querySelector("[data-name=new]"); + /** @type {HTMLElement} */ let upload_btn = html_scene.querySelector("[data-name=upload]"); /** @type {?number} */ let scene_index = null; /** @type {?XMLHttpRequest} */ let collections_req = null; /** @type {?Array} */ let collections = null; - /** @type {Array} */ let nodes = []; + /** @type {Array} */ let nodes = []; function onnew() { try { @@ -62,7 +62,7 @@ export function CollectionsScene(user, password, collection, onerror) { function onupload() { try { let upload_scene = new UploadCollectionScene(user, password, collection); - push_scene(upload_scene); + push_scene(upload_scene, false); } catch(err) { console.error(err); } @@ -104,17 +104,17 @@ export function CollectionsScene(user, password, collection, onerror) { html_scene.style.marginTop = heightOfNavBar; html_scene.style.height = "calc(100vh - " + heightOfNavBar +")"; collections.forEach(function (collection) { - let node = template.cloneNode(true); + /** @type {HTMLElement} */ let node = template.cloneNode(true); node.classList.remove("hidden"); - let title_form = node.querySelector("[data-name=title]"); - let description_form = node.querySelector("[data-name=description]"); - let contentcount_form = node.querySelector("[data-name=contentcount]"); - let url_form = node.querySelector("[data-name=url]"); - let color_form = node.querySelector("[data-name=color]"); - let delete_btn = node.querySelector("[data-name=delete]"); - let edit_btn = node.querySelector("[data-name=edit]"); - let share_btn = node.querySelector("[data-name=share]"); - let download_btn = node.querySelector("[data-name=download]"); + /** @type {HTMLElement} */ let title_form = node.querySelector("[data-name=title]"); + /** @type {HTMLElement} */ let description_form = node.querySelector("[data-name=description]"); + /** @type {HTMLElement} */ let contentcount_form = node.querySelector("[data-name=contentcount]"); + /** @type {HTMLElement} */ let url_form = node.querySelector("[data-name=url]"); + /** @type {HTMLElement} */ let color_form = node.querySelector("[data-name=color]"); + /** @type {HTMLElement} */ let delete_btn = node.querySelector("[data-name=delete]"); + /** @type {HTMLElement} */ let edit_btn = node.querySelector("[data-name=edit]"); + /** @type {HTMLElement} */ let share_btn = node.querySelector("[data-name=share]"); + /** @type {HTMLAnchorElement} */ let download_btn = node.querySelector("[data-name=download]"); if (collection.color) { color_form.style.background = collection.color; } diff --git a/radicale/web/internal_data/CreateEditCollectionScene.js b/radicale/web/internal_data/CreateEditCollectionScene.js index 94791246..49982734 100644 --- a/radicale/web/internal_data/CreateEditCollectionScene.js +++ b/radicale/web/internal_data/CreateEditCollectionScene.js @@ -37,28 +37,28 @@ import { create_collection, edit_collection } from "./api.js"; export function CreateEditCollectionScene(user, password, collection) { let edit = collection.type !== CollectionType.PRINCIPAL; let html_scene = document.getElementById(edit ? "editcollectionscene" : "createcollectionscene"); - let title_form = edit ? html_scene.querySelector("[data-name=title]") : null; - let error_form = html_scene.querySelector("[data-name=error]"); - let href_form = html_scene.querySelector("[data-name=href]"); - let href_label = html_scene.querySelector("label[for=href]"); - let displayname_form = html_scene.querySelector("[data-name=displayname]"); - let displayname_label = html_scene.querySelector("label[for=displayname]"); - let description_form = html_scene.querySelector("[data-name=description]"); - let description_label = html_scene.querySelector("label[for=description]"); - let source_form = html_scene.querySelector("[data-name=source]"); - let source_label = html_scene.querySelector("label[for=source]"); - let type_form = html_scene.querySelector("[data-name=type]"); - let type_label = html_scene.querySelector("label[for=type]"); - let color_form = html_scene.querySelector("[data-name=color]"); - let color_label = html_scene.querySelector("label[for=color]"); - let submit_btn = html_scene.querySelector("[data-name=submit]"); - let cancel_btn = html_scene.querySelector("[data-name=cancel]"); + /** @type {HTMLElement} */ let title_form = edit ? html_scene.querySelector("[data-name=title]") : null; + /** @type {HTMLElement} */ let error_form = html_scene.querySelector("[data-name=error]"); + /** @type {HTMLInputElement} */ let href_form = html_scene.querySelector("[data-name=href]"); + /** @type {HTMLElement} */ let href_label = html_scene.querySelector("label[for=href]"); + /** @type {HTMLInputElement} */ let displayname_form = html_scene.querySelector("[data-name=displayname]"); + /** @type {HTMLElement} */ let displayname_label = html_scene.querySelector("label[for=displayname]"); + /** @type {HTMLInputElement} */ let description_form = html_scene.querySelector("[data-name=description]"); + /** @type {HTMLElement} */ let description_label = html_scene.querySelector("label[for=description]"); + /** @type {HTMLInputElement} */ let source_form = html_scene.querySelector("[data-name=source]"); + /** @type {HTMLElement} */ let source_label = html_scene.querySelector("label[for=source]"); + /** @type {HTMLSelectElement} */ let type_form = html_scene.querySelector("[data-name=type]"); + /** @type {HTMLElement} */ let type_label = html_scene.querySelector("label[for=type]"); + /** @type {HTMLInputElement} */ let color_form = html_scene.querySelector("[data-name=color]"); + /** @type {HTMLElement} */ let color_label = html_scene.querySelector("label[for=color]"); + /** @type {HTMLElement} */ let submit_btn = html_scene.querySelector("[data-name=submit]"); + /** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]"); /** @type {?number} */ let scene_index = null; /** @type {?XMLHttpRequest} */ let create_edit_req = null; let error = ""; - /** @type {?Element} */ let saved_type_form = null; + /** @type {?HTMLSelectElement} */ let saved_type_form = null; let href = edit ? collection.href : collection.href + random_uuid() + "/"; let displayname = edit ? collection.displayname : ""; @@ -137,7 +137,7 @@ export function CreateEditCollectionScene(user, password, collection) { sane_color = color_match[1]; } let loading_scene = new LoadingScene(); - push_scene(loading_scene); + push_scene(loading_scene, false); let collection = new Collection(href, type, displayname, description, sane_color, 0, 0, source); let callback = function(error1) { if (scene_index === null) { diff --git a/radicale/web/internal_data/DeleteCollectionScene.js b/radicale/web/internal_data/DeleteCollectionScene.js index fd1f9d98..c7cc1c93 100644 --- a/radicale/web/internal_data/DeleteCollectionScene.js +++ b/radicale/web/internal_data/DeleteCollectionScene.js @@ -32,13 +32,13 @@ import { delete_collection } from "./api.js"; * @param {Collection} collection */ export function DeleteCollectionScene(user, password, collection) { - let html_scene = document.getElementById("deletecollectionscene"); - let title_form = html_scene.querySelector("[data-name=title]"); - let error_form = html_scene.querySelector("[data-name=error]"); - let confirmation_txt = html_scene.querySelector("[data-name=confirmationtxt]"); - let delete_confirmation_lbl = html_scene.querySelector("[data-name=deleteconfirmationtext]"); - let delete_btn = html_scene.querySelector("[data-name=delete]"); - let cancel_btn = html_scene.querySelector("[data-name=cancel]"); + /** @type {HTMLElement} */ let html_scene = document.getElementById("deletecollectionscene"); + /** @type {HTMLElement} */ let title_form = html_scene.querySelector("[data-name=title]"); + /** @type {HTMLElement} */ let error_form = html_scene.querySelector("[data-name=error]"); + /** @type {HTMLInputElement} */ let confirmation_txt = html_scene.querySelector("[data-name=confirmationtxt]"); + /** @type {HTMLElement} */ let delete_confirmation_lbl = html_scene.querySelector("[data-name=deleteconfirmationtext]"); + /** @type {HTMLElement} */ let delete_btn = html_scene.querySelector("[data-name=delete]"); + /** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]"); delete_confirmation_lbl.innerHTML = DELETE_CONFIRMATION_TEXT; confirmation_txt.value = ""; @@ -56,7 +56,7 @@ export function DeleteCollectionScene(user, password, collection) { } try { let loading_scene = new LoadingScene(); - push_scene(loading_scene); + push_scene(loading_scene, false); delete_req = delete_collection(user, password, collection, function(error1) { if (scene_index === null) { return; diff --git a/radicale/web/internal_data/LoginScene.js b/radicale/web/internal_data/LoginScene.js index 63c68f2f..b6f1a68e 100644 --- a/radicale/web/internal_data/LoginScene.js +++ b/radicale/web/internal_data/LoginScene.js @@ -29,15 +29,15 @@ import { maybe_enable_sharing_options } from "./ShareCollectionScene.js"; * @implements {Scene} */ export function LoginScene() { - let html_scene = document.getElementById("loginscene"); - let form = html_scene.querySelector("[data-name=form]"); - let user_form = html_scene.querySelector("[data-name=user]"); - let password_form = html_scene.querySelector("[data-name=password]"); - let error_form = html_scene.querySelector("[data-name=error]"); - let logout_view = document.getElementById("logoutview"); - let logout_user_form = logout_view.querySelector("[data-name=user]"); - let logout_btn = logout_view.querySelector("[data-name=logout]"); - let refresh_btn = logout_view.querySelector("[data-name=refresh]"); + /** @type {HTMLElement} */ let html_scene = document.getElementById("loginscene"); + /** @type {HTMLElement} */ let form = html_scene.querySelector("[data-name=form]"); + /** @type {HTMLInputElement} */ let user_form = html_scene.querySelector("[data-name=user]"); + /** @type {HTMLInputElement} */ let password_form = html_scene.querySelector("[data-name=password]"); + /** @type {HTMLElement} */ let error_form = html_scene.querySelector("[data-name=error]"); + /** @type {HTMLElement} */ let logout_view = document.getElementById("logoutview"); + /** @type {HTMLElement} */ let logout_user_form = logout_view.querySelector("[data-name=user]"); + /** @type {HTMLElement} */ let logout_btn = logout_view.querySelector("[data-name=logout]"); + /** @type {HTMLElement} */ let refresh_btn = logout_view.querySelector("[data-name=refresh]"); /** @type {?number} */ let scene_index = null; let user = ""; diff --git a/radicale/web/internal_data/ShareCollectionScene.js b/radicale/web/internal_data/ShareCollectionScene.js index 37a830bf..ec357bc7 100644 --- a/radicale/web/internal_data/ShareCollectionScene.js +++ b/radicale/web/internal_data/ShareCollectionScene.js @@ -24,6 +24,7 @@ import { reload_sharing_list, server_features, } from "./api.js"; +import { Collection } from "./models.js"; import { pop_scene, scene_stack } from "./scene_manager.js"; /** @@ -38,15 +39,15 @@ export function CreateShareCollectionScene(user, password, collection) { let html_scene = document.getElementById("sharecollectionscene"); - let cancel_btn = html_scene.querySelector("[data-name=cancel]"); - let share_by_token_btn_ro = html_scene.querySelector( + /** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]"); + /** @type {HTMLElement} */ let share_by_token_btn_ro = html_scene.querySelector( "[data-name=sharebytoken_ro]", ); - let share_by_token_btn_rw = html_scene.querySelector( + /** @type {HTMLElement} */ let share_by_token_btn_rw = html_scene.querySelector( "[data-name=sharebytoken_rw]", ); - let title = html_scene.querySelector("[data-name=title]"); + /** @type {HTMLElement} */ let title = html_scene.querySelector("[data-name=title]"); function oncancel() { try { @@ -109,7 +110,7 @@ function update_share_list(user, password, collection) { } function add_share_rows(user, password, collection, shares) { - let template = document.querySelector("[data-name=sharetokenrowtemplate]"); + /** @type {HTMLElement} */ let template = document.querySelector("[data-name=sharetokenrowtemplate]"); shares.forEach(function (share) { let pathortoken = share["PathOrToken"] || ""; let pathmapped = share["PathMapped"] || ""; @@ -117,9 +118,10 @@ function add_share_rows(user, password, collection, shares) { collection.href.includes(pathmapped) || collection.href.includes(pathortoken) ) { - let node = template.cloneNode(true); + let node = /** @type {HTMLElement} */ (template.cloneNode(true)); node.classList.remove("hidden"); - node.querySelector("[data-name=pathortoken]").value = pathortoken; + /** @type {HTMLInputElement} */ let pathortoken_form = node.querySelector("[data-name=pathortoken]"); + pathortoken_form.value = pathortoken; let permissions = (share["Permissions"] || "").toLowerCase(); if (permissions === "rw") { node @@ -132,7 +134,8 @@ function add_share_rows(user, password, collection, shares) { } else { console.warn("Unknown permissions", permissions); } - node.querySelector("[data-name=delete]").onclick = function () { + /** @type {HTMLElement} */ let delete_btn = node.querySelector("[data-name=delete]"); + delete_btn.onclick = function () { delete_share_by_token( user, password, diff --git a/radicale/web/internal_data/UploadCollectionScene.js b/radicale/web/internal_data/UploadCollectionScene.js index fbc5b29f..2704f461 100644 --- a/radicale/web/internal_data/UploadCollectionScene.js +++ b/radicale/web/internal_data/UploadCollectionScene.js @@ -29,19 +29,18 @@ import { upload_collection } from "./api.js"; * @param {string} user * @param {string} password * @param {Collection} collection parent collection - * @param {Array} files */ export function UploadCollectionScene(user, password, collection) { - let html_scene = document.getElementById("uploadcollectionscene"); - let template = html_scene.querySelector("[data-name=filetemplate]"); - let upload_btn = html_scene.querySelector("[data-name=submit]"); - let close_btn = html_scene.querySelector("[data-name=close]"); - let uploadfile_form = html_scene.querySelector("[data-name=uploadfile]"); - let uploadfile_lbl = html_scene.querySelector("label[for=uploadfile]"); - let href_form = html_scene.querySelector("[data-name=href]"); - let href_label = html_scene.querySelector("label[for=href]"); - let hreflimitmsg_html = html_scene.querySelector("[data-name=hreflimitmsg]"); - let pending_html = html_scene.querySelector("[data-name=pending]"); + /** @type {HTMLElement} */ let html_scene = document.getElementById("uploadcollectionscene"); + /** @type {HTMLElement} */ let template = html_scene.querySelector("[data-name=filetemplate]"); + /** @type {HTMLElement} */ let upload_btn = html_scene.querySelector("[data-name=submit]"); + /** @type {HTMLElement} */ let close_btn = html_scene.querySelector("[data-name=close]"); + /** @type {HTMLInputElement} */ let uploadfile_form = html_scene.querySelector("[data-name=uploadfile]"); + /** @type {HTMLElement} */ let uploadfile_lbl = html_scene.querySelector("label[for=uploadfile]"); + /** @type {HTMLInputElement} */ let href_form = html_scene.querySelector("[data-name=href]"); + /** @type {HTMLElement} */ let href_label = html_scene.querySelector("label[for=href]"); + /** @type {HTMLElement} */ let hreflimitmsg_html = html_scene.querySelector("[data-name=hreflimitmsg]"); + /** @type {HTMLElement} */ let pending_html = html_scene.querySelector("[data-name=pending]"); let files = uploadfile_form.files; href_form.addEventListener("keydown", cleanHREFinput); @@ -49,11 +48,12 @@ export function UploadCollectionScene(user, password, collection) { uploadfile_form.onchange = onfileschange; href_form.value = ""; + let href = ""; /** @type {?number} */ let scene_index = null; /** @type {?XMLHttpRequest} */ let upload_req = null; /** @type {Array} */ let results = []; - /** @type {?Array} */ let nodes = null; + /** @type {?Array} */ let nodes = null; function upload_start() { try { @@ -73,7 +73,7 @@ export function UploadCollectionScene(user, password, collection) { nodes = []; for (let i = 0; i < files.length; i++) { let file = files[i]; - let node = template.cloneNode(true); + /** @type {HTMLElement} */ let node = template.cloneNode(true); node.classList.remove("hidden"); let name_form = node.querySelector("[data-name=name]"); name_form.textContent = file.name; @@ -180,12 +180,6 @@ export function UploadCollectionScene(user, password, collection) { scene_index = scene_stack.length - 1; html_scene.classList.remove("hidden"); close_btn.onclick = onclose; - if(error){ - error_form.textContent = "Error: " + error; - error_form.classList.remove("hidden"); - }else{ - error_form.classList.add("hidden"); - } }; this.hide = function() { diff --git a/radicale/web/jsconfig.json b/radicale/web/jsconfig.json new file mode 100644 index 00000000..06467fa5 --- /dev/null +++ b/radicale/web/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "target": "ES6", + "checkJs": true + }, + "include": [ + "internal_data/**/*.js" + ] +} \ No newline at end of file