oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

1// booking.js — the availability pane, and the zone preselect.
2//
3// Two enhancements, neither load-bearing. The pane asks the server what
4// the availability sentence means while the host types, and swaps the
5// answer in; it parses nothing, for the same reason quickadd.js parses
6// nothing — the server's parser is the one that decides what is saved.
7// The zone preselect picks the browser's zone on a public booking page
8// when nobody has chosen one yet. Without this file the form still
9// saves, the pane still fills in on the way back, and the page still
10// offers the host's zone.
11(function () {
12 "use strict";
13
14 var DEBOUNCE_MS = 220;
15
16 function setupPane(root) {
17 var text = root.querySelector("[cal-book-text]");
18 var pane = root.querySelector("[cal-book-pane]");
19 if (!text || !pane) return;
20
21 var timer = null;
22 var inflight = null;
23 var lastSent = null;
24
25 function ask() {
26 var value = text.value.trim();
27 if (value === lastSent) return;
28 lastSent = value;
29 if (inflight) inflight.abort();
30 var ctrl = new AbortController();
31 inflight = ctrl;
32
33 var body = new URLSearchParams();
34 body.set("text", value);
35 fetch("/settings/booking/parse", {
36 method: "POST",
37 credentials: "same-origin",
38 headers: { "Content-Type": "application/x-www-form-urlencoded" },
39 body: body.toString(),
40 signal: ctrl.signal
41 })
42 .then(function (res) {
43 if (!res.ok) throw new Error("parse " + res.status);
44 return res.text();
45 })
46 .then(function (html) {
47 if (ctrl !== inflight) return;
48 pane.innerHTML = html;
49 })
50 .catch(function (err) {
51 if (err && err.name === "AbortError") return;
52 pane.innerHTML = '<p cal-book-blank>Could not check that just now. Saving will still work.</p>';
53 });
54 }
55
56 text.addEventListener("input", function () {
57 if (timer) clearTimeout(timer);
58 timer = setTimeout(ask, DEBOUNCE_MS);
59 });
60 }
61
62 // The zone select on a public page: preselect the browser's zone, but
63 // only when it is one the page offers and nobody has chosen one yet
64 // (data-chosen is set when the URL carried a zone). A guess about a
65 // device, offered once; never a silent re-timing of a chosen value.
66 function setupZone() {
67 var select = document.querySelector("[cal-book-zone]");
68 if (!select || select.hasAttribute("data-chosen")) return;
69 var guess;
70 try {
71 guess = Intl.DateTimeFormat().resolvedOptions().timeZone;
72 } catch (e) {
73 return;
74 }
75 if (!guess) return;
76 if (select.value === guess) return;
77 for (var i = 0; i < select.options.length; i++) {
78 if (select.options[i].value === guess) {
79 // Reload THROUGH the form, so the server re-renders every time
80 // on the page in the guessed zone. Changing the select alone
81 // would relabel the selector while the times stayed in the
82 // host's zone — a page that says one zone and means another.
83 select.value = guess;
84 var form = select.form;
85 if (form && typeof form.requestSubmit === "function") form.requestSubmit();
86 else if (form) form.submit();
87 return;
88 }
89 }
90 }
91
92 function init() {
93 var forms = document.querySelectorAll("[cal-book-form]");
94 for (var i = 0; i < forms.length; i++) setupPane(forms[i]);
95 setupZone();
96 }
97
98 if (document.readyState === "loading") {
99 document.addEventListener("DOMContentLoaded", init);
100 } else {
101 init();
102 }
103})();
104