diff --git a/radicale/web/README.md b/radicale/web/README.md new file mode 100644 index 00000000..869f8873 --- /dev/null +++ b/radicale/web/README.md @@ -0,0 +1,8 @@ +# Built-in web UI + +If you have tsc installed, you can type-check all JavaScript using + +``` lang=shell +tsc -p radicale/web/jsconfig.json --noEmit --pretty +``` + diff --git a/radicale/web/internal_data/CreateEditCollectionScene.js b/radicale/web/internal_data/CreateEditCollectionScene.js index 62d268ce..d68a9c80 100644 --- a/radicale/web/internal_data/CreateEditCollectionScene.js +++ b/radicale/web/internal_data/CreateEditCollectionScene.js @@ -42,17 +42,12 @@ export class CreateEditCollectionScene { /** @type {HTMLElement} */ let title_form = edit ? html_scene.querySelector("[data-name=title]") : null; /** @type {HTMLElement} */ let error_form = html_scene.querySelector("[data-name=error]"); /** @type {HTMLInputElement} */ let href_form = html_scene.querySelector("[data-name=href]"); - /** @type {HTMLElement} */ let href_label = html_scene.querySelector("label[for=href]"); /** @type {HTMLInputElement} */ let displayname_form = html_scene.querySelector("[data-name=displayname]"); - /** @type {HTMLElement} */ let displayname_label = html_scene.querySelector("label[for=displayname]"); /** @type {HTMLInputElement} */ let description_form = html_scene.querySelector("[data-name=description]"); - /** @type {HTMLElement} */ let description_label = html_scene.querySelector("label[for=description]"); /** @type {HTMLInputElement} */ let source_form = html_scene.querySelector("[data-name=source]"); /** @type {HTMLElement} */ let source_label = html_scene.querySelector("label[for=source]"); /** @type {HTMLSelectElement} */ let type_form = html_scene.querySelector("[data-name=type]"); - /** @type {HTMLElement} */ let type_label = html_scene.querySelector("label[for=type]"); /** @type {HTMLInputElement} */ let color_form = html_scene.querySelector("[data-name=color]"); - /** @type {HTMLElement} */ let color_label = html_scene.querySelector("label[for=color]"); /** @type {HTMLElement} */ let submit_btn = html_scene.querySelector("[data-name=submit]"); /** @type {HTMLElement} */ let cancel_btn = html_scene.querySelector("[data-name=cancel]"); @@ -119,7 +114,7 @@ export class CreateEditCollectionScene { error_form.classList.remove("hidden"); } error_form.classList.add("hidden"); - onTypeChange(); + onTypeChange(null); type_form.addEventListener("change", onTypeChange); } @@ -173,8 +168,10 @@ export class CreateEditCollectionScene { return false; } - - function onTypeChange(e) { + /** + * @param {Event} _e + */ + function onTypeChange(_e) { if (type_form.value == CollectionType.WEBCAL) { source_label.classList.remove("hidden"); source_form.classList.remove("hidden"); diff --git a/radicale/web/internal_data/LoadingScene.js b/radicale/web/internal_data/LoadingScene.js index 4251147d..1e7b8cfb 100644 --- a/radicale/web/internal_data/LoadingScene.js +++ b/radicale/web/internal_data/LoadingScene.js @@ -21,16 +21,17 @@ import { Scene } from "./scene_manager.js"; /** - * @constructor * @implements {Scene} */ -export function LoadingScene() { - let html_scene = document.getElementById("loadingscene"); - this.show = function() { - html_scene.classList.remove("hidden"); - }; - this.hide = function() { - html_scene.classList.add("hidden"); - }; - this.release = function() {}; +export class LoadingScene { + constructor() { + let html_scene = document.getElementById("loadingscene"); + this.show = function () { + html_scene.classList.remove("hidden"); + }; + this.hide = function () { + html_scene.classList.add("hidden"); + }; + this.release = function () { }; + } } \ No newline at end of file diff --git a/radicale/web/internal_data/api.js b/radicale/web/internal_data/api.js index 8b1f1cf9..7b7c3647 100644 --- a/radicale/web/internal_data/api.js +++ b/radicale/web/internal_data/api.js @@ -65,6 +65,7 @@ export function get_principal(user, password, callback) { "", "", 0, + 0, ""), null); } else { callback(null, "Internal error"); diff --git a/radicale/web/internal_data/models.js b/radicale/web/internal_data/models.js index 9dcdc5e9..4ba4ecea 100644 --- a/radicale/web/internal_data/models.js +++ b/radicale/web/internal_data/models.js @@ -18,21 +18,32 @@ * along with this program. If not, see . */ -/** - * @enum {string} - */ -export const CollectionType = { - PRINCIPAL: "PRINCIPAL", - ADDRESSBOOK: "ADDRESSBOOK", - CALENDAR_JOURNAL_TASKS: "CALENDAR_JOURNAL_TASKS", - CALENDAR_JOURNAL: "CALENDAR_JOURNAL", - CALENDAR_TASKS: "CALENDAR_TASKS", - JOURNAL_TASKS: "JOURNAL_TASKS", - CALENDAR: "CALENDAR", - JOURNAL: "JOURNAL", - TASKS: "TASKS", - WEBCAL: "WEBCAL", - is_subset: function(a, b) { +export class CollectionType { + // Private Fields + static #_PRINCIPAL = "PRINCIPAL"; + static #_ADDRESSBOOK = "ADDRESSBOOK"; + static #_CALENDAR_JOURNAL_TASKS = "CALENDAR_JOURNAL_TASKS"; + static #_CALENDAR_JOURNAL = "CALENDAR_JOURNAL"; + static #_CALENDAR_TASKS = "CALENDAR_TASKS"; + static #_JOURNAL_TASKS = "JOURNAL_TASKS"; + static #_CALENDAR = "CALENDAR"; + static #_JOURNAL = "JOURNAL"; + static #_TASKS = "TASKS"; + static #_WEBCAL = "WEBCAL"; + + // Accessors for "get" functions only (no "set" functions) + static get PRINCIPAL() { return this.#_PRINCIPAL; } + static get ADDRESSBOOK() { return this.#_ADDRESSBOOK; } + static get CALENDAR_JOURNAL_TASKS() { return this.#_CALENDAR_JOURNAL_TASKS; } + static get CALENDAR_JOURNAL() { return this.#_CALENDAR_JOURNAL; } + static get CALENDAR_TASKS() { return this.#_CALENDAR_TASKS; } + static get JOURNAL_TASKS() { return this.#_JOURNAL_TASKS; } + static get CALENDAR() { return this.#_CALENDAR; } + static get JOURNAL() { return this.#_JOURNAL; } + static get TASKS() { return this.#_TASKS; } + static get WEBCAL() { return this.#_WEBCAL; } + + static is_subset(/** @type {string} */ a, /** @type {string} */ b) { let components = a.split("_"); for (let i = 0; i < components.length; i++) { if (b.search(components[i]) === -1) { @@ -40,8 +51,9 @@ export const CollectionType = { } } return true; - }, - union: function(a, b) { + } + + static union(/** @type {string} */ a, /** @type {string} */ b) { if (a.search(this.ADDRESSBOOK) !== -1 || b.search(this.ADDRESSBOOK) !== -1) { if (a && a !== this.ADDRESSBOOK || b && b !== this.ADDRESSBOOK) { throw "Invalid union: " + a + " " + b; @@ -62,10 +74,14 @@ export const CollectionType = { union.push(this.WEBCAL); } return union.join("_"); - }, - valid_options_for_type: function(a){ + } + + /** + * @param {string} a + */ + static valid_options_for_type(a) { a = a.trim().toUpperCase(); - switch(a){ + switch (a) { case CollectionType.CALENDAR_JOURNAL_TASKS: case CollectionType.CALENDAR_JOURNAL: case CollectionType.CALENDAR_TASKS: @@ -80,24 +96,28 @@ export const CollectionType = { return [a]; } } -}; +} -/** - * @constructor - * @struct - * @param {string} href Must always start and end with /. - * @param {CollectionType} type - * @param {string} displayname - * @param {string} description - * @param {string} color - */ -export function Collection(href, type, displayname, description, color, contentcount, size, source) { - this.href = href; - this.type = type; - this.displayname = displayname; - this.color = color; - this.description = description; - this.source = source; - this.contentcount = contentcount; - this.size = size; + +export class Collection { + /** + * @param {string} href Must always start and end with /. + * @param {string} type + * @param {string} displayname + * @param {string} description + * @param {string} color + * @param {number} contentcount + * @param {number} size + * @param {string} source + */ + constructor(href, type, displayname, description, color, contentcount, size, source) { + this.href = href; + this.type = type; + this.displayname = displayname; + this.color = color; + this.description = description; + this.source = source; + this.contentcount = contentcount; + this.size = size; + } } \ No newline at end of file diff --git a/radicale/web/jsconfig.json b/radicale/web/jsconfig.json index 06467fa5..a14b6171 100644 --- a/radicale/web/jsconfig.json +++ b/radicale/web/jsconfig.json @@ -2,7 +2,10 @@ "compilerOptions": { "module": "CommonJS", "target": "ES6", - "checkJs": true + "checkJs": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true }, "include": [ "internal_data/**/*.js"