PageSourceSearch

https://jayreddinjr-jr-odysseus-hf.hf.space/static/js/calendar/utils.js

js jayreddinjr-jr-odysseus-hf.hf.space collected 2026-10-04 17:01:31 UTC 6,339 bytes, 166 lines download raw bytes

1// static/js/calendar/utils.js
2//
3// Pure constants + zero-state helpers for the calendar UI.
4// No DOM, no fetch, no global mutable state — safe to import anywhere.
5
6export const WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
7
8export const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June',
9  'July', 'August', 'September', 'October', 'November', 'December'];
10
11export const MON_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
12
13export const CAL_PALETTE = [
14  'var(--accent)', '#5b8abf', '#bf6b5b', '#5bbf7a', '#bf9a5b',
15  '#9a5bbf', '#5bbfb8', '#bf8a5b', '#7070c0', '#bf5b8a',
16];
17
18export const CAL_COLORS = [
19  { name: 'default', hex: '' },
20  // Pale/pastel palette — softer event tints.
21  { name: 'red',     hex: '#f0b5ba' },
22  { name: 'orange',  hex: '#e8ccb2' },
23  { name: 'yellow',  hex: '#f2dfbd' },
24  { name: 'green',   hex: '#cce0bc' },
25  { name: 'blue',    hex: '#b0d7f7' },
26  { name: 'purple',  hex: '#e2bcee' },
27  { name: 'teal',    hex: '#abdbe0' },
28  { name: 'pink',    hex: '#f0b5cc' },
29  // Custom — mirrors the notes color picker. Clicking opens a file picker
30  // and the chosen image URL is stored as a `bg:<url>` sentinel.
31  { name: 'custom',  hex: 'custom' },
32];
33
34export const _CAL_CUSTOM_GRADIENT = 'conic-gradient(from 0deg, #e06c75, #d19a66, #e5c07b, #98c379, #61afef, #c678dd, #e06c75)';
35
36// Per-event-type accent palette. Used by the colored dots in month/year
37// grids and the chip stripe behind agenda rows.
38export const _TYPE_PALETTE = {
39  '!':      '#e5a33a',  // important — amber, less harsh than red
40  work:     '#5b8abf',
41  personal: '#a07ae0',
42  health:   '#e06c75',
43  travel:   '#e5a33a',
44  meal:     '#d8b974',
45  social:   '#82c882',
46  admin:    '#888888',
47  other:    '#6b9cb5',
48  untagged: '#555',
49};
50
51// SVG icon literals reused across the calendar UI.
52export const _trashIcon = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>';
53export const _moreIcon  = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>';
54export const _bellIcon  = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>';
55
56// ── background CSS helpers ──
57
58export function _isCalBgImage(c) {
59  return typeof c === 'string' && c.startsWith('bg:');
60}
61
62export function _calBgImageUrl(c) {
63  return _isCalBgImage(c) ? c.slice(3) : '';
64}
65
66// Returns a value safe to drop into `style="background:..."`. Falls back to
67// the calendar default for bg-image events in spots where an image would be
68// too small to render usefully (small grid dots, multi-day bars).
69export function _calBgCss(c, fallback) {
70  if (_isCalBgImage(c)) {
71    const u = _calBgImageUrl(c);
72    return u ? `center/cover no-repeat url('${u.replace(/'/g, "\\'")}')` : (fallback || 'var(--accent)');
73  }
74  return c || fallback || 'var(--accent)';
75}
76
77function _hexToRgb(c) {
78  if (typeof c !== 'string') return null;
79  const m = c.trim().match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
80  if (!m) return null;
81  const hex = m[1].length === 3
82    ? m[1].split('').map(ch => ch + ch).join('')
83    : m[1];
84  return {
85    r: parseInt(hex.slice(0, 2), 16),
86    g: parseInt(hex.slice(2, 4), 16),
87    b: parseInt(hex.slice(4, 6), 16),
88  };
89}
90
91function _relativeLuminance({ r, g, b }) {
92  return [r, g, b].map(v => {
93    const c = v / 255;
94    return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
95  }).reduce((sum, c, i) => sum + c * [0.2126, 0.7152, 0.0722][i], 0);
96}
97
98function _contrastRatio(a, b) {
99  const light = Math.max(a, b);
100  const dark = Math.min(a, b);
101  return (light + 0.05) / (dark + 0.05);
102}
103
104export function _calReadableTextColor(bg) {
105  const rgb = _hexToRgb(bg);
106  if (!rgb) return 'var(--fg)';
107  const lum = _relativeLuminance(rgb);
108  const white = _contrastRatio(lum, 1);
109  const ink = _contrastRatio(lum, 0.006);
110  return ink >= white ? '#111820' : '#ffffff';
111}
112
113// ── date helpers ──
114
115// `YYYY-MM-DD` string from a Date.
116export function _ds(d) {
117  return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
118}
119
120export function _addDays(dateStr, n) {
121  if (typeof dateStr !== 'string' || !dateStr) return '';
122  const d = new Date(dateStr + 'T00:00:00');
123  if (isNaN(d)) return '';
124  d.setDate(d.getDate() + n);
125  return _ds(d);
126}
127
128export function _shiftDT(iso, days) {
129  if (typeof iso !== 'string' || !iso) return '';
130  const d = new Date(iso);
131  if (isNaN(d)) return '';
132  d.setDate(d.getDate() + days);
133  return _ds(d) + (iso.length > 10 ? 'T' + iso.slice(11) : '');
134}
135
136// Current user's UTC offset as `±HH:MM`. Used to stamp event payloads so
137// the backend can interpret naive datetimes in the user's tz.
138export function _tzOffset() {
139  const o = -new Date().getTimezoneOffset();
140  const sign = o >= 0 ? '+' : '-';
141  const h = String(Math.floor(Math.abs(o) / 60)).padStart(2, '0');
142  const m = String(Math.abs(o) % 60).padStart(2, '0');
143  return `${sign}${h}:${m}`;
144}
145
146// For naive datetimes (no tz suffix), display the date portion as written —
147// TimeTree and many sync tools store "local time" without an offset, so
148// re-interpreting them via the user's tz would shift days.
149//
150// For tz-aware ISO (`Z` or `±HH:MM`), parse as an absolute instant and
151// bucket by the USER's local date. Without this an event at
152// "2026-05-13T22:00:00Z" (07:00 May 14 JST) would render on May 13.
153export function _localDateOf(isoStr) {
154  if (typeof isoStr !== 'string' || !isoStr) return '';
155  if (isoStr.length === 10) return isoStr;
156  if (/[Zz]$|[+\-]\d{2}:?\d{2}$/.test(isoStr)) {
157    const d = new Date(isoStr);
158    if (!isNaN(d)) {
159      const y  = d.getFullYear();
160      const m  = String(d.getMonth() + 1).padStart(2, '0');
161      const dd = String(d.getDate()).padStart(2, '0');
162      return `${y}-${m}-${dd}`;
163    }
164  }
165  return isoStr.slice(0, 10);
166}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.