aboutsummaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
Diffstat (limited to 'static')
-rw-r--r--static/README.md36
-rw-r--r--static/package-lock.json34
-rw-r--r--static/package.json1
-rw-r--r--static/ts/clock.ts44
-rw-r--r--static/ts/components.ts9
-rw-r--r--static/ts/components/changelog.ts11
-rw-r--r--static/ts/components/date-time-input.ts32
-rw-r--r--static/ts/components/edit-rrule.ts12
-rw-r--r--static/ts/components/input-list.ts14
-rw-r--r--static/ts/components/popup-element.ts40
-rw-r--r--static/ts/components/tab-group-element.ts49
-rw-r--r--static/ts/components/vevent-block.ts11
-rw-r--r--static/ts/components/vevent-description.ts16
-rw-r--r--static/ts/components/vevent-dl.ts12
-rw-r--r--static/ts/components/vevent-edit.ts11
-rw-r--r--static/ts/components/vevent.ts27
-rw-r--r--static/ts/formatters.ts20
-rw-r--r--static/ts/globals.ts31
-rw-r--r--static/ts/jcal.ts4
-rw-r--r--static/ts/lib.ts136
-rw-r--r--static/ts/server_connect.ts22
-rw-r--r--static/ts/types.ts48
-rw-r--r--static/ts/vevent.ts61
-rw-r--r--static/tsconfig.json12
24 files changed, 662 insertions, 31 deletions
diff --git a/static/README.md b/static/README.md
index e69de29b..fe5f775c 100644
--- a/static/README.md
+++ b/static/README.md
@@ -0,0 +1,36 @@
+The frontend code has its entry-point in `script.ts`.
+
+## web components
+
+All elements are initialized in components.ts
+
+#### Boolean attributes
+Some components have properties/accessors which also appears as attributes on
+the actuall component (usually with a two-way maping).
+
+For boolean attributes, the attribute is either present or absent.
+
+### General Components
+- `components/date-time-input.ts`
+- `components/input-list.ts`
+
+### VEvent Components
+- `components/vevent.ts`
+- `components/changelog.ts`
+- `components/edit-rrule.ts`
+- `components/popup-element.ts`
+- `components/tab-group-element.ts`
+- `components/vevent-block.ts`
+- `components/vevent-description.ts`
+- `components/vevent-dl.ts`
+- `components/vevent-edit.ts`
+
+## About our buildsystem
+
+Currently (almost) everything is written in Typescript, and bundled
+through browserify. Ideally we would, for debug builds, export the
+single transplied Javascript files, but Chromium Chromium lacks
+support for modules on XHTML documents
+https://bugs.chromium.org/p/chromium/issues/detail?id=717643.
+However, seeing as the issue still gets frequent updates as of 2021 I
+believe that this might one day get resolved.
diff --git a/static/package-lock.json b/static/package-lock.json
index 36521446..c52c00e1 100644
--- a/static/package-lock.json
+++ b/static/package-lock.json
@@ -13,6 +13,7 @@
"uuid": "^8.3.2"
},
"devDependencies": {
+ "@mxssfd/typedoc-theme": "^1.1.2",
"@types/uuid": "^8.3.1"
},
"optionalDependencies": {
@@ -36,6 +37,18 @@
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
"integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug=="
},
+ "node_modules/@mxssfd/typedoc-theme": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@mxssfd/typedoc-theme/-/typedoc-theme-1.1.2.tgz",
+ "integrity": "sha512-Q/9Z+sff8ey92PaB7bnsGOfyNa85vTjyofO8EOH8KwEdfGnV4lGXwLFt4AUth7CCqYplqI9q4JxHNt869mlEcw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "typedoc": "^0.24.8"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1993,9 +2006,9 @@
}
},
"node_modules/typedoc": {
- "version": "0.24.6",
- "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.6.tgz",
- "integrity": "sha512-c3y3h45xJv3qYwKDAwU6Cl+26CjT0ZvblHzfHJ+SjQDM4p1mZxtgHky4lhmG0+nNarRht8kADfZlbspJWdZarQ==",
+ "version": "0.24.8",
+ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz",
+ "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==",
"dependencies": {
"lunr": "^2.3.9",
"marked": "^4.3.0",
@@ -2009,7 +2022,7 @@
"node": ">= 14.14"
},
"peerDependencies": {
- "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x"
+ "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x"
}
},
"node_modules/typedoc/node_modules/brace-expansion": {
@@ -2129,6 +2142,13 @@
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
"integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug=="
},
+ "@mxssfd/typedoc-theme": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@mxssfd/typedoc-theme/-/typedoc-theme-1.1.2.tgz",
+ "integrity": "sha512-Q/9Z+sff8ey92PaB7bnsGOfyNa85vTjyofO8EOH8KwEdfGnV4lGXwLFt4AUth7CCqYplqI9q4JxHNt869mlEcw==",
+ "dev": true,
+ "requires": {}
+ },
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3515,9 +3535,9 @@
}
},
"typedoc": {
- "version": "0.24.6",
- "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.6.tgz",
- "integrity": "sha512-c3y3h45xJv3qYwKDAwU6Cl+26CjT0ZvblHzfHJ+SjQDM4p1mZxtgHky4lhmG0+nNarRht8kADfZlbspJWdZarQ==",
+ "version": "0.24.8",
+ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz",
+ "integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==",
"requires": {
"lunr": "^2.3.9",
"marked": "^4.3.0",
diff --git a/static/package.json b/static/package.json
index 46f262f8..f376031d 100644
--- a/static/package.json
+++ b/static/package.json
@@ -3,6 +3,7 @@
"version": "0.1",
"private": true,
"devDependencies": {
+ "@mxssfd/typedoc-theme": "^1.1.2",
"@types/uuid": "^8.3.1"
},
"optionalDependencies": {
diff --git a/static/ts/clock.ts b/static/ts/clock.ts
index bbd15de0..a0e4670a 100644
--- a/static/ts/clock.ts
+++ b/static/ts/clock.ts
@@ -1,3 +1,18 @@
+/**
+ * Components for working with things which depend on the current time.
+ *
+ * Also introduces two web components:
+ *
+ * ```html
+ * <today-button />
+ * <current-time />
+ * ```
+ *
+ * TODO shouldn't these be defined with the rest of the components?
+ *
+ * @module
+ */
+
export {
SmallcalCellHighlight, Timebar,
initialize_clock_components
@@ -5,11 +20,17 @@ export {
import { makeElement, date_to_percent } from './lib'
-abstract class Clock {
+/**
+ * Interface for `things` which wants to get updated on a human timescale.
+ */
+export abstract class Clock {
+ /** Called every now and then
+ * @param now Called with the current time
+ */
abstract update(now: Date): void;
}
-
+/** The (blue) vertical line which show the current time in the current day. */
class Timebar extends Clock {
// start_time: Date
@@ -23,7 +44,6 @@ class Timebar extends Clock {
this.bar_object = null
}
-
update(now: Date) {
// if (! (this.start_time <= now.getTime() && now.getTime() < this.end_time))
// return;
@@ -46,11 +66,21 @@ class Timebar extends Clock {
}
}
+/**
+ * Highlights the current date in the small calendar to the side.
+ * Currently directly sets a border
+ *
+ * @TODO{but should preferably set a class instead}.
+*/
class SmallcalCellHighlight extends Clock {
small_cal: HTMLElement
current_cell: HTMLElement | null
+ /**
+ * @param small_cal the DOM-node of the calendar widget. It must support
+ * querySelector.
+ */
constructor(small_cal: HTMLElement) {
super();
this.small_cal = small_cal;
@@ -103,6 +133,14 @@ class ClockElement extends HTMLElement {
}
+/**
+ * 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.
+ */
class TodayButton extends ClockElement {
a: HTMLAnchorElement;
diff --git a/static/ts/components.ts b/static/ts/components.ts
index 132a9e29..cbd6dc9b 100644
--- a/static/ts/components.ts
+++ b/static/ts/components.ts
@@ -1,3 +1,12 @@
+/**
+ Actuall creation of all web components.
+
+ More text
+
+ @category Web Components
+ @module components
+ */
+
import { ComponentDescription } from './components/vevent-description'
import { ComponentEdit } from './components/vevent-edit'
import { VEventDL } from './components/vevent-dl'
diff --git a/static/ts/components/changelog.ts b/static/ts/components/changelog.ts
index d08f7cb3..720d1656 100644
--- a/static/ts/components/changelog.ts
+++ b/static/ts/components/changelog.ts
@@ -1,3 +1,14 @@
+/**
+ * `<changelog />`
+ *
+ * Display of a VEvents changelog. @ref{ChangeLogEntry}
+ *
+ * @privateRemarks @anchor{VEventChangelog}
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
import { makeElement } from '../lib'
import { ComponentVEvent } from './vevent'
import { VEvent } from '../vevent'
diff --git a/static/ts/components/date-time-input.ts b/static/ts/components/date-time-input.ts
index 20e9a505..d1ab5ba1 100644
--- a/static/ts/components/date-time-input.ts
+++ b/static/ts/components/date-time-input.ts
@@ -1,9 +1,30 @@
+/**
+ * `<date-time-input />`
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
+
export { DateTimeInput }
import { makeElement, parseDate } from '../lib'
-
-/* '<date-time-input />' */
+/**
+ * The HTML component `<date-time-input />`.
+ * An element for input for date-times. Similar to
+ * @example
+ * ```html
+ * <input type="date"/>
+ * <input type="time"/>
+ * ```
+ *
+ * But as a single unit.
+ *
+ * ### Attributes
+ * - dateonly
+ *
+ */
class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
readonly time: HTMLInputElement;
@@ -54,6 +75,11 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
}
}
+ /**
+ Setting this to true disabled the time part of the input, and makes
+ any output only have date components (alternativly, the time component
+ set to zero).
+ */
get dateonly(): boolean {
return this.hasAttribute('dateonly');
}
@@ -74,6 +100,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
this.dateonly = date.dateonly;
}
+ /** Returns current value as a Date object. */
get value(): Date {
let dt;
let date = this.date.value;
@@ -88,6 +115,7 @@ class DateTimeInput extends /* HTMLInputElement */ HTMLElement {
return dt;
}
+ /** Returns current value as an ISO-8601 formatted string. */
get stringValue(): string {
if (this.dateonly) {
return this.value.format("~Y-~m-~d")
diff --git a/static/ts/components/edit-rrule.ts b/static/ts/components/edit-rrule.ts
index a361bdee..b78171cc 100644
--- a/static/ts/components/edit-rrule.ts
+++ b/static/ts/components/edit-rrule.ts
@@ -1,3 +1,15 @@
+/**
+ * `<vevent-edit-rrule />`
+ *
+ * An edit form for a recurrence rule. Searches its template for elements
+ * with `[name="<<field name>>"]`, and binds to those.
+ *
+ * TODO rename this file
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { EditRRule }
import { ComponentVEvent } from './vevent'
diff --git a/static/ts/components/input-list.ts b/static/ts/components/input-list.ts
index 0afd4999..31dd5158 100644
--- a/static/ts/components/input-list.ts
+++ b/static/ts/components/input-list.ts
@@ -1,3 +1,13 @@
+/**
+ * `<input-list />`
+ *
+ * A list of identical input fields, which forms a group. For example
+ * useful to handle keywords.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { InputList }
/*
@@ -58,6 +68,10 @@ class InputList extends HTMLElement {
this.appendChild(new_el);
}
+ /**
+ * 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[] {
let value_list = []
for (let child of this.children) {
diff --git a/static/ts/components/popup-element.ts b/static/ts/components/popup-element.ts
index 458f543c..cc011ce3 100644
--- a/static/ts/components/popup-element.ts
+++ b/static/ts/components/popup-element.ts
@@ -1,3 +1,19 @@
+/**
+ * `<popup-element />`
+ *
+ * A (small) floating window containing information, which can be dragged
+ * arround. Consists of a navigation bar with a few buttons for
+ * controlling the window, which also works as a drag handle, along with
+ * an area for contents, which can be resized by the user.
+
+ * Currently tightly coupled to VEvent's, since their color
+ * profile is derived from their owning events calendar, and they have
+ * action buttons for the event in their navigation bar.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { PopupElement, setup_popup_element }
import { VEvent } from '../vevent'
@@ -7,12 +23,19 @@ import { ComponentVEvent } from './vevent'
import { remove_event } from '../server_connect'
-/* <popup-element /> */
+/**
+ ### Attributes
+ - visible
+ */
class PopupElement extends ComponentVEvent {
/* The popup which is the "selected" popup.
- /* Makes the popup last hovered over the selected popup, moving it to
+ * Makes the popup last hovered over the selected popup, moving it to
* the top, and allowing global keyboard bindings to affect it. */
+ /**
+ The popup which was most recently interacted with by the user. Used to
+ move it on top of all others, as well as sending relevant key events there.
+ */
static activePopup: PopupElement | null = null;
constructor(uid?: string) {
@@ -81,6 +104,12 @@ class PopupElement extends ComponentVEvent {
}
}
+ /**
+ If the popup is currently visible.
+
+ Adds the `visible` attribute to the component, which must then be handled
+ through CSS.
+ */
get visible(): boolean {
return this.hasAttribute('visible');
}
@@ -128,6 +157,10 @@ class PopupElement extends ComponentVEvent {
el.style.removeProperty('height');
}
+ /**
+ Resize the popup window to fill the current viewport (mostly). Is
+ probably bonud to the maximize button in the navigation bar.
+ */
maximize() {
/* TODO this assumes that popups are direct decendant of their parent,
which they really ought to be */
@@ -144,7 +177,8 @@ class PopupElement extends ComponentVEvent {
}
}
-/* Create a new popup element for the given VEvent, and ready it for editing the
+/**
+ Create a new popup element for the given VEvent, and ready it for editing the
event. Used when creating event (through the frontend).
The return value can safely be ignored.
*/
diff --git a/static/ts/components/tab-group-element.ts b/static/ts/components/tab-group-element.ts
index e90997e9..ce532cec 100644
--- a/static/ts/components/tab-group-element.ts
+++ b/static/ts/components/tab-group-element.ts
@@ -1,3 +1,40 @@
+/**
+ * `<tab-group />`
+
+A group of tabs, where only one can be visible at a time.
+
+@privateRemarks TODO which form does the HTML document have? For CSS purposes
+
+Each tab consists of two parts, a label which is used for selecting
+it, and a tab-element, which contains the actual content. These two
+should refer to each other as follows:
+
+@example
+```
++---------------+ +-----------------+
+| TabLabel | | Tab |
++---------------+ +-----------------+
+| id |<----| aria-labelledby |
+| aria-controls |---->| id |
++---------------+ +-----------------+
+```
+
+Further information about tabs in HTML can be found here:
+https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Tab_Role
+
+#### CSS Variables
+
+##### tabcount
+Each tab element has the style property `--tabcount` set to how
+many tabs it has. This is mostly useful to make sure the tab context
+is large enough to fit all tab labels without overflowing.
+
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
+
import { ComponentVEvent } from './vevent'
import { makeElement, gensym } from '../lib'
import { EditRRule } from './edit-rrule'
@@ -6,7 +43,7 @@ import { vcal_objects } from '../globals'
export { TabGroupElement }
-/* Lacks a template, since it's trivial
+/** Lacks a template, since it's trivial
The initial children of this element all becomes tabs, each child may have
the datapropertys 'label' and 'title' set, where label is what is shown in
the tab bar, and title is the hower text.
@@ -81,6 +118,12 @@ class TabGroupElement extends ComponentVEvent {
} /* end connectedCallback */
+ /**
+ Adds a new tab to the group. The first parameter will make up the body
+ of the tab. The label is whath should be shown in the tab selector,
+ but defaults to the first letter of the text content of the body node.
+ Title is the hoover text of the label.
+ */
addTab(child: HTMLElement, label?: string, title?: string) {
/* First character of text is a good a guess as any for our label,
@@ -128,6 +171,10 @@ class TabGroupElement extends ComponentVEvent {
this.style.setProperty('--tabcount', '' + this.tabs.length);
}
+ /**
+ HTMLElement must be one of the tab bodies in this group. This method
+ removes it, along with its TabLabel.
+ */
removeTab(tab: HTMLElement) {
let id = tab.getAttribute('aria-labelledby')!
let label = document.getElementById(id)
diff --git a/static/ts/components/vevent-block.ts b/static/ts/components/vevent-block.ts
index 9bbb8e7e..374cf103 100644
--- a/static/ts/components/vevent-block.ts
+++ b/static/ts/components/vevent-block.ts
@@ -1,3 +1,14 @@
+/**
+ * `<vevent-block />`
+ *
+ * A block in our graphical view.
+ *
+ * Unique in that it works quite differently between the week and month view.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { ComponentBlock }
import { ComponentVEvent } from './vevent'
diff --git a/static/ts/components/vevent-description.ts b/static/ts/components/vevent-description.ts
index b44185e7..bf62c10d 100644
--- a/static/ts/components/vevent-description.ts
+++ b/static/ts/components/vevent-description.ts
@@ -1,3 +1,19 @@
+/**
+ * `<vevent-description />`
+
+A text representation of a VEvent. Used as the summary tab of our
+popup windows, and in the sidebar.
+
+When redrawn, it looks for an HTML-tag inside its template having the
+attribute `data-property` matching the properties name. If one is
+found, it looks in the `formatters` table
+({@link formatters}), for a field matching the property value, and
+defaults to the key `default`.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { ComponentDescription }
import { VEvent } from '../vevent'
diff --git a/static/ts/components/vevent-dl.ts b/static/ts/components/vevent-dl.ts
index a792c07f..a4b51dd9 100644
--- a/static/ts/components/vevent-dl.ts
+++ b/static/ts/components/vevent-dl.ts
@@ -1,3 +1,15 @@
+/**
+ * `<vevent-dl />`
+ *
+ * A description list of a vevent, used for debugging.
+ *
+ * No guarantees are given about the contents of the data fields, more
+ * than that they are related to the value in question.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
export { VEventDL }
import { ComponentVEvent } from './vevent'
diff --git a/static/ts/components/vevent-edit.ts b/static/ts/components/vevent-edit.ts
index e3b5d105..5dd39ee9 100644
--- a/static/ts/components/vevent-edit.ts
+++ b/static/ts/components/vevent-edit.ts
@@ -1,3 +1,14 @@
+/**
+ * `<vevent-edit />`
+ *
+ * Edit form for a vevent, designed for useful human interaction (and
+ * thereby not being all-encompassing).
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
+
export { ComponentEdit }
import { ComponentVEvent } from './vevent'
diff --git a/static/ts/components/vevent.ts b/static/ts/components/vevent.ts
index 7487cbb6..1d400e1e 100644
--- a/static/ts/components/vevent.ts
+++ b/static/ts/components/vevent.ts
@@ -1,18 +1,34 @@
+/**
+ * Root component for all events which content is closely linked to a `VEvent` object
+ *
+ * Lacks an accompaning tag, and shouldn't be directly instanciated.
+ *
+ * Note that many of these assume that their initial children are
+ * configured specifically, that is however not completely documented.
+ *
+ * @category Web Components
+ * @mergeTarget components
+ * @module
+ */
+
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.
-*/
abstract class ComponentVEvent extends HTMLElement {
template?: HTMLTemplateElement
uid: string
+ /**
+ * This registeres itself, but doesn't redraw
+ * We do however redraw in connectedCallback
+
+ * @privateRemarks
+ * TODO what is done in the default constructor,
+ * and the default connectedCallback
+ */
constructor(uid?: string) {
super();
this.template = document.getElementById(this.tagName.toLowerCase()) as HTMLTemplateElement | undefined
@@ -64,6 +80,7 @@ abstract class ComponentVEvent extends HTMLElement {
}
}
+ /** While abstract for this, @emph{must} be overridden for everyone else */
abstract redraw(data: VEvent): void
}
diff --git a/static/ts/formatters.ts b/static/ts/formatters.ts
index e0018278..05f84d31 100644
--- a/static/ts/formatters.ts
+++ b/static/ts/formatters.ts
@@ -1,3 +1,19 @@
+/**
+ * Formatting procedures used by some components.
+ *
+ * // TODO can we have a backref of every node containing {@link formatters-proc}?
+ *
+ * {@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}.
+ *
+ * Also bound to the window object.
+ *
+ * @module
+ */
+
export {
format
}
@@ -16,6 +32,10 @@ declare global {
let formatters: Map<string, formatter>;
formatters = window.formatters = new Map();
+/**
+ * Checks if a specific formatter exists for the given key, and executes it.
+ * Defaults to 'default', and also runs that if the regular formatter throws.
+ */
async function format(targetElement: HTMLElement, data: VEvent, key: string): Promise<void> {
let d = data.getProperty(key);
if (!d) return
diff --git a/static/ts/globals.ts b/static/ts/globals.ts
index 243e15e4..75fb1df9 100644
--- a/static/ts/globals.ts
+++ b/static/ts/globals.ts
@@ -1,3 +1,10 @@
+/**
+ * Different variables and values which for different reasons needs to be
+ * global. Window Value's are those that are bound to the `window`
+ * context in JavaScript, so is really always available, no opt out.
+ * @module
+ */
+
export {
find_block,
vcal_objects, event_calendar_mapping
@@ -10,14 +17,38 @@ import { ComponentBlock } from './components/vevent-block'
import { v4 as uuid } from 'uuid'
import { setup_popup_element } from './components/popup-element'
+/**
+ * All VEvent objects on current page, indexed by their unique identifiers.
+ *
+ * A global object store.
+ *
+ * Also bound to the window object for easy access.
+ */
const vcal_objects: Map<uid, VEvent> = new Map;
+
+/**
+ * Mapping from VEvent unique identifier, to name of its calendar. Should
+ * probably not be global, so refrain from using it.
+ */
const event_calendar_mapping: Map<uid, string> = new Map;
declare global {
interface Window {
vcal_objects: Map<uid, VEvent>;
+ /**
+ * How the calendar is currently formatted. Should be set by the backend
+ * through a simple `script`-tag.
+ */
VIEW: 'month' | 'week';
+ /**
+ * However editing of events is enabled or not.
+ * Should be set by the backend through a simple `script`-tag.
+ */
EDIT_MODE: boolean;
+ /**
+ * Name of the calendar to assume when creating new events.
+ * Should be set by the backend through a simple `script`-tag.
+ */
default_calendar: string;
addNewEvent(): void;
diff --git a/static/ts/jcal.ts b/static/ts/jcal.ts
index 41f33db4..6a491e04 100644
--- a/static/ts/jcal.ts
+++ b/static/ts/jcal.ts
@@ -3,6 +3,10 @@ export { jcal_to_xcal }
import { xcal, ical_type, JCalProperty, JCal } from './types'
import { asList } from './lib'
+/**
+ * A document with the xcal namespace, and @code{icalendar} as its root
+ * element. Each child is a valid xcal representation of our JCal object.
+ */
function jcal_type_to_xcal(doc: Document, type: ical_type, value: any): Element {
let el = doc.createElementNS(xcal, type);
switch (type) {
diff --git a/static/ts/lib.ts b/static/ts/lib.ts
index 2ef5b596..14cdd4f2 100644
--- a/static/ts/lib.ts
+++ b/static/ts/lib.ts
@@ -1,3 +1,86 @@
+/**
+ * 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
+
+@defivar Date dateonly
+Boolean indicating if the time component of the Date object should be disregarded.
+@end defivar
+
+ * ```
+ * ```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
+ */
export {
makeElement, date_to_percent,
parseDate, gensym, to_local, to_boolean,
@@ -5,10 +88,6 @@ export {
}
/*
- General procedures which in theory could be used anywhere.
- */
-
-/*
* https://www.typescriptlang.org/docs/handbook/declaration-merging.html
*/
declare global {
@@ -16,6 +95,7 @@ declare global {
format: (fmt: string) => string
}
+ /** HTMLElement extensions */
interface HTMLElement {
_addEventListener: (name: string, proc: (e: Event) => void) => void
listeners: Map<string, ((e: Event) => void)[]>
@@ -70,6 +150,13 @@ HTMLElement.prototype.getListeners = function() {
century, due to how javascript works (...).
*/
+/**
+ * Takes a string `str`, which should be in ISO-8601 date-format, and
+ * returns a javascript Date object.
+ * Handles date-times, with and without seconds, trailing `Z' for
+ * time-zones, and dates without times.
+ * If no time is given the `dateonly` attribute is set to yes.
+ */
function parseDate(str: string): Date {
let year: number;
let month: number;
@@ -112,6 +199,12 @@ 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
+ * `to_local(current_time())` should show what your wall-clock shows.
+ */
function to_local(date: Date): Date {
if (!date.utc) return date;
@@ -120,6 +213,27 @@ function to_local(date: Date): Date {
/* -------------------------------------------------- */
+/**
+ * Creates a new DOM element of type `name`, with all keys in
+ * `attr` transfered to it. For example, the equivalent of
+
+ * ```html
+ * <input type='number'/>
+ * ```
+
+ * would be
+
+ * ```js
+ * values.push(makeElement('input', {
+ * type: 'number',
+ * }));
+ * ```
+ *
+ * @param name HTML tagname
+ * @param attr Attributes which will be set on the created element.
+ * @param actualAttr Attributes which will be set on the created element,
+ * but through `el.setAttribute` instead of `el[key] =`...
+ */
function makeElement(name: string, attr = {}, actualAttr = {}): HTMLElement {
let element: HTMLElement = document.createElement(name);
for (let [key, value] of Object.entries(attr)) {
@@ -131,6 +245,7 @@ function makeElement(name: string, attr = {}, actualAttr = {}): HTMLElement {
return element;
}
+/** TODO document me */
function round_time(time: number, fraction: number): number {
let scale = 1 / fraction;
return Math.round(time * scale) / scale;
@@ -142,14 +257,26 @@ function round_time(time: number, fraction: number): number {
Just doing (new Date()/(86400*1000)) would be nice, but there's no good
way to get the time in the current day.
*/
+/**
+ * Retuns how far along the date specified by `date` is, between 0
+ * and 100, where 00:00 maps to 0, and 23:59 to ~100.
+ */
function date_to_percent(date: Date): number /* in 0, 100 */ {
return (date.getHours() + (date.getMinutes() / 60)) * 100 / 24;
}
/* if only there was such a thing as a let to wrap around my lambda... */
/* js infix to not collide with stuff generated backend */
+/**
+ * Generates a new string which is (hopefully) globally unique.
+ * Compare with `gensym` from Lisp.
+ */
const gensym = (counter => (prefix = "gensym") => prefix + "js" + ++counter)(0)
+/**
+ * Ensures that `thing` is a list. Returning it outright if it
+ * already is one, otherwise wrapping it in a list.
+*/
function asList<T>(thing: Array<T> | T): Array<T> {
if (thing instanceof Array) {
return thing;
@@ -182,6 +309,7 @@ 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 {
let fmtmode = false;
let outstr = "";
diff --git a/static/ts/server_connect.ts b/static/ts/server_connect.ts
index 29f5bab2..ad4bc9eb 100644
--- a/static/ts/server_connect.ts
+++ b/static/ts/server_connect.ts
@@ -1,3 +1,9 @@
+/**
+ * Procedures for interfacing with the backend server.
+ *
+ * @module
+ */
+
export { create_event, remove_event }
import { jcal_to_xcal } from './jcal'
@@ -6,6 +12,15 @@ import { uid } from './types'
import { vcal_objects } from './globals'
import { PopupElement } from './components/popup-element'
+/**
+ * Requests that the server permanently remove the event with the given
+ * unique id from its persistant storage.
+ *
+ * If the server responds with a success also delete it from our local
+ * store (`vcal_objects`).
+ *
+ * // TODO link to our backend flow here
+*/
async function remove_event(uid: uid) {
let element = vcal_objects.get(uid);
if (!element) {
@@ -55,6 +70,13 @@ async function remove_event(uid: uid) {
// ];
// }
+/**
+ * Packs up the given event and sends it to the server to either be
+ * created, or simply be updated in the persistant database.
+
+ * Also does some minor updates registered components, to show that the
+ * event is actually created.
+*/
async function create_event(event: VEvent) {
// let xml = event.getElementsByTagName("icalendar")[0].outerHTML
diff --git a/static/ts/types.ts b/static/ts/types.ts
index 64e2c709..a0ab74a4 100644
--- a/static/ts/types.ts
+++ b/static/ts/types.ts
@@ -1,3 +1,8 @@
+/**
+ * Collection of type information for calendar data.
+ * @module
+ */
+
export {
ical_type,
valid_input_types,
@@ -6,6 +11,8 @@ export {
ChangeLogEntry
}
+
+/** Name of all valid icalendar types. */
let all_types = [
'text',
'uri',
@@ -23,6 +30,7 @@ let all_types = [
]
+/* The union of all elements in `all_types`. */
type ical_type
= 'text'
| 'uri'
@@ -39,6 +47,10 @@ type ical_type
| 'boolean'
| 'unknown'
+/**
+ * All known names properties (top level keys) can have.
+ * Such as "calscale", "dtstart", ...
+ */
let property_names = [
'calscale', 'method', 'prodid', 'version', 'attach', 'categories',
'class', 'comment', 'description', 'geo', 'location', 'percent-complete',
@@ -50,6 +62,15 @@ let property_names = [
];
+/**
+ * Which property fields each component can hold.
+ *
+ * ```json
+ * { 'VCALENDAR': ['PRODID', 'VERSION', 'CALSCALE', 'METHOD'],
+ * ...
+ * }
+ * ```
+ */
let valid_fields: Map<string, string[]> = new Map([
['VCALENDAR', ['PRODID', 'VERSION', 'CALSCALE', 'METHOD']],
['VEVENT', ['DTSTAMP', 'UID', 'DTSTART', 'CLASS', 'CREATED',
@@ -130,6 +151,18 @@ type known_ical_types
| 'URL'
| 'VERSION'
+/**
+ * Which types are valid to store under each property.
+ * If multiple values are an option for that property, then
+ * the list of possibilities will contain a sub-list (see example).
+ *
+ * ```json
+ * { 'DTSTART': ['date', 'date-time'],
+ * 'CATEGORIES': [['text']],
+ * ...
+ * }
+ * ```
+ */
let valid_input_types: Map<string, Array<ical_type | ical_type[]>> =
new Map([
['ACTION', ['text']], // AUDIO|DISPLAY|EMAIL|*other*
@@ -184,20 +217,35 @@ let valid_input_types: Map<string, Array<ical_type | ical_type[]>> =
// type JCalLine {
// }
+/** Alias of (`'vevent' | string`). */
type tagname = 'vevent' | string
+/** Alias of `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?
*/
+/* TODO link to RFC 7265 (jCal) */
+/**
+ * Alias for a record consisting of
+ * - the name of the type, as a string
+ * - all parameters of the object, as a `Record<String, any>`
+ * - An `ical_type` value, noting the type of the final field(s)
+ * - and one or more values of the type specified by the third field.
+ */
type JCalProperty
= [string, Record<string, any>, ical_type, any]
| [string, Record<string, any>, ical_type, ...any[]]
+/**
+ * A record consisting of a `tagname`, a list of
+ * `JCalProperties`, and a list of other `JCal` objects.
+*/
type JCal = [tagname, JCalProperty[], JCal[]]
+/** The xml namespace name for xcalendar */
const xcal = "urn:ietf:params:xml:ns:icalendar-2.0";
interface ChangeLogEntry {
diff --git a/static/ts/vevent.ts b/static/ts/vevent.ts
index f3606f70..07f25d02 100644
--- a/static/ts/vevent.ts
+++ b/static/ts/vevent.ts
@@ -7,11 +7,12 @@ export {
isRedrawable,
}
-/* Something which can be redrawn */
+/** Something which can be redrawn */
interface Redrawable extends HTMLElement {
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
}
@@ -32,6 +33,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;
@@ -88,6 +93,13 @@ type list_values
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`.
+ */
class VEvent {
/* Calendar properties */
@@ -103,10 +115,16 @@ class VEvent {
#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 */
+ /** Public (read only) interface to changelog. */
get changelog(): IterableIterator<[number, ChangeLogEntry]> {
return this.#changelog.entries();
}
@@ -156,6 +174,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,6 +197,7 @@ class VEvent {
return e.value;
}
+ /** Returns an iterator of all our properties. */
get boundProperties(): IterableIterator<string> {
return this.properties.keys()
}
@@ -228,6 +260,18 @@ 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);
@@ -236,6 +280,10 @@ class VEvent {
}
}
+ /**
+ * 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);
@@ -245,7 +293,7 @@ class VEvent {
}
}
-
+ /** The name of the calendar which this event belongs to. */
set calendar(calendar: string | null) {
this.addlog({
type: 'calendar',
@@ -263,14 +311,20 @@ class VEvent {
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);
@@ -332,6 +386,7 @@ class RecurrenceRule {
bysetpos?: number[]
wkst?: weekday
+ /** Converts ourselves to JCal data. */
to_jcal(): Record<string, any> {
let obj: any = {}
if (this.freq) obj['freq'] = this.freq;
@@ -368,6 +423,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 +563,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];
diff --git a/static/tsconfig.json b/static/tsconfig.json
index 69d8fe06..c421a586 100644
--- a/static/tsconfig.json
+++ b/static/tsconfig.json
@@ -34,9 +34,13 @@
"include": ["**/*"],
"exclude": ["node_modules", "**/*.spec.ts"],
"typedocOptions": {
- "entryPointStrategy": "expand",
- "entryPoints": ["./ts"],
- "exclude": ["node_modules"],
- "out": "docs"
+ "entryPointStrategy": "expand",
+ "entryPoints": ["./ts"],
+ "exclude": ["node_modules"],
+ "plugin": [
+ "@mxssfd/typedoc-theme"
+ ],
+ "theme": "my-theme",
+ "out": "docs"
}
}