diff options
Diffstat (limited to '')
-rw-r--r-- | static/ts/vevent.ts (renamed from static/vevent.ts) | 173 |
1 files changed, 154 insertions, 19 deletions
diff --git a/static/vevent.ts b/static/ts/vevent.ts index f3606f70..6aaa6984 100644 --- a/static/vevent.ts +++ b/static/ts/vevent.ts @@ -2,28 +2,49 @@ import { ical_type, valid_input_types, JCal, JCalProperty, ChangeLogEntry } from import { parseDate } from './lib' export { - VEvent, xml_to_vcal, RecurrenceRule, + Redrawable, + VEvent, + VEventValue, + freqType, isRedrawable, + list_values, + weekday, + xml_to_vcal, } -/* Something which can be redrawn */ +/** Something which can be redrawn */ interface Redrawable extends HTMLElement { + /** Method which will be called upon a redraw request. */ redraw(data: VEvent): void } +/** Checks if the given element is an instance of Redrawable. */ function isRedrawable(x: HTMLElement): x is Redrawable { return 'redraw' in x } +/** + A single value from a vcomponent. + This is basically a type tagged tuple, with an optional map of parameters. +*/ class VEventValue { + /** The value type of the contained value. */ type: ical_type - /* value should NEVER be a list, since multi-valued properties should - be split into multiple VEventValue objects! */ + /** + The actual value. + + Should NEVER be a list, since those are coded as + lists of `VEventValue`:s in `Vevent.properties` + */ value: any + + /** + VComponent parameters attached to the value. + */ parameters: Map<string, any> constructor(type: ical_type, value: any, parameters = new Map) { @@ -32,6 +53,10 @@ class VEventValue { this.parameters = parameters; } + /** + * The return value is *almost* a `JCalProperty`, just without + * the field name. + */ to_jcal(): [Record<string, any>, ical_type, any] { let value; let v = this.value; @@ -77,40 +102,71 @@ class VEventValue { } /* TODO maybe ... */ -class VEventDuration extends VEventValue { -} +// class VEventDuration extends VEventValue { +// } +/** VComponent properties which contain lists */ type list_values = 'categories' | 'resources' | 'freebusy' | 'exdate' | 'rdate' | 'CATEGORIES' | 'RESOURCES' | 'FREEBUSY' | 'EXDATE' | 'RDATE'; -/* - Abstract representation of a calendar event (or similar). -All "live" calendar data in the frontend should live in an object of this type. +/** + This class is the data container for the underlying VEVENT objects in the + backend calendar files. They also keep track on all Web Components which + wants to render part of the event. + + Note that despite the name this component isn't limited to VEVENT:s, but is + used for all VComponents in the tree. This means that even calendars and + alarms can be instances of this class. + + Property access is done through `getProperty` and `setProperty` (properties + are things such as 'SUMMARY', 'DTSTART', ...) */ class VEvent { - /* Calendar properties */ + /** + Properties bound directly on this object. + + These are things such as 'DTSTART', 'SUMMARY', ... + */ private properties: Map<string, VEventValue | VEventValue[]> - /* Children (such as alarms for events) */ + /** + Children to this component. + + Valid children depends on the type. For example, for calendars this is + primarily events, while for events it's alarm components + */ components: VEvent[] - /* HTMLElements which wants to be redrawn when this object changes. - Elements can be registered with the @code{register} method. + /** + HTMLElements which wants to be redrawn when this object changes. + Elements can be registered with the `register` method. */ registered: Redrawable[] #calendar: string | null = null; + /** + * Every write through getProperty gets logged here, and can be + * consumed. Hopefully this will one day turn into an undo system. + * TODO ref ChangeLogEntry. + */ #changelog: ChangeLogEntry[] = [] - /* Iterator instead of direct return to ensure the receiver doesn't - modify the array */ + /** + The changelog for this component. + + An iterator is returned rather than an array, to ensure modifications are + impossible. + */ get changelog(): IterableIterator<[number, ChangeLogEntry]> { return this.#changelog.entries(); } + /** + Add an entry to the changelog. + */ addlog(entry: ChangeLogEntry) { let len = this.#changelog.length let last = this.#changelog[len - 1] @@ -135,6 +191,17 @@ class VEvent { } } + /** + Construct a new Component. + + @param properties + Initial properties for the component + + @param components + Initial children for the component + + TODO where is the type of the component registered? + */ constructor( properties: Map<string, VEventValue | VEventValue[]> = new Map(), components: VEvent[] = [] @@ -156,6 +223,19 @@ class VEvent { // getProperty(key: 'categories'): string[] | undefined + /** + * Returns the value of the given property if set, or undefined otherwise. + * + * For the keys + * + * - `'CATEGORIES'`, + * - `'RESOURCES'`, + * - `'FREEBUSY'`, + * - `'EXDATE'`, and + * - `'RDATE'` + * + * instead returns a list list of values. + */ getProperty(key: string): any | any[] | undefined { key = key.toUpperCase() let e = this.properties.get(key); @@ -166,11 +246,12 @@ class VEvent { return e.value; } + /** Returns an iterator of all our properties. */ get boundProperties(): IterableIterator<string> { return this.properties.keys() } - private setPropertyInternal(key: string, value: any, type?: ical_type) { + #setPropertyInternal(key: string, value: any, type?: ical_type) { function resolve_type(key: string, type?: ical_type): ical_type { if (type) { return type; @@ -228,24 +309,40 @@ class VEvent { setProperty(key: list_values, value: any[], type?: ical_type): void; setProperty(key: string, value: any, type?: ical_type): void; + /** + * Sets the given property to the given value. If type is given it's + * stored alongside the value, possibly updating what is already + * there. Do however note that no validation between the given type and + * the type of the value is done. + * + * `value` may also be a list, but should only be so for the keys + * mentioned in `getProperty`. + * + * After the value is set, `redraw` is called on all registered + * objects, notifying them of the change. + */ setProperty(key: string, value: any, type?: ical_type) { - this.setPropertyInternal(key, value, type); + this.#setPropertyInternal(key, value, type); for (let el of this.registered) { el.redraw(this); } } + /** + * Equivalent to running `setProperty` for each element in the input + * list, but only calls `redraw` once at the end. + */ setProperties(pairs: [string, any, ical_type?][]) { for (let pair of pairs) { - this.setPropertyInternal(...pair); + this.#setPropertyInternal(...pair); } for (let el of this.registered) { el.redraw(this); } } - + /** The name of the calendar which this event belongs to. */ set calendar(calendar: string | null) { this.addlog({ type: 'calendar', @@ -259,18 +356,29 @@ class VEvent { } } + /** + Get the name of the containing calendar for this component. + + This is only valid for VEVENT components (I think) + */ get calendar(): string | null { return this.#calendar; } + /** + * Register something redrawable, which will be notified whenever this + * VEvents data is updated. + */ register(htmlNode: Redrawable) { this.registered.push(htmlNode); } + /** Stop recieving redraw events on the given component. */ unregister(htmlNode: Redrawable) { this.registered = this.registered.filter(node => node !== htmlNode) } + /** Converts the object to JCal data. */ to_jcal(): JCal { let out_properties: JCalProperty[] = [] console.log(this.properties); @@ -300,6 +408,7 @@ class VEvent { } } +/** Helper procedure when converting xml to vcal */ function make_vevent_value(value_tag: Element): VEventValue { /* TODO parameters */ return new VEventValue( @@ -313,25 +422,49 @@ function make_vevent_value(value_tag: Element): VEventValue { +/** Different frequency internals for recurrence rules. */ type freqType = 'SECONDLY' | 'MINUTELY' | 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY' + +/** Alternatives for when a week start, for recurrence rules */ type weekday = 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA' | 'SU' +/** + A recurrence rule. + + The basic semantics of this class is borrowed from RFC 5545, and maps 1-to-1 + on those instances. See individual fields for mappings. + */ class RecurrenceRule { + /** The type of frequency of this rule */ freq?: freqType + /** Final instance of this rule. */ until?: Date + /** Maximum number of recurrences for this rule */ count?: number + /** The multiplier to `freq` */ interval?: number + /** Which seconds are relevant for this rule */ bysecond?: number[] + /** Which minutes are relevant for this rule */ byminute?: number[] + /** Which hours are relevant for this rule */ byhour?: number[] + /** Which weekday or weekday offsets are relevant for this rule */ byday?: (weekday | [number, weekday])[] + /** Which month days are relevant for this rule */ bymonthday?: number[] + /** Which year days are relevant for this rule */ byyearday?: number[] + /** Which week number are relevant for this rule */ byweekno?: number[] + /** Which months relevant for this rule (interval 1-12) */ bymonth?: number[] + /** TODO see the RFC */ bysetpos?: number[] + /** Which day the week start, according to this rule */ wkst?: weekday + /** Converts ourselves to JCal data. */ to_jcal(): Record<string, any> { let obj: any = {} if (this.freq) obj['freq'] = this.freq; @@ -368,6 +501,7 @@ class RecurrenceRule { } } +/** Parse a XCAL recurrence rule into a RecurrenceRule object. */ function xml_to_recurrence_rule(xml: Element): RecurrenceRule { let rr = new RecurrenceRule; @@ -507,6 +641,7 @@ function make_vevent_value_(value_tag: Element): string | boolean | Date | numbe } } +/** Parse a complete XCAL object into a JS VEvent object. */ function xml_to_vcal(xml: Element): VEvent { /* xml MUST have a VEVENT (or equivalent) as its root */ let properties = xml.getElementsByTagName('properties')[0]; |