aboutsummaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
Diffstat (limited to 'static')
-rw-r--r--static/input_list.js6
-rw-r--r--static/jcal-tests.js32
-rw-r--r--static/jcal.js174
-rw-r--r--static/lib.js14
-rw-r--r--static/recur.js0
-rw-r--r--static/rrule.js39
-rw-r--r--static/script.js71
-rw-r--r--static/server_connect.js84
-rw-r--r--static/style.scss8
-rw-r--r--static/types.js11
10 files changed, 362 insertions, 77 deletions
diff --git a/static/input_list.js b/static/input_list.js
index 7cfbc080..a7a446f3 100644
--- a/static/input_list.js
+++ b/static/input_list.js
@@ -77,7 +77,7 @@ function init_input_list() {
if (lst.dataset.bindby) {
lst.get_value = lst.dataset.bindby;
} else if (lst.dataset.joinby) {
- lst.get_value = get_value(lst.dataset.joinby);
+ lst.get_value = get_get_value(lst.dataset.joinby);
} else {
lst.get_value = get_get_value();
}
@@ -103,8 +103,8 @@ function init_input_list() {
const get_get_value = (join=',') => function () {
return [...this.querySelectorAll('input')]
.map(x => x.value)
- .filter(x => x != '')
- .join(join);
+ .filter(x => x != '');
+ // .join(join);
}
/* -------------------------------------------------- */
diff --git a/static/jcal-tests.js b/static/jcal-tests.js
new file mode 100644
index 00000000..c84d9bd1
--- /dev/null
+++ b/static/jcal-tests.js
@@ -0,0 +1,32 @@
+/* "Test cases" for jcal.js.
+ ideally we would actually have runnable tests, but
+ `document' is only available in the browser.
+*/
+
+let doc = document.implementation.createDocument(xcal, 'icalendar');
+
+jcal = ['key', {}, 'text', 'value'];
+
+jcal_property_to_xcal_property(doc, jcal);
+
+
+
+jcal_to_xcal(['vcalendar', [], [['vevent', [['key', {}, 'text', 'value']], []]]]).childNodes[0].outerHTML
+
+/* returns (except not pretty printee)
+<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">
+ <vcalendar>
+ <properties/>
+ <components>
+ <vevent>
+ <properties>
+ <key>
+ <text>value</text>
+ </key>
+ </properties>
+ <components/>
+ </vevent>
+ </components>
+ </vcalendar>
+</icalendar>
+*/
diff --git a/static/jcal.js b/static/jcal.js
new file mode 100644
index 00000000..da17a19e
--- /dev/null
+++ b/static/jcal.js
@@ -0,0 +1,174 @@
+function jcal_type_to_xcal(doc, type, value) {
+ let el = doc.createElementNS(xcal, type);
+ switch (type) {
+ case 'boolean':
+ el.innerHTML = value ? "true" : "false";
+ break;
+
+ case 'float':
+ case 'integer':
+ el.innerHTML = '' + value;
+ break;
+
+ case 'period':
+ let [start, end] = value;
+ let startEl = doc.createElementNS(xcal, 'start');
+ startEl.innerHTML = start;
+ let endEL;
+ if (end.find('P')) {
+ endEl = doc.createElementNS(xcal, 'duration');
+ } else {
+ endEl = doc.createElementNS(xcal, 'end');
+ }
+ endEl.innerHTML = end;
+ el.appendChild(startEl);
+ el.appendChild(endEl);
+ break;
+
+ case 'recur':
+ for (var key in value) {
+ if (! value.hasOwnProperty(key)) continue;
+ let e = doc.createElementNS(xcal, key);
+ e.innerHTML = value[key];
+ el.appendChild(e);
+ }
+ break;
+
+ case 'date':
+ case 'time':
+ case 'date-time':
+
+ case 'duration':
+
+ case 'binary':
+ case 'text':
+ case 'uri':
+ case 'cal-address':
+ case 'utc-offset':
+ el.innerHTML = value;
+ break;
+
+ default:
+ /* TODO error */
+ }
+ return el;
+}
+
+function jcal_property_to_xcal_property(doc, jcal) {
+ let [propertyName, params, type, ...values] = jcal;
+
+ let tag = doc.createElementNS(xcal, propertyName);
+
+ /* setup parameters */
+ let paramEl = doc.createElementNS(xcal, 'params');
+ for (var key in params) {
+ /* Check if the key actually belongs to us.
+ At least (my) format also appears when iterating
+ over the parameters. Probably a case of builtins
+ vs user defined.
+
+ This is also the reason we can't check if params
+ is empty beforehand, and instead check the
+ number of children of paramEl below.
+ */
+ if (! params.hasOwnProperty(key)) continue;
+
+ let el = doc.createElementNS(xcal, key);
+
+ for (let v of asList(params[key])) {
+ let text = doc.createElementNS(xcal, 'text');
+ text.innerHTML = '' + v;
+ el.appendChild(text);
+ }
+
+ paramEl.appendChild(el);
+ }
+
+ if (paramEl.childCount > 0) {
+ tag.appendChild(paramEl);
+ }
+
+ /* setup value (and type) */
+ // let typeEl = doc.createElementNS(xcal, type);
+
+ switch (propertyName) {
+ case 'geo':
+ if (type == 'float') {
+ // assert values[0] == [x, y]
+ let [x, y] = values[0];
+ let lat = doc.createElementNS(xcal, 'latitude')
+ let lon = doc.createElementNS(xcal, 'longitude')
+ lat.innerHTML = x;
+ lon.innerHTML = y;
+ tag.appendChild(lat);
+ tag.appendChild(lon);
+ } else {
+ /* TODO, error */
+ }
+ break;
+ case 'request-status':
+ if (type == 'text') {
+ // assert values[0] instanceof Array
+ let [code, desc, ...data] = values[0];
+ let codeEl = doc.createElementNS(xcal, 'code')
+ code.innerHTML = code;
+ tag.appendChild(codeEl);
+
+
+ let descEl = doc.createElementNS(xcal, 'description')
+ desc.innerHTML = desc;
+ tag.appendChild(descEl);
+
+ if (data !== []) {
+ data = data[0];
+ let dataEl = doc.createElementNS(xcal, 'data')
+ data.innerHTML = data;
+ tag.appendChild(dataEl);
+ }
+ } else {
+ /* TODO, error */
+ }
+ break;
+ default:
+ for (let value of values) {
+ tag.appendChild(jcal_type_to_xcal(doc, type, value))
+ }
+ }
+
+ return tag;
+}
+
+
+function jcal_to_xcal(...jcals) {
+ let doc = document.implementation.createDocument(xcal, 'icalendar');
+ for (let jcal of jcals) {
+ doc.documentElement.appendChild(jcal_to_xcal_inner(doc, jcal));
+ }
+ return doc;
+}
+
+function jcal_to_xcal_inner(doc, jcal) {
+ let [tagname, properties, components] = jcal;
+
+ let xcal_tag = doc.createElementNS(xcal, tagname);
+
+ /* I'm not sure if the properties and components tag should be left out
+ when empty. It should however NOT be an error to leave them in.
+ */
+
+ let xcal_properties = doc.createElementNS(xcal, 'properties');
+ for (let property of properties) {
+ xcal_properties.appendChild(jcal_property_to_xcal_property(doc, property));
+ }
+
+ let xcal_children = doc.createElementNS(xcal, 'components');
+ for (let child of components) {
+ xcal_children.appendChild(jcal_to_xcal_inner(doc, child));
+ }
+
+ xcal_tag.appendChild(xcal_properties);
+ xcal_tag.appendChild(xcal_children);
+
+ return xcal_tag;
+
+}
diff --git a/static/lib.js b/static/lib.js
index ab279353..1d42100c 100644
--- a/static/lib.js
+++ b/static/lib.js
@@ -32,6 +32,9 @@ function zip(...args) {
NOTE that only the raw `get' (and NOT the `getUTC') methods
should be used on these objects, and that the reported timezone
is quite often wrong.
+
+ TODO The years between 0 and 100 (inclusive) gives dates in the twentieth
+ century, due to how javascript works (...).
*/
function parseDate(str) {
@@ -117,7 +120,16 @@ function setVar(str, val) {
}
+function asList(thing) {
+ if (thing instanceof Array) {
+ return thing;
+ } else {
+ return [thing];
+ }
+}
+
+/* internal */
function datepad(thing, width=2) {
return (thing + "").padStart(width, "0");
}
@@ -147,7 +159,7 @@ function format_date(date, str) {
}
return outstr;
}
-Object.prototype.format = function () { return this; } /* any number of arguments */
+Object.prototype.format = function () { return "" + this; } /* any number of arguments */
Date.prototype.format = function (str) { return format_date (this, str); }
/*
diff --git a/static/recur.js b/static/recur.js
deleted file mode 100644
index e69de29b..00000000
--- a/static/recur.js
+++ /dev/null
diff --git a/static/rrule.js b/static/rrule.js
index abc648af..67a4453f 100644
--- a/static/rrule.js
+++ b/static/rrule.js
@@ -8,6 +8,14 @@ function recur_xml_to_rrule(dom_element) {
return rr;
}
+function recur_jcal_to_rrule(jcal) {
+ let rr = new RRule;
+ for (var key in jcal) {
+ rr[key] = jcal[key];
+ }
+ return rr;
+}
+
class RRule {
/* direct access to fields is fine */
@@ -17,7 +25,9 @@ class RRule {
fields = ['freq', 'until', 'count', 'interval',
'bysecond', 'byminute', 'byhour',
'bymonthday', 'byyearday', 'byweekno',
- 'bymonth', 'bysetpos', 'wkst']
+ 'bymonth', 'bysetpos', 'wkst',
+ 'byday'
+ ]
constructor() {
@@ -49,16 +59,33 @@ class RRule {
this.listeners[field].push(proc);
}
- asXcal() {
+ /* NOTE this function is probably never used.
+ Deperate it and refer to RRule.asJcal
+ together with jcal_to_xcal */
+ asXcal(doc) {
/* TODO empty case */
- let str = "<recur>";
+ // let str = "<recur>";
+ let root = doc.createElementNS(xcal, 'recur');
for (let f of this.fields) {
let v = this.fields[f];
if (! v) continue;
- str += `<${f}>${v}</${f}>`;
+ let tag = doc.createElementNS(xcal, f);
+ /* TODO type formatting */
+ tag.innerHTML = `${v}`;
+ root.appendChild(tag);
+ }
+ return root;
+ }
+
+ asJcal() {
+ let obj = {};
+ for (let f of this.fields) {
+ let v = this[f];
+ if (! v) continue;
+ /* TODO special formatting for some types */
+ obj[f] = v;
}
- str += "</recur>";
- return str;
+ return obj;
}
/*
diff --git a/static/script.js b/static/script.js
index 312da431..6b7ddcd9 100644
--- a/static/script.js
+++ b/static/script.js
@@ -383,7 +383,9 @@ window.onload = function () {
*/
function get_property(el, field, default_value) {
if (! el.properties) {
+ /* TODO only have construction once */
el.properties = {};
+ el.properties.ical_properties = new Set()
}
if (! el.properties["_slot_" + field]) {
@@ -424,6 +426,7 @@ function get_property(el, field, default_value) {
function bind_properties (el, wide_event=false) {
el.properties = {}
+ el.properties.ical_properties = new Set()
let popup = popup_from_event(el);
// let children = el.getElementsByTagName("properties")[0].children;
@@ -524,6 +527,9 @@ function bind_properties (el, wide_event=false) {
}]);
}
+ for (let property of property_names) {
+ el.properties.ical_properties.add(property)
+ }
/* icalendar properties */
for (let child of el.querySelector("vevent > properties").children) {
@@ -532,29 +538,15 @@ function bind_properties (el, wide_event=false) {
let field = child.tagName;
let lst = get_property(el, field);
+ el.properties.ical_properties.add(field)
/* Bind vcomponent fields for this event */
for (let s of el.querySelectorAll(`${field} > :not(parameters)`)) {
- lst.push([s, (s, v) => {
- if (v instanceof Date) {
- if (v.isWholeDay) {
- let str = v.format('~Y-~m-~d');
- child.innerHTML = `<date>${str}</date>`;
- } else {
- let str = v.format('~Y-~m-~dT~H:~M:00~Z');
- child.innerHTML = `<date-time>${str}</date-time>`;
- }
- } else if (v instanceof RRule) {
- child.innerHTML = v.asXcal();
- } else {
- /* assume that type already is correct */
- s.innerHTML = v;
- }
- }]);
-
/* Binds value from XML-tree to javascript object
[parsedate]
+
+ TODO capture xcal type here, to enable us to output it to jcal later.
*/
switch (field) {
case 'rrule':
@@ -566,29 +558,6 @@ function bind_properties (el, wide_event=false) {
}
}
- /* Dynamicly add or remove the <location/> and <description/> elements
- from the <vevent><properties/> list.
-
- TODO generalize this to all fields, /especially/ those which are
- dynamicly added.
- */
- for (let field of ['location', 'description', 'categories']) {
- get_property(el, field).push(
- [el.querySelector('vevent > properties'),
- (s, v) => {
- let slot = s.querySelector(field);
- if (v === '' && slot) {
- slot.remove();
- } else {
- if (! slot) {
- /* finns det verkligen inget bättre sätt... */
- s.innerHTML += `<${field}><text/></${field}>`;
- }
- s.querySelector(`${field} > text`).innerHTML = v;
- }
- }]);
- }
-
/* set up graphical display changes */
let container = el.closest(".event-container");
if (container === null) {
@@ -618,28 +587,6 @@ function bind_properties (el, wide_event=false) {
}
- /* Update XML on rrule field change */
- if (el.properties.rrule) {
- for (let f of el.properties.rrule.fields) {
- el.properties.rrule.addListener(
- f, v => {
- console.log(v);
- let recur = el.querySelector('rrule recur');
- let field = recur.querySelector(f);
- if (field) {
- if (! v) {
- field.remove();
- } else {
- field.innerHTML = v;
- }
- } else {
- if (v) recur.innerHTML += `<${f}>${v}</${f}>`;
- }
- });
- }
- }
-
-
/* ---------- Calendar ------------------------------ */
if (! el.dataset.calendar) {
diff --git a/static/server_connect.js b/static/server_connect.js
index e789d72c..9794d87e 100644
--- a/static/server_connect.js
+++ b/static/server_connect.js
@@ -21,16 +21,90 @@ async function remove_event (element) {
}
}
+function event_to_jcal (event) {
+ let properties = [];
+
+ for (let prop of event.properties.ical_properties) {
+ let v = event.properties[prop];
+ if (v !== undefined) {
+
+ let type = 'text';
+ let value;
+
+ if (v instanceof Array) {
+ } else if (v instanceof Date) {
+ if (v.isWholeDay) {
+ type = 'date';
+ value = v.format("~Y-~m-~d");
+ } else {
+ type = 'date-time';
+ /* TODO TZ */
+ value = v.format("~Y-~m-~dT~H:~M:~S");
+ }
+ } else if (v === true || v === false) {
+ type = 'boolean';
+ value = v;
+ } else if (typeof(v) == 'number') {
+ /* TODO float or integer */
+ type = 'integer';
+ value = v;
+ } else if (v instanceof RRule) {
+ type = 'recur';
+ value = v.asJcal();
+ }
+ /* TODO period */
+ else {
+ /* text types */
+ value = v;
+ }
+
+ properties.push([prop, {}, type, value]);
+ }
+ }
+
+ return ['vevent', properties, [/* alarms go here */]]
+}
+
async function create_event (event) {
- let xml = event.getElementsByTagName("icalendar")[0].outerHTML
+ // let xml = event.getElementsByTagName("icalendar")[0].outerHTML
let calendar = event.properties.calendar;
- console.log(calendar, xml);
+ console.log(calendar/*, xml*/);
let data = new URLSearchParams();
data.append("cal", calendar);
- data.append("data", xml);
+ // data.append("data", xml);
+
+ console.log(event);
+
+
+
+ let jcal =
+ ['vcalendar',
+ [
+ /*
+ 'prodid' and 'version' are technically both required (RFC 5545,
+ 3.6 Calendar Components).
+ */
+ ],
+ [
+ /* vtimezone goes here */
+ event_to_jcal(event),
+ ]
+ ];
+
+ console.log(jcal);
+
+ let doc = jcal_to_xcal(jcal);
+ console.log(doc);
+ let str = doc.childNodes[0].outerHTML;
+ console.log(str);
+ data.append("data", str);
+
+ // console.log(event.properties);
+
+ // return;
let response = await fetch ( '/insert', {
method: 'POST',
@@ -54,12 +128,12 @@ async function create_event (event) {
*/
let parser = new DOMParser();
- let properties = parser
+ let return_properties = parser
.parseFromString(body, 'text/xml')
.children[0];
let child;
- while ((child = properties.firstChild)) {
+ while ((child = return_properties.firstChild)) {
let target = event.querySelector(
"vevent properties " + child.tagName);
if (target) {
diff --git a/static/style.scss b/static/style.scss
index 4cd6b410..2d6b87b6 100644
--- a/static/style.scss
+++ b/static/style.scss
@@ -173,6 +173,10 @@ html, body {
}
}
+li > button {
+ width: 100%;
+}
+
/* Eventlist
----------------------------------------
The sidebar with all the events
@@ -944,6 +948,10 @@ along with their colors.
background-color: var(--color);
}
+.side-by-side {
+ display: flex;
+}
+
/* Icalendar
----------------------------------------
*/
diff --git a/static/types.js b/static/types.js
index cfed8584..9a4aa01c 100644
--- a/static/types.js
+++ b/static/types.js
@@ -15,6 +15,17 @@ let all_types = [
'boolean',
]
+let property_names = [
+ 'calscale', 'method', 'prodid', 'version', 'attach', 'categories',
+ 'class', 'comment', 'description', 'geo', 'location', 'percent-complete',
+ 'priority', 'resources', 'status', 'summary', 'completed', 'dtend', 'due',
+ 'dtstart', 'duration', 'freebusy', 'transp', 'tzid', 'tzname', 'tzoffsetfrom',
+ 'tzoffsetto', 'tzurl', 'attendee', 'contact', 'organizer', 'recurrence-id',
+ 'related-to', 'url', 'uid', 'exdate', 'exrule', 'rdate', 'rrule', 'action',
+ 'repeat', 'trigger', 'created', 'dtstamp', 'last-modified', 'sequence', 'request-status'
+];
+
+
let valid_fields = {
'VCALENDAR': ['PRODID', 'VERSION', 'CALSCALE', 'METHOD'],
'VEVENT': ['DTSTAMP', 'UID', 'DTSTART', 'CLASS', 'CREATED',