aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hörnquist <hugo@lysator.liu.se>2023-09-07 02:58:41 +0200
committerHugo Hörnquist <hugo@lysator.liu.se>2023-09-07 02:58:41 +0200
commite753d721519f72014241b3d2fc804a919f655769 (patch)
tree5f1e93ca4d3daefcffd746eb0784183664fd241e
parentNormalize tsconfig formatting. (diff)
downloadcalp-e753d721519f72014241b3d2fc804a919f655769.tar.gz
calp-e753d721519f72014241b3d2fc804a919f655769.tar.xz
Document remaining javascript items.
-rw-r--r--static/Makefile12
-rw-r--r--static/README.md10
-rw-r--r--static/package.json1
-rw-r--r--static/ts/clock.ts54
-rw-r--r--static/ts/components.ts13
-rw-r--r--static/ts/components/changelog.ts39
-rw-r--r--static/ts/components/date-jump.ts39
-rw-r--r--static/ts/components/date-time-input.ts38
-rw-r--r--static/ts/components/input-list.ts70
-rw-r--r--static/ts/components/popup-element.ts6
-rw-r--r--static/ts/components/slider.ts70
-rw-r--r--static/ts/components/tab-group-element.ts9
-rw-r--r--static/ts/components/vevent-block.ts12
-rw-r--r--static/ts/components/vevent.ts19
-rw-r--r--static/ts/event-creator.ts77
-rw-r--r--static/ts/formatters.ts6
-rw-r--r--static/ts/globals.ts4
-rw-r--r--static/ts/jcal.ts30
-rw-r--r--static/ts/lib.ts216
-rw-r--r--static/ts/types.ts42
-rw-r--r--static/ts/vevent.ts126
-rw-r--r--static/tsconfig.json16
22 files changed, 680 insertions, 229 deletions
diff --git a/static/Makefile b/static/Makefile
index 17432585..00401503 100644
--- a/static/Makefile
+++ b/static/Makefile
@@ -15,7 +15,7 @@ __ESBUILD_FLAGS = --log-level=$(ESBUILD_LOGLEVEL) \
--sourcemap --bundle --outdir=$(CURDIR)/out \
$(ESBUILD_FLAGS)
-export PATH := $(shell npm bin):$(PATH)
+export PATH := $(CURDIR)/node_modules/.bin/:$(PATH)
all: $(TARGETS)
@@ -42,5 +42,13 @@ clean:
out/%.css: scss/%.scss
scss -E UTF-8 $(WATCH) -I. $< $@
+# The grep expression is to supress irrelevant warning messages.
+# - __type since the extensions to base classes propagate to many
+# events, but typedoc only documents them if in the entry point
+# - [.]# since it's sometimes sensible to not document
+# implementation details.
+# - connectedCallback, attributeChangedCallabck: These are part
+# of the standard API for Web Components, and usually have nothing
+# interesting to note.
doc:
- typedoc --logLevel Verbose --excludeExternals
+ typedoc --excludeExternals |& grep -vE '(__type|[.]#|connectedCallback|attributeChangedCallback)'
diff --git a/static/README.md b/static/README.md
index fe5f775c..1cb18411 100644
--- a/static/README.md
+++ b/static/README.md
@@ -1,5 +1,15 @@
The frontend code has its entry-point in `script.ts`.
+Much of this code assumes prior knowledge of the iCalendar standard (RFC
+5545). Besides that, the term "VComponent" is used to refer to any Calendar
+Component as specified in that RFC under 3.6.
+
+## Data Flow
+
+TODO document how data gets from the server to us, and from us to the server.
+
+A large part of this is how much we convert between serialization formats.
+
## web components
All elements are initialized in components.ts
diff --git a/static/package.json b/static/package.json
index f376031d..f671def1 100644
--- a/static/package.json
+++ b/static/package.json
@@ -7,6 +7,7 @@
"@types/uuid": "^8.3.1"
},
"optionalDependencies": {
+ "comment-json": "^4.2.3",
"madge": "^5.0.1"
},
"dependencies": {
diff --git a/static/ts/clock.ts b/static/ts/clock.ts
index a0e4670a..11b2b2c5 100644
--- a/static/ts/clock.ts
+++ b/static/ts/clock.ts
@@ -10,6 +10,8 @@
*
* TODO shouldn't these be defined with the rest of the components?
*
+ * TODO why isn't Timebar and SmallCellHighlight also Web Components?
+ *
* @module
*/
@@ -35,6 +37,8 @@ class Timebar extends Clock {
// start_time: Date
// end_time: Date
+
+ /** The bar to update */
bar_object: HTMLElement | null
constructor(/*start_time: Date, end_time: Date*/) {
@@ -74,7 +78,12 @@ class Timebar extends Clock {
*/
class SmallcalCellHighlight extends Clock {
+ /** The calendar which a cell should be highlighted in */
small_cal: HTMLElement
+ /**
+ The currently highlighted cell, or `null` if no cell should be
+ should be highlighted (such as if a non-current month is selected
+ */
current_cell: HTMLElement | null
/**
@@ -105,8 +114,16 @@ class SmallcalCellHighlight extends Clock {
/* -------------------------------------------------- */
+/**
+ Base class for custom HTML elements which wants to be updated for a human
+ timescale.
+
+ When creating, the attribute `interval` can be given, which specifies (in
+ seconds) how often the component should be updated.
+*/
class ClockElement extends HTMLElement {
+ /** Javascript timer id. Used if the timer needs to be canceled */
timer_id: number
constructor() {
@@ -125,21 +142,24 @@ class ClockElement extends HTMLElement {
this.update(new Date)
}
- static get observedAttributes() {
- return ['timer_id']
- }
-
+ /**
+ Method which is called each "tick" (see interval)
+ @param date
+ The current timestamp when the function is called.
+ */
update(_: Date) { /* noop */ }
}
/**
- * Updates the ``Today'' link in the side panel to point directly to the
- * correct web-address. The link works without JavaScript, but then
- * requires a redirect from the server.
- *
- * All actual updating logic is already abstracted away. It would be
- * desirable if something more was done with this.
+ A "button" which always points to the link "~Y-~m-~d.html".
+
+ This class is bound to the web component <today-button />
+
+ In the backend code, a `/today` endpoint exists. That however requires that
+ we ask the server for the correct URL, and follow a 300 (series) redirect.
+
+ Since the URL:s are stable, it's possible to jump directly to the given page.
*/
class TodayButton extends ClockElement {
a: HTMLAnchorElement;
@@ -162,12 +182,26 @@ class TodayButton extends ClockElement {
}
+/**
+ A component which displays the current time
+
+ This class is bound to the web component <current-time />
+
+ It currently is hard-coded to display time on the format ~H:~M:~S.
+*/
class CurrentTime extends ClockElement {
update(now: Date) {
this.textContent = now.format('~H:~M:~S')
}
}
+/**
+ Create Web Components mentioned on this page.
+
+ MUST be called early on in the execution.
+
+ TODO this should be merged with other web component declarations.
+*/
function initialize_clock_components() {
customElements.define('today-button', TodayButton)
customElements.define('current-time', CurrentTime)
diff --git a/static/ts/components.ts b/static/ts/components.ts
index cbd6dc9b..c78b5753 100644
--- a/static/ts/components.ts
+++ b/static/ts/components.ts
@@ -22,6 +22,19 @@ import { DateJump } from './components/date-jump'
export { initialize_components }
+/**
+ Create web components from all our components.
+
+ The reason each components module doesn't simply initialize its own component
+ is due to some components needing to be initialized AFTER some global
+ variables (see inline comments).
+
+ @TODO
+ Fix the initialization order dependency
+
+ @TODO
+ or otherwise have a static field on each component specifying it's desired name.
+ */
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
diff --git a/static/ts/components/changelog.ts b/static/ts/components/changelog.ts
index 720d1656..8f8adc1c 100644
--- a/static/ts/components/changelog.ts
+++ b/static/ts/components/changelog.ts
@@ -1,32 +1,43 @@
/**
- * `<changelog />`
- *
- * Display of a VEvents changelog. @ref{ChangeLogEntry}
- *
- * @privateRemarks @anchor{VEventChangelog}
- *
- * @category Web Components
- * @mergeTarget components
- * @module
- */
+ `<changelog />`
+
+ Display of a VEvents changelog. @ref{ChangeLogEntry}
+
+ TODO rename this file!
+
+
+ @privateRemarks @anchor{VEventChangelog}
+
+ @category Web Components
+ @mergeTarget components
+ @module
+*/
import { makeElement } from '../lib'
import { ComponentVEvent } from './vevent'
import { VEvent } from '../vevent'
export { VEventChangelog }
+/**
+ Component displaying veevents changelog.
+
+ This component is dumb, and (almost) doesn't keep any internal state. Instead
+ other parts of the program should call it with a `VEvent`, which contains the
+ actual changelog.
+*/
class VEventChangelog extends ComponentVEvent {
- readonly ul: HTMLElement
+ /** The list holding the changelog */
+ readonly #ul: HTMLElement
constructor(uid?: string) {
super(uid);
- this.ul = makeElement('ul');
+ this.#ul = makeElement('ul');
}
connectedCallback() {
- this.replaceChildren(this.ul);
+ this.replaceChildren(this.#ul);
}
redraw(data: VEvent) {
@@ -55,6 +66,6 @@ class VEventChangelog extends ComponentVEvent {
children.push(makeElement('li', { textContent: msg }));
}
- this.ul.replaceChildren(...children)
+ this.#ul.replaceChildren(...children)
}
}
diff --git a/static/ts/components/date-jump.ts b/static/ts/components/date-jump.ts
index fd3908ae..f1cfe7e6 100644
--- a/static/ts/components/date-jump.ts
+++ b/static/ts/components/date-jump.ts
@@ -1,40 +1,51 @@
+/**
+ `<date-jump />`
+
+ @category Web Components
+ @mergeTarget components
+ @module
+*/
+
export { DateJump }
-/* Replace backend-driven [today] link with frontend, with one that
+/** Replace backend-driven [today] link with frontend, with one that
gets correctly set in the frontend. Similarly, update the go to
specific date button into a link which updates wheneven the date
form updates.
+
+ TODO is this comment correct? We somehow contain an input element also.
*/
class DateJump extends HTMLElement {
- readonly golink: HTMLAnchorElement;
- readonly input: HTMLInputElement;
+ readonly #golink: HTMLAnchorElement;
+ readonly #input: HTMLInputElement;
constructor() {
super();
- this.golink = document.createElement('a')
- this.golink.classList.add('btn');
- this.golink.textContent = "➔"
- this.input = document.createElement('input')
- this.input.type = 'date';
+ this.#golink = document.createElement('a')
+ this.#golink.classList.add('btn');
+ this.#golink.textContent = "➔"
+ this.#input = document.createElement('input')
+ this.#input.type = 'date';
}
+ /** Sets the link to NOW upon mounting */
connectedCallback() {
/* Form is just here so the css works out */
let form = document.createElement('form');
- form.replaceChildren(this.input, this.golink);
+ form.replaceChildren(this.#input, this.#golink);
this.replaceChildren(form);
- this.input.onchange = () => {
- let date = this.input.valueAsDate!.format('~Y-~m-~d');
- this.golink.href = `${date}.html`
+ this.#input.onchange = () => {
+ let date = this.#input.valueAsDate!.format('~Y-~m-~d');
+ this.#golink.href = `${date}.html`
}
let now = (new Date).format("~Y-~m-~d")
- this.input.value = now;
+ this.#input.value = now;
/* onchange isn't triggered by manually setting the value */
- this.golink.href = `${now}.html`
+ this.#golink.href = `${now}.html`
}
}
diff --git a/static/ts/components/date-time-input.ts b/static/ts/components/date-time-input.ts
index d1ab5ba1..33201653 100644
--- a/static/ts/components/date-time-input.ts
+++ b/static/ts/components/date-time-input.ts
@@ -27,7 +27,9 @@ import { makeElement, parseDate } from '../lib'
*/
class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
+ /** Our time input element */
readonly time: HTMLInputElement;
+ /** Our date input element */
readonly date: HTMLInputElement;
constructor() {
@@ -43,22 +45,30 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}) as HTMLInputElement
}
+ /**
+ We set our children first when mounted.
+
+ This can be in the constructor for chromium, but NOT firefox...
+
+ - Vivaldi 4.3.2439.63 stable
+ - Mozilla Firefox 94.0.1
+ */
connectedCallback() {
- /* This can be in the constructor for chromium, but NOT firefox...
- Vivaldi 4.3.2439.63 stable
- Mozilla Firefox 94.0.1
- */
- /*
- https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#boolean_attributes
- https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute
- */
this.replaceChildren(this.date, this.time)
}
+ /**
+ Attributes which we want notifications when they are change.
+
+ Part of the Web Component API
+
+ - `dateonly`
+ */
static get observedAttributes() {
return ['dateonly']
}
+ /** Part of the Web Component API */
attributeChangedCallback(name: string, _: string | null, to: string | null): void {
switch (name) {
case 'dateonly':
@@ -84,6 +94,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
return this.hasAttribute('dateonly');
}
+ /** See getter */
set dateonly(b: boolean) {
if (b) {
this.setAttribute('dateonly', "");
@@ -92,6 +103,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}
}
+ /** See getter */
set value(date: Date) {
let [d, t] = date.format("~L~Y-~m-~dT~H:~M").split('T');
this.date.value = d;
@@ -124,6 +136,13 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}
}
+ /**
+ Set the selected date.
+
+ @param new_value
+ If given a date, set the input to that date.
+ If given a string, parse it as an ISO-8601 formatted datetime.
+ */
set stringValue(new_value: Date | string) {
let date, time, dateonly = false;
if (new_value instanceof Date) {
@@ -138,6 +157,9 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
this.time.value = time;
}
+ /**
+ Adds an event listener to both the date and time input.
+ */
addEventListener(type: string, proc: ((e: Event) => void)) {
if (type != 'input') throw "Only input supported";
diff --git a/static/ts/components/input-list.ts b/static/ts/components/input-list.ts
index 31dd5158..72d27cab 100644
--- a/static/ts/components/input-list.ts
+++ b/static/ts/components/input-list.ts
@@ -14,26 +14,62 @@ export { InputList }
TODO allow each item to be a larger unit, possibly containing multiple input
fields.
*/
+/**
+ A multi-valued input, done by creating extra input fields as needed.
+
+ The first element of body MUST be an input element, which will be used as the
+ template for each instance. A tag input could for example look like
+
+ @example
+ ```html
+ <input-list name="tags">
+ <input type="text" placeholder="tag ..." />
+ </input-list>
+ ```
+
+ Whenever one of the input elements `value` becomes the empty string, that tag
+ is removed, and whenever there is no element with the empty string as a
+ `value`, a new input element will be added onto the end.
+ */
class InputList extends HTMLElement {
- el: HTMLInputElement;
+ /** The element used as our template. Will be sourced from the initial HTML code. */
+ #el: HTMLInputElement;
+ /**
+ Registered listeners, which will be added onto each created entry
+
+ Keys are event names ('input', 'change', ...) and values event handlers.
+
+ This is a list of tuples rather than a dictionary, since multiple
+ listeners of the same type can be registered.
+ */
#listeners: [string, (e: Event) => void][] = [];
constructor() {
super();
- this.el = this.children[0].cloneNode(true) as HTMLInputElement;
+ this.#el = this.children[0].cloneNode(true) as HTMLInputElement;
}
+ /** Clears all existing children upon mount */
connectedCallback() {
for (let child of this.children) {
child.remove();
}
- this.addInstance();
+ this.#addInstance();
}
- createInstance(): HTMLInputElement {
- let new_el = this.el.cloneNode(true) as HTMLInputElement
+ /**
+ Instanciates a new instance of the input element.
+
+ An event listener for 'input' will be added, which will handle the
+ addition and removing of other elements.
+
+ All event listeners attachet on the input-list component will also be
+ added.
+ */
+ #createInstance(): HTMLInputElement {
+ let new_el = this.#el.cloneNode(true) as HTMLInputElement
let that = this;
new_el.addEventListener('input', function() {
/* TODO .value is empty both if it's actually empty, but also
@@ -49,7 +85,7 @@ class InputList extends HTMLElement {
}
} else {
if (!this.nextElementSibling) {
- that.addInstance();
+ that.#addInstance();
// window.setTimeout(() => this.focus())
this.focus();
}
@@ -63,8 +99,9 @@ class InputList extends HTMLElement {
return new_el;
}
- addInstance() {
- let new_el = this.createInstance();
+ /** Add a new instance of the input element to the container */
+ #addInstance() {
+ let new_el = this.#createInstance();
this.appendChild(new_el);
}
@@ -72,7 +109,7 @@ class InputList extends HTMLElement {
* The value from each element, except the last which should always be empty.
* Has an unspecified type, since children:s value field might give non-strings.
*/
- get value(): any[] {
+ get value(): unknown[] {
let value_list = []
for (let child of this.children) {
value_list.push((child as any).value);
@@ -83,6 +120,12 @@ class InputList extends HTMLElement {
return value_list
}
+ /**
+ Overwrite the current value with a new one.
+
+ Each entry in the array will be mapped unto one instance of the template
+ input element. A final empty element will also be added.
+ */
set value(new_value: any[]) {
let all_equal = true;
@@ -111,17 +154,22 @@ class InputList extends HTMLElement {
/* clear dictionary */
values.set(value, false);
} else {
- let new_el = this.createInstance();
+ let new_el = this.#createInstance();
new_el.value = value;
output_list.push(new_el);
}
}
/* final, trailing, element */
- output_list.push(this.createInstance());
+ output_list.push(this.#createInstance());
this.replaceChildren(...output_list);
}
+ /**
+ Add an event listener to each of the inputs.
+
+ This basically works as the "regular" version.
+ */
addEventListener(type: string, proc: ((e: Event) => void)) {
// if (type != 'input') throw "Only input supported";
diff --git a/static/ts/components/popup-element.ts b/static/ts/components/popup-element.ts
index cc011ce3..a1e81f0e 100644
--- a/static/ts/components/popup-element.ts
+++ b/static/ts/components/popup-element.ts
@@ -90,6 +90,7 @@ class PopupElement extends ComponentVEvent {
this.replaceChildren(body);
}
+ /** ['visible'] */
static get observedAttributes() {
return ['visible'];
}
@@ -99,7 +100,7 @@ class PopupElement extends ComponentVEvent {
case 'visible':
if (newValue !== null)
/* Only run resize code when showing the popup */
- this.onVisibilityChange()
+ this.#onVisibilityChange()
break;
}
}
@@ -114,6 +115,7 @@ class PopupElement extends ComponentVEvent {
return this.hasAttribute('visible');
}
+ /** Set the visibility status of the component. */
set visible(isVisible: boolean) {
if (isVisible) {
this.setAttribute('visible', 'visible');
@@ -122,7 +124,7 @@ class PopupElement extends ComponentVEvent {
}
}
- private onVisibilityChange() {
+ #onVisibilityChange() {
console.log('here');
/* TODO better way to find root */
diff --git a/static/ts/components/slider.ts b/static/ts/components/slider.ts
index 48abc91b..8be66a73 100644
--- a/static/ts/components/slider.ts
+++ b/static/ts/components/slider.ts
@@ -1,24 +1,58 @@
-export { SliderInput }
+/**
+ <slider-input />
+
+ A Web Component implementing a slider with a corresponding number input.
+
+ TODO rename this file
+
+ ### Parameters
+
+ All of these are optional, see {@linkcode dflt} for defaults.
+
+ #### min
+ Minimum allowed value.
+
+ #### max
+ Maximum allowed value.
+
+ #### step
+ How large each step of the slider/number box should be.
+
+ @module
+*/
+
+export { SliderInput, Attribute, dflt }
import { makeElement } from '../lib'
+/** Defalut values for all attributes, if not given */
const dflt = {
min: 0,
max: 100,
step: 1,
}
+/** Valid attributes for SliderInput */
type Attribute = 'min' | 'max' | 'step'
+/**
+ Component displaying an input slider, together with a corresponding numerical
+ input
+*/
class SliderInput extends HTMLElement {
/* value a string since javascript kind of expects that */
- #value = "0";
- min = 0;
- max = 100;
- step = 1;
-
+ #value = "" + dflt.min
+ /** Minimum allowed value */
+ min = dflt.min
+ /** Maximum allowed value */
+ max = dflt.max
+ /** How large each step should be */
+ step = dflt.step
+
+ /** The HTML slider component */
readonly slider: HTMLInputElement;
+ /** The HTML number input component */
readonly textIn: HTMLInputElement;
constructor(min?: number, max?: number, step?: number, value?: number) {
@@ -48,8 +82,8 @@ class SliderInput extends HTMLElement {
value: this.value,
}) as HTMLInputElement
- this.slider.addEventListener('input', e => this.propagate(e));
- this.textIn.addEventListener('input', e => this.propagate(e));
+ this.slider.addEventListener('input', e => this.#propagate(e));
+ this.textIn.addEventListener('input', e => this.#propagate(e));
/* MUST be after sub components are bound */
this.value = "" + (value || this.getAttribute('value') || defaultValue);
@@ -59,7 +93,7 @@ class SliderInput extends HTMLElement {
this.replaceChildren(this.slider, this.textIn);
}
-
+ /** ['min', 'max', 'step'] */
static get observedAttributes(): Attribute[] {
return ['min', 'max', 'step']
}
@@ -75,19 +109,35 @@ class SliderInput extends HTMLElement {
this[name] = parseFloat(to || "" + dflt[name])
}
- propagate(e: Event) {
+ /**
+ Helper for updating the value attribute
+
+ Event listeners are bound on both the input elements, which both simply
+ call this. This procedure then updates the classes value field.
+
+ TODO `oninput`?
+ */
+ #propagate(e: Event) {
this.value = (e.target as HTMLInputElement).value;
if (e instanceof InputEvent && this.oninput) {
this.oninput(e);
}
}
+ /**
+ Set a new numerical value.
+
+ A number not possible due to the current `min`, `max`, and `step`
+ properties can be set and will work, the slider will however not
+ properly show it, but rather the closest value it can display.
+ */
set value(value: string) {
this.slider.value = value;
this.textIn.value = value;
this.#value = value;
}
+ /** Get the current numerical value */
get value(): string {
return this.#value;
}
diff --git a/static/ts/components/tab-group-element.ts b/static/ts/components/tab-group-element.ts
index ce532cec..bcd45b40 100644
--- a/static/ts/components/tab-group-element.ts
+++ b/static/ts/components/tab-group-element.ts
@@ -58,9 +58,12 @@ export { TabGroupElement }
*/
class TabGroupElement extends ComponentVEvent {
+ /** The container holding all the tabLabels */
readonly menu: HTMLElement;
+ /** Contents of each tab */
tabs: HTMLElement[] = [];
+ /** Label element of each tab */
tabLabels: HTMLElement[] = [];
constructor(uid?: string) {
@@ -166,7 +169,7 @@ class TabGroupElement extends ComponentVEvent {
this.tabLabels.push(tabLabel);
this.menu.appendChild(tabLabel);
- tabLabel.addEventListener('click', () => this.tabClickedCallback(tabLabel));
+ tabLabel.addEventListener('click', () => this.#tabClickedCallback(tabLabel));
this.style.setProperty('--tabcount', '' + this.tabs.length);
}
@@ -199,7 +202,7 @@ class TabGroupElement extends ComponentVEvent {
}
/* TODO replace querySelectors here with our already saved references */
- tabClickedCallback(tab: Element) {
+ #tabClickedCallback(tab: Element) {
/* hide all tab panels */
for (let tabcontent of this.querySelectorAll('[role="tabpanel"]')) {
@@ -218,7 +221,7 @@ class TabGroupElement extends ComponentVEvent {
}
- /* returns our rrule tab if we have one */
+ /** Return our rrule tab if we have one */
has_rrule_tab(): Element | false {
for (let child of this.children) {
if (child.firstChild! instanceof EditRRule) {
diff --git a/static/ts/components/vevent-block.ts b/static/ts/components/vevent-block.ts
index 374cf103..90460740 100644
--- a/static/ts/components/vevent-block.ts
+++ b/static/ts/components/vevent-block.ts
@@ -16,10 +16,16 @@ import { VEvent } from '../vevent'
import { parseDate, to_local } from '../lib'
-/* <vevent-block />
+/**
+ A graphical block in the inline view.
- A grahpical block in the week view.
-*/
+ The back-end links what should become these to elements in the sidebar
+ containing extra info, jumping between them using fragment links.
+ That functionality is removed when we replace the non-js fallback children of
+ these elements, but we instead link it to a
+ {@linkcode components/popup-element.PopupElement}
+ containing the detailed information, along with editing controls and more.
+ */
class ComponentBlock extends ComponentVEvent {
constructor(uid?: string) {
super(uid);
diff --git a/static/ts/components/vevent.ts b/static/ts/components/vevent.ts
index 1d400e1e..50ff4a30 100644
--- a/static/ts/components/vevent.ts
+++ b/static/ts/components/vevent.ts
@@ -16,9 +16,23 @@ export { ComponentVEvent }
import { vcal_objects } from '../globals'
import { VEvent } from '../vevent'
+/**
+ Base class for all Web Components closely linked with VEvents.
+
+ TODO document how templates work.
+
+ TODO document lifecycle, and how objects are fetched from the "global" store.
+ */
abstract class ComponentVEvent extends HTMLElement {
+ /**
+ The template for this event.
+
+ TODO document how this is populate
+ */
template?: HTMLTemplateElement
+
+ /** The UID of the VEvent we are tracking */
uid: string
/**
@@ -72,6 +86,11 @@ abstract class ComponentVEvent extends HTMLElement {
should take care of that some other way */
}
+ /**
+ Called when the component is mounted.
+
+ Redraws the target if the wanted object is available at that time.
+ */
connectedCallback() {
let uid = this.dataset.uid
if (uid) {
diff --git a/static/ts/event-creator.ts b/static/ts/event-creator.ts
index 5e55e64e..a3231d24 100644
--- a/static/ts/event-creator.ts
+++ b/static/ts/event-creator.ts
@@ -6,40 +6,69 @@ import { ComponentBlock } from './components/vevent-block'
import { round_time, parseDate } from './lib'
import { ical_type } from './types'
+/**
+ Class managing the state while creating events.
+
+ This is mainly for, when in the UI, the user starts to create events by
+ dragging on the calendar.
+
+*/
class EventCreator {
- /* Event which we are trying to create */
+ /** Event which we are trying to create */
ev?: VEvent
- /* Graphical block for event. Only here so we can find its siblings,
- and update pointer events accordingly */
+ /** Graphical block for event. Only here so we can find its siblings, and
+ update pointer events accordingly */
event?: Element
- event_start: { x: number, y: number } = { x: NaN, y: NaN }
- down_on_event: boolean = false
- timeStart: number = 0
+ /**
+ Where the mouse down for the event started.
+
+ This is here to check if the user is actually dragging, or just randomly
+ clicking on the background with a shaky hand.
+
+ There are some constants in the code for what a shaky hand means
+ (currently less than 10 pixels in X, or 5 in Y)
+ */
+ #event_start: { x: number, y: number } = { x: NaN, y: NaN }
+ #down_on_event: boolean = false
+ #time_start: number = 0
+ /**
+ Event handler for `mosedown` events.
+ */
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;
+ that.#down_on_event = false;
if (e.target != intended_target) return;
- that.down_on_event = true;
+ that.#down_on_event = true;
- that.event_start.x = e.clientX;
- that.event_start.y = e.clientY;
+ 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.
+ /**
+ Event handler for `mousemove` events.
- TODO limit this to only continue when on the intended event_container.
+ @param pos_in
+ TODO
- (event → [0, 1)), 𝐑, bool → event → ()
+ @param round_to
+ what start and end times should round to when dragging, in fractionsb of
+ the width of the containing container.
+
+ @param wide_element
+ Does the element expect to grow horizontally (`true`) or vertically
+ (`false`).
+
+ 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),
@@ -48,13 +77,13 @@ class EventCreator {
): ((e: MouseEvent) => void) {
let that = this;
return function(this: HTMLElement, e: MouseEvent) {
- if (e.buttons != 1 || !that.down_on_event) return;
+ 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; }
+ 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;
@@ -112,7 +141,7 @@ class EventCreator {
(e as HTMLElement).style.pointerEvents = "none";
}
- that.timeStart = round_time(pos_in(this, e), round_to);
+ that.#time_start = round_time(pos_in(this, e), round_to);
}
let time = round_time(pos_in(this, e), round_to);
@@ -136,8 +165,8 @@ class EventCreator {
/* 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);
+ let start_in_duration = duration * Math.min(that.#time_start, time);
+ let end_in_duration = duration * Math.max(that.#time_start, 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
@@ -158,6 +187,12 @@ class EventCreator {
}
}
+ /**
+ Event handler for `mouseup` events.
+
+ TODO callback?
+ TODO return value?
+ */
create_event_finisher(callback: ((ev: VEvent) => void)) {
let that = this;
return function create_event_up(_: MouseEvent) {
diff --git a/static/ts/formatters.ts b/static/ts/formatters.ts
index 05f84d31..b5c55913 100644
--- a/static/ts/formatters.ts
+++ b/static/ts/formatters.ts
@@ -1,13 +1,13 @@
/**
* Formatting procedures used by some components.
*
- * // TODO can we have a backref of every node containing {@link formatters-proc}?
+ * // TODO can we have a backref of every node containing `{@link formatters-proc}`?
*
- * {@label formatters}
+ * {@label FORMATTERS}
*
* Each procedure takes three arguments. The HTML-element which contents
* should be replaced, the VEvent containing all data, and the target
- * value, as returned by {@link VEvent.getProperty}.
+ * value, as returned by {@linkcode vevent.VEvent.getProperty}.
*
* Also bound to the window object.
*
diff --git a/static/ts/globals.ts b/static/ts/globals.ts
index 75fb1df9..1cdf1733 100644
--- a/static/ts/globals.ts
+++ b/static/ts/globals.ts
@@ -76,6 +76,10 @@ window.addNewEvent = () => {
popup.maximize();
}
+/**
+ Find the calendar block in the inline view containing the VEvent identified
+ by the uid
+*/
function find_block(uid: uid): ComponentBlock | null {
let obj = vcal_objects.get(uid)
if (obj === undefined) {
diff --git a/static/ts/jcal.ts b/static/ts/jcal.ts
index 6a491e04..feac297b 100644
--- a/static/ts/jcal.ts
+++ b/static/ts/jcal.ts
@@ -1,3 +1,10 @@
+/**
+ Operations for working with jCal.
+
+ jCal is defined in RFC 7265, and is a JSON mapping of the iCalendar standard.
+*/
+
+
export { jcal_to_xcal }
import { xcal, ical_type, JCalProperty, JCal } from './types'
@@ -161,6 +168,17 @@ function jcal_property_to_xcal_property(
}
+/**
+ Convert a jCal document into an xCal document.
+
+ @param jcals A list of jcal components. Most iCal formats supports multiple
+ "root" levels components. jCal might do it, which is why this parameter is
+ multi-valued.
+
+ @return A document note which is the root of an xCal document.
+ The root will be an icalendar tag, with each child getting its data from each
+ element of the input.
+ */
function jcal_to_xcal(...jcals: JCal[]): Document {
let doc = document.implementation.createDocument(xcal, 'icalendar');
for (let jcal of jcals) {
@@ -169,6 +187,18 @@ function jcal_to_xcal(...jcals: JCal[]): Document {
return doc;
}
+/**
+ Convert a single jCal entry into a single xCal entry.
+
+ @param doc
+ A Document element in the xcal namespace.
+
+ @param jcal
+ The object to convert
+
+ @return
+ A 1-to-1 mapping of the jCal object as xCal.
+ */
function jcal_to_xcal_inner(doc: Document, jcal: JCal) {
let [tagname, properties, components] = jcal;
diff --git a/static/ts/lib.ts b/static/ts/lib.ts
index 14cdd4f2..d503ac5d 100644
--- a/static/ts/lib.ts
+++ b/static/ts/lib.ts
@@ -1,90 +1,16 @@
/**
- * General procedures which in theory could be used anywhere.
- *
- * Besides exporting the mentioned functions, this module also extends some base
- * classes.
- *
- * ```tex
-@node Default prototype extensions
-@subsubsection Default prototype extensions
-
-
-
-@defmethod HTMLElement addEventListener name proc
-Replace the default @code{addEventListener} with a version that stores
-all listeners in the dictionary @var{listeners}.
-@end defmethod
-
-@defivar HTMLElement listeners
-Dictionary of all registered listeners to this element.
-Keys are taken from @code{addEventListener}.
-@end defivar
-
-@defmethod DOMTokenList find regexp
-Finds the first element of the DOMTokenList whichs value matches
-the supplied regexp. Returns a pair of the index and the value.
-@end defmethod
-
-@defmethod Object format args ...
-Returns a string representation of the given object.
-Allows extending for custom types,
-@ref{date-format}
-@end defmethod
- * ```
- *
- * ---
- *
- * ```tex
-Some extensions to the builtin class ``Date'' is made.
-
-@defivar Date utc
-Boolean indicating if the given timestamp is in UTC or local time.
-true means UTC.
-@end defivar
+ General procedures which in theory could be used anywhere.
-@defivar Date dateonly
-Boolean indicating if the time component of the Date object should be disregarded.
-@end defivar
+ Besides exporting the mentioned functions, this module also
+ extends some base classes.
- * ```
- * ```tex
-@defmethod Date format str args ...
-@anchor{date-format}
-Formats a Date object according to the format specification @var{str}.
-Keeping with Guile each format specifier starts with a ~.
-
-@table @samp
-@item ~~
-literal ~
-@c Almost all fields are left padded. How do I signify this
-@c with a single footnote?
-@item ~Y
-year, left-padding with zeroes.
-@item ~m
-month number, left padded with zeroes.
-@item ~d
-day of month.
-@item ~H
-hour
-@item ~M
-minute
-@item ~S
-second
-@item ~Z
-'Z' if Date is UTC, otherwise nothing
-@item ~L
-Converts the date to local time
-(@pxref{to_local}) (doesn't modify source object). Outputs nothing
-@end table
-@end defmethod
-```
- *
- * @module
+ @module
*/
export {
makeElement, date_to_percent,
parseDate, gensym, to_local, to_boolean,
- asList, round_time
+ asList, round_time,
+ format_date,
}
/*
@@ -92,37 +18,95 @@ export {
*/
declare global {
interface Object {
+ /**
+ Introduce a format method on ALL objects
+
+ The default implementation simply stringifies the object, but this
+ allows any component to overwrite it, allowing for generic custom
+ formatting of data.
+
+ This also means that the format string is ignored for the default
+ implementation.
+
+ See `Data.prototype.format`.
+ */
format: (fmt: string) => string
}
/** HTMLElement extensions */
interface HTMLElement {
+ /**
+ "Private" property, storing the "true" add event listener. The
+ exposed addEventListener is later overwritten to also store a list of
+ which event listeners are added.
+ */
_addEventListener: (name: string, proc: (e: Event) => void) => void
- listeners: Map<string, ((e: Event) => void)[]>
+
+ /**
+ Contains all listeners added through `addEventListener`.
+
+ The keys are the same as to `addEventListener` ('load', 'mouseover',
+ ...)
+
+ Values are simply a list of all added listeners, probably in addition
+ order.
+ */
+ listeners: Map<string, ((e: Event) => void)[]>;
+
+ /**
+ Returns listeners.
+
+ TODO why does this exist?
+ */
getListeners: () => Map<string, ((e: Event) => void)[]>
}
interface Date {
+ /**
+ A proper date formatter for javascript.
+
+ See {@ref format_date} for details
+ */
format: (fmt: string) => string
+
+ /** Indicates if the date object is in UTC or local time. */
utc: boolean
+
+ /**
+ Indicates that the object only contains a date component.
+
+ This means that any time is ignored in most operations.
+ */
dateonly: boolean
// type: 'date' | 'date-time'
}
interface DOMTokenList {
+ /**
+ Searches a DOMTokenList for anything matching.
+
+ DOMTokenLists are returned by `.classList` and similar.
+
+ @return Returns the matching index, and the matched value,
+ or `undefined` if nothing was found.
+ */
find: (regex: string) => [number, string] | undefined
}
interface HTMLCollection {
+ /** Adds an iterator to HTMLCollections */
forEach: (proc: ((el: Element) => void)) => void
}
interface HTMLCollectionOf<T> {
+ /** Adds an iterator to HTMLCollections */
forEach: (proc: ((el: T) => void)) => void
}
}
+/** See interface above */
HTMLElement.prototype._addEventListener = HTMLElement.prototype.addEventListener;
+/** See interface above */
HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event) => void) {
if (!this.listeners) this.listeners = new Map
if (!this.listeners.get(name)) this.listeners.set(name, []);
@@ -130,6 +114,7 @@ HTMLElement.prototype.addEventListener = function(name: string, proc: (e: Event)
this.listeners.get(name)!.push(proc);
return this._addEventListener(name, proc);
};
+/** See interface above */
HTMLElement.prototype.getListeners = function() {
return this.listeners;
}
@@ -199,7 +184,6 @@ function parseDate(str: string): Date {
return date;
}
-/* @anchor{to_local} */
/**
* Returns a Date object (which may be new) which is guaranteed in local time.
* This means that the `utc` field is `false`, and that
@@ -245,7 +229,21 @@ function makeElement(name: string, attr = {}, actualAttr = {}): HTMLElement {
return element;
}
-/** TODO document me */
+/**
+ Round clock time to closest interval.
+
+ @param time
+ The desired clock-time, in decimal time. So 12:30 would be given as 12.30.
+
+ @param fraction
+ The time interval to round to. To round to nearest half hour, give 0.5.
+
+ @example
+ ```js
+ > round_time(10.1, 15/60)
+ 10
+ ```
+ */
function round_time(time: number, fraction: number): number {
let scale = 1 / fraction;
return Math.round(time * scale) / scale;
@@ -286,6 +284,16 @@ function asList<T>(thing: Array<T> | T): Array<T> {
}
+/**
+ Smartly converts a value into a boolean.
+
+ Booleans are returned as if,
+
+ Strings are parsed, mapping `'true'` onto `true`, `'false'` onto `false`,
+ empty strings onto `false`, and anything else onto `true`.
+
+ Anything else is left onto JavaScript to coerce a boolean.
+ */
function to_boolean(value: any): boolean {
switch (typeof value) {
case 'string':
@@ -309,13 +317,43 @@ function datepad(thing: number | string, width = 2): string {
return (thing + "").padStart(width, "0");
}
-/** Equivalent to `date.format(str)` */
-function format_date(date: Date, str: string): string {
+/**
+ Format a date into a string.
+
+ @param date
+ The datetime to format
+
+ @param format
+ How the date should be converted into a string.
+
+ The format is similar to `strftime`, but with tilde (`~`) characters
+ instead of percent signs, to match how Scheme does it. Valid format
+ specifiers are:
+
+ | Fmt | Output | Width¹ |
+ |------|----------------------------------|--------|
+ | `~~` | Literal '~' | |
+ | `~Y` | Year | 4 |
+ | `~m` | Month number | 2 |
+ | `~d` | Day of month | 2 |
+ | `~H` | Hour | 2 |
+ | `~M` | Minute | 2 |
+ | `~S` | Second | 2 |
+ | `~Z` | 'Z' if date is UTC, otherwise '' | |
+ | `~L` | Converts date to local time² | |
+
+ - ¹ These fields will be left padded with zeroes to that width
+ - ² This forces the output to be in local time, possibly converting
+ timezone if needed. It then outputs nothing.
+ See {@link to_local `to_local`} for details.
+
+*/
+function format_date(date: Date, format: string): string {
let fmtmode = false;
let outstr = "";
- for (var i = 0; i < str.length; i++) {
+ for (var i = 0; i < format.length; i++) {
if (fmtmode) {
- switch (str[i]) {
+ switch (format[i]) {
/* Moves the date into local time. */
case 'L': date = to_local(date); break;
case 'Y': outstr += datepad(date.getFullYear(), 4); break;
@@ -327,10 +365,10 @@ function format_date(date: Date, str: string): string {
case 'Z': if (date.utc) outstr += 'Z'; break;
}
fmtmode = false;
- } else if (str[i] == '~') {
+ } else if (format[i] == '~') {
fmtmode = true;
} else {
- outstr += str[i];
+ outstr += format[i];
}
}
return outstr;
diff --git a/static/ts/types.ts b/static/ts/types.ts
index a0ab74a4..a01f6672 100644
--- a/static/ts/types.ts
+++ b/static/ts/types.ts
@@ -30,7 +30,7 @@ let all_types = [
]
-/* The union of all elements in `all_types`. */
+/** The union of all elements in `all_types`. */
type ical_type
= 'text'
| 'uri'
@@ -104,6 +104,12 @@ let valid_fields: Map<string, string[]> = new Map([
valid_fields.set('DAYLIGHT', valid_fields.get('STANDARD')!);
+/**
+ All registered property types for VComponents.
+
+ Note that only some of these are valid for each type of component (VCALENDAR,
+ VEVENT, ...), and that they all support both iana and `x-` extensions.
+ */
type known_ical_types
= 'ACTION'
| 'ATTACH'
@@ -217,10 +223,7 @@ let valid_input_types: Map<string, Array<ical_type | ical_type[]>> =
// type JCalLine {
// }
-/** Alias of (`'vevent' | string`). */
-type tagname = 'vevent' | string
-
-/** Alias of `string`. */
+/** The UID of a VEvent, to make declarations clearer. */
type uid = string
/* TODO is this type correct?
@@ -240,17 +243,40 @@ type JCalProperty
| [string, Record<string, any>, ical_type, ...any[]]
/**
- * A record consisting of a `tagname`, a list of
- * `JCalProperties`, and a list of other `JCal` objects.
+ Base type for JCal documents.
+
+ Each VComponent in a JCal document is of this form.
+
+ - The first element is the components type
+ ('vevent', 'vcalendar', ...), in all lower case
+ - The second element is is all properties directly
+ on the component.
+ - The third element is a list of all children.
*/
-type JCal = [tagname, JCalProperty[], JCal[]]
+type JCal = [string, JCalProperty[], JCal[]]
/** The xml namespace name for xcalendar */
const xcal = "urn:ietf:params:xml:ns:icalendar-2.0";
+/**
+ An entry into a changelog.
+
+ This is primarily used by VEvent, to track what has happened during a
+ session.
+ */
interface ChangeLogEntry {
+ /**
+ Type of change
+
+ 'property' is used for changes to properties.
+
+ 'calendar' is used when the containing calendar of a VEVENT is changed
+ */
type: 'calendar' | 'property',
+ /** The name of the filed changed */
name: string,
+ /** The previous value, `null` if just created */
from: string | null,
+ /** The new value, `null` if removed */
to: string | null,
}
diff --git a/static/ts/vevent.ts b/static/ts/vevent.ts
index 07f25d02..6aaa6984 100644
--- a/static/ts/vevent.ts
+++ b/static/ts/vevent.ts
@@ -2,13 +2,20 @@ 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 */
interface Redrawable extends HTMLElement {
+ /** Method which will be called upon a redraw request. */
redraw(data: VEvent): void
}
@@ -17,14 +24,27 @@ 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) {
@@ -82,34 +102,46 @@ 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.
- */
/**
- * Component for a single instance of a calendar event. Almost all data
- * access should go through `getProperty` and `setProperty`,
- * with the exception of the current calendar (which is accessed directly
- * through `calendar`). Almost all changes through these interfaces
- * are logged, and can be viewed through `changelog`.
+ 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[]
@@ -122,13 +154,19 @@ class VEvent {
*/
#changelog: ChangeLogEntry[] = []
- /* Iterator instead of direct return to ensure the receiver doesn't
- modify the array */
- /** Public (read only) interface to changelog. */
+ /**
+ 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]
@@ -153,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[] = []
@@ -202,7 +251,7 @@ class VEvent {
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;
@@ -273,7 +322,7 @@ class VEvent {
* 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);
@@ -286,7 +335,7 @@ class VEvent {
*/
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);
@@ -307,6 +356,11 @@ 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;
}
@@ -354,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(
@@ -367,23 +422,46 @@ 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. */
diff --git a/static/tsconfig.json b/static/tsconfig.json
index 02d475f3..352c8ab5 100644
--- a/static/tsconfig.json
+++ b/static/tsconfig.json
@@ -3,18 +3,17 @@
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
/* Language and Environment */
- "target": "es2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. *//* Modules */
- "module": "CommonJS", /* Specify what module code is generated. *//* JavaScript Support */
- "allowJs": false, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. *//* Emit */
- // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ "target": "es2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ /* Modules */
+ "module": "CommonJS", /* Specify what module code is generated. */ /* JavaScript Support */
+ "allowJs": false, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ /* Emit */// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
"newLine": "lf", /* Set the newline character for emitting files. */
- "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. *//* Interop Constraints */
+ "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ /* Interop Constraints */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. *//* Type Checking */
- "strict": true, /* Enable all strict type-checking options. *//* Completeness */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */ /* Completeness */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
@@ -36,6 +35,9 @@
"@mxssfd/typedoc-theme"
],
"theme": "my-theme",
+ "validation": {
+ "notDocumented": true
+ },
"out": "docs"
}
} \ No newline at end of file