joplin-plugin-my-calendar 1.2.3

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/dist/index.js ADDED
@@ -0,0 +1,3327 @@
1
+ if (typeof module === 'undefined') { var module = { exports: {} }; }
2
+ if (typeof exports === 'undefined') { var exports = module.exports; }
3
+
4
+ /******/ (() => { // webpackBootstrap
5
+ /******/ "use strict";
6
+ /******/ var __webpack_modules__ = ({
7
+
8
+ /***/ "./src/index.ts"
9
+ /*!**********************!*\
10
+ !*** ./src/index.ts ***!
11
+ \**********************/
12
+ (__unused_webpack_module, __unused_webpack_exports, __webpack_require__) {
13
+
14
+
15
+ // src/index.ts
16
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
18
+ return new (P || (P = Promise))(function (resolve, reject) {
19
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
22
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
23
+ });
24
+ };
25
+ // NOT ONE import/require('api').
26
+ // Only take API from a global object that plays Joplin Runner.
27
+ (function bootstrap() {
28
+ const j = globalThis.joplin || window.joplin;
29
+ if (!j) {
30
+ // We are not in the plugin-wounder (or Runner have not yet thrown Joplin)-we do nothing.
31
+ console.log('[MyCalendar] no plugin API here (renderer).');
32
+ return;
33
+ }
34
+ try {
35
+ // IMPORTANT: REQUIRE PLUGINMAIN only after we've been convinced that there was a joplin.
36
+ // This way webpack will not overload the addiction earlier (and will not break RENDER).
37
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
38
+ const runPlugin = (__webpack_require__(/*! ./main/pluginMain */ "./src/main/pluginMain.ts")["default"]);
39
+ j.plugins.register({
40
+ onStart: () => __awaiter(this, void 0, void 0, function* () {
41
+ try {
42
+ console.log('[MyCalendar] onStart (runner)');
43
+ yield runPlugin(j);
44
+ }
45
+ catch (e) {
46
+ console.error('[MyCalendar] onStart error (caught):', e);
47
+ }
48
+ }),
49
+ });
50
+ }
51
+ catch (e) {
52
+ console.error('[MyCalendar] failed to start plugin', e);
53
+ }
54
+ })();
55
+
56
+
57
+ /***/ },
58
+
59
+ /***/ "./src/main/parsers/eventParser.ts"
60
+ /*!*****************************************!*\
61
+ !*** ./src/main/parsers/eventParser.ts ***!
62
+ \*****************************************/
63
+ (__unused_webpack_module, exports) {
64
+
65
+
66
+ // src/main/parsers/eventParser.ts
67
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
68
+ exports.parseEventsFromBody = parseEventsFromBody;
69
+ const EVENT_BLOCK_RE = /(?:^|\r?\n)[ \t]*```mycalendar-event[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*```(?=\r?\n|$)/g;
70
+ const DAY_MS = 24 * 60 * 60 * 1000;
71
+ // Map: MO..SU -> 0..6 (Mon..Sun)
72
+ const WD_MAP = { MO: 0, TU: 1, WE: 2, TH: 3, FR: 4, SA: 5, SU: 6 };
73
+ function parseKeyVal(line) {
74
+ const m = line.match(/^\s*([a-zA-Z_]+)\s*:\s*(.+)\s*$/);
75
+ return m ? [m[1].toLowerCase(), m[2]] : null;
76
+ }
77
+ function parseByWeekdays(v) {
78
+ const arr = v.split(',').map(s => s.trim().toUpperCase()).filter(Boolean);
79
+ const out = [];
80
+ for (const t of arr)
81
+ if (t in WD_MAP)
82
+ out.push(WD_MAP[t]);
83
+ return out.length ? out : undefined;
84
+ }
85
+ function parseIntSafe(v) {
86
+ const n = v ? parseInt(v, 10) : NaN;
87
+ return Number.isFinite(n) && n >= 1 ? n : undefined;
88
+ }
89
+ function parseByMonthDay(v) {
90
+ const n = parseInt(v, 10);
91
+ return Number.isFinite(n) && n >= 1 && n <= 31 ? n : undefined;
92
+ }
93
+ function parseAllDayBool(v) {
94
+ const vv = v.trim().toLowerCase();
95
+ if (vv === 'true' || vv === '1' || vv === 'yes')
96
+ return true;
97
+ if (vv === 'false' || vv === '0' || vv === 'no')
98
+ return false;
99
+ return undefined;
100
+ }
101
+ function normalizeTz(z) {
102
+ if (!z)
103
+ return undefined;
104
+ const tz = z.trim();
105
+ if (!tz)
106
+ return undefined;
107
+ try {
108
+ // If the timezone is not IANA, Intl will throw RangeError
109
+ new Intl.DateTimeFormat('en-US', { timeZone: tz }).format(new Date());
110
+ return tz;
111
+ }
112
+ catch (_a) {
113
+ return undefined;
114
+ }
115
+ }
116
+ // "2025-08-12 10:00:00-04:00" | "2025-08-12T10:00:00-04:00" | Without offset (з tz)
117
+ function parseDateTimeToUTC(text, tz) {
118
+ var _a;
119
+ const trimmed = text.trim();
120
+ if (!trimmed)
121
+ return null;
122
+ // Explicit offset or Z -> trust Date parsing (absolute moment)
123
+ if (/[+-]\d{2}:?\d{2}$/.test(trimmed) || /Z$/i.test(trimmed)) {
124
+ const canon = trimmed
125
+ .replace(' ', 'T')
126
+ .replace(/([+-]\d{2})(\d{2})$/, '$1:$2'); // +0300 -> +03:00
127
+ const d = new Date(canon);
128
+ return isNaN(d.getTime()) ? null : d.getTime();
129
+ }
130
+ // No offset: parse wall-clock components
131
+ const m = trimmed.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})[ T]([0-9]{2}):([0-9]{2})(?::([0-9]{2}))?$/);
132
+ if (!m) {
133
+ const d = new Date(trimmed.replace(' ', 'T'));
134
+ return isNaN(d.getTime()) ? null : d.getTime();
135
+ }
136
+ const y = Number(m[1]);
137
+ const mo = Number(m[2]);
138
+ const da = Number(m[3]);
139
+ const hh = Number(m[4]);
140
+ const mi = Number(m[5]);
141
+ const ss = Number((_a = m[6]) !== null && _a !== void 0 ? _a : '0');
142
+ // If tz is given but is invalid - DO NOT try to convert, return null (and the event will be skipped)
143
+ const safeTz = normalizeTz(tz);
144
+ // If tz is not provided -> interpret as device-local time (no conversion)
145
+ if (!safeTz) {
146
+ if (tz && tz.trim())
147
+ return null;
148
+ const d = new Date(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${String(ss).padStart(2, '0')}`);
149
+ return isNaN(d.getTime()) ? null : d.getTime();
150
+ }
151
+ // tz provided without offset: interpret components as wall-clock time in that tz, then convert to UTC
152
+ const wallUtc = Date.UTC(y, mo - 1, da, hh, mi, ss);
153
+ const tzOffsetMs = (utcTs, zone) => {
154
+ try {
155
+ const fmt = new Intl.DateTimeFormat('en-US', {
156
+ timeZone: zone,
157
+ year: 'numeric',
158
+ month: '2-digit',
159
+ day: '2-digit',
160
+ hour: '2-digit',
161
+ minute: '2-digit',
162
+ second: '2-digit',
163
+ hour12: false,
164
+ });
165
+ const parts = fmt.formatToParts(new Date(utcTs)).reduce((a, p) => {
166
+ if (p.type !== 'literal')
167
+ a[p.type] = p.value;
168
+ return a;
169
+ }, {});
170
+ const yy = Number(parts.year);
171
+ const mm = Number(parts.month) - 1;
172
+ const dd = Number(parts.day);
173
+ const h2 = Number(parts.hour);
174
+ const m2 = Number(parts.minute);
175
+ const s2 = Number(parts.second);
176
+ const asUtc = Date.UTC(yy, mm, dd, h2, m2, s2);
177
+ return asUtc - utcTs;
178
+ }
179
+ catch (_a) {
180
+ return null;
181
+ }
182
+ };
183
+ const off1 = tzOffsetMs(wallUtc, safeTz);
184
+ if (off1 == null)
185
+ return null;
186
+ let utc = wallUtc - off1;
187
+ const off2 = tzOffsetMs(utc, safeTz);
188
+ if (off2 == null)
189
+ return null;
190
+ // second-pass for DST boundary correctness
191
+ if (off2 !== off1)
192
+ utc = wallUtc - off2;
193
+ return utc;
194
+ }
195
+ function parseRepeatFreq(v) {
196
+ const vv = v.trim().toLowerCase();
197
+ if (vv === 'daily' || vv === 'weekly' || vv === 'monthly' || vv === 'yearly' || vv === 'none')
198
+ return vv;
199
+ return undefined;
200
+ }
201
+ function parseEventsFromBody(noteId, titleFallback, body) {
202
+ var _a, _b, _c, _d, _e;
203
+ const out = [];
204
+ let m;
205
+ // Defensive: avoid leaking RegExp.lastIndex across calls (EVENT_BLOCK_RE is /g)
206
+ EVENT_BLOCK_RE.lastIndex = 0;
207
+ while ((m = EVENT_BLOCK_RE.exec(body)) !== null) {
208
+ // IMPORTANT: reset per block (do not leak across blocks)
209
+ const block = m[1];
210
+ const lines = block.split('\n').map(l => l.trim()).filter(Boolean);
211
+ const fields = {};
212
+ for (const line of lines) {
213
+ const kv = parseKeyVal(line);
214
+ if (!kv)
215
+ continue;
216
+ const [k, v] = kv;
217
+ // store raw string values; interpret later (order-independent)
218
+ fields[k] = v;
219
+ }
220
+ const title = (((_a = fields.title) === null || _a === void 0 ? void 0 : _a.trim()) ? fields.title.trim() : titleFallback);
221
+ const description = fields.description;
222
+ const location = fields.location;
223
+ const color = fields.color;
224
+ const startText = fields.start;
225
+ const endText = fields.end;
226
+ const tz = (_b = fields.tz) === null || _b === void 0 ? void 0 : _b.trim();
227
+ const repeat = (_d = parseRepeatFreq((_c = fields.repeat) !== null && _c !== void 0 ? _c : '')) !== null && _d !== void 0 ? _d : 'none';
228
+ const repeatInterval = (_e = parseIntSafe(fields.repeat_interval)) !== null && _e !== void 0 ? _e : 1;
229
+ const byWeekdays = fields.byweekday ? parseByWeekdays(fields.byweekday) : undefined;
230
+ const byMonthDay = fields.bymonthday ? parseByMonthDay(fields.bymonthday) : undefined;
231
+ const allDay = fields.all_day ? parseAllDayBool(fields.all_day) : undefined;
232
+ if (!startText)
233
+ continue;
234
+ const startUtc = parseDateTimeToUTC(startText, tz);
235
+ if (startUtc == null)
236
+ continue;
237
+ let endUtc;
238
+ if (endText) {
239
+ const e = parseDateTimeToUTC(endText, tz);
240
+ if (e != null)
241
+ endUtc = e;
242
+ }
243
+ // repeat_until parsed AFTER tz is known (order-independent)
244
+ let repeatUntilUtc;
245
+ if (fields.repeat_until) {
246
+ const u = parseDateTimeToUTC(fields.repeat_until, tz);
247
+ if (u != null)
248
+ repeatUntilUtc = u;
249
+ }
250
+ if (allDay) {
251
+ if (endUtc != null) {
252
+ // ICS all-day uses exclusive end -> make it inclusive for UI
253
+ if (endUtc > startUtc)
254
+ endUtc = endUtc - 1;
255
+ else
256
+ endUtc = startUtc + DAY_MS - 1; // insurance
257
+ }
258
+ else {
259
+ // if end not provided, treat as one-day all-day
260
+ endUtc = startUtc + DAY_MS - 1;
261
+ }
262
+ }
263
+ out.push({
264
+ id: noteId,
265
+ title,
266
+ description,
267
+ location,
268
+ color,
269
+ startUtc,
270
+ endUtc,
271
+ tz,
272
+ startText,
273
+ endText,
274
+ repeat,
275
+ repeatInterval,
276
+ repeatUntilUtc,
277
+ byWeekdays,
278
+ byMonthDay,
279
+ allDay,
280
+ });
281
+ }
282
+ return out;
283
+ }
284
+
285
+
286
+ /***/ },
287
+
288
+ /***/ "./src/main/parsers/icsParser.ts"
289
+ /*!***************************************!*\
290
+ !*** ./src/main/parsers/icsParser.ts ***!
291
+ \***************************************/
292
+ (__unused_webpack_module, exports, __webpack_require__) {
293
+
294
+
295
+ // src/main/parsers/icsParser.ts
296
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
297
+ exports.unfoldIcsLines = unfoldIcsLines;
298
+ exports.unescapeIcsText = unescapeIcsText;
299
+ exports.parseLineValue = parseLineValue;
300
+ exports.normalizeRepeatFreq = normalizeRepeatFreq;
301
+ exports.parseRRule = parseRRule;
302
+ exports.parseMyCalKeyValueText = parseMyCalKeyValueText;
303
+ exports.parseIcs = parseIcs;
304
+ exports.parseImportText = parseImportText;
305
+ const dateTimeUtils_1 = __webpack_require__(/*! ../utils/dateTimeUtils */ "./src/main/utils/dateTimeUtils.ts");
306
+ function unfoldIcsLines(ics) {
307
+ const raw = ics.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
308
+ const out = [];
309
+ for (const line of raw) {
310
+ if (!line)
311
+ continue;
312
+ // folded line: starts with space/tab
313
+ if ((line.startsWith(' ') || line.startsWith('\t')) && out.length) {
314
+ out[out.length - 1] += line.slice(1);
315
+ }
316
+ else {
317
+ out.push(line);
318
+ }
319
+ }
320
+ return out;
321
+ }
322
+ function unescapeIcsText(s) {
323
+ // Protect escaped backslashes first, so "\\\\n" becomes literal "\n" not newline.
324
+ const BS = '\u0000';
325
+ return s
326
+ .replace(/\\\\/g, BS)
327
+ .replace(/\\n/gi, '\n')
328
+ .replace(/\\,/g, ',')
329
+ .replace(/\\;/g, ';')
330
+ .replace(new RegExp(BS, 'g'), '\\');
331
+ }
332
+ function parseLineValue(line) {
333
+ const i = line.indexOf(':');
334
+ if (i < 0)
335
+ return null;
336
+ const left = line.slice(0, i);
337
+ const value = line.slice(i + 1);
338
+ const parts = left.split(';').map(p => p.trim()).filter(Boolean);
339
+ const key = (parts[0] || '').toUpperCase();
340
+ const params = {};
341
+ for (let j = 1; j < parts.length; j++) {
342
+ const p = parts[j];
343
+ const eq = p.indexOf('=');
344
+ if (eq > 0) {
345
+ const k = p.slice(0, eq).toUpperCase();
346
+ let v = p.slice(eq + 1).trim();
347
+ // strip optional quotes: TZID="America/Toronto"
348
+ if (v.startsWith('"') && v.endsWith('"') && v.length >= 2)
349
+ v = v.slice(1, -1);
350
+ params[k] = v;
351
+ }
352
+ }
353
+ return { key, value, params };
354
+ }
355
+ function normalizeRepeatFreq(freq) {
356
+ if (!freq)
357
+ return undefined;
358
+ const f = freq.toLowerCase();
359
+ if (f === 'none')
360
+ return 'none';
361
+ if (f === 'daily' || f === 'weekly' || f === 'monthly' || f === 'yearly')
362
+ return f;
363
+ return undefined;
364
+ }
365
+ function hasMeaningfulEvent(ev) {
366
+ return !!(ev.uid ||
367
+ ev.title ||
368
+ ev.start ||
369
+ ev.end ||
370
+ ev.description ||
371
+ ev.location ||
372
+ ev.color ||
373
+ ev.repeat ||
374
+ ev.byweekday ||
375
+ ev.bymonthday ||
376
+ ev.repeat_until ||
377
+ (ev.valarms && ev.valarms.length));
378
+ }
379
+ function parseRRule(rrule) {
380
+ if (!rrule)
381
+ return {};
382
+ const parts = rrule.split(';').map(s => s.trim()).filter(Boolean);
383
+ const map = {};
384
+ for (const p of parts) {
385
+ const i = p.indexOf('=');
386
+ if (i > 0)
387
+ map[p.slice(0, i).toUpperCase()] = p.slice(i + 1);
388
+ }
389
+ const out = {};
390
+ const freq = normalizeRepeatFreq(map['FREQ']);
391
+ if (freq)
392
+ out.repeat = freq;
393
+ const interval = map['INTERVAL'] ? parseInt(map['INTERVAL'], 10) : NaN;
394
+ if (Number.isFinite(interval) && interval >= 1)
395
+ out.repeat_interval = interval;
396
+ // UNTIL is UTC in many feeds; keep as "+00:00" when Z
397
+ const until = map['UNTIL'] ? (0, dateTimeUtils_1.icsDateToMyCalText)(map['UNTIL']) : undefined;
398
+ if (until)
399
+ out.repeat_until = until;
400
+ if (map['BYDAY'])
401
+ out.byweekday = map['BYDAY'].trim();
402
+ if (map['BYMONTHDAY'])
403
+ out.bymonthday = map['BYMONTHDAY'].trim();
404
+ return out;
405
+ }
406
+ function stripInlineComment(line) {
407
+ const i = line.indexOf('#');
408
+ if (i < 0)
409
+ return line;
410
+ const before = line.slice(0, i);
411
+ if (/\s$/.test(before))
412
+ return before.trimEnd();
413
+ return line;
414
+ }
415
+ function parseMyCalKeyValueText(text) {
416
+ var _a;
417
+ const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
418
+ const events = [];
419
+ let cur = {};
420
+ const flush = () => {
421
+ const hasAny = !!cur.uid || !!cur.title || !!cur.start || !!cur.end || !!cur.description || !!cur.location || !!cur.color ||
422
+ !!cur.repeat || !!cur.byweekday || !!cur.bymonthday || !!cur.repeat_until || !!(cur.valarms && cur.valarms.length);
423
+ if (hasAny) {
424
+ events.push(cur);
425
+ cur = {};
426
+ }
427
+ };
428
+ for (const raw of lines) {
429
+ const line0 = stripInlineComment(raw).trim();
430
+ if (!line0) {
431
+ flush();
432
+ continue;
433
+ }
434
+ if (line0 === '---') {
435
+ flush();
436
+ continue;
437
+ }
438
+ const m = line0.match(/^\s*([a-zA-Z_]+)\s*:\s*(.+)\s*$/);
439
+ if (!m)
440
+ continue;
441
+ const k = m[1].toLowerCase();
442
+ const v = m[2].trim();
443
+ if (k === 'uid')
444
+ cur.uid = v;
445
+ else if (k === 'recurrence_id')
446
+ cur.recurrence_id = v;
447
+ else if (k === 'title' || k === 'summary')
448
+ cur.title = v;
449
+ else if (k === 'description')
450
+ cur.description = v;
451
+ else if (k === 'location')
452
+ cur.location = v;
453
+ else if (k === 'color')
454
+ cur.color = v;
455
+ else if (k === 'start')
456
+ cur.start = v;
457
+ else if (k === 'end')
458
+ cur.end = v;
459
+ else if (k === 'tz')
460
+ cur.tz = v;
461
+ else if (k === 'valarm') {
462
+ try {
463
+ const obj = JSON.parse(v);
464
+ if (obj && typeof obj === 'object' && typeof obj.trigger === 'string') {
465
+ ((_a = cur.valarms) !== null && _a !== void 0 ? _a : (cur.valarms = [])).push(obj);
466
+ }
467
+ }
468
+ catch (_b) {
469
+ // ignore
470
+ }
471
+ }
472
+ else if (k === 'repeat')
473
+ cur.repeat = normalizeRepeatFreq(v) || 'none';
474
+ else if (k === 'repeat_interval') {
475
+ const n = parseInt(v, 10);
476
+ if (Number.isFinite(n) && n >= 1)
477
+ cur.repeat_interval = n;
478
+ }
479
+ else if (k === 'repeat_until')
480
+ cur.repeat_until = v;
481
+ else if (k === 'byweekday')
482
+ cur.byweekday = v;
483
+ else if (k === 'bymonthday')
484
+ cur.bymonthday = v;
485
+ }
486
+ flush();
487
+ return events;
488
+ }
489
+ function parseIcs(ics) {
490
+ var _a;
491
+ const lines = unfoldIcsLines(ics);
492
+ const events = [];
493
+ let cur = null;
494
+ let curAlarm = null;
495
+ for (const line of lines) {
496
+ const L = line.trim();
497
+ if (!L)
498
+ continue;
499
+ if (L === 'BEGIN:VEVENT') {
500
+ cur = {};
501
+ curAlarm = null;
502
+ continue;
503
+ }
504
+ if (L === 'END:VEVENT') {
505
+ curAlarm = null;
506
+ if (cur && hasMeaningfulEvent(cur))
507
+ events.push(cur);
508
+ cur = null;
509
+ continue;
510
+ }
511
+ if (!cur)
512
+ continue;
513
+ if (L === 'BEGIN:VALARM') {
514
+ curAlarm = { trigger: '' };
515
+ continue;
516
+ }
517
+ if (L === 'END:VALARM') {
518
+ if (curAlarm && curAlarm.trigger) {
519
+ ((_a = cur.valarms) !== null && _a !== void 0 ? _a : (cur.valarms = [])).push(curAlarm);
520
+ }
521
+ curAlarm = null;
522
+ continue;
523
+ }
524
+ const parsed = parseLineValue(L);
525
+ if (!parsed)
526
+ continue;
527
+ const { key, value, params } = parsed;
528
+ if (curAlarm) {
529
+ if (key === 'TRIGGER') {
530
+ curAlarm.trigger = value.trim();
531
+ const rel = (params['RELATED'] || '').toUpperCase();
532
+ if (rel === 'START' || rel === 'END')
533
+ curAlarm.related = rel;
534
+ }
535
+ else if (key === 'ACTION') {
536
+ curAlarm.action = value.trim();
537
+ }
538
+ else if (key === 'DESCRIPTION') {
539
+ curAlarm.description = unescapeIcsText(value);
540
+ }
541
+ else if (key === 'SUMMARY') {
542
+ curAlarm.summary = unescapeIcsText(value);
543
+ }
544
+ else if (key === 'REPEAT') {
545
+ const n = parseInt(value.trim(), 10);
546
+ if (Number.isFinite(n))
547
+ curAlarm.repeat = n;
548
+ }
549
+ else if (key === 'DURATION') {
550
+ curAlarm.duration = value.trim();
551
+ }
552
+ continue;
553
+ }
554
+ const isDateOnly = (value, params) => (params['VALUE'] || '').toUpperCase() === 'DATE' || /^\d{8}$/.test(value.trim());
555
+ if (key === 'UID')
556
+ cur.uid = value.trim();
557
+ else if (key === 'SUMMARY')
558
+ cur.title = unescapeIcsText(value);
559
+ else if (key === 'DESCRIPTION')
560
+ cur.description = unescapeIcsText(value);
561
+ else if (key === 'LOCATION')
562
+ cur.location = unescapeIcsText(value);
563
+ else if (key === 'X-COLOR')
564
+ cur.color = value.trim();
565
+ else if (key === 'DTSTART') {
566
+ cur.start = (0, dateTimeUtils_1.icsDateToMyCalText)(value) || value.trim();
567
+ if (isDateOnly(value, params))
568
+ cur.all_day = true;
569
+ if (params['TZID'] && !cur.tz)
570
+ cur.tz = params['TZID'];
571
+ }
572
+ else if (key === 'DTEND') {
573
+ cur.end = (0, dateTimeUtils_1.icsDateToMyCalText)(value) || value.trim();
574
+ if (isDateOnly(value, params))
575
+ cur.all_day = true;
576
+ if (params['TZID'] && !cur.tz)
577
+ cur.tz = params['TZID'];
578
+ }
579
+ else if (key === 'RRULE') {
580
+ Object.assign(cur, parseRRule(value.trim()));
581
+ }
582
+ else if (key === 'RECURRENCE-ID') {
583
+ const ridVal = value.trim();
584
+ const tzid = params['TZID'];
585
+ const valType = (params['VALUE'] || '').toUpperCase(); // DATE / DATE-TIME
586
+ if (valType === 'DATE') {
587
+ cur.recurrence_id = `DATE:${ridVal}`;
588
+ }
589
+ else if (tzid) {
590
+ cur.recurrence_id = `${tzid}:${ridVal}`;
591
+ if (!cur.tz)
592
+ cur.tz = tzid;
593
+ }
594
+ else {
595
+ cur.recurrence_id = ridVal;
596
+ }
597
+ }
598
+ }
599
+ return events;
600
+ }
601
+ function parseImportText(text) {
602
+ const t = text.trim();
603
+ if (/BEGIN:VCALENDAR/i.test(t) || /BEGIN:VEVENT/i.test(t))
604
+ return parseIcs(text);
605
+ return parseMyCalKeyValueText(text);
606
+ }
607
+
608
+
609
+ /***/ },
610
+
611
+ /***/ "./src/main/pluginMain.ts"
612
+ /*!********************************!*\
613
+ !*** ./src/main/pluginMain.ts ***!
614
+ \********************************/
615
+ (__unused_webpack_module, exports, __webpack_require__) {
616
+
617
+
618
+ // src/main/pluginMain.ts
619
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
620
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
621
+ return new (P || (P = Promise))(function (resolve, reject) {
622
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
623
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
624
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
625
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
626
+ });
627
+ };
628
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
629
+ exports["default"] = runPlugin;
630
+ const calendarView_1 = __webpack_require__(/*! ./views/calendarView */ "./src/main/views/calendarView.ts");
631
+ const eventsCache_1 = __webpack_require__(/*! ./services/eventsCache */ "./src/main/services/eventsCache.ts");
632
+ const panelController_1 = __webpack_require__(/*! ./uiBridge/panelController */ "./src/main/uiBridge/panelController.ts");
633
+ const settings_1 = __webpack_require__(/*! ./settings/settings */ "./src/main/settings/settings.ts");
634
+ const uiSettings_1 = __webpack_require__(/*! ./uiBridge/uiSettings */ "./src/main/uiBridge/uiSettings.ts");
635
+ const dateUtils_1 = __webpack_require__(/*! ./utils/dateUtils */ "./src/main/utils/dateUtils.ts");
636
+ const logger_1 = __webpack_require__(/*! ./utils/logger */ "./src/main/utils/logger.ts");
637
+ function expandOccurrencesInRange(ev, fromUtc, toUtc) {
638
+ var _a, _b, _c, _d, _e;
639
+ // Invariant: recurring events must have timezone
640
+ const tz = ev.tz || Intl.DateTimeFormat().resolvedOptions().timeZone;
641
+ if (ev.repeat !== 'none' && !ev.tz) {
642
+ (0, logger_1.warn)('occurrence', 'Recurring event has no timezone; using device timezone:', { tz, title: ev.title, id: ev.id });
643
+ }
644
+ const dur = ((_a = ev.endUtc) !== null && _a !== void 0 ? _a : ev.startUtc) - ev.startUtc;
645
+ const push = (start, out) => {
646
+ if (start > toUtc)
647
+ return false;
648
+ const end = dur ? start + dur : start;
649
+ if (end < fromUtc)
650
+ return true;
651
+ out.push(Object.assign(Object.assign({}, ev), { occurrenceId: `${ev.id}#${start}`, startUtc: start, endUtc: dur ? end : undefined }));
652
+ return true;
653
+ };
654
+ if (ev.repeat === 'none') {
655
+ const end = (_b = ev.endUtc) !== null && _b !== void 0 ? _b : ev.startUtc;
656
+ if (end < fromUtc || ev.startUtc > toUtc)
657
+ return [];
658
+ return [Object.assign(Object.assign({}, ev), { occurrenceId: `${ev.id}#${ev.startUtc}` })];
659
+ }
660
+ const out = [];
661
+ const base = new Date(ev.startUtc);
662
+ const baseY = base.getUTCFullYear(), baseM = base.getUTCMonth(), baseD = base.getUTCDate();
663
+ const baseH = base.getUTCHours(), baseMin = base.getUTCMinutes(), baseS = base.getUTCSeconds();
664
+ const until = (_c = ev.repeatUntilUtc) !== null && _c !== void 0 ? _c : toUtc;
665
+ const step = Math.max(1, ev.repeatInterval || 1);
666
+ if (ev.repeat === 'daily') {
667
+ const from2 = fromUtc - Math.max(0, dur);
668
+ let k = Math.floor((from2 - ev.startUtc) / (dateUtils_1.DAY_MS * step));
669
+ if (ev.startUtc + k * step * dateUtils_1.DAY_MS < from2)
670
+ k++;
671
+ if (k < 0)
672
+ k = 0;
673
+ for (;; k++) {
674
+ const start = ev.startUtc + k * step * dateUtils_1.DAY_MS;
675
+ if (start > until)
676
+ break;
677
+ if (!push(start, out))
678
+ break;
679
+ }
680
+ return out;
681
+ }
682
+ if (ev.repeat === 'weekly') {
683
+ const tz = ev.tz;
684
+ if (!tz) {
685
+ // recurring without tz cannot be expanded safely
686
+ return out; // or continue + warning
687
+ }
688
+ const baseLocal = (0, dateUtils_1.parseYmdHmsLocal)(ev.startText);
689
+ const baseWd = (0, dateUtils_1.weekdayMon0)(baseLocal.Y, baseLocal.M, baseLocal.D);
690
+ const list = ev.byWeekdays && ev.byWeekdays.length ? ev.byWeekdays : [baseWd];
691
+ const step = Math.max(1, ev.repeatInterval || 1);
692
+ const from2 = fromUtc - Math.max(0, dur);
693
+ // Monday of base week (local date)
694
+ const mondayBase = (0, dateUtils_1.addDaysYMD)(baseLocal.Y, baseLocal.M, baseLocal.D, -baseWd);
695
+ // Start from the week containing "fromUtc" in local TZ
696
+ const fromLocal = (0, dateUtils_1.getPartsInTz)(fromUtc, tz);
697
+ const fromWd = (0, dateUtils_1.weekdayMon0)(fromLocal.Y, fromLocal.M, fromLocal.D);
698
+ const mondayFrom = (0, dateUtils_1.addDaysYMD)(fromLocal.Y, fromLocal.M, fromLocal.D, -fromWd);
699
+ // Compute week index offset (in local calendar days)
700
+ const mondayBaseMs = Date.UTC(mondayBase.Y, mondayBase.M - 1, mondayBase.D);
701
+ const mondayFromMs = Date.UTC(mondayFrom.Y, mondayFrom.M - 1, mondayFrom.D);
702
+ let weeksDiff = Math.floor((mondayFromMs - mondayBaseMs) / (7 * dateUtils_1.DAY_MS));
703
+ if (weeksDiff < 0)
704
+ weeksDiff = 0;
705
+ let weekIndex = Math.floor(weeksDiff / step);
706
+ const until = Math.min(toUtc, (_d = ev.repeatUntilUtc) !== null && _d !== void 0 ? _d : Number.POSITIVE_INFINITY);
707
+ for (;;) {
708
+ const weekStart = (0, dateUtils_1.addDaysYMD)(mondayBase.Y, mondayBase.M, mondayBase.D, weekIndex * 7 * step);
709
+ for (const wd of list) {
710
+ const occ = (0, dateUtils_1.addDaysYMD)(weekStart.Y, weekStart.M, weekStart.D, wd);
711
+ const start = (0, dateUtils_1.zonedTimeToUtcMs)(occ.Y, occ.M, occ.D, baseLocal.h, baseLocal.m, baseLocal.sec, tz);
712
+ if (start < from2 || start > until)
713
+ continue;
714
+ if (!push(start, out))
715
+ return out;
716
+ }
717
+ // stop condition: next week start beyond range
718
+ const nextWeek = (0, dateUtils_1.addDaysYMD)(mondayBase.Y, mondayBase.M, mondayBase.D, (weekIndex + 1) * 7 * step);
719
+ const nextWeekStartUtc = (0, dateUtils_1.zonedTimeToUtcMs)(nextWeek.Y, nextWeek.M, nextWeek.D, 0, 0, 0, tz);
720
+ if (nextWeekStartUtc > toUtc)
721
+ break;
722
+ weekIndex++;
723
+ }
724
+ return out;
725
+ }
726
+ if (ev.repeat === 'monthly') {
727
+ const dom = (_e = ev.byMonthDay) !== null && _e !== void 0 ? _e : baseD;
728
+ const y = baseY;
729
+ let m = baseM;
730
+ let cursor = Date.UTC(y, m, dom, baseH, baseMin, baseS);
731
+ while (cursor < ev.startUtc) {
732
+ m += 1;
733
+ cursor = Date.UTC(y + Math.floor(m / 12), (m % 12 + 12) % 12, dom, baseH, baseMin, baseS);
734
+ }
735
+ for (;;) {
736
+ if (cursor > until)
737
+ break;
738
+ const cd = new Date(cursor);
739
+ // Ensure month is what we expect (no overflow like Jan 31 -> Feb 3)
740
+ const expectedM = (m % 12 + 12) % 12;
741
+ if (cd.getUTCMonth() === expectedM) {
742
+ if (!push(cursor, out))
743
+ break;
744
+ }
745
+ m += step;
746
+ cursor = Date.UTC(y + Math.floor(m / 12), (m % 12 + 12) % 12, dom, baseH, baseMin, baseS);
747
+ if (cursor > toUtc && cursor > until)
748
+ break;
749
+ }
750
+ return out;
751
+ }
752
+ if (ev.repeat === 'yearly') {
753
+ let y = baseY;
754
+ let cursor = Date.UTC(y, baseM, baseD, baseH, baseMin, baseS);
755
+ while (cursor < ev.startUtc) {
756
+ y += 1;
757
+ cursor = Date.UTC(y, baseM, baseD, baseH, baseMin, baseS);
758
+ }
759
+ while (cursor < fromUtc) {
760
+ y += (step || 1);
761
+ cursor = Date.UTC(y, baseM, baseD, baseH, baseMin, baseS);
762
+ }
763
+ for (;;) {
764
+ if (cursor > until)
765
+ break;
766
+ const dt = new Date(cursor);
767
+ // Ensure month is still February (or whatever baseM was) - handles Leap Year Feb 29
768
+ if (dt.getUTCMonth() === baseM) {
769
+ if (!push(cursor, out))
770
+ break;
771
+ }
772
+ y += (step || 1);
773
+ cursor = Date.UTC(y, baseM, baseD, baseH, baseMin, baseS);
774
+ if (cursor > toUtc && cursor > until)
775
+ break;
776
+ }
777
+ return out;
778
+ }
779
+ return out;
780
+ }
781
+ function expandAllInRange(evs, fromUtc, toUtc) {
782
+ const out = [];
783
+ for (const ev of evs)
784
+ out.push(...expandOccurrencesInRange(ev, fromUtc, toUtc));
785
+ out.sort((a, b) => a.startUtc - b.startUtc || a.occurrenceId.localeCompare(b.occurrenceId));
786
+ return out;
787
+ }
788
+ function pad2(n) {
789
+ return String(n).padStart(2, '0');
790
+ }
791
+ function fmtICS(tsUtc) {
792
+ const d = new Date(tsUtc);
793
+ return d.getUTCFullYear().toString() + pad2(d.getUTCMonth() + 1) + pad2(d.getUTCDate()) +
794
+ 'T' + pad2(d.getUTCHours()) + pad2(d.getUTCMinutes()) + pad2(d.getUTCSeconds()) + 'Z';
795
+ }
796
+ function icsEscape(s) {
797
+ return (s || '').replace(/\\/g, '\\\\').replace(/;/g, '\\;').replace(/,/g, '\\,').replace(/\n/g, '\\n');
798
+ }
799
+ function buildICS(events, prodId = '-//MyCalendar//Joplin//EN') {
800
+ const lines = ['BEGIN:VCALENDAR', 'VERSION:2.0', `PRODID:${prodId}`, 'CALSCALE:GREGORIAN'];
801
+ for (const ev of events) {
802
+ const uid = ev.occurrenceId || `${ev.id}@mycalendar`;
803
+ lines.push('BEGIN:VEVENT');
804
+ lines.push(`UID:${icsEscape(uid)}`);
805
+ lines.push(`DTSTAMP:${fmtICS(Date.now())}`);
806
+ lines.push(`DTSTART:${fmtICS(ev.startUtc)}`);
807
+ if (ev.endUtc)
808
+ lines.push(`DTEND:${fmtICS(ev.endUtc)}`);
809
+ lines.push(`SUMMARY:${icsEscape(ev.title || 'Event')}`);
810
+ if (ev.location)
811
+ lines.push(`LOCATION:${icsEscape(ev.location)}`);
812
+ if (ev.description)
813
+ lines.push(`DESCRIPTION:${icsEscape(ev.description)}`);
814
+ if (ev.color)
815
+ lines.push(`X-COLOR:${icsEscape(ev.color)}`);
816
+ lines.push('END:VEVENT');
817
+ }
818
+ lines.push('END:VCALENDAR');
819
+ return lines.join('\r\n');
820
+ }
821
+ function getPanelsAny(joplin) {
822
+ var _a;
823
+ return (_a = joplin === null || joplin === void 0 ? void 0 : joplin.views) === null || _a === void 0 ? void 0 : _a.panels;
824
+ }
825
+ function safePostMessage(joplin, panelId, message) {
826
+ return __awaiter(this, void 0, void 0, function* () {
827
+ var _a, _b;
828
+ const pm = (_b = (_a = joplin === null || joplin === void 0 ? void 0 : joplin.views) === null || _a === void 0 ? void 0 : _a.panels) === null || _b === void 0 ? void 0 : _b.postMessage;
829
+ if (typeof pm === 'function')
830
+ yield pm(panelId, message);
831
+ });
832
+ }
833
+ // Ensure UI always receives current settings when the webview (re)initializes.
834
+ function registerUiMessageHandlers(joplin, panelId) {
835
+ return __awaiter(this, void 0, void 0, function* () {
836
+ var _a;
837
+ const onMessage = (_a = getPanelsAny(joplin)) === null || _a === void 0 ? void 0 : _a.onMessage;
838
+ if (typeof onMessage !== 'function')
839
+ return;
840
+ yield onMessage(panelId, (msg) => __awaiter(this, void 0, void 0, function* () {
841
+ // Joplin sometimes wraps payload as { message: <payload> }
842
+ if (msg && typeof msg === 'object' && 'message' in msg && msg.message) {
843
+ msg = msg.message;
844
+ }
845
+ if (!msg || !msg.name)
846
+ return;
847
+ if (msg.name === 'uiLog') {
848
+ const source = msg.source ? `[UI:${msg.source}]` : '[UI]';
849
+ const level = msg.level || 'log';
850
+ const args = Array.isArray(msg.args) ? msg.args : [];
851
+ const restored = args.map((a) => {
852
+ if (a && typeof a === 'object' && a.__error) {
853
+ const e = new Error(a.message || 'UI error');
854
+ e.stack = a.stack;
855
+ return e;
856
+ }
857
+ return a;
858
+ });
859
+ switch (level) {
860
+ case 'debug':
861
+ (0, logger_1.dbg)(source, ...restored);
862
+ break;
863
+ case 'info':
864
+ (0, logger_1.info)(source, ...restored);
865
+ break;
866
+ case 'warn':
867
+ (0, logger_1.warn)(source, ...restored);
868
+ break;
869
+ case 'error':
870
+ (0, logger_1.err)(source, ...restored);
871
+ break;
872
+ default:
873
+ (0, logger_1.log)(source, ...restored);
874
+ break;
875
+ }
876
+ return;
877
+ }
878
+ if (msg.name === 'uiReady') {
879
+ yield (0, uiSettings_1.pushUiSettings)(joplin, panelId);
880
+ // Force a redraw so weekStart takes effect immediately.
881
+ // const pm = joplin?.views?.panels?.postMessage;
882
+ // if (typeof pm === 'function') {
883
+ // await pm(panelId, {name: 'redrawMonth'});
884
+ // }
885
+ yield safePostMessage(joplin, panelId, { name: 'redrawMonth' });
886
+ return;
887
+ }
888
+ }));
889
+ });
890
+ }
891
+ function runPlugin(joplin) {
892
+ return __awaiter(this, void 0, void 0, function* () {
893
+ var _a;
894
+ (0, logger_1.log)('pluginMain', 'Plugin start');
895
+ yield (0, settings_1.registerSettings)(joplin);
896
+ const panel = yield (0, calendarView_1.createCalendarPanel)(joplin);
897
+ (0, logger_1.log)('pluginMain', 'Panel created:', panel);
898
+ yield registerUiMessageHandlers(joplin, panel);
899
+ yield (0, panelController_1.registerCalendarPanelController)(joplin, panel, {
900
+ expandAllInRange,
901
+ buildICS,
902
+ });
903
+ // warm up the cache after the UI already has handlers
904
+ void (() => __awaiter(this, void 0, void 0, function* () {
905
+ try {
906
+ const all = yield (0, eventsCache_1.ensureAllEventsCache)(joplin);
907
+ (0, logger_1.log)('eventsCache', 'Events cached:', all.length);
908
+ }
909
+ catch (error) {
910
+ (0, logger_1.err)('eventsCache', 'Error warming up cache:', error);
911
+ }
912
+ }))();
913
+ const toggleState = {
914
+ visible: true,
915
+ // Indicates whether desktop menu/toolbar were registered successfully.
916
+ active: false,
917
+ };
918
+ yield registerToggleCommand(joplin, panel, toggleState);
919
+ yield joplin.commands.register({
920
+ name: 'mycalendar.open',
921
+ label: 'Open MyCalendar',
922
+ execute: () => __awaiter(this, void 0, void 0, function* () {
923
+ var _a;
924
+ yield joplin.views.panels.show(panel);
925
+ // Ensure the UI gets the latest weekStart when the panel becomes visible.
926
+ yield (0, uiSettings_1.pushUiSettings)(joplin, panel);
927
+ try {
928
+ // const pm = joplin?.views?.panels?.postMessage;
929
+ // if (typeof pm === 'function') await pm(panel, {name: 'redrawMonth'});
930
+ yield safePostMessage(joplin, panel, { name: 'redrawMonth' });
931
+ }
932
+ catch (_err) {
933
+ // ignore
934
+ }
935
+ try {
936
+ const panelsAny = (_a = joplin.views) === null || _a === void 0 ? void 0 : _a.panels;
937
+ if (panelsAny && typeof panelsAny.focus === 'function') {
938
+ yield panelsAny.focus(panel);
939
+ }
940
+ }
941
+ catch (_b) {
942
+ // The mobile method is missing - it's expected
943
+ (0, logger_1.log)('pluginMain', 'panels.focus not available on this platform');
944
+ }
945
+ // Sync toggle state
946
+ toggleState.visible = true;
947
+ // await toggleState.update();
948
+ }),
949
+ });
950
+ yield joplin.workspace.onNoteChange((_a) => __awaiter(this, [_a], void 0, function* ({ id }) {
951
+ if (id)
952
+ (0, eventsCache_1.invalidateNote)(id);
953
+ }));
954
+ yield joplin.workspace.onSyncComplete(() => __awaiter(this, void 0, void 0, function* () {
955
+ (0, eventsCache_1.invalidateAllEventsCache)();
956
+ }));
957
+ yield (0, uiSettings_1.pushUiSettings)(joplin, panel);
958
+ yield joplin.views.panels.show(panel);
959
+ yield joplin.settings.onChange(() => __awaiter(this, void 0, void 0, function* () {
960
+ yield (0, uiSettings_1.pushUiSettings)(joplin, panel);
961
+ }));
962
+ yield registerDesktopToggle(joplin, panel, toggleState);
963
+ // --- Create the import panel (desktop)
964
+ try {
965
+ const panelsAny = (_a = joplin.views) === null || _a === void 0 ? void 0 : _a.panels;
966
+ if (panelsAny && typeof panelsAny.focus === 'function') {
967
+ yield panelsAny.focus(panel);
968
+ }
969
+ }
970
+ catch (_b) {
971
+ // On mobile this method may be missing - it's expected
972
+ (0, logger_1.log)('pluginMain', 'panels.focus not available on this platform');
973
+ }
974
+ });
975
+ }
976
+ // Register the toggle command once. The label is intentionally static because
977
+ // dynamic label updates are not reliably supported by Joplin's menu API.
978
+ function registerToggleCommand(joplin, panel, toggleState) {
979
+ return __awaiter(this, void 0, void 0, function* () {
980
+ yield joplin.commands.register({
981
+ name: 'mycalendar.togglePanel',
982
+ label: 'Toggle My Calendar',
983
+ iconName: 'fas fa-calendar-alt',
984
+ execute: () => __awaiter(this, void 0, void 0, function* () {
985
+ var _a, _b;
986
+ toggleState.visible = !toggleState.visible;
987
+ if (toggleState.visible) {
988
+ yield joplin.views.panels.show(panel);
989
+ (0, logger_1.log)('pluginMain', 'Toggle: Show');
990
+ }
991
+ else {
992
+ if ((_b = (_a = joplin.views) === null || _a === void 0 ? void 0 : _a.panels) === null || _b === void 0 ? void 0 : _b.hide) {
993
+ yield joplin.views.panels.hide(panel);
994
+ }
995
+ (0, logger_1.log)('pluginMain', 'Toggle: Hide');
996
+ }
997
+ }),
998
+ });
999
+ });
1000
+ }
1001
+ // === MyCalendar: safe desktop toggle helper ===
1002
+ function registerDesktopToggle(joplin, panel, toggleState) {
1003
+ return __awaiter(this, void 0, void 0, function* () {
1004
+ var _a, _b, _c, _d, _e, _f;
1005
+ try {
1006
+ const canShow = !!((_b = (_a = joplin === null || joplin === void 0 ? void 0 : joplin.views) === null || _a === void 0 ? void 0 : _a.panels) === null || _b === void 0 ? void 0 : _b.show);
1007
+ const canHide = !!((_d = (_c = joplin === null || joplin === void 0 ? void 0 : joplin.views) === null || _c === void 0 ? void 0 : _c.panels) === null || _d === void 0 ? void 0 : _d.hide);
1008
+ const canMenu = !!((_f = (_e = joplin === null || joplin === void 0 ? void 0 : joplin.views) === null || _e === void 0 ? void 0 : _e.menuItems) === null || _f === void 0 ? void 0 : _f.create);
1009
+ (0, logger_1.info)('pluginMain', 'Toggle capabilities:', { canShow, canHide, canMenu, panel });
1010
+ if (!canShow || !canHide) {
1011
+ (0, logger_1.info)('pluginMain', 'Toggle: panels.show/hide not available - skip');
1012
+ return;
1013
+ }
1014
+ toggleState.active = true;
1015
+ // Initial update to ensure label is correct
1016
+ // (no-op) label is intentionally static
1017
+ try {
1018
+ yield joplin.views.menuItems.create('mycalendarToggleMenu', 'mycalendar.togglePanel', 'view', { accelerator: 'Ctrl+Alt+C' });
1019
+ (0, logger_1.log)('pluginMain', 'Toggle menu item registered');
1020
+ }
1021
+ catch (e) {
1022
+ (0, logger_1.warn)('pluginMain', 'Menu create failed (non-fatal):', e);
1023
+ }
1024
+ try {
1025
+ yield joplin.views.toolbarButtons.create('mycalendarToolbarButton', 'mycalendar.togglePanel', 'noteToolbar');
1026
+ (0, logger_1.log)('pluginMain', 'Toolbar button registered');
1027
+ }
1028
+ catch (e) {
1029
+ (0, logger_1.warn)('pluginMain', 'Toolbar button create failed (non-fatal):', e);
1030
+ }
1031
+ }
1032
+ catch (e) {
1033
+ (0, logger_1.warn)('pluginMain', 'registerDesktopToggle failed (non-fatal):', e);
1034
+ }
1035
+ });
1036
+ }
1037
+
1038
+
1039
+ /***/ },
1040
+
1041
+ /***/ "./src/main/services/alarmService.ts"
1042
+ /*!*******************************************!*\
1043
+ !*** ./src/main/services/alarmService.ts ***!
1044
+ \*******************************************/
1045
+ (__unused_webpack_module, exports, __webpack_require__) {
1046
+
1047
+
1048
+ // src/main/services/alarmService.ts
1049
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
1050
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1051
+ return new (P || (P = Promise))(function (resolve, reject) {
1052
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1053
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1054
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1055
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1056
+ });
1057
+ };
1058
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1059
+ exports.syncAlarmsForEvents = syncAlarmsForEvents;
1060
+ const occurrenceService_1 = __webpack_require__(/*! ./occurrenceService */ "./src/main/services/occurrenceService.ts");
1061
+ const dateTimeUtils_1 = __webpack_require__(/*! ../utils/dateTimeUtils */ "./src/main/utils/dateTimeUtils.ts");
1062
+ const noteBuilder_1 = __webpack_require__(/*! ./noteBuilder */ "./src/main/services/noteBuilder.ts");
1063
+ const joplinUtils_1 = __webpack_require__(/*! ../utils/joplinUtils */ "./src/main/utils/joplinUtils.ts");
1064
+ const settings_1 = __webpack_require__(/*! ../settings/settings */ "./src/main/settings/settings.ts");
1065
+ const joplinNoteService_1 = __webpack_require__(/*! ./joplinNoteService */ "./src/main/services/joplinNoteService.ts");
1066
+ function isNonNegativeFiniteNumber(v) {
1067
+ return typeof v === 'number' && Number.isFinite(v) && v >= 0;
1068
+ }
1069
+ function buildDesiredAlarmsForEvent(ev, now, windowEnd) {
1070
+ const desired = [];
1071
+ const nowMs = now.getTime();
1072
+ const windowEndMs = windowEnd.getTime();
1073
+ if (!ev.valarms || ev.valarms.length === 0)
1074
+ return desired;
1075
+ const occs = (0, occurrenceService_1.expandOccurrences)(ev, now, windowEnd);
1076
+ for (const occ of occs) {
1077
+ for (const a of ev.valarms) {
1078
+ const when = (0, dateTimeUtils_1.computeAlarmWhen)(a, occ);
1079
+ if (!when)
1080
+ continue;
1081
+ const whenMs = when.getTime();
1082
+ if (whenMs >= nowMs && whenMs <= windowEndMs) {
1083
+ desired.push({ alarmTime: whenMs, eventTime: occ.start, trigger: a.trigger });
1084
+ }
1085
+ }
1086
+ }
1087
+ return desired;
1088
+ }
1089
+ function buildAlarmNoteBody(args) {
1090
+ const eventTimeStr = (0, dateTimeUtils_1.formatAlarmTitleTime)(args.eventTime);
1091
+ const triggerDesc = (0, dateTimeUtils_1.formatTriggerDescription)(args.trigger);
1092
+ return (0, noteBuilder_1.buildAlarmBody)(args.eventTitle, eventTimeStr, args.eventNoteId, args.todoTitle, args.uid, args.rid, (0, dateTimeUtils_1.formatDateForAlarm)(new Date(args.alarmTime)), triggerDesc);
1093
+ }
1094
+ function syncAlarmsForEvents(joplin, events, importedEventNotes, existingAlarms, targetFolderId, onStatus, options,
1095
+ // Legacy argument support (if needed, though we prefer options object)
1096
+ legacyAlarmsEnabled) {
1097
+ return __awaiter(this, void 0, void 0, function* () {
1098
+ var _a;
1099
+ const say = (t) => __awaiter(this, void 0, void 0, function* () {
1100
+ try {
1101
+ if (onStatus)
1102
+ yield onStatus(t);
1103
+ }
1104
+ catch ( /* ignore */_a) { /* ignore */
1105
+ }
1106
+ });
1107
+ const resolvedOptions = typeof options === 'number' ? { alarmRangeDays: options } : (options !== null && options !== void 0 ? options : {});
1108
+ // Handle legacy argument if provided and options didn't specify it
1109
+ if (legacyAlarmsEnabled !== undefined && resolvedOptions.alarmsEnabled === undefined) {
1110
+ resolvedOptions.alarmsEnabled = legacyAlarmsEnabled;
1111
+ }
1112
+ const now = (_a = resolvedOptions.now) !== null && _a !== void 0 ? _a : new Date();
1113
+ const nowMs = now.getTime();
1114
+ const alarmRangeDays = isNonNegativeFiniteNumber(resolvedOptions.alarmRangeDays) ? Math.trunc(resolvedOptions.alarmRangeDays) : yield (0, settings_1.getIcsImportAlarmRangeDays)(joplin);
1115
+ const emptyTrashAfter = typeof resolvedOptions.emptyTrashAfter === 'boolean' ? resolvedOptions.emptyTrashAfter : yield (0, settings_1.getIcsImportEmptyTrashAfter)(joplin);
1116
+ const alarmsEnabled = resolvedOptions.alarmsEnabled !== false; // Default true
1117
+ const windowEnd = (0, dateTimeUtils_1.addDays)(now, alarmRangeDays);
1118
+ let alarmsDeleted = 0;
1119
+ let alarmsCreated = 0;
1120
+ let alarmsUpdated = 0;
1121
+ for (const ev of events) {
1122
+ const uid = (ev.uid || '').trim();
1123
+ if (!uid)
1124
+ continue;
1125
+ const rid = (ev.recurrence_id || '').trim();
1126
+ const key = (0, joplinUtils_1.makeEventKey)(uid, rid);
1127
+ const eventNote = importedEventNotes[key];
1128
+ if (!eventNote)
1129
+ continue;
1130
+ const notebookId = targetFolderId || eventNote.parent_id;
1131
+ if (!notebookId)
1132
+ continue;
1133
+ // If alarms are disabled, desiredAlarms is empty -> all existing will be deleted
1134
+ const desiredAlarms = alarmsEnabled ? buildDesiredAlarmsForEvent(ev, now, windowEnd) : [];
1135
+ const oldAlarms = existingAlarms[key] || [];
1136
+ const matchedDesiredIndices = new Set();
1137
+ for (const alarm of oldAlarms) {
1138
+ if (alarm.todo_due < nowMs) {
1139
+ try {
1140
+ yield (0, joplinNoteService_1.deleteNote)(joplin, alarm.id);
1141
+ alarmsDeleted++;
1142
+ }
1143
+ catch (e) {
1144
+ yield say(`[alarmService] ERROR deleting outdated alarm: ${key} - ${String((e === null || e === void 0 ? void 0 : e.message) || e)}`);
1145
+ }
1146
+ continue;
1147
+ }
1148
+ let matchIndex = -1;
1149
+ for (let i = 0; i < desiredAlarms.length; i++) {
1150
+ if (!matchedDesiredIndices.has(i)) {
1151
+ if (Math.abs(desiredAlarms[i].alarmTime - alarm.todo_due) < 1000) {
1152
+ matchIndex = i;
1153
+ break;
1154
+ }
1155
+ }
1156
+ }
1157
+ if (matchIndex !== -1) {
1158
+ matchedDesiredIndices.add(matchIndex);
1159
+ const { alarmTime, eventTime, trigger } = desiredAlarms[matchIndex];
1160
+ const eventTitle = ev.title || 'Event';
1161
+ const todoTitle = `${eventTitle} at ${(0, dateTimeUtils_1.formatAlarmTitleTime)(eventTime)}`;
1162
+ const newBody = buildAlarmNoteBody({
1163
+ eventTitle,
1164
+ eventTime,
1165
+ eventNoteId: eventNote.id,
1166
+ todoTitle,
1167
+ uid,
1168
+ rid,
1169
+ alarmTime,
1170
+ trigger
1171
+ });
1172
+ if (newBody !== alarm.body) {
1173
+ try {
1174
+ yield (0, joplinNoteService_1.updateNote)(joplin, alarm.id, { body: newBody });
1175
+ alarmsUpdated++;
1176
+ }
1177
+ catch (e) {
1178
+ yield say(`[alarmService] ERROR updating alarm body: ${key} - ${String((e === null || e === void 0 ? void 0 : e.message) || e)}`);
1179
+ }
1180
+ }
1181
+ }
1182
+ else {
1183
+ try {
1184
+ yield (0, joplinNoteService_1.deleteNote)(joplin, alarm.id);
1185
+ alarmsDeleted++;
1186
+ }
1187
+ catch (e) {
1188
+ yield say(`[alarmService] ERROR deleting invalid alarm: ${key} - ${String((e === null || e === void 0 ? void 0 : e.message) || e)}`);
1189
+ }
1190
+ }
1191
+ }
1192
+ for (let i = 0; i < desiredAlarms.length; i++) {
1193
+ if (matchedDesiredIndices.has(i))
1194
+ continue;
1195
+ const { alarmTime, eventTime, trigger } = desiredAlarms[i];
1196
+ const eventTitle = ev.title || 'Event';
1197
+ const todoTitle = `${eventTitle} at ${(0, dateTimeUtils_1.formatAlarmTitleTime)(eventTime)}`;
1198
+ const body = buildAlarmNoteBody({
1199
+ eventTitle,
1200
+ eventTime,
1201
+ eventNoteId: eventNote.id,
1202
+ todoTitle,
1203
+ uid,
1204
+ rid,
1205
+ alarmTime,
1206
+ trigger
1207
+ });
1208
+ try {
1209
+ const noteBody = {
1210
+ title: todoTitle,
1211
+ body,
1212
+ parent_id: notebookId,
1213
+ is_todo: 1,
1214
+ todo_due: alarmTime,
1215
+ };
1216
+ const created = yield (0, joplinNoteService_1.createNote)(joplin, noteBody);
1217
+ if (created === null || created === void 0 ? void 0 : created.id) {
1218
+ // NOTE: Keeping this as a safety measure in case Joplin doesn't persist todo_due on create reliably.
1219
+ yield (0, joplinNoteService_1.updateNote)(joplin, created.id, { todo_due: alarmTime });
1220
+ }
1221
+ alarmsCreated++;
1222
+ }
1223
+ catch (e) {
1224
+ yield say(`[alarmService] ERROR creating alarm: ${key} - ${String((e === null || e === void 0 ? void 0 : e.message) || e)}`);
1225
+ }
1226
+ }
1227
+ }
1228
+ if (alarmsDeleted > 0 && emptyTrashAfter) {
1229
+ try {
1230
+ yield joplin.commands.execute('emptyTrash');
1231
+ yield say('Trash emptied.');
1232
+ }
1233
+ catch (e) {
1234
+ yield say(`[alarmService] WARNING: Failed to empty trash: ${String((e === null || e === void 0 ? void 0 : e.message) || e)}`);
1235
+ }
1236
+ }
1237
+ if (alarmsDeleted || alarmsCreated || alarmsUpdated) {
1238
+ yield say(`Alarms sync summary: deleted ${alarmsDeleted}, created ${alarmsCreated}, updated ${alarmsUpdated} (next ${alarmRangeDays} days)`);
1239
+ }
1240
+ return { alarmsCreated, alarmsDeleted, alarmsUpdated };
1241
+ });
1242
+ }
1243
+
1244
+
1245
+ /***/ },
1246
+
1247
+ /***/ "./src/main/services/eventsCache.ts"
1248
+ /*!******************************************!*\
1249
+ !*** ./src/main/services/eventsCache.ts ***!
1250
+ \******************************************/
1251
+ (__unused_webpack_module, exports, __webpack_require__) {
1252
+
1253
+
1254
+ // src/main/services/eventsCache.ts
1255
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
1256
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1257
+ return new (P || (P = Promise))(function (resolve, reject) {
1258
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1259
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1260
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1261
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1262
+ });
1263
+ };
1264
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1265
+ exports.invalidateAllEventsCache = invalidateAllEventsCache;
1266
+ exports.invalidateNote = invalidateNote;
1267
+ exports.rebuildAllEventsCache = rebuildAllEventsCache;
1268
+ exports.ensureAllEventsCache = ensureAllEventsCache;
1269
+ const eventParser_1 = __webpack_require__(/*! ../parsers/eventParser */ "./src/main/parsers/eventParser.ts");
1270
+ const logger_1 = __webpack_require__(/*! ../utils/logger */ "./src/main/utils/logger.ts");
1271
+ const NOTE_FIELDS = ['id', 'title', 'body'];
1272
+ const PAGE_LIMIT = 100;
1273
+ const eventCacheByNote = new Map();
1274
+ let allEventsCache = null;
1275
+ // Guard against concurrent rebuilds
1276
+ let rebuildPromise = null;
1277
+ function invalidateAllEventsCache() {
1278
+ allEventsCache = null;
1279
+ eventCacheByNote.clear();
1280
+ }
1281
+ function invalidateNote(noteId) {
1282
+ eventCacheByNote.delete(noteId);
1283
+ allEventsCache = null;
1284
+ }
1285
+ function fetchAllNotes(joplin) {
1286
+ return __awaiter(this, void 0, void 0, function* () {
1287
+ const items = [];
1288
+ let page = 1;
1289
+ while (true) {
1290
+ const res = yield joplin.data.get(['notes'], {
1291
+ fields: [...NOTE_FIELDS],
1292
+ limit: PAGE_LIMIT,
1293
+ page,
1294
+ });
1295
+ for (const n of res.items || [])
1296
+ items.push(n);
1297
+ if (!res.has_more)
1298
+ break;
1299
+ page++;
1300
+ }
1301
+ return items;
1302
+ });
1303
+ }
1304
+ function extractEventsFromNote(n) {
1305
+ const noteId = String((n === null || n === void 0 ? void 0 : n.id) || '');
1306
+ const title = String((n === null || n === void 0 ? void 0 : n.title) || '');
1307
+ const body = typeof (n === null || n === void 0 ? void 0 : n.body) === 'string' ? n.body : '';
1308
+ if (!noteId || !body)
1309
+ return null;
1310
+ const evs = (0, eventParser_1.parseEventsFromBody)(noteId, title, body) || [];
1311
+ if (!evs.length)
1312
+ return null;
1313
+ // Ensure noteId is present for UI
1314
+ const withNoteId = evs.map((e) => (Object.assign(Object.assign({}, e), { noteId })));
1315
+ return { noteId, events: withNoteId };
1316
+ }
1317
+ function rebuildAllEventsCache(joplin) {
1318
+ return __awaiter(this, void 0, void 0, function* () {
1319
+ // If a rebuild is already running, just await it
1320
+ if (rebuildPromise) {
1321
+ yield rebuildPromise;
1322
+ return;
1323
+ }
1324
+ rebuildPromise = (() => __awaiter(this, void 0, void 0, function* () {
1325
+ try {
1326
+ (0, logger_1.log)('eventsCache', 'Rebuilding all events cache...');
1327
+ eventCacheByNote.clear();
1328
+ const notes = yield fetchAllNotes(joplin);
1329
+ const all = [];
1330
+ for (const n of notes) {
1331
+ const extracted = extractEventsFromNote(n);
1332
+ if (!extracted)
1333
+ continue;
1334
+ eventCacheByNote.set(extracted.noteId, extracted.events);
1335
+ all.push(...extracted.events);
1336
+ }
1337
+ allEventsCache = all;
1338
+ (0, logger_1.log)('eventsCache', 'Rebuild complete. Events found:', allEventsCache.length);
1339
+ }
1340
+ catch (error) {
1341
+ (0, logger_1.err)('eventsCache', 'Error rebuilding events cache:', error);
1342
+ // Keep cache usable + avoid "stuck" state
1343
+ allEventsCache = allEventsCache || [];
1344
+ }
1345
+ }))();
1346
+ try {
1347
+ yield rebuildPromise;
1348
+ }
1349
+ finally {
1350
+ rebuildPromise = null;
1351
+ }
1352
+ });
1353
+ }
1354
+ function ensureAllEventsCache(joplin) {
1355
+ return __awaiter(this, void 0, void 0, function* () {
1356
+ if (!allEventsCache) {
1357
+ yield rebuildAllEventsCache(joplin);
1358
+ }
1359
+ return allEventsCache || [];
1360
+ });
1361
+ }
1362
+
1363
+
1364
+ /***/ },
1365
+
1366
+ /***/ "./src/main/services/folderService.ts"
1367
+ /*!********************************************!*\
1368
+ !*** ./src/main/services/folderService.ts ***!
1369
+ \********************************************/
1370
+ (__unused_webpack_module, exports) {
1371
+
1372
+
1373
+ // src/main/services/folderService.ts
1374
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
1375
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1376
+ return new (P || (P = Promise))(function (resolve, reject) {
1377
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1378
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1379
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1380
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1381
+ });
1382
+ };
1383
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1384
+ exports.getAllFolders = getAllFolders;
1385
+ exports.flattenFolderTree = flattenFolderTree;
1386
+ const FOLDERS_PAGE_LIMIT = 100;
1387
+ function getAllFolders(joplin) {
1388
+ return __awaiter(this, void 0, void 0, function* () {
1389
+ var _a;
1390
+ const out = [];
1391
+ let page = 1;
1392
+ while (true) {
1393
+ const res = yield joplin.data.get(['folders'], {
1394
+ page,
1395
+ limit: FOLDERS_PAGE_LIMIT,
1396
+ fields: ['id', 'title', 'parent_id'],
1397
+ });
1398
+ const items = ((_a = res === null || res === void 0 ? void 0 : res.items) !== null && _a !== void 0 ? _a : []);
1399
+ if (items.length)
1400
+ out.push(...items);
1401
+ if (!(res === null || res === void 0 ? void 0 : res.has_more))
1402
+ break;
1403
+ page += 1;
1404
+ }
1405
+ return out;
1406
+ });
1407
+ }
1408
+ /**
1409
+ * Flattens a folder graph into a depth-annotated list.
1410
+ *
1411
+ * Notes:
1412
+ * - Ordering is deterministic (case-insensitive alphabetical).
1413
+ * - Orphans are treated as roots.
1414
+ * - Cycles are handled: items involved in cycles are still included, and traversal stops on cycle edges.
1415
+ */
1416
+ function flattenFolderTree(rows) {
1417
+ var _a;
1418
+ const byId = new Map();
1419
+ // Build nodes; normalize parent_id to null to avoid undefined / null differences.
1420
+ for (const r of rows) {
1421
+ byId.set(r.id, { id: r.id, title: r.title, parent_id: (_a = r.parent_id) !== null && _a !== void 0 ? _a : null, children: [] });
1422
+ }
1423
+ const roots = [];
1424
+ // Link children -> parents when possible, otherwise treat as root.
1425
+ for (const node of byId.values()) {
1426
+ const parentId = node.parent_id;
1427
+ if (parentId && byId.has(parentId) && parentId !== node.id) {
1428
+ byId.get(parentId).children.push(node);
1429
+ }
1430
+ else {
1431
+ roots.push(node);
1432
+ }
1433
+ }
1434
+ const sortFn = (a, b) => a.title.localeCompare(b.title, undefined, { sensitivity: 'base' });
1435
+ roots.sort(sortFn);
1436
+ const acc = [];
1437
+ const visited = new Set();
1438
+ const visiting = new Set();
1439
+ const visit = (node, depth) => {
1440
+ if (visited.has(node.id))
1441
+ return;
1442
+ // Cycle edge: stop descent (the node is already in the current path).
1443
+ if (visiting.has(node.id))
1444
+ return;
1445
+ visiting.add(node.id);
1446
+ acc.push({ id: node.id, title: node.title, parent_id: node.parent_id, depth });
1447
+ node.children.sort(sortFn);
1448
+ for (const ch of node.children)
1449
+ visit(ch, depth + 1);
1450
+ visiting.delete(node.id);
1451
+ visited.add(node.id);
1452
+ };
1453
+ // Walk declared roots first.
1454
+ for (const r of roots)
1455
+ visit(r, 0);
1456
+ // Include any remaining nodes (covers pure cycles and disconnected graphs with no roots).
1457
+ if (visited.size !== byId.size) {
1458
+ const remaining = Array.from(byId.values()).filter(n => !visited.has(n.id));
1459
+ remaining.sort(sortFn);
1460
+ for (const n of remaining)
1461
+ visit(n, 0);
1462
+ }
1463
+ return acc;
1464
+ }
1465
+
1466
+
1467
+ /***/ },
1468
+
1469
+ /***/ "./src/main/services/icsImportService.ts"
1470
+ /*!***********************************************!*\
1471
+ !*** ./src/main/services/icsImportService.ts ***!
1472
+ \***********************************************/
1473
+ (__unused_webpack_module, exports, __webpack_require__) {
1474
+
1475
+
1476
+ // src/main/services/icsImportService.ts
1477
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
1478
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1479
+ return new (P || (P = Promise))(function (resolve, reject) {
1480
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1481
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1482
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1483
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1484
+ });
1485
+ };
1486
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1487
+ exports.importIcsIntoNotes = importIcsIntoNotes;
1488
+ const icsParser_1 = __webpack_require__(/*! ../parsers/icsParser */ "./src/main/parsers/icsParser.ts");
1489
+ const joplinUtils_1 = __webpack_require__(/*! ../utils/joplinUtils */ "./src/main/utils/joplinUtils.ts");
1490
+ const alarmService_1 = __webpack_require__(/*! ./alarmService */ "./src/main/services/alarmService.ts");
1491
+ const noteBuilder_1 = __webpack_require__(/*! ./noteBuilder */ "./src/main/services/noteBuilder.ts");
1492
+ const joplinNoteService_1 = __webpack_require__(/*! ./joplinNoteService */ "./src/main/services/joplinNoteService.ts");
1493
+ const settings_1 = __webpack_require__(/*! ../settings/settings */ "./src/main/settings/settings.ts");
1494
+ function safeStatus(onStatus) {
1495
+ return (t) => __awaiter(this, void 0, void 0, function* () {
1496
+ try {
1497
+ if (onStatus)
1498
+ yield onStatus(t);
1499
+ }
1500
+ catch ( /* ignore */_a) { /* ignore */
1501
+ }
1502
+ });
1503
+ }
1504
+ function indexExistingNotes(allNotes) {
1505
+ var _a, _b;
1506
+ var _c;
1507
+ const existingByKey = {};
1508
+ const existingAlarms = {};
1509
+ const noteIdToKeys = {};
1510
+ for (const n of allNotes) {
1511
+ if (typeof n.body !== 'string' || !n.body)
1512
+ continue;
1513
+ if (n.body.includes('```mycalendar-event')) {
1514
+ const keys = (0, joplinUtils_1.extractAllEventKeysFromBody)(n.body);
1515
+ if (keys.length) {
1516
+ noteIdToKeys[n.id] = ((_a = noteIdToKeys[n.id]) !== null && _a !== void 0 ? _a : []).concat(keys);
1517
+ }
1518
+ for (const k of keys) {
1519
+ existingByKey[k] = { id: n.id, title: n.title || '', body: n.body, parent_id: n.parent_id };
1520
+ }
1521
+ }
1522
+ if (n.body.includes('```mycalendar-alarm')) {
1523
+ const metas = (0, joplinUtils_1.extractAllAlarmKeysFromBody)(n.body);
1524
+ for (const meta of metas) {
1525
+ ((_b = existingAlarms[_c = meta.key]) !== null && _b !== void 0 ? _b : (existingAlarms[_c] = [])).push({ id: n.id, todo_due: n.todo_due || 0, body: n.body });
1526
+ }
1527
+ }
1528
+ }
1529
+ return { existingByKey, existingAlarms, noteIdToKeys };
1530
+ }
1531
+ function applyImportColors(ev, existing, preserveLocalColor, importDefaultColor) {
1532
+ const uid = (ev.uid || '').trim();
1533
+ const rid = (ev.recurrence_id || '').trim();
1534
+ const key = (0, joplinUtils_1.makeEventKey)(uid, rid);
1535
+ if (preserveLocalColor && existing[key] && !ev.color) {
1536
+ const existingColor = (0, joplinUtils_1.extractEventColorFromBody)(existing[key].body, uid, rid);
1537
+ if (existingColor)
1538
+ ev.color = existingColor;
1539
+ }
1540
+ if (!ev.color && importDefaultColor)
1541
+ ev.color = importDefaultColor;
1542
+ return key;
1543
+ }
1544
+ function importIcsIntoNotes(joplin_1, ics_1, onStatus_1, targetFolderId_1) {
1545
+ return __awaiter(this, arguments, void 0, function* (joplin, ics, onStatus, targetFolderId, preserveLocalColor = true, importDefaultColor, importAlarmRangeDays) {
1546
+ var _a;
1547
+ const say = safeStatus(onStatus);
1548
+ const eventsRaw = (0, icsParser_1.parseImportText)(ics);
1549
+ const events = eventsRaw.map(e => (Object.assign({}, e))); // avoid mutating parser output
1550
+ yield say(`Parsed ${events.length} VEVENT(s)`);
1551
+ // Request todo_due to optimize alarm syncing
1552
+ const allNotes = yield (0, joplinNoteService_1.getAllNotesPaged)(joplin, ['id', 'title', 'body', 'parent_id', 'todo_due']);
1553
+ const { existingByKey: existing, existingAlarms, noteIdToKeys } = indexExistingNotes(allNotes);
1554
+ let added = 0, updated = 0, skipped = 0, errors = 0;
1555
+ const importedEventNotes = {};
1556
+ for (const ev of events) {
1557
+ const uid = (ev.uid || '').trim();
1558
+ if (!uid) {
1559
+ skipped++;
1560
+ continue;
1561
+ }
1562
+ const rid = (ev.recurrence_id || '').trim();
1563
+ const key = applyImportColors(ev, existing, preserveLocalColor, importDefaultColor);
1564
+ const block = (0, noteBuilder_1.buildMyCalBlock)(ev);
1565
+ const desiredTitle = ev.title || 'Event';
1566
+ if (existing[key]) {
1567
+ try {
1568
+ const { id, title, parent_id } = existing[key];
1569
+ // CRITICAL: Always use the LATEST body from our local "existing" cache
1570
+ // because multiple sequential updates to the same note might have occurred
1571
+ const currentBody = existing[key].body;
1572
+ const newBody = (0, joplinUtils_1.replaceEventBlockByKey)(currentBody, uid, rid, block);
1573
+ const patch = {};
1574
+ let changedAtAll = false;
1575
+ if (newBody !== currentBody) {
1576
+ patch.body = newBody;
1577
+ // Update our cache immediately so the next event in the same note sees current state
1578
+ existing[key].body = newBody;
1579
+ changedAtAll = true;
1580
+ }
1581
+ if (desiredTitle !== title) {
1582
+ patch.title = desiredTitle;
1583
+ existing[key].title = desiredTitle;
1584
+ changedAtAll = true;
1585
+ }
1586
+ if (targetFolderId && parent_id !== targetFolderId) {
1587
+ patch.parent_id = targetFolderId;
1588
+ existing[key].parent_id = targetFolderId;
1589
+ changedAtAll = true;
1590
+ }
1591
+ if (changedAtAll) {
1592
+ yield (0, joplinNoteService_1.updateNote)(joplin, id, patch);
1593
+ updated++;
1594
+ }
1595
+ else {
1596
+ skipped++;
1597
+ }
1598
+ // IMPORTANT: update cache for all keys in the same note (O(keysInNote) instead of O(allKeys))
1599
+ const keysInSameNote = (_a = noteIdToKeys[id]) !== null && _a !== void 0 ? _a : [];
1600
+ for (const k of keysInSameNote) {
1601
+ if (existing[k])
1602
+ existing[k].body = existing[key].body;
1603
+ }
1604
+ importedEventNotes[key] = { id, parent_id: (targetFolderId || parent_id), title: desiredTitle };
1605
+ }
1606
+ catch (e) {
1607
+ errors++;
1608
+ yield say(`[icsImportService] ERROR updating note: ${key} - ${String((e === null || e === void 0 ? void 0 : e.message) || e)}`);
1609
+ }
1610
+ }
1611
+ else {
1612
+ try {
1613
+ const noteBody = { title: desiredTitle, body: block, parent_id: targetFolderId };
1614
+ const created = yield (0, joplinNoteService_1.createNote)(joplin, noteBody);
1615
+ added++;
1616
+ if (created === null || created === void 0 ? void 0 : created.id) {
1617
+ importedEventNotes[key] = { id: created.id, parent_id: targetFolderId, title: desiredTitle };
1618
+ }
1619
+ }
1620
+ catch (e) {
1621
+ errors++;
1622
+ yield say(`[icsImportService] ERROR creating note: ${key} - ${String((e === null || e === void 0 ? void 0 : e.message) || e)}`);
1623
+ }
1624
+ }
1625
+ }
1626
+ let alarmsEnabled = true;
1627
+ try {
1628
+ alarmsEnabled = yield (0, settings_1.getIcsImportAlarmsEnabled)(joplin);
1629
+ }
1630
+ catch (e) {
1631
+ // Backward compatibility: if setting read fails (old Joplin API / corrupted profile),
1632
+ // keep previous behavior (alarms enabled).
1633
+ yield say(`[icsImportService] WARNING: Failed to read alarms setting; defaulting to enabled. ${String((e === null || e === void 0 ? void 0 : e.message) || e)}`);
1634
+ alarmsEnabled = true;
1635
+ }
1636
+ const alarmRes = yield (0, alarmService_1.syncAlarmsForEvents)(joplin, events, importedEventNotes, existingAlarms, targetFolderId, onStatus, {
1637
+ alarmRangeDays: importAlarmRangeDays,
1638
+ alarmsEnabled
1639
+ });
1640
+ return Object.assign({ added, updated, skipped, errors }, alarmRes);
1641
+ });
1642
+ }
1643
+
1644
+
1645
+ /***/ },
1646
+
1647
+ /***/ "./src/main/services/joplinNoteService.ts"
1648
+ /*!************************************************!*\
1649
+ !*** ./src/main/services/joplinNoteService.ts ***!
1650
+ \************************************************/
1651
+ (__unused_webpack_module, exports) {
1652
+
1653
+
1654
+ // src/main/services/joplinNoteService.ts
1655
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
1656
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1657
+ return new (P || (P = Promise))(function (resolve, reject) {
1658
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1659
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1660
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1661
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
1662
+ });
1663
+ };
1664
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1665
+ exports.getAllNotesPaged = getAllNotesPaged;
1666
+ exports.createNote = createNote;
1667
+ exports.updateNote = updateNote;
1668
+ exports.deleteNote = deleteNote;
1669
+ const DEFAULT_PAGE_LIMIT = 100;
1670
+ const DEFAULT_MAX_PAGES = 1000;
1671
+ function getAllNotesPaged(joplin_1) {
1672
+ return __awaiter(this, arguments, void 0, function* (joplin, fields = ['id', 'title', 'body', 'parent_id'], options = {}) {
1673
+ var _a, _b, _c;
1674
+ const allNotes = [];
1675
+ let page = 1;
1676
+ const limit = (_a = options.limit) !== null && _a !== void 0 ? _a : DEFAULT_PAGE_LIMIT;
1677
+ const maxPages = (_b = options.maxPages) !== null && _b !== void 0 ? _b : DEFAULT_MAX_PAGES;
1678
+ while (true) {
1679
+ if (page > maxPages) {
1680
+ throw new Error(`getAllNotesPaged exceeded maxPages=${maxPages}`);
1681
+ }
1682
+ const res = (yield joplin.data.get(['notes'], { fields, limit, page }));
1683
+ allNotes.push(...((_c = res.items) !== null && _c !== void 0 ? _c : []));
1684
+ if (!res.has_more)
1685
+ break;
1686
+ page++;
1687
+ }
1688
+ return allNotes;
1689
+ });
1690
+ }
1691
+ function createNote(joplin, note) {
1692
+ return __awaiter(this, void 0, void 0, function* () {
1693
+ return joplin.data.post(['notes'], null, note);
1694
+ });
1695
+ }
1696
+ function updateNote(joplin, id, patch) {
1697
+ return __awaiter(this, void 0, void 0, function* () {
1698
+ yield joplin.data.put(['notes', id], null, patch);
1699
+ });
1700
+ }
1701
+ function deleteNote(joplin, id) {
1702
+ return __awaiter(this, void 0, void 0, function* () {
1703
+ yield joplin.data.delete(['notes', id]);
1704
+ });
1705
+ }
1706
+
1707
+
1708
+ /***/ },
1709
+
1710
+ /***/ "./src/main/services/noteBuilder.ts"
1711
+ /*!******************************************!*\
1712
+ !*** ./src/main/services/noteBuilder.ts ***!
1713
+ \******************************************/
1714
+ (__unused_webpack_module, exports) {
1715
+
1716
+
1717
+ // src/main/services/noteBuilder.ts
1718
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1719
+ exports.sanitizeForMarkdownBlock = sanitizeForMarkdownBlock;
1720
+ exports.isValidHexColor = isValidHexColor;
1721
+ exports.isValidJoplinNoteId = isValidJoplinNoteId;
1722
+ exports.buildMyCalBlock = buildMyCalBlock;
1723
+ exports.buildAlarmBody = buildAlarmBody;
1724
+ const MAX_TITLE_LEN = 500;
1725
+ const MAX_LOCATION_LEN = 1000;
1726
+ const MAX_DESCRIPTION_LEN = 10000;
1727
+ /**
1728
+ * Ensures a value recorded in a ```mycalendar-event``` or ```mycalendar-alarm``` block
1729
+ * cannot "break out" of the fence.
1730
+ * - Removes backticks
1731
+ * - Replaces newlines with spaces for single-line fields
1732
+ */
1733
+ function sanitizeForMarkdownBlock(input, singleLine = true) {
1734
+ let s = String(input !== null && input !== void 0 ? input : '').trim();
1735
+ // Prevent breaking out of code block (```)
1736
+ s = s.replace(/`/g, "'");
1737
+ if (singleLine) {
1738
+ // Enforce single line to prevent key: value injection
1739
+ s = s.replace(/[\r\n]+/g, ' ');
1740
+ }
1741
+ return s;
1742
+ }
1743
+ function isValidHexColor(c) {
1744
+ if (!c)
1745
+ return false;
1746
+ return /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(c);
1747
+ }
1748
+ function isValidJoplinNoteId(id) {
1749
+ if (!id)
1750
+ return false;
1751
+ return /^[0-9a-fA-F]{32}$/.test(id);
1752
+ }
1753
+ function normalizePositiveInt(value, defaultValue) {
1754
+ const n = typeof value === 'number' ? value : Number(value);
1755
+ if (!Number.isFinite(n))
1756
+ return defaultValue;
1757
+ return Math.max(1, Math.floor(n));
1758
+ }
1759
+ function pushKV(lines, key, value, opts = {}) {
1760
+ var _a;
1761
+ if (value === undefined || value === null || value === '')
1762
+ return;
1763
+ const singleLine = (_a = opts.singleLine) !== null && _a !== void 0 ? _a : true;
1764
+ const maxLen = opts.maxLen;
1765
+ let s = sanitizeForMarkdownBlock(value, singleLine);
1766
+ if (typeof maxLen === 'number')
1767
+ s = s.slice(0, maxLen);
1768
+ lines.push(`${key}: ${s}`);
1769
+ }
1770
+ function valarmToJsonLine(a) {
1771
+ // Stable key order for tests + diffs
1772
+ const o = { trigger: a.trigger };
1773
+ if (a.related)
1774
+ o.related = a.related;
1775
+ if (a.action)
1776
+ o.action = a.action;
1777
+ if (a.description)
1778
+ o.description = a.description;
1779
+ if (a.summary)
1780
+ o.summary = a.summary;
1781
+ if (typeof a.repeat === 'number')
1782
+ o.repeat = a.repeat;
1783
+ if (a.duration)
1784
+ o.duration = a.duration;
1785
+ return JSON.stringify(o);
1786
+ }
1787
+ /**
1788
+ * Form the block ```mycalendar-event ... ```
1789
+ */
1790
+ function buildMyCalBlock(ev) {
1791
+ const lines = [];
1792
+ lines.push('```mycalendar-event');
1793
+ pushKV(lines, 'title', ev.title, { maxLen: MAX_TITLE_LEN });
1794
+ pushKV(lines, 'start', ev.start);
1795
+ pushKV(lines, 'end', ev.end);
1796
+ pushKV(lines, 'tz', ev.tz);
1797
+ if (isValidHexColor(ev.color)) {
1798
+ lines.push(`color: ${ev.color}`);
1799
+ }
1800
+ pushKV(lines, 'location', ev.location, { maxLen: MAX_LOCATION_LEN });
1801
+ if (ev.description) {
1802
+ // Description can be multiline in ICS.
1803
+ // We sanitize to prevent breaking ``` but keep newlines.
1804
+ pushKV(lines, 'description', ev.description, { singleLine: false, maxLen: MAX_DESCRIPTION_LEN });
1805
+ }
1806
+ if (ev.valarms && ev.valarms.length) {
1807
+ lines.push('');
1808
+ for (const a of ev.valarms) {
1809
+ // valarm is JSON.
1810
+ const json = valarmToJsonLine(a);
1811
+ pushKV(lines, 'valarm', json);
1812
+ }
1813
+ }
1814
+ const repeat = ev.repeat && ev.repeat !== 'none' ? ev.repeat : undefined;
1815
+ if (repeat) {
1816
+ lines.push('');
1817
+ // repeat is an enum in our model, but sanitize anyway to be safe.
1818
+ pushKV(lines, 'repeat', repeat);
1819
+ lines.push(`repeat_interval: ${normalizePositiveInt(ev.repeat_interval, 1)}`);
1820
+ pushKV(lines, 'repeat_until', ev.repeat_until);
1821
+ pushKV(lines, 'byweekday', ev.byweekday);
1822
+ pushKV(lines, 'bymonthday', ev.bymonthday);
1823
+ }
1824
+ if (ev.all_day)
1825
+ lines.push(`all_day: true`);
1826
+ if (ev.uid) {
1827
+ lines.push('');
1828
+ pushKV(lines, 'uid', ev.uid);
1829
+ if (ev.recurrence_id) {
1830
+ pushKV(lines, 'recurrence_id', ev.recurrence_id);
1831
+ }
1832
+ }
1833
+ lines.push('```');
1834
+ return lines.join('\n');
1835
+ }
1836
+ function buildAlarmBody(eventTitle, eventTimeStr, eventNoteId, todoTitle, uid, rid, when, triggerDesc) {
1837
+ const safeEventTitle = sanitizeForMarkdownBlock(eventTitle);
1838
+ const safeEventTimeStr = sanitizeForMarkdownBlock(eventTimeStr);
1839
+ const safeNoteId = isValidJoplinNoteId(eventNoteId) ? eventNoteId : sanitizeForMarkdownBlock(eventNoteId);
1840
+ const safeWhen = sanitizeForMarkdownBlock(when);
1841
+ const safeTriggerDesc = sanitizeForMarkdownBlock(triggerDesc);
1842
+ return [
1843
+ `[${safeEventTitle} at ${safeEventTimeStr}](:/${safeNoteId})`,
1844
+ '',
1845
+ '```mycalendar-alarm',
1846
+ `title: ${sanitizeForMarkdownBlock(todoTitle).slice(0, MAX_TITLE_LEN)}`,
1847
+ `trigger_desc: ${safeTriggerDesc}`,
1848
+ `when: ${safeWhen}`,
1849
+ `uid: ${sanitizeForMarkdownBlock(uid)}`,
1850
+ `recurrence_id: ${sanitizeForMarkdownBlock(rid)}`,
1851
+ '```',
1852
+ '',
1853
+ ].join('\n');
1854
+ }
1855
+
1856
+
1857
+ /***/ },
1858
+
1859
+ /***/ "./src/main/services/occurrenceService.ts"
1860
+ /*!************************************************!*\
1861
+ !*** ./src/main/services/occurrenceService.ts ***!
1862
+ \************************************************/
1863
+ (__unused_webpack_module, exports, __webpack_require__) {
1864
+
1865
+
1866
+ // src/main/services/occurrenceService.ts
1867
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1868
+ exports.expandOccurrences = expandOccurrences;
1869
+ const dateTimeUtils_1 = __webpack_require__(/*! ../utils/dateTimeUtils */ "./src/main/utils/dateTimeUtils.ts");
1870
+ function expandOccurrences(ev, windowStart, windowEnd) {
1871
+ var _a;
1872
+ const start = (0, dateTimeUtils_1.parseMyCalDateToDate)(ev.start);
1873
+ if (!start)
1874
+ return [];
1875
+ const end = (_a = (0, dateTimeUtils_1.parseMyCalDateToDate)(ev.end)) !== null && _a !== void 0 ? _a : new Date(start.getTime());
1876
+ const durMs = end.getTime() - start.getTime();
1877
+ // --- UTC helpers (avoid DST/local timezone shifts) ---
1878
+ const addDaysUtc = (d, days) => {
1879
+ const out = new Date(d.getTime());
1880
+ out.setUTCDate(out.getUTCDate() + days);
1881
+ return out;
1882
+ };
1883
+ const addMonthsUtc = (d, months) => {
1884
+ const out = new Date(d.getTime());
1885
+ out.setUTCMonth(out.getUTCMonth() + months);
1886
+ return out;
1887
+ };
1888
+ // const addYearsUtc = (d: Date, years: number): Date => {
1889
+ // const out = new Date(d.getTime());
1890
+ // out.setUTCFullYear(out.getUTCFullYear() + years);
1891
+ // return out;
1892
+ // };
1893
+ const setTimeOfDayUtc = (d, base) => {
1894
+ d.setUTCHours(base.getUTCHours(), base.getUTCMinutes(), base.getUTCSeconds(), 0);
1895
+ };
1896
+ const until = (0, dateTimeUtils_1.parseMyCalDateToDate)(ev.repeat_until);
1897
+ const hardEnd = until && until.getTime() < windowEnd.getTime() ? until : windowEnd;
1898
+ const interval = ev.repeat_interval && ev.repeat_interval >= 1 ? ev.repeat_interval : 1;
1899
+ const occs = [];
1900
+ const pushIfInRange = (s) => {
1901
+ const e = new Date(s.getTime() + durMs);
1902
+ if (s.getTime() > hardEnd.getTime())
1903
+ return;
1904
+ if (e.getTime() < windowStart.getTime())
1905
+ return;
1906
+ if (s.getTime() > windowEnd.getTime())
1907
+ return;
1908
+ occs.push({ start: s, end: e, recurrence_id: undefined });
1909
+ };
1910
+ if (!ev.repeat || ev.repeat === 'none') {
1911
+ pushIfInRange(start);
1912
+ return occs;
1913
+ }
1914
+ if (ev.repeat === 'daily') {
1915
+ let cur = new Date(start.getTime());
1916
+ while (cur.getTime() <= hardEnd.getTime()) {
1917
+ pushIfInRange(cur);
1918
+ cur = addDaysUtc(cur, interval);
1919
+ }
1920
+ return sortAndDedupe(occs);
1921
+ }
1922
+ if (ev.repeat === 'weekly') {
1923
+ const days = (ev.byweekday ? ev.byweekday.split(',') : []).map(d => d.trim()).filter(Boolean);
1924
+ const jsDays = (days.length ? days : [['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'][start.getUTCDay()]])
1925
+ .map(dateTimeUtils_1.weekdayToJs)
1926
+ .filter((n) => n !== null)
1927
+ .sort((a, b) => a - b);
1928
+ let weekAnchor = new Date(start.getTime());
1929
+ while (weekAnchor.getTime() <= hardEnd.getTime()) {
1930
+ for (const wd of jsDays) {
1931
+ const s = new Date(weekAnchor.getTime());
1932
+ const delta = wd - s.getUTCDay();
1933
+ s.setUTCDate(s.getUTCDate() + delta);
1934
+ setTimeOfDayUtc(s, start);
1935
+ if (s.getTime() < weekAnchor.getTime())
1936
+ s.setUTCDate(s.getUTCDate() + 7);
1937
+ if (s.getTime() < start.getTime())
1938
+ continue;
1939
+ pushIfInRange(s);
1940
+ }
1941
+ weekAnchor = addDaysUtc(weekAnchor, 7 * interval);
1942
+ }
1943
+ return sortAndDedupe(occs);
1944
+ }
1945
+ if (ev.repeat === 'monthly') {
1946
+ const day = ev.bymonthday ? parseInt(ev.bymonthday, 10) : start.getUTCDate();
1947
+ // Anchor on the 1st of the month to avoid "rolling" when adding months
1948
+ let anchor = new Date(start.getTime());
1949
+ anchor.setUTCDate(1);
1950
+ while (anchor.getTime() <= hardEnd.getTime()) {
1951
+ const targetMonth = anchor.getUTCMonth();
1952
+ const s = new Date(anchor.getTime());
1953
+ s.setUTCDate(day);
1954
+ setTimeOfDayUtc(s, start);
1955
+ // If day does not exist in this month, JS will "roll" the date to another month.
1956
+ // According to the expected behavior of the RRULE, such instances should be skipped.
1957
+ if (s.getUTCMonth() === targetMonth) {
1958
+ if (s.getTime() >= start.getTime())
1959
+ pushIfInRange(s);
1960
+ }
1961
+ anchor = addMonthsUtc(anchor, interval);
1962
+ anchor.setUTCDate(1);
1963
+ }
1964
+ return sortAndDedupe(occs);
1965
+ }
1966
+ if (ev.repeat === 'yearly') {
1967
+ const baseMonth = start.getUTCMonth();
1968
+ const baseDay = start.getUTCDate();
1969
+ const h = start.getUTCHours();
1970
+ const m = start.getUTCMinutes();
1971
+ const sec = start.getUTCSeconds();
1972
+ let year = start.getFullYear();
1973
+ while (true) {
1974
+ const s = new Date(start.getTime());
1975
+ s.setFullYear(year);
1976
+ s.setUTCHours(h, m, sec, 0);
1977
+ // February 29 will "roll over" in a non-leap year - we skip such instances
1978
+ if (s.getUTCMonth() === baseMonth && s.getUTCDate() === baseDay) {
1979
+ if (s.getTime() > hardEnd.getTime())
1980
+ break;
1981
+ pushIfInRange(s);
1982
+ }
1983
+ else {
1984
+ // even if the date is invalid, a hardEnd stop is still required
1985
+ // we make a conservative check: if we have already "jumped" far ahead
1986
+ if (s.getTime() > hardEnd.getTime() && year !== start.getFullYear())
1987
+ break;
1988
+ }
1989
+ year += interval;
1990
+ }
1991
+ return sortAndDedupe(occs);
1992
+ }
1993
+ return sortAndDedupe(occs);
1994
+ }
1995
+ function sortAndDedupe(list) {
1996
+ const sorted = [...list].sort((a, b) => a.start.getTime() - b.start.getTime());
1997
+ const out = [];
1998
+ let prev = null;
1999
+ for (const o of sorted) {
2000
+ const t = o.start.getTime();
2001
+ if (prev === t)
2002
+ continue;
2003
+ out.push(o);
2004
+ prev = t;
2005
+ }
2006
+ return out;
2007
+ }
2008
+
2009
+
2010
+ /***/ },
2011
+
2012
+ /***/ "./src/main/settings/settings.ts"
2013
+ /*!***************************************!*\
2014
+ !*** ./src/main/settings/settings.ts ***!
2015
+ \***************************************/
2016
+ (__unused_webpack_module, exports, __webpack_require__) {
2017
+
2018
+
2019
+ // src/main/settings/settings.ts
2020
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2021
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
2022
+ return new (P || (P = Promise))(function (resolve, reject) {
2023
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
2024
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
2025
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
2026
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
2027
+ });
2028
+ };
2029
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2030
+ exports.SETTING_ICS_EXPORT_LINK4_URL = exports.SETTING_ICS_EXPORT_LINK4_TITLE = exports.SETTING_ICS_EXPORT_LINK3_URL = exports.SETTING_ICS_EXPORT_LINK3_TITLE = exports.SETTING_ICS_EXPORT_LINK2_URL = exports.SETTING_ICS_EXPORT_LINK2_TITLE = exports.SETTING_ICS_EXPORT_LINK1_URL = exports.SETTING_ICS_EXPORT_LINK1_TITLE = exports.SETTING_ICS_IMPORT_EMPTY_TRASH_AFTER = exports.SETTING_ICS_IMPORT_ALARM_RANGE_DAYS = exports.SETTING_ICS_IMPORT_ALARMS_ENABLED = exports.SETTING_DAY_EVENTS_REFRESH_MINUTES = exports.SETTING_SHOW_EVENT_TIMELINE = exports.SETTING_WEEK_START = exports.SETTING_DEBUG = void 0;
2031
+ exports.sanitizeExternalUrl = sanitizeExternalUrl;
2032
+ exports.sanitizeTitle = sanitizeTitle;
2033
+ exports.registerSettings = registerSettings;
2034
+ exports.getDebugEnabled = getDebugEnabled;
2035
+ exports.getWeekStart = getWeekStart;
2036
+ exports.getShowEventTimeline = getShowEventTimeline;
2037
+ exports.getDayEventsRefreshMinutes = getDayEventsRefreshMinutes;
2038
+ exports.getIcsImportAlarmsEnabled = getIcsImportAlarmsEnabled;
2039
+ exports.getIcsImportAlarmRangeDays = getIcsImportAlarmRangeDays;
2040
+ exports.getIcsImportEmptyTrashAfter = getIcsImportEmptyTrashAfter;
2041
+ exports.getIcsExportLinks = getIcsExportLinks;
2042
+ const logger_1 = __webpack_require__(/*! ../utils/logger */ "./src/main/utils/logger.ts");
2043
+ // Common
2044
+ exports.SETTING_DEBUG = 'mycalendar.debug';
2045
+ // Calendar
2046
+ exports.SETTING_WEEK_START = 'mycalendar.weekStart';
2047
+ // Day events
2048
+ exports.SETTING_SHOW_EVENT_TIMELINE = 'mycalendar.showEventTimeline';
2049
+ exports.SETTING_DAY_EVENTS_REFRESH_MINUTES = 'mycalendar.dayEventsRefreshMinutes';
2050
+ // ICS Import
2051
+ exports.SETTING_ICS_IMPORT_ALARMS_ENABLED = 'mycalendar.icsImportAlarmsEnabled';
2052
+ exports.SETTING_ICS_IMPORT_ALARM_RANGE_DAYS = 'mycalendar.icsImportAlarmRangeDays';
2053
+ exports.SETTING_ICS_IMPORT_EMPTY_TRASH_AFTER = 'mycalendar.icsImportEmptyTrashAfter';
2054
+ // Export links
2055
+ // New multi-link settings (up to 4). Titles are optional.
2056
+ exports.SETTING_ICS_EXPORT_LINK1_TITLE = 'mycalendar.icsExportLink1Title';
2057
+ exports.SETTING_ICS_EXPORT_LINK1_URL = 'mycalendar.icsExportLink1Url';
2058
+ exports.SETTING_ICS_EXPORT_LINK2_TITLE = 'mycalendar.icsExportLink2Title';
2059
+ exports.SETTING_ICS_EXPORT_LINK2_URL = 'mycalendar.icsExportLink2Url';
2060
+ exports.SETTING_ICS_EXPORT_LINK3_TITLE = 'mycalendar.icsExportLink3Title';
2061
+ exports.SETTING_ICS_EXPORT_LINK3_URL = 'mycalendar.icsExportLink3Url';
2062
+ exports.SETTING_ICS_EXPORT_LINK4_TITLE = 'mycalendar.icsExportLink4Title';
2063
+ exports.SETTING_ICS_EXPORT_LINK4_URL = 'mycalendar.icsExportLink4Url';
2064
+ const TITLE_MAX_LEN = 60;
2065
+ // Avoid magic numbers for setting item types (Joplin: int=1, string=2, bool=3)
2066
+ const SETTING_TYPE_INT = 1;
2067
+ const SETTING_TYPE_STRING = 2;
2068
+ const SETTING_TYPE_BOOL = 3;
2069
+ const ICS_EXPORT_LINK_PAIRS = [
2070
+ { titleKey: exports.SETTING_ICS_EXPORT_LINK1_TITLE, urlKey: exports.SETTING_ICS_EXPORT_LINK1_URL },
2071
+ { titleKey: exports.SETTING_ICS_EXPORT_LINK2_TITLE, urlKey: exports.SETTING_ICS_EXPORT_LINK2_URL },
2072
+ { titleKey: exports.SETTING_ICS_EXPORT_LINK3_TITLE, urlKey: exports.SETTING_ICS_EXPORT_LINK3_URL },
2073
+ { titleKey: exports.SETTING_ICS_EXPORT_LINK4_TITLE, urlKey: exports.SETTING_ICS_EXPORT_LINK4_URL },
2074
+ ];
2075
+ const ICS_EXPORT_URL_KEYS = ICS_EXPORT_LINK_PAIRS.map(p => p.urlKey);
2076
+ const ICS_EXPORT_TITLE_KEYS = ICS_EXPORT_LINK_PAIRS.map(p => p.titleKey);
2077
+ function sanitizeExternalUrl(input) {
2078
+ const s = String(input !== null && input !== void 0 ? input : '').trim();
2079
+ if (!s)
2080
+ return '';
2081
+ try {
2082
+ const u = new URL(s);
2083
+ // Allow only http(s) links to avoid `javascript:`, `file:`, etc.
2084
+ if (u.protocol !== 'https:' && u.protocol !== 'http:')
2085
+ return '';
2086
+ return u.toString();
2087
+ }
2088
+ catch (_a) {
2089
+ return '';
2090
+ }
2091
+ }
2092
+ function sanitizeTitle(input) {
2093
+ const s = String(input !== null && input !== void 0 ? input : '').trim();
2094
+ // Keep it short to avoid breaking the layout.
2095
+ if (!s)
2096
+ return '';
2097
+ return s.length > TITLE_MAX_LEN ? s.slice(0, TITLE_MAX_LEN) : s;
2098
+ }
2099
+ function isMobile(joplin) {
2100
+ return __awaiter(this, void 0, void 0, function* () {
2101
+ try {
2102
+ const v = yield joplin.versionInfo();
2103
+ return (v === null || v === void 0 ? void 0 : v.platform) === 'mobile';
2104
+ }
2105
+ catch (_a) {
2106
+ return false; // if the API is old/not available - consider desktop
2107
+ }
2108
+ });
2109
+ }
2110
+ function registerSettings(joplin) {
2111
+ return __awaiter(this, void 0, void 0, function* () {
2112
+ var _a, _b, _c, _d;
2113
+ if (!((_a = joplin === null || joplin === void 0 ? void 0 : joplin.settings) === null || _a === void 0 ? void 0 : _a.registerSection) || !((_b = joplin === null || joplin === void 0 ? void 0 : joplin.settings) === null || _b === void 0 ? void 0 : _b.registerSettings))
2114
+ return;
2115
+ // ---- Calendar ----------------------------------------------------------
2116
+ yield joplin.settings.registerSection('mycalendar', {
2117
+ label: 'My Calendar',
2118
+ iconName: 'fas fa-calendar',
2119
+ });
2120
+ const mobile = yield isMobile(joplin);
2121
+ yield joplin.settings.registerSettings({
2122
+ // 1) Calendar
2123
+ // 2) Week starts on
2124
+ [exports.SETTING_WEEK_START]: {
2125
+ value: 'monday',
2126
+ type: SETTING_TYPE_STRING, // string
2127
+ section: 'mycalendar',
2128
+ public: true,
2129
+ label: 'Week starts on',
2130
+ description: 'Calendar section: First day of week in calendar grid. Monday or Sunday. Monday as default.',
2131
+ isEnum: true,
2132
+ options: {
2133
+ monday: 'Monday',
2134
+ sunday: 'Sunday',
2135
+ },
2136
+ },
2137
+ // 4) Day events
2138
+ [exports.SETTING_SHOW_EVENT_TIMELINE]: {
2139
+ value: true,
2140
+ type: SETTING_TYPE_BOOL, // bool
2141
+ section: 'mycalendar',
2142
+ public: true,
2143
+ label: 'Show event timeline',
2144
+ description: 'Day events section: Show a visual timeline bar under each event in the day list. Disabling this also stops related UI update timers (now dot / past status refresh).',
2145
+ },
2146
+ // 5) Day events auto-refresh (minutes)
2147
+ [exports.SETTING_DAY_EVENTS_REFRESH_MINUTES]: {
2148
+ value: 1,
2149
+ type: SETTING_TYPE_INT, // int
2150
+ section: 'mycalendar',
2151
+ public: true,
2152
+ label: 'Day events auto-refresh (minutes)',
2153
+ description: 'Day events section: How often the list refreshes.',
2154
+ },
2155
+ // 7) ICS Import
2156
+ [exports.SETTING_ICS_IMPORT_ALARMS_ENABLED]: {
2157
+ value: false,
2158
+ type: SETTING_TYPE_BOOL, // bool
2159
+ section: 'mycalendar',
2160
+ public: !mobile,
2161
+ label: 'Enable ICS import alarms',
2162
+ description: 'ICS import section: If enabled, alarms from ICS files will be imported as Todo notes. If disabled, existing alarms will be deleted on re-import.',
2163
+ },
2164
+ [exports.SETTING_ICS_IMPORT_ALARM_RANGE_DAYS]: {
2165
+ value: 30,
2166
+ type: SETTING_TYPE_INT, // int
2167
+ section: 'mycalendar',
2168
+ public: !mobile,
2169
+ label: 'ICS import alarm range (days)',
2170
+ description: 'ICS import section: Import events alarms from now up to N days ahead. Default 30. During reimport all alarms will regenerated.',
2171
+ },
2172
+ [exports.SETTING_ICS_IMPORT_EMPTY_TRASH_AFTER]: {
2173
+ value: false,
2174
+ type: SETTING_TYPE_BOOL, // bool
2175
+ section: 'mycalendar',
2176
+ public: !mobile,
2177
+ label: 'Empty trash after alarm cleanup',
2178
+ description: 'ICS import section: If enabled, the plugin will empty the trash after deleting old alarms. WARNING: This deletes ALL items in the trash.',
2179
+ },
2180
+ // 8) ICS export links (up to 4)
2181
+ [exports.SETTING_ICS_EXPORT_LINK1_TITLE]: {
2182
+ value: '',
2183
+ type: SETTING_TYPE_STRING, // string
2184
+ section: 'mycalendar',
2185
+ public: !mobile,
2186
+ label: 'ICS export link 1 title',
2187
+ description: 'ICS import section: Optional title for export link #1 (shown on button).',
2188
+ },
2189
+ [exports.SETTING_ICS_EXPORT_LINK1_URL]: {
2190
+ value: '',
2191
+ type: SETTING_TYPE_STRING, // string
2192
+ section: 'mycalendar',
2193
+ public: !mobile,
2194
+ label: 'ICS export link 1 URL',
2195
+ description: 'ICS import section: Optional URL for export link #1 (http/https only).',
2196
+ },
2197
+ [exports.SETTING_ICS_EXPORT_LINK2_TITLE]: {
2198
+ value: '',
2199
+ type: SETTING_TYPE_STRING,
2200
+ section: 'mycalendar',
2201
+ public: !mobile,
2202
+ label: 'ICS export link 2 title',
2203
+ description: 'ICS import section: Optional title for export link #2 (shown on button).',
2204
+ },
2205
+ [exports.SETTING_ICS_EXPORT_LINK2_URL]: {
2206
+ value: '',
2207
+ type: SETTING_TYPE_STRING,
2208
+ section: 'mycalendar',
2209
+ public: !mobile,
2210
+ label: 'ICS export link 2 URL',
2211
+ description: 'ICS import section: Optional URL for export link #2 (http/https only).',
2212
+ },
2213
+ [exports.SETTING_ICS_EXPORT_LINK3_TITLE]: {
2214
+ value: '',
2215
+ type: SETTING_TYPE_STRING,
2216
+ section: 'mycalendar',
2217
+ public: !mobile,
2218
+ label: 'ICS export link 3 title',
2219
+ description: 'ICS import section: Optional title for export link #3 (shown on button).',
2220
+ },
2221
+ [exports.SETTING_ICS_EXPORT_LINK3_URL]: {
2222
+ value: '',
2223
+ type: SETTING_TYPE_STRING,
2224
+ section: 'mycalendar',
2225
+ public: !mobile,
2226
+ label: 'ICS export link 3 URL',
2227
+ description: 'ICS import section: Optional URL for export link #3 (http/https only).',
2228
+ },
2229
+ [exports.SETTING_ICS_EXPORT_LINK4_TITLE]: {
2230
+ value: '',
2231
+ type: SETTING_TYPE_STRING,
2232
+ section: 'mycalendar',
2233
+ public: !mobile,
2234
+ label: 'ICS export link 4 title',
2235
+ description: 'ICS import section: Optional title for export link #4 (shown on button).',
2236
+ },
2237
+ [exports.SETTING_ICS_EXPORT_LINK4_URL]: {
2238
+ value: '',
2239
+ type: SETTING_TYPE_STRING,
2240
+ section: 'mycalendar',
2241
+ public: !mobile,
2242
+ label: 'ICS export link 4 URL',
2243
+ description: 'ICS import section: Optional URL for export link #4 (http/https only).',
2244
+ },
2245
+ // 10) Developer
2246
+ // 11) Enable debug logging
2247
+ [exports.SETTING_DEBUG]: {
2248
+ value: false,
2249
+ type: SETTING_TYPE_BOOL, // bool
2250
+ section: 'mycalendar',
2251
+ public: true,
2252
+ label: 'Enable debug logging',
2253
+ description: 'Enable visible in the interface extra logging to help debugging.',
2254
+ },
2255
+ });
2256
+ // Keep stored URLs safe even if user pastes `javascript:` etc.
2257
+ if (typeof ((_c = joplin === null || joplin === void 0 ? void 0 : joplin.settings) === null || _c === void 0 ? void 0 : _c.onChange) === 'function' && typeof ((_d = joplin === null || joplin === void 0 ? void 0 : joplin.settings) === null || _d === void 0 ? void 0 : _d.setValue) === 'function') {
2258
+ yield joplin.settings.onChange((event) => __awaiter(this, void 0, void 0, function* () {
2259
+ try {
2260
+ const keys = (event === null || event === void 0 ? void 0 : event.keys) || [];
2261
+ const maybeFixUrl = (key) => __awaiter(this, void 0, void 0, function* () {
2262
+ const raw = yield joplin.settings.value(key);
2263
+ const safe = sanitizeExternalUrl(raw);
2264
+ if (raw !== safe)
2265
+ yield joplin.settings.setValue(key, safe);
2266
+ });
2267
+ const maybeFixTitle = (key) => __awaiter(this, void 0, void 0, function* () {
2268
+ const raw = yield joplin.settings.value(key);
2269
+ const safe = sanitizeTitle(raw);
2270
+ if (raw !== safe)
2271
+ yield joplin.settings.setValue(key, safe);
2272
+ });
2273
+ const touchedUrl = ICS_EXPORT_URL_KEYS.some((k) => keys.includes(k));
2274
+ const touchedTitle = ICS_EXPORT_TITLE_KEYS.some((k) => keys.includes(k));
2275
+ const touchedDebug = keys.includes(exports.SETTING_DEBUG);
2276
+ if (!touchedUrl && !touchedTitle && !touchedDebug)
2277
+ return;
2278
+ for (const k of ICS_EXPORT_URL_KEYS) {
2279
+ if (keys.includes(k))
2280
+ yield maybeFixUrl(k);
2281
+ }
2282
+ for (const k of ICS_EXPORT_TITLE_KEYS) {
2283
+ if (keys.includes(k))
2284
+ yield maybeFixTitle(k);
2285
+ }
2286
+ if (touchedDebug) {
2287
+ const v = yield joplin.settings.value(exports.SETTING_DEBUG);
2288
+ (0, logger_1.setDebugEnabled)(!!v);
2289
+ }
2290
+ }
2291
+ catch (_a) {
2292
+ // ignore
2293
+ }
2294
+ }));
2295
+ }
2296
+ const v = yield joplin.settings.value(exports.SETTING_DEBUG);
2297
+ (0, logger_1.setDebugEnabled)(!!v);
2298
+ });
2299
+ }
2300
+ // Common
2301
+ function getDebugEnabled(joplin) {
2302
+ return __awaiter(this, void 0, void 0, function* () {
2303
+ return !!(yield joplin.settings.value(exports.SETTING_DEBUG));
2304
+ });
2305
+ }
2306
+ // Calendar
2307
+ function getWeekStart(joplin) {
2308
+ return __awaiter(this, void 0, void 0, function* () {
2309
+ const raw = yield joplin.settings.value(exports.SETTING_WEEK_START);
2310
+ const v = String(raw !== null && raw !== void 0 ? raw : '').toLowerCase().trim();
2311
+ return (v === 'sunday' || v === 'monday') ? v : 'monday';
2312
+ });
2313
+ }
2314
+ // Day events
2315
+ function getShowEventTimeline(joplin) {
2316
+ return __awaiter(this, void 0, void 0, function* () {
2317
+ const raw = yield joplin.settings.value(exports.SETTING_SHOW_EVENT_TIMELINE);
2318
+ // Default should be true even if the setting is missing/undefined (older installs / migrations)
2319
+ if (raw === null || raw === undefined)
2320
+ return true;
2321
+ return Boolean(raw);
2322
+ });
2323
+ }
2324
+ function getDayEventsRefreshMinutes(joplin) {
2325
+ return __awaiter(this, void 0, void 0, function* () {
2326
+ const raw = yield joplin.settings.value(exports.SETTING_DAY_EVENTS_REFRESH_MINUTES);
2327
+ const n = Number(raw);
2328
+ if (!Number.isFinite(n))
2329
+ return 1;
2330
+ return Math.min(60, Math.max(1, Math.round(n)));
2331
+ });
2332
+ }
2333
+ // ICS import
2334
+ function getIcsImportAlarmsEnabled(joplin) {
2335
+ return __awaiter(this, void 0, void 0, function* () {
2336
+ return !!(yield joplin.settings.value(exports.SETTING_ICS_IMPORT_ALARMS_ENABLED));
2337
+ });
2338
+ }
2339
+ function getIcsImportAlarmRangeDays(joplin) {
2340
+ return __awaiter(this, void 0, void 0, function* () {
2341
+ const raw = yield joplin.settings.value(exports.SETTING_ICS_IMPORT_ALARM_RANGE_DAYS);
2342
+ if (raw === null || raw === undefined) {
2343
+ return 30;
2344
+ }
2345
+ const n = Number(raw);
2346
+ if (!Number.isFinite(n)) {
2347
+ return 30;
2348
+ }
2349
+ // Guardrails: keep the import range reasonable.
2350
+ // If user entered 0, we clamp to 1.
2351
+ return Math.min(365, Math.max(1, Math.round(n)));
2352
+ });
2353
+ }
2354
+ function getIcsImportEmptyTrashAfter(joplin) {
2355
+ return __awaiter(this, void 0, void 0, function* () {
2356
+ return !!(yield joplin.settings.value(exports.SETTING_ICS_IMPORT_EMPTY_TRASH_AFTER));
2357
+ });
2358
+ }
2359
+ function getIcsExportLinks(joplin) {
2360
+ return __awaiter(this, void 0, void 0, function* () {
2361
+ const out = [];
2362
+ for (const p of ICS_EXPORT_LINK_PAIRS) {
2363
+ const rawTitle = yield joplin.settings.value(p.titleKey);
2364
+ const rawUrl = yield joplin.settings.value(p.urlKey);
2365
+ const title = sanitizeTitle(rawTitle);
2366
+ const url = sanitizeExternalUrl(rawUrl);
2367
+ if (!url)
2368
+ continue;
2369
+ out.push({ title, url });
2370
+ }
2371
+ return out;
2372
+ });
2373
+ }
2374
+
2375
+
2376
+ /***/ },
2377
+
2378
+ /***/ "./src/main/uiBridge/panelController.ts"
2379
+ /*!**********************************************!*\
2380
+ !*** ./src/main/uiBridge/panelController.ts ***!
2381
+ \**********************************************/
2382
+ (__unused_webpack_module, exports, __webpack_require__) {
2383
+
2384
+
2385
+ // src/main/uiBridge/panelController.ts
2386
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2387
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
2388
+ return new (P || (P = Promise))(function (resolve, reject) {
2389
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
2390
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
2391
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
2392
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
2393
+ });
2394
+ };
2395
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2396
+ exports.registerCalendarPanelController = registerCalendarPanelController;
2397
+ const eventsCache_1 = __webpack_require__(/*! ../services/eventsCache */ "./src/main/services/eventsCache.ts");
2398
+ const icsImportService_1 = __webpack_require__(/*! ../services/icsImportService */ "./src/main/services/icsImportService.ts");
2399
+ const toast_1 = __webpack_require__(/*! ../utils/toast */ "./src/main/utils/toast.ts");
2400
+ const uiSettings_1 = __webpack_require__(/*! ./uiSettings */ "./src/main/uiBridge/uiSettings.ts");
2401
+ const logger_1 = __webpack_require__(/*! ../utils/logger */ "./src/main/utils/logger.ts");
2402
+ const settings_1 = __webpack_require__(/*! ../settings/settings */ "./src/main/settings/settings.ts");
2403
+ const folderService_1 = __webpack_require__(/*! ../services/folderService */ "./src/main/services/folderService.ts");
2404
+ function isoDate(utc) {
2405
+ return new Date(utc).toISOString().slice(0, 10);
2406
+ }
2407
+ function isNumber(v) {
2408
+ return typeof v === 'number' && Number.isFinite(v);
2409
+ }
2410
+ function isString(v) {
2411
+ return typeof v === 'string';
2412
+ }
2413
+ function isRecord(v) {
2414
+ return typeof v === 'object' && v !== null;
2415
+ }
2416
+ const KNOWN_MSG_NAMES = [
2417
+ 'uiReady',
2418
+ 'requestRangeEvents',
2419
+ 'dateClick',
2420
+ 'openNote',
2421
+ 'exportRangeIcs',
2422
+ 'icsImport',
2423
+ 'requestFolders',
2424
+ ];
2425
+ function isKnownMsgName(v) {
2426
+ return isString(v) && KNOWN_MSG_NAMES.includes(v);
2427
+ }
2428
+ function buildRangeIcsFilename(fromUtc, toUtc) {
2429
+ return `mycalendar_${isoDate(fromUtc)}_${isoDate(toUtc)}.ics`;
2430
+ }
2431
+ function registerCalendarPanelController(joplin, panel, helpers) {
2432
+ return __awaiter(this, void 0, void 0, function* () {
2433
+ const post = (message) => joplin.views.panels.postMessage(panel, message);
2434
+ yield joplin.views.panels.onMessage(panel, (rawMsg) => __awaiter(this, void 0, void 0, function* () {
2435
+ try {
2436
+ if (!isRecord(rawMsg) || !isKnownMsgName(rawMsg.name))
2437
+ return;
2438
+ const msg = rawMsg;
2439
+ switch (msg.name) {
2440
+ case 'uiReady': {
2441
+ yield (0, uiSettings_1.pushUiSettings)(joplin, panel);
2442
+ yield post({ name: 'redrawMonth' });
2443
+ return;
2444
+ }
2445
+ case 'requestRangeEvents': {
2446
+ if (!isNumber(msg.fromUtc) || !isNumber(msg.toUtc))
2447
+ return;
2448
+ const all = yield (0, eventsCache_1.ensureAllEventsCache)(joplin);
2449
+ const list = helpers.expandAllInRange(all, msg.fromUtc, msg.toUtc);
2450
+ yield post({ name: 'rangeEvents', events: list });
2451
+ return;
2452
+ }
2453
+ case 'dateClick': {
2454
+ if (!isNumber(msg.dateUtc))
2455
+ return;
2456
+ const dayStart = msg.dateUtc;
2457
+ const dayEnd = dayStart + 24 * 60 * 60 * 1000 - 1;
2458
+ const all = yield (0, eventsCache_1.ensureAllEventsCache)(joplin);
2459
+ const list = helpers
2460
+ .expandAllInRange(all, dayStart, dayEnd)
2461
+ .filter((e) => isNumber(e === null || e === void 0 ? void 0 : e.startUtc) && e.startUtc >= dayStart && e.startUtc <= dayEnd);
2462
+ yield post({
2463
+ name: 'showEvents',
2464
+ dateUtc: msg.dateUtc,
2465
+ events: list,
2466
+ });
2467
+ return;
2468
+ }
2469
+ case 'openNote': {
2470
+ if (msg.id) {
2471
+ yield joplin.commands.execute('openNote', msg.id);
2472
+ }
2473
+ return;
2474
+ }
2475
+ case 'exportRangeIcs': {
2476
+ if (!isNumber(msg.fromUtc) || !isNumber(msg.toUtc))
2477
+ return;
2478
+ const all = yield (0, eventsCache_1.ensureAllEventsCache)(joplin);
2479
+ const list = helpers.expandAllInRange(all, msg.fromUtc, msg.toUtc);
2480
+ const ics = helpers.buildICS(list);
2481
+ yield post({
2482
+ name: 'rangeIcs',
2483
+ ics,
2484
+ filename: buildRangeIcsFilename(msg.fromUtc, msg.toUtc),
2485
+ });
2486
+ return;
2487
+ }
2488
+ case 'icsImport': {
2489
+ const sendStatus = (text) => __awaiter(this, void 0, void 0, function* () {
2490
+ yield post({ name: 'importStatus', text });
2491
+ yield (0, toast_1.showToast)('info', text, 5000);
2492
+ });
2493
+ try {
2494
+ if (!isString(msg.ics) || msg.ics.length === 0) {
2495
+ const errText = 'Missing ICS content';
2496
+ yield post({ name: 'importError', error: errText });
2497
+ yield (0, toast_1.showToast)('error', `ICS import failed: ${errText}`, 5000);
2498
+ return;
2499
+ }
2500
+ const targetFolderId = isString(msg.targetFolderId) ? msg.targetFolderId : undefined;
2501
+ const preserveLocalColor = msg.preserveLocalColor !== false;
2502
+ const importDefaultColor = isString(msg.importDefaultColor) && /^#[0-9a-fA-F]{6}$/.test(msg.importDefaultColor)
2503
+ ? msg.importDefaultColor
2504
+ : undefined;
2505
+ const importAlarmRangeDays = yield (0, settings_1.getIcsImportAlarmRangeDays)(joplin);
2506
+ const res = yield (0, icsImportService_1.importIcsIntoNotes)(joplin, msg.ics, sendStatus, targetFolderId, preserveLocalColor, importDefaultColor, importAlarmRangeDays);
2507
+ (0, eventsCache_1.invalidateAllEventsCache)();
2508
+ yield post(Object.assign({ name: 'importDone' }, res));
2509
+ const doneText = `ICS import finished: added=${res.added}, updated=${res.updated}, skipped=${res.skipped}, errors=${res.errors}, alarmsCreated=${res.alarmsCreated}, alarmsDeleted=${res.alarmsDeleted}`;
2510
+ yield (0, toast_1.showToast)(res.errors > 0 ? 'warning' : 'success', doneText, 5000);
2511
+ }
2512
+ catch (e) {
2513
+ const errText = String((e === null || e === void 0 ? void 0 : e.message) || e);
2514
+ yield post({ name: 'importError', error: errText });
2515
+ yield (0, toast_1.showToast)('error', `ICS import failed: ${errText}`, 5000);
2516
+ }
2517
+ return;
2518
+ }
2519
+ case 'requestFolders': {
2520
+ const rows = yield (0, folderService_1.getAllFolders)(joplin);
2521
+ const folders = (0, folderService_1.flattenFolderTree)(rows);
2522
+ yield post({ name: 'folders', folders });
2523
+ return;
2524
+ }
2525
+ default:
2526
+ return;
2527
+ }
2528
+ }
2529
+ catch (e) {
2530
+ (0, logger_1.err)('panelController', 'onMessage error:', e);
2531
+ }
2532
+ }));
2533
+ });
2534
+ }
2535
+
2536
+
2537
+ /***/ },
2538
+
2539
+ /***/ "./src/main/uiBridge/uiSettings.ts"
2540
+ /*!*****************************************!*\
2541
+ !*** ./src/main/uiBridge/uiSettings.ts ***!
2542
+ \*****************************************/
2543
+ (__unused_webpack_module, exports, __webpack_require__) {
2544
+
2545
+
2546
+ // src/main/uiBridge/uiSettings.ts
2547
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
2548
+ if (k2 === undefined) k2 = k;
2549
+ var desc = Object.getOwnPropertyDescriptor(m, k);
2550
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2551
+ desc = { enumerable: true, get: function() { return m[k]; } };
2552
+ }
2553
+ Object.defineProperty(o, k2, desc);
2554
+ }) : (function(o, m, k, k2) {
2555
+ if (k2 === undefined) k2 = k;
2556
+ o[k2] = m[k];
2557
+ }));
2558
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
2559
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
2560
+ }) : function(o, v) {
2561
+ o["default"] = v;
2562
+ });
2563
+ var __importStar = (this && this.__importStar) || (function () {
2564
+ var ownKeys = function(o) {
2565
+ ownKeys = Object.getOwnPropertyNames || function (o) {
2566
+ var ar = [];
2567
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
2568
+ return ar;
2569
+ };
2570
+ return ownKeys(o);
2571
+ };
2572
+ return function (mod) {
2573
+ if (mod && mod.__esModule) return mod;
2574
+ var result = {};
2575
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
2576
+ __setModuleDefault(result, mod);
2577
+ return result;
2578
+ };
2579
+ })();
2580
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2581
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
2582
+ return new (P || (P = Promise))(function (resolve, reject) {
2583
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
2584
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
2585
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
2586
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
2587
+ });
2588
+ };
2589
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2590
+ exports.UI_SETTINGS_MESSAGE_NAME = void 0;
2591
+ exports.buildUiSettingsMessage = buildUiSettingsMessage;
2592
+ exports.pushUiSettings = pushUiSettings;
2593
+ const settings = __importStar(__webpack_require__(/*! ../settings/settings */ "./src/main/settings/settings.ts"));
2594
+ const logger_1 = __webpack_require__(/*! ../utils/logger */ "./src/main/utils/logger.ts");
2595
+ exports.UI_SETTINGS_MESSAGE_NAME = 'uiSettings';
2596
+ function getIcsExportLinksCompat(joplin) {
2597
+ return __awaiter(this, void 0, void 0, function* () {
2598
+ const s = settings;
2599
+ return typeof s.getIcsExportLinks === 'function' ? yield s.getIcsExportLinks(joplin) : [];
2600
+ });
2601
+ }
2602
+ /**
2603
+ * Reads UI-relevant settings from the main process.
2604
+ * Separated for easier unit testing and future reuse.
2605
+ */
2606
+ function buildUiSettingsMessage(joplin) {
2607
+ return __awaiter(this, void 0, void 0, function* () {
2608
+ const [weekStart, debugRaw, icsExportLinks, dayEventsRefreshMinutes, showEventTimeline] = yield Promise.all([
2609
+ settings.getWeekStart(joplin),
2610
+ settings.getDebugEnabled(joplin),
2611
+ getIcsExportLinksCompat(joplin),
2612
+ settings.getDayEventsRefreshMinutes(joplin),
2613
+ settings.getShowEventTimeline(joplin),
2614
+ ]);
2615
+ const debugEnabled = Boolean(debugRaw);
2616
+ // Main-side logger should follow the same setting
2617
+ (0, logger_1.setDebugEnabled)(debugEnabled);
2618
+ return {
2619
+ name: exports.UI_SETTINGS_MESSAGE_NAME,
2620
+ weekStart,
2621
+ debug: debugEnabled,
2622
+ icsExportLinks,
2623
+ dayEventsRefreshMinutes,
2624
+ showEventTimeline,
2625
+ };
2626
+ });
2627
+ }
2628
+ function pushUiSettings(joplin, panel) {
2629
+ return __awaiter(this, void 0, void 0, function* () {
2630
+ var _a, _b;
2631
+ const message = yield buildUiSettingsMessage(joplin);
2632
+ const pm = (_b = (_a = joplin === null || joplin === void 0 ? void 0 : joplin.views) === null || _a === void 0 ? void 0 : _a.panels) === null || _b === void 0 ? void 0 : _b.postMessage;
2633
+ if (typeof pm !== 'function')
2634
+ return;
2635
+ yield pm(panel, message);
2636
+ });
2637
+ }
2638
+
2639
+
2640
+ /***/ },
2641
+
2642
+ /***/ "./src/main/utils/dateTimeUtils.ts"
2643
+ /*!*****************************************!*\
2644
+ !*** ./src/main/utils/dateTimeUtils.ts ***!
2645
+ \*****************************************/
2646
+ (__unused_webpack_module, exports) {
2647
+
2648
+
2649
+ // src/main/utils/dateTimeUtils.ts
2650
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2651
+ exports.parseIsoDurationToMs = parseIsoDurationToMs;
2652
+ exports.parseMyCalDateToDate = parseMyCalDateToDate;
2653
+ exports.formatAlarmTitleTime = formatAlarmTitleTime;
2654
+ exports.addDays = addDays;
2655
+ exports.addMonths = addMonths;
2656
+ exports.addYears = addYears;
2657
+ exports.weekdayToJs = weekdayToJs;
2658
+ exports.formatDateForAlarm = formatDateForAlarm;
2659
+ exports.icsDateToMyCalText = icsDateToMyCalText;
2660
+ exports.computeAlarmWhen = computeAlarmWhen;
2661
+ exports.formatTriggerDescription = formatTriggerDescription;
2662
+ const MS_PER_SECOND = 1000;
2663
+ const MS_PER_MINUTE = 60 * MS_PER_SECOND;
2664
+ const MS_PER_HOUR = 60 * MS_PER_MINUTE;
2665
+ const MS_PER_DAY = 24 * MS_PER_HOUR;
2666
+ const WEEKDAY_TO_JS = {
2667
+ SU: 0,
2668
+ MO: 1,
2669
+ TU: 2,
2670
+ WE: 3,
2671
+ TH: 4,
2672
+ FR: 5,
2673
+ SA: 6,
2674
+ };
2675
+ const pad2 = (n) => String(n).padStart(2, '0');
2676
+ function parseIsoDurationToMs(s) {
2677
+ const t = s.trim().toUpperCase();
2678
+ if (!t)
2679
+ return null;
2680
+ const sign = t.startsWith('-') ? -1 : 1;
2681
+ const core = t.startsWith('-') || t.startsWith('+') ? t.slice(1) : t;
2682
+ // Supported subset: PnWnDTnHnMnS (no months/years)
2683
+ const m = core.match(/^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/);
2684
+ if (!m)
2685
+ return null;
2686
+ const parts = m.slice(1);
2687
+ // Reject bare "P" / "PT" (no components)
2688
+ if (parts.every(v => v == null))
2689
+ return null;
2690
+ const w = m[1] ? Number(m[1]) : 0;
2691
+ const d = m[2] ? Number(m[2]) : 0;
2692
+ const h = m[3] ? Number(m[3]) : 0;
2693
+ const mi = m[4] ? Number(m[4]) : 0;
2694
+ const se = m[5] ? Number(m[5]) : 0;
2695
+ if (![w, d, h, mi, se].every(n => Number.isFinite(n)))
2696
+ return null;
2697
+ const ms = (w * 7 + d) * MS_PER_DAY +
2698
+ h * MS_PER_HOUR +
2699
+ mi * MS_PER_MINUTE +
2700
+ se * MS_PER_SECOND;
2701
+ return sign * ms;
2702
+ }
2703
+ function parseMyCalDateToDate(s) {
2704
+ if (!s)
2705
+ return null;
2706
+ const t = s.trim();
2707
+ if (!t)
2708
+ return null;
2709
+ // Accept either "YYYY-MM-DD HH:mm:ss(+00:00)" or ISO-like forms.
2710
+ const iso = t.replace(' ', 'T');
2711
+ const d = new Date(iso);
2712
+ return Number.isNaN(d.getTime()) ? null : d;
2713
+ }
2714
+ function formatAlarmTitleTime(d) {
2715
+ // Intentionally local time (UI/notification title)
2716
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
2717
+ }
2718
+ function addDays(d, days) {
2719
+ const out = new Date(d.getTime());
2720
+ // Use UTC variants to avoid DST/timezone shifts when input is a UTC instant.
2721
+ out.setUTCDate(out.getUTCDate() + days);
2722
+ return out;
2723
+ }
2724
+ function addMonths(d, months) {
2725
+ const out = new Date(d.getTime());
2726
+ out.setUTCMonth(out.getUTCMonth() + months);
2727
+ return out;
2728
+ }
2729
+ function addYears(d, years) {
2730
+ const out = new Date(d.getTime());
2731
+ out.setUTCFullYear(out.getUTCFullYear() + years);
2732
+ return out;
2733
+ }
2734
+ function weekdayToJs(day) {
2735
+ const d = day.trim().toUpperCase();
2736
+ return Object.prototype.hasOwnProperty.call(WEEKDAY_TO_JS, d) ? WEEKDAY_TO_JS[d] : null;
2737
+ }
2738
+ function formatDateForAlarm(d) {
2739
+ return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}:${pad2(d.getUTCSeconds())}+00:00`;
2740
+ }
2741
+ function icsDateToMyCalText(icsValue) {
2742
+ if (!icsValue)
2743
+ return undefined;
2744
+ const raw = icsValue.trim();
2745
+ if (!raw)
2746
+ return undefined;
2747
+ // normalize trailing "z" -> "Z"
2748
+ const v = raw.endsWith('z') ? `${raw.slice(0, -1)}Z` : raw;
2749
+ // YYYYMMDDTHHMMSSZ
2750
+ let m = v.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/);
2751
+ if (m)
2752
+ return `${m[1]}-${m[2]}-${m[3]} ${m[4]}:${m[5]}:${m[6]}+00:00`;
2753
+ // YYYYMMDDTHHMMSS
2754
+ m = v.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})$/);
2755
+ if (m)
2756
+ return `${m[1]}-${m[2]}-${m[3]} ${m[4]}:${m[5]}:${m[6]}`;
2757
+ // YYYYMMDD (all-day)
2758
+ m = v.match(/^(\d{4})(\d{2})(\d{2})$/);
2759
+ if (m)
2760
+ return `${m[1]}-${m[2]}-${m[3]} 00:00:00`;
2761
+ // Already ISO-like -> normalize "T" to space
2762
+ if (/^\d{4}-\d{2}-\d{2}/.test(v))
2763
+ return v.replace('T', ' ');
2764
+ return undefined;
2765
+ }
2766
+ function computeAlarmWhen(alarm, occ) {
2767
+ const trig = alarm.trigger.trim();
2768
+ if (!trig)
2769
+ return null;
2770
+ const abs = icsDateToMyCalText(trig);
2771
+ if (abs)
2772
+ return parseMyCalDateToDate(abs);
2773
+ const delta = parseIsoDurationToMs(trig);
2774
+ if (delta === null)
2775
+ return null;
2776
+ const base = alarm.related === 'END' ? occ.end : occ.start;
2777
+ return new Date(base.getTime() + delta);
2778
+ }
2779
+ function formatTriggerDescription(trigger) {
2780
+ const t = trigger.trim().toUpperCase();
2781
+ if (t.startsWith('P') || t.startsWith('-P') || t.startsWith('+P')) {
2782
+ const ms = parseIsoDurationToMs(t);
2783
+ if (ms === null)
2784
+ return t;
2785
+ const isBefore = ms < 0;
2786
+ const absMs = Math.abs(ms);
2787
+ if (ms === 0)
2788
+ return 'at time of event';
2789
+ const mins = Math.floor(absMs / MS_PER_MINUTE);
2790
+ const hours = Math.floor(mins / 60);
2791
+ const days = Math.floor(hours / 24);
2792
+ let timeStr = '';
2793
+ if (days > 0) {
2794
+ timeStr = `${days} day${days > 1 ? 's' : ''}`;
2795
+ }
2796
+ else if (hours > 0) {
2797
+ timeStr = `${hours} hour${hours > 1 ? 's' : ''}`;
2798
+ }
2799
+ else {
2800
+ timeStr = `${mins} minute${mins !== 1 ? 's' : ''}`;
2801
+ }
2802
+ return isBefore ? `${timeStr} before` : `${timeStr} after`;
2803
+ }
2804
+ return 'at specific time';
2805
+ }
2806
+
2807
+
2808
+ /***/ },
2809
+
2810
+ /***/ "./src/main/utils/dateUtils.ts"
2811
+ /*!*************************************!*\
2812
+ !*** ./src/main/utils/dateUtils.ts ***!
2813
+ \*************************************/
2814
+ (__unused_webpack_module, exports) {
2815
+
2816
+
2817
+ // src/main/utils/dateUtils.ts
2818
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2819
+ exports.DAY_MS = void 0;
2820
+ exports.parseYmdHmsLocal = parseYmdHmsLocal;
2821
+ exports.addDaysYMD = addDaysYMD;
2822
+ exports.weekdayMon0 = weekdayMon0;
2823
+ exports.getPartsInTz = getPartsInTz;
2824
+ exports.zonedTimeToUtcMs = zonedTimeToUtcMs;
2825
+ exports.DAY_MS = 24 * 60 * 60 * 1000;
2826
+ const YMD_HMS_RE = /^\s*(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T]+(\d{1,2})(?::(\d{1,2}))?(?::(\d{1,2}))?)?\s*$/;
2827
+ function assertIntInRange(name, v, min, max) {
2828
+ if (!Number.isInteger(v) || v < min || v > max) {
2829
+ throw new Error(`${name} out of range: ${v}`);
2830
+ }
2831
+ }
2832
+ function isValidUtcDate(Y, M, D) {
2833
+ const dt = new Date(Date.UTC(Y, M - 1, D));
2834
+ return dt.getUTCFullYear() === Y && dt.getUTCMonth() + 1 === M && dt.getUTCDate() === D;
2835
+ }
2836
+ function parseYmdHmsLocal(s) {
2837
+ // Accepts:
2838
+ // - "2025-09-01"
2839
+ // - "2025-09-01 15:30"
2840
+ // - "2025-09-01 15:30:00"
2841
+ // - "2025-09-01T15:30:00"
2842
+ const m = s.match(YMD_HMS_RE);
2843
+ if (!m)
2844
+ throw new Error(`Invalid datetime format: "${s}"`);
2845
+ const Y = Number(m[1]);
2846
+ const M = Number(m[2]);
2847
+ const D = Number(m[3]);
2848
+ const h = m[4] != null ? Number(m[4]) : 0;
2849
+ const min = m[5] != null ? Number(m[5]) : 0;
2850
+ const sec = m[6] != null ? Number(m[6]) : 0;
2851
+ assertIntInRange('year', Y, 1, 9999);
2852
+ assertIntInRange('month', M, 1, 12);
2853
+ assertIntInRange('day', D, 1, 31);
2854
+ assertIntInRange('hour', h, 0, 23);
2855
+ assertIntInRange('minute', min, 0, 59);
2856
+ assertIntInRange('second', sec, 0, 59);
2857
+ if (!isValidUtcDate(Y, M, D)) {
2858
+ throw new Error(`Invalid date: ${Y}-${String(M).padStart(2, '0')}-${String(D).padStart(2, '0')}`);
2859
+ }
2860
+ return { Y, M, D, h, m: min, sec };
2861
+ }
2862
+ function addDaysYMD(Y, M, D, deltaDays) {
2863
+ const dt = new Date(Date.UTC(Y, M - 1, D) + deltaDays * exports.DAY_MS);
2864
+ return { Y: dt.getUTCFullYear(), M: dt.getUTCMonth() + 1, D: dt.getUTCDate() };
2865
+ }
2866
+ function weekdayMon0(Y, M, D) {
2867
+ // Monday=0..Sunday=6
2868
+ const dowSun0 = new Date(Date.UTC(Y, M - 1, D)).getUTCDay(); // Sun=0
2869
+ return (dowSun0 + 6) % 7;
2870
+ }
2871
+ function getPartsInTz(msUtc, tz) {
2872
+ const mp = formatToNumberParts(getFmtYmd(tz), msUtc);
2873
+ return { Y: mp.year, M: mp.month, D: mp.day };
2874
+ }
2875
+ const fmtYmdByTz = new Map();
2876
+ const fmtYmdHmsByTz = new Map();
2877
+ function getFmtYmd(tz) {
2878
+ const cached = fmtYmdByTz.get(tz);
2879
+ if (cached)
2880
+ return cached;
2881
+ const fmt = new Intl.DateTimeFormat('en-CA', {
2882
+ timeZone: tz,
2883
+ year: 'numeric',
2884
+ month: '2-digit',
2885
+ day: '2-digit',
2886
+ hour12: false,
2887
+ });
2888
+ fmtYmdByTz.set(tz, fmt);
2889
+ return fmt;
2890
+ }
2891
+ function getFmtYmdHms(tz) {
2892
+ const cached = fmtYmdHmsByTz.get(tz);
2893
+ if (cached)
2894
+ return cached;
2895
+ const fmt = new Intl.DateTimeFormat('en-CA', {
2896
+ timeZone: tz,
2897
+ year: 'numeric',
2898
+ month: '2-digit',
2899
+ day: '2-digit',
2900
+ hour: '2-digit',
2901
+ minute: '2-digit',
2902
+ second: '2-digit',
2903
+ hour12: false,
2904
+ });
2905
+ fmtYmdHmsByTz.set(tz, fmt);
2906
+ return fmt;
2907
+ }
2908
+ function formatToNumberParts(fmt, msUtc) {
2909
+ const parts = fmt.formatToParts(new Date(msUtc));
2910
+ const mp = {};
2911
+ for (const p of parts) {
2912
+ if (p.type !== 'literal')
2913
+ mp[p.type] = Number(p.value);
2914
+ }
2915
+ return mp;
2916
+ }
2917
+ /**
2918
+ * Convert "local datetime in tz" -> UTC ms.
2919
+ *
2920
+ * Notes:
2921
+ * - If the local time does not exist (DST "spring forward" gap), this function throws.
2922
+ * - If the local time is ambiguous (DST "fall back"), it picks the 'earlier' instant by default.
2923
+ */
2924
+ function zonedTimeToUtcMs(localY, localM, localD, localH, localMin, localSec, tz, opts = {}) {
2925
+ var _a;
2926
+ const prefer = (_a = opts.prefer) !== null && _a !== void 0 ? _a : 'earlier';
2927
+ const wantAsUtc = Date.UTC(localY, localM - 1, localD, localH, localMin, localSec);
2928
+ const getAsUtcMsAtZoned = (utcMs) => {
2929
+ const mp = formatToNumberParts(getFmtYmdHms(tz), utcMs);
2930
+ return Date.UTC(mp.year, mp.month - 1, mp.day, mp.hour, mp.minute, mp.second);
2931
+ };
2932
+ const getOffsetMsAtUtc = (utcMs) => getAsUtcMsAtZoned(utcMs) - utcMs;
2933
+ const buildCandidate = (offsetMs) => wantAsUtc - offsetMs;
2934
+ // Two-pass (usually enough).
2935
+ const off1 = getOffsetMsAtUtc(wantAsUtc);
2936
+ const utc1 = buildCandidate(off1);
2937
+ const off2 = getOffsetMsAtUtc(utc1);
2938
+ const utc2 = buildCandidate(off2);
2939
+ const mp2 = formatToNumberParts(getFmtYmdHms(tz), utc2);
2940
+ const matches = mp2.year === localY &&
2941
+ mp2.month === localM &&
2942
+ mp2.day === localD &&
2943
+ mp2.hour === localH &&
2944
+ mp2.minute === localMin &&
2945
+ mp2.second === localSec;
2946
+ if (matches) {
2947
+ // Handle potential DST "fall back" ambiguity:
2948
+ // if there are multiple UTC instants that map to the same local wall-clock time,
2949
+ // pick earlier/later based on preference.
2950
+ const matchesLocal = (utcMs) => {
2951
+ const mp = formatToNumberParts(getFmtYmdHms(tz), utcMs);
2952
+ return (mp.year === localY &&
2953
+ mp.month === localM &&
2954
+ mp.day === localD &&
2955
+ mp.hour === localH &&
2956
+ mp.minute === localMin &&
2957
+ mp.second === localSec);
2958
+ };
2959
+ const HOUR_MS = 60 * 60 * 1000;
2960
+ const candidates = new Set([utc2]);
2961
+ // ±1h is sufficient for most DST overlaps; ±2h is a small safety net for rare zones.
2962
+ for (const d of [-2 * HOUR_MS, -HOUR_MS, HOUR_MS, 2 * HOUR_MS]) {
2963
+ const u = utc2 + d;
2964
+ if (matchesLocal(u))
2965
+ candidates.add(u);
2966
+ }
2967
+ if (candidates.size === 1)
2968
+ return utc2;
2969
+ const arr = Array.from(candidates).sort((a, b) => a - b);
2970
+ return prefer === 'earlier' ? arr[0] : arr[arr.length - 1];
2971
+ }
2972
+ throw new Error(`Non-existent local time in ${tz}: ${localY}-${String(localM).padStart(2, '0')}-${String(localD).padStart(2, '0')} ${String(localH).padStart(2, '0')}:${String(localMin).padStart(2, '0')}:${String(localSec).padStart(2, '0')}`);
2973
+ }
2974
+
2975
+
2976
+ /***/ },
2977
+
2978
+ /***/ "./src/main/utils/joplinUtils.ts"
2979
+ /*!***************************************!*\
2980
+ !*** ./src/main/utils/joplinUtils.ts ***!
2981
+ \***************************************/
2982
+ (__unused_webpack_module, exports) {
2983
+
2984
+
2985
+ // src/main/utils/joplinUtils.ts
2986
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2987
+ exports.normalizeRecurrenceIdForKey = normalizeRecurrenceIdForKey;
2988
+ exports.makeEventKey = makeEventKey;
2989
+ exports.parseUidAndRecurrence = parseUidAndRecurrence;
2990
+ exports.extractAllAlarmKeysFromBody = extractAllAlarmKeysFromBody;
2991
+ exports.extractAllEventKeysFromBody = extractAllEventKeysFromBody;
2992
+ exports.replaceEventBlockByKey = replaceEventBlockByKey;
2993
+ exports.extractEventColorFromBody = extractEventColorFromBody;
2994
+ function normalizeRecurrenceIdForKey(recurrenceId) {
2995
+ const v = (recurrenceId || '').trim();
2996
+ if (!v)
2997
+ return '';
2998
+ // Keep DATE:yyyyMMdd as-is (date-only recurrence instances)
2999
+ if (/^DATE:\d{8}$/i.test(v))
3000
+ return v;
3001
+ // Backward compatible normalization
3002
+ const tzMatch = v.match(/^[^:]+:(\d{8}T\d{6}Z?)$/);
3003
+ if (tzMatch)
3004
+ return tzMatch[1];
3005
+ return v;
3006
+ }
3007
+ function makeEventKey(uid, recurrenceId) {
3008
+ const u = (uid || '').trim();
3009
+ const rid = normalizeRecurrenceIdForKey(recurrenceId);
3010
+ return `${u}|${rid}`;
3011
+ }
3012
+ function parseUidAndRecurrence(inner) {
3013
+ var _a, _b;
3014
+ const uidM = inner.match(/^[ \t]*uid[ \t]*:[ \t]*(.*?)[ \t]*$/im);
3015
+ const ridM = inner.match(/^[ \t]*recurrence_id[ \t]*:[ \t]*(.*?)[ \t]*$/im);
3016
+ return {
3017
+ uid: ((_a = uidM === null || uidM === void 0 ? void 0 : uidM[1]) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
3018
+ recurrence_id: ((_b = ridM === null || ridM === void 0 ? void 0 : ridM[1]) !== null && _b !== void 0 ? _b : '').trim(),
3019
+ };
3020
+ }
3021
+ function extractAllAlarmKeysFromBody(body) {
3022
+ var _a;
3023
+ const re = /(^|\r?\n)[ \t]*```mycalendar-alarm[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*```(?=\r?\n|$)/g;
3024
+ const out = [];
3025
+ let m;
3026
+ while ((m = re.exec(body)) !== null) {
3027
+ const inner = m[2] || '';
3028
+ const meta = parseUidAndRecurrence(inner);
3029
+ if (!meta.uid)
3030
+ continue;
3031
+ const rid = ((_a = meta.recurrence_id) !== null && _a !== void 0 ? _a : '').trim();
3032
+ out.push({ key: makeEventKey(meta.uid, rid), uid: meta.uid, recurrence_id: rid });
3033
+ }
3034
+ return out;
3035
+ }
3036
+ function extractAllEventKeysFromBody(body) {
3037
+ const re = /(^|\r?\n)[ \t]*```mycalendar-event[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*```(?=\r?\n|$)/g;
3038
+ const keys = [];
3039
+ let m;
3040
+ while ((m = re.exec(body)) !== null) {
3041
+ const inner = m[2] || '';
3042
+ const meta = parseUidAndRecurrence(inner);
3043
+ if (!meta.uid)
3044
+ continue;
3045
+ keys.push(makeEventKey(meta.uid, meta.recurrence_id));
3046
+ }
3047
+ return keys;
3048
+ }
3049
+ function replaceEventBlockByKey(body, uid, recurrenceId, newBlock) {
3050
+ const targetUid = (uid || '').trim();
3051
+ const targetRid = normalizeRecurrenceIdForKey(recurrenceId);
3052
+ const re = /(^|\r?\n)([ \t]*```mycalendar-event[ \t]*\r?\n[\s\S]*?\r?\n[ \t]*```)(?=\r?\n|$)/g;
3053
+ let changed = false;
3054
+ const out = body.replace(re, (fullMatch, prefixNL, wholeBlock) => {
3055
+ var _a;
3056
+ const innerM = wholeBlock.match(/^[ \t]*```mycalendar-event[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*```$/);
3057
+ const inner = (_a = innerM === null || innerM === void 0 ? void 0 : innerM[1]) !== null && _a !== void 0 ? _a : '';
3058
+ const meta = parseUidAndRecurrence(inner);
3059
+ const u = (meta.uid || '').trim();
3060
+ const r = normalizeRecurrenceIdForKey(meta.recurrence_id);
3061
+ if (u !== targetUid)
3062
+ return fullMatch;
3063
+ if (!targetRid) {
3064
+ if (!r) {
3065
+ changed = true;
3066
+ return `${prefixNL}${newBlock}`;
3067
+ }
3068
+ return fullMatch;
3069
+ }
3070
+ if (r === targetRid) {
3071
+ changed = true;
3072
+ return `${prefixNL}${newBlock}`;
3073
+ }
3074
+ return fullMatch;
3075
+ });
3076
+ if (changed)
3077
+ return out;
3078
+ const trimmed = (body || '').replace(/\s+$/, '');
3079
+ return (trimmed ? trimmed + '\n\n' : '') + newBlock + '\n';
3080
+ }
3081
+ function parseColor(inner) {
3082
+ var _a;
3083
+ const m = inner.match(/^\s*color\s*:\s*(.+?)\s*$/im);
3084
+ return (_a = m === null || m === void 0 ? void 0 : m[1]) === null || _a === void 0 ? void 0 : _a.trim();
3085
+ }
3086
+ function extractEventColorFromBody(body, uid, recurrenceId) {
3087
+ const targetUid = (uid || '').trim();
3088
+ const targetRid = normalizeRecurrenceIdForKey(recurrenceId);
3089
+ const re = /(^|\r?\n)[ \t]*```mycalendar-event[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*```(?=\r?\n|$)/g;
3090
+ let m;
3091
+ while ((m = re.exec(body)) !== null) {
3092
+ const inner = m[2] || '';
3093
+ const meta = parseUidAndRecurrence(inner);
3094
+ const u = (meta.uid || '').trim();
3095
+ const r = normalizeRecurrenceIdForKey(meta.recurrence_id);
3096
+ if (u !== targetUid)
3097
+ continue;
3098
+ if (!targetRid) {
3099
+ if (!r)
3100
+ return parseColor(inner);
3101
+ continue;
3102
+ }
3103
+ if (r === targetRid)
3104
+ return parseColor(inner);
3105
+ }
3106
+ return undefined;
3107
+ }
3108
+
3109
+
3110
+ /***/ },
3111
+
3112
+ /***/ "./src/main/utils/logger.ts"
3113
+ /*!**********************************!*\
3114
+ !*** ./src/main/utils/logger.ts ***!
3115
+ \**********************************/
3116
+ (__unused_webpack_module, exports) {
3117
+
3118
+
3119
+ // src/main/utils/logger.ts
3120
+ /**
3121
+ * Minimal console logger with a fixed prefix.
3122
+ *
3123
+ * Design goals:
3124
+ * - Keep current runtime behavior (string-first vs non-string-first arguments).
3125
+ * - Keep public API stable for existing imports/tests.
3126
+ * - Avoid `any` in public surface; prefer `unknown`.
3127
+ */
3128
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
3129
+ exports.setDebugEnabled = setDebugEnabled;
3130
+ exports.log = log;
3131
+ exports.info = info;
3132
+ exports.warn = warn;
3133
+ exports.err = err;
3134
+ exports.dbg = dbg;
3135
+ const PREFIX = '[MyCalendar]';
3136
+ let debugEnabled = false;
3137
+ function setDebugEnabled(v) {
3138
+ debugEnabled = Boolean(v);
3139
+ }
3140
+ function buildArgs(source, args) {
3141
+ const sourcePrefix = source ? `[${source}]` : '';
3142
+ if (args.length > 0 && typeof args[0] === 'string') {
3143
+ const [msg, ...rest] = args;
3144
+ return [`${PREFIX}${sourcePrefix} ${msg}`, ...rest];
3145
+ }
3146
+ return [PREFIX + sourcePrefix, ...args];
3147
+ }
3148
+ function write(consoleFn, source, args) {
3149
+ consoleFn(...buildArgs(source, args));
3150
+ }
3151
+ function log(source, ...args) {
3152
+ write(console.log, source, args);
3153
+ }
3154
+ function info(source, ...args) {
3155
+ write(console.info, source, args);
3156
+ }
3157
+ function warn(source, ...args) {
3158
+ write(console.warn, source, args);
3159
+ }
3160
+ /** Prefer `err` for backward-compatibility with existing code. */
3161
+ function err(source, ...args) {
3162
+ write(console.error, source, args);
3163
+ }
3164
+ /** Debug log (enabled via `setDebugEnabled(true)`). */
3165
+ function dbg(source, ...args) {
3166
+ if (!debugEnabled)
3167
+ return;
3168
+ write(console.log, source, args);
3169
+ }
3170
+
3171
+
3172
+ /***/ },
3173
+
3174
+ /***/ "./src/main/utils/toast.ts"
3175
+ /*!*********************************!*\
3176
+ !*** ./src/main/utils/toast.ts ***!
3177
+ \*********************************/
3178
+ (__unused_webpack_module, exports) {
3179
+
3180
+
3181
+ // src/main/utils/toast.ts
3182
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3183
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3184
+ return new (P || (P = Promise))(function (resolve, reject) {
3185
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
3186
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
3187
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
3188
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
3189
+ });
3190
+ };
3191
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
3192
+ exports.DEFAULT_TOAST_DURATION_MS = void 0;
3193
+ exports.showToast = showToast;
3194
+ exports.DEFAULT_TOAST_DURATION_MS = 3000;
3195
+ function getDialogs() {
3196
+ // Joplin typings can lag, so we're doing a narrow cast here, in one place.
3197
+ return joplin.views.dialogs;
3198
+ }
3199
+ function showToast(type_1, message_1) {
3200
+ return __awaiter(this, arguments, void 0, function* (type, message, duration = exports.DEFAULT_TOAST_DURATION_MS) {
3201
+ const dialogs = getDialogs();
3202
+ // Easy normalization (does not change test logic and current behavior).
3203
+ const safeDuration = Number.isFinite(duration) ? Math.trunc(duration) : exports.DEFAULT_TOAST_DURATION_MS;
3204
+ yield dialogs.showToast({
3205
+ type,
3206
+ message,
3207
+ duration: safeDuration,
3208
+ // timestamp helps if Joplin doesn't show a duplicate of the same toast.
3209
+ timestamp: Date.now(),
3210
+ });
3211
+ });
3212
+ }
3213
+
3214
+
3215
+ /***/ },
3216
+
3217
+ /***/ "./src/main/views/calendarView.ts"
3218
+ /*!****************************************!*\
3219
+ !*** ./src/main/views/calendarView.ts ***!
3220
+ \****************************************/
3221
+ (__unused_webpack_module, exports, __webpack_require__) {
3222
+
3223
+
3224
+ // src/main/views/calendarView.ts
3225
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3226
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3227
+ return new (P || (P = Promise))(function (resolve, reject) {
3228
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
3229
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
3230
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
3231
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
3232
+ });
3233
+ };
3234
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
3235
+ exports.CALENDAR_PANEL_SCRIPTS = exports.CALENDAR_PANEL_HTML = exports.CALENDAR_PANEL_ID = void 0;
3236
+ exports.createCalendarPanel = createCalendarPanel;
3237
+ const logger_1 = __webpack_require__(/*! ../utils/logger */ "./src/main/utils/logger.ts");
3238
+ exports.CALENDAR_PANEL_ID = 'mycalendarPanel';
3239
+ exports.CALENDAR_PANEL_HTML = `
3240
+ <div id="cal-root">
3241
+ <div id="mc-toolbar"></div>
3242
+ <!-- CALENDAR -->
3243
+ <div id="mc-grid"></div>
3244
+ <!-- EVENTS OF THE DAY -->
3245
+ <div id="mc-events">
3246
+ <div class="mc-events-header">
3247
+ <span>Day events</span>
3248
+ <span id="mc-events-day-label"></span>
3249
+ </div>
3250
+ <ul id="mc-events-list"></ul>
3251
+ </div>
3252
+ <!-- IMPORT (SEPARATE BLOCK BELOW) -->
3253
+ <div id="mc-import">
3254
+ <div class="mc-import-header">ICS import</div>
3255
+ <div id="mc-import-body">
3256
+ <div id="ics-root"></div>
3257
+ </div>
3258
+ </div>
3259
+ <!-- LOG -->
3260
+ <div id="mc-log"></div>
3261
+ </div>
3262
+ `;
3263
+ exports.CALENDAR_PANEL_SCRIPTS = [
3264
+ './ui/mycalendar.css',
3265
+ './ui/calendar.js',
3266
+ './ui/icsImport.js',
3267
+ ];
3268
+ function createCalendarPanel(joplin) {
3269
+ return __awaiter(this, void 0, void 0, function* () {
3270
+ const { panels } = joplin.views;
3271
+ const panel = yield panels.create(exports.CALENDAR_PANEL_ID);
3272
+ yield panels.setHtml(panel, exports.CALENDAR_PANEL_HTML);
3273
+ for (const script of exports.CALENDAR_PANEL_SCRIPTS) {
3274
+ yield panels.addScript(panel, script);
3275
+ }
3276
+ yield panels.show(panel);
3277
+ (0, logger_1.log)('calendarView', 'Panel created');
3278
+ return panel;
3279
+ });
3280
+ }
3281
+
3282
+
3283
+ /***/ }
3284
+
3285
+ /******/ });
3286
+ /************************************************************************/
3287
+ /******/ // The module cache
3288
+ /******/ var __webpack_module_cache__ = {};
3289
+ /******/
3290
+ /******/ // The require function
3291
+ /******/ function __webpack_require__(moduleId) {
3292
+ /******/ // Check if module is in cache
3293
+ /******/ var cachedModule = __webpack_module_cache__[moduleId];
3294
+ /******/ if (cachedModule !== undefined) {
3295
+ /******/ return cachedModule.exports;
3296
+ /******/ }
3297
+ /******/ // Check if module exists (development only)
3298
+ /******/ if (__webpack_modules__[moduleId] === undefined) {
3299
+ /******/ var e = new Error("Cannot find module '" + moduleId + "'");
3300
+ /******/ e.code = 'MODULE_NOT_FOUND';
3301
+ /******/ throw e;
3302
+ /******/ }
3303
+ /******/ // Create a new module (and put it into the cache)
3304
+ /******/ var module = __webpack_module_cache__[moduleId] = {
3305
+ /******/ // no module.id needed
3306
+ /******/ // no module.loaded needed
3307
+ /******/ exports: {}
3308
+ /******/ };
3309
+ /******/
3310
+ /******/ // Execute the module function
3311
+ /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
3312
+ /******/
3313
+ /******/ // Return the exports of the module
3314
+ /******/ return module.exports;
3315
+ /******/ }
3316
+ /******/
3317
+ /************************************************************************/
3318
+ /******/
3319
+ /******/ // startup
3320
+ /******/ // Load entry module and return exports
3321
+ /******/ // This entry module is referenced by other modules so it can't be inlined
3322
+ /******/ var __webpack_exports__ = __webpack_require__("./src/index.ts");
3323
+ /******/ module.exports = __webpack_exports__;
3324
+ /******/
3325
+ /******/ })()
3326
+ ;
3327
+ //# sourceMappingURL=index.js.map