aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/frontend.org24
-rw-r--r--module/calp/html/view/calendar.scm4
-rw-r--r--static/arbitary_kv.js160
-rw-r--r--static/clock.js70
-rw-r--r--static/dragable.js41
-rw-r--r--static/input_list.js5
-rw-r--r--static/popup.js61
-rw-r--r--static/script.js418
-rw-r--r--static/server_connect.js75
9 files changed, 457 insertions, 401 deletions
diff --git a/doc/frontend.org b/doc/frontend.org
new file mode 100644
index 00000000..6887d6d3
--- /dev/null
+++ b/doc/frontend.org
@@ -0,0 +1,24 @@
+** Javascript "components"
+
+*** input-list
+file:../static/input_list.js
+
+All elements having 'input-list' ∈ =class=
+
+Direct children must all have 'unit' ∈ =class=
+One direct child must have 'final' ∈ =class=
+
+binds =get_value= on instances, by default returning the value
+of all =<input/>= tags joined by =,=. This can be overwritten with
+- =data-joinby= :: Alternative character to join by
+- =data-bindby= :: replacement for get_value
+
+*** Popup
+Handles popup. Currently rather specific
+
+*** Dragable
+file:../static/dragable.js
+
+Manually apply =bind_popup_control= to the statusbar of a floating "window".
+Nothing is required from the component, but the "window" must have
+'popup-container' ∈ =class=
diff --git a/module/calp/html/view/calendar.scm b/module/calp/html/view/calendar.scm
index 0e90e5d4..0b2e50ab 100644
--- a/module/calp/html/view/calendar.scm
+++ b/module/calp/html/view/calendar.scm
@@ -106,6 +106,10 @@
(script (@ (defer) (src "/static/types.js")))
(script (@ (defer) (src "/static/lib.js")))
+ (script (@ (defer) (src "/static/dragable.js")))
+ (script (@ (defer) (src "/static/clock.js")))
+ (script (@ (defer) (src "/static/popup.js")))
+ (script (@ (defer) (src "/static/server_connect.js")))
(script (@ (defer) (src "/static/input_list.js")))
(script (@ (defer) (src "/static/script.js")))
,(calendar-styles calendars))
diff --git a/static/arbitary_kv.js b/static/arbitary_kv.js
new file mode 100644
index 00000000..baf387eb
--- /dev/null
+++ b/static/arbitary_kv.js
@@ -0,0 +1,160 @@
+/*
+ The freeform key-value fields at the bottom of any popup.
+ */
+
+function init_arbitary_kv() {
+ for (let el of document.getElementsByClassName("newfield")) {
+ let [name, type_selector, value_field] = el.children;
+
+ /* TODO list fields */
+ /* TODO add and remove fields. See update_inline_list */
+
+ function update_value_field (el) {
+ let [name_field, type_selector, value_field] = el.children;
+
+
+ let value = makeElement('input');
+ let values = [value];
+
+
+ switch (name_field.value.toUpperCase()) {
+ case 'GEO':
+ value.type = 'number';
+ values.push(makeElement('input', {
+ type: 'number',
+ }));
+ break;
+
+ case 'CLASS':
+ // Add auto completion
+ break;
+
+ case 'ACTION':
+ // Add auto completion
+ break;
+
+ case 'TRANSP':
+ // Replace with toggle betwen OPAQUE and TRANSPARENT
+ break;
+
+ case 'PERCENT-COMPLETE':
+ value.min = 0;
+ value.max = 100;
+ break;
+
+ case 'PRIORITY':
+ value.min = 0;
+ value.max = 9;
+ break;
+
+ default:
+
+
+ switch (type_selector.options[type_selector.selectedIndex].value) {
+ case 'integer':
+ case 'float':
+ value.type = 'number';
+ break;
+
+ case 'uri':
+ value.type = 'url';
+ break;
+
+ case 'binary':
+ value.type = 'file';
+ break;
+
+ case 'date-time':
+ values.push(makeElement('input', {
+ type: 'time',
+ }));
+ /* fallthrough */
+ case 'date':
+ value.type = 'date';
+ break;
+
+ case 'cal-address':
+ value.type = 'email';
+ break;
+
+ case 'utc-offset':
+ value.type = 'time';
+ let lbl = makeElement('label');
+ let id = gensym();
+
+ lbl.setAttribute('for', id);
+
+ /* TODO make these labels stand out more */
+ lbl.appendChild(makeElement('span', {
+ className: 'plus',
+ innerText: '+',
+ }));
+ lbl.appendChild(makeElement('span', {
+ className: 'minus',
+ innerText: '-',
+ }));
+ values.splice(0,0,lbl);
+ values.splice(0,0, makeElement('input', {
+ type: 'checkbox',
+ style: 'display:none',
+ className: 'plusminuscheck',
+ id: id,
+ }));
+ break;
+
+ case 'boolean':
+ value.type = 'checkbox';
+ break;
+
+ case 'period':
+ value.type = 'text';
+ // TODO validate /P\d*H/ typ
+ break;
+
+ case 'recur':
+ // TODO
+ default:
+ value.type = 'text';
+ }
+ }
+
+
+ value_field.innerHTML = '';
+ for (let v of values) {
+ console.log(v);
+ value_field.appendChild(v);
+ }
+ }
+
+ name.addEventListener('input', function setOptionDropdown () {
+ let types = valid_input_types[this.value.toUpperCase()];
+ let el = this.parentElement;
+ let [_, type_selector, value_field] = el.children;
+
+ type_selector.disabled = false;
+ if (types) {
+ type_selector.innerHTML = '';
+ for (let type of types) {
+ type_selector.appendChild(
+ makeElement('option', { value: type, innerText: type }))
+ }
+ if (types.length == 1) {
+ type_selector.disabled = true;
+ }
+ } else {
+ type_selector.innerHTML = '';
+ for (let type of all_types) {
+ type_selector.appendChild(
+ makeElement('option', { value: type, innerText: type }))
+ }
+ }
+
+ update_value_field(el);
+ });
+
+ type_selector.addEventListener('change', function () {
+ update_value_field(this.parentElement);
+ });
+ }
+
+}
diff --git a/static/clock.js b/static/clock.js
new file mode 100644
index 00000000..badfd1db
--- /dev/null
+++ b/static/clock.js
@@ -0,0 +1,70 @@
+
+class Clock {
+ update(now) {
+ }
+}
+
+
+class Timebar extends Clock {
+
+ constructor(start_time, end_time) {
+ super();
+ this.start_time = start_time;
+ this.end_time = end_time;
+ this.bar_object = false
+ }
+
+
+ update(now) {
+ if (! (this.start_time <= now.getTime() && now.getTime() < this.end_time))
+ return;
+
+ var event_area = document.getElementById(now.format("~Y-~m-~d"))
+
+ if (event_area) {
+ if (this.bar_object) {
+ this.bar_object.parentNode.removeChild(bar_object)
+ } else {
+ this.bar_object = makeElement ('div', {
+ id: 'bar',
+ className: 'eventlike current-time',
+ });
+ }
+
+ this.bar_object.style.top = date_to_percent(now) + "%";
+ event_area.append(this.bar_object)
+ }
+ }
+}
+
+class SmallcalCellHighlight extends Clock {
+ constructor(small_cal) {
+ super();
+ this.small_cal = small_cal;
+ this.current_cell = false
+ }
+
+ update(now) {
+ if (current_cell) {
+ current_cell.style.border = "";
+ }
+
+ current_cell = this.small_cal.querySelector(
+ "time[datetime='" + now.format("~Y-~m-~d") + "']");
+
+ current_cell.style.border = "1px solid black";
+ }
+}
+
+/* Update [today] button */
+class ButtonUpdater extends Clock {
+ constructor(el, proc) {
+ super();
+ this.el = el;
+ this.proc = proc;
+ }
+
+ update(now) {
+ this.proc(e, now);
+ }
+}
diff --git a/static/dragable.js b/static/dragable.js
new file mode 100644
index 00000000..41895760
--- /dev/null
+++ b/static/dragable.js
@@ -0,0 +1,41 @@
+/*
+ Apply to a given component to make it draggable.
+ Drag area (usually a title bar) should be be the only argument.
+ It is REQUIRED that the object which should be moved have the class
+ 'popup-container';
+*/
+
+
+/*
+ Given the navbar of a popup, make it dragable.
+ */
+function bind_popup_control (nav) {
+
+ if (! nav.closest('.popup-container')) {
+ throw TypeError('not a popup container');
+ }
+
+ nav.onmousedown = function (e) {
+ /* Ignore mousedown on children */
+ if (e.target != nav) return;
+ nav.style.cursor = "grabbing";
+ nav.dataset.grabbed = "true";
+ nav.dataset.grabPoint = e.clientX + ";" + e.clientY;
+ let popup = nav.closest(".popup-container");
+ nav.dataset.startPoint = popup.offsetLeft + ";" + popup.offsetTop;
+ }
+ window.addEventListener('mousemove', function (e) {
+ if (nav.dataset.grabbed) {
+ let [x, y] = nav.dataset.grabPoint.split(";").map(Number);
+ let [startX, startY] = nav.dataset.startPoint.split(";").map(Number);
+ let popup = nav.closest(".popup-container");
+
+ popup.style.left = startX + (e.clientX - x) + "px";
+ popup.style.top = startY + (e.clientY - y) + "px";
+ }
+ });
+ window.addEventListener('mouseup', function () {
+ nav.dataset.grabbed = "";
+ nav.style.cursor = "";
+ });
+}
diff --git a/static/input_list.js b/static/input_list.js
index 79e223c3..3b24b719 100644
--- a/static/input_list.js
+++ b/static/input_list.js
@@ -1,6 +1,4 @@
/*
- TODO document 'input-list'.
-
∀ children('.input-list') => 'unit' ∈ classList(child)
<div class="input-list">
@@ -78,7 +76,6 @@ function init_input_list() {
lst.get_value = lst.dataset.bindby;
} else if (lst.dataset.joinby) {
lst.get_value = get_value(lst.dataset.joinby);
- }
} else {
lst.get_value = get_get_value();
}
@@ -88,7 +85,7 @@ function init_input_list() {
/* -------------------------------------------------- */
/* different function forms since we want to capture one self */
-const get_get_value(join=',') => function () {
+const get_get_value = (join=',') => function () {
return [...self.querySelectorAll('input')]
.map(x => x.value)
.filter(x => x != '')
diff --git a/static/popup.js b/static/popup.js
new file mode 100644
index 00000000..bc4e766d
--- /dev/null
+++ b/static/popup.js
@@ -0,0 +1,61 @@
+
+
+/* event component => coresponding popup component */
+function event_from_popup(popup) {
+ return document.getElementById(popup.id.substr(5))
+}
+
+/* popup component => coresponding event component */
+function popup_from_event(event) {
+ return document.getElementById("popup" + event.id);
+}
+
+/* hides given popup */
+function close_popup(popup) {
+ popup.classList.remove("visible");
+}
+
+/* hides all popups */
+function close_all_popups () {
+ for (let popup of document.querySelectorAll(".popup-container.visible")) {
+ close_popup(popup);
+ }
+}
+
+/* open given popup */
+function open_popup(popup) {
+ popup.classList.add("visible");
+ let element = event_from_popup(popup);
+ // let root = document.body;
+ let root;
+ switch (VIEW) {
+ case 'week':
+ root = document.getElementsByClassName("days")[0];
+ break;
+ case 'month':
+ default:
+ root = document.body;
+ break;
+ }
+ /* start <X, Y> sets offset between top left corner
+ of event in calendar and popup. 10, 10 soo old
+ event is still visible */
+ let offsetX = 10, offsetY = 10;
+ while (element !== root) {
+ offsetX += element.offsetLeft;
+ offsetY += element.offsetTop;
+ element = element.offsetParent;
+ }
+ popup.style.left = offsetX + "px";
+ popup.style.top = offsetY + "px";
+}
+
+/* toggles open/closed status of popup given by id */
+function toggle_popup(popup_id) {
+ let popup = document.getElementById(popup_id);
+ if (popup.classList.contains("visible")) {
+ close_popup(popup);
+ } else {
+ open_popup(popup);
+ }
+}
diff --git a/static/script.js b/static/script.js
index 60011cd8..74c90e27 100644
--- a/static/script.js
+++ b/static/script.js
@@ -4,41 +4,6 @@
calp specific stuff
*/
-let parser = new DOMParser();
-
-/* start and end time for calendar page */
-let start_time = new Date();
-let end_time = new Date();
-
-/*
- Given the navbar of a popup, make it dragable.
- */
-function bind_popup_control (nav) {
- nav.onmousedown = function (e) {
- /* Ignore mousedown on children */
- if (e.target != nav) return;
- nav.style.cursor = "grabbing";
- nav.dataset.grabbed = "true";
- nav.dataset.grabPoint = e.clientX + ";" + e.clientY;
- let popup = nav.closest(".popup-container");
- nav.dataset.startPoint = popup.offsetLeft + ";" + popup.offsetTop;
- }
- window.addEventListener('mousemove', function (e) {
- if (nav.dataset.grabbed) {
- let [x, y] = nav.dataset.grabPoint.split(";").map(Number);
- let [startX, startY] = nav.dataset.startPoint.split(";").map(Number);
- let popup = nav.closest(".popup-container");
-
- popup.style.left = startX + (e.clientX - x) + "px";
- popup.style.top = startY + (e.clientY - y) + "px";
- }
- });
- window.addEventListener('mouseup', function () {
- nav.dataset.grabbed = "";
- nav.style.cursor = "";
- });
-}
-
class EventCreator {
/* dynamicly created event when dragging */
@@ -46,7 +11,6 @@ class EventCreator {
this.event = false;
this.event_start = { x: NaN, y: NaN };
this.down_on_event = false;
-
}
create_empty_event () {
@@ -152,7 +116,6 @@ class EventCreator {
bind_properties(event, wide_element);
/* requires that dtstart and dtend properties are initialized */
- // place_in_edit_mode(event);
/* ---------------------------------------- */
@@ -224,119 +187,6 @@ class EventCreator {
}
}
-async function remove_event (element) {
- let uid = element.querySelector("icalendar uid text").innerHTML;
-
- let data = new URLSearchParams();
- data.append('uid', uid);
-
- let response = await fetch ( '/remove', {
- method: 'POST',
- body: data
- });
-
- console.log(response);
- toggle_popup("popup" + element.id);
-
- if (response.status < 200 || response.status >= 300) {
- let body = await response.text();
- alert(`HTTP error ${response.status}\n${body}`)
- } else {
- element.remove();
- }
-}
-
-var bar_object = false
-var current_cell = false
-
-function update_current_time_bar () {
- var now = new Date()
- if (! (start_time <= now.getTime() && now.getTime() < end_time))
- return;
-
- var event_area = document.getElementById(now.format("~Y-~m-~d"))
-
- if (event_area) {
- if (bar_object) {
- bar_object.parentNode.removeChild(bar_object)
- } else {
- bar_object = makeElement ('div', {
- id: 'bar',
- className: 'eventlike current-time',
- });
- }
-
- bar_object.style.top = date_to_percent(now) + "%";
- event_area.append(bar_object)
- }
-
- /* */
-
- if (current_cell) {
- current_cell.style.border = "";
- }
- current_cell = document.querySelector(
- ".small-calendar time[datetime='" + now.format("~Y-~m-~d") + "']");
- current_cell.style.border = "1px solid black";
-
- /* Update [today] button */
-
- document.getElementById("today-button").href
- = (new Date).format("~Y-~m-~d") + ".html";
-}
-
-async function create_event (event) {
-
- let xml = event.getElementsByTagName("icalendar")[0].outerHTML
- let calendar = event.properties.calendar;
-
- console.log(calendar, xml);
-
- let data = new URLSearchParams();
- data.append("cal", calendar);
- data.append("data", xml);
-
- let response = await fetch ( '/insert', {
- method: 'POST',
- body: data
- });
-
- console.log(response);
- if (response.status < 200 || response.status >= 300) {
- let body = await response.text();
- alert(`HTTP error ${response.status}\n${body}`)
- return;
- }
-
- let body = await response.text();
-
- /* server is assumed to return an XML document on the form
- <properties>
- **xcal property** ...
- </properties>
- parse that, and update our own vevent with the data.
- */
-
- let properties = parser
- .parseFromString(body, 'text/xml')
- .children[0];
-
- let child;
- while ((child = properties.firstChild)) {
- let target = event.querySelector(
- "vevent properties " + child.tagName);
- if (target) {
- target.replaceWith(child);
- } else {
- event.querySelector("vevent properties")
- .appendChild(child);
- }
- }
-
- event.classList.remove("generated");
- toggle_popup("popup" + event.id);
-}
-
/* This incarnation of this function only adds the calendar switcher dropdown.
All events are already editable by switching to that tab.
@@ -378,11 +228,26 @@ function place_in_edit_mode (event) {
}
window.onload = function () {
- start_time.setTime(document.querySelector("meta[name='start-time']").content * 1000)
- end_time.setTime(document.querySelector("meta[name='end-time']").content * 1000)
+ let start_time = document.querySelector("meta[name='start-time']").content;
+ let end_time = document.querySelector("meta[name='end-time']").content;
+
+ const button_updater = new ButtonUpdater(
+ document.getElementById("today-button"),
+ (e, d) => e.href = d.format('~Y-~m-~d') + ".html"
+ );
+
+ const sch = new SmallcalCellHighlight(
+ document.querySelector('.small-calendar'))
- update_current_time_bar()
- window.setInterval(update_current_time_bar, 1000 * 60)
+ const timebar = new Timebar(start_time, end_time);
+
+ timebar.update(new Date);
+ window.setInterval(() => {
+ let d = new Date;
+ timebar.update(d);
+ button_updater.update(d);
+ sch.update(d);
+ }, 1000 * 60);
/* Is event creation active? */
if (EDIT_MODE) {
@@ -483,12 +348,6 @@ window.onload = function () {
/* ---------------------------------------- */
- /*
- xml.querySelector("summary text").innerHTML = "Pastahack";
- let serializer = new XMLSerializer();
- serializer.serializeToString(xml);
- */
-
/* needs to be called AFTER bind_properties, but BEFORE init_input_list
After bind_properties since that initializes categories to a possible field
Before init_input_list since we need this listener to be propagated to clones.
@@ -498,11 +357,7 @@ window.onload = function () {
let f = function () {
console.log(lst, lst.closest('.popup-container'));
let event = event_from_popup(lst.closest('.popup-container'))
- event.properties.categories = [...lst.querySelectorAll('input')]
- .map(x => x.value)
- .filter(x => x != '')
- .join(',');
-
+ event.properties.categories = lst.get_value();
};
for (let inp of lst.querySelectorAll('input')) {
@@ -510,225 +365,12 @@ window.onload = function () {
}
}
-
- /* ---------------------------------------- */
-
-
- for (let el of document.getElementsByClassName("newfield")) {
- let [name, type_selector, value_field] = el.children;
-
- /* TODO list fields */
- /* TODO add and remove fields. See update_inline_list */
-
- function update_value_field (el) {
- let [name_field, type_selector, value_field] = el.children;
-
-
- let value = makeElement('input');
- let values = [value];
-
-
- switch (name_field.value.toUpperCase()) {
- case 'GEO':
- value.type = 'number';
- values.push(makeElement('input', {
- type: 'number',
- }));
- break;
-
- case 'CLASS':
- // Add auto completion
- break;
-
- case 'ACTION':
- // Add auto completion
- break;
-
- case 'TRANSP':
- // Replace with toggle betwen OPAQUE and TRANSPARENT
- break;
-
- case 'PERCENT-COMPLETE':
- value.min = 0;
- value.max = 100;
- break;
-
- case 'PRIORITY':
- value.min = 0;
- value.max = 9;
- break;
-
- default:
-
-
- switch (type_selector.options[type_selector.selectedIndex].value) {
- case 'integer':
- case 'float':
- value.type = 'number';
- break;
-
- case 'uri':
- value.type = 'url';
- break;
-
- case 'binary':
- value.type = 'file';
- break;
-
- case 'date-time':
- values.push(makeElement('input', {
- type: 'time',
- }));
- /* fallthrough */
- case 'date':
- value.type = 'date';
- break;
-
- case 'cal-address':
- value.type = 'email';
- break;
-
- case 'utc-offset':
- value.type = 'time';
- let lbl = makeElement('label');
- let id = gensym();
-
- lbl.setAttribute('for', id);
-
- /* TODO make these labels stand out more */
- lbl.appendChild(makeElement('span', {
- className: 'plus',
- innerText: '+',
- }));
- lbl.appendChild(makeElement('span', {
- className: 'minus',
- innerText: '-',
- }));
- values.splice(0,0,lbl);
- values.splice(0,0, makeElement('input', {
- type: 'checkbox',
- style: 'display:none',
- className: 'plusminuscheck',
- id: id,
- }));
- break;
-
- case 'boolean':
- value.type = 'checkbox';
- break;
-
- case 'period':
- value.type = 'text';
- // TODO validate /P\d*H/ typ
- break;
-
- case 'recur':
- // TODO
- default:
- value.type = 'text';
- }
- }
-
-
- value_field.innerHTML = '';
- for (let v of values) {
- console.log(v);
- value_field.appendChild(v);
- }
- }
-
- name.addEventListener('input', function setOptionDropdown () {
- let types = valid_input_types[this.value.toUpperCase()];
- let el = this.parentElement;
- let [_, type_selector, value_field] = el.children;
-
- type_selector.disabled = false;
- if (types) {
- type_selector.innerHTML = '';
- for (let type of types) {
- type_selector.appendChild(
- makeElement('option', { value: type, innerText: type }))
- }
- if (types.length == 1) {
- type_selector.disabled = true;
- }
- } else {
- type_selector.innerHTML = '';
- for (let type of all_types) {
- type_selector.appendChild(
- makeElement('option', { value: type, innerText: type }))
- }
- }
-
- update_value_field(el);
- });
-
- type_selector.addEventListener('change', function () {
- update_value_field(this.parentElement);
- });
- }
-
-
+ // init_arbitary_kv();
init_input_list();
}
-function event_from_popup(popup) {
- return document.getElementById(popup.id.substr(5))
-}
-
-function popup_from_event(event) {
- return document.getElementById("popup" + event.id);
-}
-
-function close_popup(popup) {
- popup.classList.remove("visible");
-}
-
-function close_all_popups () {
- for (let popup of document.querySelectorAll(".popup-container.visible")) {
- close_popup(popup);
- }
-}
-
-function open_popup(popup) {
- popup.classList.add("visible");
- let element = event_from_popup(popup);
- // let root = document.body;
- let root;
- switch (VIEW) {
- case 'week':
- root = document.getElementsByClassName("days")[0];
- break;
- case 'month':
- default:
- root = document.body;
- break;
- }
- /* start <X, Y> sets offset between top left corner
- of event in calendar and popup. 10, 10 soo old
- event is still visible */
- let offsetX = 10, offsetY = 10;
- while (element !== root) {
- offsetX += element.offsetLeft;
- offsetY += element.offsetTop;
- element = element.offsetParent;
- }
- popup.style.left = offsetX + "px";
- popup.style.top = offsetY + "px";
-}
-
-function toggle_popup(popup_id) {
- let popup = document.getElementById(popup_id);
- if (popup.classList.contains("visible")) {
- close_popup(popup);
- } else {
- open_popup(popup);
- }
-}
-
-
/*
Returns the _value_ slot of given field in event, creating it if needed .
@@ -767,24 +409,6 @@ function get_property(el, field, default_value) {
/*
-class display_tab {
-}
-
-class edit_tab {
-}
-
-class vcomponent {
- set_value(field, value) {
- if (value === '') {
- remove_property(field);
- }
- }
-}
-
-
-*/
-
-/*
Properties are icalendar properties.
p['name'] to get and set value (also updates any connected slots)
diff --git a/static/server_connect.js b/static/server_connect.js
new file mode 100644
index 00000000..e789d72c
--- /dev/null
+++ b/static/server_connect.js
@@ -0,0 +1,75 @@
+
+async function remove_event (element) {
+ let uid = element.querySelector("icalendar uid text").innerHTML;
+
+ let data = new URLSearchParams();
+ data.append('uid', uid);
+
+ let response = await fetch ( '/remove', {
+ method: 'POST',
+ body: data
+ });
+
+ console.log(response);
+ toggle_popup("popup" + element.id);
+
+ if (response.status < 200 || response.status >= 300) {
+ let body = await response.text();
+ alert(`HTTP error ${response.status}\n${body}`)
+ } else {
+ element.remove();
+ }
+}
+
+async function create_event (event) {
+
+ let xml = event.getElementsByTagName("icalendar")[0].outerHTML
+ let calendar = event.properties.calendar;
+
+ console.log(calendar, xml);
+
+ let data = new URLSearchParams();
+ data.append("cal", calendar);
+ data.append("data", xml);
+
+ let response = await fetch ( '/insert', {
+ method: 'POST',
+ body: data
+ });
+
+ console.log(response);
+ if (response.status < 200 || response.status >= 300) {
+ let body = await response.text();
+ alert(`HTTP error ${response.status}\n${body}`)
+ return;
+ }
+
+ let body = await response.text();
+
+ /* server is assumed to return an XML document on the form
+ <properties>
+ **xcal property** ...
+ </properties>
+ parse that, and update our own vevent with the data.
+ */
+
+ let parser = new DOMParser();
+ let properties = parser
+ .parseFromString(body, 'text/xml')
+ .children[0];
+
+ let child;
+ while ((child = properties.firstChild)) {
+ let target = event.querySelector(
+ "vevent properties " + child.tagName);
+ if (target) {
+ target.replaceWith(child);
+ } else {
+ event.querySelector("vevent properties")
+ .appendChild(child);
+ }
+ }
+
+ event.classList.remove("generated");
+ toggle_popup("popup" + event.id);
+}