Skip to content

Commit 80d91a8

Browse files
committedMar 5, 2024
Added Webcal support in web UI
Added support to view, edit, and add Webcals in web UI to support functionality added in PR Kozea#1229.
1 parent 6474f8f commit 80d91a8

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed
 

‎radicale/web/internal_data/css/main.css

+1
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ button{
302302
float: right;
303303
margin-left: 10px;
304304
background: black;
305+
cursor: pointer;
305306
}
306307

307308
input, select{

‎radicale/web/internal_data/fn.js

+55-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* This file is part of Radicale Server - Calendar Server
3-
* Copyright © 2017-2018 Unrud <unrud@outlook.com>
3+
* Copyright © 2017-2024 Unrud <unrud@outlook.com>
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU General Public License as published by
@@ -63,6 +63,7 @@ const CollectionType = {
6363
CALENDAR: "CALENDAR",
6464
JOURNAL: "JOURNAL",
6565
TASKS: "TASKS",
66+
WEBCAL: "WEBCAL",
6667
is_subset: function(a, b) {
6768
let components = a.split("_");
6869
for (let i = 0; i < components.length; i++) {
@@ -89,6 +90,9 @@ const CollectionType = {
8990
if (a.search(this.TASKS) !== -1 || b.search(this.TASKS) !== -1) {
9091
union.push(this.TASKS);
9192
}
93+
if (a.search(this.WEBCAL) !== -1 || b.search(this.WEBCAL) !== -1) {
94+
union.push(this.WEBCAL);
95+
}
9296
return union.join("_");
9397
}
9498
};
@@ -102,12 +106,13 @@ const CollectionType = {
102106
* @param {string} description
103107
* @param {string} color
104108
*/
105-
function Collection(href, type, displayname, description, color) {
109+
function Collection(href, type, displayname, description, color, source) {
106110
this.href = href;
107111
this.type = type;
108112
this.displayname = displayname;
109113
this.color = color;
110114
this.description = description;
115+
this.source = source;
111116
}
112117

113118
/**
@@ -183,18 +188,25 @@ function get_collections(user, password, collection, callback) {
183188
let addressbookcolor_element = response.querySelector(response_query + " > *|propstat > *|prop > *|addressbook-color");
184189
let calendardesc_element = response.querySelector(response_query + " > *|propstat > *|prop > *|calendar-description");
185190
let addressbookdesc_element = response.querySelector(response_query + " > *|propstat > *|prop > *|addressbook-description");
191+
let webcalsource_element = response.querySelector(response_query + " > *|propstat > *|prop > *|source");
186192
let components_query = response_query + " > *|propstat > *|prop > *|supported-calendar-component-set";
187193
let components_element = response.querySelector(components_query);
188194
let href = href_element ? href_element.textContent : "";
189195
let displayname = displayname_element ? displayname_element.textContent : "";
190196
let type = "";
191197
let color = "";
192198
let description = "";
199+
let source = "";
193200
if (resourcetype_element) {
194201
if (resourcetype_element.querySelector(resourcetype_query + " > *|addressbook")) {
195202
type = CollectionType.ADDRESSBOOK;
196203
color = addressbookcolor_element ? addressbookcolor_element.textContent : "";
197204
description = addressbookdesc_element ? addressbookdesc_element.textContent : "";
205+
} else if (resourcetype_element.querySelector(resourcetype_query + " > *|subscribed")) {
206+
type = CollectionType.union(type, CollectionType.WEBCAL);
207+
source = webcalsource_element ? webcalsource_element.textContent : "";
208+
color = calendarcolor_element ? calendarcolor_element.textContent : "";
209+
description = calendardesc_element ? calendardesc_element.textContent : "";
198210
} else if (resourcetype_element.querySelector(resourcetype_query + " > *|calendar")) {
199211
if (components_element) {
200212
if (components_element.querySelector(components_query + " > *|comp[name=VEVENT]")) {
@@ -221,7 +233,7 @@ function get_collections(user, password, collection, callback) {
221233
}
222234
}
223235
if (href.substr(-1) === "/" && href !== collection.href && type) {
224-
collections.push(new Collection(href, type, displayname, description, sane_color));
236+
collections.push(new Collection(href, type, displayname, description, sane_color, source));
225237
}
226238
}
227239
collections.sort(function(a, b) {
@@ -235,11 +247,15 @@ function get_collections(user, password, collection, callback) {
235247
}
236248
};
237249
request.send('<?xml version="1.0" encoding="utf-8" ?>' +
238-
'<propfind xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav" ' +
250+
'<propfind ' +
251+
'xmlns="DAV:" ' +
252+
'xmlns:C="urn:ietf:params:xml:ns:caldav" ' +
239253
'xmlns:CR="urn:ietf:params:xml:ns:carddav" ' +
254+
'xmlns:CS="http://calendarserver.org/ns/" ' +
240255
'xmlns:I="http://apple.com/ns/ical/" ' +
241256
'xmlns:INF="http://inf-it.com/ns/ab/" ' +
242-
'xmlns:RADICALE="http://radicale.org/ns/">' +
257+
'xmlns:RADICALE="http://radicale.org/ns/"' +
258+
'>' +
243259
'<prop>' +
244260
'<resourcetype />' +
245261
'<RADICALE:displayname />' +
@@ -248,6 +264,7 @@ function get_collections(user, password, collection, callback) {
248264
'<C:calendar-description />' +
249265
'<C:supported-calendar-component-set />' +
250266
'<CR:addressbook-description />' +
267+
'<CS:source />' +
251268
'</prop>' +
252269
'</propfind>');
253270
return request;
@@ -329,12 +346,18 @@ function create_edit_collection(user, password, collection, create, callback) {
329346
let addressbook_color = "";
330347
let calendar_description = "";
331348
let addressbook_description = "";
349+
let calendar_source = "";
332350
let resourcetype;
333351
let components = "";
334352
if (collection.type === CollectionType.ADDRESSBOOK) {
335353
addressbook_color = escape_xml(collection.color + (collection.color ? "ff" : ""));
336354
addressbook_description = escape_xml(collection.description);
337355
resourcetype = '<CR:addressbook />';
356+
} else if (collection.type === CollectionType.WEBCAL) {
357+
calendar_color = escape_xml(collection.color + (collection.color ? "ff" : ""));
358+
calendar_description = escape_xml(collection.description);
359+
resourcetype = '<CS:subscribed />';
360+
calendar_source = collection.source;
338361
} else {
339362
calendar_color = escape_xml(collection.color + (collection.color ? "ff" : ""));
340363
calendar_description = escape_xml(collection.description);
@@ -351,7 +374,7 @@ function create_edit_collection(user, password, collection, create, callback) {
351374
}
352375
let xml_request = create ? "mkcol" : "propertyupdate";
353376
request.send('<?xml version="1.0" encoding="UTF-8" ?>' +
354-
'<' + 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/">' +
377+
'<' + 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/">' +
355378
'<set>' +
356379
'<prop>' +
357380
(create ? '<resourcetype><collection />' + resourcetype + '</resourcetype>' : '') +
@@ -361,6 +384,7 @@ function create_edit_collection(user, password, collection, create, callback) {
361384
(addressbook_color ? '<INF:addressbook-color>' + addressbook_color + '</INF:addressbook-color>' : '') +
362385
(addressbook_description ? '<CR:addressbook-description>' + addressbook_description + '</CR:addressbook-description>' : '') +
363386
(calendar_description ? '<C:calendar-description>' + calendar_description + '</C:calendar-description>' : '') +
387+
(calendar_source ? '<CS:source>' + calendar_source + '</CS:source>' : '') +
364388
'</prop>' +
365389
'</set>' +
366390
(!create ? ('<remove>' +
@@ -692,7 +716,7 @@ function CollectionsScene(user, password, collection, onerror) {
692716
if (collection.color) {
693717
color_form.style.background = collection.color;
694718
}
695-
let possible_types = [CollectionType.ADDRESSBOOK];
719+
let possible_types = [CollectionType.ADDRESSBOOK, CollectionType.WEBCAL];
696720
[CollectionType.CALENDAR, ""].forEach(function(e) {
697721
[CollectionType.union(e, CollectionType.JOURNAL), e].forEach(function(e) {
698722
[CollectionType.union(e, CollectionType.TASKS), e].forEach(function(e) {
@@ -1005,12 +1029,19 @@ function CreateEditCollectionScene(user, password, collection) {
10051029
let title_form = edit ? html_scene.querySelector("[data-name=title]") : null;
10061030
let error_form = html_scene.querySelector("[data-name=error]");
10071031
let displayname_form = html_scene.querySelector("[data-name=displayname]");
1032+
let displayname_label = html_scene.querySelector("label[for=displayname]");
10081033
let description_form = html_scene.querySelector("[data-name=description]");
1034+
let description_label = html_scene.querySelector("label[for=description]");
1035+
let source_form = html_scene.querySelector("[data-name=source]");
1036+
let source_label = html_scene.querySelector("label[for=source]");
10091037
let type_form = html_scene.querySelector("[data-name=type]");
1038+
let type_label = html_scene.querySelector("label[for=type]");
10101039
let color_form = html_scene.querySelector("[data-name=color]");
1040+
let color_label = html_scene.querySelector("label[for=color]");
10111041
let submit_btn = html_scene.querySelector("[data-name=submit]");
10121042
let cancel_btn = html_scene.querySelector("[data-name=cancel]");
10131043

1044+
10141045
/** @type {?number} */ let scene_index = null;
10151046
/** @type {?XMLHttpRequest} */ let create_edit_req = null;
10161047
let error = "";
@@ -1019,6 +1050,7 @@ function CreateEditCollectionScene(user, password, collection) {
10191050
let href = edit ? collection.href : collection.href + random_uuid() + "/";
10201051
let displayname = edit ? collection.displayname : "";
10211052
let description = edit ? collection.description : "";
1053+
let source = edit ? collection.source : "";
10221054
let type = edit ? collection.type : CollectionType.CALENDAR_JOURNAL_TASKS;
10231055
let color = edit && collection.color ? collection.color : "#" + random_hex(6);
10241056

@@ -1038,20 +1070,25 @@ function CreateEditCollectionScene(user, password, collection) {
10381070
function read_form() {
10391071
displayname = displayname_form.value;
10401072
description = description_form.value;
1073+
source = source_form.value;
10411074
type = type_form.value;
10421075
color = color_form.value;
10431076
}
10441077

10451078
function fill_form() {
10461079
displayname_form.value = displayname;
10471080
description_form.value = description;
1081+
source_form.value = source;
10481082
type_form.value = type;
10491083
color_form.value = color;
10501084
if(error){
10511085
error_form.textContent = "Error: " + error;
10521086
error_form.classList.remove("hidden");
10531087
}
10541088
error_form.classList.add("hidden");
1089+
1090+
onTypeChange();
1091+
type_form.addEventListener("change", onTypeChange);
10551092
}
10561093

10571094
function onsubmit() {
@@ -1069,7 +1106,7 @@ function CreateEditCollectionScene(user, password, collection) {
10691106
}
10701107
let loading_scene = new LoadingScene();
10711108
push_scene(loading_scene);
1072-
let collection = new Collection(href, type, displayname, description, sane_color);
1109+
let collection = new Collection(href, type, displayname, description, sane_color, source);
10731110
let callback = function(error1) {
10741111
if (scene_index === null) {
10751112
return;
@@ -1102,6 +1139,16 @@ function CreateEditCollectionScene(user, password, collection) {
11021139
return false;
11031140
}
11041141

1142+
function onTypeChange(e){
1143+
if(type_form.value == CollectionType.WEBCAL){
1144+
source_label.classList.remove("hidden");
1145+
source_form.classList.remove("hidden");
1146+
}else{
1147+
source_label.classList.add("hidden");
1148+
source_form.classList.add("hidden");
1149+
}
1150+
}
1151+
11051152
this.show = function() {
11061153
this.release();
11071154
scene_index = scene_stack.length - 1;

‎radicale/web/internal_data/index.html

+13-6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ <h3 class="title" data-name="title">Title</h3>
6060
<span data-name="CALENDAR">Calendar</span>
6161
<span data-name="JOURNAL">Journal</span>
6262
<span data-name="TASKS">Tasks</span>
63+
<span data-name="WEBCAL">Webcal</span>
6364
</small>
6465
<input type="text" data-name="url" value="" readonly="" onfocus="this.setSelectionRange(0, 99999);">
6566
<p data-name="description" style="word-wrap:break-word;">Description</p>
@@ -97,12 +98,15 @@ <h1>Edit Collection</h1>
9798
<option value="CALENDAR">calendar</option>
9899
<option value="JOURNAL">journal</option>
99100
<option value="TASKS">tasks</option>
101+
<option value="WEBCAL">webcal</option>
100102
</select>
101-
<br> Title: <br>
103+
<label for="displayname">Title:</label>
102104
<input data-name="displayname" type="text">
103-
<br> Description: <br>
105+
<label for="description">Description:</label>
104106
<input data-name="description" type="text">
105-
<br> Color: <br>
107+
<label for="source">Source:</label>
108+
<input data-name="source" type="url">
109+
<label for="color">Color:</label>
106110
<input data-name="color" type="color">
107111
<br>
108112
<span class="error hidden" data-name="error"></span>
@@ -125,12 +129,15 @@ <h1>Create a new Collection</h1>
125129
<option value="CALENDAR">Calendar</option>
126130
<option value="JOURNAL">Journal</option>
127131
<option value="TASKS">Tasks</option>
132+
<option value="WEBCAL">Webcal</option>
128133
</select>
129-
<br> Title: <br>
134+
<label for="displayname">Title:</label>
130135
<input data-name="displayname" type="text">
131-
<br> Description: <br>
136+
<label for="description">Description:</label>
132137
<input data-name="description" type="text">
133-
<br> Color: <br>
138+
<label for="source">Source:</label>
139+
<input data-name="source" type="url">
140+
<label for="color">Color:</label>
134141
<input data-name="color" type="color">
135142
<br>
136143
<span class="error" data-name="error"></span>

‎radicale/xmlutils.py

+3
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ def props_from_request(xml_request: Optional[ET.Element]
178178
if resource_type.tag == make_clark("C:calendar"):
179179
value = "VCALENDAR"
180180
break
181+
if resource_type.tag == make_clark("CS:subscribed"):
182+
value = "VSUBSCRIBED"
183+
break
181184
if resource_type.tag == make_clark("CR:addressbook"):
182185
value = "VADDRESSBOOK"
183186
break

0 commit comments

Comments
 (0)