floq 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,11 @@ export declare function fetchCalendarEvents(url?: string): Promise<CalendarEvent
14
14
  * Get cached events or fetch if cache is stale
15
15
  */
16
16
  export declare function getCalendarEvents(): Promise<CalendarEvent[]>;
17
+ /**
18
+ * Get events for a specific date from cache (synchronous)
19
+ * @param dayOffset - Number of days from today (0 = today, -1 = yesterday, 1 = tomorrow)
20
+ */
21
+ export declare function getEventsForDate(dayOffset?: number): CalendarEvent[];
17
22
  /**
18
23
  * Get today's events from cache (synchronous)
19
24
  */
@@ -15,11 +15,10 @@ function parseICalData(icalData) {
15
15
  const vevents = vcalendar.getAllSubcomponents('vevent');
16
16
  for (const vevent of vevents) {
17
17
  const event = new ICAL.Event(vevent);
18
- // Handle recurring events - get occurrences for today and next 7 days
18
+ // Handle recurring events - get occurrences for current month and next month
19
19
  const now = new Date();
20
- const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
21
- const endOfWeek = new Date(startOfToday);
22
- endOfWeek.setDate(endOfWeek.getDate() + 7);
20
+ const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
21
+ const endOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 2, 0); // Last day of next month
23
22
  if (event.isRecurring()) {
24
23
  try {
25
24
  const iter = event.iterator();
@@ -30,7 +29,7 @@ function parseICalData(icalData) {
30
29
  const occurrenceStart = next.toJSDate();
31
30
  const occurrenceEnd = new Date(occurrenceStart.getTime() + event.duration.toSeconds() * 1000);
32
31
  // Only include occurrences within our time window
33
- if (occurrenceStart >= startOfToday && occurrenceStart < endOfWeek) {
32
+ if (occurrenceStart >= startOfMonth && occurrenceStart <= endOfNextMonth) {
34
33
  events.push({
35
34
  id: `${event.uid}-${occurrenceStart.getTime()}`,
36
35
  title: event.summary || 'Untitled',
@@ -41,7 +40,7 @@ function parseICalData(icalData) {
41
40
  });
42
41
  }
43
42
  // Stop if we're past our window
44
- if (occurrenceStart >= endOfWeek)
43
+ if (occurrenceStart > endOfNextMonth)
45
44
  break;
46
45
  next = iter.next();
47
46
  count++;
@@ -104,11 +103,10 @@ async function fetchEventsViaOAuth() {
104
103
  return [];
105
104
  }
106
105
  const now = new Date();
107
- const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
108
- const endOfWeek = new Date(startOfToday);
109
- endOfWeek.setDate(endOfWeek.getDate() + 7);
106
+ const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
107
+ const endOfNextMonth = new Date(now.getFullYear(), now.getMonth() + 2, 0); // Last day of next month
110
108
  try {
111
- return await listGoogleEvents(accessToken, oauthConfig.calendarId, startOfToday, endOfWeek);
109
+ return await listGoogleEvents(accessToken, oauthConfig.calendarId, startOfMonth, endOfNextMonth);
112
110
  }
113
111
  catch (error) {
114
112
  console.error('Failed to fetch Google Calendar events:', error);
@@ -182,21 +180,23 @@ export async function getCalendarEvents() {
182
180
  return fetchCalendarEvents();
183
181
  }
184
182
  /**
185
- * Get today's events from cache (synchronous)
183
+ * Get events for a specific date from cache (synchronous)
184
+ * @param dayOffset - Number of days from today (0 = today, -1 = yesterday, 1 = tomorrow)
186
185
  */
187
- export function getTodayEvents() {
186
+ export function getEventsForDate(dayOffset = 0) {
188
187
  if (!eventsCache) {
189
188
  return [];
190
189
  }
191
190
  const now = new Date();
192
- const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
193
- const endOfToday = new Date(startOfToday);
194
- endOfToday.setDate(endOfToday.getDate() + 1);
191
+ const targetDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
192
+ targetDate.setDate(targetDate.getDate() + dayOffset);
193
+ const endOfTarget = new Date(targetDate);
194
+ endOfTarget.setDate(endOfTarget.getDate() + 1);
195
195
  return eventsCache.events
196
196
  .filter(event => {
197
- // Event starts today or spans today
198
- return (event.start >= startOfToday && event.start < endOfToday) ||
199
- (event.start < startOfToday && event.end >= startOfToday);
197
+ // Event starts on target date or spans target date
198
+ return (event.start >= targetDate && event.start < endOfTarget) ||
199
+ (event.start < targetDate && event.end >= targetDate);
200
200
  })
201
201
  .sort((a, b) => {
202
202
  // All-day events first, then by start time
@@ -207,6 +207,12 @@ export function getTodayEvents() {
207
207
  return a.start.getTime() - b.start.getTime();
208
208
  });
209
209
  }
210
+ /**
211
+ * Get today's events from cache (synchronous)
212
+ */
213
+ export function getTodayEvents() {
214
+ return getEventsForDate(0);
215
+ }
210
216
  /**
211
217
  * Get upcoming events (next event for each hour slot)
212
218
  */
package/dist/i18n/en.d.ts CHANGED
@@ -260,12 +260,25 @@ export declare const en: {
260
260
  calendar: {
261
261
  label: string;
262
262
  noEvents: string;
263
+ noUpcoming: string;
263
264
  allDay: string;
264
265
  upcoming: string;
265
266
  more: string;
266
267
  modalTitle: string;
268
+ yesterday: string;
269
+ today: string;
270
+ tomorrow: string;
271
+ noEventsForDay: string;
267
272
  notConfigured: string;
268
273
  setupHint: string;
274
+ addEvent: string;
275
+ eventTitle: string;
276
+ eventStart: string;
277
+ eventEnd: string;
278
+ eventAllDay: string;
279
+ eventCreated: string;
280
+ eventCreateError: string;
281
+ addNotSupported: string;
269
282
  oauthConfigured: string;
270
283
  oauthNotConfigured: string;
271
284
  loginRequired: string;
@@ -480,12 +493,25 @@ export type PomodoroTranslations = {
480
493
  export type CalendarTranslations = {
481
494
  label: string;
482
495
  noEvents: string;
496
+ noUpcoming?: string;
483
497
  allDay: string;
484
498
  upcoming: string;
485
499
  more: string;
486
500
  modalTitle?: string;
501
+ yesterday?: string;
502
+ today?: string;
503
+ tomorrow?: string;
504
+ noEventsForDay?: string;
487
505
  notConfigured?: string;
488
506
  setupHint?: string;
507
+ addEvent?: string;
508
+ eventTitle?: string;
509
+ eventStart?: string;
510
+ eventEnd?: string;
511
+ eventAllDay?: string;
512
+ eventCreated?: string;
513
+ eventCreateError?: string;
514
+ addNotSupported?: string;
489
515
  oauthConfigured?: string;
490
516
  oauthNotConfigured?: string;
491
517
  loginRequired?: string;
package/dist/i18n/en.js CHANGED
@@ -278,13 +278,27 @@ export const en = {
278
278
  calendar: {
279
279
  label: '[CAL]',
280
280
  noEvents: 'No events today',
281
+ noUpcoming: 'No more events. Good work today!',
281
282
  allDay: 'All day',
282
283
  upcoming: 'Next:',
283
284
  more: '+{count}',
284
285
  // Modal
285
286
  modalTitle: "Today's Events",
287
+ yesterday: 'Yesterday',
288
+ today: 'Today',
289
+ tomorrow: 'Tomorrow',
290
+ noEventsForDay: 'No events',
286
291
  notConfigured: 'Calendar not configured.',
287
292
  setupHint: 'Run "floq calendar --help" to set up.',
293
+ // Add event
294
+ addEvent: 'Add Event',
295
+ eventTitle: 'Title',
296
+ eventStart: 'Start',
297
+ eventEnd: 'End',
298
+ eventAllDay: 'All day',
299
+ eventCreated: 'Event created',
300
+ eventCreateError: 'Failed to create event',
301
+ addNotSupported: 'Event creation requires OAuth login',
288
302
  // OAuth messages
289
303
  oauthConfigured: 'OAuth client configured',
290
304
  oauthNotConfigured: 'OAuth client not configured',
package/dist/i18n/ja.js CHANGED
@@ -278,13 +278,27 @@ export const ja = {
278
278
  calendar: {
279
279
  label: '予定',
280
280
  noEvents: '今日の予定はありません',
281
+ noUpcoming: '次の予定はありません。おつかれさまでした!',
281
282
  allDay: '終日',
282
283
  upcoming: '次:',
283
284
  more: '+{count}',
284
285
  // Modal
285
286
  modalTitle: '今日の予定',
287
+ yesterday: '昨日',
288
+ today: '今日',
289
+ tomorrow: '明日',
290
+ noEventsForDay: '予定なし',
286
291
  notConfigured: 'カレンダーが設定されていません。',
287
292
  setupHint: '"floq calendar --help" で設定方法を確認してください。',
293
+ // Add event
294
+ addEvent: '予定を追加',
295
+ eventTitle: 'タイトル',
296
+ eventStart: '開始',
297
+ eventEnd: '終了',
298
+ eventAllDay: '終日',
299
+ eventCreated: '予定を作成しました',
300
+ eventCreateError: '予定の作成に失敗しました',
301
+ addNotSupported: '予定の追加にはOAuthログインが必要です',
288
302
  // OAuth messages
289
303
  oauthConfigured: 'OAuthクライアント設定済み',
290
304
  oauthNotConfigured: 'OAuthクライアント未設定',
@@ -62,8 +62,12 @@ export function CalendarEvents({ maxEvents = 3, showLabel = true, compact = true
62
62
  const now = new Date();
63
63
  const upcomingEvents = events.filter(e => !e.allDay && e.end > now);
64
64
  const nextEvent = upcomingEvents[0];
65
- if (compact && nextEvent) {
65
+ if (compact) {
66
66
  // Compact mode: show only the next event with start-end time
67
+ // If no upcoming events, show a friendly message
68
+ if (!nextEvent) {
69
+ return (_jsxs(Box, { children: [showLabel && (_jsxs(Text, { color: theme.colors.secondary, children: [i18n.tui.calendar?.label || '[CAL]', ' '] })), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.calendar?.noUpcoming || 'No more events. Good work today!' }), withSeparator && _jsx(Text, { color: theme.colors.textMuted, children: " | " })] }));
70
+ }
67
71
  const isOngoing = isEventOngoing(nextEvent);
68
72
  const timeDisplay = `${formatEventTime(nextEvent.start)}-${formatEventTime(nextEvent.end)}`;
69
73
  // Truncate title if too long
@@ -1,20 +1,29 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useState, useEffect } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useMemo } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
4
  import { t } from '../../i18n/index.js';
5
5
  import { useTheme } from '../theme/index.js';
6
6
  import { isCalendarEnabled, getCalendarConfig, getCalendarType } from '../../config.js';
7
- import { getCalendarEvents, getTodayEvents, formatEventTime, isEventOngoing, } from '../../calendar/index.js';
8
- const VISIBLE_LINES = 12;
7
+ import { getCalendarEvents, getEventsForDate, formatEventTime, isEventOngoing, } from '../../calendar/index.js';
8
+ const WEEKDAYS_EN = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
9
+ const WEEKDAYS_JA = ['日', '月', '火', '水', '木', '金', '土'];
10
+ const VISIBLE_EVENTS = 5;
9
11
  export function CalendarModal({ onClose }) {
10
12
  const theme = useTheme();
11
13
  const i18n = t();
12
14
  const [events, setEvents] = useState([]);
15
+ const [allEvents, setAllEvents] = useState([]);
13
16
  const [isLoading, setIsLoading] = useState(true);
14
17
  const [scrollOffset, setScrollOffset] = useState(0);
18
+ const [selectedDate, setSelectedDate] = useState(new Date());
19
+ const [viewMonth, setViewMonth] = useState(new Date());
20
+ const [mode, setMode] = useState('calendar');
15
21
  const calendarEnabled = isCalendarEnabled();
16
22
  const config = getCalendarConfig();
17
23
  const calendarType = getCalendarType();
24
+ const isJapanese = i18n.tui.calendar?.yesterday === '昨日';
25
+ const weekdays = isJapanese ? WEEKDAYS_JA : WEEKDAYS_EN;
26
+ // Load all events on mount
18
27
  useEffect(() => {
19
28
  if (!calendarEnabled) {
20
29
  setIsLoading(false);
@@ -23,9 +32,9 @@ export function CalendarModal({ onClose }) {
23
32
  let mounted = true;
24
33
  const loadEvents = async () => {
25
34
  try {
26
- await getCalendarEvents();
35
+ const loadedEvents = await getCalendarEvents();
27
36
  if (mounted) {
28
- setEvents(getTodayEvents());
37
+ setAllEvents(loadedEvents);
29
38
  setIsLoading(false);
30
39
  }
31
40
  }
@@ -40,17 +49,163 @@ export function CalendarModal({ onClose }) {
40
49
  mounted = false;
41
50
  };
42
51
  }, [calendarEnabled]);
43
- const maxScroll = Math.max(0, events.length - VISIBLE_LINES);
52
+ // Update events when selected date changes
53
+ useEffect(() => {
54
+ if (!isLoading) {
55
+ const today = new Date();
56
+ today.setHours(0, 0, 0, 0);
57
+ const selected = new Date(selectedDate);
58
+ selected.setHours(0, 0, 0, 0);
59
+ const dayOffset = Math.round((selected.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
60
+ setEvents(getEventsForDate(dayOffset));
61
+ setScrollOffset(0);
62
+ }
63
+ }, [selectedDate, isLoading, allEvents]);
64
+ // Generate calendar grid for the view month
65
+ const calendarGrid = useMemo(() => {
66
+ const year = viewMonth.getFullYear();
67
+ const month = viewMonth.getMonth();
68
+ const firstDay = new Date(year, month, 1);
69
+ const lastDay = new Date(year, month + 1, 0);
70
+ const startDayOfWeek = firstDay.getDay();
71
+ const daysInMonth = lastDay.getDate();
72
+ const grid = [];
73
+ let currentDay = 1;
74
+ for (let week = 0; week < 6; week++) {
75
+ const row = [];
76
+ for (let day = 0; day < 7; day++) {
77
+ if (week === 0 && day < startDayOfWeek) {
78
+ row.push(null);
79
+ }
80
+ else if (currentDay > daysInMonth) {
81
+ row.push(null);
82
+ }
83
+ else {
84
+ row.push(currentDay);
85
+ currentDay++;
86
+ }
87
+ }
88
+ grid.push(row);
89
+ if (currentDay > daysInMonth)
90
+ break;
91
+ }
92
+ return grid;
93
+ }, [viewMonth]);
94
+ // Check if a date has events
95
+ const hasEventsOnDay = (day) => {
96
+ const date = new Date(viewMonth.getFullYear(), viewMonth.getMonth(), day);
97
+ const nextDate = new Date(viewMonth.getFullYear(), viewMonth.getMonth(), day + 1);
98
+ return allEvents.some(event => (event.start >= date && event.start < nextDate) ||
99
+ (event.start < date && event.end >= date));
100
+ };
101
+ // Check if a date is today
102
+ const isToday = (day) => {
103
+ const today = new Date();
104
+ return day === today.getDate() &&
105
+ viewMonth.getMonth() === today.getMonth() &&
106
+ viewMonth.getFullYear() === today.getFullYear();
107
+ };
108
+ // Check if a date is selected
109
+ const isSelected = (day) => {
110
+ return day === selectedDate.getDate() &&
111
+ viewMonth.getMonth() === selectedDate.getMonth() &&
112
+ viewMonth.getFullYear() === selectedDate.getFullYear();
113
+ };
114
+ const maxScroll = Math.max(0, events.length - VISIBLE_EVENTS);
44
115
  useInput((input, key) => {
45
- if (input === 'j' || key.downArrow) {
46
- setScrollOffset(prev => Math.min(prev + 1, maxScroll));
116
+ // Events mode: scroll through events
117
+ if (mode === 'events') {
118
+ if (input === 'j' || key.downArrow) {
119
+ setScrollOffset(prev => Math.min(prev + 1, maxScroll));
120
+ return;
121
+ }
122
+ if (input === 'k' || key.upArrow) {
123
+ setScrollOffset(prev => Math.max(prev - 1, 0));
124
+ return;
125
+ }
126
+ if (key.escape || input === 'q') {
127
+ setMode('calendar');
128
+ return;
129
+ }
130
+ return;
131
+ }
132
+ // Calendar mode: navigate dates
133
+ if (input === 'h' || key.leftArrow) {
134
+ setSelectedDate(prev => {
135
+ const newDate = new Date(prev);
136
+ newDate.setDate(newDate.getDate() - 1);
137
+ if (newDate.getMonth() !== viewMonth.getMonth()) {
138
+ setViewMonth(new Date(newDate.getFullYear(), newDate.getMonth(), 1));
139
+ }
140
+ return newDate;
141
+ });
142
+ return;
143
+ }
144
+ if (input === 'l' || key.rightArrow) {
145
+ setSelectedDate(prev => {
146
+ const newDate = new Date(prev);
147
+ newDate.setDate(newDate.getDate() + 1);
148
+ if (newDate.getMonth() !== viewMonth.getMonth()) {
149
+ setViewMonth(new Date(newDate.getFullYear(), newDate.getMonth(), 1));
150
+ }
151
+ return newDate;
152
+ });
47
153
  return;
48
154
  }
49
155
  if (input === 'k' || key.upArrow) {
50
- setScrollOffset(prev => Math.max(prev - 1, 0));
156
+ setSelectedDate(prev => {
157
+ const newDate = new Date(prev);
158
+ newDate.setDate(newDate.getDate() - 7);
159
+ if (newDate.getMonth() !== viewMonth.getMonth()) {
160
+ setViewMonth(new Date(newDate.getFullYear(), newDate.getMonth(), 1));
161
+ }
162
+ return newDate;
163
+ });
164
+ return;
165
+ }
166
+ if (input === 'j' || key.downArrow) {
167
+ setSelectedDate(prev => {
168
+ const newDate = new Date(prev);
169
+ newDate.setDate(newDate.getDate() + 7);
170
+ if (newDate.getMonth() !== viewMonth.getMonth()) {
171
+ setViewMonth(new Date(newDate.getFullYear(), newDate.getMonth(), 1));
172
+ }
173
+ return newDate;
174
+ });
175
+ return;
176
+ }
177
+ // Enter events mode
178
+ if (key.return && events.length > 0) {
179
+ setMode('events');
51
180
  return;
52
181
  }
53
- if (key.escape || key.return || input === 'q' || input === ' ') {
182
+ // Previous/Next month
183
+ if (input === 'H') {
184
+ setViewMonth(prev => new Date(prev.getFullYear(), prev.getMonth() - 1, 1));
185
+ setSelectedDate(prev => {
186
+ const newDate = new Date(prev);
187
+ newDate.setMonth(newDate.getMonth() - 1);
188
+ return newDate;
189
+ });
190
+ return;
191
+ }
192
+ if (input === 'L') {
193
+ setViewMonth(prev => new Date(prev.getFullYear(), prev.getMonth() + 1, 1));
194
+ setSelectedDate(prev => {
195
+ const newDate = new Date(prev);
196
+ newDate.setMonth(newDate.getMonth() + 1);
197
+ return newDate;
198
+ });
199
+ return;
200
+ }
201
+ // Go to today
202
+ if (input === 't') {
203
+ const today = new Date();
204
+ setSelectedDate(today);
205
+ setViewMonth(new Date(today.getFullYear(), today.getMonth(), 1));
206
+ return;
207
+ }
208
+ if (key.escape || input === 'q' || input === ' ') {
54
209
  onClose();
55
210
  return;
56
211
  }
@@ -60,14 +215,61 @@ export function CalendarModal({ onClose }) {
60
215
  const calendarName = calendarType === 'oauth' && config?.oauth
61
216
  ? config.oauth.calendarName
62
217
  : config?.name || 'Calendar';
63
- const visibleEvents = events.slice(scrollOffset, scrollOffset + VISIBLE_LINES);
218
+ // Format month header
219
+ const monthNames = isJapanese
220
+ ? ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
221
+ : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
222
+ const monthHeader = isJapanese
223
+ ? `${viewMonth.getFullYear()}年 ${monthNames[viewMonth.getMonth()]}`
224
+ : `${monthNames[viewMonth.getMonth()]} ${viewMonth.getFullYear()}`;
225
+ // Format selected date
226
+ const formatSelectedDate = () => {
227
+ const today = new Date();
228
+ today.setHours(0, 0, 0, 0);
229
+ const selected = new Date(selectedDate);
230
+ selected.setHours(0, 0, 0, 0);
231
+ const diff = Math.round((selected.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
232
+ if (diff === -1)
233
+ return i18n.tui.calendar?.yesterday || 'Yesterday';
234
+ if (diff === 0)
235
+ return i18n.tui.calendar?.today || 'Today';
236
+ if (diff === 1)
237
+ return i18n.tui.calendar?.tomorrow || 'Tomorrow';
238
+ if (isJapanese) {
239
+ return `${selectedDate.getMonth() + 1}/${selectedDate.getDate()} (${weekdays[selectedDate.getDay()]})`;
240
+ }
241
+ return `${monthNames[selectedDate.getMonth()].slice(0, 3)} ${selectedDate.getDate()}`;
242
+ };
243
+ const visibleEvents = events.slice(scrollOffset, scrollOffset + VISIBLE_EVENTS);
64
244
  const showScrollUp = scrollOffset > 0;
65
245
  const showScrollDown = scrollOffset < maxScroll;
66
- return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.modal, borderColor: theme.colors.borderActive, paddingX: 2, paddingY: 1, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsxs(Text, { bold: true, color: theme.colors.secondary, children: [formatTitle(i18n.tui.calendar?.modalTitle || "Today's Events"), " - ", calendarName] }) }), _jsxs(Box, { flexDirection: "column", height: VISIBLE_LINES + 2, children: [showScrollUp && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25B2 scroll up (k)" })), !showScrollUp && _jsx(Text, { children: " " }), !calendarEnabled ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.calendar?.notConfigured || 'Calendar not configured.' }), _jsx(Text, { color: theme.colors.textMuted, children: " " }), _jsx(Text, { color: theme.colors.text, children: i18n.tui.calendar?.setupHint || 'Run "floq calendar --help" to set up.' })] })) : isLoading ? (_jsx(Text, { color: theme.colors.textMuted, children: "Loading..." })) : events.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.calendar?.noEvents || 'No events today' })) : (visibleEvents.map((event, index) => {
67
- const isOngoing = isEventOngoing(event);
68
- const timeStr = event.allDay
69
- ? (i18n.tui.calendar?.allDay || 'All day')
70
- : `${formatEventTime(event.start)} - ${formatEventTime(event.end)}`;
71
- return (_jsxs(Box, { children: [_jsx(Text, { color: isOngoing ? theme.colors.accent : theme.colors.secondary, children: timeStr.padEnd(15) }), _jsx(Text, { color: theme.colors.text, children: event.title }), event.location && (_jsxs(Text, { color: theme.colors.textMuted, children: [" (", event.location, ")"] }))] }, event.id || index));
72
- })), showScrollDown && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25BC scroll down (j)" })), !showScrollDown && _jsx(Text, { children: " " })] }), _jsx(Box, { justifyContent: "center", marginTop: 1, children: _jsxs(Text, { color: theme.colors.textMuted, children: [maxScroll > 0 ? 'j/k: scroll | ' : '', "Esc/q: ", i18n.tui.help.closeHint?.replace('Esc/q: ', '') || 'close'] }) })] }));
246
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.modal, borderColor: theme.colors.borderActive, paddingX: 2, paddingY: 1, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.colors.secondary, children: formatTitle(calendarName) }) }), !calendarEnabled ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.calendar?.notConfigured || 'Calendar not configured.' }), _jsx(Text, { color: theme.colors.textMuted, children: " " }), _jsx(Text, { color: theme.colors.text, children: i18n.tui.calendar?.setupHint || 'Run "floq calendar --help" to set up.' })] })) : isLoading ? (_jsx(Text, { color: theme.colors.textMuted, children: "Loading..." })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", alignItems: "center", children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.colors.text, children: ' ' }), _jsx(Text, { bold: true, color: theme.colors.text, children: monthHeader }), _jsx(Text, { color: theme.colors.text, children: ' ' })] }), _jsx(Box, { children: weekdays.map((day, i) => (_jsx(Box, { width: 4, justifyContent: "center", children: _jsx(Text, { color: i === 0 ? theme.colors.accent : (i === 6 ? theme.colors.secondary : theme.colors.textMuted), children: day }) }, day))) }), calendarGrid.map((week, weekIndex) => (_jsx(Box, { children: week.map((day, dayIndex) => {
247
+ if (day === null) {
248
+ return _jsx(Box, { width: 4, children: _jsx(Text, { children: " " }) }, dayIndex);
249
+ }
250
+ const selected = isSelected(day);
251
+ const today = isToday(day);
252
+ const hasEvents = hasEventsOnDay(day);
253
+ const isSunday = dayIndex === 0;
254
+ const isSaturday = dayIndex === 6;
255
+ let color = theme.colors.text;
256
+ if (isSunday)
257
+ color = theme.colors.accent;
258
+ else if (isSaturday)
259
+ color = theme.colors.secondary;
260
+ const dayStr = day.toString().padStart(2, ' ');
261
+ return (_jsx(Box, { width: 4, justifyContent: "center", children: selected ? (_jsx(Text, { backgroundColor: theme.colors.accent, color: theme.colors.background, children: hasEvents ? `${dayStr}*` : `${dayStr} ` })) : today ? (_jsx(Text, { bold: true, underline: true, color: color, children: hasEvents ? `${dayStr}*` : `${dayStr} ` })) : (_jsx(Text, { color: color, children: hasEvents ? `${dayStr}*` : `${dayStr} ` })) }, dayIndex));
262
+ }) }, weekIndex)))] }), _jsx(Box, { marginY: 1, justifyContent: "center", children: _jsx(Text, { color: theme.colors.border, children: '─'.repeat(28) }) }), _jsxs(Box, { flexDirection: "column", alignItems: "center", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: mode === 'events' ? theme.colors.accent : theme.colors.secondary, children: [formatSelectedDate(), mode === 'events' ? ' *' : ''] }) }), _jsxs(Box, { flexDirection: "column", width: 28, children: [showScrollUp && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25B2 (k)" })), events.length === 0 ? (_jsx(Box, { justifyContent: "center", children: _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.calendar?.noEventsForDay || 'No events' }) })) : (visibleEvents.map((event, index) => {
263
+ const isOngoing = isEventOngoing(event);
264
+ const timeStr = event.allDay
265
+ ? (i18n.tui.calendar?.allDay || 'All day')
266
+ : formatEventTime(event.start);
267
+ const maxTitleLen = 18;
268
+ const title = event.title.length > maxTitleLen
269
+ ? event.title.slice(0, maxTitleLen - 1) + '…'
270
+ : event.title;
271
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isOngoing ? theme.colors.accent : theme.colors.secondary, children: timeStr.padEnd(8) }), _jsx(Text, { color: theme.colors.text, children: title })] }, event.id || index));
272
+ })), showScrollDown && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25BC (j)" })), events.length > VISIBLE_EVENTS && (_jsx(Box, { justifyContent: "center", children: _jsxs(Text, { color: theme.colors.textMuted, children: ["+", events.length - VISIBLE_EVENTS, " more"] }) }))] })] })] })), _jsx(Box, { justifyContent: "center", marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: mode === 'events'
273
+ ? 'j/k: scroll | q/Esc: back'
274
+ : `hjkl: move | H/L: month | t: today${events.length > 0 ? ' | Enter: events' : ''} | q: close` }) })] }));
73
275
  }
@@ -1023,7 +1023,7 @@ export function GtdDQ({ onOpenSettings }) {
1023
1023
  setMessage(focusMode ? 'Focus mode off' : 'Focus mode on');
1024
1024
  return;
1025
1025
  }
1026
- });
1026
+ }, { isActive: mode !== 'calendar' });
1027
1027
  const tursoEnabled = isTursoEnabled();
1028
1028
  // Get parent project for display
1029
1029
  const getParentProject = (parentId) => {
@@ -918,7 +918,7 @@ export function GtdMario({ onOpenSettings }) {
918
918
  toggleFocusMode();
919
919
  return;
920
920
  }
921
- });
921
+ }, { isActive: mode !== 'calendar' });
922
922
  const tursoEnabled = isTursoEnabled();
923
923
  const getParentProject = (parentId) => {
924
924
  if (!parentId)
@@ -714,7 +714,7 @@ export function KanbanBoard({ onSwitchToGtd, onOpenSettings }) {
714
714
  });
715
715
  return;
716
716
  }
717
- });
717
+ }, { isActive: mode !== 'calendar' });
718
718
  // Help modal overlay
719
719
  if (mode === 'help') {
720
720
  return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal'), isKanban: true }) }));
@@ -495,7 +495,7 @@ export function KanbanDQ({ onOpenSettings }) {
495
495
  setMessage(i18n.tui.refreshed);
496
496
  return;
497
497
  }
498
- });
498
+ }, { isActive: mode !== 'calendar' });
499
499
  const tursoEnabled = isTursoEnabled();
500
500
  // Help modal overlay
501
501
  if (mode === 'help') {
@@ -417,7 +417,7 @@ export function KanbanMario({ onOpenSettings }) {
417
417
  setMessage(i18n.tui.refreshed);
418
418
  return;
419
419
  }
420
- });
420
+ }, { isActive: mode !== 'calendar' });
421
421
  const tursoEnabled = isTursoEnabled();
422
422
  // Help modal overlay
423
423
  if (mode === 'help') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "floq",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Floq - Getting Things Done Task Manager with MS-DOS style themes",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",