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:
@@ -26,8 +26,8 @@ import { create_request, to_error_message } from "./common.js";
|
||||
|
||||
/**
|
||||
* Find the principal collection.
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} user
|
||||
* @param {?string} password
|
||||
* @param {function(?Collection, ?string):void} callback Returns result or error
|
||||
* @return {XMLHttpRequest}
|
||||
*/
|
||||
@@ -39,18 +39,22 @@ export function get_principal(user, password, callback) {
|
||||
}
|
||||
if (request.status === 207) {
|
||||
let xml = request.responseXML;
|
||||
let principal_element = xml.querySelector("*|multistatus:root > *|response:first-of-type > *|propstat > *|prop > *|current-user-principal > *|href");
|
||||
let displayname_element = xml.querySelector("*|multistatus:root > *|response:first-of-type > *|propstat > *|prop > *|displayname");
|
||||
if (principal_element) {
|
||||
callback(new Collection(
|
||||
principal_element.textContent,
|
||||
CollectionType.PRINCIPAL,
|
||||
displayname_element ? displayname_element.textContent : "",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
""), null);
|
||||
if (xml) {
|
||||
let principal_element = xml.querySelector("*|multistatus:root > *|response:first-of-type > *|propstat > *|prop > *|current-user-principal > *|href");
|
||||
let displayname_element = xml.querySelector("*|multistatus:root > *|response:first-of-type > *|propstat > *|prop > *|displayname");
|
||||
if (principal_element) {
|
||||
callback(new Collection(
|
||||
principal_element.textContent,
|
||||
CollectionType.PRINCIPAL,
|
||||
displayname_element ? displayname_element.textContent : "",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
""), null);
|
||||
} else {
|
||||
callback(null, "No valid XML received")
|
||||
}
|
||||
} else {
|
||||
callback(null, "Internal error");
|
||||
}
|
||||
@@ -71,7 +75,7 @@ export function get_principal(user, password, callback) {
|
||||
/**
|
||||
* Find all calendars and addressbooks in collection.
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Collection} collection
|
||||
* @param {function(?Array<Collection>, ?string):void} callback Returns result or error
|
||||
* @return {XMLHttpRequest}
|
||||
@@ -85,81 +89,86 @@ export function get_collections(user, password, collection, callback) {
|
||||
}
|
||||
if (request.status === 207) {
|
||||
let xml = request.responseXML;
|
||||
let collections = [];
|
||||
let response_query = "*|multistatus:root > *|response";
|
||||
let responses = xml.querySelectorAll(response_query);
|
||||
for (let i = 0; i < responses.length; i++) {
|
||||
let response = responses[i];
|
||||
let href_element = response.querySelector(response_query + " > *|href");
|
||||
let resourcetype_query = response_query + " > *|propstat > *|prop > *|resourcetype";
|
||||
let resourcetype_element = response.querySelector(resourcetype_query);
|
||||
let displayname_element = response.querySelector(response_query + " > *|propstat > *|prop > *|displayname");
|
||||
let calendarcolor_element = response.querySelector(response_query + " > *|propstat > *|prop > *|calendar-color");
|
||||
let addressbookcolor_element = response.querySelector(response_query + " > *|propstat > *|prop > *|addressbook-color");
|
||||
let calendardesc_element = response.querySelector(response_query + " > *|propstat > *|prop > *|calendar-description");
|
||||
let addressbookdesc_element = response.querySelector(response_query + " > *|propstat > *|prop > *|addressbook-description");
|
||||
let contentcount_element = response.querySelector(response_query + " > *|propstat > *|prop > *|getcontentcount");
|
||||
let contentlength_element = response.querySelector(response_query + " > *|propstat > *|prop > *|getcontentlength");
|
||||
let webcalsource_element = response.querySelector(response_query + " > *|propstat > *|prop > *|source");
|
||||
let components_query = response_query + " > *|propstat > *|prop > *|supported-calendar-component-set";
|
||||
let components_element = response.querySelector(components_query);
|
||||
let href = href_element ? href_element.textContent : "";
|
||||
let displayname = displayname_element ? displayname_element.textContent : "";
|
||||
let type = "";
|
||||
let color = "";
|
||||
let description = "";
|
||||
let source = "";
|
||||
let count = 0;
|
||||
let size = 0;
|
||||
if (resourcetype_element) {
|
||||
if (resourcetype_element.querySelector(resourcetype_query + " > *|addressbook")) {
|
||||
type = CollectionType.ADDRESSBOOK;
|
||||
color = addressbookcolor_element ? addressbookcolor_element.textContent : "";
|
||||
description = addressbookdesc_element ? addressbookdesc_element.textContent : "";
|
||||
count = contentcount_element ? parseInt(contentcount_element.textContent) : 0;
|
||||
size = contentlength_element ? parseInt(contentlength_element.textContent) : 0;
|
||||
} else if (resourcetype_element.querySelector(resourcetype_query + " > *|subscribed")) {
|
||||
type = CollectionType.WEBCAL;
|
||||
source = webcalsource_element ? webcalsource_element.textContent : "";
|
||||
color = calendarcolor_element ? calendarcolor_element.textContent : "";
|
||||
description = calendardesc_element ? calendardesc_element.textContent : "";
|
||||
} else if (resourcetype_element.querySelector(resourcetype_query + " > *|calendar")) {
|
||||
if (components_element) {
|
||||
if (components_element.querySelector(components_query + " > *|comp[name=VEVENT]")) {
|
||||
type = CollectionType.union(type, CollectionType.CALENDAR);
|
||||
}
|
||||
if (components_element.querySelector(components_query + " > *|comp[name=VJOURNAL]")) {
|
||||
type = CollectionType.union(type, CollectionType.JOURNAL);
|
||||
}
|
||||
if (components_element.querySelector(components_query + " > *|comp[name=VTODO]")) {
|
||||
type = CollectionType.union(type, CollectionType.TASKS);
|
||||
if (xml) {
|
||||
let collections = [];
|
||||
let response_query = "*|multistatus:root > *|response";
|
||||
let responses = xml.querySelectorAll(response_query);
|
||||
for (let i = 0; i < responses.length; i++) {
|
||||
let response = responses[i];
|
||||
let href_element = response.querySelector(response_query + " > *|href");
|
||||
let resourcetype_query = response_query + " > *|propstat > *|prop > *|resourcetype";
|
||||
let resourcetype_element = response.querySelector(resourcetype_query);
|
||||
let displayname_element = response.querySelector(response_query + " > *|propstat > *|prop > *|displayname");
|
||||
let calendarcolor_element = response.querySelector(response_query + " > *|propstat > *|prop > *|calendar-color");
|
||||
let addressbookcolor_element = response.querySelector(response_query + " > *|propstat > *|prop > *|addressbook-color");
|
||||
let calendardesc_element = response.querySelector(response_query + " > *|propstat > *|prop > *|calendar-description");
|
||||
let addressbookdesc_element = response.querySelector(response_query + " > *|propstat > *|prop > *|addressbook-description");
|
||||
let contentcount_element = response.querySelector(response_query + " > *|propstat > *|prop > *|getcontentcount");
|
||||
let contentlength_element = response.querySelector(response_query + " > *|propstat > *|prop > *|getcontentlength");
|
||||
let webcalsource_element = response.querySelector(response_query + " > *|propstat > *|prop > *|source");
|
||||
let components_query = response_query + " > *|propstat > *|prop > *|supported-calendar-component-set";
|
||||
let components_element = response.querySelector(components_query);
|
||||
let href = href_element ? href_element.textContent : "";
|
||||
let displayname = displayname_element ? displayname_element.textContent : "";
|
||||
let type = "";
|
||||
let color = "";
|
||||
let description = "";
|
||||
let source = "";
|
||||
let count = 0;
|
||||
let size = 0;
|
||||
if (resourcetype_element) {
|
||||
if (resourcetype_element.querySelector(resourcetype_query + " > *|addressbook")) {
|
||||
type = CollectionType.ADDRESSBOOK;
|
||||
color = addressbookcolor_element ? addressbookcolor_element.textContent : "";
|
||||
description = addressbookdesc_element ? addressbookdesc_element.textContent : "";
|
||||
count = contentcount_element ? parseInt(contentcount_element.textContent) : 0;
|
||||
size = contentlength_element ? parseInt(contentlength_element.textContent) : 0;
|
||||
} else if (resourcetype_element.querySelector(resourcetype_query + " > *|subscribed")) {
|
||||
type = CollectionType.WEBCAL;
|
||||
source = webcalsource_element ? webcalsource_element.textContent : "";
|
||||
color = calendarcolor_element ? calendarcolor_element.textContent : "";
|
||||
description = calendardesc_element ? calendardesc_element.textContent : "";
|
||||
} else if (resourcetype_element.querySelector(resourcetype_query + " > *|calendar")) {
|
||||
if (components_element) {
|
||||
if (components_element.querySelector(components_query + " > *|comp[name=VEVENT]")) {
|
||||
type = CollectionType.union(type, CollectionType.CALENDAR);
|
||||
}
|
||||
if (components_element.querySelector(components_query + " > *|comp[name=VJOURNAL]")) {
|
||||
type = CollectionType.union(type, CollectionType.JOURNAL);
|
||||
}
|
||||
if (components_element.querySelector(components_query + " > *|comp[name=VTODO]")) {
|
||||
type = CollectionType.union(type, CollectionType.TASKS);
|
||||
}
|
||||
}
|
||||
color = calendarcolor_element ? calendarcolor_element.textContent : "";
|
||||
description = calendardesc_element ? calendardesc_element.textContent : "";
|
||||
count = contentcount_element ? parseInt(contentcount_element.textContent) : 0;
|
||||
size = contentlength_element ? parseInt(contentlength_element.textContent) : 0;
|
||||
}
|
||||
color = calendarcolor_element ? calendarcolor_element.textContent : "";
|
||||
description = calendardesc_element ? calendardesc_element.textContent : "";
|
||||
count = contentcount_element ? parseInt(contentcount_element.textContent) : 0;
|
||||
size = contentlength_element ? parseInt(contentlength_element.textContent) : 0;
|
||||
}
|
||||
let sane_color = color.trim();
|
||||
if (sane_color) {
|
||||
let color_match = COLOR_RE.exec(sane_color);
|
||||
if (color_match) {
|
||||
sane_color = color_match[1];
|
||||
} else {
|
||||
sane_color = "";
|
||||
}
|
||||
}
|
||||
if (href.substr(-1) === "/" && href !== collection.href && type) {
|
||||
collections.push(new Collection(href, type, displayname, description, sane_color, count, size, source));
|
||||
}
|
||||
}
|
||||
let sane_color = color.trim();
|
||||
if (sane_color) {
|
||||
let color_match = COLOR_RE.exec(sane_color);
|
||||
if (color_match) {
|
||||
sane_color = color_match[1];
|
||||
} else {
|
||||
sane_color = "";
|
||||
}
|
||||
}
|
||||
if (href.substr(-1) === "/" && href !== collection.href && type) {
|
||||
collections.push(new Collection(href, type, displayname, description, sane_color, count, size, source));
|
||||
}
|
||||
}
|
||||
collections.sort(function (a, b) {
|
||||
collections.sort(function (a, b) {
|
||||
/** @type {string} */ let ca = a.displayname || a.href;
|
||||
/** @type {string} */ let cb = b.displayname || b.href;
|
||||
return ca.localeCompare(cb);
|
||||
});
|
||||
callback(collections, null);
|
||||
return ca.localeCompare(cb);
|
||||
});
|
||||
callback(collections, null);
|
||||
} else {
|
||||
callback(null, "No valid XML received")
|
||||
}
|
||||
|
||||
} else {
|
||||
callback(null, to_error_message(request));
|
||||
}
|
||||
@@ -192,7 +201,7 @@ export function get_collections(user, password, collection, callback) {
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {string} collection_href Must always start and end with /.
|
||||
* @param {File} file
|
||||
* @param {function(?string):void} callback Returns error or null
|
||||
@@ -217,7 +226,7 @@ export function upload_collection(user, password, collection_href, file, callbac
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Collection} collection
|
||||
* @param {function(?string):void} callback Returns error or null
|
||||
* @return {XMLHttpRequest}
|
||||
@@ -240,7 +249,7 @@ export function delete_collection(user, password, collection, callback) {
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Collection} collection
|
||||
* @param {boolean} create
|
||||
* @param {function(?string):void} callback Returns error or null
|
||||
@@ -320,7 +329,7 @@ function create_edit_collection(user, password, collection, create, callback) {
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Collection} collection
|
||||
* @param {function(?string):void} callback Returns error or null
|
||||
* @return {XMLHttpRequest}
|
||||
@@ -331,7 +340,7 @@ export function create_collection(user, password, collection, callback) {
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Collection} collection
|
||||
* @param {function(?string):void} callback Returns error or null
|
||||
* @return {XMLHttpRequest}
|
||||
|
||||
@@ -39,12 +39,12 @@ import { create_request, to_error_message } from "./common.js";
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {string} path
|
||||
* @param {object} body
|
||||
* @param {function(string):void} on_success
|
||||
* @param {function():void} on_not_found
|
||||
* @param {function(string):void} on_error
|
||||
* @param {?function():void} on_not_found
|
||||
* @param {?function(string):void} on_error
|
||||
* @returns {XMLHttpRequest}
|
||||
*/
|
||||
function call_sharing_api(
|
||||
@@ -92,7 +92,7 @@ function call_sharing_api(
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {function(import("../api/sharing.js").ServerFeatures, ?string):void} callback
|
||||
*/
|
||||
export function discover_server_features(user, password, callback) {
|
||||
@@ -106,7 +106,11 @@ export function discover_server_features(user, password, callback) {
|
||||
let features = { "sharing": JSON.parse(response) };
|
||||
callback(features, null);
|
||||
} catch (e) {
|
||||
callback({}, e.message);
|
||||
if (e instanceof Error) {
|
||||
callback({}, e.message);
|
||||
} else {
|
||||
callback({}, e ? e.toString() : "Unknown error");
|
||||
}
|
||||
}
|
||||
},
|
||||
function () {
|
||||
@@ -119,9 +123,29 @@ export function discover_server_features(user, password, callback) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ShareData
|
||||
* @property {string} [ShareType]
|
||||
* @property {string} [PathOrToken]
|
||||
* @property {string} [PathMapped]
|
||||
* @property {string} [Owner]
|
||||
* @property {string} [User]
|
||||
* @property {string} [Permissions]
|
||||
* @property {?boolean} [EnabledByOwner]
|
||||
* @property {?boolean} [EnabledByUser]
|
||||
* @property {?boolean} [Enabled]
|
||||
* @property {?boolean} [HiddenByOwner]
|
||||
* @property {?boolean} [HiddenByUser]
|
||||
* @property {?boolean} [Hidden]
|
||||
* @property {number} [TimestampCreated]
|
||||
* @property {number} [TimestampUpdated]
|
||||
* @property {Object<String, String>} [Properties]
|
||||
*/
|
||||
|
||||
|
||||
export class Share {
|
||||
/**
|
||||
* @param {Object} [data]
|
||||
* @param {ShareData} [data]
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
/** @type {string} */ this.ShareType = data.ShareType || "";
|
||||
@@ -130,20 +154,20 @@ export class Share {
|
||||
/** @type {string} */ this.Owner = data.Owner || "";
|
||||
/** @type {string} */ this.User = data.User || "";
|
||||
/** @type {string} */ this.Permissions = data.Permissions || "r";
|
||||
/** @type {boolean} */ this.EnabledByOwner = data.EnabledByOwner ?? data.Enabled ?? false;
|
||||
/** @type {?boolean} */ this.EnabledByOwner = data.EnabledByOwner ?? data.Enabled ?? false;
|
||||
/** @type {?boolean} */ this.EnabledByUser = data.EnabledByUser ?? data.Enabled ?? null;
|
||||
/** @type {boolean} */ this.HiddenByOwner = data.HiddenByOwner ?? data.Hidden ?? false;
|
||||
/** @type {?boolean} */ this.HiddenByOwner = data.HiddenByOwner ?? data.Hidden ?? false;
|
||||
/** @type {?boolean} */ this.HiddenByUser = data.HiddenByUser ?? data.Hidden ?? null;
|
||||
/** @type {number} */ this.TimestampCreated = data.TimestampCreated || 0;
|
||||
/** @type {number} */ this.TimestampUpdated = data.TimestampUpdated || 0;
|
||||
/** @type {Object} */ this.Properties = data.Properties || {};
|
||||
/** @type {Object<String, String>} */ this.Properties = data.Properties || {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {import("../models/collection.js").Collection} collection
|
||||
* @param {?string} password
|
||||
* @param {?import("../models/collection.js").Collection} collection
|
||||
* @param {function(Array<Share>, ?string):void} callback
|
||||
*/
|
||||
export function reload_sharing_list(user, password, collection, callback) {
|
||||
@@ -155,7 +179,7 @@ export function reload_sharing_list(user, password, collection, callback) {
|
||||
body,
|
||||
function (response) {
|
||||
let parsed = JSON.parse(response);
|
||||
let shares = (parsed["Content"] || []).map(data => new Share(data));
|
||||
let shares = (parsed["Content"] || []).map((/** @type {ShareData} */ data) => new Share(data));
|
||||
callback(shares, null);
|
||||
},
|
||||
null, // on_not_found
|
||||
@@ -200,7 +224,7 @@ export function get_property_key(type, property) {
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -238,7 +262,7 @@ export function add_share_by_token(
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -278,7 +302,7 @@ export function add_share_by_map(
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -310,7 +334,7 @@ export function delete_share_by_token(
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -341,7 +365,7 @@ export function delete_share_by_map(
|
||||
}
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -379,7 +403,7 @@ export function update_share_by_token(
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -421,7 +445,7 @@ export function update_share_by_map(
|
||||
* Update a shared map entry as the recipient user.
|
||||
* Only sends fields the non-owner user is allowed to change: PathOrToken, EnabledByUser, HiddenByUser.
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -457,7 +481,7 @@ export function update_incoming_share(
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -497,7 +521,7 @@ export function add_share_by_bday(
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
@@ -529,7 +553,7 @@ export function delete_share_by_bday(
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {Share} share
|
||||
* @param {function(?string):void} callback
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,7 @@ export const ROOT_PATH = location.pathname.replace(new RegExp("/+[^/]+/*(/index\
|
||||
/**
|
||||
* Regex to match and normalize color
|
||||
* @const
|
||||
* @type {RegExp}
|
||||
*/
|
||||
export const COLOR_RE = new RegExp("^(#[0-9A-Fa-f]{6})(?:[0-9A-Fa-f]{2})?$");
|
||||
|
||||
@@ -43,5 +44,6 @@ export const COLOR_RE = new RegExp("^(#[0-9A-Fa-f]{6})(?:[0-9A-Fa-f]{2})?$");
|
||||
/**
|
||||
* The text needed to confirm deleting a collection
|
||||
* @const
|
||||
* @type {string}
|
||||
*/
|
||||
export const DELETE_CONFIRMATION_TEXT = "DELETE";
|
||||
@@ -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 = [];
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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("/")) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"] || "";
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -49,7 +49,7 @@ class CollectionsCache {
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {import("../models/collection.js").Collection} principal_collection
|
||||
* @param {function(string):void} onerror
|
||||
* @param {function(Array<import("../models/collection.js").Collection>, Array<import("../api/sharing.js").Share>, boolean):void} displayData
|
||||
@@ -63,9 +63,9 @@ class CollectionsCache {
|
||||
let loading_scene = new LoadingScene();
|
||||
push_scene(loading_scene);
|
||||
|
||||
let collections = null;
|
||||
let shares = null;
|
||||
let error = null;
|
||||
/** @type {?Array<import("../models/collection.js").Collection>} */ let collections = null;
|
||||
/** @type {?Array<import("../api/sharing.js").Share>} */ let shares = null;
|
||||
/** @type {?string} */ let error = null;
|
||||
|
||||
let check_if_completed = () => {
|
||||
if (!is_current_scene(loading_scene)) {
|
||||
@@ -101,7 +101,7 @@ class CollectionsCache {
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {function(string):void} onerror
|
||||
* @param {function(Array<import("../api/sharing.js").Share>):void} displayData
|
||||
*/
|
||||
@@ -132,7 +132,7 @@ class CollectionsCache {
|
||||
|
||||
/**
|
||||
* @param {string} user
|
||||
* @param {string} password
|
||||
* @param {?string} password
|
||||
* @param {function(string):void} onerror
|
||||
* @param {function(import("../api/sharing.js").ServerFeatures):void} displayData
|
||||
*/
|
||||
|
||||
@@ -31,10 +31,14 @@ export class ErrorHandler {
|
||||
|
||||
/**
|
||||
* Sets an error message for a given key.
|
||||
* @param {string} errorMessage
|
||||
* @param {?string} errorMessage
|
||||
*/
|
||||
setError(errorMessage) {
|
||||
this._update([errorMessage]);
|
||||
if (errorMessage) {
|
||||
this._update([errorMessage]);
|
||||
} else {
|
||||
this.clearError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@ export class FormValidator {
|
||||
*/
|
||||
constructor(error_handler) {
|
||||
this.error_handler = error_handler;
|
||||
/** @type {Array<{field: HTMLInputElement, validation_method: function(): ?string}>} */
|
||||
this.validation_methods = [];
|
||||
}
|
||||
|
||||
@@ -51,8 +52,8 @@ export class FormValidator {
|
||||
*/
|
||||
validate() {
|
||||
let errorMessages = [];
|
||||
for (let { field, validation_method } of this.validation_methods) {
|
||||
let errorMessage = validation_method(field);
|
||||
for (let { validation_method } of this.validation_methods) {
|
||||
let errorMessage = validation_method();
|
||||
if (errorMessage) {
|
||||
errorMessages.push(errorMessage);
|
||||
}
|
||||
@@ -136,7 +137,9 @@ export function validate_color(input, field_name) {
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}/**
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the input matches a specific string.
|
||||
* @param {HTMLInputElement} input
|
||||
* @param {string} target
|
||||
|
||||
@@ -48,11 +48,7 @@ export function random_uuid() {
|
||||
export function random_hex(length) {
|
||||
let bytes = new Uint8Array(Math.ceil(length / 2));
|
||||
window.crypto.getRandomValues(bytes);
|
||||
// Fallback for compatibility with older browsers which may not have padStart
|
||||
return bytes.reduce((s, b) => {
|
||||
let hex = b.toString(16);
|
||||
return s + (String.prototype["padStart"] ? hex["padStart"](2, "0") : ("0" + hex).slice(-2));
|
||||
}, "").substring(0, length);
|
||||
return bytes.reduce((s, b) => s + b.toString(16).padStart(2, "0"), "").substring(0, length);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,3 +115,30 @@ export function setupSelectAll() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element by its ID and throw an error if it's not found.
|
||||
* @param {string} id The ID of the element to find.
|
||||
* @return {HTMLElement} The found element.
|
||||
*/
|
||||
export function get_element_by_id(id) {
|
||||
const element = document.getElementById(id);
|
||||
if (!element) {
|
||||
throw new Error("Element with ID '" + id + "' not found");
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an element by a selector and throw an error if it's not found.
|
||||
* @param {ParentNode} node The parent node to search within.
|
||||
* @param {string} selector The CSS selector to use.
|
||||
* @return {HTMLElement} The found element.
|
||||
*/
|
||||
export function get_element(node, selector) {
|
||||
const element = node.querySelector(selector);
|
||||
if (!element) {
|
||||
throw new Error("Element with selector '" + selector + "' not found");
|
||||
}
|
||||
return /** @type {HTMLElement} */ (element);
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ export function displayPermissions(permissions, node) {
|
||||
permissions = (permissions || "").toLowerCase();
|
||||
if (permissions === "rw") {
|
||||
const roElement = node.querySelector("[data-name=ro]");
|
||||
if (roElement) {
|
||||
if (roElement && roElement.parentNode) {
|
||||
roElement.parentNode.removeChild(roElement);
|
||||
}
|
||||
} else if (permissions === "r") {
|
||||
const rwElement = node.querySelector("[data-name=rw]");
|
||||
if (rwElement) {
|
||||
if (rwElement && rwElement.parentNode) {
|
||||
rwElement.parentNode.removeChild(rwElement);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"target": "ES6",
|
||||
"target": "ES2017",
|
||||
"checkJs": true,
|
||||
"noEmit": true,
|
||||
"noUnusedLocals": true,
|
||||
|
||||
Reference in New Issue
Block a user