From 80d91a89871abbcebbe3df90bdcb84df552001a9 Mon Sep 17 00:00:00 2001 From: MatthewHana Date: Tue, 5 Mar 2024 23:57:58 +1100 Subject: [PATCH] Added Webcal support in web UI Added support to view, edit, and add Webcals in web UI to support functionality added in PR #1229. --- radicale/web/internal_data/css/main.css | 1 + radicale/web/internal_data/fn.js | 63 +++++++++++++++++++++---- radicale/web/internal_data/index.html | 19 +++++--- radicale/xmlutils.py | 3 ++ 4 files changed, 72 insertions(+), 14 deletions(-) diff --git a/radicale/web/internal_data/css/main.css b/radicale/web/internal_data/css/main.css index 3d0805336..ae132f518 100644 --- a/radicale/web/internal_data/css/main.css +++ b/radicale/web/internal_data/css/main.css @@ -302,6 +302,7 @@ button{ float: right; margin-left: 10px; background: black; + cursor: pointer; } input, select{ diff --git a/radicale/web/internal_data/fn.js b/radicale/web/internal_data/fn.js index 2500b2eba..43f60a898 100644 --- a/radicale/web/internal_data/fn.js +++ b/radicale/web/internal_data/fn.js @@ -1,6 +1,6 @@ /** * This file is part of Radicale Server - Calendar Server - * Copyright © 2017-2018 Unrud + * Copyright © 2017-2024 Unrud * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -63,6 +63,7 @@ const CollectionType = { CALENDAR: "CALENDAR", JOURNAL: "JOURNAL", TASKS: "TASKS", + WEBCAL: "WEBCAL", is_subset: function(a, b) { let components = a.split("_"); for (let i = 0; i < components.length; i++) { @@ -89,6 +90,9 @@ const CollectionType = { if (a.search(this.TASKS) !== -1 || b.search(this.TASKS) !== -1) { union.push(this.TASKS); } + if (a.search(this.WEBCAL) !== -1 || b.search(this.WEBCAL) !== -1) { + union.push(this.WEBCAL); + } return union.join("_"); } }; @@ -102,12 +106,13 @@ const CollectionType = { * @param {string} description * @param {string} color */ -function Collection(href, type, displayname, description, color) { +function Collection(href, type, displayname, description, color, source) { this.href = href; this.type = type; this.displayname = displayname; this.color = color; this.description = description; + this.source = source; } /** @@ -183,6 +188,7 @@ function get_collections(user, password, collection, callback) { 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 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 : ""; @@ -190,11 +196,17 @@ function get_collections(user, password, collection, callback) { let type = ""; let color = ""; let description = ""; + let source = ""; 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 : ""; + } else if (resourcetype_element.querySelector(resourcetype_query + " > *|subscribed")) { + type = CollectionType.union(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]")) { @@ -221,7 +233,7 @@ function get_collections(user, password, collection, callback) { } } if (href.substr(-1) === "/" && href !== collection.href && type) { - collections.push(new Collection(href, type, displayname, description, sane_color)); + collections.push(new Collection(href, type, displayname, description, sane_color, source)); } } collections.sort(function(a, b) { @@ -235,11 +247,15 @@ function get_collections(user, password, collection, callback) { } }; request.send('' + - '' + + 'xmlns:RADICALE="http://radicale.org/ns/"' + + '>' + '' + '' + '' + @@ -248,6 +264,7 @@ function get_collections(user, password, collection, callback) { '' + '' + '' + + '' + '' + ''); return request; @@ -329,12 +346,18 @@ function create_edit_collection(user, password, collection, create, callback) { let addressbook_color = ""; let calendar_description = ""; let addressbook_description = ""; + let calendar_source = ""; let resourcetype; let components = ""; if (collection.type === CollectionType.ADDRESSBOOK) { addressbook_color = escape_xml(collection.color + (collection.color ? "ff" : "")); addressbook_description = escape_xml(collection.description); resourcetype = ''; + } else if (collection.type === CollectionType.WEBCAL) { + calendar_color = escape_xml(collection.color + (collection.color ? "ff" : "")); + calendar_description = escape_xml(collection.description); + resourcetype = ''; + calendar_source = collection.source; } else { calendar_color = escape_xml(collection.color + (collection.color ? "ff" : "")); calendar_description = escape_xml(collection.description); @@ -351,7 +374,7 @@ function create_edit_collection(user, password, collection, create, callback) { } let xml_request = create ? "mkcol" : "propertyupdate"; request.send('' + - '<' + xml_request + ' xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav" xmlns:I="http://apple.com/ns/ical/" xmlns:INF="http://inf-it.com/ns/ab/">' + + '<' + xml_request + ' xmlns="DAV:" xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CR="urn:ietf:params:xml:ns:carddav" xmlns:CS="http://calendarserver.org/ns/" xmlns:I="http://apple.com/ns/ical/" xmlns:INF="http://inf-it.com/ns/ab/">' + '' + '' + (create ? '' + resourcetype + '' : '') + @@ -361,6 +384,7 @@ function create_edit_collection(user, password, collection, create, callback) { (addressbook_color ? '' + addressbook_color + '' : '') + (addressbook_description ? '' + addressbook_description + '' : '') + (calendar_description ? '' + calendar_description + '' : '') + + (calendar_source ? '' + calendar_source + '' : '') + '' + '' + (!create ? ('' + @@ -692,7 +716,7 @@ function CollectionsScene(user, password, collection, onerror) { if (collection.color) { color_form.style.background = collection.color; } - let possible_types = [CollectionType.ADDRESSBOOK]; + let possible_types = [CollectionType.ADDRESSBOOK, CollectionType.WEBCAL]; [CollectionType.CALENDAR, ""].forEach(function(e) { [CollectionType.union(e, CollectionType.JOURNAL), e].forEach(function(e) { [CollectionType.union(e, CollectionType.TASKS), e].forEach(function(e) { @@ -1005,12 +1029,19 @@ function CreateEditCollectionScene(user, password, collection) { let title_form = edit ? html_scene.querySelector("[data-name=title]") : null; let error_form = html_scene.querySelector("[data-name=error]"); let displayname_form = html_scene.querySelector("[data-name=displayname]"); + let displayname_label = html_scene.querySelector("label[for=displayname]"); let description_form = html_scene.querySelector("[data-name=description]"); + let description_label = html_scene.querySelector("label[for=description]"); + let source_form = html_scene.querySelector("[data-name=source]"); + let source_label = html_scene.querySelector("label[for=source]"); let type_form = html_scene.querySelector("[data-name=type]"); + let type_label = html_scene.querySelector("label[for=type]"); let color_form = html_scene.querySelector("[data-name=color]"); + let color_label = html_scene.querySelector("label[for=color]"); let submit_btn = html_scene.querySelector("[data-name=submit]"); let cancel_btn = html_scene.querySelector("[data-name=cancel]"); + /** @type {?number} */ let scene_index = null; /** @type {?XMLHttpRequest} */ let create_edit_req = null; let error = ""; @@ -1019,6 +1050,7 @@ function CreateEditCollectionScene(user, password, collection) { let href = edit ? collection.href : collection.href + random_uuid() + "/"; let displayname = edit ? collection.displayname : ""; let description = edit ? collection.description : ""; + let source = edit ? collection.source : ""; let type = edit ? collection.type : CollectionType.CALENDAR_JOURNAL_TASKS; let color = edit && collection.color ? collection.color : "#" + random_hex(6); @@ -1038,6 +1070,7 @@ function CreateEditCollectionScene(user, password, collection) { function read_form() { displayname = displayname_form.value; description = description_form.value; + source = source_form.value; type = type_form.value; color = color_form.value; } @@ -1045,6 +1078,7 @@ function CreateEditCollectionScene(user, password, collection) { function fill_form() { displayname_form.value = displayname; description_form.value = description; + source_form.value = source; type_form.value = type; color_form.value = color; if(error){ @@ -1052,6 +1086,9 @@ function CreateEditCollectionScene(user, password, collection) { error_form.classList.remove("hidden"); } error_form.classList.add("hidden"); + + onTypeChange(); + type_form.addEventListener("change", onTypeChange); } function onsubmit() { @@ -1069,7 +1106,7 @@ function CreateEditCollectionScene(user, password, collection) { } let loading_scene = new LoadingScene(); push_scene(loading_scene); - let collection = new Collection(href, type, displayname, description, sane_color); + let collection = new Collection(href, type, displayname, description, sane_color, source); let callback = function(error1) { if (scene_index === null) { return; @@ -1102,6 +1139,16 @@ function CreateEditCollectionScene(user, password, collection) { return false; } + function onTypeChange(e){ + if(type_form.value == CollectionType.WEBCAL){ + source_label.classList.remove("hidden"); + source_form.classList.remove("hidden"); + }else{ + source_label.classList.add("hidden"); + source_form.classList.add("hidden"); + } + } + this.show = function() { this.release(); scene_index = scene_stack.length - 1; diff --git a/radicale/web/internal_data/index.html b/radicale/web/internal_data/index.html index 068dd1b59..fce880d30 100644 --- a/radicale/web/internal_data/index.html +++ b/radicale/web/internal_data/index.html @@ -60,6 +60,7 @@

Title

Calendar Journal Tasks + Webcal

Description

@@ -97,12 +98,15 @@

Edit Collection

+ -
Title:
+ -
Description:
+ -
Color:
+ + +
@@ -125,12 +129,15 @@

Create a new Collection

+ -
Title:
+ -
Description:
+ -
Color:
+ + +
diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index da59c08b7..4b9c51bfc 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -178,6 +178,9 @@ def props_from_request(xml_request: Optional[ET.Element] if resource_type.tag == make_clark("C:calendar"): value = "VCALENDAR" break + if resource_type.tag == make_clark("CS:subscribed"): + value = "VSUBSCRIBED" + break if resource_type.tag == make_clark("CR:addressbook"): value = "VADDRESSBOOK" break