From 410404cfdd54c083b6609fd52334e02d320145d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Wed, 10 Nov 2021 01:40:22 +0100 Subject: Re-modularize javascript. This moves almost everything out of globals.ts, into sepparate files. Things are still slightly to tightly coupled. But that is worked on. --- static/components/date-time-input.ts | 77 +++++ static/components/popup-element.ts | 100 ++++++ static/components/tab-element.ts | 40 +++ static/components/vevent-block.ts | 63 ++++ static/components/vevent-description.ts | 12 + static/components/vevent-dl.ts | 24 ++ static/components/vevent-edit.ts | 137 ++++++++ static/components/vevent.ts | 71 ++++ static/elements.ts | 30 ++ static/event-creator.ts | 179 ++++++++++ static/globals.ts | 571 +------------------------------- static/jcal.ts | 5 +- static/lib.ts | 35 +- static/popup.ts | 19 +- static/script.ts | 197 ++--------- static/types.ts | 7 +- static/vevent.ts | 4 +- 17 files changed, 811 insertions(+), 760 deletions(-) create mode 100644 static/components/date-time-input.ts create mode 100644 static/components/popup-element.ts create mode 100644 static/components/tab-element.ts create mode 100644 static/components/vevent-block.ts create mode 100644 static/components/vevent-description.ts create mode 100644 static/components/vevent-dl.ts create mode 100644 static/components/vevent-edit.ts create mode 100644 static/components/vevent.ts create mode 100644 static/elements.ts create mode 100644 static/event-creator.ts (limited to 'static') diff --git a/static/components/date-time-input.ts b/static/components/date-time-input.ts new file mode 100644 index 00000000..1f54b15e --- /dev/null +++ b/static/components/date-time-input.ts @@ -0,0 +1,77 @@ +export { DateTimeInput } + +import { to_boolean, parseDate } from '../lib' + +/* '' */ +class DateTimeInput extends /* HTMLInputElement */ HTMLElement { + connectedCallback() { + /* This can be in the constructor for chromium, but NOT firefox... + Vivaldi 4.3.2439.63 stable + Mozilla Firefox 94.0.1 + */ + this.innerHTML = '' + // console.log('constructing datetime input') + } + + static get observedAttributes() { + return ['dateonly'] + } + + attributeChangedCallback(name: string, _: any, to: any): void { + // console.log(this, name, to_boolean(from), to_boolean(to)); + switch (name) { + case 'dateonly': + (this.querySelector('input[type="time"]') as HTMLInputElement) + .disabled = to_boolean(to) + break; + } + } + + get dateonly(): boolean { + return to_boolean(this.getAttribute('dateonly')); + } + + set dateonly(bool: boolean) { + this.setAttribute('dateonly', "" + bool); + } + + get valueAsDate(): Date { + let dt; + let date = (this.querySelector("input[type='date']") as HTMLInputElement).value; + if (to_boolean(this.getAttribute('dateonly'))) { + dt = parseDate(date); + dt.type = 'date'; + } else { + let time = (this.querySelector("input[type='time']") as HTMLInputElement).value; + dt = parseDate(date + 'T' + time) + dt.type = 'date-time'; + } + return dt; + } + + 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"); + time = new_value.format("~L~H:~M:~S"); + } else { + [date, time] = new_value.split('T') + } + (this.querySelector("input[type='date']") as HTMLInputElement).value = date; + (this.querySelector("input[type='time']") as HTMLInputElement).value = time; + } + + addEventListener(type: string, proc: ((e: Event) => void)) { + if (type != 'input') throw "Only input supported"; + + (this.querySelector("input[type='date']") as HTMLInputElement) + .addEventListener(type, proc); + (this.querySelector("input[type='time']") as HTMLInputElement) + .addEventListener(type, proc); + } +} diff --git a/static/components/popup-element.ts b/static/components/popup-element.ts new file mode 100644 index 00000000..3225fa52 --- /dev/null +++ b/static/components/popup-element.ts @@ -0,0 +1,100 @@ +export { PopupElement } + +import { gensym } from '../lib' +import { VEvent } from '../vevent' +import { bind_popup_control } from '../dragable' +import { close_popup } from '../popup' + +import { ComponentVEvent } from './vevent' +import { TabElement } from './tab-element' + +/* */ +class PopupElement extends ComponentVEvent { + + tabgroup_id: string + tabcount: number + + constructor(uid?: string) { + super(uid); + + /* TODO populate remaining */ + // this.id = 'popup' + this.dataset.uid + this.tabgroup_id = gensym(); + this.tabcount = 0 + } + + redraw(data: VEvent) { + // console.warn('IMPLEMENT ME'); + + if (data._calendar) { + this.dataset.calendar = data._calendar; + } + + /* TODO is there any case where we want to propagate the draw to any of + our tabs? or are all our tabs independent? */ + } + + connectedCallback() { + let template: HTMLTemplateElement = document.getElementById('popup-template') as HTMLTemplateElement + let body = (template.content.cloneNode(true) as DocumentFragment).firstElementChild!; + + let uid = this.uid; + // console.log(uid); + + body.getElementsByClassName('populate-with-uid') + .forEach((e) => e.setAttribute('data-uid', uid)); + + /* tabs */ + // for (let tab of body.querySelectorAll(".tabgroup .tab")) { + // } + window.setTimeout(() => { + // let tabs = this.querySelector('tab-element')! + // .shadowRoot! + // .querySelectorAll('label') + // console.log(tabs); + // console.log(this.getElementsByTagName('tab-element')) + for (let tab of this.getElementsByTagName('tab-element')) { + // console.log(tab_container); + // let tab = tab_container.shadowRoot!; + // tab.documentElement.style.setProperty('--i', i); + popuplateTab(tab as TabElement, this.tabgroup_id, this.tabcount) + this.tabcount += 1 + } + (this.querySelector('tab-element label') as HTMLInputElement).click() + }); + /* end tabs */ + + /* nav bar */ + let nav = body.getElementsByClassName("popup-control")[0] as HTMLElement; + bind_popup_control(nav); + + let btn = body.querySelector('.popup-control .close-tooltip') as HTMLButtonElement + btn.addEventListener('click', () => close_popup(this)); + /* end nav bar */ + + this.replaceChildren(body); + } + + addTab(tab: TabElement) { + let tabgroup = this.getElementsByClassName('tabgroup')![0]! + tabgroup.append(tab); + popuplateTab(tab, this.tabgroup_id, this.tabcount) + this.tabcount += 1 + } +} + +function popuplateTab(tab: HTMLElement, tabgroup: string, index: number) { + // console.log(tab); + let new_id = gensym(); + let input = tab.querySelector('input[type="radio"]') as HTMLInputElement; + let label = tab.querySelector("label")! + tab.style.setProperty('--tab-index', '' + index); + /* TODO this throws a number of errors, but somehow still works...? */ + if (input !== null) { + input.name = tabgroup + input.id = new_id; + } + if (label !== null) { + label.setAttribute('for', new_id); + } +} diff --git a/static/components/tab-element.ts b/static/components/tab-element.ts new file mode 100644 index 00000000..9403a737 --- /dev/null +++ b/static/components/tab-element.ts @@ -0,0 +1,40 @@ +export { TabElement } + +/* */ +class TabElement extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + // this.replaceChildren(template.cloneNode(true)); + let template + = (document.getElementById('tab-template') as HTMLTemplateElement) + .content + // const shadowRoot = this.attachShadow({ mode: 'open' }) + // .appendChild(template.cloneNode(true)); + // console.log(this); + let label = this.querySelector('[slot="label"]') + let content = this.querySelector('[slot="content"]') + if (!verifySlot(label)) throw "Bad label"; + if (!verifySlot(content)) throw "Bad content"; + + /* TODO set label hover title somewhere around here */ + + this.replaceChildren(template.cloneNode(true)); + this.querySelector('slot[name="label"]')!.replaceWith(label); + this.querySelector('slot[name="content"]')!.replaceWith(content); + } +} + +function verifySlot(el: Node | null): el is HTMLElement { + if (el === null) { + console.error("Element is null"); + return false; + } + if (!(el instanceof HTMLElement)) { + console.error("Node is not an HTMLElement", el); + return false; + } + return true +} diff --git a/static/components/vevent-block.ts b/static/components/vevent-block.ts new file mode 100644 index 00000000..439ba20e --- /dev/null +++ b/static/components/vevent-block.ts @@ -0,0 +1,63 @@ +export { ComponentBlock } + +import { ComponentVEvent } from './vevent' +import { VEvent } from '../vevent' +import { toggle_popup, find_popup } from '../popup' +import { parseDate, to_local } from '../lib' + + +/* + + A grahpical block in the week view. +*/ +class ComponentBlock extends ComponentVEvent { + constructor(uid?: string) { + super(uid); + + this.addEventListener('click', () => { + let uid = this.uid + let popup = find_popup(uid); + if (popup === null) throw new Error('no popup for uid ' + uid); + toggle_popup(popup); + }); + } + + redraw(data: VEvent) { + super.redraw(data); + + let p; + if ((p = data.getProperty('dtstart'))) { + let c = this.closest('.event-container') as HTMLElement + let start = parseDate(c.dataset.start!).getTime() + let end = parseDate(c.dataset.end!).getTime(); + // console.log(p); + let pp = to_local(p).getTime() + let result = 100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start) + "%" + if (c.classList.contains('longevents')) { + this.style.left = result + } else { + this.style.top = result + } + // console.log('dtstart', p); + } + if ((p = data.getProperty('dtend'))) { + // console.log('dtend', p); + let c = this.closest('.event-container') as HTMLElement + let start = parseDate(c.dataset.start!).getTime() + let end = parseDate(c.dataset.end!).getTime(); + let pp = to_local(p).getTime() + let result = 100 - (100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start)) + "%" + if (c.classList.contains('longevents')) { + this.style.width = 'unset'; + this.style.right = result; + } else { + this.style.height = 'unset'; + this.style.bottom = result; + } + } + + if (data._calendar) { + this.dataset.calendar = data._calendar; + } + } +} diff --git a/static/components/vevent-description.ts b/static/components/vevent-description.ts new file mode 100644 index 00000000..f97b60e1 --- /dev/null +++ b/static/components/vevent-description.ts @@ -0,0 +1,12 @@ +export { ComponentDescription } + +import { ComponentVEvent } from './vevent' + +/* + +*/ +class ComponentDescription extends ComponentVEvent { + constructor() { + super(); + } +} diff --git a/static/components/vevent-dl.ts b/static/components/vevent-dl.ts new file mode 100644 index 00000000..a9e60d81 --- /dev/null +++ b/static/components/vevent-dl.ts @@ -0,0 +1,24 @@ +export { VEventDL } + +import { ComponentVEvent } from './vevent' +import { VEvent } from '../vevent' +import { makeElement } from '../lib' + +/* */ +class VEventDL extends ComponentVEvent { + redraw(obj: VEvent) { + let dl = buildDescriptionList( + Array.from(obj.boundProperties) + .map(key => [key, obj.getProperty(key)])) + this.replaceChildren(dl); + } +} + +function buildDescriptionList(data: [string, any][]): HTMLElement { + let dl = document.createElement('dl'); + for (let [key, val] of data) { + dl.appendChild(makeElement('dt', { innerText: key })) + dl.appendChild(makeElement('dd', { innerText: val })) + } + return dl; +} diff --git a/static/components/vevent-edit.ts b/static/components/vevent-edit.ts new file mode 100644 index 00000000..602e1872 --- /dev/null +++ b/static/components/vevent-edit.ts @@ -0,0 +1,137 @@ +export { ComponentEdit } + +import { ComponentVEvent } from './vevent' +import { DateTimeInput } from './date-time-input' + +import { vcal_objects, event_calendar_mapping } from '../globals' +import { VEvent } from '../vevent' +import { create_event } from '../server_connect' + +/* + Edit form for a given VEvent. Used as the edit tab of popups. +*/ +class ComponentEdit extends ComponentVEvent { + + firstTime: boolean + + constructor() { + super(); + + this.firstTime = true; + } + + connectedCallback() { + + /* 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.get(this.uid) + + if (!data) { + throw `Data missing for uid ${this.dataset.uid}.` + } + + + // return; + + /* Handle calendar dropdown */ + for (let el of this.getElementsByClassName('calendar-selection')) { + for (let opt of el.getElementsByTagName('option')) { + opt.selected = false; + if (opt.value == event_calendar_mapping.get(this.uid)) { + data.setCalendar(opt.value); + opt.selected = true; + /* No break since we want to set the remainders 'selected' to false */ + } + } + + el.addEventListener('change', (e) => { + let v = (e.target as HTMLSelectElement).selectedOptions[0].value + // e.selectedOptions[0].innerText + + let obj = vcal_objects.get(this.uid)! + obj.setCalendar(v); + }); + } + + this.redraw(data); + + for (let el of this.getElementsByClassName("interactive")) { + // console.log(el); + el.addEventListener('input', () => { + let obj = vcal_objects.get(this.uid) + if (obj === undefined) { + throw 'No object with uid ' + this.uid + } + if (!(el instanceof HTMLInputElement + || el instanceof DateTimeInput)) { + console.log(el, 'not an HTMLInputElement'); + return; + } + obj.setProperty( + el.dataset.property!, + el.value) + }); + } + + let submit = this.querySelector('form') as HTMLFormElement + submit.addEventListener('submit', (e) => { + console.log(submit, e); + create_event(vcal_objects.get(this.uid)!); + + e.preventDefault(); + return false; + }); + } + + redraw(data: VEvent) { + // update ourselves from template + + if (!this.template) { + throw "Something"; + } + + let body; + if (this.firstTime) { + body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!; + } else { + body = this; + } + + for (let el of body.getElementsByClassName("interactive")) { + if (!(el instanceof HTMLElement)) 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(() => { + /* 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 */ + /* Technically we just want to cast to HTMLElement with + value field here, but multiple types implement it + sepparately, and no common interface exist */ + (el as HTMLInputElement).value = d; + }); + } + } + + for (let el of body.getElementsByTagName('calendar-selection')) { + for (let opt of el.getElementsByTagName('option')) { + opt.selected = false; + if (opt.value == data._calendar) { + opt.selected = true; + } + } + } + + if (this.firstTime) { + this.replaceChildren(body); + this.firstTime = false; + } + } + +} diff --git a/static/components/vevent.ts b/static/components/vevent.ts new file mode 100644 index 00000000..de232794 --- /dev/null +++ b/static/components/vevent.ts @@ -0,0 +1,71 @@ +export { ComponentVEvent } + +import { vcal_objects } from '../globals' +import { VEvent } from '../vevent' + +/* Root component for all events which content is closely linked to a +@code{VEvent} object + +Lacks an accompaning tag, and shouldn't be directly instanciated. +*/ +class ComponentVEvent extends HTMLElement { + + template: HTMLTemplateElement + uid: string + + constructor(uid?: string) { + super(); + this.template = document.getElementById(this.tagName) as HTMLTemplateElement; + + let real_uid; + if (this.dataset.uid) uid = this.dataset.uid; + if (uid) real_uid = uid; + + if (!real_uid) { + throw `UID required` + } + + this.uid = real_uid; + + vcal_objects.get(this.uid)?.register(this); + + /* We DON'T have a redraw here in the general case, since the + HTML rendered server-side should be fine enough for us. + Those that need a direct rerendering (such as the edit tabs) + should take care of that some other way */ + } + + connectedCallback() { + let uid, v; + if ((uid = this.dataset.uid)) { + v = vcal_objects.get(uid) + if (v) this.redraw(v); + } + } + + redraw(data: VEvent) { + // update ourselves from template + + if (!this.template) { + throw "Something"; + } + + let body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!; + + for (let el of body.getElementsByClassName("bind")) { + if (!(el instanceof HTMLElement)) continue; + let p = el.dataset.property!; + let d, fmt; + if ((d = data.getProperty(p))) { + if ((fmt = el.dataset.fmt)) { + el.innerHTML = d.format(fmt); + } else { + el.innerHTML = d; + } + } + } + + this.replaceChildren(body); + } + +} diff --git a/static/elements.ts b/static/elements.ts new file mode 100644 index 00000000..06e0e31f --- /dev/null +++ b/static/elements.ts @@ -0,0 +1,30 @@ +import { ComponentDescription } from './components/vevent-description' +import { ComponentEdit } from './components/vevent-edit' +import { VEventDL } from './components/vevent-dl' +import { ComponentBlock } from './components/vevent-block' +import { DateTimeInput } from './components/date-time-input' +import { PopupElement } from './components/popup-element' +import { TabElement } from './components/tab-element' + +export { initialize_components } + +function initialize_components() { + + + /* These MUST be created AFTER vcal_objcets and event_calendar_mapping are + inistialized, since their constructors assume that that piece of global + state is available */ + customElements.define('vevent-description', ComponentDescription); + customElements.define('vevent-edit', ComponentEdit); + customElements.define('vevent-dl', VEventDL); + customElements.define('vevent-block', ComponentBlock); + + /* date-time-input should be instansiatable any time, but we do it here + becouse why not */ + + customElements.define('date-time-input', DateTimeInput /*, { extends: 'input' } */) + + /* These maybe also require that the global maps are initialized */ + customElements.define('popup-element', PopupElement) + customElements.define('tab-element', TabElement) +} diff --git a/static/event-creator.ts b/static/event-creator.ts new file mode 100644 index 00000000..cf3468ce --- /dev/null +++ b/static/event-creator.ts @@ -0,0 +1,179 @@ +export { EventCreator } + +import { VEvent } from './vevent' +import { v4 as uuid } from 'uuid' +import { ComponentBlock } from './components/vevent-block' +import { round_time } from './lib' +import { parseDate } from './lib' + +class EventCreator { + + /* Event which we are trying to create */ + ev: VEvent | null = null; + + /* Graphical block for event. Only here so we can find its siblings, + and update pointer events accordingly */ + event: Element | null = null; + + event_start: { x: number, y: number } = { x: NaN, y: NaN } + down_on_event: boolean = false + timeStart: number = 0 + + create_event_down(intended_target: HTMLElement): (e: MouseEvent) => any { + let that = this; + return function(e: MouseEvent) { + /* Only trigger event creation stuff on actuall events background, + NOT on its children */ + that.down_on_event = false; + if (e.target != intended_target) return; + that.down_on_event = true; + + that.event_start.x = e.clientX; + that.event_start.y = e.clientY; + } + } + + /* + round_to: what start and end times should round to when dragging, in fractionsb + of the width of the containing container. + + TODO limit this to only continue when on the intended event_container. + + (event → [0, 1)), 𝐑, bool → event → () + */ + 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(this: HTMLElement, e: MouseEvent) { + if (e.buttons != 1 || !that.down_on_event) return; + + /* Create event when we start moving the mouse. */ + if (!that.ev) { + /* 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; } + + /* only allow start of dragging on background */ + if (e.target !== this) return; + + /* only on left click */ + if (e.buttons != 1) return; + + // let [popup, event] = that.create_empty_event(); + // that.event = event; + that.ev = new VEvent(); + that.ev.setProperty('summary', 'Created Event'); + that.ev.setProperty('uid', uuid()) + + // let ev_block = document.createElement('vevent-block') as ComponentBlock; + let ev_block = new ComponentBlock(that.ev.getProperty('uid')); + that.event = ev_block; + that.ev.register(ev_block); + + /* TODO better solution to add popup to DOM */ + // document.getElementsByTagName("main")[0].append(popup); + + /* [0, 1) -- where are we in the container */ + /* Ronud to force steps of quarters */ + /* NOTE for in-day events a floor here work better, while for + all day events I want a round, but which has the tip over point + around 0.7 instead of 0.5. + It might also be an idea to subtract a tiny bit from the short events + mouse position, since I feel I always get to late starts. + */ + + // that.event.dataset.time1 = '' + time; + // that.event.dataset.time2 = '' + time; + + /* ---------------------------------------- */ + + this.appendChild(ev_block); + + /* requires that event is child of an '.event-container'. */ + // new VComponent( + // event, + // wide_element=wide_element); + // bind_properties(event, wide_element); + + /* requires that dtstart and dtend properties are initialized */ + + /* ---------------------------------------- */ + + /* Makes all current events transparent when dragging over them. + Without this weird stuff happens when moving over them + + This includes ourselves. + */ + for (let e of this.children) { + (e as HTMLElement).style.pointerEvents = "none"; + } + + that.timeStart = round_time(pos_in(this, e), round_to); + } + + let time = round_time(pos_in(this, e), round_to); + + // let time1 = Number(that.event.dataset.time1); + // let time2 = round_time( + // pos_in(that.event.parentElement!, e), + // round_to); + // that.event.dataset.time2 = '' + time2 + + /* ---------------------------------------- */ + + let event_container = this.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!); + + /* ---------------------------------------- */ + + /* ms */ + let duration = container_end.valueOf() - container_start.valueOf(); + + let start_in_duration = duration * Math.min(that.timeStart, time); + let end_in_duration = duration * Math.max(that.timeStart, time); + + /* 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 + timezone doesn't give me) + */ + /* TODO Should these inherit UTC from container_*? */ + let d1 = new Date(container_start.getTime() + start_in_duration) + let d2 = new Date(container_start.getTime() + end_in_duration) + + /* TODO these writes should preferably be grouped, + to save a redraw for all registered listeners */ + that.ev.setProperty('dtstart', d1); + that.ev.setProperty('dtend', d2); + + // console.log(that.event); + // console.log(d1.format("~L~H:~M"), d2.format("~L~H:~M")); + } + } + + create_event_finisher(callback: ((ev: VEvent) => void)) { + let that = this; + return function create_event_up(e: MouseEvent) { + if (!that.ev) return; + + /* Restore pointer events for all existing events. + Allow pointer events on our new event + */ + for (let e of (that.event as Element).parentElement!.children) { + (e as HTMLElement).style.pointerEvents = ""; + } + + let localevent = that.ev; + that.ev = null + that.event = null; + + callback(localevent); + + } + } +} diff --git a/static/globals.ts b/static/globals.ts index 5187d007..be79dae7 100644 --- a/static/globals.ts +++ b/static/globals.ts @@ -1,266 +1,25 @@ export { - vcal_objects, - find_block, find_popup, PopupElement, - ComponentBlock + find_block, + VIEW, EDIT_MODE, + vcal_objects, event_calendar_mapping } -import { close_popup, toggle_popup } from './popup' -import { VEvent, xml_to_vcal } from './vevent' -import { bind_popup_control } from './dragable' -import { uid, parseDate, gensym, to_local, boolean, makeElement } from './lib' -import { create_event } from './server_connect' +import { VEvent } from './vevent' +import { uid } from './types' const vcal_objects: Map = new Map; -(window as any).vcal_objects = vcal_objects; - -interface HasValue { - value: string -} - -function hasValue(obj: any): obj is HasValue { - return 'value' in obj; -} - -/* Root component for all events which content is closely linked to a -@code{VEvent} object - -Lacks an accompaning tag, and shouldn't be directly instanciated. -*/ -class ComponentVEvent extends HTMLElement { - - template: HTMLTemplateElement - uid: string - - constructor(uid?: string) { - super(); - this.template = document.getElementById(this.tagName) as HTMLTemplateElement; - - let real_uid; - if (this.dataset.uid) uid = this.dataset.uid; - if (uid) real_uid = uid; - - if (!real_uid) { - throw `UID required` - } - - this.uid = real_uid; - - vcal_objects.get(this.uid)?.register(this); - - /* We DON'T have a redraw here in the general case, since the - HTML rendered server-side should be fine enough for us. - Those that need a direct rerendering (such as the edit tabs) - should take care of that some other way */ - } - - connectedCallback() { - let uid, v; - if ((uid = this.dataset.uid)) { - v = vcal_objects.get(uid) - if (v) this.redraw(v); - } - } - - redraw(data: VEvent) { - // update ourselves from template - - if (!this.template) { - throw "Something"; - } - - let body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!; - - for (let el of body.getElementsByClassName("bind")) { - if (!(el instanceof HTMLElement)) continue; - let p = el.dataset.property!; - let d, fmt; - if ((d = data.getProperty(p))) { - if ((fmt = el.dataset.fmt)) { - el.innerHTML = d.format(fmt); - } else { - el.innerHTML = d; - } - } - } - - this.replaceChildren(body); - } - -} - - -/* - -*/ -class ComponentDescription extends ComponentVEvent { - constructor() { - super(); - } -} - -function popuplateTab(tab: HTMLElement, tabgroup: string, index: number) { - // console.log(tab); - let new_id = gensym(); - let input = tab.querySelector('input[type="radio"]') as HTMLInputElement; - let label = tab.querySelector("label")! - tab.style.setProperty('--tab-index', '' + index); - /* TODO this throws a number of errors, but somehow still works...? */ - if (input !== null) { - input.name = tabgroup - input.id = new_id; - } - if (label !== null) { - label.setAttribute('for', new_id); - } -} +const event_calendar_mapping: Map = new Map; -/* */ -class VEventDL extends ComponentVEvent { - redraw(obj: VEvent) { - let dl = buildDescriptionList( - Array.from(obj.boundProperties) - .map(key => [key, obj.getProperty(key)])) - this.replaceChildren(dl); +declare global { + interface Window { + vcal_objects: Map; } } +window.vcal_objects = vcal_objects; -/* - Edit form for a given VEvent. Used as the edit tab of popups. -*/ -class ComponentEdit extends ComponentVEvent { - - firstTime: boolean +declare let VIEW: 'month' | 'week' +declare let EDIT_MODE: boolean - constructor() { - super(); - - this.firstTime = true; - } - - connectedCallback() { - - /* 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.get(this.uid) - - if (!data) { - throw `Data missing for uid ${this.dataset.uid}.` - } - - - // return; - - /* Handle calendar dropdown */ - for (let el of this.getElementsByClassName('calendar-selection')) { - for (let opt of el.getElementsByTagName('option')) { - opt.selected = false; - if (opt.value == event_calendar_mapping.get(this.uid)) { - data.setCalendar(opt.value); - opt.selected = true; - /* No break since we want to set the remainders 'selected' to false */ - } - } - - el.addEventListener('change', (e) => { - let v = (e.target as HTMLSelectElement).selectedOptions[0].value - // e.selectedOptions[0].innerText - - let obj = vcal_objects.get(this.uid)! - obj.setCalendar(v); - }); - } - - this.redraw(data); - - for (let el of this.getElementsByClassName("interactive")) { - // console.log(el); - el.addEventListener('input', () => { - let obj = vcal_objects.get(this.uid) - if (obj === undefined) { - throw 'No object with uid ' + this.uid - } - if (!(hasValue(el) && el instanceof HTMLElement)) { - console.log(el, 'not an HTMLInputElement'); - return; - } - obj.setProperty( - el.dataset.property!, - el.value) - }); - } - - let submit = this.querySelector('form') as HTMLFormElement - submit.addEventListener('submit', (e) => { - console.log(submit, e); - create_event(vcal_objects.get(this.uid)!); - - e.preventDefault(); - return false; - }); - } - - redraw(data: VEvent) { - // update ourselves from template - - if (!this.template) { - throw "Something"; - } - - let body; - if (this.firstTime) { - body = (this.template.content.cloneNode(true) as DocumentFragment).firstElementChild!; - } else { - body = this; - } - - for (let el of body.getElementsByClassName("interactive")) { - if (!(el instanceof HTMLElement)) 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(() => { - /* 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 */ - /* Technically we just want to cast to HTMLElement with - value field here, but multiple types implement it - sepparately, and no common interface exist */ - (el as HTMLInputElement).value = d; - }); - } - } - - for (let el of body.getElementsByTagName('calendar-selection')) { - for (let opt of el.getElementsByTagName('option')) { - opt.selected = false; - if (opt.value == data._calendar) { - opt.selected = true; - } - } - } - - if (this.firstTime) { - this.replaceChildren(body); - this.firstTime = false; - } - } - -} - -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}"]`) -} function find_block(uid: uid): HTMLElement | null { let obj = vcal_objects.get(uid) @@ -276,319 +35,17 @@ function find_block(uid: uid): HTMLElement | null { return null; } -/* - A grahpical block in the week view. -*/ -class ComponentBlock extends ComponentVEvent { - constructor(uid?: string) { - super(uid); - this.addEventListener('click', () => { - let uid = this.uid - let popup = find_popup(uid); - if (popup === null) throw new Error('no popup for uid ' + uid); - toggle_popup(popup); - }); - } - redraw(data: VEvent) { - super.redraw(data); - let p; - if ((p = data.getProperty('dtstart'))) { - let c = this.closest('.event-container') as HTMLElement - let start = parseDate(c.dataset.start!).getTime() - let end = parseDate(c.dataset.end!).getTime(); - // console.log(p); - let pp = to_local(p).getTime() - let result = 100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start) + "%" - if (c.classList.contains('longevents')) { - this.style.left = result - } else { - this.style.top = result - } - // console.log('dtstart', p); - } - if ((p = data.getProperty('dtend'))) { - // console.log('dtend', p); - let c = this.closest('.event-container') as HTMLElement - let start = parseDate(c.dataset.start!).getTime() - let end = parseDate(c.dataset.end!).getTime(); - let pp = to_local(p).getTime() - let result = 100 - (100 * (Math.min(end, Math.max(start, pp)) - start) / (end - start)) + "%" - if (c.classList.contains('longevents')) { - this.style.width = 'unset'; - this.style.right = result; - } else { - this.style.height = 'unset'; - this.style.bottom = result; - } - } - if (data._calendar) { - this.dataset.calendar = data._calendar; - } - } -} - -const event_calendar_mapping: Map = new Map; - -window.addEventListener('load', function() { - - // let json_objects_el = document.getElementById('json-objects'); - let div = document.getElementById('xcal-data')!; - let vevents = div.firstElementChild!.children; - - for (let vevent of vevents) { - let ev = xml_to_vcal(vevent); - vcal_objects.set(ev.getProperty('uid'), ev) - } - - - let div2 = document.getElementById('calendar-event-mapping')!; - for (let calendar of div2.children) { - for (let child of calendar.children) { - event_calendar_mapping.set( - child.innerHTML, calendar.getAttribute('key')!); - } - } - - /* - - .popup - - .block - - .list - */ - /* - let vevent_els = document.getElementsByClassName('vevent') - for (let el of vevent_els) { - try { - vcal_objects[el.dataset.uid].register(el); - } catch { - console.error("Invalid something, uid = ", el.dataset.uid, - "el = ", el - ); - } - } - */ - - customElements.define('vevent-description', ComponentDescription); - customElements.define('vevent-edit', ComponentEdit); - customElements.define('vevent-dl', VEventDL); - customElements.define('vevent-block', ComponentBlock); -}) - - - - -/* '' */ -class DateTimeInput extends /* HTMLInputElement */ HTMLElement { - connectedCallback() { - /* This can be in the constructor for chromium, but NOT firefox... - Vivaldi 4.3.2439.63 stable - Mozilla Firefox 94.0.1 - */ - this.innerHTML = '' - // console.log('constructing datetime input') - } - - static get observedAttributes() { - return ['dateonly'] - } - - attributeChangedCallback(name: string, _: any, to: any): void { - // console.log(this, name, boolean(from), boolean(to)); - switch (name) { - case 'dateonly': - (this.querySelector('input[type="time"]') as HTMLInputElement) - .disabled = boolean(to) - break; - } - } - - get dateonly(): boolean { - return boolean(this.getAttribute('dateonly')); - } - - set dateonly(bool: boolean) { - this.setAttribute('dateonly', "" + bool); - } - - get valueAsDate(): Date { - let dt; - 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("input[type='time']") as HTMLInputElement).value; - dt = parseDate(date + 'T' + time) - dt.type = 'date-time'; - } - return dt; - } - - 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"); - time = new_value.format("~L~H:~M:~S"); - } else { - [date, time] = new_value.split('T') - } - (this.querySelector("input[type='date']") as HTMLInputElement).value = date; - (this.querySelector("input[type='time']") as HTMLInputElement).value = time; - } - - addEventListener(type: string, proc: ((e: Event) => void)) { - if (type != 'input') throw "Only input supported"; - - (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 /*, { extends: 'input' } */) - - -function verifySlot(el: Node | null): el is HTMLElement { - if (el === null) { - console.error("Element is null"); - return false; - } - if (!(el instanceof HTMLElement)) { - console.error("Node is not an HTMLElement", el); - return false; - } - return true -} - - -/* */ -class TabElement extends HTMLElement { - constructor() { - super(); - } - - connectedCallback() { - // this.replaceChildren(template.cloneNode(true)); - let template - = (document.getElementById('tab-template') as HTMLTemplateElement) - .content - // const shadowRoot = this.attachShadow({ mode: 'open' }) - // .appendChild(template.cloneNode(true)); - // console.log(this); - let label = this.querySelector('[slot="label"]') - let content = this.querySelector('[slot="content"]') - if (!verifySlot(label)) throw "Bad label"; - if (!verifySlot(content)) throw "Bad content"; - /* TODO set label hover title somewhere around here */ - - this.replaceChildren(template.cloneNode(true)); - this.querySelector('slot[name="label"]')!.replaceWith(label); - this.querySelector('slot[name="content"]')!.replaceWith(content); - } -} - -function buildDescriptionList(data: [string, any][]): HTMLElement { - let dl = document.createElement('dl'); - for (let [key, val] of data) { - dl.appendChild(makeElement('dt', { innerText: key })) - dl.appendChild(makeElement('dd', { innerText: val })) - } - return dl; -} - -/* */ -class PopupElement extends ComponentVEvent { - - tabgroup_id: string - tabcount: number - - constructor(uid?: string) { - super(uid); - - /* TODO populate remaining */ - // this.id = 'popup' + this.dataset.uid - this.tabgroup_id = gensym(); - this.tabcount = 0 - } - - redraw(data: VEvent) { - // console.warn('IMPLEMENT ME'); - - if (data._calendar) { - this.dataset.calendar = data._calendar; - } - - /* TODO is there any case where we want to propagate the draw to any of - our tabs? or are all our tabs independent? */ - } - - connectedCallback() { - let template: HTMLTemplateElement = document.getElementById('popup-template') as HTMLTemplateElement - let body = (template.content.cloneNode(true) as DocumentFragment).firstElementChild!; - - let uid = this.uid; - // console.log(uid); - - body.getElementsByClassName('populate-with-uid') - .forEach((e) => e.setAttribute('data-uid', uid)); - - /* tabs */ - // for (let tab of body.querySelectorAll(".tabgroup .tab")) { - // } - window.setTimeout(() => { - // let tabs = this.querySelector('tab-element')! - // .shadowRoot! - // .querySelectorAll('label') - // console.log(tabs); - // console.log(this.getElementsByTagName('tab-element')) - for (let tab of this.getElementsByTagName('tab-element')) { - // console.log(tab_container); - // let tab = tab_container.shadowRoot!; - // tab.documentElement.style.setProperty('--i', i); - popuplateTab(tab as TabElement, this.tabgroup_id, this.tabcount) - this.tabcount += 1 - } - (this.querySelector('tab-element label') as HTMLInputElement).click() - }); - /* end tabs */ - - /* nav bar */ - let nav = body.getElementsByClassName("popup-control")[0] as HTMLElement; - bind_popup_control(nav); - - let btn = body.querySelector('.popup-control .close-tooltip') as HTMLButtonElement - btn.addEventListener('click', () => close_popup(this)); - /* end nav bar */ - - this.replaceChildren(body); - } - - addTab(tab: TabElement) { - let tabgroup = this.getElementsByClassName('tabgroup')![0]! - tabgroup.append(tab); - popuplateTab(tab, this.tabgroup_id, this.tabcount) - this.tabcount += 1 - } -} - -window.addEventListener('load', function() { - customElements.define('popup-element', PopupElement) - customElements.define('tab-element', TabElement) -}); +/* 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 59ee93b4..605f41e7 100644 --- a/static/jcal.ts +++ b/static/jcal.ts @@ -1,6 +1,7 @@ export { jcal_to_xcal } -import { ical_type, JCalProperty, JCal } from './types' -import { xcal, asList } from './lib' + +import { xcal, ical_type, JCalProperty, JCal } from './types' +import { asList } from './lib' function jcal_type_to_xcal(doc: Document, type: ical_type, value: any): Element { let el = doc.createElementNS(xcal, type); diff --git a/static/lib.ts b/static/lib.ts index ee8046aa..1dfd83f4 100644 --- a/static/lib.ts +++ b/static/lib.ts @@ -1,7 +1,7 @@ export { - makeElement, date_to_percent, uid, - parseDate, gensym, to_local, boolean, - xcal, asList, round_time + makeElement, date_to_percent, + parseDate, gensym, to_local, to_boolean, + asList, round_time } /* @@ -41,8 +41,6 @@ declare global { } } -type uid = string - HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener; HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event) => void) { if (!this.listeners) this.listeners = {}; @@ -52,17 +50,6 @@ HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event) }; - -/* list of lists -> list of tuples */ -/* 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()] - .map((_, i) => args.map(lst => lst[i])); -} - - /* ----- Date Extensions ---------------------------- */ /* @@ -120,13 +107,6 @@ function parseDate(str: string): Date { return 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: Date): Date { if (!date.utc) return date; @@ -162,11 +142,6 @@ function date_to_percent(date: Date): number /* in 0, 100 */ { /* js infix to not collide with stuff generated backend */ const gensym = (counter => (prefix = "gensym") => prefix + "js" + ++counter)(0) -function setVar(str: string, val: any) { - document.documentElement.style.setProperty("--" + str, val); -} - - function asList(thing: Array | T): Array { if (thing instanceof Array) { return thing; @@ -176,7 +151,7 @@ function asList(thing: Array | T): Array { } -function boolean(value: any): boolean { +function to_boolean(value: any): boolean { switch (typeof value) { case 'string': switch (value) { @@ -248,5 +223,3 @@ HTMLCollection.prototype.forEach = function(proc) { proc(el); } } - -const xcal = "urn:ietf:params:xml:ns:icalendar-2.0"; diff --git a/static/popup.ts b/static/popup.ts index 00638089..ce643854 100644 --- a/static/popup.ts +++ b/static/popup.ts @@ -1,7 +1,11 @@ -import { find_block, find_popup, PopupElement } from './globals' +import { VIEW, find_block } from './globals' +import { PopupElement } from './components/popup-element' +import { uid } from './types' + export { event_from_popup, popup_from_event, close_popup, - close_all_popups, /* VIEW, */open_popup, toggle_popup, activePopup + close_all_popups, open_popup, toggle_popup, activePopup, + find_popup } /* TODO rewrite most of this */ @@ -39,7 +43,16 @@ function close_all_popups() { } } -declare let VIEW: 'month' | 'week' + +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}"]`) +} /* open given popup */ function open_popup(popup: HTMLElement) { diff --git a/static/script.ts b/static/script.ts index 21685ee9..b9336165 100644 --- a/static/script.ts +++ b/static/script.ts @@ -1,191 +1,60 @@ import { close_all_popups } from './popup' -import { VEvent } from './vevent' +import { VEvent, xml_to_vcal } from './vevent' import { SmallcalCellHighlight, Timebar } from './clock' -import { makeElement, parseDate, round_time } from './lib' -import { vcal_objects, ComponentBlock, PopupElement } from './globals' +import { makeElement } from './lib' +import { vcal_objects, event_calendar_mapping, EDIT_MODE } from './globals' import { open_popup } from './popup' - -import { v4 as uuid } from 'uuid' +import { EventCreator } from './event-creator' +import { PopupElement } from './components/popup-element' +import { initialize_components } from './elements' /* calp specific stuff */ -class EventCreator { - - /* Event which we are trying to create */ - ev: VEvent | null = null; +window.addEventListener('load', function() { - /* Graphical block for event. Only here so we can find its siblings, - and update pointer events accordingly */ - event: Element | null = null; + // let json_objects_el = document.getElementById('json-objects'); + let div = document.getElementById('xcal-data')!; + let vevents = div.firstElementChild!.children; - event_start: { x: number, y: number } = { x: NaN, y: NaN } - down_on_event: boolean = false - timeStart: number = 0 + for (let vevent of vevents) { + let ev = xml_to_vcal(vevent); + vcal_objects.set(ev.getProperty('uid'), ev) + } - create_event_down(intended_target: HTMLElement): (e: MouseEvent) => any { - let that = this; - return function(e: MouseEvent) { - /* Only trigger event creation stuff on actuall events background, - NOT on its children */ - that.down_on_event = false; - if (e.target != intended_target) return; - that.down_on_event = true; - that.event_start.x = e.clientX; - that.event_start.y = e.clientY; + let div2 = document.getElementById('calendar-event-mapping')!; + for (let calendar of div2.children) { + for (let child of calendar.children) { + event_calendar_mapping.set( + child.innerHTML, calendar.getAttribute('key')!); } } /* - round_to: what start and end times should round to when dragging, in fractionsb - of the width of the containing container. - - TODO limit this to only continue when on the intended event_container. - - (event → [0, 1)), 𝐑, bool → event → () + - .popup + - .block + - .list */ - 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(this: HTMLElement, e: MouseEvent) { - if (e.buttons != 1 || !that.down_on_event) return; - - /* Create event when we start moving the mouse. */ - if (!that.ev) { - /* 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; } - - /* only allow start of dragging on background */ - if (e.target !== this) return; - - /* only on left click */ - if (e.buttons != 1) return; - - // let [popup, event] = that.create_empty_event(); - // that.event = event; - that.ev = new VEvent(); - that.ev.setProperty('summary', 'Created Event'); - that.ev.setProperty('uid', uuid()) - - // let ev_block = document.createElement('vevent-block') as ComponentBlock; - let ev_block = new ComponentBlock(that.ev.getProperty('uid')); - that.event = ev_block; - that.ev.register(ev_block); - - /* TODO better solution to add popup to DOM */ - // document.getElementsByTagName("main")[0].append(popup); - - /* [0, 1) -- where are we in the container */ - /* Ronud to force steps of quarters */ - /* NOTE for in-day events a floor here work better, while for - all day events I want a round, but which has the tip over point - around 0.7 instead of 0.5. - It might also be an idea to subtract a tiny bit from the short events - mouse position, since I feel I always get to late starts. - */ - - // that.event.dataset.time1 = '' + time; - // that.event.dataset.time2 = '' + time; - - /* ---------------------------------------- */ - - this.appendChild(ev_block); - - /* requires that event is child of an '.event-container'. */ - // new VComponent( - // event, - // wide_element=wide_element); - // bind_properties(event, wide_element); - - /* requires that dtstart and dtend properties are initialized */ - - /* ---------------------------------------- */ - - /* Makes all current events transparent when dragging over them. - Without this weird stuff happens when moving over them - - This includes ourselves. - */ - for (let e of this.children) { - (e as HTMLElement).style.pointerEvents = "none"; - } - - that.timeStart = round_time(pos_in(this, e), round_to); - } - - let time = round_time(pos_in(this, e), round_to); - - // let time1 = Number(that.event.dataset.time1); - // let time2 = round_time( - // pos_in(that.event.parentElement!, e), - // round_to); - // that.event.dataset.time2 = '' + time2 - - /* ---------------------------------------- */ - - let event_container = this.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!); - - /* ---------------------------------------- */ - - /* ms */ - let duration = container_end.valueOf() - container_start.valueOf(); - - let start_in_duration = duration * Math.min(that.timeStart, time); - let end_in_duration = duration * Math.max(that.timeStart, time); - - /* 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 - timezone doesn't give me) - */ - /* TODO Should these inherit UTC from container_*? */ - let d1 = new Date(container_start.getTime() + start_in_duration) - let d2 = new Date(container_start.getTime() + end_in_duration) - - /* TODO these writes should preferably be grouped, - to save a redraw for all registered listeners */ - that.ev.setProperty('dtstart', d1); - that.ev.setProperty('dtend', d2); - - // console.log(that.event); - // console.log(d1.format("~L~H:~M"), d2.format("~L~H:~M")); + /* + let vevent_els = document.getElementsByClassName('vevent') + for (let el of vevent_els) { + try { + vcal_objects[el.dataset.uid].register(el); + } catch { + console.error("Invalid something, uid = ", el.dataset.uid, + "el = ", el + ); } } + */ - create_event_finisher(callback: ((ev: VEvent) => void)) { - let that = this; - return function create_event_up(e: MouseEvent) { - if (!that.ev) return; + initialize_components(); - /* Restore pointer events for all existing events. - Allow pointer events on our new event - */ - for (let e of (that.event as Element).parentElement!.children) { - (e as HTMLElement).style.pointerEvents = ""; - } - let localevent = that.ev; - that.ev = null - that.event = null; - callback(localevent); - } - } -} - -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; diff --git a/static/types.ts b/static/types.ts index 6d1331c7..f371c72a 100644 --- a/static/types.ts +++ b/static/types.ts @@ -1,7 +1,8 @@ export { ical_type, valid_input_types, - JCalProperty, JCal + JCalProperty, JCal, + xcal, uid } let all_types = [ @@ -181,6 +182,8 @@ let valid_input_types: Map = type tagname = 'vevent' | string +type uid = string + /* TODO is this type correct? What really are valid values for any? Does that depend on ical_type? Why is the tail a list? What really is the type for the parameter map? @@ -188,3 +191,5 @@ type tagname = 'vevent' | string type JCalProperty = [string, Map, ical_type, any[]] type JCal = [tagname, JCalProperty[], JCal[]] + +const xcal = "urn:ietf:params:xml:ns:icalendar-2.0"; diff --git a/static/vevent.ts b/static/vevent.ts index d8ef58ce..ddabb507 100644 --- a/static/vevent.ts +++ b/static/vevent.ts @@ -1,5 +1,5 @@ -import { ical_type, valid_input_types, JCal, JCalProperty } from './types' -import { uid, parseDate } from './lib' +import { uid, ical_type, valid_input_types, JCal, JCalProperty } from './types' +import { parseDate } from './lib' export { VEvent, xml_to_vcal } -- cgit v1.2.3