aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2021-10-04 23:18:24 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2021-10-04 23:18:24 +0200
commit8ec2f441d40ab89b40cc3158f65c914eff497cee (patch)
treec05638cd570a641234ad5973a1790762e7bc8cca
parent{.js => .ts} on relavant files. (diff)
downloadcalp-8ec2f441d40ab89b40cc3158f65c914eff497cee.tar.gz
calp-8ec2f441d40ab89b40cc3158f65c914eff497cee.tar.xz
Major typescript work.
-rw-r--r--module/calp/html/view/calendar.scm2
-rw-r--r--module/calp/html/view/calendar/week.scm4
-rw-r--r--static/.gitignore10
-rw-r--r--static/clock.ts53
-rw-r--r--static/dragable.ts23
-rw-r--r--static/globals.ts211
-rw-r--r--static/jcal.ts143
-rw-r--r--static/lib.ts154
-rw-r--r--static/popup.ts76
-rw-r--r--static/rrule.ts.disabled (renamed from static/rrule.ts)40
-rw-r--r--static/script.ts193
-rw-r--r--static/server_connect.ts89
-rw-r--r--static/types.ts228
-rw-r--r--static/vevent.ts268
14 files changed, 867 insertions, 627 deletions
diff --git a/module/calp/html/view/calendar.scm b/module/calp/html/view/calendar.scm
index dfcd2264..c328f8b3 100644
--- a/module/calp/html/view/calendar.scm
+++ b/module/calp/html/view/calendar.scm
@@ -110,7 +110,7 @@
(script (@ (defer) (src "/static/dragable.js")))
(script (@ (defer) (src "/static/clock.js")))
(script (@ (defer) (src "/static/popup.js")))
- (script (@ (defer) (src "/static/rrule.js")))
+ ;; (script (@ (defer) (src "/static/rrule.js")))
;; (script (@ (defer) (src "/static/binders.js")))
(script (@ (defer) (src "/static/server_connect.js")))
;; (script (@ (defer) (src "/static/input_list.js")))
diff --git a/module/calp/html/view/calendar/week.scm b/module/calp/html/view/calendar/week.scm
index 9911b162..17bb3b2d 100644
--- a/module/calp/html/view/calendar/week.scm
+++ b/module/calp/html/view/calendar/week.scm
@@ -15,7 +15,7 @@
:use-module ((calp html vcomponent)
:select (make-block) )
:use-module ((calp html components)
- :select (btn tabset #; #; form with-label
+ :select (btn tabset ; form with-label
))
:use-module ((vcomponent group)
:select (group-stream get-groups-between))
@@ -170,7 +170,7 @@
;; (eq? 'TRANSPARENT (prop ev 'TRANSP)))
;; " transparent")
)
- (onclick "toggle_popup('popup' + this.id)")
+ ; (onclick "toggle_popup('popup' + this.id)")
)
;; Inner div to prevent overflow. Previously "overflow: none"
;; was set on the surounding div, but the popup /needs/ to
diff --git a/static/.gitignore b/static/.gitignore
index 735b5dce..3153016b 100644
--- a/static/.gitignore
+++ b/static/.gitignore
@@ -1,3 +1,13 @@
*.css
.*-cache
*.map
+clock.js
+dragable.js
+globals.js
+jcal.js
+lib.js
+popup.js
+script.js
+server_connect.js
+types.js
+vevent.js
diff --git a/static/clock.ts b/static/clock.ts
index d33d603a..c4feda8f 100644
--- a/static/clock.ts
+++ b/static/clock.ts
@@ -1,31 +1,35 @@
class Clock {
- update(now) {
+ update(now: Date) {
}
}
class Timebar extends Clock {
- constructor(start_time, end_time) {
+ // start_time: Date
+ // end_time: Date
+ bar_object: HTMLElement | null
+
+ constructor(/*start_time: Date, end_time: Date*/) {
super();
- this.start_time = start_time;
- this.end_time = end_time;
- this.bar_object = false
+ // this.start_time = start_time;
+ // this.end_time = end_time;
+ this.bar_object = null
}
- update(now) {
+ update(now: Date) {
// 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) {
+ if (this.bar_object !== null && this.bar_object.parentNode !== null) {
this.bar_object.parentNode.removeChild(this.bar_object)
} else {
- this.bar_object = makeElement ('div', {
+ this.bar_object = makeElement('div', {
id: 'bar',
className: 'eventlike current-time',
});
@@ -38,13 +42,17 @@ class Timebar extends Clock {
}
class SmallcalCellHighlight extends Clock {
- constructor(small_cal) {
+
+ small_cal: HTMLElement
+ current_cell: HTMLElement | null
+
+ constructor(small_cal: HTMLElement) {
super();
this.small_cal = small_cal;
- this.current_cell = false
+ this.current_cell = null
}
- update(now) {
+ update(now: Date) {
if (this.current_cell) {
this.current_cell.style.border = "";
}
@@ -63,35 +71,42 @@ class SmallcalCellHighlight extends Clock {
/* -------------------------------------------------- */
class ClockElement extends HTMLElement {
- constructor () {
+
+ timer_id: number
+
+ constructor() {
super();
+
+ this.timer_id = 0
}
- connectedCallback () {
- let interval = this.hasAttribute('interval') ? +this.getAttribute('img') : 60;
+ connectedCallback() {
+ let interval = this.hasAttribute('interval')
+ ? +(this.getAttribute('interval') as string)
+ : 60;
interval *= 1000 /* ms */
this.timer_id = window.setInterval(() => this.update(new Date), interval)
this.update(new Date)
}
- static get observedAttributes () {
+ static get observedAttributes() {
return ['timer_id']
}
- update (now) { /* noop */ }
+ update(now: Date) { /* noop */ }
}
class TodayButton extends ClockElement {
- update (now) {
- this.querySelector('a').href = now.format("~Y-~m-~d.html")
+ update(now: Date) {
+ (this.querySelector('a') as any).href = now.format("~Y-~m-~d.html")
}
}
customElements.define('today-button', TodayButton)
class CurrentTime extends ClockElement {
- update (now) {
+ update(now: Date) {
this.innerHTML = now.format('~H:~M:~S')
}
}
diff --git a/static/dragable.ts b/static/dragable.ts
index 6eb0b999..20acdd3a 100644
--- a/static/dragable.ts
+++ b/static/dragable.ts
@@ -9,34 +9,35 @@
/*
Given the navbar of a popup, make it dragable.
*/
-function bind_popup_control (nav) {
+function bind_popup_control(nav: HTMLElement) {
- if (! nav.closest('.popup-container')) {
- throw TypeError('not a popup container');
- }
+ // if (!nav.closest('popup-element')) {
+ // console.log(nav);
+ // throw TypeError('not a popup container');
+ // }
- nav.onmousedown = function (e) {
+ 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");
- let popup = nav.closest("popup-element");
+ let popup = nav.closest("popup-element") as HTMLElement;
nav.dataset.startPoint = popup.offsetLeft + ";" + popup.offsetTop;
}
- window.addEventListener('mousemove', function (e) {
+ 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 [x, y] = nav.dataset.grabPoint!.split(";").map(Number);
+ let [startX, startY] = nav.dataset.startPoint!.split(";").map(Number);
// let popup = nav.closest(".popup-container");
- let popup = nav.closest("popup-element");
+ let popup = nav.closest("popup-element") as HTMLElement;
popup.style.left = startX + (e.clientX - x) + "px";
popup.style.top = startY + (e.clientY - y) + "px";
}
});
- window.addEventListener('mouseup', function () {
+ window.addEventListener('mouseup', function() {
nav.dataset.grabbed = "";
nav.style.cursor = "";
});
diff --git a/static/globals.ts b/static/globals.ts
index 86368e9a..64a3613f 100644
--- a/static/globals.ts
+++ b/static/globals.ts
@@ -1,15 +1,18 @@
"use strict";
-const vcal_objects = {};
+const vcal_objects: Map<uid, VEvent> = new Map()
class ComponentVEvent extends HTMLElement {
- constructor () {
- super ();
- this.template = document.getElementById(this.tagName);
+
+ template: HTMLTemplateElement
+
+ constructor() {
+ super();
+ this.template = document.getElementById(this.tagName) as HTMLTemplateElement;
let uid;
if ((uid = this.dataset.uid)) {
- vcal_objects[uid].register(this);
+ vcal_objects.get(uid)?.register(this);
}
/* We DON'T have a redraw here in the general case, since the
@@ -18,24 +21,26 @@ class ComponentVEvent extends HTMLElement {
should take care of that some other way */
}
- connectedCallback () {
- let uid;
+ connectedCallback() {
+ let uid, v;
if ((uid = this.dataset.uid)) {
- this.redraw (vcal_objects[uid]);
+ v = vcal_objects.get(uid)
+ if (v) this.redraw(v);
}
}
- redraw (data) {
+ redraw(data: VEvent) {
// update ourselves from template
- if (! this.template) {
+ if (!this.template) {
throw "Something";
}
- let body = this.template.content.cloneNode(true).firstElementChild;
+ let body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
for (let el of body.getElementsByClassName("bind")) {
- let p = el.dataset.property;
+ if (!(el instanceof HTMLElement)) continue;
+ let p = el.dataset.property!;
let d, fmt;
if ((d = data.getProperty(p))) {
if ((fmt = el.dataset.fmt)) {
@@ -52,17 +57,27 @@ class ComponentVEvent extends HTMLElement {
}
class ComponentDescription extends ComponentVEvent {
- constructor () {
- super() ;
+ constructor() {
+ super();
}
}
class ComponentEdit extends ComponentVEvent {
- constructor () {
+
+ firstTime: boolean
+ uid: string
+
+ constructor() {
super();
this.firstTime = true;
+
+ if (this.dataset.uid === undefined) {
+ throw "data-uid must be set"
+ } else {
+ this.uid = this.dataset.uid;
+ }
}
connectedCallback() {
@@ -70,50 +85,59 @@ class ComponentEdit extends ComponentVEvent {
/* Edit tab is rendered here. It's left blank server-side, since
it only makes sense to have something here if we have javascript */
- let data = vcal_objects[this.dataset.uid]
+ let data = vcal_objects.get(this.uid)
- if (! data) {
+ if (!data) {
throw `Data missing for uid ${this.dataset.uid}.`
}
this.redraw(data);
+ return;
+
for (let el of this.getElementsByClassName("interactive")) {
el.addEventListener('input', () => {
- vcal_objects[this.dataset.uid].setProperty(
- el.dataset.property,
+ let obj = vcal_objects.get(this.uid)
+ if (obj === undefined) {
+ throw 'No object with uid ' + this.uid
+ }
+ if (!(el instanceof HTMLInputElement)) return;
+ obj.setProperty(
+ el.dataset.property!,
el.value)
});
}
}
- redraw (data) {
+ redraw(data: VEvent) {
// update ourselves from template
- if (! this.template) {
+ if (!this.template) {
throw "Something";
}
let body;
if (this.firstTime) {
- body = this.template.content.cloneNode(true).firstElementChild;
+ body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
} else {
body = this;
}
for (let el of body.getElementsByClassName("interactive")) {
- let p = el.dataset.property;
- let d;
+ if (!(el instanceof HTMLInputElement)) continue;
+ let p = el.dataset.property!;
+ let d: any;
if ((d = data.getProperty(p))) {
/*
https://stackoverflow.com/questions/57157830/how-can-i-specify-the-sequence-of-running-nested-web-components-constructors
*/
- window.setTimeout (() => {
+ window.setTimeout(() => {
/* NOTE Some specific types might require special formatting
here. But due to my custom components implementing custom
`.value' procedures, we might not need any special cases
here */
- el.value = d;
+ console.log(el, d);
+ (el as HTMLInputElement).value = d;
});
}
}
@@ -126,59 +150,68 @@ class ComponentEdit extends ComponentVEvent {
}
-function find_popup (uid) {
+function find_popup(uid: uid): HTMLElement | null {
// for (let el of vcal_objects[uid].registered) {
// if (el.tagName === 'popup-element') {
// return el;
// }
// }
// throw 'Popup not fonud';
- return document.querySelector(`popup-element[data-uid="${uid}"]`)
+ return document.querySelector(`popup-element[data-uid="${uid}"]`) as HTMLElement
}
-function find_block (uid) {
- for (let el of vcal_objects[uid].registered) {
+function find_block(uid: uid): HTMLElement | null {
+ let obj = vcal_objects.get(uid)
+ if (obj === undefined) {
+ return null;
+ }
+ for (let el of obj.registered) {
if (el.tagName === 'vevent-block') {
return el;
}
}
- throw 'Popup not fonud';
+ // throw 'Popup not fonud';
+ return null;
}
class ComponentBlock extends ComponentVEvent {
- constructor () {
+ constructor() {
super();
this.addEventListener('click', () => {
- toggle_popup(find_popup(this.dataset.uid));
+ let uid = this.dataset.uid
+ if (uid === undefined) throw new Error('UID missing from' + this)
+ let popup = find_popup(uid);
+ if (popup === null) throw new Error('no popup for uid ' + uid);
+ toggle_popup(popup);
});
}
- redraw (data) {
+ redraw(data: VEvent) {
super.redraw(data);
let p;
if ((p = data.getProperty('dtstart'))) {
- this.style.top = date_to_percent(to_local(p), 1) + "%";
+ this.style.top = date_to_percent(to_local(p)) + "%";
// console.log('dtstart', p);
}
if ((p = data.getProperty('dtend'))) {
this.style.height = 'unset';
// console.log('dtend', p);
- this.style.bottom = (100 - date_to_percent(to_local(p), 1)) + "%";
+ this.style.bottom = (100 - date_to_percent(to_local(p))) + "%";
}
}
}
-window.addEventListener('load', function () {
+window.addEventListener('load', function() {
// let json_objects_el = document.getElementById('json-objects');
- let div = document.getElementById('xcal-data');
- let vevents = div.firstElementChild.childNodes;
+ let div = document.getElementById('xcal-data')!;
+ let vevents = div.firstElementChild!.children;
for (let vevent of vevents) {
let ev = xml_to_vcal(vevent);
- vcal_objects[ev.getProperty('uid')] = ev
+ vcal_objects.set(ev.getProperty('uid'), ev)
}
/*
@@ -206,49 +239,56 @@ window.addEventListener('load', function () {
-class DateTimeInput extends HTMLElement {
- constructor () {
+
+class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
+ constructor() {
super();
this.innerHTML = '<input type="date" /><input type="time" />'
+ console.log('constructing datetime input')
}
- static get observedAttributes () {
- return [ 'dateonly' ]
+ static get observedAttributes() {
+ return ['dateonly']
}
- attributeChangedCallback (name, from, to) {
+ attributeChangedCallback(name: string, from: any, to: any) {
console.log(this, name, boolean(from), boolean(to));
switch (name) {
- case 'dateonly':
- this.querySelector('[type="time"]').disabled = boolean(to)
- break;
+ case 'dateonly':
+ (this.querySelector('input[type="time"]') as HTMLInputElement)
+ .disabled = boolean(to)
+ break;
}
}
- get dateonly () {
+ get dateonly(): boolean {
return boolean(this.getAttribute('dateonly'));
}
- set dateonly (bool) {
- this.setAttribute ('dateonly', bool);
+ set dateonly(bool: boolean) {
+ this.setAttribute('dateonly', "" + bool);
}
- get value () {
-
+ get valueAsDate(): Date {
let dt;
- let date = this.querySelector("[type='date']").value;
+ let date = (this.querySelector("input[type='date']") as HTMLInputElement).value;
if (boolean(this.getAttribute('dateonly'))) {
dt = parseDate(date);
dt.type = 'date';
} else {
- let time = this.querySelector("[type='time']").value;
+ let time = (this.querySelector("input[type='time']") as HTMLInputElement).value;
dt = parseDate(date + 'T' + time)
dt.type = 'date-time';
}
return dt;
}
- set value (new_value) {
+ get value(): string {
+ return this.valueAsDate.format("~Y-~m-~dT~H:~M:~S")
+ }
+
+ set value(new_value: Date | string) {
+ console.log('Setting date');
let date, time;
if (new_value instanceof Date) {
date = new_value.format("~L~Y-~m-~d");
@@ -256,36 +296,42 @@ class DateTimeInput extends HTMLElement {
} else {
[date, time] = new_value.split('T')
}
- this.querySelector("[type='date']").value = date;
- this.querySelector("[type='time']").value = time;
+ (this.querySelector("input[type='date']") as HTMLInputElement).value = date;
+ (this.querySelector("input[type='time']") as HTMLInputElement).value = time;
}
- addEventListener(type, proc) {
+ addEventListener(type: string, proc: ((e: Event) => void)) {
if (type != 'input') throw "Only input supported";
- this.querySelector("[type='date']").addEventListener(type, proc);
- this.querySelector("[type='time']").addEventListener(type, proc);
+ (this.querySelector("input[type='date']") as HTMLInputElement)
+ .addEventListener(type, proc);
+ (this.querySelector("input[type='time']") as HTMLInputElement)
+ .addEventListener(type, proc);
}
}
-customElements.define('date-time-input', DateTimeInput)
+customElements.define('date-time-input', DateTimeInput /*, { extends: 'input' } */)
class PopupElement extends HTMLElement {
- constructor () {
+ constructor() {
super();
/* TODO populate remaining */
// this.id = 'popup' + this.dataset.uid
}
- redraw () {
+ redraw() {
console.log('IMPLEMENT ME');
}
connectedCallback() {
- let body = document.getElementById('popup-template').content.cloneNode(true).firstElementChild;
+ let template: HTMLTemplateElement = document.getElementById('popup-template') as HTMLTemplateElement
+ let body = (template.content.cloneNode(true) as DocumentFragment).firstElementChild!;
- let uid = this.dataset.uid
+ if (this.dataset.uid === null) {
+ throw 'UID is required'
+ }
+ let uid = this.dataset.uid!
// console.log(uid);
body.getElementsByClassName('populate-with-uid')
@@ -295,43 +341,42 @@ class PopupElement extends HTMLElement {
let tabgroup_id = gensym();
for (let tab of body.querySelectorAll(".tabgroup .tab")) {
let new_id = gensym();
- let input = tab.querySelector("input");
+ let input = tab.querySelector("input")!;
input.id = new_id;
input.name = tabgroup_id;
- tab.querySelector("label").setAttribute('for', new_id);
+ tab.querySelector("label")!.setAttribute('for', new_id);
}
/* end tabs */
/* nav bar */
- let nav = body.getElementsByClassName("popup-control")[0];
+ let nav = body.getElementsByClassName("popup-control")[0] as HTMLElement;
bind_popup_control(nav);
- let btn = body.querySelector('.popup-control .close-tooltip')
- btn.addEventListener('click', () => {
- close_popup(this);
- });
+ let btn = body.querySelector('.popup-control .close-tooltip') as HTMLButtonElement
+ btn.addEventListener('click', () => close_popup(this));
/* end nav bar */
this.replaceChildren(body);
let that = this;
- this.getElementsByClassName("calendar-selection")
- .addEventListener('change', function () {
- let uid = that.closest('[data-uid]').dataset.uid
- let obj = vcal_objects[uid]
- this.value;
+ this.getElementsByClassName("calendar-selection")[0]
+ .addEventListener('change', function() {
+ let uid = (that.closest('[data-uid]') as HTMLElement).dataset.uid!
+ let obj = vcal_objects.get(uid)
+ // TODO this procedure
+ // this.value;
// event.properties.calendar = this.value;
});
}
}
-window.addEventListener('load', function () {
+window.addEventListener('load', function() {
customElements.define('popup-element', PopupElement)
});
-function wholeday_checkbox (box) {
- box.closest('.timeinput')
- .getElementsByTagName('date-time-input')
- .forEach(el => el.dateonly = box.checked);
+function wholeday_checkbox(box: HTMLInputElement) {
+ box.closest('.timeinput')!
+ .querySelectorAll('input[is="date-time"]')
+ .forEach((el) => { (el as DateTimeInput).dateonly = box.checked });
}
diff --git a/static/jcal.ts b/static/jcal.ts
index 003294d1..db833a3c 100644
--- a/static/jcal.ts
+++ b/static/jcal.ts
@@ -1,60 +1,63 @@
-function jcal_type_to_xcal(doc, type, value) {
+function jcal_type_to_xcal(doc: Document, type: ical_type, value: any): Element {
let el = doc.createElementNS(xcal, type);
switch (type) {
- case 'boolean':
- el.textContent = value ? "true" : "false";
- break;
-
- case 'float':
- case 'integer':
- el.textContent = '' + value;
- break;
-
- case 'period':
- let [start, end] = value;
- let startEl = doc.createElementNS(xcal, 'start');
- startEl.textContent = start;
- let endEL;
- if (end.find('P')) {
- endEl = doc.createElementNS(xcal, 'duration');
- } else {
- endEl = doc.createElementNS(xcal, 'end');
- }
- endEl.textContent = 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.textContent = value[key];
- el.appendChild(e);
- }
- break;
+ case 'boolean':
+ el.textContent = value ? "true" : "false";
+ break;
+
+ case 'float':
+ case 'integer':
+ el.textContent = '' + value;
+ break;
+
+ case 'period':
+ let [start, end] = value;
+ let startEl = doc.createElementNS(xcal, 'start');
+ startEl.textContent = start;
+ let endEl: Element;
+ if (end.find('P')) {
+ endEl = doc.createElementNS(xcal, 'duration');
+ } else {
+ endEl = doc.createElementNS(xcal, 'end');
+ }
+ endEl.textContent = 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.textContent = value[key];
+ el.appendChild(e);
+ }
+ break;
- case 'date':
- case 'time':
- case 'date-time':
+ case 'date':
+ // case 'time':
+ case 'date-time':
- case 'duration':
+ case 'duration':
- case 'binary':
- case 'text':
- case 'uri':
- case 'cal-address':
- case 'utc-offset':
- el.textContent = value;
- break;
+ case 'binary':
+ case 'text':
+ case 'uri':
+ case 'cal-address':
+ case 'utc-offset':
+ el.textContent = value;
+ break;
- default:
+ default:
/* TODO error */
}
return el;
}
-function jcal_property_to_xcal_property(doc, jcal) {
+function jcal_property_to_xcal_property(
+ doc: Document,
+ jcal: JCalProperty
+): Element {
let [propertyName, params, type, ...values] = jcal;
let tag = doc.createElementNS(xcal, propertyName);
@@ -71,7 +74,7 @@ function jcal_property_to_xcal_property(doc, jcal) {
is empty beforehand, and instead check the
number of children of paramEl below.
*/
- if (! params.hasOwnProperty(key)) continue;
+ if (!params.hasOwnProperty(key)) continue;
let el = doc.createElementNS(xcal, key);
@@ -84,7 +87,7 @@ function jcal_property_to_xcal_property(doc, jcal) {
paramEl.appendChild(el);
}
- if (paramEl.childCount > 0) {
+ if (paramEl.childElementCount > 0) {
tag.appendChild(paramEl);
}
@@ -92,20 +95,21 @@ function jcal_property_to_xcal_property(doc, jcal) {
// 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.textContent = x;
- lon.textContent = y;
- tag.appendChild(lat);
- tag.appendChild(lon);
- } else {
- /* TODO, error */
- }
- break;
+ 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.textContent = x;
+ lon.textContent = y;
+ tag.appendChild(lat);
+ tag.appendChild(lon);
+ } else {
+ /* TODO, error */
+ }
+ break;
+ /* TODO reenable this
case 'request-status':
if (type == 'text') {
// assert values[0] instanceof Array
@@ -126,20 +130,21 @@ function jcal_property_to_xcal_property(doc, jcal) {
tag.appendChild(dataEl);
}
} else {
- /* TODO, error */
+ /* TODO, error * /
}
break;
- default:
- for (let value of values) {
- tag.appendChild(jcal_type_to_xcal(doc, type, value))
- }
+ */
+ default:
+ for (let value of values) {
+ tag.appendChild(jcal_type_to_xcal(doc, type, value))
+ }
}
return tag;
}
-function jcal_to_xcal(...jcals) {
+function jcal_to_xcal(...jcals: JCal[]): Document {
let doc = document.implementation.createDocument(xcal, 'icalendar');
for (let jcal of jcals) {
doc.documentElement.appendChild(jcal_to_xcal_inner(doc, jcal));
@@ -147,7 +152,7 @@ function jcal_to_xcal(...jcals) {
return doc;
}
-function jcal_to_xcal_inner(doc, jcal) {
+function jcal_to_xcal_inner(doc: Document, jcal: JCal) {
let [tagname, properties, components] = jcal;
let xcal_tag = doc.createElementNS(xcal, tagname);
diff --git a/static/lib.ts b/static/lib.ts
index 100f4161..35b6f867 100644
--- a/static/lib.ts
+++ b/static/lib.ts
@@ -3,10 +3,39 @@
General procedures which in theory could be used anywhere.
*/
+interface Object {
+ format: (fmt: string) => string
+}
+
+interface HTMLElement {
+ _addEventListener: (name: string, proc: (e: Event) => void) => void
+ listeners: Record<string, ((e: Event) => void)[]>
+}
+
+interface Date {
+ dateonly: boolean
+ utc: boolean
+ type: 'date' | 'date-time'
+}
+
+interface DOMTokenList {
+ find: (regex: string) => [number, string] | undefined
+}
+
+interface HTMLCollection {
+ forEach: (proc: ((el: Element) => void)) => void
+}
+
+interface HTMLCollectionOf<T> {
+ forEach: (proc: ((el: T) => void)) => void
+}
+
+type uid = string
+
HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener;
-HTMLElement.prototype.addEventListener = function (name, proc) {
- if (! this.listeners) this.listeners = {};
- if (! this.listeners[name]) this.listeners[name] = [];
+HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event) => void) {
+ if (!this.listeners) this.listeners = {};
+ if (!this.listeners[name]) this.listeners[name] = [];
this.listeners[name].push(proc);
return this._addEventListener(name, proc);
};
@@ -14,7 +43,8 @@ HTMLElement.prototype.addEventListener = function (name, proc) {
/* list of lists -> list of tuples */
-function zip(...args) {
+/* TODO figure out how to type this correctly */
+function zip(...args: any[]) {
// console.log(args);
if (args === []) return [];
return [...Array(Math.min(...args.map(x => x.length))).keys()]
@@ -37,8 +67,14 @@ function zip(...args) {
century, due to how javascript works (...).
*/
-function parseDate(str) {
- let year, month, day, hour=false, minute, second=0, utc;
+function parseDate(str: string): Date {
+ let year: number;
+ let month: number;
+ let day: number;
+ let hour: number | false = false;
+ let minute: number = 0;
+ let second: number = 0;
+ let utc: boolean = false;
let end = str.length - 1;
if (str[end] == 'Z') {
@@ -47,18 +83,18 @@ function parseDate(str) {
};
switch (str.length) {
- case '2020-01-01T13:37:00'.length:
- second = str.substr(17,2);
- case '2020-01-01T13:37'.length:
- hour = str.substr(11,2);
- minute = str.substr(14,2);
- case '2020-01-01'.length:
- year = str.substr(0,4);
- month = str.substr(5,2) - 1;
- day = str.substr(8,2);
- break;
- default:
- throw 'Bad argument';
+ case '2020-01-01T13:37:00'.length:
+ second = +str.substr(17, 2);
+ case '2020-01-01T13:37'.length:
+ hour = +str.substr(11, 2);
+ minute = +str.substr(14, 2);
+ case '2020-01-01'.length:
+ year = +str.substr(0, 4);
+ month = +str.substr(5, 2) - 1;
+ day = +str.substr(8, 2);
+ break;
+ default:
+ throw 'Bad argument';
}
let date;
@@ -73,32 +109,32 @@ function parseDate(str) {
return date;
}
-function copyDate(date) {
+function copyDate(date: Date): Date {
let d = new Date(date);
d.utc = date.utc;
d.dateonly = date.dateonly;
return d;
}
-function to_local(date) {
- if (! date.utc) return date;
+function to_local(date: Date): Date {
+ if (!date.utc) return date;
return new Date(date.getTime() - date.getTimezoneOffset() * 60 * 1000);
}
/* -------------------------------------------------- */
-function makeElement (name, attr={}) {
- let element = document.createElement(name);
+function makeElement(name: string, attr = {}): HTMLElement {
+ let element: HTMLElement = document.createElement(name);
for (let [key, value] of Object.entries(attr)) {
- element[key] = value;
+ (element as any)[key] = value;
}
return element;
}
-function round_time (time, fraction) {
+function round_time(time: number, fraction: number): number {
let scale = 1 / fraction;
- return Math.round (time * scale) / scale;
+ return Math.round(time * scale) / scale;
}
/* only used by the bar.
@@ -107,20 +143,20 @@ function round_time (time, fraction) {
Just doing (new Date()/(86400*1000)) would be nice, but there's no good
way to get the time in the current day.
*/
-function date_to_percent (date) {
- return (date.getHours() + (date.getMinutes() / 60)) * 100/24;
+function date_to_percent(date: Date): number /* in 0, 100 */ {
+ return (date.getHours() + (date.getMinutes() / 60)) * 100 / 24;
}
/* if only there was such a thing as a let to wrap around my lambda... */
/* js infix to not collide with stuff generated backend */
-const gensym = (counter => (prefix="gensym") => prefix + "js" + ++counter)(0)
+const gensym = (counter => (prefix = "gensym") => prefix + "js" + ++counter)(0)
-function setVar(str, val) {
- document.documentElement.style.setProperty("--" + str, val);
+function setVar(str: string, val: any) {
+ document.documentElement.style.setProperty("--" + str, val);
}
-function asList(thing) {
+function asList<T>(thing: Array<T> | T): Array<T> {
if (thing instanceof Array) {
return thing;
} else {
@@ -129,44 +165,44 @@ function asList(thing) {
}
-function boolean (value) {
+function boolean(value: any): boolean {
switch (typeof value) {
- case 'string':
- switch (value) {
- case 'true': return true;
- case 'false': return false;
- case '': return false;
- default: return true;
- }
- case 'boolean':
- return value;
- default:
- return !! value;
+ case 'string':
+ switch (value) {
+ case 'true': return true;
+ case 'false': return false;
+ case '': return false;
+ default: return true;
+ }
+ case 'boolean':
+ return value;
+ default:
+ return !!value;
}
}
/* internal */
-function datepad(thing, width=2) {
+function datepad(thing: number | string, width = 2): string {
return (thing + "").padStart(width, "0");
}
-function format_date(date, str) {
+function format_date(date: Date, str: string): string {
let fmtmode = false;
let outstr = "";
for (var i = 0; i < str.length; i++) {
if (fmtmode) {
switch (str[i]) {
/* Moves the date into local time. */
- case 'L': date = to_local(date); break;
- case 'Y': outstr += datepad(date.getFullYear(), 4); break;
- case 'm': outstr += datepad(date.getMonth() + 1); break;
- case 'd': outstr += datepad(date.getDate()); break;
- case 'H': outstr += datepad(date.getHours()); break;
- case 'M': outstr += datepad(date.getMinutes()); break;
- case 'S': outstr += datepad(date.getSeconds()); break;
- case 'Z': if (date.utc) outstr += 'Z'; break;
+ case 'L': date = to_local(date); break;
+ case 'Y': outstr += datepad(date.getFullYear(), 4); break;
+ case 'm': outstr += datepad(date.getMonth() + 1); break;
+ case 'd': outstr += datepad(date.getDate()); break;
+ case 'H': outstr += datepad(date.getHours()); break;
+ case 'M': outstr += datepad(date.getMinutes()); break;
+ case 'S': outstr += datepad(date.getSeconds()); break;
+ case 'Z': if (date.utc) outstr += 'Z'; break;
}
fmtmode = false;
} else if (str[i] == '~') {
@@ -178,17 +214,17 @@ function format_date(date, str) {
return outstr;
}
-Object.prototype.format = function (/* any number of arguments */) { return "" + this; }
-Date.prototype.format = function (str) { return format_date (this, str); }
+Object.prototype.format = function(/* any number of arguments */) { return "" + this; }
+Date.prototype.format = function(str) { return format_date(this, str); }
/*
* Finds the first element of the DOMTokenList whichs value matches
* the supplied regexp. Returns a pair of the index and the value.
*/
-DOMTokenList.prototype.find = function (regexp) {
+DOMTokenList.prototype.find = function(regexp) {
let entries = this.entries();
let entry;
- while (! (entry = entries.next()).done) {
+ while (!(entry = entries.next()).done) {
if (entry.value[1].match(regexp)) {
return entry.value;
}
@@ -196,7 +232,7 @@ DOMTokenList.prototype.find = function (regexp) {
}
/* HTMLCollection is the result of a querySelectorAll */
-HTMLCollection.prototype.forEach = function (proc) {
+HTMLCollection.prototype.forEach = function(proc) {
for (let el of this) {
proc(el);
}
diff --git a/static/popup.ts b/static/popup.ts
index 0b04b280..8d9420c6 100644
--- a/static/popup.ts
+++ b/static/popup.ts
@@ -1,59 +1,70 @@
/* event component => coresponding popup component */
-function event_from_popup(popup) {
+function event_from_popup(popup: Element): HTMLElement | null {
// return document.getElementById(popup.id.substr(5))
- return find_block(popup.closest('[data-uid]').dataset.uid)
+ let el = popup.closest('[data-uid]')
+ if (!el) return null;
+ let uid = (el as HTMLElement).dataset.uid
+ if (!uid) return null;
+ return find_block(uid)
}
/* popup component => coresponding event component */
-function popup_from_event(event) {
+function popup_from_event(event: Element): HTMLElement | null {
// return document.getElementById("popup" + event.id);
- return find_popup(event.closest('[data-uid]').dataset.uid)
+ // return find_popup(event.closest('[data-uid]').dataset.uid)
+ let el = event.closest('[data-uid]')
+ if (!el) return null;
+ let uid = (el as HTMLElement).dataset.uid
+ if (!uid) return null;
+ return find_popup(uid)
}
/* hides given popup */
-function close_popup(popup) {
+function close_popup(popup: Element): void {
popup.classList.remove("visible");
}
/* hides all popups */
-function close_all_popups () {
+function close_all_popups() {
for (let popup of document.querySelectorAll("popup-element.visible")) {
close_popup(popup);
}
}
+declare let VIEW: 'month' | 'week'
+
/* open given popup */
-function open_popup(popup) {
+function open_popup(popup: HTMLElement) {
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;
+ 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) {
+ while (element !== root && element !== null) {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
- element = element.offsetParent;
+ element = element.offsetParent as HTMLElement;
}
popup.style.left = offsetX + "px";
popup.style.top = offsetY + "px";
}
/* toggles open/closed status of popup given by id */
-function toggle_popup(popup) {
+function toggle_popup(popup: HTMLElement) {
// let popup = document.getElementById(popup_id);
if (popup.classList.contains("visible")) {
close_popup(popup);
@@ -66,39 +77,38 @@ function toggle_popup(popup) {
/* Makes the popup last hovered over the selected popup, moving it to
* the top, and allowing global keyboard bindings to affect it. */
-let activePopup;
+let activePopup: PopupElement | undefined;
-for (let popup of document.querySelectorAll('.popup-container')) {
+for (let popup of document.querySelectorAll('popup-element')) {
/* TODO possibly only change "active" element after a fraction of
* a second, for example when moving between tabs */
- popup.addEventListener('mouseover', function () {
+ popup.addEventListener('mouseover', function() {
/* This is ever so slightly inefficient,
but it really dosen't mammet */
for (let other of
- document.querySelectorAll('.popup-container'))
- {
+ document.querySelectorAll('popup-element')) {
/* TODO get this from somewhere */
/* Currently it's manually copied from the stylesheet */
- other.style['z-index'] = 1000;
+ ((other as PopupElement).style as any)['z-index'] = 1000;
}
- popup.style['z-index'] += 1;
- activePopup = popup;
+ ((popup as PopupElement).style as any)['z-index'] += 1;
+ activePopup = popup as PopupElement
});
}
-document.addEventListener('keydown', function (event) {
+document.addEventListener('keydown', function(event) {
/* Physical key position, names are what that key would
be in QWERTY */
let i = ({
- 'KeyQ': 0,
- 'KeyW': 1,
- 'KeyE': 2,
- 'KeyR': 3,
+ 'KeyQ': 0,
+ 'KeyW': 1,
+ 'KeyE': 2,
+ 'KeyR': 3,
})[event.code];
if (i === undefined) return
- if (! activePopup) return;
- let element = activePopup.querySelectorAll(".tab > label")[i];
- if (! element) return;
+ if (!activePopup) return;
+ let element: HTMLLabelElement | undefined = activePopup.querySelectorAll(".tab > label")[i] as HTMLLabelElement;
+ if (!element) return;
element.click();
});
diff --git a/static/rrule.ts b/static/rrule.ts.disabled
index e7377370..f210ee77 100644
--- a/static/rrule.ts
+++ b/static/rrule.ts.disabled
@@ -23,11 +23,11 @@ class RRule {
be listeners */
fields = ['freq', 'until', 'count', 'interval',
- 'bysecond', 'byminute', 'byhour',
- 'bymonthday', 'byyearday', 'byweekno',
- 'bymonth', 'bysetpos', 'wkst',
- 'byday'
- ]
+ 'bysecond', 'byminute', 'byhour',
+ 'bymonthday', 'byyearday', 'byweekno',
+ 'bymonth', 'bysetpos', 'wkst',
+ 'byday'
+ ]
constructor() {
@@ -37,20 +37,20 @@ class RRule {
this[f] = false;
Object.defineProperty(
this, f, {
- /*
- TODO many of the fields should be wrapped
- in type tags. e.g. <until> elements are either
- <date> or <date-time>, NOT a raw date.
- by* fields should be wrapped with multiple values.
- */
- get: () => this['_' + f],
- set: (v) => {
- this['_' + f] = v
- for (let l of this.listeners[f]) {
- l(v);
- }
+ /*
+ TODO many of the fields should be wrapped
+ in type tags. e.g. <until> elements are either
+ <date> or <date-time>, NOT a raw date.
+ by* fields should be wrapped with multiple values.
+ */
+ get: () => this['_' + f],
+ set: (v) => {
+ this['_' + f] = v
+ for (let l of this.listeners[f]) {
+ l(v);
}
- });
+ }
+ });
this.listeners[f] = [];
}
}
@@ -68,7 +68,7 @@ class RRule {
let root = doc.createElementNS(xcal, 'recur');
for (let f of this.fields) {
let v = this.fields[f];
- if (! v) continue;
+ if (!v) continue;
let tag = doc.createElementNS(xcal, f);
/* TODO type formatting */
tag.textContent = `${v}`;
@@ -81,7 +81,7 @@ class RRule {
let obj = {};
for (let f of this.fields) {
let v = this[f];
- if (! v) continue;
+ if (!v) continue;
/* TODO special formatting for some types */
obj[f] = v;
}
diff --git a/static/script.ts b/static/script.ts
index 16ff7bbd..8984c19a 100644
--- a/static/script.ts
+++ b/static/script.ts
@@ -6,6 +6,10 @@
class EventCreator {
+ event: HTMLElement | false
+ event_start: { x: number, y: number }
+ down_on_event: boolean
+
/* dynamicly created event when dragging */
constructor() {
this.event = false;
@@ -13,7 +17,7 @@ class EventCreator {
this.down_on_event = false;
}
- create_empty_event () {
+ create_empty_event() {
/* TODO this doesn't clone JS attributes */
// let event = document.getElementById("event-template")
@@ -40,7 +44,7 @@ class EventCreator {
/* -------------------- */
/* Fix tabs for newly created popup */
- let id = gensym ("__js_event");
+ let id = gensym("__js_event");
// TODO remove button?
// $("button 2??").onclick = `remove_event(document.getElementById('${id}'))`
@@ -65,15 +69,15 @@ class EventCreator {
/* -------------------- */
- event.id = id;
- popup.id = "popup" + id;
+ // event.id = id;
+ // popup.id = "popup" + id;
- return [popup, event];
+ // return [popup, event];
}
- create_event_down (intended_target) {
+ create_event_down(intended_target: HTMLElement): (e: MouseEvent) => any {
let that = this;
- return function (e) {
+ return function(e: MouseEvent) {
/* Only trigger event creation stuff on actuall events background,
NOT on its children */
that.down_on_event = false;
@@ -93,20 +97,23 @@ class EventCreator {
(event → [0, 1)), 𝐑, bool → event → ()
*/
- create_event_move(pos_in, round_to=1, wide_element=false) {
+ create_event_move(
+ pos_in: ((c: HTMLElement, e: MouseEvent) => number),
+ round_to: number = 1,
+ wide_element: boolean = false
+ ): ((e: MouseEvent) => any) {
let that = this;
- return function (e) {
- if (e.buttons != 1 || ! that.down_on_event) return;
+ return function(this: HTMLElement, e: MouseEvent) {
+ if (e.buttons != 1 || !that.down_on_event) return;
/* Create event when we start moving the mouse. */
- if (! that.event) {
+ if (!that.event) {
/* Small deadzone so tiny click and drags aren't registered */
if (Math.abs(that.event_start.x - e.clientX) < 10
- && Math.abs(that.event_start.y - e.clientY) < 5)
- { return; }
+ && Math.abs(that.event_start.y - e.clientY) < 5) { return; }
/* only allow start of dragging on background */
- if (e.target != this) return;
+ if (e.target !== this) return;
/* only on left click */
if (e.buttons != 1) return;
@@ -128,8 +135,8 @@ class EventCreator {
*/
let time = round_time(pos_in(this, e), round_to);
- that.event.dataset.time1 = time;
- that.event.dataset.time2 = time;
+ that.event.dataset.time1 = '' + time;
+ that.event.dataset.time2 = '' + time;
/* ---------------------------------------- */
@@ -151,31 +158,32 @@ class EventCreator {
This includes ourselves.
*/
for (let e of this.children) {
- e.style.pointerEvents = "none";
+ (e as HTMLElement).style.pointerEvents = "none";
}
}
let time1 = Number(that.event.dataset.time1);
- let time2 = that.event.dataset.time2 =
- round_time(pos_in(that.event.parentElement, e),
- round_to);
+ let time2 = round_time(
+ pos_in(that.event.parentElement!, e),
+ round_to);
+ that.event.dataset.time2 = '' + time2
/* ---------------------------------------- */
- let event_container = that.event.closest(".event-container");
+ let event_container = that.event.closest(".event-container") as HTMLElement;
/* These two are in UTC */
- let container_start = parseDate(event_container.dataset.start);
- let container_end = parseDate(event_container.dataset.end);
+ let container_start = parseDate(event_container.dataset.start!);
+ let container_end = parseDate(event_container.dataset.end!);
/* ---------------------------------------- */
/* ms */
- let duration = container_end - container_start;
+ let duration = container_end.valueOf() - container_start.valueOf();
- let start_in_duration = duration * Math.min(time1,time2);
- let end_in_duration = duration * Math.max(time1,time2);
+ let start_in_duration = duration * Math.min(time1, time2);
+ let end_in_duration = duration * Math.max(time1, time2);
/* Notice that these are converted to UTC, since the intervals are given
in utc, and I only really care about local time (which a specific local
@@ -187,37 +195,40 @@ class EventCreator {
// console.log(that.event);
console.log(d1.format("~L~H:~M"), d2.format("~L~H:~M"));
- that.event.redraw({ getProperty: (name) =>
- ({ dtstart: d1, dtend: d2 })[name]});
+ // TODO
+ // (that.event as Redrawable).redraw({
+ // getProperty: (name) =>
+ // ({ dtstart: d1, dtend: d2 })[name]
+ // });
// that.event.properties.dtstart = d1;
// that.event.properties.dtend = d2;
}
}
- create_event_finisher (callback) {
+ create_event_finisher(callback: ((event: VEvent) => void)) {
let that = this;
- return function create_event_up (e) {
- if (! that.event) return;
+ return function create_event_up(e: MouseEvent) {
+ if (!that.event) return;
/* Restore pointer events for all existing events.
Allow pointer events on our new event
*/
- for (let e of that.event.parentElement.children) {
- e.style.pointerEvents = "";
+ for (let e of that.event.parentElement!.children) {
+ (e as HTMLElement).style.pointerEvents = "";
}
// place_in_edit_mode(that.event);
let localevent = that.event;
- that.event = null;
+ that.event = false
- callback (localevent);
+ // callback(localevent);
}
}
}
-
+
/* This incarnation of this function only adds the calendar switcher dropdown.
All events are already editable by switching to that tab.
@@ -259,14 +270,15 @@ class EventCreator {
// tab.querySelector("input[name='summary']").focus();
// }
-
-window.addEventListener('load', function () {
+declare let EDIT_MODE: boolean
+
+window.addEventListener('load', function() {
// let start_time = document.querySelector("meta[name='start-time']").content;
// let end_time = document.querySelector("meta[name='end-time']").content;
const sch = new SmallcalCellHighlight(
- document.querySelector('.small-calendar'))
+ document.querySelector('.small-calendar')!)
const timebar = new Timebar(/*start_time, end_time*/);
@@ -282,42 +294,45 @@ window.addEventListener('load', function () {
if (true && EDIT_MODE) {
let eventCreator = new EventCreator;
for (let c of document.getElementsByClassName("events")) {
- c.onmousedown = eventCreator.create_event_down(c);
- c.onmousemove = eventCreator.create_event_move(
- (c,e) => e.offsetY / c.clientHeight,
+ if (!(c instanceof HTMLElement)) continue;
+ c.addEventListener('mousedown', eventCreator.create_event_down(c));
+ c.addEventListener('mousemove', eventCreator.create_event_move(
+ (c, e) => e.offsetY / c.clientHeight,
/* every quarter, every hour */
- 1/(24*4), false
- );
- c.onmouseup = eventCreator.create_event_finisher(
- function (event) {
- let popupElement = document.getElementById("popup" + event.id);
- open_popup(popupElement);
+ 1 / (24 * 4), false
+ ));
+ c.addEventListener('mouseup', eventCreator.create_event_finisher(
+ function(event: VEvent) {
+ // let popupElement = document.getElementById("popup" + event.id);
+ // open_popup(popup_from_event(event));
- popupElement.querySelector("input[name='summary']").focus();
+ // popupElement.querySelector("input[name='summary']").focus();
- });
+ }));
}
for (let c of document.getElementsByClassName("longevents")) {
+ if (!(c instanceof HTMLElement)) continue;
c.onmousedown = eventCreator.create_event_down(c);
c.onmousemove = eventCreator.create_event_move(
- (c,e) => e.offsetX / c.clientWidth,
+ (c, e) => e.offsetX / c.clientWidth,
/* every day, NOTE should be changed to check
interval of longevents */
- 1/7, true
+ 1 / 7, true
);
c.onmouseup = eventCreator.create_event_finisher(
- function (event) {
- let popupElement = document.getElementById("popup" + event.id);
- open_popup(popupElement);
+ function(event) {
+ // TODO restore this
+ // let popupElement = document.getElementById("popup" + event.id);
+ // open_popup(popupElement);
- popupElement.querySelector("input[name='summary']").focus();
+ // popupElement.querySelector("input[name='summary']").focus();
- /* This assumes that it's unchecked beforehand.
- Preferably we would just ensure that it's checked here,
- But we also need to make sure that the proper handlers
- are run then */
- popupElement.querySelector("input[name='wholeday']").click();
+ // /* This assumes that it's unchecked beforehand.
+ // Preferably we would just ensure that it's checked here,
+ // But we also need to make sure that the proper handlers
+ // are run then */
+ // popupElement.querySelector("input[name='wholeday']").click();
});
}
@@ -332,7 +347,7 @@ window.addEventListener('load', function () {
On mobile they also have the problem that they make
the whole page scroll there.
*/
- el.parentElement.removeAttribute("href");
+ el.parentElement!.removeAttribute("href");
let popup = document.getElementById("popup" + el.id);
// popup.getElementsByClassName("edit-form")[0].onsubmit = function () {
@@ -349,12 +364,12 @@ window.addEventListener('load', function () {
}
- document.onkeydown = function (evt) {
- evt = evt || window.event;
- if (! evt.key) return;
- if (evt.key.startsWith("Esc")) {
- close_all_popups();
- }
+ document.onkeydown = function(evt) {
+ evt = evt || window.event;
+ if (!evt.key) return;
+ if (evt.key.startsWith("Esc")) {
+ close_all_popups();
+ }
}
@@ -364,20 +379,21 @@ window.addEventListener('load', function () {
form updates.
*/
- let gotodatebtn = document.querySelector("#jump-to .btn");
+ let gotodatebtn = document.querySelector("#jump-to .btn")!;
let target_href = (new Date).format("~Y-~m-~d") + ".html";
let golink = makeElement('a', {
className: 'btn',
href: target_href,
innerHTML: gotodatebtn.innerHTML,
- });
+ }) as HTMLAnchorElement
gotodatebtn.replaceWith(golink);
- document.querySelector("#jump-to input[name='date']").onchange = function () {
- let date = this.valueAsDate.format("~Y-~m-~d");
- console.log(date);
- golink.href = date + ".html";
- }
+ (document.querySelector("#jump-to input[name='date']") as HTMLInputElement)
+ .onchange = function() {
+ let date = (this as HTMLInputElement).valueAsDate!.format("~Y-~m-~d");
+ console.log(date);
+ golink.href = date + ".html";
+ }
/* ---------------------------------------- */
@@ -386,26 +402,27 @@ window.addEventListener('load', function () {
Before init_input_list since we need this listener to be propagated to clones.
[CATEGORIES_BIND]
*/
- for (let lst of document.querySelectorAll(".input-list[data-property='categories']")) {
- let f = function () {
- console.log(lst, lst.closest('.popup-container'));
- let event = event_from_popup(lst.closest('.popup-container'))
- event.properties.categories = lst.get_value();
- };
-
- for (let inp of lst.querySelectorAll('input')) {
- inp.addEventListener('input', f);
- }
- }
+ // TODO fix this
+ // for (let lst of document.querySelectorAll(".input-list[data-property='categories']")) {
+ // let f = function() {
+ // console.log(lst, lst.closest('.popup-container'));
+ // let event = event_from_popup(lst.closest('.popup-container'))
+ // event.properties.categories = lst.get_value();
+ // };
+
+ // for (let inp of lst.querySelectorAll('input')) {
+ // inp.addEventListener('input', f);
+ // }
+ // }
// init_arbitary_kv();
// init_input_list();
- document.addEventListener('keydown', function (event) {
+ document.addEventListener('keydown', function(event) {
if (event.key == '/') {
- let searchbox = document.querySelector('.simplesearch [name=q]')
+ let searchbox = document.querySelector('.simplesearch [name=q]') as HTMLInputElement
// focuses the input, and selects all the text in it
searchbox.select();
event.preventDefault();
diff --git a/static/server_connect.ts b/static/server_connect.ts
index ef5de5a9..a6599500 100644
--- a/static/server_connect.ts
+++ b/static/server_connect.ts
@@ -1,17 +1,22 @@
-async function remove_event (element) {
- let uid = element.querySelector("icalendar uid text").textContent;
+/*
+async function remove_event(element: Element): void {
+ let uidElement = element.querySelector("icalendar uid text")
+ if (uidElement === null) {
+ throw "Element lacks uid, giving up"
+ }
+ let uid: uid = uidElement.textContent!;
let data = new URLSearchParams();
data.append('uid', uid);
- let response = await fetch ( '/remove', {
+ let response = await fetch('/remove', {
method: 'POST',
body: data
});
console.log(response);
- toggle_popup("popup" + element.id);
+ toggle_popup(popup_from_event(element));
if (response.status < 200 || response.status >= 300) {
let body = await response.text();
@@ -20,28 +25,29 @@ async function remove_event (element) {
element.remove();
}
}
-
-function event_to_jcal (event) {
- /* encapsulate event in a shim calendar, to ensure that
- we always send correct stuff */
- return ['vcalendar',
- [
- /*
- 'prodid' and 'version' are technically both required (RFC 5545,
- 3.6 Calendar Components).
- */
- ],
- [
- /* vtimezone goes here */
- event.properties.to_jcal()
- ]
- ];
-}
-
-async function create_event (event) {
+*/
+
+// function event_to_jcal(event) {
+// /* encapsulate event in a shim calendar, to ensure that
+// we always send correct stuff */
+// return ['vcalendar',
+// [
+// /*
+// 'prodid' and 'version' are technically both required (RFC 5545,
+// 3.6 Calendar Components).
+// */
+// ],
+// [
+// /* vtimezone goes here */
+// event.properties.to_jcal()
+// ]
+// ];
+// }
+
+async function create_event(event: VEvent) {
// let xml = event.getElementsByTagName("icalendar")[0].outerHTML
- let calendar = event.properties.calendar.value;
+ let calendar = event.getProperty('x-hnh-calendar-name');
console.log('calendar=', calendar/*, xml*/);
@@ -51,11 +57,9 @@ async function create_event (event) {
console.log(event);
- let jcal = event_to_jcal(event);
+ let jcal = event.to_jcal();
-
-
- let doc = jcal_to_xcal(jcal);
+ let doc: Document = jcal_to_xcal(jcal);
console.log(doc);
let str = doc.documentElement.outerHTML;
console.log(str);
@@ -65,7 +69,7 @@ async function create_event (event) {
// return;
- let response = await fetch ( '/insert', {
+ let response = await fetch('/insert', {
method: 'POST',
body: data
});
@@ -91,18 +95,19 @@ async function create_event (event) {
.parseFromString(body, 'text/xml')
.children[0];
- let child;
- while ((child = return_properties.firstChild)) {
- let target = event.querySelector(
- "vevent properties " + child.tagName);
- if (target) {
- target.replaceWith(child);
- } else {
- event.querySelector("vevent properties")
- .appendChild(child);
- }
- }
+ // let child;
+ // while ((child = return_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_from);
- event.classList.remove("generated");
- toggle_popup("popup" + event.id);
}
diff --git a/static/types.ts b/static/types.ts
index 02ae2261..7e10e90e 100644
--- a/static/types.ts
+++ b/static/types.ts
@@ -15,6 +15,22 @@ let all_types = [
'boolean', /* boolean */
]
+type ical_type
+ = 'text'
+ | 'uri'
+ | 'binary'
+ | 'float'
+ | 'integer'
+ | 'date-time'
+ | 'date'
+ | 'duration'
+ | 'period'
+ | 'utc-offset'
+ | 'cal-address'
+ | 'recur'
+ | 'boolean'
+ | 'unknown'
+
let property_names = [
'calscale', 'method', 'prodid', 'version', 'attach', 'categories',
'class', 'comment', 'description', 'geo', 'location', 'percent-complete',
@@ -26,84 +42,140 @@ let property_names = [
];
-let valid_fields = {
- 'VCALENDAR': ['PRODID', 'VERSION', 'CALSCALE', 'METHOD'],
- 'VEVENT': ['DTSTAMP', 'UID', 'DTSTART', 'CLASS', 'CREATED',
- 'DESCRIPTION', 'GEO', 'LAST-MODIFIED', 'LOCATION',
- 'ORGANIZER', 'PRIORITY', 'SEQUENCE', 'STATUS',
- 'SUMMARY', 'TRANSP', 'URL', 'RECURRENCE-ID',
- 'RRULE', 'DTEND', 'DURATION', 'ATTACH', 'ATTENDEE',
- 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
- 'REQUEST-STATUS', 'RELATED-TO', 'RESOURCES', 'RDATE'],
- 'VTODO': ['DTSTAMP', 'UID', 'CLASS', 'COMPLETED', 'CREATED',
- 'DESCRIPTION', 'DTSTART', 'GEO', 'LAST-MODIFIED',
- 'LOCATION', 'ORGANIZER', 'PERCENT-COMPLETE', 'PRIORITY',
- 'RECURRENCE-ID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'URL',
- 'RRULE', 'DUE', 'DURATION', 'ATTACH', 'ATTENDEE', 'CATEGORIES',
- 'COMMENT', 'CONTACT', 'EXDATE', 'REQUEST-STATUS', 'RELATED-TO',
- 'RESOURCES', 'RDATE',],
- 'VJOURNAL': ['DTSTAMP', 'UID', 'CLASS', 'CREATED', 'DTSTART', 'LAST-MODIFIED',
- 'ORGANIZER', 'RECURRENCE-ID', 'SEQUENCE', 'STATUS', 'SUMMARY',
- 'URL', 'RRULE', 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT',
- 'CONTACT', 'DESCRIPTION', 'EXDATE', 'RELATED-TO', 'RDATE',
- 'REQUEST-STATUS'],
- 'VFREEBUSY': ['DTSTAMP', 'UID', 'CONTACT', 'DTSTART', 'DTEND',
- 'ORGANIZER', 'URL', 'ATTENDEE', 'COMMENT', 'FREEBUSY',
- 'REQUEST-STATUS'],
- 'VTIMEZONE': ['TZID', 'LAST-MODIFIED', 'TZURL'],
- 'VALARM': ['ACTION', 'TRIGGER', 'DURATION', 'REPEAT', 'ATTACH',
- 'DESCRIPTION', 'SUMMARY', 'ATTENDEE'],
- 'STANDARD': ['DTSTART', 'TZOFFSETFROM', 'TZOFFSETTO', 'RRULE',
- 'COMMENT', 'RDATE', 'TZNAME'],
-};
+let valid_fields: Map<string, string[]> = new Map([
+ ['VCALENDAR', ['PRODID', 'VERSION', 'CALSCALE', 'METHOD']],
+ ['VEVENT', ['DTSTAMP', 'UID', 'DTSTART', 'CLASS', 'CREATED',
+ 'DESCRIPTION', 'GEO', 'LAST-MODIFIED', 'LOCATION',
+ 'ORGANIZER', 'PRIORITY', 'SEQUENCE', 'STATUS',
+ 'SUMMARY', 'TRANSP', 'URL', 'RECURRENCE-ID',
+ 'RRULE', 'DTEND', 'DURATION', 'ATTACH', 'ATTENDEE',
+ 'CATEGORIES', 'COMMENT', 'CONTACT', 'EXDATE',
+ 'REQUEST-STATUS', 'RELATED-TO', 'RESOURCES', 'RDATE']],
+ ['VTODO', ['DTSTAMP', 'UID', 'CLASS', 'COMPLETED', 'CREATED',
+ 'DESCRIPTION', 'DTSTART', 'GEO', 'LAST-MODIFIED',
+ 'LOCATION', 'ORGANIZER', 'PERCENT-COMPLETE', 'PRIORITY',
+ 'RECURRENCE-ID', 'SEQUENCE', 'STATUS', 'SUMMARY', 'URL',
+ 'RRULE', 'DUE', 'DURATION', 'ATTACH', 'ATTENDEE', 'CATEGORIES',
+ 'COMMENT', 'CONTACT', 'EXDATE', 'REQUEST-STATUS', 'RELATED-TO',
+ 'RESOURCES', 'RDATE',]],
+ ['VJOURNAL', ['DTSTAMP', 'UID', 'CLASS', 'CREATED', 'DTSTART', 'LAST-MODIFIED',
+ 'ORGANIZER', 'RECURRENCE-ID', 'SEQUENCE', 'STATUS', 'SUMMARY',
+ 'URL', 'RRULE', 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT',
+ 'CONTACT', 'DESCRIPTION', 'EXDATE', 'RELATED-TO', 'RDATE',
+ 'REQUEST-STATUS']],
+ ['VFREEBUSY', ['DTSTAMP', 'UID', 'CONTACT', 'DTSTART', 'DTEND',
+ 'ORGANIZER', 'URL', 'ATTENDEE', 'COMMENT', 'FREEBUSY',
+ 'REQUEST-STATUS']],
+ ['VTIMEZONE', ['TZID', 'LAST-MODIFIED', 'TZURL']],
+ ['VALARM', ['ACTION', 'TRIGGER', 'DURATION', 'REPEAT', 'ATTACH',
+ 'DESCRIPTION', 'SUMMARY', 'ATTENDEE']],
+ ['STANDARD', ['DTSTART', 'TZOFFSETFROM', 'TZOFFSETTO', 'RRULE',
+ 'COMMENT', 'RDATE', 'TZNAME']],
+])
+
+valid_fields.set('DAYLIGHT', valid_fields.get('STANDARD')!);
+
+type known_ical_types
+ = 'ACTION'
+ | 'ATTACH'
+ | 'ATTENDEE'
+ | 'CALSCALE'
+ | 'CATEGORIES'
+ | 'CLASS'
+ | 'COMMENT'
+ | 'COMPLETED'
+ | 'CONTACT'
+ | 'CREATED'
+ | 'DESCRIPTION'
+ | 'DTEND'
+ | 'DTSTAMP'
+ | 'DTSTART'
+ | 'DUE'
+ | 'DURATION'
+ | 'EXDATE'
+ | 'FREEBUSY'
+ | 'GEO'
+ | 'LAST-MODIFIED'
+ | 'LOCATION'
+ | 'METHOD'
+ | 'ORGANIZER'
+ | 'PERCENT-COMPLETE'
+ | 'PRIORITY'
+ | 'PRODID'
+ | 'RDATE'
+ | 'RECURRENCE-ID'
+ | 'RELATED-TO'
+ | 'REPEAT'
+ | 'REQUEST-STATUS'
+ | 'RESOURCES'
+ | 'RRULE'
+ | 'SEQUENCE'
+ | 'STATUS'
+ | 'SUMMARY'
+ | 'TRANSP'
+ | 'TRIGGER'
+ | 'TZID'
+ | 'TZNAME'
+ | 'TZOFFSETFROM'
+ | 'TZOFFSETTO'
+ | 'TZURL'
+ | 'URL'
+ | 'VERSION'
+
+let valid_input_types: Map<string, ical_type | ical_type[]> =
+ new Map([
+ ['ACTION', ['text']], // AUDIO|DISPLAY|EMAIL|*other*
+ ['ATTACH', ['uri', 'binary']],
+ ['ATTENDEE', ['cal-address']],
+ ['CALSCALE', ['text']],
+ ['CATEGORIES', [['text']]],
+ ['CLASS', ['text']], // PUBLIC|PRIVATE|CONFIDENTIAL|*other*
+ ['COMMENT', ['text']],
+ ['COMPLETED', ['date-time']],
+ ['CONTACT', ['text']],
+ ['CREATED', ['date-time']],
+ ['DESCRIPTION', ['text']],
+ ['DTEND', ['date', 'date-time']],
+ ['DTSTAMP', ['date-time']],
+ ['DTSTART', ['date', 'date-time']],
+ ['DUE', ['date', 'date-time']],
+ ['DURATION', ['duration']],
+ ['EXDATE', [['date', 'date-time']]],
+ ['FREEBUSY', [['period']]],
+ ['GEO', ['float']], // pair of floats
+ ['LAST-MODIFIED', ['date-time']],
+ ['LOCATION', ['text']],
+ ['METHOD', ['text']],
+ ['ORGANIZER', ['cal-address']],
+ ['PERCENT-COMPLETE', ['integer']], // 0-100
+ ['PRIORITY', ['integer']], // 0-9
+ ['PRODID', ['text']],
+ ['RDATE', [['date', 'date-time', 'period']]],
+ ['RECURRENCE-ID', ['date', 'date-time']],
+ ['RELATED-TO', ['text']],
+ ['REPEAT', ['integer']],
+ ['REQUEST-STATUS', ['text']],
+ ['RESOURCES', [['text']]],
+ ['RRULE', ['recur']],
+ ['SEQUENCE', ['integer']],
+ ['STATUS', ['text']], // see 3.8.1.11
+ ['SUMMARY', ['text']],
+ ['TRANSP', ['text']], // OPAQUE|TRANSPARENT
+ ['TRIGGER', ['duration', 'date-time']],
+ ['TZID', ['text']],
+ ['TZNAME', ['text']],
+ ['TZOFFSETFROM', ['utc-offset']],
+ ['TZOFFSETTO', ['utc-offset']],
+ ['TZURL', ['uri']],
+ ['URL', ['uri']],
+ ['VERSION', ['text']],
+ ]) as Map<string, ical_type | ical_type[]>
+
+// type JCalLine {
+// }
-valid_fields['DAYLIGHT'] = valid_fields['STANDARD'];
+type tagname = 'vevent' | string
+type JCalProperty = [string, any, ical_type, any[]]
-let valid_input_types = {
- 'ACTION': ['text'], // AUDIO|DISPLAY|EMAIL|*other*
- 'ATTACH': ['uri', 'binary'],
- 'ATTENDEE': ['cal-address'],
- 'CALSCALE': ['text'],
- 'CATEGORIES': [['text']],
- 'CLASS': ['text'], // PUBLIC|PRIVATE|CONFIDENTIAL|*other*
- 'COMMENT': ['text'],
- 'COMPLETED': ['date-time'],
- 'CONTACT': ['text'],
- 'CREATED': ['date-time'],
- 'DESCRIPTION': ['text'],
- 'DTEND': ['date', 'date-time'],
- 'DTSTAMP': ['date-time'],
- 'DTSTART': ['date', 'date-time'],
- 'DUE': ['date', 'date-time'],
- 'DURATION': ['duration'],
- 'EXDATE': [['date', 'date-time']],
- 'FREEBUSY': [['period']],
- 'GEO': ['float'], // pair of floats
- 'LAST-MODIFIED': ['date-time'],
- 'LOCATION': ['text'],
- 'METHOD': ['text'],
- 'ORGANIZER': ['cal-address'],
- 'PERCENT-COMPLETE': ['integer'], // 0-100
- 'PRIORITY': ['integer'], // 0-9
- 'PRODID': ['text'],
- 'RDATE': [['date', 'date-time', 'period']],
- 'RECURRENCE-ID': ['date', 'date-time'],
- 'RELATED-TO': ['text'],
- 'REPEAT': ['integer'],
- 'REQUEST-STATUS': ['text'],
- 'RESOURCES': [['text']],
- 'RRULE': ['recur'],
- 'SEQUENCE': ['integer'],
- 'STATUS': ['text'], // see 3.8.1.11
- 'SUMMARY': ['text'],
- 'TRANSP': ['text'], // OPAQUE|TRANSPARENT
- 'TRIGGER': ['duration', 'date-time'],
- 'TZID': ['text'],
- 'TZNAME': ['text'],
- 'TZOFFSETFROM': ['utc-offset'],
- 'TZOFFSETTO': ['utc-offset'],
- 'TZURL': ['uri'],
- 'URL': ['uri'],
- 'VERSION': ['text'],
-}
+type JCal = [tagname, JCalProperty[], JCal[]]
diff --git a/static/vevent.ts b/static/vevent.ts
index 678f2134..72ab28b1 100644
--- a/static/vevent.ts
+++ b/static/vevent.ts
@@ -1,46 +1,55 @@
"use strict";
+interface Redrawable extends HTMLElement {
+ redraw: ((data: VEvent) => void)
+}
+
class VEventValue {
- constructor (type, value, parameters = {}) {
+
+ type: ical_type
+ value: any
+ parameters: Map<string, any>
+
+ constructor(type: ical_type, value: any, parameters = new Map()) {
this.type = type;
this.value = value;
this.parameters = parameters;
}
- to_jcal () {
+ to_jcal(): [Map<string, any>, ical_type, any] {
let value;
let v = this.value;
switch (this.type) {
- case 'binary':
- /* TOOD */
- break;
- case 'date-time':
- value = v.format("~Y-~m-~dT~H:~M:~S");
- // TODO TZ
- break;
- case 'date':
- value = v.format("~Y-~m-~d");
- break;
- case 'duration':
- /* TODO */
- break;
- case 'period':
- /* TODO */
- break;
- case 'utc-offset':
- /* TODO */
- break;
- case 'recur':
- value = v.asJcal();
- break;
-
- case 'float':
- case 'integer':
- case 'text':
- case 'uri':
- case 'cal-address':
- case 'boolean':
- value = v;
+ case 'binary':
+ /* TOOD */
+ break;
+ case 'date-time':
+ value = v.format("~Y-~m-~dT~H:~M:~S");
+ // TODO TZ
+ break;
+ case 'date':
+ value = v.format("~Y-~m-~d");
+ break;
+ case 'duration':
+ /* TODO */
+ break;
+ case 'period':
+ /* TODO */
+ break;
+ case 'utc-offset':
+ /* TODO */
+ break;
+ case 'recur':
+ value = v.asJcal();
+ break;
+
+ case 'float':
+ case 'integer':
+ case 'text':
+ case 'uri':
+ case 'cal-address':
+ case 'boolean':
+ value = v;
}
return [this.parameters, this.type, value];
}
@@ -51,24 +60,38 @@ class VEventDuration extends VEventValue {
}
class VEvent {
- constructor (properties = {}, components = []) {
+
+ properties: Map<uid, VEventValue>
+ components: VEvent[]
+ registered: Redrawable[]
+
+ constructor(properties: Map<uid, VEventValue> = new Map(), components: VEvent[] = []) {
this.properties = properties;
this.components = components;
this.registered = [];
}
- getProperty (key) {
- let e = this.properties[key];
- if (! e) return e;
+ getProperty(key: string): any | undefined {
+ let e = this.properties.get(key);
+ if (!e) return e;
return e.value;
}
- setProperty (key, value) {
- let e = this.properties[key];
- if (! e) {
- let type = (valid_input_types[key.toUpperCase()] || ['unknown'])[0]
- if (typeof type === typeof []) type = type[0];
- e = this.properties[key] = new VEventValue(type, value);
+ setProperty(key: string, value: any) {
+ let e = this.properties.get(key);
+ if (!e) {
+ key = key.toUpperCase()
+ let type: ical_type
+ let type_ = valid_input_types.get(key)
+ if (type_ === undefined) {
+ type = 'unknown'
+ } else if (type_ instanceof Array) {
+ type = type_[0]
+ } else {
+ type = type_
+ }
+ e = new VEventValue(type, value)
+ this.properties.set(key, e);
} else {
e.value = value;
}
@@ -79,11 +102,11 @@ class VEvent {
}
}
- register (htmlNode) {
+ register(htmlNode: Redrawable) {
this.registered.push(htmlNode);
}
- to_jcal () {
+ to_jcal(): JCal {
let out_properties = []
for (let [key, value] of Object.entries(this.properties)) {
let sub = value.to_jcal();
@@ -94,118 +117,119 @@ class VEvent {
}
}
-function make_vevent_value (value_tag) {
+function make_vevent_value(value_tag: Element) {
/* TODO parameters */
- return new VEventValue (value_tag.tagName, make_vevent_value_ (value_tag));
+ return new VEventValue(
+ /* TODO error on invalid type? */
+ value_tag.tagName as ical_type,
+ make_vevent_value_(value_tag));
}
-function make_vevent_value_ (value_tag) {
+function make_vevent_value_(value_tag: Element) {
/* RFC6321 3.6. */
switch (value_tag.tagName) {
- case 'binary':
- /* Base64 to binary
- Seems to handle inline whitespace, which xCal standard reqires
- */
- return atob(value_tag.innerHTML)
- break;
-
- case 'boolean':
- switch (value_tag.innerHTML) {
- case 'true': return true;
- case 'false': return false;
- default:
- console.warn(`Bad boolean ${value_tag.innerHTML}, defaulting with !!`)
- return !! value_tag.innerHTML;
- }
- break;
-
- case 'time':
- case 'date':
- case 'date-time':
- return parseDate(value_tag.innerHTML);
- break;
-
- case 'duration':
- /* TODO duration parser here 'P1D' */
- return value_tag.innerHTML;
- break;
-
- case 'float':
- case 'integer':
- return +value_tag.innerHTML;
- break;
-
- case 'period':
- /* TODO has sub components, meaning that a string wont do */
- let start = value_tag.getElementsByTagName('start')[0]
- parseDate(start.innerHTML);
- let other;
- if ((other = value_tag.getElementsByTagName('end')[0])) {
- return parseDate(other.innerHTML)
- } else if ((other = value_tag.getElementsByTagName('duration')[0])) {
- /* TODO parse duration */
- return other.innerHTML
- } else {
- console.warn('Invalid end to period, defaulting to 1H');
- return new Date(3600);
- }
+ case 'binary':
+ /* Base64 to binary
+ Seems to handle inline whitespace, which xCal standard reqires
+ */
+ return atob(value_tag.innerHTML)
+
+ case 'boolean':
+ switch (value_tag.innerHTML) {
+ case 'true': return true;
+ case 'false': return false;
+ default:
+ console.warn(`Bad boolean ${value_tag.innerHTML}, defaulting with !!`)
+ return !!value_tag.innerHTML;
+ }
+
+ case 'time':
+ case 'date':
+ case 'date-time':
+ return parseDate(value_tag.innerHTML);
+
+ case 'duration':
+ /* TODO duration parser here 'P1D' */
+ return value_tag.innerHTML;
+
+ case 'float':
+ case 'integer':
+ return +value_tag.innerHTML;
+
+ case 'period':
+ /* TODO has sub components, meaning that a string wont do */
+ let start = value_tag.getElementsByTagName('start')[0]
+ parseDate(start.innerHTML);
+ let other;
+ if ((other = value_tag.getElementsByTagName('end')[0])) {
+ return parseDate(other.innerHTML)
+ } else if ((other = value_tag.getElementsByTagName('duration')[0])) {
+ /* TODO parse duration */
+ return other.innerHTML
+ } else {
+ console.warn('Invalid end to period, defaulting to 1H');
+ return new Date(3600);
+ }
- case 'recur':
- /* TODO parse */
- return "";
+ case 'recur':
+ /* TODO parse */
+ return "";
- case 'utc-offset':
- /* TODO parse */
- return "";
+ case 'utc-offset':
+ /* TODO parse */
+ return "";
- default:
- console.warn(`Unknown type '${value_tag.tagName}', defaulting to string`)
- case 'cal-address':
- case 'uri':
- case 'text':
- return value_tag.innerHTML;
+ default:
+ console.warn(`Unknown type '${value_tag.tagName}', defaulting to string`)
+ case 'cal-address':
+ case 'uri':
+ case 'text':
+ return value_tag.innerHTML;
}
}
/* xml dom object -> class VEvent */
-function xml_to_vcal (xml) {
+function xml_to_vcal(xml: Element): VEvent {
/* xml MUST have a VEVENT (or equivalent) as its root */
let properties = xml.getElementsByTagName('properties')[0];
let components = xml.getElementsByTagName('components')[0];
- let property_map = {}
+ let property_map = new Map()
if (properties) {
for (var i = 0; i < properties.childElementCount; i++) {
let tag = properties.childNodes[i];
+ if (!(tag instanceof Element)) continue;
let parameters = {};
- let value = [];
+ let value: VEventValue | VEventValue[] = [];
for (var j = 0; j < tag.childElementCount; j++) {
let child = tag.childNodes[j];
+ if (!(child instanceof Element)) continue;
switch (tag.tagName) {
- case 'parameters':
- parameters = /* handle parameters */ {};
- break;
+ case 'parameters':
+ parameters = /* TODO handle parameters */ {};
+ break;
/* These can contain multiple value tags, per
RFC6321 3.4.1.1. */
- case 'categories':
- case 'resources':
- case 'freebusy':
- case 'exdate':
- case 'rdate':
- value.push(make_vevent_value(child));
- break;
- default:
- value = make_vevent_value(child);
+ case 'categories':
+ case 'resources':
+ case 'freebusy':
+ case 'exdate':
+ case 'rdate':
+ (value as VEventValue[]).push(make_vevent_value(child));
+ break;
+ default:
+ value = make_vevent_value(child);
}
}
- property_map[tag.tagName] = value;
+ property_map.set(tag.tagName, value);
}
}
let component_list = []
if (components) {
for (let child of components.childNodes) {
+ if (!(child instanceof Element)) continue;
component_list.push(xml_to_vcal(child))
}
}