(define-module (calp html view calendar) :use-module (hnh util) :use-module (vcomponent) :use-module ((vcomponent datetime) :select (ev-timesxcal)) ) ;;; Main-stuff ;;; NOTE ;;; The side bar filters all earlier events for each day to not create repeats, ;;; and the html-generate procedure also filters, but instead to find earlier eventns. ;;; All this filtering is probably slow, and should be looked into. ;; TODO place this somewhere proper (define repo-url (make-parameter "https://git.hornquist.se/calp")) ;; Mapping showing which events belongs to which calendar, ;; on the form ;; (calendar (@ (key ,(base64-encode calendar-name))) ;; (li ,event-uid) ...) (define (calendar-event-mapping events) `(div (@ (style "display:none !important;") (id "calendar-event-mapping")) ;; ,(for (calendar entries ...) in (group-by parent events) ;; `(calendar (@ (key ,(base64encode (prop calendar 'NAME)))) ;; ,@(map (lambda (uid) `(li ,uid)) ;; (map (extract 'UID) entries)))) ,(let ((ht (make-hash-table))) (for-each (lambda (event) (define name (prop (parent event) 'NAME)) (hash-set! ht name (cons (prop event 'UID) (hash-ref ht name '())))) events) (hash-map->list (lambda (key values) `(calendar (@ (key ,(base64encode key))) ,@(map (lambda (uid) `(li ,uid)) values))) ht)))) ;; TODO document what @var{render-calendar} is supposed to take and return. ;; Can at least note that @var{render-calendar} is strongly encouraged to include ;; (script "const VIEW='??';"), where ?? is replaced by the name of the view. (define*-public (html-generate key: (intervaltype 'all) ; 'week | 'month | 'all calendars ; All calendars to work on, probably (get-calendars global-event-object) events ; All events which can be worked on, probably (get-event-set global-event-object) start-date ; First date in interval to show end-date ; Last date in interval to show render-calendar ; (bunch of kv args) → (list sxml) next-start ; date → date prev-start ; date → date ;; The pre and post dates are if we want to show some dates just ;; outside our actuall interval. Primarily for whole month views, ;; which needs a bit on each side. (pre-start start-date) (post-end end-date)) ;; NOTE maybe don't do this again for every month (define evs (get-groups-between (group-stream events) start-date end-date)) (define (nav-link display date) `(a (@ (href ,(date->string date "~Y-~m-~d") ".html") (class "nav hidelink")) (div (@ (class "nav")) ,display))) (unless next-start (scm-error 'misc-error "html-generate" (_ "Next-start needs to be a procedure") #f #f)) (unless prev-start (scm-error 'misc-error "html-generate" (_ "Prev-start needs to be a procedure") #f #f)) (xhtml-doc (@ (lang sv)) (head (title "Calendar") (meta (@ (charset "utf-8"))) ;; (meta (@ (http-equiv "Content-Type") (content "application/xhtml+xml"))) (meta (@ (name viewport) (content "width=device-width, initial-scale=0.5"))) (meta (@ (name description) (content ,(format #f (_ "Calendar for the dates between ~a and ~a") (date->string start-date (_ "~Y-~m-~d")) (date->string end-date (_ "~Y-~m-~d")))))) ;; NOTE this is only for the time actually part of this calendar. ;; overflowing times from pre-start and post-end is currently ignored here. (meta (@ (name start-time) (content ,(date->string start-date "~s")))) (meta (@ (name end-time) (content ,(date->string (date+ end-date (date day: 1)) "~s")))) (script ,(lambda () (format #t " EDIT_MODE=~:[false~;true~]; window.default_calendar='~a';" (edit-mode) (base64encode ((@ (vcomponent) default-calendar)))))) (style ,(format #f "html { --editmode: 1.0; --event-font-size: 8pt; }")) ,(include-css "/static/style.css") ,(include-alt-css "/static/dark.css" '(title "Dark")) ,(include-alt-css "/static/light.css" '(title "Light")) (script (@ (src "/static/script.out.js"))) (script (@ (src "/static/user/user-additions.js"))) (style ,(lambda () (calendar-styles calendars #t))) ,@(when (debug) '((style ":root { --background-color: pink; }")))) (body (div (@ (class "root")) (main ;; Actuall calendar (@ (style "grid-area: main")) ,@(render-calendar calendars: calendars events: events start-date: start-date end-date: end-date pre-start: pre-start post-end: post-end next-start: next-start prev-start: prev-start ) ,(btn onclick: "addNewEvent()" "+") ;; Popups used to be here, but was moved into render-calendar so each ;; sub-view can itself decide where to put them. This is important ;; since they need to be placed as children to the scrolling ;; component, if one such component exists. ) ;; Page footer (footer (@ (style "grid-area: footer")) (span ,(_ "Page generated ") ,(date->string (current-date) (_ "~Y-~m-~d"))) (span ,(_ "Current time ") (current-time (@ (interval 1)))) (span (a (@ (href ,(repo-url))) ,(_ "Source Code")))) ;; Small calendar and navigation (nav (@ (class "calnav") (style "grid-area: nav")) (div (@ (class "change-view")) ,(btn href: (date->string (if (= 1 (day start-date)) (start-of-week start-date) start-date) "/week/~1.html") ;; Button to view week (_ "Week")) ,(btn href: (date->string (set (day start-date) 1) "/month/~1.html") ;; button to view month (_ "Month")) (today-button (a (@ (class "btn") (href ,(string-append "/today?" (case intervaltype [(month) "view=month"] [(week) "view=week"] [else ""])))) ;; Button to go to today ,(_ "Today")))) (date-jump ;; Firefox's accessability complain about each date ;; component, meaning that it's broken. This label ;; is for the whole input, which can be enabled ;; if wanted. ;; (label (@ (for "date")) "Hoppa till") (form (@ (action "/today")) (input (@ (type hidden) (name "view") (value ,(case intervaltype [(month week) => symbol->string] [else "month"])))) (input (@ (type date) (name "date") (value ,(date->string start-date "~1")))) ,(btn "➔")))) (details (@ (open) (style "grid-area: cal")) (summary ,(_ "Month overview")) (div (@ (class "smallcall-head")) ,(string-titlecase (date->string start-date ;; Header of small calendar (_ "~B ~Y")))) ;; NOTE it might be a good idea to put the navigation buttons ;; earlier in the DOM-tree/tag order. At least Vimium's ;; @key{[[} keybind sometimes finds parts of events instead. (div (@ (class "smallcal")) ;; prev button ,(nav-link "«" (prev-start start-date)) ;; calendar table (div ,(cal-table start-date: start-date end-date: end-date next-start: next-start prev-start: prev-start )) ;; next button ,(nav-link "»" (next-start start-date)))) (div (@ (style "grid-area: details")) ;; TODO Style this from as all other input forms in the sidebar. (form (@ (class "simplesearch") (action "/search/text")) (input (@ (type "text") (name "q") ;; Search placeholder (placeholder ,(_ "Search")))) (input (@ (type "submit") (value ">")))) ,(when (or (debug) (edit-mode)) `(details (@ (class "sliders")) (summary ,(_ "Option sliders")) ,@(when (edit-mode) `((label ,(_ "Event blankspace")) ,(slider-input variable: "editmode" min: 0 max: 1 step: 0.01 value: 1))) ,@(when (debug) `((label ,(_ "Fontsize")) ,(slider-input unit: "pt" min: 1 max: 20 step: 1 value: 8 variable: "event-font-size"))))) ;; List of calendars (details (@ (class "calendarlist")) (summary ,(_ "Calendar list")) (ul ,@(map (lambda (calendar) `(li (@ (data-calendar ,(base64encode (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) "")))))))) ,(prop calendar 'NAME)))) calendars)) ;; (div (@ (id "calendar-dropdown-template") (class "template")) ;; ) )) ;; List of events (div (@ (class "eventlist") (style "grid-area: events")) ;; Events which started before our start point, ;; but "spill" into our time span. (section (@ (class "text-day")) (header (h2 ,(_ "Earlier"))) ;; TODO this group gets styles applied incorrectly. ;; Figure out way to merge it with the below call. ,@(stream->list (stream-map (lambda (ev) (fmt-single-event ev `((id ,(html-id ev)) (data-calendar ,(base64encode (or (prop (parent ev) 'NAME) "unknown")))))) (stream-take-while (compose (cut date/-time start-date) (extract 'DTSTART)) (cdr (stream-car evs)))))) ,@(stream->list (stream-map fmt-day evs)))) ;; This would idealy be a