Address null-safety identified by TSC compiler v6

There are no actual functional changes. This just adds many additional
annotations and function which ensure type safety and null safety.
This commit is contained in:
Max Berger
2026-03-25 21:32:32 +01:00
parent fd067a6a59
commit 951cfde278
19 changed files with 461 additions and 350 deletions

View File

@@ -25,7 +25,7 @@ import { SERVER } from "../constants.js";
import { Collection, CollectionType } from "../models/collection.js";
import { collectionsCache } from "../utils/collections_cache.js";
import { ErrorHandler } from "../utils/error.js";
import { bytesToHumanReadable } from "../utils/misc.js";
import { bytesToHumanReadable, get_element, get_element_by_id } from "../utils/misc.js";
import { CreateEditCollectionScene } from "./CreateEditCollectionScene.js";
import { DeleteConfirmationScene } from "./DeleteConfirmationScene.js";
import { IncomingSharingScene } from "./IncomingSharingScene.js";
@@ -39,18 +39,18 @@ import { UploadCollectionScene } from "./UploadCollectionScene.js";
export class CollectionsScene {
/**
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {Collection} principal_collection The princial collection
* @param {function(string):void} onerror Called when an error occurs, before the
* @param {function(?string):void} onerror Called when an error occurs, before the
* scene is popped.
*/
constructor(user, password, principal_collection, onerror) {
/** @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 {HTMLElement} */ let incomingshares_btn = html_scene.querySelector("[data-name=incomingshares]");
/** @type {HTMLElement} */ let error_div = html_scene.querySelector("[data-name=collectionsscene_error]");
/** @type {HTMLElement} */ let html_scene = get_element_by_id("collectionsscene");
/** @type {HTMLElement} */ let template = get_element(html_scene, "[data-name=collectiontemplate]");
/** @type {HTMLElement} */ let new_btn = get_element(html_scene, "[data-name=new]");
/** @type {HTMLElement} */ let upload_btn = get_element(html_scene, "[data-name=upload]");
/** @type {HTMLElement} */ let incomingshares_btn = get_element(html_scene, "[data-name=incomingshares]");
/** @type {HTMLElement} */ let error_div = get_element(html_scene, "[data-name=collectionsscene_error]");
/** @type {Array<HTMLElement>} */ let nodes = [];
let errorHandler = new ErrorHandler(error_div);
@@ -133,7 +133,7 @@ export class CollectionsScene {
* @param {boolean} clear_error
*/
function show_collections(collections, shares, clear_error) {
/** @type {HTMLElement} */ let navBar = document.querySelector("#logoutview");
/** @type {HTMLElement} */ let navBar = get_element(document, "#logoutview");
let heightOfNavBar = navBar.offsetHeight + "px";
html_scene.style.marginTop = heightOfNavBar;
html_scene.style.height = "calc(100vh - " + heightOfNavBar + ")";
@@ -144,22 +144,24 @@ export class CollectionsScene {
// Clear old nodes
nodes.forEach(function (node) {
node.parentNode.removeChild(node);
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = [];
collections.forEach(function (/** @type {Collection} */ collection) {
/** @type {HTMLElement} */ let node = /** @type {HTMLElement} */(template.cloneNode(true));
node.classList.remove("hidden");
/** @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 {HTMLInputElement} */ 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]");
/** @type {HTMLElement} */ let title_form = get_element(node, "[data-name=title]");
/** @type {HTMLElement} */ let description_form = get_element(node, "[data-name=description]");
/** @type {HTMLElement} */ let contentcount_form = get_element(node, "[data-name=contentcount]");
/** @type {HTMLInputElement} */ let url_form = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=url]"));
/** @type {HTMLElement} */ let color_form = get_element(node, "[data-name=color]");
/** @type {HTMLElement} */ let delete_btn = get_element(node, "[data-name=delete]");
/** @type {HTMLElement} */ let edit_btn = get_element(node, "[data-name=edit]");
/** @type {HTMLElement} */ let share_btn = get_element(node, "[data-name=share]");
/** @type {HTMLAnchorElement} */ let download_btn = /** @type {HTMLAnchorElement} */ (get_element(node, "[data-name=download]"));
if (collection.color) {
color_form.style.background = collection.color;
}
@@ -175,22 +177,22 @@ export class CollectionsScene {
});
possible_types.forEach(function (e) {
if (e !== collection.type) {
node.querySelector("[data-name=" + e + "]").classList.add("hidden");
get_element(node, "[data-name=" + e + "]").classList.add("hidden");
}
});
let share_info = node.querySelector("[data-name=shared-by]");
let transformed_from = node.querySelector("[data-name=transformed-from]");
let share_info = get_element(node, "[data-name=shared-by]");
let transformed_from = get_element(node, "[data-name=transformed-from]");
let share = (shares || []).find(
s => (s.ShareType === "map" || s.ShareType === "bday") &&
(s.PathOrToken || "").replace(/\/+$/, "") === (collection.href || "").replace(/\/+$/, ""));
if (share) {
if (share.Owner !== user) {
share_info.classList.remove("hidden");
node.querySelector("[data-name=shared-by-owner]").textContent = share.Owner;
get_element(node, "[data-name=shared-by-owner]").textContent = share.Owner;
} else {
transformed_from.classList.remove("hidden");
}
let share_option = node.querySelector("[data-name=shareoption]");
let share_option = get_element(node, "[data-name=shareoption]");
if (share_option) {
share_option.classList.add("hidden");
share_option.removeAttribute("data-name");
@@ -223,10 +225,10 @@ export class CollectionsScene {
download_btn.onclick = function (event) {
event.preventDefault();
let auth = get_auth_header(user, password);
let headers = auth ? { 'Authorization': auth } : {};
fetch(href, {
headers: headers
}).then(function (response) {
let headers = auth ? {
'Authorization': auth
} : undefined;
fetch(href, { headers: headers }).then(function (response) {
if (response.ok) {
return response.blob();
}
@@ -245,14 +247,18 @@ export class CollectionsScene {
});
};
if (collection.type == CollectionType.WEBCAL) {
download_btn.parentElement.classList.add("hidden");
if (download_btn.parentElement) {
download_btn.parentElement.classList.add("hidden");
}
}
delete_btn.onclick = function () { return ondelete(collection); };
edit_btn.onclick = function () { return onedit(collection); };
share_btn.onclick = function () { return onshare(collection); };
node.classList.remove("hidden");
nodes.push(node);
template.parentNode.insertBefore(node, template);
if (template.parentNode) {
template.parentNode.insertBefore(node, template);
}
});
}
@@ -276,7 +282,9 @@ export class CollectionsScene {
incomingshares_btn.onclick = null;
// remove collection
nodes.forEach(function (node) {
node.parentNode.removeChild(node);
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = [];
};

View File

@@ -25,7 +25,7 @@ import { Collection, CollectionType } from "../models/collection.js";
import { collectionsCache } from "../utils/collections_cache.js";
import { ErrorHandler } from "../utils/error.js";
import { FormValidator, validate_color, validate_href } from "../utils/form_validator.js";
import { cleanHREFinput, onCleanHREFinput, random_hex, random_uuid } from "../utils/misc.js";
import { cleanHREFinput, get_element, get_element_by_id, onCleanHREFinput, random_hex, random_uuid } from "../utils/misc.js";
import { LoadingScene } from "./LoadingScene.js";
import { Scene, is_current_scene, pop_scene, pop_to_parent, push_scene } from "./scene_manager.js";
@@ -35,25 +35,25 @@ import { Scene, is_current_scene, pop_scene, pop_to_parent, push_scene } from ".
export class CreateEditCollectionScene {
/**
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {Collection} collection if it's a principal collection, a new
* collection will be created inside of it.
* Otherwise the collection will be edited.
*/
constructor(user, password, collection) {
let edit = collection.type !== CollectionType.PRINCIPAL;
let html_scene = document.getElementById(edit ? "editcollectionscene" : "createcollectionscene");
/** @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 {HTMLInputElement} */ let displayname_form = html_scene.querySelector("[data-name=displayname]");
/** @type {HTMLInputElement} */ let description_form = html_scene.querySelector("[data-name=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 {HTMLInputElement} */ let color_form = html_scene.querySelector("[data-name=color]");
/** @type {HTMLElement} */ let submit_btn = html_scene.querySelector("[data-name=submit]");
/** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]");
let html_scene = get_element_by_id(edit ? "editcollectionscene" : "createcollectionscene");
/** @type {?HTMLElement} */ let title_form = edit ? get_element(html_scene, "[data-name=title]") : null;
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
/** @type {?HTMLInputElement} */ let href_form = edit ? null : /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=href]"));
/** @type {HTMLInputElement} */ let displayname_form = /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=displayname]"));
/** @type {HTMLInputElement} */ let description_form = /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=description]"));
/** @type {HTMLInputElement} */ let source_form = /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=source]"));
/** @type {HTMLElement} */ let source_label = get_element(html_scene, "label[for=source]");
/** @type {HTMLSelectElement} */ let type_form = /** @type {HTMLSelectElement} */(get_element(html_scene, "[data-name=type]"));
/** @type {HTMLInputElement} */ let color_form = /** @type {HTMLInputElement} */(get_element(html_scene, "[data-name=color]"));
/** @type {HTMLElement} */ let submit_btn = get_element(html_scene, "[data-name=submit]");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
/** @type {?XMLHttpRequest} */ let create_edit_req = null;
@@ -62,7 +62,7 @@ export class CreateEditCollectionScene {
let errorHandler = new ErrorHandler(error_form);
let validator = new FormValidator(errorHandler);
if (!edit) {
if (!edit && href_form) {
validator.addValidator(href_form, validate_href(href_form, "HREF"));
}
validator.addValidator(color_form, validate_color(color_form, "Color"));
@@ -74,7 +74,7 @@ export class CreateEditCollectionScene {
let type = edit ? collection.type : CollectionType.CALENDAR_JOURNAL_TASKS;
let color = edit && collection.color ? collection.color : "#" + random_hex(6);
if (!edit) {
if (!edit && href_form) {
href_form.addEventListener("input", onCleanHREFinput);
}
@@ -93,7 +93,7 @@ export class CreateEditCollectionScene {
}
function read_form() {
if (!edit) {
if (!edit && href_form) {
cleanHREFinput(href_form);
let newhreftxtvalue = href_form.value.trim().toLowerCase();
href = collection.href + newhreftxtvalue + "/";
@@ -107,7 +107,7 @@ export class CreateEditCollectionScene {
}
function fill_form() {
if (!edit) {
if (!edit && href_form) {
href_form.value = random_uuid();
}
displayname_form.value = displayname;
@@ -127,12 +127,15 @@ export class CreateEditCollectionScene {
read_form();
let sane_color = color.trim();
if (sane_color) {
sane_color = COLOR_RE.exec(sane_color)[1];
/** @type {?RegExpExecArray} */ let match = COLOR_RE.exec(sane_color);
if (match) {
sane_color = match[1];
}
}
let loading_scene = new LoadingScene();
push_scene(loading_scene);
let collection = new Collection(href, type, displayname, description, sane_color, 0, 0, source);
let callback = function (error1) {
let callback = function (/** @type {?string} */ error1) {
if (!is_current_scene(loading_scene)) {
return;
}
@@ -166,7 +169,7 @@ export class CreateEditCollectionScene {
}
/**
* @param {Event} _e
* @param {?Event} _e
*/
function onTypeChange(_e) {
if (type_form.value == CollectionType.WEBCAL) {
@@ -183,10 +186,12 @@ export class CreateEditCollectionScene {
// Clone type_form because it's impossible to hide options without removing them
saved_type_form = type_form;
type_form = /** @type {HTMLSelectElement} */ (type_form.cloneNode(true));
saved_type_form.parentNode.replaceChild(type_form, saved_type_form);
if (saved_type_form.parentNode) {
saved_type_form.parentNode.replaceChild(type_form, saved_type_form);
}
remove_invalid_types();
html_scene.classList.remove("hidden");
if (edit) {
if (edit && title_form) {
title_form.textContent = collection.displayname || collection.href;
}
fill_form();
@@ -198,8 +203,10 @@ export class CreateEditCollectionScene {
read_form();
html_scene.classList.add("hidden");
// restore type_form
type_form.parentNode.replaceChild(saved_type_form, type_form);
type_form = saved_type_form;
if (type_form.parentNode && saved_type_form) {
type_form.parentNode.replaceChild(saved_type_form, type_form);
type_form = saved_type_form;
}
saved_type_form = null;
submit_btn.onclick = null;
cancel_btn.onclick = null;

View File

@@ -24,7 +24,7 @@ import { CollectionType } from "../models/collection.js";
import { collectionsCache } from "../utils/collections_cache.js";
import { ErrorHandler } from "../utils/error.js";
import { FormValidator, validate_href, validate_non_empty, validate_not_empty_or_equals } from "../utils/form_validator.js";
import { onCleanHREFinput, random_uuid } from "../utils/misc.js";
import { get_element, get_element_by_id, onCleanHREFinput, random_uuid } from "../utils/misc.js";
import { Scene, is_current_scene, pop_scene } from "./scene_manager.js";
/**
@@ -33,7 +33,7 @@ import { Scene, is_current_scene, pop_scene } from "./scene_manager.js";
export class CreateEditShareScene {
/**
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {import("../models/collection.js").Collection} collection
* @param {string} shareType
* @param {Share} [share] If provided, the scene will be in edit mode.
@@ -42,27 +42,27 @@ export class CreateEditShareScene {
let self = this;
let edit = !!share;
let pathMapped = collection.href;
/** @type {HTMLElement} */ let html_scene = document.getElementById("newshare");
/** @type {HTMLFormElement} */ let form = html_scene.querySelector("form");
/** @type {HTMLElement} */ let sharemapfields = html_scene.querySelector("[data-name=sharemapfields]");
/** @type {HTMLInputElement} */ let shareuser_input = html_scene.querySelector("[data-name=shareuser]");
/** @type {HTMLInputElement} */ let sharehref_input = html_scene.querySelector("[data-name=sharehref]");
/** @type {HTMLInputElement} */ let enabled_checkbox = html_scene.querySelector("[data-name=enabled]");
/** @type {HTMLInputElement} */ let hidden_checkbox = html_scene.querySelector("[data-name=hidden]");
let permissions_ro_radio = /** @type {HTMLInputElement} */ (document.getElementById("newshare_attr_permissions_ro"));
let permissions_rw_radio = /** @type {HTMLInputElement} */ (document.getElementById("newshare_attr_permissions_rw"));
/** @type {HTMLElement} */ let html_scene = get_element_by_id("newshare");
/** @type {HTMLFormElement} */ let form = /** @type {HTMLFormElement} */ (get_element(html_scene, "form"));
/** @type {HTMLElement} */ let sharemapfields = get_element(html_scene, "[data-name=sharemapfields]");
/** @type {HTMLInputElement} */ let shareuser_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=shareuser]"));
/** @type {HTMLInputElement} */ let sharehref_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=sharehref]"));
/** @type {HTMLInputElement} */ let enabled_checkbox = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=enabled]"));
/** @type {HTMLInputElement} */ let hidden_checkbox = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=hidden]"));
let permissions_ro_radio = /** @type {HTMLInputElement} */ (get_element_by_id("newshare_attr_permissions_ro"));
let permissions_rw_radio = /** @type {HTMLInputElement} */ (get_element_by_id("newshare_attr_permissions_rw"));
/** @type {HTMLDetailsElement} */ let properties_fieldset = /** @type {HTMLDetailsElement} */ (html_scene.querySelector("[data-name=properties_override]"));
/** @type {HTMLInputElement} */ let displayname_override_enabled = html_scene.querySelector("[data-name=displayname_override_enabled]");
/** @type {HTMLInputElement} */ let displayname_override_input = html_scene.querySelector("[data-name=displayname_override]");
/** @type {HTMLInputElement} */ let description_override_enabled = html_scene.querySelector("[data-name=description_override_enabled]");
/** @type {HTMLInputElement} */ let description_override_input = html_scene.querySelector("[data-name=description_override]");
/** @type {HTMLInputElement} */ let color_override_enabled = html_scene.querySelector("[data-name=color_override_enabled]");
/** @type {HTMLInputElement} */ let color_override_input = html_scene.querySelector("[data-name=color_override]");
/** @type {HTMLDetailsElement} */ let properties_fieldset = /** @type {HTMLDetailsElement} */ (get_element(html_scene, "[data-name=properties_override]"));
/** @type {HTMLInputElement} */ let displayname_override_enabled = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=displayname_override_enabled]"));
/** @type {HTMLInputElement} */ let displayname_override_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=displayname_override]"));
/** @type {HTMLInputElement} */ let description_override_enabled = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=description_override_enabled]"));
/** @type {HTMLInputElement} */ let description_override_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=description_override]"));
/** @type {HTMLInputElement} */ let color_override_enabled = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=color_override_enabled]"));
/** @type {HTMLInputElement} */ let color_override_input = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=color_override]"));
/** @type {HTMLElement} */ let error_form = html_scene.querySelector("[data-name=error]");
/** @type {HTMLElement} */ let submit_btn = html_scene.querySelector("[data-name=submit]");
/** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]");
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let submit_btn = get_element(html_scene, "[data-name=submit]");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
let errorHandler = new ErrorHandler(error_form);
let map_validator = new FormValidator(errorHandler);
@@ -108,7 +108,7 @@ export class CreateEditShareScene {
let hidden_by_owner = (shareType === "bday" && shareuser_input.value === user) ? false : hidden_checkbox.checked;
let permissions = permissions_rw_radio.checked ? "rw" : "r";
let properties = {};
/** @type {Object<string, string>} */ let properties = {};
if (displayname_override_enabled.checked) {
let key = get_property_key(collection.type, "DISPLAYNAME");
if (key) properties[key] = displayname_override_input.value;
@@ -122,7 +122,7 @@ export class CreateEditShareScene {
if (key) properties[key] = color_override_input.value + (color_override_input.value ? "ff" : "");
}
let callback = function (/** @type {string} */ error) {
let callback = function (/** @type {?string} */ error) {
if (!is_current_scene(self)) {
return;
}
@@ -143,12 +143,12 @@ export class CreateEditShareScene {
PathMapped: pathMapped,
Permissions: permissions,
EnabledByOwner: enabled_by_owner,
EnabledByUser: edit ? share.EnabledByUser : null,
EnabledByUser: (edit && share) ? share.EnabledByUser : null,
HiddenByOwner: hidden_by_owner,
HiddenByUser: edit ? share.HiddenByUser : null,
HiddenByUser: (edit && share) ? share.HiddenByUser : null,
Properties: properties,
User: edit ? share.User : shareuser_input.value,
PathOrToken: edit ? share.PathOrToken : ((shareType === "map" || shareType === "bday") ? "/" + shareuser_input.value + "/" + sharehref_input.value + "/" : ""),
User: (edit && share) ? share.User : shareuser_input.value,
PathOrToken: (edit && share) ? share.PathOrToken : ((shareType === "map" || shareType === "bday") ? "/" + shareuser_input.value + "/" + sharehref_input.value + "/" : ""),
});
if (edit) {
@@ -187,13 +187,14 @@ export class CreateEditShareScene {
cancel_btn.onclick = oncancel;
form.onsubmit = onsubmit;
html_scene.querySelector("h1").textContent = edit ? "Edit Share" : "New Share";
/** @type {HTMLHeadingElement} */ let title = /** @type {HTMLHeadingElement} */ (get_element(html_scene, "h1"));
title.textContent = edit ? "Edit Share" : "New Share";
submit_btn.textContent = edit ? "Save" : "Create";
shareuser_input.value = edit ? share.User : (shareType === "bday" ? user : "");
shareuser_input.value = (edit && share) ? share.User : (shareType === "bday" ? user : "");
shareuser_input.disabled = edit;
enabled_checkbox.checked = edit ? share.EnabledByOwner : true;
hidden_checkbox.checked = edit ? share.HiddenByOwner : false;
enabled_checkbox.checked = (edit && share && share.EnabledByOwner !== null) ? share.EnabledByOwner : true;
hidden_checkbox.checked = (edit && share && share.HiddenByOwner !== null) ? share.HiddenByOwner : false;
if (shareType === "bday") {
// bday is always read-only; hide the permissions section entirely
@@ -206,8 +207,8 @@ export class CreateEditShareScene {
permissions_ro_radio.closest("details")?.classList.remove("hidden");
permissions_ro_radio.disabled = false;
permissions_rw_radio.disabled = false;
permissions_ro_radio.checked = edit ? share.Permissions.toLowerCase() === "r" : true;
permissions_rw_radio.checked = edit ? share.Permissions.toLowerCase() === "rw" : false;
permissions_ro_radio.checked = (edit && share) ? share.Permissions.toLowerCase() === "r" : true;
permissions_rw_radio.checked = (edit && share) ? share.Permissions.toLowerCase() === "rw" : false;
}
let displayname = collection.displayname || "";
@@ -217,7 +218,7 @@ export class CreateEditShareScene {
let description_override_enabled_value = false;
let color_override_enabled_value = false;
if (edit && share.Properties) {
if (edit && share && share.Properties) {
let displayname_key = get_property_key(collection.type, "DISPLAYNAME");
if (displayname_key && share.Properties[displayname_key] !== undefined) {
displayname = share.Properties[displayname_key];
@@ -257,15 +258,23 @@ export class CreateEditShareScene {
properties_fieldset.open = true;
}
if (is_calendar || is_addressbook) {
description_override_enabled.parentElement.classList.remove("hidden");
color_override_enabled.parentElement.classList.remove("hidden");
if (description_override_enabled.parentElement) {
description_override_enabled.parentElement.classList.remove("hidden");
}
if (color_override_enabled.parentElement) {
color_override_enabled.parentElement.classList.remove("hidden");
}
} else {
description_override_enabled.parentElement.classList.add("hidden");
color_override_enabled.parentElement.classList.add("hidden");
if (description_override_enabled.parentElement) {
description_override_enabled.parentElement.classList.add("hidden");
}
if (color_override_enabled.parentElement) {
color_override_enabled.parentElement.classList.add("hidden");
}
}
if (shareType === "map" || shareType === "bday") {
if (edit) {
if (edit && share) {
sharehref_input.value = share.PathOrToken.split("/").filter(Boolean).pop() || "";
} else {
sharehref_input.value = random_uuid();

View File

@@ -23,6 +23,7 @@ import { DELETE_CONFIRMATION_TEXT } from "../constants.js";
import { collectionsCache } from "../utils/collections_cache.js";
import { ErrorHandler } from "../utils/error.js";
import { FormValidator, validate_equals } from "../utils/form_validator.js";
import { get_element, get_element_by_id } from "../utils/misc.js";
import { LoadingScene } from "./LoadingScene.js";
import { Scene, is_current_scene, pop_scene, push_scene } from "./scene_manager.js";
@@ -32,7 +33,7 @@ import { Scene, is_current_scene, pop_scene, push_scene } from "./scene_manager.
export class DeleteConfirmationScene {
/**
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {string} header_title
* @param {any} item
* @param {string} item_title
@@ -41,16 +42,16 @@ export class DeleteConfirmationScene {
* @param {function} [on_success]
*/
constructor(user, password, header_title, item, item_title, delete_action, needsconfirmation, on_success) {
/** @type {HTMLElement} */ let html_scene = document.getElementById("deleteconfirmationscene");
/** @type {HTMLElement} */ let header_html = html_scene.querySelector("[data-name=headertitle]");
/** @type {HTMLElement} */ let html_scene = get_element_by_id("deleteconfirmationscene");
/** @type {HTMLElement} */ let header_html = get_element(html_scene, "[data-name=headertitle]");
if (header_html) header_html.textContent = header_title;
/** @type {HTMLElement} */ let title_form = html_scene.querySelector("[data-name=title]");
/** @type {HTMLElement} */ let error_form = html_scene.querySelector("[data-name=error]");
/** @type {HTMLElement} */ let confirmation_prompt = html_scene.querySelector("[data-name=confirmationprompt]");
/** @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]");
/** @type {HTMLElement} */ let title_form = get_element(html_scene, "[data-name=title]");
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let confirmation_prompt = get_element(html_scene, "[data-name=confirmationprompt]");
/** @type {HTMLInputElement} */ let confirmation_txt = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=confirmationtxt]"));
/** @type {HTMLElement} */ let delete_confirmation_lbl = get_element(html_scene, "[data-name=deleteconfirmationtext]");
/** @type {HTMLElement} */ let delete_btn = get_element(html_scene, "[data-name=delete]");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
if (needsconfirmation) {
delete_confirmation_lbl.innerHTML = DELETE_CONFIRMATION_TEXT;
@@ -80,7 +81,7 @@ export class DeleteConfirmationScene {
try {
let loading_scene = new LoadingScene();
push_scene(loading_scene);
delete_req = delete_action(user, password, item, function (error1) {
delete_req = delete_action(user, password, item, function (/** @type {?string} */ error1) {
if (!is_current_scene(loading_scene)) {
return;
}
@@ -113,8 +114,8 @@ export class DeleteConfirmationScene {
return false;
}
function onkeydown(event) {
if (event.keyCode !== 13) {
function onkeydown(/** @type {KeyboardEvent}*/ event) {
if (event.code !== "Enter") {
return;
}
ondelete();

View File

@@ -22,6 +22,7 @@
import { update_incoming_share } from "../api/sharing.js";
import { collectionsCache } from "../utils/collections_cache.js";
import { ErrorHandler } from "../utils/error.js";
import { get_element, get_element_by_id } from "../utils/misc.js";
import { displayPermissions } from "../utils/permissions.js";
import { Scene, pop_scene } from "./scene_manager.js";
@@ -31,16 +32,16 @@ import { Scene, pop_scene } from "./scene_manager.js";
export class IncomingSharingScene {
/**
* @param {string} user
* @param {string} password
* @param {?string} password
*/
constructor(user, password) {
/** @type {HTMLElement} */ let html_scene = document.getElementById("incomingsharingscene");
/** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]");
/** @type {HTMLElement} */ let error_element = html_scene.querySelector("[data-name=error]");
/** @type {HTMLElement} */ let tbody = html_scene.querySelector("tbody[data-name=incomingsharesbody]");
/** @type {HTMLElement} */ let template = tbody.querySelector("[data-name=incomingsharerowtemplate]");
/** @type {HTMLElement} */ let table = html_scene.querySelector("table");
/** @type {HTMLElement} */ let noshares_message = html_scene.querySelector("[data-name=nosharesmessage]");
/** @type {HTMLElement} */ let html_scene = get_element_by_id("incomingsharingscene");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
/** @type {HTMLElement} */ let error_element = get_element(html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let tbody = get_element(html_scene, "tbody[data-name=incomingsharesbody]");
/** @type {HTMLElement} */ let template = get_element(tbody, "[data-name=incomingsharerowtemplate]");
/** @type {HTMLElement} */ let table = get_element(html_scene, "table");
/** @type {HTMLElement} */ let noshares_message = get_element(html_scene, "[data-name=nosharesmessage]");
let error_handler = new ErrorHandler(error_element);
@@ -62,8 +63,8 @@ export class IncomingSharingScene {
enabled_cb.disabled = true;
shown_cb.disabled = true;
let old_enabled = share.EnabledByUser;
let old_hidden = share.HiddenByUser;
let old_enabled = share.EnabledByUser || false;
let old_hidden = share.HiddenByUser || false;
share.EnabledByUser = enabled_cb.checked;
share.HiddenByUser = !shown_cb.checked;
@@ -90,7 +91,9 @@ export class IncomingSharingScene {
function render_shares(shares) {
// clear old nodes
nodes.forEach(function (node) {
node.parentNode.removeChild(node);
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = [];
@@ -111,11 +114,11 @@ export class IncomingSharingScene {
let node = /** @type {HTMLElement} */(template.cloneNode(true));
node.classList.remove("hidden");
let pathortoken = /** @type {HTMLInputElement} */ (node.querySelector("[data-name=pathortoken]"));
let owner_td = node.querySelector("[data-name=owner]");
let permissions_td = /** @type {HTMLElement} */ (node.querySelector("[data-name=permissions]"));
let enabled_cb = /** @type {HTMLInputElement} */ (node.querySelector("[data-name=enabled]"));
let shown_cb = /** @type {HTMLInputElement} */ (node.querySelector("[data-name=shown]"));
let pathortoken = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=pathortoken]"));
let owner_td = get_element(node, "[data-name=owner]");
let permissions_td = /** @type {HTMLElement} */ (get_element(node, "[data-name=permissions]"));
let enabled_cb = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=enabled]"));
let shown_cb = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=shown]"));
let displayPath = share.PathOrToken.substring(prefix.length);
if (displayPath.endsWith("/")) {

View File

@@ -19,6 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { get_element_by_id } from "../utils/misc.js";
import { Scene } from "./scene_manager.js";
/**
@@ -26,7 +27,7 @@ import { Scene } from "./scene_manager.js";
*/
export class LoadingScene {
constructor() {
this.html_scene = document.getElementById("loadingscene");
this.html_scene = get_element_by_id("loadingscene");
}
show() {
this.html_scene.classList.remove("hidden");

View File

@@ -21,27 +21,30 @@
import { get_principal } from "../api/api.js";
import { ROOT_PATH, SERVER } from "../constants.js";
import { Collection } from "../models/collection.js";
import { collectionsCache } from "../utils/collections_cache.js";
import { ErrorHandler } from "../utils/error.js";
import { FormValidator, validate_non_empty } from "../utils/form_validator.js";
import { get_element, get_element_by_id } from "../utils/misc.js";
import { CollectionsScene } from "./CollectionsScene.js";
import { LoadingScene } from "./LoadingScene.js";
import { Scene, is_current_scene, pop_scene, pop_to_root, push_scene, replace_scene } from "./scene_manager.js";
/**
* @constructor
* @implements {Scene}
*/
export class LoginScene {
constructor() {
/** @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 {HTMLElement} */ let html_scene = get_element_by_id("loginscene");
/** @type {HTMLElement} */ let form = get_element(html_scene, "[data-name=form]");
/** @type {HTMLInputElement} */ let user_form = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=user]"));
/** @type {HTMLInputElement} */ let password_form = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=password]"));
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
/** @type {HTMLElement} */ let logout_view = get_element_by_id("logoutview");
/** @type {HTMLElement} */ let logout_user_form = get_element(logout_view, "[data-name=user]");
/** @type {HTMLElement} */ let logout_btn = get_element(logout_view, "[data-name=logout]");
/** @type {HTMLElement} */ let refresh_btn = get_element(logout_view, "[data-name=refresh]");
let user = "";
/** @type {?XMLHttpRequest} */ let principal_req = null;
@@ -60,7 +63,7 @@ export class LoginScene {
/**
* @param {string} p_user
* @param {string} p_password
* @param {?string} p_password
*/
function perform_login(p_user, p_password) {
user = p_user;
@@ -77,7 +80,7 @@ export class LoginScene {
// Fetch principal
let loading_scene = new LoadingScene();
push_scene(loading_scene);
principal_req = get_principal(user, p_password, function (principal_collection, error1) {
principal_req = get_principal(user, p_password, function (/** @type {?Collection} */ principal_collection, error1) {
if (!is_current_scene(loading_scene)) {
return;
}
@@ -85,12 +88,12 @@ export class LoginScene {
if (error1) {
errorHandler.setError(error1);
pop_scene();
} else {
} else if (principal_collection) {
// show collections
let saved_user = user;
user = "";
let collections_scene = new CollectionsScene(
saved_user, p_password, principal_collection, function (error1) {
saved_user, p_password, principal_collection, function (/** @type {?string} */ error1) {
errorHandler.setError(error1);
user = saved_user;
});
@@ -150,29 +153,27 @@ export class LoginScene {
// Probe for existing authentication (e.g. X-Remote-User)
// Use fetch with credentials: 'omit' to avoid browser login prompt on 401
if (window.fetch) {
fetch(SERVER + ROOT_PATH, {
method: 'PROPFIND',
headers: { 'Depth': '0' },
credentials: 'omit'
}).then(function (response) {
if (response.ok) {
// Authenticated! Now it's safe to call get_principal
get_principal(null, null, function (principal_collection, error) {
if (!error && principal_collection) {
let authenticated_user = principal_collection.displayname;
if (!authenticated_user) {
let href = principal_collection.href.replace(/\/+$/, "");
authenticated_user = href.substring(href.lastIndexOf("/") + 1);
}
perform_login(authenticated_user, null);
fetch(SERVER + ROOT_PATH, {
method: 'PROPFIND',
headers: { 'Depth': '0' },
credentials: 'omit'
}).then(function (response) {
if (response.ok) {
// Authenticated! Now it's safe to call get_principal
get_principal(null, null, function (/** @type {?Collection} */ principal_collection, error) {
if (!error && principal_collection) {
let authenticated_user = principal_collection.displayname;
if (!authenticated_user) {
let href = principal_collection.href.replace(/\/+$/, "");
authenticated_user = href.substring(href.lastIndexOf("/") + 1);
}
});
}
})["catch"](function () {
// Ignore error: we are not authenticated or something else went wrong
});
}
perform_login(authenticated_user, null);
}
});
}
})["catch"](function () {
// Ignore error: we are not authenticated or something else went wrong
});
};
this.hide = function () {
read_form();

View File

@@ -28,6 +28,7 @@ import {
import { Collection, CollectionType } from "../models/collection.js";
import { collectionsCache } from "../utils/collections_cache.js";
import { ErrorHandler } from "../utils/error.js";
import { get_element, get_element_by_id } from "../utils/misc.js";
import { displayPermissions } from "../utils/permissions.js";
import { CreateEditShareScene } from "./CreateEditShareScene.js";
import { DeleteConfirmationScene } from "./DeleteConfirmationScene.js";
@@ -39,37 +40,25 @@ import { Scene, pop_scene, push_scene } from "./scene_manager.js";
export class ShareCollectionScene {
/**
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {Collection} collection The collection on which to edit sharing setting. Must exist.
*/
constructor(user, password, collection) {
let html_scene = document.getElementById("sharecollectionscene");
let html_scene = get_element_by_id("sharecollectionscene");
/** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]");
/** @type {HTMLElement} */ let share_by_token_btn = html_scene.querySelector(
"button[data-name=sharebytoken]"
);
/** @type {HTMLElement} */ let share_by_map_btn = html_scene.querySelector(
"button[data-name=sharebymap]"
);
/** @type {HTMLElement} */ let share_by_bday_btn = html_scene.querySelector(
"button[data-name=sharebybday]"
);
/** @type {HTMLElement} */ let share_by_token_div = html_scene.querySelector(
"div[data-name=sharebytoken]"
);
/** @type {HTMLElement} */ let share_by_map_div = html_scene.querySelector(
"div[data-name=sharebymap]"
);
/** @type {HTMLElement} */ let share_by_bday_div = html_scene.querySelector(
"div[data-name=sharebybday]"
);
/** @type {HTMLElement} */ let error_form = html_scene.querySelector("[data-name=error]");
/** @type {HTMLElement} */ let cancel_btn = get_element(html_scene, "[data-name=cancel]");
/** @type {HTMLElement} */ let share_by_token_btn = get_element(html_scene, "button[data-name=sharebytoken]");
/** @type {HTMLElement} */ let share_by_map_btn = get_element(html_scene, "button[data-name=sharebymap]");
/** @type {HTMLElement} */ let share_by_bday_btn = get_element(html_scene, "button[data-name=sharebybday]");
/** @type {HTMLElement} */ let share_by_token_div = get_element(html_scene, "div[data-name=sharebytoken]");
/** @type {HTMLElement} */ let share_by_map_div = get_element(html_scene, "div[data-name=sharebymap]");
/** @type {HTMLElement} */ let share_by_bday_div = get_element(html_scene, "div[data-name=sharebybday]");
/** @type {HTMLElement} */ let error_form = get_element(html_scene, "[data-name=error]");
let errorHandler = new ErrorHandler(error_form);
/** @type {HTMLElement} */ let title = html_scene.querySelector("[data-name=title]");
/** @type {HTMLElement} */ let title = get_element(html_scene, "[data-name=title]");
function oncancel() {
try {
@@ -169,7 +158,7 @@ export class ShareCollectionScene {
/**
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {Collection} collection
* @param {ErrorHandler} errorHandler
*/
@@ -179,7 +168,9 @@ function update_share_list(user, password, collection, errorHandler) {
);
share_rows.forEach(function (row) {
if (!row.classList.contains("hidden")) {
row.parentNode.removeChild(row);
if (row.parentNode) {
row.parentNode.removeChild(row);
}
}
});
@@ -195,7 +186,7 @@ function update_share_list(user, password, collection, errorHandler) {
/**
*
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {Collection} collection
* @param {import('../api/sharing.js').Share} share
* @param {HTMLElement} template
@@ -209,7 +200,7 @@ function add_share_row_node(user, password, collection, share, template, delete_
let node = /** @type {HTMLElement} */ (template.cloneNode(true));
node.classList.remove("hidden");
/** @type {HTMLInputElement} */ let pathortoken_form = node.querySelector("[data-name=pathortoken]");
/** @type {HTMLInputElement} */ let pathortoken_form = /** @type {HTMLInputElement} */ (get_element(node, "[data-name=pathortoken]"));
if (pathortoken_form) {
pathortoken_form.value = pathortoken;
}
@@ -217,13 +208,13 @@ function add_share_row_node(user, password, collection, share, template, delete_
let permissions = (share["Permissions"] || "").toLowerCase();
displayPermissions(permissions, node);
/** @type {HTMLElement} */ let edit_btn = node.querySelector("[data-name=edit]");
/** @type {HTMLElement} */ let edit_btn = get_element(node, "[data-name=edit]");
edit_btn.onclick = function () {
let create_edit_share_scene = new CreateEditShareScene(user, password, collection, share.ShareType, share);
push_scene(create_edit_share_scene);
};
/** @type {HTMLElement} */ let delete_btn = node.querySelector("[data-name=delete]");
/** @type {HTMLElement} */ let delete_btn = get_element(node, "[data-name=delete]");
delete_btn.onclick = function () {
let delete_collection_scene = new DeleteConfirmationScene(
user, password, "Delete Share", share, delete_label + " " + pathortoken, delete_action, false,
@@ -236,20 +227,22 @@ function add_share_row_node(user, password, collection, share, template, delete_
push_scene(delete_collection_scene);
};
template.parentNode.insertBefore(node, template);
if (template.parentNode) {
template.parentNode.insertBefore(node, template);
}
}
/**
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {Collection} collection
* @param {Array<import('../api/sharing.js').Share>} shares
* @param {ErrorHandler} errorHandler
*/
function add_share_rows(user, password, collection, shares, errorHandler) {
/** @type {HTMLElement} */ let token_template = document.querySelector("[data-name=sharetokenrowtemplate]");
/** @type {HTMLElement} */ let map_template = document.querySelector("[data-name=sharemaprowtemplate]");
/** @type {HTMLElement} */ let bday_template = document.querySelector("[data-name=sharebdayrowtemplate]");
/** @type {HTMLElement} */ let token_template = get_element(document, "[data-name=sharetokenrowtemplate]");
/** @type {HTMLElement} */ let map_template = get_element(document, "[data-name=sharemaprowtemplate]");
/** @type {HTMLElement} */ let bday_template = get_element(document, "[data-name=sharebdayrowtemplate]");
shares.forEach(function (share) {
let pathortoken = share["PathOrToken"] || "";
let pathmapped = share["PathMapped"] || "";

View File

@@ -24,7 +24,7 @@ import { Collection } from "../models/collection.js";
import { collectionsCache } from "../utils/collections_cache.js";
import { ErrorHandler } from "../utils/error.js";
import { FormValidator, validate_files, validate_href } from "../utils/form_validator.js";
import { cleanHREFinput, onCleanHREFinput, random_uuid } from "../utils/misc.js";
import { cleanHREFinput, get_element, get_element_by_id, onCleanHREFinput, random_uuid } from "../utils/misc.js";
import { Scene, pop_scene } from "./scene_manager.js";
/**
@@ -33,21 +33,21 @@ import { Scene, pop_scene } from "./scene_manager.js";
export class UploadCollectionScene {
/**
* @param {string} user
* @param {string} password
* @param {?string} password
* @param {Collection} collection parent collection
*/
constructor(user, password, collection) {
/** @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]");
/** @type {HTMLElement} */ let error_form = html_scene.querySelector(":scope > span[data-name=error]");
/** @type {HTMLElement} */ let html_scene = get_element_by_id("uploadcollectionscene");
/** @type {HTMLElement} */ let template = get_element(html_scene, "[data-name=filetemplate]");
/** @type {HTMLElement} */ let upload_btn = get_element(html_scene, "[data-name=submit]");
/** @type {HTMLElement} */ let close_btn = get_element(html_scene, "[data-name=close]");
/** @type {HTMLInputElement} */ let uploadfile_form = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=uploadfile]"));
/** @type {HTMLElement} */ let uploadfile_lbl = get_element(html_scene, "label[for=uploadfile]");
/** @type {HTMLInputElement} */ let href_form = /** @type {HTMLInputElement} */ (get_element(html_scene, "[data-name=href]"));
/** @type {HTMLElement} */ let href_label = get_element(html_scene, "label[for=href]");
/** @type {HTMLElement} */ let hreflimitmsg_html = get_element(html_scene, "[data-name=hreflimitmsg]");
/** @type {HTMLElement} */ let pending_html = get_element(html_scene, "[data-name=pending]");
/** @type {HTMLElement} */ let error_form = get_element(html_scene, ":scope > span[data-name=error]");
let files = uploadfile_form.files;
href_form.addEventListener("input", onCleanHREFinput);
@@ -64,7 +64,7 @@ export class UploadCollectionScene {
validator.addValidator(uploadfile_form, validate_files(uploadfile_form, "file"));
/** @type {?XMLHttpRequest} */ let upload_req = null;
/** @type {Array<string>} */ let results = [];
/** @type {Array<?string>} */ let results = [];
/** @type {?Array<HTMLElement>} */ let nodes = null;
function upload_start() {
@@ -72,6 +72,9 @@ export class UploadCollectionScene {
if (!validator.validate()) {
return false;
}
if (!files) {
return false;
}
read_form();
uploadfile_form.classList.add("hidden");
uploadfile_lbl.classList.add("hidden");
@@ -88,12 +91,14 @@ export class UploadCollectionScene {
let file = files[i];
let node = /** @type {HTMLElement} */ (template.cloneNode(true));
node.classList.remove("hidden");
let name_form = node.querySelector("[data-name=name]");
let name_form = get_element(node, "[data-name=name]");
name_form.textContent = file.name;
node.classList.remove("hidden");
nodes.push(node);
updateFileStatus(i);
template.parentNode.insertBefore(node, template);
if (template.parentNode) {
template.parentNode.insertBefore(node, template);
}
}
upload_next();
} catch (err) {
@@ -103,6 +108,9 @@ export class UploadCollectionScene {
}
function upload_next() {
if (!files) {
return;
}
try {
if (files.length === results.length) {
pending_html.classList.add("hidden");
@@ -145,8 +153,8 @@ export class UploadCollectionScene {
if (nodes === null) {
return;
}
/** @type {HTMLElement} */ let file_success_form = nodes[i].querySelector("[data-name=success]");
/** @type {HTMLElement} */ let file_error_form = nodes[i].querySelector("[data-name=error]");
/** @type {HTMLElement} */ let file_success_form = get_element(nodes[i], "[data-name=success]");
/** @type {HTMLElement} */ let file_error_form = get_element(nodes[i], "[data-name=error]");
if (results.length > i) {
if (results[i]) {
file_success_form.classList.add("hidden");
@@ -171,7 +179,7 @@ export class UploadCollectionScene {
function onfileschange() {
files = uploadfile_form.files;
if (files.length > 1) {
if (files && files.length > 1) {
hreflimitmsg_html.classList.remove("hidden");
href_form.classList.add("hidden");
href_label.classList.add("hidden");
@@ -213,7 +221,9 @@ export class UploadCollectionScene {
return;
}
nodes.forEach(function (node) {
node.parentNode.removeChild(node);
if (node.parentNode) {
node.parentNode.removeChild(node);
}
});
nodes = null;
};

View File

@@ -56,6 +56,19 @@ export function push_scene(scene) {
scene.show();
}
/**
* Pop the current scene and release it.
*/
function pop_and_release() {
if (scene_stack.length === 0) {
return;
}
let scene = scene_stack.pop();
if (scene) {
scene.release();
}
}
/**
* Replace the current scene with a new one.
* @param {Scene} scene
@@ -63,7 +76,7 @@ export function push_scene(scene) {
export function replace_scene(scene) {
if (scene_stack.length >= 1) {
scene_stack[scene_stack.length - 1].hide();
scene_stack.pop().release();
pop_and_release();
}
scene_stack.push(scene);
scene.show();
@@ -77,7 +90,7 @@ export function pop_scene() {
return;
}
scene_stack[scene_stack.length - 1].hide();
scene_stack.pop().release();
pop_and_release();
if (scene_stack.length >= 1) {
scene_stack[scene_stack.length - 1].show();
}
@@ -92,9 +105,9 @@ export function pop_to_parent() {
return;
}
scene_stack[scene_stack.length - 1].hide();
scene_stack.pop().release();
pop_and_release();
if (scene_stack.length >= 1) {
scene_stack.pop().release();
pop_and_release();
}
if (scene_stack.length >= 1) {
scene_stack[scene_stack.length - 1].show();
@@ -112,7 +125,7 @@ export function pop_to_root() {
// Pop all scenes until only the first one remains
while (scene_stack.length > 1) {
scene_stack[scene_stack.length - 1].hide();
scene_stack.pop().release();
pop_and_release();
}
// The root scene is now at the top (index 0) and should be shown
if (scene_stack.length === 1) {