rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1/*! Fiesta 1.2.0 | MPL-2.0 | https://fiesta.rastrillo.org */
2(function () {
3 "use strict";
4
5
6 var COMBINING = /[\u0300-\u036f]/g;
7
8 var LETTERISH = /[a-z\u00c0-\u024f\u0370-\u03ff\u0400-\u04ff\u0530-\u058f\u0590-\u05ff\u0600-\u06ff\u0900-\u097f\u0980-\u09ff\u0b80-\u0bff\u0e00-\u0e7f]/;
9 var BREAKS = /[\s,;\u060c\u061b\uff0c\u3001\u3002]/;
10
11 function pad(n) { return (n < 10 ? "0" : "") + n; }
12
13 function midnight(d) {
14 return new Date(d.getFullYear(), d.getMonth(), d.getDate());
15 }
16
17 function shiftDays(d, n) {
18 var x = midnight(d);
19 x.setDate(x.getDate() + n);
20 return x;
21 }
22
23 function comingWeekday(now, target) {
24 var delta = (target - now.getDay() + 7) % 7;
25 return shiftDays(now, delta === 0 ? 7 : delta);
26 }
27
28 function lastWeekday(now, target) {
29 var delta = (now.getDay() - target + 7) % 7;
30 return shiftDays(now, -(delta === 0 ? 7 : delta));
31 }
32
33 function inferYear(now, mo, d) {
34 var y = now.getFullYear();
35 return new Date(y, mo, d) < midnight(now) ? y + 1 : y;
36 }
37
38 function makeDate(y, mo, d, h, mi) {
39 if (y < 1000) return null;
40 if (mo < 0 || mo > 11 || d < 1 || d > 31) return null;
41 if (h < 0 || h > 23 || mi < 0 || mi > 59) return null;
42 var dt = new Date(y, mo, d, h, mi, 0, 0);
43 if (dt.getMonth() !== mo || dt.getDate() !== d) return null;
44 return dt;
45 }
46
47 var NUMBERING = ["arab", "arabext", "beng", "deva"];
48
49 function foldDigits(nf, map) {
50 var i, parts, k, digit, add = {}, differs = false;
51 for (i = 0; i <= 9; i++) {
52 parts = nf.formatToParts(i);
53 digit = "";
54 for (k = 0; k < parts.length; k++) if (parts[k].type === "integer") digit = parts[k].value;
55 if (digit.length !== 1) return false;
56 if (add[digit] !== undefined) return false;
57 if (map[digit] !== undefined && map[digit] !== String(i)) return false;
58 if (digit !== String(i)) differs = true;
59 add[digit] = String(i);
60 }
61 for (digit in add) if (Object.prototype.hasOwnProperty.call(add, digit)) map[digit] = add[digit];
62 return differs;
63 }
64
65 function digitFolder(locale) {
66 var map = {}, differs = false, base, root, i;
67 try {
68 base = new Intl.NumberFormat(locale, { useGrouping: false });
69 } catch (e) {
70 return null;
71 }
72 if (foldDigits(base, map)) differs = true;
73 try {
74 root = base.resolvedOptions().locale.split("-u-")[0];
75 } catch (e) {
76 root = "";
77 }
78 for (i = 0; root && i < NUMBERING.length; i++) {
79 try {
80 if (foldDigits(new Intl.NumberFormat(root + "-u-nu-" + NUMBERING[i], { useGrouping: false }), map)) differs = true;
81 } catch (e) {
82 }
83 }
84 if (!differs) return null;
85 return function (s) {
86 var out = "", j;
87 for (j = 0; j < s.length; j++) out += map[s.charAt(j)] !== undefined ? map[s.charAt(j)] : s.charAt(j);
88 return out;
89 };
90 }
91
92 function makeFold(digits) {
93 return function (s) {
94 var out = String(s === null || s === undefined ? "" : s);
95 if (out.normalize) out = out.normalize("NFD").replace(COMBINING, "");
96 out = out.toLowerCase();
97 if (digits) out = digits(out);
98 return out.replace(/\s+/g, " ").trim();
99 };
100 }
101
102 function nameTable(locale, type, dates, fold) {
103 var widths = ["long", "short"], out = [], i, c, w, opts, fmt, parts, k, value;
104 var contexts = type === "month" ? [null, "numeric"] : [null];
105 for (i = 0; i < dates.length; i++) out.push([]);
106 for (c = 0; c < contexts.length; c++)
107 for (w = 0; w < widths.length; w++) {
108 opts = { timeZone: "UTC" };
109 opts[type] = widths[w];
110 if (contexts[c]) opts.day = contexts[c];
111 try {
112 fmt = new Intl.DateTimeFormat(locale, opts);
113 } catch (e) {
114 continue;
115 }
116 for (i = 0; i < dates.length; i++) {
117 parts = fmt.formatToParts(dates[i]);
118 value = "";
119 for (k = 0; k < parts.length; k++) if (parts[k].type === type) value = parts[k].value;
120 value = fold(value);
121 if (!value) continue;
122 if (!/[^\d\s.,\/\-]/.test(value)) continue;
123 if (out[i].indexOf(value) === -1) out[i].push(value);
124 if (value.charAt(value.length - 1) === "." && value.length > 1) {
125 value = value.slice(0, -1);
126 if (out[i].indexOf(value) === -1) out[i].push(value);
127 }
128 }
129 }
130 return out;
131 }
132
133 function displayOptions(kind) {
134 var opts = kind === "time"
135 ? { hour: "numeric", minute: "2-digit" }
136 : { weekday: "short", day: "numeric", month: "short", year: "numeric" };
137 if (kind === "datetime") {
138 opts.hour = "numeric";
139 opts.minute = "2-digit";
140 }
141 return opts;
142 }
143
144 function displayTokens(locale, fold) {
145 var probes = [displayOptions("date"), displayOptions("datetime"), displayOptions("time"),
146 { day: "numeric", month: "long" }, { day: "numeric", month: "short" }];
147 var when = [new Date(Date.UTC(2026, 11, 25, 15, 30)), new Date(Date.UTC(2026, 2, 3, 9, 5))];
148 var out = { literal: {}, am: {}, pm: {} }, p, i, fmt, parts, k, v, c, run;
149 function keep(bag, s) { s = fold(s); if (s) bag[s] = true; }
150 for (p = 0; p < probes.length; p++) {
151 probes[p].timeZone = "UTC";
152 try {
153 fmt = new Intl.DateTimeFormat(locale, probes[p]);
154 } catch (e) {
155 continue;
156 }
157 for (i = 0; i < when.length; i++) {
158 parts = fmt.formatToParts(when[i]);
159 for (k = 0; k < parts.length; k++) {
160 v = parts[k].value;
161 if (parts[k].type === "dayPeriod") {
162 keep(when[i].getUTCHours() < 12 ? out.am : out.pm, v);
163 continue;
164 }
165 if (parts[k].type !== "literal") continue;
166 for (c = 0, run = ""; c <= v.length; c++) {
167 if (c < v.length && letterish(v.charAt(c))) { run += v.charAt(c); continue; }
168 keep(out.literal, run);
169 run = "";
170 if (c < v.length && !BREAKS.test(v.charAt(c))) keep(out.literal, v.charAt(c));
171 }
172 }
173 }
174 }
175 return out;
176 }
177
178 var TABLES = {};
179
180 function tables(locale) {
181 var key = locale || "";
182 if (TABLES[key]) return TABLES[key];
183 var loc = locale || undefined;
184 var fold = makeFold(digitFolder(loc));
185 var monthDates = [], weekDates = [], i;
186 for (i = 0; i < 12; i++) monthDates.push(new Date(Date.UTC(2026, i, 15)));
187 for (i = 0; i < 7; i++) weekDates.push(new Date(Date.UTC(2026, 1, 1 + i)));
188 var t = {
189 locale: loc,
190 fold: fold,
191 months: nameTable(loc, "month", monthDates, fold),
192 weekdays: nameTable(loc, "weekday", weekDates, fold),
193 display: displayTokens(loc, fold)
194 };
195 t.namedMonths = false;
196 for (i = 0; i < t.months.length; i++) if (t.months[i].length) t.namedMonths = true;
197 TABLES[key] = t;
198 return t;
199 }
200
201 function index(vocab, tbl) {
202 var list = [], name, alts, i, m, k;
203 for (name in vocab) {
204 if (!Object.prototype.hasOwnProperty.call(vocab, name)) continue;
205 alts = String(vocab[name]).split("|");
206 for (i = 0; i < alts.length; i++) {
207 var text = tbl.fold(alts[i]);
208 if (text) list.push({ text: text, role: "word", kind: name, at: -1 });
209 }
210 }
211 var seen = ["literal", "am", "pm"], kind;
212 for (i = 0; i < seen.length; i++) {
213 kind = seen[i] === "literal" ? "ignore" : seen[i];
214 for (name in tbl.display[seen[i]])
215 if (Object.prototype.hasOwnProperty.call(tbl.display[seen[i]], name))
216 list.push({ text: name, role: "word", kind: kind, at: -1 });
217 }
218 for (m = 0; m < tbl.months.length; m++)
219 for (k = 0; k < tbl.months[m].length; k++)
220 list.push({ text: tbl.months[m][k], role: "month", kind: "", at: m });
221 for (m = 0; m < tbl.weekdays.length; m++)
222 for (k = 0; k < tbl.weekdays[m].length; k++)
223 list.push({ text: tbl.weekdays[m][k], role: "weekday", kind: "", at: m });
224 list.sort(function (a, b) { return b.text.length - a.text.length; });
225 return list;
226 }
227
228 function indexFor(vocab, tbl) {
229 var key = "", name;
230 for (name in vocab) if (Object.prototype.hasOwnProperty.call(vocab, name)) key += name + "=" + vocab[name] + "\u0000";
231 if (tbl.indexKey !== key) {
232 tbl.index = index(vocab, tbl);
233 tbl.indexKey = key;
234 }
235 return tbl.index;
236 }
237
238 function letterish(c) {
239 return !!c && !BREAKS.test(c) && LETTERISH.test(c);
240 }
241
242 function boundaryOK(s, from, to) {
243 if (from > 0 && letterish(s.charAt(from - 1)) && letterish(s.charAt(from))) return false;
244 if (to < s.length && letterish(s.charAt(to)) && letterish(s.charAt(to - 1))) return false;
245 return true;
246 }
247
248 function matchAt(s, pos, idx) {
249 var best = -1, tok = null, i, e;
250 for (i = 0; i < idx.length; i++) {
251 e = idx[i];
252 if (best >= 0 && e.text.length < best) break;
253 if (s.slice(pos, pos + e.text.length) !== e.text) continue;
254 if (!boundaryOK(s, pos, pos + e.text.length)) continue;
255 if (best < 0) {
256 best = e.text.length;
257 tok = { t: "word", kinds: {}, month: -1, weekday: -1 };
258 }
259 if (e.role === "word") tok.kinds[e.kind] = true;
260 else if (e.role === "month") tok.month = e.at;
261 else tok.weekday = e.at;
262 }
263 return tok ? { tok: tok, len: best } : null;
264 }
265
266 function scan(s, idx) {
267 var toks = [], pos = 0, n = s.length, m, hit, start, glued = false;
268 function push(tok, from, to) {
269 tok.glued = glued;
270 tok.from = from;
271 tok.to = to;
272 toks.push(tok);
273 glued = true;
274 }
275 while (pos < n) {
276 if (BREAKS.test(s.charAt(pos))) { pos++; glued = false; continue; }
277 m = /^(\d{1,2}):(\d{2})/.exec(s.slice(pos));
278 if (m) {
279 push({ t: "clock", h: +m[1], mi: +m[2] }, pos, pos + m[0].length);
280 pos += m[0].length;
281 continue;
282 }
283 m = /^(\d{1,2})[\/.\-](\d{1,2})[\/.\-](\d{4})(?!\d)/.exec(s.slice(pos));
284 if (m) {
285 push({ t: "ndate", d: +m[1], mo: +m[2], y: +m[3] }, pos, pos + m[0].length);
286 pos += m[0].length;
287 continue;
288 }
289 hit = matchAt(s, pos, idx);
290 if (hit) { push(hit.tok, pos, pos + hit.len); pos += hit.len; continue; }
291 m = /^\d+/.exec(s.slice(pos));
292 if (m) {
293 push({ t: "num", n: +m[0], digits: m[0].length }, pos, pos + m[0].length);
294 pos += m[0].length;
295 continue;
296 }
297 start = pos;
298 while (pos < n && !BREAKS.test(s.charAt(pos)) && !/\d/.test(s.charAt(pos))) {
299 if (pos > start && matchAt(s, pos, idx)) break;
300 pos++;
301 }
302 if (pos === start) pos++;
303 push({ t: "other", raw: s.slice(start, pos) }, start, pos);
304 }
305 return toks;
306 }
307
308 function parse(text, vocab, tbl, now, ctx) {
309 ctx = ctx || {};
310 var raw = tbl.fold(text);
311 if (!raw) return null;
312 var idx = indexFor(vocab, tbl);
313
314 var prev = ctx.prev || null;
315 var base = ctx.base || null;
316 var anchor = midnight(base || prev || now);
317 var own = midnight(prev || base || now);
318 var clockH = prev ? prev.getHours() : 9;
319 var clockM = prev ? prev.getMinutes() : 0;
320
321 var m, d;
322
323 m = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[t ](\d{1,2}):(\d{2})(?::\d{2})?)?$/.exec(raw);
324 if (m) {
325 d = makeDate(+m[1], +m[2] - 1, +m[3],
326 m[4] === undefined ? clockH : +m[4],
327 m[4] === undefined ? clockM : +m[5]);
328 return d ? { date: d, hasDate: true, hasTime: m[4] !== undefined } : null;
329 }
330 m = /^(\d{4})[\/.](\d{1,2})[\/.](\d{1,2})$/.exec(raw);
331 if (m) {
332 d = makeDate(+m[1], +m[2] - 1, +m[3], clockH, clockM);
333 return d ? { date: d, hasDate: true, hasTime: false } : null;
334 }
335 function dmy(day, mon, year) {
336 if (mon > 12 && day <= 12) { var swap = day; day = mon; mon = swap; }
337 return makeDate(year === undefined ? inferYear(now, mon - 1, day) : year < 100 ? 2000 + year : year,
338 mon - 1, day, clockH, clockM);
339 }
340 m = /^(\d{1,2})[\/.\-](\d{1,2})(?:[\/.\-](\d{2}|\d{4}))?$/.exec(raw);
341 if (m) {
342 d = dmy(+m[1], +m[2], m[3] === undefined ? undefined : +m[3]);
343 return d ? { date: d, hasDate: true, hasTime: false } : null;
344 }
345
346 var toks = scan(raw, idx), i;
347 if (!toks.length) return null;
348 for (i = 0; i < toks.length; i++) if (toks[i].t === "other") return null;
349
350 var used = [];
351 function find(kind) {
352 for (var k = 0; k < toks.length; k++)
353 if (!used[k] && toks[k].t === "word" && toks[k].kinds[kind]) return k;
354 return -1;
355 }
356 function findRole(role) {
357 for (var k = 0; k < toks.length; k++) {
358 if (used[k] || toks[k].t !== "word" || toks[k][role] < 0) continue;
359 if (role === "weekday" && k > 0 && toks[k].glued && isCounter(k, k - 1)) continue;
360 return k;
361 }
362 return -1;
363 }
364 function findKind(t) {
365 for (var k = 0; k < toks.length; k++) if (!used[k] && toks[k].t === t) return k;
366 return -1;
367 }
368 function after(k) {
369 return k + 1 < toks.length && !used[k + 1] ? k + 1 : -1;
370 }
371 function before(k) {
372 return k > 0 && !used[k - 1] ? k - 1 : -1;
373 }
374 function unit() {
375 var names = ["minute", "hour", "day", "week", "month"], k, at;
376 for (k = 0; k < names.length; k++) {
377 at = find(names[k]);
378 if (at >= 0) return { name: names[k], at: at };
379 }
380 return null;
381 }
382 function isCounter(k, p) {
383 if (toks[p].t === "num") return true;
384 if (toks[p].t !== "word") return false;
385 if (!toks[p].kinds.next && !toks[p].kinds.last) return false;
386 return !!(toks[k].kinds.day || toks[k].kinds.week || toks[k].kinds.month);
387 }
388 function isWord(k, kind) {
389 return k >= 0 && toks[k].t === "word" && !!toks[k].kinds[kind];
390 }
391 function claimCounter(at, kind) {
392 var nx = at >= 0 ? after(at) : -1;
393 if (nx < 0 || toks[nx].t !== "word" || !toks[nx].glued) return false;
394 if (!toks[nx].kinds[kind]) return false;
395 used[nx] = true;
396 return true;
397 }
398 function counterMonth() {
399 if (tbl.namedMonths) return -1;
400 for (var k = 0; k < toks.length; k++) {
401 if (used[k] || toks[k].t !== "num" || toks[k].digits > 2) continue;
402 var nx = after(k);
403 if (nx >= 0 && toks[nx].t === "word" && toks[nx].glued && toks[nx].kinds.month) return k;
404 }
405 return -1;
406 }
407
408 var date = null, hasDate = false, hasTime = false, h = 0, mi = 0;
409
410 var quantified = findKind("num") >= 0;
411 var into = find("in"), back = find("ago");
412 if (into >= 0 && back >= 0 && into !== back) return null;
413 if (quantified && (into >= 0 || back >= 0)) {
414 var u = unit();
415 if (u) {
416 var qi = findKind("num");
417 var qty = toks[qi].n, sign = back >= 0 ? -1 : 1;
418 used[qi] = true;
419 used[u.at] = true;
420 if (into >= 0) used[into] = true;
421 if (back >= 0) used[back] = true;
422 if (u.name === "minute" || u.name === "hour") {
423 date = new Date(now.getTime() + sign * qty * (u.name === "hour" ? 3600000 : 60000));
424 hasDate = true;
425 hasTime = true;
426 h = date.getHours();
427 mi = date.getMinutes();
428 } else {
429 date = midnight(now);
430 if (u.name === "day") date.setDate(date.getDate() + sign * qty);
431 else if (u.name === "week") date.setDate(date.getDate() + sign * qty * 7);
432 else date.setMonth(date.getMonth() + sign * qty);
433 hasDate = true;
434 }
435 }
436 }
437
438 if (!hasTime) {
439 var noon = find("noon"), mid = find("midnight");
440 if (noon >= 0) { h = 12; mi = 0; hasTime = true; used[noon] = true; }
441 else if (mid >= 0) { h = 0; mi = 0; hasTime = true; used[mid] = true; }
442 else {
443 var ci = findKind("clock");
444 if (ci >= 0) {
445 h = toks[ci].h;
446 mi = toks[ci].mi;
447 used[ci] = true;
448 hasTime = true;
449 } else {
450 for (i = 0; i < toks.length && !hasTime; i++) {
451 if (used[i] || toks[i].t !== "num") continue;
452 var nx = after(i), pv = before(i);
453 if (isWord(nx, "am") || isWord(nx, "pm") || isWord(pv, "am") || isWord(pv, "pm")) {
454 h = toks[i].n;
455 mi = 0;
456 used[i] = true;
457 hasTime = true;
458 if (isWord(nx, "hour")) {
459 used[nx] = true;
460 var mnx = after(nx);
461 if (mnx >= 0 && toks[mnx].t === "num" && (after(mnx) < 0 || isWord(after(mnx), "minute"))) {
462 mi = toks[mnx].n;
463 used[mnx] = true;
464 if (after(mnx) >= 0) used[after(mnx)] = true;
465 }
466 }
467 } else if (isWord(nx, "hour")) {
468 h = toks[i].n;
469 mi = 0;
470 used[i] = true;
471 used[nx] = true;
472 hasTime = true;
473 var mn = after(nx);
474 if (mn >= 0 && toks[mn].t === "num" && (after(mn) < 0 || isWord(after(mn), "minute"))) {
475 mi = toks[mn].n;
476 used[mn] = true;
477 if (after(mn) >= 0) used[after(mn)] = true;
478 }
479 } else if (isWord(pv, "at") || isWord(nx, "at")) {
480 h = toks[i].n;
481 mi = 0;
482 used[i] = true;
483 used[isWord(pv, "at") ? pv : nx] = true;
484 hasTime = true;
485 }
486 }
487 }
488 if (hasTime) {
489 var pm = find("pm"), am = find("am");
490 if (pm >= 0 || am >= 0) {
491 if (h < 0 || h > 12) return null;
492 if (pm >= 0) { used[pm] = true; h = (h % 12) + 12; }
493 else { used[am] = true; h = h % 12; }
494 }
495 }
496 }
497 if (hasTime && (h > 23 || mi > 59)) return null;
498 }
499
500 var nd = findKind("ndate");
501 if (!hasDate && nd >= 0) {
502 used[nd] = true;
503 if (findRole("weekday") >= 0) used[findRole("weekday")] = true;
504 date = dmy(toks[nd].d, toks[nd].mo, toks[nd].y);
505 if (!date) return null;
506 hasDate = true;
507 }
508 if (!hasDate) {
509 var tod = find("today"), tom = find("tomorrow"), yes = find("yesterday");
510 if (tom >= 0) { date = shiftDays(now, 1); used[tom] = true; hasDate = true; }
511 else if (yes >= 0) { date = shiftDays(now, -1); used[yes] = true; hasDate = true; }
512 else if (tod >= 0) { date = midnight(now); used[tod] = true; hasDate = true; }
513 }
514 if (!hasDate) {
515 var next = find("next"), last = find("last"), dup;
516 var cm = counterMonth();
517 var wi = findRole("weekday");
518 var mo = findRole("month");
519 if (wi >= 0 && mo >= 0) {
520 if (wi === mo) {
521 for (dup = wi + 1; dup < toks.length; dup++)
522 if (!used[dup] && toks[dup].t === "word" && toks[dup].month >= 0) { mo = dup; break; }
523 if (mo !== wi) { used[wi] = true; wi = -1; }
524 else if (findKind("num") >= 0) wi = -1;
525 else mo = -1;
526 } else {
527 used[wi] = true;
528 wi = -1;
529 }
530 } else if (wi >= 0 && cm >= 0) {
531 used[wi] = true;
532 wi = -1;
533 }
534 if (wi >= 0) {
535 used[wi] = true;
536 if (last >= 0) { used[last] = true; date = lastWeekday(now, toks[wi].weekday); }
537 else {
538 if (next >= 0) used[next] = true;
539 date = comingWeekday(now, toks[wi].weekday);
540 }
541 hasDate = true;
542 } else {
543 if (mo >= 0) {
544 used[mo] = true;
545 var dnum = -1, ynum = -1, dat = -1, k;
546 for (k = 0; k < toks.length; k++) {
547 if (used[k] || toks[k].t !== "num") continue;
548 if (toks[k].digits === 4) {
549 if (ynum >= 0) break;
550 ynum = toks[k].n;
551 used[k] = true;
552 } else if (dnum < 0) {
553 dnum = toks[k].n;
554 dat = k;
555 used[k] = true;
556 } else if (ynum < 0) {
557 ynum = toks[k].n < 100 ? 2000 + toks[k].n : toks[k].n;
558 used[k] = true;
559 } else break;
560 }
561 if (dnum < 0) return null;
562 claimCounter(dat, "day");
563 date = makeDate(ynum >= 0 ? ynum : inferYear(now, toks[mo].month, dnum),
564 toks[mo].month, dnum, 0, 0);
565 if (!date) return null;
566 hasDate = true;
567 } else if (cm >= 0) {
568 var cmon = toks[cm].n - 1, cday = -1, cyear = -1, ck;
569 used[cm] = true;
570 used[cm + 1] = true;
571 ck = after(cm + 1);
572 if (ck >= 0 && toks[ck].t === "num" && toks[ck].digits <= 2) {
573 cday = toks[ck].n;
574 used[ck] = true;
575 claimCounter(ck, "day");
576 }
577 if (cday < 0) return null;
578 for (ck = 0; ck < toks.length && cyear < 0; ck++) {
579 if (used[ck] || toks[ck].t !== "num" || toks[ck].digits !== 4) continue;
580 cyear = toks[ck].n;
581 used[ck] = true;
582 }
583 date = makeDate(cyear >= 0 ? cyear : inferYear(now, cmon, cday), cmon, cday, 0, 0);
584 if (!date) return null;
585 hasDate = true;
586 } else if (next >= 0 || last >= 0) {
587 var u2 = unit();
588 if (u2 && (u2.name === "day" || u2.name === "week" || u2.name === "month")) {
589 used[u2.at] = true;
590 var dir = last >= 0 ? -1 : 1;
591 used[last >= 0 ? last : next] = true;
592 date = midnight(now);
593 if (u2.name === "day") date.setDate(date.getDate() + dir);
594 else if (u2.name === "week") date.setDate(date.getDate() + 7 * dir);
595 else date.setMonth(date.getMonth() + dir);
596 hasDate = true;
597 }
598 }
599 }
600 }
601 if (!hasDate) {
602 var only = -1, count = 0;
603 for (i = 0; i < toks.length; i++) {
604 if (used[i] || toks[i].t !== "num") continue;
605 count++;
606 if (toks[i].digits === 4) only = i;
607 }
608 if (count === 1 && only >= 0 && toks[only].n >= 1000) {
609 used[only] = true;
610 date = new Date(own.getFullYear(), own.getMonth(), own.getDate());
611 date.setFullYear(toks[only].n);
612 hasDate = true;
613 }
614 }
615
616 for (i = 0; i < toks.length; i++) {
617 if (used[i]) continue;
618 if (toks[i].t !== "word") return null;
619 if (toks[i].month >= 0 || toks[i].weekday >= 0) return null;
620 for (var kind in toks[i].kinds) if (kind !== "at" && kind !== "ignore") return null;
621 }
622 if (!hasDate && !hasTime) return null;
623
624 var on = hasDate ? date : anchor;
625 var out = makeDate(on.getFullYear(), on.getMonth(), on.getDate(),
626 hasTime ? h : clockH, hasTime ? mi : clockM);
627 return out ? { date: out, hasDate: hasDate, hasTime: hasTime } : null;
628 }
629
630
631 var MAX_COMPLETIONS = 6;
632
633 function completionsFor(run, idx) {
634 var out = [], seen = {}, i, e;
635 if (!run) return out;
636 for (i = 0; i < idx.length; i++) {
637 e = idx[i];
638 if (e.role === "word" && (e.kind === "ignore" || e.kind === "at")) continue;
639 if (e.text.length <= run.length) continue;
640 if (e.text.slice(0, run.length) !== run) continue;
641 if (seen[e.text]) continue;
642 seen[e.text] = true;
643 out.push(e.text);
644 }
645 return out;
646 }
647
648 function parseAll(text, vocab, tbl, now, ctx) {
649 var exact = parse(text, vocab, tbl, now, ctx);
650 if (exact) return [exact];
651 var raw = tbl.fold(text);
652 if (!raw) return [];
653 var idx = indexFor(vocab, tbl);
654 var toks = scan(raw, idx), at = -1, i;
655 for (i = 0; i < toks.length; i++) {
656 if (toks[i].t !== "other") continue;
657 if (at >= 0) return [];
658 at = i;
659 }
660 if (at < 0) return [];
661 var names = completionsFor(toks[at].raw, idx), out = [], seen = {}, read, key;
662 for (i = 0; i < names.length; i++) {
663 read = parse(raw.slice(0, toks[at].from) + " " + names[i] + " " + raw.slice(toks[at].to),
664 vocab, tbl, now, ctx);
665 if (!read) continue;
666 key = String(read.date.getTime()) + (read.hasTime ? "t" : "");
667 if (seen[key]) continue;
668 seen[key] = true;
669 out.push(read);
670 }
671 out.sort(function (a, b) { return a.date - b.date; });
672 return out.slice(0, MAX_COMPLETIONS);
673 }
674
675
676 var ENGLISH_WORDS = {
677 ago: "ago", am: "am|a.m.", at: "at", day: "day|days", hour: "hour|hours|h", in: "in",
678 last: "last", midnight: "midnight", minute: "minute|minutes|min|mins|m",
679 month: "month|months", next: "next", noon: "noon|midday", pm: "pm|p.m.",
680 today: "today", tomorrow: "tomorrow", week: "week|weeks", yesterday: "yesterday"
681 };
682
683 var NO_ATTRIBUTE = { getAttribute: function () { return null; } };
684
685 function vocabulary(native) {
686 var words = fiestaWord(native, "date-words", ENGLISH_WORDS);
687 if (typeof words === "string") {
688 try {
689 words = JSON.parse(words);
690 } catch (e) {
691 words = fiestaWord(NO_ATTRIBUTE, "date-words", ENGLISH_WORDS);
692 }
693 }
694 return words || ENGLISH_WORDS;
695 }
696
697 if (typeof document !== "undefined") {
698
699 var resetsPending = new WeakMap(), resetsAccepted = new WeakMap();
700 function tally(map, form) { return (form && map.get(form)) || 0; }
701 function count(map, form, by) { if (form) map.set(form, tally(map, form) + by); }
702 document.addEventListener("reset", function (e) {
703 var form = e.target;
704 count(resetsPending, form, 1);
705 setTimeout(function () {
706 count(resetsPending, form, -1);
707 if (!e.defaultPrevented) count(resetsAccepted, form, 1);
708 }, 0);
709 });
710
711 var CAL_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
712 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">' +
713 '<rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>';
714
715 var CLOCK_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
716 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">' +
717 '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>';
718
719 var seq = 0;
720
721 function toWire(kind, d) {
722 var day = d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
723 var clock = pad(d.getHours()) + ":" + pad(d.getMinutes());
724 if (kind === "date") return day;
725 if (kind === "time") return clock;
726 return day + "T" + clock;
727 }
728
729 function fromWire(kind, value, now) {
730 var s = String(value || ""), m;
731 if (kind === "time") {
732 m = /^(\d{2}):(\d{2})/.exec(s);
733 return m ? new Date(now.getFullYear(), now.getMonth(), now.getDate(), +m[1], +m[2]) : null;
734 }
735 m = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2}))?/.exec(s);
736 if (!m) return null;
737 return new Date(+m[1], +m[2] - 1, +m[3], m[4] ? +m[4] : 0, m[5] ? +m[5] : 0);
738 }
739
740 function formatter(locale, kind) {
741 try {
742 return new Intl.DateTimeFormat(locale, displayOptions(kind));
743 } catch (e) {
744 return null;
745 }
746 }
747
748 function canPick(el) { return !!el.showPicker; }
749
750 function enhance(native) {
751 if (native.dataset.rstEnhanced) return null;
752 native.dataset.rstEnhanced = "true";
753
754 var kind = native.type === "time" ? "time" : (native.type === "date" ? "date" : "datetime");
755 var locale = document.documentElement.getAttribute("lang") || undefined;
756 var tbl = tables(locale);
757 var fmt = formatter(locale, kind);
758 var vocab = vocabulary(native);
759
760 var id = native.id || "rst-dtp-" + ++seq;
761 var listId = id + "-listbox";
762 var setLabel = fiestaWord(native, "date-set", "Set");
763 var hintText = fiestaWord(native, "date-hint", "");
764 var pickLabel = fiestaWord(native, "date-pick", "Open the calendar");
765 var clockLabel = fiestaWord(native, "date-pick-time", "Open the clock");
766 var calLabel = fiestaWord(native, "date-calendar", "Calendar");
767 var prevLabel = fiestaWord(native, "date-prev-month", "Previous month");
768 var nextLabel = fiestaWord(native, "date-next-month", "Next month");
769 var manyFmt = fiestaWord(native, "date-results", "{n} suggestions");
770 var oneFmt = fiestaWord(native, "date-result-one", "1 suggestion");
771 var quick = {
772 today: fiestaWord(native, "date-quick-today", "Today"),
773 tomorrow: fiestaWord(native, "date-quick-tomorrow", "Tomorrow"),
774 nextWeek: fiestaWord(native, "date-quick-next-week", "In a week"),
775 plus1h: fiestaWord(native, "date-quick-plus-1h", "An hour later"),
776 plus2h: fiestaWord(native, "date-quick-plus-2h", "Two hours later"),
777 endOfDay: fiestaWord(native, "date-quick-end-of-day", "End of that day"),
778 nextDay: fiestaWord(native, "date-quick-next-day", "Same time next day")
779 };
780
781 var wrap = document.createElement("div");
782 wrap.setAttribute("rst-dtp", "");
783
784 var input = document.createElement("input");
785 input.type = "text";
786 input.setAttribute("rst-input", "");
787 input.setAttribute("rst-dtp-input", "");
788 input.id = id + "-combo";
789 input.autocomplete = "off";
790 input.spellcheck = false;
791 input.setAttribute("role", "combobox");
792 input.setAttribute("aria-expanded", "false");
793 input.setAttribute("aria-controls", listId);
794 input.setAttribute("aria-autocomplete", "list");
795
796 var list = document.createElement("ul");
797 list.setAttribute("rst-dtp-list", "");
798 list.id = listId;
799 list.hidden = true;
800 list.setAttribute("role", "listbox");
801
802 var status = document.createElement("p");
803 status.className = "rst-sr-only";
804 status.setAttribute("role", "status");
805 status.setAttribute("aria-live", "polite");
806
807 native.parentNode.insertBefore(wrap, native);
808 wrap.appendChild(input);
809 wrap.appendChild(list);
810 wrap.appendChild(status);
811 wrap.appendChild(native);
812
813 var label = native.id ? document.querySelector('label[for="' + native.id + '"]') : null;
814 if (label) label.setAttribute("for", input.id);
815 else if (native.getAttribute("aria-label")) input.setAttribute("aria-label", native.getAttribute("aria-label"));
816 if (native.getAttribute("aria-describedby"))
817 input.setAttribute("aria-describedby", native.getAttribute("aria-describedby"));
818
819 native.removeAttribute("rst-input");
820 native.classList.add("rst-sr-only");
821 native.setAttribute("tabindex", "-1");
822 native.setAttribute("aria-hidden", "true");
823
824 var rows = [];
825 var active = -1;
826 var baseOf = null;
827 var onCommit = null;
828 var isEnd = false;
829 var mine = false;
830
831 function current() { return fromWire(kind, native.value, new Date()); }
832 function show(d) { return fmt ? fmt.format(d) : toWire(kind, d); }
833 function paint() {
834 var d = current();
835 input.value = d ? show(d) : "";
836 }
837
838 function setActive(i) {
839 active = i;
840 for (var k = 0; k < rows.length; k++) rows[k].li.classList.toggle("is-active", k === i);
841 if (i >= 0) input.setAttribute("aria-activedescendant", rows[i].li.id);
842 else input.removeAttribute("aria-activedescendant");
843 }
844
845 function addRow(label, meta, date, hasTime, variant, extra) {
846 var li = document.createElement("li");
847 li.setAttribute("rst-dtp-row", variant || "");
848 if (extra) li.setAttribute(extra, "");
849 li.id = listId + "-" + rows.length;
850 li.setAttribute("role", "option");
851 li.setAttribute("aria-selected", toWire(kind, date) === native.value ? "true" : "false");
852 var main = document.createElement("span");
853 main.setAttribute("rst-dtp-label", "");
854 main.textContent = label;
855 li.appendChild(main);
856 if (meta) {
857 var m = document.createElement("span");
858 m.setAttribute("rst-dtp-set", "");
859 m.textContent = meta;
860 li.appendChild(m);
861 }
862 li.addEventListener("mousedown", function (e) {
863 e.preventDefault();
864 commit(date, hasTime);
865 });
866 list.appendChild(li);
867 rows.push({ li: li, date: date, hasTime: hasTime });
868 }
869
870 function picks(now) {
871 var start = baseOf ? baseOf() : null;
872 var out = [], at;
873 if (isEnd && start && kind !== "date") {
874 return [
875 { label: quick.plus1h, date: new Date(start.getTime() + 3600000) },
876 { label: quick.plus2h, date: new Date(start.getTime() + 7200000) },
877 { label: quick.endOfDay, date: new Date(start.getFullYear(), start.getMonth(), start.getDate(), 23, 59) },
878 { label: quick.nextDay, date: new Date(start.getTime() + 86400000) }
879 ];
880 }
881 if (kind === "time") {
882 var from = current() || now;
883 return [
884 { label: quick.plus1h, date: new Date(from.getTime() + 3600000) },
885 { label: quick.plus2h, date: new Date(from.getTime() + 7200000) },
886 { label: quick.endOfDay, date: new Date(from.getFullYear(), from.getMonth(), from.getDate(), 23, 59) }
887 ];
888 }
889 at = function (d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 9, 0); };
890 out.push({ label: quick.today, date: at(now) });
891 out.push({ label: quick.tomorrow, date: at(shiftDays(now, 1)) });
892 out.push({ label: quick.nextWeek, date: at(shiftDays(now, 7)) });
893 return out;
894 }
895
896 function ctx() {
897 return { prev: current(), base: baseOf ? baseOf() : null };
898 }
899
900 function rebuild(query) {
901 var now = new Date();
902 list.textContent = "";
903 rows = [];
904 var text = String(query || "").trim();
905 var reads = text ? parseAll(text, vocab, tbl, now, ctx()) : [];
906 var i, skip = {}, wire;
907 for (i = 0; i < reads.length; i++) {
908 addRow(show(reads[i].date), i === 0 ? setLabel : "", reads[i].date, reads[i].hasTime,
909 i === 0 ? "set" : "alt");
910 skip[toWire(kind, reads[i].date)] = true;
911 }
912 var quickPicks = picks(now);
913 for (i = 0; i < quickPicks.length; i++) {
914 wire = toWire(kind, quickPicks[i].date);
915 if (skip[wire]) continue;
916 addRow(quickPicks[i].label, show(quickPicks[i].date), quickPicks[i].date, kind !== "date", "", "rst-dtp-quick");
917 }
918 if (hintText) {
919 var hint = document.createElement("li");
920 hint.setAttribute("rst-dtp-hint", "");
921 hint.setAttribute("aria-hidden", "true");
922 hint.textContent = hintText.replace("{example}", show(new Date(now.getFullYear(), now.getMonth(), now.getDate(), 9, 0)));
923 list.appendChild(hint);
924 }
925 status.textContent = rows.length === 1 ? oneFmt : manyFmt.replace("{n}", String(rows.length));
926 setActive(rows.length && (reads.length || !text) ? 0 : -1);
927 }
928
929 function bounds() {
930 var when = new Date();
931 var lo = fromWire(kind, native.getAttribute("min"), when);
932 var hi = fromWire(kind, native.getAttribute("max"), when);
933 var start = baseOf ? baseOf() : null;
934 if (isEnd && start && (!lo || start > lo)) lo = midnight(start);
935 return { min: lo, max: hi };
936 }
937
938 function previewOf(text) {
939 var t = String(text || "").trim();
940 if (!t) return null;
941 var reads = parseAll(t, vocab, tbl, new Date(), ctx());
942 return reads.length ? reads[0].date : null;
943 }
944
945 var makeCal = (typeof window !== "undefined" && window.fiestaCalendar) || null;
946 var cal = null;
947
948 if (makeCal && kind !== "time") {
949 cal = makeCal({
950 id: id,
951 locale: locale,
952 labels: { calendar: calLabel, prev: prevLabel, next: nextLabel },
953 value: current,
954 bounds: bounds,
955 onPick: function (day) {
956 var had = current();
957 commit(new Date(day.getFullYear(), day.getMonth(), day.getDate(),
958 had ? had.getHours() : 9, had ? had.getMinutes() : 0), kind !== "date");
959 },
960 onDismiss: function () {
961 closeAll();
962 input.focus();
963 }
964 });
965 cal.el.id = id + "-cal";
966 wrap.appendChild(cal.el);
967 }
968
969 var pick = null;
970 var ownsPopup = !!cal || kind === "time";
971 function pickExpanded(open) {
972 if (pick && ownsPopup) pick.setAttribute("aria-expanded", open ? "true" : "false");
973 }
974 if (cal || kind === "time" || canPick(native)) {
975 pick = document.createElement("button");
976 pick.type = "button";
977 pick.setAttribute("rst-dtp-pick", "");
978 pick.setAttribute("aria-label", kind === "time" ? clockLabel : pickLabel);
979 if (ownsPopup) {
980 pick.setAttribute("aria-expanded", "false");
981 pick.setAttribute("aria-haspopup", cal ? "dialog" : "listbox");
982 }
983 pick.innerHTML = kind === "time" ? CLOCK_SVG : CAL_SVG;
984 wrap.insertBefore(pick, list);
985 }
986
987
988 var mode = "";
989
990 function closeAll() {
991 mode = "";
992 list.hidden = true;
993 active = -1;
994 input.setAttribute("aria-expanded", "false");
995 input.setAttribute("aria-controls", listId);
996 input.removeAttribute("aria-activedescendant");
997 if (cal) cal.close();
998 pickExpanded(false);
999 wrap.classList.remove("is-above");
1000 }
1001
1002 function place() {
1003 var panel = mode === "cal" && cal ? cal.el : list;
1004 wrap.classList.remove("is-above");
1005 if (!panel || panel.hidden) return;
1006 var box = wrap.getBoundingClientRect();
1007 var need = panel.getBoundingClientRect().height;
1008 if (need > window.innerHeight - box.bottom && box.top > need) {
1009 wrap.classList.add("is-above");
1010 }
1011 }
1012
1013 function openList() {
1014 if (locked()) return;
1015 if (cal) cal.close();
1016 mode = "list";
1017 list.hidden = false;
1018 input.setAttribute("aria-expanded", "true");
1019 input.setAttribute("aria-controls", listId);
1020 pickExpanded(false);
1021 rebuild(input.value);
1022 place();
1023 }
1024
1025 function openCal() {
1026 if (locked()) return;
1027 list.hidden = true;
1028 active = -1;
1029 input.removeAttribute("aria-activedescendant");
1030 mode = "cal";
1031 cal.open(previewOf(input.value));
1032 input.setAttribute("aria-expanded", "true");
1033 input.setAttribute("aria-controls", cal.el.id);
1034 pickExpanded(true);
1035 place();
1036 }
1037
1038 function openSlots() {
1039 if (locked()) return;
1040 var base = current() || new Date();
1041 var day = midnight(base);
1042 var want = base.getHours() * 60 + base.getMinutes();
1043 var i, at, best = -1, near = 0, gap;
1044 mode = "slots";
1045 list.hidden = false;
1046 list.textContent = "";
1047 rows = [];
1048 for (i = 0; i < 48; i++) {
1049 at = new Date(day.getFullYear(), day.getMonth(), day.getDate(),
1050 Math.floor(i / 2), (i % 2) * 30);
1051 gap = Math.abs(at.getHours() * 60 + at.getMinutes() - want);
1052 if (best < 0 || gap < best) { best = gap; near = rows.length; }
1053 addRow(show(at), "", at, true, "", "rst-dtp-quick");
1054 }
1055 input.setAttribute("aria-expanded", "true");
1056 input.setAttribute("aria-controls", listId);
1057 pickExpanded(true);
1058 status.textContent = manyFmt.replace("{n}", String(rows.length));
1059 setActive(near);
1060 place();
1061 if (rows[near]) {
1062 list.scrollTop = rows[near].li.offsetTop - Math.round(list.clientHeight / 2);
1063 }
1064 }
1065
1066 function locked() { return native.readOnly || native.matches(":disabled"); }
1067 function follow() {
1068 input.disabled = native.disabled;
1069 input.readOnly = native.readOnly;
1070 if (pick) pick.disabled = native.disabled || native.readOnly;
1071 if (locked()) closeAll();
1072 }
1073 new MutationObserver(follow).observe(native, { attributes: true, attributeFilter: ["disabled", "readonly"] });
1074 follow();
1075
1076 function commit(date, hasTime) {
1077 if (locked()) { paint(); closeAll(); return; }
1078 mine = true;
1079 native.value = toWire(kind, date);
1080 native.dispatchEvent(new Event("change", { bubbles: true }));
1081 mine = false;
1082 input.value = show(date);
1083 closeAll();
1084 if (onCommit) onCommit(date, hasTime);
1085 }
1086
1087 function commitText() {
1088 if (locked()) { paint(); return; }
1089 var text = input.value.trim();
1090 if (!text) {
1091 input.value = "";
1092 if (native.value) {
1093 mine = true;
1094 native.value = "";
1095 native.dispatchEvent(new Event("change", { bubbles: true }));
1096 mine = false;
1097 }
1098 return;
1099 }
1100 var reads = parseAll(text, vocab, tbl, new Date(), ctx());
1101 if (reads.length) commit(reads[0].date, reads[0].hasTime);
1102 else paint();
1103 }
1104
1105 input.addEventListener("focus", function () {
1106 input.select();
1107 if (!mode) openList();
1108 });
1109 input.addEventListener("click", function () {
1110 if (!mode) openList();
1111 });
1112 input.addEventListener("input", function () {
1113 if (mode === "cal" && cal) return cal.show(previewOf(input.value));
1114 openList();
1115 });
1116 input.addEventListener("keydown", function (e) {
1117 switch (e.key) {
1118 case "ArrowDown":
1119 e.preventDefault();
1120 if (mode === "cal" && cal) { cal.focusGrid(); break; }
1121 if (!mode) { openList(); break; }
1122 if (rows.length) setActive(Math.min(active + 1, rows.length - 1));
1123 break;
1124 case "ArrowUp":
1125 e.preventDefault();
1126 if (mode === "cal" && cal) { cal.focusGrid(); break; }
1127 if (!mode) { openList(); break; }
1128 if (rows.length) setActive(Math.max(active - 1, 0));
1129 break;
1130 case "Enter":
1131 e.preventDefault();
1132 if (mode !== "cal" && !list.hidden && active >= 0) commit(rows[active].date, rows[active].hasTime);
1133 else { commitText(); closeAll(); }
1134 break;
1135 case "Escape":
1136 e.preventDefault();
1137 paint();
1138 closeAll();
1139 break;
1140 }
1141 });
1142
1143 input.addEventListener("keypress", function (e) {
1144 if (e.key === "Enter") e.preventDefault();
1145 });
1146
1147 wrap.addEventListener("focusout", function () {
1148 var form = native.form, at = tally(resetsAccepted, form);
1149 var settle = function () {
1150 if (wrap.contains(document.activeElement)) return;
1151 if (tally(resetsPending, form) > 0) { setTimeout(settle, 0); return; }
1152 if (tally(resetsAccepted, form) !== at) return;
1153 commitText();
1154 closeAll();
1155 };
1156 setTimeout(settle, 0);
1157 });
1158
1159 if (pick) {
1160 pick.addEventListener("mousedown", function (e) { e.preventDefault(); });
1161 pick.addEventListener("click", function () {
1162 if (mode === "cal" || mode === "slots") {
1163 closeAll();
1164 input.focus();
1165 return;
1166 }
1167 input.focus();
1168 if (cal) return openCal();
1169 if (kind === "time") return openSlots();
1170 closeAll();
1171 try {
1172 native.showPicker();
1173 } catch (err) {
1174 }
1175 });
1176 }
1177
1178 native.addEventListener("change", function () {
1179 if (mine) return;
1180 if (mode && document.activeElement === input) return;
1181 paint();
1182 });
1183
1184 document.addEventListener("reset", function (e) {
1185 if (e.target !== native.form) return;
1186 setTimeout(function () {
1187 if (e.defaultPrevented) return;
1188 closeAll();
1189 paint();
1190 }, 0);
1191 });
1192
1193 paint();
1194
1195 return {
1196 get value() { return current(); },
1197 commit: commit,
1198 follow: function (fn) { baseOf = fn; isEnd = true; },
1199 committed: function (fn) { onCommit = fn; }
1200 };
1201 }
1202
1203 function wireRange(wrapper, start, end) {
1204 end.follow(function () { return start.value; });
1205 if (wrapper.getAttribute("data-rst-range") !== "session") return;
1206 start.committed(function (d) {
1207 var have = end.value;
1208 if (!have || have <= d) end.commit(new Date(d.getTime() + 3600000), true);
1209 });
1210 }
1211
1212 function scanPage(root) {
1213 var armed = root.querySelectorAll("input[data-rst-date], input[data-rst-time]");
1214 var made = [], i, k, wrappers, inside, pair;
1215 for (i = 0; i < armed.length; i++) made.push([armed[i], enhance(armed[i])]);
1216 wrappers = root.querySelectorAll("[data-rst-range]");
1217 for (i = 0; i < wrappers.length; i++) {
1218 inside = wrappers[i].querySelectorAll("input[data-rst-date], input[data-rst-time]");
1219 pair = [];
1220 for (k = 0; k < inside.length && pair.length < 2; k++) {
1221 for (var j = 0; j < made.length; j++)
1222 if (made[j][0] === inside[k] && made[j][1]) pair.push(made[j][1]);
1223 }
1224 if (pair.length === 2) wireRange(wrappers[i], pair[0], pair[1]);
1225 }
1226 }
1227
1228 if (document.readyState === "loading") {
1229 document.addEventListener("DOMContentLoaded", function () { scanPage(document); });
1230 } else {
1231 scanPage(document);
1232 }
1233 }
1234
1235
1236 function fiestaPickLang(tag, registry) {
1237 var want = String(tag || "").toLowerCase();
1238 if (!want) return null;
1239 var keys = Object.keys(registry || {}), i;
1240 for (i = 0; i < keys.length; i++) {
1241 if (keys[i].toLowerCase() === want) return keys[i];
1242 }
1243 var primary = want.split("-")[0];
1244 for (i = 0; i < keys.length; i++) {
1245 if (keys[i].toLowerCase().split("-")[0] === primary) return keys[i];
1246 }
1247 return null;
1248 }
1249
1250 function fiestaWord(el, key, fallback) {
1251 var v = el.getAttribute("data-rst-" + key);
1252 if (v !== null && v !== "") return v;
1253 var registry = (typeof self !== "undefined" && self.fiestaStrings) || {};
1254 var lang = fiestaPickLang(document.documentElement.lang, registry);
1255 var table = lang ? registry[lang] : null;
1256 return table && table[key] !== undefined && table[key] !== "" ? table[key] : fallback;
1257 }
1258
1259 if (typeof module !== "undefined" && module && module.exports) {
1260 module.exports = {
1261 parse: parse,
1262 parseAll: parseAll,
1263 tables: tables,
1264 displayOptions: displayOptions,
1265 vocabulary: vocabulary,
1266 fiestaPickLang: fiestaPickLang,
1267 fiestaWord: fiestaWord
1268 };
1269 }
1270})();
1271