aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--module/calp/html/vcomponent.scm335
-rw-r--r--module/calp/html/view/calendar.scm64
-rw-r--r--module/calp/html/view/calendar/week.scm93
-rw-r--r--module/vcomponent/vdir/save-delete.scm2
-rw-r--r--static/globals.js257
-rw-r--r--static/script.js437
-rw-r--r--static/vcal.js2
7 files changed, 781 insertions, 409 deletions
diff --git a/module/calp/html/vcomponent.scm b/module/calp/html/vcomponent.scm
index 105c6cc5..4b3e9ec7 100644
--- a/module/calp/html/vcomponent.scm
+++ b/module/calp/html/vcomponent.scm
@@ -62,54 +62,56 @@
optional: (attributes '())
key: (fmt-header list))
;; (format (current-error-port) "fmt-single-event: ~a~%" (prop ev 'X-HNH-FILENAME))
- `(div (@ ,@(assq-merge
- attributes
- `((data-bindby "bind_view")
- (class " eventtext summary-tab "
- ,(when (and (prop ev 'PARTSTAT)
- (eq? 'TENTATIVE (prop ev 'PARTSTAT)))
- " tentative ")))))
- (h3 ,(fmt-header
- (when (prop ev 'RRULE)
- `(span (@ (class "repeating")) "↺"))
- `(span (@ (class "bind summary")
- (data-property "summary"))
- ,(prop ev 'SUMMARY))))
- (div
- ,(call-with-values (lambda () (fmt-time-span ev))
- (case-lambda [(start)
- `(div (time (@ (class "bind dtstart")
- (data-property "dtstart")
- (data-fmt ,(string-append "~L" start))
- (datetime ,(datetime->string
- (as-datetime (prop ev 'DTSTART))
- "~1T~3")))
- ,(datetime->string
- (as-datetime (prop ev 'DTSTART))
- start)))]
- [(start end)
- `(div (time (@ (class "bind dtstart")
- (data-property "dtstart")
- (data-fmt ,(string-append "~L" start))
- (datetime ,(datetime->string
- (as-datetime (prop ev 'DTSTART))
- "~1T~3")))
- ,(datetime->string (as-datetime (prop ev 'DTSTART))
- start))
- " — "
- (time (@ (class "bind dtend")
- (data-property "dtend")
- (data-fmt ,(string-append "~L" end))
- (datetime ,(datetime->string
- (as-datetime (prop ev 'DTSTART))
- "~1T~3")))
- ,(datetime->string (as-datetime (prop ev 'DTEND))
- end)))]))
-
- ;; TODO add optional fields when added in frontend
- ;; Possibly by always having them here, just hidden.
-
- (div (@ (class "fields"))
+ `(vevent-description
+ (@ ,@(assq-merge
+ attributes
+ `(
+ (class " vevent eventtext summary-tab "
+ ,(when (and (prop ev 'PARTSTAT)
+ (eq? 'TENTATIVE (prop ev 'PARTSTAT)))
+ " tentative "))
+ (data-uid ,(prop ev 'UID)))))
+ (h3 ,(fmt-header
+ (when (prop ev 'RRULE)
+ `(span (@ (class "repeating")) "↺"))
+ `(span (@ (class "bind summary")
+ (data-property "summary"))
+ ,(prop ev 'SUMMARY))))
+ (div
+ ,(call-with-values (lambda () (fmt-time-span ev))
+ (case-lambda [(start)
+ `(div (time (@ (class "bind dtstart")
+ (data-property "dtstart")
+ (data-fmt ,(string-append "~L" start))
+ (datetime ,(datetime->string
+ (as-datetime (prop ev 'DTSTART))
+ "~1T~3")))
+ ,(datetime->string
+ (as-datetime (prop ev 'DTSTART))
+ start)))]
+ [(start end)
+ `(div (time (@ (class "bind dtstart")
+ (data-property "dtstart")
+ (data-fmt ,(string-append "~L" start))
+ (datetime ,(datetime->string
+ (as-datetime (prop ev 'DTSTART))
+ "~1T~3")))
+ ,(datetime->string (as-datetime (prop ev 'DTSTART))
+ start))
+ " — "
+ (time (@ (class "bind dtend")
+ (data-property "dtend")
+ (data-fmt ,(string-append "~L" end))
+ (datetime ,(datetime->string
+ (as-datetime (prop ev 'DTSTART))
+ "~1T~3")))
+ ,(datetime->string (as-datetime (prop ev 'DTEND))
+ end)))]))
+
+ ;; TODO add optional fields when added in frontend
+ ;; Possibly by always having them here, just hidden.
+
+ (div (@ (class "fields"))
,(when (and=> (prop ev 'LOCATION) (negate string-null?))
`(div (b "Plats: ")
(div (@ (class "bind location") (data-property "location"))
@@ -118,7 +120,7 @@
,(awhen (prop ev 'DESCRIPTION)
`(div (@ (class "bind description")
(data-property "description"))
- ,(format-description ev it)))
+ ,(format-description ev it)))
,@(awhen (prop* ev 'ATTACH)
;; attach satisfies @code{vline?}
@@ -193,123 +195,127 @@
`(div (@ (class "last-modified")) "Senast ändrad "
,(datetime->string (prop ev 'LAST-MODIFIED) "~1 ~H:~M"))))
- )))
+ )))
(define*-public (fmt-for-edit ev
optional: (attributes '())
key: (fmt-header list))
- `(div (@ (class " eventtext edit-tab ")
- (data-bindby "bind_edit"))
- (form (@ (class "edit-form"))
- (div (@ (class "dropdown-goes-here")))
- (h3 (input (@ (type "text")
- (placeholder "Sammanfattning")
- (name "summary") (required)
- (class "bind") (data-property "summary")
- (value ,(prop ev 'SUMMARY)))))
-
- ,(let ((start (prop ev 'DTSTART))
- (end (prop ev 'DTEND)))
- `(div (@ (class "timeinput"))
-
- ,@(with-label
- "Starttid"
- `(div (@ (class "date-time bind")
- (data-bindby "bind_date_time")
- (name "dtstart"))
- (input (@ (type "date")
- (value ,(date->string (as-date start)))))
- (input (@ (type "time")
- (value ,(time->string (as-time start) "~H:~M"))
- ,@(when (date? start) '((disabled)))
- ))))
-
- ;; TODO some way to add an endtime if missing beforehand
- ;; TODO, actually proper support for event without end times
- ,@(when end
- (with-label
- "Sluttid"
- `(div (@ (class "date-time bind")
- (data-bindby "bind_date_time")
- (name "dtend"))
- (input (@ (type "date")
- (value ,(date->string (as-date end)))))
- (input (@ (type "time")
- (value ,(time->string (as-time end) "~H:~M"))
- ,@(when (date? end) '((disabled))))))))
-
- (div
- ,@(with-label
- "Heldag?"
- `(input (@ (type "checkbox")
- (class "bind")
- (data-bindby "bind_wholeday")
- (name "wholeday")
- ,@(when (date? start) '((checked)))))))
-
- ))
-
- ,@(with-label
- "Plats"
- `(input (@ (placeholder "Plats")
- (name "location")
- (type "text")
- (class "bind") (data-property "location")
- (value ,(or (prop ev 'LOCATION) "")))))
-
- ,@(with-label
- "Beskrivning"
- `(textarea (@ (placeholder "Beskrivning")
- (class "bind") (data-property "description")
- (name "description"))
- ,(prop ev 'DESCRIPTION)))
-
- ,@(with-label
- "Kategorier"
- ;; It would be better if these input-list's worked on the same
- ;; class=bind system as the fields above. The problem with that
- ;; is however that each input-list requires different search
- ;; and join procedures. Currently this is bound in the JS, see
- ;; [CATEGORIES_BIND].
- ;; It matches on ".input-list[data-property='categories']".
- `(div (@ (class "input-list")
- (data-property "categories"))
- ,@(awhen (prop ev 'CATEGORIES)
- (map (lambda (c)
- `(input (@ (size 2)
- (class "unit")
- (value ,c))))
- it))
-
- (input (@ (class "unit final")
- (size 2)
- (type "text")
+ `(vevent-edit (@ (class "vevent")
+ (data-uid ,(prop ev 'UID)))))
+
+(define-public (edit-template)
+ `(div (@ (class " eventtext edit-tab "))
+ (form (@ (class "edit-form"))
+ (div (@ (class "dropdown-goes-here")))
+ (h3 (input (@ (type "text")
+ (placeholder "Sammanfattning")
+ (name "summary") (required)
+ (class "bind") (data-property "summary")
+ ; (value ,(prop ev 'SUMMARY))
+ )))
+
+ (div (@ (class "timeinput"))
+
+ ,@(with-label
+ "Starttid"
+ `(div (@ (class "date-time")
+ (name "dtstart"))
+ (input (@ (type "date")
+ ; (value ,(date->string (as-date start)))
+ ))
+ (input (@ (type "time")
+ ; (value ,(time->string (as-time start) "~H:~M"))
+ ; ,@(when (date? start) '((disabled)))
+ ))))
+
+ ;; TODO some way to add an endtime if missing beforehand
+ ;; TODO, actually proper support for event without end times
+ ,@(with-label
+ "Sluttid"
+ `(div (@ (class "date-time")
+ (name "dtend"))
+ (input (@ (type "date")
+ ; (value ,(date->string (as-date end)))
+ ))
+ (input (@ (type "time")
+ ; (value ,(time->string (as-time end) "~H:~M"))
+ ; ,@(when (date? end) '((disabled)))
+ ))))
+
+ (div
+ ,@(with-label
+ "Heldag?"
+ `(input (@ (type "checkbox")
+ (name "wholeday")
+ ; ,@(when (date? start) '((checked)))
))))
- (hr)
-
- ;; For custom user fields
- ;; TODO these are currently not bound to anything, so entering data
- ;; here does nothing. Bigest hurdle to overcome is supporting arbitrary
- ;; fields which will come and go in the JavaScript.
- ;; TODO also, all (most? maybe not LAST-MODIFIED) remaining properties
- ;; should be exposed here.
- (div (@ (class "input-list"))
- (div (@ (class "unit final newfield"))
- (input (@ (type "text")
- (list "known-fields")
- (placeholder "Nytt fält")))
- (select (@ (name "TYPE"))
- (option (@ (value "TEXT")) "Text"))
- (span
- (input (@ (type "text")
- (placeholder "Värde"))))))
-
- (hr)
-
-
- (input (@ (type "submit")))
- )))
+ )
+
+ ,@(with-label
+ "Plats"
+ `(input (@ (placeholder "Plats")
+ (name "location")
+ (type "text")
+ (data-property "location")
+ ; (value ,(or (prop ev 'LOCATION) ""))
+ )))
+
+ ,@(with-label
+ "Beskrivning"
+ `(textarea (@ (placeholder "Beskrivning")
+ (data-property "description")
+ (name "description"))
+ ; ,(prop ev 'DESCRIPTION)
+ ))
+
+ ,@(with-label
+ "Kategorier"
+ ;; It would be better if these input-list's worked on the same
+ ;; class=bind system as the fields above. The problem with that
+ ;; is however that each input-list requires different search
+ ;; and join procedures. Currently this is bound in the JS, see
+ ;; [CATEGORIES_BIND].
+ ;; It matches on ".input-list[data-property='categories']".
+ `(div (@ (class "input-list")
+ (data-property "categories"))
+ #;
+ ,@(awhen (prop ev 'CATEGORIES)
+ (map (lambda (c)
+ `(input (@ (size 2)
+ (class "unit")
+ (value ,c))))
+ it))
+
+ (input (@ (class "unit final")
+ (size 2)
+ (type "text")
+ ))))
+
+ (hr)
+
+ ;; For custom user fields
+ ;; TODO these are currently not bound to anything, so entering data
+ ;; here does nothing. Bigest hurdle to overcome is supporting arbitrary
+ ;; fields which will come and go in the JavaScript.
+ ;; TODO also, all (most? maybe not LAST-MODIFIED) remaining properties
+ ;; should be exposed here.
+ (div (@ (class "input-list"))
+ (div (@ (class "unit final newfield"))
+ (input (@ (type "text")
+ (list "known-fields")
+ (placeholder "Nytt fält")))
+ (select (@ (name "TYPE"))
+ (option (@ (value "TEXT")) "Text"))
+ (span
+ (input (@ (type "text")
+ (placeholder "Värde"))))))
+
+ (hr)
+
+
+ (input (@ (type "submit")))
+ )))
;; Single event in side bar (text objects)
@@ -357,12 +363,12 @@
`((a (@ (href "#" ,(html-id ev))
(class "hidelink"))
- (div (@ ,@(assq-merge
+ (vevent-block (@ ,@(assq-merge
extra-attributes
`((id ,(html-id ev))
(data-calendar ,(html-attr (or (prop (parent ev) 'NAME) "unknown")))
;; (data-bindon "bind_view")
- (class "event CAL_" ,(html-attr (or (prop (parent ev) 'NAME)
+ (class "vevent event CAL_" ,(html-attr (or (prop (parent ev) 'NAME)
"unknown"))
,(when (and (prop ev 'PARTSTAT)
(eq? 'TENTATIVE (prop ev 'PARTSTAT)))
@@ -371,6 +377,7 @@
(eq? 'TRANSPARENT (prop ev 'TRANSP)))
" transparent")
)
+ (data-uid ,(prop ev 'UID))
(onclick "toggle_popup('popup' + this.id)")
)))
;; Inner div to prevent overflow. Previously "overflow: none"
@@ -391,10 +398,12 @@
,(when (and=> (prop ev 'DESCRIPTION) (negate string-null?))
`(span (@ (class "description"))
"🗎")))
+ #;
(div (@ (style "display:none !important;"))
,((@ (vcomponent xcal output) ns-wrap)
((@ (vcomponent xcal output) vcomponent->sxcal)
- ev)))))))
+ ev)))
+ ))))
(define (repeat-info event)
diff --git a/module/calp/html/view/calendar.scm b/module/calp/html/view/calendar.scm
index 4574f517..00451984 100644
--- a/module/calp/html/view/calendar.scm
+++ b/module/calp/html/view/calendar.scm
@@ -111,12 +111,16 @@
(script (@ (defer) (src "/static/clock.js")))
(script (@ (defer) (src "/static/popup.js")))
(script (@ (defer) (src "/static/rrule.js")))
- (script (@ (defer) (src "/static/binders.js")))
+ ;; (script (@ (defer) (src "/static/binders.js")))
(script (@ (defer) (src "/static/server_connect.js")))
- (script (@ (defer) (src "/static/input_list.js")))
+ ;; (script (@ (defer) (src "/static/input_list.js")))
(script (@ (defer) (src "/static/date_time.js")))
- (script (@ (defer) (src "/static/vcal.js")))
+ ;; (script (@ (defer) (src "/static/vcal.js")))
(script (@ (defer) (src "/static/script.js")))
+ (script (@ (defer) (src "/static/globals.js")))
+
+ ;; on load
+
,(calendar-styles calendars)
,@(when (debug)
@@ -251,15 +255,15 @@
`(li (@ (class "CAL_"
,(html-attr (prop calendar 'NAME))))
(a (@ (href "/search?"
- ,((@ (web uri-query) encode-query-parameters)
- `((q . (and (date/-time<=?
- ,(current-datetime)
- (prop event 'DTSTART))
- ;; TODO this seems to miss some calendars,
- ;; I belive it's due to some setting X-WR-CALNAME,
- ;; which is only transfered /sometimes/ into NAME.
- (string=? ,(->string (prop calendar 'NAME))
- (or (prop (parent event) 'NAME) ""))))))))
+ ,((@ (web uri-query) encode-query-parameters)
+ `((q . (and (date/-time<=?
+ ,(current-datetime)
+ (prop event 'DTSTART))
+ ;; TODO this seems to miss some calendars,
+ ;; I belive it's due to some setting X-WR-CALNAME,
+ ;; which is only transfered /sometimes/ into NAME.
+ (string=? ,(->string (prop calendar 'NAME))
+ (or (prop (parent event) 'NAME) ""))))))))
,(prop calendar 'NAME))))
calendars))
(div (@ (id "calendar-dropdown-template") (class "template"))
@@ -311,17 +315,19 @@
;; it.
description: ""))))
(event (car (children cal))))
- `((div (@ (class "template event-container") (id "event-template")
- ;; Only needed to create a duration. So actual dates
- ;; dosen't matter
- (data-start "2020-01-01")
- (data-end "2020-01-02"))
- ,(caddar ; strip <a> tag
- (make-block event `((class " generated ")))))
+ `(
+ ;; (div (@ (class "template event-container") (id "event-template")
+ ;; ;; Only needed to create a duration. So actual dates
+ ;; ;; dosen't matter
+ ;; (data-start "2020-01-01")
+ ;; (data-end "2020-01-02"))
+ ;; ,(caddar ; strip <a> tag
+ ;; (make-block event `((class " generated ")))))
;; TODO merge this into the event-set, add attribute
;; for non-displaying elements.
- (div (@ (class "template") (id "popup-template"))
- ,(popup event (string-append "popup" (html-id event))))))
+ ;; (div (@ (class "template") (id "popup-template"))
+ ;; ,(popup event (string-append "popup" (html-id event))))
+ ))
;; Auto-complets when adding new fields to a component
;; Any string is however still valid.
@@ -344,4 +350,18 @@
RDATE RRULE ACTION REPEAT
TRIGGER CREATED DTSTAMP LAST-MODIFIED
SEQUENCE REQUEST-STATUS
- ))))))
+ )))
+
+ (div (@ (style "display:none !important;")
+ (id "xcal-data"))
+ ,((@ (vcomponent xcal output) ns-wrap)
+ (map (@ (vcomponent xcal output) vcomponent->sxcal)
+ (stream->list
+ (filter-sorted-stream
+ (lambda (ev)
+ ((@ (vcomponent datetime) event-overlaps?)
+ ev start-date
+ (date+ end-date (date day: 1))))
+ events))))))
+
+ ))
diff --git a/module/calp/html/view/calendar/week.scm b/module/calp/html/view/calendar/week.scm
index 556c3d85..340db7d5 100644
--- a/module/calp/html/view/calendar/week.scm
+++ b/module/calp/html/view/calendar/week.scm
@@ -52,10 +52,95 @@
,@(for event in (stream->list
(events-between start-date end-date events))
- ((@ (calp html vcomponent ) popup) event (string-append "popup" (html-id event))))
-
- )))))
-
+ ((@ (calp html vcomponent ) popup)
+ event (string-append "popup" (html-id event))))
+
+ ))
+ ;; description in sidebar / tab of popup
+ (template (@ (id "vevent-description"))
+ ,(description-template)
+ )
+
+ ;; edit tab of popup
+ (template (@ (id "vevent-edit"))
+ ,((@ (calp html vcomponent) edit-template)))
+
+ ;; "physical" block
+ (template (@ (id "vevent-block"))
+ ,(block-template)
+ )
+
+ )))
+
+;; based on the output of fmt-single-event
+(define (description-template)
+ '(div (@ (class " eventtext summary-tab " ()))
+ (h3 ((span (@ (class "repeating")) "↺")
+ (span (@ (class "bind summary")
+ (data-property "summary"))
+ "Test")))
+ (div (div (time (@ (class "bind dtstart")
+ (data-property "dtstart")
+ (data-fmt "~L~H:~M")
+ (datetime "2021-09-29T19:56:46"))
+ "19:56")
+ "\xa0—\xa0"
+ (time (@ (class "bind dtend")
+ (data-property "dtend")
+ (data-fmt "~L~H:~M")
+ (datetime "2021-09-29T19:56:46"))
+ "20:56"))
+ (div (@ (class "fields"))
+ (div (b "Plats: ")
+ (div (@ (class "bind location")
+ (data-property "location"))
+ "Alsättersgatan 13"))
+ (div (@ (class "bind description")
+ (data-property "description"))
+ ("With a description"))
+ (div (@ (class "categories"))
+ (a (@ (class "category")
+ (href "/search/?"
+ "q=%28member%20%22test%22%20%28or%20%28prop%20event%20%28quote%20CATEGORIES%29%29%20%28quote%20%28%29%29%29%29"))
+ test))
+ (div (@ (class "rrule"))
+ "Upprepas "
+ "varje vecka"
+ ".")
+ (div (@ (class "last-modified"))
+ "Senast ändrad "
+ "2021-09-29 19:56")))))
+
+(define (block-template)
+ `(div (@ ; (id ,(html-id ev))
+ (data-calendar "unknown")
+ (class "event CAL_unknown"
+ ;; ,(when (and (prop ev 'PARTSTAT)
+ ;; (eq? 'TENTATIVE (prop ev 'PARTSTAT)))
+ ;; " tentative")
+ ;; ,(when (and (prop ev 'TRANSP)
+ ;; (eq? 'TRANSPARENT (prop ev 'TRANSP)))
+ ;; " transparent")
+ )
+ (onclick "toggle_popup('popup' + this.id)")
+ )
+ ;; Inner div to prevent overflow. Previously "overflow: none"
+ ;; was set on the surounding div, but the popup /needs/ to
+ ;; overflow (for the tabs?).
+ (div (@ (class "event-body"))
+ `(span (@ (class "repeating")) ; "↺"
+ )
+ (span (@ (class "bind summary")
+ (data-property "summary"))
+ ; ,(format-summary ev (prop ev 'SUMMARY))
+ )
+ `(span (@ (class "bind location")
+ (data-property "location")))
+ ;; Document symbol when we have text
+ `(span (@ (class "description"))
+ ; "🗎"
+ ))
+ ) )
(define (time-marker-div)
diff --git a/module/vcomponent/vdir/save-delete.scm b/module/vcomponent/vdir/save-delete.scm
index d17b595e..b3c7f9c5 100644
--- a/module/vcomponent/vdir/save-delete.scm
+++ b/module/vcomponent/vdir/save-delete.scm
@@ -11,7 +11,7 @@
(define-module (vcomponent vdir save-delete)
:use-module (calp util)
- :use-module ((calp util exceptions) :select (assert))
+ :use-module ((calp util exceptions) :select (assert))
:use-module (vcomponent ical output)
:use-module (vcomponent)
:use-module ((calp util io) :select (with-atomic-output-to-file))
diff --git a/static/globals.js b/static/globals.js
new file mode 100644
index 00000000..b881c531
--- /dev/null
+++ b/static/globals.js
@@ -0,0 +1,257 @@
+"use strict";
+
+class VEventValue {
+ constructor (type, value, parameters = {}) {
+ this.type = type;
+ this.value = value;
+ this.parameters = parameters;
+ }
+}
+
+/* maybe ... */
+class VEventDuration extends VEventValue {
+}
+
+class VEvent {
+ constructor (properties = {}, components = []) {
+ this.properties = properties;
+ this.components = components;
+ this.registered = [];
+ }
+
+ getProperty (key) {
+ let e = this.properties[key];
+ if (! e) return e;
+ return e.value;
+ }
+
+ setProperty (key, value) {
+ let e = this.properties[key];
+ if (! e) {
+ let type = (valid_input_types[key.toUpperCase()] || ['unknown'])[0]
+ if (typeof type === typeof []) type = type[0];
+ e = this.properties[key] = new VEventValue(type, value);
+ } else {
+ e.value = value;
+ }
+ for (let el of this.registered) {
+ el.redraw(this);
+ }
+ }
+
+ register (htmlNode) {
+ this.registered.push(htmlNode);
+ }
+}
+
+function make_vevent_value (value_tag) {
+ /* TODO parameters */
+ return new VEventValue (value_tag.tagName, make_vevent_value_ (value_tag));
+}
+
+function make_vevent_value_ (value_tag) {
+ /* RFC6321 3.6. */
+ switch (value_tag.tagName) {
+ case 'binary':
+ /* Base64 to binary
+ Seems to handle inline whitespace, which xCal standard reqires
+ */
+ return atob(value_tag.innerHTML)
+ break;
+
+ case 'boolean':
+ switch (value_tag.innerHTML) {
+ case 'true': return true;
+ case 'false': return false;
+ default:
+ console.warn(`Bad boolean ${value_tag.innerHTML}, defaulting with !!`)
+ return !! value_tag.innerHTML;
+ }
+ break;
+
+ case 'time':
+ case 'date':
+ case 'date-time':
+ return parseDate(value_tag.innerHTML);
+ break;
+
+ case 'duration':
+ /* TODO duration parser here 'P1D' */
+ return value_tag.innerHTML;
+ break;
+
+ case 'float':
+ case 'integer':
+ return +value_tag.innerHTML;
+ break;
+
+ case 'period':
+ /* TODO has sub components, meaning that a string wont do */
+ let start = value_tag.getElementsByTagName('start')[0]
+ parseDate(start.innerHTML);
+ let other;
+ if ((other = value_tag.getElementsByTagName('end')[0])) {
+ return parseDate(other.innerHTML)
+ } else if ((other = value_tag.getElementsByTagName('duration')[0])) {
+ /* TODO parse duration */
+ return other.innerHTML
+ } else {
+ console.warn('Invalid end to period, defaulting to 1H');
+ return new Date(3600);
+ }
+
+ case 'recur':
+ /* TODO parse */
+ return "";
+
+ case 'uc-offset':
+ /* TODO parse */
+ return "";
+
+ default:
+ console.warn(`Unknown type '${value_tag.tagName}', defaulting to string`)
+ case 'cal-address':
+ case 'uri':
+ case 'text':
+ return value_tag.innerHTML;
+ }
+}
+
+
+/* xml dom object -> class VEvent */
+function xml_to_vcal (xml) {
+ /* xml MUST have a VEVENT (or equivalent) as its root */
+ let properties = xml.getElementsByTagName('properties')[0];
+ let components = xml.getElementsByTagName('components')[0];
+
+ let property_map = {}
+ if (properties) {
+ for (var i = 0; i < properties.childElementCount; i++) {
+ let tag = properties.childNodes[i];
+ let parameters = {};
+ let value = [];
+ for (var j = 0; j < tag.childElementCount; j++) {
+ let child = tag.childNodes[j];
+ switch (tag.tagName) {
+ case 'parameters':
+ parameters = /* handle parameters */ {};
+ break;
+
+ /* These can contain multiple value tags, per
+ RFC6321 3.4.1.1. */
+ case 'categories':
+ case 'resources':
+ case 'freebusy':
+ case 'exdate':
+ case 'rdate':
+ value.push(make_vevent_value(child));
+ break;
+ default:
+ value = make_vevent_value(child);
+ }
+ }
+ property_map[tag.tagName] = value;
+ }
+ }
+
+ let component_list = []
+ if (components) {
+ for (let child of components.childNodes) {
+ component_list.push(xml_to_vcal(child))
+ }
+ }
+
+ return new VEvent(property_map, component_list)
+}
+
+const vcal_objects = {};
+
+class ComponentVEvent extends HTMLElement {
+ constructor () {
+ super ();
+ this.template = document.getElementById(this.tagName);
+
+ /* We DON'T have a redraw here in the general case, since the
+ HTML rendered server-side should be fine enough for us.
+ Those that need a direct rerendering (such as the edit tabs)
+ should take care of that some other way */
+ }
+
+ redraw (data) {
+ // update ourselves from template
+
+ if (! this.template) {
+ throw "Something";
+ }
+
+ let body = this.template.content.cloneNode(true).firstElementChild;
+
+ for (let el of body.getElementsByClassName("bind")) {
+ let p = el.dataset.property;
+ let d;
+ if ((d = data.getProperty(p))) {
+ /* TODO format */
+ el.innerHTML = d;
+ }
+ }
+
+ this.replaceChildren(body);
+ }
+
+}
+
+class ComponentDescription extends ComponentVEvent {
+ constructor () {
+ super() ;
+ }
+
+}
+
+class ComponentEdit extends ComponentVEvent {
+ constructor () {
+ super();
+
+ /* Edit tab is rendered here. It's left blank server-side, since
+ it only makes sense to have something here if we have javascript */
+ this.redraw(vcal_objects[this.dataset.uid]);
+ }
+}
+
+class ComponentBlock extends ComponentVEvent {
+ constructor () {
+ super();
+ }
+}
+
+window.addEventListener('load', function () {
+
+ // let json_objects_el = document.getElementById('json-objects');
+ let div = document.getElementById('xcal-data');
+ let vevents = div.firstElementChild.childNodes;
+
+ for (let vevent of vevents) {
+ let ev = xml_to_vcal(vevent);
+ vcal_objects[ev.getProperty('uid')] = ev
+ }
+
+ /*
+ - .popup
+ - .block
+ - .list
+ */
+ let vevent_els = document.getElementsByClassName('vevent')
+ for (let el of vevent_els) {
+ try {
+ vcal_objects[el.dataset.uid].register(el);
+ } catch {
+ console.error("Invalid something, uid = ", el.dataset.uid,
+ "el = ", el
+ );
+ }
+ }
+
+ customElements.define('vevent-description', ComponentDescription);
+ customElements.define('vevent-edit', ComponentEdit);
+ customElements.define('vevent-block', ComponentBlock);
+})
+
diff --git a/static/script.js b/static/script.js
index a0d58c27..05ae61c5 100644
--- a/static/script.js
+++ b/static/script.js
@@ -4,211 +4,211 @@
calp specific stuff
*/
-class EventCreator {
-
- /* dynamicly created event when dragging */
- constructor() {
- this.event = false;
- this.event_start = { x: NaN, y: NaN };
- this.down_on_event = false;
- }
-
- create_empty_event () {
- /* TODO this doesn't clone JS attributes */
-
- let event = document.getElementById("event-template")
- .firstChild.cloneNode(true);
- let popup = document.getElementById("popup-template")
- .firstChild.cloneNode(true);
-
- /* -------------------- */
- /* Manually transfer or recreate attributes which we still need */
- /* TODO shouldn't these use transferListeners (or similar)?
- See input_list.js:transferListeners */
-
- for (let dt of popup.getElementsByClassName("date-time")) {
- init_date_time_single(dt);
- }
-
- popup.getElementsByClassName("edit-form")[0].onsubmit = function () {
- create_event(event);
- return false; /* stop default */
- }
-
- /* -------------------- */
- /* Fix tabs for newly created popup */
-
- let id = gensym ("__js_event");
-
- // TODO remove button?
- // $("button 2??").onclick = `remove_event(document.getElementById('${id}'))`
-
- let tabgroup_id = gensym();
- for (let tab of popup.querySelectorAll(".tabgroup .tab")) {
- let new_id = gensym();
- let input = tab.querySelector("input");
- input.id = new_id;
- input.name = tabgroup_id;
- tab.querySelector("label").setAttribute('for', new_id);
- }
-
- let nav = popup.getElementsByClassName("popup-control")[0];
- bind_popup_control(nav);
-
- /* -------------------- */
-
- // TODO download links
-
- /* -------------------- */
-
- event.id = id;
- popup.id = "popup" + id;
-
- return [popup, event];
- }
-
- create_event_down (intended_target) {
- let that = this;
- return function (e) {
- /* Only trigger event creation stuff on actuall events background,
- NOT on its children */
- that.down_on_event = false;
- if (e.target != intended_target) return;
- that.down_on_event = true;
-
- that.event_start.x = e.clientX;
- that.event_start.y = e.clientY;
- }
- }
-
- /*
- round_to: what start and end times should round to when dragging, in fractions
- of the width of the containing container.
-
- TODO limit this to only continue when on the intended event_container.
-
- (event → [0, 1)), 𝐑, bool → event → ()
- */
- create_event_move(pos_in, round_to=1, wide_element=false) {
- let that = this;
- return function (e) {
- if (e.buttons != 1 || ! that.down_on_event) return;
-
- /* Create event when we start moving the mouse. */
- if (! that.event) {
- /* Small deadzone so tiny click and drags aren't registered */
- if (Math.abs(that.event_start.x - e.clientX) < 10
- && Math.abs(that.event_start.y - e.clientY) < 5)
- { return; }
-
- /* only allow start of dragging on background */
- if (e.target != this) return;
-
- /* only on left click */
- if (e.buttons != 1) return;
-
- let [popup, event] = that.create_empty_event();
- that.event = event;
-
- /* TODO better solution to add popup to DOM */
- document.getElementsByTagName("main")[0].append(popup);
-
- /* [0, 1) -- where are we in the container */
- /* Ronud to force steps of quarters */
- /* NOTE for in-day events a floor here work better, while for
- all day events I want a round, but which has the tip over point
- around 0.7 instead of 0.5.
- It might also be an idea to subtract a tiny bit from the short events
- mouse position, since I feel I always get to late starts.
- */
- let time = round_time(pos_in(this, e), round_to);
-
- event.dataset.time1 = time;
- event.dataset.time2 = time;
-
- /* ---------------------------------------- */
-
- this.appendChild(event);
-
- /* requires that event is child of an '.event-container'. */
- new VComponent(
- event,
- wide_element=wide_element);
- // bind_properties(event, wide_element);
-
- /* requires that dtstart and dtend properties are initialized */
-
- /* ---------------------------------------- */
-
- /* Makes all current events transparent when dragging over them.
- Without this weird stuff happens when moving over them
-
- This includes ourselves.
- */
- for (let e of this.children) {
- e.style.pointerEvents = "none";
- }
-
- }
-
- let time1 = Number(that.event.dataset.time1);
- let time2 = that.event.dataset.time2 =
- round_time(pos_in(that.event.parentElement, e),
- round_to);
-
- /* ---------------------------------------- */
-
- let event_container = that.event.closest(".event-container");
-
- /* These two are in UTC */
- let container_start = parseDate(event_container.dataset.start);
- let container_end = parseDate(event_container.dataset.end);
-
- /* ---------------------------------------- */
-
- /* ms */
- let duration = container_end - container_start;
-
- let start_in_duration = duration * Math.min(time1,time2);
- let end_in_duration = duration * Math.max(time1,time2);
-
- /* Notice that these are converted to UTC, since the intervals are given
- in utc, and I only really care about local time (which a specific local
- timezone doesn't give me)
- */
- /* TODO Should these inherit UTC from container_*? */
- let d1 = new Date(container_start.getTime() + start_in_duration)
- let d2 = new Date(container_start.getTime() + end_in_duration)
-
- // console.log(that.event);
- console.log(d1, d2);
- that.event.properties.dtstart = d1;
- that.event.properties.dtend = d2;
- }
- }
-
- create_event_finisher (callback) {
- let that = this;
- return function create_event_up (e) {
- if (! that.event) return;
-
- /* Restore pointer events for all existing events.
- Allow pointer events on our new event
- */
- for (let e of that.event.parentElement.children) {
- e.style.pointerEvents = "";
- }
-
- place_in_edit_mode(that.event);
-
- let localevent = that.event;
- that.event = null;
-
- callback (localevent);
-
- }
- }
-}
+// class EventCreator {
+//
+// /* dynamicly created event when dragging */
+// constructor() {
+// this.event = false;
+// this.event_start = { x: NaN, y: NaN };
+// this.down_on_event = false;
+// }
+//
+// create_empty_event () {
+// /* TODO this doesn't clone JS attributes */
+//
+// let event = document.getElementById("event-template")
+// .firstChild.cloneNode(true);
+// let popup = document.getElementById("popup-template")
+// .firstChild.cloneNode(true);
+//
+// /* -------------------- */
+// /* Manually transfer or recreate attributes which we still need */
+// /* TODO shouldn't these use transferListeners (or similar)?
+// See input_list.js:transferListeners */
+//
+// for (let dt of popup.getElementsByClassName("date-time")) {
+// init_date_time_single(dt);
+// }
+//
+// popup.getElementsByClassName("edit-form")[0].onsubmit = function () {
+// create_event(event);
+// return false; /* stop default */
+// }
+//
+// /* -------------------- */
+// /* Fix tabs for newly created popup */
+//
+// let id = gensym ("__js_event");
+//
+// // TODO remove button?
+// // $("button 2??").onclick = `remove_event(document.getElementById('${id}'))`
+//
+// let tabgroup_id = gensym();
+// for (let tab of popup.querySelectorAll(".tabgroup .tab")) {
+// let new_id = gensym();
+// let input = tab.querySelector("input");
+// input.id = new_id;
+// input.name = tabgroup_id;
+// tab.querySelector("label").setAttribute('for', new_id);
+// }
+//
+// let nav = popup.getElementsByClassName("popup-control")[0];
+// bind_popup_control(nav);
+//
+// /* -------------------- */
+//
+// // TODO download links
+//
+// /* -------------------- */
+//
+// event.id = id;
+// popup.id = "popup" + id;
+//
+// return [popup, event];
+// }
+//
+// create_event_down (intended_target) {
+// let that = this;
+// return function (e) {
+// /* Only trigger event creation stuff on actuall events background,
+// NOT on its children */
+// that.down_on_event = false;
+// if (e.target != intended_target) return;
+// that.down_on_event = true;
+//
+// that.event_start.x = e.clientX;
+// that.event_start.y = e.clientY;
+// }
+// }
+//
+// /*
+// round_to: what start and end times should round to when dragging, in fractions
+// of the width of the containing container.
+//
+// TODO limit this to only continue when on the intended event_container.
+//
+// (event → [0, 1)), 𝐑, bool → event → ()
+// */
+// create_event_move(pos_in, round_to=1, wide_element=false) {
+// let that = this;
+// return function (e) {
+// if (e.buttons != 1 || ! that.down_on_event) return;
+//
+// /* Create event when we start moving the mouse. */
+// if (! that.event) {
+// /* Small deadzone so tiny click and drags aren't registered */
+// if (Math.abs(that.event_start.x - e.clientX) < 10
+// && Math.abs(that.event_start.y - e.clientY) < 5)
+// { return; }
+//
+// /* only allow start of dragging on background */
+// if (e.target != this) return;
+//
+// /* only on left click */
+// if (e.buttons != 1) return;
+//
+// let [popup, event] = that.create_empty_event();
+// that.event = event;
+//
+// /* TODO better solution to add popup to DOM */
+// document.getElementsByTagName("main")[0].append(popup);
+//
+// /* [0, 1) -- where are we in the container */
+// /* Ronud to force steps of quarters */
+// /* NOTE for in-day events a floor here work better, while for
+// all day events I want a round, but which has the tip over point
+// around 0.7 instead of 0.5.
+// It might also be an idea to subtract a tiny bit from the short events
+// mouse position, since I feel I always get to late starts.
+// */
+// let time = round_time(pos_in(this, e), round_to);
+//
+// event.dataset.time1 = time;
+// event.dataset.time2 = time;
+//
+// /* ---------------------------------------- */
+//
+// this.appendChild(event);
+//
+// /* requires that event is child of an '.event-container'. */
+// new VComponent(
+// event,
+// wide_element=wide_element);
+// // bind_properties(event, wide_element);
+//
+// /* requires that dtstart and dtend properties are initialized */
+//
+// /* ---------------------------------------- */
+//
+// /* Makes all current events transparent when dragging over them.
+// Without this weird stuff happens when moving over them
+//
+// This includes ourselves.
+// */
+// for (let e of this.children) {
+// e.style.pointerEvents = "none";
+// }
+//
+// }
+//
+// let time1 = Number(that.event.dataset.time1);
+// let time2 = that.event.dataset.time2 =
+// round_time(pos_in(that.event.parentElement, e),
+// round_to);
+//
+// /* ---------------------------------------- */
+//
+// let event_container = that.event.closest(".event-container");
+//
+// /* These two are in UTC */
+// let container_start = parseDate(event_container.dataset.start);
+// let container_end = parseDate(event_container.dataset.end);
+//
+// /* ---------------------------------------- */
+//
+// /* ms */
+// let duration = container_end - container_start;
+//
+// let start_in_duration = duration * Math.min(time1,time2);
+// let end_in_duration = duration * Math.max(time1,time2);
+//
+// /* Notice that these are converted to UTC, since the intervals are given
+// in utc, and I only really care about local time (which a specific local
+// timezone doesn't give me)
+// */
+// /* TODO Should these inherit UTC from container_*? */
+// let d1 = new Date(container_start.getTime() + start_in_duration)
+// let d2 = new Date(container_start.getTime() + end_in_duration)
+//
+// // console.log(that.event);
+// console.log(d1, d2);
+// that.event.properties.dtstart = d1;
+// that.event.properties.dtend = d2;
+// }
+// }
+//
+// create_event_finisher (callback) {
+// let that = this;
+// return function create_event_up (e) {
+// if (! that.event) return;
+//
+// /* Restore pointer events for all existing events.
+// Allow pointer events on our new event
+// */
+// for (let e of that.event.parentElement.children) {
+// e.style.pointerEvents = "";
+// }
+//
+// place_in_edit_mode(that.event);
+//
+// let localevent = that.event;
+// that.event = null;
+//
+// callback (localevent);
+//
+// }
+// }
+// }
@@ -254,7 +254,7 @@ function place_in_edit_mode (event) {
-window.onload = function () {
+window.addEventListener('load', function () {
// let start_time = document.querySelector("meta[name='start-time']").content;
// let end_time = document.querySelector("meta[name='end-time']").content;
@@ -280,7 +280,7 @@ window.onload = function () {
init_date_time();
/* Is event creation active? */
- if (EDIT_MODE) {
+ if (false && EDIT_MODE) {
let eventCreator = new EventCreator;
for (let c of document.getElementsByClassName("events")) {
c.onmousedown = eventCreator.create_event_down(c);
@@ -336,17 +336,17 @@ window.onload = function () {
el.parentElement.removeAttribute("href");
let popup = document.getElementById("popup" + el.id);
- popup.getElementsByClassName("edit-form")[0].onsubmit = function () {
- create_event(el);
- return false; /* stop default */
- }
+ // popup.getElementsByClassName("edit-form")[0].onsubmit = function () {
+ // create_event(el);
+ // return false; /* stop default */
+ // }
/* Bind all vcomponent properties into javascript. */
- if (el.closest(".longevents")) {
- new VComponent(el, true);
- } else {
- new VComponent(el, false);
- }
+ // if (el.closest(".longevents")) {
+ // new VComponent(el, true);
+ // } else {
+ // new VComponent(el, false);
+ // }
}
@@ -402,7 +402,7 @@ window.onload = function () {
// init_arbitary_kv();
- init_input_list();
+ // init_input_list();
document.addEventListener('keydown', function (event) {
@@ -413,5 +413,4 @@ window.onload = function () {
event.preventDefault();
}
});
-}
-
+})
diff --git a/static/vcal.js b/static/vcal.js
index 93cfc028..f86c7f65 100644
--- a/static/vcal.js
+++ b/static/vcal.js
@@ -9,6 +9,8 @@
cdr a procedure which takes a slot and a value
and binds the value to the slot.
*/
+
+/* DEPRECATED */
class VComponent {
constructor(el, wide_event=false) {