@luckydye/calendar 1.3.2 → 1.4.0

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.
package/src/Keybinds.ts CHANGED
@@ -9,7 +9,12 @@ export interface Keybind {
9
9
  export function registerKeybinds(bindings: Keybind[]): () => void {
10
10
  const handler = (e: KeyboardEvent): void => {
11
11
  const focused = e.composedPath()[0] as HTMLElement;
12
- if (focused?.tagName === "INPUT" || focused?.tagName === "TEXTAREA" || focused?.isContentEditable) return;
12
+ if (
13
+ focused?.tagName === "INPUT" ||
14
+ focused?.tagName === "TEXTAREA" ||
15
+ focused?.isContentEditable
16
+ )
17
+ return;
13
18
 
14
19
  for (const binding of bindings) {
15
20
  const keyMatch = e.key === binding.key;
@@ -11,12 +11,13 @@ export interface ScheduledNotification {
11
11
  }
12
12
 
13
13
  export class NotificationScheduler {
14
-
15
- constructor(private workerPromise: () => Promise<ServiceWorkerRegistration>) {}
16
-
14
+ constructor(
15
+ private workerPromise: () => Promise<ServiceWorkerRegistration>,
16
+ ) {}
17
+
17
18
  async scheduleEventNotifications(event: CalendarEvent): Promise<void> {
18
- await this.cancelEventNotifications(event.id);
19
-
19
+ await this.cancelEventNotifications(event.id);
20
+
20
21
  if (!event.reminders?.length) return;
21
22
 
22
23
  const notifications = this.buildScheduledNotifications(event);
@@ -45,7 +46,9 @@ export class NotificationScheduler {
45
46
 
46
47
  async getScheduledNotifications(): Promise<ScheduledNotification[]> {
47
48
  try {
48
- const response = await this.sendMessage({ type: "GET_SCHEDULED_NOTIFICATIONS" });
49
+ const response = await this.sendMessage({
50
+ type: "GET_SCHEDULED_NOTIFICATIONS",
51
+ });
49
52
  return response.notifications || [];
50
53
  } catch (error) {
51
54
  console.error("Failed to get scheduled notifications:", error);
@@ -77,8 +80,8 @@ export class NotificationScheduler {
77
80
  }
78
81
 
79
82
  async sendMessage(message: any): Promise<any> {
80
- const worker = await this.workerPromise?.();
81
-
83
+ const worker = await this.workerPromise?.();
84
+
82
85
  return new Promise((resolve, reject) => {
83
86
  const channel = new MessageChannel();
84
87
  channel.port1.onmessage = (e) => {
package/src/StatusBar.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { LitElement, html, css } from "lit";
1
+ import { LitElement, css, html } from "lit";
2
2
  import "./StatusMessage.ts";
3
3
 
4
4
  export interface StatusBarData {
@@ -92,7 +92,11 @@ export class StatusBarElement extends LitElement {
92
92
 
93
93
  <status-message></status-message>
94
94
 
95
- ${this.data.altKeyActive ? html`<span class="meta-key">Meta</span>` : html``}
95
+ ${
96
+ this.data.altKeyActive
97
+ ? html`<span class="meta-key">Meta</span>`
98
+ : html``
99
+ }
96
100
  </div>
97
101
 
98
102
  <div class="status-bar-right">
@@ -101,9 +105,9 @@ export class StatusBarElement extends LitElement {
101
105
  ? html`
102
106
  <div class="status-item">
103
107
  <span class="status-label">Selected:</span>
104
- <span class="status-value">${this.data.selectedEventsCount} event${
105
- this.data.selectedEventsCount === 1 ? "" : "s"
106
- }</span>
108
+ <span class="status-value">${
109
+ this.data.selectedEventsCount
110
+ } event${this.data.selectedEventsCount === 1 ? "" : "s"}</span>
107
111
  </div>
108
112
  <div class="status-item">
109
113
  <span class="status-label">Duration:</span>
@@ -113,9 +117,9 @@ export class StatusBarElement extends LitElement {
113
117
  : html`
114
118
  <div class="status-item">
115
119
  <span class="status-label">Total:</span>
116
- <span class="status-value">${this.data.totalEventsCount} event${
117
- this.data.totalEventsCount === 1 ? "" : "s"
118
- }</span>
120
+ <span class="status-value">${
121
+ this.data.totalEventsCount
122
+ } event${this.data.totalEventsCount === 1 ? "" : "s"}</span>
119
123
  </div>
120
124
  `
121
125
  }
@@ -1,4 +1,4 @@
1
- import { css, html, LitElement } from "lit";
1
+ import { LitElement, css, html } from "lit";
2
2
 
3
3
  interface QueuedMessage {
4
4
  text: string;
@@ -116,7 +116,7 @@ export class StatusMessageElement extends LitElement {
116
116
  }
117
117
 
118
118
  try {
119
- customElements.define("status-message", StatusMessageElement);
119
+ customElements.define("status-message", StatusMessageElement);
120
120
  } catch (error) {
121
121
  console.error("Failed to register custom element:", error);
122
122
  }
package/src/Theme.ts CHANGED
@@ -1,6 +1,15 @@
1
- export type ThemeName = "auto" | "dark" | "light" | "solarized" | "high-contrast";
1
+ export type ThemeName =
2
+ | "auto"
3
+ | "dark"
4
+ | "light"
5
+ | "solarized"
6
+ | "high-contrast";
2
7
 
3
- export type ConcreteThemeName = "dark" | "light" | "solarized" | "high-contrast";
8
+ export type ConcreteThemeName =
9
+ | "dark"
10
+ | "light"
11
+ | "solarized"
12
+ | "high-contrast";
4
13
 
5
14
  export interface ThemeDefinition {
6
15
  name: ThemeName;
@@ -159,7 +168,9 @@ let mediaQuery: MediaQueryList | null = null;
159
168
  let onSystemThemeChange: (() => void) | null = null;
160
169
 
161
170
  export function getSystemTheme(): ConcreteThemeName {
162
- return window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ? "dark" : "light";
171
+ return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches
172
+ ? "dark"
173
+ : "light";
163
174
  }
164
175
 
165
176
  export function getEffectiveTheme(themeName: ThemeName): ConcreteThemeName {
@@ -176,7 +187,10 @@ function updateThemeMetaTag(bgColor: string): void {
176
187
  }
177
188
  }
178
189
 
179
- export function applyTheme(themeName: ThemeName, element: HTMLElement = document.body): void {
190
+ export function applyTheme(
191
+ themeName: ThemeName,
192
+ element: HTMLElement = document.body,
193
+ ): void {
180
194
  const effectiveTheme = getEffectiveTheme(themeName);
181
195
  const theme = themes[effectiveTheme];
182
196
  if (!theme) return;
@@ -208,15 +222,15 @@ export function loadThemePreference(): ThemeName {
208
222
  export function initializeTheme(): void {
209
223
  const theme = loadThemePreference();
210
224
  applyTheme(theme);
211
-
225
+
212
226
  // Listen for system theme changes when in auto mode
213
- mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
227
+ mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
214
228
  onSystemThemeChange = () => {
215
229
  if (loadThemePreference() === "auto") {
216
230
  applyTheme("auto");
217
231
  }
218
232
  };
219
- mediaQuery.addEventListener('change', onSystemThemeChange);
233
+ mediaQuery.addEventListener("change", onSystemThemeChange);
220
234
  }
221
235
 
222
236
  export const availableThemes = [
@@ -2,113 +2,113 @@ import type { CalendarCredentials } from "./CalendarIntegration.js";
2
2
  import type { CalendarEvent } from "./CalendarInternal.js";
3
3
 
4
4
  export interface TimeseriesJsonCredentials extends CalendarCredentials {
5
- url: string;
6
- timestampField?: string;
7
- titleField?: string;
5
+ url: string;
6
+ timestampField?: string;
7
+ titleField?: string;
8
8
  }
9
9
 
10
10
  function parseTimestamp(value: unknown): Date | null {
11
- if (!value) return null;
12
- if (value instanceof Date) {
13
- return Number.isNaN(value.getTime()) ? null : value;
14
- }
15
- if (typeof value === "number") {
16
- const normalized = value < 1_000_000_000_000 ? value * 1000 : value;
17
- const date = new Date(normalized);
18
- return Number.isNaN(date.getTime()) ? null : date;
19
- }
20
- if (typeof value === "string") {
21
- const date = new Date(value);
22
- return Number.isNaN(date.getTime()) ? null : date;
23
- }
24
- return null;
11
+ if (!value) return null;
12
+ if (value instanceof Date) {
13
+ return Number.isNaN(value.getTime()) ? null : value;
14
+ }
15
+ if (typeof value === "number") {
16
+ const normalized = value < 1_000_000_000_000 ? value * 1000 : value;
17
+ const date = new Date(normalized);
18
+ return Number.isNaN(date.getTime()) ? null : date;
19
+ }
20
+ if (typeof value === "string") {
21
+ const date = new Date(value);
22
+ return Number.isNaN(date.getTime()) ? null : date;
23
+ }
24
+ return null;
25
25
  }
26
26
 
27
27
  function readStringField(
28
- record: Record<string, unknown>,
29
- field: string,
28
+ record: Record<string, unknown>,
29
+ field: string,
30
30
  ): string | undefined {
31
- const value = record[field];
32
- if (typeof value !== "string") return undefined;
33
- const trimmed = value.trim();
34
- return trimmed ? trimmed : undefined;
31
+ const value = record[field];
32
+ if (typeof value !== "string") return undefined;
33
+ const trimmed = value.trim();
34
+ return trimmed ? trimmed : undefined;
35
35
  }
36
36
 
37
37
  export async function fetchTimeseriesJsonEvents(
38
- credentials: TimeseriesJsonCredentials,
39
- color: string,
40
- calendar: string,
41
- sourceId?: string,
38
+ credentials: TimeseriesJsonCredentials,
39
+ color: string,
40
+ calendar: string,
41
+ sourceId?: string,
42
42
  ): Promise<CalendarEvent[]> {
43
- if (!credentials.url) {
44
- throw new Error("Timeseries JSON URL is required");
45
- }
46
-
47
- const response = await fetch(credentials.url, { cache: "no-store" });
48
- if (!response.ok) {
49
- throw new Error(
50
- `Failed to fetch Timeseries JSON: ${response.status} ${response.statusText}`,
51
- );
52
- }
53
-
54
- const data = await response.json();
55
- if (!Array.isArray(data)) {
56
- throw new Error("Timeseries JSON must be an array of objects");
57
- }
58
-
59
- const timestampField = credentials.timestampField || "timestamp";
60
- const titleField = credentials.titleField || "title";
61
- const defaultDurationMinutes = 1;
62
-
63
- const events: CalendarEvent[] = [];
64
-
65
- data.forEach((raw, index) => {
66
- if (!raw || typeof raw !== "object") return;
67
- const record = raw as Record<string, unknown>;
68
-
69
- const start = parseTimestamp(record[timestampField]);
70
- if (!start) return;
71
-
72
- const isAllDay = Boolean(record.isAllDay ?? record.allDay);
73
- const durationMinutes =
74
- typeof record.durationMinutes === "number" &&
75
- Number.isFinite(record.durationMinutes) &&
76
- record.durationMinutes > 0
77
- ? record.durationMinutes
78
- : defaultDurationMinutes;
79
-
80
- let end =
81
- parseTimestamp(record.end) ||
82
- parseTimestamp(record.endTimestamp) ||
83
- parseTimestamp(record.endTime);
84
-
85
- if (!end || end <= start) {
86
- const fallbackDuration = isAllDay ? 24 * 60 : durationMinutes;
87
- end = new Date(start.getTime() + fallbackDuration * 60 * 1000);
88
- }
89
-
90
- const title = readStringField(record, titleField) || calendar;
91
- const id =
92
- readStringField(record, "id") ||
93
- `${sourceId || calendar}-${start.getTime()}-${index}`;
94
-
95
- events.push({
96
- id,
97
- title,
98
- start,
99
- end,
100
- color,
101
- calendar,
102
- calendarId: sourceId,
103
- sourceId,
104
- description: readStringField(record, "description"),
105
- location: readStringField(record, "location"),
106
- url: readStringField(record, "url"),
107
- readOnly: true,
108
- isAllDay: isAllDay || undefined,
109
- visualStyle: "heatmap",
110
- });
111
- });
112
-
113
- return events;
43
+ if (!credentials.url) {
44
+ throw new Error("Timeseries JSON URL is required");
45
+ }
46
+
47
+ const response = await fetch(credentials.url, { cache: "no-store" });
48
+ if (!response.ok) {
49
+ throw new Error(
50
+ `Failed to fetch Timeseries JSON: ${response.status} ${response.statusText}`,
51
+ );
52
+ }
53
+
54
+ const data = await response.json();
55
+ if (!Array.isArray(data)) {
56
+ throw new Error("Timeseries JSON must be an array of objects");
57
+ }
58
+
59
+ const timestampField = credentials.timestampField || "timestamp";
60
+ const titleField = credentials.titleField || "title";
61
+ const defaultDurationMinutes = 1;
62
+
63
+ const events: CalendarEvent[] = [];
64
+
65
+ data.forEach((raw, index) => {
66
+ if (!raw || typeof raw !== "object") return;
67
+ const record = raw as Record<string, unknown>;
68
+
69
+ const start = parseTimestamp(record[timestampField]);
70
+ if (!start) return;
71
+
72
+ const isAllDay = Boolean(record.isAllDay ?? record.allDay);
73
+ const durationMinutes =
74
+ typeof record.durationMinutes === "number" &&
75
+ Number.isFinite(record.durationMinutes) &&
76
+ record.durationMinutes > 0
77
+ ? record.durationMinutes
78
+ : defaultDurationMinutes;
79
+
80
+ let end =
81
+ parseTimestamp(record.end) ||
82
+ parseTimestamp(record.endTimestamp) ||
83
+ parseTimestamp(record.endTime);
84
+
85
+ if (!end || end <= start) {
86
+ const fallbackDuration = isAllDay ? 24 * 60 : durationMinutes;
87
+ end = new Date(start.getTime() + fallbackDuration * 60 * 1000);
88
+ }
89
+
90
+ const title = readStringField(record, titleField) || calendar;
91
+ const id =
92
+ readStringField(record, "id") ||
93
+ `${sourceId || calendar}-${start.getTime()}-${index}`;
94
+
95
+ events.push({
96
+ id,
97
+ title,
98
+ start,
99
+ end,
100
+ color,
101
+ calendar,
102
+ calendarId: sourceId,
103
+ sourceId,
104
+ description: readStringField(record, "description"),
105
+ location: readStringField(record, "location"),
106
+ url: readStringField(record, "url"),
107
+ readOnly: true,
108
+ isAllDay: isAllDay || undefined,
109
+ visualStyle: "heatmap",
110
+ });
111
+ });
112
+
113
+ return events;
114
114
  }